2 * backend.c -- Common back end for X and Windows NT versions of
4 * Copyright 1991 by Digital Equipment Corporation, Maynard,
7 * Enhancements Copyright 1992-2001, 2002, 2003, 2004, 2005, 2006,
8 * 2007, 2008, 2009, 2010, 2011, 2012, 2013 Free Software Foundation, Inc.
10 * Enhancements Copyright 2005 Alessandro Scotti
12 * The following terms apply to Digital Equipment Corporation's copyright
14 * ------------------------------------------------------------------------
17 * Permission to use, copy, modify, and distribute this software and its
18 * documentation for any purpose and without fee is hereby granted,
19 * provided that the above copyright notice appear in all copies and that
20 * both that copyright notice and this permission notice appear in
21 * supporting documentation, and that the name of Digital not be
22 * used in advertising or publicity pertaining to distribution of the
23 * software without specific, written prior permission.
25 * DIGITAL DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
26 * ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL
27 * DIGITAL BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR
28 * ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
29 * WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
30 * ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
32 * ------------------------------------------------------------------------
34 * The following terms apply to the enhanced version of XBoard
35 * distributed by the Free Software Foundation:
36 * ------------------------------------------------------------------------
38 * GNU XBoard is free software: you can redistribute it and/or modify
39 * it under the terms of the GNU General Public License as published by
40 * the Free Software Foundation, either version 3 of the License, or (at
41 * your option) any later version.
43 * GNU XBoard is distributed in the hope that it will be useful, but
44 * WITHOUT ANY WARRANTY; without even the implied warranty of
45 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
46 * General Public License for more details.
48 * You should have received a copy of the GNU General Public License
49 * along with this program. If not, see http://www.gnu.org/licenses/. *
51 *------------------------------------------------------------------------
52 ** See the file ChangeLog for a revision history. */
54 /* [AS] Also useful here for debugging */
58 int flock(int f, int code);
75 #include <sys/types.h>
84 #else /* not STDC_HEADERS */
87 # else /* not HAVE_STRING_H */
89 # endif /* not HAVE_STRING_H */
90 #endif /* not STDC_HEADERS */
93 # include <sys/fcntl.h>
94 #else /* not HAVE_SYS_FCNTL_H */
97 # endif /* HAVE_FCNTL_H */
98 #endif /* not HAVE_SYS_FCNTL_H */
100 #if TIME_WITH_SYS_TIME
101 # include <sys/time.h>
105 # include <sys/time.h>
111 #if defined(_amigados) && !defined(__GNUC__)
116 extern int gettimeofday(struct timeval *, struct timezone *);
124 #include "frontend.h"
131 #include "backendz.h"
132 #include "evalgraph.h"
136 # define _(s) gettext (s)
137 # define N_(s) gettext_noop (s)
138 # define T_(s) gettext(s)
151 int establish P((void));
152 void read_from_player P((InputSourceRef isr, VOIDSTAR closure,
153 char *buf, int count, int error));
154 void read_from_ics P((InputSourceRef isr, VOIDSTAR closure,
155 char *buf, int count, int error));
156 void SendToICS P((char *s));
157 void SendToICSDelayed P((char *s, long msdelay));
158 void SendMoveToICS P((ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar));
159 void HandleMachineMove P((char *message, ChessProgramState *cps));
160 int AutoPlayOneMove P((void));
161 int LoadGameOneMove P((ChessMove readAhead));
162 int LoadGameFromFile P((char *filename, int n, char *title, int useList));
163 int LoadPositionFromFile P((char *filename, int n, char *title));
164 int SavePositionToFile P((char *filename));
165 void MakeMove P((int fromX, int fromY, int toX, int toY, int promoChar));
166 void ShowMove P((int fromX, int fromY, int toX, int toY));
167 int FinishMove P((ChessMove moveType, int fromX, int fromY, int toX, int toY,
168 /*char*/int promoChar));
169 void BackwardInner P((int target));
170 void ForwardInner P((int target));
171 int Adjudicate P((ChessProgramState *cps));
172 void GameEnds P((ChessMove result, char *resultDetails, int whosays));
173 void EditPositionDone P((Boolean fakeRights));
174 void PrintOpponents P((FILE *fp));
175 void PrintPosition P((FILE *fp, int move));
176 void StartChessProgram P((ChessProgramState *cps));
177 void SendToProgram P((char *message, ChessProgramState *cps));
178 void SendMoveToProgram P((int moveNum, ChessProgramState *cps));
179 void ReceiveFromProgram P((InputSourceRef isr, VOIDSTAR closure,
180 char *buf, int count, int error));
181 void SendTimeControl P((ChessProgramState *cps,
182 int mps, long tc, int inc, int sd, int st));
183 char *TimeControlTagValue P((void));
184 void Attention P((ChessProgramState *cps));
185 void FeedMovesToProgram P((ChessProgramState *cps, int upto));
186 int ResurrectChessProgram P((void));
187 void DisplayComment P((int moveNumber, char *text));
188 void DisplayMove P((int moveNumber));
190 void ParseGameHistory P((char *game));
191 void ParseBoard12 P((char *string));
192 void KeepAlive P((void));
193 void StartClocks P((void));
194 void SwitchClocks P((int nr));
195 void StopClocks P((void));
196 void ResetClocks P((void));
197 char *PGNDate P((void));
198 void SetGameInfo P((void));
199 int RegisterMove P((void));
200 void MakeRegisteredMove P((void));
201 void TruncateGame P((void));
202 int looking_at P((char *, int *, char *));
203 void CopyPlayerNameIntoFileName P((char **, char *));
204 char *SavePart P((char *));
205 int SaveGameOldStyle P((FILE *));
206 int SaveGamePGN P((FILE *));
207 int CheckFlags P((void));
208 long NextTickLength P((long));
209 void CheckTimeControl P((void));
210 void show_bytes P((FILE *, char *, int));
211 int string_to_rating P((char *str));
212 void ParseFeatures P((char* args, ChessProgramState *cps));
213 void InitBackEnd3 P((void));
214 void FeatureDone P((ChessProgramState* cps, int val));
215 void InitChessProgram P((ChessProgramState *cps, int setup));
216 void OutputKibitz(int window, char *text);
217 int PerpetualChase(int first, int last);
218 int EngineOutputIsUp();
219 void InitDrawingSizes(int x, int y);
220 void NextMatchGame P((void));
221 int NextTourneyGame P((int nr, int *swap));
222 int Pairing P((int nr, int nPlayers, int *w, int *b, int *sync));
223 FILE *WriteTourneyFile P((char *results, FILE *f));
224 void DisplayTwoMachinesTitle P(());
225 static void ExcludeClick P((int index));
226 void ToggleSecond P((void));
227 void PauseEngine P((ChessProgramState *cps));
228 static int NonStandardBoardSize P((void));
231 extern void ConsoleCreate();
234 ChessProgramState *WhitePlayer();
235 void InsertIntoMemo P((int which, char *text)); // [HGM] kibitz: in engineo.c
236 int VerifyDisplayMode P(());
238 char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment
239 void InitEngineUCI( const char * iniDir, ChessProgramState * cps ); // [HGM] moved here from winboard.c
240 char *ProbeBook P((int moveNr, char *book)); // [HGM] book: returns a book move
241 char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book
242 void ics_update_width P((int new_width));
243 extern char installDir[MSG_SIZ];
244 VariantClass startVariant; /* [HGM] nicks: initial variant */
247 extern int tinyLayout, smallLayout;
248 ChessProgramStats programStats;
249 char lastPV[2][2*MSG_SIZ]; /* [HGM] pv: last PV in thinking output of each engine */
251 static int exiting = 0; /* [HGM] moved to top */
252 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
253 int startedFromPositionFile = FALSE; Board filePosition; /* [HGM] loadPos */
254 Board partnerBoard; /* [HGM] bughouse: for peeking at partner game */
255 int partnerHighlight[2];
256 Boolean partnerBoardValid = 0;
257 char partnerStatus[MSG_SIZ];
259 Boolean originalFlip;
260 Boolean twoBoards = 0;
261 char endingGame = 0; /* [HGM] crash: flag to prevent recursion of GameEnds() */
262 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS */
263 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
264 int lastIndex = 0; /* [HGM] autoinc: last game/position used in match mode */
265 Boolean connectionAlive;/* [HGM] alive: ICS connection status from probing */
266 int opponentKibitzes;
267 int lastSavedGame; /* [HGM] save: ID of game */
268 char chatPartner[MAX_CHAT][MSG_SIZ]; /* [HGM] chat: list of chatting partners */
269 extern int chatCount;
271 char marker[BOARD_RANKS][BOARD_FILES]; /* [HGM] marks for target squares */
272 char legal[BOARD_RANKS][BOARD_FILES]; /* [HGM] legal target squares */
273 char lastMsg[MSG_SIZ];
274 ChessSquare pieceSweep = EmptySquare;
275 ChessSquare promoSweep = EmptySquare, defaultPromoChoice;
276 int promoDefaultAltered;
277 int keepInfo = 0; /* [HGM] to protect PGN tags in auto-step game analysis */
279 /* States for ics_getting_history */
281 #define H_REQUESTED 1
282 #define H_GOT_REQ_HEADER 2
283 #define H_GOT_UNREQ_HEADER 3
284 #define H_GETTING_MOVES 4
285 #define H_GOT_UNWANTED_HEADER 5
287 /* whosays values for GameEnds */
296 /* Maximum number of games in a cmail message */
297 #define CMAIL_MAX_GAMES 20
299 /* Different types of move when calling RegisterMove */
301 #define CMAIL_RESIGN 1
303 #define CMAIL_ACCEPT 3
305 /* Different types of result to remember for each game */
306 #define CMAIL_NOT_RESULT 0
307 #define CMAIL_OLD_RESULT 1
308 #define CMAIL_NEW_RESULT 2
310 /* Telnet protocol constants */
321 safeStrCpy (char *dst, const char *src, size_t count)
324 assert( dst != NULL );
325 assert( src != NULL );
328 for(i=0; i<count; i++) if((dst[i] = src[i]) == NULLCHAR) break;
329 if( i == count && dst[count-1] != NULLCHAR)
331 dst[ count-1 ] = '\0'; // make sure incomplete copy still null-terminated
332 if(appData.debugMode)
333 fprintf(debugFP, "safeStrCpy: copying %s into %s didn't work, not enough space %d\n",src,dst, (int)count);
339 /* Some compiler can't cast u64 to double
340 * This function do the job for us:
342 * We use the highest bit for cast, this only
343 * works if the highest bit is not
344 * in use (This should not happen)
346 * We used this for all compiler
349 u64ToDouble (u64 value)
352 u64 tmp = value & u64Const(0x7fffffffffffffff);
353 r = (double)(s64)tmp;
354 if (value & u64Const(0x8000000000000000))
355 r += 9.2233720368547758080e18; /* 2^63 */
359 /* Fake up flags for now, as we aren't keeping track of castling
360 availability yet. [HGM] Change of logic: the flag now only
361 indicates the type of castlings allowed by the rule of the game.
362 The actual rights themselves are maintained in the array
363 castlingRights, as part of the game history, and are not probed
369 int flags = F_ALL_CASTLE_OK;
370 if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
371 switch (gameInfo.variant) {
373 flags &= ~F_ALL_CASTLE_OK;
374 case VariantGiveaway: // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
375 flags |= F_IGNORE_CHECK;
377 flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
380 flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
382 case VariantKriegspiel:
383 flags |= F_KRIEGSPIEL_CAPTURE;
385 case VariantCapaRandom:
386 case VariantFischeRandom:
387 flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
388 case VariantNoCastle:
389 case VariantShatranj:
394 flags &= ~F_ALL_CASTLE_OK;
402 FILE *gameFileFP, *debugFP, *serverFP;
403 char *currentDebugFile; // [HGM] debug split: to remember name
406 [AS] Note: sometimes, the sscanf() function is used to parse the input
407 into a fixed-size buffer. Because of this, we must be prepared to
408 receive strings as long as the size of the input buffer, which is currently
409 set to 4K for Windows and 8K for the rest.
410 So, we must either allocate sufficiently large buffers here, or
411 reduce the size of the input buffer in the input reading part.
414 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
415 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
416 char thinkOutput1[MSG_SIZ*10];
418 ChessProgramState first, second, pairing;
420 /* premove variables */
423 int premoveFromX = 0;
424 int premoveFromY = 0;
425 int premovePromoChar = 0;
427 Boolean alarmSounded;
428 /* end premove variables */
430 char *ics_prefix = "$";
431 enum ICS_TYPE ics_type = ICS_GENERIC;
433 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
434 int pauseExamForwardMostMove = 0;
435 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
436 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
437 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
438 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
439 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
440 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
441 int whiteFlag = FALSE, blackFlag = FALSE;
442 int userOfferedDraw = FALSE;
443 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
444 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
445 int cmailMoveType[CMAIL_MAX_GAMES];
446 long ics_clock_paused = 0;
447 ProcRef icsPR = NoProc, cmailPR = NoProc;
448 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
449 GameMode gameMode = BeginningOfGame;
450 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
451 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
452 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
453 int hiddenThinkOutputState = 0; /* [AS] */
454 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
455 int adjudicateLossPlies = 6;
456 char white_holding[64], black_holding[64];
457 TimeMark lastNodeCountTime;
458 long lastNodeCount=0;
459 int shiftKey, controlKey; // [HGM] set by mouse handler
461 int have_sent_ICS_logon = 0;
463 int suddenDeath, whiteStartMove, blackStartMove; /* [HGM] for implementation of 'any per time' sessions, as in first part of byoyomi TC */
464 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement, lastWhite, lastBlack, activePartnerTime;
465 Boolean adjustedClock;
466 long timeControl_2; /* [AS] Allow separate time controls */
467 char *fullTimeControlString = NULL, *nextSession, *whiteTC, *blackTC, activePartner; /* [HGM] secondary TC: merge of MPS, TC and inc */
468 long timeRemaining[2][MAX_MOVES];
469 int matchGame = 0, nextGame = 0, roundNr = 0;
470 Boolean waitingForGame = FALSE, startingEngine = FALSE;
471 TimeMark programStartTime, pauseStart;
472 char ics_handle[MSG_SIZ];
473 int have_set_title = 0;
475 /* animateTraining preserves the state of appData.animate
476 * when Training mode is activated. This allows the
477 * response to be animated when appData.animate == TRUE and
478 * appData.animateDragging == TRUE.
480 Boolean animateTraining;
486 Board boards[MAX_MOVES];
487 /* [HGM] Following 7 needed for accurate legality tests: */
488 signed char castlingRank[BOARD_FILES]; // and corresponding ranks
489 signed char initialRights[BOARD_FILES];
490 int nrCastlingRights; // For TwoKings, or to implement castling-unknown status
491 int initialRulePlies, FENrulePlies;
492 FILE *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
494 Boolean shuffleOpenings;
495 int mute; // mute all sounds
497 // [HGM] vari: next 12 to save and restore variations
498 #define MAX_VARIATIONS 10
499 int framePtr = MAX_MOVES-1; // points to free stack entry
501 int savedFirst[MAX_VARIATIONS];
502 int savedLast[MAX_VARIATIONS];
503 int savedFramePtr[MAX_VARIATIONS];
504 char *savedDetails[MAX_VARIATIONS];
505 ChessMove savedResult[MAX_VARIATIONS];
507 void PushTail P((int firstMove, int lastMove));
508 Boolean PopTail P((Boolean annotate));
509 void PushInner P((int firstMove, int lastMove));
510 void PopInner P((Boolean annotate));
511 void CleanupTail P((void));
513 ChessSquare FIDEArray[2][BOARD_FILES] = {
514 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
515 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
516 { BlackRook, BlackKnight, BlackBishop, BlackQueen,
517 BlackKing, BlackBishop, BlackKnight, BlackRook }
520 ChessSquare twoKingsArray[2][BOARD_FILES] = {
521 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
522 WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
523 { BlackRook, BlackKnight, BlackBishop, BlackQueen,
524 BlackKing, BlackKing, BlackKnight, BlackRook }
527 ChessSquare KnightmateArray[2][BOARD_FILES] = {
528 { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
529 WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
530 { BlackRook, BlackMan, BlackBishop, BlackQueen,
531 BlackUnicorn, BlackBishop, BlackMan, BlackRook }
534 ChessSquare SpartanArray[2][BOARD_FILES] = {
535 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
536 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
537 { BlackAlfil, BlackMarshall, BlackKing, BlackDragon,
538 BlackDragon, BlackKing, BlackAngel, BlackAlfil }
541 ChessSquare fairyArray[2][BOARD_FILES] = { /* [HGM] Queen side differs from King side */
542 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
543 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
544 { BlackCardinal, BlackAlfil, BlackMarshall, BlackAngel,
545 BlackKing, BlackMarshall, BlackAlfil, BlackCardinal }
548 ChessSquare ShatranjArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
549 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
550 WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
551 { BlackRook, BlackKnight, BlackAlfil, BlackKing,
552 BlackFerz, BlackAlfil, BlackKnight, BlackRook }
555 ChessSquare makrukArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
556 { WhiteRook, WhiteKnight, WhiteMan, WhiteKing,
557 WhiteFerz, WhiteMan, WhiteKnight, WhiteRook },
558 { BlackRook, BlackKnight, BlackMan, BlackFerz,
559 BlackKing, BlackMan, BlackKnight, BlackRook }
562 ChessSquare aseanArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
563 { WhiteRook, WhiteKnight, WhiteMan, WhiteFerz,
564 WhiteKing, WhiteMan, WhiteKnight, WhiteRook },
565 { BlackRook, BlackKnight, BlackMan, BlackFerz,
566 BlackKing, BlackMan, BlackKnight, BlackRook }
570 #if (BOARD_FILES>=10)
571 ChessSquare ShogiArray[2][BOARD_FILES] = {
572 { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
573 WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
574 { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
575 BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
578 ChessSquare XiangqiArray[2][BOARD_FILES] = {
579 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
580 WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
581 { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
582 BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
585 ChessSquare CapablancaArray[2][BOARD_FILES] = {
586 { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
587 WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
588 { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
589 BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
592 ChessSquare GreatArray[2][BOARD_FILES] = {
593 { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
594 WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
595 { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
596 BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
599 ChessSquare JanusArray[2][BOARD_FILES] = {
600 { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
601 WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
602 { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
603 BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
606 ChessSquare GrandArray[2][BOARD_FILES] = {
607 { EmptySquare, WhiteKnight, WhiteBishop, WhiteQueen, WhiteKing,
608 WhiteMarshall, WhiteAngel, WhiteBishop, WhiteKnight, EmptySquare },
609 { EmptySquare, BlackKnight, BlackBishop, BlackQueen, BlackKing,
610 BlackMarshall, BlackAngel, BlackBishop, BlackKnight, EmptySquare }
614 ChessSquare GothicArray[2][BOARD_FILES] = {
615 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
616 WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
617 { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
618 BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
621 #define GothicArray CapablancaArray
625 ChessSquare FalconArray[2][BOARD_FILES] = {
626 { WhiteRook, WhiteKnight, WhiteBishop, WhiteFalcon, WhiteQueen,
627 WhiteKing, WhiteFalcon, WhiteBishop, WhiteKnight, WhiteRook },
628 { BlackRook, BlackKnight, BlackBishop, BlackFalcon, BlackQueen,
629 BlackKing, BlackFalcon, BlackBishop, BlackKnight, BlackRook }
632 #define FalconArray CapablancaArray
635 #else // !(BOARD_FILES>=10)
636 #define XiangqiPosition FIDEArray
637 #define CapablancaArray FIDEArray
638 #define GothicArray FIDEArray
639 #define GreatArray FIDEArray
640 #endif // !(BOARD_FILES>=10)
642 #if (BOARD_FILES>=12)
643 ChessSquare CourierArray[2][BOARD_FILES] = {
644 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
645 WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
646 { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
647 BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
649 #else // !(BOARD_FILES>=12)
650 #define CourierArray CapablancaArray
651 #endif // !(BOARD_FILES>=12)
654 Board initialPosition;
657 /* Convert str to a rating. Checks for special cases of "----",
659 "++++", etc. Also strips ()'s */
661 string_to_rating (char *str)
663 while(*str && !isdigit(*str)) ++str;
665 return 0; /* One of the special "no rating" cases */
673 /* Init programStats */
674 programStats.movelist[0] = 0;
675 programStats.depth = 0;
676 programStats.nr_moves = 0;
677 programStats.moves_left = 0;
678 programStats.nodes = 0;
679 programStats.time = -1; // [HGM] PGNtime: make invalid to recognize engine output
680 programStats.score = 0;
681 programStats.got_only_move = 0;
682 programStats.got_fail = 0;
683 programStats.line_is_book = 0;
688 { // [HGM] moved some code here from InitBackend1 that has to be done after both engines have contributed their settings
689 if (appData.firstPlaysBlack) {
690 first.twoMachinesColor = "black\n";
691 second.twoMachinesColor = "white\n";
693 first.twoMachinesColor = "white\n";
694 second.twoMachinesColor = "black\n";
697 first.other = &second;
698 second.other = &first;
701 if(appData.timeOddsMode) {
702 norm = appData.timeOdds[0];
703 if(norm > appData.timeOdds[1]) norm = appData.timeOdds[1];
705 first.timeOdds = appData.timeOdds[0]/norm;
706 second.timeOdds = appData.timeOdds[1]/norm;
709 if(programVersion) free(programVersion);
710 if (appData.noChessProgram) {
711 programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
712 sprintf(programVersion, "%s", PACKAGE_STRING);
714 /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
715 programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
716 sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
721 UnloadEngine (ChessProgramState *cps)
723 /* Kill off first chess program */
724 if (cps->isr != NULL)
725 RemoveInputSource(cps->isr);
728 if (cps->pr != NoProc) {
730 DoSleep( appData.delayBeforeQuit );
731 SendToProgram("quit\n", cps);
732 DoSleep( appData.delayAfterQuit );
733 DestroyChildProcess(cps->pr, cps->useSigterm);
736 if(appData.debugMode) fprintf(debugFP, "Unload %s\n", cps->which);
740 ClearOptions (ChessProgramState *cps)
743 cps->nrOptions = cps->comboCnt = 0;
744 for(i=0; i<MAX_OPTIONS; i++) {
745 cps->option[i].min = cps->option[i].max = cps->option[i].value = 0;
746 cps->option[i].textValue = 0;
750 char *engineNames[] = {
751 /* TRANSLATORS: "first" is the first of possible two chess engines. It is inserted into strings
752 such as "%s engine" / "%s chess program" / "%s machine" - all meaning the same thing */
754 /* TRANSLATORS: "second" is the second of possible two chess engines. It is inserted into strings
755 such as "%s engine" / "%s chess program" / "%s machine" - all meaning the same thing */
760 InitEngine (ChessProgramState *cps, int n)
761 { // [HGM] all engine initialiation put in a function that does one engine
765 cps->which = engineNames[n];
766 cps->maybeThinking = FALSE;
770 cps->sendDrawOffers = 1;
772 cps->program = appData.chessProgram[n];
773 cps->host = appData.host[n];
774 cps->dir = appData.directory[n];
775 cps->initString = appData.engInitString[n];
776 cps->computerString = appData.computerString[n];
777 cps->useSigint = TRUE;
778 cps->useSigterm = TRUE;
779 cps->reuse = appData.reuse[n];
780 cps->nps = appData.NPS[n]; // [HGM] nps: copy nodes per second
781 cps->useSetboard = FALSE;
783 cps->usePing = FALSE;
786 cps->usePlayother = FALSE;
787 cps->useColors = TRUE;
788 cps->useUsermove = FALSE;
789 cps->sendICS = FALSE;
790 cps->sendName = appData.icsActive;
791 cps->sdKludge = FALSE;
792 cps->stKludge = FALSE;
793 if(cps->tidy == NULL) cps->tidy = (char*) malloc(MSG_SIZ);
794 TidyProgramName(cps->program, cps->host, cps->tidy);
796 ASSIGN(cps->variants, appData.variant);
797 cps->analysisSupport = 2; /* detect */
798 cps->analyzing = FALSE;
799 cps->initDone = FALSE;
802 /* New features added by Tord: */
803 cps->useFEN960 = FALSE;
804 cps->useOOCastle = TRUE;
805 /* End of new features added by Tord. */
806 cps->fenOverride = appData.fenOverride[n];
808 /* [HGM] time odds: set factor for each machine */
809 cps->timeOdds = appData.timeOdds[n];
811 /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
812 cps->accumulateTC = appData.accumulateTC[n];
813 cps->maxNrOfSessions = 1;
818 cps->supportsNPS = UNKNOWN;
819 cps->memSize = FALSE;
820 cps->maxCores = FALSE;
821 ASSIGN(cps->egtFormats, "");
824 cps->optionSettings = appData.engOptions[n];
826 cps->scoreIsAbsolute = appData.scoreIsAbsolute[n]; /* [AS] */
827 cps->isUCI = appData.isUCI[n]; /* [AS] */
828 cps->hasOwnBookUCI = appData.hasOwnBookUCI[n]; /* [AS] */
831 if (appData.protocolVersion[n] > PROTOVER
832 || appData.protocolVersion[n] < 1)
837 len = snprintf(buf, MSG_SIZ, _("protocol version %d not supported"),
838 appData.protocolVersion[n]);
839 if( (len >= MSG_SIZ) && appData.debugMode )
840 fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
842 DisplayFatalError(buf, 0, 2);
846 cps->protocolVersion = appData.protocolVersion[n];
849 InitEngineUCI( installDir, cps ); // [HGM] moved here from winboard.c, to make available in xboard
850 ParseFeatures(appData.featureDefaults, cps);
853 ChessProgramState *savCps;
861 if(WaitForEngine(savCps, LoadEngine)) return;
862 CommonEngineInit(); // recalculate time odds
863 if(gameInfo.variant != StringToVariant(appData.variant)) {
864 // we changed variant when loading the engine; this forces us to reset
865 Reset(TRUE, savCps != &first);
866 oldMode = BeginningOfGame; // to prevent restoring old mode
868 InitChessProgram(savCps, FALSE);
869 if(gameMode == EditGame) SendToProgram("force\n", savCps); // in EditGame mode engine must be in force mode
870 DisplayMessage("", "");
871 if (startedFromSetupPosition) SendBoard(savCps, backwardMostMove);
872 for (i = backwardMostMove; i < currentMove; i++) SendMoveToProgram(i, savCps);
875 if(oldMode == AnalyzeMode) AnalyzeModeEvent();
879 ReplaceEngine (ChessProgramState *cps, int n)
881 oldMode = gameMode; // remember mode, so it can be restored after loading sequence is complete
883 if(oldMode != BeginningOfGame) EditGameEvent();
886 appData.noChessProgram = FALSE;
887 appData.clockMode = TRUE;
890 if(n) return; // only startup first engine immediately; second can wait
891 savCps = cps; // parameter to LoadEngine passed as globals, to allow scheduled calling :-(
895 extern char *engineName, *engineDir, *engineChoice, *engineLine, *nickName, *params;
896 extern Boolean isUCI, hasBook, storeVariant, v1, addToList, useNick;
898 static char resetOptions[] =
899 "-reuse -firstIsUCI false -firstHasOwnBookUCI true -firstTimeOdds 1 "
900 "-firstInitString \"" INIT_STRING "\" -firstComputerString \"" COMPUTER_STRING "\" "
901 "-firstFeatures \"\" -firstLogo \"\" -firstAccumulateTC 1 "
902 "-firstOptions \"\" -firstNPS -1 -fn \"\" -firstScoreAbs false";
905 FloatToFront(char **list, char *engineLine)
907 char buf[MSG_SIZ], tidy[MSG_SIZ], *p = buf, *q, *r = buf;
909 if(appData.recentEngines <= 0) return;
910 TidyProgramName(engineLine, "localhost", tidy+1);
911 tidy[0] = buf[0] = '\n'; strcat(tidy, "\n");
912 strncpy(buf+1, *list, MSG_SIZ-50);
913 if(p = strstr(buf, tidy)) { // tidy name appears in list
914 q = strchr(++p, '\n'); if(q == NULL) return; // malformed, don't touch
915 while(*p++ = *++q); // squeeze out
917 strcat(tidy, buf+1); // put list behind tidy name
918 p = tidy + 1; while(q = strchr(p, '\n')) i++, r = p, p = q + 1; // count entries in new list
919 if(i > appData.recentEngines) *r = NULLCHAR; // if maximum rached, strip off last
920 ASSIGN(*list, tidy+1);
923 char *insert, *wbOptions; // point in ChessProgramNames were we should insert new engine
926 Load (ChessProgramState *cps, int i)
928 char *p, *q, buf[MSG_SIZ], command[MSG_SIZ], buf2[MSG_SIZ];
929 if(engineLine && engineLine[0]) { // an engine was selected from the combo box
930 snprintf(buf, MSG_SIZ, "-fcp %s", engineLine);
931 SwapEngines(i); // kludge to parse -f* / -first* like it is -s* / -second*
932 ParseArgsFromString(resetOptions); appData.pvSAN[0] = FALSE;
933 FREE(appData.fenOverride[0]); appData.fenOverride[0] = NULL;
934 appData.firstProtocolVersion = PROTOVER;
935 ParseArgsFromString(buf);
937 ReplaceEngine(cps, i);
938 FloatToFront(&appData.recentEngineList, engineLine);
942 while(q = strchr(p, SLASH)) p = q+1;
943 if(*p== NULLCHAR) { DisplayError(_("You did not specify the engine executable"), 0); return; }
944 if(engineDir[0] != NULLCHAR) {
945 ASSIGN(appData.directory[i], engineDir); p = engineName;
946 } else if(p != engineName) { // derive directory from engine path, when not given
948 ASSIGN(appData.directory[i], engineName);
950 if(SLASH == '/' && p - engineName > 1) *(p -= 2) = '.'; // for XBoard use ./exeName as command after split!
951 } else { ASSIGN(appData.directory[i], "."); }
953 if(strchr(p, ' ') && !strchr(p, '"')) snprintf(buf2, MSG_SIZ, "\"%s\"", p), p = buf2; // quote if it contains spaces
954 snprintf(command, MSG_SIZ, "%s %s", p, params);
957 ASSIGN(appData.chessProgram[i], p);
958 appData.isUCI[i] = isUCI;
959 appData.protocolVersion[i] = v1 ? 1 : PROTOVER;
960 appData.hasOwnBookUCI[i] = hasBook;
961 if(!nickName[0]) useNick = FALSE;
962 if(useNick) ASSIGN(appData.pgnName[i], nickName);
966 q = firstChessProgramNames;
967 if(nickName[0]) snprintf(buf, MSG_SIZ, "\"%s\" -fcp ", nickName); else buf[0] = NULLCHAR;
968 quote = strchr(p, '"') ? '\'' : '"'; // use single quotes around engine command if it contains double quotes
969 snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), "%c%s%c -fd \"%s\"%s%s%s%s%s%s%s%s\n",
970 quote, p, quote, appData.directory[i],
971 useNick ? " -fn \"" : "",
972 useNick ? nickName : "",
974 v1 ? " -firstProtocolVersion 1" : "",
975 hasBook ? "" : " -fNoOwnBookUCI",
976 isUCI ? (isUCI == TRUE ? " -fUCI" : gameInfo.variant == VariantShogi ? " -fUSI" : " -fUCCI") : "",
977 storeVariant ? " -variant " : "",
978 storeVariant ? VariantName(gameInfo.variant) : "");
979 if(wbOptions && wbOptions[0]) snprintf(buf+strlen(buf)-1, MSG_SIZ-strlen(buf), " %s\n", wbOptions);
980 firstChessProgramNames = malloc(len = strlen(q) + strlen(buf) + 1);
981 if(insert != q) insert[-1] = NULLCHAR;
982 snprintf(firstChessProgramNames, len, "%s\n%s%s", q, buf, insert);
984 FloatToFront(&appData.recentEngineList, buf);
986 ReplaceEngine(cps, i);
992 int matched, min, sec;
994 * Parse timeControl resource
996 if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
997 appData.movesPerSession)) {
999 snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
1000 DisplayFatalError(buf, 0, 2);
1004 * Parse searchTime resource
1006 if (*appData.searchTime != NULLCHAR) {
1007 matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
1009 searchTime = min * 60;
1010 } else if (matched == 2) {
1011 searchTime = min * 60 + sec;
1014 snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
1015 DisplayFatalError(buf, 0, 2);
1024 ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
1025 startVariant = StringToVariant(appData.variant); // [HGM] nicks: remember original variant
1027 GetTimeMark(&programStartTime);
1028 srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
1029 appData.seedBase = random() + (random()<<15);
1030 pauseStart = programStartTime; pauseStart.sec -= 100; // [HGM] matchpause: fake a pause that has long since ended
1032 ClearProgramStats();
1033 programStats.ok_to_send = 1;
1034 programStats.seen_stat = 0;
1037 * Initialize game list
1043 * Internet chess server status
1045 if (appData.icsActive) {
1046 appData.matchMode = FALSE;
1047 appData.matchGames = 0;
1049 appData.noChessProgram = !appData.zippyPlay;
1051 appData.zippyPlay = FALSE;
1052 appData.zippyTalk = FALSE;
1053 appData.noChessProgram = TRUE;
1055 if (*appData.icsHelper != NULLCHAR) {
1056 appData.useTelnet = TRUE;
1057 appData.telnetProgram = appData.icsHelper;
1060 appData.zippyTalk = appData.zippyPlay = FALSE;
1063 /* [AS] Initialize pv info list [HGM] and game state */
1067 for( i=0; i<=framePtr; i++ ) {
1068 pvInfoList[i].depth = -1;
1069 boards[i][EP_STATUS] = EP_NONE;
1070 for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
1076 /* [AS] Adjudication threshold */
1077 adjudicateLossThreshold = appData.adjudicateLossThreshold;
1079 InitEngine(&first, 0);
1080 InitEngine(&second, 1);
1083 pairing.which = "pairing"; // pairing engine
1084 pairing.pr = NoProc;
1086 pairing.program = appData.pairingEngine;
1087 pairing.host = "localhost";
1090 if (appData.icsActive) {
1091 appData.clockMode = TRUE; /* changes dynamically in ICS mode */
1092 } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
1093 appData.clockMode = FALSE;
1094 first.sendTime = second.sendTime = 0;
1098 /* Override some settings from environment variables, for backward
1099 compatibility. Unfortunately it's not feasible to have the env
1100 vars just set defaults, at least in xboard. Ugh.
1102 if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
1107 if (!appData.icsActive) {
1111 /* Check for variants that are supported only in ICS mode,
1112 or not at all. Some that are accepted here nevertheless
1113 have bugs; see comments below.
1115 VariantClass variant = StringToVariant(appData.variant);
1117 case VariantBughouse: /* need four players and two boards */
1118 case VariantKriegspiel: /* need to hide pieces and move details */
1119 /* case VariantFischeRandom: (Fabien: moved below) */
1120 len = snprintf(buf,MSG_SIZ, _("Variant %s supported only in ICS mode"), appData.variant);
1121 if( (len >= MSG_SIZ) && appData.debugMode )
1122 fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1124 DisplayFatalError(buf, 0, 2);
1127 case VariantUnknown:
1128 case VariantLoadable:
1138 len = snprintf(buf, MSG_SIZ, _("Unknown variant name %s"), appData.variant);
1139 if( (len >= MSG_SIZ) && appData.debugMode )
1140 fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1142 DisplayFatalError(buf, 0, 2);
1145 case VariantXiangqi: /* [HGM] repetition rules not implemented */
1146 case VariantFairy: /* [HGM] TestLegality definitely off! */
1147 case VariantGothic: /* [HGM] should work */
1148 case VariantCapablanca: /* [HGM] should work */
1149 case VariantCourier: /* [HGM] initial forced moves not implemented */
1150 case VariantShogi: /* [HGM] could still mate with pawn drop */
1151 case VariantKnightmate: /* [HGM] should work */
1152 case VariantCylinder: /* [HGM] untested */
1153 case VariantFalcon: /* [HGM] untested */
1154 case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
1155 offboard interposition not understood */
1156 case VariantNormal: /* definitely works! */
1157 case VariantWildCastle: /* pieces not automatically shuffled */
1158 case VariantNoCastle: /* pieces not automatically shuffled */
1159 case VariantFischeRandom: /* [HGM] works and shuffles pieces */
1160 case VariantLosers: /* should work except for win condition,
1161 and doesn't know captures are mandatory */
1162 case VariantSuicide: /* should work except for win condition,
1163 and doesn't know captures are mandatory */
1164 case VariantGiveaway: /* should work except for win condition,
1165 and doesn't know captures are mandatory */
1166 case VariantTwoKings: /* should work */
1167 case VariantAtomic: /* should work except for win condition */
1168 case Variant3Check: /* should work except for win condition */
1169 case VariantShatranj: /* should work except for all win conditions */
1170 case VariantMakruk: /* should work except for draw countdown */
1171 case VariantASEAN : /* should work except for draw countdown */
1172 case VariantBerolina: /* might work if TestLegality is off */
1173 case VariantCapaRandom: /* should work */
1174 case VariantJanus: /* should work */
1175 case VariantSuper: /* experimental */
1176 case VariantGreat: /* experimental, requires legality testing to be off */
1177 case VariantSChess: /* S-Chess, should work */
1178 case VariantGrand: /* should work */
1179 case VariantSpartan: /* should work */
1187 NextIntegerFromString (char ** str, long * value)
1192 while( *s == ' ' || *s == '\t' ) {
1198 if( *s >= '0' && *s <= '9' ) {
1199 while( *s >= '0' && *s <= '9' ) {
1200 *value = *value * 10 + (*s - '0');
1213 NextTimeControlFromString (char ** str, long * value)
1216 int result = NextIntegerFromString( str, &temp );
1219 *value = temp * 60; /* Minutes */
1220 if( **str == ':' ) {
1222 result = NextIntegerFromString( str, &temp );
1223 *value += temp; /* Seconds */
1231 NextSessionFromString (char ** str, int *moves, long * tc, long *inc, int *incType)
1232 { /* [HGM] routine added to read '+moves/time' for secondary time control. */
1233 int result = -1, type = 0; long temp, temp2;
1235 if(**str != ':') return -1; // old params remain in force!
1237 if(**str == '*') type = *(*str)++, temp = 0; // sandclock TC
1238 if( NextIntegerFromString( str, &temp ) ) return -1;
1239 if(type) { *moves = 0; *tc = temp * 500; *inc = temp * 1000; *incType = '*'; return 0; }
1242 /* time only: incremental or sudden-death time control */
1243 if(**str == '+') { /* increment follows; read it */
1245 if(**str == '!') type = *(*str)++; // Bronstein TC
1246 if(result = NextIntegerFromString( str, &temp2)) return -1;
1247 *inc = temp2 * 1000;
1248 if(**str == '.') { // read fraction of increment
1249 char *start = ++(*str);
1250 if(result = NextIntegerFromString( str, &temp2)) return -1;
1252 while(start++ < *str) temp2 /= 10;
1256 *moves = 0; *tc = temp * 1000; *incType = type;
1260 (*str)++; /* classical time control */
1261 result = NextIntegerFromString( str, &temp2); // NOTE: already converted to seconds by ParseTimeControl()
1273 GetTimeQuota (int movenr, int lastUsed, char *tcString)
1274 { /* [HGM] get time to add from the multi-session time-control string */
1275 int incType, moves=1; /* kludge to force reading of first session */
1276 long time, increment;
1279 if(!s || !*s) return 0; // empty TC string means we ran out of the last sudden-death version
1281 if(moves) NextSessionFromString(&s, &moves, &time, &increment, &incType);
1282 nextSession = s; suddenDeath = moves == 0 && increment == 0;
1283 if(movenr == -1) return time; /* last move before new session */
1284 if(incType == '*') increment = 0; else // for sandclock, time is added while not thinking
1285 if(incType == '!' && lastUsed < increment) increment = lastUsed;
1286 if(!moves) return increment; /* current session is incremental */
1287 if(movenr >= 0) movenr -= moves; /* we already finished this session */
1288 } while(movenr >= -1); /* try again for next session */
1290 return 0; // no new time quota on this move
1294 ParseTimeControl (char *tc, float ti, int mps)
1298 char buf[MSG_SIZ], buf2[MSG_SIZ], *mytc = tc;
1301 if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1302 if(!strchr(tc, '+') && !strchr(tc, '/') && sscanf(tc, "%d:%d", &min, &sec) >= 1)
1303 sprintf(mytc=buf2, "%d", 60*min+sec); // convert 'classical' min:sec tc string to seconds
1307 snprintf(buf, MSG_SIZ, ":%d/%s+%g", mps, mytc, ti);
1309 snprintf(buf, MSG_SIZ, ":%s+%g", mytc, ti);
1312 snprintf(buf, MSG_SIZ, ":%d/%s", mps, mytc);
1314 snprintf(buf, MSG_SIZ, ":%s", mytc);
1316 fullTimeControlString = StrSave(buf); // this should now be in PGN format
1318 if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1323 /* Parse second time control */
1326 if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1334 timeControl_2 = tc2 * 1000;
1344 timeControl = tc1 * 1000;
1347 timeIncrement = ti * 1000; /* convert to ms */
1348 movesPerSession = 0;
1351 movesPerSession = mps;
1359 if (appData.debugMode) {
1360 # ifdef __GIT_VERSION
1361 fprintf(debugFP, "Version: %s (%s)\n", programVersion, __GIT_VERSION);
1363 fprintf(debugFP, "Version: %s\n", programVersion);
1366 ASSIGN(currentDebugFile, appData.nameOfDebugFile); // [HGM] debug split: remember initial name in use
1368 set_cont_sequence(appData.wrapContSeq);
1369 if (appData.matchGames > 0) {
1370 appData.matchMode = TRUE;
1371 } else if (appData.matchMode) {
1372 appData.matchGames = 1;
1374 if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1375 appData.matchGames = appData.sameColorGames;
1376 if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1377 if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1378 if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1381 if (appData.noChessProgram || first.protocolVersion == 1) {
1384 /* kludge: allow timeout for initial "feature" commands */
1386 DisplayMessage("", _("Starting chess program"));
1387 ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1392 CalculateIndex (int index, int gameNr)
1393 { // [HGM] autoinc: absolute way to determine load index from game number (taking auto-inc and rewind into account)
1395 if(index > 0) return index; // fixed nmber
1396 if(index == 0) return 1;
1397 res = (index == -1 ? gameNr : (gameNr-1)/2 + 1); // autoinc
1398 if(appData.rewindIndex > 0) res = (res-1) % appData.rewindIndex + 1; // rewind
1403 LoadGameOrPosition (int gameNr)
1404 { // [HGM] taken out of MatchEvent and NextMatchGame (to combine it)
1405 if (*appData.loadGameFile != NULLCHAR) {
1406 if (!LoadGameFromFile(appData.loadGameFile,
1407 CalculateIndex(appData.loadGameIndex, gameNr),
1408 appData.loadGameFile, FALSE)) {
1409 DisplayFatalError(_("Bad game file"), 0, 1);
1412 } else if (*appData.loadPositionFile != NULLCHAR) {
1413 if (!LoadPositionFromFile(appData.loadPositionFile,
1414 CalculateIndex(appData.loadPositionIndex, gameNr),
1415 appData.loadPositionFile)) {
1416 DisplayFatalError(_("Bad position file"), 0, 1);
1424 ReserveGame (int gameNr, char resChar)
1426 FILE *tf = fopen(appData.tourneyFile, "r+");
1427 char *p, *q, c, buf[MSG_SIZ];
1428 if(tf == NULL) { nextGame = appData.matchGames + 1; return; } // kludge to terminate match
1429 safeStrCpy(buf, lastMsg, MSG_SIZ);
1430 DisplayMessage(_("Pick new game"), "");
1431 flock(fileno(tf), LOCK_EX); // lock the tourney file while we are messing with it
1432 ParseArgsFromFile(tf);
1433 p = q = appData.results;
1434 if(appData.debugMode) {
1435 char *r = appData.participants;
1436 fprintf(debugFP, "results = '%s'\n", p);
1437 while(*r) fprintf(debugFP, *r >= ' ' ? "%c" : "\\%03o", *r), r++;
1438 fprintf(debugFP, "\n");
1440 while(*q && *q != ' ') q++; // get first un-played game (could be beyond end!)
1442 q = malloc(strlen(p) + 2); // could be arbitrary long, but allow to extend by one!
1443 safeStrCpy(q, p, strlen(p) + 2);
1444 if(gameNr >= 0) q[gameNr] = resChar; // replace '*' with result
1445 if(appData.debugMode) fprintf(debugFP, "pick next game from '%s': %d\n", q, nextGame);
1446 if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch) { // reserve next game if tourney not yet done
1447 if(q[nextGame] == NULLCHAR) q[nextGame+1] = NULLCHAR; // append one char
1450 fseek(tf, -(strlen(p)+4), SEEK_END);
1452 if(c != '"') // depending on DOS or Unix line endings we can be one off
1453 fseek(tf, -(strlen(p)+2), SEEK_END);
1454 else fseek(tf, -(strlen(p)+3), SEEK_END);
1455 fprintf(tf, "%s\"\n", q); fclose(tf); // update, and flush by closing
1456 DisplayMessage(buf, "");
1457 free(p); appData.results = q;
1458 if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch &&
1459 (gameNr < 0 || nextGame / appData.defaultMatchGames != gameNr / appData.defaultMatchGames)) {
1460 int round = appData.defaultMatchGames * appData.tourneyType;
1461 if(gameNr < 0 || appData.tourneyType < 1 || // gauntlet engine can always stay loaded as first engine
1462 appData.tourneyType > 1 && nextGame/round != gameNr/round) // in multi-gauntlet change only after round
1463 UnloadEngine(&first); // next game belongs to other pairing;
1464 UnloadEngine(&second); // already unload the engines, so TwoMachinesEvent will load new ones.
1466 if(appData.debugMode) fprintf(debugFP, "Reserved, next=%d, nr=%d\n", nextGame, gameNr);
1470 MatchEvent (int mode)
1471 { // [HGM] moved out of InitBackend3, to make it callable when match starts through menu
1473 if(matchMode) { // already in match mode: switch it off
1475 if(!appData.tourneyFile[0]) appData.matchGames = matchGame; // kludge to let match terminate after next game.
1478 // if(gameMode != BeginningOfGame) {
1479 // DisplayError(_("You can only start a match from the initial position."), 0);
1483 if(mode == 2) appData.matchGames = appData.defaultMatchGames;
1484 /* Set up machine vs. machine match */
1486 NextTourneyGame(-1, &dummy); // sets appData.matchGames if this is tourney, to make sure ReserveGame knows it
1487 if(appData.tourneyFile[0]) {
1489 if(nextGame > appData.matchGames) {
1491 if(strchr(appData.results, '*') == NULL) {
1493 appData.tourneyCycles++;
1494 if(f = WriteTourneyFile(appData.results, NULL)) { // make a tourney file with increased number of cycles
1496 NextTourneyGame(-1, &dummy);
1498 if(nextGame <= appData.matchGames) {
1499 DisplayNote(_("You restarted an already completed tourney.\nOne more cycle will now be added to it.\nGames commence in 10 sec."));
1501 ScheduleDelayedEvent(NextMatchGame, 10000);
1506 snprintf(buf, MSG_SIZ, _("All games in tourney '%s' are already played or playing"), appData.tourneyFile);
1507 DisplayError(buf, 0);
1508 appData.tourneyFile[0] = 0;
1512 if (appData.noChessProgram) { // [HGM] in tourney engines are loaded automatically
1513 DisplayFatalError(_("Can't have a match with no chess programs"),
1518 matchGame = roundNr = 1;
1519 first.matchWins = second.matchWins = 0; // [HGM] match: needed in later matches
1523 char *comboLine = NULL; // [HGM] recent: WinBoard's first-engine combobox line
1526 InitBackEnd3 P((void))
1528 GameMode initialMode;
1532 InitChessProgram(&first, startedFromSetupPosition);
1534 if(!appData.noChessProgram) { /* [HGM] tidy: redo program version to use name from myname feature */
1535 free(programVersion);
1536 programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1537 sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1538 FloatToFront(&appData.recentEngineList, comboLine ? comboLine : appData.firstChessProgram);
1541 if (appData.icsActive) {
1543 /* [DM] Make a console window if needed [HGM] merged ifs */
1549 if (*appData.icsCommPort != NULLCHAR)
1550 len = snprintf(buf, MSG_SIZ, _("Could not open comm port %s"),
1551 appData.icsCommPort);
1553 len = snprintf(buf, MSG_SIZ, _("Could not connect to host %s, port %s"),
1554 appData.icsHost, appData.icsPort);
1556 if( (len >= MSG_SIZ) && appData.debugMode )
1557 fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1559 DisplayFatalError(buf, err, 1);
1564 AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1566 AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1567 if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1568 ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1569 } else if (appData.noChessProgram) {
1575 if (*appData.cmailGameName != NULLCHAR) {
1577 OpenLoopback(&cmailPR);
1579 AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1583 DisplayMessage("", "");
1584 if (StrCaseCmp(appData.initialMode, "") == 0) {
1585 initialMode = BeginningOfGame;
1586 if(!appData.icsActive && appData.noChessProgram) { // [HGM] could be fall-back
1587 gameMode = MachinePlaysBlack; // "Machine Black" might have been implicitly highlighted
1588 ModeHighlight(); // make sure XBoard knows it is highlighted, so it will un-highlight it
1589 gameMode = BeginningOfGame; // in case BeginningOfGame now means "Edit Position"
1592 } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1593 initialMode = TwoMachinesPlay;
1594 } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1595 initialMode = AnalyzeFile;
1596 } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1597 initialMode = AnalyzeMode;
1598 } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1599 initialMode = MachinePlaysWhite;
1600 } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1601 initialMode = MachinePlaysBlack;
1602 } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1603 initialMode = EditGame;
1604 } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1605 initialMode = EditPosition;
1606 } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1607 initialMode = Training;
1609 len = snprintf(buf, MSG_SIZ, _("Unknown initialMode %s"), appData.initialMode);
1610 if( (len >= MSG_SIZ) && appData.debugMode )
1611 fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1613 DisplayFatalError(buf, 0, 2);
1617 if (appData.matchMode) {
1618 if(appData.tourneyFile[0]) { // start tourney from command line
1620 if(f = fopen(appData.tourneyFile, "r")) {
1621 ParseArgsFromFile(f); // make sure tourney parmeters re known
1623 appData.clockMode = TRUE;
1625 } else appData.tourneyFile[0] = NULLCHAR; // for now ignore bad tourney file
1628 } else if (*appData.cmailGameName != NULLCHAR) {
1629 /* Set up cmail mode */
1630 ReloadCmailMsgEvent(TRUE);
1632 /* Set up other modes */
1633 if (initialMode == AnalyzeFile) {
1634 if (*appData.loadGameFile == NULLCHAR) {
1635 DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1639 if (*appData.loadGameFile != NULLCHAR) {
1640 (void) LoadGameFromFile(appData.loadGameFile,
1641 appData.loadGameIndex,
1642 appData.loadGameFile, TRUE);
1643 } else if (*appData.loadPositionFile != NULLCHAR) {
1644 (void) LoadPositionFromFile(appData.loadPositionFile,
1645 appData.loadPositionIndex,
1646 appData.loadPositionFile);
1647 /* [HGM] try to make self-starting even after FEN load */
1648 /* to allow automatic setup of fairy variants with wtm */
1649 if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1650 gameMode = BeginningOfGame;
1651 setboardSpoiledMachineBlack = 1;
1653 /* [HGM] loadPos: make that every new game uses the setup */
1654 /* from file as long as we do not switch variant */
1655 if(!blackPlaysFirst) {
1656 startedFromPositionFile = TRUE;
1657 CopyBoard(filePosition, boards[0]);
1660 if (initialMode == AnalyzeMode) {
1661 if (appData.noChessProgram) {
1662 DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1665 if (appData.icsActive) {
1666 DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1670 } else if (initialMode == AnalyzeFile) {
1671 appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1672 ShowThinkingEvent();
1674 AnalysisPeriodicEvent(1);
1675 } else if (initialMode == MachinePlaysWhite) {
1676 if (appData.noChessProgram) {
1677 DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1681 if (appData.icsActive) {
1682 DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1686 MachineWhiteEvent();
1687 } else if (initialMode == MachinePlaysBlack) {
1688 if (appData.noChessProgram) {
1689 DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1693 if (appData.icsActive) {
1694 DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1698 MachineBlackEvent();
1699 } else if (initialMode == TwoMachinesPlay) {
1700 if (appData.noChessProgram) {
1701 DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1705 if (appData.icsActive) {
1706 DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1711 } else if (initialMode == EditGame) {
1713 } else if (initialMode == EditPosition) {
1714 EditPositionEvent();
1715 } else if (initialMode == Training) {
1716 if (*appData.loadGameFile == NULLCHAR) {
1717 DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1726 HistorySet (char movelist[][2*MOVE_LEN], int first, int last, int current)
1728 DisplayBook(current+1);
1730 MoveHistorySet( movelist, first, last, current, pvInfoList );
1732 EvalGraphSet( first, last, current, pvInfoList );
1734 MakeEngineOutputTitle();
1738 * Establish will establish a contact to a remote host.port.
1739 * Sets icsPR to a ProcRef for a process (or pseudo-process)
1740 * used to talk to the host.
1741 * Returns 0 if okay, error code if not.
1748 if (*appData.icsCommPort != NULLCHAR) {
1749 /* Talk to the host through a serial comm port */
1750 return OpenCommPort(appData.icsCommPort, &icsPR);
1752 } else if (*appData.gateway != NULLCHAR) {
1753 if (*appData.remoteShell == NULLCHAR) {
1754 /* Use the rcmd protocol to run telnet program on a gateway host */
1755 snprintf(buf, sizeof(buf), "%s %s %s",
1756 appData.telnetProgram, appData.icsHost, appData.icsPort);
1757 return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1760 /* Use the rsh program to run telnet program on a gateway host */
1761 if (*appData.remoteUser == NULLCHAR) {
1762 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1763 appData.gateway, appData.telnetProgram,
1764 appData.icsHost, appData.icsPort);
1766 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1767 appData.remoteShell, appData.gateway,
1768 appData.remoteUser, appData.telnetProgram,
1769 appData.icsHost, appData.icsPort);
1771 return StartChildProcess(buf, "", &icsPR);
1774 } else if (appData.useTelnet) {
1775 return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1778 /* TCP socket interface differs somewhat between
1779 Unix and NT; handle details in the front end.
1781 return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1786 EscapeExpand (char *p, char *q)
1787 { // [HGM] initstring: routine to shape up string arguments
1788 while(*p++ = *q++) if(p[-1] == '\\')
1790 case 'n': p[-1] = '\n'; break;
1791 case 'r': p[-1] = '\r'; break;
1792 case 't': p[-1] = '\t'; break;
1793 case '\\': p[-1] = '\\'; break;
1794 case 0: *p = 0; return;
1795 default: p[-1] = q[-1]; break;
1800 show_bytes (FILE *fp, char *buf, int count)
1803 if (*buf < 040 || *(unsigned char *) buf > 0177) {
1804 fprintf(fp, "\\%03o", *buf & 0xff);
1813 /* Returns an errno value */
1815 OutputMaybeTelnet (ProcRef pr, char *message, int count, int *outError)
1817 char buf[8192], *p, *q, *buflim;
1818 int left, newcount, outcount;
1820 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1821 *appData.gateway != NULLCHAR) {
1822 if (appData.debugMode) {
1823 fprintf(debugFP, ">ICS: ");
1824 show_bytes(debugFP, message, count);
1825 fprintf(debugFP, "\n");
1827 return OutputToProcess(pr, message, count, outError);
1830 buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1837 if (appData.debugMode) {
1838 fprintf(debugFP, ">ICS: ");
1839 show_bytes(debugFP, buf, newcount);
1840 fprintf(debugFP, "\n");
1842 outcount = OutputToProcess(pr, buf, newcount, outError);
1843 if (outcount < newcount) return -1; /* to be sure */
1850 } else if (((unsigned char) *p) == TN_IAC) {
1851 *q++ = (char) TN_IAC;
1858 if (appData.debugMode) {
1859 fprintf(debugFP, ">ICS: ");
1860 show_bytes(debugFP, buf, newcount);
1861 fprintf(debugFP, "\n");
1863 outcount = OutputToProcess(pr, buf, newcount, outError);
1864 if (outcount < newcount) return -1; /* to be sure */
1869 read_from_player (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
1871 int outError, outCount;
1872 static int gotEof = 0;
1875 /* Pass data read from player on to ICS */
1878 outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1879 if (outCount < count) {
1880 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1882 if(have_sent_ICS_logon == 2) {
1883 if(ini = fopen(appData.icsLogon, "w")) { // save first two lines (presumably username & password) on init script file
1884 fprintf(ini, "%s", message);
1885 have_sent_ICS_logon = 3;
1887 have_sent_ICS_logon = 1;
1888 } else if(have_sent_ICS_logon == 3) {
1889 fprintf(ini, "%s", message);
1891 have_sent_ICS_logon = 1;
1893 } else if (count < 0) {
1894 RemoveInputSource(isr);
1895 DisplayFatalError(_("Error reading from keyboard"), error, 1);
1896 } else if (gotEof++ > 0) {
1897 RemoveInputSource(isr);
1898 DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1904 { // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1905 if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1906 connectionAlive = FALSE; // only sticks if no response to 'date' command.
1907 SendToICS("date\n");
1908 if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1911 /* added routine for printf style output to ics */
1913 ics_printf (char *format, ...)
1915 char buffer[MSG_SIZ];
1918 va_start(args, format);
1919 vsnprintf(buffer, sizeof(buffer), format, args);
1920 buffer[sizeof(buffer)-1] = '\0';
1928 int count, outCount, outError;
1930 if (icsPR == NoProc) return;
1933 outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1934 if (outCount < count) {
1935 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1939 /* This is used for sending logon scripts to the ICS. Sending
1940 without a delay causes problems when using timestamp on ICC
1941 (at least on my machine). */
1943 SendToICSDelayed (char *s, long msdelay)
1945 int count, outCount, outError;
1947 if (icsPR == NoProc) return;
1950 if (appData.debugMode) {
1951 fprintf(debugFP, ">ICS: ");
1952 show_bytes(debugFP, s, count);
1953 fprintf(debugFP, "\n");
1955 outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1957 if (outCount < count) {
1958 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1963 /* Remove all highlighting escape sequences in s
1964 Also deletes any suffix starting with '('
1967 StripHighlightAndTitle (char *s)
1969 static char retbuf[MSG_SIZ];
1972 while (*s != NULLCHAR) {
1973 while (*s == '\033') {
1974 while (*s != NULLCHAR && !isalpha(*s)) s++;
1975 if (*s != NULLCHAR) s++;
1977 while (*s != NULLCHAR && *s != '\033') {
1978 if (*s == '(' || *s == '[') {
1989 /* Remove all highlighting escape sequences in s */
1991 StripHighlight (char *s)
1993 static char retbuf[MSG_SIZ];
1996 while (*s != NULLCHAR) {
1997 while (*s == '\033') {
1998 while (*s != NULLCHAR && !isalpha(*s)) s++;
1999 if (*s != NULLCHAR) s++;
2001 while (*s != NULLCHAR && *s != '\033') {
2009 char engineVariant[MSG_SIZ];
2010 char *variantNames[] = VARIANT_NAMES;
2012 VariantName (VariantClass v)
2014 if(v == VariantUnknown || *engineVariant) return engineVariant;
2015 return variantNames[v];
2019 /* Identify a variant from the strings the chess servers use or the
2020 PGN Variant tag names we use. */
2022 StringToVariant (char *e)
2026 VariantClass v = VariantNormal;
2027 int i, found = FALSE;
2033 /* [HGM] skip over optional board-size prefixes */
2034 if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
2035 sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
2036 while( *e++ != '_');
2039 if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
2043 for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
2044 if (StrCaseStr(e, variantNames[i])) {
2045 v = (VariantClass) i;
2052 if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
2053 || StrCaseStr(e, "wild/fr")
2054 || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
2055 v = VariantFischeRandom;
2056 } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
2057 (i = 1, p = StrCaseStr(e, "w"))) {
2059 while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
2066 case 0: /* FICS only, actually */
2068 /* Castling legal even if K starts on d-file */
2069 v = VariantWildCastle;
2074 /* Castling illegal even if K & R happen to start in
2075 normal positions. */
2076 v = VariantNoCastle;
2089 /* Castling legal iff K & R start in normal positions */
2095 /* Special wilds for position setup; unclear what to do here */
2096 v = VariantLoadable;
2099 /* Bizarre ICC game */
2100 v = VariantTwoKings;
2103 v = VariantKriegspiel;
2109 v = VariantFischeRandom;
2112 v = VariantCrazyhouse;
2115 v = VariantBughouse;
2121 /* Not quite the same as FICS suicide! */
2122 v = VariantGiveaway;
2128 v = VariantShatranj;
2131 /* Temporary names for future ICC types. The name *will* change in
2132 the next xboard/WinBoard release after ICC defines it. */
2170 v = VariantCapablanca;
2173 v = VariantKnightmate;
2179 v = VariantCylinder;
2185 v = VariantCapaRandom;
2188 v = VariantBerolina;
2200 /* Found "wild" or "w" in the string but no number;
2201 must assume it's normal chess. */
2205 len = snprintf(buf, MSG_SIZ, _("Unknown wild type %d"), wnum);
2206 if( (len >= MSG_SIZ) && appData.debugMode )
2207 fprintf(debugFP, "StringToVariant: buffer truncated.\n");
2209 DisplayError(buf, 0);
2215 if (appData.debugMode) {
2216 fprintf(debugFP, "recognized '%s' (%d) as variant %s\n",
2217 e, wnum, VariantName(v));
2222 static int leftover_start = 0, leftover_len = 0;
2223 char star_match[STAR_MATCH_N][MSG_SIZ];
2225 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
2226 advance *index beyond it, and set leftover_start to the new value of
2227 *index; else return FALSE. If pattern contains the character '*', it
2228 matches any sequence of characters not containing '\r', '\n', or the
2229 character following the '*' (if any), and the matched sequence(s) are
2230 copied into star_match.
2233 looking_at ( char *buf, int *index, char *pattern)
2235 char *bufp = &buf[*index], *patternp = pattern;
2237 char *matchp = star_match[0];
2240 if (*patternp == NULLCHAR) {
2241 *index = leftover_start = bufp - buf;
2245 if (*bufp == NULLCHAR) return FALSE;
2246 if (*patternp == '*') {
2247 if (*bufp == *(patternp + 1)) {
2249 matchp = star_match[++star_count];
2253 } else if (*bufp == '\n' || *bufp == '\r') {
2255 if (*patternp == NULLCHAR)
2260 *matchp++ = *bufp++;
2264 if (*patternp != *bufp) return FALSE;
2271 SendToPlayer (char *data, int length)
2273 int error, outCount;
2274 outCount = OutputToProcess(NoProc, data, length, &error);
2275 if (outCount < length) {
2276 DisplayFatalError(_("Error writing to display"), error, 1);
2281 PackHolding (char packed[], char *holding)
2291 switch (runlength) {
2302 sprintf(q, "%d", runlength);
2314 /* Telnet protocol requests from the front end */
2316 TelnetRequest (unsigned char ddww, unsigned char option)
2318 unsigned char msg[3];
2319 int outCount, outError;
2321 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
2323 if (appData.debugMode) {
2324 char buf1[8], buf2[8], *ddwwStr, *optionStr;
2340 snprintf(buf1,sizeof(buf1)/sizeof(buf1[0]), "%d", ddww);
2349 snprintf(buf2,sizeof(buf2)/sizeof(buf2[0]), "%d", option);
2352 fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
2357 outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
2359 DisplayFatalError(_("Error writing to ICS"), outError, 1);
2366 if (!appData.icsActive) return;
2367 TelnetRequest(TN_DO, TN_ECHO);
2373 if (!appData.icsActive) return;
2374 TelnetRequest(TN_DONT, TN_ECHO);
2378 CopyHoldings (Board board, char *holdings, ChessSquare lowestPiece)
2380 /* put the holdings sent to us by the server on the board holdings area */
2381 int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2385 if(gameInfo.holdingsWidth < 2) return;
2386 if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2387 return; // prevent overwriting by pre-board holdings
2389 if( (int)lowestPiece >= BlackPawn ) {
2392 holdingsStartRow = BOARD_HEIGHT-1;
2395 holdingsColumn = BOARD_WIDTH-1;
2396 countsColumn = BOARD_WIDTH-2;
2397 holdingsStartRow = 0;
2401 for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
2402 board[i][holdingsColumn] = EmptySquare;
2403 board[i][countsColumn] = (ChessSquare) 0;
2405 while( (p=*holdings++) != NULLCHAR ) {
2406 piece = CharToPiece( ToUpper(p) );
2407 if(piece == EmptySquare) continue;
2408 /*j = (int) piece - (int) WhitePawn;*/
2409 j = PieceToNumber(piece);
2410 if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2411 if(j < 0) continue; /* should not happen */
2412 piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2413 board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2414 board[holdingsStartRow+j*direction][countsColumn]++;
2420 VariantSwitch (Board board, VariantClass newVariant)
2422 int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2423 static Board oldBoard;
2425 startedFromPositionFile = FALSE;
2426 if(gameInfo.variant == newVariant) return;
2428 /* [HGM] This routine is called each time an assignment is made to
2429 * gameInfo.variant during a game, to make sure the board sizes
2430 * are set to match the new variant. If that means adding or deleting
2431 * holdings, we shift the playing board accordingly
2432 * This kludge is needed because in ICS observe mode, we get boards
2433 * of an ongoing game without knowing the variant, and learn about the
2434 * latter only later. This can be because of the move list we requested,
2435 * in which case the game history is refilled from the beginning anyway,
2436 * but also when receiving holdings of a crazyhouse game. In the latter
2437 * case we want to add those holdings to the already received position.
2441 if (appData.debugMode) {
2442 fprintf(debugFP, "Switch board from %s to %s\n",
2443 VariantName(gameInfo.variant), VariantName(newVariant));
2444 setbuf(debugFP, NULL);
2446 shuffleOpenings = 0; /* [HGM] shuffle */
2447 gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2451 newWidth = 9; newHeight = 9;
2452 gameInfo.holdingsSize = 7;
2453 case VariantBughouse:
2454 case VariantCrazyhouse:
2455 newHoldingsWidth = 2; break;
2459 newHoldingsWidth = 2;
2460 gameInfo.holdingsSize = 8;
2463 case VariantCapablanca:
2464 case VariantCapaRandom:
2467 newHoldingsWidth = gameInfo.holdingsSize = 0;
2470 if(newWidth != gameInfo.boardWidth ||
2471 newHeight != gameInfo.boardHeight ||
2472 newHoldingsWidth != gameInfo.holdingsWidth ) {
2474 /* shift position to new playing area, if needed */
2475 if(newHoldingsWidth > gameInfo.holdingsWidth) {
2476 for(i=0; i<BOARD_HEIGHT; i++)
2477 for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2478 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2480 for(i=0; i<newHeight; i++) {
2481 board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2482 board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2484 } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2485 for(i=0; i<BOARD_HEIGHT; i++)
2486 for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2487 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2490 board[HOLDINGS_SET] = 0;
2491 gameInfo.boardWidth = newWidth;
2492 gameInfo.boardHeight = newHeight;
2493 gameInfo.holdingsWidth = newHoldingsWidth;
2494 gameInfo.variant = newVariant;
2495 InitDrawingSizes(-2, 0);
2496 } else gameInfo.variant = newVariant;
2497 CopyBoard(oldBoard, board); // remember correctly formatted board
2498 InitPosition(FALSE); /* this sets up board[0], but also other stuff */
2499 DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2502 static int loggedOn = FALSE;
2504 /*-- Game start info cache: --*/
2506 char gs_kind[MSG_SIZ];
2507 static char player1Name[128] = "";
2508 static char player2Name[128] = "";
2509 static char cont_seq[] = "\n\\ ";
2510 static int player1Rating = -1;
2511 static int player2Rating = -1;
2512 /*----------------------------*/
2514 ColorClass curColor = ColorNormal;
2515 int suppressKibitz = 0;
2518 Boolean soughtPending = FALSE;
2519 Boolean seekGraphUp;
2520 #define MAX_SEEK_ADS 200
2522 char *seekAdList[MAX_SEEK_ADS];
2523 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2524 float tcList[MAX_SEEK_ADS];
2525 char colorList[MAX_SEEK_ADS];
2526 int nrOfSeekAds = 0;
2527 int minRating = 1010, maxRating = 2800;
2528 int hMargin = 10, vMargin = 20, h, w;
2529 extern int squareSize, lineGap;
2534 int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2535 xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2536 if(r < minRating+100 && r >=0 ) r = minRating+100;
2537 if(r > maxRating) r = maxRating;
2538 if(tc < 1.f) tc = 1.f;
2539 if(tc > 95.f) tc = 95.f;
2540 x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2541 y = ((double)r - minRating)/(maxRating - minRating)
2542 * (h-vMargin-squareSize/8-1) + vMargin;
2543 if(ratingList[i] < 0) y = vMargin + squareSize/4;
2544 if(strstr(seekAdList[i], " u ")) color = 1;
2545 if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2546 !strstr(seekAdList[i], "bullet") &&
2547 !strstr(seekAdList[i], "blitz") &&
2548 !strstr(seekAdList[i], "standard") ) color = 2;
2549 if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2550 DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2554 PlotSingleSeekAd (int i)
2560 AddAd (char *handle, char *rating, int base, int inc, char rated, char *type, int nr, Boolean plot)
2562 char buf[MSG_SIZ], *ext = "";
2563 VariantClass v = StringToVariant(type);
2564 if(strstr(type, "wild")) {
2565 ext = type + 4; // append wild number
2566 if(v == VariantFischeRandom) type = "chess960"; else
2567 if(v == VariantLoadable) type = "setup"; else
2568 type = VariantName(v);
2570 snprintf(buf, MSG_SIZ, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2571 if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2572 if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2573 ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2574 sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2575 tcList[nrOfSeekAds] = base + (2./3.)*inc;
2576 seekNrList[nrOfSeekAds] = nr;
2577 zList[nrOfSeekAds] = 0;
2578 seekAdList[nrOfSeekAds++] = StrSave(buf);
2579 if(plot) PlotSingleSeekAd(nrOfSeekAds-1);
2584 EraseSeekDot (int i)
2586 int x = xList[i], y = yList[i], d=squareSize/4, k;
2587 DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2588 if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2589 // now replot every dot that overlapped
2590 for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2591 int xx = xList[k], yy = yList[k];
2592 if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2593 DrawSeekDot(xx, yy, colorList[k]);
2598 RemoveSeekAd (int nr)
2601 for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2603 if(seekAdList[i]) free(seekAdList[i]);
2604 seekAdList[i] = seekAdList[--nrOfSeekAds];
2605 seekNrList[i] = seekNrList[nrOfSeekAds];
2606 ratingList[i] = ratingList[nrOfSeekAds];
2607 colorList[i] = colorList[nrOfSeekAds];
2608 tcList[i] = tcList[nrOfSeekAds];
2609 xList[i] = xList[nrOfSeekAds];
2610 yList[i] = yList[nrOfSeekAds];
2611 zList[i] = zList[nrOfSeekAds];
2612 seekAdList[nrOfSeekAds] = NULL;
2618 MatchSoughtLine (char *line)
2620 char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2621 int nr, base, inc, u=0; char dummy;
2623 if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2624 sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2626 (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2627 sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7) ) {
2628 // match: compact and save the line
2629 AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2639 if(!seekGraphUp) return FALSE;
2640 h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
2641 w = BOARD_WIDTH * (squareSize + lineGap) + lineGap;
2643 DrawSeekBackground(0, 0, w, h);
2644 DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2645 DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2646 for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2647 int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2649 DrawSeekAxis(hMargin-5, yy, hMargin+5*(i%500==0), yy); // rating ticks
2652 snprintf(buf, MSG_SIZ, "%d", i);
2653 DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2656 DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2657 for(i=1; i<100; i+=(i<10?1:5)) {
2658 int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2659 DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2660 if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2662 snprintf(buf, MSG_SIZ, "%d", i);
2663 DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2666 for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2671 SeekGraphClick (ClickType click, int x, int y, int moving)
2673 static int lastDown = 0, displayed = 0, lastSecond;
2674 if(y < 0) return FALSE;
2675 if(!(appData.seekGraph && appData.icsActive && loggedOn &&
2676 (gameMode == BeginningOfGame || gameMode == IcsIdle))) {
2677 if(!seekGraphUp) return FALSE;
2678 seekGraphUp = FALSE; // seek graph is up when it shouldn't be: take it down
2679 DrawPosition(TRUE, NULL);
2682 if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2683 if(click == Release || moving) return FALSE;
2685 soughtPending = TRUE;
2686 SendToICS(ics_prefix);
2687 SendToICS("sought\n"); // should this be "sought all"?
2688 } else { // issue challenge based on clicked ad
2689 int dist = 10000; int i, closest = 0, second = 0;
2690 for(i=0; i<nrOfSeekAds; i++) {
2691 int d = (x-xList[i])*(x-xList[i]) + (y-yList[i])*(y-yList[i]) + zList[i];
2692 if(d < dist) { dist = d; closest = i; }
2693 second += (d - zList[i] < 120); // count in-range ads
2694 if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2698 second = (second > 1);
2699 if(displayed != closest || second != lastSecond) {
2700 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2701 lastSecond = second; displayed = closest;
2703 if(click == Press) {
2704 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2707 } // on press 'hit', only show info
2708 if(moving == 2) return TRUE; // ignore right up-clicks on dot
2709 snprintf(buf, MSG_SIZ, "play %d\n", seekNrList[closest]);
2710 SendToICS(ics_prefix);
2712 return TRUE; // let incoming board of started game pop down the graph
2713 } else if(click == Release) { // release 'miss' is ignored
2714 zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2715 if(moving == 2) { // right up-click
2716 nrOfSeekAds = 0; // refresh graph
2717 soughtPending = TRUE;
2718 SendToICS(ics_prefix);
2719 SendToICS("sought\n"); // should this be "sought all"?
2722 } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2723 // press miss or release hit 'pop down' seek graph
2724 seekGraphUp = FALSE;
2725 DrawPosition(TRUE, NULL);
2731 read_from_ics (InputSourceRef isr, VOIDSTAR closure, char *data, int count, int error)
2733 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2734 #define STARTED_NONE 0
2735 #define STARTED_MOVES 1
2736 #define STARTED_BOARD 2
2737 #define STARTED_OBSERVE 3
2738 #define STARTED_HOLDINGS 4
2739 #define STARTED_CHATTER 5
2740 #define STARTED_COMMENT 6
2741 #define STARTED_MOVES_NOHIDE 7
2743 static int started = STARTED_NONE;
2744 static char parse[20000];
2745 static int parse_pos = 0;
2746 static char buf[BUF_SIZE + 1];
2747 static int firstTime = TRUE, intfSet = FALSE;
2748 static ColorClass prevColor = ColorNormal;
2749 static int savingComment = FALSE;
2750 static int cmatch = 0; // continuation sequence match
2757 int backup; /* [DM] For zippy color lines */
2759 char talker[MSG_SIZ]; // [HGM] chat
2762 connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2764 if (appData.debugMode) {
2766 fprintf(debugFP, "<ICS: ");
2767 show_bytes(debugFP, data, count);
2768 fprintf(debugFP, "\n");
2772 if (appData.debugMode) { int f = forwardMostMove;
2773 fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2774 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2775 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2778 /* If last read ended with a partial line that we couldn't parse,
2779 prepend it to the new read and try again. */
2780 if (leftover_len > 0) {
2781 for (i=0; i<leftover_len; i++)
2782 buf[i] = buf[leftover_start + i];
2785 /* copy new characters into the buffer */
2786 bp = buf + leftover_len;
2787 buf_len=leftover_len;
2788 for (i=0; i<count; i++)
2791 if (data[i] == '\r')
2794 // join lines split by ICS?
2795 if (!appData.noJoin)
2798 Joining just consists of finding matches against the
2799 continuation sequence, and discarding that sequence
2800 if found instead of copying it. So, until a match
2801 fails, there's nothing to do since it might be the
2802 complete sequence, and thus, something we don't want
2805 if (data[i] == cont_seq[cmatch])
2808 if (cmatch == strlen(cont_seq))
2810 cmatch = 0; // complete match. just reset the counter
2813 it's possible for the ICS to not include the space
2814 at the end of the last word, making our [correct]
2815 join operation fuse two separate words. the server
2816 does this when the space occurs at the width setting.
2818 if (!buf_len || buf[buf_len-1] != ' ')
2829 match failed, so we have to copy what matched before
2830 falling through and copying this character. In reality,
2831 this will only ever be just the newline character, but
2832 it doesn't hurt to be precise.
2834 strncpy(bp, cont_seq, cmatch);
2846 buf[buf_len] = NULLCHAR;
2847 // next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2852 while (i < buf_len) {
2853 /* Deal with part of the TELNET option negotiation
2854 protocol. We refuse to do anything beyond the
2855 defaults, except that we allow the WILL ECHO option,
2856 which ICS uses to turn off password echoing when we are
2857 directly connected to it. We reject this option
2858 if localLineEditing mode is on (always on in xboard)
2859 and we are talking to port 23, which might be a real
2860 telnet server that will try to keep WILL ECHO on permanently.
2862 if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2863 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2864 unsigned char option;
2866 switch ((unsigned char) buf[++i]) {
2868 if (appData.debugMode)
2869 fprintf(debugFP, "\n<WILL ");
2870 switch (option = (unsigned char) buf[++i]) {
2872 if (appData.debugMode)
2873 fprintf(debugFP, "ECHO ");
2874 /* Reply only if this is a change, according
2875 to the protocol rules. */
2876 if (remoteEchoOption) break;
2877 if (appData.localLineEditing &&
2878 atoi(appData.icsPort) == TN_PORT) {
2879 TelnetRequest(TN_DONT, TN_ECHO);
2882 TelnetRequest(TN_DO, TN_ECHO);
2883 remoteEchoOption = TRUE;
2887 if (appData.debugMode)
2888 fprintf(debugFP, "%d ", option);
2889 /* Whatever this is, we don't want it. */
2890 TelnetRequest(TN_DONT, option);
2895 if (appData.debugMode)
2896 fprintf(debugFP, "\n<WONT ");
2897 switch (option = (unsigned char) buf[++i]) {
2899 if (appData.debugMode)
2900 fprintf(debugFP, "ECHO ");
2901 /* Reply only if this is a change, according
2902 to the protocol rules. */
2903 if (!remoteEchoOption) break;
2905 TelnetRequest(TN_DONT, TN_ECHO);
2906 remoteEchoOption = FALSE;
2909 if (appData.debugMode)
2910 fprintf(debugFP, "%d ", (unsigned char) option);
2911 /* Whatever this is, it must already be turned
2912 off, because we never agree to turn on
2913 anything non-default, so according to the
2914 protocol rules, we don't reply. */
2919 if (appData.debugMode)
2920 fprintf(debugFP, "\n<DO ");
2921 switch (option = (unsigned char) buf[++i]) {
2923 /* Whatever this is, we refuse to do it. */
2924 if (appData.debugMode)
2925 fprintf(debugFP, "%d ", option);
2926 TelnetRequest(TN_WONT, option);
2931 if (appData.debugMode)
2932 fprintf(debugFP, "\n<DONT ");
2933 switch (option = (unsigned char) buf[++i]) {
2935 if (appData.debugMode)
2936 fprintf(debugFP, "%d ", option);
2937 /* Whatever this is, we are already not doing
2938 it, because we never agree to do anything
2939 non-default, so according to the protocol
2940 rules, we don't reply. */
2945 if (appData.debugMode)
2946 fprintf(debugFP, "\n<IAC ");
2947 /* Doubled IAC; pass it through */
2951 if (appData.debugMode)
2952 fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2953 /* Drop all other telnet commands on the floor */
2956 if (oldi > next_out)
2957 SendToPlayer(&buf[next_out], oldi - next_out);
2963 /* OK, this at least will *usually* work */
2964 if (!loggedOn && looking_at(buf, &i, "ics%")) {
2968 if (loggedOn && !intfSet) {
2969 if (ics_type == ICS_ICC) {
2970 snprintf(str, MSG_SIZ,
2971 "/set-quietly interface %s\n/set-quietly style 12\n",
2973 if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2974 strcat(str, "/set-2 51 1\n/set seek 1\n");
2975 } else if (ics_type == ICS_CHESSNET) {
2976 snprintf(str, MSG_SIZ, "/style 12\n");
2978 safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
2979 strcat(str, programVersion);
2980 strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2981 if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2982 strcat(str, "$iset seekremove 1\n$set seek 1\n");
2984 strcat(str, "$iset nohighlight 1\n");
2986 strcat(str, "$iset lock 1\n$style 12\n");
2989 NotifyFrontendLogin();
2993 if (started == STARTED_COMMENT) {
2994 /* Accumulate characters in comment */
2995 parse[parse_pos++] = buf[i];
2996 if (buf[i] == '\n') {
2997 parse[parse_pos] = NULLCHAR;
2998 if(chattingPartner>=0) {
3000 snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
3001 OutputChatMessage(chattingPartner, mess);
3002 chattingPartner = -1;
3003 next_out = i+1; // [HGM] suppress printing in ICS window
3005 if(!suppressKibitz) // [HGM] kibitz
3006 AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
3007 else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
3008 int nrDigit = 0, nrAlph = 0, j;
3009 if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
3010 { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
3011 parse[parse_pos] = NULLCHAR;
3012 // try to be smart: if it does not look like search info, it should go to
3013 // ICS interaction window after all, not to engine-output window.
3014 for(j=0; j<parse_pos; j++) { // count letters and digits
3015 nrDigit += (parse[j] >= '0' && parse[j] <= '9');
3016 nrAlph += (parse[j] >= 'a' && parse[j] <= 'z');
3017 nrAlph += (parse[j] >= 'A' && parse[j] <= 'Z');
3019 if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
3020 int depth=0; float score;
3021 if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
3022 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
3023 pvInfoList[forwardMostMove-1].depth = depth;
3024 pvInfoList[forwardMostMove-1].score = 100*score;
3026 OutputKibitz(suppressKibitz, parse);
3029 if(gameMode == IcsObserving) // restore original ICS messages
3030 /* TRANSLATORS: to 'kibitz' is to send a message to all players and the game observers */
3031 snprintf(tmp, MSG_SIZ, "%s kibitzes: %s", star_match[0], parse);
3033 /* TRANSLATORS: to 'kibitz' is to send a message to all players and the game observers */
3034 snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
3035 SendToPlayer(tmp, strlen(tmp));
3037 next_out = i+1; // [HGM] suppress printing in ICS window
3039 started = STARTED_NONE;
3041 /* Don't match patterns against characters in comment */
3046 if (started == STARTED_CHATTER) {
3047 if (buf[i] != '\n') {
3048 /* Don't match patterns against characters in chatter */
3052 started = STARTED_NONE;
3053 if(suppressKibitz) next_out = i+1;
3056 /* Kludge to deal with rcmd protocol */
3057 if (firstTime && looking_at(buf, &i, "\001*")) {
3058 DisplayFatalError(&buf[1], 0, 1);
3064 if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
3067 if (appData.debugMode)
3068 fprintf(debugFP, "ics_type %d\n", ics_type);
3071 if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
3072 ics_type = ICS_FICS;
3074 if (appData.debugMode)
3075 fprintf(debugFP, "ics_type %d\n", ics_type);
3078 if (!loggedOn && looking_at(buf, &i, "chess.net")) {
3079 ics_type = ICS_CHESSNET;
3081 if (appData.debugMode)
3082 fprintf(debugFP, "ics_type %d\n", ics_type);
3087 (looking_at(buf, &i, "\"*\" is *a registered name") ||
3088 looking_at(buf, &i, "Logging you in as \"*\"") ||
3089 looking_at(buf, &i, "will be \"*\""))) {
3090 safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
3094 if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
3096 snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
3097 DisplayIcsInteractionTitle(buf);
3098 have_set_title = TRUE;
3101 /* skip finger notes */
3102 if (started == STARTED_NONE &&
3103 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
3104 (buf[i] == '1' && buf[i+1] == '0')) &&
3105 buf[i+2] == ':' && buf[i+3] == ' ') {
3106 started = STARTED_CHATTER;
3112 // [HGM] seekgraph: recognize sought lines and end-of-sought message
3113 if(appData.seekGraph) {
3114 if(soughtPending && MatchSoughtLine(buf+i)) {
3115 i = strstr(buf+i, "rated") - buf;
3116 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3117 next_out = leftover_start = i;
3118 started = STARTED_CHATTER;
3119 suppressKibitz = TRUE;
3122 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
3123 && looking_at(buf, &i, "* ads displayed")) {
3124 soughtPending = FALSE;
3129 if(appData.autoRefresh) {
3130 if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
3131 int s = (ics_type == ICS_ICC); // ICC format differs
3133 AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
3134 star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
3135 looking_at(buf, &i, "*% "); // eat prompt
3136 if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
3137 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3138 next_out = i; // suppress
3141 if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
3142 char *p = star_match[0];
3144 if(seekGraphUp) RemoveSeekAd(atoi(p));
3145 while(*p && *p++ != ' '); // next
3147 looking_at(buf, &i, "*% "); // eat prompt
3148 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3155 /* skip formula vars */
3156 if (started == STARTED_NONE &&
3157 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
3158 started = STARTED_CHATTER;
3163 // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
3164 if (appData.autoKibitz && started == STARTED_NONE &&
3165 !appData.icsEngineAnalyze && // [HGM] [DM] ICS analyze
3166 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
3167 if((looking_at(buf, &i, "\n* kibitzes: ") || looking_at(buf, &i, "\n* whispers: ") ||
3168 looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
3169 (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
3170 StrStr(star_match[0], gameInfo.black) == star_match[0] )) { // kibitz of self or opponent
3171 suppressKibitz = TRUE;
3172 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3174 if((StrStr(star_match[0], gameInfo.white) == star_match[0]
3175 && (gameMode == IcsPlayingWhite)) ||
3176 (StrStr(star_match[0], gameInfo.black) == star_match[0]
3177 && (gameMode == IcsPlayingBlack)) ) // opponent kibitz
3178 started = STARTED_CHATTER; // own kibitz we simply discard
3180 started = STARTED_COMMENT; // make sure it will be collected in parse[]
3181 parse_pos = 0; parse[0] = NULLCHAR;
3182 savingComment = TRUE;
3183 suppressKibitz = gameMode != IcsObserving ? 2 :
3184 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
3188 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
3189 looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
3190 && atoi(star_match[0])) {
3191 // suppress the acknowledgements of our own autoKibitz
3193 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3194 if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
3195 SendToPlayer(star_match[0], strlen(star_match[0]));
3196 if(looking_at(buf, &i, "*% ")) // eat prompt
3197 suppressKibitz = FALSE;
3201 } // [HGM] kibitz: end of patch
3203 if(looking_at(buf, &i, "* rating adjustment: * --> *\n")) continue;
3205 // [HGM] chat: intercept tells by users for which we have an open chat window
3207 if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
3208 looking_at(buf, &i, "* whispers:") ||
3209 looking_at(buf, &i, "* kibitzes:") ||
3210 looking_at(buf, &i, "* shouts:") ||
3211 looking_at(buf, &i, "* c-shouts:") ||
3212 looking_at(buf, &i, "--> * ") ||
3213 looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
3214 looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
3215 looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
3216 looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
3218 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
3219 chattingPartner = -1;
3221 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
3222 for(p=0; p<MAX_CHAT; p++) {
3223 if(chatPartner[p][0] >= '0' && chatPartner[p][0] <= '9' && channel == atoi(chatPartner[p])) {
3224 talker[0] = '['; strcat(talker, "] ");
3225 Colorize(channel == 1 ? ColorChannel1 : ColorChannel, FALSE);
3226 chattingPartner = p; break;
3229 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
3230 for(p=0; p<MAX_CHAT; p++) {
3231 if(!strcmp("kibitzes", chatPartner[p])) {
3232 talker[0] = '['; strcat(talker, "] ");
3233 chattingPartner = p; break;
3236 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
3237 for(p=0; p<MAX_CHAT; p++) {
3238 if(!strcmp("whispers", chatPartner[p])) {
3239 talker[0] = '['; strcat(talker, "] ");
3240 chattingPartner = p; break;
3243 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
3244 if(buf[i-8] == '-' && buf[i-3] == 't')
3245 for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
3246 if(!strcmp("c-shouts", chatPartner[p])) {
3247 talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
3248 chattingPartner = p; break;
3251 if(chattingPartner < 0)
3252 for(p=0; p<MAX_CHAT; p++) {
3253 if(!strcmp("shouts", chatPartner[p])) {
3254 if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
3255 else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
3256 else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
3257 chattingPartner = p; break;
3261 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
3262 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3263 talker[0] = 0; Colorize(ColorTell, FALSE);
3264 chattingPartner = p; break;
3266 if(chattingPartner<0) i = oldi; else {
3267 Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
3268 if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
3269 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3270 started = STARTED_COMMENT;
3271 parse_pos = 0; parse[0] = NULLCHAR;
3272 savingComment = 3 + chattingPartner; // counts as TRUE
3273 suppressKibitz = TRUE;
3276 } // [HGM] chat: end of patch
3279 if (appData.zippyTalk || appData.zippyPlay) {
3280 /* [DM] Backup address for color zippy lines */
3282 if (loggedOn == TRUE)
3283 if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
3284 (appData.zippyPlay && ZippyMatch(buf, &backup)));
3286 } // [DM] 'else { ' deleted
3288 /* Regular tells and says */
3289 (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
3290 looking_at(buf, &i, "* (your partner) tells you: ") ||
3291 looking_at(buf, &i, "* says: ") ||
3292 /* Don't color "message" or "messages" output */
3293 (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
3294 looking_at(buf, &i, "*. * at *:*: ") ||
3295 looking_at(buf, &i, "--* (*:*): ") ||
3296 /* Message notifications (same color as tells) */
3297 looking_at(buf, &i, "* has left a message ") ||
3298 looking_at(buf, &i, "* just sent you a message:\n") ||
3299 /* Whispers and kibitzes */
3300 (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
3301 looking_at(buf, &i, "* kibitzes: ") ||
3303 (tkind = 3, looking_at(buf, &i, "*(*: "))) {
3305 if (tkind == 1 && strchr(star_match[0], ':')) {
3306 /* Avoid "tells you:" spoofs in channels */
3309 if (star_match[0][0] == NULLCHAR ||
3310 strchr(star_match[0], ' ') ||
3311 (tkind == 3 && strchr(star_match[1], ' '))) {
3312 /* Reject bogus matches */
3315 if (appData.colorize) {
3316 if (oldi > next_out) {
3317 SendToPlayer(&buf[next_out], oldi - next_out);
3322 Colorize(ColorTell, FALSE);
3323 curColor = ColorTell;
3326 Colorize(ColorKibitz, FALSE);
3327 curColor = ColorKibitz;
3330 p = strrchr(star_match[1], '(');
3337 Colorize(ColorChannel1, FALSE);
3338 curColor = ColorChannel1;
3340 Colorize(ColorChannel, FALSE);
3341 curColor = ColorChannel;
3345 curColor = ColorNormal;
3349 if (started == STARTED_NONE && appData.autoComment &&
3350 (gameMode == IcsObserving ||
3351 gameMode == IcsPlayingWhite ||
3352 gameMode == IcsPlayingBlack)) {
3353 parse_pos = i - oldi;
3354 memcpy(parse, &buf[oldi], parse_pos);
3355 parse[parse_pos] = NULLCHAR;
3356 started = STARTED_COMMENT;
3357 savingComment = TRUE;
3359 started = STARTED_CHATTER;
3360 savingComment = FALSE;
3367 if (looking_at(buf, &i, "* s-shouts: ") ||
3368 looking_at(buf, &i, "* c-shouts: ")) {
3369 if (appData.colorize) {
3370 if (oldi > next_out) {
3371 SendToPlayer(&buf[next_out], oldi - next_out);
3374 Colorize(ColorSShout, FALSE);
3375 curColor = ColorSShout;
3378 started = STARTED_CHATTER;
3382 if (looking_at(buf, &i, "--->")) {
3387 if (looking_at(buf, &i, "* shouts: ") ||
3388 looking_at(buf, &i, "--> ")) {
3389 if (appData.colorize) {
3390 if (oldi > next_out) {
3391 SendToPlayer(&buf[next_out], oldi - next_out);
3394 Colorize(ColorShout, FALSE);
3395 curColor = ColorShout;
3398 started = STARTED_CHATTER;
3402 if (looking_at( buf, &i, "Challenge:")) {
3403 if (appData.colorize) {
3404 if (oldi > next_out) {
3405 SendToPlayer(&buf[next_out], oldi - next_out);
3408 Colorize(ColorChallenge, FALSE);
3409 curColor = ColorChallenge;
3415 if (looking_at(buf, &i, "* offers you") ||
3416 looking_at(buf, &i, "* offers to be") ||
3417 looking_at(buf, &i, "* would like to") ||
3418 looking_at(buf, &i, "* requests to") ||
3419 looking_at(buf, &i, "Your opponent offers") ||
3420 looking_at(buf, &i, "Your opponent requests")) {
3422 if (appData.colorize) {
3423 if (oldi > next_out) {
3424 SendToPlayer(&buf[next_out], oldi - next_out);
3427 Colorize(ColorRequest, FALSE);
3428 curColor = ColorRequest;
3433 if (looking_at(buf, &i, "* (*) seeking")) {
3434 if (appData.colorize) {
3435 if (oldi > next_out) {
3436 SendToPlayer(&buf[next_out], oldi - next_out);
3439 Colorize(ColorSeek, FALSE);
3440 curColor = ColorSeek;
3445 if(i < backup) { i = backup; continue; } // [HGM] for if ZippyControl matches, but the colorie code doesn't
3447 if (looking_at(buf, &i, "\\ ")) {
3448 if (prevColor != ColorNormal) {
3449 if (oldi > next_out) {
3450 SendToPlayer(&buf[next_out], oldi - next_out);
3453 Colorize(prevColor, TRUE);
3454 curColor = prevColor;
3456 if (savingComment) {
3457 parse_pos = i - oldi;
3458 memcpy(parse, &buf[oldi], parse_pos);
3459 parse[parse_pos] = NULLCHAR;
3460 started = STARTED_COMMENT;
3461 if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3462 chattingPartner = savingComment - 3; // kludge to remember the box
3464 started = STARTED_CHATTER;
3469 if (looking_at(buf, &i, "Black Strength :") ||
3470 looking_at(buf, &i, "<<< style 10 board >>>") ||
3471 looking_at(buf, &i, "<10>") ||
3472 looking_at(buf, &i, "#@#")) {
3473 /* Wrong board style */
3475 SendToICS(ics_prefix);
3476 SendToICS("set style 12\n");
3477 SendToICS(ics_prefix);
3478 SendToICS("refresh\n");
3482 if (looking_at(buf, &i, "login:")) {
3483 if (!have_sent_ICS_logon) {
3485 have_sent_ICS_logon = 1;
3486 else // no init script was found
3487 have_sent_ICS_logon = (appData.autoCreateLogon ? 2 : 1); // flag that we should capture username + password
3488 } else { // we have sent (or created) the InitScript, but apparently the ICS rejected it
3489 have_sent_ICS_logon = (appData.autoCreateLogon ? 2 : 1); // request creation of a new script
3494 if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3495 (looking_at(buf, &i, "\n<12> ") ||
3496 looking_at(buf, &i, "<12> "))) {
3498 if (oldi > next_out) {
3499 SendToPlayer(&buf[next_out], oldi - next_out);
3502 started = STARTED_BOARD;
3507 if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3508 looking_at(buf, &i, "<b1> ")) {
3509 if (oldi > next_out) {
3510 SendToPlayer(&buf[next_out], oldi - next_out);
3513 started = STARTED_HOLDINGS;
3518 if (looking_at(buf, &i, "* *vs. * *--- *")) {
3520 /* Header for a move list -- first line */
3522 switch (ics_getting_history) {
3526 case BeginningOfGame:
3527 /* User typed "moves" or "oldmoves" while we
3528 were idle. Pretend we asked for these
3529 moves and soak them up so user can step
3530 through them and/or save them.
3533 gameMode = IcsObserving;
3536 ics_getting_history = H_GOT_UNREQ_HEADER;
3538 case EditGame: /*?*/
3539 case EditPosition: /*?*/
3540 /* Should above feature work in these modes too? */
3541 /* For now it doesn't */
3542 ics_getting_history = H_GOT_UNWANTED_HEADER;
3545 ics_getting_history = H_GOT_UNWANTED_HEADER;
3550 /* Is this the right one? */
3551 if (gameInfo.white && gameInfo.black &&
3552 strcmp(gameInfo.white, star_match[0]) == 0 &&
3553 strcmp(gameInfo.black, star_match[2]) == 0) {
3555 ics_getting_history = H_GOT_REQ_HEADER;
3558 case H_GOT_REQ_HEADER:
3559 case H_GOT_UNREQ_HEADER:
3560 case H_GOT_UNWANTED_HEADER:
3561 case H_GETTING_MOVES:
3562 /* Should not happen */
3563 DisplayError(_("Error gathering move list: two headers"), 0);
3564 ics_getting_history = H_FALSE;
3568 /* Save player ratings into gameInfo if needed */
3569 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3570 ics_getting_history == H_GOT_UNREQ_HEADER) &&
3571 (gameInfo.whiteRating == -1 ||
3572 gameInfo.blackRating == -1)) {
3574 gameInfo.whiteRating = string_to_rating(star_match[1]);
3575 gameInfo.blackRating = string_to_rating(star_match[3]);
3576 if (appData.debugMode)
3577 fprintf(debugFP, "Ratings from header: W %d, B %d\n",
3578 gameInfo.whiteRating, gameInfo.blackRating);
3583 if (looking_at(buf, &i,
3584 "* * match, initial time: * minute*, increment: * second")) {
3585 /* Header for a move list -- second line */
3586 /* Initial board will follow if this is a wild game */
3587 if (gameInfo.event != NULL) free(gameInfo.event);
3588 snprintf(str, MSG_SIZ, "ICS %s %s match", star_match[0], star_match[1]);
3589 gameInfo.event = StrSave(str);
3590 /* [HGM] we switched variant. Translate boards if needed. */
3591 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3595 if (looking_at(buf, &i, "Move ")) {
3596 /* Beginning of a move list */
3597 switch (ics_getting_history) {
3599 /* Normally should not happen */
3600 /* Maybe user hit reset while we were parsing */
3603 /* Happens if we are ignoring a move list that is not
3604 * the one we just requested. Common if the user
3605 * tries to observe two games without turning off
3608 case H_GETTING_MOVES:
3609 /* Should not happen */
3610 DisplayError(_("Error gathering move list: nested"), 0);
3611 ics_getting_history = H_FALSE;
3613 case H_GOT_REQ_HEADER:
3614 ics_getting_history = H_GETTING_MOVES;
3615 started = STARTED_MOVES;
3617 if (oldi > next_out) {
3618 SendToPlayer(&buf[next_out], oldi - next_out);
3621 case H_GOT_UNREQ_HEADER:
3622 ics_getting_history = H_GETTING_MOVES;
3623 started = STARTED_MOVES_NOHIDE;
3626 case H_GOT_UNWANTED_HEADER:
3627 ics_getting_history = H_FALSE;
3633 if (looking_at(buf, &i, "% ") ||
3634 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3635 && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3636 if(soughtPending && nrOfSeekAds) { // [HGM] seekgraph: on ICC sought-list has no termination line
3637 soughtPending = FALSE;
3641 if(suppressKibitz) next_out = i;
3642 savingComment = FALSE;
3646 case STARTED_MOVES_NOHIDE:
3647 memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3648 parse[parse_pos + i - oldi] = NULLCHAR;
3649 ParseGameHistory(parse);
3651 if (appData.zippyPlay && first.initDone) {
3652 FeedMovesToProgram(&first, forwardMostMove);
3653 if (gameMode == IcsPlayingWhite) {
3654 if (WhiteOnMove(forwardMostMove)) {
3655 if (first.sendTime) {
3656 if (first.useColors) {
3657 SendToProgram("black\n", &first);
3659 SendTimeRemaining(&first, TRUE);
3661 if (first.useColors) {
3662 SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3664 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3665 first.maybeThinking = TRUE;
3667 if (first.usePlayother) {
3668 if (first.sendTime) {
3669 SendTimeRemaining(&first, TRUE);
3671 SendToProgram("playother\n", &first);
3677 } else if (gameMode == IcsPlayingBlack) {
3678 if (!WhiteOnMove(forwardMostMove)) {
3679 if (first.sendTime) {
3680 if (first.useColors) {
3681 SendToProgram("white\n", &first);
3683 SendTimeRemaining(&first, FALSE);
3685 if (first.useColors) {
3686 SendToProgram("black\n", &first);
3688 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3689 first.maybeThinking = TRUE;
3691 if (first.usePlayother) {
3692 if (first.sendTime) {
3693 SendTimeRemaining(&first, FALSE);
3695 SendToProgram("playother\n", &first);
3704 if (gameMode == IcsObserving && ics_gamenum == -1) {
3705 /* Moves came from oldmoves or moves command
3706 while we weren't doing anything else.
3708 currentMove = forwardMostMove;
3709 ClearHighlights();/*!!could figure this out*/
3710 flipView = appData.flipView;
3711 DrawPosition(TRUE, boards[currentMove]);
3712 DisplayBothClocks();
3713 snprintf(str, MSG_SIZ, "%s %s %s",
3714 gameInfo.white, _("vs."), gameInfo.black);
3718 /* Moves were history of an active game */
3719 if (gameInfo.resultDetails != NULL) {
3720 free(gameInfo.resultDetails);
3721 gameInfo.resultDetails = NULL;
3724 HistorySet(parseList, backwardMostMove,
3725 forwardMostMove, currentMove-1);
3726 DisplayMove(currentMove - 1);
3727 if (started == STARTED_MOVES) next_out = i;
3728 started = STARTED_NONE;
3729 ics_getting_history = H_FALSE;
3732 case STARTED_OBSERVE:
3733 started = STARTED_NONE;
3734 SendToICS(ics_prefix);
3735 SendToICS("refresh\n");
3741 if(bookHit) { // [HGM] book: simulate book reply
3742 static char bookMove[MSG_SIZ]; // a bit generous?
3744 programStats.nodes = programStats.depth = programStats.time =
3745 programStats.score = programStats.got_only_move = 0;
3746 sprintf(programStats.movelist, "%s (xbook)", bookHit);
3748 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
3749 strcat(bookMove, bookHit);
3750 HandleMachineMove(bookMove, &first);
3755 if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3756 started == STARTED_HOLDINGS ||
3757 started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3758 /* Accumulate characters in move list or board */
3759 parse[parse_pos++] = buf[i];
3762 /* Start of game messages. Mostly we detect start of game
3763 when the first board image arrives. On some versions
3764 of the ICS, though, we need to do a "refresh" after starting
3765 to observe in order to get the current board right away. */
3766 if (looking_at(buf, &i, "Adding game * to observation list")) {
3767 started = STARTED_OBSERVE;
3771 /* Handle auto-observe */
3772 if (appData.autoObserve &&
3773 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3774 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3776 /* Choose the player that was highlighted, if any. */
3777 if (star_match[0][0] == '\033' ||
3778 star_match[1][0] != '\033') {
3779 player = star_match[0];
3781 player = star_match[2];
3783 snprintf(str, MSG_SIZ, "%sobserve %s\n",
3784 ics_prefix, StripHighlightAndTitle(player));
3787 /* Save ratings from notify string */
3788 safeStrCpy(player1Name, star_match[0], sizeof(player1Name)/sizeof(player1Name[0]));
3789 player1Rating = string_to_rating(star_match[1]);
3790 safeStrCpy(player2Name, star_match[2], sizeof(player2Name)/sizeof(player2Name[0]));
3791 player2Rating = string_to_rating(star_match[3]);
3793 if (appData.debugMode)
3795 "Ratings from 'Game notification:' %s %d, %s %d\n",
3796 player1Name, player1Rating,
3797 player2Name, player2Rating);
3802 /* Deal with automatic examine mode after a game,
3803 and with IcsObserving -> IcsExamining transition */
3804 if (looking_at(buf, &i, "Entering examine mode for game *") ||
3805 looking_at(buf, &i, "has made you an examiner of game *")) {
3807 int gamenum = atoi(star_match[0]);
3808 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3809 gamenum == ics_gamenum) {
3810 /* We were already playing or observing this game;
3811 no need to refetch history */
3812 gameMode = IcsExamining;
3814 pauseExamForwardMostMove = forwardMostMove;
3815 } else if (currentMove < forwardMostMove) {
3816 ForwardInner(forwardMostMove);
3819 /* I don't think this case really can happen */
3820 SendToICS(ics_prefix);
3821 SendToICS("refresh\n");
3826 /* Error messages */
3827 // if (ics_user_moved) {
3828 if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3829 if (looking_at(buf, &i, "Illegal move") ||
3830 looking_at(buf, &i, "Not a legal move") ||
3831 looking_at(buf, &i, "Your king is in check") ||
3832 looking_at(buf, &i, "It isn't your turn") ||
3833 looking_at(buf, &i, "It is not your move")) {
3835 if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3836 currentMove = forwardMostMove-1;
3837 DisplayMove(currentMove - 1); /* before DMError */
3838 DrawPosition(FALSE, boards[currentMove]);
3839 SwitchClocks(forwardMostMove-1); // [HGM] race
3840 DisplayBothClocks();
3842 DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3848 if (looking_at(buf, &i, "still have time") ||
3849 looking_at(buf, &i, "not out of time") ||
3850 looking_at(buf, &i, "either player is out of time") ||
3851 looking_at(buf, &i, "has timeseal; checking")) {
3852 /* We must have called his flag a little too soon */
3853 whiteFlag = blackFlag = FALSE;
3857 if (looking_at(buf, &i, "added * seconds to") ||
3858 looking_at(buf, &i, "seconds were added to")) {
3859 /* Update the clocks */
3860 SendToICS(ics_prefix);
3861 SendToICS("refresh\n");
3865 if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3866 ics_clock_paused = TRUE;
3871 if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3872 ics_clock_paused = FALSE;
3877 /* Grab player ratings from the Creating: message.
3878 Note we have to check for the special case when
3879 the ICS inserts things like [white] or [black]. */
3880 if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3881 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3883 0 player 1 name (not necessarily white)
3885 2 empty, white, or black (IGNORED)
3886 3 player 2 name (not necessarily black)
3889 The names/ratings are sorted out when the game
3890 actually starts (below).
3892 safeStrCpy(player1Name, StripHighlightAndTitle(star_match[0]), sizeof(player1Name)/sizeof(player1Name[0]));
3893 player1Rating = string_to_rating(star_match[1]);
3894 safeStrCpy(player2Name, StripHighlightAndTitle(star_match[3]), sizeof(player2Name)/sizeof(player2Name[0]));
3895 player2Rating = string_to_rating(star_match[4]);
3897 if (appData.debugMode)
3899 "Ratings from 'Creating:' %s %d, %s %d\n",
3900 player1Name, player1Rating,
3901 player2Name, player2Rating);
3906 /* Improved generic start/end-of-game messages */
3907 if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3908 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3909 /* If tkind == 0: */
3910 /* star_match[0] is the game number */
3911 /* [1] is the white player's name */
3912 /* [2] is the black player's name */
3913 /* For end-of-game: */
3914 /* [3] is the reason for the game end */
3915 /* [4] is a PGN end game-token, preceded by " " */
3916 /* For start-of-game: */
3917 /* [3] begins with "Creating" or "Continuing" */
3918 /* [4] is " *" or empty (don't care). */
3919 int gamenum = atoi(star_match[0]);
3920 char *whitename, *blackname, *why, *endtoken;
3921 ChessMove endtype = EndOfFile;
3924 whitename = star_match[1];
3925 blackname = star_match[2];
3926 why = star_match[3];
3927 endtoken = star_match[4];
3929 whitename = star_match[1];
3930 blackname = star_match[3];
3931 why = star_match[5];
3932 endtoken = star_match[6];
3935 /* Game start messages */
3936 if (strncmp(why, "Creating ", 9) == 0 ||
3937 strncmp(why, "Continuing ", 11) == 0) {
3938 gs_gamenum = gamenum;
3939 safeStrCpy(gs_kind, strchr(why, ' ') + 1,sizeof(gs_kind)/sizeof(gs_kind[0]));
3940 if(ics_gamenum == -1) // [HGM] only if we are not already involved in a game (because gin=1 sends us such messages)
3941 VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
3943 if (appData.zippyPlay) {
3944 ZippyGameStart(whitename, blackname);
3947 partnerBoardValid = FALSE; // [HGM] bughouse
3951 /* Game end messages */
3952 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3953 ics_gamenum != gamenum) {
3956 while (endtoken[0] == ' ') endtoken++;
3957 switch (endtoken[0]) {
3960 endtype = GameUnfinished;
3963 endtype = BlackWins;
3966 if (endtoken[1] == '/')
3967 endtype = GameIsDrawn;
3969 endtype = WhiteWins;
3972 GameEnds(endtype, why, GE_ICS);
3974 if (appData.zippyPlay && first.initDone) {
3975 ZippyGameEnd(endtype, why);
3976 if (first.pr == NoProc) {
3977 /* Start the next process early so that we'll
3978 be ready for the next challenge */
3979 StartChessProgram(&first);
3981 /* Send "new" early, in case this command takes
3982 a long time to finish, so that we'll be ready
3983 for the next challenge. */
3984 gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3988 if(appData.bgObserve && partnerBoardValid) DrawPosition(TRUE, partnerBoard);
3992 if (looking_at(buf, &i, "Removing game * from observation") ||
3993 looking_at(buf, &i, "no longer observing game *") ||
3994 looking_at(buf, &i, "Game * (*) has no examiners")) {
3995 if (gameMode == IcsObserving &&
3996 atoi(star_match[0]) == ics_gamenum)
3998 /* icsEngineAnalyze */
3999 if (appData.icsEngineAnalyze) {
4006 ics_user_moved = FALSE;
4011 if (looking_at(buf, &i, "no longer examining game *")) {
4012 if (gameMode == IcsExamining &&
4013 atoi(star_match[0]) == ics_gamenum)
4017 ics_user_moved = FALSE;
4022 /* Advance leftover_start past any newlines we find,
4023 so only partial lines can get reparsed */
4024 if (looking_at(buf, &i, "\n")) {
4025 prevColor = curColor;
4026 if (curColor != ColorNormal) {
4027 if (oldi > next_out) {
4028 SendToPlayer(&buf[next_out], oldi - next_out);
4031 Colorize(ColorNormal, FALSE);
4032 curColor = ColorNormal;
4034 if (started == STARTED_BOARD) {
4035 started = STARTED_NONE;
4036 parse[parse_pos] = NULLCHAR;
4037 ParseBoard12(parse);
4040 /* Send premove here */
4041 if (appData.premove) {
4043 if (currentMove == 0 &&
4044 gameMode == IcsPlayingWhite &&
4045 appData.premoveWhite) {
4046 snprintf(str, MSG_SIZ, "%s\n", appData.premoveWhiteText);
4047 if (appData.debugMode)
4048 fprintf(debugFP, "Sending premove:\n");
4050 } else if (currentMove == 1 &&
4051 gameMode == IcsPlayingBlack &&
4052 appData.premoveBlack) {
4053 snprintf(str, MSG_SIZ, "%s\n", appData.premoveBlackText);
4054 if (appData.debugMode)
4055 fprintf(debugFP, "Sending premove:\n");
4057 } else if (gotPremove) {
4059 ClearPremoveHighlights();
4060 if (appData.debugMode)
4061 fprintf(debugFP, "Sending premove:\n");
4062 UserMoveEvent(premoveFromX, premoveFromY,
4063 premoveToX, premoveToY,
4068 /* Usually suppress following prompt */
4069 if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
4070 while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
4071 if (looking_at(buf, &i, "*% ")) {
4072 savingComment = FALSE;
4077 } else if (started == STARTED_HOLDINGS) {
4079 char new_piece[MSG_SIZ];
4080 started = STARTED_NONE;
4081 parse[parse_pos] = NULLCHAR;
4082 if (appData.debugMode)
4083 fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
4084 parse, currentMove);
4085 if (sscanf(parse, " game %d", &gamenum) == 1) {
4086 if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game
4087 if (gameInfo.variant == VariantNormal) {
4088 /* [HGM] We seem to switch variant during a game!
4089 * Presumably no holdings were displayed, so we have
4090 * to move the position two files to the right to
4091 * create room for them!
4093 VariantClass newVariant;
4094 switch(gameInfo.boardWidth) { // base guess on board width
4095 case 9: newVariant = VariantShogi; break;
4096 case 10: newVariant = VariantGreat; break;
4097 default: newVariant = VariantCrazyhouse; break;
4099 VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4100 /* Get a move list just to see the header, which
4101 will tell us whether this is really bug or zh */
4102 if (ics_getting_history == H_FALSE) {
4103 ics_getting_history = H_REQUESTED;
4104 snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4108 new_piece[0] = NULLCHAR;
4109 sscanf(parse, "game %d white [%s black [%s <- %s",
4110 &gamenum, white_holding, black_holding,
4112 white_holding[strlen(white_holding)-1] = NULLCHAR;
4113 black_holding[strlen(black_holding)-1] = NULLCHAR;
4114 /* [HGM] copy holdings to board holdings area */
4115 CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
4116 CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
4117 boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
4119 if (appData.zippyPlay && first.initDone) {
4120 ZippyHoldings(white_holding, black_holding,
4124 if (tinyLayout || smallLayout) {
4125 char wh[16], bh[16];
4126 PackHolding(wh, white_holding);
4127 PackHolding(bh, black_holding);
4128 snprintf(str, MSG_SIZ, "[%s-%s] %s-%s", wh, bh,
4129 gameInfo.white, gameInfo.black);
4131 snprintf(str, MSG_SIZ, "%s [%s] %s %s [%s]",
4132 gameInfo.white, white_holding, _("vs."),
4133 gameInfo.black, black_holding);
4135 if(!partnerUp) // [HGM] bughouse: when peeking at partner game we already know what he captured...
4136 DrawPosition(FALSE, boards[currentMove]);
4138 } else if(appData.bgObserve) { // [HGM] bughouse: holdings of other game => background
4139 sscanf(parse, "game %d white [%s black [%s <- %s",
4140 &gamenum, white_holding, black_holding,
4142 white_holding[strlen(white_holding)-1] = NULLCHAR;
4143 black_holding[strlen(black_holding)-1] = NULLCHAR;
4144 /* [HGM] copy holdings to partner-board holdings area */
4145 CopyHoldings(partnerBoard, white_holding, WhitePawn);
4146 CopyHoldings(partnerBoard, black_holding, BlackPawn);
4147 if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual: always draw
4148 if(partnerUp) DrawPosition(FALSE, partnerBoard);
4149 if(twoBoards) { partnerUp = 0; flipView = !flipView; }
4152 /* Suppress following prompt */
4153 if (looking_at(buf, &i, "*% ")) {
4154 if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
4155 savingComment = FALSE;
4163 i++; /* skip unparsed character and loop back */
4166 if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
4167 // started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
4168 // SendToPlayer(&buf[next_out], i - next_out);
4169 started != STARTED_HOLDINGS && leftover_start > next_out) {
4170 SendToPlayer(&buf[next_out], leftover_start - next_out);
4174 leftover_len = buf_len - leftover_start;
4175 /* if buffer ends with something we couldn't parse,
4176 reparse it after appending the next read */
4178 } else if (count == 0) {
4179 RemoveInputSource(isr);
4180 DisplayFatalError(_("Connection closed by ICS"), 0, 0);
4182 DisplayFatalError(_("Error reading from ICS"), error, 1);
4187 /* Board style 12 looks like this:
4189 <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
4191 * The "<12> " is stripped before it gets to this routine. The two
4192 * trailing 0's (flip state and clock ticking) are later addition, and
4193 * some chess servers may not have them, or may have only the first.
4194 * Additional trailing fields may be added in the future.
4197 #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"
4199 #define RELATION_OBSERVING_PLAYED 0
4200 #define RELATION_OBSERVING_STATIC -2 /* examined, oldmoves, or smoves */
4201 #define RELATION_PLAYING_MYMOVE 1
4202 #define RELATION_PLAYING_NOTMYMOVE -1
4203 #define RELATION_EXAMINING 2
4204 #define RELATION_ISOLATED_BOARD -3
4205 #define RELATION_STARTING_POSITION -4 /* FICS only */
4208 ParseBoard12 (char *string)
4212 char *bookHit = NULL; // [HGM] book
4214 GameMode newGameMode;
4215 int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0;
4216 int j, k, n, moveNum, white_stren, black_stren, white_time, black_time;
4217 int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
4218 char to_play, board_chars[200];
4219 char move_str[MSG_SIZ], str[MSG_SIZ], elapsed_time[MSG_SIZ];
4220 char black[32], white[32];
4222 int prevMove = currentMove;
4225 int fromX, fromY, toX, toY;
4227 int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
4228 Boolean weird = FALSE, reqFlag = FALSE;
4230 fromX = fromY = toX = toY = -1;
4234 if (appData.debugMode)
4235 fprintf(debugFP, "Parsing board: %s\n", string);
4237 move_str[0] = NULLCHAR;
4238 elapsed_time[0] = NULLCHAR;
4239 { /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
4241 while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
4242 if(string[i] == ' ') { ranks++; files = 0; }
4244 if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
4247 for(j = 0; j <i; j++) board_chars[j] = string[j];
4248 board_chars[i] = '\0';
4251 n = sscanf(string, PATTERN, &to_play, &double_push,
4252 &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
4253 &gamenum, white, black, &relation, &basetime, &increment,
4254 &white_stren, &black_stren, &white_time, &black_time,
4255 &moveNum, str, elapsed_time, move_str, &ics_flip,
4259 snprintf(str, MSG_SIZ, _("Failed to parse board string:\n\"%s\""), string);
4260 DisplayError(str, 0);
4264 /* Convert the move number to internal form */
4265 moveNum = (moveNum - 1) * 2;
4266 if (to_play == 'B') moveNum++;
4267 if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
4268 DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
4274 case RELATION_OBSERVING_PLAYED:
4275 case RELATION_OBSERVING_STATIC:
4276 if (gamenum == -1) {
4277 /* Old ICC buglet */
4278 relation = RELATION_OBSERVING_STATIC;
4280 newGameMode = IcsObserving;
4282 case RELATION_PLAYING_MYMOVE:
4283 case RELATION_PLAYING_NOTMYMOVE:
4285 ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
4286 IcsPlayingWhite : IcsPlayingBlack;
4287 soughtPending =FALSE; // [HGM] seekgraph: solve race condition
4289 case RELATION_EXAMINING:
4290 newGameMode = IcsExamining;
4292 case RELATION_ISOLATED_BOARD:
4294 /* Just display this board. If user was doing something else,
4295 we will forget about it until the next board comes. */
4296 newGameMode = IcsIdle;
4298 case RELATION_STARTING_POSITION:
4299 newGameMode = gameMode;
4303 if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
4304 gameMode == IcsObserving && appData.dualBoard) // also allow use of second board for observing two games
4305 && newGameMode == IcsObserving && gamenum != ics_gamenum && appData.bgObserve) {
4306 // [HGM] bughouse: don't act on alien boards while we play. Just parse the board and save it */
4307 int fac = strchr(elapsed_time, '.') ? 1 : 1000;
4308 static int lastBgGame = -1;
4310 for (k = 0; k < ranks; k++) {
4311 for (j = 0; j < files; j++)
4312 board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4313 if(gameInfo.holdingsWidth > 1) {
4314 board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4315 board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4318 CopyBoard(partnerBoard, board);
4319 if(toSqr = strchr(str, '/')) { // extract highlights from long move
4320 partnerBoard[EP_STATUS-3] = toSqr[1] - AAA; // kludge: hide highlighting info in board
4321 partnerBoard[EP_STATUS-4] = toSqr[2] - ONE;
4322 } else partnerBoard[EP_STATUS-4] = partnerBoard[EP_STATUS-3] = -1;
4323 if(toSqr = strchr(str, '-')) {
4324 partnerBoard[EP_STATUS-1] = toSqr[1] - AAA;
4325 partnerBoard[EP_STATUS-2] = toSqr[2] - ONE;
4326 } else partnerBoard[EP_STATUS-1] = partnerBoard[EP_STATUS-2] = -1;
4327 if(appData.dualBoard && !twoBoards) { twoBoards = 1; InitDrawingSizes(-2,0); }
4328 if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual
4329 if(partnerUp) DrawPosition(FALSE, partnerBoard);
4331 DisplayWhiteClock(white_time*fac, to_play == 'W');
4332 DisplayBlackClock(black_time*fac, to_play != 'W');
4333 activePartner = to_play;
4334 if(gamenum != lastBgGame) {
4336 snprintf(buf, MSG_SIZ, "%s %s %s", white, _("vs."), black);
4339 lastBgGame = gamenum;
4340 activePartnerTime = to_play == 'W' ? white_time*fac : black_time*fac;
4341 partnerUp = 0; flipView = !flipView; } // [HGM] dual
4342 snprintf(partnerStatus, MSG_SIZ,"W: %d:%02d B: %d:%02d (%d-%d) %c", white_time*fac/60000, (white_time*fac%60000)/1000,
4343 (black_time*fac/60000), (black_time*fac%60000)/1000, white_stren, black_stren, to_play);
4344 if(!twoBoards) DisplayMessage(partnerStatus, "");
4345 partnerBoardValid = TRUE;
4349 if(appData.dualBoard && appData.bgObserve) {
4350 if((newGameMode == IcsPlayingWhite || newGameMode == IcsPlayingBlack) && moveNum == 1)
4351 SendToICS(ics_prefix), SendToICS("pobserve\n");
4352 else if(newGameMode == IcsObserving && (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
4354 snprintf(buf, MSG_SIZ, "%spobserve %s\n", ics_prefix, white);
4359 /* Modify behavior for initial board display on move listing
4362 switch (ics_getting_history) {
4366 case H_GOT_REQ_HEADER:
4367 case H_GOT_UNREQ_HEADER:
4368 /* This is the initial position of the current game */
4369 gamenum = ics_gamenum;
4370 moveNum = 0; /* old ICS bug workaround */
4371 if (to_play == 'B') {
4372 startedFromSetupPosition = TRUE;
4373 blackPlaysFirst = TRUE;
4375 if (forwardMostMove == 0) forwardMostMove = 1;
4376 if (backwardMostMove == 0) backwardMostMove = 1;
4377 if (currentMove == 0) currentMove = 1;
4379 newGameMode = gameMode;
4380 relation = RELATION_STARTING_POSITION; /* ICC needs this */
4382 case H_GOT_UNWANTED_HEADER:
4383 /* This is an initial board that we don't want */
4385 case H_GETTING_MOVES:
4386 /* Should not happen */
4387 DisplayError(_("Error gathering move list: extra board"), 0);
4388 ics_getting_history = H_FALSE;
4392 if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
4393 move_str[1] == '@' && !gameInfo.holdingsWidth ||
4394 weird && (int)gameInfo.variant < (int)VariantShogi) {
4395 /* [HGM] We seem to have switched variant unexpectedly
4396 * Try to guess new variant from board size
4398 VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
4399 if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
4400 if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
4401 if(ranks == 8 && files == 12) newVariant = VariantCourier; else
4402 if(ranks == 9 && files == 9) newVariant = VariantShogi; else
4403 if(ranks == 10 && files == 10) newVariant = VariantGrand; else
4404 if(!weird) newVariant = move_str[1] == '@' ? VariantCrazyhouse : VariantNormal;
4405 VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4406 /* Get a move list just to see the header, which
4407 will tell us whether this is really bug or zh */
4408 if (ics_getting_history == H_FALSE) {
4409 ics_getting_history = H_REQUESTED; reqFlag = TRUE;
4410 snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4415 /* Take action if this is the first board of a new game, or of a
4416 different game than is currently being displayed. */
4417 if (gamenum != ics_gamenum || newGameMode != gameMode ||
4418 relation == RELATION_ISOLATED_BOARD) {
4420 /* Forget the old game and get the history (if any) of the new one */
4421 if (gameMode != BeginningOfGame) {
4425 if (appData.autoRaiseBoard) BoardToTop();
4427 if (gamenum == -1) {
4428 newGameMode = IcsIdle;
4429 } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
4430 appData.getMoveList && !reqFlag) {
4431 /* Need to get game history */
4432 ics_getting_history = H_REQUESTED;
4433 snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4437 /* Initially flip the board to have black on the bottom if playing
4438 black or if the ICS flip flag is set, but let the user change
4439 it with the Flip View button. */
4440 flipView = appData.autoFlipView ?
4441 (newGameMode == IcsPlayingBlack) || ics_flip :
4444 /* Done with values from previous mode; copy in new ones */
4445 gameMode = newGameMode;
4447 ics_gamenum = gamenum;
4448 if (gamenum == gs_gamenum) {
4449 int klen = strlen(gs_kind);
4450 if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
4451 snprintf(str, MSG_SIZ, "ICS %s", gs_kind);
4452 gameInfo.event = StrSave(str);
4454 gameInfo.event = StrSave("ICS game");
4456 gameInfo.site = StrSave(appData.icsHost);
4457 gameInfo.date = PGNDate();
4458 gameInfo.round = StrSave("-");
4459 gameInfo.white = StrSave(white);
4460 gameInfo.black = StrSave(black);
4461 timeControl = basetime * 60 * 1000;
4463 timeIncrement = increment * 1000;
4464 movesPerSession = 0;
4465 gameInfo.timeControl = TimeControlTagValue();
4466 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
4467 if (appData.debugMode) {
4468 fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
4469 fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
4470 setbuf(debugFP, NULL);
4473 gameInfo.outOfBook = NULL;
4475 /* Do we have the ratings? */
4476 if (strcmp(player1Name, white) == 0 &&
4477 strcmp(player2Name, black) == 0) {
4478 if (appData.debugMode)
4479 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4480 player1Rating, player2Rating);
4481 gameInfo.whiteRating = player1Rating;
4482 gameInfo.blackRating = player2Rating;
4483 } else if (strcmp(player2Name, white) == 0 &&
4484 strcmp(player1Name, black) == 0) {
4485 if (appData.debugMode)
4486 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4487 player2Rating, player1Rating);
4488 gameInfo.whiteRating = player2Rating;
4489 gameInfo.blackRating = player1Rating;
4491 player1Name[0] = player2Name[0] = NULLCHAR;
4493 /* Silence shouts if requested */
4494 if (appData.quietPlay &&
4495 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
4496 SendToICS(ics_prefix);
4497 SendToICS("set shout 0\n");
4501 /* Deal with midgame name changes */
4503 if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
4504 if (gameInfo.white) free(gameInfo.white);
4505 gameInfo.white = StrSave(white);
4507 if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
4508 if (gameInfo.black) free(gameInfo.black);
4509 gameInfo.black = StrSave(black);
4513 /* Throw away game result if anything actually changes in examine mode */
4514 if (gameMode == IcsExamining && !newGame) {
4515 gameInfo.result = GameUnfinished;
4516 if (gameInfo.resultDetails != NULL) {
4517 free(gameInfo.resultDetails);
4518 gameInfo.resultDetails = NULL;
4522 /* In pausing && IcsExamining mode, we ignore boards coming
4523 in if they are in a different variation than we are. */
4524 if (pauseExamInvalid) return;
4525 if (pausing && gameMode == IcsExamining) {
4526 if (moveNum <= pauseExamForwardMostMove) {
4527 pauseExamInvalid = TRUE;
4528 forwardMostMove = pauseExamForwardMostMove;
4533 if (appData.debugMode) {
4534 fprintf(debugFP, "load %dx%d board\n", files, ranks);
4536 /* Parse the board */
4537 for (k = 0; k < ranks; k++) {
4538 for (j = 0; j < files; j++)
4539 board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4540 if(gameInfo.holdingsWidth > 1) {
4541 board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4542 board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4545 if(moveNum==0 && gameInfo.variant == VariantSChess) {
4546 board[5][BOARD_RGHT+1] = WhiteAngel;
4547 board[6][BOARD_RGHT+1] = WhiteMarshall;
4548 board[1][0] = BlackMarshall;
4549 board[2][0] = BlackAngel;
4550 board[1][1] = board[2][1] = board[5][BOARD_RGHT] = board[6][BOARD_RGHT] = 1;
4552 CopyBoard(boards[moveNum], board);
4553 boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
4555 startedFromSetupPosition =
4556 !CompareBoards(board, initialPosition);
4557 if(startedFromSetupPosition)
4558 initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
4561 /* [HGM] Set castling rights. Take the outermost Rooks,
4562 to make it also work for FRC opening positions. Note that board12
4563 is really defective for later FRC positions, as it has no way to
4564 indicate which Rook can castle if they are on the same side of King.
4565 For the initial position we grant rights to the outermost Rooks,
4566 and remember thos rights, and we then copy them on positions
4567 later in an FRC game. This means WB might not recognize castlings with
4568 Rooks that have moved back to their original position as illegal,
4569 but in ICS mode that is not its job anyway.
4571 if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
4572 { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
4574 for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4575 if(board[0][i] == WhiteRook) j = i;
4576 initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4577 for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4578 if(board[0][i] == WhiteRook) j = i;
4579 initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4580 for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4581 if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4582 initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4583 for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4584 if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4585 initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4587 boards[moveNum][CASTLING][2] = boards[moveNum][CASTLING][5] = NoRights;
4588 if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
4589 for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4590 if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
4591 for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4592 if(board[BOARD_HEIGHT-1][k] == bKing)
4593 initialRights[5] = boards[moveNum][CASTLING][5] = k;
4594 if(gameInfo.variant == VariantTwoKings) {
4595 // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
4596 if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
4597 if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
4600 r = boards[moveNum][CASTLING][0] = initialRights[0];
4601 if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4602 r = boards[moveNum][CASTLING][1] = initialRights[1];
4603 if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4604 r = boards[moveNum][CASTLING][3] = initialRights[3];
4605 if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4606 r = boards[moveNum][CASTLING][4] = initialRights[4];
4607 if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4608 /* wildcastle kludge: always assume King has rights */
4609 r = boards[moveNum][CASTLING][2] = initialRights[2];
4610 r = boards[moveNum][CASTLING][5] = initialRights[5];
4612 /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4613 boards[moveNum][EP_STATUS] = EP_NONE;
4614 if(str[0] == 'P') boards[moveNum][EP_STATUS] = EP_PAWN_MOVE;
4615 if(strchr(move_str, 'x')) boards[moveNum][EP_STATUS] = EP_CAPTURE;
4616 if(double_push != -1) boards[moveNum][EP_STATUS] = double_push + BOARD_LEFT;
4619 if (ics_getting_history == H_GOT_REQ_HEADER ||
4620 ics_getting_history == H_GOT_UNREQ_HEADER) {
4621 /* This was an initial position from a move list, not
4622 the current position */
4626 /* Update currentMove and known move number limits */
4627 newMove = newGame || moveNum > forwardMostMove;
4630 forwardMostMove = backwardMostMove = currentMove = moveNum;
4631 if (gameMode == IcsExamining && moveNum == 0) {
4632 /* Workaround for ICS limitation: we are not told the wild
4633 type when starting to examine a game. But if we ask for
4634 the move list, the move list header will tell us */
4635 ics_getting_history = H_REQUESTED;
4636 snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4639 } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4640 || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4642 /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4643 /* [HGM] applied this also to an engine that is silently watching */
4644 if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4645 (gameMode == IcsObserving || gameMode == IcsExamining) &&
4646 gameInfo.variant == currentlyInitializedVariant) {
4647 takeback = forwardMostMove - moveNum;
4648 for (i = 0; i < takeback; i++) {
4649 if (appData.debugMode) fprintf(debugFP, "take back move\n");
4650 SendToProgram("undo\n", &first);
4655 forwardMostMove = moveNum;
4656 if (!pausing || currentMove > forwardMostMove)
4657 currentMove = forwardMostMove;
4659 /* New part of history that is not contiguous with old part */
4660 if (pausing && gameMode == IcsExamining) {
4661 pauseExamInvalid = TRUE;
4662 forwardMostMove = pauseExamForwardMostMove;
4665 if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4667 if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4668 // [HGM] when we will receive the move list we now request, it will be
4669 // fed to the engine from the first move on. So if the engine is not
4670 // in the initial position now, bring it there.
4671 InitChessProgram(&first, 0);
4674 ics_getting_history = H_REQUESTED;
4675 snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4678 forwardMostMove = backwardMostMove = currentMove = moveNum;
4681 /* Update the clocks */
4682 if (strchr(elapsed_time, '.')) {
4684 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4685 timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4687 /* Time is in seconds */
4688 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4689 timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4694 if (appData.zippyPlay && newGame &&
4695 gameMode != IcsObserving && gameMode != IcsIdle &&
4696 gameMode != IcsExamining)
4697 ZippyFirstBoard(moveNum, basetime, increment);
4700 /* Put the move on the move list, first converting
4701 to canonical algebraic form. */
4703 if (appData.debugMode) {
4704 int f = forwardMostMove;
4705 fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4706 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4707 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4708 fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4709 fprintf(debugFP, "moveNum = %d\n", moveNum);
4710 fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4711 setbuf(debugFP, NULL);
4713 if (moveNum <= backwardMostMove) {
4714 /* We don't know what the board looked like before
4716 safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4717 strcat(parseList[moveNum - 1], " ");
4718 strcat(parseList[moveNum - 1], elapsed_time);
4719 moveList[moveNum - 1][0] = NULLCHAR;
4720 } else if (strcmp(move_str, "none") == 0) {
4721 // [HGM] long SAN: swapped order; test for 'none' before parsing move
4722 /* Again, we don't know what the board looked like;
4723 this is really the start of the game. */
4724 parseList[moveNum - 1][0] = NULLCHAR;
4725 moveList[moveNum - 1][0] = NULLCHAR;
4726 backwardMostMove = moveNum;
4727 startedFromSetupPosition = TRUE;
4728 fromX = fromY = toX = toY = -1;
4730 // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
4731 // So we parse the long-algebraic move string in stead of the SAN move
4732 int valid; char buf[MSG_SIZ], *prom;
4734 if(gameInfo.variant == VariantShogi && !strchr(move_str, '=') && !strchr(move_str, '@'))
4735 strcat(move_str, "="); // if ICS does not say 'promote' on non-drop, we defer.
4736 // str looks something like "Q/a1-a2"; kill the slash
4738 snprintf(buf, MSG_SIZ,"%c%s", str[0], str+2);
4739 else safeStrCpy(buf, str, sizeof(buf)/sizeof(buf[0])); // might be castling
4740 if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
4741 strcat(buf, prom); // long move lacks promo specification!
4742 if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4743 if(appData.debugMode)
4744 fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4745 safeStrCpy(move_str, buf, MSG_SIZ);
4747 valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4748 &fromX, &fromY, &toX, &toY, &promoChar)
4749 || ParseOneMove(buf, moveNum - 1, &moveType,
4750 &fromX, &fromY, &toX, &toY, &promoChar);
4751 // end of long SAN patch
4753 (void) CoordsToAlgebraic(boards[moveNum - 1],
4754 PosFlags(moveNum - 1),
4755 fromY, fromX, toY, toX, promoChar,
4756 parseList[moveNum-1]);
4757 switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4763 if(gameInfo.variant != VariantShogi)
4764 strcat(parseList[moveNum - 1], "+");
4767 case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4768 strcat(parseList[moveNum - 1], "#");
4771 strcat(parseList[moveNum - 1], " ");
4772 strcat(parseList[moveNum - 1], elapsed_time);
4773 /* currentMoveString is set as a side-effect of ParseOneMove */
4774 if(gameInfo.variant == VariantShogi && currentMoveString[4]) currentMoveString[4] = '^';
4775 safeStrCpy(moveList[moveNum - 1], currentMoveString, sizeof(moveList[moveNum - 1])/sizeof(moveList[moveNum - 1][0]));
4776 strcat(moveList[moveNum - 1], "\n");
4778 if(gameInfo.holdingsWidth && !appData.disguise && gameInfo.variant != VariantSuper && gameInfo.variant != VariantGreat
4779 && gameInfo.variant != VariantGrand&& gameInfo.variant != VariantSChess) // inherit info that ICS does not give from previous board
4780 for(k=0; k<ranks; k++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
4781 ChessSquare old, new = boards[moveNum][k][j];
4782 if(fromY == DROP_RANK && k==toY && j==toX) continue; // dropped pieces always stand for themselves
4783 old = (k==toY && j==toX) ? boards[moveNum-1][fromY][fromX] : boards[moveNum-1][k][j]; // trace back mover
4784 if(old == new) continue;
4785 if(old == PROMOTED new) boards[moveNum][k][j] = old; // prevent promoted pieces to revert to primordial ones
4786 else if(new == WhiteWazir || new == BlackWazir) {
4787 if(old < WhiteCannon || old >= BlackPawn && old < BlackCannon)
4788 boards[moveNum][k][j] = PROMOTED old; // choose correct type of Gold in promotion
4789 else boards[moveNum][k][j] = old; // preserve type of Gold
4790 } else if((old == WhitePawn || old == BlackPawn) && new != EmptySquare) // Pawn promotions (but not e.p.capture!)
4791 boards[moveNum][k][j] = PROMOTED new; // use non-primordial representation of chosen piece
4794 /* Move from ICS was illegal!? Punt. */
4795 if (appData.debugMode) {
4796 fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4797 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4799 safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4800 strcat(parseList[moveNum - 1], " ");
4801 strcat(parseList[moveNum - 1], elapsed_time);
4802 moveList[moveNum - 1][0] = NULLCHAR;
4803 fromX = fromY = toX = toY = -1;
4806 if (appData.debugMode) {
4807 fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4808 setbuf(debugFP, NULL);
4812 /* Send move to chess program (BEFORE animating it). */
4813 if (appData.zippyPlay && !newGame && newMove &&
4814 (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4816 if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4817 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4818 if (moveList[moveNum - 1][0] == NULLCHAR) {
4819 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"),
4821 DisplayError(str, 0);
4823 if (first.sendTime) {
4824 SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4826 bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4827 if (firstMove && !bookHit) {
4829 if (first.useColors) {
4830 SendToProgram(gameMode == IcsPlayingWhite ?
4832 "black\ngo\n", &first);
4834 SendToProgram("go\n", &first);
4836 first.maybeThinking = TRUE;
4839 } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4840 if (moveList[moveNum - 1][0] == NULLCHAR) {
4841 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"), move_str);
4842 DisplayError(str, 0);
4844 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4845 SendMoveToProgram(moveNum - 1, &first);
4852 if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4853 /* If move comes from a remote source, animate it. If it
4854 isn't remote, it will have already been animated. */
4855 if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4856 AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4858 if (!pausing && appData.highlightLastMove) {
4859 SetHighlights(fromX, fromY, toX, toY);
4863 /* Start the clocks */
4864 whiteFlag = blackFlag = FALSE;
4865 appData.clockMode = !(basetime == 0 && increment == 0);
4867 ics_clock_paused = TRUE;
4869 } else if (ticking == 1) {
4870 ics_clock_paused = FALSE;
4872 if (gameMode == IcsIdle ||
4873 relation == RELATION_OBSERVING_STATIC ||
4874 relation == RELATION_EXAMINING ||
4876 DisplayBothClocks();
4880 /* Display opponents and material strengths */
4881 if (gameInfo.variant != VariantBughouse &&
4882 gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4883 if (tinyLayout || smallLayout) {
4884 if(gameInfo.variant == VariantNormal)
4885 snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d}",
4886 gameInfo.white, white_stren, gameInfo.black, black_stren,
4887 basetime, increment);
4889 snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d w%d}",
4890 gameInfo.white, white_stren, gameInfo.black, black_stren,
4891 basetime, increment, (int) gameInfo.variant);
4893 if(gameInfo.variant == VariantNormal)
4894 snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d}",
4895 gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
4896 basetime, increment);
4898 snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d %s}",
4899 gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
4900 basetime, increment, VariantName(gameInfo.variant));
4903 if (appData.debugMode) {
4904 fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
4909 /* Display the board */
4910 if (!pausing && !appData.noGUI) {
4912 if (appData.premove)
4914 ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
4915 ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
4916 ClearPremoveHighlights();
4918 j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
4919 if(partnerUp) { flipView = originalFlip; partnerUp = FALSE; j = TRUE; } // [HGM] bughouse: restore view
4920 DrawPosition(j, boards[currentMove]);
4922 DisplayMove(moveNum - 1);
4923 if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4924 !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4925 (gameMode == IcsPlayingBlack) && (WhiteOnMove(moveNum)) ) ) {
4926 if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4930 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4932 if(bookHit) { // [HGM] book: simulate book reply
4933 static char bookMove[MSG_SIZ]; // a bit generous?
4935 programStats.nodes = programStats.depth = programStats.time =
4936 programStats.score = programStats.got_only_move = 0;
4937 sprintf(programStats.movelist, "%s (xbook)", bookHit);
4939 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
4940 strcat(bookMove, bookHit);
4941 HandleMachineMove(bookMove, &first);
4950 if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4951 ics_getting_history = H_REQUESTED;
4952 snprintf(buf, MSG_SIZ, "%smoves %d\n", ics_prefix, ics_gamenum);
4958 SendToBoth (char *msg)
4959 { // to make it easy to keep two engines in step in dual analysis
4960 SendToProgram(msg, &first);
4961 if(second.analyzing) SendToProgram(msg, &second);
4965 AnalysisPeriodicEvent (int force)
4967 if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4968 && !force) || !appData.periodicUpdates)
4971 /* Send . command to Crafty to collect stats */
4974 /* Don't send another until we get a response (this makes
4975 us stop sending to old Crafty's which don't understand
4976 the "." command (sending illegal cmds resets node count & time,
4977 which looks bad)) */
4978 programStats.ok_to_send = 0;
4982 ics_update_width (int new_width)
4984 ics_printf("set width %d\n", new_width);
4988 SendMoveToProgram (int moveNum, ChessProgramState *cps)
4992 if(moveList[moveNum][1] == '@' && moveList[moveNum][0] == '@') {
4993 // null move in variant where engine does not understand it (for analysis purposes)
4994 SendBoard(cps, moveNum + 1); // send position after move in stead.
4997 if (cps->useUsermove) {
4998 SendToProgram("usermove ", cps);
5002 if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
5003 int len = space - parseList[moveNum];
5004 memcpy(buf, parseList[moveNum], len);
5006 buf[len] = NULLCHAR;
5008 snprintf(buf, MSG_SIZ,"%s\n", parseList[moveNum]);
5010 SendToProgram(buf, cps);
5012 if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
5013 AlphaRank(moveList[moveNum], 4);
5014 SendToProgram(moveList[moveNum], cps);
5015 AlphaRank(moveList[moveNum], 4); // and back
5017 /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
5018 * the engine. It would be nice to have a better way to identify castle
5020 if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
5021 && cps->useOOCastle) {
5022 int fromX = moveList[moveNum][0] - AAA;
5023 int fromY = moveList[moveNum][1] - ONE;
5024 int toX = moveList[moveNum][2] - AAA;
5025 int toY = moveList[moveNum][3] - ONE;
5026 if((boards[moveNum][fromY][fromX] == WhiteKing
5027 && boards[moveNum][toY][toX] == WhiteRook)
5028 || (boards[moveNum][fromY][fromX] == BlackKing
5029 && boards[moveNum][toY][toX] == BlackRook)) {
5030 if(toX > fromX) SendToProgram("O-O\n", cps);
5031 else SendToProgram("O-O-O\n", cps);
5033 else SendToProgram(moveList[moveNum], cps);
5035 if(BOARD_HEIGHT > 10) { // [HGM] big: convert ranks to double-digit where needed
5036 if(moveList[moveNum][1] == '@' && (BOARD_HEIGHT < 16 || moveList[moveNum][0] <= 'Z')) { // drop move
5037 if(moveList[moveNum][0]== '@') snprintf(buf, MSG_SIZ, "@@@@\n"); else
5038 snprintf(buf, MSG_SIZ, "%c@%c%d%s", moveList[moveNum][0],
5039 moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
5041 snprintf(buf, MSG_SIZ, "%c%d%c%d%s", moveList[moveNum][0], moveList[moveNum][1] - '0',
5042 moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
5043 SendToProgram(buf, cps);
5045 else SendToProgram(moveList[moveNum], cps);
5046 /* End of additions by Tord */
5049 /* [HGM] setting up the opening has brought engine in force mode! */
5050 /* Send 'go' if we are in a mode where machine should play. */
5051 if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
5052 (gameMode == TwoMachinesPlay ||
5054 gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite ||
5056 gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
5057 SendToProgram("go\n", cps);
5058 if (appData.debugMode) {
5059 fprintf(debugFP, "(extra)\n");
5062 setboardSpoiledMachineBlack = 0;
5066 SendMoveToICS (ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar)
5068 char user_move[MSG_SIZ];
5071 if(gameInfo.variant == VariantSChess && promoChar) {
5072 snprintf(suffix, 4, "=%c", toX == BOARD_WIDTH<<1 ? ToUpper(promoChar) : ToLower(promoChar));
5073 if(moveType == NormalMove) moveType = WhitePromotion; // kludge to do gating
5074 } else suffix[0] = NULLCHAR;
5078 snprintf(user_move, MSG_SIZ, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
5079 (int)moveType, fromX, fromY, toX, toY);
5080 DisplayError(user_move + strlen("say "), 0);
5082 case WhiteKingSideCastle:
5083 case BlackKingSideCastle:
5084 case WhiteQueenSideCastleWild:
5085 case BlackQueenSideCastleWild:
5087 case WhiteHSideCastleFR:
5088 case BlackHSideCastleFR:
5090 snprintf(user_move, MSG_SIZ, "o-o%s\n", suffix);
5092 case WhiteQueenSideCastle:
5093 case BlackQueenSideCastle:
5094 case WhiteKingSideCastleWild:
5095 case BlackKingSideCastleWild:
5097 case WhiteASideCastleFR:
5098 case BlackASideCastleFR:
5100 snprintf(user_move, MSG_SIZ, "o-o-o%s\n",suffix);
5102 case WhiteNonPromotion:
5103 case BlackNonPromotion:
5104 sprintf(user_move, "%c%c%c%c==\n", AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5106 case WhitePromotion:
5107 case BlackPromotion:
5108 if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
5109 gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN)
5110 snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5111 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5112 PieceToChar(WhiteFerz));
5113 else if(gameInfo.variant == VariantGreat)
5114 snprintf(user_move, MSG_SIZ,"%c%c%c%c=%c\n",
5115 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5116 PieceToChar(WhiteMan));
5118 snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5119 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5125 snprintf(user_move, MSG_SIZ, "%c@%c%c\n",
5126 ToUpper(PieceToChar((ChessSquare) fromX)),
5127 AAA + toX, ONE + toY);
5129 case IllegalMove: /* could be a variant we don't quite understand */
5130 if(fromY == DROP_RANK) goto drop; // We need 'IllegalDrop' move type?
5132 case WhiteCapturesEnPassant:
5133 case BlackCapturesEnPassant:
5134 snprintf(user_move, MSG_SIZ,"%c%c%c%c\n",
5135 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5138 SendToICS(user_move);
5139 if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
5140 ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
5145 { // [HGM] upload: send entire stored game to ICS as long-algebraic moves.
5146 int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
5147 static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
5148 if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
5149 DisplayError(_("You cannot do this while you are playing or observing"), 0);
5152 if(gameMode != IcsExamining) { // is this ever not the case?
5153 char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
5155 if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
5156 snprintf(command,MSG_SIZ, "match %s", ics_handle);
5157 } else { // on FICS we must first go to general examine mode
5158 safeStrCpy(command, "examine\nbsetup", sizeof(command)/sizeof(command[0])); // and specify variant within it with bsetups
5160 if(gameInfo.variant != VariantNormal) {
5161 // try figure out wild number, as xboard names are not always valid on ICS
5162 for(i=1; i<=36; i++) {
5163 snprintf(buf, MSG_SIZ, "wild/%d", i);
5164 if(StringToVariant(buf) == gameInfo.variant) break;
5166 if(i<=36 && ics_type == ICS_ICC) snprintf(buf, MSG_SIZ,"%s w%d\n", command, i);
5167 else if(i == 22) snprintf(buf,MSG_SIZ, "%s fr\n", command);
5168 else snprintf(buf, MSG_SIZ,"%s %s\n", command, VariantName(gameInfo.variant));
5169 } else snprintf(buf, MSG_SIZ,"%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
5170 SendToICS(ics_prefix);
5172 if(startedFromSetupPosition || backwardMostMove != 0) {
5173 fen = PositionToFEN(backwardMostMove, NULL, 1);
5174 if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
5175 snprintf(buf, MSG_SIZ,"loadfen %s\n", fen);
5177 } else { // FICS: everything has to set by separate bsetup commands
5178 p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board
5179 snprintf(buf, MSG_SIZ,"bsetup fen %s\n", fen);
5181 if(!WhiteOnMove(backwardMostMove)) {
5182 SendToICS("bsetup tomove black\n");
5184 i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL);
5185 snprintf(buf, MSG_SIZ,"bsetup wcastle %s\n", castlingStrings[i]);
5187 i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
5188 snprintf(buf, MSG_SIZ, "bsetup bcastle %s\n", castlingStrings[i]);
5190 i = boards[backwardMostMove][EP_STATUS];
5191 if(i >= 0) { // set e.p.
5192 snprintf(buf, MSG_SIZ,"bsetup eppos %c\n", i+AAA);
5198 if(bsetup || ics_type != ICS_ICC && gameInfo.variant != VariantNormal)
5199 SendToICS("bsetup done\n"); // switch to normal examining.
5201 for(i = backwardMostMove; i<last; i++) {
5203 snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s\n", parseList[i]);
5204 if((*buf == 'b' || *buf == 'B') && buf[1] == 'x') { // work-around for stupid FICS bug, which thinks bxc3 can be a Bishop move
5205 int len = strlen(moveList[i]);
5206 snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s", moveList[i]); // use long algebraic
5207 if(!isdigit(buf[len-2])) snprintf(buf+len-2, 20-len, "=%c\n", ToUpper(buf[len-2])); // promotion must have '=' in ICS format
5211 SendToICS(ics_prefix);
5212 SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
5216 CoordsToComputerAlgebraic (int rf, int ff, int rt, int ft, char promoChar, char move[7])
5218 if (rf == DROP_RANK) {
5219 if(ff == EmptySquare) sprintf(move, "@@@@\n"); else // [HGM] pass
5220 sprintf(move, "%c@%c%c\n",
5221 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
5223 if (promoChar == 'x' || promoChar == NULLCHAR) {
5224 sprintf(move, "%c%c%c%c\n",
5225 AAA + ff, ONE + rf, AAA + ft, ONE + rt);
5227 sprintf(move, "%c%c%c%c%c\n",
5228 AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
5234 ProcessICSInitScript (FILE *f)
5238 while (fgets(buf, MSG_SIZ, f)) {
5239 SendToICSDelayed(buf,(long)appData.msLoginDelay);
5246 static int lastX, lastY, lastLeftX, lastLeftY, selectFlag, dragging;
5247 static ClickType lastClickType;
5252 ChessSquare king = WhiteKing, pawn = WhitePawn, last = promoSweep;
5253 if(gameInfo.variant == VariantKnightmate) king = WhiteUnicorn;
5254 if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway) king = EmptySquare;
5255 if(promoSweep >= BlackPawn) king = WHITE_TO_BLACK king, pawn = WHITE_TO_BLACK pawn;
5256 if(gameInfo.variant == VariantSpartan && pawn == BlackPawn) pawn = BlackLance, king = EmptySquare;
5257 if(fromY != BOARD_HEIGHT-2 && fromY != 1) pawn = EmptySquare;
5260 if(promoSweep == EmptySquare) promoSweep = BlackPawn; // wrap
5261 else if((int)promoSweep == -1) promoSweep = WhiteKing;
5262 else if(promoSweep == BlackPawn && step < 0) promoSweep = WhitePawn;
5263 else if(promoSweep == WhiteKing && step > 0) promoSweep = BlackKing;
5264 if(!step) step = -1;
5265 } while(PieceToChar(promoSweep) == '.' || PieceToChar(promoSweep) == '~' || promoSweep == pawn ||
5266 appData.testLegality && (promoSweep == king ||
5267 gameInfo.variant == VariantShogi && promoSweep != PROMOTED last && last != PROMOTED promoSweep && last != promoSweep));
5269 int victim = boards[currentMove][toY][toX];
5270 boards[currentMove][toY][toX] = promoSweep;
5271 DrawPosition(FALSE, boards[currentMove]);
5272 boards[currentMove][toY][toX] = victim;
5274 ChangeDragPiece(promoSweep);
5278 PromoScroll (int x, int y)
5282 if(promoSweep == EmptySquare || !appData.sweepSelect) return FALSE;
5283 if(abs(x - lastX) < 25 && abs(y - lastY) < 25) return FALSE;
5284 if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5285 if(!step) return FALSE;
5286 lastX = x; lastY = y;
5287 if((promoSweep < BlackPawn) == flipView) step = -step;
5288 if(step > 0) selectFlag = 1;
5289 if(!selectFlag) Sweep(step);
5294 NextPiece (int step)
5296 ChessSquare piece = boards[currentMove][toY][toX];
5299 if(pieceSweep == EmptySquare) pieceSweep = WhitePawn; // wrap
5300 if((int)pieceSweep == -1) pieceSweep = BlackKing;
5301 if(!step) step = -1;
5302 } while(PieceToChar(pieceSweep) == '.');
5303 boards[currentMove][toY][toX] = pieceSweep;
5304 DrawPosition(FALSE, boards[currentMove]);
5305 boards[currentMove][toY][toX] = piece;
5307 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
5309 AlphaRank (char *move, int n)
5311 // char *p = move, c; int x, y;
5313 if (appData.debugMode) {
5314 fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
5318 move[2]>='0' && move[2]<='9' &&
5319 move[3]>='a' && move[3]<='x' ) {
5321 move[2] = BOARD_RGHT -1 - (move[2]-'1') + AAA;
5322 move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5324 if(move[0]>='0' && move[0]<='9' &&
5325 move[1]>='a' && move[1]<='x' &&
5326 move[2]>='0' && move[2]<='9' &&
5327 move[3]>='a' && move[3]<='x' ) {
5328 /* input move, Shogi -> normal */
5329 move[0] = BOARD_RGHT -1 - (move[0]-'1') + AAA;
5330 move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
5331 move[2] = BOARD_RGHT -1 - (move[2]-'1') + AAA;
5332 move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5335 move[3]>='0' && move[3]<='9' &&
5336 move[2]>='a' && move[2]<='x' ) {
5338 move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5339 move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5342 move[0]>='a' && move[0]<='x' &&
5343 move[3]>='0' && move[3]<='9' &&
5344 move[2]>='a' && move[2]<='x' ) {
5345 /* output move, normal -> Shogi */
5346 move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
5347 move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
5348 move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5349 move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5350 if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
5352 if (appData.debugMode) {
5353 fprintf(debugFP, " out = '%s'\n", move);
5357 char yy_textstr[8000];
5359 /* Parser for moves from gnuchess, ICS, or user typein box */
5361 ParseOneMove (char *move, int moveNum, ChessMove *moveType, int *fromX, int *fromY, int *toX, int *toY, char *promoChar)
5363 *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
5365 switch (*moveType) {
5366 case WhitePromotion:
5367 case BlackPromotion:
5368 case WhiteNonPromotion:
5369 case BlackNonPromotion:
5371 case WhiteCapturesEnPassant:
5372 case BlackCapturesEnPassant:
5373 case WhiteKingSideCastle:
5374 case WhiteQueenSideCastle:
5375 case BlackKingSideCastle:
5376 case BlackQueenSideCastle:
5377 case WhiteKingSideCastleWild:
5378 case WhiteQueenSideCastleWild:
5379 case BlackKingSideCastleWild:
5380 case BlackQueenSideCastleWild:
5381 /* Code added by Tord: */
5382 case WhiteHSideCastleFR:
5383 case WhiteASideCastleFR:
5384 case BlackHSideCastleFR:
5385 case BlackASideCastleFR:
5386 /* End of code added by Tord */
5387 case IllegalMove: /* bug or odd chess variant */
5388 *fromX = currentMoveString[0] - AAA;
5389 *fromY = currentMoveString[1] - ONE;
5390 *toX = currentMoveString[2] - AAA;
5391 *toY = currentMoveString[3] - ONE;
5392 *promoChar = currentMoveString[4];
5393 if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
5394 *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
5395 if (appData.debugMode) {
5396 fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
5398 *fromX = *fromY = *toX = *toY = 0;
5401 if (appData.testLegality) {
5402 return (*moveType != IllegalMove);
5404 return !(*fromX == *toX && *fromY == *toY) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
5405 WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
5410 *fromX = *moveType == WhiteDrop ?
5411 (int) CharToPiece(ToUpper(currentMoveString[0])) :
5412 (int) CharToPiece(ToLower(currentMoveString[0]));
5414 *toX = currentMoveString[2] - AAA;
5415 *toY = currentMoveString[3] - ONE;
5416 *promoChar = NULLCHAR;
5420 case ImpossibleMove:
5430 if (appData.debugMode) {
5431 fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
5434 *fromX = *fromY = *toX = *toY = 0;
5435 *promoChar = NULLCHAR;
5440 Boolean pushed = FALSE;
5441 char *lastParseAttempt;
5444 ParsePV (char *pv, Boolean storeComments, Boolean atEnd)
5445 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
5446 int fromX, fromY, toX, toY; char promoChar;
5451 lastParseAttempt = pv; if(!*pv) return; // turns out we crash when we parse an empty PV
5452 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) && currentMove < forwardMostMove) {
5453 PushInner(currentMove, forwardMostMove); // [HGM] engine might not be thinking on forwardMost position!
5456 endPV = forwardMostMove;
5458 while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
5459 if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
5460 lastParseAttempt = pv;
5461 valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
5462 if(!valid && nr == 0 &&
5463 ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
5464 nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
5465 // Hande case where played move is different from leading PV move
5466 CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
5467 CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
5468 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
5469 if(!CompareBoards(boards[endPV], boards[endPV+2])) {
5470 endPV += 2; // if position different, keep this
5471 moveList[endPV-1][0] = fromX + AAA;
5472 moveList[endPV-1][1] = fromY + ONE;
5473 moveList[endPV-1][2] = toX + AAA;
5474 moveList[endPV-1][3] = toY + ONE;
5475 parseList[endPV-1][0] = NULLCHAR;
5476 safeStrCpy(moveList[endPV-2], "_0_0", sizeof(moveList[endPV-2])/sizeof(moveList[endPV-2][0])); // suppress premove highlight on takeback move
5479 pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
5480 if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
5481 if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
5482 if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
5483 valid++; // allow comments in PV
5487 if(endPV+1 > framePtr) break; // no space, truncate
5490 CopyBoard(boards[endPV], boards[endPV-1]);
5491 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
5492 CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, moveList[endPV - 1]);
5493 strncat(moveList[endPV-1], "\n", MOVE_LEN);
5494 CoordsToAlgebraic(boards[endPV - 1],
5495 PosFlags(endPV - 1),
5496 fromY, fromX, toY, toX, promoChar,
5497 parseList[endPV - 1]);
5499 if(atEnd == 2) return; // used hidden, for PV conversion
5500 currentMove = (atEnd || endPV == forwardMostMove) ? endPV : forwardMostMove + 1;
5501 if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5502 SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5503 moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5504 DrawPosition(TRUE, boards[currentMove]);
5508 MultiPV (ChessProgramState *cps)
5509 { // check if engine supports MultiPV, and if so, return the number of the option that sets it
5511 for(i=0; i<cps->nrOptions; i++)
5512 if(!strcmp(cps->option[i].name, "MultiPV") && cps->option[i].type == Spin)
5517 Boolean extendGame; // signals to UnLoadPV() if walked part of PV has to be appended to game
5520 LoadMultiPV (int x, int y, char *buf, int index, int *start, int *end, int pane)
5522 int startPV, multi, lineStart, origIndex = index;
5523 char *p, buf2[MSG_SIZ];
5524 ChessProgramState *cps = (pane ? &second : &first);
5526 if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
5527 lastX = x; lastY = y;
5528 while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
5529 lineStart = startPV = index;
5530 while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
5531 if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
5533 do{ while(buf[index] && buf[index] != '\n') index++;
5534 } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
5536 if(lineStart == 0 && gameMode == AnalyzeMode && (multi = MultiPV(cps)) >= 0) {
5537 int n = cps->option[multi].value;
5538 if(origIndex > 17 && origIndex < 24) { if(n>1) n--; } else if(origIndex > index - 6) n++;
5539 snprintf(buf2, MSG_SIZ, "option MultiPV=%d\n", n);
5540 if(cps->option[multi].value != n) SendToProgram(buf2, cps);
5541 cps->option[multi].value = n;
5544 } else if(strstr(buf+lineStart, "exclude:") == buf+lineStart) { // exclude moves clicked
5545 ExcludeClick(origIndex - lineStart);
5548 ParsePV(buf+startPV, FALSE, gameMode != AnalyzeMode);
5549 *start = startPV; *end = index-1;
5550 extendGame = (gameMode == AnalyzeMode && appData.autoExtend);
5557 static char buf[10*MSG_SIZ];
5558 int i, k=0, savedEnd=endPV, saveFMM = forwardMostMove;
5560 if(forwardMostMove < endPV) PushInner(forwardMostMove, endPV); // shelve PV of PV-walk
5561 ParsePV(pv, FALSE, 2); // this appends PV to game, suppressing any display of it
5562 for(i = forwardMostMove; i<endPV; i++){
5563 if(i&1) snprintf(buf+k, 10*MSG_SIZ-k, "%s ", parseList[i]);
5564 else snprintf(buf+k, 10*MSG_SIZ-k, "%d. %s ", i/2 + 1, parseList[i]);
5567 snprintf(buf+k, 10*MSG_SIZ-k, "%s", lastParseAttempt); // if we ran into stuff that could not be parsed, print it verbatim
5568 if(pushed) { PopInner(0); pushed = FALSE; } // restore game continuation shelved by ParsePV
5569 if(forwardMostMove < savedEnd) { PopInner(0); forwardMostMove = saveFMM; } // PopInner would set fmm to endPV!
5575 LoadPV (int x, int y)
5576 { // called on right mouse click to load PV
5577 int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
5578 lastX = x; lastY = y;
5579 ParsePV(lastPV[which], FALSE, TRUE); // load the PV of the thinking engine in the boards array.
5587 int oldFMM = forwardMostMove; // N.B.: this was currentMove before PV was loaded!
5588 if(endPV < 0) return;
5589 if(appData.autoCopyPV) CopyFENToClipboard();
5591 if(extendGame && currentMove > forwardMostMove) {
5592 Boolean saveAnimate = appData.animate;
5594 if(shiftKey && storedGames < MAX_VARIATIONS-2) { // wants to start variation, and there is space
5595 if(storedGames == 1) GreyRevert(FALSE); // we already pushed the tail, so just make it official
5596 } else storedGames--; // abandon shelved tail of original game
5599 forwardMostMove = currentMove;
5600 currentMove = oldFMM;
5601 appData.animate = FALSE;
5602 ToNrEvent(forwardMostMove);
5603 appData.animate = saveAnimate;
5605 currentMove = forwardMostMove;
5606 if(pushed) { PopInner(0); pushed = FALSE; } // restore shelved game continuation
5607 ClearPremoveHighlights();
5608 DrawPosition(TRUE, boards[currentMove]);
5612 MovePV (int x, int y, int h)
5613 { // step through PV based on mouse coordinates (called on mouse move)
5614 int margin = h>>3, step = 0, threshold = (pieceSweep == EmptySquare ? 10 : 15);
5616 // we must somehow check if right button is still down (might be released off board!)
5617 if(endPV < 0 && pieceSweep == EmptySquare) return; // needed in XBoard because lastX/Y is shared :-(
5618 if(abs(x - lastX) < threshold && abs(y - lastY) < threshold) return;
5619 if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5621 lastX = x; lastY = y;
5623 if(pieceSweep != EmptySquare) { NextPiece(step); return; }
5624 if(endPV < 0) return;
5625 if(y < margin) step = 1; else
5626 if(y > h - margin) step = -1;
5627 if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
5628 currentMove += step;
5629 if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5630 SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5631 moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5632 DrawPosition(FALSE, boards[currentMove]);
5636 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
5637 // All positions will have equal probability, but the current method will not provide a unique
5638 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
5644 int piecesLeft[(int)BlackPawn];
5645 int seed, nrOfShuffles;
5648 GetPositionNumber ()
5649 { // sets global variable seed
5652 seed = appData.defaultFrcPosition;
5653 if(seed < 0) { // randomize based on time for negative FRC position numbers
5654 for(i=0; i<50; i++) seed += random();
5655 seed = random() ^ random() >> 8 ^ random() << 8;
5656 if(seed<0) seed = -seed;
5661 put (Board board, int pieceType, int rank, int n, int shade)
5662 // put the piece on the (n-1)-th empty squares of the given shade
5666 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5667 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5668 board[rank][i] = (ChessSquare) pieceType;
5669 squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5671 piecesLeft[pieceType]--;
5680 AddOnePiece (Board board, int pieceType, int rank, int shade)
5681 // calculate where the next piece goes, (any empty square), and put it there
5685 i = seed % squaresLeft[shade];
5686 nrOfShuffles *= squaresLeft[shade];
5687 seed /= squaresLeft[shade];
5688 put(board, pieceType, rank, i, shade);
5692 AddTwoPieces (Board board, int pieceType, int rank)
5693 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5695 int i, n=squaresLeft[ANY], j=n-1, k;
5697 k = n*(n-1)/2; // nr of possibilities, not counting permutations
5698 i = seed % k; // pick one
5701 while(i >= j) i -= j--;
5702 j = n - 1 - j; i += j;
5703 put(board, pieceType, rank, j, ANY);
5704 put(board, pieceType, rank, i, ANY);
5708 SetUpShuffle (Board board, int number)
5712 GetPositionNumber(); nrOfShuffles = 1;
5714 squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5715 squaresLeft[ANY] = BOARD_RGHT - BOARD_LEFT;
5716 squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5718 for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5720 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5721 p = (int) board[0][i];
5722 if(p < (int) BlackPawn) piecesLeft[p] ++;
5723 board[0][i] = EmptySquare;
5726 if(PosFlags(0) & F_ALL_CASTLE_OK) {
5727 // shuffles restricted to allow normal castling put KRR first
5728 if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5729 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5730 else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5731 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5732 if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5733 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5734 if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5735 put(board, WhiteRook, 0, 0, ANY);
5736 // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5739 if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5740 // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5741 for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5742 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5743 while(piecesLeft[p] >= 2) {
5744 AddOnePiece(board, p, 0, LITE);
5745 AddOnePiece(board, p, 0, DARK);
5747 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5750 for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5751 // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5752 // but we leave King and Rooks for last, to possibly obey FRC restriction
5753 if(p == (int)WhiteRook) continue;
5754 while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5755 if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY); // add the odd piece
5758 // now everything is placed, except perhaps King (Unicorn) and Rooks
5760 if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5761 // Last King gets castling rights
5762 while(piecesLeft[(int)WhiteUnicorn]) {
5763 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5764 initialRights[2] = initialRights[5] = board[CASTLING][2] = board[CASTLING][5] = i;
5767 while(piecesLeft[(int)WhiteKing]) {
5768 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5769 initialRights[2] = initialRights[5] = board[CASTLING][2] = board[CASTLING][5] = i;
5774 while(piecesLeft[(int)WhiteKing]) AddOnePiece(board, WhiteKing, 0, ANY);
5775 while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
5778 // Only Rooks can be left; simply place them all
5779 while(piecesLeft[(int)WhiteRook]) {
5780 i = put(board, WhiteRook, 0, 0, ANY);
5781 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
5784 initialRights[1] = initialRights[4] = board[CASTLING][1] = board[CASTLING][4] = i;
5786 initialRights[0] = initialRights[3] = board[CASTLING][0] = board[CASTLING][3] = i;
5789 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
5790 board[BOARD_HEIGHT-1][i] = (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
5793 if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
5797 SetCharTable (char *table, const char * map)
5798 /* [HGM] moved here from winboard.c because of its general usefulness */
5799 /* Basically a safe strcpy that uses the last character as King */
5801 int result = FALSE; int NrPieces;
5803 if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
5804 && NrPieces >= 12 && !(NrPieces&1)) {
5805 int i; /* [HGM] Accept even length from 12 to 34 */
5807 for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
5808 for( i=0; i<NrPieces/2-1; i++ ) {
5810 table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
5812 table[(int) WhiteKing] = map[NrPieces/2-1];
5813 table[(int) BlackKing] = map[NrPieces-1];
5822 Prelude (Board board)
5823 { // [HGM] superchess: random selection of exo-pieces
5824 int i, j, k; ChessSquare p;
5825 static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
5827 GetPositionNumber(); // use FRC position number
5829 if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
5830 SetCharTable(pieceToChar, appData.pieceToCharTable);
5831 for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
5832 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
5835 j = seed%4; seed /= 4;
5836 p = board[0][BOARD_LEFT+j]; board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5837 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
5838 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
5839 j = seed%3 + (seed%3 >= j); seed /= 3;
5840 p = board[0][BOARD_LEFT+j]; board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5841 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
5842 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
5843 j = seed%3; seed /= 3;
5844 p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5845 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
5846 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
5847 j = seed%2 + (seed%2 >= j); seed /= 2;
5848 p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5849 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
5850 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
5851 j = seed%4; seed /= 4; put(board, exoPieces[3], 0, j, ANY);
5852 j = seed%3; seed /= 3; put(board, exoPieces[2], 0, j, ANY);
5853 j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
5854 put(board, exoPieces[0], 0, 0, ANY);
5855 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
5859 InitPosition (int redraw)
5861 ChessSquare (* pieces)[BOARD_FILES];
5862 int i, j, pawnRow, overrule,
5863 oldx = gameInfo.boardWidth,
5864 oldy = gameInfo.boardHeight,
5865 oldh = gameInfo.holdingsWidth;
5868 if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
5870 /* [AS] Initialize pv info list [HGM] and game status */
5872 for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
5873 pvInfoList[i].depth = 0;
5874 boards[i][EP_STATUS] = EP_NONE;
5875 for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
5878 initialRulePlies = 0; /* 50-move counter start */
5880 castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
5881 castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
5885 /* [HGM] logic here is completely changed. In stead of full positions */
5886 /* the initialized data only consist of the two backranks. The switch */
5887 /* selects which one we will use, which is than copied to the Board */
5888 /* initialPosition, which for the rest is initialized by Pawns and */
5889 /* empty squares. This initial position is then copied to boards[0], */
5890 /* possibly after shuffling, so that it remains available. */
5892 gameInfo.holdingsWidth = 0; /* default board sizes */
5893 gameInfo.boardWidth = 8;
5894 gameInfo.boardHeight = 8;
5895 gameInfo.holdingsSize = 0;
5896 nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
5897 for(i=0; i<BOARD_FILES-2; i++)
5898 initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
5899 initialPosition[EP_STATUS] = EP_NONE;
5900 SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
5901 if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
5902 SetCharTable(pieceNickName, appData.pieceNickNames);
5903 else SetCharTable(pieceNickName, "............");
5906 switch (gameInfo.variant) {
5907 case VariantFischeRandom:
5908 shuffleOpenings = TRUE;
5911 case VariantShatranj:
5912 pieces = ShatranjArray;
5913 nrCastlingRights = 0;
5914 SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
5917 pieces = makrukArray;
5918 nrCastlingRights = 0;
5919 SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
5922 pieces = aseanArray;
5923 nrCastlingRights = 0;
5924 SetCharTable(pieceToChar, "PN.R.Q....BKpn.r.q....bk");
5926 case VariantTwoKings:
5927 pieces = twoKingsArray;
5930 pieces = GrandArray;
5931 nrCastlingRights = 0;
5932 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5933 gameInfo.boardWidth = 10;
5934 gameInfo.boardHeight = 10;
5935 gameInfo.holdingsSize = 7;
5937 case VariantCapaRandom:
5938 shuffleOpenings = TRUE;
5939 case VariantCapablanca:
5940 pieces = CapablancaArray;
5941 gameInfo.boardWidth = 10;
5942 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5945 pieces = GothicArray;
5946 gameInfo.boardWidth = 10;
5947 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5950 SetCharTable(pieceToChar, "PNBRQ..HEKpnbrq..hek");
5951 gameInfo.holdingsSize = 7;
5952 for(i=0; i<BOARD_FILES; i++) initialPosition[VIRGIN][i] = VIRGIN_W | VIRGIN_B;
5955 pieces = JanusArray;
5956 gameInfo.boardWidth = 10;
5957 SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
5958 nrCastlingRights = 6;
5959 initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5960 initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5961 initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
5962 initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5963 initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5964 initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
5967 pieces = FalconArray;
5968 gameInfo.boardWidth = 10;
5969 SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
5971 case VariantXiangqi:
5972 pieces = XiangqiArray;
5973 gameInfo.boardWidth = 9;
5974 gameInfo.boardHeight = 10;
5975 nrCastlingRights = 0;
5976 SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
5979 pieces = ShogiArray;
5980 gameInfo.boardWidth = 9;
5981 gameInfo.boardHeight = 9;
5982 gameInfo.holdingsSize = 7;
5983 nrCastlingRights = 0;
5984 SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
5986 case VariantCourier:
5987 pieces = CourierArray;
5988 gameInfo.boardWidth = 12;
5989 nrCastlingRights = 0;
5990 SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
5992 case VariantKnightmate:
5993 pieces = KnightmateArray;
5994 SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
5996 case VariantSpartan:
5997 pieces = SpartanArray;
5998 SetCharTable(pieceToChar, "PNBRQ................K......lwg.....c...h..k");
6001 pieces = fairyArray;
6002 SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
6005 pieces = GreatArray;
6006 gameInfo.boardWidth = 10;
6007 SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
6008 gameInfo.holdingsSize = 8;
6012 SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
6013 gameInfo.holdingsSize = 8;
6014 startedFromSetupPosition = TRUE;
6016 case VariantCrazyhouse:
6017 case VariantBughouse:
6019 SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
6020 gameInfo.holdingsSize = 5;
6022 case VariantWildCastle:
6024 /* !!?shuffle with kings guaranteed to be on d or e file */
6025 shuffleOpenings = 1;
6027 case VariantNoCastle:
6029 nrCastlingRights = 0;
6030 /* !!?unconstrained back-rank shuffle */
6031 shuffleOpenings = 1;
6036 if(appData.NrFiles >= 0) {
6037 if(gameInfo.boardWidth != appData.NrFiles) overrule++;
6038 gameInfo.boardWidth = appData.NrFiles;
6040 if(appData.NrRanks >= 0) {
6041 gameInfo.boardHeight = appData.NrRanks;
6043 if(appData.holdingsSize >= 0) {
6044 i = appData.holdingsSize;
6045 if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
6046 gameInfo.holdingsSize = i;
6048 if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
6049 if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
6050 DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
6052 pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
6053 if(pawnRow < 1) pawnRow = 1;
6054 if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN || gameInfo.variant == VariantGrand) pawnRow = 2;
6056 /* User pieceToChar list overrules defaults */
6057 if(appData.pieceToCharTable != NULL)
6058 SetCharTable(pieceToChar, appData.pieceToCharTable);
6060 for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
6062 if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
6063 s = (ChessSquare) 0; /* account holding counts in guard band */
6064 for( i=0; i<BOARD_HEIGHT; i++ )
6065 initialPosition[i][j] = s;
6067 if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
6068 initialPosition[gameInfo.variant == VariantGrand][j] = pieces[0][j-gameInfo.holdingsWidth];
6069 initialPosition[pawnRow][j] = WhitePawn;
6070 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = gameInfo.variant == VariantSpartan ? BlackLance : BlackPawn;
6071 if(gameInfo.variant == VariantXiangqi) {
6073 initialPosition[pawnRow][j] =
6074 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
6075 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
6076 initialPosition[2][j] = WhiteCannon;
6077 initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
6081 if(gameInfo.variant == VariantGrand) {
6082 if(j==BOARD_LEFT || j>=BOARD_RGHT-1) {
6083 initialPosition[0][j] = WhiteRook;
6084 initialPosition[BOARD_HEIGHT-1][j] = BlackRook;
6087 initialPosition[BOARD_HEIGHT-1-(gameInfo.variant == VariantGrand)][j] = pieces[1][j-gameInfo.holdingsWidth];
6089 if( (gameInfo.variant == VariantShogi) && !overrule ) {
6092 initialPosition[1][j] = WhiteBishop;
6093 initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
6095 initialPosition[1][j] = WhiteRook;
6096 initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
6099 if( nrCastlingRights == -1) {
6100 /* [HGM] Build normal castling rights (must be done after board sizing!) */
6101 /* This sets default castling rights from none to normal corners */
6102 /* Variants with other castling rights must set them themselves above */
6103 nrCastlingRights = 6;
6105 initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
6106 initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
6107 initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
6108 initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
6109 initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
6110 initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
6113 if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
6114 if(gameInfo.variant == VariantGreat) { // promotion commoners
6115 initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
6116 initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
6117 initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
6118 initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
6120 if( gameInfo.variant == VariantSChess ) {
6121 initialPosition[1][0] = BlackMarshall;
6122 initialPosition[2][0] = BlackAngel;
6123 initialPosition[6][BOARD_WIDTH-1] = WhiteMarshall;
6124 initialPosition[5][BOARD_WIDTH-1] = WhiteAngel;
6125 initialPosition[1][1] = initialPosition[2][1] =
6126 initialPosition[6][BOARD_WIDTH-2] = initialPosition[5][BOARD_WIDTH-2] = 1;
6128 if (appData.debugMode) {
6129 fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
6131 if(shuffleOpenings) {
6132 SetUpShuffle(initialPosition, appData.defaultFrcPosition);
6133 startedFromSetupPosition = TRUE;
6135 if(startedFromPositionFile) {
6136 /* [HGM] loadPos: use PositionFile for every new game */
6137 CopyBoard(initialPosition, filePosition);
6138 for(i=0; i<nrCastlingRights; i++)
6139 initialRights[i] = filePosition[CASTLING][i];
6140 startedFromSetupPosition = TRUE;
6143 CopyBoard(boards[0], initialPosition);
6145 if(oldx != gameInfo.boardWidth ||
6146 oldy != gameInfo.boardHeight ||
6147 oldv != gameInfo.variant ||
6148 oldh != gameInfo.holdingsWidth
6150 InitDrawingSizes(-2 ,0);
6152 oldv = gameInfo.variant;
6154 DrawPosition(TRUE, boards[currentMove]);
6158 SendBoard (ChessProgramState *cps, int moveNum)
6160 char message[MSG_SIZ];
6162 if (cps->useSetboard) {
6163 char* fen = PositionToFEN(moveNum, cps->fenOverride, 1);
6164 snprintf(message, MSG_SIZ,"setboard %s\n", fen);
6165 SendToProgram(message, cps);
6170 int i, j, left=0, right=BOARD_WIDTH;
6171 /* Kludge to set black to move, avoiding the troublesome and now
6172 * deprecated "black" command.
6174 if (!WhiteOnMove(moveNum)) // [HGM] but better a deprecated command than an illegal move...
6175 SendToProgram(boards[0][1][BOARD_LEFT] == WhitePawn ? "a2a3\n" : "black\n", cps);
6177 if(!cps->extendedEdit) left = BOARD_LEFT, right = BOARD_RGHT; // only board proper
6179 SendToProgram("edit\n", cps);
6180 SendToProgram("#\n", cps);
6181 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6182 bp = &boards[moveNum][i][left];
6183 for (j = left; j < right; j++, bp++) {
6184 if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6185 if ((int) *bp < (int) BlackPawn) {
6186 if(j == BOARD_RGHT+1)
6187 snprintf(message, MSG_SIZ, "%c@%d\n", PieceToChar(*bp), bp[-1]);
6188 else snprintf(message, MSG_SIZ, "%c%c%c\n", PieceToChar(*bp), AAA + j, ONE + i);
6189 if(message[0] == '+' || message[0] == '~') {
6190 snprintf(message, MSG_SIZ,"%c%c%c+\n",
6191 PieceToChar((ChessSquare)(DEMOTED *bp)),
6194 if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6195 message[1] = BOARD_RGHT - 1 - j + '1';
6196 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6198 SendToProgram(message, cps);
6203 SendToProgram("c\n", cps);
6204 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6205 bp = &boards[moveNum][i][left];
6206 for (j = left; j < right; j++, bp++) {
6207 if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6208 if (((int) *bp != (int) EmptySquare)
6209 && ((int) *bp >= (int) BlackPawn)) {
6210 if(j == BOARD_LEFT-2)
6211 snprintf(message, MSG_SIZ, "%c@%d\n", ToUpper(PieceToChar(*bp)), bp[1]);
6212 else snprintf(message,MSG_SIZ, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
6214 if(message[0] == '+' || message[0] == '~') {
6215 snprintf(message, MSG_SIZ,"%c%c%c+\n",
6216 PieceToChar((ChessSquare)(DEMOTED *bp)),
6219 if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6220 message[1] = BOARD_RGHT - 1 - j + '1';
6221 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6223 SendToProgram(message, cps);
6228 SendToProgram(".\n", cps);
6230 setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
6233 char exclusionHeader[MSG_SIZ];
6234 int exCnt, excludePtr;
6235 typedef struct { int ff, fr, tf, tr, pc, mark; } Exclusion;
6236 static Exclusion excluTab[200];
6237 static char excludeMap[(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8]; // [HGM] exclude: bitmap for excluced moves
6243 for(j=0; j<(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8; j++) excludeMap[j] = s;
6244 exclusionHeader[19] = s ? '-' : '+'; // update tail state
6250 safeStrCpy(exclusionHeader, "exclude: none best +tail \n", MSG_SIZ);
6251 excludePtr = 24; exCnt = 0;
6256 UpdateExcludeHeader (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6257 { // search given move in table of header moves, to know where it is listed (and add if not there), and update state
6258 char buf[2*MOVE_LEN], *p;
6259 Exclusion *e = excluTab;
6261 for(i=0; i<exCnt; i++)
6262 if(e[i].ff == fromX && e[i].fr == fromY &&
6263 e[i].tf == toX && e[i].tr == toY && e[i].pc == promoChar) break;
6264 if(i == exCnt) { // was not in exclude list; add it
6265 CoordsToAlgebraic(boards[currentMove], PosFlags(currentMove), fromY, fromX, toY, toX, promoChar, buf);
6266 if(strlen(exclusionHeader + excludePtr) < strlen(buf)) { // no space to write move
6267 if(state != exclusionHeader[19]) exclusionHeader[19] = '*'; // tail is now in mixed state
6270 e[i].ff = fromX; e[i].fr = fromY; e[i].tf = toX; e[i].tr = toY; e[i].pc = promoChar;
6271 excludePtr++; e[i].mark = excludePtr++;
6272 for(p=buf; *p; p++) exclusionHeader[excludePtr++] = *p; // copy move
6275 exclusionHeader[e[i].mark] = state;
6279 ExcludeOneMove (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6280 { // include or exclude the given move, as specified by state ('+' or '-'), or toggle
6284 if((signed char)promoChar == -1) { // kludge to indicate best move
6285 if(!ParseOneMove(lastPV[0], currentMove, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) // get current best move from last PV
6286 return 1; // if unparsable, abort
6288 // update exclusion map (resolving toggle by consulting existing state)
6289 k=(BOARD_FILES*fromY+fromX)*BOARD_RANKS*BOARD_FILES + (BOARD_FILES*toY+toX);
6291 if(state == '*') state = (excludeMap[k] & 1<<j ? '+' : '-'); // toggle
6292 if(state == '-' && !promoChar) // only non-promotions get marked as excluded, to allow exclusion of under-promotions
6293 excludeMap[k] |= 1<<j;
6294 else excludeMap[k] &= ~(1<<j);
6296 UpdateExcludeHeader(fromY, fromX, toY, toX, promoChar, state);
6298 snprintf(buf, MSG_SIZ, "%sclude ", state == '+' ? "in" : "ex");
6299 CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, buf+8);
6301 return (state == '+');
6305 ExcludeClick (int index)
6308 Exclusion *e = excluTab;
6309 if(index < 25) { // none, best or tail clicked
6310 if(index < 13) { // none: include all
6311 WriteMap(0); // clear map
6312 for(i=0; i<exCnt; i++) exclusionHeader[excluTab[i].mark] = '+'; // and moves
6313 SendToBoth("include all\n"); // and inform engine
6314 } else if(index > 18) { // tail
6315 if(exclusionHeader[19] == '-') { // tail was excluded
6316 SendToBoth("include all\n");
6317 WriteMap(0); // clear map completely
6318 // now re-exclude selected moves
6319 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '-')
6320 ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '-');
6321 } else { // tail was included or in mixed state
6322 SendToBoth("exclude all\n");
6323 WriteMap(0xFF); // fill map completely
6324 // now re-include selected moves
6325 j = 0; // count them
6326 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '+')
6327 ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '+'), j++;
6328 if(!j) ExcludeOneMove(0, 0, 0, 0, -1, '+'); // if no moves were selected, keep best
6331 ExcludeOneMove(0, 0, 0, 0, -1, '-'); // exclude it
6334 for(i=0; i<exCnt; i++) if(i == exCnt-1 || excluTab[i+1].mark > index) {
6335 char *p=exclusionHeader + excluTab[i].mark; // do trust header more than map (promotions!)
6336 ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, *p == '+' ? '-' : '+');
6343 DefaultPromoChoice (int white)
6346 if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6347 gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN)
6348 result = WhiteFerz; // no choice
6349 else if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway)
6350 result= WhiteKing; // in Suicide Q is the last thing we want
6351 else if(gameInfo.variant == VariantSpartan)
6352 result = white ? WhiteQueen : WhiteAngel;
6353 else result = WhiteQueen;
6354 if(!white) result = WHITE_TO_BLACK result;
6358 static int autoQueen; // [HGM] oneclick
6361 HasPromotionChoice (int fromX, int fromY, int toX, int toY, char *promoChoice, int sweepSelect)
6363 /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
6364 /* [HGM] add Shogi promotions */
6365 int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
6370 if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
6371 if(toX < BOARD_LEFT || toX >= BOARD_RGHT) return FALSE; // move into holdings
6373 if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
6374 !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
6377 piece = boards[currentMove][fromY][fromX];
6378 if(gameInfo.variant == VariantShogi) {
6379 promotionZoneSize = BOARD_HEIGHT/3;
6380 highestPromotingPiece = (int)WhiteFerz;
6381 } else if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand) {
6382 promotionZoneSize = 3;
6385 // Treat Lance as Pawn when it is not representing Amazon
6386 if(gameInfo.variant != VariantSuper) {
6387 if(piece == WhiteLance) piece = WhitePawn; else
6388 if(piece == BlackLance) piece = BlackPawn;
6391 // next weed out all moves that do not touch the promotion zone at all
6392 if((int)piece >= BlackPawn) {
6393 if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
6395 highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
6397 if( toY < BOARD_HEIGHT - promotionZoneSize &&
6398 fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
6401 if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
6403 // weed out mandatory Shogi promotions
6404 if(gameInfo.variant == VariantShogi) {
6405 if(piece >= BlackPawn) {
6406 if(toY == 0 && piece == BlackPawn ||
6407 toY == 0 && piece == BlackQueen ||
6408 toY <= 1 && piece == BlackKnight) {
6413 if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
6414 toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
6415 toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
6422 // weed out obviously illegal Pawn moves
6423 if(appData.testLegality && (piece == WhitePawn || piece == BlackPawn) ) {
6424 if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
6425 if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
6426 if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
6427 if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
6428 // note we are not allowed to test for valid (non-)capture, due to premove
6431 // we either have a choice what to promote to, or (in Shogi) whether to promote
6432 if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6433 gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN) {
6434 *promoChoice = PieceToChar(BlackFerz); // no choice
6437 // no sense asking what we must promote to if it is going to explode...
6438 if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) {
6439 *promoChoice = PieceToChar(BlackQueen); // Queen as good as any
6442 // give caller the default choice even if we will not make it
6443 *promoChoice = ToLower(PieceToChar(defaultPromoChoice));
6444 if(gameInfo.variant == VariantShogi) *promoChoice = (defaultPromoChoice == piece ? '=' : '+');
6445 if( sweepSelect && gameInfo.variant != VariantGreat
6446 && gameInfo.variant != VariantGrand
6447 && gameInfo.variant != VariantSuper) return FALSE;
6448 if(autoQueen) return FALSE; // predetermined
6450 // suppress promotion popup on illegal moves that are not premoves
6451 premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
6452 gameMode == IcsPlayingBlack && WhiteOnMove(currentMove);
6453 if(appData.testLegality && !premove) {
6454 moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6455 fromY, fromX, toY, toX, gameInfo.variant == VariantShogi ? '+' : NULLCHAR);
6456 if(moveType != WhitePromotion && moveType != BlackPromotion)
6464 InPalace (int row, int column)
6465 { /* [HGM] for Xiangqi */
6466 if( (row < 3 || row > BOARD_HEIGHT-4) &&
6467 column < (BOARD_WIDTH + 4)/2 &&
6468 column > (BOARD_WIDTH - 5)/2 ) return TRUE;
6473 PieceForSquare (int x, int y)
6475 if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
6478 return boards[currentMove][y][x];
6482 OKToStartUserMove (int x, int y)
6484 ChessSquare from_piece;
6487 if (matchMode) return FALSE;
6488 if (gameMode == EditPosition) return TRUE;
6490 if (x >= 0 && y >= 0)
6491 from_piece = boards[currentMove][y][x];
6493 from_piece = EmptySquare;
6495 if (from_piece == EmptySquare) return FALSE;
6497 white_piece = (int)from_piece >= (int)WhitePawn &&
6498 (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
6502 case TwoMachinesPlay:
6510 case MachinePlaysWhite:
6511 case IcsPlayingBlack:
6512 if (appData.zippyPlay) return FALSE;
6514 DisplayMoveError(_("You are playing Black"));
6519 case MachinePlaysBlack:
6520 case IcsPlayingWhite:
6521 if (appData.zippyPlay) return FALSE;
6523 DisplayMoveError(_("You are playing White"));
6528 case PlayFromGameFile:
6529 if(!shiftKey || !appData.variations) return FALSE; // [HGM] allow starting variation in this mode
6531 if (!white_piece && WhiteOnMove(currentMove)) {
6532 DisplayMoveError(_("It is White's turn"));
6535 if (white_piece && !WhiteOnMove(currentMove)) {
6536 DisplayMoveError(_("It is Black's turn"));
6539 if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
6540 /* Editing correspondence game history */
6541 /* Could disallow this or prompt for confirmation */
6546 case BeginningOfGame:
6547 if (appData.icsActive) return FALSE;
6548 if (!appData.noChessProgram) {
6550 DisplayMoveError(_("You are playing White"));
6557 if (!white_piece && WhiteOnMove(currentMove)) {
6558 DisplayMoveError(_("It is White's turn"));
6561 if (white_piece && !WhiteOnMove(currentMove)) {
6562 DisplayMoveError(_("It is Black's turn"));
6571 if (currentMove != forwardMostMove && gameMode != AnalyzeMode
6572 && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
6573 && gameMode != PlayFromGameFile // [HGM] as EditGame, with protected main line
6574 && gameMode != AnalyzeFile && gameMode != Training) {
6575 DisplayMoveError(_("Displayed position is not current"));
6582 OnlyMove (int *x, int *y, Boolean captures)
6584 DisambiguateClosure cl;
6585 if (appData.zippyPlay || !appData.testLegality) return FALSE;
6587 case MachinePlaysBlack:
6588 case IcsPlayingWhite:
6589 case BeginningOfGame:
6590 if(!WhiteOnMove(currentMove)) return FALSE;
6592 case MachinePlaysWhite:
6593 case IcsPlayingBlack:
6594 if(WhiteOnMove(currentMove)) return FALSE;
6601 cl.pieceIn = EmptySquare;
6606 cl.promoCharIn = NULLCHAR;
6607 Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6608 if( cl.kind == NormalMove ||
6609 cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6610 cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6611 cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6618 if(cl.kind != ImpossibleMove) return FALSE;
6619 cl.pieceIn = EmptySquare;
6624 cl.promoCharIn = NULLCHAR;
6625 Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6626 if( cl.kind == NormalMove ||
6627 cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6628 cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6629 cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6634 autoQueen = TRUE; // act as if autoQueen on when we click to-square
6640 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
6641 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
6642 int lastLoadGameUseList = FALSE;
6643 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
6644 ChessMove lastLoadGameStart = EndOfFile;
6648 UserMoveEvent(int fromX, int fromY, int toX, int toY, int promoChar)
6652 int ff=fromX, rf=fromY, ft=toX, rt=toY;
6654 /* Check if the user is playing in turn. This is complicated because we
6655 let the user "pick up" a piece before it is his turn. So the piece he
6656 tried to pick up may have been captured by the time he puts it down!
6657 Therefore we use the color the user is supposed to be playing in this
6658 test, not the color of the piece that is currently on the starting
6659 square---except in EditGame mode, where the user is playing both
6660 sides; fortunately there the capture race can't happen. (It can
6661 now happen in IcsExamining mode, but that's just too bad. The user
6662 will get a somewhat confusing message in that case.)
6667 case TwoMachinesPlay:
6671 /* We switched into a game mode where moves are not accepted,
6672 perhaps while the mouse button was down. */
6675 case MachinePlaysWhite:
6676 /* User is moving for Black */
6677 if (WhiteOnMove(currentMove)) {
6678 DisplayMoveError(_("It is White's turn"));
6683 case MachinePlaysBlack:
6684 /* User is moving for White */
6685 if (!WhiteOnMove(currentMove)) {
6686 DisplayMoveError(_("It is Black's turn"));
6691 case PlayFromGameFile:
6692 if(!shiftKey ||!appData.variations) return; // [HGM] only variations
6695 case BeginningOfGame:
6698 if(fromY == DROP_RANK) break; // [HGM] drop moves (entered through move type-in) are automatically assigned to side-to-move
6699 if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
6700 (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
6701 /* User is moving for Black */
6702 if (WhiteOnMove(currentMove)) {
6703 DisplayMoveError(_("It is White's turn"));
6707 /* User is moving for White */
6708 if (!WhiteOnMove(currentMove)) {
6709 DisplayMoveError(_("It is Black's turn"));
6715 case IcsPlayingBlack:
6716 /* User is moving for Black */
6717 if (WhiteOnMove(currentMove)) {
6718 if (!appData.premove) {
6719 DisplayMoveError(_("It is White's turn"));
6720 } else if (toX >= 0 && toY >= 0) {
6723 premoveFromX = fromX;
6724 premoveFromY = fromY;
6725 premovePromoChar = promoChar;
6727 if (appData.debugMode)
6728 fprintf(debugFP, "Got premove: fromX %d,"
6729 "fromY %d, toX %d, toY %d\n",
6730 fromX, fromY, toX, toY);
6736 case IcsPlayingWhite:
6737 /* User is moving for White */
6738 if (!WhiteOnMove(currentMove)) {
6739 if (!appData.premove) {
6740 DisplayMoveError(_("It is Black's turn"));
6741 } else if (toX >= 0 && toY >= 0) {
6744 premoveFromX = fromX;
6745 premoveFromY = fromY;
6746 premovePromoChar = promoChar;
6748 if (appData.debugMode)
6749 fprintf(debugFP, "Got premove: fromX %d,"
6750 "fromY %d, toX %d, toY %d\n",
6751 fromX, fromY, toX, toY);
6761 /* EditPosition, empty square, or different color piece;
6762 click-click move is possible */
6763 if (toX == -2 || toY == -2) {
6764 boards[0][fromY][fromX] = EmptySquare;
6765 DrawPosition(FALSE, boards[currentMove]);
6767 } else if (toX >= 0 && toY >= 0) {
6768 boards[0][toY][toX] = boards[0][fromY][fromX];
6769 if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
6770 if(boards[0][fromY][0] != EmptySquare) {
6771 if(boards[0][fromY][1]) boards[0][fromY][1]--;
6772 if(boards[0][fromY][1] == 0) boards[0][fromY][0] = EmptySquare;
6775 if(fromX == BOARD_RGHT+1) {
6776 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
6777 if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
6778 if(boards[0][fromY][BOARD_WIDTH-2] == 0) boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
6781 boards[0][fromY][fromX] = gatingPiece;
6782 DrawPosition(FALSE, boards[currentMove]);
6788 if(toX < 0 || toY < 0) return;
6789 pup = boards[currentMove][toY][toX];
6791 /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
6792 if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ) {
6793 if( pup != EmptySquare ) return;
6794 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
6795 if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n",
6796 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
6797 // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
6798 if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
6799 fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
6800 while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++;
6804 /* [HGM] always test for legality, to get promotion info */
6805 moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6806 fromY, fromX, toY, toX, promoChar);
6808 if(fromY == DROP_RANK && fromX == EmptySquare && (gameMode == AnalyzeMode || gameMode == EditGame)) moveType = NormalMove;
6810 /* [HGM] but possibly ignore an IllegalMove result */
6811 if (appData.testLegality) {
6812 if (moveType == IllegalMove || moveType == ImpossibleMove) {
6813 DisplayMoveError(_("Illegal move"));
6818 if(doubleClick && gameMode == AnalyzeMode) { // [HGM] exclude: move entered with double-click on from square is for exclusion, not playing
6819 if(ExcludeOneMove(fromY, fromX, toY, toX, promoChar, '*')) // toggle
6820 ClearPremoveHighlights(); // was included
6821 else ClearHighlights(), SetPremoveHighlights(ff, rf, ft, rt); // exclusion indicated by premove highlights
6825 FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
6828 /* Common tail of UserMoveEvent and DropMenuEvent */
6830 FinishMove (ChessMove moveType, int fromX, int fromY, int toX, int toY, int promoChar)
6834 if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) && promoChar != NULLCHAR) {
6835 // [HGM] superchess: suppress promotions to non-available piece (but P always allowed)
6836 int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
6837 if(WhiteOnMove(currentMove)) {
6838 if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
6840 if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
6844 /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
6845 move type in caller when we know the move is a legal promotion */
6846 if(moveType == NormalMove && promoChar)
6847 moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
6849 /* [HGM] <popupFix> The following if has been moved here from
6850 UserMoveEvent(). Because it seemed to belong here (why not allow
6851 piece drops in training games?), and because it can only be
6852 performed after it is known to what we promote. */
6853 if (gameMode == Training) {
6854 /* compare the move played on the board to the next move in the
6855 * game. If they match, display the move and the opponent's response.
6856 * If they don't match, display an error message.
6860 CopyBoard(testBoard, boards[currentMove]);
6861 ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
6863 if (CompareBoards(testBoard, boards[currentMove+1])) {
6864 ForwardInner(currentMove+1);
6866 /* Autoplay the opponent's response.
6867 * if appData.animate was TRUE when Training mode was entered,
6868 * the response will be animated.
6870 saveAnimate = appData.animate;
6871 appData.animate = animateTraining;
6872 ForwardInner(currentMove+1);
6873 appData.animate = saveAnimate;
6875 /* check for the end of the game */
6876 if (currentMove >= forwardMostMove) {
6877 gameMode = PlayFromGameFile;
6879 SetTrainingModeOff();
6880 DisplayInformation(_("End of game"));
6883 DisplayError(_("Incorrect move"), 0);
6888 /* Ok, now we know that the move is good, so we can kill
6889 the previous line in Analysis Mode */
6890 if ((gameMode == AnalyzeMode || gameMode == EditGame || gameMode == PlayFromGameFile && appData.variations && shiftKey)
6891 && currentMove < forwardMostMove) {
6892 if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
6893 else forwardMostMove = currentMove;
6898 /* If we need the chess program but it's dead, restart it */
6899 ResurrectChessProgram();
6901 /* A user move restarts a paused game*/
6905 thinkOutput[0] = NULLCHAR;
6907 MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
6909 if(Adjudicate(NULL)) { // [HGM] adjudicate: take care of automatic game end
6910 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6914 if (gameMode == BeginningOfGame) {
6915 if (appData.noChessProgram) {
6916 gameMode = EditGame;
6920 gameMode = MachinePlaysBlack;
6923 snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
6925 if (first.sendName) {
6926 snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
6927 SendToProgram(buf, &first);
6934 /* Relay move to ICS or chess engine */
6935 if (appData.icsActive) {
6936 if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
6937 gameMode == IcsExamining) {
6938 if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
6939 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
6941 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6943 // also send plain move, in case ICS does not understand atomic claims
6944 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6948 if (first.sendTime && (gameMode == BeginningOfGame ||
6949 gameMode == MachinePlaysWhite ||
6950 gameMode == MachinePlaysBlack)) {
6951 SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
6953 if (gameMode != EditGame && gameMode != PlayFromGameFile && gameMode != AnalyzeMode) {
6954 // [HGM] book: if program might be playing, let it use book
6955 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
6956 first.maybeThinking = TRUE;
6957 } else if(fromY == DROP_RANK && fromX == EmptySquare) {
6958 if(!first.useSetboard) SendToProgram("undo\n", &first); // kludge to change stm in engines that do not support setboard
6959 SendBoard(&first, currentMove+1);
6960 if(second.analyzing) {
6961 if(!second.useSetboard) SendToProgram("undo\n", &second);
6962 SendBoard(&second, currentMove+1);
6965 SendMoveToProgram(forwardMostMove-1, &first);
6966 if(second.analyzing) SendMoveToProgram(forwardMostMove-1, &second);
6968 if (currentMove == cmailOldMove + 1) {
6969 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
6973 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6977 if(appData.testLegality)
6978 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
6984 if (WhiteOnMove(currentMove)) {
6985 GameEnds(BlackWins, "Black mates", GE_PLAYER);
6987 GameEnds(WhiteWins, "White mates", GE_PLAYER);
6991 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
6996 case MachinePlaysBlack:
6997 case MachinePlaysWhite:
6998 /* disable certain menu options while machine is thinking */
6999 SetMachineThinkingEnables();
7006 userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
7007 promoDefaultAltered = FALSE; // [HGM] fall back on default choice
7009 if(bookHit) { // [HGM] book: simulate book reply
7010 static char bookMove[MSG_SIZ]; // a bit generous?
7012 programStats.nodes = programStats.depth = programStats.time =
7013 programStats.score = programStats.got_only_move = 0;
7014 sprintf(programStats.movelist, "%s (xbook)", bookHit);
7016 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
7017 strcat(bookMove, bookHit);
7018 HandleMachineMove(bookMove, &first);
7024 MarkByFEN(char *fen)
7027 if(!appData.markers || !appData.highlightDragging) return;
7028 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) legal[r][f] = 0;
7029 r=BOARD_HEIGHT-1; f=BOARD_LEFT;
7033 if(*fen == 'M') legal[r][f] = 2; else // request promotion choice
7034 if(*fen >= 'A' && *fen <= 'Z') legal[r][f] = 1; else
7035 if(*fen >= 'a' && *fen <= 'z') *fen += 'A' - 'a';
7036 if(*fen == '/' && f > BOARD_LEFT) f = BOARD_LEFT, r--; else
7037 if(*fen == 'T') marker[r][f++] = 0; else
7038 if(*fen == 'Y') marker[r][f++] = 1; else
7039 if(*fen == 'G') marker[r][f++] = 3; else
7040 if(*fen == 'B') marker[r][f++] = 4; else
7041 if(*fen == 'C') marker[r][f++] = 5; else
7042 if(*fen == 'M') marker[r][f++] = 6; else
7043 if(*fen == 'W') marker[r][f++] = 7; else
7044 if(*fen == 'D') marker[r][f++] = 8; else
7045 if(*fen == 'R') marker[r][f++] = 2; else {
7046 while(*fen <= '9' && *fen >= '0') s = 10*s + *fen++ - '0';
7049 while(f >= BOARD_RGHT) f -= BOARD_RGHT - BOARD_LEFT, r--;
7053 DrawPosition(TRUE, NULL);
7057 Mark (Board board, int flags, ChessMove kind, int rf, int ff, int rt, int ft, VOIDSTAR closure)
7059 typedef char Markers[BOARD_RANKS][BOARD_FILES];
7060 Markers *m = (Markers *) closure;
7061 if(rf == fromY && ff == fromX)
7062 (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
7063 || kind == WhiteCapturesEnPassant
7064 || kind == BlackCapturesEnPassant);
7065 else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
7069 MarkTargetSquares (int clear)
7072 if(clear) { // no reason to ever suppress clearing
7073 for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) sum += marker[y][x], marker[y][x] = 0;
7074 if(!sum) return; // nothing was cleared,no redraw needed
7077 if(!appData.markers || !appData.highlightDragging || appData.icsActive && gameInfo.variant < VariantShogi ||
7078 !appData.testLegality || gameMode == EditPosition) return;
7079 GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker, EmptySquare);
7080 if(PosFlags(0) & F_MANDATORY_CAPTURE) {
7081 for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
7083 for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
7086 DrawPosition(FALSE, NULL);
7090 Explode (Board board, int fromX, int fromY, int toX, int toY)
7092 if(gameInfo.variant == VariantAtomic &&
7093 (board[toY][toX] != EmptySquare || // capture?
7094 toX != fromX && (board[fromY][fromX] == WhitePawn || // e.p. ?
7095 board[fromY][fromX] == BlackPawn )
7097 AnimateAtomicCapture(board, fromX, fromY, toX, toY);
7103 ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
7106 CanPromote (ChessSquare piece, int y)
7108 if(gameMode == EditPosition) return FALSE; // no promotions when editing position
7109 // some variants have fixed promotion piece, no promotion at all, or another selection mechanism
7110 if(gameInfo.variant == VariantShogi || gameInfo.variant == VariantXiangqi ||
7111 gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat ||
7112 gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
7113 gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN) return FALSE;
7114 return (piece == BlackPawn && y == 1 ||
7115 piece == WhitePawn && y == BOARD_HEIGHT-2 ||
7116 piece == BlackLance && y == 1 ||
7117 piece == WhiteLance && y == BOARD_HEIGHT-2 );
7121 HoverEvent (int hiX, int hiY, int x, int y)
7123 static char baseMarker[BOARD_RANKS][BOARD_FILES], baseLegal[BOARD_RANKS][BOARD_FILES];
7125 if(!first.highlight) return;
7126 if(hiX == -1 && hiY == -1 && x == fromX && y == fromY) // record markings
7127 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7128 baseMarker[r][f] = marker[r][f], baseLegal[r][f] = legal[r][f];
7129 else if(hiX != x || hiY != y) {
7130 // [HGM] lift: entered new to-square; redraw arrow, and inform engine
7131 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7132 marker[r][f] = baseMarker[r][f], legal[r][f] = baseLegal[r][f];
7133 if((marker[y][x] == 2 || marker[y][x] == 6) && legal[y][x]) {
7135 snprintf(buf, MSG_SIZ, "hover %c%d\n", x + AAA, y + ONE - '0');
7136 SendToProgram(buf, &first);
7138 SetHighlights(fromX, fromY, x, y);
7142 void ReportClick(char *action, int x, int y)
7144 char buf[MSG_SIZ]; // Inform engine of what user does
7146 if(action[0] == 'l') // mark any target square of a lifted piece as legal to-square, clear markers
7147 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) legal[r][f] = 1, marker[r][f] = 0;
7148 if(!first.highlight || gameMode == EditPosition) return;
7149 snprintf(buf, MSG_SIZ, "%s %c%d%s\n", action, x+AAA, y+ONE-'0', controlKey && action[0]=='p' ? "," : "");
7150 SendToProgram(buf, &first);
7154 LeftClick (ClickType clickType, int xPix, int yPix)
7157 Boolean saveAnimate;
7158 static int second = 0, promotionChoice = 0, clearFlag = 0, sweepSelecting = 0;
7159 char promoChoice = NULLCHAR;
7161 static TimeMark lastClickTime, prevClickTime;
7163 if(SeekGraphClick(clickType, xPix, yPix, 0)) return;
7165 prevClickTime = lastClickTime; GetTimeMark(&lastClickTime);
7167 if (clickType == Press) ErrorPopDown();
7168 lastClickType = clickType, lastLeftX = xPix, lastLeftY = yPix; // [HGM] alien: remember state
7170 x = EventToSquare(xPix, BOARD_WIDTH);
7171 y = EventToSquare(yPix, BOARD_HEIGHT);
7172 if (!flipView && y >= 0) {
7173 y = BOARD_HEIGHT - 1 - y;
7175 if (flipView && x >= 0) {
7176 x = BOARD_WIDTH - 1 - x;
7179 if(promoSweep != EmptySquare) { // up-click during sweep-select of promo-piece
7180 defaultPromoChoice = promoSweep;
7181 promoSweep = EmptySquare; // terminate sweep
7182 promoDefaultAltered = TRUE;
7183 if(!selectFlag && !sweepSelecting && (x != toX || y != toY)) x = fromX, y = fromY; // and fake up-click on same square if we were still selecting
7186 if(promotionChoice) { // we are waiting for a click to indicate promotion piece
7187 if(clickType == Release) return; // ignore upclick of click-click destination
7188 promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
7189 if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
7190 if(gameInfo.holdingsWidth &&
7191 (WhiteOnMove(currentMove)
7192 ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y >= 0
7193 : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT) ) {
7194 // click in right holdings, for determining promotion piece
7195 ChessSquare p = boards[currentMove][y][x];
7196 if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
7197 if(p == WhitePawn || p == BlackPawn) p = EmptySquare; // [HGM] Pawns could be valid as deferral
7198 if(p != EmptySquare || gameInfo.variant == VariantGrand && toY != 0 && toY != BOARD_HEIGHT-1) { // [HGM] grand: empty square means defer
7199 FinishMove(NormalMove, fromX, fromY, toX, toY, p==EmptySquare ? NULLCHAR : ToLower(PieceToChar(p)));
7204 DrawPosition(FALSE, boards[currentMove]);
7208 /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
7209 if(clickType == Press
7210 && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
7211 || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
7212 || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
7215 if(gotPremove && x == premoveFromX && y == premoveFromY && clickType == Release) {
7216 // could be static click on premove from-square: abort premove
7218 ClearPremoveHighlights();
7221 if(clickType == Press && fromX == x && fromY == y && promoDefaultAltered && SubtractTimeMarks(&lastClickTime, &prevClickTime) >= 200)
7222 fromX = fromY = -1; // second click on piece after altering default promo piece treated as first click
7224 if(!promoDefaultAltered) { // determine default promotion piece, based on the side the user is moving for
7225 int side = (gameMode == IcsPlayingWhite || gameMode == MachinePlaysBlack ||
7226 gameMode != MachinePlaysWhite && gameMode != IcsPlayingBlack && WhiteOnMove(currentMove));
7227 defaultPromoChoice = DefaultPromoChoice(side);
7230 autoQueen = appData.alwaysPromoteToQueen;
7234 gatingPiece = EmptySquare;
7235 if (clickType != Press) {
7236 if(dragging) { // [HGM] from-square must have been reset due to game end since last press
7237 DragPieceEnd(xPix, yPix); dragging = 0;
7238 DrawPosition(FALSE, NULL);
7242 doubleClick = FALSE;
7243 if(gameMode == AnalyzeMode && (pausing || controlKey) && first.excludeMoves) { // use pause state to exclude moves
7244 doubleClick = TRUE; gatingPiece = boards[currentMove][y][x];
7246 fromX = x; fromY = y; toX = toY = -1;
7247 if(!appData.oneClick || !OnlyMove(&x, &y, FALSE) ||
7248 // even if only move, we treat as normal when this would trigger a promotion popup, to allow sweep selection
7249 appData.sweepSelect && CanPromote(boards[currentMove][fromY][fromX], fromY) && originalY != y) {
7251 if (OKToStartUserMove(fromX, fromY)) {
7253 ReportClick("lift", x, y);
7254 MarkTargetSquares(0);
7255 if(gameMode == EditPosition && controlKey) gatingPiece = boards[currentMove][fromY][fromX];
7256 DragPieceBegin(xPix, yPix, FALSE); dragging = 1;
7257 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][fromY][fromX], fromY)) {
7258 promoSweep = defaultPromoChoice;
7259 selectFlag = 0; lastX = xPix; lastY = yPix;
7260 Sweep(0); // Pawn that is going to promote: preview promotion piece
7261 DisplayMessage("", _("Pull pawn backwards to under-promote"));
7263 if (appData.highlightDragging) {
7264 SetHighlights(fromX, fromY, -1, -1);
7268 } else fromX = fromY = -1;
7274 if (clickType == Press && gameMode != EditPosition) {
7279 // ignore off-board to clicks
7280 if(y < 0 || x < 0) return;
7282 /* Check if clicking again on the same color piece */
7283 fromP = boards[currentMove][fromY][fromX];
7284 toP = boards[currentMove][y][x];
7285 frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess;
7286 if ((WhitePawn <= fromP && fromP <= WhiteKing &&
7287 WhitePawn <= toP && toP <= WhiteKing &&
7288 !(fromP == WhiteKing && toP == WhiteRook && frc) &&
7289 !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
7290 (BlackPawn <= fromP && fromP <= BlackKing &&
7291 BlackPawn <= toP && toP <= BlackKing &&
7292 !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
7293 !(fromP == BlackKing && toP == BlackRook && frc))) {
7294 /* Clicked again on same color piece -- changed his mind */
7295 second = (x == fromX && y == fromY);
7296 if(second && gameMode == AnalyzeMode && SubtractTimeMarks(&lastClickTime, &prevClickTime) < 200) {
7297 second = FALSE; // first double-click rather than scond click
7298 doubleClick = first.excludeMoves; // used by UserMoveEvent to recognize exclude moves
7300 promoDefaultAltered = FALSE;
7301 MarkTargetSquares(1);
7302 if(!(second && appData.oneClick && OnlyMove(&x, &y, TRUE))) {
7303 if (appData.highlightDragging) {
7304 SetHighlights(x, y, -1, -1);
7308 if (OKToStartUserMove(x, y)) {
7309 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
7310 (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
7311 y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
7312 gatingPiece = boards[currentMove][fromY][fromX];
7313 else gatingPiece = doubleClick ? fromP : EmptySquare;
7315 fromY = y; dragging = 1;
7316 ReportClick("lift", x, y);
7317 MarkTargetSquares(0);
7318 DragPieceBegin(xPix, yPix, FALSE);
7319 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) {
7320 promoSweep = defaultPromoChoice;
7321 selectFlag = 0; lastX = xPix; lastY = yPix;
7322 Sweep(0); // Pawn that is going to promote: preview promotion piece
7326 if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
7329 // ignore clicks on holdings
7330 if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
7333 if (clickType == Release && x == fromX && y == fromY) {
7334 DragPieceEnd(xPix, yPix); dragging = 0;
7336 // a deferred attempt to click-click move an empty square on top of a piece
7337 boards[currentMove][y][x] = EmptySquare;
7339 DrawPosition(FALSE, boards[currentMove]);
7340 fromX = fromY = -1; clearFlag = 0;
7343 if (appData.animateDragging) {
7344 /* Undo animation damage if any */
7345 DrawPosition(FALSE, NULL);
7347 if (second || sweepSelecting) {
7348 /* Second up/down in same square; just abort move */
7349 if(sweepSelecting) DrawPosition(FALSE, boards[currentMove]);
7350 second = sweepSelecting = 0;
7352 gatingPiece = EmptySquare;
7353 MarkTargetSquares(1);
7356 ClearPremoveHighlights();
7358 /* First upclick in same square; start click-click mode */
7359 SetHighlights(x, y, -1, -1);
7366 if(gameMode != EditPosition && !appData.testLegality && !legal[y][x]) {
7367 if(dragging) DragPieceEnd(xPix, yPix), dragging = 0;
7368 DisplayMessage(_("only marked squares are legal"),"");
7369 DrawPosition(TRUE, NULL);
7370 return; // ignore to-click
7373 /* we now have a different from- and (possibly off-board) to-square */
7374 /* Completed move */
7375 if(!sweepSelecting) {
7378 } else sweepSelecting = 0; // this must be the up-click corresponding to the down-click that started the sweep
7380 saveAnimate = appData.animate;
7381 if (clickType == Press) {
7382 if(gameMode == EditPosition && boards[currentMove][fromY][fromX] == EmptySquare) {
7383 // must be Edit Position mode with empty-square selected
7384 fromX = x; fromY = y; DragPieceBegin(xPix, yPix, FALSE); dragging = 1; // consider this a new attempt to drag
7385 if(x >= BOARD_LEFT && x < BOARD_RGHT) clearFlag = 1; // and defer click-click move of empty-square to up-click
7388 if(legal[y][x] == 2 || HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, FALSE)) {
7389 if(appData.sweepSelect) {
7390 ChessSquare piece = boards[currentMove][fromY][fromX];
7391 promoSweep = defaultPromoChoice;
7392 if(PieceToChar(PROMOTED piece) == '+') promoSweep = PROMOTED piece;
7393 selectFlag = 0; lastX = xPix; lastY = yPix;
7394 Sweep(0); // Pawn that is going to promote: preview promotion piece
7396 DisplayMessage("", _("Pull pawn backwards to under-promote"));
7397 MarkTargetSquares(1);
7399 return; // promo popup appears on up-click
7401 /* Finish clickclick move */
7402 if (appData.animate || appData.highlightLastMove) {
7403 SetHighlights(fromX, fromY, toX, toY);
7409 // [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
7410 /* Finish drag move */
7411 if (appData.highlightLastMove) {
7412 SetHighlights(fromX, fromY, toX, toY);
7417 DragPieceEnd(xPix, yPix); dragging = 0;
7418 /* Don't animate move and drag both */
7419 appData.animate = FALSE;
7422 // moves into holding are invalid for now (except in EditPosition, adapting to-square)
7423 if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
7424 ChessSquare piece = boards[currentMove][fromY][fromX];
7425 if(gameMode == EditPosition && piece != EmptySquare &&
7426 fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
7429 if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
7430 n = PieceToNumber(piece - (int)BlackPawn);
7431 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
7432 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
7433 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
7435 if(x == BOARD_RGHT+1 && piece < BlackPawn) {
7436 n = PieceToNumber(piece);
7437 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
7438 boards[currentMove][n][BOARD_WIDTH-1] = piece;
7439 boards[currentMove][n][BOARD_WIDTH-2]++;
7441 boards[currentMove][fromY][fromX] = EmptySquare;
7445 MarkTargetSquares(1);
7446 DrawPosition(TRUE, boards[currentMove]);
7450 // off-board moves should not be highlighted
7451 if(x < 0 || y < 0) ClearHighlights();
7452 else ReportClick("put", x, y);
7454 if(gatingPiece != EmptySquare && gameInfo.variant == VariantSChess) promoChoice = ToLower(PieceToChar(gatingPiece));
7456 if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, appData.sweepSelect)) {
7457 SetHighlights(fromX, fromY, toX, toY);
7458 MarkTargetSquares(1);
7459 if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
7460 // [HGM] super: promotion to captured piece selected from holdings
7461 ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
7462 promotionChoice = TRUE;
7463 // kludge follows to temporarily execute move on display, without promoting yet
7464 boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
7465 boards[currentMove][toY][toX] = p;
7466 DrawPosition(FALSE, boards[currentMove]);
7467 boards[currentMove][fromY][fromX] = p; // take back, but display stays
7468 boards[currentMove][toY][toX] = q;
7469 DisplayMessage("Click in holdings to choose piece", "");
7474 int oldMove = currentMove;
7475 UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
7476 if (!appData.highlightLastMove || gotPremove) ClearHighlights();
7477 if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
7478 if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
7479 Explode(boards[currentMove-1], fromX, fromY, toX, toY))
7480 DrawPosition(TRUE, boards[currentMove]);
7481 MarkTargetSquares(1);
7484 appData.animate = saveAnimate;
7485 if (appData.animate || appData.animateDragging) {
7486 /* Undo animation damage if needed */
7487 DrawPosition(FALSE, NULL);
7492 RightClick (ClickType action, int x, int y, int *fromX, int *fromY)
7493 { // front-end-free part taken out of PieceMenuPopup
7494 int whichMenu; int xSqr, ySqr;
7496 if(seekGraphUp) { // [HGM] seekgraph
7497 if(action == Press) SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
7498 if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
7502 if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
7503 && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
7504 if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
7505 if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
7506 if(action == Press) {
7507 originalFlip = flipView;
7508 flipView = !flipView; // temporarily flip board to see game from partners perspective
7509 DrawPosition(TRUE, partnerBoard);
7510 DisplayMessage(partnerStatus, "");
7512 } else if(action == Release) {
7513 flipView = originalFlip;
7514 DrawPosition(TRUE, boards[currentMove]);
7520 xSqr = EventToSquare(x, BOARD_WIDTH);
7521 ySqr = EventToSquare(y, BOARD_HEIGHT);
7522 if (action == Release) {
7523 if(pieceSweep != EmptySquare) {
7524 EditPositionMenuEvent(pieceSweep, toX, toY);
7525 pieceSweep = EmptySquare;
7526 } else UnLoadPV(); // [HGM] pv
7528 if (action != Press) return -2; // return code to be ignored
7531 if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;
7533 if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;
7534 if (xSqr < 0 || ySqr < 0) return -1;
7535 if(appData.pieceMenu) { whichMenu = 0; break; } // edit-position menu
7536 pieceSweep = shiftKey ? BlackPawn : WhitePawn; // [HGM] sweep: prepare selecting piece by mouse sweep
7537 toX = xSqr; toY = ySqr; lastX = x, lastY = y;
7538 if(flipView) toX = BOARD_WIDTH - 1 - toX; else toY = BOARD_HEIGHT - 1 - toY;
7542 if(!appData.icsEngineAnalyze) return -1;
7543 case IcsPlayingWhite:
7544 case IcsPlayingBlack:
7545 if(!appData.zippyPlay) goto noZip;
7548 case MachinePlaysWhite:
7549 case MachinePlaysBlack:
7550 case TwoMachinesPlay: // [HGM] pv: use for showing PV
7551 if (!appData.dropMenu) {
7553 return 2; // flag front-end to grab mouse events
7555 if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
7556 gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
7559 if (xSqr < 0 || ySqr < 0) return -1;
7560 if (!appData.dropMenu || appData.testLegality &&
7561 gameInfo.variant != VariantBughouse &&
7562 gameInfo.variant != VariantCrazyhouse) return -1;
7563 whichMenu = 1; // drop menu
7569 if (((*fromX = xSqr) < 0) ||
7570 ((*fromY = ySqr) < 0)) {
7571 *fromX = *fromY = -1;
7575 *fromX = BOARD_WIDTH - 1 - *fromX;
7577 *fromY = BOARD_HEIGHT - 1 - *fromY;
7583 SendProgramStatsToFrontend (ChessProgramState * cps, ChessProgramStats * cpstats)
7585 // char * hint = lastHint;
7586 FrontEndProgramStats stats;
7588 stats.which = cps == &first ? 0 : 1;
7589 stats.depth = cpstats->depth;
7590 stats.nodes = cpstats->nodes;
7591 stats.score = cpstats->score;
7592 stats.time = cpstats->time;
7593 stats.pv = cpstats->movelist;
7594 stats.hint = lastHint;
7595 stats.an_move_index = 0;
7596 stats.an_move_count = 0;
7598 if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
7599 stats.hint = cpstats->move_name;
7600 stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
7601 stats.an_move_count = cpstats->nr_moves;
7604 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
7606 SetProgramStats( &stats );
7610 ClearEngineOutputPane (int which)
7612 static FrontEndProgramStats dummyStats;
7613 dummyStats.which = which;
7614 dummyStats.pv = "#";
7615 SetProgramStats( &dummyStats );
7618 #define MAXPLAYERS 500
7621 TourneyStandings (int display)
7623 int i, w, b, color, wScore, bScore, dummy, nr=0, nPlayers=0;
7624 int score[MAXPLAYERS], ranking[MAXPLAYERS], points[MAXPLAYERS], games[MAXPLAYERS];
7625 char result, *p, *names[MAXPLAYERS];
7627 if(appData.tourneyType < 0 && !strchr(appData.results, '*'))
7628 return strdup(_("Swiss tourney finished")); // standings of Swiss yet TODO
7629 names[0] = p = strdup(appData.participants);
7630 while(p = strchr(p, '\n')) *p++ = NULLCHAR, names[++nPlayers] = p; // count participants
7632 for(i=0; i<nPlayers; i++) score[i] = games[i] = 0;
7634 while(result = appData.results[nr]) {
7635 color = Pairing(nr, nPlayers, &w, &b, &dummy);
7636 if(!(color ^ matchGame & 1)) { dummy = w; w = b; b = dummy; }
7637 wScore = bScore = 0;
7639 case '+': wScore = 2; break;
7640 case '-': bScore = 2; break;
7641 case '=': wScore = bScore = 1; break;
7643 case '*': return strdup("busy"); // tourney not finished
7651 if(appData.tourneyType > 0) nPlayers = appData.tourneyType; // in gauntlet, list only gauntlet engine(s)
7652 for(w=0; w<nPlayers; w++) {
7654 for(i=0; i<nPlayers; i++) if(score[i] > bScore) bScore = score[i], b = i;
7655 ranking[w] = b; points[w] = bScore; score[b] = -2;
7657 p = malloc(nPlayers*34+1);
7658 for(w=0; w<nPlayers && w<display; w++)
7659 sprintf(p+34*w, "%2d. %5.1f/%-3d %-19.19s\n", w+1, points[w]/2., games[ranking[w]], names[ranking[w]]);
7665 Count (Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
7666 { // count all piece types
7668 *nB = *nW = *wStale = *bStale = *bishopColor = 0;
7669 for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
7670 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
7673 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
7674 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
7675 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
7676 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
7677 p == BlackBishop || p == BlackFerz || p == BlackAlfil )
7678 *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
7683 SufficientDefence (int pCnt[], int side, int nMine, int nHis)
7685 int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
7686 int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
7688 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
7689 if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
7690 if(myPawns == 2 && nMine == 3) // KPP
7691 return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
7692 if(myPawns == 1 && nMine == 2) // KP
7693 return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
7694 if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
7695 return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
7696 if(myPawns) return FALSE;
7697 if(pCnt[WhiteRook+side])
7698 return pCnt[BlackRook-side] ||
7699 pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
7700 pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
7701 pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
7702 if(pCnt[WhiteCannon+side]) {
7703 if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
7704 return majorDefense || pCnt[BlackAlfil-side] >= 2;
7706 if(pCnt[WhiteKnight+side])
7707 return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
7712 MatingPotential (int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
7714 VariantClass v = gameInfo.variant;
7716 if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
7717 if(v == VariantShatranj) return TRUE; // always winnable through baring
7718 if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
7719 if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
7721 if(v == VariantXiangqi) {
7722 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
7724 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
7725 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
7726 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
7727 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
7728 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
7729 if(stale) // we have at least one last-rank P plus perhaps C
7730 return majors // KPKX
7731 || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
7733 return pCnt[WhiteFerz+side] // KCAK
7734 || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
7735 || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
7736 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
7738 } else if(v == VariantKnightmate) {
7739 if(nMine == 1) return FALSE;
7740 if(nMine == 2 && nHis == 1 && pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side] + pCnt[WhiteKnight+side]) return FALSE; // KBK is only draw
7741 } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
7742 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
7744 if(nMine == 1) return FALSE; // bare King
7745 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
7746 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
7747 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
7748 // by now we have King + 1 piece (or multiple Bishops on the same color)
7749 if(pCnt[WhiteKnight+side])
7750 return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
7751 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
7752 || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
7754 return (pCnt[BlackKnight-side]); // KBKN, KFKN
7755 if(pCnt[WhiteAlfil+side])
7756 return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
7757 if(pCnt[WhiteWazir+side])
7758 return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
7765 CompareWithRights (Board b1, Board b2)
7768 if(!CompareBoards(b1, b2)) return FALSE;
7769 if(b1[EP_STATUS] != b2[EP_STATUS]) return FALSE;
7770 /* compare castling rights */
7771 if( b1[CASTLING][2] != b2[CASTLING][2] && (b2[CASTLING][0] != NoRights || b2[CASTLING][1] != NoRights) )
7772 rights++; /* King lost rights, while rook still had them */
7773 if( b1[CASTLING][2] != NoRights ) { /* king has rights */
7774 if( b1[CASTLING][0] != b2[CASTLING][0] || b1[CASTLING][1] != b2[CASTLING][1] )
7775 rights++; /* but at least one rook lost them */
7777 if( b1[CASTLING][5] != b1[CASTLING][5] && (b2[CASTLING][3] != NoRights || b2[CASTLING][4] != NoRights) )
7779 if( b1[CASTLING][5] != NoRights ) {
7780 if( b1[CASTLING][3] != b2[CASTLING][3] || b1[CASTLING][4] != b2[CASTLING][4] )
7787 Adjudicate (ChessProgramState *cps)
7788 { // [HGM] some adjudications useful with buggy engines
7789 // [HGM] adjudicate: made into separate routine, which now can be called after every move
7790 // In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
7791 // Actually ending the game is now based on the additional internal condition canAdjudicate.
7792 // Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
7793 int k, drop, count = 0; static int bare = 1;
7794 ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
7795 Boolean canAdjudicate = !appData.icsActive;
7797 // most tests only when we understand the game, i.e. legality-checking on
7798 if( appData.testLegality )
7799 { /* [HGM] Some more adjudications for obstinate engines */
7800 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+1], i;
7801 static int moveCount = 6;
7803 char *reason = NULL;
7805 /* Count what is on board. */
7806 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
7808 /* Some material-based adjudications that have to be made before stalemate test */
7809 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
7810 // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
7811 boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
7812 if(canAdjudicate && appData.checkMates) {
7814 SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7815 GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
7816 "Xboard adjudication: King destroyed", GE_XBOARD );
7821 /* Bare King in Shatranj (loses) or Losers (wins) */
7822 if( nrW == 1 || nrB == 1) {
7823 if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
7824 boards[forwardMostMove][EP_STATUS] = EP_WINS; // mark as win, so it becomes claimable
7825 if(canAdjudicate && appData.checkMates) {
7827 SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
7828 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7829 "Xboard adjudication: Bare king", GE_XBOARD );
7833 if( gameInfo.variant == VariantShatranj && --bare < 0)
7835 boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
7836 if(canAdjudicate && appData.checkMates) {
7837 /* but only adjudicate if adjudication enabled */
7839 SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7840 GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
7841 "Xboard adjudication: Bare king", GE_XBOARD );
7848 // don't wait for engine to announce game end if we can judge ourselves
7849 switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
7851 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
7852 int i, checkCnt = 0; // (should really be done by making nr of checks part of game state)
7853 for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
7854 if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
7857 reason = "Xboard adjudication: 3rd check";
7858 boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
7868 reason = "Xboard adjudication: Stalemate";
7869 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
7870 boards[forwardMostMove][EP_STATUS] = EP_STALEMATE; // default result for stalemate is draw
7871 if(gameInfo.variant == VariantLosers || gameInfo.variant == VariantGiveaway) // [HGM] losers:
7872 boards[forwardMostMove][EP_STATUS] = EP_WINS; // in these variants stalemated is always a win
7873 else if(gameInfo.variant == VariantSuicide) // in suicide it depends
7874 boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
7875 ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
7876 EP_CHECKMATE : EP_WINS);
7877 else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi)
7878 boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
7882 reason = "Xboard adjudication: Checkmate";
7883 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
7884 if(gameInfo.variant == VariantShogi) {
7885 if(forwardMostMove > backwardMostMove
7886 && moveList[forwardMostMove-1][1] == '@'
7887 && CharToPiece(ToUpper(moveList[forwardMostMove-1][0])) == WhitePawn) {
7888 reason = "XBoard adjudication: pawn-drop mate";
7889 boards[forwardMostMove][EP_STATUS] = EP_WINS;
7895 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
7897 result = GameIsDrawn; break;
7899 result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
7901 result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
7905 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
7907 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7908 GameEnds( result, reason, GE_XBOARD );
7912 /* Next absolutely insufficient mating material. */
7913 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
7914 !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
7915 { /* includes KBK, KNK, KK of KBKB with like Bishops */
7917 /* always flag draws, for judging claims */
7918 boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
7920 if(canAdjudicate && appData.materialDraws) {
7921 /* but only adjudicate them if adjudication enabled */
7922 if(engineOpponent) {
7923 SendToProgram("force\n", engineOpponent); // suppress reply
7924 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
7926 GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
7931 /* Then some trivial draws (only adjudicate, cannot be claimed) */
7932 if(gameInfo.variant == VariantXiangqi ?
7933 SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
7935 ( nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
7936 || nr[WhiteQueen] && nr[BlackQueen]==1 /* KQKQ */
7937 || nr[WhiteKnight]==2 || nr[BlackKnight]==2 /* KNNK */
7938 || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
7940 if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
7941 { /* if the first 3 moves do not show a tactical win, declare draw */
7942 if(engineOpponent) {
7943 SendToProgram("force\n", engineOpponent); // suppress reply
7944 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7946 GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
7949 } else moveCount = 6;
7952 // Repetition draws and 50-move rule can be applied independently of legality testing
7954 /* Check for rep-draws */
7956 drop = gameInfo.holdingsSize && (gameInfo.variant != VariantSuper && gameInfo.variant != VariantSChess
7957 && gameInfo.variant != VariantGreat && gameInfo.variant != VariantGrand);
7958 for(k = forwardMostMove-2;
7959 k>=backwardMostMove && k>=forwardMostMove-100 && (drop ||
7960 (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
7961 (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE);
7964 if(CompareBoards(boards[k], boards[forwardMostMove])) {
7965 /* compare castling rights */
7966 if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
7967 (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
7968 rights++; /* King lost rights, while rook still had them */
7969 if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
7970 if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
7971 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
7972 rights++; /* but at least one rook lost them */
7974 if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
7975 (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
7977 if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
7978 if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
7979 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
7982 if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
7983 && appData.drawRepeats > 1) {
7984 /* adjudicate after user-specified nr of repeats */
7985 int result = GameIsDrawn;
7986 char *details = "XBoard adjudication: repetition draw";
7987 if((gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi) && appData.testLegality) {
7988 // [HGM] xiangqi: check for forbidden perpetuals
7989 int m, ourPerpetual = 1, hisPerpetual = 1;
7990 for(m=forwardMostMove; m>k; m-=2) {
7991 if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
7992 ourPerpetual = 0; // the current mover did not always check
7993 if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
7994 hisPerpetual = 0; // the opponent did not always check
7996 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
7997 ourPerpetual, hisPerpetual);
7998 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
7999 result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8000 details = "Xboard adjudication: perpetual checking";
8002 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
8003 break; // (or we would have caught him before). Abort repetition-checking loop.
8005 if(gameInfo.variant == VariantShogi) { // in Shogi other repetitions are draws
8006 if(BOARD_HEIGHT == 5 && BOARD_RGHT - BOARD_LEFT == 5) { // but in mini-Shogi gote wins!
8008 details = "Xboard adjudication: repetition";
8010 } else // it must be XQ
8011 // Now check for perpetual chases
8012 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
8013 hisPerpetual = PerpetualChase(k, forwardMostMove);
8014 ourPerpetual = PerpetualChase(k+1, forwardMostMove);
8015 if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
8016 static char resdet[MSG_SIZ];
8017 result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8019 snprintf(resdet, MSG_SIZ, "Xboard adjudication: perpetual chasing of %c%c", ourPerpetual>>8, ourPerpetual&255);
8021 if(hisPerpetual && !ourPerpetual) // he is chasing us, but did not repeat yet
8022 break; // Abort repetition-checking loop.
8024 // if neither of us is checking or chasing all the time, or both are, it is draw
8026 if(engineOpponent) {
8027 SendToProgram("force\n", engineOpponent); // suppress reply
8028 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8030 GameEnds( result, details, GE_XBOARD );
8033 if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
8034 boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
8038 /* Now we test for 50-move draws. Determine ply count */
8039 count = forwardMostMove;
8040 /* look for last irreversble move */
8041 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
8043 /* if we hit starting position, add initial plies */
8044 if( count == backwardMostMove )
8045 count -= initialRulePlies;
8046 count = forwardMostMove - count;
8047 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
8048 // adjust reversible move counter for checks in Xiangqi
8049 int i = forwardMostMove - count, inCheck = 0, lastCheck;
8050 if(i < backwardMostMove) i = backwardMostMove;
8051 while(i <= forwardMostMove) {
8052 lastCheck = inCheck; // check evasion does not count
8053 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
8054 if(inCheck || lastCheck) count--; // check does not count
8059 boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
8060 /* this is used to judge if draw claims are legal */
8061 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
8062 if(engineOpponent) {
8063 SendToProgram("force\n", engineOpponent); // suppress reply
8064 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8066 GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
8070 /* if draw offer is pending, treat it as a draw claim
8071 * when draw condition present, to allow engines a way to
8072 * claim draws before making their move to avoid a race
8073 * condition occurring after their move
8075 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
8077 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
8078 p = "Draw claim: 50-move rule";
8079 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
8080 p = "Draw claim: 3-fold repetition";
8081 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
8082 p = "Draw claim: insufficient mating material";
8083 if( p != NULL && canAdjudicate) {
8084 if(engineOpponent) {
8085 SendToProgram("force\n", engineOpponent); // suppress reply
8086 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8088 GameEnds( GameIsDrawn, p, GE_XBOARD );
8093 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
8094 if(engineOpponent) {
8095 SendToProgram("force\n", engineOpponent); // suppress reply
8096 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8098 GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
8105 SendMoveToBookUser (int moveNr, ChessProgramState *cps, int initial)
8106 { // [HGM] book: this routine intercepts moves to simulate book replies
8107 char *bookHit = NULL;
8109 //first determine if the incoming move brings opponent into his book
8110 if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
8111 bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
8112 if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
8113 if(bookHit != NULL && !cps->bookSuspend) {
8114 // make sure opponent is not going to reply after receiving move to book position
8115 SendToProgram("force\n", cps);
8116 cps->bookSuspend = TRUE; // flag indicating it has to be restarted
8118 if(bookHit) setboardSpoiledMachineBlack = FALSE; // suppress 'go' in SendMoveToProgram
8119 if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
8120 // now arrange restart after book miss
8122 // after a book hit we never send 'go', and the code after the call to this routine
8123 // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
8124 char buf[MSG_SIZ], *move = bookHit;
8126 int fromX, fromY, toX, toY;
8130 if (ParseOneMove(bookHit, forwardMostMove, &moveType,
8131 &fromX, &fromY, &toX, &toY, &promoChar)) {
8132 (void) CoordsToAlgebraic(boards[forwardMostMove],
8133 PosFlags(forwardMostMove),
8134 fromY, fromX, toY, toX, promoChar, move);
8136 if(appData.debugMode) fprintf(debugFP, "Book move could not be parsed\n");
8140 snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), move); // force book move into program supposed to play it
8141 SendToProgram(buf, cps);
8142 if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
8143 } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
8144 SendToProgram("go\n", cps);
8145 cps->bookSuspend = FALSE; // after a 'go' we are never suspended
8146 } else { // 'go' might be sent based on 'firstMove' after this routine returns
8147 if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
8148 SendToProgram("go\n", cps);
8149 cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
8151 return bookHit; // notify caller of hit, so it can take action to send move to opponent
8155 LoadError (char *errmess, ChessProgramState *cps)
8156 { // unloads engine and switches back to -ncp mode if it was first
8157 if(cps->initDone) return FALSE;
8158 cps->isr = NULL; // this should suppress further error popups from breaking pipes
8159 DestroyChildProcess(cps->pr, 9 ); // just to be sure
8162 appData.noChessProgram = TRUE;
8163 gameMode = MachinePlaysBlack; ModeHighlight(); // kludge to unmark Machine Black menu
8164 gameMode = BeginningOfGame; ModeHighlight();
8167 if(GetDelayedEvent()) CancelDelayedEvent(), ThawUI(); // [HGM] cancel remaining loading effort scheduled after feature timeout
8168 DisplayMessage("", ""); // erase waiting message
8169 if(errmess) DisplayError(errmess, 0); // announce reason, if given
8174 ChessProgramState *savedState;
8176 DeferredBookMove (void)
8178 if(savedState->lastPing != savedState->lastPong)
8179 ScheduleDelayedEvent(DeferredBookMove, 10);
8181 HandleMachineMove(savedMessage, savedState);
8184 static int savedWhitePlayer, savedBlackPlayer, pairingReceived;
8185 static ChessProgramState *stalledEngine;
8186 static char stashedInputMove[MSG_SIZ];
8189 HandleMachineMove (char *message, ChessProgramState *cps)
8191 char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
8192 char realname[MSG_SIZ];
8193 int fromX, fromY, toX, toY;
8197 int machineWhite, oldError;
8200 if(cps == &pairing && sscanf(message, "%d-%d", &savedWhitePlayer, &savedBlackPlayer) == 2) {
8201 // [HGM] pairing: Mega-hack! Pairing engine also uses this routine (so it could give other WB commands).
8202 if(savedWhitePlayer == 0 || savedBlackPlayer == 0) {
8203 DisplayError(_("Invalid pairing from pairing engine"), 0);
8206 pairingReceived = 1;
8208 return; // Skim the pairing messages here.
8211 oldError = cps->userError; cps->userError = 0;
8213 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
8215 * Kludge to ignore BEL characters
8217 while (*message == '\007') message++;
8220 * [HGM] engine debug message: ignore lines starting with '#' character
8222 if(cps->debug && *message == '#') return;
8225 * Look for book output
8227 if (cps == &first && bookRequested) {
8228 if (message[0] == '\t' || message[0] == ' ') {
8229 /* Part of the book output is here; append it */
8230 strcat(bookOutput, message);
8231 strcat(bookOutput, " \n");
8233 } else if (bookOutput[0] != NULLCHAR) {
8234 /* All of book output has arrived; display it */
8235 char *p = bookOutput;
8236 while (*p != NULLCHAR) {
8237 if (*p == '\t') *p = ' ';
8240 DisplayInformation(bookOutput);
8241 bookRequested = FALSE;
8242 /* Fall through to parse the current output */
8247 * Look for machine move.
8249 if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
8250 (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
8252 if(pausing && !cps->pause) { // for pausing engine that does not support 'pause', we stash its move for processing when we resume.
8253 if(appData.debugMode) fprintf(debugFP, "pause %s engine after move\n", cps->which);
8254 safeStrCpy(stashedInputMove, message, MSG_SIZ);
8255 stalledEngine = cps;
8256 if(appData.ponderNextMove) { // bring opponent out of ponder
8257 if(gameMode == TwoMachinesPlay) {
8258 if(cps->other->pause)
8259 PauseEngine(cps->other);
8261 SendToProgram("easy\n", cps->other);
8268 /* This method is only useful on engines that support ping */
8269 if (cps->lastPing != cps->lastPong) {
8270 if (gameMode == BeginningOfGame) {
8271 /* Extra move from before last new; ignore */
8272 if (appData.debugMode) {
8273 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8276 if (appData.debugMode) {
8277 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8278 cps->which, gameMode);
8281 SendToProgram("undo\n", cps);
8287 case BeginningOfGame:
8288 /* Extra move from before last reset; ignore */
8289 if (appData.debugMode) {
8290 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8297 /* Extra move after we tried to stop. The mode test is
8298 not a reliable way of detecting this problem, but it's
8299 the best we can do on engines that don't support ping.
8301 if (appData.debugMode) {
8302 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8303 cps->which, gameMode);
8305 SendToProgram("undo\n", cps);
8308 case MachinePlaysWhite:
8309 case IcsPlayingWhite:
8310 machineWhite = TRUE;
8313 case MachinePlaysBlack:
8314 case IcsPlayingBlack:
8315 machineWhite = FALSE;
8318 case TwoMachinesPlay:
8319 machineWhite = (cps->twoMachinesColor[0] == 'w');
8322 if (WhiteOnMove(forwardMostMove) != machineWhite) {
8323 if (appData.debugMode) {
8325 "Ignoring move out of turn by %s, gameMode %d"
8326 ", forwardMost %d\n",
8327 cps->which, gameMode, forwardMostMove);
8332 if(cps->alphaRank) AlphaRank(machineMove, 4);
8333 if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
8334 &fromX, &fromY, &toX, &toY, &promoChar)) {
8335 /* Machine move could not be parsed; ignore it. */
8336 snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
8337 machineMove, _(cps->which));
8338 DisplayMoveError(buf1);
8339 snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
8340 machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
8341 if (gameMode == TwoMachinesPlay) {
8342 GameEnds(machineWhite ? BlackWins : WhiteWins,
8348 /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
8349 /* So we have to redo legality test with true e.p. status here, */
8350 /* to make sure an illegal e.p. capture does not slip through, */
8351 /* to cause a forfeit on a justified illegal-move complaint */
8352 /* of the opponent. */
8353 if( gameMode==TwoMachinesPlay && appData.testLegality ) {
8355 moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
8356 fromY, fromX, toY, toX, promoChar);
8357 if(moveType == IllegalMove) {
8358 snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
8359 machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
8360 GameEnds(machineWhite ? BlackWins : WhiteWins,
8363 } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
8364 /* [HGM] Kludge to handle engines that send FRC-style castling
8365 when they shouldn't (like TSCP-Gothic) */
8367 case WhiteASideCastleFR:
8368 case BlackASideCastleFR:
8370 currentMoveString[2]++;
8372 case WhiteHSideCastleFR:
8373 case BlackHSideCastleFR:
8375 currentMoveString[2]--;
8377 default: ; // nothing to do, but suppresses warning of pedantic compilers
8380 hintRequested = FALSE;
8381 lastHint[0] = NULLCHAR;
8382 bookRequested = FALSE;
8383 /* Program may be pondering now */
8384 cps->maybeThinking = TRUE;
8385 if (cps->sendTime == 2) cps->sendTime = 1;
8386 if (cps->offeredDraw) cps->offeredDraw--;
8388 /* [AS] Save move info*/
8389 pvInfoList[ forwardMostMove ].score = programStats.score;
8390 pvInfoList[ forwardMostMove ].depth = programStats.depth;
8391 pvInfoList[ forwardMostMove ].time = programStats.time; // [HGM] PGNtime: take time from engine stats
8393 MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
8395 /* Test suites abort the 'game' after one move */
8396 if(*appData.finger) {
8398 char *fen = PositionToFEN(backwardMostMove, NULL, 0); // no counts in EPD
8399 if(!f) f = fopen(appData.finger, "w");
8400 if(f) fprintf(f, "%s bm %s;\n", fen, parseList[backwardMostMove]), fflush(f);
8401 else { DisplayFatalError("Bad output file", errno, 0); return; }
8403 GameEnds(GameUnfinished, NULL, GE_XBOARD);
8406 /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
8407 if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
8410 while( count < adjudicateLossPlies ) {
8411 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
8414 score = -score; /* Flip score for winning side */
8417 if( score > adjudicateLossThreshold ) {
8424 if( count >= adjudicateLossPlies ) {
8425 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8427 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
8428 "Xboard adjudication",
8435 if(Adjudicate(cps)) {
8436 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8437 return; // [HGM] adjudicate: for all automatic game ends
8441 if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
8443 if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
8444 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
8446 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8448 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8450 if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
8451 char buf[3*MSG_SIZ];
8453 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
8454 programStats.score / 100.,
8456 programStats.time / 100.,
8457 (unsigned int)programStats.nodes,
8458 (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
8459 programStats.movelist);
8461 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
8466 /* [AS] Clear stats for next move */
8467 ClearProgramStats();
8468 thinkOutput[0] = NULLCHAR;
8469 hiddenThinkOutputState = 0;
8472 if (gameMode == TwoMachinesPlay) {
8473 /* [HGM] relaying draw offers moved to after reception of move */
8474 /* and interpreting offer as claim if it brings draw condition */
8475 if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
8476 SendToProgram("draw\n", cps->other);
8478 if (cps->other->sendTime) {
8479 SendTimeRemaining(cps->other,
8480 cps->other->twoMachinesColor[0] == 'w');
8482 bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
8483 if (firstMove && !bookHit) {
8485 if (cps->other->useColors) {
8486 SendToProgram(cps->other->twoMachinesColor, cps->other);
8488 SendToProgram("go\n", cps->other);
8490 cps->other->maybeThinking = TRUE;
8493 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8495 if (!pausing && appData.ringBellAfterMoves) {
8500 * Reenable menu items that were disabled while
8501 * machine was thinking
8503 if (gameMode != TwoMachinesPlay)
8504 SetUserThinkingEnables();
8506 // [HGM] book: after book hit opponent has received move and is now in force mode
8507 // force the book reply into it, and then fake that it outputted this move by jumping
8508 // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
8510 static char bookMove[MSG_SIZ]; // a bit generous?
8512 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
8513 strcat(bookMove, bookHit);
8516 programStats.nodes = programStats.depth = programStats.time =
8517 programStats.score = programStats.got_only_move = 0;
8518 sprintf(programStats.movelist, "%s (xbook)", bookHit);
8520 if(cps->lastPing != cps->lastPong) {
8521 savedMessage = message; // args for deferred call
8523 ScheduleDelayedEvent(DeferredBookMove, 10);
8532 /* Set special modes for chess engines. Later something general
8533 * could be added here; for now there is just one kludge feature,
8534 * needed because Crafty 15.10 and earlier don't ignore SIGINT
8535 * when "xboard" is given as an interactive command.
8537 if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
8538 cps->useSigint = FALSE;
8539 cps->useSigterm = FALSE;
8541 if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
8542 ParseFeatures(message+8, cps);
8543 return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
8546 if (!strncmp(message, "setup ", 6) &&
8547 (!appData.testLegality || gameInfo.variant == VariantFairy || gameInfo.variant == VariantUnknown || NonStandardBoardSize())
8548 ) { // [HGM] allow first engine to define opening position
8549 int dummy, w, h, hand, s=6; char buf[MSG_SIZ], varName[MSG_SIZ];
8550 if(appData.icsActive || forwardMostMove != 0 || cps != &first) return;
8552 if(sscanf(message, "setup (%s", buf) == 1) s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTable(pieceToChar, buf);
8553 if(startedFromSetupPosition) return;
8554 dummy = sscanf(message+s, "%dx%d+%d_%s", &w, &h, &hand, varName);
8556 while(message[s] && message[s++] != ' ');
8557 if(BOARD_HEIGHT != h || BOARD_WIDTH != w + 4*(hand != 0) || gameInfo.holdingsSize != hand ||
8558 dummy == 4 && gameInfo.variant != StringToVariant(varName) ) { // engine wants to change board format or variant
8559 appData.NrFiles = w; appData.NrRanks = h; appData.holdingsSize = hand;
8560 if(dummy == 4) gameInfo.variant = StringToVariant(varName); // parent variant
8561 InitPosition(1); // calls InitDrawingSizes to let new parameters take effect
8562 if(*buf) SetCharTable(pieceToChar, buf); // do again, for it was spoiled by InitPosition
8565 ParseFEN(boards[0], &dummy, message+s);
8566 DrawPosition(TRUE, boards[0]);
8567 startedFromSetupPosition = TRUE;
8570 /* [HGM] Allow engine to set up a position. Don't ask me why one would
8571 * want this, I was asked to put it in, and obliged.
8573 if (!strncmp(message, "setboard ", 9)) {
8574 Board initial_position;
8576 GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
8578 if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
8579 DisplayError(_("Bad FEN received from engine"), 0);
8583 CopyBoard(boards[0], initial_position);
8584 initialRulePlies = FENrulePlies;
8585 if(blackPlaysFirst) gameMode = MachinePlaysWhite;
8586 else gameMode = MachinePlaysBlack;
8587 DrawPosition(FALSE, boards[currentMove]);
8593 * Look for communication commands
8595 if (!strncmp(message, "telluser ", 9)) {
8596 if(message[9] == '\\' && message[10] == '\\')
8597 EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
8599 DisplayNote(message + 9);
8602 if (!strncmp(message, "tellusererror ", 14)) {
8604 if(message[14] == '\\' && message[15] == '\\')
8605 EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
8607 DisplayError(message + 14, 0);
8610 if (!strncmp(message, "tellopponent ", 13)) {
8611 if (appData.icsActive) {
8613 snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
8617 DisplayNote(message + 13);
8621 if (!strncmp(message, "tellothers ", 11)) {
8622 if (appData.icsActive) {
8624 snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
8627 } else if(appData.autoComment) AppendComment (forwardMostMove, message + 11, 1); // in local mode, add as move comment
8630 if (!strncmp(message, "tellall ", 8)) {
8631 if (appData.icsActive) {
8633 snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
8637 DisplayNote(message + 8);
8641 if (strncmp(message, "warning", 7) == 0) {
8642 /* Undocumented feature, use tellusererror in new code */
8643 DisplayError(message, 0);
8646 if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
8647 safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
8648 strcat(realname, " query");
8649 AskQuestion(realname, buf2, buf1, cps->pr);
8652 /* Commands from the engine directly to ICS. We don't allow these to be
8653 * sent until we are logged on. Crafty kibitzes have been known to
8654 * interfere with the login process.
8657 if (!strncmp(message, "tellics ", 8)) {
8658 SendToICS(message + 8);
8662 if (!strncmp(message, "tellicsnoalias ", 15)) {
8663 SendToICS(ics_prefix);
8664 SendToICS(message + 15);
8668 /* The following are for backward compatibility only */
8669 if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
8670 !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
8671 SendToICS(ics_prefix);
8677 if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
8680 if(!strncmp(message, "highlight ", 10)) {
8681 if(appData.testLegality && appData.markers) return;
8682 MarkByFEN(message+10); // [HGM] alien: allow engine to mark board squares
8685 if(!strncmp(message, "click ", 6)) {
8686 char f, c=0; int x, y; // [HGM] alien: allow engine to finish user moves (i.e. engine-driven one-click moving)
8687 if(appData.testLegality || !appData.oneClick) return;
8688 sscanf(message+6, "%c%d%c", &f, &y, &c);
8689 x = f - 'a' + BOARD_LEFT, y -= ONE - '0';
8690 if(flipView) x = BOARD_WIDTH-1 - x; else y = BOARD_HEIGHT-1 - y;
8691 x = x*squareSize + (x+1)*lineGap + squareSize/2;
8692 y = y*squareSize + (y+1)*lineGap + squareSize/2;
8693 f = first.highlight; first.highlight = 0; // kludge to suppress lift/put in response to own clicks
8694 if(lastClickType == Press) // if button still down, fake release on same square, to be ready for next click
8695 LeftClick(Release, lastLeftX, lastLeftY);
8696 controlKey = (c == ',');
8697 LeftClick(Press, x, y);
8698 LeftClick(Release, x, y);
8699 first.highlight = f;
8703 * If the move is illegal, cancel it and redraw the board.
8704 * Also deal with other error cases. Matching is rather loose
8705 * here to accommodate engines written before the spec.
8707 if (strncmp(message + 1, "llegal move", 11) == 0 ||
8708 strncmp(message, "Error", 5) == 0) {
8709 if (StrStr(message, "name") ||
8710 StrStr(message, "rating") || StrStr(message, "?") ||
8711 StrStr(message, "result") || StrStr(message, "board") ||
8712 StrStr(message, "bk") || StrStr(message, "computer") ||
8713 StrStr(message, "variant") || StrStr(message, "hint") ||
8714 StrStr(message, "random") || StrStr(message, "depth") ||
8715 StrStr(message, "accepted")) {
8718 if (StrStr(message, "protover")) {
8719 /* Program is responding to input, so it's apparently done
8720 initializing, and this error message indicates it is
8721 protocol version 1. So we don't need to wait any longer
8722 for it to initialize and send feature commands. */
8723 FeatureDone(cps, 1);
8724 cps->protocolVersion = 1;
8727 cps->maybeThinking = FALSE;
8729 if (StrStr(message, "draw")) {
8730 /* Program doesn't have "draw" command */
8731 cps->sendDrawOffers = 0;
8734 if (cps->sendTime != 1 &&
8735 (StrStr(message, "time") || StrStr(message, "otim"))) {
8736 /* Program apparently doesn't have "time" or "otim" command */
8740 if (StrStr(message, "analyze")) {
8741 cps->analysisSupport = FALSE;
8742 cps->analyzing = FALSE;
8743 // Reset(FALSE, TRUE); // [HGM] this caused discrepancy between display and internal state!
8744 EditGameEvent(); // [HGM] try to preserve loaded game
8745 snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
8746 DisplayError(buf2, 0);
8749 if (StrStr(message, "(no matching move)st")) {
8750 /* Special kludge for GNU Chess 4 only */
8751 cps->stKludge = TRUE;
8752 SendTimeControl(cps, movesPerSession, timeControl,
8753 timeIncrement, appData.searchDepth,
8757 if (StrStr(message, "(no matching move)sd")) {
8758 /* Special kludge for GNU Chess 4 only */
8759 cps->sdKludge = TRUE;
8760 SendTimeControl(cps, movesPerSession, timeControl,
8761 timeIncrement, appData.searchDepth,
8765 if (!StrStr(message, "llegal")) {
8768 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8769 gameMode == IcsIdle) return;
8770 if (forwardMostMove <= backwardMostMove) return;
8771 if (pausing) PauseEvent();
8772 if(appData.forceIllegal) {
8773 // [HGM] illegal: machine refused move; force position after move into it
8774 SendToProgram("force\n", cps);
8775 if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
8776 // we have a real problem now, as SendBoard will use the a2a3 kludge
8777 // when black is to move, while there might be nothing on a2 or black
8778 // might already have the move. So send the board as if white has the move.
8779 // But first we must change the stm of the engine, as it refused the last move
8780 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
8781 if(WhiteOnMove(forwardMostMove)) {
8782 SendToProgram("a7a6\n", cps); // for the engine black still had the move
8783 SendBoard(cps, forwardMostMove); // kludgeless board
8785 SendToProgram("a2a3\n", cps); // for the engine white still had the move
8786 CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
8787 SendBoard(cps, forwardMostMove+1); // kludgeless board
8789 } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
8790 if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
8791 gameMode == TwoMachinesPlay)
8792 SendToProgram("go\n", cps);
8795 if (gameMode == PlayFromGameFile) {
8796 /* Stop reading this game file */
8797 gameMode = EditGame;
8800 /* [HGM] illegal-move claim should forfeit game when Xboard */
8801 /* only passes fully legal moves */
8802 if( appData.testLegality && gameMode == TwoMachinesPlay ) {
8803 GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
8804 "False illegal-move claim", GE_XBOARD );
8805 return; // do not take back move we tested as valid
8807 currentMove = forwardMostMove-1;
8808 DisplayMove(currentMove-1); /* before DisplayMoveError */
8809 SwitchClocks(forwardMostMove-1); // [HGM] race
8810 DisplayBothClocks();
8811 snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
8812 parseList[currentMove], _(cps->which));
8813 DisplayMoveError(buf1);
8814 DrawPosition(FALSE, boards[currentMove]);
8816 SetUserThinkingEnables();
8819 if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
8820 /* Program has a broken "time" command that
8821 outputs a string not ending in newline.
8827 * If chess program startup fails, exit with an error message.
8828 * Attempts to recover here are futile. [HGM] Well, we try anyway
8830 if ((StrStr(message, "unknown host") != NULL)
8831 || (StrStr(message, "No remote directory") != NULL)
8832 || (StrStr(message, "not found") != NULL)
8833 || (StrStr(message, "No such file") != NULL)
8834 || (StrStr(message, "can't alloc") != NULL)
8835 || (StrStr(message, "Permission denied") != NULL)) {
8837 cps->maybeThinking = FALSE;
8838 snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
8839 _(cps->which), cps->program, cps->host, message);
8840 RemoveInputSource(cps->isr);
8841 if(appData.icsActive) DisplayFatalError(buf1, 0, 1); else {
8842 if(LoadError(oldError ? NULL : buf1, cps)) return; // error has then been handled by LoadError
8843 if(!oldError) DisplayError(buf1, 0); // if reason neatly announced, suppress general error popup
8849 * Look for hint output
8851 if (sscanf(message, "Hint: %s", buf1) == 1) {
8852 if (cps == &first && hintRequested) {
8853 hintRequested = FALSE;
8854 if (ParseOneMove(buf1, forwardMostMove, &moveType,
8855 &fromX, &fromY, &toX, &toY, &promoChar)) {
8856 (void) CoordsToAlgebraic(boards[forwardMostMove],
8857 PosFlags(forwardMostMove),
8858 fromY, fromX, toY, toX, promoChar, buf1);
8859 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
8860 DisplayInformation(buf2);
8862 /* Hint move could not be parsed!? */
8863 snprintf(buf2, sizeof(buf2),
8864 _("Illegal hint move \"%s\"\nfrom %s chess program"),
8865 buf1, _(cps->which));
8866 DisplayError(buf2, 0);
8869 safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
8875 * Ignore other messages if game is not in progress
8877 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8878 gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
8881 * look for win, lose, draw, or draw offer
8883 if (strncmp(message, "1-0", 3) == 0) {
8884 char *p, *q, *r = "";
8885 p = strchr(message, '{');
8893 GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
8895 } else if (strncmp(message, "0-1", 3) == 0) {
8896 char *p, *q, *r = "";
8897 p = strchr(message, '{');
8905 /* Kludge for Arasan 4.1 bug */
8906 if (strcmp(r, "Black resigns") == 0) {
8907 GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
8910 GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
8912 } else if (strncmp(message, "1/2", 3) == 0) {
8913 char *p, *q, *r = "";
8914 p = strchr(message, '{');
8923 GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
8926 } else if (strncmp(message, "White resign", 12) == 0) {
8927 GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8929 } else if (strncmp(message, "Black resign", 12) == 0) {
8930 GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8932 } else if (strncmp(message, "White matches", 13) == 0 ||
8933 strncmp(message, "Black matches", 13) == 0 ) {
8934 /* [HGM] ignore GNUShogi noises */
8936 } else if (strncmp(message, "White", 5) == 0 &&
8937 message[5] != '(' &&
8938 StrStr(message, "Black") == NULL) {
8939 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8941 } else if (strncmp(message, "Black", 5) == 0 &&
8942 message[5] != '(') {
8943 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8945 } else if (strcmp(message, "resign") == 0 ||
8946 strcmp(message, "computer resigns") == 0) {
8948 case MachinePlaysBlack:
8949 case IcsPlayingBlack:
8950 GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
8952 case MachinePlaysWhite:
8953 case IcsPlayingWhite:
8954 GameEnds(BlackWins, "White resigns", GE_ENGINE);
8956 case TwoMachinesPlay:
8957 if (cps->twoMachinesColor[0] == 'w')
8958 GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8960 GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8967 } else if (strncmp(message, "opponent mates", 14) == 0) {
8969 case MachinePlaysBlack:
8970 case IcsPlayingBlack:
8971 GameEnds(WhiteWins, "White mates", GE_ENGINE);
8973 case MachinePlaysWhite:
8974 case IcsPlayingWhite:
8975 GameEnds(BlackWins, "Black mates", GE_ENGINE);
8977 case TwoMachinesPlay:
8978 if (cps->twoMachinesColor[0] == 'w')
8979 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8981 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8988 } else if (strncmp(message, "computer mates", 14) == 0) {
8990 case MachinePlaysBlack:
8991 case IcsPlayingBlack:
8992 GameEnds(BlackWins, "Black mates", GE_ENGINE1);
8994 case MachinePlaysWhite:
8995 case IcsPlayingWhite:
8996 GameEnds(WhiteWins, "White mates", GE_ENGINE);
8998 case TwoMachinesPlay:
8999 if (cps->twoMachinesColor[0] == 'w')
9000 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9002 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9009 } else if (strncmp(message, "checkmate", 9) == 0) {
9010 if (WhiteOnMove(forwardMostMove)) {
9011 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9013 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9016 } else if (strstr(message, "Draw") != NULL ||
9017 strstr(message, "game is a draw") != NULL) {
9018 GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
9020 } else if (strstr(message, "offer") != NULL &&
9021 strstr(message, "draw") != NULL) {
9023 if (appData.zippyPlay && first.initDone) {
9024 /* Relay offer to ICS */
9025 SendToICS(ics_prefix);
9026 SendToICS("draw\n");
9029 cps->offeredDraw = 2; /* valid until this engine moves twice */
9030 if (gameMode == TwoMachinesPlay) {
9031 if (cps->other->offeredDraw) {
9032 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
9033 /* [HGM] in two-machine mode we delay relaying draw offer */
9034 /* until after we also have move, to see if it is really claim */
9036 } else if (gameMode == MachinePlaysWhite ||
9037 gameMode == MachinePlaysBlack) {
9038 if (userOfferedDraw) {
9039 DisplayInformation(_("Machine accepts your draw offer"));
9040 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
9042 DisplayInformation(_("Machine offers a draw.\nSelect Action / Draw to accept."));
9049 * Look for thinking output
9051 if ( appData.showThinking // [HGM] thinking: test all options that cause this output
9052 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
9054 int plylev, mvleft, mvtot, curscore, time;
9055 char mvname[MOVE_LEN];
9059 int prefixHint = FALSE;
9060 mvname[0] = NULLCHAR;
9063 case MachinePlaysBlack:
9064 case IcsPlayingBlack:
9065 if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
9067 case MachinePlaysWhite:
9068 case IcsPlayingWhite:
9069 if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
9074 case IcsObserving: /* [DM] icsEngineAnalyze */
9075 if (!appData.icsEngineAnalyze) ignore = TRUE;
9077 case TwoMachinesPlay:
9078 if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
9088 ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
9090 if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
9091 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
9093 if (plyext != ' ' && plyext != '\t') {
9097 /* [AS] Negate score if machine is playing black and reporting absolute scores */
9098 if( cps->scoreIsAbsolute &&
9099 ( gameMode == MachinePlaysBlack ||
9100 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
9101 gameMode == IcsPlayingBlack || // [HGM] also add other situations where engine should report black POV
9102 (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
9103 !WhiteOnMove(currentMove)
9106 curscore = -curscore;
9109 if(appData.pvSAN[cps==&second]) pv = PvToSAN(buf1);
9111 if(serverMoves && (time > 100 || time == 0 && plylev > 7)) {
9114 snprintf(buf, MSG_SIZ, "%s", appData.serverMovesName);
9115 buf[strlen(buf)-1] = gameMode == MachinePlaysWhite ? 'w' :
9116 gameMode == MachinePlaysBlack ? 'b' : cps->twoMachinesColor[0];
9117 if(appData.debugMode) fprintf(debugFP, "write PV on file '%s'\n", buf);
9118 if(f = fopen(buf, "w")) { // export PV to applicable PV file
9119 fprintf(f, "%5.2f/%-2d %s", curscore/100., plylev, pv);
9123 /* TRANSLATORS: PV = principal variation, the variation the chess engine thinks is the best for everyone */
9124 DisplayError(_("failed writing PV"), 0);
9127 tempStats.depth = plylev;
9128 tempStats.nodes = nodes;
9129 tempStats.time = time;
9130 tempStats.score = curscore;
9131 tempStats.got_only_move = 0;
9133 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
9136 if(cps->nps == 0) ticklen = 10*time; // use engine reported time
9137 else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
9138 if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
9139 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
9140 whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
9141 if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
9142 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
9143 blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
9146 /* Buffer overflow protection */
9147 if (pv[0] != NULLCHAR) {
9148 if (strlen(pv) >= sizeof(tempStats.movelist)
9149 && appData.debugMode) {
9151 "PV is too long; using the first %u bytes.\n",
9152 (unsigned) sizeof(tempStats.movelist) - 1);
9155 safeStrCpy( tempStats.movelist, pv, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
9157 sprintf(tempStats.movelist, " no PV\n");
9160 if (tempStats.seen_stat) {
9161 tempStats.ok_to_send = 1;
9164 if (strchr(tempStats.movelist, '(') != NULL) {
9165 tempStats.line_is_book = 1;
9166 tempStats.nr_moves = 0;
9167 tempStats.moves_left = 0;
9169 tempStats.line_is_book = 0;
9172 if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
9173 programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
9175 SendProgramStatsToFrontend( cps, &tempStats );
9178 [AS] Protect the thinkOutput buffer from overflow... this
9179 is only useful if buf1 hasn't overflowed first!
9181 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%+.2f %s%s",
9183 (gameMode == TwoMachinesPlay ?
9184 ToUpper(cps->twoMachinesColor[0]) : ' '),
9185 ((double) curscore) / 100.0,
9186 prefixHint ? lastHint : "",
9187 prefixHint ? " " : "" );
9189 if( buf1[0] != NULLCHAR ) {
9190 unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
9192 if( strlen(pv) > max_len ) {
9193 if( appData.debugMode) {
9194 fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
9196 pv[max_len+1] = '\0';
9199 strcat( thinkOutput, pv);
9202 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
9203 || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9204 DisplayMove(currentMove - 1);
9208 } else if ((p=StrStr(message, "(only move)")) != NULL) {
9209 /* crafty (9.25+) says "(only move) <move>"
9210 * if there is only 1 legal move
9212 sscanf(p, "(only move) %s", buf1);
9213 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
9214 sprintf(programStats.movelist, "%s (only move)", buf1);
9215 programStats.depth = 1;
9216 programStats.nr_moves = 1;
9217 programStats.moves_left = 1;
9218 programStats.nodes = 1;
9219 programStats.time = 1;
9220 programStats.got_only_move = 1;
9222 /* Not really, but we also use this member to
9223 mean "line isn't going to change" (Crafty
9224 isn't searching, so stats won't change) */
9225 programStats.line_is_book = 1;
9227 SendProgramStatsToFrontend( cps, &programStats );
9229 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9230 gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9231 DisplayMove(currentMove - 1);
9234 } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
9235 &time, &nodes, &plylev, &mvleft,
9236 &mvtot, mvname) >= 5) {
9237 /* The stat01: line is from Crafty (9.29+) in response
9238 to the "." command */
9239 programStats.seen_stat = 1;
9240 cps->maybeThinking = TRUE;
9242 if (programStats.got_only_move || !appData.periodicUpdates)
9245 programStats.depth = plylev;
9246 programStats.time = time;
9247 programStats.nodes = nodes;
9248 programStats.moves_left = mvleft;
9249 programStats.nr_moves = mvtot;
9250 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
9251 programStats.ok_to_send = 1;
9252 programStats.movelist[0] = '\0';
9254 SendProgramStatsToFrontend( cps, &programStats );
9258 } else if (strncmp(message,"++",2) == 0) {
9259 /* Crafty 9.29+ outputs this */
9260 programStats.got_fail = 2;
9263 } else if (strncmp(message,"--",2) == 0) {
9264 /* Crafty 9.29+ outputs this */
9265 programStats.got_fail = 1;
9268 } else if (thinkOutput[0] != NULLCHAR &&
9269 strncmp(message, " ", 4) == 0) {
9270 unsigned message_len;
9273 while (*p && *p == ' ') p++;
9275 message_len = strlen( p );
9277 /* [AS] Avoid buffer overflow */
9278 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
9279 strcat(thinkOutput, " ");
9280 strcat(thinkOutput, p);
9283 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
9284 strcat(programStats.movelist, " ");
9285 strcat(programStats.movelist, p);
9288 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9289 gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9290 DisplayMove(currentMove - 1);
9298 if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
9299 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
9301 ChessProgramStats cpstats;
9303 if (plyext != ' ' && plyext != '\t') {
9307 /* [AS] Negate score if machine is playing black and reporting absolute scores */
9308 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
9309 curscore = -curscore;
9312 cpstats.depth = plylev;
9313 cpstats.nodes = nodes;
9314 cpstats.time = time;
9315 cpstats.score = curscore;
9316 cpstats.got_only_move = 0;
9317 cpstats.movelist[0] = '\0';
9319 if (buf1[0] != NULLCHAR) {
9320 safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
9323 cpstats.ok_to_send = 0;
9324 cpstats.line_is_book = 0;
9325 cpstats.nr_moves = 0;
9326 cpstats.moves_left = 0;
9328 SendProgramStatsToFrontend( cps, &cpstats );
9335 /* Parse a game score from the character string "game", and
9336 record it as the history of the current game. The game
9337 score is NOT assumed to start from the standard position.
9338 The display is not updated in any way.
9341 ParseGameHistory (char *game)
9344 int fromX, fromY, toX, toY, boardIndex;
9349 if (appData.debugMode)
9350 fprintf(debugFP, "Parsing game history: %s\n", game);
9352 if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
9353 gameInfo.site = StrSave(appData.icsHost);
9354 gameInfo.date = PGNDate();
9355 gameInfo.round = StrSave("-");
9357 /* Parse out names of players */
9358 while (*game == ' ') game++;
9360 while (*game != ' ') *p++ = *game++;
9362 gameInfo.white = StrSave(buf);
9363 while (*game == ' ') game++;
9365 while (*game != ' ' && *game != '\n') *p++ = *game++;
9367 gameInfo.black = StrSave(buf);
9370 boardIndex = blackPlaysFirst ? 1 : 0;
9373 yyboardindex = boardIndex;
9374 moveType = (ChessMove) Myylex();
9376 case IllegalMove: /* maybe suicide chess, etc. */
9377 if (appData.debugMode) {
9378 fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
9379 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9380 setbuf(debugFP, NULL);
9382 case WhitePromotion:
9383 case BlackPromotion:
9384 case WhiteNonPromotion:
9385 case BlackNonPromotion:
9387 case WhiteCapturesEnPassant:
9388 case BlackCapturesEnPassant:
9389 case WhiteKingSideCastle:
9390 case WhiteQueenSideCastle:
9391 case BlackKingSideCastle:
9392 case BlackQueenSideCastle:
9393 case WhiteKingSideCastleWild:
9394 case WhiteQueenSideCastleWild:
9395 case BlackKingSideCastleWild:
9396 case BlackQueenSideCastleWild:
9398 case WhiteHSideCastleFR:
9399 case WhiteASideCastleFR:
9400 case BlackHSideCastleFR:
9401 case BlackASideCastleFR:
9403 fromX = currentMoveString[0] - AAA;
9404 fromY = currentMoveString[1] - ONE;
9405 toX = currentMoveString[2] - AAA;
9406 toY = currentMoveString[3] - ONE;
9407 promoChar = currentMoveString[4];
9411 if(currentMoveString[0] == '@') continue; // no null moves in ICS mode!
9412 fromX = moveType == WhiteDrop ?
9413 (int) CharToPiece(ToUpper(currentMoveString[0])) :
9414 (int) CharToPiece(ToLower(currentMoveString[0]));
9416 toX = currentMoveString[2] - AAA;
9417 toY = currentMoveString[3] - ONE;
9418 promoChar = NULLCHAR;
9422 snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
9423 if (appData.debugMode) {
9424 fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
9425 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9426 setbuf(debugFP, NULL);
9428 DisplayError(buf, 0);
9430 case ImpossibleMove:
9432 snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
9433 if (appData.debugMode) {
9434 fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
9435 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9436 setbuf(debugFP, NULL);
9438 DisplayError(buf, 0);
9441 if (boardIndex < backwardMostMove) {
9442 /* Oops, gap. How did that happen? */
9443 DisplayError(_("Gap in move list"), 0);
9446 backwardMostMove = blackPlaysFirst ? 1 : 0;
9447 if (boardIndex > forwardMostMove) {
9448 forwardMostMove = boardIndex;
9452 if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
9453 strcat(parseList[boardIndex-1], " ");
9454 strcat(parseList[boardIndex-1], yy_text);
9466 case GameUnfinished:
9467 if (gameMode == IcsExamining) {
9468 if (boardIndex < backwardMostMove) {
9469 /* Oops, gap. How did that happen? */
9472 backwardMostMove = blackPlaysFirst ? 1 : 0;
9475 gameInfo.result = moveType;
9476 p = strchr(yy_text, '{');
9477 if (p == NULL) p = strchr(yy_text, '(');
9480 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
9482 q = strchr(p, *p == '{' ? '}' : ')');
9483 if (q != NULL) *q = NULLCHAR;
9486 while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
9487 gameInfo.resultDetails = StrSave(p);
9490 if (boardIndex >= forwardMostMove &&
9491 !(gameMode == IcsObserving && ics_gamenum == -1)) {
9492 backwardMostMove = blackPlaysFirst ? 1 : 0;
9495 (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
9496 fromY, fromX, toY, toX, promoChar,
9497 parseList[boardIndex]);
9498 CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
9499 /* currentMoveString is set as a side-effect of yylex */
9500 safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
9501 strcat(moveList[boardIndex], "\n");
9503 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
9504 switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
9510 if(gameInfo.variant != VariantShogi)
9511 strcat(parseList[boardIndex - 1], "+");
9515 strcat(parseList[boardIndex - 1], "#");
9522 /* Apply a move to the given board */
9524 ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board)
9526 ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
9527 int promoRank = gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand ? 3 : 1;
9529 /* [HGM] compute & store e.p. status and castling rights for new position */
9530 /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
9532 if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
9533 oldEP = (signed char)board[EP_STATUS];
9534 board[EP_STATUS] = EP_NONE;
9536 if (fromY == DROP_RANK) {
9538 if(fromX == EmptySquare) { // [HGM] pass: empty drop encodes null move; nothing to change.
9539 board[EP_STATUS] = EP_CAPTURE; // null move considered irreversible
9542 piece = board[toY][toX] = (ChessSquare) fromX;
9546 if( board[toY][toX] != EmptySquare )
9547 board[EP_STATUS] = EP_CAPTURE;
9549 if( board[fromY][fromX] == WhiteLance || board[fromY][fromX] == BlackLance ) {
9550 if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi )
9551 board[EP_STATUS] = EP_PAWN_MOVE; // Lance is Pawn-like in most variants
9553 if( board[fromY][fromX] == WhitePawn ) {
9554 if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9555 board[EP_STATUS] = EP_PAWN_MOVE;
9557 if(toX>BOARD_LEFT && board[toY][toX-1] == BlackPawn &&
9558 gameInfo.variant != VariantBerolina || toX < fromX)
9559 board[EP_STATUS] = toX | berolina;
9560 if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
9561 gameInfo.variant != VariantBerolina || toX > fromX)
9562 board[EP_STATUS] = toX;
9565 if( board[fromY][fromX] == BlackPawn ) {
9566 if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9567 board[EP_STATUS] = EP_PAWN_MOVE;
9568 if( toY-fromY== -2) {
9569 if(toX>BOARD_LEFT && board[toY][toX-1] == WhitePawn &&
9570 gameInfo.variant != VariantBerolina || toX < fromX)
9571 board[EP_STATUS] = toX | berolina;
9572 if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
9573 gameInfo.variant != VariantBerolina || toX > fromX)
9574 board[EP_STATUS] = toX;
9578 for(i=0; i<nrCastlingRights; i++) {
9579 if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
9580 board[CASTLING][i] == toX && castlingRank[i] == toY
9581 ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
9584 if(gameInfo.variant == VariantSChess) { // update virginity
9585 if(fromY == 0) board[VIRGIN][fromX] &= ~VIRGIN_W; // loss by moving
9586 if(fromY == BOARD_HEIGHT-1) board[VIRGIN][fromX] &= ~VIRGIN_B;
9587 if(toY == 0) board[VIRGIN][toX] &= ~VIRGIN_W; // loss by capture
9588 if(toY == BOARD_HEIGHT-1) board[VIRGIN][toX] &= ~VIRGIN_B;
9591 if (fromX == toX && fromY == toY) return;
9593 piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
9594 king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
9595 if(gameInfo.variant == VariantKnightmate)
9596 king += (int) WhiteUnicorn - (int) WhiteKing;
9598 /* Code added by Tord: */
9599 /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
9600 if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
9601 board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
9602 board[fromY][fromX] = EmptySquare;
9603 board[toY][toX] = EmptySquare;
9604 if((toX > fromX) != (piece == WhiteRook)) {
9605 board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
9607 board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
9609 } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
9610 board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
9611 board[fromY][fromX] = EmptySquare;
9612 board[toY][toX] = EmptySquare;
9613 if((toX > fromX) != (piece == BlackRook)) {
9614 board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
9616 board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
9618 /* End of code added by Tord */
9620 } else if (board[fromY][fromX] == king
9621 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9622 && toY == fromY && toX > fromX+1) {
9623 board[fromY][fromX] = EmptySquare;
9624 board[toY][toX] = king;
9625 board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9626 board[fromY][BOARD_RGHT-1] = EmptySquare;
9627 } else if (board[fromY][fromX] == king
9628 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9629 && toY == fromY && toX < fromX-1) {
9630 board[fromY][fromX] = EmptySquare;
9631 board[toY][toX] = king;
9632 board[toY][toX+1] = board[fromY][BOARD_LEFT];
9633 board[fromY][BOARD_LEFT] = EmptySquare;
9634 } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
9635 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9636 && toY >= BOARD_HEIGHT-promoRank && promoChar // defaulting to Q is done elsewhere
9638 /* white pawn promotion */
9639 board[toY][toX] = CharToPiece(ToUpper(promoChar));
9640 if(board[toY][toX] < WhiteCannon && PieceToChar(PROMOTED board[toY][toX]) == '~') /* [HGM] use shadow piece (if available) */
9641 board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9642 board[fromY][fromX] = EmptySquare;
9643 } else if ((fromY >= BOARD_HEIGHT>>1)
9644 && (oldEP == toX || oldEP == EP_UNKNOWN || appData.testLegality)
9646 && gameInfo.variant != VariantXiangqi
9647 && gameInfo.variant != VariantBerolina
9648 && (board[fromY][fromX] == WhitePawn)
9649 && (board[toY][toX] == EmptySquare)) {
9650 board[fromY][fromX] = EmptySquare;
9651 board[toY][toX] = WhitePawn;
9652 captured = board[toY - 1][toX];
9653 board[toY - 1][toX] = EmptySquare;
9654 } else if ((fromY == BOARD_HEIGHT-4)
9656 && gameInfo.variant == VariantBerolina
9657 && (board[fromY][fromX] == WhitePawn)
9658 && (board[toY][toX] == EmptySquare)) {
9659 board[fromY][fromX] = EmptySquare;
9660 board[toY][toX] = WhitePawn;
9661 if(oldEP & EP_BEROLIN_A) {
9662 captured = board[fromY][fromX-1];
9663 board[fromY][fromX-1] = EmptySquare;
9664 }else{ captured = board[fromY][fromX+1];
9665 board[fromY][fromX+1] = EmptySquare;
9667 } else if (board[fromY][fromX] == king
9668 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9669 && toY == fromY && toX > fromX+1) {
9670 board[fromY][fromX] = EmptySquare;
9671 board[toY][toX] = king;
9672 board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9673 board[fromY][BOARD_RGHT-1] = EmptySquare;
9674 } else if (board[fromY][fromX] == king
9675 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9676 && toY == fromY && toX < fromX-1) {
9677 board[fromY][fromX] = EmptySquare;
9678 board[toY][toX] = king;
9679 board[toY][toX+1] = board[fromY][BOARD_LEFT];
9680 board[fromY][BOARD_LEFT] = EmptySquare;
9681 } else if (fromY == 7 && fromX == 3
9682 && board[fromY][fromX] == BlackKing
9683 && toY == 7 && toX == 5) {
9684 board[fromY][fromX] = EmptySquare;
9685 board[toY][toX] = BlackKing;
9686 board[fromY][7] = EmptySquare;
9687 board[toY][4] = BlackRook;
9688 } else if (fromY == 7 && fromX == 3
9689 && board[fromY][fromX] == BlackKing
9690 && toY == 7 && toX == 1) {
9691 board[fromY][fromX] = EmptySquare;
9692 board[toY][toX] = BlackKing;
9693 board[fromY][0] = EmptySquare;
9694 board[toY][2] = BlackRook;
9695 } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
9696 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9697 && toY < promoRank && promoChar
9699 /* black pawn promotion */
9700 board[toY][toX] = CharToPiece(ToLower(promoChar));
9701 if(board[toY][toX] < BlackCannon && PieceToChar(PROMOTED board[toY][toX]) == '~') /* [HGM] use shadow piece (if available) */
9702 board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9703 board[fromY][fromX] = EmptySquare;
9704 } else if ((fromY < BOARD_HEIGHT>>1)
9705 && (oldEP == toX || oldEP == EP_UNKNOWN || appData.testLegality)
9707 && gameInfo.variant != VariantXiangqi
9708 && gameInfo.variant != VariantBerolina
9709 && (board[fromY][fromX] == BlackPawn)
9710 && (board[toY][toX] == EmptySquare)) {
9711 board[fromY][fromX] = EmptySquare;
9712 board[toY][toX] = BlackPawn;
9713 captured = board[toY + 1][toX];
9714 board[toY + 1][toX] = EmptySquare;
9715 } else if ((fromY == 3)
9717 && gameInfo.variant == VariantBerolina
9718 && (board[fromY][fromX] == BlackPawn)
9719 && (board[toY][toX] == EmptySquare)) {
9720 board[fromY][fromX] = EmptySquare;
9721 board[toY][toX] = BlackPawn;
9722 if(oldEP & EP_BEROLIN_A) {
9723 captured = board[fromY][fromX-1];
9724 board[fromY][fromX-1] = EmptySquare;
9725 }else{ captured = board[fromY][fromX+1];
9726 board[fromY][fromX+1] = EmptySquare;
9729 board[toY][toX] = board[fromY][fromX];
9730 board[fromY][fromX] = EmptySquare;
9734 if (gameInfo.holdingsWidth != 0) {
9736 /* !!A lot more code needs to be written to support holdings */
9737 /* [HGM] OK, so I have written it. Holdings are stored in the */
9738 /* penultimate board files, so they are automaticlly stored */
9739 /* in the game history. */
9740 if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
9741 && promoChar && piece != WhitePawn && piece != BlackPawn) {
9742 /* Delete from holdings, by decreasing count */
9743 /* and erasing image if necessary */
9744 p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
9745 if(p < (int) BlackPawn) { /* white drop */
9746 p -= (int)WhitePawn;
9747 p = PieceToNumber((ChessSquare)p);
9748 if(p >= gameInfo.holdingsSize) p = 0;
9749 if(--board[p][BOARD_WIDTH-2] <= 0)
9750 board[p][BOARD_WIDTH-1] = EmptySquare;
9751 if((int)board[p][BOARD_WIDTH-2] < 0)
9752 board[p][BOARD_WIDTH-2] = 0;
9753 } else { /* black drop */
9754 p -= (int)BlackPawn;
9755 p = PieceToNumber((ChessSquare)p);
9756 if(p >= gameInfo.holdingsSize) p = 0;
9757 if(--board[BOARD_HEIGHT-1-p][1] <= 0)
9758 board[BOARD_HEIGHT-1-p][0] = EmptySquare;
9759 if((int)board[BOARD_HEIGHT-1-p][1] < 0)
9760 board[BOARD_HEIGHT-1-p][1] = 0;
9763 if (captured != EmptySquare && gameInfo.holdingsSize > 0
9764 && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess ) {
9765 /* [HGM] holdings: Add to holdings, if holdings exist */
9766 if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
9767 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
9768 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
9771 if (p >= (int) BlackPawn) {
9772 p -= (int)BlackPawn;
9773 if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9774 /* in Shogi restore piece to its original first */
9775 captured = (ChessSquare) (DEMOTED captured);
9778 p = PieceToNumber((ChessSquare)p);
9779 if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
9780 board[p][BOARD_WIDTH-2]++;
9781 board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
9783 p -= (int)WhitePawn;
9784 if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9785 captured = (ChessSquare) (DEMOTED captured);
9788 p = PieceToNumber((ChessSquare)p);
9789 if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
9790 board[BOARD_HEIGHT-1-p][1]++;
9791 board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
9794 } else if (gameInfo.variant == VariantAtomic) {
9795 if (captured != EmptySquare) {
9797 for (y = toY-1; y <= toY+1; y++) {
9798 for (x = toX-1; x <= toX+1; x++) {
9799 if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
9800 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
9801 board[y][x] = EmptySquare;
9805 board[toY][toX] = EmptySquare;
9808 if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
9809 board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
9811 if(promoChar == '+') {
9812 /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite ordinary Pawn promotion) */
9813 board[toY][toX] = (ChessSquare) (PROMOTED piece);
9814 } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
9815 ChessSquare newPiece = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
9816 if((newPiece <= WhiteMan || newPiece >= BlackPawn && newPiece <= BlackMan) // unpromoted piece specified
9817 && pieceToChar[PROMOTED newPiece] == '~') newPiece = PROMOTED newPiece; // but promoted version available
9818 board[toY][toX] = newPiece;
9820 if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
9821 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
9822 // [HGM] superchess: take promotion piece out of holdings
9823 int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
9824 if((int)piece < (int)BlackPawn) { // determine stm from piece color
9825 if(!--board[k][BOARD_WIDTH-2])
9826 board[k][BOARD_WIDTH-1] = EmptySquare;
9828 if(!--board[BOARD_HEIGHT-1-k][1])
9829 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
9835 /* Updates forwardMostMove */
9837 MakeMove (int fromX, int fromY, int toX, int toY, int promoChar)
9839 // forwardMostMove++; // [HGM] bare: moved downstream
9841 (void) CoordsToAlgebraic(boards[forwardMostMove],
9842 PosFlags(forwardMostMove),
9843 fromY, fromX, toY, toX, promoChar,
9844 parseList[forwardMostMove]);
9846 if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
9847 int timeLeft; static int lastLoadFlag=0; int king, piece;
9848 piece = boards[forwardMostMove][fromY][fromX];
9849 king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
9850 if(gameInfo.variant == VariantKnightmate)
9851 king += (int) WhiteUnicorn - (int) WhiteKing;
9852 if(forwardMostMove == 0) {
9853 if(gameMode == MachinePlaysBlack || gameMode == BeginningOfGame)
9854 fprintf(serverMoves, "%s;", UserName());
9855 else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b')
9856 fprintf(serverMoves, "%s;", second.tidy);
9857 fprintf(serverMoves, "%s;", first.tidy);
9858 if(gameMode == MachinePlaysWhite)
9859 fprintf(serverMoves, "%s;", UserName());
9860 else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
9861 fprintf(serverMoves, "%s;", second.tidy);
9862 } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
9863 lastLoadFlag = loadFlag;
9865 fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
9866 // print castling suffix
9867 if( toY == fromY && piece == king ) {
9869 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
9871 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
9874 if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
9875 boards[forwardMostMove][fromY][fromX] == BlackPawn ) &&
9876 boards[forwardMostMove][toY][toX] == EmptySquare
9877 && fromX != toX && fromY != toY)
9878 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
9880 if(promoChar != NULLCHAR) {
9881 if(fromY == 0 || fromY == BOARD_HEIGHT-1)
9882 fprintf(serverMoves, ":%c%c:%c%c", WhiteOnMove(forwardMostMove) ? 'w' : 'b',
9883 ToLower(promoChar), AAA+fromX, ONE+fromY); // Seirawan gating
9884 else fprintf(serverMoves, ":%c:%c%c", ToLower(promoChar), AAA+toX, ONE+toY);
9887 char buf[MOVE_LEN*2], *p; int len;
9888 fprintf(serverMoves, "/%d/%d",
9889 pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
9890 if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
9891 else timeLeft = blackTimeRemaining/1000;
9892 fprintf(serverMoves, "/%d", timeLeft);
9893 strncpy(buf, parseList[forwardMostMove], MOVE_LEN*2);
9894 if(p = strchr(buf, '/')) *p = NULLCHAR; else
9895 if(p = strchr(buf, '=')) *p = NULLCHAR;
9896 len = strlen(buf); if(len > 1 && buf[len-2] != '-') buf[len-2] = NULLCHAR; // strip to-square
9897 fprintf(serverMoves, "/%s", buf);
9899 fflush(serverMoves);
9902 if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations..
9903 GameEnds(GameUnfinished, _("Game too long; increase MAX_MOVES and recompile"), GE_XBOARD);
9906 UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
9907 if (commentList[forwardMostMove+1] != NULL) {
9908 free(commentList[forwardMostMove+1]);
9909 commentList[forwardMostMove+1] = NULL;
9911 CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
9912 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
9913 // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
9914 SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
9915 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
9916 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
9917 adjustedClock = FALSE;
9918 gameInfo.result = GameUnfinished;
9919 if (gameInfo.resultDetails != NULL) {
9920 free(gameInfo.resultDetails);
9921 gameInfo.resultDetails = NULL;
9923 CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
9924 moveList[forwardMostMove - 1]);
9925 switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
9931 if(gameInfo.variant != VariantShogi)
9932 strcat(parseList[forwardMostMove - 1], "+");
9936 strcat(parseList[forwardMostMove - 1], "#");
9942 /* Updates currentMove if not pausing */
9944 ShowMove (int fromX, int fromY, int toX, int toY)
9946 int instant = (gameMode == PlayFromGameFile) ?
9947 (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
9948 if(appData.noGUI) return;
9949 if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
9951 if (forwardMostMove == currentMove + 1) {
9952 AnimateMove(boards[forwardMostMove - 1],
9953 fromX, fromY, toX, toY);
9956 currentMove = forwardMostMove;
9959 if (instant) return;
9961 DisplayMove(currentMove - 1);
9962 if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
9963 if (appData.highlightLastMove) { // [HGM] moved to after DrawPosition, as with arrow it could redraw old board
9964 SetHighlights(fromX, fromY, toX, toY);
9967 DrawPosition(FALSE, boards[currentMove]);
9968 DisplayBothClocks();
9969 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
9973 SendEgtPath (ChessProgramState *cps)
9974 { /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
9975 char buf[MSG_SIZ], name[MSG_SIZ], *p;
9977 if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
9980 char c, *q = name+1, *r, *s;
9982 name[0] = ','; // extract next format name from feature and copy with prefixed ','
9983 while(*p && *p != ',') *q++ = *p++;
9985 if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
9986 strcmp(name, ",nalimov:") == 0 ) {
9987 // take nalimov path from the menu-changeable option first, if it is defined
9988 snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
9989 SendToProgram(buf,cps); // send egtbpath command for nalimov
9991 if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
9992 (s = StrStr(appData.egtFormats, name)) != NULL) {
9993 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
9994 s = r = StrStr(s, ":") + 1; // beginning of path info
9995 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
9996 c = *r; *r = 0; // temporarily null-terminate path info
9997 *--q = 0; // strip of trailig ':' from name
9998 snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
10000 SendToProgram(buf,cps); // send egtbpath command for this format
10002 if(*p == ',') p++; // read away comma to position for next format name
10007 NonStandardBoardSize ()
10009 /* [HGM] Awkward testing. Should really be a table */
10010 int overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
10011 if( gameInfo.variant == VariantUnknown || *engineVariant) return 0; // engine-defined name never needs prefix
10012 if( gameInfo.variant == VariantXiangqi )
10013 overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
10014 if( gameInfo.variant == VariantShogi )
10015 overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
10016 if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
10017 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
10018 if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
10019 gameInfo.variant == VariantGothic || gameInfo.variant == VariantFalcon || gameInfo.variant == VariantJanus )
10020 overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
10021 if( gameInfo.variant == VariantCourier )
10022 overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
10023 if( gameInfo.variant == VariantSuper )
10024 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
10025 if( gameInfo.variant == VariantGreat )
10026 overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
10027 if( gameInfo.variant == VariantSChess )
10028 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 7;
10029 if( gameInfo.variant == VariantGrand )
10030 overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 7;
10035 InitChessProgram (ChessProgramState *cps, int setup)
10036 /* setup needed to setup FRC opening position */
10038 char buf[MSG_SIZ], b[MSG_SIZ];
10039 if (appData.noChessProgram) return;
10040 hintRequested = FALSE;
10041 bookRequested = FALSE;
10043 ParseFeatures(appData.features[cps == &second], cps); // [HGM] allow user to overrule features
10044 /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
10045 /* moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
10046 if(cps->memSize) { /* [HGM] memory */
10047 snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
10048 SendToProgram(buf, cps);
10050 SendEgtPath(cps); /* [HGM] EGT */
10051 if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
10052 snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
10053 SendToProgram(buf, cps);
10056 SendToProgram(cps->initString, cps);
10057 if (gameInfo.variant != VariantNormal &&
10058 gameInfo.variant != VariantLoadable
10059 /* [HGM] also send variant if board size non-standard */
10060 || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
10062 char *v = VariantName(gameInfo.variant);
10063 if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
10064 /* [HGM] in protocol 1 we have to assume all variants valid */
10065 snprintf(buf, MSG_SIZ, _("Variant %s not supported by %s"), v, cps->tidy);
10066 DisplayFatalError(buf, 0, 1);
10070 if(NonStandardBoardSize()) { /* [HGM] make prefix for non-standard board size. */
10071 snprintf(b, MSG_SIZ, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
10072 gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
10073 /* [HGM] varsize: try first if this defiant size variant is specifically known */
10074 if(StrStr(cps->variants, b) == NULL) {
10075 // specific sized variant not known, check if general sizing allowed
10076 if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
10077 if(StrStr(cps->variants, "boardsize") == NULL) {
10078 snprintf(buf, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
10079 gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
10080 DisplayFatalError(buf, 0, 1);
10083 /* [HGM] here we really should compare with the maximum supported board size */
10086 } else snprintf(b, MSG_SIZ,"%s", VariantName(gameInfo.variant));
10087 snprintf(buf, MSG_SIZ, "variant %s\n", b);
10088 SendToProgram(buf, cps);
10090 currentlyInitializedVariant = gameInfo.variant;
10092 /* [HGM] send opening position in FRC to first engine */
10094 SendToProgram("force\n", cps);
10096 /* engine is now in force mode! Set flag to wake it up after first move. */
10097 setboardSpoiledMachineBlack = 1;
10100 if (cps->sendICS) {
10101 snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
10102 SendToProgram(buf, cps);
10104 cps->maybeThinking = FALSE;
10105 cps->offeredDraw = 0;
10106 if (!appData.icsActive) {
10107 SendTimeControl(cps, movesPerSession, timeControl,
10108 timeIncrement, appData.searchDepth,
10111 if (appData.showThinking
10112 // [HGM] thinking: four options require thinking output to be sent
10113 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
10115 SendToProgram("post\n", cps);
10117 SendToProgram("hard\n", cps);
10118 if (!appData.ponderNextMove) {
10119 /* Warning: "easy" is a toggle in GNU Chess, so don't send
10120 it without being sure what state we are in first. "hard"
10121 is not a toggle, so that one is OK.
10123 SendToProgram("easy\n", cps);
10125 if (cps->usePing) {
10126 snprintf(buf, MSG_SIZ, "ping %d\n", ++cps->lastPing);
10127 SendToProgram(buf, cps);
10129 cps->initDone = TRUE;
10130 ClearEngineOutputPane(cps == &second);
10135 ResendOptions (ChessProgramState *cps)
10136 { // send the stored value of the options
10139 Option *opt = cps->option;
10140 for(i=0; i<cps->nrOptions; i++, opt++) {
10141 switch(opt->type) {
10145 snprintf(buf, MSG_SIZ, "option %s=%d\n", opt->name, opt->value);
10148 snprintf(buf, MSG_SIZ, "option %s=%s\n", opt->name, opt->choice[opt->value]);
10151 snprintf(buf, MSG_SIZ, "option %s=%s\n", opt->name, opt->textValue);
10157 SendToProgram(buf, cps);
10162 StartChessProgram (ChessProgramState *cps)
10167 if (appData.noChessProgram) return;
10168 cps->initDone = FALSE;
10170 if (strcmp(cps->host, "localhost") == 0) {
10171 err = StartChildProcess(cps->program, cps->dir, &cps->pr);
10172 } else if (*appData.remoteShell == NULLCHAR) {
10173 err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
10175 if (*appData.remoteUser == NULLCHAR) {
10176 snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
10179 snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
10180 cps->host, appData.remoteUser, cps->program);
10182 err = StartChildProcess(buf, "", &cps->pr);
10186 snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
10187 DisplayError(buf, err); // [HGM] bit of a rough kludge: ignore failure, (which XBoard would do anyway), and let I/O discover it
10188 if(cps != &first) return;
10189 appData.noChessProgram = TRUE;
10192 // DisplayFatalError(buf, err, 1);
10193 // cps->pr = NoProc;
10194 // cps->isr = NULL;
10198 cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
10199 if (cps->protocolVersion > 1) {
10200 snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
10201 if(!cps->reload) { // do not clear options when reloading because of -xreuse
10202 cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
10203 cps->comboCnt = 0; // and values of combo boxes
10205 SendToProgram(buf, cps);
10206 if(cps->reload) ResendOptions(cps);
10208 SendToProgram("xboard\n", cps);
10213 TwoMachinesEventIfReady P((void))
10215 static int curMess = 0;
10216 if (first.lastPing != first.lastPong) {
10217 if(curMess != 1) DisplayMessage("", _("Waiting for first chess program")); curMess = 1;
10218 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
10221 if (second.lastPing != second.lastPong) {
10222 if(curMess != 2) DisplayMessage("", _("Waiting for second chess program")); curMess = 2;
10223 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
10226 DisplayMessage("", ""); curMess = 0;
10227 TwoMachinesEvent();
10231 MakeName (char *template)
10235 static char buf[MSG_SIZ];
10239 clock = time((time_t *)NULL);
10240 tm = localtime(&clock);
10242 while(*p++ = *template++) if(p[-1] == '%') {
10243 switch(*template++) {
10244 case 0: *p = 0; return buf;
10245 case 'Y': i = tm->tm_year+1900; break;
10246 case 'y': i = tm->tm_year-100; break;
10247 case 'M': i = tm->tm_mon+1; break;
10248 case 'd': i = tm->tm_mday; break;
10249 case 'h': i = tm->tm_hour; break;
10250 case 'm': i = tm->tm_min; break;
10251 case 's': i = tm->tm_sec; break;
10254 snprintf(p-1, MSG_SIZ-10 - (p - buf), "%02d", i); p += strlen(p);
10260 CountPlayers (char *p)
10263 while(p = strchr(p, '\n')) p++, n++; // count participants
10268 WriteTourneyFile (char *results, FILE *f)
10269 { // write tournament parameters on tourneyFile; on success return the stream pointer for closing
10270 if(f == NULL) f = fopen(appData.tourneyFile, "w");
10271 if(f == NULL) DisplayError(_("Could not write on tourney file"), 0); else {
10272 // create a file with tournament description
10273 fprintf(f, "-participants {%s}\n", appData.participants);
10274 fprintf(f, "-seedBase %d\n", appData.seedBase);
10275 fprintf(f, "-tourneyType %d\n", appData.tourneyType);
10276 fprintf(f, "-tourneyCycles %d\n", appData.tourneyCycles);
10277 fprintf(f, "-defaultMatchGames %d\n", appData.defaultMatchGames);
10278 fprintf(f, "-syncAfterRound %s\n", appData.roundSync ? "true" : "false");
10279 fprintf(f, "-syncAfterCycle %s\n", appData.cycleSync ? "true" : "false");
10280 fprintf(f, "-saveGameFile \"%s\"\n", appData.saveGameFile);
10281 fprintf(f, "-loadGameFile \"%s\"\n", appData.loadGameFile);
10282 fprintf(f, "-loadGameIndex %d\n", appData.loadGameIndex);
10283 fprintf(f, "-loadPositionFile \"%s\"\n", appData.loadPositionFile);
10284 fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex);
10285 fprintf(f, "-rewindIndex %d\n", appData.rewindIndex);
10286 fprintf(f, "-usePolyglotBook %s\n", appData.usePolyglotBook ? "true" : "false");
10287 fprintf(f, "-polyglotBook \"%s\"\n", appData.polyglotBook);
10288 fprintf(f, "-bookDepth %d\n", appData.bookDepth);
10289 fprintf(f, "-bookVariation %d\n", appData.bookStrength);
10290 fprintf(f, "-discourageOwnBooks %s\n", appData.defNoBook ? "true" : "false");
10291 fprintf(f, "-defaultHashSize %d\n", appData.defaultHashSize);
10292 fprintf(f, "-defaultCacheSizeEGTB %d\n", appData.defaultCacheSizeEGTB);
10293 fprintf(f, "-ponderNextMove %s\n", appData.ponderNextMove ? "true" : "false");
10294 fprintf(f, "-smpCores %d\n", appData.smpCores);
10296 fprintf(f, "-searchTime \"%d:%02d\"\n", searchTime/60, searchTime%60);
10298 fprintf(f, "-mps %d\n", appData.movesPerSession);
10299 fprintf(f, "-tc %s\n", appData.timeControl);
10300 fprintf(f, "-inc %.2f\n", appData.timeIncrement);
10302 fprintf(f, "-results \"%s\"\n", results);
10307 char *command[MAXENGINES], *mnemonic[MAXENGINES];
10310 Substitute (char *participants, int expunge)
10312 int i, changed, changes=0, nPlayers=0;
10313 char *p, *q, *r, buf[MSG_SIZ];
10314 if(participants == NULL) return;
10315 if(appData.tourneyFile[0] == NULLCHAR) { free(participants); return; }
10316 r = p = participants; q = appData.participants;
10317 while(*p && *p == *q) {
10318 if(*p == '\n') r = p+1, nPlayers++;
10321 if(*p) { // difference
10322 while(*p && *p++ != '\n');
10323 while(*q && *q++ != '\n');
10324 changed = nPlayers;
10325 changes = 1 + (strcmp(p, q) != 0);
10327 if(changes == 1) { // a single engine mnemonic was changed
10328 q = r; while(*q) nPlayers += (*q++ == '\n');
10329 p = buf; while(*r && (*p = *r++) != '\n') p++;
10331 NamesToList(firstChessProgramNames, command, mnemonic, "all");
10332 for(i=1; mnemonic[i]; i++) if(!strcmp(buf, mnemonic[i])) break;
10333 if(mnemonic[i]) { // The substitute is valid
10335 if(appData.tourneyFile[0] && (f = fopen(appData.tourneyFile, "r+")) ) {
10336 flock(fileno(f), LOCK_EX);
10337 ParseArgsFromFile(f);
10338 fseek(f, 0, SEEK_SET);
10339 FREE(appData.participants); appData.participants = participants;
10340 if(expunge) { // erase results of replaced engine
10341 int len = strlen(appData.results), w, b, dummy;
10342 for(i=0; i<len; i++) {
10343 Pairing(i, nPlayers, &w, &b, &dummy);
10344 if((w == changed || b == changed) && appData.results[i] == '*') {
10345 DisplayError(_("You cannot replace an engine while it is engaged!\nTerminate its game first."), 0);
10350 for(i=0; i<len; i++) {
10351 Pairing(i, nPlayers, &w, &b, &dummy);
10352 if(w == changed || b == changed) appData.results[i] = ' '; // mark as not played
10355 WriteTourneyFile(appData.results, f);
10356 fclose(f); // release lock
10359 } else DisplayError(_("No engine with the name you gave is installed"), 0);
10361 if(changes == 0) DisplayError(_("First change an engine by editing the participants list\nof the Tournament Options dialog"), 0);
10362 if(changes > 1) DisplayError(_("You can only change one engine at the time"), 0);
10363 free(participants);
10368 CheckPlayers (char *participants)
10371 char buf[MSG_SIZ], *p;
10372 NamesToList(firstChessProgramNames, command, mnemonic, "all");
10373 while(p = strchr(participants, '\n')) {
10375 for(i=1; mnemonic[i]; i++) if(!strcmp(participants, mnemonic[i])) break;
10377 snprintf(buf, MSG_SIZ, _("No engine %s is installed"), participants);
10379 DisplayError(buf, 0);
10383 participants = p + 1;
10389 CreateTourney (char *name)
10392 if(matchMode && strcmp(name, appData.tourneyFile)) {
10393 ASSIGN(name, appData.tourneyFile); //do not allow change of tourneyfile while playing
10395 if(name[0] == NULLCHAR) {
10396 if(appData.participants[0])
10397 DisplayError(_("You must supply a tournament file,\nfor storing the tourney progress"), 0);
10400 f = fopen(name, "r");
10401 if(f) { // file exists
10402 ASSIGN(appData.tourneyFile, name);
10403 ParseArgsFromFile(f); // parse it
10405 if(!appData.participants[0]) return 0; // ignore tourney file if non-existing & no participants
10406 if(CountPlayers(appData.participants) < (appData.tourneyType>0 ? appData.tourneyType+1 : 2)) {
10407 DisplayError(_("Not enough participants"), 0);
10410 if(CheckPlayers(appData.participants)) return 0;
10411 ASSIGN(appData.tourneyFile, name);
10412 if(appData.tourneyType < 0) appData.defaultMatchGames = 1; // Swiss forces games/pairing = 1
10413 if((f = WriteTourneyFile("", NULL)) == NULL) return 0;
10416 appData.noChessProgram = FALSE;
10417 appData.clockMode = TRUE;
10423 NamesToList (char *names, char **engineList, char **engineMnemonic, char *group)
10425 char buf[MSG_SIZ], *p, *q;
10426 int i=1, header, skip, all = !strcmp(group, "all"), depth = 0;
10427 insert = names; // afterwards, this global will point just after last retrieved engine line or group end in the 'names'
10428 skip = !all && group[0]; // if group requested, we start in skip mode
10429 for(;*names && depth >= 0 && i < MAXENGINES-1; names = p) {
10430 p = names; q = buf; header = 0;
10431 while(*p && *p != '\n') *q++ = *p++;
10433 if(*p == '\n') p++;
10434 if(buf[0] == '#') {
10435 if(strstr(buf, "# end") == buf) { if(!--depth) insert = p; continue; } // leave group, and suppress printing label
10436 depth++; // we must be entering a new group
10437 if(all) continue; // suppress printing group headers when complete list requested
10439 if(skip && !strcmp(group, buf)) { depth = 0; skip = FALSE; } // start when we reach requested group
10441 if(depth != header && !all || skip) continue; // skip contents of group (but print first-level header)
10442 if(engineList[i]) free(engineList[i]);
10443 engineList[i] = strdup(buf);
10444 if(buf[0] != '#') insert = p, TidyProgramName(engineList[i], "localhost", buf); // group headers not tidied
10445 if(engineMnemonic[i]) free(engineMnemonic[i]);
10446 if((q = strstr(engineList[i]+2, "variant")) && q[-2]== ' ' && (q[-1]=='/' || q[-1]=='-') && (q[7]==' ' || q[7]=='=')) {
10448 sscanf(q + 8, "%s", buf + strlen(buf));
10451 engineMnemonic[i] = strdup(buf);
10454 engineList[i] = engineMnemonic[i] = NULL;
10458 // following implemented as macro to avoid type limitations
10459 #define SWAP(item, temp) temp = appData.item[0]; appData.item[0] = appData.item[n]; appData.item[n] = temp;
10462 SwapEngines (int n)
10463 { // swap settings for first engine and other engine (so far only some selected options)
10468 SWAP(chessProgram, p)
10470 SWAP(hasOwnBookUCI, h)
10471 SWAP(protocolVersion, h)
10473 SWAP(scoreIsAbsolute, h)
10478 SWAP(engOptions, p)
10479 SWAP(engInitString, p)
10480 SWAP(computerString, p)
10482 SWAP(fenOverride, p)
10484 SWAP(accumulateTC, h)
10489 GetEngineLine (char *s, int n)
10493 extern char *icsNames;
10494 if(!s || !*s) return 0;
10495 NamesToList(n >= 10 ? icsNames : firstChessProgramNames, command, mnemonic, "all");
10496 for(i=1; mnemonic[i]; i++) if(!strcmp(s, mnemonic[i])) break;
10497 if(!mnemonic[i]) return 0;
10498 if(n == 11) return 1; // just testing if there was a match
10499 snprintf(buf, MSG_SIZ, "-%s %s", n == 10 ? "icshost" : "fcp", command[i]);
10500 if(n == 1) SwapEngines(n);
10501 ParseArgsFromString(buf);
10502 if(n == 1) SwapEngines(n);
10503 if(n == 0 && *appData.secondChessProgram == NULLCHAR) {
10504 SwapEngines(1); // set second same as first if not yet set (to suppress WB startup dialog)
10505 ParseArgsFromString(buf);
10511 SetPlayer (int player, char *p)
10512 { // [HGM] find the engine line of the partcipant given by number, and parse its options.
10514 char buf[MSG_SIZ], *engineName;
10515 for(i=0; i<player; i++) p = strchr(p, '\n') + 1;
10516 engineName = strdup(p); if(p = strchr(engineName, '\n')) *p = NULLCHAR;
10517 for(i=1; command[i]; i++) if(!strcmp(mnemonic[i], engineName)) break;
10519 snprintf(buf, MSG_SIZ, "-fcp %s", command[i]);
10520 ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL; appData.pvSAN[0] = FALSE;
10521 appData.firstHasOwnBookUCI = !appData.defNoBook; appData.protocolVersion[0] = PROTOVER;
10522 ParseArgsFromString(buf);
10523 } else { // no engine with this nickname is installed!
10524 snprintf(buf, MSG_SIZ, _("No engine %s is installed"), engineName);
10525 ReserveGame(nextGame, ' '); // unreserve game and drop out of match mode with error
10526 matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
10528 DisplayError(buf, 0);
10535 char *recentEngines;
10538 RecentEngineEvent (int nr)
10541 // SwapEngines(1); // bump first to second
10542 // ReplaceEngine(&second, 1); // and load it there
10543 NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
10544 n = SetPlayer(nr, recentEngines); // select new (using original menu order!)
10545 if(mnemonic[n]) { // if somehow the engine with the selected nickname is no longer found in the list, we skip
10546 ReplaceEngine(&first, 0);
10547 FloatToFront(&appData.recentEngineList, command[n]);
10552 Pairing (int nr, int nPlayers, int *whitePlayer, int *blackPlayer, int *syncInterval)
10553 { // determine players from game number
10554 int curCycle, curRound, curPairing, gamesPerCycle, gamesPerRound, roundsPerCycle=1, pairingsPerRound=1;
10556 if(appData.tourneyType == 0) {
10557 roundsPerCycle = (nPlayers - 1) | 1;
10558 pairingsPerRound = nPlayers / 2;
10559 } else if(appData.tourneyType > 0) {
10560 roundsPerCycle = nPlayers - appData.tourneyType;
10561 pairingsPerRound = appData.tourneyType;
10563 gamesPerRound = pairingsPerRound * appData.defaultMatchGames;
10564 gamesPerCycle = gamesPerRound * roundsPerCycle;
10565 appData.matchGames = gamesPerCycle * appData.tourneyCycles - 1; // fake like all games are one big match
10566 curCycle = nr / gamesPerCycle; nr %= gamesPerCycle;
10567 curRound = nr / gamesPerRound; nr %= gamesPerRound;
10568 curPairing = nr / appData.defaultMatchGames; nr %= appData.defaultMatchGames;
10569 matchGame = nr + curCycle * appData.defaultMatchGames + 1; // fake game nr that loads correct game or position from file
10570 roundNr = (curCycle * roundsPerCycle + curRound) * appData.defaultMatchGames + nr + 1;
10572 if(appData.cycleSync) *syncInterval = gamesPerCycle;
10573 if(appData.roundSync) *syncInterval = gamesPerRound;
10575 if(appData.debugMode) fprintf(debugFP, "cycle=%d, round=%d, pairing=%d curGame=%d\n", curCycle, curRound, curPairing, matchGame);
10577 if(appData.tourneyType == 0) {
10578 if(curPairing == (nPlayers-1)/2 ) {
10579 *whitePlayer = curRound;
10580 *blackPlayer = nPlayers - 1; // this is the 'bye' when nPlayer is odd
10582 *whitePlayer = curRound - (nPlayers-1)/2 + curPairing;
10583 if(*whitePlayer < 0) *whitePlayer += nPlayers-1+(nPlayers&1);
10584 *blackPlayer = curRound + (nPlayers-1)/2 - curPairing;
10585 if(*blackPlayer >= nPlayers-1+(nPlayers&1)) *blackPlayer -= nPlayers-1+(nPlayers&1);
10587 } else if(appData.tourneyType > 1) {
10588 *blackPlayer = curPairing; // in multi-gauntlet, assign gauntlet engines to second, so first an be kept loaded during round
10589 *whitePlayer = curRound + appData.tourneyType;
10590 } else if(appData.tourneyType > 0) {
10591 *whitePlayer = curPairing;
10592 *blackPlayer = curRound + appData.tourneyType;
10595 // take care of white/black alternation per round.
10596 // For cycles and games this is already taken care of by default, derived from matchGame!
10597 return curRound & 1;
10601 NextTourneyGame (int nr, int *swapColors)
10602 { // !!!major kludge!!! fiddle appData settings to get everything in order for next tourney game
10604 int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers, OK = 1;
10606 if(appData.tourneyFile[0] == NULLCHAR) return 1; // no tourney, always allow next game
10607 tf = fopen(appData.tourneyFile, "r");
10608 if(tf == NULL) { DisplayFatalError(_("Bad tournament file"), 0, 1); return 0; }
10609 ParseArgsFromFile(tf); fclose(tf);
10610 InitTimeControls(); // TC might be altered from tourney file
10612 nPlayers = CountPlayers(appData.participants); // count participants
10613 if(appData.tourneyType < 0) syncInterval = nPlayers/2; else
10614 *swapColors = Pairing(nr<0 ? 0 : nr, nPlayers, &whitePlayer, &blackPlayer, &syncInterval);
10617 p = q = appData.results;
10618 while(*q) if(*q++ == '*' || q[-1] == ' ') { firstBusy = q - p - 1; break; }
10619 if(firstBusy/syncInterval < (nextGame/syncInterval)) {
10620 DisplayMessage(_("Waiting for other game(s)"),"");
10621 waitingForGame = TRUE;
10622 ScheduleDelayedEvent(NextMatchGame, 1000); // wait for all games of previous round to finish
10625 waitingForGame = FALSE;
10628 if(appData.tourneyType < 0) {
10629 if(nr>=0 && !pairingReceived) {
10631 if(pairing.pr == NoProc) {
10632 if(!appData.pairingEngine[0]) {
10633 DisplayFatalError(_("No pairing engine specified"), 0, 1);
10636 StartChessProgram(&pairing); // starts the pairing engine
10638 snprintf(buf, 1<<16, "results %d %s\n", nPlayers, appData.results);
10639 SendToProgram(buf, &pairing);
10640 snprintf(buf, 1<<16, "pairing %d\n", nr+1);
10641 SendToProgram(buf, &pairing);
10642 return 0; // wait for pairing engine to answer (which causes NextTourneyGame to be called again...
10644 pairingReceived = 0; // ... so we continue here
10646 appData.matchGames = appData.tourneyCycles * syncInterval - 1;
10647 whitePlayer = savedWhitePlayer-1; blackPlayer = savedBlackPlayer-1;
10648 matchGame = 1; roundNr = nr / syncInterval + 1;
10651 if(first.pr != NoProc && second.pr != NoProc || nr<0) return 1; // engines already loaded
10653 // redefine engines, engine dir, etc.
10654 NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
10655 if(first.pr == NoProc) {
10656 if(!SetPlayer(whitePlayer, appData.participants)) OK = 0; // find white player amongst it, and parse its engine line
10657 InitEngine(&first, 0); // initialize ChessProgramStates based on new settings.
10659 if(second.pr == NoProc) {
10661 if(!SetPlayer(blackPlayer, appData.participants)) OK = 0; // find black player amongst it, and parse its engine line
10662 SwapEngines(1); // and make that valid for second engine by swapping
10663 InitEngine(&second, 1);
10665 CommonEngineInit(); // after this TwoMachinesEvent will create correct engine processes
10666 UpdateLogos(FALSE); // leave display to ModeHiglight()
10672 { // performs game initialization that does not invoke engines, and then tries to start the game
10673 int res, firstWhite, swapColors = 0;
10674 if(!NextTourneyGame(nextGame, &swapColors)) return; // this sets matchGame, -fcp / -scp and other options for next game, if needed
10675 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
10677 snprintf(buf, MSG_SIZ, appData.nameOfDebugFile, nextGame+1); // expand name of debug file with %d in it
10678 if(strcmp(buf, currentDebugFile)) { // name has changed
10679 FILE *f = fopen(buf, "w");
10680 if(f) { // if opening the new file failed, just keep using the old one
10681 ASSIGN(currentDebugFile, buf);
10685 if(appData.serverFileName) {
10686 if(serverFP) fclose(serverFP);
10687 serverFP = fopen(appData.serverFileName, "w");
10688 if(serverFP && first.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", first.tidy);
10689 if(serverFP && second.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", second.tidy);
10693 firstWhite = appData.firstPlaysBlack ^ (matchGame & 1 | appData.sameColorGames > 1); // non-incremental default
10694 firstWhite ^= swapColors; // reverses if NextTourneyGame says we are in an odd round
10695 first.twoMachinesColor = firstWhite ? "white\n" : "black\n"; // perform actual color assignement
10696 second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
10697 appData.noChessProgram = (first.pr == NoProc); // kludge to prevent Reset from starting up chess program
10698 if(appData.loadGameIndex == -2) srandom(appData.seedBase + 68163*(nextGame & ~1)); // deterministic seed to force same opening
10699 Reset(FALSE, first.pr != NoProc);
10700 res = LoadGameOrPosition(matchGame); // setup game
10701 appData.noChessProgram = FALSE; // LoadGameOrPosition might call Reset too!
10702 if(!res) return; // abort when bad game/pos file
10703 TwoMachinesEvent();
10707 UserAdjudicationEvent (int result)
10709 ChessMove gameResult = GameIsDrawn;
10712 gameResult = WhiteWins;
10714 else if( result < 0 ) {
10715 gameResult = BlackWins;
10718 if( gameMode == TwoMachinesPlay ) {
10719 GameEnds( gameResult, "User adjudication", GE_XBOARD );
10724 // [HGM] save: calculate checksum of game to make games easily identifiable
10726 StringCheckSum (char *s)
10729 if(s==NULL) return 0;
10730 while(*s) i = i*259 + *s++;
10738 for(i=backwardMostMove; i<forwardMostMove; i++) {
10739 sum += pvInfoList[i].depth;
10740 sum += StringCheckSum(parseList[i]);
10741 sum += StringCheckSum(commentList[i]);
10744 if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
10745 return sum + StringCheckSum(commentList[i]);
10746 } // end of save patch
10749 GameEnds (ChessMove result, char *resultDetails, int whosays)
10751 GameMode nextGameMode;
10753 char buf[MSG_SIZ], popupRequested = 0, *ranking = NULL;
10755 if(endingGame) return; /* [HGM] crash: forbid recursion */
10757 if(twoBoards) { // [HGM] dual: switch back to one board
10758 twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
10759 DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
10761 if (appData.debugMode) {
10762 fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
10763 result, resultDetails ? resultDetails : "(null)", whosays);
10766 fromX = fromY = -1; // [HGM] abort any move the user is entering.
10768 if(pausing) PauseEvent(); // can happen when we abort a paused game (New Game or Quit)
10770 if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
10771 /* If we are playing on ICS, the server decides when the
10772 game is over, but the engine can offer to draw, claim
10776 if (appData.zippyPlay && first.initDone) {
10777 if (result == GameIsDrawn) {
10778 /* In case draw still needs to be claimed */
10779 SendToICS(ics_prefix);
10780 SendToICS("draw\n");
10781 } else if (StrCaseStr(resultDetails, "resign")) {
10782 SendToICS(ics_prefix);
10783 SendToICS("resign\n");
10787 endingGame = 0; /* [HGM] crash */
10791 /* If we're loading the game from a file, stop */
10792 if (whosays == GE_FILE) {
10793 (void) StopLoadGameTimer();
10797 /* Cancel draw offers */
10798 first.offeredDraw = second.offeredDraw = 0;
10800 /* If this is an ICS game, only ICS can really say it's done;
10801 if not, anyone can. */
10802 isIcsGame = (gameMode == IcsPlayingWhite ||
10803 gameMode == IcsPlayingBlack ||
10804 gameMode == IcsObserving ||
10805 gameMode == IcsExamining);
10807 if (!isIcsGame || whosays == GE_ICS) {
10808 /* OK -- not an ICS game, or ICS said it was done */
10810 if (!isIcsGame && !appData.noChessProgram)
10811 SetUserThinkingEnables();
10813 /* [HGM] if a machine claims the game end we verify this claim */
10814 if(gameMode == TwoMachinesPlay && appData.testClaims) {
10815 if(appData.testLegality && whosays >= GE_ENGINE1 ) {
10817 ChessMove trueResult = (ChessMove) -1;
10819 claimer = whosays == GE_ENGINE1 ? /* color of claimer */
10820 first.twoMachinesColor[0] :
10821 second.twoMachinesColor[0] ;
10823 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
10824 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
10825 /* [HGM] verify: engine mate claims accepted if they were flagged */
10826 trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
10828 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
10829 /* [HGM] verify: engine mate claims accepted if they were flagged */
10830 trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
10832 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
10833 trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
10836 // now verify win claims, but not in drop games, as we don't understand those yet
10837 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
10838 || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) &&
10839 (result == WhiteWins && claimer == 'w' ||
10840 result == BlackWins && claimer == 'b' ) ) { // case to verify: engine claims own win
10841 if (appData.debugMode) {
10842 fprintf(debugFP, "result=%d sp=%d move=%d\n",
10843 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
10845 if(result != trueResult) {
10846 snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
10847 result = claimer == 'w' ? BlackWins : WhiteWins;
10848 resultDetails = buf;
10851 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
10852 && (forwardMostMove <= backwardMostMove ||
10853 (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
10854 (claimer=='b')==(forwardMostMove&1))
10856 /* [HGM] verify: draws that were not flagged are false claims */
10857 snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
10858 result = claimer == 'w' ? BlackWins : WhiteWins;
10859 resultDetails = buf;
10861 /* (Claiming a loss is accepted no questions asked!) */
10862 } else if(matchMode && result == GameIsDrawn && !strcmp(resultDetails, "Engine Abort Request")) {
10863 forwardMostMove = backwardMostMove; // [HGM] delete game to surpress saving
10864 result = GameUnfinished;
10865 if(!*appData.tourneyFile) matchGame--; // replay even in plain match
10867 /* [HGM] bare: don't allow bare King to win */
10868 if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
10869 || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
10870 && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
10871 && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
10872 && result != GameIsDrawn)
10873 { int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
10874 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
10875 int p = (signed char)boards[forwardMostMove][i][j] - color;
10876 if(p >= 0 && p <= (int)WhiteKing) k++;
10878 if (appData.debugMode) {
10879 fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
10880 result, resultDetails ? resultDetails : "(null)", whosays, k, color);
10883 result = GameIsDrawn;
10884 snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
10885 resultDetails = buf;
10891 if(serverMoves != NULL && !loadFlag) { char c = '=';
10892 if(result==WhiteWins) c = '+';
10893 if(result==BlackWins) c = '-';
10894 if(resultDetails != NULL)
10895 fprintf(serverMoves, ";%c;%s\n", c, resultDetails), fflush(serverMoves);
10897 if (resultDetails != NULL) {
10898 gameInfo.result = result;
10899 gameInfo.resultDetails = StrSave(resultDetails);
10901 /* display last move only if game was not loaded from file */
10902 if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
10903 DisplayMove(currentMove - 1);
10905 if (forwardMostMove != 0) {
10906 if (gameMode != PlayFromGameFile && gameMode != EditGame
10907 && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
10909 if (*appData.saveGameFile != NULLCHAR) {
10910 if(result == GameUnfinished && matchMode && *appData.tourneyFile)
10911 AutoSaveGame(); // [HGM] protect tourney PGN from aborted games, and prompt for name instead
10913 SaveGameToFile(appData.saveGameFile, TRUE);
10914 } else if (appData.autoSaveGames) {
10915 if(gameMode != IcsObserving || !appData.onlyOwn) AutoSaveGame();
10917 if (*appData.savePositionFile != NULLCHAR) {
10918 SavePositionToFile(appData.savePositionFile);
10920 AddGameToBook(FALSE); // Only does something during Monte-Carlo book building
10924 /* Tell program how game ended in case it is learning */
10925 /* [HGM] Moved this to after saving the PGN, just in case */
10926 /* engine died and we got here through time loss. In that */
10927 /* case we will get a fatal error writing the pipe, which */
10928 /* would otherwise lose us the PGN. */
10929 /* [HGM] crash: not needed anymore, but doesn't hurt; */
10930 /* output during GameEnds should never be fatal anymore */
10931 if (gameMode == MachinePlaysWhite ||
10932 gameMode == MachinePlaysBlack ||
10933 gameMode == TwoMachinesPlay ||
10934 gameMode == IcsPlayingWhite ||
10935 gameMode == IcsPlayingBlack ||
10936 gameMode == BeginningOfGame) {
10938 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
10940 if (first.pr != NoProc) {
10941 SendToProgram(buf, &first);
10943 if (second.pr != NoProc &&
10944 gameMode == TwoMachinesPlay) {
10945 SendToProgram(buf, &second);
10950 if (appData.icsActive) {
10951 if (appData.quietPlay &&
10952 (gameMode == IcsPlayingWhite ||
10953 gameMode == IcsPlayingBlack)) {
10954 SendToICS(ics_prefix);
10955 SendToICS("set shout 1\n");
10957 nextGameMode = IcsIdle;
10958 ics_user_moved = FALSE;
10959 /* clean up premove. It's ugly when the game has ended and the
10960 * premove highlights are still on the board.
10963 gotPremove = FALSE;
10964 ClearPremoveHighlights();
10965 DrawPosition(FALSE, boards[currentMove]);
10967 if (whosays == GE_ICS) {
10970 if (gameMode == IcsPlayingWhite)
10972 else if(gameMode == IcsPlayingBlack)
10973 PlayIcsLossSound();
10976 if (gameMode == IcsPlayingBlack)
10978 else if(gameMode == IcsPlayingWhite)
10979 PlayIcsLossSound();
10982 PlayIcsDrawSound();
10985 PlayIcsUnfinishedSound();
10988 if(appData.quitNext) { ExitEvent(0); return; }
10989 } else if (gameMode == EditGame ||
10990 gameMode == PlayFromGameFile ||
10991 gameMode == AnalyzeMode ||
10992 gameMode == AnalyzeFile) {
10993 nextGameMode = gameMode;
10995 nextGameMode = EndOfGame;
11000 nextGameMode = gameMode;
11003 if (appData.noChessProgram) {
11004 gameMode = nextGameMode;
11006 endingGame = 0; /* [HGM] crash */
11011 /* Put first chess program into idle state */
11012 if (first.pr != NoProc &&
11013 (gameMode == MachinePlaysWhite ||
11014 gameMode == MachinePlaysBlack ||
11015 gameMode == TwoMachinesPlay ||
11016 gameMode == IcsPlayingWhite ||
11017 gameMode == IcsPlayingBlack ||
11018 gameMode == BeginningOfGame)) {
11019 SendToProgram("force\n", &first);
11020 if (first.usePing) {
11022 snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
11023 SendToProgram(buf, &first);
11026 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
11027 /* Kill off first chess program */
11028 if (first.isr != NULL)
11029 RemoveInputSource(first.isr);
11032 if (first.pr != NoProc) {
11034 DoSleep( appData.delayBeforeQuit );
11035 SendToProgram("quit\n", &first);
11036 DoSleep( appData.delayAfterQuit );
11037 DestroyChildProcess(first.pr, first.useSigterm);
11038 first.reload = TRUE;
11042 if (second.reuse) {
11043 /* Put second chess program into idle state */
11044 if (second.pr != NoProc &&
11045 gameMode == TwoMachinesPlay) {
11046 SendToProgram("force\n", &second);
11047 if (second.usePing) {
11049 snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
11050 SendToProgram(buf, &second);
11053 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
11054 /* Kill off second chess program */
11055 if (second.isr != NULL)
11056 RemoveInputSource(second.isr);
11059 if (second.pr != NoProc) {
11060 DoSleep( appData.delayBeforeQuit );
11061 SendToProgram("quit\n", &second);
11062 DoSleep( appData.delayAfterQuit );
11063 DestroyChildProcess(second.pr, second.useSigterm);
11064 second.reload = TRUE;
11066 second.pr = NoProc;
11069 if (matchMode && (gameMode == TwoMachinesPlay || (waitingForGame || startingEngine) && exiting)) {
11070 char resChar = '=';
11074 if (first.twoMachinesColor[0] == 'w') {
11077 second.matchWins++;
11082 if (first.twoMachinesColor[0] == 'b') {
11085 second.matchWins++;
11088 case GameUnfinished:
11094 if(exiting) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game
11095 if(appData.tourneyFile[0]){ // [HGM] we are in a tourney; update tourney file with game result
11096 if(appData.afterGame && appData.afterGame[0]) RunCommand(appData.afterGame);
11097 ReserveGame(nextGame, resChar); // sets nextGame
11098 if(nextGame > appData.matchGames) appData.tourneyFile[0] = 0, ranking = TourneyStandings(3); // tourney is done
11099 else ranking = strdup("busy"); //suppress popup when aborted but not finished
11100 } else roundNr = nextGame = matchGame + 1; // normal match, just increment; round equals matchGame
11102 if (nextGame <= appData.matchGames && !abortMatch) {
11103 gameMode = nextGameMode;
11104 matchGame = nextGame; // this will be overruled in tourney mode!
11105 GetTimeMark(&pauseStart); // [HGM] matchpause: stipulate a pause
11106 ScheduleDelayedEvent(NextMatchGame, 10); // but start game immediately (as it will wait out the pause itself)
11107 endingGame = 0; /* [HGM] crash */
11110 gameMode = nextGameMode;
11111 snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
11112 first.tidy, second.tidy,
11113 first.matchWins, second.matchWins,
11114 appData.matchGames - (first.matchWins + second.matchWins));
11115 if(!appData.tourneyFile[0]) matchGame++, DisplayTwoMachinesTitle(); // [HGM] update result in window title
11116 if(ranking && strcmp(ranking, "busy") && appData.afterTourney && appData.afterTourney[0]) RunCommand(appData.afterTourney);
11117 popupRequested++; // [HGM] crash: postpone to after resetting endingGame
11118 if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
11119 first.twoMachinesColor = "black\n";
11120 second.twoMachinesColor = "white\n";
11122 first.twoMachinesColor = "white\n";
11123 second.twoMachinesColor = "black\n";
11127 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
11128 !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
11130 gameMode = nextGameMode;
11132 endingGame = 0; /* [HGM] crash */
11133 if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
11134 if(matchMode == TRUE) { // match through command line: exit with or without popup
11136 ToNrEvent(forwardMostMove);
11137 if(strcmp(ranking, "busy")) DisplayFatalError(ranking, 0, 0);
11139 } else DisplayFatalError(buf, 0, 0);
11140 } else { // match through menu; just stop, with or without popup
11141 matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
11144 if(strcmp(ranking, "busy")) DisplayNote(ranking);
11145 } else DisplayNote(buf);
11147 if(ranking) free(ranking);
11151 /* Assumes program was just initialized (initString sent).
11152 Leaves program in force mode. */
11154 FeedMovesToProgram (ChessProgramState *cps, int upto)
11158 if (appData.debugMode)
11159 fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
11160 startedFromSetupPosition ? "position and " : "",
11161 backwardMostMove, upto, cps->which);
11162 if(currentlyInitializedVariant != gameInfo.variant) {
11164 // [HGM] variantswitch: make engine aware of new variant
11165 if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
11166 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
11167 snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
11168 SendToProgram(buf, cps);
11169 currentlyInitializedVariant = gameInfo.variant;
11171 SendToProgram("force\n", cps);
11172 if (startedFromSetupPosition) {
11173 SendBoard(cps, backwardMostMove);
11174 if (appData.debugMode) {
11175 fprintf(debugFP, "feedMoves\n");
11178 for (i = backwardMostMove; i < upto; i++) {
11179 SendMoveToProgram(i, cps);
11185 ResurrectChessProgram ()
11187 /* The chess program may have exited.
11188 If so, restart it and feed it all the moves made so far. */
11189 static int doInit = 0;
11191 if (appData.noChessProgram) return 1;
11193 if(matchMode /*&& appData.tourneyFile[0]*/) { // [HGM] tourney: make sure we get features after engine replacement. (Should we always do this?)
11194 if(WaitForEngine(&first, TwoMachinesEventIfReady)) { doInit = 1; return 0; } // request to do init on next visit, because we started engine
11195 if(!doInit) return 1; // this replaces testing first.pr != NoProc, which is true when we get here, but first time no reason to abort
11196 doInit = 0; // we fell through (first time after starting the engine); make sure it doesn't happen again
11198 if (first.pr != NoProc) return 1;
11199 StartChessProgram(&first);
11201 InitChessProgram(&first, FALSE);
11202 FeedMovesToProgram(&first, currentMove);
11204 if (!first.sendTime) {
11205 /* can't tell gnuchess what its clock should read,
11206 so we bow to its notion. */
11208 timeRemaining[0][currentMove] = whiteTimeRemaining;
11209 timeRemaining[1][currentMove] = blackTimeRemaining;
11212 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
11213 appData.icsEngineAnalyze) && first.analysisSupport) {
11214 SendToProgram("analyze\n", &first);
11215 first.analyzing = TRUE;
11221 * Button procedures
11224 Reset (int redraw, int init)
11228 if (appData.debugMode) {
11229 fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
11230 redraw, init, gameMode);
11232 CleanupTail(); // [HGM] vari: delete any stored variations
11233 CommentPopDown(); // [HGM] make sure no comments to the previous game keep hanging on
11234 pausing = pauseExamInvalid = FALSE;
11235 startedFromSetupPosition = blackPlaysFirst = FALSE;
11237 whiteFlag = blackFlag = FALSE;
11238 userOfferedDraw = FALSE;
11239 hintRequested = bookRequested = FALSE;
11240 first.maybeThinking = FALSE;
11241 second.maybeThinking = FALSE;
11242 first.bookSuspend = FALSE; // [HGM] book
11243 second.bookSuspend = FALSE;
11244 thinkOutput[0] = NULLCHAR;
11245 lastHint[0] = NULLCHAR;
11246 ClearGameInfo(&gameInfo);
11247 gameInfo.variant = StringToVariant(appData.variant);
11248 if(gameInfo.variant == VariantNormal && strcmp(appData.variant, "normal")) gameInfo.variant = VariantUnknown;
11249 ics_user_moved = ics_clock_paused = FALSE;
11250 ics_getting_history = H_FALSE;
11252 white_holding[0] = black_holding[0] = NULLCHAR;
11253 ClearProgramStats();
11254 opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
11258 flipView = appData.flipView;
11259 ClearPremoveHighlights();
11260 gotPremove = FALSE;
11261 alarmSounded = FALSE;
11263 GameEnds(EndOfFile, NULL, GE_PLAYER);
11264 if(appData.serverMovesName != NULL) {
11265 /* [HGM] prepare to make moves file for broadcasting */
11266 clock_t t = clock();
11267 if(serverMoves != NULL) fclose(serverMoves);
11268 serverMoves = fopen(appData.serverMovesName, "r");
11269 if(serverMoves != NULL) {
11270 fclose(serverMoves);
11271 /* delay 15 sec before overwriting, so all clients can see end */
11272 while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
11274 serverMoves = fopen(appData.serverMovesName, "w");
11278 gameMode = BeginningOfGame;
11280 if(appData.icsActive) gameInfo.variant = VariantNormal;
11281 currentMove = forwardMostMove = backwardMostMove = 0;
11282 MarkTargetSquares(1);
11283 InitPosition(redraw);
11284 for (i = 0; i < MAX_MOVES; i++) {
11285 if (commentList[i] != NULL) {
11286 free(commentList[i]);
11287 commentList[i] = NULL;
11291 timeRemaining[0][0] = whiteTimeRemaining;
11292 timeRemaining[1][0] = blackTimeRemaining;
11294 if (first.pr == NoProc) {
11295 StartChessProgram(&first);
11298 InitChessProgram(&first, startedFromSetupPosition);
11301 DisplayMessage("", "");
11302 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11303 lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
11304 ClearMap(); // [HGM] exclude: invalidate map
11308 AutoPlayGameLoop ()
11311 if (!AutoPlayOneMove())
11313 if (matchMode || appData.timeDelay == 0)
11315 if (appData.timeDelay < 0)
11317 StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
11325 ReloadGame(1); // next game
11331 int fromX, fromY, toX, toY;
11333 if (appData.debugMode) {
11334 fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
11337 if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
11340 if (gameMode == AnalyzeFile && currentMove > backwardMostMove && programStats.depth) {
11341 pvInfoList[currentMove].depth = programStats.depth;
11342 pvInfoList[currentMove].score = programStats.score;
11343 pvInfoList[currentMove].time = 0;
11344 if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
11345 else { // append analysis of final position as comment
11347 snprintf(buf, MSG_SIZ, "{final score %+4.2f/%d}", programStats.score/100., programStats.depth);
11348 AppendComment(currentMove, buf, 3); // the 3 prevents stripping of the score/depth!
11350 programStats.depth = 0;
11353 if (currentMove >= forwardMostMove) {
11354 if(gameMode == AnalyzeFile) {
11355 if(appData.loadGameIndex == -1) {
11356 GameEnds(gameInfo.result, gameInfo.resultDetails ? gameInfo.resultDetails : "", GE_FILE);
11357 ScheduleDelayedEvent(AnalyzeNextGame, 10);
11359 ExitAnalyzeMode(); SendToProgram("force\n", &first);
11362 // gameMode = EndOfGame;
11363 // ModeHighlight();
11365 /* [AS] Clear current move marker at the end of a game */
11366 /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
11371 toX = moveList[currentMove][2] - AAA;
11372 toY = moveList[currentMove][3] - ONE;
11374 if (moveList[currentMove][1] == '@') {
11375 if (appData.highlightLastMove) {
11376 SetHighlights(-1, -1, toX, toY);
11379 fromX = moveList[currentMove][0] - AAA;
11380 fromY = moveList[currentMove][1] - ONE;
11382 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
11384 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
11386 if (appData.highlightLastMove) {
11387 SetHighlights(fromX, fromY, toX, toY);
11390 DisplayMove(currentMove);
11391 SendMoveToProgram(currentMove++, &first);
11392 DisplayBothClocks();
11393 DrawPosition(FALSE, boards[currentMove]);
11394 // [HGM] PV info: always display, routine tests if empty
11395 DisplayComment(currentMove - 1, commentList[currentMove]);
11401 LoadGameOneMove (ChessMove readAhead)
11403 int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
11404 char promoChar = NULLCHAR;
11405 ChessMove moveType;
11406 char move[MSG_SIZ];
11409 if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
11410 gameMode != AnalyzeMode && gameMode != Training) {
11415 yyboardindex = forwardMostMove;
11416 if (readAhead != EndOfFile) {
11417 moveType = readAhead;
11419 if (gameFileFP == NULL)
11421 moveType = (ChessMove) Myylex();
11425 switch (moveType) {
11427 if (appData.debugMode)
11428 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11431 /* append the comment but don't display it */
11432 AppendComment(currentMove, p, FALSE);
11435 case WhiteCapturesEnPassant:
11436 case BlackCapturesEnPassant:
11437 case WhitePromotion:
11438 case BlackPromotion:
11439 case WhiteNonPromotion:
11440 case BlackNonPromotion:
11442 case WhiteKingSideCastle:
11443 case WhiteQueenSideCastle:
11444 case BlackKingSideCastle:
11445 case BlackQueenSideCastle:
11446 case WhiteKingSideCastleWild:
11447 case WhiteQueenSideCastleWild:
11448 case BlackKingSideCastleWild:
11449 case BlackQueenSideCastleWild:
11451 case WhiteHSideCastleFR:
11452 case WhiteASideCastleFR:
11453 case BlackHSideCastleFR:
11454 case BlackASideCastleFR:
11456 if (appData.debugMode)
11457 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
11458 fromX = currentMoveString[0] - AAA;
11459 fromY = currentMoveString[1] - ONE;
11460 toX = currentMoveString[2] - AAA;
11461 toY = currentMoveString[3] - ONE;
11462 promoChar = currentMoveString[4];
11467 if (appData.debugMode)
11468 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
11469 fromX = moveType == WhiteDrop ?
11470 (int) CharToPiece(ToUpper(currentMoveString[0])) :
11471 (int) CharToPiece(ToLower(currentMoveString[0]));
11473 toX = currentMoveString[2] - AAA;
11474 toY = currentMoveString[3] - ONE;
11480 case GameUnfinished:
11481 if (appData.debugMode)
11482 fprintf(debugFP, "Parsed game end: %s\n", yy_text);
11483 p = strchr(yy_text, '{');
11484 if (p == NULL) p = strchr(yy_text, '(');
11487 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
11489 q = strchr(p, *p == '{' ? '}' : ')');
11490 if (q != NULL) *q = NULLCHAR;
11493 while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
11494 GameEnds(moveType, p, GE_FILE);
11496 if (cmailMsgLoaded) {
11498 flipView = WhiteOnMove(currentMove);
11499 if (moveType == GameUnfinished) flipView = !flipView;
11500 if (appData.debugMode)
11501 fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
11506 if (appData.debugMode)
11507 fprintf(debugFP, "Parser hit end of file\n");
11508 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11514 if (WhiteOnMove(currentMove)) {
11515 GameEnds(BlackWins, "Black mates", GE_FILE);
11517 GameEnds(WhiteWins, "White mates", GE_FILE);
11521 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
11527 case MoveNumberOne:
11528 if (lastLoadGameStart == GNUChessGame) {
11529 /* GNUChessGames have numbers, but they aren't move numbers */
11530 if (appData.debugMode)
11531 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
11532 yy_text, (int) moveType);
11533 return LoadGameOneMove(EndOfFile); /* tail recursion */
11535 /* else fall thru */
11540 /* Reached start of next game in file */
11541 if (appData.debugMode)
11542 fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
11543 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11549 if (WhiteOnMove(currentMove)) {
11550 GameEnds(BlackWins, "Black mates", GE_FILE);
11552 GameEnds(WhiteWins, "White mates", GE_FILE);
11556 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
11562 case PositionDiagram: /* should not happen; ignore */
11563 case ElapsedTime: /* ignore */
11564 case NAG: /* ignore */
11565 if (appData.debugMode)
11566 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
11567 yy_text, (int) moveType);
11568 return LoadGameOneMove(EndOfFile); /* tail recursion */
11571 if (appData.testLegality) {
11572 if (appData.debugMode)
11573 fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
11574 snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
11575 (forwardMostMove / 2) + 1,
11576 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11577 DisplayError(move, 0);
11580 if (appData.debugMode)
11581 fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
11582 yy_text, currentMoveString);
11583 fromX = currentMoveString[0] - AAA;
11584 fromY = currentMoveString[1] - ONE;
11585 toX = currentMoveString[2] - AAA;
11586 toY = currentMoveString[3] - ONE;
11587 promoChar = currentMoveString[4];
11591 case AmbiguousMove:
11592 if (appData.debugMode)
11593 fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
11594 snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
11595 (forwardMostMove / 2) + 1,
11596 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11597 DisplayError(move, 0);
11602 case ImpossibleMove:
11603 if (appData.debugMode)
11604 fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
11605 snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
11606 (forwardMostMove / 2) + 1,
11607 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11608 DisplayError(move, 0);
11614 if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
11615 DrawPosition(FALSE, boards[currentMove]);
11616 DisplayBothClocks();
11617 if (!appData.matchMode) // [HGM] PV info: routine tests if empty
11618 DisplayComment(currentMove - 1, commentList[currentMove]);
11620 (void) StopLoadGameTimer();
11622 cmailOldMove = forwardMostMove;
11625 /* currentMoveString is set as a side-effect of yylex */
11627 thinkOutput[0] = NULLCHAR;
11628 MakeMove(fromX, fromY, toX, toY, promoChar);
11629 currentMove = forwardMostMove;
11634 /* Load the nth game from the given file */
11636 LoadGameFromFile (char *filename, int n, char *title, int useList)
11641 if (strcmp(filename, "-") == 0) {
11645 f = fopen(filename, "rb");
11647 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
11648 DisplayError(buf, errno);
11652 if (fseek(f, 0, 0) == -1) {
11653 /* f is not seekable; probably a pipe */
11656 if (useList && n == 0) {
11657 int error = GameListBuild(f);
11659 DisplayError(_("Cannot build game list"), error);
11660 } else if (!ListEmpty(&gameList) &&
11661 ((ListGame *) gameList.tailPred)->number > 1) {
11662 GameListPopUp(f, title);
11669 return LoadGame(f, n, title, FALSE);
11674 MakeRegisteredMove ()
11676 int fromX, fromY, toX, toY;
11678 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11679 switch (cmailMoveType[lastLoadGameNumber - 1]) {
11682 if (appData.debugMode)
11683 fprintf(debugFP, "Restoring %s for game %d\n",
11684 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
11686 thinkOutput[0] = NULLCHAR;
11687 safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
11688 fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
11689 fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
11690 toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
11691 toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
11692 promoChar = cmailMove[lastLoadGameNumber - 1][4];
11693 MakeMove(fromX, fromY, toX, toY, promoChar);
11694 ShowMove(fromX, fromY, toX, toY);
11696 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11703 if (WhiteOnMove(currentMove)) {
11704 GameEnds(BlackWins, "Black mates", GE_PLAYER);
11706 GameEnds(WhiteWins, "White mates", GE_PLAYER);
11711 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
11718 if (WhiteOnMove(currentMove)) {
11719 GameEnds(BlackWins, "White resigns", GE_PLAYER);
11721 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11726 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11737 /* Wrapper around LoadGame for use when a Cmail message is loaded */
11739 CmailLoadGame (FILE *f, int gameNumber, char *title, int useList)
11743 if (gameNumber > nCmailGames) {
11744 DisplayError(_("No more games in this message"), 0);
11747 if (f == lastLoadGameFP) {
11748 int offset = gameNumber - lastLoadGameNumber;
11750 cmailMsg[0] = NULLCHAR;
11751 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11752 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
11753 nCmailMovesRegistered--;
11755 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11756 if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
11757 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
11760 if (! RegisterMove()) return FALSE;
11764 retVal = LoadGame(f, gameNumber, title, useList);
11766 /* Make move registered during previous look at this game, if any */
11767 MakeRegisteredMove();
11769 if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
11770 commentList[currentMove]
11771 = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
11772 DisplayComment(currentMove - 1, commentList[currentMove]);
11778 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
11780 ReloadGame (int offset)
11782 int gameNumber = lastLoadGameNumber + offset;
11783 if (lastLoadGameFP == NULL) {
11784 DisplayError(_("No game has been loaded yet"), 0);
11787 if (gameNumber <= 0) {
11788 DisplayError(_("Can't back up any further"), 0);
11791 if (cmailMsgLoaded) {
11792 return CmailLoadGame(lastLoadGameFP, gameNumber,
11793 lastLoadGameTitle, lastLoadGameUseList);
11795 return LoadGame(lastLoadGameFP, gameNumber,
11796 lastLoadGameTitle, lastLoadGameUseList);
11800 int keys[EmptySquare+1];
11803 PositionMatches (Board b1, Board b2)
11806 switch(appData.searchMode) {
11807 case 1: return CompareWithRights(b1, b2);
11809 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11810 if(b2[r][f] != EmptySquare && b1[r][f] != b2[r][f]) return FALSE;
11814 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11815 if((b2[r][f] == WhitePawn || b2[r][f] == BlackPawn) && b1[r][f] != b2[r][f]) return FALSE;
11816 sum += keys[b1[r][f]] - keys[b2[r][f]];
11820 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11821 sum += keys[b1[r][f]] - keys[b2[r][f]];
11833 int pieceList[256], quickBoard[256];
11834 ChessSquare pieceType[256] = { EmptySquare };
11835 Board soughtBoard, reverseBoard, flipBoard, rotateBoard;
11836 int counts[EmptySquare], minSought[EmptySquare], minReverse[EmptySquare], maxSought[EmptySquare], maxReverse[EmptySquare];
11837 int soughtTotal, turn;
11838 Boolean epOK, flipSearch;
11841 unsigned char piece, to;
11844 #define DSIZE (250000)
11846 Move initialSpace[DSIZE+1000]; // gamble on that game will not be more than 500 moves
11847 Move *moveDatabase = initialSpace;
11848 unsigned int movePtr, dataSize = DSIZE;
11851 MakePieceList (Board board, int *counts)
11853 int r, f, n=Q_PROMO, total=0;
11854 for(r=0;r<EmptySquare;r++) counts[r] = 0; // piece-type counts
11855 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11856 int sq = f + (r<<4);
11857 if(board[r][f] == EmptySquare) quickBoard[sq] = 0; else {
11858 quickBoard[sq] = ++n;
11860 pieceType[n] = board[r][f];
11861 counts[board[r][f]]++;
11862 if(board[r][f] == WhiteKing) pieceList[1] = n; else
11863 if(board[r][f] == BlackKing) pieceList[2] = n; // remember which are Kings, for castling
11867 epOK = gameInfo.variant != VariantXiangqi && gameInfo.variant != VariantBerolina;
11872 PackMove (int fromX, int fromY, int toX, int toY, ChessSquare promoPiece)
11874 int sq = fromX + (fromY<<4);
11875 int piece = quickBoard[sq];
11876 quickBoard[sq] = 0;
11877 moveDatabase[movePtr].to = pieceList[piece] = sq = toX + (toY<<4);
11878 if(piece == pieceList[1] && fromY == toY && (toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
11879 int from = toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT;
11880 moveDatabase[movePtr++].piece = Q_WCASTL;
11881 quickBoard[sq] = piece;
11882 piece = quickBoard[from]; quickBoard[from] = 0;
11883 moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
11885 if(piece == pieceList[2] && fromY == toY && (toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
11886 int from = (toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT) + (BOARD_HEIGHT-1 <<4);
11887 moveDatabase[movePtr++].piece = Q_BCASTL;
11888 quickBoard[sq] = piece;
11889 piece = quickBoard[from]; quickBoard[from] = 0;
11890 moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
11892 if(epOK && (pieceType[piece] == WhitePawn || pieceType[piece] == BlackPawn) && fromX != toX && quickBoard[sq] == 0) {
11893 quickBoard[(fromY<<4)+toX] = 0;
11894 moveDatabase[movePtr].piece = Q_EP;
11895 moveDatabase[movePtr++].to = (fromY<<4)+toX;
11896 moveDatabase[movePtr].to = sq;
11898 if(promoPiece != pieceType[piece]) {
11899 moveDatabase[movePtr++].piece = Q_PROMO;
11900 moveDatabase[movePtr].to = pieceType[piece] = (int) promoPiece;
11902 moveDatabase[movePtr].piece = piece;
11903 quickBoard[sq] = piece;
11908 PackGame (Board board)
11910 Move *newSpace = NULL;
11911 moveDatabase[movePtr].piece = 0; // terminate previous game
11912 if(movePtr > dataSize) {
11913 if(appData.debugMode) fprintf(debugFP, "move-cache overflow, enlarge to %d MB\n", dataSize/128);
11914 dataSize *= 8; // increase size by factor 8 (512KB -> 4MB -> 32MB -> 256MB -> 2GB)
11915 if(dataSize) newSpace = (Move*) calloc(dataSize + 1000, sizeof(Move));
11918 Move *p = moveDatabase, *q = newSpace;
11919 for(i=0; i<movePtr; i++) *q++ = *p++; // copy to newly allocated space
11920 if(dataSize > 8*DSIZE) free(moveDatabase); // and free old space (if it was allocated)
11921 moveDatabase = newSpace;
11922 } else { // calloc failed, we must be out of memory. Too bad...
11923 dataSize = 0; // prevent calloc events for all subsequent games
11924 return 0; // and signal this one isn't cached
11928 MakePieceList(board, counts);
11933 QuickCompare (Board board, int *minCounts, int *maxCounts)
11934 { // compare according to search mode
11936 switch(appData.searchMode)
11938 case 1: // exact position match
11939 if(!(turn & board[EP_STATUS-1])) return FALSE; // wrong side to move
11940 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11941 if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11944 case 2: // can have extra material on empty squares
11945 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11946 if(board[r][f] == EmptySquare) continue;
11947 if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11950 case 3: // material with exact Pawn structure
11951 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11952 if(board[r][f] != WhitePawn && board[r][f] != BlackPawn) continue;
11953 if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11954 } // fall through to material comparison
11955 case 4: // exact material
11956 for(r=0; r<EmptySquare; r++) if(counts[r] != maxCounts[r]) return FALSE;
11958 case 6: // material range with given imbalance
11959 for(r=0; r<BlackPawn; r++) if(counts[r] - minCounts[r] != counts[r+BlackPawn] - minCounts[r+BlackPawn]) return FALSE;
11960 // fall through to range comparison
11961 case 5: // material range
11962 for(r=0; r<EmptySquare; r++) if(counts[r] < minCounts[r] || counts[r] > maxCounts[r]) return FALSE;
11968 QuickScan (Board board, Move *move)
11969 { // reconstruct game,and compare all positions in it
11970 int cnt=0, stretch=0, total = MakePieceList(board, counts);
11972 int piece = move->piece;
11973 int to = move->to, from = pieceList[piece];
11974 if(piece <= Q_PROMO) { // special moves encoded by otherwise invalid piece numbers 1-4
11975 if(!piece) return -1;
11976 if(piece == Q_PROMO) { // promotion, encoded as (Q_PROMO, to) + (piece, promoType)
11977 piece = (++move)->piece;
11978 from = pieceList[piece];
11979 counts[pieceType[piece]]--;
11980 pieceType[piece] = (ChessSquare) move->to;
11981 counts[move->to]++;
11982 } else if(piece == Q_EP) { // e.p. capture, encoded as (Q_EP, ep-sqr) + (piece, to)
11983 counts[pieceType[quickBoard[to]]]--;
11984 quickBoard[to] = 0; total--;
11987 } else if(piece <= Q_BCASTL) { // castling, encoded as (Q_XCASTL, king-to) + (rook, rook-to)
11988 piece = pieceList[piece]; // first two elements of pieceList contain King numbers
11989 from = pieceList[piece]; // so this must be King
11990 quickBoard[from] = 0;
11991 pieceList[piece] = to;
11992 from = pieceList[(++move)->piece]; // for FRC this has to be done here
11993 quickBoard[from] = 0; // rook
11994 quickBoard[to] = piece;
11995 to = move->to; piece = move->piece;
11999 if(appData.searchMode > 2) counts[pieceType[quickBoard[to]]]--; // account capture
12000 if((total -= (quickBoard[to] != 0)) < soughtTotal) return -1; // piece count dropped below what we search for
12001 quickBoard[from] = 0;
12003 quickBoard[to] = piece;
12004 pieceList[piece] = to;
12006 if(QuickCompare(soughtBoard, minSought, maxSought) ||
12007 appData.ignoreColors && QuickCompare(reverseBoard, minReverse, maxReverse) ||
12008 flipSearch && (QuickCompare(flipBoard, minSought, maxSought) ||
12009 appData.ignoreColors && QuickCompare(rotateBoard, minReverse, maxReverse))
12011 static int lastCounts[EmptySquare+1];
12013 if(stretch) for(i=0; i<EmptySquare; i++) if(lastCounts[i] != counts[i]) { stretch = 0; break; } // reset if material changes
12014 if(stretch++ == 0) for(i=0; i<EmptySquare; i++) lastCounts[i] = counts[i]; // remember actual material
12015 } else stretch = 0;
12016 if(stretch && (appData.searchMode == 1 || stretch >= appData.stretch)) return cnt + 1 - stretch;
12025 flipSearch = FALSE;
12026 CopyBoard(soughtBoard, boards[currentMove]);
12027 soughtTotal = MakePieceList(soughtBoard, maxSought);
12028 soughtBoard[EP_STATUS-1] = (currentMove & 1) + 1;
12029 if(currentMove == 0 && gameMode == EditPosition) soughtBoard[EP_STATUS-1] = blackPlaysFirst + 1; // (!)
12030 CopyBoard(reverseBoard, boards[currentMove]);
12031 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12032 int piece = boards[currentMove][BOARD_HEIGHT-1-r][f];
12033 if(piece < BlackPawn) piece += BlackPawn; else if(piece < EmptySquare) piece -= BlackPawn; // color-flip
12034 reverseBoard[r][f] = piece;
12036 reverseBoard[EP_STATUS-1] = soughtBoard[EP_STATUS-1] ^ 3;
12037 for(r=0; r<6; r++) reverseBoard[CASTLING][r] = boards[currentMove][CASTLING][(r+3)%6];
12038 if(appData.findMirror && appData.searchMode <= 3 && (!nrCastlingRights
12039 || (boards[currentMove][CASTLING][2] == NoRights ||
12040 boards[currentMove][CASTLING][0] == NoRights && boards[currentMove][CASTLING][1] == NoRights )
12041 && (boards[currentMove][CASTLING][5] == NoRights ||
12042 boards[currentMove][CASTLING][3] == NoRights && boards[currentMove][CASTLING][4] == NoRights ) )
12045 CopyBoard(flipBoard, soughtBoard);
12046 CopyBoard(rotateBoard, reverseBoard);
12047 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12048 flipBoard[r][f] = soughtBoard[r][BOARD_WIDTH-1-f];
12049 rotateBoard[r][f] = reverseBoard[r][BOARD_WIDTH-1-f];
12052 for(r=0; r<BlackPawn; r++) maxReverse[r] = maxSought[r+BlackPawn], maxReverse[r+BlackPawn] = maxSought[r];
12053 if(appData.searchMode >= 5) {
12054 for(r=BOARD_HEIGHT/2; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) soughtBoard[r][f] = EmptySquare;
12055 MakePieceList(soughtBoard, minSought);
12056 for(r=0; r<BlackPawn; r++) minReverse[r] = minSought[r+BlackPawn], minReverse[r+BlackPawn] = minSought[r];
12058 if(gameInfo.variant == VariantCrazyhouse || gameInfo.variant == VariantShogi || gameInfo.variant == VariantBughouse)
12059 soughtTotal = 0; // in drop games nr of pieces does not fall monotonously
12062 GameInfo dummyInfo;
12063 static int creatingBook;
12066 GameContainsPosition (FILE *f, ListGame *lg)
12068 int next, btm=0, plyNr=0, scratch=forwardMostMove+2&~1;
12069 int fromX, fromY, toX, toY;
12071 static int initDone=FALSE;
12073 // weed out games based on numerical tag comparison
12074 if(lg->gameInfo.variant != gameInfo.variant) return -1; // wrong variant
12075 if(appData.eloThreshold1 && (lg->gameInfo.whiteRating < appData.eloThreshold1 && lg->gameInfo.blackRating < appData.eloThreshold1)) return -1;
12076 if(appData.eloThreshold2 && (lg->gameInfo.whiteRating < appData.eloThreshold2 || lg->gameInfo.blackRating < appData.eloThreshold2)) return -1;
12077 if(appData.dateThreshold && (!lg->gameInfo.date || atoi(lg->gameInfo.date) < appData.dateThreshold)) return -1;
12079 for(next = WhitePawn; next<EmptySquare; next++) keys[next] = random()>>8 ^ random()<<6 ^random()<<20;
12082 if(lg->gameInfo.fen) ParseFEN(boards[scratch], &btm, lg->gameInfo.fen);
12083 else CopyBoard(boards[scratch], initialPosition); // default start position
12086 if((next = QuickScan( boards[scratch], &moveDatabase[lg->moves] )) < 0) return -1; // quick scan rules out it is there
12087 if(appData.searchMode >= 4) return next; // for material searches, trust QuickScan.
12090 if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
12091 fseek(f, lg->offset, 0);
12094 yyboardindex = scratch;
12095 quickFlag = plyNr+1;
12100 if(plyNr) return -1; // after we have seen moves, any tags will be start of next game
12106 if(plyNr) return -1; // after we have seen moves, this is for new game
12109 case AmbiguousMove: // we cannot reconstruct the game beyond these two
12110 case ImpossibleMove:
12111 case WhiteWins: // game ends here with these four
12114 case GameUnfinished:
12118 if(appData.testLegality) return -1;
12119 case WhiteCapturesEnPassant:
12120 case BlackCapturesEnPassant:
12121 case WhitePromotion:
12122 case BlackPromotion:
12123 case WhiteNonPromotion:
12124 case BlackNonPromotion:
12126 case WhiteKingSideCastle:
12127 case WhiteQueenSideCastle:
12128 case BlackKingSideCastle:
12129 case BlackQueenSideCastle:
12130 case WhiteKingSideCastleWild:
12131 case WhiteQueenSideCastleWild:
12132 case BlackKingSideCastleWild:
12133 case BlackQueenSideCastleWild:
12134 case WhiteHSideCastleFR:
12135 case WhiteASideCastleFR:
12136 case BlackHSideCastleFR:
12137 case BlackASideCastleFR:
12138 fromX = currentMoveString[0] - AAA;
12139 fromY = currentMoveString[1] - ONE;
12140 toX = currentMoveString[2] - AAA;
12141 toY = currentMoveString[3] - ONE;
12142 promoChar = currentMoveString[4];
12146 fromX = next == WhiteDrop ?
12147 (int) CharToPiece(ToUpper(currentMoveString[0])) :
12148 (int) CharToPiece(ToLower(currentMoveString[0]));
12150 toX = currentMoveString[2] - AAA;
12151 toY = currentMoveString[3] - ONE;
12155 // Move encountered; peform it. We need to shuttle between two boards, as even/odd index determines side to move
12157 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[scratch]);
12158 if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
12159 if(appData.ignoreColors && PositionMatches(boards[scratch], reverseBoard)) return plyNr;
12160 if(appData.findMirror) {
12161 if(PositionMatches(boards[scratch], flipBoard)) return plyNr;
12162 if(appData.ignoreColors && PositionMatches(boards[scratch], rotateBoard)) return plyNr;
12167 /* Load the nth game from open file f */
12169 LoadGame (FILE *f, int gameNumber, char *title, int useList)
12173 int gn = gameNumber;
12174 ListGame *lg = NULL;
12175 int numPGNTags = 0;
12177 GameMode oldGameMode;
12178 VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
12180 if (appData.debugMode)
12181 fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
12183 if (gameMode == Training )
12184 SetTrainingModeOff();
12186 oldGameMode = gameMode;
12187 if (gameMode != BeginningOfGame) {
12188 Reset(FALSE, TRUE);
12192 if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
12193 fclose(lastLoadGameFP);
12197 lg = (ListGame *) ListElem(&gameList, gameNumber-1);
12200 fseek(f, lg->offset, 0);
12201 GameListHighlight(gameNumber);
12202 pos = lg->position;
12206 if(oldGameMode == AnalyzeFile && appData.loadGameIndex == -1)
12207 appData.loadGameIndex = 0; // [HGM] suppress error message if we reach file end after auto-stepping analysis
12209 DisplayError(_("Game number out of range"), 0);
12214 if (fseek(f, 0, 0) == -1) {
12215 if (f == lastLoadGameFP ?
12216 gameNumber == lastLoadGameNumber + 1 :
12220 DisplayError(_("Can't seek on game file"), 0);
12225 lastLoadGameFP = f;
12226 lastLoadGameNumber = gameNumber;
12227 safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
12228 lastLoadGameUseList = useList;
12232 if (lg && lg->gameInfo.white && lg->gameInfo.black) {
12233 snprintf(buf, sizeof(buf), "%s %s %s", lg->gameInfo.white, _("vs."),
12234 lg->gameInfo.black);
12236 } else if (*title != NULLCHAR) {
12237 if (gameNumber > 1) {
12238 snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
12241 DisplayTitle(title);
12245 if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
12246 gameMode = PlayFromGameFile;
12250 currentMove = forwardMostMove = backwardMostMove = 0;
12251 CopyBoard(boards[0], initialPosition);
12255 * Skip the first gn-1 games in the file.
12256 * Also skip over anything that precedes an identifiable
12257 * start of game marker, to avoid being confused by
12258 * garbage at the start of the file. Currently
12259 * recognized start of game markers are the move number "1",
12260 * the pattern "gnuchess .* game", the pattern
12261 * "^[#;%] [^ ]* game file", and a PGN tag block.
12262 * A game that starts with one of the latter two patterns
12263 * will also have a move number 1, possibly
12264 * following a position diagram.
12265 * 5-4-02: Let's try being more lenient and allowing a game to
12266 * start with an unnumbered move. Does that break anything?
12268 cm = lastLoadGameStart = EndOfFile;
12270 yyboardindex = forwardMostMove;
12271 cm = (ChessMove) Myylex();
12274 if (cmailMsgLoaded) {
12275 nCmailGames = CMAIL_MAX_GAMES - gn;
12278 DisplayError(_("Game not found in file"), 0);
12285 lastLoadGameStart = cm;
12288 case MoveNumberOne:
12289 switch (lastLoadGameStart) {
12294 case MoveNumberOne:
12296 gn--; /* count this game */
12297 lastLoadGameStart = cm;
12306 switch (lastLoadGameStart) {
12309 case MoveNumberOne:
12311 gn--; /* count this game */
12312 lastLoadGameStart = cm;
12315 lastLoadGameStart = cm; /* game counted already */
12323 yyboardindex = forwardMostMove;
12324 cm = (ChessMove) Myylex();
12325 } while (cm == PGNTag || cm == Comment);
12332 if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
12333 if ( cmailResult[CMAIL_MAX_GAMES - gn - 1]
12334 != CMAIL_OLD_RESULT) {
12336 cmailResult[ CMAIL_MAX_GAMES
12337 - gn - 1] = CMAIL_OLD_RESULT;
12343 /* Only a NormalMove can be at the start of a game
12344 * without a position diagram. */
12345 if (lastLoadGameStart == EndOfFile ) {
12347 lastLoadGameStart = MoveNumberOne;
12356 if (appData.debugMode)
12357 fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
12359 if (cm == XBoardGame) {
12360 /* Skip any header junk before position diagram and/or move 1 */
12362 yyboardindex = forwardMostMove;
12363 cm = (ChessMove) Myylex();
12365 if (cm == EndOfFile ||
12366 cm == GNUChessGame || cm == XBoardGame) {
12367 /* Empty game; pretend end-of-file and handle later */
12372 if (cm == MoveNumberOne || cm == PositionDiagram ||
12373 cm == PGNTag || cm == Comment)
12376 } else if (cm == GNUChessGame) {
12377 if (gameInfo.event != NULL) {
12378 free(gameInfo.event);
12380 gameInfo.event = StrSave(yy_text);
12383 startedFromSetupPosition = FALSE;
12384 while (cm == PGNTag) {
12385 if (appData.debugMode)
12386 fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
12387 err = ParsePGNTag(yy_text, &gameInfo);
12388 if (!err) numPGNTags++;
12390 /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
12391 if(gameInfo.variant != oldVariant) {
12392 startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
12393 ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
12394 InitPosition(TRUE);
12395 oldVariant = gameInfo.variant;
12396 if (appData.debugMode)
12397 fprintf(debugFP, "New variant %d\n", (int) oldVariant);
12401 if (gameInfo.fen != NULL) {
12402 Board initial_position;
12403 startedFromSetupPosition = TRUE;
12404 if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
12406 DisplayError(_("Bad FEN position in file"), 0);
12409 CopyBoard(boards[0], initial_position);
12410 if (blackPlaysFirst) {
12411 currentMove = forwardMostMove = backwardMostMove = 1;
12412 CopyBoard(boards[1], initial_position);
12413 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12414 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12415 timeRemaining[0][1] = whiteTimeRemaining;
12416 timeRemaining[1][1] = blackTimeRemaining;
12417 if (commentList[0] != NULL) {
12418 commentList[1] = commentList[0];
12419 commentList[0] = NULL;
12422 currentMove = forwardMostMove = backwardMostMove = 0;
12424 /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
12426 initialRulePlies = FENrulePlies;
12427 for( i=0; i< nrCastlingRights; i++ )
12428 initialRights[i] = initial_position[CASTLING][i];
12430 yyboardindex = forwardMostMove;
12431 free(gameInfo.fen);
12432 gameInfo.fen = NULL;
12435 yyboardindex = forwardMostMove;
12436 cm = (ChessMove) Myylex();
12438 /* Handle comments interspersed among the tags */
12439 while (cm == Comment) {
12441 if (appData.debugMode)
12442 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
12444 AppendComment(currentMove, p, FALSE);
12445 yyboardindex = forwardMostMove;
12446 cm = (ChessMove) Myylex();
12450 /* don't rely on existence of Event tag since if game was
12451 * pasted from clipboard the Event tag may not exist
12453 if (numPGNTags > 0){
12455 if (gameInfo.variant == VariantNormal) {
12456 VariantClass v = StringToVariant(gameInfo.event);
12457 // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
12458 if(v < VariantShogi) gameInfo.variant = v;
12461 if( appData.autoDisplayTags ) {
12462 tags = PGNTags(&gameInfo);
12463 TagsPopUp(tags, CmailMsg());
12468 /* Make something up, but don't display it now */
12473 if (cm == PositionDiagram) {
12476 Board initial_position;
12478 if (appData.debugMode)
12479 fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
12481 if (!startedFromSetupPosition) {
12483 for (i = BOARD_HEIGHT - 1; i >= 0; i--)
12484 for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
12495 initial_position[i][j++] = CharToPiece(*p);
12498 while (*p == ' ' || *p == '\t' ||
12499 *p == '\n' || *p == '\r') p++;
12501 if (strncmp(p, "black", strlen("black"))==0)
12502 blackPlaysFirst = TRUE;
12504 blackPlaysFirst = FALSE;
12505 startedFromSetupPosition = TRUE;
12507 CopyBoard(boards[0], initial_position);
12508 if (blackPlaysFirst) {
12509 currentMove = forwardMostMove = backwardMostMove = 1;
12510 CopyBoard(boards[1], initial_position);
12511 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12512 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12513 timeRemaining[0][1] = whiteTimeRemaining;
12514 timeRemaining[1][1] = blackTimeRemaining;
12515 if (commentList[0] != NULL) {
12516 commentList[1] = commentList[0];
12517 commentList[0] = NULL;
12520 currentMove = forwardMostMove = backwardMostMove = 0;
12523 yyboardindex = forwardMostMove;
12524 cm = (ChessMove) Myylex();
12527 if(!creatingBook) {
12528 if (first.pr == NoProc) {
12529 StartChessProgram(&first);
12531 InitChessProgram(&first, FALSE);
12532 SendToProgram("force\n", &first);
12533 if (startedFromSetupPosition) {
12534 SendBoard(&first, forwardMostMove);
12535 if (appData.debugMode) {
12536 fprintf(debugFP, "Load Game\n");
12538 DisplayBothClocks();
12542 /* [HGM] server: flag to write setup moves in broadcast file as one */
12543 loadFlag = appData.suppressLoadMoves;
12545 while (cm == Comment) {
12547 if (appData.debugMode)
12548 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
12550 AppendComment(currentMove, p, FALSE);
12551 yyboardindex = forwardMostMove;
12552 cm = (ChessMove) Myylex();
12555 if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
12556 cm == WhiteWins || cm == BlackWins ||
12557 cm == GameIsDrawn || cm == GameUnfinished) {
12558 DisplayMessage("", _("No moves in game"));
12559 if (cmailMsgLoaded) {
12560 if (appData.debugMode)
12561 fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
12565 DrawPosition(FALSE, boards[currentMove]);
12566 DisplayBothClocks();
12567 gameMode = EditGame;
12574 // [HGM] PV info: routine tests if comment empty
12575 if (!matchMode && (pausing || appData.timeDelay != 0)) {
12576 DisplayComment(currentMove - 1, commentList[currentMove]);
12578 if (!matchMode && appData.timeDelay != 0)
12579 DrawPosition(FALSE, boards[currentMove]);
12581 if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
12582 programStats.ok_to_send = 1;
12585 /* if the first token after the PGN tags is a move
12586 * and not move number 1, retrieve it from the parser
12588 if (cm != MoveNumberOne)
12589 LoadGameOneMove(cm);
12591 /* load the remaining moves from the file */
12592 while (LoadGameOneMove(EndOfFile)) {
12593 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12594 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12597 /* rewind to the start of the game */
12598 currentMove = backwardMostMove;
12600 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12602 if (oldGameMode == AnalyzeFile) {
12603 appData.loadGameIndex = -1; // [HGM] order auto-stepping through games
12604 AnalyzeFileEvent();
12606 if (oldGameMode == AnalyzeMode) {
12607 AnalyzeFileEvent();
12610 if(creatingBook) return TRUE;
12611 if (!matchMode && pos > 0) {
12612 ToNrEvent(pos); // [HGM] no autoplay if selected on position
12614 if (matchMode || appData.timeDelay == 0) {
12616 } else if (appData.timeDelay > 0) {
12617 AutoPlayGameLoop();
12620 if (appData.debugMode)
12621 fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
12623 loadFlag = 0; /* [HGM] true game starts */
12627 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
12629 ReloadPosition (int offset)
12631 int positionNumber = lastLoadPositionNumber + offset;
12632 if (lastLoadPositionFP == NULL) {
12633 DisplayError(_("No position has been loaded yet"), 0);
12636 if (positionNumber <= 0) {
12637 DisplayError(_("Can't back up any further"), 0);
12640 return LoadPosition(lastLoadPositionFP, positionNumber,
12641 lastLoadPositionTitle);
12644 /* Load the nth position from the given file */
12646 LoadPositionFromFile (char *filename, int n, char *title)
12651 if (strcmp(filename, "-") == 0) {
12652 return LoadPosition(stdin, n, "stdin");
12654 f = fopen(filename, "rb");
12656 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12657 DisplayError(buf, errno);
12660 return LoadPosition(f, n, title);
12665 /* Load the nth position from the given open file, and close it */
12667 LoadPosition (FILE *f, int positionNumber, char *title)
12669 char *p, line[MSG_SIZ];
12670 Board initial_position;
12671 int i, j, fenMode, pn;
12673 if (gameMode == Training )
12674 SetTrainingModeOff();
12676 if (gameMode != BeginningOfGame) {
12677 Reset(FALSE, TRUE);
12679 if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
12680 fclose(lastLoadPositionFP);
12682 if (positionNumber == 0) positionNumber = 1;
12683 lastLoadPositionFP = f;
12684 lastLoadPositionNumber = positionNumber;
12685 safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
12686 if (first.pr == NoProc && !appData.noChessProgram) {
12687 StartChessProgram(&first);
12688 InitChessProgram(&first, FALSE);
12690 pn = positionNumber;
12691 if (positionNumber < 0) {
12692 /* Negative position number means to seek to that byte offset */
12693 if (fseek(f, -positionNumber, 0) == -1) {
12694 DisplayError(_("Can't seek on position file"), 0);
12699 if (fseek(f, 0, 0) == -1) {
12700 if (f == lastLoadPositionFP ?
12701 positionNumber == lastLoadPositionNumber + 1 :
12702 positionNumber == 1) {
12705 DisplayError(_("Can't seek on position file"), 0);
12710 /* See if this file is FEN or old-style xboard */
12711 if (fgets(line, MSG_SIZ, f) == NULL) {
12712 DisplayError(_("Position not found in file"), 0);
12715 // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
12716 fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
12719 if (fenMode || line[0] == '#') pn--;
12721 /* skip positions before number pn */
12722 if (fgets(line, MSG_SIZ, f) == NULL) {
12724 DisplayError(_("Position not found in file"), 0);
12727 if (fenMode || line[0] == '#') pn--;
12732 if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
12733 DisplayError(_("Bad FEN position in file"), 0);
12737 (void) fgets(line, MSG_SIZ, f);
12738 (void) fgets(line, MSG_SIZ, f);
12740 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
12741 (void) fgets(line, MSG_SIZ, f);
12742 for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
12745 initial_position[i][j++] = CharToPiece(*p);
12749 blackPlaysFirst = FALSE;
12751 (void) fgets(line, MSG_SIZ, f);
12752 if (strncmp(line, "black", strlen("black"))==0)
12753 blackPlaysFirst = TRUE;
12756 startedFromSetupPosition = TRUE;
12758 CopyBoard(boards[0], initial_position);
12759 if (blackPlaysFirst) {
12760 currentMove = forwardMostMove = backwardMostMove = 1;
12761 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12762 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12763 CopyBoard(boards[1], initial_position);
12764 DisplayMessage("", _("Black to play"));
12766 currentMove = forwardMostMove = backwardMostMove = 0;
12767 DisplayMessage("", _("White to play"));
12769 initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
12770 if(first.pr != NoProc) { // [HGM] in tourney-mode a position can be loaded before the chess engine is installed
12771 SendToProgram("force\n", &first);
12772 SendBoard(&first, forwardMostMove);
12774 if (appData.debugMode) {
12776 for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
12777 for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
12778 fprintf(debugFP, "Load Position\n");
12781 if (positionNumber > 1) {
12782 snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
12783 DisplayTitle(line);
12785 DisplayTitle(title);
12787 gameMode = EditGame;
12790 timeRemaining[0][1] = whiteTimeRemaining;
12791 timeRemaining[1][1] = blackTimeRemaining;
12792 DrawPosition(FALSE, boards[currentMove]);
12799 CopyPlayerNameIntoFileName (char **dest, char *src)
12801 while (*src != NULLCHAR && *src != ',') {
12806 *(*dest)++ = *src++;
12812 DefaultFileName (char *ext)
12814 static char def[MSG_SIZ];
12817 if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
12819 CopyPlayerNameIntoFileName(&p, gameInfo.white);
12821 CopyPlayerNameIntoFileName(&p, gameInfo.black);
12823 safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
12830 /* Save the current game to the given file */
12832 SaveGameToFile (char *filename, int append)
12836 int result, i, t,tot=0;
12838 if (strcmp(filename, "-") == 0) {
12839 return SaveGame(stdout, 0, NULL);
12841 for(i=0; i<10; i++) { // upto 10 tries
12842 f = fopen(filename, append ? "a" : "w");
12843 if(f && i) fprintf(f, "[Delay \"%d retries, %d msec\"]\n",i,tot);
12844 if(f || errno != 13) break;
12845 DoSleep(t = 5 + random()%11); // wait 5-15 msec
12849 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12850 DisplayError(buf, errno);
12853 safeStrCpy(buf, lastMsg, MSG_SIZ);
12854 DisplayMessage(_("Waiting for access to save file"), "");
12855 flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing
12856 DisplayMessage(_("Saving game"), "");
12857 if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError(_("Bad Seek"), errno); // better safe than sorry...
12858 result = SaveGame(f, 0, NULL);
12859 DisplayMessage(buf, "");
12866 SavePart (char *str)
12868 static char buf[MSG_SIZ];
12871 p = strchr(str, ' ');
12872 if (p == NULL) return str;
12873 strncpy(buf, str, p - str);
12874 buf[p - str] = NULLCHAR;
12878 #define PGN_MAX_LINE 75
12880 #define PGN_SIDE_WHITE 0
12881 #define PGN_SIDE_BLACK 1
12884 FindFirstMoveOutOfBook (int side)
12888 if( backwardMostMove == 0 && ! startedFromSetupPosition) {
12889 int index = backwardMostMove;
12890 int has_book_hit = 0;
12892 if( (index % 2) != side ) {
12896 while( index < forwardMostMove ) {
12897 /* Check to see if engine is in book */
12898 int depth = pvInfoList[index].depth;
12899 int score = pvInfoList[index].score;
12905 else if( score == 0 && depth == 63 ) {
12906 in_book = 1; /* Zappa */
12908 else if( score == 2 && depth == 99 ) {
12909 in_book = 1; /* Abrok */
12912 has_book_hit += in_book;
12928 GetOutOfBookInfo (char * buf)
12932 int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12934 oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
12935 oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
12939 if( oob[0] >= 0 || oob[1] >= 0 ) {
12940 for( i=0; i<2; i++ ) {
12944 if( i > 0 && oob[0] >= 0 ) {
12945 strcat( buf, " " );
12948 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
12949 sprintf( buf+strlen(buf), "%s%.2f",
12950 pvInfoList[idx].score >= 0 ? "+" : "",
12951 pvInfoList[idx].score / 100.0 );
12957 /* Save game in PGN style and close the file */
12959 SaveGamePGN (FILE *f)
12961 int i, offset, linelen, newblock;
12964 int movelen, numlen, blank;
12965 char move_buffer[100]; /* [AS] Buffer for move+PV info */
12967 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12969 PrintPGNTags(f, &gameInfo);
12971 if(appData.numberTag && matchMode) fprintf(f, "[Number \"%d\"]\n", nextGame+1); // [HGM] number tag
12973 if (backwardMostMove > 0 || startedFromSetupPosition) {
12974 char *fen = PositionToFEN(backwardMostMove, NULL, 1);
12975 fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
12976 fprintf(f, "\n{--------------\n");
12977 PrintPosition(f, backwardMostMove);
12978 fprintf(f, "--------------}\n");
12982 /* [AS] Out of book annotation */
12983 if( appData.saveOutOfBookInfo ) {
12986 GetOutOfBookInfo( buf );
12988 if( buf[0] != '\0' ) {
12989 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
12996 i = backwardMostMove;
13000 while (i < forwardMostMove) {
13001 /* Print comments preceding this move */
13002 if (commentList[i] != NULL) {
13003 if (linelen > 0) fprintf(f, "\n");
13004 fprintf(f, "%s", commentList[i]);
13009 /* Format move number */
13011 snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
13014 snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
13016 numtext[0] = NULLCHAR;
13018 numlen = strlen(numtext);
13021 /* Print move number */
13022 blank = linelen > 0 && numlen > 0;
13023 if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
13032 fprintf(f, "%s", numtext);
13036 safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
13037 movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
13040 blank = linelen > 0 && movelen > 0;
13041 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
13050 fprintf(f, "%s", move_buffer);
13051 linelen += movelen;
13053 /* [AS] Add PV info if present */
13054 if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
13055 /* [HGM] add time */
13056 char buf[MSG_SIZ]; int seconds;
13058 seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
13064 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
13067 seconds = (seconds + 4)/10; // round to full seconds
13069 snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
13071 snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
13074 snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
13075 pvInfoList[i].score >= 0 ? "+" : "",
13076 pvInfoList[i].score / 100.0,
13077 pvInfoList[i].depth,
13080 movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
13082 /* Print score/depth */
13083 blank = linelen > 0 && movelen > 0;
13084 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
13093 fprintf(f, "%s", move_buffer);
13094 linelen += movelen;
13100 /* Start a new line */
13101 if (linelen > 0) fprintf(f, "\n");
13103 /* Print comments after last move */
13104 if (commentList[i] != NULL) {
13105 fprintf(f, "%s\n", commentList[i]);
13109 if (gameInfo.resultDetails != NULL &&
13110 gameInfo.resultDetails[0] != NULLCHAR) {
13111 fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
13112 PGNResult(gameInfo.result));
13114 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
13118 lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
13122 /* Save game in old style and close the file */
13124 SaveGameOldStyle (FILE *f)
13129 tm = time((time_t *) NULL);
13131 fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
13134 if (backwardMostMove > 0 || startedFromSetupPosition) {
13135 fprintf(f, "\n[--------------\n");
13136 PrintPosition(f, backwardMostMove);
13137 fprintf(f, "--------------]\n");
13142 i = backwardMostMove;
13143 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13145 while (i < forwardMostMove) {
13146 if (commentList[i] != NULL) {
13147 fprintf(f, "[%s]\n", commentList[i]);
13150 if ((i % 2) == 1) {
13151 fprintf(f, "%d. ... %s\n", (i - offset)/2 + 1, parseList[i]);
13154 fprintf(f, "%d. %s ", (i - offset)/2 + 1, parseList[i]);
13156 if (commentList[i] != NULL) {
13160 if (i >= forwardMostMove) {
13164 fprintf(f, "%s\n", parseList[i]);
13169 if (commentList[i] != NULL) {
13170 fprintf(f, "[%s]\n", commentList[i]);
13173 /* This isn't really the old style, but it's close enough */
13174 if (gameInfo.resultDetails != NULL &&
13175 gameInfo.resultDetails[0] != NULLCHAR) {
13176 fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
13177 gameInfo.resultDetails);
13179 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
13186 /* Save the current game to open file f and close the file */
13188 SaveGame (FILE *f, int dummy, char *dummy2)
13190 if (gameMode == EditPosition) EditPositionDone(TRUE);
13191 lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
13192 if (appData.oldSaveStyle)
13193 return SaveGameOldStyle(f);
13195 return SaveGamePGN(f);
13198 /* Save the current position to the given file */
13200 SavePositionToFile (char *filename)
13205 if (strcmp(filename, "-") == 0) {
13206 return SavePosition(stdout, 0, NULL);
13208 f = fopen(filename, "a");
13210 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13211 DisplayError(buf, errno);
13214 safeStrCpy(buf, lastMsg, MSG_SIZ);
13215 DisplayMessage(_("Waiting for access to save file"), "");
13216 flock(fileno(f), LOCK_EX); // [HGM] lock
13217 DisplayMessage(_("Saving position"), "");
13218 lseek(fileno(f), 0, SEEK_END); // better safe than sorry...
13219 SavePosition(f, 0, NULL);
13220 DisplayMessage(buf, "");
13226 /* Save the current position to the given open file and close the file */
13228 SavePosition (FILE *f, int dummy, char *dummy2)
13233 if (gameMode == EditPosition) EditPositionDone(TRUE);
13234 if (appData.oldSaveStyle) {
13235 tm = time((time_t *) NULL);
13237 fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
13239 fprintf(f, "[--------------\n");
13240 PrintPosition(f, currentMove);
13241 fprintf(f, "--------------]\n");
13243 fen = PositionToFEN(currentMove, NULL, 1);
13244 fprintf(f, "%s\n", fen);
13252 ReloadCmailMsgEvent (int unregister)
13255 static char *inFilename = NULL;
13256 static char *outFilename;
13258 struct stat inbuf, outbuf;
13261 /* Any registered moves are unregistered if unregister is set, */
13262 /* i.e. invoked by the signal handler */
13264 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
13265 cmailMoveRegistered[i] = FALSE;
13266 if (cmailCommentList[i] != NULL) {
13267 free(cmailCommentList[i]);
13268 cmailCommentList[i] = NULL;
13271 nCmailMovesRegistered = 0;
13274 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
13275 cmailResult[i] = CMAIL_NOT_RESULT;
13279 if (inFilename == NULL) {
13280 /* Because the filenames are static they only get malloced once */
13281 /* and they never get freed */
13282 inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
13283 sprintf(inFilename, "%s.game.in", appData.cmailGameName);
13285 outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
13286 sprintf(outFilename, "%s.out", appData.cmailGameName);
13289 status = stat(outFilename, &outbuf);
13291 cmailMailedMove = FALSE;
13293 status = stat(inFilename, &inbuf);
13294 cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
13297 /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
13298 counts the games, notes how each one terminated, etc.
13300 It would be nice to remove this kludge and instead gather all
13301 the information while building the game list. (And to keep it
13302 in the game list nodes instead of having a bunch of fixed-size
13303 parallel arrays.) Note this will require getting each game's
13304 termination from the PGN tags, as the game list builder does
13305 not process the game moves. --mann
13307 cmailMsgLoaded = TRUE;
13308 LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
13310 /* Load first game in the file or popup game menu */
13311 LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
13313 #endif /* !WIN32 */
13321 char string[MSG_SIZ];
13323 if ( cmailMailedMove
13324 || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
13325 return TRUE; /* Allow free viewing */
13328 /* Unregister move to ensure that we don't leave RegisterMove */
13329 /* with the move registered when the conditions for registering no */
13331 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
13332 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
13333 nCmailMovesRegistered --;
13335 if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
13337 free(cmailCommentList[lastLoadGameNumber - 1]);
13338 cmailCommentList[lastLoadGameNumber - 1] = NULL;
13342 if (cmailOldMove == -1) {
13343 DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
13347 if (currentMove > cmailOldMove + 1) {
13348 DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
13352 if (currentMove < cmailOldMove) {
13353 DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
13357 if (forwardMostMove > currentMove) {
13358 /* Silently truncate extra moves */
13362 if ( (currentMove == cmailOldMove + 1)
13363 || ( (currentMove == cmailOldMove)
13364 && ( (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
13365 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
13366 if (gameInfo.result != GameUnfinished) {
13367 cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
13370 if (commentList[currentMove] != NULL) {
13371 cmailCommentList[lastLoadGameNumber - 1]
13372 = StrSave(commentList[currentMove]);
13374 safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
13376 if (appData.debugMode)
13377 fprintf(debugFP, "Saving %s for game %d\n",
13378 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
13380 snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
13382 f = fopen(string, "w");
13383 if (appData.oldSaveStyle) {
13384 SaveGameOldStyle(f); /* also closes the file */
13386 snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
13387 f = fopen(string, "w");
13388 SavePosition(f, 0, NULL); /* also closes the file */
13390 fprintf(f, "{--------------\n");
13391 PrintPosition(f, currentMove);
13392 fprintf(f, "--------------}\n\n");
13394 SaveGame(f, 0, NULL); /* also closes the file*/
13397 cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
13398 nCmailMovesRegistered ++;
13399 } else if (nCmailGames == 1) {
13400 DisplayError(_("You have not made a move yet"), 0);
13411 static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
13412 FILE *commandOutput;
13413 char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
13414 int nBytes = 0; /* Suppress warnings on uninitialized variables */
13420 if (! cmailMsgLoaded) {
13421 DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
13425 if (nCmailGames == nCmailResults) {
13426 DisplayError(_("No unfinished games"), 0);
13430 #if CMAIL_PROHIBIT_REMAIL
13431 if (cmailMailedMove) {
13432 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);
13433 DisplayError(msg, 0);
13438 if (! (cmailMailedMove || RegisterMove())) return;
13440 if ( cmailMailedMove
13441 || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
13442 snprintf(string, MSG_SIZ, partCommandString,
13443 appData.debugMode ? " -v" : "", appData.cmailGameName);
13444 commandOutput = popen(string, "r");
13446 if (commandOutput == NULL) {
13447 DisplayError(_("Failed to invoke cmail"), 0);
13449 for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
13450 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
13452 if (nBuffers > 1) {
13453 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
13454 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
13455 nBytes = MSG_SIZ - 1;
13457 (void) memcpy(msg, buffer, nBytes);
13459 *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
13461 if(StrStr(msg, "Mailed cmail message to ") != NULL) {
13462 cmailMailedMove = TRUE; /* Prevent >1 moves */
13465 for (i = 0; i < nCmailGames; i ++) {
13466 if (cmailResult[i] == CMAIL_NOT_RESULT) {
13471 && ( (arcDir = (char *) getenv("CMAIL_ARCDIR"))
13473 snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
13475 appData.cmailGameName,
13477 LoadGameFromFile(buffer, 1, buffer, FALSE);
13478 cmailMsgLoaded = FALSE;
13482 DisplayInformation(msg);
13483 pclose(commandOutput);
13486 if ((*cmailMsg) != '\0') {
13487 DisplayInformation(cmailMsg);
13492 #endif /* !WIN32 */
13501 int prependComma = 0;
13503 char string[MSG_SIZ]; /* Space for game-list */
13506 if (!cmailMsgLoaded) return "";
13508 if (cmailMailedMove) {
13509 snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
13511 /* Create a list of games left */
13512 snprintf(string, MSG_SIZ, "[");
13513 for (i = 0; i < nCmailGames; i ++) {
13514 if (! ( cmailMoveRegistered[i]
13515 || (cmailResult[i] == CMAIL_OLD_RESULT))) {
13516 if (prependComma) {
13517 snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
13519 snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
13523 strcat(string, number);
13526 strcat(string, "]");
13528 if (nCmailMovesRegistered + nCmailResults == 0) {
13529 switch (nCmailGames) {
13531 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
13535 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
13539 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
13544 switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
13546 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
13551 if (nCmailResults == nCmailGames) {
13552 snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
13554 snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
13559 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
13571 if (gameMode == Training)
13572 SetTrainingModeOff();
13575 cmailMsgLoaded = FALSE;
13576 if (appData.icsActive) {
13577 SendToICS(ics_prefix);
13578 SendToICS("refresh\n");
13583 ExitEvent (int status)
13587 /* Give up on clean exit */
13591 /* Keep trying for clean exit */
13595 if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
13597 if (telnetISR != NULL) {
13598 RemoveInputSource(telnetISR);
13600 if (icsPR != NoProc) {
13601 DestroyChildProcess(icsPR, TRUE);
13604 /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
13605 GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
13607 /* [HGM] crash: the above GameEnds() is a dud if another one was running */
13608 /* make sure this other one finishes before killing it! */
13609 if(endingGame) { int count = 0;
13610 if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
13611 while(endingGame && count++ < 10) DoSleep(1);
13612 if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
13615 /* Kill off chess programs */
13616 if (first.pr != NoProc) {
13619 DoSleep( appData.delayBeforeQuit );
13620 SendToProgram("quit\n", &first);
13621 DoSleep( appData.delayAfterQuit );
13622 DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
13624 if (second.pr != NoProc) {
13625 DoSleep( appData.delayBeforeQuit );
13626 SendToProgram("quit\n", &second);
13627 DoSleep( appData.delayAfterQuit );
13628 DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
13630 if (first.isr != NULL) {
13631 RemoveInputSource(first.isr);
13633 if (second.isr != NULL) {
13634 RemoveInputSource(second.isr);
13637 if (pairing.pr != NoProc) SendToProgram("quit\n", &pairing);
13638 if (pairing.isr != NULL) RemoveInputSource(pairing.isr);
13640 ShutDownFrontEnd();
13645 PauseEngine (ChessProgramState *cps)
13647 SendToProgram("pause\n", cps);
13652 UnPauseEngine (ChessProgramState *cps)
13654 SendToProgram("resume\n", cps);
13661 if (appData.debugMode)
13662 fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
13666 if(stalledEngine) { // [HGM] pause: resume game by releasing withheld move
13668 if(gameMode == TwoMachinesPlay) { // we might have to make the opponent resume pondering
13669 if(stalledEngine->other->pause == 2) UnPauseEngine(stalledEngine->other);
13670 else if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine->other);
13672 if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine);
13673 HandleMachineMove(stashedInputMove, stalledEngine);
13674 stalledEngine = NULL;
13677 if (gameMode == MachinePlaysWhite ||
13678 gameMode == TwoMachinesPlay ||
13679 gameMode == MachinePlaysBlack) { // the thinking engine must have used pause mode, or it would have been stalledEngine
13680 if(first.pause) UnPauseEngine(&first);
13681 else if(appData.ponderNextMove) SendToProgram("hard\n", &first);
13682 if(second.pause) UnPauseEngine(&second);
13683 else if(gameMode == TwoMachinesPlay && appData.ponderNextMove) SendToProgram("hard\n", &second);
13686 DisplayBothClocks();
13688 if (gameMode == PlayFromGameFile) {
13689 if (appData.timeDelay >= 0)
13690 AutoPlayGameLoop();
13691 } else if (gameMode == IcsExamining && pauseExamInvalid) {
13692 Reset(FALSE, TRUE);
13693 SendToICS(ics_prefix);
13694 SendToICS("refresh\n");
13695 } else if (currentMove < forwardMostMove && gameMode != AnalyzeMode) {
13696 ForwardInner(forwardMostMove);
13698 pauseExamInvalid = FALSE;
13700 switch (gameMode) {
13704 pauseExamForwardMostMove = forwardMostMove;
13705 pauseExamInvalid = FALSE;
13708 case IcsPlayingWhite:
13709 case IcsPlayingBlack:
13713 case PlayFromGameFile:
13714 (void) StopLoadGameTimer();
13718 case BeginningOfGame:
13719 if (appData.icsActive) return;
13720 /* else fall through */
13721 case MachinePlaysWhite:
13722 case MachinePlaysBlack:
13723 case TwoMachinesPlay:
13724 if (forwardMostMove == 0)
13725 return; /* don't pause if no one has moved */
13726 if(gameMode == TwoMachinesPlay) { // [HGM] pause: stop clocks if engine can be paused immediately
13727 ChessProgramState *onMove = (WhiteOnMove(forwardMostMove) == (first.twoMachinesColor[0] == 'w') ? &first : &second);
13728 if(onMove->pause) { // thinking engine can be paused
13729 PauseEngine(onMove); // do it
13730 if(onMove->other->pause) // pondering opponent can always be paused immediately
13731 PauseEngine(onMove->other);
13733 SendToProgram("easy\n", onMove->other);
13735 } else if(appData.ponderNextMove) SendToProgram("easy\n", onMove); // pre-emptively bring out of ponder
13736 } else if(gameMode == (WhiteOnMove(forwardMostMove) ? MachinePlaysWhite : MachinePlaysBlack)) { // engine on move
13738 PauseEngine(&first);
13740 } else if(appData.ponderNextMove) SendToProgram("easy\n", &first); // pre-emptively bring out of ponder
13741 } else { // human on move, pause pondering by either method
13743 PauseEngine(&first);
13744 else if(appData.ponderNextMove)
13745 SendToProgram("easy\n", &first);
13748 // if no immediate pausing is possible, wait for engine to move, and stop clocks then
13758 EditCommentEvent ()
13760 char title[MSG_SIZ];
13762 if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
13763 safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
13765 snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
13766 WhiteOnMove(currentMove - 1) ? " " : ".. ",
13767 parseList[currentMove - 1]);
13770 EditCommentPopUp(currentMove, title, commentList[currentMove]);
13777 char *tags = PGNTags(&gameInfo);
13779 EditTagsPopUp(tags, NULL);
13786 if(second.analyzing) {
13787 SendToProgram("exit\n", &second);
13788 second.analyzing = FALSE;
13790 if (second.pr == NoProc) StartChessProgram(&second);
13791 InitChessProgram(&second, FALSE);
13792 FeedMovesToProgram(&second, currentMove);
13794 SendToProgram("analyze\n", &second);
13795 second.analyzing = TRUE;
13799 /* Toggle ShowThinking */
13801 ToggleShowThinking()
13803 appData.showThinking = !appData.showThinking;
13804 ShowThinkingEvent();
13808 AnalyzeModeEvent ()
13812 if (!first.analysisSupport) {
13813 snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
13814 DisplayError(buf, 0);
13817 /* [DM] icsEngineAnalyze [HGM] This is horrible code; reverse the gameMode and isEngineAnalyze tests! */
13818 if (appData.icsActive) {
13819 if (gameMode != IcsObserving) {
13820 snprintf(buf, MSG_SIZ, _("You are not observing a game"));
13821 DisplayError(buf, 0);
13823 if (appData.icsEngineAnalyze) {
13824 if (appData.debugMode)
13825 fprintf(debugFP, "Found unexpected active ICS engine analyze \n");
13831 /* if enable, user wants to disable icsEngineAnalyze */
13832 if (appData.icsEngineAnalyze) {
13837 appData.icsEngineAnalyze = TRUE;
13838 if (appData.debugMode)
13839 fprintf(debugFP, "ICS engine analyze starting... \n");
13842 if (gameMode == AnalyzeMode) { ToggleSecond(); return 0; }
13843 if (appData.noChessProgram || gameMode == AnalyzeMode)
13846 if (gameMode != AnalyzeFile) {
13847 if (!appData.icsEngineAnalyze) {
13849 if (gameMode != EditGame) return 0;
13851 if (!appData.showThinking) ToggleShowThinking();
13852 ResurrectChessProgram();
13853 SendToProgram("analyze\n", &first);
13854 first.analyzing = TRUE;
13855 /*first.maybeThinking = TRUE;*/
13856 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13857 EngineOutputPopUp();
13859 if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
13864 StartAnalysisClock();
13865 GetTimeMark(&lastNodeCountTime);
13871 AnalyzeFileEvent ()
13873 if (appData.noChessProgram || gameMode == AnalyzeFile)
13876 if (!first.analysisSupport) {
13878 snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
13879 DisplayError(buf, 0);
13883 if (gameMode != AnalyzeMode) {
13884 keepInfo = 1; // mere annotating should not alter PGN tags
13887 if (gameMode != EditGame) return;
13888 if (!appData.showThinking) ToggleShowThinking();
13889 ResurrectChessProgram();
13890 SendToProgram("analyze\n", &first);
13891 first.analyzing = TRUE;
13892 /*first.maybeThinking = TRUE;*/
13893 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13894 EngineOutputPopUp();
13896 gameMode = AnalyzeFile;
13900 StartAnalysisClock();
13901 GetTimeMark(&lastNodeCountTime);
13903 if(appData.timeDelay > 0) StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
13904 AnalysisPeriodicEvent(1);
13908 MachineWhiteEvent ()
13911 char *bookHit = NULL;
13913 if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
13917 if (gameMode == PlayFromGameFile ||
13918 gameMode == TwoMachinesPlay ||
13919 gameMode == Training ||
13920 gameMode == AnalyzeMode ||
13921 gameMode == EndOfGame)
13924 if (gameMode == EditPosition)
13925 EditPositionDone(TRUE);
13927 if (!WhiteOnMove(currentMove)) {
13928 DisplayError(_("It is not White's turn"), 0);
13932 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
13935 if (gameMode == EditGame || gameMode == AnalyzeMode ||
13936 gameMode == AnalyzeFile)
13939 ResurrectChessProgram(); /* in case it isn't running */
13940 if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
13941 gameMode = MachinePlaysWhite;
13944 gameMode = MachinePlaysWhite;
13948 snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
13950 if (first.sendName) {
13951 snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
13952 SendToProgram(buf, &first);
13954 if (first.sendTime) {
13955 if (first.useColors) {
13956 SendToProgram("black\n", &first); /*gnu kludge*/
13958 SendTimeRemaining(&first, TRUE);
13960 if (first.useColors) {
13961 SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
13963 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
13964 SetMachineThinkingEnables();
13965 first.maybeThinking = TRUE;
13969 if (appData.autoFlipView && !flipView) {
13970 flipView = !flipView;
13971 DrawPosition(FALSE, NULL);
13972 DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;
13975 if(bookHit) { // [HGM] book: simulate book reply
13976 static char bookMove[MSG_SIZ]; // a bit generous?
13978 programStats.nodes = programStats.depth = programStats.time =
13979 programStats.score = programStats.got_only_move = 0;
13980 sprintf(programStats.movelist, "%s (xbook)", bookHit);
13982 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13983 strcat(bookMove, bookHit);
13984 HandleMachineMove(bookMove, &first);
13989 MachineBlackEvent ()
13992 char *bookHit = NULL;
13994 if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
13998 if (gameMode == PlayFromGameFile ||
13999 gameMode == TwoMachinesPlay ||
14000 gameMode == Training ||
14001 gameMode == AnalyzeMode ||
14002 gameMode == EndOfGame)
14005 if (gameMode == EditPosition)
14006 EditPositionDone(TRUE);
14008 if (WhiteOnMove(currentMove)) {
14009 DisplayError(_("It is not Black's turn"), 0);
14013 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
14016 if (gameMode == EditGame || gameMode == AnalyzeMode ||
14017 gameMode == AnalyzeFile)
14020 ResurrectChessProgram(); /* in case it isn't running */
14021 gameMode = MachinePlaysBlack;
14025 snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14027 if (first.sendName) {
14028 snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
14029 SendToProgram(buf, &first);
14031 if (first.sendTime) {
14032 if (first.useColors) {
14033 SendToProgram("white\n", &first); /*gnu kludge*/
14035 SendTimeRemaining(&first, FALSE);
14037 if (first.useColors) {
14038 SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
14040 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
14041 SetMachineThinkingEnables();
14042 first.maybeThinking = TRUE;
14045 if (appData.autoFlipView && flipView) {
14046 flipView = !flipView;
14047 DrawPosition(FALSE, NULL);
14048 DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;
14050 if(bookHit) { // [HGM] book: simulate book reply
14051 static char bookMove[MSG_SIZ]; // a bit generous?
14053 programStats.nodes = programStats.depth = programStats.time =
14054 programStats.score = programStats.got_only_move = 0;
14055 sprintf(programStats.movelist, "%s (xbook)", bookHit);
14057 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14058 strcat(bookMove, bookHit);
14059 HandleMachineMove(bookMove, &first);
14065 DisplayTwoMachinesTitle ()
14068 if (appData.matchGames > 0) {
14069 if(appData.tourneyFile[0]) {
14070 snprintf(buf, MSG_SIZ, "%s %s %s (%d/%d%s)",
14071 gameInfo.white, _("vs."), gameInfo.black,
14072 nextGame+1, appData.matchGames+1,
14073 appData.tourneyType>0 ? "gt" : appData.tourneyType<0 ? "sw" : "rr");
14075 if (first.twoMachinesColor[0] == 'w') {
14076 snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
14077 gameInfo.white, _("vs."), gameInfo.black,
14078 first.matchWins, second.matchWins,
14079 matchGame - 1 - (first.matchWins + second.matchWins));
14081 snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
14082 gameInfo.white, _("vs."), gameInfo.black,
14083 second.matchWins, first.matchWins,
14084 matchGame - 1 - (first.matchWins + second.matchWins));
14087 snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14093 SettingsMenuIfReady ()
14095 if (second.lastPing != second.lastPong) {
14096 DisplayMessage("", _("Waiting for second chess program"));
14097 ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
14101 DisplayMessage("", "");
14102 SettingsPopUp(&second);
14106 WaitForEngine (ChessProgramState *cps, DelayedEventCallback retry)
14109 if (cps->pr == NoProc) {
14110 StartChessProgram(cps);
14111 if (cps->protocolVersion == 1) {
14113 ScheduleDelayedEvent(retry, 1); // Do this also through timeout to avoid recursive calling of 'retry'
14115 /* kludge: allow timeout for initial "feature" command */
14116 if(retry != TwoMachinesEventIfReady) FreezeUI();
14117 snprintf(buf, MSG_SIZ, _("Starting %s chess program"), _(cps->which));
14118 DisplayMessage("", buf);
14119 ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
14127 TwoMachinesEvent P((void))
14131 ChessProgramState *onmove;
14132 char *bookHit = NULL;
14133 static int stalling = 0;
14137 if (appData.noChessProgram) return;
14139 switch (gameMode) {
14140 case TwoMachinesPlay:
14142 case MachinePlaysWhite:
14143 case MachinePlaysBlack:
14144 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
14145 DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
14149 case BeginningOfGame:
14150 case PlayFromGameFile:
14153 if (gameMode != EditGame) return;
14156 EditPositionDone(TRUE);
14167 // forwardMostMove = currentMove;
14168 TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
14169 startingEngine = TRUE;
14171 if(!ResurrectChessProgram()) return; /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
14173 if(!first.initDone && GetDelayedEvent() == TwoMachinesEventIfReady) return; // [HGM] engine #1 still waiting for feature timeout
14174 if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
14175 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
14178 if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
14180 if(second.protocolVersion >= 2 && !strstr(second.variants, VariantName(gameInfo.variant))) {
14181 startingEngine = FALSE;
14182 DisplayError("second engine does not play this", 0);
14187 InitChessProgram(&second, FALSE); // unbalances ping of second engine
14188 SendToProgram("force\n", &second);
14190 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
14193 GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
14194 if(appData.matchPause>10000 || appData.matchPause<10)
14195 appData.matchPause = 10000; /* [HGM] make pause adjustable */
14196 wait = SubtractTimeMarks(&now, &pauseStart);
14197 if(wait < appData.matchPause) {
14198 ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
14201 // we are now committed to starting the game
14203 DisplayMessage("", "");
14204 if (startedFromSetupPosition) {
14205 SendBoard(&second, backwardMostMove);
14206 if (appData.debugMode) {
14207 fprintf(debugFP, "Two Machines\n");
14210 for (i = backwardMostMove; i < forwardMostMove; i++) {
14211 SendMoveToProgram(i, &second);
14214 gameMode = TwoMachinesPlay;
14215 pausing = startingEngine = FALSE;
14216 ModeHighlight(); // [HGM] logo: this triggers display update of logos
14218 DisplayTwoMachinesTitle();
14220 if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
14225 if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
14226 SendToProgram(first.computerString, &first);
14227 if (first.sendName) {
14228 snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
14229 SendToProgram(buf, &first);
14231 SendToProgram(second.computerString, &second);
14232 if (second.sendName) {
14233 snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
14234 SendToProgram(buf, &second);
14238 if (!first.sendTime || !second.sendTime) {
14239 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
14240 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
14242 if (onmove->sendTime) {
14243 if (onmove->useColors) {
14244 SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
14246 SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
14248 if (onmove->useColors) {
14249 SendToProgram(onmove->twoMachinesColor, onmove);
14251 bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
14252 // SendToProgram("go\n", onmove);
14253 onmove->maybeThinking = TRUE;
14254 SetMachineThinkingEnables();
14258 if(bookHit) { // [HGM] book: simulate book reply
14259 static char bookMove[MSG_SIZ]; // a bit generous?
14261 programStats.nodes = programStats.depth = programStats.time =
14262 programStats.score = programStats.got_only_move = 0;
14263 sprintf(programStats.movelist, "%s (xbook)", bookHit);
14265 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14266 strcat(bookMove, bookHit);
14267 savedMessage = bookMove; // args for deferred call
14268 savedState = onmove;
14269 ScheduleDelayedEvent(DeferredBookMove, 1);
14276 if (gameMode == Training) {
14277 SetTrainingModeOff();
14278 gameMode = PlayFromGameFile;
14279 DisplayMessage("", _("Training mode off"));
14281 gameMode = Training;
14282 animateTraining = appData.animate;
14284 /* make sure we are not already at the end of the game */
14285 if (currentMove < forwardMostMove) {
14286 SetTrainingModeOn();
14287 DisplayMessage("", _("Training mode on"));
14289 gameMode = PlayFromGameFile;
14290 DisplayError(_("Already at end of game"), 0);
14299 if (!appData.icsActive) return;
14300 switch (gameMode) {
14301 case IcsPlayingWhite:
14302 case IcsPlayingBlack:
14305 case BeginningOfGame:
14313 EditPositionDone(TRUE);
14326 gameMode = IcsIdle;
14336 switch (gameMode) {
14338 SetTrainingModeOff();
14340 case MachinePlaysWhite:
14341 case MachinePlaysBlack:
14342 case BeginningOfGame:
14343 SendToProgram("force\n", &first);
14344 SetUserThinkingEnables();
14346 case PlayFromGameFile:
14347 (void) StopLoadGameTimer();
14348 if (gameFileFP != NULL) {
14353 EditPositionDone(TRUE);
14358 SendToProgram("force\n", &first);
14360 case TwoMachinesPlay:
14361 GameEnds(EndOfFile, NULL, GE_PLAYER);
14362 ResurrectChessProgram();
14363 SetUserThinkingEnables();
14366 ResurrectChessProgram();
14368 case IcsPlayingBlack:
14369 case IcsPlayingWhite:
14370 DisplayError(_("Warning: You are still playing a game"), 0);
14373 DisplayError(_("Warning: You are still observing a game"), 0);
14376 DisplayError(_("Warning: You are still examining a game"), 0);
14387 first.offeredDraw = second.offeredDraw = 0;
14389 if (gameMode == PlayFromGameFile) {
14390 whiteTimeRemaining = timeRemaining[0][currentMove];
14391 blackTimeRemaining = timeRemaining[1][currentMove];
14395 if (gameMode == MachinePlaysWhite ||
14396 gameMode == MachinePlaysBlack ||
14397 gameMode == TwoMachinesPlay ||
14398 gameMode == EndOfGame) {
14399 i = forwardMostMove;
14400 while (i > currentMove) {
14401 SendToProgram("undo\n", &first);
14404 if(!adjustedClock) {
14405 whiteTimeRemaining = timeRemaining[0][currentMove];
14406 blackTimeRemaining = timeRemaining[1][currentMove];
14407 DisplayBothClocks();
14409 if (whiteFlag || blackFlag) {
14410 whiteFlag = blackFlag = 0;
14415 gameMode = EditGame;
14422 EditPositionEvent ()
14424 if (gameMode == EditPosition) {
14430 if (gameMode != EditGame) return;
14432 gameMode = EditPosition;
14435 if (currentMove > 0)
14436 CopyBoard(boards[0], boards[currentMove]);
14438 blackPlaysFirst = !WhiteOnMove(currentMove);
14440 currentMove = forwardMostMove = backwardMostMove = 0;
14441 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
14443 if(!appData.pieceMenu) DisplayMessage(_("Click clock to clear board"), "");
14449 /* [DM] icsEngineAnalyze - possible call from other functions */
14450 if (appData.icsEngineAnalyze) {
14451 appData.icsEngineAnalyze = FALSE;
14453 DisplayMessage("",_("Close ICS engine analyze..."));
14455 if (first.analysisSupport && first.analyzing) {
14456 SendToBoth("exit\n");
14457 first.analyzing = second.analyzing = FALSE;
14459 thinkOutput[0] = NULLCHAR;
14463 EditPositionDone (Boolean fakeRights)
14465 int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
14467 startedFromSetupPosition = TRUE;
14468 InitChessProgram(&first, FALSE);
14469 if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
14470 boards[0][EP_STATUS] = EP_NONE;
14471 boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
14472 if(boards[0][0][BOARD_WIDTH>>1] == king) {
14473 boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? BOARD_LEFT : NoRights;
14474 boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
14475 } else boards[0][CASTLING][2] = NoRights;
14476 if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
14477 boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? BOARD_LEFT : NoRights;
14478 boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
14479 } else boards[0][CASTLING][5] = NoRights;
14480 if(gameInfo.variant == VariantSChess) {
14482 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // pieces in their original position are assumed virgin
14483 boards[0][VIRGIN][i] = 0;
14484 if(boards[0][0][i] == FIDEArray[0][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_W;
14485 if(boards[0][BOARD_HEIGHT-1][i] == FIDEArray[1][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_B;
14489 SendToProgram("force\n", &first);
14490 if (blackPlaysFirst) {
14491 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
14492 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
14493 currentMove = forwardMostMove = backwardMostMove = 1;
14494 CopyBoard(boards[1], boards[0]);
14496 currentMove = forwardMostMove = backwardMostMove = 0;
14498 SendBoard(&first, forwardMostMove);
14499 if (appData.debugMode) {
14500 fprintf(debugFP, "EditPosDone\n");
14503 DisplayMessage("", "");
14504 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
14505 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
14506 gameMode = EditGame;
14508 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
14509 ClearHighlights(); /* [AS] */
14512 /* Pause for `ms' milliseconds */
14513 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
14515 TimeDelay (long ms)
14522 } while (SubtractTimeMarks(&m2, &m1) < ms);
14525 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
14527 SendMultiLineToICS (char *buf)
14529 char temp[MSG_SIZ+1], *p;
14536 strncpy(temp, buf, len);
14541 if (*p == '\n' || *p == '\r')
14546 strcat(temp, "\n");
14548 SendToPlayer(temp, strlen(temp));
14552 SetWhiteToPlayEvent ()
14554 if (gameMode == EditPosition) {
14555 blackPlaysFirst = FALSE;
14556 DisplayBothClocks(); /* works because currentMove is 0 */
14557 } else if (gameMode == IcsExamining) {
14558 SendToICS(ics_prefix);
14559 SendToICS("tomove white\n");
14564 SetBlackToPlayEvent ()
14566 if (gameMode == EditPosition) {
14567 blackPlaysFirst = TRUE;
14568 currentMove = 1; /* kludge */
14569 DisplayBothClocks();
14571 } else if (gameMode == IcsExamining) {
14572 SendToICS(ics_prefix);
14573 SendToICS("tomove black\n");
14578 EditPositionMenuEvent (ChessSquare selection, int x, int y)
14581 ChessSquare piece = boards[0][y][x];
14583 if (gameMode != EditPosition && gameMode != IcsExamining) return;
14585 switch (selection) {
14587 if (gameMode == IcsExamining && ics_type == ICS_FICS) {
14588 SendToICS(ics_prefix);
14589 SendToICS("bsetup clear\n");
14590 } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
14591 SendToICS(ics_prefix);
14592 SendToICS("clearboard\n");
14594 for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
14595 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
14596 for (y = 0; y < BOARD_HEIGHT; y++) {
14597 if (gameMode == IcsExamining) {
14598 if (boards[currentMove][y][x] != EmptySquare) {
14599 snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
14604 boards[0][y][x] = p;
14609 if (gameMode == EditPosition) {
14610 DrawPosition(FALSE, boards[0]);
14615 SetWhiteToPlayEvent();
14619 SetBlackToPlayEvent();
14623 if (gameMode == IcsExamining) {
14624 if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
14625 snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
14628 if(x < BOARD_LEFT || x >= BOARD_RGHT) {
14629 if(x == BOARD_LEFT-2) {
14630 if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
14631 boards[0][y][1] = 0;
14633 if(x == BOARD_RGHT+1) {
14634 if(y >= gameInfo.holdingsSize) break;
14635 boards[0][y][BOARD_WIDTH-2] = 0;
14638 boards[0][y][x] = EmptySquare;
14639 DrawPosition(FALSE, boards[0]);
14644 if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
14645 piece >= (int)BlackPawn && piece < (int)BlackMan ) {
14646 selection = (ChessSquare) (PROMOTED piece);
14647 } else if(piece == EmptySquare) selection = WhiteSilver;
14648 else selection = (ChessSquare)((int)piece - 1);
14652 if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
14653 piece > (int)BlackMan && piece <= (int)BlackKing ) {
14654 selection = (ChessSquare) (DEMOTED piece);
14655 } else if(piece == EmptySquare) selection = BlackSilver;
14656 else selection = (ChessSquare)((int)piece + 1);
14661 if(gameInfo.variant == VariantShatranj ||
14662 gameInfo.variant == VariantXiangqi ||
14663 gameInfo.variant == VariantCourier ||
14664 gameInfo.variant == VariantASEAN ||
14665 gameInfo.variant == VariantMakruk )
14666 selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
14671 if(gameInfo.variant == VariantXiangqi)
14672 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
14673 if(gameInfo.variant == VariantKnightmate)
14674 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
14677 if (gameMode == IcsExamining) {
14678 if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
14679 snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
14680 PieceToChar(selection), AAA + x, ONE + y);
14683 if(x < BOARD_LEFT || x >= BOARD_RGHT) {
14685 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
14686 n = PieceToNumber(selection - BlackPawn);
14687 if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
14688 boards[0][BOARD_HEIGHT-1-n][0] = selection;
14689 boards[0][BOARD_HEIGHT-1-n][1]++;
14691 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
14692 n = PieceToNumber(selection);
14693 if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
14694 boards[0][n][BOARD_WIDTH-1] = selection;
14695 boards[0][n][BOARD_WIDTH-2]++;
14698 boards[0][y][x] = selection;
14699 DrawPosition(TRUE, boards[0]);
14701 fromX = fromY = -1;
14709 DropMenuEvent (ChessSquare selection, int x, int y)
14711 ChessMove moveType;
14713 switch (gameMode) {
14714 case IcsPlayingWhite:
14715 case MachinePlaysBlack:
14716 if (!WhiteOnMove(currentMove)) {
14717 DisplayMoveError(_("It is Black's turn"));
14720 moveType = WhiteDrop;
14722 case IcsPlayingBlack:
14723 case MachinePlaysWhite:
14724 if (WhiteOnMove(currentMove)) {
14725 DisplayMoveError(_("It is White's turn"));
14728 moveType = BlackDrop;
14731 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
14737 if (moveType == BlackDrop && selection < BlackPawn) {
14738 selection = (ChessSquare) ((int) selection
14739 + (int) BlackPawn - (int) WhitePawn);
14741 if (boards[currentMove][y][x] != EmptySquare) {
14742 DisplayMoveError(_("That square is occupied"));
14746 FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
14752 /* Accept a pending offer of any kind from opponent */
14754 if (appData.icsActive) {
14755 SendToICS(ics_prefix);
14756 SendToICS("accept\n");
14757 } else if (cmailMsgLoaded) {
14758 if (currentMove == cmailOldMove &&
14759 commentList[cmailOldMove] != NULL &&
14760 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14761 "Black offers a draw" : "White offers a draw")) {
14763 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
14764 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
14766 DisplayError(_("There is no pending offer on this move"), 0);
14767 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
14770 /* Not used for offers from chess program */
14777 /* Decline a pending offer of any kind from opponent */
14779 if (appData.icsActive) {
14780 SendToICS(ics_prefix);
14781 SendToICS("decline\n");
14782 } else if (cmailMsgLoaded) {
14783 if (currentMove == cmailOldMove &&
14784 commentList[cmailOldMove] != NULL &&
14785 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14786 "Black offers a draw" : "White offers a draw")) {
14788 AppendComment(cmailOldMove, "Draw declined", TRUE);
14789 DisplayComment(cmailOldMove - 1, "Draw declined");
14792 DisplayError(_("There is no pending offer on this move"), 0);
14795 /* Not used for offers from chess program */
14802 /* Issue ICS rematch command */
14803 if (appData.icsActive) {
14804 SendToICS(ics_prefix);
14805 SendToICS("rematch\n");
14812 /* Call your opponent's flag (claim a win on time) */
14813 if (appData.icsActive) {
14814 SendToICS(ics_prefix);
14815 SendToICS("flag\n");
14817 switch (gameMode) {
14820 case MachinePlaysWhite:
14823 GameEnds(GameIsDrawn, "Both players ran out of time",
14826 GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
14828 DisplayError(_("Your opponent is not out of time"), 0);
14831 case MachinePlaysBlack:
14834 GameEnds(GameIsDrawn, "Both players ran out of time",
14837 GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
14839 DisplayError(_("Your opponent is not out of time"), 0);
14847 ClockClick (int which)
14848 { // [HGM] code moved to back-end from winboard.c
14849 if(which) { // black clock
14850 if (gameMode == EditPosition || gameMode == IcsExamining) {
14851 if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
14852 SetBlackToPlayEvent();
14853 } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !blackFlag && WhiteOnMove(currentMove)) {
14854 UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move: if not out of time, enters null move
14855 } else if (shiftKey) {
14856 AdjustClock(which, -1);
14857 } else if (gameMode == IcsPlayingWhite ||
14858 gameMode == MachinePlaysBlack) {
14861 } else { // white clock
14862 if (gameMode == EditPosition || gameMode == IcsExamining) {
14863 if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
14864 SetWhiteToPlayEvent();
14865 } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !whiteFlag && !WhiteOnMove(currentMove)) {
14866 UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move
14867 } else if (shiftKey) {
14868 AdjustClock(which, -1);
14869 } else if (gameMode == IcsPlayingBlack ||
14870 gameMode == MachinePlaysWhite) {
14879 /* Offer draw or accept pending draw offer from opponent */
14881 if (appData.icsActive) {
14882 /* Note: tournament rules require draw offers to be
14883 made after you make your move but before you punch
14884 your clock. Currently ICS doesn't let you do that;
14885 instead, you immediately punch your clock after making
14886 a move, but you can offer a draw at any time. */
14888 SendToICS(ics_prefix);
14889 SendToICS("draw\n");
14890 userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
14891 } else if (cmailMsgLoaded) {
14892 if (currentMove == cmailOldMove &&
14893 commentList[cmailOldMove] != NULL &&
14894 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14895 "Black offers a draw" : "White offers a draw")) {
14896 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
14897 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
14898 } else if (currentMove == cmailOldMove + 1) {
14899 char *offer = WhiteOnMove(cmailOldMove) ?
14900 "White offers a draw" : "Black offers a draw";
14901 AppendComment(currentMove, offer, TRUE);
14902 DisplayComment(currentMove - 1, offer);
14903 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
14905 DisplayError(_("You must make your move before offering a draw"), 0);
14906 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
14908 } else if (first.offeredDraw) {
14909 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
14911 if (first.sendDrawOffers) {
14912 SendToProgram("draw\n", &first);
14913 userOfferedDraw = TRUE;
14921 /* Offer Adjourn or accept pending Adjourn offer from opponent */
14923 if (appData.icsActive) {
14924 SendToICS(ics_prefix);
14925 SendToICS("adjourn\n");
14927 /* Currently GNU Chess doesn't offer or accept Adjourns */
14935 /* Offer Abort or accept pending Abort offer from opponent */
14937 if (appData.icsActive) {
14938 SendToICS(ics_prefix);
14939 SendToICS("abort\n");
14941 GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
14948 /* Resign. You can do this even if it's not your turn. */
14950 if (appData.icsActive) {
14951 SendToICS(ics_prefix);
14952 SendToICS("resign\n");
14954 switch (gameMode) {
14955 case MachinePlaysWhite:
14956 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
14958 case MachinePlaysBlack:
14959 GameEnds(BlackWins, "White resigns", GE_PLAYER);
14962 if (cmailMsgLoaded) {
14964 if (WhiteOnMove(cmailOldMove)) {
14965 GameEnds(BlackWins, "White resigns", GE_PLAYER);
14967 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
14969 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
14980 StopObservingEvent ()
14982 /* Stop observing current games */
14983 SendToICS(ics_prefix);
14984 SendToICS("unobserve\n");
14988 StopExaminingEvent ()
14990 /* Stop observing current game */
14991 SendToICS(ics_prefix);
14992 SendToICS("unexamine\n");
14996 ForwardInner (int target)
14998 int limit; int oldSeekGraphUp = seekGraphUp;
15000 if (appData.debugMode)
15001 fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
15002 target, currentMove, forwardMostMove);
15004 if (gameMode == EditPosition)
15007 seekGraphUp = FALSE;
15008 MarkTargetSquares(1);
15010 if (gameMode == PlayFromGameFile && !pausing)
15013 if (gameMode == IcsExamining && pausing)
15014 limit = pauseExamForwardMostMove;
15016 limit = forwardMostMove;
15018 if (target > limit) target = limit;
15020 if (target > 0 && moveList[target - 1][0]) {
15021 int fromX, fromY, toX, toY;
15022 toX = moveList[target - 1][2] - AAA;
15023 toY = moveList[target - 1][3] - ONE;
15024 if (moveList[target - 1][1] == '@') {
15025 if (appData.highlightLastMove) {
15026 SetHighlights(-1, -1, toX, toY);
15029 fromX = moveList[target - 1][0] - AAA;
15030 fromY = moveList[target - 1][1] - ONE;
15031 if (target == currentMove + 1) {
15032 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
15034 if (appData.highlightLastMove) {
15035 SetHighlights(fromX, fromY, toX, toY);
15039 if (gameMode == EditGame || gameMode == AnalyzeMode ||
15040 gameMode == Training || gameMode == PlayFromGameFile ||
15041 gameMode == AnalyzeFile) {
15042 while (currentMove < target) {
15043 if(second.analyzing) SendMoveToProgram(currentMove, &second);
15044 SendMoveToProgram(currentMove++, &first);
15047 currentMove = target;
15050 if (gameMode == EditGame || gameMode == EndOfGame) {
15051 whiteTimeRemaining = timeRemaining[0][currentMove];
15052 blackTimeRemaining = timeRemaining[1][currentMove];
15054 DisplayBothClocks();
15055 DisplayMove(currentMove - 1);
15056 DrawPosition(oldSeekGraphUp, boards[currentMove]);
15057 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
15058 if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
15059 DisplayComment(currentMove - 1, commentList[currentMove]);
15061 ClearMap(); // [HGM] exclude: invalidate map
15068 if (gameMode == IcsExamining && !pausing) {
15069 SendToICS(ics_prefix);
15070 SendToICS("forward\n");
15072 ForwardInner(currentMove + 1);
15079 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15080 /* to optimze, we temporarily turn off analysis mode while we feed
15081 * the remaining moves to the engine. Otherwise we get analysis output
15084 if (first.analysisSupport) {
15085 SendToProgram("exit\nforce\n", &first);
15086 first.analyzing = FALSE;
15090 if (gameMode == IcsExamining && !pausing) {
15091 SendToICS(ics_prefix);
15092 SendToICS("forward 999999\n");
15094 ForwardInner(forwardMostMove);
15097 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15098 /* we have fed all the moves, so reactivate analysis mode */
15099 SendToProgram("analyze\n", &first);
15100 first.analyzing = TRUE;
15101 /*first.maybeThinking = TRUE;*/
15102 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
15107 BackwardInner (int target)
15109 int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
15111 if (appData.debugMode)
15112 fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
15113 target, currentMove, forwardMostMove);
15115 if (gameMode == EditPosition) return;
15116 seekGraphUp = FALSE;
15117 MarkTargetSquares(1);
15118 if (currentMove <= backwardMostMove) {
15120 DrawPosition(full_redraw, boards[currentMove]);
15123 if (gameMode == PlayFromGameFile && !pausing)
15126 if (moveList[target][0]) {
15127 int fromX, fromY, toX, toY;
15128 toX = moveList[target][2] - AAA;
15129 toY = moveList[target][3] - ONE;
15130 if (moveList[target][1] == '@') {
15131 if (appData.highlightLastMove) {
15132 SetHighlights(-1, -1, toX, toY);
15135 fromX = moveList[target][0] - AAA;
15136 fromY = moveList[target][1] - ONE;
15137 if (target == currentMove - 1) {
15138 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
15140 if (appData.highlightLastMove) {
15141 SetHighlights(fromX, fromY, toX, toY);
15145 if (gameMode == EditGame || gameMode==AnalyzeMode ||
15146 gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
15147 while (currentMove > target) {
15148 if(moveList[currentMove-1][1] == '@' && moveList[currentMove-1][0] == '@') {
15149 // null move cannot be undone. Reload program with move history before it.
15151 for(i=target; i>backwardMostMove; i--) { // seek back to start or previous null move
15152 if(moveList[i-1][1] == '@' && moveList[i-1][0] == '@') break;
15154 SendBoard(&first, i);
15155 if(second.analyzing) SendBoard(&second, i);
15156 for(currentMove=i; currentMove<target; currentMove++) {
15157 SendMoveToProgram(currentMove, &first);
15158 if(second.analyzing) SendMoveToProgram(currentMove, &second);
15162 SendToBoth("undo\n");
15166 currentMove = target;
15169 if (gameMode == EditGame || gameMode == EndOfGame) {
15170 whiteTimeRemaining = timeRemaining[0][currentMove];
15171 blackTimeRemaining = timeRemaining[1][currentMove];
15173 DisplayBothClocks();
15174 DisplayMove(currentMove - 1);
15175 DrawPosition(full_redraw, boards[currentMove]);
15176 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
15177 // [HGM] PV info: routine tests if comment empty
15178 DisplayComment(currentMove - 1, commentList[currentMove]);
15179 ClearMap(); // [HGM] exclude: invalidate map
15185 if (gameMode == IcsExamining && !pausing) {
15186 SendToICS(ics_prefix);
15187 SendToICS("backward\n");
15189 BackwardInner(currentMove - 1);
15196 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15197 /* to optimize, we temporarily turn off analysis mode while we undo
15198 * all the moves. Otherwise we get analysis output after each undo.
15200 if (first.analysisSupport) {
15201 SendToProgram("exit\nforce\n", &first);
15202 first.analyzing = FALSE;
15206 if (gameMode == IcsExamining && !pausing) {
15207 SendToICS(ics_prefix);
15208 SendToICS("backward 999999\n");
15210 BackwardInner(backwardMostMove);
15213 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15214 /* we have fed all the moves, so reactivate analysis mode */
15215 SendToProgram("analyze\n", &first);
15216 first.analyzing = TRUE;
15217 /*first.maybeThinking = TRUE;*/
15218 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
15225 if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
15226 if (to >= forwardMostMove) to = forwardMostMove;
15227 if (to <= backwardMostMove) to = backwardMostMove;
15228 if (to < currentMove) {
15236 RevertEvent (Boolean annotate)
15238 if(PopTail(annotate)) { // [HGM] vari: restore old game tail
15241 if (gameMode != IcsExamining) {
15242 DisplayError(_("You are not examining a game"), 0);
15246 DisplayError(_("You can't revert while pausing"), 0);
15249 SendToICS(ics_prefix);
15250 SendToICS("revert\n");
15254 RetractMoveEvent ()
15256 switch (gameMode) {
15257 case MachinePlaysWhite:
15258 case MachinePlaysBlack:
15259 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
15260 DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
15263 if (forwardMostMove < 2) return;
15264 currentMove = forwardMostMove = forwardMostMove - 2;
15265 whiteTimeRemaining = timeRemaining[0][currentMove];
15266 blackTimeRemaining = timeRemaining[1][currentMove];
15267 DisplayBothClocks();
15268 DisplayMove(currentMove - 1);
15269 ClearHighlights();/*!! could figure this out*/
15270 DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
15271 SendToProgram("remove\n", &first);
15272 /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
15275 case BeginningOfGame:
15279 case IcsPlayingWhite:
15280 case IcsPlayingBlack:
15281 if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
15282 SendToICS(ics_prefix);
15283 SendToICS("takeback 2\n");
15285 SendToICS(ics_prefix);
15286 SendToICS("takeback 1\n");
15295 ChessProgramState *cps;
15297 switch (gameMode) {
15298 case MachinePlaysWhite:
15299 if (!WhiteOnMove(forwardMostMove)) {
15300 DisplayError(_("It is your turn"), 0);
15305 case MachinePlaysBlack:
15306 if (WhiteOnMove(forwardMostMove)) {
15307 DisplayError(_("It is your turn"), 0);
15312 case TwoMachinesPlay:
15313 if (WhiteOnMove(forwardMostMove) ==
15314 (first.twoMachinesColor[0] == 'w')) {
15320 case BeginningOfGame:
15324 SendToProgram("?\n", cps);
15328 TruncateGameEvent ()
15331 if (gameMode != EditGame) return;
15338 CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
15339 if (forwardMostMove > currentMove) {
15340 if (gameInfo.resultDetails != NULL) {
15341 free(gameInfo.resultDetails);
15342 gameInfo.resultDetails = NULL;
15343 gameInfo.result = GameUnfinished;
15345 forwardMostMove = currentMove;
15346 HistorySet(parseList, backwardMostMove, forwardMostMove,
15354 if (appData.noChessProgram) return;
15355 switch (gameMode) {
15356 case MachinePlaysWhite:
15357 if (WhiteOnMove(forwardMostMove)) {
15358 DisplayError(_("Wait until your turn"), 0);
15362 case BeginningOfGame:
15363 case MachinePlaysBlack:
15364 if (!WhiteOnMove(forwardMostMove)) {
15365 DisplayError(_("Wait until your turn"), 0);
15370 DisplayError(_("No hint available"), 0);
15373 SendToProgram("hint\n", &first);
15374 hintRequested = TRUE;
15380 ListGame * lg = (ListGame *) gameList.head;
15383 static int secondTime = FALSE;
15385 if( !(f = GameFile()) || ((ListGame *) gameList.tailPred)->number <= 0 ) {
15386 DisplayError(_("Game list not loaded or empty"), 0);
15390 if(!secondTime && (g = fopen(appData.polyglotBook, "r"))) {
15393 DisplayNote(_("Book file exists! Try again for overwrite."));
15397 creatingBook = TRUE;
15398 secondTime = FALSE;
15400 /* Get list size */
15401 for (nItem = 1; nItem <= ((ListGame *) gameList.tailPred)->number; nItem++){
15402 LoadGame(f, nItem, "", TRUE);
15403 AddGameToBook(TRUE);
15404 lg = (ListGame *) lg->node.succ;
15407 creatingBook = FALSE;
15414 if (appData.noChessProgram) return;
15415 switch (gameMode) {
15416 case MachinePlaysWhite:
15417 if (WhiteOnMove(forwardMostMove)) {
15418 DisplayError(_("Wait until your turn"), 0);
15422 case BeginningOfGame:
15423 case MachinePlaysBlack:
15424 if (!WhiteOnMove(forwardMostMove)) {
15425 DisplayError(_("Wait until your turn"), 0);
15430 EditPositionDone(TRUE);
15432 case TwoMachinesPlay:
15437 SendToProgram("bk\n", &first);
15438 bookOutput[0] = NULLCHAR;
15439 bookRequested = TRUE;
15445 char *tags = PGNTags(&gameInfo);
15446 TagsPopUp(tags, CmailMsg());
15450 /* end button procedures */
15453 PrintPosition (FILE *fp, int move)
15457 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
15458 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
15459 char c = PieceToChar(boards[move][i][j]);
15460 fputc(c == 'x' ? '.' : c, fp);
15461 fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
15464 if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
15465 fprintf(fp, "white to play\n");
15467 fprintf(fp, "black to play\n");
15471 PrintOpponents (FILE *fp)
15473 if (gameInfo.white != NULL) {
15474 fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
15480 /* Find last component of program's own name, using some heuristics */
15482 TidyProgramName (char *prog, char *host, char buf[MSG_SIZ])
15485 int local = (strcmp(host, "localhost") == 0);
15486 while (!local && (p = strchr(prog, ';')) != NULL) {
15488 while (*p == ' ') p++;
15491 if (*prog == '"' || *prog == '\'') {
15492 q = strchr(prog + 1, *prog);
15494 q = strchr(prog, ' ');
15496 if (q == NULL) q = prog + strlen(prog);
15498 while (p >= prog && *p != '/' && *p != '\\') p--;
15500 if(p == prog && *p == '"') p++;
15502 if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) *q = c, q -= 4; else *q = c;
15503 memcpy(buf, p, q - p);
15504 buf[q - p] = NULLCHAR;
15512 TimeControlTagValue ()
15515 if (!appData.clockMode) {
15516 safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
15517 } else if (movesPerSession > 0) {
15518 snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
15519 } else if (timeIncrement == 0) {
15520 snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
15522 snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
15524 return StrSave(buf);
15530 /* This routine is used only for certain modes */
15531 VariantClass v = gameInfo.variant;
15532 ChessMove r = GameUnfinished;
15535 if(keepInfo) return;
15537 if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
15538 r = gameInfo.result;
15539 p = gameInfo.resultDetails;
15540 gameInfo.resultDetails = NULL;
15542 ClearGameInfo(&gameInfo);
15543 gameInfo.variant = v;
15545 switch (gameMode) {
15546 case MachinePlaysWhite:
15547 gameInfo.event = StrSave( appData.pgnEventHeader );
15548 gameInfo.site = StrSave(HostName());
15549 gameInfo.date = PGNDate();
15550 gameInfo.round = StrSave("-");
15551 gameInfo.white = StrSave(first.tidy);
15552 gameInfo.black = StrSave(UserName());
15553 gameInfo.timeControl = TimeControlTagValue();
15556 case MachinePlaysBlack:
15557 gameInfo.event = StrSave( appData.pgnEventHeader );
15558 gameInfo.site = StrSave(HostName());
15559 gameInfo.date = PGNDate();
15560 gameInfo.round = StrSave("-");
15561 gameInfo.white = StrSave(UserName());
15562 gameInfo.black = StrSave(first.tidy);
15563 gameInfo.timeControl = TimeControlTagValue();
15566 case TwoMachinesPlay:
15567 gameInfo.event = StrSave( appData.pgnEventHeader );
15568 gameInfo.site = StrSave(HostName());
15569 gameInfo.date = PGNDate();
15572 snprintf(buf, MSG_SIZ, "%d", roundNr);
15573 gameInfo.round = StrSave(buf);
15575 gameInfo.round = StrSave("-");
15577 if (first.twoMachinesColor[0] == 'w') {
15578 gameInfo.white = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
15579 gameInfo.black = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
15581 gameInfo.white = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
15582 gameInfo.black = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
15584 gameInfo.timeControl = TimeControlTagValue();
15588 gameInfo.event = StrSave("Edited game");
15589 gameInfo.site = StrSave(HostName());
15590 gameInfo.date = PGNDate();
15591 gameInfo.round = StrSave("-");
15592 gameInfo.white = StrSave("-");
15593 gameInfo.black = StrSave("-");
15594 gameInfo.result = r;
15595 gameInfo.resultDetails = p;
15599 gameInfo.event = StrSave("Edited position");
15600 gameInfo.site = StrSave(HostName());
15601 gameInfo.date = PGNDate();
15602 gameInfo.round = StrSave("-");
15603 gameInfo.white = StrSave("-");
15604 gameInfo.black = StrSave("-");
15607 case IcsPlayingWhite:
15608 case IcsPlayingBlack:
15613 case PlayFromGameFile:
15614 gameInfo.event = StrSave("Game from non-PGN file");
15615 gameInfo.site = StrSave(HostName());
15616 gameInfo.date = PGNDate();
15617 gameInfo.round = StrSave("-");
15618 gameInfo.white = StrSave("?");
15619 gameInfo.black = StrSave("?");
15628 ReplaceComment (int index, char *text)
15634 if(index && sscanf(text, "%f/%d", &score, &len) == 2 &&
15635 pvInfoList[index-1].depth == len &&
15636 fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
15637 (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
15638 while (*text == '\n') text++;
15639 len = strlen(text);
15640 while (len > 0 && text[len - 1] == '\n') len--;
15642 if (commentList[index] != NULL)
15643 free(commentList[index]);
15646 commentList[index] = NULL;
15649 if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
15650 *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
15651 *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
15652 commentList[index] = (char *) malloc(len + 2);
15653 strncpy(commentList[index], text, len);
15654 commentList[index][len] = '\n';
15655 commentList[index][len + 1] = NULLCHAR;
15657 // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
15659 commentList[index] = (char *) malloc(len + 7);
15660 safeStrCpy(commentList[index], "{\n", 3);
15661 safeStrCpy(commentList[index]+2, text, len+1);
15662 commentList[index][len+2] = NULLCHAR;
15663 while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
15664 strcat(commentList[index], "\n}\n");
15669 CrushCRs (char *text)
15677 if (ch == '\r') continue;
15679 } while (ch != '\0');
15683 AppendComment (int index, char *text, Boolean addBraces)
15684 /* addBraces tells if we should add {} */
15689 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces);
15690 if(addBraces == 3) addBraces = 0; else // force appending literally
15691 text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
15694 while (*text == '\n') text++;
15695 len = strlen(text);
15696 while (len > 0 && text[len - 1] == '\n') len--;
15697 text[len] = NULLCHAR;
15699 if (len == 0) return;
15701 if (commentList[index] != NULL) {
15702 Boolean addClosingBrace = addBraces;
15703 old = commentList[index];
15704 oldlen = strlen(old);
15705 while(commentList[index][oldlen-1] == '\n')
15706 commentList[index][--oldlen] = NULLCHAR;
15707 commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
15708 safeStrCpy(commentList[index], old, oldlen + len + 6);
15710 // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
15711 if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
15712 if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
15713 while (*text == '\n') { text++; len--; }
15714 commentList[index][--oldlen] = NULLCHAR;
15716 if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
15717 else strcat(commentList[index], "\n");
15718 strcat(commentList[index], text);
15719 if(addClosingBrace) strcat(commentList[index], addClosingBrace == 2 ? ")\n" : "\n}\n");
15720 else strcat(commentList[index], "\n");
15722 commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
15724 safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
15725 else commentList[index][0] = NULLCHAR;
15726 strcat(commentList[index], text);
15727 strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
15728 if(addBraces == TRUE) strcat(commentList[index], "}\n");
15733 FindStr (char * text, char * sub_text)
15735 char * result = strstr( text, sub_text );
15737 if( result != NULL ) {
15738 result += strlen( sub_text );
15744 /* [AS] Try to extract PV info from PGN comment */
15745 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
15747 GetInfoFromComment (int index, char * text)
15749 char * sep = text, *p;
15751 if( text != NULL && index > 0 ) {
15754 int time = -1, sec = 0, deci;
15755 char * s_eval = FindStr( text, "[%eval " );
15756 char * s_emt = FindStr( text, "[%emt " );
15758 if( s_eval != NULL || s_emt != NULL ) {
15760 if(0) { // [HGM] this code is not finished, and could actually be detrimental
15765 if( s_eval != NULL ) {
15766 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
15770 if( delim != ']' ) {
15775 if( s_emt != NULL ) {
15780 /* We expect something like: [+|-]nnn.nn/dd */
15783 if(*text != '{') return text; // [HGM] braces: must be normal comment
15785 sep = strchr( text, '/' );
15786 if( sep == NULL || sep < (text+4) ) {
15791 if(!strncmp(p+1, "final score ", 12)) p += 12, index++; else
15792 if(p[1] == '(') { // comment starts with PV
15793 p = strchr(p, ')'); // locate end of PV
15794 if(p == NULL || sep < p+5) return text;
15795 // at this point we have something like "{(.*) +0.23/6 ..."
15796 p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
15797 *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
15798 // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
15800 time = -1; sec = -1; deci = -1;
15801 if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
15802 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
15803 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
15804 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3 ) {
15808 if( score_lo < 0 || score_lo >= 100 ) {
15812 if(sec >= 0) time = 600*time + 10*sec; else
15813 if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
15815 score = score > 0 || !score & p[1] != '-' ? score*100 + score_lo : score*100 - score_lo;
15817 /* [HGM] PV time: now locate end of PV info */
15818 while( *++sep >= '0' && *sep <= '9'); // strip depth
15820 while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
15822 while( *++sep >= '0' && *sep <= '9'); // strip seconds
15824 while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
15825 while(*sep == ' ' || *sep == '\n' || *sep == '\r') sep++;
15836 pvInfoList[index-1].depth = depth;
15837 pvInfoList[index-1].score = score;
15838 pvInfoList[index-1].time = 10*time; // centi-sec
15839 if(*sep == '}') *sep = 0; else *--sep = '{';
15840 if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
15846 SendToProgram (char *message, ChessProgramState *cps)
15848 int count, outCount, error;
15851 if (cps->pr == NoProc) return;
15854 if (appData.debugMode) {
15857 fprintf(debugFP, "%ld >%-6s: %s",
15858 SubtractTimeMarks(&now, &programStartTime),
15859 cps->which, message);
15861 fprintf(serverFP, "%ld >%-6s: %s",
15862 SubtractTimeMarks(&now, &programStartTime),
15863 cps->which, message), fflush(serverFP);
15866 count = strlen(message);
15867 outCount = OutputToProcess(cps->pr, message, count, &error);
15868 if (outCount < count && !exiting
15869 && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
15870 if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
15871 snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
15872 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
15873 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
15874 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
15875 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
15876 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
15878 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
15879 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
15880 gameInfo.result = res;
15882 gameInfo.resultDetails = StrSave(buf);
15884 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
15885 if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
15890 ReceiveFromProgram (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
15894 ChessProgramState *cps = (ChessProgramState *)closure;
15896 if (isr != cps->isr) return; /* Killed intentionally */
15899 RemoveInputSource(cps->isr);
15900 snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
15901 _(cps->which), cps->program);
15902 if(LoadError(cps->userError ? NULL : buf, cps)) return; // [HGM] should not generate fatal error during engine load
15903 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
15904 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
15905 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
15906 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
15907 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
15909 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
15910 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
15911 gameInfo.result = res;
15913 gameInfo.resultDetails = StrSave(buf);
15915 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
15916 if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
15918 snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
15919 _(cps->which), cps->program);
15920 RemoveInputSource(cps->isr);
15922 /* [AS] Program is misbehaving badly... kill it */
15923 if( count == -2 ) {
15924 DestroyChildProcess( cps->pr, 9 );
15928 if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
15933 if ((end_str = strchr(message, '\r')) != NULL)
15934 *end_str = NULLCHAR;
15935 if ((end_str = strchr(message, '\n')) != NULL)
15936 *end_str = NULLCHAR;
15938 if (appData.debugMode) {
15939 TimeMark now; int print = 1;
15940 char *quote = ""; char c; int i;
15942 if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
15943 char start = message[0];
15944 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
15945 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
15946 sscanf(message, "move %c", &c)!=1 && sscanf(message, "offer%c", &c)!=1 &&
15947 sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
15948 sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
15949 sscanf(message, "tell%c", &c)!=1 && sscanf(message, "0-1 %c", &c)!=1 &&
15950 sscanf(message, "1-0 %c", &c)!=1 && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
15951 sscanf(message, "setboard %c", &c)!=1 && sscanf(message, "setup %c", &c)!=1 &&
15952 sscanf(message, "hint: %c", &c)!=1 &&
15953 sscanf(message, "pong %c", &c)!=1 && start != '#') {
15954 quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
15955 print = (appData.engineComments >= 2);
15957 message[0] = start; // restore original message
15961 fprintf(debugFP, "%ld <%-6s: %s%s\n",
15962 SubtractTimeMarks(&now, &programStartTime), cps->which,
15966 fprintf(serverFP, "%ld <%-6s: %s%s\n",
15967 SubtractTimeMarks(&now, &programStartTime), cps->which,
15969 message), fflush(serverFP);
15973 /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
15974 if (appData.icsEngineAnalyze) {
15975 if (strstr(message, "whisper") != NULL ||
15976 strstr(message, "kibitz") != NULL ||
15977 strstr(message, "tellics") != NULL) return;
15980 HandleMachineMove(message, cps);
15985 SendTimeControl (ChessProgramState *cps, int mps, long tc, int inc, int sd, int st)
15990 if( timeControl_2 > 0 ) {
15991 if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
15992 tc = timeControl_2;
15995 tc /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
15996 inc /= cps->timeOdds;
15997 st /= cps->timeOdds;
15999 seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
16002 /* Set exact time per move, normally using st command */
16003 if (cps->stKludge) {
16004 /* GNU Chess 4 has no st command; uses level in a nonstandard way */
16006 if (seconds == 0) {
16007 snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
16009 snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
16012 snprintf(buf, MSG_SIZ, "st %d\n", st);
16015 /* Set conventional or incremental time control, using level command */
16016 if (seconds == 0) {
16017 /* Note old gnuchess bug -- minutes:seconds used to not work.
16018 Fixed in later versions, but still avoid :seconds
16019 when seconds is 0. */
16020 snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
16022 snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
16023 seconds, inc/1000.);
16026 SendToProgram(buf, cps);
16028 /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
16029 /* Orthogonally, limit search to given depth */
16031 if (cps->sdKludge) {
16032 snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
16034 snprintf(buf, MSG_SIZ, "sd %d\n", sd);
16036 SendToProgram(buf, cps);
16039 if(cps->nps >= 0) { /* [HGM] nps */
16040 if(cps->supportsNPS == FALSE)
16041 cps->nps = -1; // don't use if engine explicitly says not supported!
16043 snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
16044 SendToProgram(buf, cps);
16049 ChessProgramState *
16051 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
16053 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
16054 gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
16060 SendTimeRemaining (ChessProgramState *cps, int machineWhite)
16062 char message[MSG_SIZ];
16065 /* Note: this routine must be called when the clocks are stopped
16066 or when they have *just* been set or switched; otherwise
16067 it will be off by the time since the current tick started.
16069 if (machineWhite) {
16070 time = whiteTimeRemaining / 10;
16071 otime = blackTimeRemaining / 10;
16073 time = blackTimeRemaining / 10;
16074 otime = whiteTimeRemaining / 10;
16076 /* [HGM] translate opponent's time by time-odds factor */
16077 otime = (otime * cps->other->timeOdds) / cps->timeOdds;
16079 if (time <= 0) time = 1;
16080 if (otime <= 0) otime = 1;
16082 snprintf(message, MSG_SIZ, "time %ld\n", time);
16083 SendToProgram(message, cps);
16085 snprintf(message, MSG_SIZ, "otim %ld\n", otime);
16086 SendToProgram(message, cps);
16090 EngineDefinedVariant (ChessProgramState *cps, int n)
16091 { // return name of n-th unknown variant that engine supports
16092 static char buf[MSG_SIZ];
16093 char *p, *s = cps->variants;
16094 if(!s) return NULL;
16095 do { // parse string from variants feature
16097 p = strchr(s, ',');
16098 if(p) *p = NULLCHAR;
16099 v = StringToVariant(s);
16100 if(v == VariantNormal && strcmp(s, "normal") && !strstr(s, "_normal")) v = VariantUnknown; // garbage is recognized as normal
16101 if(v == VariantUnknown) { // non-standard variant in list of engine-supported variants
16102 if(--n < 0) safeStrCpy(buf, s, MSG_SIZ);
16105 if(n < 0) return buf;
16111 BoolFeature (char **p, char *name, int *loc, ChessProgramState *cps)
16114 int len = strlen(name);
16117 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
16119 sscanf(*p, "%d", &val);
16121 while (**p && **p != ' ')
16123 snprintf(buf, MSG_SIZ, "accepted %s\n", name);
16124 SendToProgram(buf, cps);
16131 IntFeature (char **p, char *name, int *loc, ChessProgramState *cps)
16134 int len = strlen(name);
16135 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
16137 sscanf(*p, "%d", loc);
16138 while (**p && **p != ' ') (*p)++;
16139 snprintf(buf, MSG_SIZ, "accepted %s\n", name);
16140 SendToProgram(buf, cps);
16147 StringFeature (char **p, char *name, char **loc, ChessProgramState *cps)
16150 int len = strlen(name);
16151 if (strncmp((*p), name, len) == 0
16152 && (*p)[len] == '=' && (*p)[len+1] == '\"') {
16154 ASSIGN(*loc, *p); // kludge alert: assign rest of line just to be sure allocation is large enough so that sscanf below always fits
16155 sscanf(*p, "%[^\"]", *loc);
16156 while (**p && **p != '\"') (*p)++;
16157 if (**p == '\"') (*p)++;
16158 snprintf(buf, MSG_SIZ, "accepted %s\n", name);
16159 SendToProgram(buf, cps);
16166 ParseOption (Option *opt, ChessProgramState *cps)
16167 // [HGM] options: process the string that defines an engine option, and determine
16168 // name, type, default value, and allowed value range
16170 char *p, *q, buf[MSG_SIZ];
16171 int n, min = (-1)<<31, max = 1<<31, def;
16173 if(p = strstr(opt->name, " -spin ")) {
16174 if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
16175 if(max < min) max = min; // enforce consistency
16176 if(def < min) def = min;
16177 if(def > max) def = max;
16182 } else if((p = strstr(opt->name, " -slider "))) {
16183 // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
16184 if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
16185 if(max < min) max = min; // enforce consistency
16186 if(def < min) def = min;
16187 if(def > max) def = max;
16191 opt->type = Spin; // Slider;
16192 } else if((p = strstr(opt->name, " -string "))) {
16193 opt->textValue = p+9;
16194 opt->type = TextBox;
16195 } else if((p = strstr(opt->name, " -file "))) {
16196 // for now -file is a synonym for -string, to already provide compatibility with future polyglots
16197 opt->textValue = p+7;
16198 opt->type = FileName; // FileName;
16199 } else if((p = strstr(opt->name, " -path "))) {
16200 // for now -file is a synonym for -string, to already provide compatibility with future polyglots
16201 opt->textValue = p+7;
16202 opt->type = PathName; // PathName;
16203 } else if(p = strstr(opt->name, " -check ")) {
16204 if(sscanf(p, " -check %d", &def) < 1) return FALSE;
16205 opt->value = (def != 0);
16206 opt->type = CheckBox;
16207 } else if(p = strstr(opt->name, " -combo ")) {
16208 opt->textValue = (char*) (opt->choice = &cps->comboList[cps->comboCnt]); // cheat with pointer type
16209 cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
16210 if(*q == '*') cps->comboList[cps->comboCnt-1]++;
16211 opt->value = n = 0;
16212 while(q = StrStr(q, " /// ")) {
16213 n++; *q = 0; // count choices, and null-terminate each of them
16215 if(*q == '*') { // remember default, which is marked with * prefix
16219 cps->comboList[cps->comboCnt++] = q;
16221 cps->comboList[cps->comboCnt++] = NULL;
16223 opt->type = ComboBox;
16224 } else if(p = strstr(opt->name, " -button")) {
16225 opt->type = Button;
16226 } else if(p = strstr(opt->name, " -save")) {
16227 opt->type = SaveButton;
16228 } else return FALSE;
16229 *p = 0; // terminate option name
16230 // now look if the command-line options define a setting for this engine option.
16231 if(cps->optionSettings && cps->optionSettings[0])
16232 p = strstr(cps->optionSettings, opt->name); else p = NULL;
16233 if(p && (p == cps->optionSettings || p[-1] == ',')) {
16234 snprintf(buf, MSG_SIZ, "option %s", p);
16235 if(p = strstr(buf, ",")) *p = 0;
16236 if(q = strchr(buf, '=')) switch(opt->type) {
16238 for(n=0; n<opt->max; n++)
16239 if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
16242 safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
16246 opt->value = atoi(q+1);
16251 SendToProgram(buf, cps);
16257 FeatureDone (ChessProgramState *cps, int val)
16259 DelayedEventCallback cb = GetDelayedEvent();
16260 if ((cb == InitBackEnd3 && cps == &first) ||
16261 (cb == SettingsMenuIfReady && cps == &second) ||
16262 (cb == LoadEngine) ||
16263 (cb == TwoMachinesEventIfReady)) {
16264 CancelDelayedEvent();
16265 ScheduleDelayedEvent(cb, val ? 1 : 3600000);
16267 cps->initDone = val;
16268 if(val) cps->reload = FALSE;
16271 /* Parse feature command from engine */
16273 ParseFeatures (char *args, ChessProgramState *cps)
16281 while (*p == ' ') p++;
16282 if (*p == NULLCHAR) return;
16284 if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
16285 if (BoolFeature(&p, "xedit", &cps->extendedEdit, cps)) continue;
16286 if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
16287 if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
16288 if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
16289 if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
16290 if (BoolFeature(&p, "reuse", &val, cps)) {
16291 /* Engine can disable reuse, but can't enable it if user said no */
16292 if (!val) cps->reuse = FALSE;
16295 if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
16296 if (StringFeature(&p, "myname", &cps->tidy, cps)) {
16297 if (gameMode == TwoMachinesPlay) {
16298 DisplayTwoMachinesTitle();
16304 if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
16305 if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
16306 if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
16307 if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
16308 if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
16309 if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
16310 if (BoolFeature(&p, "exclude", &cps->excludeMoves, cps)) continue;
16311 if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
16312 if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
16313 if (BoolFeature(&p, "pause", &cps->pause, cps)) continue; // [HGM] pause
16314 if (IntFeature(&p, "done", &val, cps)) {
16315 FeatureDone(cps, val);
16318 /* Added by Tord: */
16319 if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
16320 if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
16321 /* End of additions by Tord */
16323 /* [HGM] added features: */
16324 if (BoolFeature(&p, "highlight", &cps->highlight, cps)) continue;
16325 if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
16326 if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
16327 if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
16328 if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
16329 if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
16330 if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
16331 if (StringFeature(&p, "option", &q, cps)) { // read to freshly allocated temp buffer first
16332 if(cps->reload) { FREE(q); q = NULL; continue; } // we are reloading because of xreuse
16333 FREE(cps->option[cps->nrOptions].name);
16334 cps->option[cps->nrOptions].name = q; q = NULL;
16335 if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
16336 snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
16337 SendToProgram(buf, cps);
16340 if(cps->nrOptions >= MAX_OPTIONS) {
16342 snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
16343 DisplayError(buf, 0);
16347 /* End of additions by HGM */
16349 /* unknown feature: complain and skip */
16351 while (*q && *q != '=') q++;
16352 snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
16353 SendToProgram(buf, cps);
16359 while (*p && *p != '\"') p++;
16360 if (*p == '\"') p++;
16362 while (*p && *p != ' ') p++;
16370 PeriodicUpdatesEvent (int newState)
16372 if (newState == appData.periodicUpdates)
16375 appData.periodicUpdates=newState;
16377 /* Display type changes, so update it now */
16378 // DisplayAnalysis();
16380 /* Get the ball rolling again... */
16382 AnalysisPeriodicEvent(1);
16383 StartAnalysisClock();
16388 PonderNextMoveEvent (int newState)
16390 if (newState == appData.ponderNextMove) return;
16391 if (gameMode == EditPosition) EditPositionDone(TRUE);
16393 SendToProgram("hard\n", &first);
16394 if (gameMode == TwoMachinesPlay) {
16395 SendToProgram("hard\n", &second);
16398 SendToProgram("easy\n", &first);
16399 thinkOutput[0] = NULLCHAR;
16400 if (gameMode == TwoMachinesPlay) {
16401 SendToProgram("easy\n", &second);
16404 appData.ponderNextMove = newState;
16408 NewSettingEvent (int option, int *feature, char *command, int value)
16412 if (gameMode == EditPosition) EditPositionDone(TRUE);
16413 snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
16414 if(feature == NULL || *feature) SendToProgram(buf, &first);
16415 if (gameMode == TwoMachinesPlay) {
16416 if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
16421 ShowThinkingEvent ()
16422 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
16424 static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
16425 int newState = appData.showThinking
16426 // [HGM] thinking: other features now need thinking output as well
16427 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
16429 if (oldState == newState) return;
16430 oldState = newState;
16431 if (gameMode == EditPosition) EditPositionDone(TRUE);
16433 SendToProgram("post\n", &first);
16434 if (gameMode == TwoMachinesPlay) {
16435 SendToProgram("post\n", &second);
16438 SendToProgram("nopost\n", &first);
16439 thinkOutput[0] = NULLCHAR;
16440 if (gameMode == TwoMachinesPlay) {
16441 SendToProgram("nopost\n", &second);
16444 // appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
16448 AskQuestionEvent (char *title, char *question, char *replyPrefix, char *which)
16450 ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
16451 if (pr == NoProc) return;
16452 AskQuestion(title, question, replyPrefix, pr);
16456 TypeInEvent (char firstChar)
16458 if ((gameMode == BeginningOfGame && !appData.icsActive) ||
16459 gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
16460 gameMode == AnalyzeMode || gameMode == EditGame ||
16461 gameMode == EditPosition || gameMode == IcsExamining ||
16462 gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
16463 isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes
16464 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||
16465 gameMode == IcsObserving || gameMode == TwoMachinesPlay ) ||
16466 gameMode == Training) PopUpMoveDialog(firstChar);
16470 TypeInDoneEvent (char *move)
16473 int n, fromX, fromY, toX, toY;
16475 ChessMove moveType;
16478 if(gameMode == EditPosition && ParseFEN(board, &n, move) ) {
16479 EditPositionPasteFEN(move);
16482 // [HGM] movenum: allow move number to be typed in any mode
16483 if(sscanf(move, "%d", &n) == 1 && n != 0 ) {
16487 // undocumented kludge: allow command-line option to be typed in!
16488 // (potentially fatal, and does not implement the effect of the option.)
16489 // should only be used for options that are values on which future decisions will be made,
16490 // and definitely not on options that would be used during initialization.
16491 if(strstr(move, "!!! -") == move) {
16492 ParseArgsFromString(move+4);
16496 if (gameMode != EditGame && currentMove != forwardMostMove &&
16497 gameMode != Training) {
16498 DisplayMoveError(_("Displayed move is not current"));
16500 int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
16501 &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
16502 if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized
16503 if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
16504 &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {
16505 UserMoveEvent(fromX, fromY, toX, toY, promoChar);
16507 DisplayMoveError(_("Could not parse move"));
16513 DisplayMove (int moveNumber)
16515 char message[MSG_SIZ];
16517 char cpThinkOutput[MSG_SIZ];
16519 if(appData.noGUI) return; // [HGM] fast: suppress display of moves
16521 if (moveNumber == forwardMostMove - 1 ||
16522 gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
16524 safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
16526 if (strchr(cpThinkOutput, '\n')) {
16527 *strchr(cpThinkOutput, '\n') = NULLCHAR;
16530 *cpThinkOutput = NULLCHAR;
16533 /* [AS] Hide thinking from human user */
16534 if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
16535 *cpThinkOutput = NULLCHAR;
16536 if( thinkOutput[0] != NULLCHAR ) {
16539 for( i=0; i<=hiddenThinkOutputState; i++ ) {
16540 cpThinkOutput[i] = '.';
16542 cpThinkOutput[i] = NULLCHAR;
16543 hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
16547 if (moveNumber == forwardMostMove - 1 &&
16548 gameInfo.resultDetails != NULL) {
16549 if (gameInfo.resultDetails[0] == NULLCHAR) {
16550 snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
16552 snprintf(res, MSG_SIZ, " {%s} %s",
16553 T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
16559 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
16560 DisplayMessage(res, cpThinkOutput);
16562 snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
16563 WhiteOnMove(moveNumber) ? " " : ".. ",
16564 parseList[moveNumber], res);
16565 DisplayMessage(message, cpThinkOutput);
16570 DisplayComment (int moveNumber, char *text)
16572 char title[MSG_SIZ];
16574 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
16575 safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
16577 snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
16578 WhiteOnMove(moveNumber) ? " " : ".. ",
16579 parseList[moveNumber]);
16581 if (text != NULL && (appData.autoDisplayComment || commentUp))
16582 CommentPopUp(title, text);
16585 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
16586 * might be busy thinking or pondering. It can be omitted if your
16587 * gnuchess is configured to stop thinking immediately on any user
16588 * input. However, that gnuchess feature depends on the FIONREAD
16589 * ioctl, which does not work properly on some flavors of Unix.
16592 Attention (ChessProgramState *cps)
16595 if (!cps->useSigint) return;
16596 if (appData.noChessProgram || (cps->pr == NoProc)) return;
16597 switch (gameMode) {
16598 case MachinePlaysWhite:
16599 case MachinePlaysBlack:
16600 case TwoMachinesPlay:
16601 case IcsPlayingWhite:
16602 case IcsPlayingBlack:
16605 /* Skip if we know it isn't thinking */
16606 if (!cps->maybeThinking) return;
16607 if (appData.debugMode)
16608 fprintf(debugFP, "Interrupting %s\n", cps->which);
16609 InterruptChildProcess(cps->pr);
16610 cps->maybeThinking = FALSE;
16615 #endif /*ATTENTION*/
16621 if (whiteTimeRemaining <= 0) {
16624 if (appData.icsActive) {
16625 if (appData.autoCallFlag &&
16626 gameMode == IcsPlayingBlack && !blackFlag) {
16627 SendToICS(ics_prefix);
16628 SendToICS("flag\n");
16632 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
16634 if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
16635 if (appData.autoCallFlag) {
16636 GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
16643 if (blackTimeRemaining <= 0) {
16646 if (appData.icsActive) {
16647 if (appData.autoCallFlag &&
16648 gameMode == IcsPlayingWhite && !whiteFlag) {
16649 SendToICS(ics_prefix);
16650 SendToICS("flag\n");
16654 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
16656 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
16657 if (appData.autoCallFlag) {
16658 GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
16669 CheckTimeControl ()
16671 if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
16672 gameMode == PlayFromGameFile || forwardMostMove == 0) return;
16675 * add time to clocks when time control is achieved ([HGM] now also used for increment)
16677 if ( !WhiteOnMove(forwardMostMove) ) {
16678 /* White made time control */
16679 lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
16680 whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
16681 /* [HGM] time odds: correct new time quota for time odds! */
16682 / WhitePlayer()->timeOdds;
16683 lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
16685 lastBlack -= blackTimeRemaining;
16686 /* Black made time control */
16687 blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
16688 / WhitePlayer()->other->timeOdds;
16689 lastWhite = whiteTimeRemaining;
16694 DisplayBothClocks ()
16696 int wom = gameMode == EditPosition ?
16697 !blackPlaysFirst : WhiteOnMove(currentMove);
16698 DisplayWhiteClock(whiteTimeRemaining, wom);
16699 DisplayBlackClock(blackTimeRemaining, !wom);
16703 /* Timekeeping seems to be a portability nightmare. I think everyone
16704 has ftime(), but I'm really not sure, so I'm including some ifdefs
16705 to use other calls if you don't. Clocks will be less accurate if
16706 you have neither ftime nor gettimeofday.
16709 /* VS 2008 requires the #include outside of the function */
16710 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
16711 #include <sys/timeb.h>
16714 /* Get the current time as a TimeMark */
16716 GetTimeMark (TimeMark *tm)
16718 #if HAVE_GETTIMEOFDAY
16720 struct timeval timeVal;
16721 struct timezone timeZone;
16723 gettimeofday(&timeVal, &timeZone);
16724 tm->sec = (long) timeVal.tv_sec;
16725 tm->ms = (int) (timeVal.tv_usec / 1000L);
16727 #else /*!HAVE_GETTIMEOFDAY*/
16730 // include <sys/timeb.h> / moved to just above start of function
16731 struct timeb timeB;
16734 tm->sec = (long) timeB.time;
16735 tm->ms = (int) timeB.millitm;
16737 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
16738 tm->sec = (long) time(NULL);
16744 /* Return the difference in milliseconds between two
16745 time marks. We assume the difference will fit in a long!
16748 SubtractTimeMarks (TimeMark *tm2, TimeMark *tm1)
16750 return 1000L*(tm2->sec - tm1->sec) +
16751 (long) (tm2->ms - tm1->ms);
16756 * Code to manage the game clocks.
16758 * In tournament play, black starts the clock and then white makes a move.
16759 * We give the human user a slight advantage if he is playing white---the
16760 * clocks don't run until he makes his first move, so it takes zero time.
16761 * Also, we don't account for network lag, so we could get out of sync
16762 * with GNU Chess's clock -- but then, referees are always right.
16765 static TimeMark tickStartTM;
16766 static long intendedTickLength;
16769 NextTickLength (long timeRemaining)
16771 long nominalTickLength, nextTickLength;
16773 if (timeRemaining > 0L && timeRemaining <= 10000L)
16774 nominalTickLength = 100L;
16776 nominalTickLength = 1000L;
16777 nextTickLength = timeRemaining % nominalTickLength;
16778 if (nextTickLength <= 0) nextTickLength += nominalTickLength;
16780 return nextTickLength;
16783 /* Adjust clock one minute up or down */
16785 AdjustClock (Boolean which, int dir)
16787 if(appData.autoCallFlag) { DisplayError(_("Clock adjustment not allowed in auto-flag mode"), 0); return; }
16788 if(which) blackTimeRemaining += 60000*dir;
16789 else whiteTimeRemaining += 60000*dir;
16790 DisplayBothClocks();
16791 adjustedClock = TRUE;
16794 /* Stop clocks and reset to a fresh time control */
16798 (void) StopClockTimer();
16799 if (appData.icsActive) {
16800 whiteTimeRemaining = blackTimeRemaining = 0;
16801 } else if (searchTime) {
16802 whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
16803 blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
16804 } else { /* [HGM] correct new time quote for time odds */
16805 whiteTC = blackTC = fullTimeControlString;
16806 whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
16807 blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
16809 if (whiteFlag || blackFlag) {
16811 whiteFlag = blackFlag = FALSE;
16813 lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
16814 DisplayBothClocks();
16815 adjustedClock = FALSE;
16818 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
16820 /* Decrement running clock by amount of time that has passed */
16824 long timeRemaining;
16825 long lastTickLength, fudge;
16828 if (!appData.clockMode) return;
16829 if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
16833 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16835 /* Fudge if we woke up a little too soon */
16836 fudge = intendedTickLength - lastTickLength;
16837 if (fudge < 0 || fudge > FUDGE) fudge = 0;
16839 if (WhiteOnMove(forwardMostMove)) {
16840 if(whiteNPS >= 0) lastTickLength = 0;
16841 timeRemaining = whiteTimeRemaining -= lastTickLength;
16842 if(timeRemaining < 0 && !appData.icsActive) {
16843 GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
16844 if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
16845 whiteStartMove = forwardMostMove; whiteTC = nextSession;
16846 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
16849 DisplayWhiteClock(whiteTimeRemaining - fudge,
16850 WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
16852 if(blackNPS >= 0) lastTickLength = 0;
16853 timeRemaining = blackTimeRemaining -= lastTickLength;
16854 if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
16855 GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
16857 blackStartMove = forwardMostMove;
16858 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
16861 DisplayBlackClock(blackTimeRemaining - fudge,
16862 !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
16864 if (CheckFlags()) return;
16866 if(twoBoards) { // count down secondary board's clocks as well
16867 activePartnerTime -= lastTickLength;
16869 if(activePartner == 'W')
16870 DisplayWhiteClock(activePartnerTime, TRUE); // the counting clock is always the highlighted one!
16872 DisplayBlackClock(activePartnerTime, TRUE);
16877 intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
16878 StartClockTimer(intendedTickLength);
16880 /* if the time remaining has fallen below the alarm threshold, sound the
16881 * alarm. if the alarm has sounded and (due to a takeback or time control
16882 * with increment) the time remaining has increased to a level above the
16883 * threshold, reset the alarm so it can sound again.
16886 if (appData.icsActive && appData.icsAlarm) {
16888 /* make sure we are dealing with the user's clock */
16889 if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
16890 ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
16893 if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
16894 alarmSounded = FALSE;
16895 } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
16897 alarmSounded = TRUE;
16903 /* A player has just moved, so stop the previously running
16904 clock and (if in clock mode) start the other one.
16905 We redisplay both clocks in case we're in ICS mode, because
16906 ICS gives us an update to both clocks after every move.
16907 Note that this routine is called *after* forwardMostMove
16908 is updated, so the last fractional tick must be subtracted
16909 from the color that is *not* on move now.
16912 SwitchClocks (int newMoveNr)
16914 long lastTickLength;
16916 int flagged = FALSE;
16920 if (StopClockTimer() && appData.clockMode) {
16921 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16922 if (!WhiteOnMove(forwardMostMove)) {
16923 if(blackNPS >= 0) lastTickLength = 0;
16924 blackTimeRemaining -= lastTickLength;
16925 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
16926 // if(pvInfoList[forwardMostMove].time == -1)
16927 pvInfoList[forwardMostMove].time = // use GUI time
16928 (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
16930 if(whiteNPS >= 0) lastTickLength = 0;
16931 whiteTimeRemaining -= lastTickLength;
16932 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
16933 // if(pvInfoList[forwardMostMove].time == -1)
16934 pvInfoList[forwardMostMove].time =
16935 (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
16937 flagged = CheckFlags();
16939 forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
16940 CheckTimeControl();
16942 if (flagged || !appData.clockMode) return;
16944 switch (gameMode) {
16945 case MachinePlaysBlack:
16946 case MachinePlaysWhite:
16947 case BeginningOfGame:
16948 if (pausing) return;
16952 case PlayFromGameFile:
16960 if (searchTime) { // [HGM] st: set clock of player that has to move to max time
16961 if(WhiteOnMove(forwardMostMove))
16962 whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
16963 else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
16967 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
16968 whiteTimeRemaining : blackTimeRemaining);
16969 StartClockTimer(intendedTickLength);
16973 /* Stop both clocks */
16977 long lastTickLength;
16980 if (!StopClockTimer()) return;
16981 if (!appData.clockMode) return;
16985 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16986 if (WhiteOnMove(forwardMostMove)) {
16987 if(whiteNPS >= 0) lastTickLength = 0;
16988 whiteTimeRemaining -= lastTickLength;
16989 DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
16991 if(blackNPS >= 0) lastTickLength = 0;
16992 blackTimeRemaining -= lastTickLength;
16993 DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
16998 /* Start clock of player on move. Time may have been reset, so
16999 if clock is already running, stop and restart it. */
17003 (void) StopClockTimer(); /* in case it was running already */
17004 DisplayBothClocks();
17005 if (CheckFlags()) return;
17007 if (!appData.clockMode) return;
17008 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
17010 GetTimeMark(&tickStartTM);
17011 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
17012 whiteTimeRemaining : blackTimeRemaining);
17014 /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
17015 whiteNPS = blackNPS = -1;
17016 if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
17017 || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
17018 whiteNPS = first.nps;
17019 if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
17020 || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
17021 blackNPS = first.nps;
17022 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
17023 whiteNPS = second.nps;
17024 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
17025 blackNPS = second.nps;
17026 if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
17028 StartClockTimer(intendedTickLength);
17032 TimeString (long ms)
17034 long second, minute, hour, day;
17036 static char buf[32];
17038 if (ms > 0 && ms <= 9900) {
17039 /* convert milliseconds to tenths, rounding up */
17040 double tenths = floor( ((double)(ms + 99L)) / 100.00 );
17042 snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
17046 /* convert milliseconds to seconds, rounding up */
17047 /* use floating point to avoid strangeness of integer division
17048 with negative dividends on many machines */
17049 second = (long) floor(((double) (ms + 999L)) / 1000.0);
17056 day = second / (60 * 60 * 24);
17057 second = second % (60 * 60 * 24);
17058 hour = second / (60 * 60);
17059 second = second % (60 * 60);
17060 minute = second / 60;
17061 second = second % 60;
17064 snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
17065 sign, day, hour, minute, second);
17067 snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
17069 snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
17076 * This is necessary because some C libraries aren't ANSI C compliant yet.
17079 StrStr (char *string, char *match)
17083 length = strlen(match);
17085 for (i = strlen(string) - length; i >= 0; i--, string++)
17086 if (!strncmp(match, string, length))
17093 StrCaseStr (char *string, char *match)
17097 length = strlen(match);
17099 for (i = strlen(string) - length; i >= 0; i--, string++) {
17100 for (j = 0; j < length; j++) {
17101 if (ToLower(match[j]) != ToLower(string[j]))
17104 if (j == length) return string;
17112 StrCaseCmp (char *s1, char *s2)
17117 c1 = ToLower(*s1++);
17118 c2 = ToLower(*s2++);
17119 if (c1 > c2) return 1;
17120 if (c1 < c2) return -1;
17121 if (c1 == NULLCHAR) return 0;
17129 return isupper(c) ? tolower(c) : c;
17136 return islower(c) ? toupper(c) : c;
17138 #endif /* !_amigados */
17145 if ((ret = (char *) malloc(strlen(s) + 1)))
17147 safeStrCpy(ret, s, strlen(s)+1);
17153 StrSavePtr (char *s, char **savePtr)
17158 if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
17159 safeStrCpy(*savePtr, s, strlen(s)+1);
17171 clock = time((time_t *)NULL);
17172 tm = localtime(&clock);
17173 snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
17174 tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
17175 return StrSave(buf);
17180 PositionToFEN (int move, char *overrideCastling, int moveCounts)
17182 int i, j, fromX, fromY, toX, toY;
17189 whiteToPlay = (gameMode == EditPosition) ?
17190 !blackPlaysFirst : (move % 2 == 0);
17193 /* Piece placement data */
17194 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
17195 if(MSG_SIZ - (p - buf) < BOARD_RGHT - BOARD_LEFT + 20) { *p = 0; return StrSave(buf); }
17197 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
17198 if (boards[move][i][j] == EmptySquare) {
17200 } else { ChessSquare piece = boards[move][i][j];
17201 if (emptycount > 0) {
17202 if(emptycount<10) /* [HGM] can be >= 10 */
17203 *p++ = '0' + emptycount;
17204 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
17207 if(PieceToChar(piece) == '+') {
17208 /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
17210 piece = (ChessSquare)(DEMOTED piece);
17212 *p++ = (piece == DarkSquare ? '*' : PieceToChar(piece));
17214 /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
17215 p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
17220 if (emptycount > 0) {
17221 if(emptycount<10) /* [HGM] can be >= 10 */
17222 *p++ = '0' + emptycount;
17223 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
17230 /* [HGM] print Crazyhouse or Shogi holdings */
17231 if( gameInfo.holdingsWidth ) {
17232 *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
17234 for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
17235 piece = boards[move][i][BOARD_WIDTH-1];
17236 if( piece != EmptySquare )
17237 for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
17238 *p++ = PieceToChar(piece);
17240 for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
17241 piece = boards[move][BOARD_HEIGHT-i-1][0];
17242 if( piece != EmptySquare )
17243 for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
17244 *p++ = PieceToChar(piece);
17247 if( q == p ) *p++ = '-';
17253 *p++ = whiteToPlay ? 'w' : 'b';
17256 if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
17257 while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
17259 if(nrCastlingRights) {
17261 if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
17262 /* [HGM] write directly from rights */
17263 if(boards[move][CASTLING][2] != NoRights &&
17264 boards[move][CASTLING][0] != NoRights )
17265 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
17266 if(boards[move][CASTLING][2] != NoRights &&
17267 boards[move][CASTLING][1] != NoRights )
17268 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
17269 if(boards[move][CASTLING][5] != NoRights &&
17270 boards[move][CASTLING][3] != NoRights )
17271 *p++ = boards[move][CASTLING][3] + AAA;
17272 if(boards[move][CASTLING][5] != NoRights &&
17273 boards[move][CASTLING][4] != NoRights )
17274 *p++ = boards[move][CASTLING][4] + AAA;
17277 /* [HGM] write true castling rights */
17278 if( nrCastlingRights == 6 ) {
17280 if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
17281 boards[move][CASTLING][2] != NoRights ) k = 1, *p++ = 'K';
17282 q = (boards[move][CASTLING][1] == BOARD_LEFT &&
17283 boards[move][CASTLING][2] != NoRights );
17284 if(gameInfo.variant == VariantSChess) { // for S-Chess, indicate all vrgin backrank pieces
17285 for(i=j=0; i<BOARD_HEIGHT; i++) j += boards[move][i][BOARD_RGHT]; // count white held pieces
17286 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q && j; i--)
17287 if((boards[move][0][i] != WhiteKing || k+q == 0) &&
17288 boards[move][VIRGIN][i] & VIRGIN_W) *p++ = i + AAA + 'A' - 'a';
17292 if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
17293 boards[move][CASTLING][5] != NoRights ) k = 1, *p++ = 'k';
17294 q = (boards[move][CASTLING][4] == BOARD_LEFT &&
17295 boards[move][CASTLING][5] != NoRights );
17296 if(gameInfo.variant == VariantSChess) {
17297 for(i=j=0; i<BOARD_HEIGHT; i++) j += boards[move][i][BOARD_LEFT-1]; // count black held pieces
17298 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q && j; i--)
17299 if((boards[move][BOARD_HEIGHT-1][i] != BlackKing || k+q == 0) &&
17300 boards[move][VIRGIN][i] & VIRGIN_B) *p++ = i + AAA;
17305 if (q == p) *p++ = '-'; /* No castling rights */
17309 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
17310 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier &&
17311 gameInfo.variant != VariantMakruk && gameInfo.variant != VariantASEAN ) {
17312 /* En passant target square */
17313 if (move > backwardMostMove) {
17314 fromX = moveList[move - 1][0] - AAA;
17315 fromY = moveList[move - 1][1] - ONE;
17316 toX = moveList[move - 1][2] - AAA;
17317 toY = moveList[move - 1][3] - ONE;
17318 if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
17319 toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
17320 boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
17322 /* 2-square pawn move just happened */
17324 *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
17328 } else if(move == backwardMostMove) {
17329 // [HGM] perhaps we should always do it like this, and forget the above?
17330 if((signed char)boards[move][EP_STATUS] >= 0) {
17331 *p++ = boards[move][EP_STATUS] + AAA;
17332 *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
17344 { int i = 0, j=move;
17346 /* [HGM] find reversible plies */
17347 if (appData.debugMode) { int k;
17348 fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
17349 for(k=backwardMostMove; k<=forwardMostMove; k++)
17350 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
17354 while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
17355 if( j == backwardMostMove ) i += initialRulePlies;
17356 sprintf(p, "%d ", i);
17357 p += i>=100 ? 4 : i >= 10 ? 3 : 2;
17359 /* Fullmove number */
17360 sprintf(p, "%d", (move / 2) + 1);
17361 } else *--p = NULLCHAR;
17363 return StrSave(buf);
17367 ParseFEN (Board board, int *blackPlaysFirst, char *fen)
17371 int emptycount, virgin[BOARD_FILES];
17376 /* [HGM] by default clear Crazyhouse holdings, if present */
17377 if(gameInfo.holdingsWidth) {
17378 for(i=0; i<BOARD_HEIGHT; i++) {
17379 board[i][0] = EmptySquare; /* black holdings */
17380 board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
17381 board[i][1] = (ChessSquare) 0; /* black counts */
17382 board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
17386 /* Piece placement data */
17387 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
17390 if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
17391 if (*p == '/') p++;
17392 emptycount = gameInfo.boardWidth - j;
17393 while (emptycount--)
17394 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
17396 #if(BOARD_FILES >= 10)
17397 } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
17398 p++; emptycount=10;
17399 if (j + emptycount > gameInfo.boardWidth) return FALSE;
17400 while (emptycount--)
17401 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
17403 } else if (*p == '*') {
17404 board[i][(j++)+gameInfo.holdingsWidth] = DarkSquare; p++;
17405 } else if (isdigit(*p)) {
17406 emptycount = *p++ - '0';
17407 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
17408 if (j + emptycount > gameInfo.boardWidth) return FALSE;
17409 while (emptycount--)
17410 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
17411 } else if (*p == '+' || isalpha(*p)) {
17412 if (j >= gameInfo.boardWidth) return FALSE;
17414 piece = CharToPiece(*++p);
17415 if(piece == EmptySquare) return FALSE; /* unknown piece */
17416 piece = (ChessSquare) (PROMOTED piece ); p++;
17417 if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
17418 } else piece = CharToPiece(*p++);
17420 if(piece==EmptySquare) return FALSE; /* unknown piece */
17421 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
17422 piece = (ChessSquare) (PROMOTED piece);
17423 if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
17426 board[i][(j++)+gameInfo.holdingsWidth] = piece;
17432 while (*p == '/' || *p == ' ') p++;
17434 /* [HGM] look for Crazyhouse holdings here */
17435 while(*p==' ') p++;
17436 if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
17438 if(*p == '-' ) p++; /* empty holdings */ else {
17439 if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
17440 /* if we would allow FEN reading to set board size, we would */
17441 /* have to add holdings and shift the board read so far here */
17442 while( (piece = CharToPiece(*p) ) != EmptySquare ) {
17444 if((int) piece >= (int) BlackPawn ) {
17445 i = (int)piece - (int)BlackPawn;
17446 i = PieceToNumber((ChessSquare)i);
17447 if( i >= gameInfo.holdingsSize ) return FALSE;
17448 board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
17449 board[BOARD_HEIGHT-1-i][1]++; /* black counts */
17451 i = (int)piece - (int)WhitePawn;
17452 i = PieceToNumber((ChessSquare)i);
17453 if( i >= gameInfo.holdingsSize ) return FALSE;
17454 board[i][BOARD_WIDTH-1] = piece; /* white holdings */
17455 board[i][BOARD_WIDTH-2]++; /* black holdings */
17462 while(*p == ' ') p++;
17466 if(appData.colorNickNames) {
17467 if( c == appData.colorNickNames[0] ) c = 'w'; else
17468 if( c == appData.colorNickNames[1] ) c = 'b';
17472 *blackPlaysFirst = FALSE;
17475 *blackPlaysFirst = TRUE;
17481 /* [HGM] We NO LONGER ignore the rest of the FEN notation */
17482 /* return the extra info in global variiables */
17484 /* set defaults in case FEN is incomplete */
17485 board[EP_STATUS] = EP_UNKNOWN;
17486 for(i=0; i<nrCastlingRights; i++ ) {
17487 board[CASTLING][i] =
17488 gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
17489 } /* assume possible unless obviously impossible */
17490 if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
17491 if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
17492 if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
17493 && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
17494 if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
17495 if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
17496 if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
17497 && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
17500 while(*p==' ') p++;
17501 if(nrCastlingRights) {
17502 if(gameInfo.variant == VariantSChess) for(i=0; i<BOARD_FILES; i++) virgin[i] = 0;
17503 if(*p >= 'A' && *p <= 'Z' || *p >= 'a' && *p <= 'z' || *p=='-') {
17504 /* castling indicator present, so default becomes no castlings */
17505 for(i=0; i<nrCastlingRights; i++ ) {
17506 board[CASTLING][i] = NoRights;
17509 while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
17510 (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess) &&
17511 ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
17512 ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth) ) {
17513 int c = *p++, whiteKingFile=NoRights, blackKingFile=NoRights;
17515 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
17516 if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
17517 if(board[0 ][i] == WhiteKing) whiteKingFile = i;
17519 if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
17520 whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
17521 if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
17522 && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
17523 if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
17524 && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
17527 for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
17528 board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
17529 board[CASTLING][2] = whiteKingFile;
17530 if(board[CASTLING][0] != NoRights) virgin[board[CASTLING][0]] |= VIRGIN_W;
17531 if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
17534 for(i=BOARD_LEFT; i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
17535 board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
17536 board[CASTLING][2] = whiteKingFile;
17537 if(board[CASTLING][1] != NoRights) virgin[board[CASTLING][1]] |= VIRGIN_W;
17538 if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
17541 for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
17542 board[CASTLING][3] = i != blackKingFile ? i : NoRights;
17543 board[CASTLING][5] = blackKingFile;
17544 if(board[CASTLING][3] != NoRights) virgin[board[CASTLING][3]] |= VIRGIN_B;
17545 if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
17548 for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
17549 board[CASTLING][4] = i != blackKingFile ? i : NoRights;
17550 board[CASTLING][5] = blackKingFile;
17551 if(board[CASTLING][4] != NoRights) virgin[board[CASTLING][4]] |= VIRGIN_B;
17552 if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
17555 default: /* FRC castlings */
17556 if(c >= 'a') { /* black rights */
17557 if(gameInfo.variant == VariantSChess) { virgin[c-AAA] |= VIRGIN_B; break; } // in S-Chess castlings are always kq, so just virginity
17558 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
17559 if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
17560 if(i == BOARD_RGHT) break;
17561 board[CASTLING][5] = i;
17563 if(board[BOARD_HEIGHT-1][c] < BlackPawn ||
17564 board[BOARD_HEIGHT-1][c] >= BlackKing ) break;
17566 board[CASTLING][3] = c;
17568 board[CASTLING][4] = c;
17569 } else { /* white rights */
17570 if(gameInfo.variant == VariantSChess) { virgin[c-AAA-'A'+'a'] |= VIRGIN_W; break; } // in S-Chess castlings are always KQ
17571 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
17572 if(board[0][i] == WhiteKing) break;
17573 if(i == BOARD_RGHT) break;
17574 board[CASTLING][2] = i;
17575 c -= AAA - 'a' + 'A';
17576 if(board[0][c] >= WhiteKing) break;
17578 board[CASTLING][0] = c;
17580 board[CASTLING][1] = c;
17584 for(i=0; i<nrCastlingRights; i++)
17585 if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
17586 if(gameInfo.variant == VariantSChess) for(i=0; i<BOARD_FILES; i++) board[VIRGIN][i] = virgin[i];
17587 if (appData.debugMode) {
17588 fprintf(debugFP, "FEN castling rights:");
17589 for(i=0; i<nrCastlingRights; i++)
17590 fprintf(debugFP, " %d", board[CASTLING][i]);
17591 fprintf(debugFP, "\n");
17594 while(*p==' ') p++;
17597 /* read e.p. field in games that know e.p. capture */
17598 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
17599 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier &&
17600 gameInfo.variant != VariantMakruk && gameInfo.variant != VariantASEAN ) {
17602 p++; board[EP_STATUS] = EP_NONE;
17604 char c = *p++ - AAA;
17606 if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
17607 if(*p >= '0' && *p <='9') p++;
17608 board[EP_STATUS] = c;
17613 if(sscanf(p, "%d", &i) == 1) {
17614 FENrulePlies = i; /* 50-move ply counter */
17615 /* (The move number is still ignored) */
17622 EditPositionPasteFEN (char *fen)
17625 Board initial_position;
17627 if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
17628 DisplayError(_("Bad FEN position in clipboard"), 0);
17631 int savedBlackPlaysFirst = blackPlaysFirst;
17632 EditPositionEvent();
17633 blackPlaysFirst = savedBlackPlaysFirst;
17634 CopyBoard(boards[0], initial_position);
17635 initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
17636 EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
17637 DisplayBothClocks();
17638 DrawPosition(FALSE, boards[currentMove]);
17643 static char cseq[12] = "\\ ";
17646 set_cont_sequence (char *new_seq)
17651 // handle bad attempts to set the sequence
17653 return 0; // acceptable error - no debug
17655 len = strlen(new_seq);
17656 ret = (len > 0) && (len < sizeof(cseq));
17658 safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
17659 else if (appData.debugMode)
17660 fprintf(debugFP, "Invalid continuation sequence \"%s\" (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
17665 reformat a source message so words don't cross the width boundary. internal
17666 newlines are not removed. returns the wrapped size (no null character unless
17667 included in source message). If dest is NULL, only calculate the size required
17668 for the dest buffer. lp argument indicats line position upon entry, and it's
17669 passed back upon exit.
17672 wrap (char *dest, char *src, int count, int width, int *lp)
17674 int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
17676 cseq_len = strlen(cseq);
17677 old_line = line = *lp;
17678 ansi = len = clen = 0;
17680 for (i=0; i < count; i++)
17682 if (src[i] == '\033')
17685 // if we hit the width, back up
17686 if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
17688 // store i & len in case the word is too long
17689 old_i = i, old_len = len;
17691 // find the end of the last word
17692 while (i && src[i] != ' ' && src[i] != '\n')
17698 // word too long? restore i & len before splitting it
17699 if ((old_i-i+clen) >= width)
17706 if (i && src[i-1] == ' ')
17709 if (src[i] != ' ' && src[i] != '\n')
17716 // now append the newline and continuation sequence
17721 strncpy(dest+len, cseq, cseq_len);
17729 dest[len] = src[i];
17733 if (src[i] == '\n')
17738 if (dest && appData.debugMode)
17740 fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
17741 count, width, line, len, *lp);
17742 show_bytes(debugFP, src, count);
17743 fprintf(debugFP, "\ndest: ");
17744 show_bytes(debugFP, dest, len);
17745 fprintf(debugFP, "\n");
17747 *lp = dest ? line : old_line;
17752 // [HGM] vari: routines for shelving variations
17753 Boolean modeRestore = FALSE;
17756 PushInner (int firstMove, int lastMove)
17758 int i, j, nrMoves = lastMove - firstMove;
17760 // push current tail of game on stack
17761 savedResult[storedGames] = gameInfo.result;
17762 savedDetails[storedGames] = gameInfo.resultDetails;
17763 gameInfo.resultDetails = NULL;
17764 savedFirst[storedGames] = firstMove;
17765 savedLast [storedGames] = lastMove;
17766 savedFramePtr[storedGames] = framePtr;
17767 framePtr -= nrMoves; // reserve space for the boards
17768 for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
17769 CopyBoard(boards[framePtr+i], boards[firstMove+i]);
17770 for(j=0; j<MOVE_LEN; j++)
17771 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
17772 for(j=0; j<2*MOVE_LEN; j++)
17773 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
17774 timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
17775 timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
17776 pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
17777 pvInfoList[firstMove+i-1].depth = 0;
17778 commentList[framePtr+i] = commentList[firstMove+i];
17779 commentList[firstMove+i] = NULL;
17783 forwardMostMove = firstMove; // truncate game so we can start variation
17787 PushTail (int firstMove, int lastMove)
17789 if(appData.icsActive) { // only in local mode
17790 forwardMostMove = currentMove; // mimic old ICS behavior
17793 if(storedGames >= MAX_VARIATIONS-2) return; // leave one for PV-walk
17795 PushInner(firstMove, lastMove);
17796 if(storedGames == 1) GreyRevert(FALSE);
17797 if(gameMode == PlayFromGameFile) gameMode = EditGame, modeRestore = TRUE;
17801 PopInner (Boolean annotate)
17804 char buf[8000], moveBuf[20];
17806 ToNrEvent(savedFirst[storedGames-1]); // sets currentMove
17807 storedGames--; // do this after ToNrEvent, to make sure HistorySet will refresh entire game after PopInner returns
17808 nrMoves = savedLast[storedGames] - currentMove;
17811 if(!WhiteOnMove(currentMove))
17812 snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
17813 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
17814 for(i=currentMove; i<forwardMostMove; i++) {
17816 snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
17817 else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
17818 strcat(buf, moveBuf);
17819 if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
17820 if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
17824 for(i=1; i<=nrMoves; i++) { // copy last variation back
17825 CopyBoard(boards[currentMove+i], boards[framePtr+i]);
17826 for(j=0; j<MOVE_LEN; j++)
17827 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
17828 for(j=0; j<2*MOVE_LEN; j++)
17829 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
17830 timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
17831 timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
17832 pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
17833 if(commentList[currentMove+i]) free(commentList[currentMove+i]);
17834 commentList[currentMove+i] = commentList[framePtr+i];
17835 commentList[framePtr+i] = NULL;
17837 if(annotate) AppendComment(currentMove+1, buf, FALSE);
17838 framePtr = savedFramePtr[storedGames];
17839 gameInfo.result = savedResult[storedGames];
17840 if(gameInfo.resultDetails != NULL) {
17841 free(gameInfo.resultDetails);
17843 gameInfo.resultDetails = savedDetails[storedGames];
17844 forwardMostMove = currentMove + nrMoves;
17848 PopTail (Boolean annotate)
17850 if(appData.icsActive) return FALSE; // only in local mode
17851 if(!storedGames) return FALSE; // sanity
17852 CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
17854 PopInner(annotate);
17855 if(currentMove < forwardMostMove) ForwardEvent(); else
17856 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
17858 if(storedGames == 0) { GreyRevert(TRUE); if(modeRestore) modeRestore = FALSE, gameMode = PlayFromGameFile; }
17864 { // remove all shelved variations
17866 for(i=0; i<storedGames; i++) {
17867 if(savedDetails[i])
17868 free(savedDetails[i]);
17869 savedDetails[i] = NULL;
17871 for(i=framePtr; i<MAX_MOVES; i++) {
17872 if(commentList[i]) free(commentList[i]);
17873 commentList[i] = NULL;
17875 framePtr = MAX_MOVES-1;
17880 LoadVariation (int index, char *text)
17881 { // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
17882 char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
17883 int level = 0, move;
17885 if(gameMode != EditGame && gameMode != AnalyzeMode && gameMode != PlayFromGameFile) return;
17886 // first find outermost bracketing variation
17887 while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
17888 if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
17889 if(*p == '{') wait = '}'; else
17890 if(*p == '[') wait = ']'; else
17891 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
17892 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
17894 if(*p == wait) wait = NULLCHAR; // closing ]} found
17897 if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
17898 if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
17899 end[1] = NULLCHAR; // clip off comment beyond variation
17900 ToNrEvent(currentMove-1);
17901 PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
17902 // kludge: use ParsePV() to append variation to game
17903 move = currentMove;
17904 ParsePV(start, TRUE, TRUE);
17905 forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
17906 ClearPremoveHighlights();
17908 ToNrEvent(currentMove+1);
17914 char *p, *q, buf[MSG_SIZ];
17915 if(engineLine && engineLine[0]) { // a theme was selected from the listbox
17916 snprintf(buf, MSG_SIZ, "-theme %s", engineLine);
17917 ParseArgsFromString(buf);
17918 ActivateTheme(TRUE); // also redo colors
17922 if(*p && !strchr(p, '"')) // theme name specified and well-formed; add settings to theme list
17925 q = appData.themeNames;
17926 snprintf(buf, MSG_SIZ, "\"%s\"", nickName);
17927 if(appData.useBitmaps) {
17928 snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt true -lbtf \"%s\" -dbtf \"%s\" -lbtm %d -dbtm %d",
17929 appData.liteBackTextureFile, appData.darkBackTextureFile,
17930 appData.liteBackTextureMode,
17931 appData.darkBackTextureMode );
17933 snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt false -lsc %s -dsc %s",
17934 Col2Text(2), // lightSquareColor
17935 Col2Text(3) ); // darkSquareColor
17937 if(appData.useBorder) {
17938 snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub true -border \"%s\"",
17941 snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub false");
17943 if(appData.useFont) {
17944 snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf true -pf \"%s\" -fptc \"%s\" -fpfcw %s -fpbcb %s",
17945 appData.renderPiecesWithFont,
17946 appData.fontToPieceTable,
17947 Col2Text(9), // appData.fontBackColorWhite
17948 Col2Text(10) ); // appData.fontForeColorBlack
17950 snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf false -pid \"%s\"",
17951 appData.pieceDirectory);
17952 if(!appData.pieceDirectory[0])
17953 snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -wpc %s -bpc %s",
17954 Col2Text(0), // whitePieceColor
17955 Col2Text(1) ); // blackPieceColor
17957 snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -hsc %s -phc %s\n",
17958 Col2Text(4), // highlightSquareColor
17959 Col2Text(5) ); // premoveHighlightColor
17960 appData.themeNames = malloc(len = strlen(q) + strlen(buf) + 1);
17961 if(insert != q) insert[-1] = NULLCHAR;
17962 snprintf(appData.themeNames, len, "%s\n%s%s", q, buf, insert);
17965 ActivateTheme(FALSE);