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], buf3[MSG_SIZ], jar;
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], "."); }
952 jar = (strstr(p, ".jar") == p + strlen(p) - 4);
954 if(strchr(p, ' ') && !strchr(p, '"')) snprintf(buf2, MSG_SIZ, "\"%s\"", p), p = buf2; // quote if it contains spaces
955 snprintf(command, MSG_SIZ, "%s %s", p, params);
958 if(jar) { snprintf(buf3, MSG_SIZ, "java -jar %s", p); p = buf3; }
959 ASSIGN(appData.chessProgram[i], p);
960 appData.isUCI[i] = isUCI;
961 appData.protocolVersion[i] = v1 ? 1 : PROTOVER;
962 appData.hasOwnBookUCI[i] = hasBook;
963 if(!nickName[0]) useNick = FALSE;
964 if(useNick) ASSIGN(appData.pgnName[i], nickName);
968 q = firstChessProgramNames;
969 if(nickName[0]) snprintf(buf, MSG_SIZ, "\"%s\" -fcp ", nickName); else buf[0] = NULLCHAR;
970 quote = strchr(p, '"') ? '\'' : '"'; // use single quotes around engine command if it contains double quotes
971 snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), "%c%s%c -fd \"%s\"%s%s%s%s%s%s%s%s\n",
972 quote, p, quote, appData.directory[i],
973 useNick ? " -fn \"" : "",
974 useNick ? nickName : "",
976 v1 ? " -firstProtocolVersion 1" : "",
977 hasBook ? "" : " -fNoOwnBookUCI",
978 isUCI ? (isUCI == TRUE ? " -fUCI" : gameInfo.variant == VariantShogi ? " -fUSI" : " -fUCCI") : "",
979 storeVariant ? " -variant " : "",
980 storeVariant ? VariantName(gameInfo.variant) : "");
981 if(wbOptions && wbOptions[0]) snprintf(buf+strlen(buf)-1, MSG_SIZ-strlen(buf), " %s\n", wbOptions);
982 firstChessProgramNames = malloc(len = strlen(q) + strlen(buf) + 1);
983 if(insert != q) insert[-1] = NULLCHAR;
984 snprintf(firstChessProgramNames, len, "%s\n%s%s", q, buf, insert);
986 FloatToFront(&appData.recentEngineList, buf);
988 ReplaceEngine(cps, i);
994 int matched, min, sec;
996 * Parse timeControl resource
998 if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
999 appData.movesPerSession)) {
1001 snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
1002 DisplayFatalError(buf, 0, 2);
1006 * Parse searchTime resource
1008 if (*appData.searchTime != NULLCHAR) {
1009 matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
1011 searchTime = min * 60;
1012 } else if (matched == 2) {
1013 searchTime = min * 60 + sec;
1016 snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
1017 DisplayFatalError(buf, 0, 2);
1026 ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
1027 startVariant = StringToVariant(appData.variant); // [HGM] nicks: remember original variant
1029 GetTimeMark(&programStartTime);
1030 srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
1031 appData.seedBase = random() + (random()<<15);
1032 pauseStart = programStartTime; pauseStart.sec -= 100; // [HGM] matchpause: fake a pause that has long since ended
1034 ClearProgramStats();
1035 programStats.ok_to_send = 1;
1036 programStats.seen_stat = 0;
1039 * Initialize game list
1045 * Internet chess server status
1047 if (appData.icsActive) {
1048 appData.matchMode = FALSE;
1049 appData.matchGames = 0;
1051 appData.noChessProgram = !appData.zippyPlay;
1053 appData.zippyPlay = FALSE;
1054 appData.zippyTalk = FALSE;
1055 appData.noChessProgram = TRUE;
1057 if (*appData.icsHelper != NULLCHAR) {
1058 appData.useTelnet = TRUE;
1059 appData.telnetProgram = appData.icsHelper;
1062 appData.zippyTalk = appData.zippyPlay = FALSE;
1065 /* [AS] Initialize pv info list [HGM] and game state */
1069 for( i=0; i<=framePtr; i++ ) {
1070 pvInfoList[i].depth = -1;
1071 boards[i][EP_STATUS] = EP_NONE;
1072 for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
1078 /* [AS] Adjudication threshold */
1079 adjudicateLossThreshold = appData.adjudicateLossThreshold;
1081 InitEngine(&first, 0);
1082 InitEngine(&second, 1);
1085 pairing.which = "pairing"; // pairing engine
1086 pairing.pr = NoProc;
1088 pairing.program = appData.pairingEngine;
1089 pairing.host = "localhost";
1092 if (appData.icsActive) {
1093 appData.clockMode = TRUE; /* changes dynamically in ICS mode */
1094 } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
1095 appData.clockMode = FALSE;
1096 first.sendTime = second.sendTime = 0;
1100 /* Override some settings from environment variables, for backward
1101 compatibility. Unfortunately it's not feasible to have the env
1102 vars just set defaults, at least in xboard. Ugh.
1104 if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
1109 if (!appData.icsActive) {
1113 /* Check for variants that are supported only in ICS mode,
1114 or not at all. Some that are accepted here nevertheless
1115 have bugs; see comments below.
1117 VariantClass variant = StringToVariant(appData.variant);
1119 case VariantBughouse: /* need four players and two boards */
1120 case VariantKriegspiel: /* need to hide pieces and move details */
1121 /* case VariantFischeRandom: (Fabien: moved below) */
1122 len = snprintf(buf,MSG_SIZ, _("Variant %s supported only in ICS mode"), appData.variant);
1123 if( (len >= MSG_SIZ) && appData.debugMode )
1124 fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1126 DisplayFatalError(buf, 0, 2);
1129 case VariantUnknown:
1130 case VariantLoadable:
1140 len = snprintf(buf, MSG_SIZ, _("Unknown variant name %s"), appData.variant);
1141 if( (len >= MSG_SIZ) && appData.debugMode )
1142 fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1144 DisplayFatalError(buf, 0, 2);
1147 case VariantXiangqi: /* [HGM] repetition rules not implemented */
1148 case VariantFairy: /* [HGM] TestLegality definitely off! */
1149 case VariantGothic: /* [HGM] should work */
1150 case VariantCapablanca: /* [HGM] should work */
1151 case VariantCourier: /* [HGM] initial forced moves not implemented */
1152 case VariantShogi: /* [HGM] could still mate with pawn drop */
1153 case VariantKnightmate: /* [HGM] should work */
1154 case VariantCylinder: /* [HGM] untested */
1155 case VariantFalcon: /* [HGM] untested */
1156 case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
1157 offboard interposition not understood */
1158 case VariantNormal: /* definitely works! */
1159 case VariantWildCastle: /* pieces not automatically shuffled */
1160 case VariantNoCastle: /* pieces not automatically shuffled */
1161 case VariantFischeRandom: /* [HGM] works and shuffles pieces */
1162 case VariantLosers: /* should work except for win condition,
1163 and doesn't know captures are mandatory */
1164 case VariantSuicide: /* should work except for win condition,
1165 and doesn't know captures are mandatory */
1166 case VariantGiveaway: /* should work except for win condition,
1167 and doesn't know captures are mandatory */
1168 case VariantTwoKings: /* should work */
1169 case VariantAtomic: /* should work except for win condition */
1170 case Variant3Check: /* should work except for win condition */
1171 case VariantShatranj: /* should work except for all win conditions */
1172 case VariantMakruk: /* should work except for draw countdown */
1173 case VariantASEAN : /* should work except for draw countdown */
1174 case VariantBerolina: /* might work if TestLegality is off */
1175 case VariantCapaRandom: /* should work */
1176 case VariantJanus: /* should work */
1177 case VariantSuper: /* experimental */
1178 case VariantGreat: /* experimental, requires legality testing to be off */
1179 case VariantSChess: /* S-Chess, should work */
1180 case VariantGrand: /* should work */
1181 case VariantSpartan: /* should work */
1189 NextIntegerFromString (char ** str, long * value)
1194 while( *s == ' ' || *s == '\t' ) {
1200 if( *s >= '0' && *s <= '9' ) {
1201 while( *s >= '0' && *s <= '9' ) {
1202 *value = *value * 10 + (*s - '0');
1215 NextTimeControlFromString (char ** str, long * value)
1218 int result = NextIntegerFromString( str, &temp );
1221 *value = temp * 60; /* Minutes */
1222 if( **str == ':' ) {
1224 result = NextIntegerFromString( str, &temp );
1225 *value += temp; /* Seconds */
1233 NextSessionFromString (char ** str, int *moves, long * tc, long *inc, int *incType)
1234 { /* [HGM] routine added to read '+moves/time' for secondary time control. */
1235 int result = -1, type = 0; long temp, temp2;
1237 if(**str != ':') return -1; // old params remain in force!
1239 if(**str == '*') type = *(*str)++, temp = 0; // sandclock TC
1240 if( NextIntegerFromString( str, &temp ) ) return -1;
1241 if(type) { *moves = 0; *tc = temp * 500; *inc = temp * 1000; *incType = '*'; return 0; }
1244 /* time only: incremental or sudden-death time control */
1245 if(**str == '+') { /* increment follows; read it */
1247 if(**str == '!') type = *(*str)++; // Bronstein TC
1248 if(result = NextIntegerFromString( str, &temp2)) return -1;
1249 *inc = temp2 * 1000;
1250 if(**str == '.') { // read fraction of increment
1251 char *start = ++(*str);
1252 if(result = NextIntegerFromString( str, &temp2)) return -1;
1254 while(start++ < *str) temp2 /= 10;
1258 *moves = 0; *tc = temp * 1000; *incType = type;
1262 (*str)++; /* classical time control */
1263 result = NextIntegerFromString( str, &temp2); // NOTE: already converted to seconds by ParseTimeControl()
1275 GetTimeQuota (int movenr, int lastUsed, char *tcString)
1276 { /* [HGM] get time to add from the multi-session time-control string */
1277 int incType, moves=1; /* kludge to force reading of first session */
1278 long time, increment;
1281 if(!s || !*s) return 0; // empty TC string means we ran out of the last sudden-death version
1283 if(moves) NextSessionFromString(&s, &moves, &time, &increment, &incType);
1284 nextSession = s; suddenDeath = moves == 0 && increment == 0;
1285 if(movenr == -1) return time; /* last move before new session */
1286 if(incType == '*') increment = 0; else // for sandclock, time is added while not thinking
1287 if(incType == '!' && lastUsed < increment) increment = lastUsed;
1288 if(!moves) return increment; /* current session is incremental */
1289 if(movenr >= 0) movenr -= moves; /* we already finished this session */
1290 } while(movenr >= -1); /* try again for next session */
1292 return 0; // no new time quota on this move
1296 ParseTimeControl (char *tc, float ti, int mps)
1300 char buf[MSG_SIZ], buf2[MSG_SIZ], *mytc = tc;
1303 if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1304 if(!strchr(tc, '+') && !strchr(tc, '/') && sscanf(tc, "%d:%d", &min, &sec) >= 1)
1305 sprintf(mytc=buf2, "%d", 60*min+sec); // convert 'classical' min:sec tc string to seconds
1309 snprintf(buf, MSG_SIZ, ":%d/%s+%g", mps, mytc, ti);
1311 snprintf(buf, MSG_SIZ, ":%s+%g", mytc, ti);
1314 snprintf(buf, MSG_SIZ, ":%d/%s", mps, mytc);
1316 snprintf(buf, MSG_SIZ, ":%s", mytc);
1318 fullTimeControlString = StrSave(buf); // this should now be in PGN format
1320 if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1325 /* Parse second time control */
1328 if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1336 timeControl_2 = tc2 * 1000;
1346 timeControl = tc1 * 1000;
1349 timeIncrement = ti * 1000; /* convert to ms */
1350 movesPerSession = 0;
1353 movesPerSession = mps;
1361 if (appData.debugMode) {
1362 # ifdef __GIT_VERSION
1363 fprintf(debugFP, "Version: %s (%s)\n", programVersion, __GIT_VERSION);
1365 fprintf(debugFP, "Version: %s\n", programVersion);
1368 ASSIGN(currentDebugFile, appData.nameOfDebugFile); // [HGM] debug split: remember initial name in use
1370 set_cont_sequence(appData.wrapContSeq);
1371 if (appData.matchGames > 0) {
1372 appData.matchMode = TRUE;
1373 } else if (appData.matchMode) {
1374 appData.matchGames = 1;
1376 if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1377 appData.matchGames = appData.sameColorGames;
1378 if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1379 if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1380 if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1383 if (appData.noChessProgram || first.protocolVersion == 1) {
1386 /* kludge: allow timeout for initial "feature" commands */
1388 DisplayMessage("", _("Starting chess program"));
1389 ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1394 CalculateIndex (int index, int gameNr)
1395 { // [HGM] autoinc: absolute way to determine load index from game number (taking auto-inc and rewind into account)
1397 if(index > 0) return index; // fixed nmber
1398 if(index == 0) return 1;
1399 res = (index == -1 ? gameNr : (gameNr-1)/2 + 1); // autoinc
1400 if(appData.rewindIndex > 0) res = (res-1) % appData.rewindIndex + 1; // rewind
1405 LoadGameOrPosition (int gameNr)
1406 { // [HGM] taken out of MatchEvent and NextMatchGame (to combine it)
1407 if (*appData.loadGameFile != NULLCHAR) {
1408 if (!LoadGameFromFile(appData.loadGameFile,
1409 CalculateIndex(appData.loadGameIndex, gameNr),
1410 appData.loadGameFile, FALSE)) {
1411 DisplayFatalError(_("Bad game file"), 0, 1);
1414 } else if (*appData.loadPositionFile != NULLCHAR) {
1415 if (!LoadPositionFromFile(appData.loadPositionFile,
1416 CalculateIndex(appData.loadPositionIndex, gameNr),
1417 appData.loadPositionFile)) {
1418 DisplayFatalError(_("Bad position file"), 0, 1);
1426 ReserveGame (int gameNr, char resChar)
1428 FILE *tf = fopen(appData.tourneyFile, "r+");
1429 char *p, *q, c, buf[MSG_SIZ];
1430 if(tf == NULL) { nextGame = appData.matchGames + 1; return; } // kludge to terminate match
1431 safeStrCpy(buf, lastMsg, MSG_SIZ);
1432 DisplayMessage(_("Pick new game"), "");
1433 flock(fileno(tf), LOCK_EX); // lock the tourney file while we are messing with it
1434 ParseArgsFromFile(tf);
1435 p = q = appData.results;
1436 if(appData.debugMode) {
1437 char *r = appData.participants;
1438 fprintf(debugFP, "results = '%s'\n", p);
1439 while(*r) fprintf(debugFP, *r >= ' ' ? "%c" : "\\%03o", *r), r++;
1440 fprintf(debugFP, "\n");
1442 while(*q && *q != ' ') q++; // get first un-played game (could be beyond end!)
1444 q = malloc(strlen(p) + 2); // could be arbitrary long, but allow to extend by one!
1445 safeStrCpy(q, p, strlen(p) + 2);
1446 if(gameNr >= 0) q[gameNr] = resChar; // replace '*' with result
1447 if(appData.debugMode) fprintf(debugFP, "pick next game from '%s': %d\n", q, nextGame);
1448 if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch) { // reserve next game if tourney not yet done
1449 if(q[nextGame] == NULLCHAR) q[nextGame+1] = NULLCHAR; // append one char
1452 fseek(tf, -(strlen(p)+4), SEEK_END);
1454 if(c != '"') // depending on DOS or Unix line endings we can be one off
1455 fseek(tf, -(strlen(p)+2), SEEK_END);
1456 else fseek(tf, -(strlen(p)+3), SEEK_END);
1457 fprintf(tf, "%s\"\n", q); fclose(tf); // update, and flush by closing
1458 DisplayMessage(buf, "");
1459 free(p); appData.results = q;
1460 if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch &&
1461 (gameNr < 0 || nextGame / appData.defaultMatchGames != gameNr / appData.defaultMatchGames)) {
1462 int round = appData.defaultMatchGames * appData.tourneyType;
1463 if(gameNr < 0 || appData.tourneyType < 1 || // gauntlet engine can always stay loaded as first engine
1464 appData.tourneyType > 1 && nextGame/round != gameNr/round) // in multi-gauntlet change only after round
1465 UnloadEngine(&first); // next game belongs to other pairing;
1466 UnloadEngine(&second); // already unload the engines, so TwoMachinesEvent will load new ones.
1468 if(appData.debugMode) fprintf(debugFP, "Reserved, next=%d, nr=%d\n", nextGame, gameNr);
1472 MatchEvent (int mode)
1473 { // [HGM] moved out of InitBackend3, to make it callable when match starts through menu
1475 if(matchMode) { // already in match mode: switch it off
1477 if(!appData.tourneyFile[0]) appData.matchGames = matchGame; // kludge to let match terminate after next game.
1480 // if(gameMode != BeginningOfGame) {
1481 // DisplayError(_("You can only start a match from the initial position."), 0);
1485 if(mode == 2) appData.matchGames = appData.defaultMatchGames;
1486 /* Set up machine vs. machine match */
1488 NextTourneyGame(-1, &dummy); // sets appData.matchGames if this is tourney, to make sure ReserveGame knows it
1489 if(appData.tourneyFile[0]) {
1491 if(nextGame > appData.matchGames) {
1493 if(strchr(appData.results, '*') == NULL) {
1495 appData.tourneyCycles++;
1496 if(f = WriteTourneyFile(appData.results, NULL)) { // make a tourney file with increased number of cycles
1498 NextTourneyGame(-1, &dummy);
1500 if(nextGame <= appData.matchGames) {
1501 DisplayNote(_("You restarted an already completed tourney.\nOne more cycle will now be added to it.\nGames commence in 10 sec."));
1503 ScheduleDelayedEvent(NextMatchGame, 10000);
1508 snprintf(buf, MSG_SIZ, _("All games in tourney '%s' are already played or playing"), appData.tourneyFile);
1509 DisplayError(buf, 0);
1510 appData.tourneyFile[0] = 0;
1514 if (appData.noChessProgram) { // [HGM] in tourney engines are loaded automatically
1515 DisplayFatalError(_("Can't have a match with no chess programs"),
1520 matchGame = roundNr = 1;
1521 first.matchWins = second.matchWins = 0; // [HGM] match: needed in later matches
1525 char *comboLine = NULL; // [HGM] recent: WinBoard's first-engine combobox line
1528 InitBackEnd3 P((void))
1530 GameMode initialMode;
1534 InitChessProgram(&first, startedFromSetupPosition);
1536 if(!appData.noChessProgram) { /* [HGM] tidy: redo program version to use name from myname feature */
1537 free(programVersion);
1538 programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1539 sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1540 FloatToFront(&appData.recentEngineList, comboLine ? comboLine : appData.firstChessProgram);
1543 if (appData.icsActive) {
1545 /* [DM] Make a console window if needed [HGM] merged ifs */
1551 if (*appData.icsCommPort != NULLCHAR)
1552 len = snprintf(buf, MSG_SIZ, _("Could not open comm port %s"),
1553 appData.icsCommPort);
1555 len = snprintf(buf, MSG_SIZ, _("Could not connect to host %s, port %s"),
1556 appData.icsHost, appData.icsPort);
1558 if( (len >= MSG_SIZ) && appData.debugMode )
1559 fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1561 DisplayFatalError(buf, err, 1);
1566 AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1568 AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1569 if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1570 ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1571 } else if (appData.noChessProgram) {
1577 if (*appData.cmailGameName != NULLCHAR) {
1579 OpenLoopback(&cmailPR);
1581 AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1585 DisplayMessage("", "");
1586 if (StrCaseCmp(appData.initialMode, "") == 0) {
1587 initialMode = BeginningOfGame;
1588 if(!appData.icsActive && appData.noChessProgram) { // [HGM] could be fall-back
1589 gameMode = MachinePlaysBlack; // "Machine Black" might have been implicitly highlighted
1590 ModeHighlight(); // make sure XBoard knows it is highlighted, so it will un-highlight it
1591 gameMode = BeginningOfGame; // in case BeginningOfGame now means "Edit Position"
1594 } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1595 initialMode = TwoMachinesPlay;
1596 } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1597 initialMode = AnalyzeFile;
1598 } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1599 initialMode = AnalyzeMode;
1600 } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1601 initialMode = MachinePlaysWhite;
1602 } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1603 initialMode = MachinePlaysBlack;
1604 } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1605 initialMode = EditGame;
1606 } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1607 initialMode = EditPosition;
1608 } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1609 initialMode = Training;
1611 len = snprintf(buf, MSG_SIZ, _("Unknown initialMode %s"), appData.initialMode);
1612 if( (len >= MSG_SIZ) && appData.debugMode )
1613 fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1615 DisplayFatalError(buf, 0, 2);
1619 if (appData.matchMode) {
1620 if(appData.tourneyFile[0]) { // start tourney from command line
1622 if(f = fopen(appData.tourneyFile, "r")) {
1623 ParseArgsFromFile(f); // make sure tourney parmeters re known
1625 appData.clockMode = TRUE;
1627 } else appData.tourneyFile[0] = NULLCHAR; // for now ignore bad tourney file
1630 } else if (*appData.cmailGameName != NULLCHAR) {
1631 /* Set up cmail mode */
1632 ReloadCmailMsgEvent(TRUE);
1634 /* Set up other modes */
1635 if (initialMode == AnalyzeFile) {
1636 if (*appData.loadGameFile == NULLCHAR) {
1637 DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1641 if (*appData.loadGameFile != NULLCHAR) {
1642 (void) LoadGameFromFile(appData.loadGameFile,
1643 appData.loadGameIndex,
1644 appData.loadGameFile, TRUE);
1645 } else if (*appData.loadPositionFile != NULLCHAR) {
1646 (void) LoadPositionFromFile(appData.loadPositionFile,
1647 appData.loadPositionIndex,
1648 appData.loadPositionFile);
1649 /* [HGM] try to make self-starting even after FEN load */
1650 /* to allow automatic setup of fairy variants with wtm */
1651 if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1652 gameMode = BeginningOfGame;
1653 setboardSpoiledMachineBlack = 1;
1655 /* [HGM] loadPos: make that every new game uses the setup */
1656 /* from file as long as we do not switch variant */
1657 if(!blackPlaysFirst) {
1658 startedFromPositionFile = TRUE;
1659 CopyBoard(filePosition, boards[0]);
1662 if (initialMode == AnalyzeMode) {
1663 if (appData.noChessProgram) {
1664 DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1667 if (appData.icsActive) {
1668 DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1672 } else if (initialMode == AnalyzeFile) {
1673 appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1674 ShowThinkingEvent();
1676 AnalysisPeriodicEvent(1);
1677 } else if (initialMode == MachinePlaysWhite) {
1678 if (appData.noChessProgram) {
1679 DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1683 if (appData.icsActive) {
1684 DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1688 MachineWhiteEvent();
1689 } else if (initialMode == MachinePlaysBlack) {
1690 if (appData.noChessProgram) {
1691 DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1695 if (appData.icsActive) {
1696 DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1700 MachineBlackEvent();
1701 } else if (initialMode == TwoMachinesPlay) {
1702 if (appData.noChessProgram) {
1703 DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1707 if (appData.icsActive) {
1708 DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1713 } else if (initialMode == EditGame) {
1715 } else if (initialMode == EditPosition) {
1716 EditPositionEvent();
1717 } else if (initialMode == Training) {
1718 if (*appData.loadGameFile == NULLCHAR) {
1719 DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1728 HistorySet (char movelist[][2*MOVE_LEN], int first, int last, int current)
1730 DisplayBook(current+1);
1732 MoveHistorySet( movelist, first, last, current, pvInfoList );
1734 EvalGraphSet( first, last, current, pvInfoList );
1736 MakeEngineOutputTitle();
1740 * Establish will establish a contact to a remote host.port.
1741 * Sets icsPR to a ProcRef for a process (or pseudo-process)
1742 * used to talk to the host.
1743 * Returns 0 if okay, error code if not.
1750 if (*appData.icsCommPort != NULLCHAR) {
1751 /* Talk to the host through a serial comm port */
1752 return OpenCommPort(appData.icsCommPort, &icsPR);
1754 } else if (*appData.gateway != NULLCHAR) {
1755 if (*appData.remoteShell == NULLCHAR) {
1756 /* Use the rcmd protocol to run telnet program on a gateway host */
1757 snprintf(buf, sizeof(buf), "%s %s %s",
1758 appData.telnetProgram, appData.icsHost, appData.icsPort);
1759 return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1762 /* Use the rsh program to run telnet program on a gateway host */
1763 if (*appData.remoteUser == NULLCHAR) {
1764 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1765 appData.gateway, appData.telnetProgram,
1766 appData.icsHost, appData.icsPort);
1768 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1769 appData.remoteShell, appData.gateway,
1770 appData.remoteUser, appData.telnetProgram,
1771 appData.icsHost, appData.icsPort);
1773 return StartChildProcess(buf, "", &icsPR);
1776 } else if (appData.useTelnet) {
1777 return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1780 /* TCP socket interface differs somewhat between
1781 Unix and NT; handle details in the front end.
1783 return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1788 EscapeExpand (char *p, char *q)
1789 { // [HGM] initstring: routine to shape up string arguments
1790 while(*p++ = *q++) if(p[-1] == '\\')
1792 case 'n': p[-1] = '\n'; break;
1793 case 'r': p[-1] = '\r'; break;
1794 case 't': p[-1] = '\t'; break;
1795 case '\\': p[-1] = '\\'; break;
1796 case 0: *p = 0; return;
1797 default: p[-1] = q[-1]; break;
1802 show_bytes (FILE *fp, char *buf, int count)
1805 if (*buf < 040 || *(unsigned char *) buf > 0177) {
1806 fprintf(fp, "\\%03o", *buf & 0xff);
1815 /* Returns an errno value */
1817 OutputMaybeTelnet (ProcRef pr, char *message, int count, int *outError)
1819 char buf[8192], *p, *q, *buflim;
1820 int left, newcount, outcount;
1822 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1823 *appData.gateway != NULLCHAR) {
1824 if (appData.debugMode) {
1825 fprintf(debugFP, ">ICS: ");
1826 show_bytes(debugFP, message, count);
1827 fprintf(debugFP, "\n");
1829 return OutputToProcess(pr, message, count, outError);
1832 buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1839 if (appData.debugMode) {
1840 fprintf(debugFP, ">ICS: ");
1841 show_bytes(debugFP, buf, newcount);
1842 fprintf(debugFP, "\n");
1844 outcount = OutputToProcess(pr, buf, newcount, outError);
1845 if (outcount < newcount) return -1; /* to be sure */
1852 } else if (((unsigned char) *p) == TN_IAC) {
1853 *q++ = (char) TN_IAC;
1860 if (appData.debugMode) {
1861 fprintf(debugFP, ">ICS: ");
1862 show_bytes(debugFP, buf, newcount);
1863 fprintf(debugFP, "\n");
1865 outcount = OutputToProcess(pr, buf, newcount, outError);
1866 if (outcount < newcount) return -1; /* to be sure */
1871 read_from_player (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
1873 int outError, outCount;
1874 static int gotEof = 0;
1877 /* Pass data read from player on to ICS */
1880 outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1881 if (outCount < count) {
1882 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1884 if(have_sent_ICS_logon == 2) {
1885 if(ini = fopen(appData.icsLogon, "w")) { // save first two lines (presumably username & password) on init script file
1886 fprintf(ini, "%s", message);
1887 have_sent_ICS_logon = 3;
1889 have_sent_ICS_logon = 1;
1890 } else if(have_sent_ICS_logon == 3) {
1891 fprintf(ini, "%s", message);
1893 have_sent_ICS_logon = 1;
1895 } else if (count < 0) {
1896 RemoveInputSource(isr);
1897 DisplayFatalError(_("Error reading from keyboard"), error, 1);
1898 } else if (gotEof++ > 0) {
1899 RemoveInputSource(isr);
1900 DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1906 { // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1907 if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1908 connectionAlive = FALSE; // only sticks if no response to 'date' command.
1909 SendToICS("date\n");
1910 if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1913 /* added routine for printf style output to ics */
1915 ics_printf (char *format, ...)
1917 char buffer[MSG_SIZ];
1920 va_start(args, format);
1921 vsnprintf(buffer, sizeof(buffer), format, args);
1922 buffer[sizeof(buffer)-1] = '\0';
1930 int count, outCount, outError;
1932 if (icsPR == NoProc) return;
1935 outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1936 if (outCount < count) {
1937 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1941 /* This is used for sending logon scripts to the ICS. Sending
1942 without a delay causes problems when using timestamp on ICC
1943 (at least on my machine). */
1945 SendToICSDelayed (char *s, long msdelay)
1947 int count, outCount, outError;
1949 if (icsPR == NoProc) return;
1952 if (appData.debugMode) {
1953 fprintf(debugFP, ">ICS: ");
1954 show_bytes(debugFP, s, count);
1955 fprintf(debugFP, "\n");
1957 outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1959 if (outCount < count) {
1960 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1965 /* Remove all highlighting escape sequences in s
1966 Also deletes any suffix starting with '('
1969 StripHighlightAndTitle (char *s)
1971 static char retbuf[MSG_SIZ];
1974 while (*s != NULLCHAR) {
1975 while (*s == '\033') {
1976 while (*s != NULLCHAR && !isalpha(*s)) s++;
1977 if (*s != NULLCHAR) s++;
1979 while (*s != NULLCHAR && *s != '\033') {
1980 if (*s == '(' || *s == '[') {
1991 /* Remove all highlighting escape sequences in s */
1993 StripHighlight (char *s)
1995 static char retbuf[MSG_SIZ];
1998 while (*s != NULLCHAR) {
1999 while (*s == '\033') {
2000 while (*s != NULLCHAR && !isalpha(*s)) s++;
2001 if (*s != NULLCHAR) s++;
2003 while (*s != NULLCHAR && *s != '\033') {
2011 char engineVariant[MSG_SIZ];
2012 char *variantNames[] = VARIANT_NAMES;
2014 VariantName (VariantClass v)
2016 if(v == VariantUnknown || *engineVariant) return engineVariant;
2017 return variantNames[v];
2021 /* Identify a variant from the strings the chess servers use or the
2022 PGN Variant tag names we use. */
2024 StringToVariant (char *e)
2028 VariantClass v = VariantNormal;
2029 int i, found = FALSE;
2035 /* [HGM] skip over optional board-size prefixes */
2036 if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
2037 sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
2038 while( *e++ != '_');
2041 if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
2045 for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
2046 if (StrCaseStr(e, variantNames[i])) {
2047 v = (VariantClass) i;
2054 if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
2055 || StrCaseStr(e, "wild/fr")
2056 || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
2057 v = VariantFischeRandom;
2058 } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
2059 (i = 1, p = StrCaseStr(e, "w"))) {
2061 while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
2068 case 0: /* FICS only, actually */
2070 /* Castling legal even if K starts on d-file */
2071 v = VariantWildCastle;
2076 /* Castling illegal even if K & R happen to start in
2077 normal positions. */
2078 v = VariantNoCastle;
2091 /* Castling legal iff K & R start in normal positions */
2097 /* Special wilds for position setup; unclear what to do here */
2098 v = VariantLoadable;
2101 /* Bizarre ICC game */
2102 v = VariantTwoKings;
2105 v = VariantKriegspiel;
2111 v = VariantFischeRandom;
2114 v = VariantCrazyhouse;
2117 v = VariantBughouse;
2123 /* Not quite the same as FICS suicide! */
2124 v = VariantGiveaway;
2130 v = VariantShatranj;
2133 /* Temporary names for future ICC types. The name *will* change in
2134 the next xboard/WinBoard release after ICC defines it. */
2172 v = VariantCapablanca;
2175 v = VariantKnightmate;
2181 v = VariantCylinder;
2187 v = VariantCapaRandom;
2190 v = VariantBerolina;
2202 /* Found "wild" or "w" in the string but no number;
2203 must assume it's normal chess. */
2207 len = snprintf(buf, MSG_SIZ, _("Unknown wild type %d"), wnum);
2208 if( (len >= MSG_SIZ) && appData.debugMode )
2209 fprintf(debugFP, "StringToVariant: buffer truncated.\n");
2211 DisplayError(buf, 0);
2217 if (appData.debugMode) {
2218 fprintf(debugFP, "recognized '%s' (%d) as variant %s\n",
2219 e, wnum, VariantName(v));
2224 static int leftover_start = 0, leftover_len = 0;
2225 char star_match[STAR_MATCH_N][MSG_SIZ];
2227 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
2228 advance *index beyond it, and set leftover_start to the new value of
2229 *index; else return FALSE. If pattern contains the character '*', it
2230 matches any sequence of characters not containing '\r', '\n', or the
2231 character following the '*' (if any), and the matched sequence(s) are
2232 copied into star_match.
2235 looking_at ( char *buf, int *index, char *pattern)
2237 char *bufp = &buf[*index], *patternp = pattern;
2239 char *matchp = star_match[0];
2242 if (*patternp == NULLCHAR) {
2243 *index = leftover_start = bufp - buf;
2247 if (*bufp == NULLCHAR) return FALSE;
2248 if (*patternp == '*') {
2249 if (*bufp == *(patternp + 1)) {
2251 matchp = star_match[++star_count];
2255 } else if (*bufp == '\n' || *bufp == '\r') {
2257 if (*patternp == NULLCHAR)
2262 *matchp++ = *bufp++;
2266 if (*patternp != *bufp) return FALSE;
2273 SendToPlayer (char *data, int length)
2275 int error, outCount;
2276 outCount = OutputToProcess(NoProc, data, length, &error);
2277 if (outCount < length) {
2278 DisplayFatalError(_("Error writing to display"), error, 1);
2283 PackHolding (char packed[], char *holding)
2293 switch (runlength) {
2304 sprintf(q, "%d", runlength);
2316 /* Telnet protocol requests from the front end */
2318 TelnetRequest (unsigned char ddww, unsigned char option)
2320 unsigned char msg[3];
2321 int outCount, outError;
2323 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
2325 if (appData.debugMode) {
2326 char buf1[8], buf2[8], *ddwwStr, *optionStr;
2342 snprintf(buf1,sizeof(buf1)/sizeof(buf1[0]), "%d", ddww);
2351 snprintf(buf2,sizeof(buf2)/sizeof(buf2[0]), "%d", option);
2354 fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
2359 outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
2361 DisplayFatalError(_("Error writing to ICS"), outError, 1);
2368 if (!appData.icsActive) return;
2369 TelnetRequest(TN_DO, TN_ECHO);
2375 if (!appData.icsActive) return;
2376 TelnetRequest(TN_DONT, TN_ECHO);
2380 CopyHoldings (Board board, char *holdings, ChessSquare lowestPiece)
2382 /* put the holdings sent to us by the server on the board holdings area */
2383 int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2387 if(gameInfo.holdingsWidth < 2) return;
2388 if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2389 return; // prevent overwriting by pre-board holdings
2391 if( (int)lowestPiece >= BlackPawn ) {
2394 holdingsStartRow = BOARD_HEIGHT-1;
2397 holdingsColumn = BOARD_WIDTH-1;
2398 countsColumn = BOARD_WIDTH-2;
2399 holdingsStartRow = 0;
2403 for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
2404 board[i][holdingsColumn] = EmptySquare;
2405 board[i][countsColumn] = (ChessSquare) 0;
2407 while( (p=*holdings++) != NULLCHAR ) {
2408 piece = CharToPiece( ToUpper(p) );
2409 if(piece == EmptySquare) continue;
2410 /*j = (int) piece - (int) WhitePawn;*/
2411 j = PieceToNumber(piece);
2412 if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2413 if(j < 0) continue; /* should not happen */
2414 piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2415 board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2416 board[holdingsStartRow+j*direction][countsColumn]++;
2422 VariantSwitch (Board board, VariantClass newVariant)
2424 int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2425 static Board oldBoard;
2427 startedFromPositionFile = FALSE;
2428 if(gameInfo.variant == newVariant) return;
2430 /* [HGM] This routine is called each time an assignment is made to
2431 * gameInfo.variant during a game, to make sure the board sizes
2432 * are set to match the new variant. If that means adding or deleting
2433 * holdings, we shift the playing board accordingly
2434 * This kludge is needed because in ICS observe mode, we get boards
2435 * of an ongoing game without knowing the variant, and learn about the
2436 * latter only later. This can be because of the move list we requested,
2437 * in which case the game history is refilled from the beginning anyway,
2438 * but also when receiving holdings of a crazyhouse game. In the latter
2439 * case we want to add those holdings to the already received position.
2443 if (appData.debugMode) {
2444 fprintf(debugFP, "Switch board from %s to %s\n",
2445 VariantName(gameInfo.variant), VariantName(newVariant));
2446 setbuf(debugFP, NULL);
2448 shuffleOpenings = 0; /* [HGM] shuffle */
2449 gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2453 newWidth = 9; newHeight = 9;
2454 gameInfo.holdingsSize = 7;
2455 case VariantBughouse:
2456 case VariantCrazyhouse:
2457 newHoldingsWidth = 2; break;
2461 newHoldingsWidth = 2;
2462 gameInfo.holdingsSize = 8;
2465 case VariantCapablanca:
2466 case VariantCapaRandom:
2469 newHoldingsWidth = gameInfo.holdingsSize = 0;
2472 if(newWidth != gameInfo.boardWidth ||
2473 newHeight != gameInfo.boardHeight ||
2474 newHoldingsWidth != gameInfo.holdingsWidth ) {
2476 /* shift position to new playing area, if needed */
2477 if(newHoldingsWidth > gameInfo.holdingsWidth) {
2478 for(i=0; i<BOARD_HEIGHT; i++)
2479 for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2480 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2482 for(i=0; i<newHeight; i++) {
2483 board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2484 board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2486 } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2487 for(i=0; i<BOARD_HEIGHT; i++)
2488 for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2489 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2492 board[HOLDINGS_SET] = 0;
2493 gameInfo.boardWidth = newWidth;
2494 gameInfo.boardHeight = newHeight;
2495 gameInfo.holdingsWidth = newHoldingsWidth;
2496 gameInfo.variant = newVariant;
2497 InitDrawingSizes(-2, 0);
2498 } else gameInfo.variant = newVariant;
2499 CopyBoard(oldBoard, board); // remember correctly formatted board
2500 InitPosition(FALSE); /* this sets up board[0], but also other stuff */
2501 DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2504 static int loggedOn = FALSE;
2506 /*-- Game start info cache: --*/
2508 char gs_kind[MSG_SIZ];
2509 static char player1Name[128] = "";
2510 static char player2Name[128] = "";
2511 static char cont_seq[] = "\n\\ ";
2512 static int player1Rating = -1;
2513 static int player2Rating = -1;
2514 /*----------------------------*/
2516 ColorClass curColor = ColorNormal;
2517 int suppressKibitz = 0;
2520 Boolean soughtPending = FALSE;
2521 Boolean seekGraphUp;
2522 #define MAX_SEEK_ADS 200
2524 char *seekAdList[MAX_SEEK_ADS];
2525 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2526 float tcList[MAX_SEEK_ADS];
2527 char colorList[MAX_SEEK_ADS];
2528 int nrOfSeekAds = 0;
2529 int minRating = 1010, maxRating = 2800;
2530 int hMargin = 10, vMargin = 20, h, w;
2531 extern int squareSize, lineGap;
2536 int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2537 xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2538 if(r < minRating+100 && r >=0 ) r = minRating+100;
2539 if(r > maxRating) r = maxRating;
2540 if(tc < 1.f) tc = 1.f;
2541 if(tc > 95.f) tc = 95.f;
2542 x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2543 y = ((double)r - minRating)/(maxRating - minRating)
2544 * (h-vMargin-squareSize/8-1) + vMargin;
2545 if(ratingList[i] < 0) y = vMargin + squareSize/4;
2546 if(strstr(seekAdList[i], " u ")) color = 1;
2547 if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2548 !strstr(seekAdList[i], "bullet") &&
2549 !strstr(seekAdList[i], "blitz") &&
2550 !strstr(seekAdList[i], "standard") ) color = 2;
2551 if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2552 DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2556 PlotSingleSeekAd (int i)
2562 AddAd (char *handle, char *rating, int base, int inc, char rated, char *type, int nr, Boolean plot)
2564 char buf[MSG_SIZ], *ext = "";
2565 VariantClass v = StringToVariant(type);
2566 if(strstr(type, "wild")) {
2567 ext = type + 4; // append wild number
2568 if(v == VariantFischeRandom) type = "chess960"; else
2569 if(v == VariantLoadable) type = "setup"; else
2570 type = VariantName(v);
2572 snprintf(buf, MSG_SIZ, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2573 if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2574 if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2575 ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2576 sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2577 tcList[nrOfSeekAds] = base + (2./3.)*inc;
2578 seekNrList[nrOfSeekAds] = nr;
2579 zList[nrOfSeekAds] = 0;
2580 seekAdList[nrOfSeekAds++] = StrSave(buf);
2581 if(plot) PlotSingleSeekAd(nrOfSeekAds-1);
2586 EraseSeekDot (int i)
2588 int x = xList[i], y = yList[i], d=squareSize/4, k;
2589 DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2590 if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2591 // now replot every dot that overlapped
2592 for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2593 int xx = xList[k], yy = yList[k];
2594 if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2595 DrawSeekDot(xx, yy, colorList[k]);
2600 RemoveSeekAd (int nr)
2603 for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2605 if(seekAdList[i]) free(seekAdList[i]);
2606 seekAdList[i] = seekAdList[--nrOfSeekAds];
2607 seekNrList[i] = seekNrList[nrOfSeekAds];
2608 ratingList[i] = ratingList[nrOfSeekAds];
2609 colorList[i] = colorList[nrOfSeekAds];
2610 tcList[i] = tcList[nrOfSeekAds];
2611 xList[i] = xList[nrOfSeekAds];
2612 yList[i] = yList[nrOfSeekAds];
2613 zList[i] = zList[nrOfSeekAds];
2614 seekAdList[nrOfSeekAds] = NULL;
2620 MatchSoughtLine (char *line)
2622 char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2623 int nr, base, inc, u=0; char dummy;
2625 if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2626 sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2628 (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2629 sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7) ) {
2630 // match: compact and save the line
2631 AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2641 if(!seekGraphUp) return FALSE;
2642 h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
2643 w = BOARD_WIDTH * (squareSize + lineGap) + lineGap;
2645 DrawSeekBackground(0, 0, w, h);
2646 DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2647 DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2648 for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2649 int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2651 DrawSeekAxis(hMargin-5, yy, hMargin+5*(i%500==0), yy); // rating ticks
2654 snprintf(buf, MSG_SIZ, "%d", i);
2655 DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2658 DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2659 for(i=1; i<100; i+=(i<10?1:5)) {
2660 int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2661 DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2662 if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2664 snprintf(buf, MSG_SIZ, "%d", i);
2665 DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2668 for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2673 SeekGraphClick (ClickType click, int x, int y, int moving)
2675 static int lastDown = 0, displayed = 0, lastSecond;
2676 if(y < 0) return FALSE;
2677 if(!(appData.seekGraph && appData.icsActive && loggedOn &&
2678 (gameMode == BeginningOfGame || gameMode == IcsIdle))) {
2679 if(!seekGraphUp) return FALSE;
2680 seekGraphUp = FALSE; // seek graph is up when it shouldn't be: take it down
2681 DrawPosition(TRUE, NULL);
2684 if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2685 if(click == Release || moving) return FALSE;
2687 soughtPending = TRUE;
2688 SendToICS(ics_prefix);
2689 SendToICS("sought\n"); // should this be "sought all"?
2690 } else { // issue challenge based on clicked ad
2691 int dist = 10000; int i, closest = 0, second = 0;
2692 for(i=0; i<nrOfSeekAds; i++) {
2693 int d = (x-xList[i])*(x-xList[i]) + (y-yList[i])*(y-yList[i]) + zList[i];
2694 if(d < dist) { dist = d; closest = i; }
2695 second += (d - zList[i] < 120); // count in-range ads
2696 if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2700 second = (second > 1);
2701 if(displayed != closest || second != lastSecond) {
2702 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2703 lastSecond = second; displayed = closest;
2705 if(click == Press) {
2706 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2709 } // on press 'hit', only show info
2710 if(moving == 2) return TRUE; // ignore right up-clicks on dot
2711 snprintf(buf, MSG_SIZ, "play %d\n", seekNrList[closest]);
2712 SendToICS(ics_prefix);
2714 return TRUE; // let incoming board of started game pop down the graph
2715 } else if(click == Release) { // release 'miss' is ignored
2716 zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2717 if(moving == 2) { // right up-click
2718 nrOfSeekAds = 0; // refresh graph
2719 soughtPending = TRUE;
2720 SendToICS(ics_prefix);
2721 SendToICS("sought\n"); // should this be "sought all"?
2724 } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2725 // press miss or release hit 'pop down' seek graph
2726 seekGraphUp = FALSE;
2727 DrawPosition(TRUE, NULL);
2733 read_from_ics (InputSourceRef isr, VOIDSTAR closure, char *data, int count, int error)
2735 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2736 #define STARTED_NONE 0
2737 #define STARTED_MOVES 1
2738 #define STARTED_BOARD 2
2739 #define STARTED_OBSERVE 3
2740 #define STARTED_HOLDINGS 4
2741 #define STARTED_CHATTER 5
2742 #define STARTED_COMMENT 6
2743 #define STARTED_MOVES_NOHIDE 7
2745 static int started = STARTED_NONE;
2746 static char parse[20000];
2747 static int parse_pos = 0;
2748 static char buf[BUF_SIZE + 1];
2749 static int firstTime = TRUE, intfSet = FALSE;
2750 static ColorClass prevColor = ColorNormal;
2751 static int savingComment = FALSE;
2752 static int cmatch = 0; // continuation sequence match
2759 int backup; /* [DM] For zippy color lines */
2761 char talker[MSG_SIZ]; // [HGM] chat
2764 connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2766 if (appData.debugMode) {
2768 fprintf(debugFP, "<ICS: ");
2769 show_bytes(debugFP, data, count);
2770 fprintf(debugFP, "\n");
2774 if (appData.debugMode) { int f = forwardMostMove;
2775 fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2776 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2777 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2780 /* If last read ended with a partial line that we couldn't parse,
2781 prepend it to the new read and try again. */
2782 if (leftover_len > 0) {
2783 for (i=0; i<leftover_len; i++)
2784 buf[i] = buf[leftover_start + i];
2787 /* copy new characters into the buffer */
2788 bp = buf + leftover_len;
2789 buf_len=leftover_len;
2790 for (i=0; i<count; i++)
2793 if (data[i] == '\r')
2796 // join lines split by ICS?
2797 if (!appData.noJoin)
2800 Joining just consists of finding matches against the
2801 continuation sequence, and discarding that sequence
2802 if found instead of copying it. So, until a match
2803 fails, there's nothing to do since it might be the
2804 complete sequence, and thus, something we don't want
2807 if (data[i] == cont_seq[cmatch])
2810 if (cmatch == strlen(cont_seq))
2812 cmatch = 0; // complete match. just reset the counter
2815 it's possible for the ICS to not include the space
2816 at the end of the last word, making our [correct]
2817 join operation fuse two separate words. the server
2818 does this when the space occurs at the width setting.
2820 if (!buf_len || buf[buf_len-1] != ' ')
2831 match failed, so we have to copy what matched before
2832 falling through and copying this character. In reality,
2833 this will only ever be just the newline character, but
2834 it doesn't hurt to be precise.
2836 strncpy(bp, cont_seq, cmatch);
2848 buf[buf_len] = NULLCHAR;
2849 // next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2854 while (i < buf_len) {
2855 /* Deal with part of the TELNET option negotiation
2856 protocol. We refuse to do anything beyond the
2857 defaults, except that we allow the WILL ECHO option,
2858 which ICS uses to turn off password echoing when we are
2859 directly connected to it. We reject this option
2860 if localLineEditing mode is on (always on in xboard)
2861 and we are talking to port 23, which might be a real
2862 telnet server that will try to keep WILL ECHO on permanently.
2864 if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2865 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2866 unsigned char option;
2868 switch ((unsigned char) buf[++i]) {
2870 if (appData.debugMode)
2871 fprintf(debugFP, "\n<WILL ");
2872 switch (option = (unsigned char) buf[++i]) {
2874 if (appData.debugMode)
2875 fprintf(debugFP, "ECHO ");
2876 /* Reply only if this is a change, according
2877 to the protocol rules. */
2878 if (remoteEchoOption) break;
2879 if (appData.localLineEditing &&
2880 atoi(appData.icsPort) == TN_PORT) {
2881 TelnetRequest(TN_DONT, TN_ECHO);
2884 TelnetRequest(TN_DO, TN_ECHO);
2885 remoteEchoOption = TRUE;
2889 if (appData.debugMode)
2890 fprintf(debugFP, "%d ", option);
2891 /* Whatever this is, we don't want it. */
2892 TelnetRequest(TN_DONT, option);
2897 if (appData.debugMode)
2898 fprintf(debugFP, "\n<WONT ");
2899 switch (option = (unsigned char) buf[++i]) {
2901 if (appData.debugMode)
2902 fprintf(debugFP, "ECHO ");
2903 /* Reply only if this is a change, according
2904 to the protocol rules. */
2905 if (!remoteEchoOption) break;
2907 TelnetRequest(TN_DONT, TN_ECHO);
2908 remoteEchoOption = FALSE;
2911 if (appData.debugMode)
2912 fprintf(debugFP, "%d ", (unsigned char) option);
2913 /* Whatever this is, it must already be turned
2914 off, because we never agree to turn on
2915 anything non-default, so according to the
2916 protocol rules, we don't reply. */
2921 if (appData.debugMode)
2922 fprintf(debugFP, "\n<DO ");
2923 switch (option = (unsigned char) buf[++i]) {
2925 /* Whatever this is, we refuse to do it. */
2926 if (appData.debugMode)
2927 fprintf(debugFP, "%d ", option);
2928 TelnetRequest(TN_WONT, option);
2933 if (appData.debugMode)
2934 fprintf(debugFP, "\n<DONT ");
2935 switch (option = (unsigned char) buf[++i]) {
2937 if (appData.debugMode)
2938 fprintf(debugFP, "%d ", option);
2939 /* Whatever this is, we are already not doing
2940 it, because we never agree to do anything
2941 non-default, so according to the protocol
2942 rules, we don't reply. */
2947 if (appData.debugMode)
2948 fprintf(debugFP, "\n<IAC ");
2949 /* Doubled IAC; pass it through */
2953 if (appData.debugMode)
2954 fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2955 /* Drop all other telnet commands on the floor */
2958 if (oldi > next_out)
2959 SendToPlayer(&buf[next_out], oldi - next_out);
2965 /* OK, this at least will *usually* work */
2966 if (!loggedOn && looking_at(buf, &i, "ics%")) {
2970 if (loggedOn && !intfSet) {
2971 if (ics_type == ICS_ICC) {
2972 snprintf(str, MSG_SIZ,
2973 "/set-quietly interface %s\n/set-quietly style 12\n",
2975 if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2976 strcat(str, "/set-2 51 1\n/set seek 1\n");
2977 } else if (ics_type == ICS_CHESSNET) {
2978 snprintf(str, MSG_SIZ, "/style 12\n");
2980 safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
2981 strcat(str, programVersion);
2982 strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2983 if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2984 strcat(str, "$iset seekremove 1\n$set seek 1\n");
2986 strcat(str, "$iset nohighlight 1\n");
2988 strcat(str, "$iset lock 1\n$style 12\n");
2991 NotifyFrontendLogin();
2995 if (started == STARTED_COMMENT) {
2996 /* Accumulate characters in comment */
2997 parse[parse_pos++] = buf[i];
2998 if (buf[i] == '\n') {
2999 parse[parse_pos] = NULLCHAR;
3000 if(chattingPartner>=0) {
3002 snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
3003 OutputChatMessage(chattingPartner, mess);
3004 chattingPartner = -1;
3005 next_out = i+1; // [HGM] suppress printing in ICS window
3007 if(!suppressKibitz) // [HGM] kibitz
3008 AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
3009 else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
3010 int nrDigit = 0, nrAlph = 0, j;
3011 if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
3012 { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
3013 parse[parse_pos] = NULLCHAR;
3014 // try to be smart: if it does not look like search info, it should go to
3015 // ICS interaction window after all, not to engine-output window.
3016 for(j=0; j<parse_pos; j++) { // count letters and digits
3017 nrDigit += (parse[j] >= '0' && parse[j] <= '9');
3018 nrAlph += (parse[j] >= 'a' && parse[j] <= 'z');
3019 nrAlph += (parse[j] >= 'A' && parse[j] <= 'Z');
3021 if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
3022 int depth=0; float score;
3023 if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
3024 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
3025 pvInfoList[forwardMostMove-1].depth = depth;
3026 pvInfoList[forwardMostMove-1].score = 100*score;
3028 OutputKibitz(suppressKibitz, parse);
3031 if(gameMode == IcsObserving) // restore original ICS messages
3032 /* TRANSLATORS: to 'kibitz' is to send a message to all players and the game observers */
3033 snprintf(tmp, MSG_SIZ, "%s kibitzes: %s", star_match[0], parse);
3035 /* TRANSLATORS: to 'kibitz' is to send a message to all players and the game observers */
3036 snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
3037 SendToPlayer(tmp, strlen(tmp));
3039 next_out = i+1; // [HGM] suppress printing in ICS window
3041 started = STARTED_NONE;
3043 /* Don't match patterns against characters in comment */
3048 if (started == STARTED_CHATTER) {
3049 if (buf[i] != '\n') {
3050 /* Don't match patterns against characters in chatter */
3054 started = STARTED_NONE;
3055 if(suppressKibitz) next_out = i+1;
3058 /* Kludge to deal with rcmd protocol */
3059 if (firstTime && looking_at(buf, &i, "\001*")) {
3060 DisplayFatalError(&buf[1], 0, 1);
3066 if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
3069 if (appData.debugMode)
3070 fprintf(debugFP, "ics_type %d\n", ics_type);
3073 if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
3074 ics_type = ICS_FICS;
3076 if (appData.debugMode)
3077 fprintf(debugFP, "ics_type %d\n", ics_type);
3080 if (!loggedOn && looking_at(buf, &i, "chess.net")) {
3081 ics_type = ICS_CHESSNET;
3083 if (appData.debugMode)
3084 fprintf(debugFP, "ics_type %d\n", ics_type);
3089 (looking_at(buf, &i, "\"*\" is *a registered name") ||
3090 looking_at(buf, &i, "Logging you in as \"*\"") ||
3091 looking_at(buf, &i, "will be \"*\""))) {
3092 safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
3096 if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
3098 snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
3099 DisplayIcsInteractionTitle(buf);
3100 have_set_title = TRUE;
3103 /* skip finger notes */
3104 if (started == STARTED_NONE &&
3105 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
3106 (buf[i] == '1' && buf[i+1] == '0')) &&
3107 buf[i+2] == ':' && buf[i+3] == ' ') {
3108 started = STARTED_CHATTER;
3114 // [HGM] seekgraph: recognize sought lines and end-of-sought message
3115 if(appData.seekGraph) {
3116 if(soughtPending && MatchSoughtLine(buf+i)) {
3117 i = strstr(buf+i, "rated") - buf;
3118 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3119 next_out = leftover_start = i;
3120 started = STARTED_CHATTER;
3121 suppressKibitz = TRUE;
3124 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
3125 && looking_at(buf, &i, "* ads displayed")) {
3126 soughtPending = FALSE;
3131 if(appData.autoRefresh) {
3132 if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
3133 int s = (ics_type == ICS_ICC); // ICC format differs
3135 AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
3136 star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
3137 looking_at(buf, &i, "*% "); // eat prompt
3138 if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
3139 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3140 next_out = i; // suppress
3143 if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
3144 char *p = star_match[0];
3146 if(seekGraphUp) RemoveSeekAd(atoi(p));
3147 while(*p && *p++ != ' '); // next
3149 looking_at(buf, &i, "*% "); // eat prompt
3150 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3157 /* skip formula vars */
3158 if (started == STARTED_NONE &&
3159 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
3160 started = STARTED_CHATTER;
3165 // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
3166 if (appData.autoKibitz && started == STARTED_NONE &&
3167 !appData.icsEngineAnalyze && // [HGM] [DM] ICS analyze
3168 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
3169 if((looking_at(buf, &i, "\n* kibitzes: ") || looking_at(buf, &i, "\n* whispers: ") ||
3170 looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
3171 (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
3172 StrStr(star_match[0], gameInfo.black) == star_match[0] )) { // kibitz of self or opponent
3173 suppressKibitz = TRUE;
3174 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3176 if((StrStr(star_match[0], gameInfo.white) == star_match[0]
3177 && (gameMode == IcsPlayingWhite)) ||
3178 (StrStr(star_match[0], gameInfo.black) == star_match[0]
3179 && (gameMode == IcsPlayingBlack)) ) // opponent kibitz
3180 started = STARTED_CHATTER; // own kibitz we simply discard
3182 started = STARTED_COMMENT; // make sure it will be collected in parse[]
3183 parse_pos = 0; parse[0] = NULLCHAR;
3184 savingComment = TRUE;
3185 suppressKibitz = gameMode != IcsObserving ? 2 :
3186 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
3190 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
3191 looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
3192 && atoi(star_match[0])) {
3193 // suppress the acknowledgements of our own autoKibitz
3195 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3196 if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
3197 SendToPlayer(star_match[0], strlen(star_match[0]));
3198 if(looking_at(buf, &i, "*% ")) // eat prompt
3199 suppressKibitz = FALSE;
3203 } // [HGM] kibitz: end of patch
3205 if(looking_at(buf, &i, "* rating adjustment: * --> *\n")) continue;
3207 // [HGM] chat: intercept tells by users for which we have an open chat window
3209 if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
3210 looking_at(buf, &i, "* whispers:") ||
3211 looking_at(buf, &i, "* kibitzes:") ||
3212 looking_at(buf, &i, "* shouts:") ||
3213 looking_at(buf, &i, "* c-shouts:") ||
3214 looking_at(buf, &i, "--> * ") ||
3215 looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
3216 looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
3217 looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
3218 looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
3220 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
3221 chattingPartner = -1;
3223 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
3224 for(p=0; p<MAX_CHAT; p++) {
3225 if(chatPartner[p][0] >= '0' && chatPartner[p][0] <= '9' && channel == atoi(chatPartner[p])) {
3226 talker[0] = '['; strcat(talker, "] ");
3227 Colorize(channel == 1 ? ColorChannel1 : ColorChannel, FALSE);
3228 chattingPartner = p; break;
3231 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
3232 for(p=0; p<MAX_CHAT; p++) {
3233 if(!strcmp("kibitzes", chatPartner[p])) {
3234 talker[0] = '['; strcat(talker, "] ");
3235 chattingPartner = p; break;
3238 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
3239 for(p=0; p<MAX_CHAT; p++) {
3240 if(!strcmp("whispers", chatPartner[p])) {
3241 talker[0] = '['; strcat(talker, "] ");
3242 chattingPartner = p; break;
3245 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
3246 if(buf[i-8] == '-' && buf[i-3] == 't')
3247 for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
3248 if(!strcmp("c-shouts", chatPartner[p])) {
3249 talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
3250 chattingPartner = p; break;
3253 if(chattingPartner < 0)
3254 for(p=0; p<MAX_CHAT; p++) {
3255 if(!strcmp("shouts", chatPartner[p])) {
3256 if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
3257 else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
3258 else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
3259 chattingPartner = p; break;
3263 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
3264 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3265 talker[0] = 0; Colorize(ColorTell, FALSE);
3266 chattingPartner = p; break;
3268 if(chattingPartner<0) i = oldi; else {
3269 Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
3270 if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
3271 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3272 started = STARTED_COMMENT;
3273 parse_pos = 0; parse[0] = NULLCHAR;
3274 savingComment = 3 + chattingPartner; // counts as TRUE
3275 suppressKibitz = TRUE;
3278 } // [HGM] chat: end of patch
3281 if (appData.zippyTalk || appData.zippyPlay) {
3282 /* [DM] Backup address for color zippy lines */
3284 if (loggedOn == TRUE)
3285 if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
3286 (appData.zippyPlay && ZippyMatch(buf, &backup)));
3288 } // [DM] 'else { ' deleted
3290 /* Regular tells and says */
3291 (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
3292 looking_at(buf, &i, "* (your partner) tells you: ") ||
3293 looking_at(buf, &i, "* says: ") ||
3294 /* Don't color "message" or "messages" output */
3295 (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
3296 looking_at(buf, &i, "*. * at *:*: ") ||
3297 looking_at(buf, &i, "--* (*:*): ") ||
3298 /* Message notifications (same color as tells) */
3299 looking_at(buf, &i, "* has left a message ") ||
3300 looking_at(buf, &i, "* just sent you a message:\n") ||
3301 /* Whispers and kibitzes */
3302 (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
3303 looking_at(buf, &i, "* kibitzes: ") ||
3305 (tkind = 3, looking_at(buf, &i, "*(*: "))) {
3307 if (tkind == 1 && strchr(star_match[0], ':')) {
3308 /* Avoid "tells you:" spoofs in channels */
3311 if (star_match[0][0] == NULLCHAR ||
3312 strchr(star_match[0], ' ') ||
3313 (tkind == 3 && strchr(star_match[1], ' '))) {
3314 /* Reject bogus matches */
3317 if (appData.colorize) {
3318 if (oldi > next_out) {
3319 SendToPlayer(&buf[next_out], oldi - next_out);
3324 Colorize(ColorTell, FALSE);
3325 curColor = ColorTell;
3328 Colorize(ColorKibitz, FALSE);
3329 curColor = ColorKibitz;
3332 p = strrchr(star_match[1], '(');
3339 Colorize(ColorChannel1, FALSE);
3340 curColor = ColorChannel1;
3342 Colorize(ColorChannel, FALSE);
3343 curColor = ColorChannel;
3347 curColor = ColorNormal;
3351 if (started == STARTED_NONE && appData.autoComment &&
3352 (gameMode == IcsObserving ||
3353 gameMode == IcsPlayingWhite ||
3354 gameMode == IcsPlayingBlack)) {
3355 parse_pos = i - oldi;
3356 memcpy(parse, &buf[oldi], parse_pos);
3357 parse[parse_pos] = NULLCHAR;
3358 started = STARTED_COMMENT;
3359 savingComment = TRUE;
3361 started = STARTED_CHATTER;
3362 savingComment = FALSE;
3369 if (looking_at(buf, &i, "* s-shouts: ") ||
3370 looking_at(buf, &i, "* c-shouts: ")) {
3371 if (appData.colorize) {
3372 if (oldi > next_out) {
3373 SendToPlayer(&buf[next_out], oldi - next_out);
3376 Colorize(ColorSShout, FALSE);
3377 curColor = ColorSShout;
3380 started = STARTED_CHATTER;
3384 if (looking_at(buf, &i, "--->")) {
3389 if (looking_at(buf, &i, "* shouts: ") ||
3390 looking_at(buf, &i, "--> ")) {
3391 if (appData.colorize) {
3392 if (oldi > next_out) {
3393 SendToPlayer(&buf[next_out], oldi - next_out);
3396 Colorize(ColorShout, FALSE);
3397 curColor = ColorShout;
3400 started = STARTED_CHATTER;
3404 if (looking_at( buf, &i, "Challenge:")) {
3405 if (appData.colorize) {
3406 if (oldi > next_out) {
3407 SendToPlayer(&buf[next_out], oldi - next_out);
3410 Colorize(ColorChallenge, FALSE);
3411 curColor = ColorChallenge;
3417 if (looking_at(buf, &i, "* offers you") ||
3418 looking_at(buf, &i, "* offers to be") ||
3419 looking_at(buf, &i, "* would like to") ||
3420 looking_at(buf, &i, "* requests to") ||
3421 looking_at(buf, &i, "Your opponent offers") ||
3422 looking_at(buf, &i, "Your opponent requests")) {
3424 if (appData.colorize) {
3425 if (oldi > next_out) {
3426 SendToPlayer(&buf[next_out], oldi - next_out);
3429 Colorize(ColorRequest, FALSE);
3430 curColor = ColorRequest;
3435 if (looking_at(buf, &i, "* (*) seeking")) {
3436 if (appData.colorize) {
3437 if (oldi > next_out) {
3438 SendToPlayer(&buf[next_out], oldi - next_out);
3441 Colorize(ColorSeek, FALSE);
3442 curColor = ColorSeek;
3447 if(i < backup) { i = backup; continue; } // [HGM] for if ZippyControl matches, but the colorie code doesn't
3449 if (looking_at(buf, &i, "\\ ")) {
3450 if (prevColor != ColorNormal) {
3451 if (oldi > next_out) {
3452 SendToPlayer(&buf[next_out], oldi - next_out);
3455 Colorize(prevColor, TRUE);
3456 curColor = prevColor;
3458 if (savingComment) {
3459 parse_pos = i - oldi;
3460 memcpy(parse, &buf[oldi], parse_pos);
3461 parse[parse_pos] = NULLCHAR;
3462 started = STARTED_COMMENT;
3463 if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3464 chattingPartner = savingComment - 3; // kludge to remember the box
3466 started = STARTED_CHATTER;
3471 if (looking_at(buf, &i, "Black Strength :") ||
3472 looking_at(buf, &i, "<<< style 10 board >>>") ||
3473 looking_at(buf, &i, "<10>") ||
3474 looking_at(buf, &i, "#@#")) {
3475 /* Wrong board style */
3477 SendToICS(ics_prefix);
3478 SendToICS("set style 12\n");
3479 SendToICS(ics_prefix);
3480 SendToICS("refresh\n");
3484 if (looking_at(buf, &i, "login:")) {
3485 if (!have_sent_ICS_logon) {
3487 have_sent_ICS_logon = 1;
3488 else // no init script was found
3489 have_sent_ICS_logon = (appData.autoCreateLogon ? 2 : 1); // flag that we should capture username + password
3490 } else { // we have sent (or created) the InitScript, but apparently the ICS rejected it
3491 have_sent_ICS_logon = (appData.autoCreateLogon ? 2 : 1); // request creation of a new script
3496 if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3497 (looking_at(buf, &i, "\n<12> ") ||
3498 looking_at(buf, &i, "<12> "))) {
3500 if (oldi > next_out) {
3501 SendToPlayer(&buf[next_out], oldi - next_out);
3504 started = STARTED_BOARD;
3509 if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3510 looking_at(buf, &i, "<b1> ")) {
3511 if (oldi > next_out) {
3512 SendToPlayer(&buf[next_out], oldi - next_out);
3515 started = STARTED_HOLDINGS;
3520 if (looking_at(buf, &i, "* *vs. * *--- *")) {
3522 /* Header for a move list -- first line */
3524 switch (ics_getting_history) {
3528 case BeginningOfGame:
3529 /* User typed "moves" or "oldmoves" while we
3530 were idle. Pretend we asked for these
3531 moves and soak them up so user can step
3532 through them and/or save them.
3535 gameMode = IcsObserving;
3538 ics_getting_history = H_GOT_UNREQ_HEADER;
3540 case EditGame: /*?*/
3541 case EditPosition: /*?*/
3542 /* Should above feature work in these modes too? */
3543 /* For now it doesn't */
3544 ics_getting_history = H_GOT_UNWANTED_HEADER;
3547 ics_getting_history = H_GOT_UNWANTED_HEADER;
3552 /* Is this the right one? */
3553 if (gameInfo.white && gameInfo.black &&
3554 strcmp(gameInfo.white, star_match[0]) == 0 &&
3555 strcmp(gameInfo.black, star_match[2]) == 0) {
3557 ics_getting_history = H_GOT_REQ_HEADER;
3560 case H_GOT_REQ_HEADER:
3561 case H_GOT_UNREQ_HEADER:
3562 case H_GOT_UNWANTED_HEADER:
3563 case H_GETTING_MOVES:
3564 /* Should not happen */
3565 DisplayError(_("Error gathering move list: two headers"), 0);
3566 ics_getting_history = H_FALSE;
3570 /* Save player ratings into gameInfo if needed */
3571 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3572 ics_getting_history == H_GOT_UNREQ_HEADER) &&
3573 (gameInfo.whiteRating == -1 ||
3574 gameInfo.blackRating == -1)) {
3576 gameInfo.whiteRating = string_to_rating(star_match[1]);
3577 gameInfo.blackRating = string_to_rating(star_match[3]);
3578 if (appData.debugMode)
3579 fprintf(debugFP, "Ratings from header: W %d, B %d\n",
3580 gameInfo.whiteRating, gameInfo.blackRating);
3585 if (looking_at(buf, &i,
3586 "* * match, initial time: * minute*, increment: * second")) {
3587 /* Header for a move list -- second line */
3588 /* Initial board will follow if this is a wild game */
3589 if (gameInfo.event != NULL) free(gameInfo.event);
3590 snprintf(str, MSG_SIZ, "ICS %s %s match", star_match[0], star_match[1]);
3591 gameInfo.event = StrSave(str);
3592 /* [HGM] we switched variant. Translate boards if needed. */
3593 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3597 if (looking_at(buf, &i, "Move ")) {
3598 /* Beginning of a move list */
3599 switch (ics_getting_history) {
3601 /* Normally should not happen */
3602 /* Maybe user hit reset while we were parsing */
3605 /* Happens if we are ignoring a move list that is not
3606 * the one we just requested. Common if the user
3607 * tries to observe two games without turning off
3610 case H_GETTING_MOVES:
3611 /* Should not happen */
3612 DisplayError(_("Error gathering move list: nested"), 0);
3613 ics_getting_history = H_FALSE;
3615 case H_GOT_REQ_HEADER:
3616 ics_getting_history = H_GETTING_MOVES;
3617 started = STARTED_MOVES;
3619 if (oldi > next_out) {
3620 SendToPlayer(&buf[next_out], oldi - next_out);
3623 case H_GOT_UNREQ_HEADER:
3624 ics_getting_history = H_GETTING_MOVES;
3625 started = STARTED_MOVES_NOHIDE;
3628 case H_GOT_UNWANTED_HEADER:
3629 ics_getting_history = H_FALSE;
3635 if (looking_at(buf, &i, "% ") ||
3636 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3637 && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3638 if(soughtPending && nrOfSeekAds) { // [HGM] seekgraph: on ICC sought-list has no termination line
3639 soughtPending = FALSE;
3643 if(suppressKibitz) next_out = i;
3644 savingComment = FALSE;
3648 case STARTED_MOVES_NOHIDE:
3649 memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3650 parse[parse_pos + i - oldi] = NULLCHAR;
3651 ParseGameHistory(parse);
3653 if (appData.zippyPlay && first.initDone) {
3654 FeedMovesToProgram(&first, forwardMostMove);
3655 if (gameMode == IcsPlayingWhite) {
3656 if (WhiteOnMove(forwardMostMove)) {
3657 if (first.sendTime) {
3658 if (first.useColors) {
3659 SendToProgram("black\n", &first);
3661 SendTimeRemaining(&first, TRUE);
3663 if (first.useColors) {
3664 SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3666 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3667 first.maybeThinking = TRUE;
3669 if (first.usePlayother) {
3670 if (first.sendTime) {
3671 SendTimeRemaining(&first, TRUE);
3673 SendToProgram("playother\n", &first);
3679 } else if (gameMode == IcsPlayingBlack) {
3680 if (!WhiteOnMove(forwardMostMove)) {
3681 if (first.sendTime) {
3682 if (first.useColors) {
3683 SendToProgram("white\n", &first);
3685 SendTimeRemaining(&first, FALSE);
3687 if (first.useColors) {
3688 SendToProgram("black\n", &first);
3690 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3691 first.maybeThinking = TRUE;
3693 if (first.usePlayother) {
3694 if (first.sendTime) {
3695 SendTimeRemaining(&first, FALSE);
3697 SendToProgram("playother\n", &first);
3706 if (gameMode == IcsObserving && ics_gamenum == -1) {
3707 /* Moves came from oldmoves or moves command
3708 while we weren't doing anything else.
3710 currentMove = forwardMostMove;
3711 ClearHighlights();/*!!could figure this out*/
3712 flipView = appData.flipView;
3713 DrawPosition(TRUE, boards[currentMove]);
3714 DisplayBothClocks();
3715 snprintf(str, MSG_SIZ, "%s %s %s",
3716 gameInfo.white, _("vs."), gameInfo.black);
3720 /* Moves were history of an active game */
3721 if (gameInfo.resultDetails != NULL) {
3722 free(gameInfo.resultDetails);
3723 gameInfo.resultDetails = NULL;
3726 HistorySet(parseList, backwardMostMove,
3727 forwardMostMove, currentMove-1);
3728 DisplayMove(currentMove - 1);
3729 if (started == STARTED_MOVES) next_out = i;
3730 started = STARTED_NONE;
3731 ics_getting_history = H_FALSE;
3734 case STARTED_OBSERVE:
3735 started = STARTED_NONE;
3736 SendToICS(ics_prefix);
3737 SendToICS("refresh\n");
3743 if(bookHit) { // [HGM] book: simulate book reply
3744 static char bookMove[MSG_SIZ]; // a bit generous?
3746 programStats.nodes = programStats.depth = programStats.time =
3747 programStats.score = programStats.got_only_move = 0;
3748 sprintf(programStats.movelist, "%s (xbook)", bookHit);
3750 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
3751 strcat(bookMove, bookHit);
3752 HandleMachineMove(bookMove, &first);
3757 if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3758 started == STARTED_HOLDINGS ||
3759 started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3760 /* Accumulate characters in move list or board */
3761 parse[parse_pos++] = buf[i];
3764 /* Start of game messages. Mostly we detect start of game
3765 when the first board image arrives. On some versions
3766 of the ICS, though, we need to do a "refresh" after starting
3767 to observe in order to get the current board right away. */
3768 if (looking_at(buf, &i, "Adding game * to observation list")) {
3769 started = STARTED_OBSERVE;
3773 /* Handle auto-observe */
3774 if (appData.autoObserve &&
3775 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3776 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3778 /* Choose the player that was highlighted, if any. */
3779 if (star_match[0][0] == '\033' ||
3780 star_match[1][0] != '\033') {
3781 player = star_match[0];
3783 player = star_match[2];
3785 snprintf(str, MSG_SIZ, "%sobserve %s\n",
3786 ics_prefix, StripHighlightAndTitle(player));
3789 /* Save ratings from notify string */
3790 safeStrCpy(player1Name, star_match[0], sizeof(player1Name)/sizeof(player1Name[0]));
3791 player1Rating = string_to_rating(star_match[1]);
3792 safeStrCpy(player2Name, star_match[2], sizeof(player2Name)/sizeof(player2Name[0]));
3793 player2Rating = string_to_rating(star_match[3]);
3795 if (appData.debugMode)
3797 "Ratings from 'Game notification:' %s %d, %s %d\n",
3798 player1Name, player1Rating,
3799 player2Name, player2Rating);
3804 /* Deal with automatic examine mode after a game,
3805 and with IcsObserving -> IcsExamining transition */
3806 if (looking_at(buf, &i, "Entering examine mode for game *") ||
3807 looking_at(buf, &i, "has made you an examiner of game *")) {
3809 int gamenum = atoi(star_match[0]);
3810 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3811 gamenum == ics_gamenum) {
3812 /* We were already playing or observing this game;
3813 no need to refetch history */
3814 gameMode = IcsExamining;
3816 pauseExamForwardMostMove = forwardMostMove;
3817 } else if (currentMove < forwardMostMove) {
3818 ForwardInner(forwardMostMove);
3821 /* I don't think this case really can happen */
3822 SendToICS(ics_prefix);
3823 SendToICS("refresh\n");
3828 /* Error messages */
3829 // if (ics_user_moved) {
3830 if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3831 if (looking_at(buf, &i, "Illegal move") ||
3832 looking_at(buf, &i, "Not a legal move") ||
3833 looking_at(buf, &i, "Your king is in check") ||
3834 looking_at(buf, &i, "It isn't your turn") ||
3835 looking_at(buf, &i, "It is not your move")) {
3837 if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3838 currentMove = forwardMostMove-1;
3839 DisplayMove(currentMove - 1); /* before DMError */
3840 DrawPosition(FALSE, boards[currentMove]);
3841 SwitchClocks(forwardMostMove-1); // [HGM] race
3842 DisplayBothClocks();
3844 DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3850 if (looking_at(buf, &i, "still have time") ||
3851 looking_at(buf, &i, "not out of time") ||
3852 looking_at(buf, &i, "either player is out of time") ||
3853 looking_at(buf, &i, "has timeseal; checking")) {
3854 /* We must have called his flag a little too soon */
3855 whiteFlag = blackFlag = FALSE;
3859 if (looking_at(buf, &i, "added * seconds to") ||
3860 looking_at(buf, &i, "seconds were added to")) {
3861 /* Update the clocks */
3862 SendToICS(ics_prefix);
3863 SendToICS("refresh\n");
3867 if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3868 ics_clock_paused = TRUE;
3873 if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3874 ics_clock_paused = FALSE;
3879 /* Grab player ratings from the Creating: message.
3880 Note we have to check for the special case when
3881 the ICS inserts things like [white] or [black]. */
3882 if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3883 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3885 0 player 1 name (not necessarily white)
3887 2 empty, white, or black (IGNORED)
3888 3 player 2 name (not necessarily black)
3891 The names/ratings are sorted out when the game
3892 actually starts (below).
3894 safeStrCpy(player1Name, StripHighlightAndTitle(star_match[0]), sizeof(player1Name)/sizeof(player1Name[0]));
3895 player1Rating = string_to_rating(star_match[1]);
3896 safeStrCpy(player2Name, StripHighlightAndTitle(star_match[3]), sizeof(player2Name)/sizeof(player2Name[0]));
3897 player2Rating = string_to_rating(star_match[4]);
3899 if (appData.debugMode)
3901 "Ratings from 'Creating:' %s %d, %s %d\n",
3902 player1Name, player1Rating,
3903 player2Name, player2Rating);
3908 /* Improved generic start/end-of-game messages */
3909 if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3910 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3911 /* If tkind == 0: */
3912 /* star_match[0] is the game number */
3913 /* [1] is the white player's name */
3914 /* [2] is the black player's name */
3915 /* For end-of-game: */
3916 /* [3] is the reason for the game end */
3917 /* [4] is a PGN end game-token, preceded by " " */
3918 /* For start-of-game: */
3919 /* [3] begins with "Creating" or "Continuing" */
3920 /* [4] is " *" or empty (don't care). */
3921 int gamenum = atoi(star_match[0]);
3922 char *whitename, *blackname, *why, *endtoken;
3923 ChessMove endtype = EndOfFile;
3926 whitename = star_match[1];
3927 blackname = star_match[2];
3928 why = star_match[3];
3929 endtoken = star_match[4];
3931 whitename = star_match[1];
3932 blackname = star_match[3];
3933 why = star_match[5];
3934 endtoken = star_match[6];
3937 /* Game start messages */
3938 if (strncmp(why, "Creating ", 9) == 0 ||
3939 strncmp(why, "Continuing ", 11) == 0) {
3940 gs_gamenum = gamenum;
3941 safeStrCpy(gs_kind, strchr(why, ' ') + 1,sizeof(gs_kind)/sizeof(gs_kind[0]));
3942 if(ics_gamenum == -1) // [HGM] only if we are not already involved in a game (because gin=1 sends us such messages)
3943 VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
3945 if (appData.zippyPlay) {
3946 ZippyGameStart(whitename, blackname);
3949 partnerBoardValid = FALSE; // [HGM] bughouse
3953 /* Game end messages */
3954 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3955 ics_gamenum != gamenum) {
3958 while (endtoken[0] == ' ') endtoken++;
3959 switch (endtoken[0]) {
3962 endtype = GameUnfinished;
3965 endtype = BlackWins;
3968 if (endtoken[1] == '/')
3969 endtype = GameIsDrawn;
3971 endtype = WhiteWins;
3974 GameEnds(endtype, why, GE_ICS);
3976 if (appData.zippyPlay && first.initDone) {
3977 ZippyGameEnd(endtype, why);
3978 if (first.pr == NoProc) {
3979 /* Start the next process early so that we'll
3980 be ready for the next challenge */
3981 StartChessProgram(&first);
3983 /* Send "new" early, in case this command takes
3984 a long time to finish, so that we'll be ready
3985 for the next challenge. */
3986 gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3990 if(appData.bgObserve && partnerBoardValid) DrawPosition(TRUE, partnerBoard);
3994 if (looking_at(buf, &i, "Removing game * from observation") ||
3995 looking_at(buf, &i, "no longer observing game *") ||
3996 looking_at(buf, &i, "Game * (*) has no examiners")) {
3997 if (gameMode == IcsObserving &&
3998 atoi(star_match[0]) == ics_gamenum)
4000 /* icsEngineAnalyze */
4001 if (appData.icsEngineAnalyze) {
4008 ics_user_moved = FALSE;
4013 if (looking_at(buf, &i, "no longer examining game *")) {
4014 if (gameMode == IcsExamining &&
4015 atoi(star_match[0]) == ics_gamenum)
4019 ics_user_moved = FALSE;
4024 /* Advance leftover_start past any newlines we find,
4025 so only partial lines can get reparsed */
4026 if (looking_at(buf, &i, "\n")) {
4027 prevColor = curColor;
4028 if (curColor != ColorNormal) {
4029 if (oldi > next_out) {
4030 SendToPlayer(&buf[next_out], oldi - next_out);
4033 Colorize(ColorNormal, FALSE);
4034 curColor = ColorNormal;
4036 if (started == STARTED_BOARD) {
4037 started = STARTED_NONE;
4038 parse[parse_pos] = NULLCHAR;
4039 ParseBoard12(parse);
4042 /* Send premove here */
4043 if (appData.premove) {
4045 if (currentMove == 0 &&
4046 gameMode == IcsPlayingWhite &&
4047 appData.premoveWhite) {
4048 snprintf(str, MSG_SIZ, "%s\n", appData.premoveWhiteText);
4049 if (appData.debugMode)
4050 fprintf(debugFP, "Sending premove:\n");
4052 } else if (currentMove == 1 &&
4053 gameMode == IcsPlayingBlack &&
4054 appData.premoveBlack) {
4055 snprintf(str, MSG_SIZ, "%s\n", appData.premoveBlackText);
4056 if (appData.debugMode)
4057 fprintf(debugFP, "Sending premove:\n");
4059 } else if (gotPremove) {
4061 ClearPremoveHighlights();
4062 if (appData.debugMode)
4063 fprintf(debugFP, "Sending premove:\n");
4064 UserMoveEvent(premoveFromX, premoveFromY,
4065 premoveToX, premoveToY,
4070 /* Usually suppress following prompt */
4071 if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
4072 while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
4073 if (looking_at(buf, &i, "*% ")) {
4074 savingComment = FALSE;
4079 } else if (started == STARTED_HOLDINGS) {
4081 char new_piece[MSG_SIZ];
4082 started = STARTED_NONE;
4083 parse[parse_pos] = NULLCHAR;
4084 if (appData.debugMode)
4085 fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
4086 parse, currentMove);
4087 if (sscanf(parse, " game %d", &gamenum) == 1) {
4088 if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game
4089 if (gameInfo.variant == VariantNormal) {
4090 /* [HGM] We seem to switch variant during a game!
4091 * Presumably no holdings were displayed, so we have
4092 * to move the position two files to the right to
4093 * create room for them!
4095 VariantClass newVariant;
4096 switch(gameInfo.boardWidth) { // base guess on board width
4097 case 9: newVariant = VariantShogi; break;
4098 case 10: newVariant = VariantGreat; break;
4099 default: newVariant = VariantCrazyhouse; break;
4101 VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4102 /* Get a move list just to see the header, which
4103 will tell us whether this is really bug or zh */
4104 if (ics_getting_history == H_FALSE) {
4105 ics_getting_history = H_REQUESTED;
4106 snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4110 new_piece[0] = NULLCHAR;
4111 sscanf(parse, "game %d white [%s black [%s <- %s",
4112 &gamenum, white_holding, black_holding,
4114 white_holding[strlen(white_holding)-1] = NULLCHAR;
4115 black_holding[strlen(black_holding)-1] = NULLCHAR;
4116 /* [HGM] copy holdings to board holdings area */
4117 CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
4118 CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
4119 boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
4121 if (appData.zippyPlay && first.initDone) {
4122 ZippyHoldings(white_holding, black_holding,
4126 if (tinyLayout || smallLayout) {
4127 char wh[16], bh[16];
4128 PackHolding(wh, white_holding);
4129 PackHolding(bh, black_holding);
4130 snprintf(str, MSG_SIZ, "[%s-%s] %s-%s", wh, bh,
4131 gameInfo.white, gameInfo.black);
4133 snprintf(str, MSG_SIZ, "%s [%s] %s %s [%s]",
4134 gameInfo.white, white_holding, _("vs."),
4135 gameInfo.black, black_holding);
4137 if(!partnerUp) // [HGM] bughouse: when peeking at partner game we already know what he captured...
4138 DrawPosition(FALSE, boards[currentMove]);
4140 } else if(appData.bgObserve) { // [HGM] bughouse: holdings of other game => background
4141 sscanf(parse, "game %d white [%s black [%s <- %s",
4142 &gamenum, white_holding, black_holding,
4144 white_holding[strlen(white_holding)-1] = NULLCHAR;
4145 black_holding[strlen(black_holding)-1] = NULLCHAR;
4146 /* [HGM] copy holdings to partner-board holdings area */
4147 CopyHoldings(partnerBoard, white_holding, WhitePawn);
4148 CopyHoldings(partnerBoard, black_holding, BlackPawn);
4149 if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual: always draw
4150 if(partnerUp) DrawPosition(FALSE, partnerBoard);
4151 if(twoBoards) { partnerUp = 0; flipView = !flipView; }
4154 /* Suppress following prompt */
4155 if (looking_at(buf, &i, "*% ")) {
4156 if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
4157 savingComment = FALSE;
4165 i++; /* skip unparsed character and loop back */
4168 if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
4169 // started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
4170 // SendToPlayer(&buf[next_out], i - next_out);
4171 started != STARTED_HOLDINGS && leftover_start > next_out) {
4172 SendToPlayer(&buf[next_out], leftover_start - next_out);
4176 leftover_len = buf_len - leftover_start;
4177 /* if buffer ends with something we couldn't parse,
4178 reparse it after appending the next read */
4180 } else if (count == 0) {
4181 RemoveInputSource(isr);
4182 DisplayFatalError(_("Connection closed by ICS"), 0, 0);
4184 DisplayFatalError(_("Error reading from ICS"), error, 1);
4189 /* Board style 12 looks like this:
4191 <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
4193 * The "<12> " is stripped before it gets to this routine. The two
4194 * trailing 0's (flip state and clock ticking) are later addition, and
4195 * some chess servers may not have them, or may have only the first.
4196 * Additional trailing fields may be added in the future.
4199 #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"
4201 #define RELATION_OBSERVING_PLAYED 0
4202 #define RELATION_OBSERVING_STATIC -2 /* examined, oldmoves, or smoves */
4203 #define RELATION_PLAYING_MYMOVE 1
4204 #define RELATION_PLAYING_NOTMYMOVE -1
4205 #define RELATION_EXAMINING 2
4206 #define RELATION_ISOLATED_BOARD -3
4207 #define RELATION_STARTING_POSITION -4 /* FICS only */
4210 ParseBoard12 (char *string)
4214 char *bookHit = NULL; // [HGM] book
4216 GameMode newGameMode;
4217 int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0;
4218 int j, k, n, moveNum, white_stren, black_stren, white_time, black_time;
4219 int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
4220 char to_play, board_chars[200];
4221 char move_str[MSG_SIZ], str[MSG_SIZ], elapsed_time[MSG_SIZ];
4222 char black[32], white[32];
4224 int prevMove = currentMove;
4227 int fromX, fromY, toX, toY;
4229 int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
4230 Boolean weird = FALSE, reqFlag = FALSE;
4232 fromX = fromY = toX = toY = -1;
4236 if (appData.debugMode)
4237 fprintf(debugFP, "Parsing board: %s\n", string);
4239 move_str[0] = NULLCHAR;
4240 elapsed_time[0] = NULLCHAR;
4241 { /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
4243 while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
4244 if(string[i] == ' ') { ranks++; files = 0; }
4246 if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
4249 for(j = 0; j <i; j++) board_chars[j] = string[j];
4250 board_chars[i] = '\0';
4253 n = sscanf(string, PATTERN, &to_play, &double_push,
4254 &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
4255 &gamenum, white, black, &relation, &basetime, &increment,
4256 &white_stren, &black_stren, &white_time, &black_time,
4257 &moveNum, str, elapsed_time, move_str, &ics_flip,
4261 snprintf(str, MSG_SIZ, _("Failed to parse board string:\n\"%s\""), string);
4262 DisplayError(str, 0);
4266 /* Convert the move number to internal form */
4267 moveNum = (moveNum - 1) * 2;
4268 if (to_play == 'B') moveNum++;
4269 if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
4270 DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
4276 case RELATION_OBSERVING_PLAYED:
4277 case RELATION_OBSERVING_STATIC:
4278 if (gamenum == -1) {
4279 /* Old ICC buglet */
4280 relation = RELATION_OBSERVING_STATIC;
4282 newGameMode = IcsObserving;
4284 case RELATION_PLAYING_MYMOVE:
4285 case RELATION_PLAYING_NOTMYMOVE:
4287 ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
4288 IcsPlayingWhite : IcsPlayingBlack;
4289 soughtPending =FALSE; // [HGM] seekgraph: solve race condition
4291 case RELATION_EXAMINING:
4292 newGameMode = IcsExamining;
4294 case RELATION_ISOLATED_BOARD:
4296 /* Just display this board. If user was doing something else,
4297 we will forget about it until the next board comes. */
4298 newGameMode = IcsIdle;
4300 case RELATION_STARTING_POSITION:
4301 newGameMode = gameMode;
4305 if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
4306 gameMode == IcsObserving && appData.dualBoard) // also allow use of second board for observing two games
4307 && newGameMode == IcsObserving && gamenum != ics_gamenum && appData.bgObserve) {
4308 // [HGM] bughouse: don't act on alien boards while we play. Just parse the board and save it */
4309 int fac = strchr(elapsed_time, '.') ? 1 : 1000;
4310 static int lastBgGame = -1;
4312 for (k = 0; k < ranks; k++) {
4313 for (j = 0; j < files; j++)
4314 board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4315 if(gameInfo.holdingsWidth > 1) {
4316 board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4317 board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4320 CopyBoard(partnerBoard, board);
4321 if(toSqr = strchr(str, '/')) { // extract highlights from long move
4322 partnerBoard[EP_STATUS-3] = toSqr[1] - AAA; // kludge: hide highlighting info in board
4323 partnerBoard[EP_STATUS-4] = toSqr[2] - ONE;
4324 } else partnerBoard[EP_STATUS-4] = partnerBoard[EP_STATUS-3] = -1;
4325 if(toSqr = strchr(str, '-')) {
4326 partnerBoard[EP_STATUS-1] = toSqr[1] - AAA;
4327 partnerBoard[EP_STATUS-2] = toSqr[2] - ONE;
4328 } else partnerBoard[EP_STATUS-1] = partnerBoard[EP_STATUS-2] = -1;
4329 if(appData.dualBoard && !twoBoards) { twoBoards = 1; InitDrawingSizes(-2,0); }
4330 if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual
4331 if(partnerUp) DrawPosition(FALSE, partnerBoard);
4333 DisplayWhiteClock(white_time*fac, to_play == 'W');
4334 DisplayBlackClock(black_time*fac, to_play != 'W');
4335 activePartner = to_play;
4336 if(gamenum != lastBgGame) {
4338 snprintf(buf, MSG_SIZ, "%s %s %s", white, _("vs."), black);
4341 lastBgGame = gamenum;
4342 activePartnerTime = to_play == 'W' ? white_time*fac : black_time*fac;
4343 partnerUp = 0; flipView = !flipView; } // [HGM] dual
4344 snprintf(partnerStatus, MSG_SIZ,"W: %d:%02d B: %d:%02d (%d-%d) %c", white_time*fac/60000, (white_time*fac%60000)/1000,
4345 (black_time*fac/60000), (black_time*fac%60000)/1000, white_stren, black_stren, to_play);
4346 if(!twoBoards) DisplayMessage(partnerStatus, "");
4347 partnerBoardValid = TRUE;
4351 if(appData.dualBoard && appData.bgObserve) {
4352 if((newGameMode == IcsPlayingWhite || newGameMode == IcsPlayingBlack) && moveNum == 1)
4353 SendToICS(ics_prefix), SendToICS("pobserve\n");
4354 else if(newGameMode == IcsObserving && (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
4356 snprintf(buf, MSG_SIZ, "%spobserve %s\n", ics_prefix, white);
4361 /* Modify behavior for initial board display on move listing
4364 switch (ics_getting_history) {
4368 case H_GOT_REQ_HEADER:
4369 case H_GOT_UNREQ_HEADER:
4370 /* This is the initial position of the current game */
4371 gamenum = ics_gamenum;
4372 moveNum = 0; /* old ICS bug workaround */
4373 if (to_play == 'B') {
4374 startedFromSetupPosition = TRUE;
4375 blackPlaysFirst = TRUE;
4377 if (forwardMostMove == 0) forwardMostMove = 1;
4378 if (backwardMostMove == 0) backwardMostMove = 1;
4379 if (currentMove == 0) currentMove = 1;
4381 newGameMode = gameMode;
4382 relation = RELATION_STARTING_POSITION; /* ICC needs this */
4384 case H_GOT_UNWANTED_HEADER:
4385 /* This is an initial board that we don't want */
4387 case H_GETTING_MOVES:
4388 /* Should not happen */
4389 DisplayError(_("Error gathering move list: extra board"), 0);
4390 ics_getting_history = H_FALSE;
4394 if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
4395 move_str[1] == '@' && !gameInfo.holdingsWidth ||
4396 weird && (int)gameInfo.variant < (int)VariantShogi) {
4397 /* [HGM] We seem to have switched variant unexpectedly
4398 * Try to guess new variant from board size
4400 VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
4401 if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
4402 if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
4403 if(ranks == 8 && files == 12) newVariant = VariantCourier; else
4404 if(ranks == 9 && files == 9) newVariant = VariantShogi; else
4405 if(ranks == 10 && files == 10) newVariant = VariantGrand; else
4406 if(!weird) newVariant = move_str[1] == '@' ? VariantCrazyhouse : VariantNormal;
4407 VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4408 /* Get a move list just to see the header, which
4409 will tell us whether this is really bug or zh */
4410 if (ics_getting_history == H_FALSE) {
4411 ics_getting_history = H_REQUESTED; reqFlag = TRUE;
4412 snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4417 /* Take action if this is the first board of a new game, or of a
4418 different game than is currently being displayed. */
4419 if (gamenum != ics_gamenum || newGameMode != gameMode ||
4420 relation == RELATION_ISOLATED_BOARD) {
4422 /* Forget the old game and get the history (if any) of the new one */
4423 if (gameMode != BeginningOfGame) {
4427 if (appData.autoRaiseBoard) BoardToTop();
4429 if (gamenum == -1) {
4430 newGameMode = IcsIdle;
4431 } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
4432 appData.getMoveList && !reqFlag) {
4433 /* Need to get game history */
4434 ics_getting_history = H_REQUESTED;
4435 snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4439 /* Initially flip the board to have black on the bottom if playing
4440 black or if the ICS flip flag is set, but let the user change
4441 it with the Flip View button. */
4442 flipView = appData.autoFlipView ?
4443 (newGameMode == IcsPlayingBlack) || ics_flip :
4446 /* Done with values from previous mode; copy in new ones */
4447 gameMode = newGameMode;
4449 ics_gamenum = gamenum;
4450 if (gamenum == gs_gamenum) {
4451 int klen = strlen(gs_kind);
4452 if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
4453 snprintf(str, MSG_SIZ, "ICS %s", gs_kind);
4454 gameInfo.event = StrSave(str);
4456 gameInfo.event = StrSave("ICS game");
4458 gameInfo.site = StrSave(appData.icsHost);
4459 gameInfo.date = PGNDate();
4460 gameInfo.round = StrSave("-");
4461 gameInfo.white = StrSave(white);
4462 gameInfo.black = StrSave(black);
4463 timeControl = basetime * 60 * 1000;
4465 timeIncrement = increment * 1000;
4466 movesPerSession = 0;
4467 gameInfo.timeControl = TimeControlTagValue();
4468 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
4469 if (appData.debugMode) {
4470 fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
4471 fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
4472 setbuf(debugFP, NULL);
4475 gameInfo.outOfBook = NULL;
4477 /* Do we have the ratings? */
4478 if (strcmp(player1Name, white) == 0 &&
4479 strcmp(player2Name, black) == 0) {
4480 if (appData.debugMode)
4481 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4482 player1Rating, player2Rating);
4483 gameInfo.whiteRating = player1Rating;
4484 gameInfo.blackRating = player2Rating;
4485 } else if (strcmp(player2Name, white) == 0 &&
4486 strcmp(player1Name, black) == 0) {
4487 if (appData.debugMode)
4488 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4489 player2Rating, player1Rating);
4490 gameInfo.whiteRating = player2Rating;
4491 gameInfo.blackRating = player1Rating;
4493 player1Name[0] = player2Name[0] = NULLCHAR;
4495 /* Silence shouts if requested */
4496 if (appData.quietPlay &&
4497 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
4498 SendToICS(ics_prefix);
4499 SendToICS("set shout 0\n");
4503 /* Deal with midgame name changes */
4505 if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
4506 if (gameInfo.white) free(gameInfo.white);
4507 gameInfo.white = StrSave(white);
4509 if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
4510 if (gameInfo.black) free(gameInfo.black);
4511 gameInfo.black = StrSave(black);
4515 /* Throw away game result if anything actually changes in examine mode */
4516 if (gameMode == IcsExamining && !newGame) {
4517 gameInfo.result = GameUnfinished;
4518 if (gameInfo.resultDetails != NULL) {
4519 free(gameInfo.resultDetails);
4520 gameInfo.resultDetails = NULL;
4524 /* In pausing && IcsExamining mode, we ignore boards coming
4525 in if they are in a different variation than we are. */
4526 if (pauseExamInvalid) return;
4527 if (pausing && gameMode == IcsExamining) {
4528 if (moveNum <= pauseExamForwardMostMove) {
4529 pauseExamInvalid = TRUE;
4530 forwardMostMove = pauseExamForwardMostMove;
4535 if (appData.debugMode) {
4536 fprintf(debugFP, "load %dx%d board\n", files, ranks);
4538 /* Parse the board */
4539 for (k = 0; k < ranks; k++) {
4540 for (j = 0; j < files; j++)
4541 board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4542 if(gameInfo.holdingsWidth > 1) {
4543 board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4544 board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4547 if(moveNum==0 && gameInfo.variant == VariantSChess) {
4548 board[5][BOARD_RGHT+1] = WhiteAngel;
4549 board[6][BOARD_RGHT+1] = WhiteMarshall;
4550 board[1][0] = BlackMarshall;
4551 board[2][0] = BlackAngel;
4552 board[1][1] = board[2][1] = board[5][BOARD_RGHT] = board[6][BOARD_RGHT] = 1;
4554 CopyBoard(boards[moveNum], board);
4555 boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
4557 startedFromSetupPosition =
4558 !CompareBoards(board, initialPosition);
4559 if(startedFromSetupPosition)
4560 initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
4563 /* [HGM] Set castling rights. Take the outermost Rooks,
4564 to make it also work for FRC opening positions. Note that board12
4565 is really defective for later FRC positions, as it has no way to
4566 indicate which Rook can castle if they are on the same side of King.
4567 For the initial position we grant rights to the outermost Rooks,
4568 and remember thos rights, and we then copy them on positions
4569 later in an FRC game. This means WB might not recognize castlings with
4570 Rooks that have moved back to their original position as illegal,
4571 but in ICS mode that is not its job anyway.
4573 if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
4574 { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
4576 for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4577 if(board[0][i] == WhiteRook) j = i;
4578 initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4579 for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4580 if(board[0][i] == WhiteRook) j = i;
4581 initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4582 for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4583 if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4584 initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4585 for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4586 if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4587 initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4589 boards[moveNum][CASTLING][2] = boards[moveNum][CASTLING][5] = NoRights;
4590 if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
4591 for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4592 if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
4593 for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4594 if(board[BOARD_HEIGHT-1][k] == bKing)
4595 initialRights[5] = boards[moveNum][CASTLING][5] = k;
4596 if(gameInfo.variant == VariantTwoKings) {
4597 // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
4598 if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
4599 if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
4602 r = boards[moveNum][CASTLING][0] = initialRights[0];
4603 if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4604 r = boards[moveNum][CASTLING][1] = initialRights[1];
4605 if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4606 r = boards[moveNum][CASTLING][3] = initialRights[3];
4607 if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4608 r = boards[moveNum][CASTLING][4] = initialRights[4];
4609 if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4610 /* wildcastle kludge: always assume King has rights */
4611 r = boards[moveNum][CASTLING][2] = initialRights[2];
4612 r = boards[moveNum][CASTLING][5] = initialRights[5];
4614 /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4615 boards[moveNum][EP_STATUS] = EP_NONE;
4616 if(str[0] == 'P') boards[moveNum][EP_STATUS] = EP_PAWN_MOVE;
4617 if(strchr(move_str, 'x')) boards[moveNum][EP_STATUS] = EP_CAPTURE;
4618 if(double_push != -1) boards[moveNum][EP_STATUS] = double_push + BOARD_LEFT;
4621 if (ics_getting_history == H_GOT_REQ_HEADER ||
4622 ics_getting_history == H_GOT_UNREQ_HEADER) {
4623 /* This was an initial position from a move list, not
4624 the current position */
4628 /* Update currentMove and known move number limits */
4629 newMove = newGame || moveNum > forwardMostMove;
4632 forwardMostMove = backwardMostMove = currentMove = moveNum;
4633 if (gameMode == IcsExamining && moveNum == 0) {
4634 /* Workaround for ICS limitation: we are not told the wild
4635 type when starting to examine a game. But if we ask for
4636 the move list, the move list header will tell us */
4637 ics_getting_history = H_REQUESTED;
4638 snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4641 } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4642 || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4644 /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4645 /* [HGM] applied this also to an engine that is silently watching */
4646 if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4647 (gameMode == IcsObserving || gameMode == IcsExamining) &&
4648 gameInfo.variant == currentlyInitializedVariant) {
4649 takeback = forwardMostMove - moveNum;
4650 for (i = 0; i < takeback; i++) {
4651 if (appData.debugMode) fprintf(debugFP, "take back move\n");
4652 SendToProgram("undo\n", &first);
4657 forwardMostMove = moveNum;
4658 if (!pausing || currentMove > forwardMostMove)
4659 currentMove = forwardMostMove;
4661 /* New part of history that is not contiguous with old part */
4662 if (pausing && gameMode == IcsExamining) {
4663 pauseExamInvalid = TRUE;
4664 forwardMostMove = pauseExamForwardMostMove;
4667 if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4669 if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4670 // [HGM] when we will receive the move list we now request, it will be
4671 // fed to the engine from the first move on. So if the engine is not
4672 // in the initial position now, bring it there.
4673 InitChessProgram(&first, 0);
4676 ics_getting_history = H_REQUESTED;
4677 snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4680 forwardMostMove = backwardMostMove = currentMove = moveNum;
4683 /* Update the clocks */
4684 if (strchr(elapsed_time, '.')) {
4686 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4687 timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4689 /* Time is in seconds */
4690 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4691 timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4696 if (appData.zippyPlay && newGame &&
4697 gameMode != IcsObserving && gameMode != IcsIdle &&
4698 gameMode != IcsExamining)
4699 ZippyFirstBoard(moveNum, basetime, increment);
4702 /* Put the move on the move list, first converting
4703 to canonical algebraic form. */
4705 if (appData.debugMode) {
4706 int f = forwardMostMove;
4707 fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4708 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4709 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4710 fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4711 fprintf(debugFP, "moveNum = %d\n", moveNum);
4712 fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4713 setbuf(debugFP, NULL);
4715 if (moveNum <= backwardMostMove) {
4716 /* We don't know what the board looked like before
4718 safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4719 strcat(parseList[moveNum - 1], " ");
4720 strcat(parseList[moveNum - 1], elapsed_time);
4721 moveList[moveNum - 1][0] = NULLCHAR;
4722 } else if (strcmp(move_str, "none") == 0) {
4723 // [HGM] long SAN: swapped order; test for 'none' before parsing move
4724 /* Again, we don't know what the board looked like;
4725 this is really the start of the game. */
4726 parseList[moveNum - 1][0] = NULLCHAR;
4727 moveList[moveNum - 1][0] = NULLCHAR;
4728 backwardMostMove = moveNum;
4729 startedFromSetupPosition = TRUE;
4730 fromX = fromY = toX = toY = -1;
4732 // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
4733 // So we parse the long-algebraic move string in stead of the SAN move
4734 int valid; char buf[MSG_SIZ], *prom;
4736 if(gameInfo.variant == VariantShogi && !strchr(move_str, '=') && !strchr(move_str, '@'))
4737 strcat(move_str, "="); // if ICS does not say 'promote' on non-drop, we defer.
4738 // str looks something like "Q/a1-a2"; kill the slash
4740 snprintf(buf, MSG_SIZ,"%c%s", str[0], str+2);
4741 else safeStrCpy(buf, str, sizeof(buf)/sizeof(buf[0])); // might be castling
4742 if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
4743 strcat(buf, prom); // long move lacks promo specification!
4744 if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4745 if(appData.debugMode)
4746 fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4747 safeStrCpy(move_str, buf, MSG_SIZ);
4749 valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4750 &fromX, &fromY, &toX, &toY, &promoChar)
4751 || ParseOneMove(buf, moveNum - 1, &moveType,
4752 &fromX, &fromY, &toX, &toY, &promoChar);
4753 // end of long SAN patch
4755 (void) CoordsToAlgebraic(boards[moveNum - 1],
4756 PosFlags(moveNum - 1),
4757 fromY, fromX, toY, toX, promoChar,
4758 parseList[moveNum-1]);
4759 switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4765 if(gameInfo.variant != VariantShogi)
4766 strcat(parseList[moveNum - 1], "+");
4769 case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4770 strcat(parseList[moveNum - 1], "#");
4773 strcat(parseList[moveNum - 1], " ");
4774 strcat(parseList[moveNum - 1], elapsed_time);
4775 /* currentMoveString is set as a side-effect of ParseOneMove */
4776 if(gameInfo.variant == VariantShogi && currentMoveString[4]) currentMoveString[4] = '^';
4777 safeStrCpy(moveList[moveNum - 1], currentMoveString, sizeof(moveList[moveNum - 1])/sizeof(moveList[moveNum - 1][0]));
4778 strcat(moveList[moveNum - 1], "\n");
4780 if(gameInfo.holdingsWidth && !appData.disguise && gameInfo.variant != VariantSuper && gameInfo.variant != VariantGreat
4781 && gameInfo.variant != VariantGrand&& gameInfo.variant != VariantSChess) // inherit info that ICS does not give from previous board
4782 for(k=0; k<ranks; k++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
4783 ChessSquare old, new = boards[moveNum][k][j];
4784 if(fromY == DROP_RANK && k==toY && j==toX) continue; // dropped pieces always stand for themselves
4785 old = (k==toY && j==toX) ? boards[moveNum-1][fromY][fromX] : boards[moveNum-1][k][j]; // trace back mover
4786 if(old == new) continue;
4787 if(old == PROMOTED new) boards[moveNum][k][j] = old; // prevent promoted pieces to revert to primordial ones
4788 else if(new == WhiteWazir || new == BlackWazir) {
4789 if(old < WhiteCannon || old >= BlackPawn && old < BlackCannon)
4790 boards[moveNum][k][j] = PROMOTED old; // choose correct type of Gold in promotion
4791 else boards[moveNum][k][j] = old; // preserve type of Gold
4792 } else if((old == WhitePawn || old == BlackPawn) && new != EmptySquare) // Pawn promotions (but not e.p.capture!)
4793 boards[moveNum][k][j] = PROMOTED new; // use non-primordial representation of chosen piece
4796 /* Move from ICS was illegal!? Punt. */
4797 if (appData.debugMode) {
4798 fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4799 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4801 safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4802 strcat(parseList[moveNum - 1], " ");
4803 strcat(parseList[moveNum - 1], elapsed_time);
4804 moveList[moveNum - 1][0] = NULLCHAR;
4805 fromX = fromY = toX = toY = -1;
4808 if (appData.debugMode) {
4809 fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4810 setbuf(debugFP, NULL);
4814 /* Send move to chess program (BEFORE animating it). */
4815 if (appData.zippyPlay && !newGame && newMove &&
4816 (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4818 if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4819 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4820 if (moveList[moveNum - 1][0] == NULLCHAR) {
4821 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"),
4823 DisplayError(str, 0);
4825 if (first.sendTime) {
4826 SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4828 bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4829 if (firstMove && !bookHit) {
4831 if (first.useColors) {
4832 SendToProgram(gameMode == IcsPlayingWhite ?
4834 "black\ngo\n", &first);
4836 SendToProgram("go\n", &first);
4838 first.maybeThinking = TRUE;
4841 } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4842 if (moveList[moveNum - 1][0] == NULLCHAR) {
4843 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"), move_str);
4844 DisplayError(str, 0);
4846 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4847 SendMoveToProgram(moveNum - 1, &first);
4854 if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4855 /* If move comes from a remote source, animate it. If it
4856 isn't remote, it will have already been animated. */
4857 if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4858 AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4860 if (!pausing && appData.highlightLastMove) {
4861 SetHighlights(fromX, fromY, toX, toY);
4865 /* Start the clocks */
4866 whiteFlag = blackFlag = FALSE;
4867 appData.clockMode = !(basetime == 0 && increment == 0);
4869 ics_clock_paused = TRUE;
4871 } else if (ticking == 1) {
4872 ics_clock_paused = FALSE;
4874 if (gameMode == IcsIdle ||
4875 relation == RELATION_OBSERVING_STATIC ||
4876 relation == RELATION_EXAMINING ||
4878 DisplayBothClocks();
4882 /* Display opponents and material strengths */
4883 if (gameInfo.variant != VariantBughouse &&
4884 gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4885 if (tinyLayout || smallLayout) {
4886 if(gameInfo.variant == VariantNormal)
4887 snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d}",
4888 gameInfo.white, white_stren, gameInfo.black, black_stren,
4889 basetime, increment);
4891 snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d w%d}",
4892 gameInfo.white, white_stren, gameInfo.black, black_stren,
4893 basetime, increment, (int) gameInfo.variant);
4895 if(gameInfo.variant == VariantNormal)
4896 snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d}",
4897 gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
4898 basetime, increment);
4900 snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d %s}",
4901 gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
4902 basetime, increment, VariantName(gameInfo.variant));
4905 if (appData.debugMode) {
4906 fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
4911 /* Display the board */
4912 if (!pausing && !appData.noGUI) {
4914 if (appData.premove)
4916 ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
4917 ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
4918 ClearPremoveHighlights();
4920 j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
4921 if(partnerUp) { flipView = originalFlip; partnerUp = FALSE; j = TRUE; } // [HGM] bughouse: restore view
4922 DrawPosition(j, boards[currentMove]);
4924 DisplayMove(moveNum - 1);
4925 if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4926 !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4927 (gameMode == IcsPlayingBlack) && (WhiteOnMove(moveNum)) ) ) {
4928 if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4932 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4934 if(bookHit) { // [HGM] book: simulate book reply
4935 static char bookMove[MSG_SIZ]; // a bit generous?
4937 programStats.nodes = programStats.depth = programStats.time =
4938 programStats.score = programStats.got_only_move = 0;
4939 sprintf(programStats.movelist, "%s (xbook)", bookHit);
4941 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
4942 strcat(bookMove, bookHit);
4943 HandleMachineMove(bookMove, &first);
4952 if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4953 ics_getting_history = H_REQUESTED;
4954 snprintf(buf, MSG_SIZ, "%smoves %d\n", ics_prefix, ics_gamenum);
4960 SendToBoth (char *msg)
4961 { // to make it easy to keep two engines in step in dual analysis
4962 SendToProgram(msg, &first);
4963 if(second.analyzing) SendToProgram(msg, &second);
4967 AnalysisPeriodicEvent (int force)
4969 if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4970 && !force) || !appData.periodicUpdates)
4973 /* Send . command to Crafty to collect stats */
4976 /* Don't send another until we get a response (this makes
4977 us stop sending to old Crafty's which don't understand
4978 the "." command (sending illegal cmds resets node count & time,
4979 which looks bad)) */
4980 programStats.ok_to_send = 0;
4984 ics_update_width (int new_width)
4986 ics_printf("set width %d\n", new_width);
4990 SendMoveToProgram (int moveNum, ChessProgramState *cps)
4994 if(moveList[moveNum][1] == '@' && moveList[moveNum][0] == '@') {
4995 // null move in variant where engine does not understand it (for analysis purposes)
4996 SendBoard(cps, moveNum + 1); // send position after move in stead.
4999 if (cps->useUsermove) {
5000 SendToProgram("usermove ", cps);
5004 if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
5005 int len = space - parseList[moveNum];
5006 memcpy(buf, parseList[moveNum], len);
5008 buf[len] = NULLCHAR;
5010 snprintf(buf, MSG_SIZ,"%s\n", parseList[moveNum]);
5012 SendToProgram(buf, cps);
5014 if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
5015 AlphaRank(moveList[moveNum], 4);
5016 SendToProgram(moveList[moveNum], cps);
5017 AlphaRank(moveList[moveNum], 4); // and back
5019 /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
5020 * the engine. It would be nice to have a better way to identify castle
5022 if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
5023 && cps->useOOCastle) {
5024 int fromX = moveList[moveNum][0] - AAA;
5025 int fromY = moveList[moveNum][1] - ONE;
5026 int toX = moveList[moveNum][2] - AAA;
5027 int toY = moveList[moveNum][3] - ONE;
5028 if((boards[moveNum][fromY][fromX] == WhiteKing
5029 && boards[moveNum][toY][toX] == WhiteRook)
5030 || (boards[moveNum][fromY][fromX] == BlackKing
5031 && boards[moveNum][toY][toX] == BlackRook)) {
5032 if(toX > fromX) SendToProgram("O-O\n", cps);
5033 else SendToProgram("O-O-O\n", cps);
5035 else SendToProgram(moveList[moveNum], cps);
5037 if(BOARD_HEIGHT > 10) { // [HGM] big: convert ranks to double-digit where needed
5038 if(moveList[moveNum][1] == '@' && (BOARD_HEIGHT < 16 || moveList[moveNum][0] <= 'Z')) { // drop move
5039 if(moveList[moveNum][0]== '@') snprintf(buf, MSG_SIZ, "@@@@\n"); else
5040 snprintf(buf, MSG_SIZ, "%c@%c%d%s", moveList[moveNum][0],
5041 moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
5043 snprintf(buf, MSG_SIZ, "%c%d%c%d%s", moveList[moveNum][0], moveList[moveNum][1] - '0',
5044 moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
5045 SendToProgram(buf, cps);
5047 else SendToProgram(moveList[moveNum], cps);
5048 /* End of additions by Tord */
5051 /* [HGM] setting up the opening has brought engine in force mode! */
5052 /* Send 'go' if we are in a mode where machine should play. */
5053 if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
5054 (gameMode == TwoMachinesPlay ||
5056 gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite ||
5058 gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
5059 SendToProgram("go\n", cps);
5060 if (appData.debugMode) {
5061 fprintf(debugFP, "(extra)\n");
5064 setboardSpoiledMachineBlack = 0;
5068 SendMoveToICS (ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar)
5070 char user_move[MSG_SIZ];
5073 if(gameInfo.variant == VariantSChess && promoChar) {
5074 snprintf(suffix, 4, "=%c", toX == BOARD_WIDTH<<1 ? ToUpper(promoChar) : ToLower(promoChar));
5075 if(moveType == NormalMove) moveType = WhitePromotion; // kludge to do gating
5076 } else suffix[0] = NULLCHAR;
5080 snprintf(user_move, MSG_SIZ, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
5081 (int)moveType, fromX, fromY, toX, toY);
5082 DisplayError(user_move + strlen("say "), 0);
5084 case WhiteKingSideCastle:
5085 case BlackKingSideCastle:
5086 case WhiteQueenSideCastleWild:
5087 case BlackQueenSideCastleWild:
5089 case WhiteHSideCastleFR:
5090 case BlackHSideCastleFR:
5092 snprintf(user_move, MSG_SIZ, "o-o%s\n", suffix);
5094 case WhiteQueenSideCastle:
5095 case BlackQueenSideCastle:
5096 case WhiteKingSideCastleWild:
5097 case BlackKingSideCastleWild:
5099 case WhiteASideCastleFR:
5100 case BlackASideCastleFR:
5102 snprintf(user_move, MSG_SIZ, "o-o-o%s\n",suffix);
5104 case WhiteNonPromotion:
5105 case BlackNonPromotion:
5106 sprintf(user_move, "%c%c%c%c==\n", AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5108 case WhitePromotion:
5109 case BlackPromotion:
5110 if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
5111 gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN)
5112 snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5113 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5114 PieceToChar(WhiteFerz));
5115 else if(gameInfo.variant == VariantGreat)
5116 snprintf(user_move, MSG_SIZ,"%c%c%c%c=%c\n",
5117 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5118 PieceToChar(WhiteMan));
5120 snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5121 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5127 snprintf(user_move, MSG_SIZ, "%c@%c%c\n",
5128 ToUpper(PieceToChar((ChessSquare) fromX)),
5129 AAA + toX, ONE + toY);
5131 case IllegalMove: /* could be a variant we don't quite understand */
5132 if(fromY == DROP_RANK) goto drop; // We need 'IllegalDrop' move type?
5134 case WhiteCapturesEnPassant:
5135 case BlackCapturesEnPassant:
5136 snprintf(user_move, MSG_SIZ,"%c%c%c%c\n",
5137 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5140 SendToICS(user_move);
5141 if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
5142 ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
5147 { // [HGM] upload: send entire stored game to ICS as long-algebraic moves.
5148 int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
5149 static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
5150 if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
5151 DisplayError(_("You cannot do this while you are playing or observing"), 0);
5154 if(gameMode != IcsExamining) { // is this ever not the case?
5155 char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
5157 if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
5158 snprintf(command,MSG_SIZ, "match %s", ics_handle);
5159 } else { // on FICS we must first go to general examine mode
5160 safeStrCpy(command, "examine\nbsetup", sizeof(command)/sizeof(command[0])); // and specify variant within it with bsetups
5162 if(gameInfo.variant != VariantNormal) {
5163 // try figure out wild number, as xboard names are not always valid on ICS
5164 for(i=1; i<=36; i++) {
5165 snprintf(buf, MSG_SIZ, "wild/%d", i);
5166 if(StringToVariant(buf) == gameInfo.variant) break;
5168 if(i<=36 && ics_type == ICS_ICC) snprintf(buf, MSG_SIZ,"%s w%d\n", command, i);
5169 else if(i == 22) snprintf(buf,MSG_SIZ, "%s fr\n", command);
5170 else snprintf(buf, MSG_SIZ,"%s %s\n", command, VariantName(gameInfo.variant));
5171 } else snprintf(buf, MSG_SIZ,"%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
5172 SendToICS(ics_prefix);
5174 if(startedFromSetupPosition || backwardMostMove != 0) {
5175 fen = PositionToFEN(backwardMostMove, NULL, 1);
5176 if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
5177 snprintf(buf, MSG_SIZ,"loadfen %s\n", fen);
5179 } else { // FICS: everything has to set by separate bsetup commands
5180 p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board
5181 snprintf(buf, MSG_SIZ,"bsetup fen %s\n", fen);
5183 if(!WhiteOnMove(backwardMostMove)) {
5184 SendToICS("bsetup tomove black\n");
5186 i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL);
5187 snprintf(buf, MSG_SIZ,"bsetup wcastle %s\n", castlingStrings[i]);
5189 i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
5190 snprintf(buf, MSG_SIZ, "bsetup bcastle %s\n", castlingStrings[i]);
5192 i = boards[backwardMostMove][EP_STATUS];
5193 if(i >= 0) { // set e.p.
5194 snprintf(buf, MSG_SIZ,"bsetup eppos %c\n", i+AAA);
5200 if(bsetup || ics_type != ICS_ICC && gameInfo.variant != VariantNormal)
5201 SendToICS("bsetup done\n"); // switch to normal examining.
5203 for(i = backwardMostMove; i<last; i++) {
5205 snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s\n", parseList[i]);
5206 if((*buf == 'b' || *buf == 'B') && buf[1] == 'x') { // work-around for stupid FICS bug, which thinks bxc3 can be a Bishop move
5207 int len = strlen(moveList[i]);
5208 snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s", moveList[i]); // use long algebraic
5209 if(!isdigit(buf[len-2])) snprintf(buf+len-2, 20-len, "=%c\n", ToUpper(buf[len-2])); // promotion must have '=' in ICS format
5213 SendToICS(ics_prefix);
5214 SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
5218 CoordsToComputerAlgebraic (int rf, int ff, int rt, int ft, char promoChar, char move[7])
5220 if (rf == DROP_RANK) {
5221 if(ff == EmptySquare) sprintf(move, "@@@@\n"); else // [HGM] pass
5222 sprintf(move, "%c@%c%c\n",
5223 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
5225 if (promoChar == 'x' || promoChar == NULLCHAR) {
5226 sprintf(move, "%c%c%c%c\n",
5227 AAA + ff, ONE + rf, AAA + ft, ONE + rt);
5229 sprintf(move, "%c%c%c%c%c\n",
5230 AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
5236 ProcessICSInitScript (FILE *f)
5240 while (fgets(buf, MSG_SIZ, f)) {
5241 SendToICSDelayed(buf,(long)appData.msLoginDelay);
5248 static int lastX, lastY, lastLeftX, lastLeftY, selectFlag, dragging;
5249 static ClickType lastClickType;
5254 ChessSquare king = WhiteKing, pawn = WhitePawn, last = promoSweep;
5255 if(gameInfo.variant == VariantKnightmate) king = WhiteUnicorn;
5256 if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway) king = EmptySquare;
5257 if(promoSweep >= BlackPawn) king = WHITE_TO_BLACK king, pawn = WHITE_TO_BLACK pawn;
5258 if(gameInfo.variant == VariantSpartan && pawn == BlackPawn) pawn = BlackLance, king = EmptySquare;
5259 if(fromY != BOARD_HEIGHT-2 && fromY != 1) pawn = EmptySquare;
5262 if(promoSweep == EmptySquare) promoSweep = BlackPawn; // wrap
5263 else if((int)promoSweep == -1) promoSweep = WhiteKing;
5264 else if(promoSweep == BlackPawn && step < 0) promoSweep = WhitePawn;
5265 else if(promoSweep == WhiteKing && step > 0) promoSweep = BlackKing;
5266 if(!step) step = -1;
5267 } while(PieceToChar(promoSweep) == '.' || PieceToChar(promoSweep) == '~' || promoSweep == pawn ||
5268 appData.testLegality && (promoSweep == king ||
5269 gameInfo.variant == VariantShogi && promoSweep != PROMOTED last && last != PROMOTED promoSweep && last != promoSweep));
5271 int victim = boards[currentMove][toY][toX];
5272 boards[currentMove][toY][toX] = promoSweep;
5273 DrawPosition(FALSE, boards[currentMove]);
5274 boards[currentMove][toY][toX] = victim;
5276 ChangeDragPiece(promoSweep);
5280 PromoScroll (int x, int y)
5284 if(promoSweep == EmptySquare || !appData.sweepSelect) return FALSE;
5285 if(abs(x - lastX) < 25 && abs(y - lastY) < 25) return FALSE;
5286 if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5287 if(!step) return FALSE;
5288 lastX = x; lastY = y;
5289 if((promoSweep < BlackPawn) == flipView) step = -step;
5290 if(step > 0) selectFlag = 1;
5291 if(!selectFlag) Sweep(step);
5296 NextPiece (int step)
5298 ChessSquare piece = boards[currentMove][toY][toX];
5301 if(pieceSweep == EmptySquare) pieceSweep = WhitePawn; // wrap
5302 if((int)pieceSweep == -1) pieceSweep = BlackKing;
5303 if(!step) step = -1;
5304 } while(PieceToChar(pieceSweep) == '.');
5305 boards[currentMove][toY][toX] = pieceSweep;
5306 DrawPosition(FALSE, boards[currentMove]);
5307 boards[currentMove][toY][toX] = piece;
5309 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
5311 AlphaRank (char *move, int n)
5313 // char *p = move, c; int x, y;
5315 if (appData.debugMode) {
5316 fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
5320 move[2]>='0' && move[2]<='9' &&
5321 move[3]>='a' && move[3]<='x' ) {
5323 move[2] = BOARD_RGHT -1 - (move[2]-'1') + AAA;
5324 move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5326 if(move[0]>='0' && move[0]<='9' &&
5327 move[1]>='a' && move[1]<='x' &&
5328 move[2]>='0' && move[2]<='9' &&
5329 move[3]>='a' && move[3]<='x' ) {
5330 /* input move, Shogi -> normal */
5331 move[0] = BOARD_RGHT -1 - (move[0]-'1') + AAA;
5332 move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
5333 move[2] = BOARD_RGHT -1 - (move[2]-'1') + AAA;
5334 move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5337 move[3]>='0' && move[3]<='9' &&
5338 move[2]>='a' && move[2]<='x' ) {
5340 move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5341 move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5344 move[0]>='a' && move[0]<='x' &&
5345 move[3]>='0' && move[3]<='9' &&
5346 move[2]>='a' && move[2]<='x' ) {
5347 /* output move, normal -> Shogi */
5348 move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
5349 move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
5350 move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5351 move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5352 if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
5354 if (appData.debugMode) {
5355 fprintf(debugFP, " out = '%s'\n", move);
5359 char yy_textstr[8000];
5361 /* Parser for moves from gnuchess, ICS, or user typein box */
5363 ParseOneMove (char *move, int moveNum, ChessMove *moveType, int *fromX, int *fromY, int *toX, int *toY, char *promoChar)
5365 *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
5367 switch (*moveType) {
5368 case WhitePromotion:
5369 case BlackPromotion:
5370 case WhiteNonPromotion:
5371 case BlackNonPromotion:
5373 case WhiteCapturesEnPassant:
5374 case BlackCapturesEnPassant:
5375 case WhiteKingSideCastle:
5376 case WhiteQueenSideCastle:
5377 case BlackKingSideCastle:
5378 case BlackQueenSideCastle:
5379 case WhiteKingSideCastleWild:
5380 case WhiteQueenSideCastleWild:
5381 case BlackKingSideCastleWild:
5382 case BlackQueenSideCastleWild:
5383 /* Code added by Tord: */
5384 case WhiteHSideCastleFR:
5385 case WhiteASideCastleFR:
5386 case BlackHSideCastleFR:
5387 case BlackASideCastleFR:
5388 /* End of code added by Tord */
5389 case IllegalMove: /* bug or odd chess variant */
5390 *fromX = currentMoveString[0] - AAA;
5391 *fromY = currentMoveString[1] - ONE;
5392 *toX = currentMoveString[2] - AAA;
5393 *toY = currentMoveString[3] - ONE;
5394 *promoChar = currentMoveString[4];
5395 if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
5396 *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
5397 if (appData.debugMode) {
5398 fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
5400 *fromX = *fromY = *toX = *toY = 0;
5403 if (appData.testLegality) {
5404 return (*moveType != IllegalMove);
5406 return !(*fromX == *toX && *fromY == *toY) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
5407 WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
5412 *fromX = *moveType == WhiteDrop ?
5413 (int) CharToPiece(ToUpper(currentMoveString[0])) :
5414 (int) CharToPiece(ToLower(currentMoveString[0]));
5416 *toX = currentMoveString[2] - AAA;
5417 *toY = currentMoveString[3] - ONE;
5418 *promoChar = NULLCHAR;
5422 case ImpossibleMove:
5432 if (appData.debugMode) {
5433 fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
5436 *fromX = *fromY = *toX = *toY = 0;
5437 *promoChar = NULLCHAR;
5442 Boolean pushed = FALSE;
5443 char *lastParseAttempt;
5446 ParsePV (char *pv, Boolean storeComments, Boolean atEnd)
5447 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
5448 int fromX, fromY, toX, toY; char promoChar;
5453 lastParseAttempt = pv; if(!*pv) return; // turns out we crash when we parse an empty PV
5454 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) && currentMove < forwardMostMove) {
5455 PushInner(currentMove, forwardMostMove); // [HGM] engine might not be thinking on forwardMost position!
5458 endPV = forwardMostMove;
5460 while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
5461 if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
5462 lastParseAttempt = pv;
5463 valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
5464 if(!valid && nr == 0 &&
5465 ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
5466 nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
5467 // Hande case where played move is different from leading PV move
5468 CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
5469 CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
5470 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
5471 if(!CompareBoards(boards[endPV], boards[endPV+2])) {
5472 endPV += 2; // if position different, keep this
5473 moveList[endPV-1][0] = fromX + AAA;
5474 moveList[endPV-1][1] = fromY + ONE;
5475 moveList[endPV-1][2] = toX + AAA;
5476 moveList[endPV-1][3] = toY + ONE;
5477 parseList[endPV-1][0] = NULLCHAR;
5478 safeStrCpy(moveList[endPV-2], "_0_0", sizeof(moveList[endPV-2])/sizeof(moveList[endPV-2][0])); // suppress premove highlight on takeback move
5481 pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
5482 if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
5483 if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
5484 if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
5485 valid++; // allow comments in PV
5489 if(endPV+1 > framePtr) break; // no space, truncate
5492 CopyBoard(boards[endPV], boards[endPV-1]);
5493 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
5494 CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, moveList[endPV - 1]);
5495 strncat(moveList[endPV-1], "\n", MOVE_LEN);
5496 CoordsToAlgebraic(boards[endPV - 1],
5497 PosFlags(endPV - 1),
5498 fromY, fromX, toY, toX, promoChar,
5499 parseList[endPV - 1]);
5501 if(atEnd == 2) return; // used hidden, for PV conversion
5502 currentMove = (atEnd || endPV == forwardMostMove) ? endPV : forwardMostMove + 1;
5503 if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5504 SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5505 moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5506 DrawPosition(TRUE, boards[currentMove]);
5510 MultiPV (ChessProgramState *cps)
5511 { // check if engine supports MultiPV, and if so, return the number of the option that sets it
5513 for(i=0; i<cps->nrOptions; i++)
5514 if(!strcmp(cps->option[i].name, "MultiPV") && cps->option[i].type == Spin)
5519 Boolean extendGame; // signals to UnLoadPV() if walked part of PV has to be appended to game
5522 LoadMultiPV (int x, int y, char *buf, int index, int *start, int *end, int pane)
5524 int startPV, multi, lineStart, origIndex = index;
5525 char *p, buf2[MSG_SIZ];
5526 ChessProgramState *cps = (pane ? &second : &first);
5528 if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
5529 lastX = x; lastY = y;
5530 while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
5531 lineStart = startPV = index;
5532 while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
5533 if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
5535 do{ while(buf[index] && buf[index] != '\n') index++;
5536 } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
5538 if(lineStart == 0 && gameMode == AnalyzeMode && (multi = MultiPV(cps)) >= 0) {
5539 int n = cps->option[multi].value;
5540 if(origIndex > 17 && origIndex < 24) { if(n>1) n--; } else if(origIndex > index - 6) n++;
5541 snprintf(buf2, MSG_SIZ, "option MultiPV=%d\n", n);
5542 if(cps->option[multi].value != n) SendToProgram(buf2, cps);
5543 cps->option[multi].value = n;
5546 } else if(strstr(buf+lineStart, "exclude:") == buf+lineStart) { // exclude moves clicked
5547 ExcludeClick(origIndex - lineStart);
5550 ParsePV(buf+startPV, FALSE, gameMode != AnalyzeMode);
5551 *start = startPV; *end = index-1;
5552 extendGame = (gameMode == AnalyzeMode && appData.autoExtend);
5559 static char buf[10*MSG_SIZ];
5560 int i, k=0, savedEnd=endPV, saveFMM = forwardMostMove;
5562 if(forwardMostMove < endPV) PushInner(forwardMostMove, endPV); // shelve PV of PV-walk
5563 ParsePV(pv, FALSE, 2); // this appends PV to game, suppressing any display of it
5564 for(i = forwardMostMove; i<endPV; i++){
5565 if(i&1) snprintf(buf+k, 10*MSG_SIZ-k, "%s ", parseList[i]);
5566 else snprintf(buf+k, 10*MSG_SIZ-k, "%d. %s ", i/2 + 1, parseList[i]);
5569 snprintf(buf+k, 10*MSG_SIZ-k, "%s", lastParseAttempt); // if we ran into stuff that could not be parsed, print it verbatim
5570 if(pushed) { PopInner(0); pushed = FALSE; } // restore game continuation shelved by ParsePV
5571 if(forwardMostMove < savedEnd) { PopInner(0); forwardMostMove = saveFMM; } // PopInner would set fmm to endPV!
5577 LoadPV (int x, int y)
5578 { // called on right mouse click to load PV
5579 int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
5580 lastX = x; lastY = y;
5581 ParsePV(lastPV[which], FALSE, TRUE); // load the PV of the thinking engine in the boards array.
5589 int oldFMM = forwardMostMove; // N.B.: this was currentMove before PV was loaded!
5590 if(endPV < 0) return;
5591 if(appData.autoCopyPV) CopyFENToClipboard();
5593 if(extendGame && currentMove > forwardMostMove) {
5594 Boolean saveAnimate = appData.animate;
5596 if(shiftKey && storedGames < MAX_VARIATIONS-2) { // wants to start variation, and there is space
5597 if(storedGames == 1) GreyRevert(FALSE); // we already pushed the tail, so just make it official
5598 } else storedGames--; // abandon shelved tail of original game
5601 forwardMostMove = currentMove;
5602 currentMove = oldFMM;
5603 appData.animate = FALSE;
5604 ToNrEvent(forwardMostMove);
5605 appData.animate = saveAnimate;
5607 currentMove = forwardMostMove;
5608 if(pushed) { PopInner(0); pushed = FALSE; } // restore shelved game continuation
5609 ClearPremoveHighlights();
5610 DrawPosition(TRUE, boards[currentMove]);
5614 MovePV (int x, int y, int h)
5615 { // step through PV based on mouse coordinates (called on mouse move)
5616 int margin = h>>3, step = 0, threshold = (pieceSweep == EmptySquare ? 10 : 15);
5618 // we must somehow check if right button is still down (might be released off board!)
5619 if(endPV < 0 && pieceSweep == EmptySquare) return; // needed in XBoard because lastX/Y is shared :-(
5620 if(abs(x - lastX) < threshold && abs(y - lastY) < threshold) return;
5621 if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5623 lastX = x; lastY = y;
5625 if(pieceSweep != EmptySquare) { NextPiece(step); return; }
5626 if(endPV < 0) return;
5627 if(y < margin) step = 1; else
5628 if(y > h - margin) step = -1;
5629 if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
5630 currentMove += step;
5631 if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5632 SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5633 moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5634 DrawPosition(FALSE, boards[currentMove]);
5638 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
5639 // All positions will have equal probability, but the current method will not provide a unique
5640 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
5646 int piecesLeft[(int)BlackPawn];
5647 int seed, nrOfShuffles;
5650 GetPositionNumber ()
5651 { // sets global variable seed
5654 seed = appData.defaultFrcPosition;
5655 if(seed < 0) { // randomize based on time for negative FRC position numbers
5656 for(i=0; i<50; i++) seed += random();
5657 seed = random() ^ random() >> 8 ^ random() << 8;
5658 if(seed<0) seed = -seed;
5663 put (Board board, int pieceType, int rank, int n, int shade)
5664 // put the piece on the (n-1)-th empty squares of the given shade
5668 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5669 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5670 board[rank][i] = (ChessSquare) pieceType;
5671 squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5673 piecesLeft[pieceType]--;
5682 AddOnePiece (Board board, int pieceType, int rank, int shade)
5683 // calculate where the next piece goes, (any empty square), and put it there
5687 i = seed % squaresLeft[shade];
5688 nrOfShuffles *= squaresLeft[shade];
5689 seed /= squaresLeft[shade];
5690 put(board, pieceType, rank, i, shade);
5694 AddTwoPieces (Board board, int pieceType, int rank)
5695 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5697 int i, n=squaresLeft[ANY], j=n-1, k;
5699 k = n*(n-1)/2; // nr of possibilities, not counting permutations
5700 i = seed % k; // pick one
5703 while(i >= j) i -= j--;
5704 j = n - 1 - j; i += j;
5705 put(board, pieceType, rank, j, ANY);
5706 put(board, pieceType, rank, i, ANY);
5710 SetUpShuffle (Board board, int number)
5714 GetPositionNumber(); nrOfShuffles = 1;
5716 squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5717 squaresLeft[ANY] = BOARD_RGHT - BOARD_LEFT;
5718 squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5720 for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5722 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5723 p = (int) board[0][i];
5724 if(p < (int) BlackPawn) piecesLeft[p] ++;
5725 board[0][i] = EmptySquare;
5728 if(PosFlags(0) & F_ALL_CASTLE_OK) {
5729 // shuffles restricted to allow normal castling put KRR first
5730 if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5731 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5732 else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5733 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5734 if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5735 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5736 if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5737 put(board, WhiteRook, 0, 0, ANY);
5738 // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5741 if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5742 // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5743 for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5744 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5745 while(piecesLeft[p] >= 2) {
5746 AddOnePiece(board, p, 0, LITE);
5747 AddOnePiece(board, p, 0, DARK);
5749 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5752 for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5753 // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5754 // but we leave King and Rooks for last, to possibly obey FRC restriction
5755 if(p == (int)WhiteRook) continue;
5756 while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5757 if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY); // add the odd piece
5760 // now everything is placed, except perhaps King (Unicorn) and Rooks
5762 if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5763 // Last King gets castling rights
5764 while(piecesLeft[(int)WhiteUnicorn]) {
5765 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5766 initialRights[2] = initialRights[5] = board[CASTLING][2] = board[CASTLING][5] = i;
5769 while(piecesLeft[(int)WhiteKing]) {
5770 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5771 initialRights[2] = initialRights[5] = board[CASTLING][2] = board[CASTLING][5] = i;
5776 while(piecesLeft[(int)WhiteKing]) AddOnePiece(board, WhiteKing, 0, ANY);
5777 while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
5780 // Only Rooks can be left; simply place them all
5781 while(piecesLeft[(int)WhiteRook]) {
5782 i = put(board, WhiteRook, 0, 0, ANY);
5783 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
5786 initialRights[1] = initialRights[4] = board[CASTLING][1] = board[CASTLING][4] = i;
5788 initialRights[0] = initialRights[3] = board[CASTLING][0] = board[CASTLING][3] = i;
5791 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
5792 board[BOARD_HEIGHT-1][i] = (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
5795 if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
5799 SetCharTable (char *table, const char * map)
5800 /* [HGM] moved here from winboard.c because of its general usefulness */
5801 /* Basically a safe strcpy that uses the last character as King */
5803 int result = FALSE; int NrPieces;
5805 if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
5806 && NrPieces >= 12 && !(NrPieces&1)) {
5807 int i; /* [HGM] Accept even length from 12 to 34 */
5809 for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
5810 for( i=0; i<NrPieces/2-1; i++ ) {
5812 table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
5814 table[(int) WhiteKing] = map[NrPieces/2-1];
5815 table[(int) BlackKing] = map[NrPieces-1];
5824 Prelude (Board board)
5825 { // [HGM] superchess: random selection of exo-pieces
5826 int i, j, k; ChessSquare p;
5827 static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
5829 GetPositionNumber(); // use FRC position number
5831 if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
5832 SetCharTable(pieceToChar, appData.pieceToCharTable);
5833 for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
5834 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
5837 j = seed%4; seed /= 4;
5838 p = board[0][BOARD_LEFT+j]; board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5839 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
5840 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
5841 j = seed%3 + (seed%3 >= j); seed /= 3;
5842 p = board[0][BOARD_LEFT+j]; board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5843 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
5844 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
5845 j = seed%3; seed /= 3;
5846 p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5847 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
5848 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
5849 j = seed%2 + (seed%2 >= j); seed /= 2;
5850 p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5851 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
5852 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
5853 j = seed%4; seed /= 4; put(board, exoPieces[3], 0, j, ANY);
5854 j = seed%3; seed /= 3; put(board, exoPieces[2], 0, j, ANY);
5855 j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
5856 put(board, exoPieces[0], 0, 0, ANY);
5857 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
5861 InitPosition (int redraw)
5863 ChessSquare (* pieces)[BOARD_FILES];
5864 int i, j, pawnRow, overrule,
5865 oldx = gameInfo.boardWidth,
5866 oldy = gameInfo.boardHeight,
5867 oldh = gameInfo.holdingsWidth;
5870 if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
5872 /* [AS] Initialize pv info list [HGM] and game status */
5874 for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
5875 pvInfoList[i].depth = 0;
5876 boards[i][EP_STATUS] = EP_NONE;
5877 for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
5880 initialRulePlies = 0; /* 50-move counter start */
5882 castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
5883 castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
5887 /* [HGM] logic here is completely changed. In stead of full positions */
5888 /* the initialized data only consist of the two backranks. The switch */
5889 /* selects which one we will use, which is than copied to the Board */
5890 /* initialPosition, which for the rest is initialized by Pawns and */
5891 /* empty squares. This initial position is then copied to boards[0], */
5892 /* possibly after shuffling, so that it remains available. */
5894 gameInfo.holdingsWidth = 0; /* default board sizes */
5895 gameInfo.boardWidth = 8;
5896 gameInfo.boardHeight = 8;
5897 gameInfo.holdingsSize = 0;
5898 nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
5899 for(i=0; i<BOARD_FILES-2; i++)
5900 initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
5901 initialPosition[EP_STATUS] = EP_NONE;
5902 SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
5903 if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
5904 SetCharTable(pieceNickName, appData.pieceNickNames);
5905 else SetCharTable(pieceNickName, "............");
5908 switch (gameInfo.variant) {
5909 case VariantFischeRandom:
5910 shuffleOpenings = TRUE;
5913 case VariantShatranj:
5914 pieces = ShatranjArray;
5915 nrCastlingRights = 0;
5916 SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
5919 pieces = makrukArray;
5920 nrCastlingRights = 0;
5921 SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
5924 pieces = aseanArray;
5925 nrCastlingRights = 0;
5926 SetCharTable(pieceToChar, "PN.R.Q....BKpn.r.q....bk");
5928 case VariantTwoKings:
5929 pieces = twoKingsArray;
5932 pieces = GrandArray;
5933 nrCastlingRights = 0;
5934 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5935 gameInfo.boardWidth = 10;
5936 gameInfo.boardHeight = 10;
5937 gameInfo.holdingsSize = 7;
5939 case VariantCapaRandom:
5940 shuffleOpenings = TRUE;
5941 case VariantCapablanca:
5942 pieces = CapablancaArray;
5943 gameInfo.boardWidth = 10;
5944 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5947 pieces = GothicArray;
5948 gameInfo.boardWidth = 10;
5949 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5952 SetCharTable(pieceToChar, "PNBRQ..HEKpnbrq..hek");
5953 gameInfo.holdingsSize = 7;
5954 for(i=0; i<BOARD_FILES; i++) initialPosition[VIRGIN][i] = VIRGIN_W | VIRGIN_B;
5957 pieces = JanusArray;
5958 gameInfo.boardWidth = 10;
5959 SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
5960 nrCastlingRights = 6;
5961 initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5962 initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5963 initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
5964 initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5965 initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5966 initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
5969 pieces = FalconArray;
5970 gameInfo.boardWidth = 10;
5971 SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
5973 case VariantXiangqi:
5974 pieces = XiangqiArray;
5975 gameInfo.boardWidth = 9;
5976 gameInfo.boardHeight = 10;
5977 nrCastlingRights = 0;
5978 SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
5981 pieces = ShogiArray;
5982 gameInfo.boardWidth = 9;
5983 gameInfo.boardHeight = 9;
5984 gameInfo.holdingsSize = 7;
5985 nrCastlingRights = 0;
5986 SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
5988 case VariantCourier:
5989 pieces = CourierArray;
5990 gameInfo.boardWidth = 12;
5991 nrCastlingRights = 0;
5992 SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
5994 case VariantKnightmate:
5995 pieces = KnightmateArray;
5996 SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
5998 case VariantSpartan:
5999 pieces = SpartanArray;
6000 SetCharTable(pieceToChar, "PNBRQ................K......lwg.....c...h..k");
6003 pieces = fairyArray;
6004 SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
6007 pieces = GreatArray;
6008 gameInfo.boardWidth = 10;
6009 SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
6010 gameInfo.holdingsSize = 8;
6014 SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
6015 gameInfo.holdingsSize = 8;
6016 startedFromSetupPosition = TRUE;
6018 case VariantCrazyhouse:
6019 case VariantBughouse:
6021 SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
6022 gameInfo.holdingsSize = 5;
6024 case VariantWildCastle:
6026 /* !!?shuffle with kings guaranteed to be on d or e file */
6027 shuffleOpenings = 1;
6029 case VariantNoCastle:
6031 nrCastlingRights = 0;
6032 /* !!?unconstrained back-rank shuffle */
6033 shuffleOpenings = 1;
6038 if(appData.NrFiles >= 0) {
6039 if(gameInfo.boardWidth != appData.NrFiles) overrule++;
6040 gameInfo.boardWidth = appData.NrFiles;
6042 if(appData.NrRanks >= 0) {
6043 gameInfo.boardHeight = appData.NrRanks;
6045 if(appData.holdingsSize >= 0) {
6046 i = appData.holdingsSize;
6047 if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
6048 gameInfo.holdingsSize = i;
6050 if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
6051 if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
6052 DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
6054 pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
6055 if(pawnRow < 1) pawnRow = 1;
6056 if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN || gameInfo.variant == VariantGrand) pawnRow = 2;
6058 /* User pieceToChar list overrules defaults */
6059 if(appData.pieceToCharTable != NULL)
6060 SetCharTable(pieceToChar, appData.pieceToCharTable);
6062 for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
6064 if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
6065 s = (ChessSquare) 0; /* account holding counts in guard band */
6066 for( i=0; i<BOARD_HEIGHT; i++ )
6067 initialPosition[i][j] = s;
6069 if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
6070 initialPosition[gameInfo.variant == VariantGrand][j] = pieces[0][j-gameInfo.holdingsWidth];
6071 initialPosition[pawnRow][j] = WhitePawn;
6072 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = gameInfo.variant == VariantSpartan ? BlackLance : BlackPawn;
6073 if(gameInfo.variant == VariantXiangqi) {
6075 initialPosition[pawnRow][j] =
6076 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
6077 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
6078 initialPosition[2][j] = WhiteCannon;
6079 initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
6083 if(gameInfo.variant == VariantGrand) {
6084 if(j==BOARD_LEFT || j>=BOARD_RGHT-1) {
6085 initialPosition[0][j] = WhiteRook;
6086 initialPosition[BOARD_HEIGHT-1][j] = BlackRook;
6089 initialPosition[BOARD_HEIGHT-1-(gameInfo.variant == VariantGrand)][j] = pieces[1][j-gameInfo.holdingsWidth];
6091 if( (gameInfo.variant == VariantShogi) && !overrule ) {
6094 initialPosition[1][j] = WhiteBishop;
6095 initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
6097 initialPosition[1][j] = WhiteRook;
6098 initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
6101 if( nrCastlingRights == -1) {
6102 /* [HGM] Build normal castling rights (must be done after board sizing!) */
6103 /* This sets default castling rights from none to normal corners */
6104 /* Variants with other castling rights must set them themselves above */
6105 nrCastlingRights = 6;
6107 initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
6108 initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
6109 initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
6110 initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
6111 initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
6112 initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
6115 if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
6116 if(gameInfo.variant == VariantGreat) { // promotion commoners
6117 initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
6118 initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
6119 initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
6120 initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
6122 if( gameInfo.variant == VariantSChess ) {
6123 initialPosition[1][0] = BlackMarshall;
6124 initialPosition[2][0] = BlackAngel;
6125 initialPosition[6][BOARD_WIDTH-1] = WhiteMarshall;
6126 initialPosition[5][BOARD_WIDTH-1] = WhiteAngel;
6127 initialPosition[1][1] = initialPosition[2][1] =
6128 initialPosition[6][BOARD_WIDTH-2] = initialPosition[5][BOARD_WIDTH-2] = 1;
6130 if (appData.debugMode) {
6131 fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
6133 if(shuffleOpenings) {
6134 SetUpShuffle(initialPosition, appData.defaultFrcPosition);
6135 startedFromSetupPosition = TRUE;
6137 if(startedFromPositionFile) {
6138 /* [HGM] loadPos: use PositionFile for every new game */
6139 CopyBoard(initialPosition, filePosition);
6140 for(i=0; i<nrCastlingRights; i++)
6141 initialRights[i] = filePosition[CASTLING][i];
6142 startedFromSetupPosition = TRUE;
6145 CopyBoard(boards[0], initialPosition);
6147 if(oldx != gameInfo.boardWidth ||
6148 oldy != gameInfo.boardHeight ||
6149 oldv != gameInfo.variant ||
6150 oldh != gameInfo.holdingsWidth
6152 InitDrawingSizes(-2 ,0);
6154 oldv = gameInfo.variant;
6156 DrawPosition(TRUE, boards[currentMove]);
6160 SendBoard (ChessProgramState *cps, int moveNum)
6162 char message[MSG_SIZ];
6164 if (cps->useSetboard) {
6165 char* fen = PositionToFEN(moveNum, cps->fenOverride, 1);
6166 snprintf(message, MSG_SIZ,"setboard %s\n", fen);
6167 SendToProgram(message, cps);
6172 int i, j, left=0, right=BOARD_WIDTH;
6173 /* Kludge to set black to move, avoiding the troublesome and now
6174 * deprecated "black" command.
6176 if (!WhiteOnMove(moveNum)) // [HGM] but better a deprecated command than an illegal move...
6177 SendToProgram(boards[0][1][BOARD_LEFT] == WhitePawn ? "a2a3\n" : "black\n", cps);
6179 if(!cps->extendedEdit) left = BOARD_LEFT, right = BOARD_RGHT; // only board proper
6181 SendToProgram("edit\n", cps);
6182 SendToProgram("#\n", cps);
6183 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6184 bp = &boards[moveNum][i][left];
6185 for (j = left; j < right; j++, bp++) {
6186 if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6187 if ((int) *bp < (int) BlackPawn) {
6188 if(j == BOARD_RGHT+1)
6189 snprintf(message, MSG_SIZ, "%c@%d\n", PieceToChar(*bp), bp[-1]);
6190 else snprintf(message, MSG_SIZ, "%c%c%c\n", PieceToChar(*bp), AAA + j, ONE + i);
6191 if(message[0] == '+' || message[0] == '~') {
6192 snprintf(message, MSG_SIZ,"%c%c%c+\n",
6193 PieceToChar((ChessSquare)(DEMOTED *bp)),
6196 if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6197 message[1] = BOARD_RGHT - 1 - j + '1';
6198 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6200 SendToProgram(message, cps);
6205 SendToProgram("c\n", cps);
6206 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6207 bp = &boards[moveNum][i][left];
6208 for (j = left; j < right; j++, bp++) {
6209 if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6210 if (((int) *bp != (int) EmptySquare)
6211 && ((int) *bp >= (int) BlackPawn)) {
6212 if(j == BOARD_LEFT-2)
6213 snprintf(message, MSG_SIZ, "%c@%d\n", ToUpper(PieceToChar(*bp)), bp[1]);
6214 else snprintf(message,MSG_SIZ, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
6216 if(message[0] == '+' || message[0] == '~') {
6217 snprintf(message, MSG_SIZ,"%c%c%c+\n",
6218 PieceToChar((ChessSquare)(DEMOTED *bp)),
6221 if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6222 message[1] = BOARD_RGHT - 1 - j + '1';
6223 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6225 SendToProgram(message, cps);
6230 SendToProgram(".\n", cps);
6232 setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
6235 char exclusionHeader[MSG_SIZ];
6236 int exCnt, excludePtr;
6237 typedef struct { int ff, fr, tf, tr, pc, mark; } Exclusion;
6238 static Exclusion excluTab[200];
6239 static char excludeMap[(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8]; // [HGM] exclude: bitmap for excluced moves
6245 for(j=0; j<(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8; j++) excludeMap[j] = s;
6246 exclusionHeader[19] = s ? '-' : '+'; // update tail state
6252 safeStrCpy(exclusionHeader, "exclude: none best +tail \n", MSG_SIZ);
6253 excludePtr = 24; exCnt = 0;
6258 UpdateExcludeHeader (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6259 { // search given move in table of header moves, to know where it is listed (and add if not there), and update state
6260 char buf[2*MOVE_LEN], *p;
6261 Exclusion *e = excluTab;
6263 for(i=0; i<exCnt; i++)
6264 if(e[i].ff == fromX && e[i].fr == fromY &&
6265 e[i].tf == toX && e[i].tr == toY && e[i].pc == promoChar) break;
6266 if(i == exCnt) { // was not in exclude list; add it
6267 CoordsToAlgebraic(boards[currentMove], PosFlags(currentMove), fromY, fromX, toY, toX, promoChar, buf);
6268 if(strlen(exclusionHeader + excludePtr) < strlen(buf)) { // no space to write move
6269 if(state != exclusionHeader[19]) exclusionHeader[19] = '*'; // tail is now in mixed state
6272 e[i].ff = fromX; e[i].fr = fromY; e[i].tf = toX; e[i].tr = toY; e[i].pc = promoChar;
6273 excludePtr++; e[i].mark = excludePtr++;
6274 for(p=buf; *p; p++) exclusionHeader[excludePtr++] = *p; // copy move
6277 exclusionHeader[e[i].mark] = state;
6281 ExcludeOneMove (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6282 { // include or exclude the given move, as specified by state ('+' or '-'), or toggle
6286 if((signed char)promoChar == -1) { // kludge to indicate best move
6287 if(!ParseOneMove(lastPV[0], currentMove, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) // get current best move from last PV
6288 return 1; // if unparsable, abort
6290 // update exclusion map (resolving toggle by consulting existing state)
6291 k=(BOARD_FILES*fromY+fromX)*BOARD_RANKS*BOARD_FILES + (BOARD_FILES*toY+toX);
6293 if(state == '*') state = (excludeMap[k] & 1<<j ? '+' : '-'); // toggle
6294 if(state == '-' && !promoChar) // only non-promotions get marked as excluded, to allow exclusion of under-promotions
6295 excludeMap[k] |= 1<<j;
6296 else excludeMap[k] &= ~(1<<j);
6298 UpdateExcludeHeader(fromY, fromX, toY, toX, promoChar, state);
6300 snprintf(buf, MSG_SIZ, "%sclude ", state == '+' ? "in" : "ex");
6301 CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, buf+8);
6303 return (state == '+');
6307 ExcludeClick (int index)
6310 Exclusion *e = excluTab;
6311 if(index < 25) { // none, best or tail clicked
6312 if(index < 13) { // none: include all
6313 WriteMap(0); // clear map
6314 for(i=0; i<exCnt; i++) exclusionHeader[excluTab[i].mark] = '+'; // and moves
6315 SendToBoth("include all\n"); // and inform engine
6316 } else if(index > 18) { // tail
6317 if(exclusionHeader[19] == '-') { // tail was excluded
6318 SendToBoth("include all\n");
6319 WriteMap(0); // clear map completely
6320 // now re-exclude selected moves
6321 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '-')
6322 ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '-');
6323 } else { // tail was included or in mixed state
6324 SendToBoth("exclude all\n");
6325 WriteMap(0xFF); // fill map completely
6326 // now re-include selected moves
6327 j = 0; // count them
6328 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '+')
6329 ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '+'), j++;
6330 if(!j) ExcludeOneMove(0, 0, 0, 0, -1, '+'); // if no moves were selected, keep best
6333 ExcludeOneMove(0, 0, 0, 0, -1, '-'); // exclude it
6336 for(i=0; i<exCnt; i++) if(i == exCnt-1 || excluTab[i+1].mark > index) {
6337 char *p=exclusionHeader + excluTab[i].mark; // do trust header more than map (promotions!)
6338 ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, *p == '+' ? '-' : '+');
6345 DefaultPromoChoice (int white)
6348 if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6349 gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN)
6350 result = WhiteFerz; // no choice
6351 else if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway)
6352 result= WhiteKing; // in Suicide Q is the last thing we want
6353 else if(gameInfo.variant == VariantSpartan)
6354 result = white ? WhiteQueen : WhiteAngel;
6355 else result = WhiteQueen;
6356 if(!white) result = WHITE_TO_BLACK result;
6360 static int autoQueen; // [HGM] oneclick
6363 HasPromotionChoice (int fromX, int fromY, int toX, int toY, char *promoChoice, int sweepSelect)
6365 /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
6366 /* [HGM] add Shogi promotions */
6367 int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
6372 if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
6373 if(toX < BOARD_LEFT || toX >= BOARD_RGHT) return FALSE; // move into holdings
6375 if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
6376 !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
6379 piece = boards[currentMove][fromY][fromX];
6380 if(gameInfo.variant == VariantShogi) {
6381 promotionZoneSize = BOARD_HEIGHT/3;
6382 highestPromotingPiece = (int)WhiteFerz;
6383 } else if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand) {
6384 promotionZoneSize = 3;
6387 // Treat Lance as Pawn when it is not representing Amazon
6388 if(gameInfo.variant != VariantSuper) {
6389 if(piece == WhiteLance) piece = WhitePawn; else
6390 if(piece == BlackLance) piece = BlackPawn;
6393 // next weed out all moves that do not touch the promotion zone at all
6394 if((int)piece >= BlackPawn) {
6395 if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
6397 highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
6399 if( toY < BOARD_HEIGHT - promotionZoneSize &&
6400 fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
6403 if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
6405 // weed out mandatory Shogi promotions
6406 if(gameInfo.variant == VariantShogi) {
6407 if(piece >= BlackPawn) {
6408 if(toY == 0 && piece == BlackPawn ||
6409 toY == 0 && piece == BlackQueen ||
6410 toY <= 1 && piece == BlackKnight) {
6415 if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
6416 toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
6417 toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
6424 // weed out obviously illegal Pawn moves
6425 if(appData.testLegality && (piece == WhitePawn || piece == BlackPawn) ) {
6426 if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
6427 if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
6428 if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
6429 if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
6430 // note we are not allowed to test for valid (non-)capture, due to premove
6433 // we either have a choice what to promote to, or (in Shogi) whether to promote
6434 if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6435 gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN) {
6436 *promoChoice = PieceToChar(BlackFerz); // no choice
6439 // no sense asking what we must promote to if it is going to explode...
6440 if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) {
6441 *promoChoice = PieceToChar(BlackQueen); // Queen as good as any
6444 // give caller the default choice even if we will not make it
6445 *promoChoice = ToLower(PieceToChar(defaultPromoChoice));
6446 if(gameInfo.variant == VariantShogi) *promoChoice = (defaultPromoChoice == piece ? '=' : '+');
6447 if( sweepSelect && gameInfo.variant != VariantGreat
6448 && gameInfo.variant != VariantGrand
6449 && gameInfo.variant != VariantSuper) return FALSE;
6450 if(autoQueen) return FALSE; // predetermined
6452 // suppress promotion popup on illegal moves that are not premoves
6453 premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
6454 gameMode == IcsPlayingBlack && WhiteOnMove(currentMove);
6455 if(appData.testLegality && !premove) {
6456 moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6457 fromY, fromX, toY, toX, gameInfo.variant == VariantShogi ? '+' : NULLCHAR);
6458 if(moveType != WhitePromotion && moveType != BlackPromotion)
6466 InPalace (int row, int column)
6467 { /* [HGM] for Xiangqi */
6468 if( (row < 3 || row > BOARD_HEIGHT-4) &&
6469 column < (BOARD_WIDTH + 4)/2 &&
6470 column > (BOARD_WIDTH - 5)/2 ) return TRUE;
6475 PieceForSquare (int x, int y)
6477 if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
6480 return boards[currentMove][y][x];
6484 OKToStartUserMove (int x, int y)
6486 ChessSquare from_piece;
6489 if (matchMode) return FALSE;
6490 if (gameMode == EditPosition) return TRUE;
6492 if (x >= 0 && y >= 0)
6493 from_piece = boards[currentMove][y][x];
6495 from_piece = EmptySquare;
6497 if (from_piece == EmptySquare) return FALSE;
6499 white_piece = (int)from_piece >= (int)WhitePawn &&
6500 (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
6504 case TwoMachinesPlay:
6512 case MachinePlaysWhite:
6513 case IcsPlayingBlack:
6514 if (appData.zippyPlay) return FALSE;
6516 DisplayMoveError(_("You are playing Black"));
6521 case MachinePlaysBlack:
6522 case IcsPlayingWhite:
6523 if (appData.zippyPlay) return FALSE;
6525 DisplayMoveError(_("You are playing White"));
6530 case PlayFromGameFile:
6531 if(!shiftKey || !appData.variations) return FALSE; // [HGM] allow starting variation in this mode
6533 if (!white_piece && WhiteOnMove(currentMove)) {
6534 DisplayMoveError(_("It is White's turn"));
6537 if (white_piece && !WhiteOnMove(currentMove)) {
6538 DisplayMoveError(_("It is Black's turn"));
6541 if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
6542 /* Editing correspondence game history */
6543 /* Could disallow this or prompt for confirmation */
6548 case BeginningOfGame:
6549 if (appData.icsActive) return FALSE;
6550 if (!appData.noChessProgram) {
6552 DisplayMoveError(_("You are playing White"));
6559 if (!white_piece && WhiteOnMove(currentMove)) {
6560 DisplayMoveError(_("It is White's turn"));
6563 if (white_piece && !WhiteOnMove(currentMove)) {
6564 DisplayMoveError(_("It is Black's turn"));
6573 if (currentMove != forwardMostMove && gameMode != AnalyzeMode
6574 && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
6575 && gameMode != PlayFromGameFile // [HGM] as EditGame, with protected main line
6576 && gameMode != AnalyzeFile && gameMode != Training) {
6577 DisplayMoveError(_("Displayed position is not current"));
6584 OnlyMove (int *x, int *y, Boolean captures)
6586 DisambiguateClosure cl;
6587 if (appData.zippyPlay || !appData.testLegality) return FALSE;
6589 case MachinePlaysBlack:
6590 case IcsPlayingWhite:
6591 case BeginningOfGame:
6592 if(!WhiteOnMove(currentMove)) return FALSE;
6594 case MachinePlaysWhite:
6595 case IcsPlayingBlack:
6596 if(WhiteOnMove(currentMove)) return FALSE;
6603 cl.pieceIn = EmptySquare;
6608 cl.promoCharIn = NULLCHAR;
6609 Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6610 if( cl.kind == NormalMove ||
6611 cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6612 cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6613 cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6620 if(cl.kind != ImpossibleMove) return FALSE;
6621 cl.pieceIn = EmptySquare;
6626 cl.promoCharIn = NULLCHAR;
6627 Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6628 if( cl.kind == NormalMove ||
6629 cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6630 cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6631 cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6636 autoQueen = TRUE; // act as if autoQueen on when we click to-square
6642 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
6643 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
6644 int lastLoadGameUseList = FALSE;
6645 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
6646 ChessMove lastLoadGameStart = EndOfFile;
6650 UserMoveEvent(int fromX, int fromY, int toX, int toY, int promoChar)
6654 int ff=fromX, rf=fromY, ft=toX, rt=toY;
6656 /* Check if the user is playing in turn. This is complicated because we
6657 let the user "pick up" a piece before it is his turn. So the piece he
6658 tried to pick up may have been captured by the time he puts it down!
6659 Therefore we use the color the user is supposed to be playing in this
6660 test, not the color of the piece that is currently on the starting
6661 square---except in EditGame mode, where the user is playing both
6662 sides; fortunately there the capture race can't happen. (It can
6663 now happen in IcsExamining mode, but that's just too bad. The user
6664 will get a somewhat confusing message in that case.)
6669 case TwoMachinesPlay:
6673 /* We switched into a game mode where moves are not accepted,
6674 perhaps while the mouse button was down. */
6677 case MachinePlaysWhite:
6678 /* User is moving for Black */
6679 if (WhiteOnMove(currentMove)) {
6680 DisplayMoveError(_("It is White's turn"));
6685 case MachinePlaysBlack:
6686 /* User is moving for White */
6687 if (!WhiteOnMove(currentMove)) {
6688 DisplayMoveError(_("It is Black's turn"));
6693 case PlayFromGameFile:
6694 if(!shiftKey ||!appData.variations) return; // [HGM] only variations
6697 case BeginningOfGame:
6700 if(fromY == DROP_RANK) break; // [HGM] drop moves (entered through move type-in) are automatically assigned to side-to-move
6701 if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
6702 (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
6703 /* User is moving for Black */
6704 if (WhiteOnMove(currentMove)) {
6705 DisplayMoveError(_("It is White's turn"));
6709 /* User is moving for White */
6710 if (!WhiteOnMove(currentMove)) {
6711 DisplayMoveError(_("It is Black's turn"));
6717 case IcsPlayingBlack:
6718 /* User is moving for Black */
6719 if (WhiteOnMove(currentMove)) {
6720 if (!appData.premove) {
6721 DisplayMoveError(_("It is White's turn"));
6722 } else if (toX >= 0 && toY >= 0) {
6725 premoveFromX = fromX;
6726 premoveFromY = fromY;
6727 premovePromoChar = promoChar;
6729 if (appData.debugMode)
6730 fprintf(debugFP, "Got premove: fromX %d,"
6731 "fromY %d, toX %d, toY %d\n",
6732 fromX, fromY, toX, toY);
6738 case IcsPlayingWhite:
6739 /* User is moving for White */
6740 if (!WhiteOnMove(currentMove)) {
6741 if (!appData.premove) {
6742 DisplayMoveError(_("It is Black's turn"));
6743 } else if (toX >= 0 && toY >= 0) {
6746 premoveFromX = fromX;
6747 premoveFromY = fromY;
6748 premovePromoChar = promoChar;
6750 if (appData.debugMode)
6751 fprintf(debugFP, "Got premove: fromX %d,"
6752 "fromY %d, toX %d, toY %d\n",
6753 fromX, fromY, toX, toY);
6763 /* EditPosition, empty square, or different color piece;
6764 click-click move is possible */
6765 if (toX == -2 || toY == -2) {
6766 boards[0][fromY][fromX] = EmptySquare;
6767 DrawPosition(FALSE, boards[currentMove]);
6769 } else if (toX >= 0 && toY >= 0) {
6770 boards[0][toY][toX] = boards[0][fromY][fromX];
6771 if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
6772 if(boards[0][fromY][0] != EmptySquare) {
6773 if(boards[0][fromY][1]) boards[0][fromY][1]--;
6774 if(boards[0][fromY][1] == 0) boards[0][fromY][0] = EmptySquare;
6777 if(fromX == BOARD_RGHT+1) {
6778 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
6779 if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
6780 if(boards[0][fromY][BOARD_WIDTH-2] == 0) boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
6783 boards[0][fromY][fromX] = gatingPiece;
6784 DrawPosition(FALSE, boards[currentMove]);
6790 if(toX < 0 || toY < 0) return;
6791 pup = boards[currentMove][toY][toX];
6793 /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
6794 if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ) {
6795 if( pup != EmptySquare ) return;
6796 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
6797 if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n",
6798 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
6799 // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
6800 if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
6801 fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
6802 while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++;
6806 /* [HGM] always test for legality, to get promotion info */
6807 moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6808 fromY, fromX, toY, toX, promoChar);
6810 if(fromY == DROP_RANK && fromX == EmptySquare && (gameMode == AnalyzeMode || gameMode == EditGame)) moveType = NormalMove;
6812 /* [HGM] but possibly ignore an IllegalMove result */
6813 if (appData.testLegality) {
6814 if (moveType == IllegalMove || moveType == ImpossibleMove) {
6815 DisplayMoveError(_("Illegal move"));
6820 if(doubleClick && gameMode == AnalyzeMode) { // [HGM] exclude: move entered with double-click on from square is for exclusion, not playing
6821 if(ExcludeOneMove(fromY, fromX, toY, toX, promoChar, '*')) // toggle
6822 ClearPremoveHighlights(); // was included
6823 else ClearHighlights(), SetPremoveHighlights(ff, rf, ft, rt); // exclusion indicated by premove highlights
6827 FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
6830 /* Common tail of UserMoveEvent and DropMenuEvent */
6832 FinishMove (ChessMove moveType, int fromX, int fromY, int toX, int toY, int promoChar)
6836 if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) && promoChar != NULLCHAR) {
6837 // [HGM] superchess: suppress promotions to non-available piece (but P always allowed)
6838 int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
6839 if(WhiteOnMove(currentMove)) {
6840 if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
6842 if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
6846 /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
6847 move type in caller when we know the move is a legal promotion */
6848 if(moveType == NormalMove && promoChar)
6849 moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
6851 /* [HGM] <popupFix> The following if has been moved here from
6852 UserMoveEvent(). Because it seemed to belong here (why not allow
6853 piece drops in training games?), and because it can only be
6854 performed after it is known to what we promote. */
6855 if (gameMode == Training) {
6856 /* compare the move played on the board to the next move in the
6857 * game. If they match, display the move and the opponent's response.
6858 * If they don't match, display an error message.
6862 CopyBoard(testBoard, boards[currentMove]);
6863 ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
6865 if (CompareBoards(testBoard, boards[currentMove+1])) {
6866 ForwardInner(currentMove+1);
6868 /* Autoplay the opponent's response.
6869 * if appData.animate was TRUE when Training mode was entered,
6870 * the response will be animated.
6872 saveAnimate = appData.animate;
6873 appData.animate = animateTraining;
6874 ForwardInner(currentMove+1);
6875 appData.animate = saveAnimate;
6877 /* check for the end of the game */
6878 if (currentMove >= forwardMostMove) {
6879 gameMode = PlayFromGameFile;
6881 SetTrainingModeOff();
6882 DisplayInformation(_("End of game"));
6885 DisplayError(_("Incorrect move"), 0);
6890 /* Ok, now we know that the move is good, so we can kill
6891 the previous line in Analysis Mode */
6892 if ((gameMode == AnalyzeMode || gameMode == EditGame || gameMode == PlayFromGameFile && appData.variations && shiftKey)
6893 && currentMove < forwardMostMove) {
6894 if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
6895 else forwardMostMove = currentMove;
6900 /* If we need the chess program but it's dead, restart it */
6901 ResurrectChessProgram();
6903 /* A user move restarts a paused game*/
6907 thinkOutput[0] = NULLCHAR;
6909 MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
6911 if(Adjudicate(NULL)) { // [HGM] adjudicate: take care of automatic game end
6912 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6916 if (gameMode == BeginningOfGame) {
6917 if (appData.noChessProgram) {
6918 gameMode = EditGame;
6922 gameMode = MachinePlaysBlack;
6925 snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
6927 if (first.sendName) {
6928 snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
6929 SendToProgram(buf, &first);
6936 /* Relay move to ICS or chess engine */
6937 if (appData.icsActive) {
6938 if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
6939 gameMode == IcsExamining) {
6940 if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
6941 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
6943 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6945 // also send plain move, in case ICS does not understand atomic claims
6946 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6950 if (first.sendTime && (gameMode == BeginningOfGame ||
6951 gameMode == MachinePlaysWhite ||
6952 gameMode == MachinePlaysBlack)) {
6953 SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
6955 if (gameMode != EditGame && gameMode != PlayFromGameFile && gameMode != AnalyzeMode) {
6956 // [HGM] book: if program might be playing, let it use book
6957 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
6958 first.maybeThinking = TRUE;
6959 } else if(fromY == DROP_RANK && fromX == EmptySquare) {
6960 if(!first.useSetboard) SendToProgram("undo\n", &first); // kludge to change stm in engines that do not support setboard
6961 SendBoard(&first, currentMove+1);
6962 if(second.analyzing) {
6963 if(!second.useSetboard) SendToProgram("undo\n", &second);
6964 SendBoard(&second, currentMove+1);
6967 SendMoveToProgram(forwardMostMove-1, &first);
6968 if(second.analyzing) SendMoveToProgram(forwardMostMove-1, &second);
6970 if (currentMove == cmailOldMove + 1) {
6971 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
6975 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6979 if(appData.testLegality)
6980 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
6986 if (WhiteOnMove(currentMove)) {
6987 GameEnds(BlackWins, "Black mates", GE_PLAYER);
6989 GameEnds(WhiteWins, "White mates", GE_PLAYER);
6993 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
6998 case MachinePlaysBlack:
6999 case MachinePlaysWhite:
7000 /* disable certain menu options while machine is thinking */
7001 SetMachineThinkingEnables();
7008 userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
7009 promoDefaultAltered = FALSE; // [HGM] fall back on default choice
7011 if(bookHit) { // [HGM] book: simulate book reply
7012 static char bookMove[MSG_SIZ]; // a bit generous?
7014 programStats.nodes = programStats.depth = programStats.time =
7015 programStats.score = programStats.got_only_move = 0;
7016 sprintf(programStats.movelist, "%s (xbook)", bookHit);
7018 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
7019 strcat(bookMove, bookHit);
7020 HandleMachineMove(bookMove, &first);
7026 MarkByFEN(char *fen)
7029 if(!appData.markers || !appData.highlightDragging) return;
7030 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) legal[r][f] = 0;
7031 r=BOARD_HEIGHT-1; f=BOARD_LEFT;
7035 if(*fen == 'M') legal[r][f] = 2; else // request promotion choice
7036 if(*fen >= 'A' && *fen <= 'Z') legal[r][f] = 1; else
7037 if(*fen >= 'a' && *fen <= 'z') *fen += 'A' - 'a';
7038 if(*fen == '/' && f > BOARD_LEFT) f = BOARD_LEFT, r--; else
7039 if(*fen == 'T') marker[r][f++] = 0; else
7040 if(*fen == 'Y') marker[r][f++] = 1; else
7041 if(*fen == 'G') marker[r][f++] = 3; else
7042 if(*fen == 'B') marker[r][f++] = 4; else
7043 if(*fen == 'C') marker[r][f++] = 5; else
7044 if(*fen == 'M') marker[r][f++] = 6; else
7045 if(*fen == 'W') marker[r][f++] = 7; else
7046 if(*fen == 'D') marker[r][f++] = 8; else
7047 if(*fen == 'R') marker[r][f++] = 2; else {
7048 while(*fen <= '9' && *fen >= '0') s = 10*s + *fen++ - '0';
7051 while(f >= BOARD_RGHT) f -= BOARD_RGHT - BOARD_LEFT, r--;
7055 DrawPosition(TRUE, NULL);
7059 Mark (Board board, int flags, ChessMove kind, int rf, int ff, int rt, int ft, VOIDSTAR closure)
7061 typedef char Markers[BOARD_RANKS][BOARD_FILES];
7062 Markers *m = (Markers *) closure;
7063 if(rf == fromY && ff == fromX)
7064 (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
7065 || kind == WhiteCapturesEnPassant
7066 || kind == BlackCapturesEnPassant);
7067 else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
7071 MarkTargetSquares (int clear)
7074 if(clear) { // no reason to ever suppress clearing
7075 for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) sum += marker[y][x], marker[y][x] = 0;
7076 if(!sum) return; // nothing was cleared,no redraw needed
7079 if(!appData.markers || !appData.highlightDragging || appData.icsActive && gameInfo.variant < VariantShogi ||
7080 !appData.testLegality || gameMode == EditPosition) return;
7081 GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker, EmptySquare);
7082 if(PosFlags(0) & F_MANDATORY_CAPTURE) {
7083 for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
7085 for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
7088 DrawPosition(FALSE, NULL);
7092 Explode (Board board, int fromX, int fromY, int toX, int toY)
7094 if(gameInfo.variant == VariantAtomic &&
7095 (board[toY][toX] != EmptySquare || // capture?
7096 toX != fromX && (board[fromY][fromX] == WhitePawn || // e.p. ?
7097 board[fromY][fromX] == BlackPawn )
7099 AnimateAtomicCapture(board, fromX, fromY, toX, toY);
7105 ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
7108 CanPromote (ChessSquare piece, int y)
7110 if(gameMode == EditPosition) return FALSE; // no promotions when editing position
7111 // some variants have fixed promotion piece, no promotion at all, or another selection mechanism
7112 if(gameInfo.variant == VariantShogi || gameInfo.variant == VariantXiangqi ||
7113 gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat ||
7114 gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
7115 gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN) return FALSE;
7116 return (piece == BlackPawn && y == 1 ||
7117 piece == WhitePawn && y == BOARD_HEIGHT-2 ||
7118 piece == BlackLance && y == 1 ||
7119 piece == WhiteLance && y == BOARD_HEIGHT-2 );
7123 HoverEvent (int hiX, int hiY, int x, int y)
7125 static char baseMarker[BOARD_RANKS][BOARD_FILES], baseLegal[BOARD_RANKS][BOARD_FILES];
7127 if(!first.highlight) return;
7128 if(hiX == -1 && hiY == -1 && x == fromX && y == fromY) // record markings
7129 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7130 baseMarker[r][f] = marker[r][f], baseLegal[r][f] = legal[r][f];
7131 else if(hiX != x || hiY != y) {
7132 // [HGM] lift: entered new to-square; redraw arrow, and inform engine
7133 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7134 marker[r][f] = baseMarker[r][f], legal[r][f] = baseLegal[r][f];
7135 if((marker[y][x] == 2 || marker[y][x] == 6) && legal[y][x]) {
7137 snprintf(buf, MSG_SIZ, "hover %c%d\n", x + AAA, y + ONE - '0');
7138 SendToProgram(buf, &first);
7140 SetHighlights(fromX, fromY, x, y);
7144 void ReportClick(char *action, int x, int y)
7146 char buf[MSG_SIZ]; // Inform engine of what user does
7148 if(action[0] == 'l') // mark any target square of a lifted piece as legal to-square, clear markers
7149 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) legal[r][f] = 1, marker[r][f] = 0;
7150 if(!first.highlight || gameMode == EditPosition) return;
7151 snprintf(buf, MSG_SIZ, "%s %c%d%s\n", action, x+AAA, y+ONE-'0', controlKey && action[0]=='p' ? "," : "");
7152 SendToProgram(buf, &first);
7156 LeftClick (ClickType clickType, int xPix, int yPix)
7159 Boolean saveAnimate;
7160 static int second = 0, promotionChoice = 0, clearFlag = 0, sweepSelecting = 0;
7161 char promoChoice = NULLCHAR;
7163 static TimeMark lastClickTime, prevClickTime;
7165 if(SeekGraphClick(clickType, xPix, yPix, 0)) return;
7167 prevClickTime = lastClickTime; GetTimeMark(&lastClickTime);
7169 if (clickType == Press) ErrorPopDown();
7170 lastClickType = clickType, lastLeftX = xPix, lastLeftY = yPix; // [HGM] alien: remember state
7172 x = EventToSquare(xPix, BOARD_WIDTH);
7173 y = EventToSquare(yPix, BOARD_HEIGHT);
7174 if (!flipView && y >= 0) {
7175 y = BOARD_HEIGHT - 1 - y;
7177 if (flipView && x >= 0) {
7178 x = BOARD_WIDTH - 1 - x;
7181 if(promoSweep != EmptySquare) { // up-click during sweep-select of promo-piece
7182 defaultPromoChoice = promoSweep;
7183 promoSweep = EmptySquare; // terminate sweep
7184 promoDefaultAltered = TRUE;
7185 if(!selectFlag && !sweepSelecting && (x != toX || y != toY)) x = fromX, y = fromY; // and fake up-click on same square if we were still selecting
7188 if(promotionChoice) { // we are waiting for a click to indicate promotion piece
7189 if(clickType == Release) return; // ignore upclick of click-click destination
7190 promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
7191 if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
7192 if(gameInfo.holdingsWidth &&
7193 (WhiteOnMove(currentMove)
7194 ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y >= 0
7195 : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT) ) {
7196 // click in right holdings, for determining promotion piece
7197 ChessSquare p = boards[currentMove][y][x];
7198 if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
7199 if(p == WhitePawn || p == BlackPawn) p = EmptySquare; // [HGM] Pawns could be valid as deferral
7200 if(p != EmptySquare || gameInfo.variant == VariantGrand && toY != 0 && toY != BOARD_HEIGHT-1) { // [HGM] grand: empty square means defer
7201 FinishMove(NormalMove, fromX, fromY, toX, toY, p==EmptySquare ? NULLCHAR : ToLower(PieceToChar(p)));
7206 DrawPosition(FALSE, boards[currentMove]);
7210 /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
7211 if(clickType == Press
7212 && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
7213 || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
7214 || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
7217 if(gotPremove && x == premoveFromX && y == premoveFromY && clickType == Release) {
7218 // could be static click on premove from-square: abort premove
7220 ClearPremoveHighlights();
7223 if(clickType == Press && fromX == x && fromY == y && promoDefaultAltered && SubtractTimeMarks(&lastClickTime, &prevClickTime) >= 200)
7224 fromX = fromY = -1; // second click on piece after altering default promo piece treated as first click
7226 if(!promoDefaultAltered) { // determine default promotion piece, based on the side the user is moving for
7227 int side = (gameMode == IcsPlayingWhite || gameMode == MachinePlaysBlack ||
7228 gameMode != MachinePlaysWhite && gameMode != IcsPlayingBlack && WhiteOnMove(currentMove));
7229 defaultPromoChoice = DefaultPromoChoice(side);
7232 autoQueen = appData.alwaysPromoteToQueen;
7236 gatingPiece = EmptySquare;
7237 if (clickType != Press) {
7238 if(dragging) { // [HGM] from-square must have been reset due to game end since last press
7239 DragPieceEnd(xPix, yPix); dragging = 0;
7240 DrawPosition(FALSE, NULL);
7244 doubleClick = FALSE;
7245 if(gameMode == AnalyzeMode && (pausing || controlKey) && first.excludeMoves) { // use pause state to exclude moves
7246 doubleClick = TRUE; gatingPiece = boards[currentMove][y][x];
7248 fromX = x; fromY = y; toX = toY = -1;
7249 if(!appData.oneClick || !OnlyMove(&x, &y, FALSE) ||
7250 // even if only move, we treat as normal when this would trigger a promotion popup, to allow sweep selection
7251 appData.sweepSelect && CanPromote(boards[currentMove][fromY][fromX], fromY) && originalY != y) {
7253 if (OKToStartUserMove(fromX, fromY)) {
7255 ReportClick("lift", x, y);
7256 MarkTargetSquares(0);
7257 if(gameMode == EditPosition && controlKey) gatingPiece = boards[currentMove][fromY][fromX];
7258 DragPieceBegin(xPix, yPix, FALSE); dragging = 1;
7259 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][fromY][fromX], fromY)) {
7260 promoSweep = defaultPromoChoice;
7261 selectFlag = 0; lastX = xPix; lastY = yPix;
7262 Sweep(0); // Pawn that is going to promote: preview promotion piece
7263 DisplayMessage("", _("Pull pawn backwards to under-promote"));
7265 if (appData.highlightDragging) {
7266 SetHighlights(fromX, fromY, -1, -1);
7270 } else fromX = fromY = -1;
7276 if (clickType == Press && gameMode != EditPosition) {
7281 // ignore off-board to clicks
7282 if(y < 0 || x < 0) return;
7284 /* Check if clicking again on the same color piece */
7285 fromP = boards[currentMove][fromY][fromX];
7286 toP = boards[currentMove][y][x];
7287 frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess;
7288 if ((WhitePawn <= fromP && fromP <= WhiteKing &&
7289 WhitePawn <= toP && toP <= WhiteKing &&
7290 !(fromP == WhiteKing && toP == WhiteRook && frc) &&
7291 !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
7292 (BlackPawn <= fromP && fromP <= BlackKing &&
7293 BlackPawn <= toP && toP <= BlackKing &&
7294 !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
7295 !(fromP == BlackKing && toP == BlackRook && frc))) {
7296 /* Clicked again on same color piece -- changed his mind */
7297 second = (x == fromX && y == fromY);
7298 if(second && gameMode == AnalyzeMode && SubtractTimeMarks(&lastClickTime, &prevClickTime) < 200) {
7299 second = FALSE; // first double-click rather than scond click
7300 doubleClick = first.excludeMoves; // used by UserMoveEvent to recognize exclude moves
7302 promoDefaultAltered = FALSE;
7303 MarkTargetSquares(1);
7304 if(!(second && appData.oneClick && OnlyMove(&x, &y, TRUE))) {
7305 if (appData.highlightDragging) {
7306 SetHighlights(x, y, -1, -1);
7310 if (OKToStartUserMove(x, y)) {
7311 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
7312 (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
7313 y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
7314 gatingPiece = boards[currentMove][fromY][fromX];
7315 else gatingPiece = doubleClick ? fromP : EmptySquare;
7317 fromY = y; dragging = 1;
7318 ReportClick("lift", x, y);
7319 MarkTargetSquares(0);
7320 DragPieceBegin(xPix, yPix, FALSE);
7321 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) {
7322 promoSweep = defaultPromoChoice;
7323 selectFlag = 0; lastX = xPix; lastY = yPix;
7324 Sweep(0); // Pawn that is going to promote: preview promotion piece
7328 if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
7331 // ignore clicks on holdings
7332 if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
7335 if (clickType == Release && x == fromX && y == fromY) {
7336 DragPieceEnd(xPix, yPix); dragging = 0;
7338 // a deferred attempt to click-click move an empty square on top of a piece
7339 boards[currentMove][y][x] = EmptySquare;
7341 DrawPosition(FALSE, boards[currentMove]);
7342 fromX = fromY = -1; clearFlag = 0;
7345 if (appData.animateDragging) {
7346 /* Undo animation damage if any */
7347 DrawPosition(FALSE, NULL);
7349 if (second || sweepSelecting) {
7350 /* Second up/down in same square; just abort move */
7351 if(sweepSelecting) DrawPosition(FALSE, boards[currentMove]);
7352 second = sweepSelecting = 0;
7354 gatingPiece = EmptySquare;
7355 MarkTargetSquares(1);
7358 ClearPremoveHighlights();
7360 /* First upclick in same square; start click-click mode */
7361 SetHighlights(x, y, -1, -1);
7368 if(gameMode != EditPosition && !appData.testLegality && !legal[y][x]) {
7369 if(dragging) DragPieceEnd(xPix, yPix), dragging = 0;
7370 DisplayMessage(_("only marked squares are legal"),"");
7371 DrawPosition(TRUE, NULL);
7372 return; // ignore to-click
7375 /* we now have a different from- and (possibly off-board) to-square */
7376 /* Completed move */
7377 if(!sweepSelecting) {
7380 } else sweepSelecting = 0; // this must be the up-click corresponding to the down-click that started the sweep
7382 saveAnimate = appData.animate;
7383 if (clickType == Press) {
7384 if(gameMode == EditPosition && boards[currentMove][fromY][fromX] == EmptySquare) {
7385 // must be Edit Position mode with empty-square selected
7386 fromX = x; fromY = y; DragPieceBegin(xPix, yPix, FALSE); dragging = 1; // consider this a new attempt to drag
7387 if(x >= BOARD_LEFT && x < BOARD_RGHT) clearFlag = 1; // and defer click-click move of empty-square to up-click
7390 if(legal[y][x] == 2 || HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, FALSE)) {
7391 if(appData.sweepSelect) {
7392 ChessSquare piece = boards[currentMove][fromY][fromX];
7393 promoSweep = defaultPromoChoice;
7394 if(PieceToChar(PROMOTED piece) == '+') promoSweep = PROMOTED piece;
7395 selectFlag = 0; lastX = xPix; lastY = yPix;
7396 Sweep(0); // Pawn that is going to promote: preview promotion piece
7398 DisplayMessage("", _("Pull pawn backwards to under-promote"));
7399 MarkTargetSquares(1);
7401 return; // promo popup appears on up-click
7403 /* Finish clickclick move */
7404 if (appData.animate || appData.highlightLastMove) {
7405 SetHighlights(fromX, fromY, toX, toY);
7411 // [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
7412 /* Finish drag move */
7413 if (appData.highlightLastMove) {
7414 SetHighlights(fromX, fromY, toX, toY);
7419 DragPieceEnd(xPix, yPix); dragging = 0;
7420 /* Don't animate move and drag both */
7421 appData.animate = FALSE;
7424 // moves into holding are invalid for now (except in EditPosition, adapting to-square)
7425 if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
7426 ChessSquare piece = boards[currentMove][fromY][fromX];
7427 if(gameMode == EditPosition && piece != EmptySquare &&
7428 fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
7431 if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
7432 n = PieceToNumber(piece - (int)BlackPawn);
7433 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
7434 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
7435 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
7437 if(x == BOARD_RGHT+1 && piece < BlackPawn) {
7438 n = PieceToNumber(piece);
7439 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
7440 boards[currentMove][n][BOARD_WIDTH-1] = piece;
7441 boards[currentMove][n][BOARD_WIDTH-2]++;
7443 boards[currentMove][fromY][fromX] = EmptySquare;
7447 MarkTargetSquares(1);
7448 DrawPosition(TRUE, boards[currentMove]);
7452 // off-board moves should not be highlighted
7453 if(x < 0 || y < 0) ClearHighlights();
7454 else ReportClick("put", x, y);
7456 if(gatingPiece != EmptySquare && gameInfo.variant == VariantSChess) promoChoice = ToLower(PieceToChar(gatingPiece));
7458 if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, appData.sweepSelect)) {
7459 SetHighlights(fromX, fromY, toX, toY);
7460 MarkTargetSquares(1);
7461 if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
7462 // [HGM] super: promotion to captured piece selected from holdings
7463 ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
7464 promotionChoice = TRUE;
7465 // kludge follows to temporarily execute move on display, without promoting yet
7466 boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
7467 boards[currentMove][toY][toX] = p;
7468 DrawPosition(FALSE, boards[currentMove]);
7469 boards[currentMove][fromY][fromX] = p; // take back, but display stays
7470 boards[currentMove][toY][toX] = q;
7471 DisplayMessage("Click in holdings to choose piece", "");
7476 int oldMove = currentMove;
7477 UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
7478 if (!appData.highlightLastMove || gotPremove) ClearHighlights();
7479 if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
7480 if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
7481 Explode(boards[currentMove-1], fromX, fromY, toX, toY))
7482 DrawPosition(TRUE, boards[currentMove]);
7483 MarkTargetSquares(1);
7486 appData.animate = saveAnimate;
7487 if (appData.animate || appData.animateDragging) {
7488 /* Undo animation damage if needed */
7489 DrawPosition(FALSE, NULL);
7494 RightClick (ClickType action, int x, int y, int *fromX, int *fromY)
7495 { // front-end-free part taken out of PieceMenuPopup
7496 int whichMenu; int xSqr, ySqr;
7498 if(seekGraphUp) { // [HGM] seekgraph
7499 if(action == Press) SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
7500 if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
7504 if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
7505 && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
7506 if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
7507 if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
7508 if(action == Press) {
7509 originalFlip = flipView;
7510 flipView = !flipView; // temporarily flip board to see game from partners perspective
7511 DrawPosition(TRUE, partnerBoard);
7512 DisplayMessage(partnerStatus, "");
7514 } else if(action == Release) {
7515 flipView = originalFlip;
7516 DrawPosition(TRUE, boards[currentMove]);
7522 xSqr = EventToSquare(x, BOARD_WIDTH);
7523 ySqr = EventToSquare(y, BOARD_HEIGHT);
7524 if (action == Release) {
7525 if(pieceSweep != EmptySquare) {
7526 EditPositionMenuEvent(pieceSweep, toX, toY);
7527 pieceSweep = EmptySquare;
7528 } else UnLoadPV(); // [HGM] pv
7530 if (action != Press) return -2; // return code to be ignored
7533 if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;
7535 if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;
7536 if (xSqr < 0 || ySqr < 0) return -1;
7537 if(appData.pieceMenu) { whichMenu = 0; break; } // edit-position menu
7538 pieceSweep = shiftKey ? BlackPawn : WhitePawn; // [HGM] sweep: prepare selecting piece by mouse sweep
7539 toX = xSqr; toY = ySqr; lastX = x, lastY = y;
7540 if(flipView) toX = BOARD_WIDTH - 1 - toX; else toY = BOARD_HEIGHT - 1 - toY;
7544 if(!appData.icsEngineAnalyze) return -1;
7545 case IcsPlayingWhite:
7546 case IcsPlayingBlack:
7547 if(!appData.zippyPlay) goto noZip;
7550 case MachinePlaysWhite:
7551 case MachinePlaysBlack:
7552 case TwoMachinesPlay: // [HGM] pv: use for showing PV
7553 if (!appData.dropMenu) {
7555 return 2; // flag front-end to grab mouse events
7557 if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
7558 gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
7561 if (xSqr < 0 || ySqr < 0) return -1;
7562 if (!appData.dropMenu || appData.testLegality &&
7563 gameInfo.variant != VariantBughouse &&
7564 gameInfo.variant != VariantCrazyhouse) return -1;
7565 whichMenu = 1; // drop menu
7571 if (((*fromX = xSqr) < 0) ||
7572 ((*fromY = ySqr) < 0)) {
7573 *fromX = *fromY = -1;
7577 *fromX = BOARD_WIDTH - 1 - *fromX;
7579 *fromY = BOARD_HEIGHT - 1 - *fromY;
7585 SendProgramStatsToFrontend (ChessProgramState * cps, ChessProgramStats * cpstats)
7587 // char * hint = lastHint;
7588 FrontEndProgramStats stats;
7590 stats.which = cps == &first ? 0 : 1;
7591 stats.depth = cpstats->depth;
7592 stats.nodes = cpstats->nodes;
7593 stats.score = cpstats->score;
7594 stats.time = cpstats->time;
7595 stats.pv = cpstats->movelist;
7596 stats.hint = lastHint;
7597 stats.an_move_index = 0;
7598 stats.an_move_count = 0;
7600 if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
7601 stats.hint = cpstats->move_name;
7602 stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
7603 stats.an_move_count = cpstats->nr_moves;
7606 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
7608 SetProgramStats( &stats );
7612 ClearEngineOutputPane (int which)
7614 static FrontEndProgramStats dummyStats;
7615 dummyStats.which = which;
7616 dummyStats.pv = "#";
7617 SetProgramStats( &dummyStats );
7620 #define MAXPLAYERS 500
7623 TourneyStandings (int display)
7625 int i, w, b, color, wScore, bScore, dummy, nr=0, nPlayers=0;
7626 int score[MAXPLAYERS], ranking[MAXPLAYERS], points[MAXPLAYERS], games[MAXPLAYERS];
7627 char result, *p, *names[MAXPLAYERS];
7629 if(appData.tourneyType < 0 && !strchr(appData.results, '*'))
7630 return strdup(_("Swiss tourney finished")); // standings of Swiss yet TODO
7631 names[0] = p = strdup(appData.participants);
7632 while(p = strchr(p, '\n')) *p++ = NULLCHAR, names[++nPlayers] = p; // count participants
7634 for(i=0; i<nPlayers; i++) score[i] = games[i] = 0;
7636 while(result = appData.results[nr]) {
7637 color = Pairing(nr, nPlayers, &w, &b, &dummy);
7638 if(!(color ^ matchGame & 1)) { dummy = w; w = b; b = dummy; }
7639 wScore = bScore = 0;
7641 case '+': wScore = 2; break;
7642 case '-': bScore = 2; break;
7643 case '=': wScore = bScore = 1; break;
7645 case '*': return strdup("busy"); // tourney not finished
7653 if(appData.tourneyType > 0) nPlayers = appData.tourneyType; // in gauntlet, list only gauntlet engine(s)
7654 for(w=0; w<nPlayers; w++) {
7656 for(i=0; i<nPlayers; i++) if(score[i] > bScore) bScore = score[i], b = i;
7657 ranking[w] = b; points[w] = bScore; score[b] = -2;
7659 p = malloc(nPlayers*34+1);
7660 for(w=0; w<nPlayers && w<display; w++)
7661 sprintf(p+34*w, "%2d. %5.1f/%-3d %-19.19s\n", w+1, points[w]/2., games[ranking[w]], names[ranking[w]]);
7667 Count (Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
7668 { // count all piece types
7670 *nB = *nW = *wStale = *bStale = *bishopColor = 0;
7671 for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
7672 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
7675 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
7676 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
7677 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
7678 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
7679 p == BlackBishop || p == BlackFerz || p == BlackAlfil )
7680 *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
7685 SufficientDefence (int pCnt[], int side, int nMine, int nHis)
7687 int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
7688 int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
7690 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
7691 if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
7692 if(myPawns == 2 && nMine == 3) // KPP
7693 return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
7694 if(myPawns == 1 && nMine == 2) // KP
7695 return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
7696 if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
7697 return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
7698 if(myPawns) return FALSE;
7699 if(pCnt[WhiteRook+side])
7700 return pCnt[BlackRook-side] ||
7701 pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
7702 pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
7703 pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
7704 if(pCnt[WhiteCannon+side]) {
7705 if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
7706 return majorDefense || pCnt[BlackAlfil-side] >= 2;
7708 if(pCnt[WhiteKnight+side])
7709 return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
7714 MatingPotential (int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
7716 VariantClass v = gameInfo.variant;
7718 if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
7719 if(v == VariantShatranj) return TRUE; // always winnable through baring
7720 if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
7721 if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
7723 if(v == VariantXiangqi) {
7724 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
7726 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
7727 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
7728 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
7729 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
7730 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
7731 if(stale) // we have at least one last-rank P plus perhaps C
7732 return majors // KPKX
7733 || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
7735 return pCnt[WhiteFerz+side] // KCAK
7736 || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
7737 || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
7738 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
7740 } else if(v == VariantKnightmate) {
7741 if(nMine == 1) return FALSE;
7742 if(nMine == 2 && nHis == 1 && pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side] + pCnt[WhiteKnight+side]) return FALSE; // KBK is only draw
7743 } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
7744 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
7746 if(nMine == 1) return FALSE; // bare King
7747 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
7748 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
7749 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
7750 // by now we have King + 1 piece (or multiple Bishops on the same color)
7751 if(pCnt[WhiteKnight+side])
7752 return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
7753 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
7754 || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
7756 return (pCnt[BlackKnight-side]); // KBKN, KFKN
7757 if(pCnt[WhiteAlfil+side])
7758 return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
7759 if(pCnt[WhiteWazir+side])
7760 return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
7767 CompareWithRights (Board b1, Board b2)
7770 if(!CompareBoards(b1, b2)) return FALSE;
7771 if(b1[EP_STATUS] != b2[EP_STATUS]) return FALSE;
7772 /* compare castling rights */
7773 if( b1[CASTLING][2] != b2[CASTLING][2] && (b2[CASTLING][0] != NoRights || b2[CASTLING][1] != NoRights) )
7774 rights++; /* King lost rights, while rook still had them */
7775 if( b1[CASTLING][2] != NoRights ) { /* king has rights */
7776 if( b1[CASTLING][0] != b2[CASTLING][0] || b1[CASTLING][1] != b2[CASTLING][1] )
7777 rights++; /* but at least one rook lost them */
7779 if( b1[CASTLING][5] != b1[CASTLING][5] && (b2[CASTLING][3] != NoRights || b2[CASTLING][4] != NoRights) )
7781 if( b1[CASTLING][5] != NoRights ) {
7782 if( b1[CASTLING][3] != b2[CASTLING][3] || b1[CASTLING][4] != b2[CASTLING][4] )
7789 Adjudicate (ChessProgramState *cps)
7790 { // [HGM] some adjudications useful with buggy engines
7791 // [HGM] adjudicate: made into separate routine, which now can be called after every move
7792 // In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
7793 // Actually ending the game is now based on the additional internal condition canAdjudicate.
7794 // Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
7795 int k, drop, count = 0; static int bare = 1;
7796 ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
7797 Boolean canAdjudicate = !appData.icsActive;
7799 // most tests only when we understand the game, i.e. legality-checking on
7800 if( appData.testLegality )
7801 { /* [HGM] Some more adjudications for obstinate engines */
7802 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+1], i;
7803 static int moveCount = 6;
7805 char *reason = NULL;
7807 /* Count what is on board. */
7808 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
7810 /* Some material-based adjudications that have to be made before stalemate test */
7811 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
7812 // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
7813 boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
7814 if(canAdjudicate && appData.checkMates) {
7816 SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7817 GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
7818 "Xboard adjudication: King destroyed", GE_XBOARD );
7823 /* Bare King in Shatranj (loses) or Losers (wins) */
7824 if( nrW == 1 || nrB == 1) {
7825 if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
7826 boards[forwardMostMove][EP_STATUS] = EP_WINS; // mark as win, so it becomes claimable
7827 if(canAdjudicate && appData.checkMates) {
7829 SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
7830 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7831 "Xboard adjudication: Bare king", GE_XBOARD );
7835 if( gameInfo.variant == VariantShatranj && --bare < 0)
7837 boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
7838 if(canAdjudicate && appData.checkMates) {
7839 /* but only adjudicate if adjudication enabled */
7841 SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7842 GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
7843 "Xboard adjudication: Bare king", GE_XBOARD );
7850 // don't wait for engine to announce game end if we can judge ourselves
7851 switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
7853 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
7854 int i, checkCnt = 0; // (should really be done by making nr of checks part of game state)
7855 for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
7856 if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
7859 reason = "Xboard adjudication: 3rd check";
7860 boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
7870 reason = "Xboard adjudication: Stalemate";
7871 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
7872 boards[forwardMostMove][EP_STATUS] = EP_STALEMATE; // default result for stalemate is draw
7873 if(gameInfo.variant == VariantLosers || gameInfo.variant == VariantGiveaway) // [HGM] losers:
7874 boards[forwardMostMove][EP_STATUS] = EP_WINS; // in these variants stalemated is always a win
7875 else if(gameInfo.variant == VariantSuicide) // in suicide it depends
7876 boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
7877 ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
7878 EP_CHECKMATE : EP_WINS);
7879 else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi)
7880 boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
7884 reason = "Xboard adjudication: Checkmate";
7885 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
7886 if(gameInfo.variant == VariantShogi) {
7887 if(forwardMostMove > backwardMostMove
7888 && moveList[forwardMostMove-1][1] == '@'
7889 && CharToPiece(ToUpper(moveList[forwardMostMove-1][0])) == WhitePawn) {
7890 reason = "XBoard adjudication: pawn-drop mate";
7891 boards[forwardMostMove][EP_STATUS] = EP_WINS;
7897 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
7899 result = GameIsDrawn; break;
7901 result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
7903 result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
7907 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
7909 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7910 GameEnds( result, reason, GE_XBOARD );
7914 /* Next absolutely insufficient mating material. */
7915 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
7916 !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
7917 { /* includes KBK, KNK, KK of KBKB with like Bishops */
7919 /* always flag draws, for judging claims */
7920 boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
7922 if(canAdjudicate && appData.materialDraws) {
7923 /* but only adjudicate them if adjudication enabled */
7924 if(engineOpponent) {
7925 SendToProgram("force\n", engineOpponent); // suppress reply
7926 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
7928 GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
7933 /* Then some trivial draws (only adjudicate, cannot be claimed) */
7934 if(gameInfo.variant == VariantXiangqi ?
7935 SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
7937 ( nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
7938 || nr[WhiteQueen] && nr[BlackQueen]==1 /* KQKQ */
7939 || nr[WhiteKnight]==2 || nr[BlackKnight]==2 /* KNNK */
7940 || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
7942 if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
7943 { /* if the first 3 moves do not show a tactical win, declare draw */
7944 if(engineOpponent) {
7945 SendToProgram("force\n", engineOpponent); // suppress reply
7946 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7948 GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
7951 } else moveCount = 6;
7954 // Repetition draws and 50-move rule can be applied independently of legality testing
7956 /* Check for rep-draws */
7958 drop = gameInfo.holdingsSize && (gameInfo.variant != VariantSuper && gameInfo.variant != VariantSChess
7959 && gameInfo.variant != VariantGreat && gameInfo.variant != VariantGrand);
7960 for(k = forwardMostMove-2;
7961 k>=backwardMostMove && k>=forwardMostMove-100 && (drop ||
7962 (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
7963 (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE);
7966 if(CompareBoards(boards[k], boards[forwardMostMove])) {
7967 /* compare castling rights */
7968 if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
7969 (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
7970 rights++; /* King lost rights, while rook still had them */
7971 if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
7972 if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
7973 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
7974 rights++; /* but at least one rook lost them */
7976 if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
7977 (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
7979 if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
7980 if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
7981 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
7984 if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
7985 && appData.drawRepeats > 1) {
7986 /* adjudicate after user-specified nr of repeats */
7987 int result = GameIsDrawn;
7988 char *details = "XBoard adjudication: repetition draw";
7989 if((gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi) && appData.testLegality) {
7990 // [HGM] xiangqi: check for forbidden perpetuals
7991 int m, ourPerpetual = 1, hisPerpetual = 1;
7992 for(m=forwardMostMove; m>k; m-=2) {
7993 if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
7994 ourPerpetual = 0; // the current mover did not always check
7995 if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
7996 hisPerpetual = 0; // the opponent did not always check
7998 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
7999 ourPerpetual, hisPerpetual);
8000 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
8001 result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8002 details = "Xboard adjudication: perpetual checking";
8004 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
8005 break; // (or we would have caught him before). Abort repetition-checking loop.
8007 if(gameInfo.variant == VariantShogi) { // in Shogi other repetitions are draws
8008 if(BOARD_HEIGHT == 5 && BOARD_RGHT - BOARD_LEFT == 5) { // but in mini-Shogi gote wins!
8010 details = "Xboard adjudication: repetition";
8012 } else // it must be XQ
8013 // Now check for perpetual chases
8014 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
8015 hisPerpetual = PerpetualChase(k, forwardMostMove);
8016 ourPerpetual = PerpetualChase(k+1, forwardMostMove);
8017 if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
8018 static char resdet[MSG_SIZ];
8019 result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8021 snprintf(resdet, MSG_SIZ, "Xboard adjudication: perpetual chasing of %c%c", ourPerpetual>>8, ourPerpetual&255);
8023 if(hisPerpetual && !ourPerpetual) // he is chasing us, but did not repeat yet
8024 break; // Abort repetition-checking loop.
8026 // if neither of us is checking or chasing all the time, or both are, it is draw
8028 if(engineOpponent) {
8029 SendToProgram("force\n", engineOpponent); // suppress reply
8030 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8032 GameEnds( result, details, GE_XBOARD );
8035 if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
8036 boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
8040 /* Now we test for 50-move draws. Determine ply count */
8041 count = forwardMostMove;
8042 /* look for last irreversble move */
8043 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
8045 /* if we hit starting position, add initial plies */
8046 if( count == backwardMostMove )
8047 count -= initialRulePlies;
8048 count = forwardMostMove - count;
8049 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
8050 // adjust reversible move counter for checks in Xiangqi
8051 int i = forwardMostMove - count, inCheck = 0, lastCheck;
8052 if(i < backwardMostMove) i = backwardMostMove;
8053 while(i <= forwardMostMove) {
8054 lastCheck = inCheck; // check evasion does not count
8055 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
8056 if(inCheck || lastCheck) count--; // check does not count
8061 boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
8062 /* this is used to judge if draw claims are legal */
8063 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
8064 if(engineOpponent) {
8065 SendToProgram("force\n", engineOpponent); // suppress reply
8066 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8068 GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
8072 /* if draw offer is pending, treat it as a draw claim
8073 * when draw condition present, to allow engines a way to
8074 * claim draws before making their move to avoid a race
8075 * condition occurring after their move
8077 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
8079 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
8080 p = "Draw claim: 50-move rule";
8081 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
8082 p = "Draw claim: 3-fold repetition";
8083 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
8084 p = "Draw claim: insufficient mating material";
8085 if( p != NULL && canAdjudicate) {
8086 if(engineOpponent) {
8087 SendToProgram("force\n", engineOpponent); // suppress reply
8088 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8090 GameEnds( GameIsDrawn, p, GE_XBOARD );
8095 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
8096 if(engineOpponent) {
8097 SendToProgram("force\n", engineOpponent); // suppress reply
8098 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8100 GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
8107 SendMoveToBookUser (int moveNr, ChessProgramState *cps, int initial)
8108 { // [HGM] book: this routine intercepts moves to simulate book replies
8109 char *bookHit = NULL;
8111 //first determine if the incoming move brings opponent into his book
8112 if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
8113 bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
8114 if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
8115 if(bookHit != NULL && !cps->bookSuspend) {
8116 // make sure opponent is not going to reply after receiving move to book position
8117 SendToProgram("force\n", cps);
8118 cps->bookSuspend = TRUE; // flag indicating it has to be restarted
8120 if(bookHit) setboardSpoiledMachineBlack = FALSE; // suppress 'go' in SendMoveToProgram
8121 if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
8122 // now arrange restart after book miss
8124 // after a book hit we never send 'go', and the code after the call to this routine
8125 // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
8126 char buf[MSG_SIZ], *move = bookHit;
8128 int fromX, fromY, toX, toY;
8132 if (ParseOneMove(bookHit, forwardMostMove, &moveType,
8133 &fromX, &fromY, &toX, &toY, &promoChar)) {
8134 (void) CoordsToAlgebraic(boards[forwardMostMove],
8135 PosFlags(forwardMostMove),
8136 fromY, fromX, toY, toX, promoChar, move);
8138 if(appData.debugMode) fprintf(debugFP, "Book move could not be parsed\n");
8142 snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), move); // force book move into program supposed to play it
8143 SendToProgram(buf, cps);
8144 if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
8145 } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
8146 SendToProgram("go\n", cps);
8147 cps->bookSuspend = FALSE; // after a 'go' we are never suspended
8148 } else { // 'go' might be sent based on 'firstMove' after this routine returns
8149 if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
8150 SendToProgram("go\n", cps);
8151 cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
8153 return bookHit; // notify caller of hit, so it can take action to send move to opponent
8157 LoadError (char *errmess, ChessProgramState *cps)
8158 { // unloads engine and switches back to -ncp mode if it was first
8159 if(cps->initDone) return FALSE;
8160 cps->isr = NULL; // this should suppress further error popups from breaking pipes
8161 DestroyChildProcess(cps->pr, 9 ); // just to be sure
8164 appData.noChessProgram = TRUE;
8165 gameMode = MachinePlaysBlack; ModeHighlight(); // kludge to unmark Machine Black menu
8166 gameMode = BeginningOfGame; ModeHighlight();
8169 if(GetDelayedEvent()) CancelDelayedEvent(), ThawUI(); // [HGM] cancel remaining loading effort scheduled after feature timeout
8170 DisplayMessage("", ""); // erase waiting message
8171 if(errmess) DisplayError(errmess, 0); // announce reason, if given
8176 ChessProgramState *savedState;
8178 DeferredBookMove (void)
8180 if(savedState->lastPing != savedState->lastPong)
8181 ScheduleDelayedEvent(DeferredBookMove, 10);
8183 HandleMachineMove(savedMessage, savedState);
8186 static int savedWhitePlayer, savedBlackPlayer, pairingReceived;
8187 static ChessProgramState *stalledEngine;
8188 static char stashedInputMove[MSG_SIZ];
8191 HandleMachineMove (char *message, ChessProgramState *cps)
8193 char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
8194 char realname[MSG_SIZ];
8195 int fromX, fromY, toX, toY;
8199 int machineWhite, oldError;
8202 if(cps == &pairing && sscanf(message, "%d-%d", &savedWhitePlayer, &savedBlackPlayer) == 2) {
8203 // [HGM] pairing: Mega-hack! Pairing engine also uses this routine (so it could give other WB commands).
8204 if(savedWhitePlayer == 0 || savedBlackPlayer == 0) {
8205 DisplayError(_("Invalid pairing from pairing engine"), 0);
8208 pairingReceived = 1;
8210 return; // Skim the pairing messages here.
8213 oldError = cps->userError; cps->userError = 0;
8215 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
8217 * Kludge to ignore BEL characters
8219 while (*message == '\007') message++;
8222 * [HGM] engine debug message: ignore lines starting with '#' character
8224 if(cps->debug && *message == '#') return;
8227 * Look for book output
8229 if (cps == &first && bookRequested) {
8230 if (message[0] == '\t' || message[0] == ' ') {
8231 /* Part of the book output is here; append it */
8232 strcat(bookOutput, message);
8233 strcat(bookOutput, " \n");
8235 } else if (bookOutput[0] != NULLCHAR) {
8236 /* All of book output has arrived; display it */
8237 char *p = bookOutput;
8238 while (*p != NULLCHAR) {
8239 if (*p == '\t') *p = ' ';
8242 DisplayInformation(bookOutput);
8243 bookRequested = FALSE;
8244 /* Fall through to parse the current output */
8249 * Look for machine move.
8251 if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
8252 (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
8254 if(pausing && !cps->pause) { // for pausing engine that does not support 'pause', we stash its move for processing when we resume.
8255 if(appData.debugMode) fprintf(debugFP, "pause %s engine after move\n", cps->which);
8256 safeStrCpy(stashedInputMove, message, MSG_SIZ);
8257 stalledEngine = cps;
8258 if(appData.ponderNextMove) { // bring opponent out of ponder
8259 if(gameMode == TwoMachinesPlay) {
8260 if(cps->other->pause)
8261 PauseEngine(cps->other);
8263 SendToProgram("easy\n", cps->other);
8270 /* This method is only useful on engines that support ping */
8271 if (cps->lastPing != cps->lastPong) {
8272 if (gameMode == BeginningOfGame) {
8273 /* Extra move from before last new; ignore */
8274 if (appData.debugMode) {
8275 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8278 if (appData.debugMode) {
8279 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8280 cps->which, gameMode);
8283 SendToProgram("undo\n", cps);
8289 case BeginningOfGame:
8290 /* Extra move from before last reset; ignore */
8291 if (appData.debugMode) {
8292 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8299 /* Extra move after we tried to stop. The mode test is
8300 not a reliable way of detecting this problem, but it's
8301 the best we can do on engines that don't support ping.
8303 if (appData.debugMode) {
8304 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8305 cps->which, gameMode);
8307 SendToProgram("undo\n", cps);
8310 case MachinePlaysWhite:
8311 case IcsPlayingWhite:
8312 machineWhite = TRUE;
8315 case MachinePlaysBlack:
8316 case IcsPlayingBlack:
8317 machineWhite = FALSE;
8320 case TwoMachinesPlay:
8321 machineWhite = (cps->twoMachinesColor[0] == 'w');
8324 if (WhiteOnMove(forwardMostMove) != machineWhite) {
8325 if (appData.debugMode) {
8327 "Ignoring move out of turn by %s, gameMode %d"
8328 ", forwardMost %d\n",
8329 cps->which, gameMode, forwardMostMove);
8334 if(cps->alphaRank) AlphaRank(machineMove, 4);
8335 if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
8336 &fromX, &fromY, &toX, &toY, &promoChar)) {
8337 /* Machine move could not be parsed; ignore it. */
8338 snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
8339 machineMove, _(cps->which));
8340 DisplayMoveError(buf1);
8341 snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
8342 machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
8343 if (gameMode == TwoMachinesPlay) {
8344 GameEnds(machineWhite ? BlackWins : WhiteWins,
8350 /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
8351 /* So we have to redo legality test with true e.p. status here, */
8352 /* to make sure an illegal e.p. capture does not slip through, */
8353 /* to cause a forfeit on a justified illegal-move complaint */
8354 /* of the opponent. */
8355 if( gameMode==TwoMachinesPlay && appData.testLegality ) {
8357 moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
8358 fromY, fromX, toY, toX, promoChar);
8359 if(moveType == IllegalMove) {
8360 snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
8361 machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
8362 GameEnds(machineWhite ? BlackWins : WhiteWins,
8365 } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
8366 /* [HGM] Kludge to handle engines that send FRC-style castling
8367 when they shouldn't (like TSCP-Gothic) */
8369 case WhiteASideCastleFR:
8370 case BlackASideCastleFR:
8372 currentMoveString[2]++;
8374 case WhiteHSideCastleFR:
8375 case BlackHSideCastleFR:
8377 currentMoveString[2]--;
8379 default: ; // nothing to do, but suppresses warning of pedantic compilers
8382 hintRequested = FALSE;
8383 lastHint[0] = NULLCHAR;
8384 bookRequested = FALSE;
8385 /* Program may be pondering now */
8386 cps->maybeThinking = TRUE;
8387 if (cps->sendTime == 2) cps->sendTime = 1;
8388 if (cps->offeredDraw) cps->offeredDraw--;
8390 /* [AS] Save move info*/
8391 pvInfoList[ forwardMostMove ].score = programStats.score;
8392 pvInfoList[ forwardMostMove ].depth = programStats.depth;
8393 pvInfoList[ forwardMostMove ].time = programStats.time; // [HGM] PGNtime: take time from engine stats
8395 MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
8397 /* Test suites abort the 'game' after one move */
8398 if(*appData.finger) {
8400 char *fen = PositionToFEN(backwardMostMove, NULL, 0); // no counts in EPD
8401 if(!f) f = fopen(appData.finger, "w");
8402 if(f) fprintf(f, "%s bm %s;\n", fen, parseList[backwardMostMove]), fflush(f);
8403 else { DisplayFatalError("Bad output file", errno, 0); return; }
8405 GameEnds(GameUnfinished, NULL, GE_XBOARD);
8408 /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
8409 if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
8412 while( count < adjudicateLossPlies ) {
8413 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
8416 score = -score; /* Flip score for winning side */
8419 if( score > adjudicateLossThreshold ) {
8426 if( count >= adjudicateLossPlies ) {
8427 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8429 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
8430 "Xboard adjudication",
8437 if(Adjudicate(cps)) {
8438 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8439 return; // [HGM] adjudicate: for all automatic game ends
8443 if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
8445 if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
8446 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
8448 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8450 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8452 if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
8453 char buf[3*MSG_SIZ];
8455 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
8456 programStats.score / 100.,
8458 programStats.time / 100.,
8459 (unsigned int)programStats.nodes,
8460 (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
8461 programStats.movelist);
8463 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
8468 /* [AS] Clear stats for next move */
8469 ClearProgramStats();
8470 thinkOutput[0] = NULLCHAR;
8471 hiddenThinkOutputState = 0;
8474 if (gameMode == TwoMachinesPlay) {
8475 /* [HGM] relaying draw offers moved to after reception of move */
8476 /* and interpreting offer as claim if it brings draw condition */
8477 if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
8478 SendToProgram("draw\n", cps->other);
8480 if (cps->other->sendTime) {
8481 SendTimeRemaining(cps->other,
8482 cps->other->twoMachinesColor[0] == 'w');
8484 bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
8485 if (firstMove && !bookHit) {
8487 if (cps->other->useColors) {
8488 SendToProgram(cps->other->twoMachinesColor, cps->other);
8490 SendToProgram("go\n", cps->other);
8492 cps->other->maybeThinking = TRUE;
8495 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8497 if (!pausing && appData.ringBellAfterMoves) {
8502 * Reenable menu items that were disabled while
8503 * machine was thinking
8505 if (gameMode != TwoMachinesPlay)
8506 SetUserThinkingEnables();
8508 // [HGM] book: after book hit opponent has received move and is now in force mode
8509 // force the book reply into it, and then fake that it outputted this move by jumping
8510 // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
8512 static char bookMove[MSG_SIZ]; // a bit generous?
8514 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
8515 strcat(bookMove, bookHit);
8518 programStats.nodes = programStats.depth = programStats.time =
8519 programStats.score = programStats.got_only_move = 0;
8520 sprintf(programStats.movelist, "%s (xbook)", bookHit);
8522 if(cps->lastPing != cps->lastPong) {
8523 savedMessage = message; // args for deferred call
8525 ScheduleDelayedEvent(DeferredBookMove, 10);
8534 /* Set special modes for chess engines. Later something general
8535 * could be added here; for now there is just one kludge feature,
8536 * needed because Crafty 15.10 and earlier don't ignore SIGINT
8537 * when "xboard" is given as an interactive command.
8539 if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
8540 cps->useSigint = FALSE;
8541 cps->useSigterm = FALSE;
8543 if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
8544 ParseFeatures(message+8, cps);
8545 return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
8548 if (!strncmp(message, "setup ", 6) &&
8549 (!appData.testLegality || gameInfo.variant == VariantFairy || gameInfo.variant == VariantUnknown || NonStandardBoardSize())
8550 ) { // [HGM] allow first engine to define opening position
8551 int dummy, w, h, hand, s=6; char buf[MSG_SIZ], varName[MSG_SIZ];
8552 if(appData.icsActive || forwardMostMove != 0 || cps != &first) return;
8554 if(sscanf(message, "setup (%s", buf) == 1) s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTable(pieceToChar, buf);
8555 if(startedFromSetupPosition) return;
8556 dummy = sscanf(message+s, "%dx%d+%d_%s", &w, &h, &hand, varName);
8558 while(message[s] && message[s++] != ' ');
8559 if(BOARD_HEIGHT != h || BOARD_WIDTH != w + 4*(hand != 0) || gameInfo.holdingsSize != hand ||
8560 dummy == 4 && gameInfo.variant != StringToVariant(varName) ) { // engine wants to change board format or variant
8561 appData.NrFiles = w; appData.NrRanks = h; appData.holdingsSize = hand;
8562 if(dummy == 4) gameInfo.variant = StringToVariant(varName); // parent variant
8563 InitPosition(1); // calls InitDrawingSizes to let new parameters take effect
8564 if(*buf) SetCharTable(pieceToChar, buf); // do again, for it was spoiled by InitPosition
8567 ParseFEN(boards[0], &dummy, message+s);
8568 DrawPosition(TRUE, boards[0]);
8569 startedFromSetupPosition = TRUE;
8572 /* [HGM] Allow engine to set up a position. Don't ask me why one would
8573 * want this, I was asked to put it in, and obliged.
8575 if (!strncmp(message, "setboard ", 9)) {
8576 Board initial_position;
8578 GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
8580 if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
8581 DisplayError(_("Bad FEN received from engine"), 0);
8585 CopyBoard(boards[0], initial_position);
8586 initialRulePlies = FENrulePlies;
8587 if(blackPlaysFirst) gameMode = MachinePlaysWhite;
8588 else gameMode = MachinePlaysBlack;
8589 DrawPosition(FALSE, boards[currentMove]);
8595 * Look for communication commands
8597 if (!strncmp(message, "telluser ", 9)) {
8598 if(message[9] == '\\' && message[10] == '\\')
8599 EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
8601 DisplayNote(message + 9);
8604 if (!strncmp(message, "tellusererror ", 14)) {
8606 if(message[14] == '\\' && message[15] == '\\')
8607 EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
8609 DisplayError(message + 14, 0);
8612 if (!strncmp(message, "tellopponent ", 13)) {
8613 if (appData.icsActive) {
8615 snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
8619 DisplayNote(message + 13);
8623 if (!strncmp(message, "tellothers ", 11)) {
8624 if (appData.icsActive) {
8626 snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
8629 } else if(appData.autoComment) AppendComment (forwardMostMove, message + 11, 1); // in local mode, add as move comment
8632 if (!strncmp(message, "tellall ", 8)) {
8633 if (appData.icsActive) {
8635 snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
8639 DisplayNote(message + 8);
8643 if (strncmp(message, "warning", 7) == 0) {
8644 /* Undocumented feature, use tellusererror in new code */
8645 DisplayError(message, 0);
8648 if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
8649 safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
8650 strcat(realname, " query");
8651 AskQuestion(realname, buf2, buf1, cps->pr);
8654 /* Commands from the engine directly to ICS. We don't allow these to be
8655 * sent until we are logged on. Crafty kibitzes have been known to
8656 * interfere with the login process.
8659 if (!strncmp(message, "tellics ", 8)) {
8660 SendToICS(message + 8);
8664 if (!strncmp(message, "tellicsnoalias ", 15)) {
8665 SendToICS(ics_prefix);
8666 SendToICS(message + 15);
8670 /* The following are for backward compatibility only */
8671 if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
8672 !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
8673 SendToICS(ics_prefix);
8679 if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
8682 if(!strncmp(message, "highlight ", 10)) {
8683 if(appData.testLegality && appData.markers) return;
8684 MarkByFEN(message+10); // [HGM] alien: allow engine to mark board squares
8687 if(!strncmp(message, "click ", 6)) {
8688 char f, c=0; int x, y; // [HGM] alien: allow engine to finish user moves (i.e. engine-driven one-click moving)
8689 if(appData.testLegality || !appData.oneClick) return;
8690 sscanf(message+6, "%c%d%c", &f, &y, &c);
8691 x = f - 'a' + BOARD_LEFT, y -= ONE - '0';
8692 if(flipView) x = BOARD_WIDTH-1 - x; else y = BOARD_HEIGHT-1 - y;
8693 x = x*squareSize + (x+1)*lineGap + squareSize/2;
8694 y = y*squareSize + (y+1)*lineGap + squareSize/2;
8695 f = first.highlight; first.highlight = 0; // kludge to suppress lift/put in response to own clicks
8696 if(lastClickType == Press) // if button still down, fake release on same square, to be ready for next click
8697 LeftClick(Release, lastLeftX, lastLeftY);
8698 controlKey = (c == ',');
8699 LeftClick(Press, x, y);
8700 LeftClick(Release, x, y);
8701 first.highlight = f;
8705 * If the move is illegal, cancel it and redraw the board.
8706 * Also deal with other error cases. Matching is rather loose
8707 * here to accommodate engines written before the spec.
8709 if (strncmp(message + 1, "llegal move", 11) == 0 ||
8710 strncmp(message, "Error", 5) == 0) {
8711 if (StrStr(message, "name") ||
8712 StrStr(message, "rating") || StrStr(message, "?") ||
8713 StrStr(message, "result") || StrStr(message, "board") ||
8714 StrStr(message, "bk") || StrStr(message, "computer") ||
8715 StrStr(message, "variant") || StrStr(message, "hint") ||
8716 StrStr(message, "random") || StrStr(message, "depth") ||
8717 StrStr(message, "accepted")) {
8720 if (StrStr(message, "protover")) {
8721 /* Program is responding to input, so it's apparently done
8722 initializing, and this error message indicates it is
8723 protocol version 1. So we don't need to wait any longer
8724 for it to initialize and send feature commands. */
8725 FeatureDone(cps, 1);
8726 cps->protocolVersion = 1;
8729 cps->maybeThinking = FALSE;
8731 if (StrStr(message, "draw")) {
8732 /* Program doesn't have "draw" command */
8733 cps->sendDrawOffers = 0;
8736 if (cps->sendTime != 1 &&
8737 (StrStr(message, "time") || StrStr(message, "otim"))) {
8738 /* Program apparently doesn't have "time" or "otim" command */
8742 if (StrStr(message, "analyze")) {
8743 cps->analysisSupport = FALSE;
8744 cps->analyzing = FALSE;
8745 // Reset(FALSE, TRUE); // [HGM] this caused discrepancy between display and internal state!
8746 EditGameEvent(); // [HGM] try to preserve loaded game
8747 snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
8748 DisplayError(buf2, 0);
8751 if (StrStr(message, "(no matching move)st")) {
8752 /* Special kludge for GNU Chess 4 only */
8753 cps->stKludge = TRUE;
8754 SendTimeControl(cps, movesPerSession, timeControl,
8755 timeIncrement, appData.searchDepth,
8759 if (StrStr(message, "(no matching move)sd")) {
8760 /* Special kludge for GNU Chess 4 only */
8761 cps->sdKludge = TRUE;
8762 SendTimeControl(cps, movesPerSession, timeControl,
8763 timeIncrement, appData.searchDepth,
8767 if (!StrStr(message, "llegal")) {
8770 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8771 gameMode == IcsIdle) return;
8772 if (forwardMostMove <= backwardMostMove) return;
8773 if (pausing) PauseEvent();
8774 if(appData.forceIllegal) {
8775 // [HGM] illegal: machine refused move; force position after move into it
8776 SendToProgram("force\n", cps);
8777 if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
8778 // we have a real problem now, as SendBoard will use the a2a3 kludge
8779 // when black is to move, while there might be nothing on a2 or black
8780 // might already have the move. So send the board as if white has the move.
8781 // But first we must change the stm of the engine, as it refused the last move
8782 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
8783 if(WhiteOnMove(forwardMostMove)) {
8784 SendToProgram("a7a6\n", cps); // for the engine black still had the move
8785 SendBoard(cps, forwardMostMove); // kludgeless board
8787 SendToProgram("a2a3\n", cps); // for the engine white still had the move
8788 CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
8789 SendBoard(cps, forwardMostMove+1); // kludgeless board
8791 } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
8792 if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
8793 gameMode == TwoMachinesPlay)
8794 SendToProgram("go\n", cps);
8797 if (gameMode == PlayFromGameFile) {
8798 /* Stop reading this game file */
8799 gameMode = EditGame;
8802 /* [HGM] illegal-move claim should forfeit game when Xboard */
8803 /* only passes fully legal moves */
8804 if( appData.testLegality && gameMode == TwoMachinesPlay ) {
8805 GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
8806 "False illegal-move claim", GE_XBOARD );
8807 return; // do not take back move we tested as valid
8809 currentMove = forwardMostMove-1;
8810 DisplayMove(currentMove-1); /* before DisplayMoveError */
8811 SwitchClocks(forwardMostMove-1); // [HGM] race
8812 DisplayBothClocks();
8813 snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
8814 parseList[currentMove], _(cps->which));
8815 DisplayMoveError(buf1);
8816 DrawPosition(FALSE, boards[currentMove]);
8818 SetUserThinkingEnables();
8821 if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
8822 /* Program has a broken "time" command that
8823 outputs a string not ending in newline.
8829 * If chess program startup fails, exit with an error message.
8830 * Attempts to recover here are futile. [HGM] Well, we try anyway
8832 if ((StrStr(message, "unknown host") != NULL)
8833 || (StrStr(message, "No remote directory") != NULL)
8834 || (StrStr(message, "not found") != NULL)
8835 || (StrStr(message, "No such file") != NULL)
8836 || (StrStr(message, "can't alloc") != NULL)
8837 || (StrStr(message, "Permission denied") != NULL)) {
8839 cps->maybeThinking = FALSE;
8840 snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
8841 _(cps->which), cps->program, cps->host, message);
8842 RemoveInputSource(cps->isr);
8843 if(appData.icsActive) DisplayFatalError(buf1, 0, 1); else {
8844 if(LoadError(oldError ? NULL : buf1, cps)) return; // error has then been handled by LoadError
8845 if(!oldError) DisplayError(buf1, 0); // if reason neatly announced, suppress general error popup
8851 * Look for hint output
8853 if (sscanf(message, "Hint: %s", buf1) == 1) {
8854 if (cps == &first && hintRequested) {
8855 hintRequested = FALSE;
8856 if (ParseOneMove(buf1, forwardMostMove, &moveType,
8857 &fromX, &fromY, &toX, &toY, &promoChar)) {
8858 (void) CoordsToAlgebraic(boards[forwardMostMove],
8859 PosFlags(forwardMostMove),
8860 fromY, fromX, toY, toX, promoChar, buf1);
8861 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
8862 DisplayInformation(buf2);
8864 /* Hint move could not be parsed!? */
8865 snprintf(buf2, sizeof(buf2),
8866 _("Illegal hint move \"%s\"\nfrom %s chess program"),
8867 buf1, _(cps->which));
8868 DisplayError(buf2, 0);
8871 safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
8877 * Ignore other messages if game is not in progress
8879 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8880 gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
8883 * look for win, lose, draw, or draw offer
8885 if (strncmp(message, "1-0", 3) == 0) {
8886 char *p, *q, *r = "";
8887 p = strchr(message, '{');
8895 GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
8897 } else if (strncmp(message, "0-1", 3) == 0) {
8898 char *p, *q, *r = "";
8899 p = strchr(message, '{');
8907 /* Kludge for Arasan 4.1 bug */
8908 if (strcmp(r, "Black resigns") == 0) {
8909 GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
8912 GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
8914 } else if (strncmp(message, "1/2", 3) == 0) {
8915 char *p, *q, *r = "";
8916 p = strchr(message, '{');
8925 GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
8928 } else if (strncmp(message, "White resign", 12) == 0) {
8929 GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8931 } else if (strncmp(message, "Black resign", 12) == 0) {
8932 GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8934 } else if (strncmp(message, "White matches", 13) == 0 ||
8935 strncmp(message, "Black matches", 13) == 0 ) {
8936 /* [HGM] ignore GNUShogi noises */
8938 } else if (strncmp(message, "White", 5) == 0 &&
8939 message[5] != '(' &&
8940 StrStr(message, "Black") == NULL) {
8941 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8943 } else if (strncmp(message, "Black", 5) == 0 &&
8944 message[5] != '(') {
8945 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8947 } else if (strcmp(message, "resign") == 0 ||
8948 strcmp(message, "computer resigns") == 0) {
8950 case MachinePlaysBlack:
8951 case IcsPlayingBlack:
8952 GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
8954 case MachinePlaysWhite:
8955 case IcsPlayingWhite:
8956 GameEnds(BlackWins, "White resigns", GE_ENGINE);
8958 case TwoMachinesPlay:
8959 if (cps->twoMachinesColor[0] == 'w')
8960 GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8962 GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8969 } else if (strncmp(message, "opponent mates", 14) == 0) {
8971 case MachinePlaysBlack:
8972 case IcsPlayingBlack:
8973 GameEnds(WhiteWins, "White mates", GE_ENGINE);
8975 case MachinePlaysWhite:
8976 case IcsPlayingWhite:
8977 GameEnds(BlackWins, "Black mates", GE_ENGINE);
8979 case TwoMachinesPlay:
8980 if (cps->twoMachinesColor[0] == 'w')
8981 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8983 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8990 } else if (strncmp(message, "computer mates", 14) == 0) {
8992 case MachinePlaysBlack:
8993 case IcsPlayingBlack:
8994 GameEnds(BlackWins, "Black mates", GE_ENGINE1);
8996 case MachinePlaysWhite:
8997 case IcsPlayingWhite:
8998 GameEnds(WhiteWins, "White mates", GE_ENGINE);
9000 case TwoMachinesPlay:
9001 if (cps->twoMachinesColor[0] == 'w')
9002 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9004 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9011 } else if (strncmp(message, "checkmate", 9) == 0) {
9012 if (WhiteOnMove(forwardMostMove)) {
9013 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9015 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9018 } else if (strstr(message, "Draw") != NULL ||
9019 strstr(message, "game is a draw") != NULL) {
9020 GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
9022 } else if (strstr(message, "offer") != NULL &&
9023 strstr(message, "draw") != NULL) {
9025 if (appData.zippyPlay && first.initDone) {
9026 /* Relay offer to ICS */
9027 SendToICS(ics_prefix);
9028 SendToICS("draw\n");
9031 cps->offeredDraw = 2; /* valid until this engine moves twice */
9032 if (gameMode == TwoMachinesPlay) {
9033 if (cps->other->offeredDraw) {
9034 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
9035 /* [HGM] in two-machine mode we delay relaying draw offer */
9036 /* until after we also have move, to see if it is really claim */
9038 } else if (gameMode == MachinePlaysWhite ||
9039 gameMode == MachinePlaysBlack) {
9040 if (userOfferedDraw) {
9041 DisplayInformation(_("Machine accepts your draw offer"));
9042 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
9044 DisplayInformation(_("Machine offers a draw.\nSelect Action / Draw to accept."));
9051 * Look for thinking output
9053 if ( appData.showThinking // [HGM] thinking: test all options that cause this output
9054 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
9056 int plylev, mvleft, mvtot, curscore, time;
9057 char mvname[MOVE_LEN];
9061 int prefixHint = FALSE;
9062 mvname[0] = NULLCHAR;
9065 case MachinePlaysBlack:
9066 case IcsPlayingBlack:
9067 if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
9069 case MachinePlaysWhite:
9070 case IcsPlayingWhite:
9071 if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
9076 case IcsObserving: /* [DM] icsEngineAnalyze */
9077 if (!appData.icsEngineAnalyze) ignore = TRUE;
9079 case TwoMachinesPlay:
9080 if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
9090 ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
9092 if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
9093 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
9095 if (plyext != ' ' && plyext != '\t') {
9099 /* [AS] Negate score if machine is playing black and reporting absolute scores */
9100 if( cps->scoreIsAbsolute &&
9101 ( gameMode == MachinePlaysBlack ||
9102 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
9103 gameMode == IcsPlayingBlack || // [HGM] also add other situations where engine should report black POV
9104 (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
9105 !WhiteOnMove(currentMove)
9108 curscore = -curscore;
9111 if(appData.pvSAN[cps==&second]) pv = PvToSAN(buf1);
9113 if(serverMoves && (time > 100 || time == 0 && plylev > 7)) {
9116 snprintf(buf, MSG_SIZ, "%s", appData.serverMovesName);
9117 buf[strlen(buf)-1] = gameMode == MachinePlaysWhite ? 'w' :
9118 gameMode == MachinePlaysBlack ? 'b' : cps->twoMachinesColor[0];
9119 if(appData.debugMode) fprintf(debugFP, "write PV on file '%s'\n", buf);
9120 if(f = fopen(buf, "w")) { // export PV to applicable PV file
9121 fprintf(f, "%5.2f/%-2d %s", curscore/100., plylev, pv);
9125 /* TRANSLATORS: PV = principal variation, the variation the chess engine thinks is the best for everyone */
9126 DisplayError(_("failed writing PV"), 0);
9129 tempStats.depth = plylev;
9130 tempStats.nodes = nodes;
9131 tempStats.time = time;
9132 tempStats.score = curscore;
9133 tempStats.got_only_move = 0;
9135 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
9138 if(cps->nps == 0) ticklen = 10*time; // use engine reported time
9139 else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
9140 if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
9141 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
9142 whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
9143 if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
9144 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
9145 blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
9148 /* Buffer overflow protection */
9149 if (pv[0] != NULLCHAR) {
9150 if (strlen(pv) >= sizeof(tempStats.movelist)
9151 && appData.debugMode) {
9153 "PV is too long; using the first %u bytes.\n",
9154 (unsigned) sizeof(tempStats.movelist) - 1);
9157 safeStrCpy( tempStats.movelist, pv, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
9159 sprintf(tempStats.movelist, " no PV\n");
9162 if (tempStats.seen_stat) {
9163 tempStats.ok_to_send = 1;
9166 if (strchr(tempStats.movelist, '(') != NULL) {
9167 tempStats.line_is_book = 1;
9168 tempStats.nr_moves = 0;
9169 tempStats.moves_left = 0;
9171 tempStats.line_is_book = 0;
9174 if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
9175 programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
9177 SendProgramStatsToFrontend( cps, &tempStats );
9180 [AS] Protect the thinkOutput buffer from overflow... this
9181 is only useful if buf1 hasn't overflowed first!
9183 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%+.2f %s%s",
9185 (gameMode == TwoMachinesPlay ?
9186 ToUpper(cps->twoMachinesColor[0]) : ' '),
9187 ((double) curscore) / 100.0,
9188 prefixHint ? lastHint : "",
9189 prefixHint ? " " : "" );
9191 if( buf1[0] != NULLCHAR ) {
9192 unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
9194 if( strlen(pv) > max_len ) {
9195 if( appData.debugMode) {
9196 fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
9198 pv[max_len+1] = '\0';
9201 strcat( thinkOutput, pv);
9204 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
9205 || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9206 DisplayMove(currentMove - 1);
9210 } else if ((p=StrStr(message, "(only move)")) != NULL) {
9211 /* crafty (9.25+) says "(only move) <move>"
9212 * if there is only 1 legal move
9214 sscanf(p, "(only move) %s", buf1);
9215 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
9216 sprintf(programStats.movelist, "%s (only move)", buf1);
9217 programStats.depth = 1;
9218 programStats.nr_moves = 1;
9219 programStats.moves_left = 1;
9220 programStats.nodes = 1;
9221 programStats.time = 1;
9222 programStats.got_only_move = 1;
9224 /* Not really, but we also use this member to
9225 mean "line isn't going to change" (Crafty
9226 isn't searching, so stats won't change) */
9227 programStats.line_is_book = 1;
9229 SendProgramStatsToFrontend( cps, &programStats );
9231 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9232 gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9233 DisplayMove(currentMove - 1);
9236 } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
9237 &time, &nodes, &plylev, &mvleft,
9238 &mvtot, mvname) >= 5) {
9239 /* The stat01: line is from Crafty (9.29+) in response
9240 to the "." command */
9241 programStats.seen_stat = 1;
9242 cps->maybeThinking = TRUE;
9244 if (programStats.got_only_move || !appData.periodicUpdates)
9247 programStats.depth = plylev;
9248 programStats.time = time;
9249 programStats.nodes = nodes;
9250 programStats.moves_left = mvleft;
9251 programStats.nr_moves = mvtot;
9252 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
9253 programStats.ok_to_send = 1;
9254 programStats.movelist[0] = '\0';
9256 SendProgramStatsToFrontend( cps, &programStats );
9260 } else if (strncmp(message,"++",2) == 0) {
9261 /* Crafty 9.29+ outputs this */
9262 programStats.got_fail = 2;
9265 } else if (strncmp(message,"--",2) == 0) {
9266 /* Crafty 9.29+ outputs this */
9267 programStats.got_fail = 1;
9270 } else if (thinkOutput[0] != NULLCHAR &&
9271 strncmp(message, " ", 4) == 0) {
9272 unsigned message_len;
9275 while (*p && *p == ' ') p++;
9277 message_len = strlen( p );
9279 /* [AS] Avoid buffer overflow */
9280 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
9281 strcat(thinkOutput, " ");
9282 strcat(thinkOutput, p);
9285 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
9286 strcat(programStats.movelist, " ");
9287 strcat(programStats.movelist, p);
9290 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9291 gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9292 DisplayMove(currentMove - 1);
9300 if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
9301 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
9303 ChessProgramStats cpstats;
9305 if (plyext != ' ' && plyext != '\t') {
9309 /* [AS] Negate score if machine is playing black and reporting absolute scores */
9310 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
9311 curscore = -curscore;
9314 cpstats.depth = plylev;
9315 cpstats.nodes = nodes;
9316 cpstats.time = time;
9317 cpstats.score = curscore;
9318 cpstats.got_only_move = 0;
9319 cpstats.movelist[0] = '\0';
9321 if (buf1[0] != NULLCHAR) {
9322 safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
9325 cpstats.ok_to_send = 0;
9326 cpstats.line_is_book = 0;
9327 cpstats.nr_moves = 0;
9328 cpstats.moves_left = 0;
9330 SendProgramStatsToFrontend( cps, &cpstats );
9337 /* Parse a game score from the character string "game", and
9338 record it as the history of the current game. The game
9339 score is NOT assumed to start from the standard position.
9340 The display is not updated in any way.
9343 ParseGameHistory (char *game)
9346 int fromX, fromY, toX, toY, boardIndex;
9351 if (appData.debugMode)
9352 fprintf(debugFP, "Parsing game history: %s\n", game);
9354 if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
9355 gameInfo.site = StrSave(appData.icsHost);
9356 gameInfo.date = PGNDate();
9357 gameInfo.round = StrSave("-");
9359 /* Parse out names of players */
9360 while (*game == ' ') game++;
9362 while (*game != ' ') *p++ = *game++;
9364 gameInfo.white = StrSave(buf);
9365 while (*game == ' ') game++;
9367 while (*game != ' ' && *game != '\n') *p++ = *game++;
9369 gameInfo.black = StrSave(buf);
9372 boardIndex = blackPlaysFirst ? 1 : 0;
9375 yyboardindex = boardIndex;
9376 moveType = (ChessMove) Myylex();
9378 case IllegalMove: /* maybe suicide chess, etc. */
9379 if (appData.debugMode) {
9380 fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
9381 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9382 setbuf(debugFP, NULL);
9384 case WhitePromotion:
9385 case BlackPromotion:
9386 case WhiteNonPromotion:
9387 case BlackNonPromotion:
9389 case WhiteCapturesEnPassant:
9390 case BlackCapturesEnPassant:
9391 case WhiteKingSideCastle:
9392 case WhiteQueenSideCastle:
9393 case BlackKingSideCastle:
9394 case BlackQueenSideCastle:
9395 case WhiteKingSideCastleWild:
9396 case WhiteQueenSideCastleWild:
9397 case BlackKingSideCastleWild:
9398 case BlackQueenSideCastleWild:
9400 case WhiteHSideCastleFR:
9401 case WhiteASideCastleFR:
9402 case BlackHSideCastleFR:
9403 case BlackASideCastleFR:
9405 fromX = currentMoveString[0] - AAA;
9406 fromY = currentMoveString[1] - ONE;
9407 toX = currentMoveString[2] - AAA;
9408 toY = currentMoveString[3] - ONE;
9409 promoChar = currentMoveString[4];
9413 if(currentMoveString[0] == '@') continue; // no null moves in ICS mode!
9414 fromX = moveType == WhiteDrop ?
9415 (int) CharToPiece(ToUpper(currentMoveString[0])) :
9416 (int) CharToPiece(ToLower(currentMoveString[0]));
9418 toX = currentMoveString[2] - AAA;
9419 toY = currentMoveString[3] - ONE;
9420 promoChar = NULLCHAR;
9424 snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
9425 if (appData.debugMode) {
9426 fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
9427 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9428 setbuf(debugFP, NULL);
9430 DisplayError(buf, 0);
9432 case ImpossibleMove:
9434 snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
9435 if (appData.debugMode) {
9436 fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
9437 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9438 setbuf(debugFP, NULL);
9440 DisplayError(buf, 0);
9443 if (boardIndex < backwardMostMove) {
9444 /* Oops, gap. How did that happen? */
9445 DisplayError(_("Gap in move list"), 0);
9448 backwardMostMove = blackPlaysFirst ? 1 : 0;
9449 if (boardIndex > forwardMostMove) {
9450 forwardMostMove = boardIndex;
9454 if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
9455 strcat(parseList[boardIndex-1], " ");
9456 strcat(parseList[boardIndex-1], yy_text);
9468 case GameUnfinished:
9469 if (gameMode == IcsExamining) {
9470 if (boardIndex < backwardMostMove) {
9471 /* Oops, gap. How did that happen? */
9474 backwardMostMove = blackPlaysFirst ? 1 : 0;
9477 gameInfo.result = moveType;
9478 p = strchr(yy_text, '{');
9479 if (p == NULL) p = strchr(yy_text, '(');
9482 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
9484 q = strchr(p, *p == '{' ? '}' : ')');
9485 if (q != NULL) *q = NULLCHAR;
9488 while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
9489 gameInfo.resultDetails = StrSave(p);
9492 if (boardIndex >= forwardMostMove &&
9493 !(gameMode == IcsObserving && ics_gamenum == -1)) {
9494 backwardMostMove = blackPlaysFirst ? 1 : 0;
9497 (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
9498 fromY, fromX, toY, toX, promoChar,
9499 parseList[boardIndex]);
9500 CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
9501 /* currentMoveString is set as a side-effect of yylex */
9502 safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
9503 strcat(moveList[boardIndex], "\n");
9505 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
9506 switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
9512 if(gameInfo.variant != VariantShogi)
9513 strcat(parseList[boardIndex - 1], "+");
9517 strcat(parseList[boardIndex - 1], "#");
9524 /* Apply a move to the given board */
9526 ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board)
9528 ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
9529 int promoRank = gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand ? 3 : 1;
9531 /* [HGM] compute & store e.p. status and castling rights for new position */
9532 /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
9534 if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
9535 oldEP = (signed char)board[EP_STATUS];
9536 board[EP_STATUS] = EP_NONE;
9538 if (fromY == DROP_RANK) {
9540 if(fromX == EmptySquare) { // [HGM] pass: empty drop encodes null move; nothing to change.
9541 board[EP_STATUS] = EP_CAPTURE; // null move considered irreversible
9544 piece = board[toY][toX] = (ChessSquare) fromX;
9548 if( board[toY][toX] != EmptySquare )
9549 board[EP_STATUS] = EP_CAPTURE;
9551 if( board[fromY][fromX] == WhiteLance || board[fromY][fromX] == BlackLance ) {
9552 if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi )
9553 board[EP_STATUS] = EP_PAWN_MOVE; // Lance is Pawn-like in most variants
9555 if( board[fromY][fromX] == WhitePawn ) {
9556 if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9557 board[EP_STATUS] = EP_PAWN_MOVE;
9559 if(toX>BOARD_LEFT && board[toY][toX-1] == BlackPawn &&
9560 gameInfo.variant != VariantBerolina || toX < fromX)
9561 board[EP_STATUS] = toX | berolina;
9562 if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
9563 gameInfo.variant != VariantBerolina || toX > fromX)
9564 board[EP_STATUS] = toX;
9567 if( board[fromY][fromX] == BlackPawn ) {
9568 if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9569 board[EP_STATUS] = EP_PAWN_MOVE;
9570 if( toY-fromY== -2) {
9571 if(toX>BOARD_LEFT && board[toY][toX-1] == WhitePawn &&
9572 gameInfo.variant != VariantBerolina || toX < fromX)
9573 board[EP_STATUS] = toX | berolina;
9574 if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
9575 gameInfo.variant != VariantBerolina || toX > fromX)
9576 board[EP_STATUS] = toX;
9580 for(i=0; i<nrCastlingRights; i++) {
9581 if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
9582 board[CASTLING][i] == toX && castlingRank[i] == toY
9583 ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
9586 if(gameInfo.variant == VariantSChess) { // update virginity
9587 if(fromY == 0) board[VIRGIN][fromX] &= ~VIRGIN_W; // loss by moving
9588 if(fromY == BOARD_HEIGHT-1) board[VIRGIN][fromX] &= ~VIRGIN_B;
9589 if(toY == 0) board[VIRGIN][toX] &= ~VIRGIN_W; // loss by capture
9590 if(toY == BOARD_HEIGHT-1) board[VIRGIN][toX] &= ~VIRGIN_B;
9593 if (fromX == toX && fromY == toY) return;
9595 piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
9596 king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
9597 if(gameInfo.variant == VariantKnightmate)
9598 king += (int) WhiteUnicorn - (int) WhiteKing;
9600 /* Code added by Tord: */
9601 /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
9602 if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
9603 board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
9604 board[fromY][fromX] = EmptySquare;
9605 board[toY][toX] = EmptySquare;
9606 if((toX > fromX) != (piece == WhiteRook)) {
9607 board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
9609 board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
9611 } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
9612 board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
9613 board[fromY][fromX] = EmptySquare;
9614 board[toY][toX] = EmptySquare;
9615 if((toX > fromX) != (piece == BlackRook)) {
9616 board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
9618 board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
9620 /* End of code added by Tord */
9622 } else if (board[fromY][fromX] == king
9623 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9624 && toY == fromY && toX > fromX+1) {
9625 board[fromY][fromX] = EmptySquare;
9626 board[toY][toX] = king;
9627 board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9628 board[fromY][BOARD_RGHT-1] = EmptySquare;
9629 } else if (board[fromY][fromX] == king
9630 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9631 && toY == fromY && toX < fromX-1) {
9632 board[fromY][fromX] = EmptySquare;
9633 board[toY][toX] = king;
9634 board[toY][toX+1] = board[fromY][BOARD_LEFT];
9635 board[fromY][BOARD_LEFT] = EmptySquare;
9636 } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
9637 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9638 && toY >= BOARD_HEIGHT-promoRank && promoChar // defaulting to Q is done elsewhere
9640 /* white pawn promotion */
9641 board[toY][toX] = CharToPiece(ToUpper(promoChar));
9642 if(board[toY][toX] < WhiteCannon && PieceToChar(PROMOTED board[toY][toX]) == '~') /* [HGM] use shadow piece (if available) */
9643 board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9644 board[fromY][fromX] = EmptySquare;
9645 } else if ((fromY >= BOARD_HEIGHT>>1)
9646 && (oldEP == toX || oldEP == EP_UNKNOWN || appData.testLegality)
9648 && gameInfo.variant != VariantXiangqi
9649 && gameInfo.variant != VariantBerolina
9650 && (board[fromY][fromX] == WhitePawn)
9651 && (board[toY][toX] == EmptySquare)) {
9652 board[fromY][fromX] = EmptySquare;
9653 board[toY][toX] = WhitePawn;
9654 captured = board[toY - 1][toX];
9655 board[toY - 1][toX] = EmptySquare;
9656 } else if ((fromY == BOARD_HEIGHT-4)
9658 && gameInfo.variant == VariantBerolina
9659 && (board[fromY][fromX] == WhitePawn)
9660 && (board[toY][toX] == EmptySquare)) {
9661 board[fromY][fromX] = EmptySquare;
9662 board[toY][toX] = WhitePawn;
9663 if(oldEP & EP_BEROLIN_A) {
9664 captured = board[fromY][fromX-1];
9665 board[fromY][fromX-1] = EmptySquare;
9666 }else{ captured = board[fromY][fromX+1];
9667 board[fromY][fromX+1] = EmptySquare;
9669 } else if (board[fromY][fromX] == king
9670 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9671 && toY == fromY && toX > fromX+1) {
9672 board[fromY][fromX] = EmptySquare;
9673 board[toY][toX] = king;
9674 board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9675 board[fromY][BOARD_RGHT-1] = EmptySquare;
9676 } else if (board[fromY][fromX] == king
9677 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9678 && toY == fromY && toX < fromX-1) {
9679 board[fromY][fromX] = EmptySquare;
9680 board[toY][toX] = king;
9681 board[toY][toX+1] = board[fromY][BOARD_LEFT];
9682 board[fromY][BOARD_LEFT] = EmptySquare;
9683 } else if (fromY == 7 && fromX == 3
9684 && board[fromY][fromX] == BlackKing
9685 && toY == 7 && toX == 5) {
9686 board[fromY][fromX] = EmptySquare;
9687 board[toY][toX] = BlackKing;
9688 board[fromY][7] = EmptySquare;
9689 board[toY][4] = BlackRook;
9690 } else if (fromY == 7 && fromX == 3
9691 && board[fromY][fromX] == BlackKing
9692 && toY == 7 && toX == 1) {
9693 board[fromY][fromX] = EmptySquare;
9694 board[toY][toX] = BlackKing;
9695 board[fromY][0] = EmptySquare;
9696 board[toY][2] = BlackRook;
9697 } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
9698 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9699 && toY < promoRank && promoChar
9701 /* black pawn promotion */
9702 board[toY][toX] = CharToPiece(ToLower(promoChar));
9703 if(board[toY][toX] < BlackCannon && PieceToChar(PROMOTED board[toY][toX]) == '~') /* [HGM] use shadow piece (if available) */
9704 board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9705 board[fromY][fromX] = EmptySquare;
9706 } else if ((fromY < BOARD_HEIGHT>>1)
9707 && (oldEP == toX || oldEP == EP_UNKNOWN || appData.testLegality)
9709 && gameInfo.variant != VariantXiangqi
9710 && gameInfo.variant != VariantBerolina
9711 && (board[fromY][fromX] == BlackPawn)
9712 && (board[toY][toX] == EmptySquare)) {
9713 board[fromY][fromX] = EmptySquare;
9714 board[toY][toX] = BlackPawn;
9715 captured = board[toY + 1][toX];
9716 board[toY + 1][toX] = EmptySquare;
9717 } else if ((fromY == 3)
9719 && gameInfo.variant == VariantBerolina
9720 && (board[fromY][fromX] == BlackPawn)
9721 && (board[toY][toX] == EmptySquare)) {
9722 board[fromY][fromX] = EmptySquare;
9723 board[toY][toX] = BlackPawn;
9724 if(oldEP & EP_BEROLIN_A) {
9725 captured = board[fromY][fromX-1];
9726 board[fromY][fromX-1] = EmptySquare;
9727 }else{ captured = board[fromY][fromX+1];
9728 board[fromY][fromX+1] = EmptySquare;
9731 board[toY][toX] = board[fromY][fromX];
9732 board[fromY][fromX] = EmptySquare;
9736 if (gameInfo.holdingsWidth != 0) {
9738 /* !!A lot more code needs to be written to support holdings */
9739 /* [HGM] OK, so I have written it. Holdings are stored in the */
9740 /* penultimate board files, so they are automaticlly stored */
9741 /* in the game history. */
9742 if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
9743 && promoChar && piece != WhitePawn && piece != BlackPawn) {
9744 /* Delete from holdings, by decreasing count */
9745 /* and erasing image if necessary */
9746 p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
9747 if(p < (int) BlackPawn) { /* white drop */
9748 p -= (int)WhitePawn;
9749 p = PieceToNumber((ChessSquare)p);
9750 if(p >= gameInfo.holdingsSize) p = 0;
9751 if(--board[p][BOARD_WIDTH-2] <= 0)
9752 board[p][BOARD_WIDTH-1] = EmptySquare;
9753 if((int)board[p][BOARD_WIDTH-2] < 0)
9754 board[p][BOARD_WIDTH-2] = 0;
9755 } else { /* black drop */
9756 p -= (int)BlackPawn;
9757 p = PieceToNumber((ChessSquare)p);
9758 if(p >= gameInfo.holdingsSize) p = 0;
9759 if(--board[BOARD_HEIGHT-1-p][1] <= 0)
9760 board[BOARD_HEIGHT-1-p][0] = EmptySquare;
9761 if((int)board[BOARD_HEIGHT-1-p][1] < 0)
9762 board[BOARD_HEIGHT-1-p][1] = 0;
9765 if (captured != EmptySquare && gameInfo.holdingsSize > 0
9766 && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess ) {
9767 /* [HGM] holdings: Add to holdings, if holdings exist */
9768 if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
9769 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
9770 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
9773 if (p >= (int) BlackPawn) {
9774 p -= (int)BlackPawn;
9775 if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9776 /* in Shogi restore piece to its original first */
9777 captured = (ChessSquare) (DEMOTED captured);
9780 p = PieceToNumber((ChessSquare)p);
9781 if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
9782 board[p][BOARD_WIDTH-2]++;
9783 board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
9785 p -= (int)WhitePawn;
9786 if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9787 captured = (ChessSquare) (DEMOTED captured);
9790 p = PieceToNumber((ChessSquare)p);
9791 if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
9792 board[BOARD_HEIGHT-1-p][1]++;
9793 board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
9796 } else if (gameInfo.variant == VariantAtomic) {
9797 if (captured != EmptySquare) {
9799 for (y = toY-1; y <= toY+1; y++) {
9800 for (x = toX-1; x <= toX+1; x++) {
9801 if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
9802 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
9803 board[y][x] = EmptySquare;
9807 board[toY][toX] = EmptySquare;
9810 if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
9811 board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
9813 if(promoChar == '+') {
9814 /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite ordinary Pawn promotion) */
9815 board[toY][toX] = (ChessSquare) (PROMOTED piece);
9816 } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
9817 ChessSquare newPiece = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
9818 if((newPiece <= WhiteMan || newPiece >= BlackPawn && newPiece <= BlackMan) // unpromoted piece specified
9819 && pieceToChar[PROMOTED newPiece] == '~') newPiece = PROMOTED newPiece; // but promoted version available
9820 board[toY][toX] = newPiece;
9822 if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
9823 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
9824 // [HGM] superchess: take promotion piece out of holdings
9825 int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
9826 if((int)piece < (int)BlackPawn) { // determine stm from piece color
9827 if(!--board[k][BOARD_WIDTH-2])
9828 board[k][BOARD_WIDTH-1] = EmptySquare;
9830 if(!--board[BOARD_HEIGHT-1-k][1])
9831 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
9837 /* Updates forwardMostMove */
9839 MakeMove (int fromX, int fromY, int toX, int toY, int promoChar)
9841 // forwardMostMove++; // [HGM] bare: moved downstream
9843 (void) CoordsToAlgebraic(boards[forwardMostMove],
9844 PosFlags(forwardMostMove),
9845 fromY, fromX, toY, toX, promoChar,
9846 parseList[forwardMostMove]);
9848 if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
9849 int timeLeft; static int lastLoadFlag=0; int king, piece;
9850 piece = boards[forwardMostMove][fromY][fromX];
9851 king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
9852 if(gameInfo.variant == VariantKnightmate)
9853 king += (int) WhiteUnicorn - (int) WhiteKing;
9854 if(forwardMostMove == 0) {
9855 if(gameMode == MachinePlaysBlack || gameMode == BeginningOfGame)
9856 fprintf(serverMoves, "%s;", UserName());
9857 else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b')
9858 fprintf(serverMoves, "%s;", second.tidy);
9859 fprintf(serverMoves, "%s;", first.tidy);
9860 if(gameMode == MachinePlaysWhite)
9861 fprintf(serverMoves, "%s;", UserName());
9862 else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
9863 fprintf(serverMoves, "%s;", second.tidy);
9864 } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
9865 lastLoadFlag = loadFlag;
9867 fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
9868 // print castling suffix
9869 if( toY == fromY && piece == king ) {
9871 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
9873 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
9876 if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
9877 boards[forwardMostMove][fromY][fromX] == BlackPawn ) &&
9878 boards[forwardMostMove][toY][toX] == EmptySquare
9879 && fromX != toX && fromY != toY)
9880 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
9882 if(promoChar != NULLCHAR) {
9883 if(fromY == 0 || fromY == BOARD_HEIGHT-1)
9884 fprintf(serverMoves, ":%c%c:%c%c", WhiteOnMove(forwardMostMove) ? 'w' : 'b',
9885 ToLower(promoChar), AAA+fromX, ONE+fromY); // Seirawan gating
9886 else fprintf(serverMoves, ":%c:%c%c", ToLower(promoChar), AAA+toX, ONE+toY);
9889 char buf[MOVE_LEN*2], *p; int len;
9890 fprintf(serverMoves, "/%d/%d",
9891 pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
9892 if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
9893 else timeLeft = blackTimeRemaining/1000;
9894 fprintf(serverMoves, "/%d", timeLeft);
9895 strncpy(buf, parseList[forwardMostMove], MOVE_LEN*2);
9896 if(p = strchr(buf, '/')) *p = NULLCHAR; else
9897 if(p = strchr(buf, '=')) *p = NULLCHAR;
9898 len = strlen(buf); if(len > 1 && buf[len-2] != '-') buf[len-2] = NULLCHAR; // strip to-square
9899 fprintf(serverMoves, "/%s", buf);
9901 fflush(serverMoves);
9904 if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations..
9905 GameEnds(GameUnfinished, _("Game too long; increase MAX_MOVES and recompile"), GE_XBOARD);
9908 UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
9909 if (commentList[forwardMostMove+1] != NULL) {
9910 free(commentList[forwardMostMove+1]);
9911 commentList[forwardMostMove+1] = NULL;
9913 CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
9914 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
9915 // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
9916 SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
9917 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
9918 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
9919 adjustedClock = FALSE;
9920 gameInfo.result = GameUnfinished;
9921 if (gameInfo.resultDetails != NULL) {
9922 free(gameInfo.resultDetails);
9923 gameInfo.resultDetails = NULL;
9925 CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
9926 moveList[forwardMostMove - 1]);
9927 switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
9933 if(gameInfo.variant != VariantShogi)
9934 strcat(parseList[forwardMostMove - 1], "+");
9938 strcat(parseList[forwardMostMove - 1], "#");
9944 /* Updates currentMove if not pausing */
9946 ShowMove (int fromX, int fromY, int toX, int toY)
9948 int instant = (gameMode == PlayFromGameFile) ?
9949 (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
9950 if(appData.noGUI) return;
9951 if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
9953 if (forwardMostMove == currentMove + 1) {
9954 AnimateMove(boards[forwardMostMove - 1],
9955 fromX, fromY, toX, toY);
9958 currentMove = forwardMostMove;
9961 if (instant) return;
9963 DisplayMove(currentMove - 1);
9964 if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
9965 if (appData.highlightLastMove) { // [HGM] moved to after DrawPosition, as with arrow it could redraw old board
9966 SetHighlights(fromX, fromY, toX, toY);
9969 DrawPosition(FALSE, boards[currentMove]);
9970 DisplayBothClocks();
9971 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
9975 SendEgtPath (ChessProgramState *cps)
9976 { /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
9977 char buf[MSG_SIZ], name[MSG_SIZ], *p;
9979 if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
9982 char c, *q = name+1, *r, *s;
9984 name[0] = ','; // extract next format name from feature and copy with prefixed ','
9985 while(*p && *p != ',') *q++ = *p++;
9987 if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
9988 strcmp(name, ",nalimov:") == 0 ) {
9989 // take nalimov path from the menu-changeable option first, if it is defined
9990 snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
9991 SendToProgram(buf,cps); // send egtbpath command for nalimov
9993 if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
9994 (s = StrStr(appData.egtFormats, name)) != NULL) {
9995 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
9996 s = r = StrStr(s, ":") + 1; // beginning of path info
9997 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
9998 c = *r; *r = 0; // temporarily null-terminate path info
9999 *--q = 0; // strip of trailig ':' from name
10000 snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
10002 SendToProgram(buf,cps); // send egtbpath command for this format
10004 if(*p == ',') p++; // read away comma to position for next format name
10009 NonStandardBoardSize ()
10011 /* [HGM] Awkward testing. Should really be a table */
10012 int overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
10013 if( gameInfo.variant == VariantUnknown || *engineVariant) return 0; // engine-defined name never needs prefix
10014 if( gameInfo.variant == VariantXiangqi )
10015 overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
10016 if( gameInfo.variant == VariantShogi )
10017 overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
10018 if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
10019 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
10020 if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
10021 gameInfo.variant == VariantGothic || gameInfo.variant == VariantFalcon || gameInfo.variant == VariantJanus )
10022 overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
10023 if( gameInfo.variant == VariantCourier )
10024 overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
10025 if( gameInfo.variant == VariantSuper )
10026 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
10027 if( gameInfo.variant == VariantGreat )
10028 overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
10029 if( gameInfo.variant == VariantSChess )
10030 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 7;
10031 if( gameInfo.variant == VariantGrand )
10032 overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 7;
10037 InitChessProgram (ChessProgramState *cps, int setup)
10038 /* setup needed to setup FRC opening position */
10040 char buf[MSG_SIZ], b[MSG_SIZ];
10041 if (appData.noChessProgram) return;
10042 hintRequested = FALSE;
10043 bookRequested = FALSE;
10045 ParseFeatures(appData.features[cps == &second], cps); // [HGM] allow user to overrule features
10046 /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
10047 /* moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
10048 if(cps->memSize) { /* [HGM] memory */
10049 snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
10050 SendToProgram(buf, cps);
10052 SendEgtPath(cps); /* [HGM] EGT */
10053 if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
10054 snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
10055 SendToProgram(buf, cps);
10058 SendToProgram(cps->initString, cps);
10059 if (gameInfo.variant != VariantNormal &&
10060 gameInfo.variant != VariantLoadable
10061 /* [HGM] also send variant if board size non-standard */
10062 || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
10064 char *v = VariantName(gameInfo.variant);
10065 if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
10066 /* [HGM] in protocol 1 we have to assume all variants valid */
10067 snprintf(buf, MSG_SIZ, _("Variant %s not supported by %s"), v, cps->tidy);
10068 DisplayFatalError(buf, 0, 1);
10072 if(NonStandardBoardSize()) { /* [HGM] make prefix for non-standard board size. */
10073 snprintf(b, MSG_SIZ, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
10074 gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
10075 /* [HGM] varsize: try first if this defiant size variant is specifically known */
10076 if(StrStr(cps->variants, b) == NULL) {
10077 // specific sized variant not known, check if general sizing allowed
10078 if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
10079 if(StrStr(cps->variants, "boardsize") == NULL) {
10080 snprintf(buf, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
10081 gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
10082 DisplayFatalError(buf, 0, 1);
10085 /* [HGM] here we really should compare with the maximum supported board size */
10088 } else snprintf(b, MSG_SIZ,"%s", VariantName(gameInfo.variant));
10089 snprintf(buf, MSG_SIZ, "variant %s\n", b);
10090 SendToProgram(buf, cps);
10092 currentlyInitializedVariant = gameInfo.variant;
10094 /* [HGM] send opening position in FRC to first engine */
10096 SendToProgram("force\n", cps);
10098 /* engine is now in force mode! Set flag to wake it up after first move. */
10099 setboardSpoiledMachineBlack = 1;
10102 if (cps->sendICS) {
10103 snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
10104 SendToProgram(buf, cps);
10106 cps->maybeThinking = FALSE;
10107 cps->offeredDraw = 0;
10108 if (!appData.icsActive) {
10109 SendTimeControl(cps, movesPerSession, timeControl,
10110 timeIncrement, appData.searchDepth,
10113 if (appData.showThinking
10114 // [HGM] thinking: four options require thinking output to be sent
10115 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
10117 SendToProgram("post\n", cps);
10119 SendToProgram("hard\n", cps);
10120 if (!appData.ponderNextMove) {
10121 /* Warning: "easy" is a toggle in GNU Chess, so don't send
10122 it without being sure what state we are in first. "hard"
10123 is not a toggle, so that one is OK.
10125 SendToProgram("easy\n", cps);
10127 if (cps->usePing) {
10128 snprintf(buf, MSG_SIZ, "ping %d\n", ++cps->lastPing);
10129 SendToProgram(buf, cps);
10131 cps->initDone = TRUE;
10132 ClearEngineOutputPane(cps == &second);
10137 ResendOptions (ChessProgramState *cps)
10138 { // send the stored value of the options
10141 Option *opt = cps->option;
10142 for(i=0; i<cps->nrOptions; i++, opt++) {
10143 switch(opt->type) {
10147 snprintf(buf, MSG_SIZ, "option %s=%d\n", opt->name, opt->value);
10150 snprintf(buf, MSG_SIZ, "option %s=%s\n", opt->name, opt->choice[opt->value]);
10153 snprintf(buf, MSG_SIZ, "option %s=%s\n", opt->name, opt->textValue);
10159 SendToProgram(buf, cps);
10164 StartChessProgram (ChessProgramState *cps)
10169 if (appData.noChessProgram) return;
10170 cps->initDone = FALSE;
10172 if (strcmp(cps->host, "localhost") == 0) {
10173 err = StartChildProcess(cps->program, cps->dir, &cps->pr);
10174 } else if (*appData.remoteShell == NULLCHAR) {
10175 err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
10177 if (*appData.remoteUser == NULLCHAR) {
10178 snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
10181 snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
10182 cps->host, appData.remoteUser, cps->program);
10184 err = StartChildProcess(buf, "", &cps->pr);
10188 snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
10189 DisplayError(buf, err); // [HGM] bit of a rough kludge: ignore failure, (which XBoard would do anyway), and let I/O discover it
10190 if(cps != &first) return;
10191 appData.noChessProgram = TRUE;
10194 // DisplayFatalError(buf, err, 1);
10195 // cps->pr = NoProc;
10196 // cps->isr = NULL;
10200 cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
10201 if (cps->protocolVersion > 1) {
10202 snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
10203 if(!cps->reload) { // do not clear options when reloading because of -xreuse
10204 cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
10205 cps->comboCnt = 0; // and values of combo boxes
10207 SendToProgram(buf, cps);
10208 if(cps->reload) ResendOptions(cps);
10210 SendToProgram("xboard\n", cps);
10215 TwoMachinesEventIfReady P((void))
10217 static int curMess = 0;
10218 if (first.lastPing != first.lastPong) {
10219 if(curMess != 1) DisplayMessage("", _("Waiting for first chess program")); curMess = 1;
10220 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
10223 if (second.lastPing != second.lastPong) {
10224 if(curMess != 2) DisplayMessage("", _("Waiting for second chess program")); curMess = 2;
10225 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
10228 DisplayMessage("", ""); curMess = 0;
10229 TwoMachinesEvent();
10233 MakeName (char *template)
10237 static char buf[MSG_SIZ];
10241 clock = time((time_t *)NULL);
10242 tm = localtime(&clock);
10244 while(*p++ = *template++) if(p[-1] == '%') {
10245 switch(*template++) {
10246 case 0: *p = 0; return buf;
10247 case 'Y': i = tm->tm_year+1900; break;
10248 case 'y': i = tm->tm_year-100; break;
10249 case 'M': i = tm->tm_mon+1; break;
10250 case 'd': i = tm->tm_mday; break;
10251 case 'h': i = tm->tm_hour; break;
10252 case 'm': i = tm->tm_min; break;
10253 case 's': i = tm->tm_sec; break;
10256 snprintf(p-1, MSG_SIZ-10 - (p - buf), "%02d", i); p += strlen(p);
10262 CountPlayers (char *p)
10265 while(p = strchr(p, '\n')) p++, n++; // count participants
10270 WriteTourneyFile (char *results, FILE *f)
10271 { // write tournament parameters on tourneyFile; on success return the stream pointer for closing
10272 if(f == NULL) f = fopen(appData.tourneyFile, "w");
10273 if(f == NULL) DisplayError(_("Could not write on tourney file"), 0); else {
10274 // create a file with tournament description
10275 fprintf(f, "-participants {%s}\n", appData.participants);
10276 fprintf(f, "-seedBase %d\n", appData.seedBase);
10277 fprintf(f, "-tourneyType %d\n", appData.tourneyType);
10278 fprintf(f, "-tourneyCycles %d\n", appData.tourneyCycles);
10279 fprintf(f, "-defaultMatchGames %d\n", appData.defaultMatchGames);
10280 fprintf(f, "-syncAfterRound %s\n", appData.roundSync ? "true" : "false");
10281 fprintf(f, "-syncAfterCycle %s\n", appData.cycleSync ? "true" : "false");
10282 fprintf(f, "-saveGameFile \"%s\"\n", appData.saveGameFile);
10283 fprintf(f, "-loadGameFile \"%s\"\n", appData.loadGameFile);
10284 fprintf(f, "-loadGameIndex %d\n", appData.loadGameIndex);
10285 fprintf(f, "-loadPositionFile \"%s\"\n", appData.loadPositionFile);
10286 fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex);
10287 fprintf(f, "-rewindIndex %d\n", appData.rewindIndex);
10288 fprintf(f, "-usePolyglotBook %s\n", appData.usePolyglotBook ? "true" : "false");
10289 fprintf(f, "-polyglotBook \"%s\"\n", appData.polyglotBook);
10290 fprintf(f, "-bookDepth %d\n", appData.bookDepth);
10291 fprintf(f, "-bookVariation %d\n", appData.bookStrength);
10292 fprintf(f, "-discourageOwnBooks %s\n", appData.defNoBook ? "true" : "false");
10293 fprintf(f, "-defaultHashSize %d\n", appData.defaultHashSize);
10294 fprintf(f, "-defaultCacheSizeEGTB %d\n", appData.defaultCacheSizeEGTB);
10295 fprintf(f, "-ponderNextMove %s\n", appData.ponderNextMove ? "true" : "false");
10296 fprintf(f, "-smpCores %d\n", appData.smpCores);
10298 fprintf(f, "-searchTime \"%d:%02d\"\n", searchTime/60, searchTime%60);
10300 fprintf(f, "-mps %d\n", appData.movesPerSession);
10301 fprintf(f, "-tc %s\n", appData.timeControl);
10302 fprintf(f, "-inc %.2f\n", appData.timeIncrement);
10304 fprintf(f, "-results \"%s\"\n", results);
10309 char *command[MAXENGINES], *mnemonic[MAXENGINES];
10312 Substitute (char *participants, int expunge)
10314 int i, changed, changes=0, nPlayers=0;
10315 char *p, *q, *r, buf[MSG_SIZ];
10316 if(participants == NULL) return;
10317 if(appData.tourneyFile[0] == NULLCHAR) { free(participants); return; }
10318 r = p = participants; q = appData.participants;
10319 while(*p && *p == *q) {
10320 if(*p == '\n') r = p+1, nPlayers++;
10323 if(*p) { // difference
10324 while(*p && *p++ != '\n');
10325 while(*q && *q++ != '\n');
10326 changed = nPlayers;
10327 changes = 1 + (strcmp(p, q) != 0);
10329 if(changes == 1) { // a single engine mnemonic was changed
10330 q = r; while(*q) nPlayers += (*q++ == '\n');
10331 p = buf; while(*r && (*p = *r++) != '\n') p++;
10333 NamesToList(firstChessProgramNames, command, mnemonic, "all");
10334 for(i=1; mnemonic[i]; i++) if(!strcmp(buf, mnemonic[i])) break;
10335 if(mnemonic[i]) { // The substitute is valid
10337 if(appData.tourneyFile[0] && (f = fopen(appData.tourneyFile, "r+")) ) {
10338 flock(fileno(f), LOCK_EX);
10339 ParseArgsFromFile(f);
10340 fseek(f, 0, SEEK_SET);
10341 FREE(appData.participants); appData.participants = participants;
10342 if(expunge) { // erase results of replaced engine
10343 int len = strlen(appData.results), w, b, dummy;
10344 for(i=0; i<len; i++) {
10345 Pairing(i, nPlayers, &w, &b, &dummy);
10346 if((w == changed || b == changed) && appData.results[i] == '*') {
10347 DisplayError(_("You cannot replace an engine while it is engaged!\nTerminate its game first."), 0);
10352 for(i=0; i<len; i++) {
10353 Pairing(i, nPlayers, &w, &b, &dummy);
10354 if(w == changed || b == changed) appData.results[i] = ' '; // mark as not played
10357 WriteTourneyFile(appData.results, f);
10358 fclose(f); // release lock
10361 } else DisplayError(_("No engine with the name you gave is installed"), 0);
10363 if(changes == 0) DisplayError(_("First change an engine by editing the participants list\nof the Tournament Options dialog"), 0);
10364 if(changes > 1) DisplayError(_("You can only change one engine at the time"), 0);
10365 free(participants);
10370 CheckPlayers (char *participants)
10373 char buf[MSG_SIZ], *p;
10374 NamesToList(firstChessProgramNames, command, mnemonic, "all");
10375 while(p = strchr(participants, '\n')) {
10377 for(i=1; mnemonic[i]; i++) if(!strcmp(participants, mnemonic[i])) break;
10379 snprintf(buf, MSG_SIZ, _("No engine %s is installed"), participants);
10381 DisplayError(buf, 0);
10385 participants = p + 1;
10391 CreateTourney (char *name)
10394 if(matchMode && strcmp(name, appData.tourneyFile)) {
10395 ASSIGN(name, appData.tourneyFile); //do not allow change of tourneyfile while playing
10397 if(name[0] == NULLCHAR) {
10398 if(appData.participants[0])
10399 DisplayError(_("You must supply a tournament file,\nfor storing the tourney progress"), 0);
10402 f = fopen(name, "r");
10403 if(f) { // file exists
10404 ASSIGN(appData.tourneyFile, name);
10405 ParseArgsFromFile(f); // parse it
10407 if(!appData.participants[0]) return 0; // ignore tourney file if non-existing & no participants
10408 if(CountPlayers(appData.participants) < (appData.tourneyType>0 ? appData.tourneyType+1 : 2)) {
10409 DisplayError(_("Not enough participants"), 0);
10412 if(CheckPlayers(appData.participants)) return 0;
10413 ASSIGN(appData.tourneyFile, name);
10414 if(appData.tourneyType < 0) appData.defaultMatchGames = 1; // Swiss forces games/pairing = 1
10415 if((f = WriteTourneyFile("", NULL)) == NULL) return 0;
10418 appData.noChessProgram = FALSE;
10419 appData.clockMode = TRUE;
10425 NamesToList (char *names, char **engineList, char **engineMnemonic, char *group)
10427 char buf[MSG_SIZ], *p, *q;
10428 int i=1, header, skip, all = !strcmp(group, "all"), depth = 0;
10429 insert = names; // afterwards, this global will point just after last retrieved engine line or group end in the 'names'
10430 skip = !all && group[0]; // if group requested, we start in skip mode
10431 for(;*names && depth >= 0 && i < MAXENGINES-1; names = p) {
10432 p = names; q = buf; header = 0;
10433 while(*p && *p != '\n') *q++ = *p++;
10435 if(*p == '\n') p++;
10436 if(buf[0] == '#') {
10437 if(strstr(buf, "# end") == buf) { if(!--depth) insert = p; continue; } // leave group, and suppress printing label
10438 depth++; // we must be entering a new group
10439 if(all) continue; // suppress printing group headers when complete list requested
10441 if(skip && !strcmp(group, buf)) { depth = 0; skip = FALSE; } // start when we reach requested group
10443 if(depth != header && !all || skip) continue; // skip contents of group (but print first-level header)
10444 if(engineList[i]) free(engineList[i]);
10445 engineList[i] = strdup(buf);
10446 if(buf[0] != '#') insert = p, TidyProgramName(engineList[i], "localhost", buf); // group headers not tidied
10447 if(engineMnemonic[i]) free(engineMnemonic[i]);
10448 if((q = strstr(engineList[i]+2, "variant")) && q[-2]== ' ' && (q[-1]=='/' || q[-1]=='-') && (q[7]==' ' || q[7]=='=')) {
10450 sscanf(q + 8, "%s", buf + strlen(buf));
10453 engineMnemonic[i] = strdup(buf);
10456 engineList[i] = engineMnemonic[i] = NULL;
10460 // following implemented as macro to avoid type limitations
10461 #define SWAP(item, temp) temp = appData.item[0]; appData.item[0] = appData.item[n]; appData.item[n] = temp;
10464 SwapEngines (int n)
10465 { // swap settings for first engine and other engine (so far only some selected options)
10470 SWAP(chessProgram, p)
10472 SWAP(hasOwnBookUCI, h)
10473 SWAP(protocolVersion, h)
10475 SWAP(scoreIsAbsolute, h)
10480 SWAP(engOptions, p)
10481 SWAP(engInitString, p)
10482 SWAP(computerString, p)
10484 SWAP(fenOverride, p)
10486 SWAP(accumulateTC, h)
10491 GetEngineLine (char *s, int n)
10495 extern char *icsNames;
10496 if(!s || !*s) return 0;
10497 NamesToList(n >= 10 ? icsNames : firstChessProgramNames, command, mnemonic, "all");
10498 for(i=1; mnemonic[i]; i++) if(!strcmp(s, mnemonic[i])) break;
10499 if(!mnemonic[i]) return 0;
10500 if(n == 11) return 1; // just testing if there was a match
10501 snprintf(buf, MSG_SIZ, "-%s %s", n == 10 ? "icshost" : "fcp", command[i]);
10502 if(n == 1) SwapEngines(n);
10503 ParseArgsFromString(buf);
10504 if(n == 1) SwapEngines(n);
10505 if(n == 0 && *appData.secondChessProgram == NULLCHAR) {
10506 SwapEngines(1); // set second same as first if not yet set (to suppress WB startup dialog)
10507 ParseArgsFromString(buf);
10513 SetPlayer (int player, char *p)
10514 { // [HGM] find the engine line of the partcipant given by number, and parse its options.
10516 char buf[MSG_SIZ], *engineName;
10517 for(i=0; i<player; i++) p = strchr(p, '\n') + 1;
10518 engineName = strdup(p); if(p = strchr(engineName, '\n')) *p = NULLCHAR;
10519 for(i=1; command[i]; i++) if(!strcmp(mnemonic[i], engineName)) break;
10521 snprintf(buf, MSG_SIZ, "-fcp %s", command[i]);
10522 ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL; appData.pvSAN[0] = FALSE;
10523 appData.firstHasOwnBookUCI = !appData.defNoBook; appData.protocolVersion[0] = PROTOVER;
10524 ParseArgsFromString(buf);
10525 } else { // no engine with this nickname is installed!
10526 snprintf(buf, MSG_SIZ, _("No engine %s is installed"), engineName);
10527 ReserveGame(nextGame, ' '); // unreserve game and drop out of match mode with error
10528 matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
10530 DisplayError(buf, 0);
10537 char *recentEngines;
10540 RecentEngineEvent (int nr)
10543 // SwapEngines(1); // bump first to second
10544 // ReplaceEngine(&second, 1); // and load it there
10545 NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
10546 n = SetPlayer(nr, recentEngines); // select new (using original menu order!)
10547 if(mnemonic[n]) { // if somehow the engine with the selected nickname is no longer found in the list, we skip
10548 ReplaceEngine(&first, 0);
10549 FloatToFront(&appData.recentEngineList, command[n]);
10554 Pairing (int nr, int nPlayers, int *whitePlayer, int *blackPlayer, int *syncInterval)
10555 { // determine players from game number
10556 int curCycle, curRound, curPairing, gamesPerCycle, gamesPerRound, roundsPerCycle=1, pairingsPerRound=1;
10558 if(appData.tourneyType == 0) {
10559 roundsPerCycle = (nPlayers - 1) | 1;
10560 pairingsPerRound = nPlayers / 2;
10561 } else if(appData.tourneyType > 0) {
10562 roundsPerCycle = nPlayers - appData.tourneyType;
10563 pairingsPerRound = appData.tourneyType;
10565 gamesPerRound = pairingsPerRound * appData.defaultMatchGames;
10566 gamesPerCycle = gamesPerRound * roundsPerCycle;
10567 appData.matchGames = gamesPerCycle * appData.tourneyCycles - 1; // fake like all games are one big match
10568 curCycle = nr / gamesPerCycle; nr %= gamesPerCycle;
10569 curRound = nr / gamesPerRound; nr %= gamesPerRound;
10570 curPairing = nr / appData.defaultMatchGames; nr %= appData.defaultMatchGames;
10571 matchGame = nr + curCycle * appData.defaultMatchGames + 1; // fake game nr that loads correct game or position from file
10572 roundNr = (curCycle * roundsPerCycle + curRound) * appData.defaultMatchGames + nr + 1;
10574 if(appData.cycleSync) *syncInterval = gamesPerCycle;
10575 if(appData.roundSync) *syncInterval = gamesPerRound;
10577 if(appData.debugMode) fprintf(debugFP, "cycle=%d, round=%d, pairing=%d curGame=%d\n", curCycle, curRound, curPairing, matchGame);
10579 if(appData.tourneyType == 0) {
10580 if(curPairing == (nPlayers-1)/2 ) {
10581 *whitePlayer = curRound;
10582 *blackPlayer = nPlayers - 1; // this is the 'bye' when nPlayer is odd
10584 *whitePlayer = curRound - (nPlayers-1)/2 + curPairing;
10585 if(*whitePlayer < 0) *whitePlayer += nPlayers-1+(nPlayers&1);
10586 *blackPlayer = curRound + (nPlayers-1)/2 - curPairing;
10587 if(*blackPlayer >= nPlayers-1+(nPlayers&1)) *blackPlayer -= nPlayers-1+(nPlayers&1);
10589 } else if(appData.tourneyType > 1) {
10590 *blackPlayer = curPairing; // in multi-gauntlet, assign gauntlet engines to second, so first an be kept loaded during round
10591 *whitePlayer = curRound + appData.tourneyType;
10592 } else if(appData.tourneyType > 0) {
10593 *whitePlayer = curPairing;
10594 *blackPlayer = curRound + appData.tourneyType;
10597 // take care of white/black alternation per round.
10598 // For cycles and games this is already taken care of by default, derived from matchGame!
10599 return curRound & 1;
10603 NextTourneyGame (int nr, int *swapColors)
10604 { // !!!major kludge!!! fiddle appData settings to get everything in order for next tourney game
10606 int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers, OK = 1;
10608 if(appData.tourneyFile[0] == NULLCHAR) return 1; // no tourney, always allow next game
10609 tf = fopen(appData.tourneyFile, "r");
10610 if(tf == NULL) { DisplayFatalError(_("Bad tournament file"), 0, 1); return 0; }
10611 ParseArgsFromFile(tf); fclose(tf);
10612 InitTimeControls(); // TC might be altered from tourney file
10614 nPlayers = CountPlayers(appData.participants); // count participants
10615 if(appData.tourneyType < 0) syncInterval = nPlayers/2; else
10616 *swapColors = Pairing(nr<0 ? 0 : nr, nPlayers, &whitePlayer, &blackPlayer, &syncInterval);
10619 p = q = appData.results;
10620 while(*q) if(*q++ == '*' || q[-1] == ' ') { firstBusy = q - p - 1; break; }
10621 if(firstBusy/syncInterval < (nextGame/syncInterval)) {
10622 DisplayMessage(_("Waiting for other game(s)"),"");
10623 waitingForGame = TRUE;
10624 ScheduleDelayedEvent(NextMatchGame, 1000); // wait for all games of previous round to finish
10627 waitingForGame = FALSE;
10630 if(appData.tourneyType < 0) {
10631 if(nr>=0 && !pairingReceived) {
10633 if(pairing.pr == NoProc) {
10634 if(!appData.pairingEngine[0]) {
10635 DisplayFatalError(_("No pairing engine specified"), 0, 1);
10638 StartChessProgram(&pairing); // starts the pairing engine
10640 snprintf(buf, 1<<16, "results %d %s\n", nPlayers, appData.results);
10641 SendToProgram(buf, &pairing);
10642 snprintf(buf, 1<<16, "pairing %d\n", nr+1);
10643 SendToProgram(buf, &pairing);
10644 return 0; // wait for pairing engine to answer (which causes NextTourneyGame to be called again...
10646 pairingReceived = 0; // ... so we continue here
10648 appData.matchGames = appData.tourneyCycles * syncInterval - 1;
10649 whitePlayer = savedWhitePlayer-1; blackPlayer = savedBlackPlayer-1;
10650 matchGame = 1; roundNr = nr / syncInterval + 1;
10653 if(first.pr != NoProc && second.pr != NoProc || nr<0) return 1; // engines already loaded
10655 // redefine engines, engine dir, etc.
10656 NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
10657 if(first.pr == NoProc) {
10658 if(!SetPlayer(whitePlayer, appData.participants)) OK = 0; // find white player amongst it, and parse its engine line
10659 InitEngine(&first, 0); // initialize ChessProgramStates based on new settings.
10661 if(second.pr == NoProc) {
10663 if(!SetPlayer(blackPlayer, appData.participants)) OK = 0; // find black player amongst it, and parse its engine line
10664 SwapEngines(1); // and make that valid for second engine by swapping
10665 InitEngine(&second, 1);
10667 CommonEngineInit(); // after this TwoMachinesEvent will create correct engine processes
10668 UpdateLogos(FALSE); // leave display to ModeHiglight()
10674 { // performs game initialization that does not invoke engines, and then tries to start the game
10675 int res, firstWhite, swapColors = 0;
10676 if(!NextTourneyGame(nextGame, &swapColors)) return; // this sets matchGame, -fcp / -scp and other options for next game, if needed
10677 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
10679 snprintf(buf, MSG_SIZ, appData.nameOfDebugFile, nextGame+1); // expand name of debug file with %d in it
10680 if(strcmp(buf, currentDebugFile)) { // name has changed
10681 FILE *f = fopen(buf, "w");
10682 if(f) { // if opening the new file failed, just keep using the old one
10683 ASSIGN(currentDebugFile, buf);
10687 if(appData.serverFileName) {
10688 if(serverFP) fclose(serverFP);
10689 serverFP = fopen(appData.serverFileName, "w");
10690 if(serverFP && first.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", first.tidy);
10691 if(serverFP && second.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", second.tidy);
10695 firstWhite = appData.firstPlaysBlack ^ (matchGame & 1 | appData.sameColorGames > 1); // non-incremental default
10696 firstWhite ^= swapColors; // reverses if NextTourneyGame says we are in an odd round
10697 first.twoMachinesColor = firstWhite ? "white\n" : "black\n"; // perform actual color assignement
10698 second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
10699 appData.noChessProgram = (first.pr == NoProc); // kludge to prevent Reset from starting up chess program
10700 if(appData.loadGameIndex == -2) srandom(appData.seedBase + 68163*(nextGame & ~1)); // deterministic seed to force same opening
10701 Reset(FALSE, first.pr != NoProc);
10702 res = LoadGameOrPosition(matchGame); // setup game
10703 appData.noChessProgram = FALSE; // LoadGameOrPosition might call Reset too!
10704 if(!res) return; // abort when bad game/pos file
10705 TwoMachinesEvent();
10709 UserAdjudicationEvent (int result)
10711 ChessMove gameResult = GameIsDrawn;
10714 gameResult = WhiteWins;
10716 else if( result < 0 ) {
10717 gameResult = BlackWins;
10720 if( gameMode == TwoMachinesPlay ) {
10721 GameEnds( gameResult, "User adjudication", GE_XBOARD );
10726 // [HGM] save: calculate checksum of game to make games easily identifiable
10728 StringCheckSum (char *s)
10731 if(s==NULL) return 0;
10732 while(*s) i = i*259 + *s++;
10740 for(i=backwardMostMove; i<forwardMostMove; i++) {
10741 sum += pvInfoList[i].depth;
10742 sum += StringCheckSum(parseList[i]);
10743 sum += StringCheckSum(commentList[i]);
10746 if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
10747 return sum + StringCheckSum(commentList[i]);
10748 } // end of save patch
10751 GameEnds (ChessMove result, char *resultDetails, int whosays)
10753 GameMode nextGameMode;
10755 char buf[MSG_SIZ], popupRequested = 0, *ranking = NULL;
10757 if(endingGame) return; /* [HGM] crash: forbid recursion */
10759 if(twoBoards) { // [HGM] dual: switch back to one board
10760 twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
10761 DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
10763 if (appData.debugMode) {
10764 fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
10765 result, resultDetails ? resultDetails : "(null)", whosays);
10768 fromX = fromY = -1; // [HGM] abort any move the user is entering.
10770 if(pausing) PauseEvent(); // can happen when we abort a paused game (New Game or Quit)
10772 if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
10773 /* If we are playing on ICS, the server decides when the
10774 game is over, but the engine can offer to draw, claim
10778 if (appData.zippyPlay && first.initDone) {
10779 if (result == GameIsDrawn) {
10780 /* In case draw still needs to be claimed */
10781 SendToICS(ics_prefix);
10782 SendToICS("draw\n");
10783 } else if (StrCaseStr(resultDetails, "resign")) {
10784 SendToICS(ics_prefix);
10785 SendToICS("resign\n");
10789 endingGame = 0; /* [HGM] crash */
10793 /* If we're loading the game from a file, stop */
10794 if (whosays == GE_FILE) {
10795 (void) StopLoadGameTimer();
10799 /* Cancel draw offers */
10800 first.offeredDraw = second.offeredDraw = 0;
10802 /* If this is an ICS game, only ICS can really say it's done;
10803 if not, anyone can. */
10804 isIcsGame = (gameMode == IcsPlayingWhite ||
10805 gameMode == IcsPlayingBlack ||
10806 gameMode == IcsObserving ||
10807 gameMode == IcsExamining);
10809 if (!isIcsGame || whosays == GE_ICS) {
10810 /* OK -- not an ICS game, or ICS said it was done */
10812 if (!isIcsGame && !appData.noChessProgram)
10813 SetUserThinkingEnables();
10815 /* [HGM] if a machine claims the game end we verify this claim */
10816 if(gameMode == TwoMachinesPlay && appData.testClaims) {
10817 if(appData.testLegality && whosays >= GE_ENGINE1 ) {
10819 ChessMove trueResult = (ChessMove) -1;
10821 claimer = whosays == GE_ENGINE1 ? /* color of claimer */
10822 first.twoMachinesColor[0] :
10823 second.twoMachinesColor[0] ;
10825 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
10826 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
10827 /* [HGM] verify: engine mate claims accepted if they were flagged */
10828 trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
10830 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
10831 /* [HGM] verify: engine mate claims accepted if they were flagged */
10832 trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
10834 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
10835 trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
10838 // now verify win claims, but not in drop games, as we don't understand those yet
10839 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
10840 || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) &&
10841 (result == WhiteWins && claimer == 'w' ||
10842 result == BlackWins && claimer == 'b' ) ) { // case to verify: engine claims own win
10843 if (appData.debugMode) {
10844 fprintf(debugFP, "result=%d sp=%d move=%d\n",
10845 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
10847 if(result != trueResult) {
10848 snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
10849 result = claimer == 'w' ? BlackWins : WhiteWins;
10850 resultDetails = buf;
10853 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
10854 && (forwardMostMove <= backwardMostMove ||
10855 (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
10856 (claimer=='b')==(forwardMostMove&1))
10858 /* [HGM] verify: draws that were not flagged are false claims */
10859 snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
10860 result = claimer == 'w' ? BlackWins : WhiteWins;
10861 resultDetails = buf;
10863 /* (Claiming a loss is accepted no questions asked!) */
10864 } else if(matchMode && result == GameIsDrawn && !strcmp(resultDetails, "Engine Abort Request")) {
10865 forwardMostMove = backwardMostMove; // [HGM] delete game to surpress saving
10866 result = GameUnfinished;
10867 if(!*appData.tourneyFile) matchGame--; // replay even in plain match
10869 /* [HGM] bare: don't allow bare King to win */
10870 if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
10871 || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
10872 && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
10873 && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
10874 && result != GameIsDrawn)
10875 { int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
10876 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
10877 int p = (signed char)boards[forwardMostMove][i][j] - color;
10878 if(p >= 0 && p <= (int)WhiteKing) k++;
10880 if (appData.debugMode) {
10881 fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
10882 result, resultDetails ? resultDetails : "(null)", whosays, k, color);
10885 result = GameIsDrawn;
10886 snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
10887 resultDetails = buf;
10893 if(serverMoves != NULL && !loadFlag) { char c = '=';
10894 if(result==WhiteWins) c = '+';
10895 if(result==BlackWins) c = '-';
10896 if(resultDetails != NULL)
10897 fprintf(serverMoves, ";%c;%s\n", c, resultDetails), fflush(serverMoves);
10899 if (resultDetails != NULL) {
10900 gameInfo.result = result;
10901 gameInfo.resultDetails = StrSave(resultDetails);
10903 /* display last move only if game was not loaded from file */
10904 if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
10905 DisplayMove(currentMove - 1);
10907 if (forwardMostMove != 0) {
10908 if (gameMode != PlayFromGameFile && gameMode != EditGame
10909 && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
10911 if (*appData.saveGameFile != NULLCHAR) {
10912 if(result == GameUnfinished && matchMode && *appData.tourneyFile)
10913 AutoSaveGame(); // [HGM] protect tourney PGN from aborted games, and prompt for name instead
10915 SaveGameToFile(appData.saveGameFile, TRUE);
10916 } else if (appData.autoSaveGames) {
10917 if(gameMode != IcsObserving || !appData.onlyOwn) AutoSaveGame();
10919 if (*appData.savePositionFile != NULLCHAR) {
10920 SavePositionToFile(appData.savePositionFile);
10922 AddGameToBook(FALSE); // Only does something during Monte-Carlo book building
10926 /* Tell program how game ended in case it is learning */
10927 /* [HGM] Moved this to after saving the PGN, just in case */
10928 /* engine died and we got here through time loss. In that */
10929 /* case we will get a fatal error writing the pipe, which */
10930 /* would otherwise lose us the PGN. */
10931 /* [HGM] crash: not needed anymore, but doesn't hurt; */
10932 /* output during GameEnds should never be fatal anymore */
10933 if (gameMode == MachinePlaysWhite ||
10934 gameMode == MachinePlaysBlack ||
10935 gameMode == TwoMachinesPlay ||
10936 gameMode == IcsPlayingWhite ||
10937 gameMode == IcsPlayingBlack ||
10938 gameMode == BeginningOfGame) {
10940 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
10942 if (first.pr != NoProc) {
10943 SendToProgram(buf, &first);
10945 if (second.pr != NoProc &&
10946 gameMode == TwoMachinesPlay) {
10947 SendToProgram(buf, &second);
10952 if (appData.icsActive) {
10953 if (appData.quietPlay &&
10954 (gameMode == IcsPlayingWhite ||
10955 gameMode == IcsPlayingBlack)) {
10956 SendToICS(ics_prefix);
10957 SendToICS("set shout 1\n");
10959 nextGameMode = IcsIdle;
10960 ics_user_moved = FALSE;
10961 /* clean up premove. It's ugly when the game has ended and the
10962 * premove highlights are still on the board.
10965 gotPremove = FALSE;
10966 ClearPremoveHighlights();
10967 DrawPosition(FALSE, boards[currentMove]);
10969 if (whosays == GE_ICS) {
10972 if (gameMode == IcsPlayingWhite)
10974 else if(gameMode == IcsPlayingBlack)
10975 PlayIcsLossSound();
10978 if (gameMode == IcsPlayingBlack)
10980 else if(gameMode == IcsPlayingWhite)
10981 PlayIcsLossSound();
10984 PlayIcsDrawSound();
10987 PlayIcsUnfinishedSound();
10990 if(appData.quitNext) { ExitEvent(0); return; }
10991 } else if (gameMode == EditGame ||
10992 gameMode == PlayFromGameFile ||
10993 gameMode == AnalyzeMode ||
10994 gameMode == AnalyzeFile) {
10995 nextGameMode = gameMode;
10997 nextGameMode = EndOfGame;
11002 nextGameMode = gameMode;
11005 if (appData.noChessProgram) {
11006 gameMode = nextGameMode;
11008 endingGame = 0; /* [HGM] crash */
11013 /* Put first chess program into idle state */
11014 if (first.pr != NoProc &&
11015 (gameMode == MachinePlaysWhite ||
11016 gameMode == MachinePlaysBlack ||
11017 gameMode == TwoMachinesPlay ||
11018 gameMode == IcsPlayingWhite ||
11019 gameMode == IcsPlayingBlack ||
11020 gameMode == BeginningOfGame)) {
11021 SendToProgram("force\n", &first);
11022 if (first.usePing) {
11024 snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
11025 SendToProgram(buf, &first);
11028 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
11029 /* Kill off first chess program */
11030 if (first.isr != NULL)
11031 RemoveInputSource(first.isr);
11034 if (first.pr != NoProc) {
11036 DoSleep( appData.delayBeforeQuit );
11037 SendToProgram("quit\n", &first);
11038 DoSleep( appData.delayAfterQuit );
11039 DestroyChildProcess(first.pr, first.useSigterm);
11040 first.reload = TRUE;
11044 if (second.reuse) {
11045 /* Put second chess program into idle state */
11046 if (second.pr != NoProc &&
11047 gameMode == TwoMachinesPlay) {
11048 SendToProgram("force\n", &second);
11049 if (second.usePing) {
11051 snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
11052 SendToProgram(buf, &second);
11055 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
11056 /* Kill off second chess program */
11057 if (second.isr != NULL)
11058 RemoveInputSource(second.isr);
11061 if (second.pr != NoProc) {
11062 DoSleep( appData.delayBeforeQuit );
11063 SendToProgram("quit\n", &second);
11064 DoSleep( appData.delayAfterQuit );
11065 DestroyChildProcess(second.pr, second.useSigterm);
11066 second.reload = TRUE;
11068 second.pr = NoProc;
11071 if (matchMode && (gameMode == TwoMachinesPlay || (waitingForGame || startingEngine) && exiting)) {
11072 char resChar = '=';
11076 if (first.twoMachinesColor[0] == 'w') {
11079 second.matchWins++;
11084 if (first.twoMachinesColor[0] == 'b') {
11087 second.matchWins++;
11090 case GameUnfinished:
11096 if(exiting) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game
11097 if(appData.tourneyFile[0]){ // [HGM] we are in a tourney; update tourney file with game result
11098 if(appData.afterGame && appData.afterGame[0]) RunCommand(appData.afterGame);
11099 ReserveGame(nextGame, resChar); // sets nextGame
11100 if(nextGame > appData.matchGames) appData.tourneyFile[0] = 0, ranking = TourneyStandings(3); // tourney is done
11101 else ranking = strdup("busy"); //suppress popup when aborted but not finished
11102 } else roundNr = nextGame = matchGame + 1; // normal match, just increment; round equals matchGame
11104 if (nextGame <= appData.matchGames && !abortMatch) {
11105 gameMode = nextGameMode;
11106 matchGame = nextGame; // this will be overruled in tourney mode!
11107 GetTimeMark(&pauseStart); // [HGM] matchpause: stipulate a pause
11108 ScheduleDelayedEvent(NextMatchGame, 10); // but start game immediately (as it will wait out the pause itself)
11109 endingGame = 0; /* [HGM] crash */
11112 gameMode = nextGameMode;
11113 snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
11114 first.tidy, second.tidy,
11115 first.matchWins, second.matchWins,
11116 appData.matchGames - (first.matchWins + second.matchWins));
11117 if(!appData.tourneyFile[0]) matchGame++, DisplayTwoMachinesTitle(); // [HGM] update result in window title
11118 if(ranking && strcmp(ranking, "busy") && appData.afterTourney && appData.afterTourney[0]) RunCommand(appData.afterTourney);
11119 popupRequested++; // [HGM] crash: postpone to after resetting endingGame
11120 if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
11121 first.twoMachinesColor = "black\n";
11122 second.twoMachinesColor = "white\n";
11124 first.twoMachinesColor = "white\n";
11125 second.twoMachinesColor = "black\n";
11129 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
11130 !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
11132 gameMode = nextGameMode;
11134 endingGame = 0; /* [HGM] crash */
11135 if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
11136 if(matchMode == TRUE) { // match through command line: exit with or without popup
11138 ToNrEvent(forwardMostMove);
11139 if(strcmp(ranking, "busy")) DisplayFatalError(ranking, 0, 0);
11141 } else DisplayFatalError(buf, 0, 0);
11142 } else { // match through menu; just stop, with or without popup
11143 matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
11146 if(strcmp(ranking, "busy")) DisplayNote(ranking);
11147 } else DisplayNote(buf);
11149 if(ranking) free(ranking);
11153 /* Assumes program was just initialized (initString sent).
11154 Leaves program in force mode. */
11156 FeedMovesToProgram (ChessProgramState *cps, int upto)
11160 if (appData.debugMode)
11161 fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
11162 startedFromSetupPosition ? "position and " : "",
11163 backwardMostMove, upto, cps->which);
11164 if(currentlyInitializedVariant != gameInfo.variant) {
11166 // [HGM] variantswitch: make engine aware of new variant
11167 if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
11168 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
11169 snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
11170 SendToProgram(buf, cps);
11171 currentlyInitializedVariant = gameInfo.variant;
11173 SendToProgram("force\n", cps);
11174 if (startedFromSetupPosition) {
11175 SendBoard(cps, backwardMostMove);
11176 if (appData.debugMode) {
11177 fprintf(debugFP, "feedMoves\n");
11180 for (i = backwardMostMove; i < upto; i++) {
11181 SendMoveToProgram(i, cps);
11187 ResurrectChessProgram ()
11189 /* The chess program may have exited.
11190 If so, restart it and feed it all the moves made so far. */
11191 static int doInit = 0;
11193 if (appData.noChessProgram) return 1;
11195 if(matchMode /*&& appData.tourneyFile[0]*/) { // [HGM] tourney: make sure we get features after engine replacement. (Should we always do this?)
11196 if(WaitForEngine(&first, TwoMachinesEventIfReady)) { doInit = 1; return 0; } // request to do init on next visit, because we started engine
11197 if(!doInit) return 1; // this replaces testing first.pr != NoProc, which is true when we get here, but first time no reason to abort
11198 doInit = 0; // we fell through (first time after starting the engine); make sure it doesn't happen again
11200 if (first.pr != NoProc) return 1;
11201 StartChessProgram(&first);
11203 InitChessProgram(&first, FALSE);
11204 FeedMovesToProgram(&first, currentMove);
11206 if (!first.sendTime) {
11207 /* can't tell gnuchess what its clock should read,
11208 so we bow to its notion. */
11210 timeRemaining[0][currentMove] = whiteTimeRemaining;
11211 timeRemaining[1][currentMove] = blackTimeRemaining;
11214 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
11215 appData.icsEngineAnalyze) && first.analysisSupport) {
11216 SendToProgram("analyze\n", &first);
11217 first.analyzing = TRUE;
11223 * Button procedures
11226 Reset (int redraw, int init)
11230 if (appData.debugMode) {
11231 fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
11232 redraw, init, gameMode);
11234 CleanupTail(); // [HGM] vari: delete any stored variations
11235 CommentPopDown(); // [HGM] make sure no comments to the previous game keep hanging on
11236 pausing = pauseExamInvalid = FALSE;
11237 startedFromSetupPosition = blackPlaysFirst = FALSE;
11239 whiteFlag = blackFlag = FALSE;
11240 userOfferedDraw = FALSE;
11241 hintRequested = bookRequested = FALSE;
11242 first.maybeThinking = FALSE;
11243 second.maybeThinking = FALSE;
11244 first.bookSuspend = FALSE; // [HGM] book
11245 second.bookSuspend = FALSE;
11246 thinkOutput[0] = NULLCHAR;
11247 lastHint[0] = NULLCHAR;
11248 ClearGameInfo(&gameInfo);
11249 gameInfo.variant = StringToVariant(appData.variant);
11250 if(gameInfo.variant == VariantNormal && strcmp(appData.variant, "normal")) gameInfo.variant = VariantUnknown;
11251 ics_user_moved = ics_clock_paused = FALSE;
11252 ics_getting_history = H_FALSE;
11254 white_holding[0] = black_holding[0] = NULLCHAR;
11255 ClearProgramStats();
11256 opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
11260 flipView = appData.flipView;
11261 ClearPremoveHighlights();
11262 gotPremove = FALSE;
11263 alarmSounded = FALSE;
11265 GameEnds(EndOfFile, NULL, GE_PLAYER);
11266 if(appData.serverMovesName != NULL) {
11267 /* [HGM] prepare to make moves file for broadcasting */
11268 clock_t t = clock();
11269 if(serverMoves != NULL) fclose(serverMoves);
11270 serverMoves = fopen(appData.serverMovesName, "r");
11271 if(serverMoves != NULL) {
11272 fclose(serverMoves);
11273 /* delay 15 sec before overwriting, so all clients can see end */
11274 while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
11276 serverMoves = fopen(appData.serverMovesName, "w");
11280 gameMode = BeginningOfGame;
11282 if(appData.icsActive) gameInfo.variant = VariantNormal;
11283 currentMove = forwardMostMove = backwardMostMove = 0;
11284 MarkTargetSquares(1);
11285 InitPosition(redraw);
11286 for (i = 0; i < MAX_MOVES; i++) {
11287 if (commentList[i] != NULL) {
11288 free(commentList[i]);
11289 commentList[i] = NULL;
11293 timeRemaining[0][0] = whiteTimeRemaining;
11294 timeRemaining[1][0] = blackTimeRemaining;
11296 if (first.pr == NoProc) {
11297 StartChessProgram(&first);
11300 InitChessProgram(&first, startedFromSetupPosition);
11303 DisplayMessage("", "");
11304 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11305 lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
11306 ClearMap(); // [HGM] exclude: invalidate map
11310 AutoPlayGameLoop ()
11313 if (!AutoPlayOneMove())
11315 if (matchMode || appData.timeDelay == 0)
11317 if (appData.timeDelay < 0)
11319 StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
11327 ReloadGame(1); // next game
11333 int fromX, fromY, toX, toY;
11335 if (appData.debugMode) {
11336 fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
11339 if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
11342 if (gameMode == AnalyzeFile && currentMove > backwardMostMove && programStats.depth) {
11343 pvInfoList[currentMove].depth = programStats.depth;
11344 pvInfoList[currentMove].score = programStats.score;
11345 pvInfoList[currentMove].time = 0;
11346 if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
11347 else { // append analysis of final position as comment
11349 snprintf(buf, MSG_SIZ, "{final score %+4.2f/%d}", programStats.score/100., programStats.depth);
11350 AppendComment(currentMove, buf, 3); // the 3 prevents stripping of the score/depth!
11352 programStats.depth = 0;
11355 if (currentMove >= forwardMostMove) {
11356 if(gameMode == AnalyzeFile) {
11357 if(appData.loadGameIndex == -1) {
11358 GameEnds(gameInfo.result, gameInfo.resultDetails ? gameInfo.resultDetails : "", GE_FILE);
11359 ScheduleDelayedEvent(AnalyzeNextGame, 10);
11361 ExitAnalyzeMode(); SendToProgram("force\n", &first);
11364 // gameMode = EndOfGame;
11365 // ModeHighlight();
11367 /* [AS] Clear current move marker at the end of a game */
11368 /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
11373 toX = moveList[currentMove][2] - AAA;
11374 toY = moveList[currentMove][3] - ONE;
11376 if (moveList[currentMove][1] == '@') {
11377 if (appData.highlightLastMove) {
11378 SetHighlights(-1, -1, toX, toY);
11381 fromX = moveList[currentMove][0] - AAA;
11382 fromY = moveList[currentMove][1] - ONE;
11384 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
11386 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
11388 if (appData.highlightLastMove) {
11389 SetHighlights(fromX, fromY, toX, toY);
11392 DisplayMove(currentMove);
11393 SendMoveToProgram(currentMove++, &first);
11394 DisplayBothClocks();
11395 DrawPosition(FALSE, boards[currentMove]);
11396 // [HGM] PV info: always display, routine tests if empty
11397 DisplayComment(currentMove - 1, commentList[currentMove]);
11403 LoadGameOneMove (ChessMove readAhead)
11405 int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
11406 char promoChar = NULLCHAR;
11407 ChessMove moveType;
11408 char move[MSG_SIZ];
11411 if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
11412 gameMode != AnalyzeMode && gameMode != Training) {
11417 yyboardindex = forwardMostMove;
11418 if (readAhead != EndOfFile) {
11419 moveType = readAhead;
11421 if (gameFileFP == NULL)
11423 moveType = (ChessMove) Myylex();
11427 switch (moveType) {
11429 if (appData.debugMode)
11430 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11433 /* append the comment but don't display it */
11434 AppendComment(currentMove, p, FALSE);
11437 case WhiteCapturesEnPassant:
11438 case BlackCapturesEnPassant:
11439 case WhitePromotion:
11440 case BlackPromotion:
11441 case WhiteNonPromotion:
11442 case BlackNonPromotion:
11444 case WhiteKingSideCastle:
11445 case WhiteQueenSideCastle:
11446 case BlackKingSideCastle:
11447 case BlackQueenSideCastle:
11448 case WhiteKingSideCastleWild:
11449 case WhiteQueenSideCastleWild:
11450 case BlackKingSideCastleWild:
11451 case BlackQueenSideCastleWild:
11453 case WhiteHSideCastleFR:
11454 case WhiteASideCastleFR:
11455 case BlackHSideCastleFR:
11456 case BlackASideCastleFR:
11458 if (appData.debugMode)
11459 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
11460 fromX = currentMoveString[0] - AAA;
11461 fromY = currentMoveString[1] - ONE;
11462 toX = currentMoveString[2] - AAA;
11463 toY = currentMoveString[3] - ONE;
11464 promoChar = currentMoveString[4];
11469 if (appData.debugMode)
11470 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
11471 fromX = moveType == WhiteDrop ?
11472 (int) CharToPiece(ToUpper(currentMoveString[0])) :
11473 (int) CharToPiece(ToLower(currentMoveString[0]));
11475 toX = currentMoveString[2] - AAA;
11476 toY = currentMoveString[3] - ONE;
11482 case GameUnfinished:
11483 if (appData.debugMode)
11484 fprintf(debugFP, "Parsed game end: %s\n", yy_text);
11485 p = strchr(yy_text, '{');
11486 if (p == NULL) p = strchr(yy_text, '(');
11489 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
11491 q = strchr(p, *p == '{' ? '}' : ')');
11492 if (q != NULL) *q = NULLCHAR;
11495 while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
11496 GameEnds(moveType, p, GE_FILE);
11498 if (cmailMsgLoaded) {
11500 flipView = WhiteOnMove(currentMove);
11501 if (moveType == GameUnfinished) flipView = !flipView;
11502 if (appData.debugMode)
11503 fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
11508 if (appData.debugMode)
11509 fprintf(debugFP, "Parser hit end of file\n");
11510 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11516 if (WhiteOnMove(currentMove)) {
11517 GameEnds(BlackWins, "Black mates", GE_FILE);
11519 GameEnds(WhiteWins, "White mates", GE_FILE);
11523 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
11529 case MoveNumberOne:
11530 if (lastLoadGameStart == GNUChessGame) {
11531 /* GNUChessGames have numbers, but they aren't move numbers */
11532 if (appData.debugMode)
11533 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
11534 yy_text, (int) moveType);
11535 return LoadGameOneMove(EndOfFile); /* tail recursion */
11537 /* else fall thru */
11542 /* Reached start of next game in file */
11543 if (appData.debugMode)
11544 fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
11545 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11551 if (WhiteOnMove(currentMove)) {
11552 GameEnds(BlackWins, "Black mates", GE_FILE);
11554 GameEnds(WhiteWins, "White mates", GE_FILE);
11558 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
11564 case PositionDiagram: /* should not happen; ignore */
11565 case ElapsedTime: /* ignore */
11566 case NAG: /* ignore */
11567 if (appData.debugMode)
11568 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
11569 yy_text, (int) moveType);
11570 return LoadGameOneMove(EndOfFile); /* tail recursion */
11573 if (appData.testLegality) {
11574 if (appData.debugMode)
11575 fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
11576 snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
11577 (forwardMostMove / 2) + 1,
11578 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11579 DisplayError(move, 0);
11582 if (appData.debugMode)
11583 fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
11584 yy_text, currentMoveString);
11585 fromX = currentMoveString[0] - AAA;
11586 fromY = currentMoveString[1] - ONE;
11587 toX = currentMoveString[2] - AAA;
11588 toY = currentMoveString[3] - ONE;
11589 promoChar = currentMoveString[4];
11593 case AmbiguousMove:
11594 if (appData.debugMode)
11595 fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
11596 snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
11597 (forwardMostMove / 2) + 1,
11598 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11599 DisplayError(move, 0);
11604 case ImpossibleMove:
11605 if (appData.debugMode)
11606 fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
11607 snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
11608 (forwardMostMove / 2) + 1,
11609 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11610 DisplayError(move, 0);
11616 if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
11617 DrawPosition(FALSE, boards[currentMove]);
11618 DisplayBothClocks();
11619 if (!appData.matchMode) // [HGM] PV info: routine tests if empty
11620 DisplayComment(currentMove - 1, commentList[currentMove]);
11622 (void) StopLoadGameTimer();
11624 cmailOldMove = forwardMostMove;
11627 /* currentMoveString is set as a side-effect of yylex */
11629 thinkOutput[0] = NULLCHAR;
11630 MakeMove(fromX, fromY, toX, toY, promoChar);
11631 currentMove = forwardMostMove;
11636 /* Load the nth game from the given file */
11638 LoadGameFromFile (char *filename, int n, char *title, int useList)
11643 if (strcmp(filename, "-") == 0) {
11647 f = fopen(filename, "rb");
11649 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
11650 DisplayError(buf, errno);
11654 if (fseek(f, 0, 0) == -1) {
11655 /* f is not seekable; probably a pipe */
11658 if (useList && n == 0) {
11659 int error = GameListBuild(f);
11661 DisplayError(_("Cannot build game list"), error);
11662 } else if (!ListEmpty(&gameList) &&
11663 ((ListGame *) gameList.tailPred)->number > 1) {
11664 GameListPopUp(f, title);
11671 return LoadGame(f, n, title, FALSE);
11676 MakeRegisteredMove ()
11678 int fromX, fromY, toX, toY;
11680 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11681 switch (cmailMoveType[lastLoadGameNumber - 1]) {
11684 if (appData.debugMode)
11685 fprintf(debugFP, "Restoring %s for game %d\n",
11686 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
11688 thinkOutput[0] = NULLCHAR;
11689 safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
11690 fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
11691 fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
11692 toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
11693 toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
11694 promoChar = cmailMove[lastLoadGameNumber - 1][4];
11695 MakeMove(fromX, fromY, toX, toY, promoChar);
11696 ShowMove(fromX, fromY, toX, toY);
11698 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11705 if (WhiteOnMove(currentMove)) {
11706 GameEnds(BlackWins, "Black mates", GE_PLAYER);
11708 GameEnds(WhiteWins, "White mates", GE_PLAYER);
11713 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
11720 if (WhiteOnMove(currentMove)) {
11721 GameEnds(BlackWins, "White resigns", GE_PLAYER);
11723 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11728 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11739 /* Wrapper around LoadGame for use when a Cmail message is loaded */
11741 CmailLoadGame (FILE *f, int gameNumber, char *title, int useList)
11745 if (gameNumber > nCmailGames) {
11746 DisplayError(_("No more games in this message"), 0);
11749 if (f == lastLoadGameFP) {
11750 int offset = gameNumber - lastLoadGameNumber;
11752 cmailMsg[0] = NULLCHAR;
11753 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11754 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
11755 nCmailMovesRegistered--;
11757 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11758 if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
11759 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
11762 if (! RegisterMove()) return FALSE;
11766 retVal = LoadGame(f, gameNumber, title, useList);
11768 /* Make move registered during previous look at this game, if any */
11769 MakeRegisteredMove();
11771 if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
11772 commentList[currentMove]
11773 = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
11774 DisplayComment(currentMove - 1, commentList[currentMove]);
11780 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
11782 ReloadGame (int offset)
11784 int gameNumber = lastLoadGameNumber + offset;
11785 if (lastLoadGameFP == NULL) {
11786 DisplayError(_("No game has been loaded yet"), 0);
11789 if (gameNumber <= 0) {
11790 DisplayError(_("Can't back up any further"), 0);
11793 if (cmailMsgLoaded) {
11794 return CmailLoadGame(lastLoadGameFP, gameNumber,
11795 lastLoadGameTitle, lastLoadGameUseList);
11797 return LoadGame(lastLoadGameFP, gameNumber,
11798 lastLoadGameTitle, lastLoadGameUseList);
11802 int keys[EmptySquare+1];
11805 PositionMatches (Board b1, Board b2)
11808 switch(appData.searchMode) {
11809 case 1: return CompareWithRights(b1, b2);
11811 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11812 if(b2[r][f] != EmptySquare && b1[r][f] != b2[r][f]) return FALSE;
11816 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11817 if((b2[r][f] == WhitePawn || b2[r][f] == BlackPawn) && b1[r][f] != b2[r][f]) return FALSE;
11818 sum += keys[b1[r][f]] - keys[b2[r][f]];
11822 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11823 sum += keys[b1[r][f]] - keys[b2[r][f]];
11835 int pieceList[256], quickBoard[256];
11836 ChessSquare pieceType[256] = { EmptySquare };
11837 Board soughtBoard, reverseBoard, flipBoard, rotateBoard;
11838 int counts[EmptySquare], minSought[EmptySquare], minReverse[EmptySquare], maxSought[EmptySquare], maxReverse[EmptySquare];
11839 int soughtTotal, turn;
11840 Boolean epOK, flipSearch;
11843 unsigned char piece, to;
11846 #define DSIZE (250000)
11848 Move initialSpace[DSIZE+1000]; // gamble on that game will not be more than 500 moves
11849 Move *moveDatabase = initialSpace;
11850 unsigned int movePtr, dataSize = DSIZE;
11853 MakePieceList (Board board, int *counts)
11855 int r, f, n=Q_PROMO, total=0;
11856 for(r=0;r<EmptySquare;r++) counts[r] = 0; // piece-type counts
11857 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11858 int sq = f + (r<<4);
11859 if(board[r][f] == EmptySquare) quickBoard[sq] = 0; else {
11860 quickBoard[sq] = ++n;
11862 pieceType[n] = board[r][f];
11863 counts[board[r][f]]++;
11864 if(board[r][f] == WhiteKing) pieceList[1] = n; else
11865 if(board[r][f] == BlackKing) pieceList[2] = n; // remember which are Kings, for castling
11869 epOK = gameInfo.variant != VariantXiangqi && gameInfo.variant != VariantBerolina;
11874 PackMove (int fromX, int fromY, int toX, int toY, ChessSquare promoPiece)
11876 int sq = fromX + (fromY<<4);
11877 int piece = quickBoard[sq];
11878 quickBoard[sq] = 0;
11879 moveDatabase[movePtr].to = pieceList[piece] = sq = toX + (toY<<4);
11880 if(piece == pieceList[1] && fromY == toY && (toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
11881 int from = toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT;
11882 moveDatabase[movePtr++].piece = Q_WCASTL;
11883 quickBoard[sq] = piece;
11884 piece = quickBoard[from]; quickBoard[from] = 0;
11885 moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
11887 if(piece == pieceList[2] && fromY == toY && (toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
11888 int from = (toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT) + (BOARD_HEIGHT-1 <<4);
11889 moveDatabase[movePtr++].piece = Q_BCASTL;
11890 quickBoard[sq] = piece;
11891 piece = quickBoard[from]; quickBoard[from] = 0;
11892 moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
11894 if(epOK && (pieceType[piece] == WhitePawn || pieceType[piece] == BlackPawn) && fromX != toX && quickBoard[sq] == 0) {
11895 quickBoard[(fromY<<4)+toX] = 0;
11896 moveDatabase[movePtr].piece = Q_EP;
11897 moveDatabase[movePtr++].to = (fromY<<4)+toX;
11898 moveDatabase[movePtr].to = sq;
11900 if(promoPiece != pieceType[piece]) {
11901 moveDatabase[movePtr++].piece = Q_PROMO;
11902 moveDatabase[movePtr].to = pieceType[piece] = (int) promoPiece;
11904 moveDatabase[movePtr].piece = piece;
11905 quickBoard[sq] = piece;
11910 PackGame (Board board)
11912 Move *newSpace = NULL;
11913 moveDatabase[movePtr].piece = 0; // terminate previous game
11914 if(movePtr > dataSize) {
11915 if(appData.debugMode) fprintf(debugFP, "move-cache overflow, enlarge to %d MB\n", dataSize/128);
11916 dataSize *= 8; // increase size by factor 8 (512KB -> 4MB -> 32MB -> 256MB -> 2GB)
11917 if(dataSize) newSpace = (Move*) calloc(dataSize + 1000, sizeof(Move));
11920 Move *p = moveDatabase, *q = newSpace;
11921 for(i=0; i<movePtr; i++) *q++ = *p++; // copy to newly allocated space
11922 if(dataSize > 8*DSIZE) free(moveDatabase); // and free old space (if it was allocated)
11923 moveDatabase = newSpace;
11924 } else { // calloc failed, we must be out of memory. Too bad...
11925 dataSize = 0; // prevent calloc events for all subsequent games
11926 return 0; // and signal this one isn't cached
11930 MakePieceList(board, counts);
11935 QuickCompare (Board board, int *minCounts, int *maxCounts)
11936 { // compare according to search mode
11938 switch(appData.searchMode)
11940 case 1: // exact position match
11941 if(!(turn & board[EP_STATUS-1])) return FALSE; // wrong side to move
11942 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11943 if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11946 case 2: // can have extra material on empty squares
11947 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11948 if(board[r][f] == EmptySquare) continue;
11949 if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11952 case 3: // material with exact Pawn structure
11953 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11954 if(board[r][f] != WhitePawn && board[r][f] != BlackPawn) continue;
11955 if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11956 } // fall through to material comparison
11957 case 4: // exact material
11958 for(r=0; r<EmptySquare; r++) if(counts[r] != maxCounts[r]) return FALSE;
11960 case 6: // material range with given imbalance
11961 for(r=0; r<BlackPawn; r++) if(counts[r] - minCounts[r] != counts[r+BlackPawn] - minCounts[r+BlackPawn]) return FALSE;
11962 // fall through to range comparison
11963 case 5: // material range
11964 for(r=0; r<EmptySquare; r++) if(counts[r] < minCounts[r] || counts[r] > maxCounts[r]) return FALSE;
11970 QuickScan (Board board, Move *move)
11971 { // reconstruct game,and compare all positions in it
11972 int cnt=0, stretch=0, total = MakePieceList(board, counts);
11974 int piece = move->piece;
11975 int to = move->to, from = pieceList[piece];
11976 if(piece <= Q_PROMO) { // special moves encoded by otherwise invalid piece numbers 1-4
11977 if(!piece) return -1;
11978 if(piece == Q_PROMO) { // promotion, encoded as (Q_PROMO, to) + (piece, promoType)
11979 piece = (++move)->piece;
11980 from = pieceList[piece];
11981 counts[pieceType[piece]]--;
11982 pieceType[piece] = (ChessSquare) move->to;
11983 counts[move->to]++;
11984 } else if(piece == Q_EP) { // e.p. capture, encoded as (Q_EP, ep-sqr) + (piece, to)
11985 counts[pieceType[quickBoard[to]]]--;
11986 quickBoard[to] = 0; total--;
11989 } else if(piece <= Q_BCASTL) { // castling, encoded as (Q_XCASTL, king-to) + (rook, rook-to)
11990 piece = pieceList[piece]; // first two elements of pieceList contain King numbers
11991 from = pieceList[piece]; // so this must be King
11992 quickBoard[from] = 0;
11993 pieceList[piece] = to;
11994 from = pieceList[(++move)->piece]; // for FRC this has to be done here
11995 quickBoard[from] = 0; // rook
11996 quickBoard[to] = piece;
11997 to = move->to; piece = move->piece;
12001 if(appData.searchMode > 2) counts[pieceType[quickBoard[to]]]--; // account capture
12002 if((total -= (quickBoard[to] != 0)) < soughtTotal) return -1; // piece count dropped below what we search for
12003 quickBoard[from] = 0;
12005 quickBoard[to] = piece;
12006 pieceList[piece] = to;
12008 if(QuickCompare(soughtBoard, minSought, maxSought) ||
12009 appData.ignoreColors && QuickCompare(reverseBoard, minReverse, maxReverse) ||
12010 flipSearch && (QuickCompare(flipBoard, minSought, maxSought) ||
12011 appData.ignoreColors && QuickCompare(rotateBoard, minReverse, maxReverse))
12013 static int lastCounts[EmptySquare+1];
12015 if(stretch) for(i=0; i<EmptySquare; i++) if(lastCounts[i] != counts[i]) { stretch = 0; break; } // reset if material changes
12016 if(stretch++ == 0) for(i=0; i<EmptySquare; i++) lastCounts[i] = counts[i]; // remember actual material
12017 } else stretch = 0;
12018 if(stretch && (appData.searchMode == 1 || stretch >= appData.stretch)) return cnt + 1 - stretch;
12027 flipSearch = FALSE;
12028 CopyBoard(soughtBoard, boards[currentMove]);
12029 soughtTotal = MakePieceList(soughtBoard, maxSought);
12030 soughtBoard[EP_STATUS-1] = (currentMove & 1) + 1;
12031 if(currentMove == 0 && gameMode == EditPosition) soughtBoard[EP_STATUS-1] = blackPlaysFirst + 1; // (!)
12032 CopyBoard(reverseBoard, boards[currentMove]);
12033 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12034 int piece = boards[currentMove][BOARD_HEIGHT-1-r][f];
12035 if(piece < BlackPawn) piece += BlackPawn; else if(piece < EmptySquare) piece -= BlackPawn; // color-flip
12036 reverseBoard[r][f] = piece;
12038 reverseBoard[EP_STATUS-1] = soughtBoard[EP_STATUS-1] ^ 3;
12039 for(r=0; r<6; r++) reverseBoard[CASTLING][r] = boards[currentMove][CASTLING][(r+3)%6];
12040 if(appData.findMirror && appData.searchMode <= 3 && (!nrCastlingRights
12041 || (boards[currentMove][CASTLING][2] == NoRights ||
12042 boards[currentMove][CASTLING][0] == NoRights && boards[currentMove][CASTLING][1] == NoRights )
12043 && (boards[currentMove][CASTLING][5] == NoRights ||
12044 boards[currentMove][CASTLING][3] == NoRights && boards[currentMove][CASTLING][4] == NoRights ) )
12047 CopyBoard(flipBoard, soughtBoard);
12048 CopyBoard(rotateBoard, reverseBoard);
12049 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12050 flipBoard[r][f] = soughtBoard[r][BOARD_WIDTH-1-f];
12051 rotateBoard[r][f] = reverseBoard[r][BOARD_WIDTH-1-f];
12054 for(r=0; r<BlackPawn; r++) maxReverse[r] = maxSought[r+BlackPawn], maxReverse[r+BlackPawn] = maxSought[r];
12055 if(appData.searchMode >= 5) {
12056 for(r=BOARD_HEIGHT/2; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) soughtBoard[r][f] = EmptySquare;
12057 MakePieceList(soughtBoard, minSought);
12058 for(r=0; r<BlackPawn; r++) minReverse[r] = minSought[r+BlackPawn], minReverse[r+BlackPawn] = minSought[r];
12060 if(gameInfo.variant == VariantCrazyhouse || gameInfo.variant == VariantShogi || gameInfo.variant == VariantBughouse)
12061 soughtTotal = 0; // in drop games nr of pieces does not fall monotonously
12064 GameInfo dummyInfo;
12065 static int creatingBook;
12068 GameContainsPosition (FILE *f, ListGame *lg)
12070 int next, btm=0, plyNr=0, scratch=forwardMostMove+2&~1;
12071 int fromX, fromY, toX, toY;
12073 static int initDone=FALSE;
12075 // weed out games based on numerical tag comparison
12076 if(lg->gameInfo.variant != gameInfo.variant) return -1; // wrong variant
12077 if(appData.eloThreshold1 && (lg->gameInfo.whiteRating < appData.eloThreshold1 && lg->gameInfo.blackRating < appData.eloThreshold1)) return -1;
12078 if(appData.eloThreshold2 && (lg->gameInfo.whiteRating < appData.eloThreshold2 || lg->gameInfo.blackRating < appData.eloThreshold2)) return -1;
12079 if(appData.dateThreshold && (!lg->gameInfo.date || atoi(lg->gameInfo.date) < appData.dateThreshold)) return -1;
12081 for(next = WhitePawn; next<EmptySquare; next++) keys[next] = random()>>8 ^ random()<<6 ^random()<<20;
12084 if(lg->gameInfo.fen) ParseFEN(boards[scratch], &btm, lg->gameInfo.fen);
12085 else CopyBoard(boards[scratch], initialPosition); // default start position
12088 if((next = QuickScan( boards[scratch], &moveDatabase[lg->moves] )) < 0) return -1; // quick scan rules out it is there
12089 if(appData.searchMode >= 4) return next; // for material searches, trust QuickScan.
12092 if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
12093 fseek(f, lg->offset, 0);
12096 yyboardindex = scratch;
12097 quickFlag = plyNr+1;
12102 if(plyNr) return -1; // after we have seen moves, any tags will be start of next game
12108 if(plyNr) return -1; // after we have seen moves, this is for new game
12111 case AmbiguousMove: // we cannot reconstruct the game beyond these two
12112 case ImpossibleMove:
12113 case WhiteWins: // game ends here with these four
12116 case GameUnfinished:
12120 if(appData.testLegality) return -1;
12121 case WhiteCapturesEnPassant:
12122 case BlackCapturesEnPassant:
12123 case WhitePromotion:
12124 case BlackPromotion:
12125 case WhiteNonPromotion:
12126 case BlackNonPromotion:
12128 case WhiteKingSideCastle:
12129 case WhiteQueenSideCastle:
12130 case BlackKingSideCastle:
12131 case BlackQueenSideCastle:
12132 case WhiteKingSideCastleWild:
12133 case WhiteQueenSideCastleWild:
12134 case BlackKingSideCastleWild:
12135 case BlackQueenSideCastleWild:
12136 case WhiteHSideCastleFR:
12137 case WhiteASideCastleFR:
12138 case BlackHSideCastleFR:
12139 case BlackASideCastleFR:
12140 fromX = currentMoveString[0] - AAA;
12141 fromY = currentMoveString[1] - ONE;
12142 toX = currentMoveString[2] - AAA;
12143 toY = currentMoveString[3] - ONE;
12144 promoChar = currentMoveString[4];
12148 fromX = next == WhiteDrop ?
12149 (int) CharToPiece(ToUpper(currentMoveString[0])) :
12150 (int) CharToPiece(ToLower(currentMoveString[0]));
12152 toX = currentMoveString[2] - AAA;
12153 toY = currentMoveString[3] - ONE;
12157 // Move encountered; peform it. We need to shuttle between two boards, as even/odd index determines side to move
12159 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[scratch]);
12160 if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
12161 if(appData.ignoreColors && PositionMatches(boards[scratch], reverseBoard)) return plyNr;
12162 if(appData.findMirror) {
12163 if(PositionMatches(boards[scratch], flipBoard)) return plyNr;
12164 if(appData.ignoreColors && PositionMatches(boards[scratch], rotateBoard)) return plyNr;
12169 /* Load the nth game from open file f */
12171 LoadGame (FILE *f, int gameNumber, char *title, int useList)
12175 int gn = gameNumber;
12176 ListGame *lg = NULL;
12177 int numPGNTags = 0;
12179 GameMode oldGameMode;
12180 VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
12182 if (appData.debugMode)
12183 fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
12185 if (gameMode == Training )
12186 SetTrainingModeOff();
12188 oldGameMode = gameMode;
12189 if (gameMode != BeginningOfGame) {
12190 Reset(FALSE, TRUE);
12194 if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
12195 fclose(lastLoadGameFP);
12199 lg = (ListGame *) ListElem(&gameList, gameNumber-1);
12202 fseek(f, lg->offset, 0);
12203 GameListHighlight(gameNumber);
12204 pos = lg->position;
12208 if(oldGameMode == AnalyzeFile && appData.loadGameIndex == -1)
12209 appData.loadGameIndex = 0; // [HGM] suppress error message if we reach file end after auto-stepping analysis
12211 DisplayError(_("Game number out of range"), 0);
12216 if (fseek(f, 0, 0) == -1) {
12217 if (f == lastLoadGameFP ?
12218 gameNumber == lastLoadGameNumber + 1 :
12222 DisplayError(_("Can't seek on game file"), 0);
12227 lastLoadGameFP = f;
12228 lastLoadGameNumber = gameNumber;
12229 safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
12230 lastLoadGameUseList = useList;
12234 if (lg && lg->gameInfo.white && lg->gameInfo.black) {
12235 snprintf(buf, sizeof(buf), "%s %s %s", lg->gameInfo.white, _("vs."),
12236 lg->gameInfo.black);
12238 } else if (*title != NULLCHAR) {
12239 if (gameNumber > 1) {
12240 snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
12243 DisplayTitle(title);
12247 if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
12248 gameMode = PlayFromGameFile;
12252 currentMove = forwardMostMove = backwardMostMove = 0;
12253 CopyBoard(boards[0], initialPosition);
12257 * Skip the first gn-1 games in the file.
12258 * Also skip over anything that precedes an identifiable
12259 * start of game marker, to avoid being confused by
12260 * garbage at the start of the file. Currently
12261 * recognized start of game markers are the move number "1",
12262 * the pattern "gnuchess .* game", the pattern
12263 * "^[#;%] [^ ]* game file", and a PGN tag block.
12264 * A game that starts with one of the latter two patterns
12265 * will also have a move number 1, possibly
12266 * following a position diagram.
12267 * 5-4-02: Let's try being more lenient and allowing a game to
12268 * start with an unnumbered move. Does that break anything?
12270 cm = lastLoadGameStart = EndOfFile;
12272 yyboardindex = forwardMostMove;
12273 cm = (ChessMove) Myylex();
12276 if (cmailMsgLoaded) {
12277 nCmailGames = CMAIL_MAX_GAMES - gn;
12280 DisplayError(_("Game not found in file"), 0);
12287 lastLoadGameStart = cm;
12290 case MoveNumberOne:
12291 switch (lastLoadGameStart) {
12296 case MoveNumberOne:
12298 gn--; /* count this game */
12299 lastLoadGameStart = cm;
12308 switch (lastLoadGameStart) {
12311 case MoveNumberOne:
12313 gn--; /* count this game */
12314 lastLoadGameStart = cm;
12317 lastLoadGameStart = cm; /* game counted already */
12325 yyboardindex = forwardMostMove;
12326 cm = (ChessMove) Myylex();
12327 } while (cm == PGNTag || cm == Comment);
12334 if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
12335 if ( cmailResult[CMAIL_MAX_GAMES - gn - 1]
12336 != CMAIL_OLD_RESULT) {
12338 cmailResult[ CMAIL_MAX_GAMES
12339 - gn - 1] = CMAIL_OLD_RESULT;
12345 /* Only a NormalMove can be at the start of a game
12346 * without a position diagram. */
12347 if (lastLoadGameStart == EndOfFile ) {
12349 lastLoadGameStart = MoveNumberOne;
12358 if (appData.debugMode)
12359 fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
12361 if (cm == XBoardGame) {
12362 /* Skip any header junk before position diagram and/or move 1 */
12364 yyboardindex = forwardMostMove;
12365 cm = (ChessMove) Myylex();
12367 if (cm == EndOfFile ||
12368 cm == GNUChessGame || cm == XBoardGame) {
12369 /* Empty game; pretend end-of-file and handle later */
12374 if (cm == MoveNumberOne || cm == PositionDiagram ||
12375 cm == PGNTag || cm == Comment)
12378 } else if (cm == GNUChessGame) {
12379 if (gameInfo.event != NULL) {
12380 free(gameInfo.event);
12382 gameInfo.event = StrSave(yy_text);
12385 startedFromSetupPosition = FALSE;
12386 while (cm == PGNTag) {
12387 if (appData.debugMode)
12388 fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
12389 err = ParsePGNTag(yy_text, &gameInfo);
12390 if (!err) numPGNTags++;
12392 /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
12393 if(gameInfo.variant != oldVariant) {
12394 startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
12395 ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
12396 InitPosition(TRUE);
12397 oldVariant = gameInfo.variant;
12398 if (appData.debugMode)
12399 fprintf(debugFP, "New variant %d\n", (int) oldVariant);
12403 if (gameInfo.fen != NULL) {
12404 Board initial_position;
12405 startedFromSetupPosition = TRUE;
12406 if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
12408 DisplayError(_("Bad FEN position in file"), 0);
12411 CopyBoard(boards[0], initial_position);
12412 if (blackPlaysFirst) {
12413 currentMove = forwardMostMove = backwardMostMove = 1;
12414 CopyBoard(boards[1], initial_position);
12415 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12416 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12417 timeRemaining[0][1] = whiteTimeRemaining;
12418 timeRemaining[1][1] = blackTimeRemaining;
12419 if (commentList[0] != NULL) {
12420 commentList[1] = commentList[0];
12421 commentList[0] = NULL;
12424 currentMove = forwardMostMove = backwardMostMove = 0;
12426 /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
12428 initialRulePlies = FENrulePlies;
12429 for( i=0; i< nrCastlingRights; i++ )
12430 initialRights[i] = initial_position[CASTLING][i];
12432 yyboardindex = forwardMostMove;
12433 free(gameInfo.fen);
12434 gameInfo.fen = NULL;
12437 yyboardindex = forwardMostMove;
12438 cm = (ChessMove) Myylex();
12440 /* Handle comments interspersed among the tags */
12441 while (cm == Comment) {
12443 if (appData.debugMode)
12444 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
12446 AppendComment(currentMove, p, FALSE);
12447 yyboardindex = forwardMostMove;
12448 cm = (ChessMove) Myylex();
12452 /* don't rely on existence of Event tag since if game was
12453 * pasted from clipboard the Event tag may not exist
12455 if (numPGNTags > 0){
12457 if (gameInfo.variant == VariantNormal) {
12458 VariantClass v = StringToVariant(gameInfo.event);
12459 // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
12460 if(v < VariantShogi) gameInfo.variant = v;
12463 if( appData.autoDisplayTags ) {
12464 tags = PGNTags(&gameInfo);
12465 TagsPopUp(tags, CmailMsg());
12470 /* Make something up, but don't display it now */
12475 if (cm == PositionDiagram) {
12478 Board initial_position;
12480 if (appData.debugMode)
12481 fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
12483 if (!startedFromSetupPosition) {
12485 for (i = BOARD_HEIGHT - 1; i >= 0; i--)
12486 for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
12497 initial_position[i][j++] = CharToPiece(*p);
12500 while (*p == ' ' || *p == '\t' ||
12501 *p == '\n' || *p == '\r') p++;
12503 if (strncmp(p, "black", strlen("black"))==0)
12504 blackPlaysFirst = TRUE;
12506 blackPlaysFirst = FALSE;
12507 startedFromSetupPosition = TRUE;
12509 CopyBoard(boards[0], initial_position);
12510 if (blackPlaysFirst) {
12511 currentMove = forwardMostMove = backwardMostMove = 1;
12512 CopyBoard(boards[1], initial_position);
12513 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12514 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12515 timeRemaining[0][1] = whiteTimeRemaining;
12516 timeRemaining[1][1] = blackTimeRemaining;
12517 if (commentList[0] != NULL) {
12518 commentList[1] = commentList[0];
12519 commentList[0] = NULL;
12522 currentMove = forwardMostMove = backwardMostMove = 0;
12525 yyboardindex = forwardMostMove;
12526 cm = (ChessMove) Myylex();
12529 if(!creatingBook) {
12530 if (first.pr == NoProc) {
12531 StartChessProgram(&first);
12533 InitChessProgram(&first, FALSE);
12534 SendToProgram("force\n", &first);
12535 if (startedFromSetupPosition) {
12536 SendBoard(&first, forwardMostMove);
12537 if (appData.debugMode) {
12538 fprintf(debugFP, "Load Game\n");
12540 DisplayBothClocks();
12544 /* [HGM] server: flag to write setup moves in broadcast file as one */
12545 loadFlag = appData.suppressLoadMoves;
12547 while (cm == Comment) {
12549 if (appData.debugMode)
12550 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
12552 AppendComment(currentMove, p, FALSE);
12553 yyboardindex = forwardMostMove;
12554 cm = (ChessMove) Myylex();
12557 if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
12558 cm == WhiteWins || cm == BlackWins ||
12559 cm == GameIsDrawn || cm == GameUnfinished) {
12560 DisplayMessage("", _("No moves in game"));
12561 if (cmailMsgLoaded) {
12562 if (appData.debugMode)
12563 fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
12567 DrawPosition(FALSE, boards[currentMove]);
12568 DisplayBothClocks();
12569 gameMode = EditGame;
12576 // [HGM] PV info: routine tests if comment empty
12577 if (!matchMode && (pausing || appData.timeDelay != 0)) {
12578 DisplayComment(currentMove - 1, commentList[currentMove]);
12580 if (!matchMode && appData.timeDelay != 0)
12581 DrawPosition(FALSE, boards[currentMove]);
12583 if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
12584 programStats.ok_to_send = 1;
12587 /* if the first token after the PGN tags is a move
12588 * and not move number 1, retrieve it from the parser
12590 if (cm != MoveNumberOne)
12591 LoadGameOneMove(cm);
12593 /* load the remaining moves from the file */
12594 while (LoadGameOneMove(EndOfFile)) {
12595 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12596 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12599 /* rewind to the start of the game */
12600 currentMove = backwardMostMove;
12602 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12604 if (oldGameMode == AnalyzeFile) {
12605 appData.loadGameIndex = -1; // [HGM] order auto-stepping through games
12606 AnalyzeFileEvent();
12608 if (oldGameMode == AnalyzeMode) {
12609 AnalyzeFileEvent();
12612 if(gameInfo.result == GameUnfinished && gameInfo.resultDetails && appData.clockMode) {
12613 long int w, b; // [HGM] adjourn: restore saved clock times
12614 char *p = strstr(gameInfo.resultDetails, "(Clocks:");
12615 if(p && sscanf(p+8, "%ld,%ld", &w, &b) == 2) {
12616 timeRemaining[0][forwardMostMove] = whiteTimeRemaining = 1000*w + 500;
12617 timeRemaining[1][forwardMostMove] = blackTimeRemaining = 1000*b + 500;
12621 if(creatingBook) return TRUE;
12622 if (!matchMode && pos > 0) {
12623 ToNrEvent(pos); // [HGM] no autoplay if selected on position
12625 if (matchMode || appData.timeDelay == 0) {
12627 } else if (appData.timeDelay > 0) {
12628 AutoPlayGameLoop();
12631 if (appData.debugMode)
12632 fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
12634 loadFlag = 0; /* [HGM] true game starts */
12638 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
12640 ReloadPosition (int offset)
12642 int positionNumber = lastLoadPositionNumber + offset;
12643 if (lastLoadPositionFP == NULL) {
12644 DisplayError(_("No position has been loaded yet"), 0);
12647 if (positionNumber <= 0) {
12648 DisplayError(_("Can't back up any further"), 0);
12651 return LoadPosition(lastLoadPositionFP, positionNumber,
12652 lastLoadPositionTitle);
12655 /* Load the nth position from the given file */
12657 LoadPositionFromFile (char *filename, int n, char *title)
12662 if (strcmp(filename, "-") == 0) {
12663 return LoadPosition(stdin, n, "stdin");
12665 f = fopen(filename, "rb");
12667 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12668 DisplayError(buf, errno);
12671 return LoadPosition(f, n, title);
12676 /* Load the nth position from the given open file, and close it */
12678 LoadPosition (FILE *f, int positionNumber, char *title)
12680 char *p, line[MSG_SIZ];
12681 Board initial_position;
12682 int i, j, fenMode, pn;
12684 if (gameMode == Training )
12685 SetTrainingModeOff();
12687 if (gameMode != BeginningOfGame) {
12688 Reset(FALSE, TRUE);
12690 if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
12691 fclose(lastLoadPositionFP);
12693 if (positionNumber == 0) positionNumber = 1;
12694 lastLoadPositionFP = f;
12695 lastLoadPositionNumber = positionNumber;
12696 safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
12697 if (first.pr == NoProc && !appData.noChessProgram) {
12698 StartChessProgram(&first);
12699 InitChessProgram(&first, FALSE);
12701 pn = positionNumber;
12702 if (positionNumber < 0) {
12703 /* Negative position number means to seek to that byte offset */
12704 if (fseek(f, -positionNumber, 0) == -1) {
12705 DisplayError(_("Can't seek on position file"), 0);
12710 if (fseek(f, 0, 0) == -1) {
12711 if (f == lastLoadPositionFP ?
12712 positionNumber == lastLoadPositionNumber + 1 :
12713 positionNumber == 1) {
12716 DisplayError(_("Can't seek on position file"), 0);
12721 /* See if this file is FEN or old-style xboard */
12722 if (fgets(line, MSG_SIZ, f) == NULL) {
12723 DisplayError(_("Position not found in file"), 0);
12726 // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
12727 fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
12730 if (fenMode || line[0] == '#') pn--;
12732 /* skip positions before number pn */
12733 if (fgets(line, MSG_SIZ, f) == NULL) {
12735 DisplayError(_("Position not found in file"), 0);
12738 if (fenMode || line[0] == '#') pn--;
12743 if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
12744 DisplayError(_("Bad FEN position in file"), 0);
12748 (void) fgets(line, MSG_SIZ, f);
12749 (void) fgets(line, MSG_SIZ, f);
12751 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
12752 (void) fgets(line, MSG_SIZ, f);
12753 for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
12756 initial_position[i][j++] = CharToPiece(*p);
12760 blackPlaysFirst = FALSE;
12762 (void) fgets(line, MSG_SIZ, f);
12763 if (strncmp(line, "black", strlen("black"))==0)
12764 blackPlaysFirst = TRUE;
12767 startedFromSetupPosition = TRUE;
12769 CopyBoard(boards[0], initial_position);
12770 if (blackPlaysFirst) {
12771 currentMove = forwardMostMove = backwardMostMove = 1;
12772 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12773 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12774 CopyBoard(boards[1], initial_position);
12775 DisplayMessage("", _("Black to play"));
12777 currentMove = forwardMostMove = backwardMostMove = 0;
12778 DisplayMessage("", _("White to play"));
12780 initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
12781 if(first.pr != NoProc) { // [HGM] in tourney-mode a position can be loaded before the chess engine is installed
12782 SendToProgram("force\n", &first);
12783 SendBoard(&first, forwardMostMove);
12785 if (appData.debugMode) {
12787 for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
12788 for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
12789 fprintf(debugFP, "Load Position\n");
12792 if (positionNumber > 1) {
12793 snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
12794 DisplayTitle(line);
12796 DisplayTitle(title);
12798 gameMode = EditGame;
12801 timeRemaining[0][1] = whiteTimeRemaining;
12802 timeRemaining[1][1] = blackTimeRemaining;
12803 DrawPosition(FALSE, boards[currentMove]);
12810 CopyPlayerNameIntoFileName (char **dest, char *src)
12812 while (*src != NULLCHAR && *src != ',') {
12817 *(*dest)++ = *src++;
12823 DefaultFileName (char *ext)
12825 static char def[MSG_SIZ];
12828 if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
12830 CopyPlayerNameIntoFileName(&p, gameInfo.white);
12832 CopyPlayerNameIntoFileName(&p, gameInfo.black);
12834 safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
12841 /* Save the current game to the given file */
12843 SaveGameToFile (char *filename, int append)
12847 int result, i, t,tot=0;
12849 if (strcmp(filename, "-") == 0) {
12850 return SaveGame(stdout, 0, NULL);
12852 for(i=0; i<10; i++) { // upto 10 tries
12853 f = fopen(filename, append ? "a" : "w");
12854 if(f && i) fprintf(f, "[Delay \"%d retries, %d msec\"]\n",i,tot);
12855 if(f || errno != 13) break;
12856 DoSleep(t = 5 + random()%11); // wait 5-15 msec
12860 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12861 DisplayError(buf, errno);
12864 safeStrCpy(buf, lastMsg, MSG_SIZ);
12865 DisplayMessage(_("Waiting for access to save file"), "");
12866 flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing
12867 DisplayMessage(_("Saving game"), "");
12868 if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError(_("Bad Seek"), errno); // better safe than sorry...
12869 result = SaveGame(f, 0, NULL);
12870 DisplayMessage(buf, "");
12877 SavePart (char *str)
12879 static char buf[MSG_SIZ];
12882 p = strchr(str, ' ');
12883 if (p == NULL) return str;
12884 strncpy(buf, str, p - str);
12885 buf[p - str] = NULLCHAR;
12889 #define PGN_MAX_LINE 75
12891 #define PGN_SIDE_WHITE 0
12892 #define PGN_SIDE_BLACK 1
12895 FindFirstMoveOutOfBook (int side)
12899 if( backwardMostMove == 0 && ! startedFromSetupPosition) {
12900 int index = backwardMostMove;
12901 int has_book_hit = 0;
12903 if( (index % 2) != side ) {
12907 while( index < forwardMostMove ) {
12908 /* Check to see if engine is in book */
12909 int depth = pvInfoList[index].depth;
12910 int score = pvInfoList[index].score;
12916 else if( score == 0 && depth == 63 ) {
12917 in_book = 1; /* Zappa */
12919 else if( score == 2 && depth == 99 ) {
12920 in_book = 1; /* Abrok */
12923 has_book_hit += in_book;
12939 GetOutOfBookInfo (char * buf)
12943 int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12945 oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
12946 oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
12950 if( oob[0] >= 0 || oob[1] >= 0 ) {
12951 for( i=0; i<2; i++ ) {
12955 if( i > 0 && oob[0] >= 0 ) {
12956 strcat( buf, " " );
12959 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
12960 sprintf( buf+strlen(buf), "%s%.2f",
12961 pvInfoList[idx].score >= 0 ? "+" : "",
12962 pvInfoList[idx].score / 100.0 );
12968 /* Save game in PGN style and close the file */
12970 SaveGamePGN (FILE *f)
12972 int i, offset, linelen, newblock;
12975 int movelen, numlen, blank;
12976 char move_buffer[100]; /* [AS] Buffer for move+PV info */
12978 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12980 PrintPGNTags(f, &gameInfo);
12982 if(appData.numberTag && matchMode) fprintf(f, "[Number \"%d\"]\n", nextGame+1); // [HGM] number tag
12984 if (backwardMostMove > 0 || startedFromSetupPosition) {
12985 char *fen = PositionToFEN(backwardMostMove, NULL, 1);
12986 fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
12987 fprintf(f, "\n{--------------\n");
12988 PrintPosition(f, backwardMostMove);
12989 fprintf(f, "--------------}\n");
12993 /* [AS] Out of book annotation */
12994 if( appData.saveOutOfBookInfo ) {
12997 GetOutOfBookInfo( buf );
12999 if( buf[0] != '\0' ) {
13000 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
13007 i = backwardMostMove;
13011 while (i < forwardMostMove) {
13012 /* Print comments preceding this move */
13013 if (commentList[i] != NULL) {
13014 if (linelen > 0) fprintf(f, "\n");
13015 fprintf(f, "%s", commentList[i]);
13020 /* Format move number */
13022 snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
13025 snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
13027 numtext[0] = NULLCHAR;
13029 numlen = strlen(numtext);
13032 /* Print move number */
13033 blank = linelen > 0 && numlen > 0;
13034 if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
13043 fprintf(f, "%s", numtext);
13047 safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
13048 movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
13051 blank = linelen > 0 && movelen > 0;
13052 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
13061 fprintf(f, "%s", move_buffer);
13062 linelen += movelen;
13064 /* [AS] Add PV info if present */
13065 if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
13066 /* [HGM] add time */
13067 char buf[MSG_SIZ]; int seconds;
13069 seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
13075 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
13078 seconds = (seconds + 4)/10; // round to full seconds
13080 snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
13082 snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
13085 snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
13086 pvInfoList[i].score >= 0 ? "+" : "",
13087 pvInfoList[i].score / 100.0,
13088 pvInfoList[i].depth,
13091 movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
13093 /* Print score/depth */
13094 blank = linelen > 0 && movelen > 0;
13095 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
13104 fprintf(f, "%s", move_buffer);
13105 linelen += movelen;
13111 /* Start a new line */
13112 if (linelen > 0) fprintf(f, "\n");
13114 /* Print comments after last move */
13115 if (commentList[i] != NULL) {
13116 fprintf(f, "%s\n", commentList[i]);
13120 if (gameInfo.resultDetails != NULL &&
13121 gameInfo.resultDetails[0] != NULLCHAR) {
13122 char buf[MSG_SIZ], *p = gameInfo.resultDetails;
13123 if(gameInfo.result == GameUnfinished && appData.clockMode && !appData.icsActive) // [HGM] adjourn: save clock settings
13124 snprintf(buf, MSG_SIZ, "%s (Clocks: %ld, %ld)", p, whiteTimeRemaining/1000, blackTimeRemaining/1000), p = buf;
13125 fprintf(f, "{%s} %s\n\n", p, PGNResult(gameInfo.result));
13127 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
13131 lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
13135 /* Save game in old style and close the file */
13137 SaveGameOldStyle (FILE *f)
13142 tm = time((time_t *) NULL);
13144 fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
13147 if (backwardMostMove > 0 || startedFromSetupPosition) {
13148 fprintf(f, "\n[--------------\n");
13149 PrintPosition(f, backwardMostMove);
13150 fprintf(f, "--------------]\n");
13155 i = backwardMostMove;
13156 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13158 while (i < forwardMostMove) {
13159 if (commentList[i] != NULL) {
13160 fprintf(f, "[%s]\n", commentList[i]);
13163 if ((i % 2) == 1) {
13164 fprintf(f, "%d. ... %s\n", (i - offset)/2 + 1, parseList[i]);
13167 fprintf(f, "%d. %s ", (i - offset)/2 + 1, parseList[i]);
13169 if (commentList[i] != NULL) {
13173 if (i >= forwardMostMove) {
13177 fprintf(f, "%s\n", parseList[i]);
13182 if (commentList[i] != NULL) {
13183 fprintf(f, "[%s]\n", commentList[i]);
13186 /* This isn't really the old style, but it's close enough */
13187 if (gameInfo.resultDetails != NULL &&
13188 gameInfo.resultDetails[0] != NULLCHAR) {
13189 fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
13190 gameInfo.resultDetails);
13192 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
13199 /* Save the current game to open file f and close the file */
13201 SaveGame (FILE *f, int dummy, char *dummy2)
13203 if (gameMode == EditPosition) EditPositionDone(TRUE);
13204 lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
13205 if (appData.oldSaveStyle)
13206 return SaveGameOldStyle(f);
13208 return SaveGamePGN(f);
13211 /* Save the current position to the given file */
13213 SavePositionToFile (char *filename)
13218 if (strcmp(filename, "-") == 0) {
13219 return SavePosition(stdout, 0, NULL);
13221 f = fopen(filename, "a");
13223 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13224 DisplayError(buf, errno);
13227 safeStrCpy(buf, lastMsg, MSG_SIZ);
13228 DisplayMessage(_("Waiting for access to save file"), "");
13229 flock(fileno(f), LOCK_EX); // [HGM] lock
13230 DisplayMessage(_("Saving position"), "");
13231 lseek(fileno(f), 0, SEEK_END); // better safe than sorry...
13232 SavePosition(f, 0, NULL);
13233 DisplayMessage(buf, "");
13239 /* Save the current position to the given open file and close the file */
13241 SavePosition (FILE *f, int dummy, char *dummy2)
13246 if (gameMode == EditPosition) EditPositionDone(TRUE);
13247 if (appData.oldSaveStyle) {
13248 tm = time((time_t *) NULL);
13250 fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
13252 fprintf(f, "[--------------\n");
13253 PrintPosition(f, currentMove);
13254 fprintf(f, "--------------]\n");
13256 fen = PositionToFEN(currentMove, NULL, 1);
13257 fprintf(f, "%s\n", fen);
13265 ReloadCmailMsgEvent (int unregister)
13268 static char *inFilename = NULL;
13269 static char *outFilename;
13271 struct stat inbuf, outbuf;
13274 /* Any registered moves are unregistered if unregister is set, */
13275 /* i.e. invoked by the signal handler */
13277 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
13278 cmailMoveRegistered[i] = FALSE;
13279 if (cmailCommentList[i] != NULL) {
13280 free(cmailCommentList[i]);
13281 cmailCommentList[i] = NULL;
13284 nCmailMovesRegistered = 0;
13287 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
13288 cmailResult[i] = CMAIL_NOT_RESULT;
13292 if (inFilename == NULL) {
13293 /* Because the filenames are static they only get malloced once */
13294 /* and they never get freed */
13295 inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
13296 sprintf(inFilename, "%s.game.in", appData.cmailGameName);
13298 outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
13299 sprintf(outFilename, "%s.out", appData.cmailGameName);
13302 status = stat(outFilename, &outbuf);
13304 cmailMailedMove = FALSE;
13306 status = stat(inFilename, &inbuf);
13307 cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
13310 /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
13311 counts the games, notes how each one terminated, etc.
13313 It would be nice to remove this kludge and instead gather all
13314 the information while building the game list. (And to keep it
13315 in the game list nodes instead of having a bunch of fixed-size
13316 parallel arrays.) Note this will require getting each game's
13317 termination from the PGN tags, as the game list builder does
13318 not process the game moves. --mann
13320 cmailMsgLoaded = TRUE;
13321 LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
13323 /* Load first game in the file or popup game menu */
13324 LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
13326 #endif /* !WIN32 */
13334 char string[MSG_SIZ];
13336 if ( cmailMailedMove
13337 || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
13338 return TRUE; /* Allow free viewing */
13341 /* Unregister move to ensure that we don't leave RegisterMove */
13342 /* with the move registered when the conditions for registering no */
13344 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
13345 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
13346 nCmailMovesRegistered --;
13348 if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
13350 free(cmailCommentList[lastLoadGameNumber - 1]);
13351 cmailCommentList[lastLoadGameNumber - 1] = NULL;
13355 if (cmailOldMove == -1) {
13356 DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
13360 if (currentMove > cmailOldMove + 1) {
13361 DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
13365 if (currentMove < cmailOldMove) {
13366 DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
13370 if (forwardMostMove > currentMove) {
13371 /* Silently truncate extra moves */
13375 if ( (currentMove == cmailOldMove + 1)
13376 || ( (currentMove == cmailOldMove)
13377 && ( (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
13378 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
13379 if (gameInfo.result != GameUnfinished) {
13380 cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
13383 if (commentList[currentMove] != NULL) {
13384 cmailCommentList[lastLoadGameNumber - 1]
13385 = StrSave(commentList[currentMove]);
13387 safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
13389 if (appData.debugMode)
13390 fprintf(debugFP, "Saving %s for game %d\n",
13391 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
13393 snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
13395 f = fopen(string, "w");
13396 if (appData.oldSaveStyle) {
13397 SaveGameOldStyle(f); /* also closes the file */
13399 snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
13400 f = fopen(string, "w");
13401 SavePosition(f, 0, NULL); /* also closes the file */
13403 fprintf(f, "{--------------\n");
13404 PrintPosition(f, currentMove);
13405 fprintf(f, "--------------}\n\n");
13407 SaveGame(f, 0, NULL); /* also closes the file*/
13410 cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
13411 nCmailMovesRegistered ++;
13412 } else if (nCmailGames == 1) {
13413 DisplayError(_("You have not made a move yet"), 0);
13424 static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
13425 FILE *commandOutput;
13426 char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
13427 int nBytes = 0; /* Suppress warnings on uninitialized variables */
13433 if (! cmailMsgLoaded) {
13434 DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
13438 if (nCmailGames == nCmailResults) {
13439 DisplayError(_("No unfinished games"), 0);
13443 #if CMAIL_PROHIBIT_REMAIL
13444 if (cmailMailedMove) {
13445 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);
13446 DisplayError(msg, 0);
13451 if (! (cmailMailedMove || RegisterMove())) return;
13453 if ( cmailMailedMove
13454 || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
13455 snprintf(string, MSG_SIZ, partCommandString,
13456 appData.debugMode ? " -v" : "", appData.cmailGameName);
13457 commandOutput = popen(string, "r");
13459 if (commandOutput == NULL) {
13460 DisplayError(_("Failed to invoke cmail"), 0);
13462 for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
13463 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
13465 if (nBuffers > 1) {
13466 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
13467 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
13468 nBytes = MSG_SIZ - 1;
13470 (void) memcpy(msg, buffer, nBytes);
13472 *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
13474 if(StrStr(msg, "Mailed cmail message to ") != NULL) {
13475 cmailMailedMove = TRUE; /* Prevent >1 moves */
13478 for (i = 0; i < nCmailGames; i ++) {
13479 if (cmailResult[i] == CMAIL_NOT_RESULT) {
13484 && ( (arcDir = (char *) getenv("CMAIL_ARCDIR"))
13486 snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
13488 appData.cmailGameName,
13490 LoadGameFromFile(buffer, 1, buffer, FALSE);
13491 cmailMsgLoaded = FALSE;
13495 DisplayInformation(msg);
13496 pclose(commandOutput);
13499 if ((*cmailMsg) != '\0') {
13500 DisplayInformation(cmailMsg);
13505 #endif /* !WIN32 */
13514 int prependComma = 0;
13516 char string[MSG_SIZ]; /* Space for game-list */
13519 if (!cmailMsgLoaded) return "";
13521 if (cmailMailedMove) {
13522 snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
13524 /* Create a list of games left */
13525 snprintf(string, MSG_SIZ, "[");
13526 for (i = 0; i < nCmailGames; i ++) {
13527 if (! ( cmailMoveRegistered[i]
13528 || (cmailResult[i] == CMAIL_OLD_RESULT))) {
13529 if (prependComma) {
13530 snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
13532 snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
13536 strcat(string, number);
13539 strcat(string, "]");
13541 if (nCmailMovesRegistered + nCmailResults == 0) {
13542 switch (nCmailGames) {
13544 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
13548 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
13552 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
13557 switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
13559 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
13564 if (nCmailResults == nCmailGames) {
13565 snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
13567 snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
13572 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
13584 if (gameMode == Training)
13585 SetTrainingModeOff();
13588 cmailMsgLoaded = FALSE;
13589 if (appData.icsActive) {
13590 SendToICS(ics_prefix);
13591 SendToICS("refresh\n");
13596 ExitEvent (int status)
13600 /* Give up on clean exit */
13604 /* Keep trying for clean exit */
13608 if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
13610 if (telnetISR != NULL) {
13611 RemoveInputSource(telnetISR);
13613 if (icsPR != NoProc) {
13614 DestroyChildProcess(icsPR, TRUE);
13617 /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
13618 GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
13620 /* [HGM] crash: the above GameEnds() is a dud if another one was running */
13621 /* make sure this other one finishes before killing it! */
13622 if(endingGame) { int count = 0;
13623 if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
13624 while(endingGame && count++ < 10) DoSleep(1);
13625 if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
13628 /* Kill off chess programs */
13629 if (first.pr != NoProc) {
13632 DoSleep( appData.delayBeforeQuit );
13633 SendToProgram("quit\n", &first);
13634 DoSleep( appData.delayAfterQuit );
13635 DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
13637 if (second.pr != NoProc) {
13638 DoSleep( appData.delayBeforeQuit );
13639 SendToProgram("quit\n", &second);
13640 DoSleep( appData.delayAfterQuit );
13641 DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
13643 if (first.isr != NULL) {
13644 RemoveInputSource(first.isr);
13646 if (second.isr != NULL) {
13647 RemoveInputSource(second.isr);
13650 if (pairing.pr != NoProc) SendToProgram("quit\n", &pairing);
13651 if (pairing.isr != NULL) RemoveInputSource(pairing.isr);
13653 ShutDownFrontEnd();
13658 PauseEngine (ChessProgramState *cps)
13660 SendToProgram("pause\n", cps);
13665 UnPauseEngine (ChessProgramState *cps)
13667 SendToProgram("resume\n", cps);
13674 if (appData.debugMode)
13675 fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
13679 if(stalledEngine) { // [HGM] pause: resume game by releasing withheld move
13681 if(gameMode == TwoMachinesPlay) { // we might have to make the opponent resume pondering
13682 if(stalledEngine->other->pause == 2) UnPauseEngine(stalledEngine->other);
13683 else if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine->other);
13685 if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine);
13686 HandleMachineMove(stashedInputMove, stalledEngine);
13687 stalledEngine = NULL;
13690 if (gameMode == MachinePlaysWhite ||
13691 gameMode == TwoMachinesPlay ||
13692 gameMode == MachinePlaysBlack) { // the thinking engine must have used pause mode, or it would have been stalledEngine
13693 if(first.pause) UnPauseEngine(&first);
13694 else if(appData.ponderNextMove) SendToProgram("hard\n", &first);
13695 if(second.pause) UnPauseEngine(&second);
13696 else if(gameMode == TwoMachinesPlay && appData.ponderNextMove) SendToProgram("hard\n", &second);
13699 DisplayBothClocks();
13701 if (gameMode == PlayFromGameFile) {
13702 if (appData.timeDelay >= 0)
13703 AutoPlayGameLoop();
13704 } else if (gameMode == IcsExamining && pauseExamInvalid) {
13705 Reset(FALSE, TRUE);
13706 SendToICS(ics_prefix);
13707 SendToICS("refresh\n");
13708 } else if (currentMove < forwardMostMove && gameMode != AnalyzeMode) {
13709 ForwardInner(forwardMostMove);
13711 pauseExamInvalid = FALSE;
13713 switch (gameMode) {
13717 pauseExamForwardMostMove = forwardMostMove;
13718 pauseExamInvalid = FALSE;
13721 case IcsPlayingWhite:
13722 case IcsPlayingBlack:
13726 case PlayFromGameFile:
13727 (void) StopLoadGameTimer();
13731 case BeginningOfGame:
13732 if (appData.icsActive) return;
13733 /* else fall through */
13734 case MachinePlaysWhite:
13735 case MachinePlaysBlack:
13736 case TwoMachinesPlay:
13737 if (forwardMostMove == 0)
13738 return; /* don't pause if no one has moved */
13739 if(gameMode == TwoMachinesPlay) { // [HGM] pause: stop clocks if engine can be paused immediately
13740 ChessProgramState *onMove = (WhiteOnMove(forwardMostMove) == (first.twoMachinesColor[0] == 'w') ? &first : &second);
13741 if(onMove->pause) { // thinking engine can be paused
13742 PauseEngine(onMove); // do it
13743 if(onMove->other->pause) // pondering opponent can always be paused immediately
13744 PauseEngine(onMove->other);
13746 SendToProgram("easy\n", onMove->other);
13748 } else if(appData.ponderNextMove) SendToProgram("easy\n", onMove); // pre-emptively bring out of ponder
13749 } else if(gameMode == (WhiteOnMove(forwardMostMove) ? MachinePlaysWhite : MachinePlaysBlack)) { // engine on move
13751 PauseEngine(&first);
13753 } else if(appData.ponderNextMove) SendToProgram("easy\n", &first); // pre-emptively bring out of ponder
13754 } else { // human on move, pause pondering by either method
13756 PauseEngine(&first);
13757 else if(appData.ponderNextMove)
13758 SendToProgram("easy\n", &first);
13761 // if no immediate pausing is possible, wait for engine to move, and stop clocks then
13771 EditCommentEvent ()
13773 char title[MSG_SIZ];
13775 if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
13776 safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
13778 snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
13779 WhiteOnMove(currentMove - 1) ? " " : ".. ",
13780 parseList[currentMove - 1]);
13783 EditCommentPopUp(currentMove, title, commentList[currentMove]);
13790 char *tags = PGNTags(&gameInfo);
13792 EditTagsPopUp(tags, NULL);
13799 if(second.analyzing) {
13800 SendToProgram("exit\n", &second);
13801 second.analyzing = FALSE;
13803 if (second.pr == NoProc) StartChessProgram(&second);
13804 InitChessProgram(&second, FALSE);
13805 FeedMovesToProgram(&second, currentMove);
13807 SendToProgram("analyze\n", &second);
13808 second.analyzing = TRUE;
13812 /* Toggle ShowThinking */
13814 ToggleShowThinking()
13816 appData.showThinking = !appData.showThinking;
13817 ShowThinkingEvent();
13821 AnalyzeModeEvent ()
13825 if (!first.analysisSupport) {
13826 snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
13827 DisplayError(buf, 0);
13830 /* [DM] icsEngineAnalyze [HGM] This is horrible code; reverse the gameMode and isEngineAnalyze tests! */
13831 if (appData.icsActive) {
13832 if (gameMode != IcsObserving) {
13833 snprintf(buf, MSG_SIZ, _("You are not observing a game"));
13834 DisplayError(buf, 0);
13836 if (appData.icsEngineAnalyze) {
13837 if (appData.debugMode)
13838 fprintf(debugFP, "Found unexpected active ICS engine analyze \n");
13844 /* if enable, user wants to disable icsEngineAnalyze */
13845 if (appData.icsEngineAnalyze) {
13850 appData.icsEngineAnalyze = TRUE;
13851 if (appData.debugMode)
13852 fprintf(debugFP, "ICS engine analyze starting... \n");
13855 if (gameMode == AnalyzeMode) { ToggleSecond(); return 0; }
13856 if (appData.noChessProgram || gameMode == AnalyzeMode)
13859 if (gameMode != AnalyzeFile) {
13860 if (!appData.icsEngineAnalyze) {
13862 if (gameMode != EditGame) return 0;
13864 if (!appData.showThinking) ToggleShowThinking();
13865 ResurrectChessProgram();
13866 SendToProgram("analyze\n", &first);
13867 first.analyzing = TRUE;
13868 /*first.maybeThinking = TRUE;*/
13869 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13870 EngineOutputPopUp();
13872 if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
13877 StartAnalysisClock();
13878 GetTimeMark(&lastNodeCountTime);
13884 AnalyzeFileEvent ()
13886 if (appData.noChessProgram || gameMode == AnalyzeFile)
13889 if (!first.analysisSupport) {
13891 snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
13892 DisplayError(buf, 0);
13896 if (gameMode != AnalyzeMode) {
13897 keepInfo = 1; // mere annotating should not alter PGN tags
13900 if (gameMode != EditGame) return;
13901 if (!appData.showThinking) ToggleShowThinking();
13902 ResurrectChessProgram();
13903 SendToProgram("analyze\n", &first);
13904 first.analyzing = TRUE;
13905 /*first.maybeThinking = TRUE;*/
13906 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13907 EngineOutputPopUp();
13909 gameMode = AnalyzeFile;
13913 StartAnalysisClock();
13914 GetTimeMark(&lastNodeCountTime);
13916 if(appData.timeDelay > 0) StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
13917 AnalysisPeriodicEvent(1);
13921 MachineWhiteEvent ()
13924 char *bookHit = NULL;
13926 if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
13930 if (gameMode == PlayFromGameFile ||
13931 gameMode == TwoMachinesPlay ||
13932 gameMode == Training ||
13933 gameMode == AnalyzeMode ||
13934 gameMode == EndOfGame)
13937 if (gameMode == EditPosition)
13938 EditPositionDone(TRUE);
13940 if (!WhiteOnMove(currentMove)) {
13941 DisplayError(_("It is not White's turn"), 0);
13945 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
13948 if (gameMode == EditGame || gameMode == AnalyzeMode ||
13949 gameMode == AnalyzeFile)
13952 ResurrectChessProgram(); /* in case it isn't running */
13953 if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
13954 gameMode = MachinePlaysWhite;
13957 gameMode = MachinePlaysWhite;
13961 snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
13963 if (first.sendName) {
13964 snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
13965 SendToProgram(buf, &first);
13967 if (first.sendTime) {
13968 if (first.useColors) {
13969 SendToProgram("black\n", &first); /*gnu kludge*/
13971 SendTimeRemaining(&first, TRUE);
13973 if (first.useColors) {
13974 SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
13976 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
13977 SetMachineThinkingEnables();
13978 first.maybeThinking = TRUE;
13982 if (appData.autoFlipView && !flipView) {
13983 flipView = !flipView;
13984 DrawPosition(FALSE, NULL);
13985 DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;
13988 if(bookHit) { // [HGM] book: simulate book reply
13989 static char bookMove[MSG_SIZ]; // a bit generous?
13991 programStats.nodes = programStats.depth = programStats.time =
13992 programStats.score = programStats.got_only_move = 0;
13993 sprintf(programStats.movelist, "%s (xbook)", bookHit);
13995 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13996 strcat(bookMove, bookHit);
13997 HandleMachineMove(bookMove, &first);
14002 MachineBlackEvent ()
14005 char *bookHit = NULL;
14007 if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
14011 if (gameMode == PlayFromGameFile ||
14012 gameMode == TwoMachinesPlay ||
14013 gameMode == Training ||
14014 gameMode == AnalyzeMode ||
14015 gameMode == EndOfGame)
14018 if (gameMode == EditPosition)
14019 EditPositionDone(TRUE);
14021 if (WhiteOnMove(currentMove)) {
14022 DisplayError(_("It is not Black's turn"), 0);
14026 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
14029 if (gameMode == EditGame || gameMode == AnalyzeMode ||
14030 gameMode == AnalyzeFile)
14033 ResurrectChessProgram(); /* in case it isn't running */
14034 gameMode = MachinePlaysBlack;
14038 snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14040 if (first.sendName) {
14041 snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
14042 SendToProgram(buf, &first);
14044 if (first.sendTime) {
14045 if (first.useColors) {
14046 SendToProgram("white\n", &first); /*gnu kludge*/
14048 SendTimeRemaining(&first, FALSE);
14050 if (first.useColors) {
14051 SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
14053 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
14054 SetMachineThinkingEnables();
14055 first.maybeThinking = TRUE;
14058 if (appData.autoFlipView && flipView) {
14059 flipView = !flipView;
14060 DrawPosition(FALSE, NULL);
14061 DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;
14063 if(bookHit) { // [HGM] book: simulate book reply
14064 static char bookMove[MSG_SIZ]; // a bit generous?
14066 programStats.nodes = programStats.depth = programStats.time =
14067 programStats.score = programStats.got_only_move = 0;
14068 sprintf(programStats.movelist, "%s (xbook)", bookHit);
14070 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14071 strcat(bookMove, bookHit);
14072 HandleMachineMove(bookMove, &first);
14078 DisplayTwoMachinesTitle ()
14081 if (appData.matchGames > 0) {
14082 if(appData.tourneyFile[0]) {
14083 snprintf(buf, MSG_SIZ, "%s %s %s (%d/%d%s)",
14084 gameInfo.white, _("vs."), gameInfo.black,
14085 nextGame+1, appData.matchGames+1,
14086 appData.tourneyType>0 ? "gt" : appData.tourneyType<0 ? "sw" : "rr");
14088 if (first.twoMachinesColor[0] == 'w') {
14089 snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
14090 gameInfo.white, _("vs."), gameInfo.black,
14091 first.matchWins, second.matchWins,
14092 matchGame - 1 - (first.matchWins + second.matchWins));
14094 snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
14095 gameInfo.white, _("vs."), gameInfo.black,
14096 second.matchWins, first.matchWins,
14097 matchGame - 1 - (first.matchWins + second.matchWins));
14100 snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14106 SettingsMenuIfReady ()
14108 if (second.lastPing != second.lastPong) {
14109 DisplayMessage("", _("Waiting for second chess program"));
14110 ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
14114 DisplayMessage("", "");
14115 SettingsPopUp(&second);
14119 WaitForEngine (ChessProgramState *cps, DelayedEventCallback retry)
14122 if (cps->pr == NoProc) {
14123 StartChessProgram(cps);
14124 if (cps->protocolVersion == 1) {
14126 ScheduleDelayedEvent(retry, 1); // Do this also through timeout to avoid recursive calling of 'retry'
14128 /* kludge: allow timeout for initial "feature" command */
14129 if(retry != TwoMachinesEventIfReady) FreezeUI();
14130 snprintf(buf, MSG_SIZ, _("Starting %s chess program"), _(cps->which));
14131 DisplayMessage("", buf);
14132 ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
14140 TwoMachinesEvent P((void))
14144 ChessProgramState *onmove;
14145 char *bookHit = NULL;
14146 static int stalling = 0;
14150 if (appData.noChessProgram) return;
14152 switch (gameMode) {
14153 case TwoMachinesPlay:
14155 case MachinePlaysWhite:
14156 case MachinePlaysBlack:
14157 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
14158 DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
14162 case BeginningOfGame:
14163 case PlayFromGameFile:
14166 if (gameMode != EditGame) return;
14169 EditPositionDone(TRUE);
14180 // forwardMostMove = currentMove;
14181 TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
14182 startingEngine = TRUE;
14184 if(!ResurrectChessProgram()) return; /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
14186 if(!first.initDone && GetDelayedEvent() == TwoMachinesEventIfReady) return; // [HGM] engine #1 still waiting for feature timeout
14187 if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
14188 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
14191 if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
14193 if(second.protocolVersion >= 2 && !strstr(second.variants, VariantName(gameInfo.variant))) {
14194 startingEngine = FALSE;
14195 DisplayError("second engine does not play this", 0);
14200 InitChessProgram(&second, FALSE); // unbalances ping of second engine
14201 SendToProgram("force\n", &second);
14203 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
14206 GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
14207 if(appData.matchPause>10000 || appData.matchPause<10)
14208 appData.matchPause = 10000; /* [HGM] make pause adjustable */
14209 wait = SubtractTimeMarks(&now, &pauseStart);
14210 if(wait < appData.matchPause) {
14211 ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
14214 // we are now committed to starting the game
14216 DisplayMessage("", "");
14217 if (startedFromSetupPosition) {
14218 SendBoard(&second, backwardMostMove);
14219 if (appData.debugMode) {
14220 fprintf(debugFP, "Two Machines\n");
14223 for (i = backwardMostMove; i < forwardMostMove; i++) {
14224 SendMoveToProgram(i, &second);
14227 gameMode = TwoMachinesPlay;
14228 pausing = startingEngine = FALSE;
14229 ModeHighlight(); // [HGM] logo: this triggers display update of logos
14231 DisplayTwoMachinesTitle();
14233 if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
14238 if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
14239 SendToProgram(first.computerString, &first);
14240 if (first.sendName) {
14241 snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
14242 SendToProgram(buf, &first);
14244 SendToProgram(second.computerString, &second);
14245 if (second.sendName) {
14246 snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
14247 SendToProgram(buf, &second);
14251 if (!first.sendTime || !second.sendTime) {
14252 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
14253 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
14255 if (onmove->sendTime) {
14256 if (onmove->useColors) {
14257 SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
14259 SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
14261 if (onmove->useColors) {
14262 SendToProgram(onmove->twoMachinesColor, onmove);
14264 bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
14265 // SendToProgram("go\n", onmove);
14266 onmove->maybeThinking = TRUE;
14267 SetMachineThinkingEnables();
14271 if(bookHit) { // [HGM] book: simulate book reply
14272 static char bookMove[MSG_SIZ]; // a bit generous?
14274 programStats.nodes = programStats.depth = programStats.time =
14275 programStats.score = programStats.got_only_move = 0;
14276 sprintf(programStats.movelist, "%s (xbook)", bookHit);
14278 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14279 strcat(bookMove, bookHit);
14280 savedMessage = bookMove; // args for deferred call
14281 savedState = onmove;
14282 ScheduleDelayedEvent(DeferredBookMove, 1);
14289 if (gameMode == Training) {
14290 SetTrainingModeOff();
14291 gameMode = PlayFromGameFile;
14292 DisplayMessage("", _("Training mode off"));
14294 gameMode = Training;
14295 animateTraining = appData.animate;
14297 /* make sure we are not already at the end of the game */
14298 if (currentMove < forwardMostMove) {
14299 SetTrainingModeOn();
14300 DisplayMessage("", _("Training mode on"));
14302 gameMode = PlayFromGameFile;
14303 DisplayError(_("Already at end of game"), 0);
14312 if (!appData.icsActive) return;
14313 switch (gameMode) {
14314 case IcsPlayingWhite:
14315 case IcsPlayingBlack:
14318 case BeginningOfGame:
14326 EditPositionDone(TRUE);
14339 gameMode = IcsIdle;
14349 switch (gameMode) {
14351 SetTrainingModeOff();
14353 case MachinePlaysWhite:
14354 case MachinePlaysBlack:
14355 case BeginningOfGame:
14356 SendToProgram("force\n", &first);
14357 SetUserThinkingEnables();
14359 case PlayFromGameFile:
14360 (void) StopLoadGameTimer();
14361 if (gameFileFP != NULL) {
14366 EditPositionDone(TRUE);
14371 SendToProgram("force\n", &first);
14373 case TwoMachinesPlay:
14374 GameEnds(EndOfFile, NULL, GE_PLAYER);
14375 ResurrectChessProgram();
14376 SetUserThinkingEnables();
14379 ResurrectChessProgram();
14381 case IcsPlayingBlack:
14382 case IcsPlayingWhite:
14383 DisplayError(_("Warning: You are still playing a game"), 0);
14386 DisplayError(_("Warning: You are still observing a game"), 0);
14389 DisplayError(_("Warning: You are still examining a game"), 0);
14400 first.offeredDraw = second.offeredDraw = 0;
14402 if (gameMode == PlayFromGameFile) {
14403 whiteTimeRemaining = timeRemaining[0][currentMove];
14404 blackTimeRemaining = timeRemaining[1][currentMove];
14408 if (gameMode == MachinePlaysWhite ||
14409 gameMode == MachinePlaysBlack ||
14410 gameMode == TwoMachinesPlay ||
14411 gameMode == EndOfGame) {
14412 i = forwardMostMove;
14413 while (i > currentMove) {
14414 SendToProgram("undo\n", &first);
14417 if(!adjustedClock) {
14418 whiteTimeRemaining = timeRemaining[0][currentMove];
14419 blackTimeRemaining = timeRemaining[1][currentMove];
14420 DisplayBothClocks();
14422 if (whiteFlag || blackFlag) {
14423 whiteFlag = blackFlag = 0;
14428 gameMode = EditGame;
14435 EditPositionEvent ()
14437 if (gameMode == EditPosition) {
14443 if (gameMode != EditGame) return;
14445 gameMode = EditPosition;
14448 if (currentMove > 0)
14449 CopyBoard(boards[0], boards[currentMove]);
14451 blackPlaysFirst = !WhiteOnMove(currentMove);
14453 currentMove = forwardMostMove = backwardMostMove = 0;
14454 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
14456 if(!appData.pieceMenu) DisplayMessage(_("Click clock to clear board"), "");
14462 /* [DM] icsEngineAnalyze - possible call from other functions */
14463 if (appData.icsEngineAnalyze) {
14464 appData.icsEngineAnalyze = FALSE;
14466 DisplayMessage("",_("Close ICS engine analyze..."));
14468 if (first.analysisSupport && first.analyzing) {
14469 SendToBoth("exit\n");
14470 first.analyzing = second.analyzing = FALSE;
14472 thinkOutput[0] = NULLCHAR;
14476 EditPositionDone (Boolean fakeRights)
14478 int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
14480 startedFromSetupPosition = TRUE;
14481 InitChessProgram(&first, FALSE);
14482 if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
14483 boards[0][EP_STATUS] = EP_NONE;
14484 boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
14485 if(boards[0][0][BOARD_WIDTH>>1] == king) {
14486 boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? BOARD_LEFT : NoRights;
14487 boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
14488 } else boards[0][CASTLING][2] = NoRights;
14489 if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
14490 boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? BOARD_LEFT : NoRights;
14491 boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
14492 } else boards[0][CASTLING][5] = NoRights;
14493 if(gameInfo.variant == VariantSChess) {
14495 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // pieces in their original position are assumed virgin
14496 boards[0][VIRGIN][i] = 0;
14497 if(boards[0][0][i] == FIDEArray[0][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_W;
14498 if(boards[0][BOARD_HEIGHT-1][i] == FIDEArray[1][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_B;
14502 SendToProgram("force\n", &first);
14503 if (blackPlaysFirst) {
14504 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
14505 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
14506 currentMove = forwardMostMove = backwardMostMove = 1;
14507 CopyBoard(boards[1], boards[0]);
14509 currentMove = forwardMostMove = backwardMostMove = 0;
14511 SendBoard(&first, forwardMostMove);
14512 if (appData.debugMode) {
14513 fprintf(debugFP, "EditPosDone\n");
14516 DisplayMessage("", "");
14517 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
14518 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
14519 gameMode = EditGame;
14521 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
14522 ClearHighlights(); /* [AS] */
14525 /* Pause for `ms' milliseconds */
14526 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
14528 TimeDelay (long ms)
14535 } while (SubtractTimeMarks(&m2, &m1) < ms);
14538 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
14540 SendMultiLineToICS (char *buf)
14542 char temp[MSG_SIZ+1], *p;
14549 strncpy(temp, buf, len);
14554 if (*p == '\n' || *p == '\r')
14559 strcat(temp, "\n");
14561 SendToPlayer(temp, strlen(temp));
14565 SetWhiteToPlayEvent ()
14567 if (gameMode == EditPosition) {
14568 blackPlaysFirst = FALSE;
14569 DisplayBothClocks(); /* works because currentMove is 0 */
14570 } else if (gameMode == IcsExamining) {
14571 SendToICS(ics_prefix);
14572 SendToICS("tomove white\n");
14577 SetBlackToPlayEvent ()
14579 if (gameMode == EditPosition) {
14580 blackPlaysFirst = TRUE;
14581 currentMove = 1; /* kludge */
14582 DisplayBothClocks();
14584 } else if (gameMode == IcsExamining) {
14585 SendToICS(ics_prefix);
14586 SendToICS("tomove black\n");
14591 EditPositionMenuEvent (ChessSquare selection, int x, int y)
14594 ChessSquare piece = boards[0][y][x];
14596 if (gameMode != EditPosition && gameMode != IcsExamining) return;
14598 switch (selection) {
14600 if (gameMode == IcsExamining && ics_type == ICS_FICS) {
14601 SendToICS(ics_prefix);
14602 SendToICS("bsetup clear\n");
14603 } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
14604 SendToICS(ics_prefix);
14605 SendToICS("clearboard\n");
14607 for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
14608 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
14609 for (y = 0; y < BOARD_HEIGHT; y++) {
14610 if (gameMode == IcsExamining) {
14611 if (boards[currentMove][y][x] != EmptySquare) {
14612 snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
14617 boards[0][y][x] = p;
14622 if (gameMode == EditPosition) {
14623 DrawPosition(FALSE, boards[0]);
14628 SetWhiteToPlayEvent();
14632 SetBlackToPlayEvent();
14636 if (gameMode == IcsExamining) {
14637 if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
14638 snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
14641 if(x < BOARD_LEFT || x >= BOARD_RGHT) {
14642 if(x == BOARD_LEFT-2) {
14643 if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
14644 boards[0][y][1] = 0;
14646 if(x == BOARD_RGHT+1) {
14647 if(y >= gameInfo.holdingsSize) break;
14648 boards[0][y][BOARD_WIDTH-2] = 0;
14651 boards[0][y][x] = EmptySquare;
14652 DrawPosition(FALSE, boards[0]);
14657 if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
14658 piece >= (int)BlackPawn && piece < (int)BlackMan ) {
14659 selection = (ChessSquare) (PROMOTED piece);
14660 } else if(piece == EmptySquare) selection = WhiteSilver;
14661 else selection = (ChessSquare)((int)piece - 1);
14665 if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
14666 piece > (int)BlackMan && piece <= (int)BlackKing ) {
14667 selection = (ChessSquare) (DEMOTED piece);
14668 } else if(piece == EmptySquare) selection = BlackSilver;
14669 else selection = (ChessSquare)((int)piece + 1);
14674 if(gameInfo.variant == VariantShatranj ||
14675 gameInfo.variant == VariantXiangqi ||
14676 gameInfo.variant == VariantCourier ||
14677 gameInfo.variant == VariantASEAN ||
14678 gameInfo.variant == VariantMakruk )
14679 selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
14684 if(gameInfo.variant == VariantXiangqi)
14685 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
14686 if(gameInfo.variant == VariantKnightmate)
14687 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
14690 if (gameMode == IcsExamining) {
14691 if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
14692 snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
14693 PieceToChar(selection), AAA + x, ONE + y);
14696 if(x < BOARD_LEFT || x >= BOARD_RGHT) {
14698 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
14699 n = PieceToNumber(selection - BlackPawn);
14700 if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
14701 boards[0][BOARD_HEIGHT-1-n][0] = selection;
14702 boards[0][BOARD_HEIGHT-1-n][1]++;
14704 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
14705 n = PieceToNumber(selection);
14706 if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
14707 boards[0][n][BOARD_WIDTH-1] = selection;
14708 boards[0][n][BOARD_WIDTH-2]++;
14711 boards[0][y][x] = selection;
14712 DrawPosition(TRUE, boards[0]);
14714 fromX = fromY = -1;
14722 DropMenuEvent (ChessSquare selection, int x, int y)
14724 ChessMove moveType;
14726 switch (gameMode) {
14727 case IcsPlayingWhite:
14728 case MachinePlaysBlack:
14729 if (!WhiteOnMove(currentMove)) {
14730 DisplayMoveError(_("It is Black's turn"));
14733 moveType = WhiteDrop;
14735 case IcsPlayingBlack:
14736 case MachinePlaysWhite:
14737 if (WhiteOnMove(currentMove)) {
14738 DisplayMoveError(_("It is White's turn"));
14741 moveType = BlackDrop;
14744 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
14750 if (moveType == BlackDrop && selection < BlackPawn) {
14751 selection = (ChessSquare) ((int) selection
14752 + (int) BlackPawn - (int) WhitePawn);
14754 if (boards[currentMove][y][x] != EmptySquare) {
14755 DisplayMoveError(_("That square is occupied"));
14759 FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
14765 /* Accept a pending offer of any kind from opponent */
14767 if (appData.icsActive) {
14768 SendToICS(ics_prefix);
14769 SendToICS("accept\n");
14770 } else if (cmailMsgLoaded) {
14771 if (currentMove == cmailOldMove &&
14772 commentList[cmailOldMove] != NULL &&
14773 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14774 "Black offers a draw" : "White offers a draw")) {
14776 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
14777 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
14779 DisplayError(_("There is no pending offer on this move"), 0);
14780 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
14783 /* Not used for offers from chess program */
14790 /* Decline a pending offer of any kind from opponent */
14792 if (appData.icsActive) {
14793 SendToICS(ics_prefix);
14794 SendToICS("decline\n");
14795 } else if (cmailMsgLoaded) {
14796 if (currentMove == cmailOldMove &&
14797 commentList[cmailOldMove] != NULL &&
14798 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14799 "Black offers a draw" : "White offers a draw")) {
14801 AppendComment(cmailOldMove, "Draw declined", TRUE);
14802 DisplayComment(cmailOldMove - 1, "Draw declined");
14805 DisplayError(_("There is no pending offer on this move"), 0);
14808 /* Not used for offers from chess program */
14815 /* Issue ICS rematch command */
14816 if (appData.icsActive) {
14817 SendToICS(ics_prefix);
14818 SendToICS("rematch\n");
14825 /* Call your opponent's flag (claim a win on time) */
14826 if (appData.icsActive) {
14827 SendToICS(ics_prefix);
14828 SendToICS("flag\n");
14830 switch (gameMode) {
14833 case MachinePlaysWhite:
14836 GameEnds(GameIsDrawn, "Both players ran out of time",
14839 GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
14841 DisplayError(_("Your opponent is not out of time"), 0);
14844 case MachinePlaysBlack:
14847 GameEnds(GameIsDrawn, "Both players ran out of time",
14850 GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
14852 DisplayError(_("Your opponent is not out of time"), 0);
14860 ClockClick (int which)
14861 { // [HGM] code moved to back-end from winboard.c
14862 if(which) { // black clock
14863 if (gameMode == EditPosition || gameMode == IcsExamining) {
14864 if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
14865 SetBlackToPlayEvent();
14866 } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !blackFlag && WhiteOnMove(currentMove)) {
14867 UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move: if not out of time, enters null move
14868 } else if (shiftKey) {
14869 AdjustClock(which, -1);
14870 } else if (gameMode == IcsPlayingWhite ||
14871 gameMode == MachinePlaysBlack) {
14874 } else { // white clock
14875 if (gameMode == EditPosition || gameMode == IcsExamining) {
14876 if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
14877 SetWhiteToPlayEvent();
14878 } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !whiteFlag && !WhiteOnMove(currentMove)) {
14879 UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move
14880 } else if (shiftKey) {
14881 AdjustClock(which, -1);
14882 } else if (gameMode == IcsPlayingBlack ||
14883 gameMode == MachinePlaysWhite) {
14892 /* Offer draw or accept pending draw offer from opponent */
14894 if (appData.icsActive) {
14895 /* Note: tournament rules require draw offers to be
14896 made after you make your move but before you punch
14897 your clock. Currently ICS doesn't let you do that;
14898 instead, you immediately punch your clock after making
14899 a move, but you can offer a draw at any time. */
14901 SendToICS(ics_prefix);
14902 SendToICS("draw\n");
14903 userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
14904 } else if (cmailMsgLoaded) {
14905 if (currentMove == cmailOldMove &&
14906 commentList[cmailOldMove] != NULL &&
14907 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14908 "Black offers a draw" : "White offers a draw")) {
14909 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
14910 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
14911 } else if (currentMove == cmailOldMove + 1) {
14912 char *offer = WhiteOnMove(cmailOldMove) ?
14913 "White offers a draw" : "Black offers a draw";
14914 AppendComment(currentMove, offer, TRUE);
14915 DisplayComment(currentMove - 1, offer);
14916 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
14918 DisplayError(_("You must make your move before offering a draw"), 0);
14919 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
14921 } else if (first.offeredDraw) {
14922 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
14924 if (first.sendDrawOffers) {
14925 SendToProgram("draw\n", &first);
14926 userOfferedDraw = TRUE;
14934 /* Offer Adjourn or accept pending Adjourn offer from opponent */
14936 if (appData.icsActive) {
14937 SendToICS(ics_prefix);
14938 SendToICS("adjourn\n");
14940 /* Currently GNU Chess doesn't offer or accept Adjourns */
14948 /* Offer Abort or accept pending Abort offer from opponent */
14950 if (appData.icsActive) {
14951 SendToICS(ics_prefix);
14952 SendToICS("abort\n");
14954 GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
14961 /* Resign. You can do this even if it's not your turn. */
14963 if (appData.icsActive) {
14964 SendToICS(ics_prefix);
14965 SendToICS("resign\n");
14967 switch (gameMode) {
14968 case MachinePlaysWhite:
14969 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
14971 case MachinePlaysBlack:
14972 GameEnds(BlackWins, "White resigns", GE_PLAYER);
14975 if (cmailMsgLoaded) {
14977 if (WhiteOnMove(cmailOldMove)) {
14978 GameEnds(BlackWins, "White resigns", GE_PLAYER);
14980 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
14982 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
14993 StopObservingEvent ()
14995 /* Stop observing current games */
14996 SendToICS(ics_prefix);
14997 SendToICS("unobserve\n");
15001 StopExaminingEvent ()
15003 /* Stop observing current game */
15004 SendToICS(ics_prefix);
15005 SendToICS("unexamine\n");
15009 ForwardInner (int target)
15011 int limit; int oldSeekGraphUp = seekGraphUp;
15013 if (appData.debugMode)
15014 fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
15015 target, currentMove, forwardMostMove);
15017 if (gameMode == EditPosition)
15020 seekGraphUp = FALSE;
15021 MarkTargetSquares(1);
15023 if (gameMode == PlayFromGameFile && !pausing)
15026 if (gameMode == IcsExamining && pausing)
15027 limit = pauseExamForwardMostMove;
15029 limit = forwardMostMove;
15031 if (target > limit) target = limit;
15033 if (target > 0 && moveList[target - 1][0]) {
15034 int fromX, fromY, toX, toY;
15035 toX = moveList[target - 1][2] - AAA;
15036 toY = moveList[target - 1][3] - ONE;
15037 if (moveList[target - 1][1] == '@') {
15038 if (appData.highlightLastMove) {
15039 SetHighlights(-1, -1, toX, toY);
15042 fromX = moveList[target - 1][0] - AAA;
15043 fromY = moveList[target - 1][1] - ONE;
15044 if (target == currentMove + 1) {
15045 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
15047 if (appData.highlightLastMove) {
15048 SetHighlights(fromX, fromY, toX, toY);
15052 if (gameMode == EditGame || gameMode == AnalyzeMode ||
15053 gameMode == Training || gameMode == PlayFromGameFile ||
15054 gameMode == AnalyzeFile) {
15055 while (currentMove < target) {
15056 if(second.analyzing) SendMoveToProgram(currentMove, &second);
15057 SendMoveToProgram(currentMove++, &first);
15060 currentMove = target;
15063 if (gameMode == EditGame || gameMode == EndOfGame) {
15064 whiteTimeRemaining = timeRemaining[0][currentMove];
15065 blackTimeRemaining = timeRemaining[1][currentMove];
15067 DisplayBothClocks();
15068 DisplayMove(currentMove - 1);
15069 DrawPosition(oldSeekGraphUp, boards[currentMove]);
15070 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
15071 if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
15072 DisplayComment(currentMove - 1, commentList[currentMove]);
15074 ClearMap(); // [HGM] exclude: invalidate map
15081 if (gameMode == IcsExamining && !pausing) {
15082 SendToICS(ics_prefix);
15083 SendToICS("forward\n");
15085 ForwardInner(currentMove + 1);
15092 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15093 /* to optimze, we temporarily turn off analysis mode while we feed
15094 * the remaining moves to the engine. Otherwise we get analysis output
15097 if (first.analysisSupport) {
15098 SendToProgram("exit\nforce\n", &first);
15099 first.analyzing = FALSE;
15103 if (gameMode == IcsExamining && !pausing) {
15104 SendToICS(ics_prefix);
15105 SendToICS("forward 999999\n");
15107 ForwardInner(forwardMostMove);
15110 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15111 /* we have fed all the moves, so reactivate analysis mode */
15112 SendToProgram("analyze\n", &first);
15113 first.analyzing = TRUE;
15114 /*first.maybeThinking = TRUE;*/
15115 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
15120 BackwardInner (int target)
15122 int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
15124 if (appData.debugMode)
15125 fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
15126 target, currentMove, forwardMostMove);
15128 if (gameMode == EditPosition) return;
15129 seekGraphUp = FALSE;
15130 MarkTargetSquares(1);
15131 if (currentMove <= backwardMostMove) {
15133 DrawPosition(full_redraw, boards[currentMove]);
15136 if (gameMode == PlayFromGameFile && !pausing)
15139 if (moveList[target][0]) {
15140 int fromX, fromY, toX, toY;
15141 toX = moveList[target][2] - AAA;
15142 toY = moveList[target][3] - ONE;
15143 if (moveList[target][1] == '@') {
15144 if (appData.highlightLastMove) {
15145 SetHighlights(-1, -1, toX, toY);
15148 fromX = moveList[target][0] - AAA;
15149 fromY = moveList[target][1] - ONE;
15150 if (target == currentMove - 1) {
15151 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
15153 if (appData.highlightLastMove) {
15154 SetHighlights(fromX, fromY, toX, toY);
15158 if (gameMode == EditGame || gameMode==AnalyzeMode ||
15159 gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
15160 while (currentMove > target) {
15161 if(moveList[currentMove-1][1] == '@' && moveList[currentMove-1][0] == '@') {
15162 // null move cannot be undone. Reload program with move history before it.
15164 for(i=target; i>backwardMostMove; i--) { // seek back to start or previous null move
15165 if(moveList[i-1][1] == '@' && moveList[i-1][0] == '@') break;
15167 SendBoard(&first, i);
15168 if(second.analyzing) SendBoard(&second, i);
15169 for(currentMove=i; currentMove<target; currentMove++) {
15170 SendMoveToProgram(currentMove, &first);
15171 if(second.analyzing) SendMoveToProgram(currentMove, &second);
15175 SendToBoth("undo\n");
15179 currentMove = target;
15182 if (gameMode == EditGame || gameMode == EndOfGame) {
15183 whiteTimeRemaining = timeRemaining[0][currentMove];
15184 blackTimeRemaining = timeRemaining[1][currentMove];
15186 DisplayBothClocks();
15187 DisplayMove(currentMove - 1);
15188 DrawPosition(full_redraw, boards[currentMove]);
15189 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
15190 // [HGM] PV info: routine tests if comment empty
15191 DisplayComment(currentMove - 1, commentList[currentMove]);
15192 ClearMap(); // [HGM] exclude: invalidate map
15198 if (gameMode == IcsExamining && !pausing) {
15199 SendToICS(ics_prefix);
15200 SendToICS("backward\n");
15202 BackwardInner(currentMove - 1);
15209 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15210 /* to optimize, we temporarily turn off analysis mode while we undo
15211 * all the moves. Otherwise we get analysis output after each undo.
15213 if (first.analysisSupport) {
15214 SendToProgram("exit\nforce\n", &first);
15215 first.analyzing = FALSE;
15219 if (gameMode == IcsExamining && !pausing) {
15220 SendToICS(ics_prefix);
15221 SendToICS("backward 999999\n");
15223 BackwardInner(backwardMostMove);
15226 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15227 /* we have fed all the moves, so reactivate analysis mode */
15228 SendToProgram("analyze\n", &first);
15229 first.analyzing = TRUE;
15230 /*first.maybeThinking = TRUE;*/
15231 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
15238 if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
15239 if (to >= forwardMostMove) to = forwardMostMove;
15240 if (to <= backwardMostMove) to = backwardMostMove;
15241 if (to < currentMove) {
15249 RevertEvent (Boolean annotate)
15251 if(PopTail(annotate)) { // [HGM] vari: restore old game tail
15254 if (gameMode != IcsExamining) {
15255 DisplayError(_("You are not examining a game"), 0);
15259 DisplayError(_("You can't revert while pausing"), 0);
15262 SendToICS(ics_prefix);
15263 SendToICS("revert\n");
15267 RetractMoveEvent ()
15269 switch (gameMode) {
15270 case MachinePlaysWhite:
15271 case MachinePlaysBlack:
15272 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
15273 DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
15276 if (forwardMostMove < 2) return;
15277 currentMove = forwardMostMove = forwardMostMove - 2;
15278 whiteTimeRemaining = timeRemaining[0][currentMove];
15279 blackTimeRemaining = timeRemaining[1][currentMove];
15280 DisplayBothClocks();
15281 DisplayMove(currentMove - 1);
15282 ClearHighlights();/*!! could figure this out*/
15283 DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
15284 SendToProgram("remove\n", &first);
15285 /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
15288 case BeginningOfGame:
15292 case IcsPlayingWhite:
15293 case IcsPlayingBlack:
15294 if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
15295 SendToICS(ics_prefix);
15296 SendToICS("takeback 2\n");
15298 SendToICS(ics_prefix);
15299 SendToICS("takeback 1\n");
15308 ChessProgramState *cps;
15310 switch (gameMode) {
15311 case MachinePlaysWhite:
15312 if (!WhiteOnMove(forwardMostMove)) {
15313 DisplayError(_("It is your turn"), 0);
15318 case MachinePlaysBlack:
15319 if (WhiteOnMove(forwardMostMove)) {
15320 DisplayError(_("It is your turn"), 0);
15325 case TwoMachinesPlay:
15326 if (WhiteOnMove(forwardMostMove) ==
15327 (first.twoMachinesColor[0] == 'w')) {
15333 case BeginningOfGame:
15337 SendToProgram("?\n", cps);
15341 TruncateGameEvent ()
15344 if (gameMode != EditGame) return;
15351 CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
15352 if (forwardMostMove > currentMove) {
15353 if (gameInfo.resultDetails != NULL) {
15354 free(gameInfo.resultDetails);
15355 gameInfo.resultDetails = NULL;
15356 gameInfo.result = GameUnfinished;
15358 forwardMostMove = currentMove;
15359 HistorySet(parseList, backwardMostMove, forwardMostMove,
15367 if (appData.noChessProgram) return;
15368 switch (gameMode) {
15369 case MachinePlaysWhite:
15370 if (WhiteOnMove(forwardMostMove)) {
15371 DisplayError(_("Wait until your turn"), 0);
15375 case BeginningOfGame:
15376 case MachinePlaysBlack:
15377 if (!WhiteOnMove(forwardMostMove)) {
15378 DisplayError(_("Wait until your turn"), 0);
15383 DisplayError(_("No hint available"), 0);
15386 SendToProgram("hint\n", &first);
15387 hintRequested = TRUE;
15393 ListGame * lg = (ListGame *) gameList.head;
15396 static int secondTime = FALSE;
15398 if( !(f = GameFile()) || ((ListGame *) gameList.tailPred)->number <= 0 ) {
15399 DisplayError(_("Game list not loaded or empty"), 0);
15403 if(!secondTime && (g = fopen(appData.polyglotBook, "r"))) {
15406 DisplayNote(_("Book file exists! Try again for overwrite."));
15410 creatingBook = TRUE;
15411 secondTime = FALSE;
15413 /* Get list size */
15414 for (nItem = 1; nItem <= ((ListGame *) gameList.tailPred)->number; nItem++){
15415 LoadGame(f, nItem, "", TRUE);
15416 AddGameToBook(TRUE);
15417 lg = (ListGame *) lg->node.succ;
15420 creatingBook = FALSE;
15427 if (appData.noChessProgram) return;
15428 switch (gameMode) {
15429 case MachinePlaysWhite:
15430 if (WhiteOnMove(forwardMostMove)) {
15431 DisplayError(_("Wait until your turn"), 0);
15435 case BeginningOfGame:
15436 case MachinePlaysBlack:
15437 if (!WhiteOnMove(forwardMostMove)) {
15438 DisplayError(_("Wait until your turn"), 0);
15443 EditPositionDone(TRUE);
15445 case TwoMachinesPlay:
15450 SendToProgram("bk\n", &first);
15451 bookOutput[0] = NULLCHAR;
15452 bookRequested = TRUE;
15458 char *tags = PGNTags(&gameInfo);
15459 TagsPopUp(tags, CmailMsg());
15463 /* end button procedures */
15466 PrintPosition (FILE *fp, int move)
15470 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
15471 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
15472 char c = PieceToChar(boards[move][i][j]);
15473 fputc(c == 'x' ? '.' : c, fp);
15474 fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
15477 if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
15478 fprintf(fp, "white to play\n");
15480 fprintf(fp, "black to play\n");
15484 PrintOpponents (FILE *fp)
15486 if (gameInfo.white != NULL) {
15487 fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
15493 /* Find last component of program's own name, using some heuristics */
15495 TidyProgramName (char *prog, char *host, char buf[MSG_SIZ])
15498 int local = (strcmp(host, "localhost") == 0);
15499 while (!local && (p = strchr(prog, ';')) != NULL) {
15501 while (*p == ' ') p++;
15504 if (*prog == '"' || *prog == '\'') {
15505 q = strchr(prog + 1, *prog);
15507 q = strchr(prog, ' ');
15509 if (q == NULL) q = prog + strlen(prog);
15511 while (p >= prog && *p != '/' && *p != '\\') p--;
15513 if(p == prog && *p == '"') p++;
15515 if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) *q = c, q -= 4; else *q = c;
15516 memcpy(buf, p, q - p);
15517 buf[q - p] = NULLCHAR;
15525 TimeControlTagValue ()
15528 if (!appData.clockMode) {
15529 safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
15530 } else if (movesPerSession > 0) {
15531 snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
15532 } else if (timeIncrement == 0) {
15533 snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
15535 snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
15537 return StrSave(buf);
15543 /* This routine is used only for certain modes */
15544 VariantClass v = gameInfo.variant;
15545 ChessMove r = GameUnfinished;
15548 if(keepInfo) return;
15550 if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
15551 r = gameInfo.result;
15552 p = gameInfo.resultDetails;
15553 gameInfo.resultDetails = NULL;
15555 ClearGameInfo(&gameInfo);
15556 gameInfo.variant = v;
15558 switch (gameMode) {
15559 case MachinePlaysWhite:
15560 gameInfo.event = StrSave( appData.pgnEventHeader );
15561 gameInfo.site = StrSave(HostName());
15562 gameInfo.date = PGNDate();
15563 gameInfo.round = StrSave("-");
15564 gameInfo.white = StrSave(first.tidy);
15565 gameInfo.black = StrSave(UserName());
15566 gameInfo.timeControl = TimeControlTagValue();
15569 case MachinePlaysBlack:
15570 gameInfo.event = StrSave( appData.pgnEventHeader );
15571 gameInfo.site = StrSave(HostName());
15572 gameInfo.date = PGNDate();
15573 gameInfo.round = StrSave("-");
15574 gameInfo.white = StrSave(UserName());
15575 gameInfo.black = StrSave(first.tidy);
15576 gameInfo.timeControl = TimeControlTagValue();
15579 case TwoMachinesPlay:
15580 gameInfo.event = StrSave( appData.pgnEventHeader );
15581 gameInfo.site = StrSave(HostName());
15582 gameInfo.date = PGNDate();
15585 snprintf(buf, MSG_SIZ, "%d", roundNr);
15586 gameInfo.round = StrSave(buf);
15588 gameInfo.round = StrSave("-");
15590 if (first.twoMachinesColor[0] == 'w') {
15591 gameInfo.white = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
15592 gameInfo.black = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
15594 gameInfo.white = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
15595 gameInfo.black = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
15597 gameInfo.timeControl = TimeControlTagValue();
15601 gameInfo.event = StrSave("Edited game");
15602 gameInfo.site = StrSave(HostName());
15603 gameInfo.date = PGNDate();
15604 gameInfo.round = StrSave("-");
15605 gameInfo.white = StrSave("-");
15606 gameInfo.black = StrSave("-");
15607 gameInfo.result = r;
15608 gameInfo.resultDetails = p;
15612 gameInfo.event = StrSave("Edited position");
15613 gameInfo.site = StrSave(HostName());
15614 gameInfo.date = PGNDate();
15615 gameInfo.round = StrSave("-");
15616 gameInfo.white = StrSave("-");
15617 gameInfo.black = StrSave("-");
15620 case IcsPlayingWhite:
15621 case IcsPlayingBlack:
15626 case PlayFromGameFile:
15627 gameInfo.event = StrSave("Game from non-PGN file");
15628 gameInfo.site = StrSave(HostName());
15629 gameInfo.date = PGNDate();
15630 gameInfo.round = StrSave("-");
15631 gameInfo.white = StrSave("?");
15632 gameInfo.black = StrSave("?");
15641 ReplaceComment (int index, char *text)
15647 if(index && sscanf(text, "%f/%d", &score, &len) == 2 &&
15648 pvInfoList[index-1].depth == len &&
15649 fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
15650 (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
15651 while (*text == '\n') text++;
15652 len = strlen(text);
15653 while (len > 0 && text[len - 1] == '\n') len--;
15655 if (commentList[index] != NULL)
15656 free(commentList[index]);
15659 commentList[index] = NULL;
15662 if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
15663 *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
15664 *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
15665 commentList[index] = (char *) malloc(len + 2);
15666 strncpy(commentList[index], text, len);
15667 commentList[index][len] = '\n';
15668 commentList[index][len + 1] = NULLCHAR;
15670 // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
15672 commentList[index] = (char *) malloc(len + 7);
15673 safeStrCpy(commentList[index], "{\n", 3);
15674 safeStrCpy(commentList[index]+2, text, len+1);
15675 commentList[index][len+2] = NULLCHAR;
15676 while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
15677 strcat(commentList[index], "\n}\n");
15682 CrushCRs (char *text)
15690 if (ch == '\r') continue;
15692 } while (ch != '\0');
15696 AppendComment (int index, char *text, Boolean addBraces)
15697 /* addBraces tells if we should add {} */
15702 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces);
15703 if(addBraces == 3) addBraces = 0; else // force appending literally
15704 text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
15707 while (*text == '\n') text++;
15708 len = strlen(text);
15709 while (len > 0 && text[len - 1] == '\n') len--;
15710 text[len] = NULLCHAR;
15712 if (len == 0) return;
15714 if (commentList[index] != NULL) {
15715 Boolean addClosingBrace = addBraces;
15716 old = commentList[index];
15717 oldlen = strlen(old);
15718 while(commentList[index][oldlen-1] == '\n')
15719 commentList[index][--oldlen] = NULLCHAR;
15720 commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
15721 safeStrCpy(commentList[index], old, oldlen + len + 6);
15723 // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
15724 if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
15725 if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
15726 while (*text == '\n') { text++; len--; }
15727 commentList[index][--oldlen] = NULLCHAR;
15729 if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
15730 else strcat(commentList[index], "\n");
15731 strcat(commentList[index], text);
15732 if(addClosingBrace) strcat(commentList[index], addClosingBrace == 2 ? ")\n" : "\n}\n");
15733 else strcat(commentList[index], "\n");
15735 commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
15737 safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
15738 else commentList[index][0] = NULLCHAR;
15739 strcat(commentList[index], text);
15740 strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
15741 if(addBraces == TRUE) strcat(commentList[index], "}\n");
15746 FindStr (char * text, char * sub_text)
15748 char * result = strstr( text, sub_text );
15750 if( result != NULL ) {
15751 result += strlen( sub_text );
15757 /* [AS] Try to extract PV info from PGN comment */
15758 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
15760 GetInfoFromComment (int index, char * text)
15762 char * sep = text, *p;
15764 if( text != NULL && index > 0 ) {
15767 int time = -1, sec = 0, deci;
15768 char * s_eval = FindStr( text, "[%eval " );
15769 char * s_emt = FindStr( text, "[%emt " );
15771 if( s_eval != NULL || s_emt != NULL ) {
15773 if(0) { // [HGM] this code is not finished, and could actually be detrimental
15778 if( s_eval != NULL ) {
15779 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
15783 if( delim != ']' ) {
15788 if( s_emt != NULL ) {
15793 /* We expect something like: [+|-]nnn.nn/dd */
15796 if(*text != '{') return text; // [HGM] braces: must be normal comment
15798 sep = strchr( text, '/' );
15799 if( sep == NULL || sep < (text+4) ) {
15804 if(!strncmp(p+1, "final score ", 12)) p += 12, index++; else
15805 if(p[1] == '(') { // comment starts with PV
15806 p = strchr(p, ')'); // locate end of PV
15807 if(p == NULL || sep < p+5) return text;
15808 // at this point we have something like "{(.*) +0.23/6 ..."
15809 p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
15810 *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
15811 // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
15813 time = -1; sec = -1; deci = -1;
15814 if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
15815 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
15816 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
15817 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3 ) {
15821 if( score_lo < 0 || score_lo >= 100 ) {
15825 if(sec >= 0) time = 600*time + 10*sec; else
15826 if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
15828 score = score > 0 || !score & p[1] != '-' ? score*100 + score_lo : score*100 - score_lo;
15830 /* [HGM] PV time: now locate end of PV info */
15831 while( *++sep >= '0' && *sep <= '9'); // strip depth
15833 while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
15835 while( *++sep >= '0' && *sep <= '9'); // strip seconds
15837 while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
15838 while(*sep == ' ' || *sep == '\n' || *sep == '\r') sep++;
15849 pvInfoList[index-1].depth = depth;
15850 pvInfoList[index-1].score = score;
15851 pvInfoList[index-1].time = 10*time; // centi-sec
15852 if(*sep == '}') *sep = 0; else *--sep = '{';
15853 if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
15859 SendToProgram (char *message, ChessProgramState *cps)
15861 int count, outCount, error;
15864 if (cps->pr == NoProc) return;
15867 if (appData.debugMode) {
15870 fprintf(debugFP, "%ld >%-6s: %s",
15871 SubtractTimeMarks(&now, &programStartTime),
15872 cps->which, message);
15874 fprintf(serverFP, "%ld >%-6s: %s",
15875 SubtractTimeMarks(&now, &programStartTime),
15876 cps->which, message), fflush(serverFP);
15879 count = strlen(message);
15880 outCount = OutputToProcess(cps->pr, message, count, &error);
15881 if (outCount < count && !exiting
15882 && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
15883 if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
15884 snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
15885 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
15886 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
15887 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
15888 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
15889 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
15891 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
15892 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
15893 gameInfo.result = res;
15895 gameInfo.resultDetails = StrSave(buf);
15897 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
15898 if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
15903 ReceiveFromProgram (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
15907 ChessProgramState *cps = (ChessProgramState *)closure;
15909 if (isr != cps->isr) return; /* Killed intentionally */
15912 RemoveInputSource(cps->isr);
15913 snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
15914 _(cps->which), cps->program);
15915 if(LoadError(cps->userError ? NULL : buf, cps)) return; // [HGM] should not generate fatal error during engine load
15916 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
15917 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
15918 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
15919 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
15920 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
15922 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
15923 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
15924 gameInfo.result = res;
15926 gameInfo.resultDetails = StrSave(buf);
15928 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
15929 if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
15931 snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
15932 _(cps->which), cps->program);
15933 RemoveInputSource(cps->isr);
15935 /* [AS] Program is misbehaving badly... kill it */
15936 if( count == -2 ) {
15937 DestroyChildProcess( cps->pr, 9 );
15941 if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
15946 if ((end_str = strchr(message, '\r')) != NULL)
15947 *end_str = NULLCHAR;
15948 if ((end_str = strchr(message, '\n')) != NULL)
15949 *end_str = NULLCHAR;
15951 if (appData.debugMode) {
15952 TimeMark now; int print = 1;
15953 char *quote = ""; char c; int i;
15955 if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
15956 char start = message[0];
15957 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
15958 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
15959 sscanf(message, "move %c", &c)!=1 && sscanf(message, "offer%c", &c)!=1 &&
15960 sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
15961 sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
15962 sscanf(message, "tell%c", &c)!=1 && sscanf(message, "0-1 %c", &c)!=1 &&
15963 sscanf(message, "1-0 %c", &c)!=1 && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
15964 sscanf(message, "setboard %c", &c)!=1 && sscanf(message, "setup %c", &c)!=1 &&
15965 sscanf(message, "hint: %c", &c)!=1 &&
15966 sscanf(message, "pong %c", &c)!=1 && start != '#') {
15967 quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
15968 print = (appData.engineComments >= 2);
15970 message[0] = start; // restore original message
15974 fprintf(debugFP, "%ld <%-6s: %s%s\n",
15975 SubtractTimeMarks(&now, &programStartTime), cps->which,
15979 fprintf(serverFP, "%ld <%-6s: %s%s\n",
15980 SubtractTimeMarks(&now, &programStartTime), cps->which,
15982 message), fflush(serverFP);
15986 /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
15987 if (appData.icsEngineAnalyze) {
15988 if (strstr(message, "whisper") != NULL ||
15989 strstr(message, "kibitz") != NULL ||
15990 strstr(message, "tellics") != NULL) return;
15993 HandleMachineMove(message, cps);
15998 SendTimeControl (ChessProgramState *cps, int mps, long tc, int inc, int sd, int st)
16003 if( timeControl_2 > 0 ) {
16004 if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
16005 tc = timeControl_2;
16008 tc /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
16009 inc /= cps->timeOdds;
16010 st /= cps->timeOdds;
16012 seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
16015 /* Set exact time per move, normally using st command */
16016 if (cps->stKludge) {
16017 /* GNU Chess 4 has no st command; uses level in a nonstandard way */
16019 if (seconds == 0) {
16020 snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
16022 snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
16025 snprintf(buf, MSG_SIZ, "st %d\n", st);
16028 /* Set conventional or incremental time control, using level command */
16029 if (seconds == 0) {
16030 /* Note old gnuchess bug -- minutes:seconds used to not work.
16031 Fixed in later versions, but still avoid :seconds
16032 when seconds is 0. */
16033 snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
16035 snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
16036 seconds, inc/1000.);
16039 SendToProgram(buf, cps);
16041 /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
16042 /* Orthogonally, limit search to given depth */
16044 if (cps->sdKludge) {
16045 snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
16047 snprintf(buf, MSG_SIZ, "sd %d\n", sd);
16049 SendToProgram(buf, cps);
16052 if(cps->nps >= 0) { /* [HGM] nps */
16053 if(cps->supportsNPS == FALSE)
16054 cps->nps = -1; // don't use if engine explicitly says not supported!
16056 snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
16057 SendToProgram(buf, cps);
16062 ChessProgramState *
16064 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
16066 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
16067 gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
16073 SendTimeRemaining (ChessProgramState *cps, int machineWhite)
16075 char message[MSG_SIZ];
16078 /* Note: this routine must be called when the clocks are stopped
16079 or when they have *just* been set or switched; otherwise
16080 it will be off by the time since the current tick started.
16082 if (machineWhite) {
16083 time = whiteTimeRemaining / 10;
16084 otime = blackTimeRemaining / 10;
16086 time = blackTimeRemaining / 10;
16087 otime = whiteTimeRemaining / 10;
16089 /* [HGM] translate opponent's time by time-odds factor */
16090 otime = (otime * cps->other->timeOdds) / cps->timeOdds;
16092 if (time <= 0) time = 1;
16093 if (otime <= 0) otime = 1;
16095 snprintf(message, MSG_SIZ, "time %ld\n", time);
16096 SendToProgram(message, cps);
16098 snprintf(message, MSG_SIZ, "otim %ld\n", otime);
16099 SendToProgram(message, cps);
16103 EngineDefinedVariant (ChessProgramState *cps, int n)
16104 { // return name of n-th unknown variant that engine supports
16105 static char buf[MSG_SIZ];
16106 char *p, *s = cps->variants;
16107 if(!s) return NULL;
16108 do { // parse string from variants feature
16110 p = strchr(s, ',');
16111 if(p) *p = NULLCHAR;
16112 v = StringToVariant(s);
16113 if(v == VariantNormal && strcmp(s, "normal") && !strstr(s, "_normal")) v = VariantUnknown; // garbage is recognized as normal
16114 if(v == VariantUnknown) { // non-standard variant in list of engine-supported variants
16115 if(--n < 0) safeStrCpy(buf, s, MSG_SIZ);
16118 if(n < 0) return buf;
16124 BoolFeature (char **p, char *name, int *loc, ChessProgramState *cps)
16127 int len = strlen(name);
16130 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
16132 sscanf(*p, "%d", &val);
16134 while (**p && **p != ' ')
16136 snprintf(buf, MSG_SIZ, "accepted %s\n", name);
16137 SendToProgram(buf, cps);
16144 IntFeature (char **p, char *name, int *loc, ChessProgramState *cps)
16147 int len = strlen(name);
16148 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
16150 sscanf(*p, "%d", loc);
16151 while (**p && **p != ' ') (*p)++;
16152 snprintf(buf, MSG_SIZ, "accepted %s\n", name);
16153 SendToProgram(buf, cps);
16160 StringFeature (char **p, char *name, char **loc, ChessProgramState *cps)
16163 int len = strlen(name);
16164 if (strncmp((*p), name, len) == 0
16165 && (*p)[len] == '=' && (*p)[len+1] == '\"') {
16167 ASSIGN(*loc, *p); // kludge alert: assign rest of line just to be sure allocation is large enough so that sscanf below always fits
16168 sscanf(*p, "%[^\"]", *loc);
16169 while (**p && **p != '\"') (*p)++;
16170 if (**p == '\"') (*p)++;
16171 snprintf(buf, MSG_SIZ, "accepted %s\n", name);
16172 SendToProgram(buf, cps);
16179 ParseOption (Option *opt, ChessProgramState *cps)
16180 // [HGM] options: process the string that defines an engine option, and determine
16181 // name, type, default value, and allowed value range
16183 char *p, *q, buf[MSG_SIZ];
16184 int n, min = (-1)<<31, max = 1<<31, def;
16186 if(p = strstr(opt->name, " -spin ")) {
16187 if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
16188 if(max < min) max = min; // enforce consistency
16189 if(def < min) def = min;
16190 if(def > max) def = max;
16195 } else if((p = strstr(opt->name, " -slider "))) {
16196 // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
16197 if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
16198 if(max < min) max = min; // enforce consistency
16199 if(def < min) def = min;
16200 if(def > max) def = max;
16204 opt->type = Spin; // Slider;
16205 } else if((p = strstr(opt->name, " -string "))) {
16206 opt->textValue = p+9;
16207 opt->type = TextBox;
16208 } else if((p = strstr(opt->name, " -file "))) {
16209 // for now -file is a synonym for -string, to already provide compatibility with future polyglots
16210 opt->textValue = p+7;
16211 opt->type = FileName; // FileName;
16212 } else if((p = strstr(opt->name, " -path "))) {
16213 // for now -file is a synonym for -string, to already provide compatibility with future polyglots
16214 opt->textValue = p+7;
16215 opt->type = PathName; // PathName;
16216 } else if(p = strstr(opt->name, " -check ")) {
16217 if(sscanf(p, " -check %d", &def) < 1) return FALSE;
16218 opt->value = (def != 0);
16219 opt->type = CheckBox;
16220 } else if(p = strstr(opt->name, " -combo ")) {
16221 opt->textValue = (char*) (opt->choice = &cps->comboList[cps->comboCnt]); // cheat with pointer type
16222 cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
16223 if(*q == '*') cps->comboList[cps->comboCnt-1]++;
16224 opt->value = n = 0;
16225 while(q = StrStr(q, " /// ")) {
16226 n++; *q = 0; // count choices, and null-terminate each of them
16228 if(*q == '*') { // remember default, which is marked with * prefix
16232 cps->comboList[cps->comboCnt++] = q;
16234 cps->comboList[cps->comboCnt++] = NULL;
16236 opt->type = ComboBox;
16237 } else if(p = strstr(opt->name, " -button")) {
16238 opt->type = Button;
16239 } else if(p = strstr(opt->name, " -save")) {
16240 opt->type = SaveButton;
16241 } else return FALSE;
16242 *p = 0; // terminate option name
16243 // now look if the command-line options define a setting for this engine option.
16244 if(cps->optionSettings && cps->optionSettings[0])
16245 p = strstr(cps->optionSettings, opt->name); else p = NULL;
16246 if(p && (p == cps->optionSettings || p[-1] == ',')) {
16247 snprintf(buf, MSG_SIZ, "option %s", p);
16248 if(p = strstr(buf, ",")) *p = 0;
16249 if(q = strchr(buf, '=')) switch(opt->type) {
16251 for(n=0; n<opt->max; n++)
16252 if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
16255 safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
16259 opt->value = atoi(q+1);
16264 SendToProgram(buf, cps);
16270 FeatureDone (ChessProgramState *cps, int val)
16272 DelayedEventCallback cb = GetDelayedEvent();
16273 if ((cb == InitBackEnd3 && cps == &first) ||
16274 (cb == SettingsMenuIfReady && cps == &second) ||
16275 (cb == LoadEngine) ||
16276 (cb == TwoMachinesEventIfReady)) {
16277 CancelDelayedEvent();
16278 ScheduleDelayedEvent(cb, val ? 1 : 3600000);
16280 cps->initDone = val;
16281 if(val) cps->reload = FALSE;
16284 /* Parse feature command from engine */
16286 ParseFeatures (char *args, ChessProgramState *cps)
16294 while (*p == ' ') p++;
16295 if (*p == NULLCHAR) return;
16297 if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
16298 if (BoolFeature(&p, "xedit", &cps->extendedEdit, cps)) continue;
16299 if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
16300 if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
16301 if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
16302 if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
16303 if (BoolFeature(&p, "reuse", &val, cps)) {
16304 /* Engine can disable reuse, but can't enable it if user said no */
16305 if (!val) cps->reuse = FALSE;
16308 if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
16309 if (StringFeature(&p, "myname", &cps->tidy, cps)) {
16310 if (gameMode == TwoMachinesPlay) {
16311 DisplayTwoMachinesTitle();
16317 if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
16318 if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
16319 if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
16320 if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
16321 if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
16322 if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
16323 if (BoolFeature(&p, "exclude", &cps->excludeMoves, cps)) continue;
16324 if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
16325 if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
16326 if (BoolFeature(&p, "pause", &cps->pause, cps)) continue; // [HGM] pause
16327 if (IntFeature(&p, "done", &val, cps)) {
16328 FeatureDone(cps, val);
16331 /* Added by Tord: */
16332 if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
16333 if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
16334 /* End of additions by Tord */
16336 /* [HGM] added features: */
16337 if (BoolFeature(&p, "highlight", &cps->highlight, cps)) continue;
16338 if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
16339 if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
16340 if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
16341 if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
16342 if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
16343 if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
16344 if (StringFeature(&p, "option", &q, cps)) { // read to freshly allocated temp buffer first
16345 if(cps->reload) { FREE(q); q = NULL; continue; } // we are reloading because of xreuse
16346 FREE(cps->option[cps->nrOptions].name);
16347 cps->option[cps->nrOptions].name = q; q = NULL;
16348 if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
16349 snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
16350 SendToProgram(buf, cps);
16353 if(cps->nrOptions >= MAX_OPTIONS) {
16355 snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
16356 DisplayError(buf, 0);
16360 /* End of additions by HGM */
16362 /* unknown feature: complain and skip */
16364 while (*q && *q != '=') q++;
16365 snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
16366 SendToProgram(buf, cps);
16372 while (*p && *p != '\"') p++;
16373 if (*p == '\"') p++;
16375 while (*p && *p != ' ') p++;
16383 PeriodicUpdatesEvent (int newState)
16385 if (newState == appData.periodicUpdates)
16388 appData.periodicUpdates=newState;
16390 /* Display type changes, so update it now */
16391 // DisplayAnalysis();
16393 /* Get the ball rolling again... */
16395 AnalysisPeriodicEvent(1);
16396 StartAnalysisClock();
16401 PonderNextMoveEvent (int newState)
16403 if (newState == appData.ponderNextMove) return;
16404 if (gameMode == EditPosition) EditPositionDone(TRUE);
16406 SendToProgram("hard\n", &first);
16407 if (gameMode == TwoMachinesPlay) {
16408 SendToProgram("hard\n", &second);
16411 SendToProgram("easy\n", &first);
16412 thinkOutput[0] = NULLCHAR;
16413 if (gameMode == TwoMachinesPlay) {
16414 SendToProgram("easy\n", &second);
16417 appData.ponderNextMove = newState;
16421 NewSettingEvent (int option, int *feature, char *command, int value)
16425 if (gameMode == EditPosition) EditPositionDone(TRUE);
16426 snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
16427 if(feature == NULL || *feature) SendToProgram(buf, &first);
16428 if (gameMode == TwoMachinesPlay) {
16429 if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
16434 ShowThinkingEvent ()
16435 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
16437 static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
16438 int newState = appData.showThinking
16439 // [HGM] thinking: other features now need thinking output as well
16440 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
16442 if (oldState == newState) return;
16443 oldState = newState;
16444 if (gameMode == EditPosition) EditPositionDone(TRUE);
16446 SendToProgram("post\n", &first);
16447 if (gameMode == TwoMachinesPlay) {
16448 SendToProgram("post\n", &second);
16451 SendToProgram("nopost\n", &first);
16452 thinkOutput[0] = NULLCHAR;
16453 if (gameMode == TwoMachinesPlay) {
16454 SendToProgram("nopost\n", &second);
16457 // appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
16461 AskQuestionEvent (char *title, char *question, char *replyPrefix, char *which)
16463 ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
16464 if (pr == NoProc) return;
16465 AskQuestion(title, question, replyPrefix, pr);
16469 TypeInEvent (char firstChar)
16471 if ((gameMode == BeginningOfGame && !appData.icsActive) ||
16472 gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
16473 gameMode == AnalyzeMode || gameMode == EditGame ||
16474 gameMode == EditPosition || gameMode == IcsExamining ||
16475 gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
16476 isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes
16477 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||
16478 gameMode == IcsObserving || gameMode == TwoMachinesPlay ) ||
16479 gameMode == Training) PopUpMoveDialog(firstChar);
16483 TypeInDoneEvent (char *move)
16486 int n, fromX, fromY, toX, toY;
16488 ChessMove moveType;
16491 if(gameMode == EditPosition && ParseFEN(board, &n, move) ) {
16492 EditPositionPasteFEN(move);
16495 // [HGM] movenum: allow move number to be typed in any mode
16496 if(sscanf(move, "%d", &n) == 1 && n != 0 ) {
16500 // undocumented kludge: allow command-line option to be typed in!
16501 // (potentially fatal, and does not implement the effect of the option.)
16502 // should only be used for options that are values on which future decisions will be made,
16503 // and definitely not on options that would be used during initialization.
16504 if(strstr(move, "!!! -") == move) {
16505 ParseArgsFromString(move+4);
16509 if (gameMode != EditGame && currentMove != forwardMostMove &&
16510 gameMode != Training) {
16511 DisplayMoveError(_("Displayed move is not current"));
16513 int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
16514 &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
16515 if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized
16516 if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
16517 &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {
16518 UserMoveEvent(fromX, fromY, toX, toY, promoChar);
16520 DisplayMoveError(_("Could not parse move"));
16526 DisplayMove (int moveNumber)
16528 char message[MSG_SIZ];
16530 char cpThinkOutput[MSG_SIZ];
16532 if(appData.noGUI) return; // [HGM] fast: suppress display of moves
16534 if (moveNumber == forwardMostMove - 1 ||
16535 gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
16537 safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
16539 if (strchr(cpThinkOutput, '\n')) {
16540 *strchr(cpThinkOutput, '\n') = NULLCHAR;
16543 *cpThinkOutput = NULLCHAR;
16546 /* [AS] Hide thinking from human user */
16547 if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
16548 *cpThinkOutput = NULLCHAR;
16549 if( thinkOutput[0] != NULLCHAR ) {
16552 for( i=0; i<=hiddenThinkOutputState; i++ ) {
16553 cpThinkOutput[i] = '.';
16555 cpThinkOutput[i] = NULLCHAR;
16556 hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
16560 if (moveNumber == forwardMostMove - 1 &&
16561 gameInfo.resultDetails != NULL) {
16562 if (gameInfo.resultDetails[0] == NULLCHAR) {
16563 snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
16565 snprintf(res, MSG_SIZ, " {%s} %s",
16566 T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
16572 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
16573 DisplayMessage(res, cpThinkOutput);
16575 snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
16576 WhiteOnMove(moveNumber) ? " " : ".. ",
16577 parseList[moveNumber], res);
16578 DisplayMessage(message, cpThinkOutput);
16583 DisplayComment (int moveNumber, char *text)
16585 char title[MSG_SIZ];
16587 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
16588 safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
16590 snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
16591 WhiteOnMove(moveNumber) ? " " : ".. ",
16592 parseList[moveNumber]);
16594 if (text != NULL && (appData.autoDisplayComment || commentUp))
16595 CommentPopUp(title, text);
16598 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
16599 * might be busy thinking or pondering. It can be omitted if your
16600 * gnuchess is configured to stop thinking immediately on any user
16601 * input. However, that gnuchess feature depends on the FIONREAD
16602 * ioctl, which does not work properly on some flavors of Unix.
16605 Attention (ChessProgramState *cps)
16608 if (!cps->useSigint) return;
16609 if (appData.noChessProgram || (cps->pr == NoProc)) return;
16610 switch (gameMode) {
16611 case MachinePlaysWhite:
16612 case MachinePlaysBlack:
16613 case TwoMachinesPlay:
16614 case IcsPlayingWhite:
16615 case IcsPlayingBlack:
16618 /* Skip if we know it isn't thinking */
16619 if (!cps->maybeThinking) return;
16620 if (appData.debugMode)
16621 fprintf(debugFP, "Interrupting %s\n", cps->which);
16622 InterruptChildProcess(cps->pr);
16623 cps->maybeThinking = FALSE;
16628 #endif /*ATTENTION*/
16634 if (whiteTimeRemaining <= 0) {
16637 if (appData.icsActive) {
16638 if (appData.autoCallFlag &&
16639 gameMode == IcsPlayingBlack && !blackFlag) {
16640 SendToICS(ics_prefix);
16641 SendToICS("flag\n");
16645 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
16647 if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
16648 if (appData.autoCallFlag) {
16649 GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
16656 if (blackTimeRemaining <= 0) {
16659 if (appData.icsActive) {
16660 if (appData.autoCallFlag &&
16661 gameMode == IcsPlayingWhite && !whiteFlag) {
16662 SendToICS(ics_prefix);
16663 SendToICS("flag\n");
16667 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
16669 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
16670 if (appData.autoCallFlag) {
16671 GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
16682 CheckTimeControl ()
16684 if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
16685 gameMode == PlayFromGameFile || forwardMostMove == 0) return;
16688 * add time to clocks when time control is achieved ([HGM] now also used for increment)
16690 if ( !WhiteOnMove(forwardMostMove) ) {
16691 /* White made time control */
16692 lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
16693 whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
16694 /* [HGM] time odds: correct new time quota for time odds! */
16695 / WhitePlayer()->timeOdds;
16696 lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
16698 lastBlack -= blackTimeRemaining;
16699 /* Black made time control */
16700 blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
16701 / WhitePlayer()->other->timeOdds;
16702 lastWhite = whiteTimeRemaining;
16707 DisplayBothClocks ()
16709 int wom = gameMode == EditPosition ?
16710 !blackPlaysFirst : WhiteOnMove(currentMove);
16711 DisplayWhiteClock(whiteTimeRemaining, wom);
16712 DisplayBlackClock(blackTimeRemaining, !wom);
16716 /* Timekeeping seems to be a portability nightmare. I think everyone
16717 has ftime(), but I'm really not sure, so I'm including some ifdefs
16718 to use other calls if you don't. Clocks will be less accurate if
16719 you have neither ftime nor gettimeofday.
16722 /* VS 2008 requires the #include outside of the function */
16723 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
16724 #include <sys/timeb.h>
16727 /* Get the current time as a TimeMark */
16729 GetTimeMark (TimeMark *tm)
16731 #if HAVE_GETTIMEOFDAY
16733 struct timeval timeVal;
16734 struct timezone timeZone;
16736 gettimeofday(&timeVal, &timeZone);
16737 tm->sec = (long) timeVal.tv_sec;
16738 tm->ms = (int) (timeVal.tv_usec / 1000L);
16740 #else /*!HAVE_GETTIMEOFDAY*/
16743 // include <sys/timeb.h> / moved to just above start of function
16744 struct timeb timeB;
16747 tm->sec = (long) timeB.time;
16748 tm->ms = (int) timeB.millitm;
16750 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
16751 tm->sec = (long) time(NULL);
16757 /* Return the difference in milliseconds between two
16758 time marks. We assume the difference will fit in a long!
16761 SubtractTimeMarks (TimeMark *tm2, TimeMark *tm1)
16763 return 1000L*(tm2->sec - tm1->sec) +
16764 (long) (tm2->ms - tm1->ms);
16769 * Code to manage the game clocks.
16771 * In tournament play, black starts the clock and then white makes a move.
16772 * We give the human user a slight advantage if he is playing white---the
16773 * clocks don't run until he makes his first move, so it takes zero time.
16774 * Also, we don't account for network lag, so we could get out of sync
16775 * with GNU Chess's clock -- but then, referees are always right.
16778 static TimeMark tickStartTM;
16779 static long intendedTickLength;
16782 NextTickLength (long timeRemaining)
16784 long nominalTickLength, nextTickLength;
16786 if (timeRemaining > 0L && timeRemaining <= 10000L)
16787 nominalTickLength = 100L;
16789 nominalTickLength = 1000L;
16790 nextTickLength = timeRemaining % nominalTickLength;
16791 if (nextTickLength <= 0) nextTickLength += nominalTickLength;
16793 return nextTickLength;
16796 /* Adjust clock one minute up or down */
16798 AdjustClock (Boolean which, int dir)
16800 if(appData.autoCallFlag) { DisplayError(_("Clock adjustment not allowed in auto-flag mode"), 0); return; }
16801 if(which) blackTimeRemaining += 60000*dir;
16802 else whiteTimeRemaining += 60000*dir;
16803 DisplayBothClocks();
16804 adjustedClock = TRUE;
16807 /* Stop clocks and reset to a fresh time control */
16811 (void) StopClockTimer();
16812 if (appData.icsActive) {
16813 whiteTimeRemaining = blackTimeRemaining = 0;
16814 } else if (searchTime) {
16815 whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
16816 blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
16817 } else { /* [HGM] correct new time quote for time odds */
16818 whiteTC = blackTC = fullTimeControlString;
16819 whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
16820 blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
16822 if (whiteFlag || blackFlag) {
16824 whiteFlag = blackFlag = FALSE;
16826 lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
16827 DisplayBothClocks();
16828 adjustedClock = FALSE;
16831 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
16833 /* Decrement running clock by amount of time that has passed */
16837 long timeRemaining;
16838 long lastTickLength, fudge;
16841 if (!appData.clockMode) return;
16842 if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
16846 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16848 /* Fudge if we woke up a little too soon */
16849 fudge = intendedTickLength - lastTickLength;
16850 if (fudge < 0 || fudge > FUDGE) fudge = 0;
16852 if (WhiteOnMove(forwardMostMove)) {
16853 if(whiteNPS >= 0) lastTickLength = 0;
16854 timeRemaining = whiteTimeRemaining -= lastTickLength;
16855 if(timeRemaining < 0 && !appData.icsActive) {
16856 GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
16857 if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
16858 whiteStartMove = forwardMostMove; whiteTC = nextSession;
16859 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
16862 DisplayWhiteClock(whiteTimeRemaining - fudge,
16863 WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
16865 if(blackNPS >= 0) lastTickLength = 0;
16866 timeRemaining = blackTimeRemaining -= lastTickLength;
16867 if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
16868 GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
16870 blackStartMove = forwardMostMove;
16871 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
16874 DisplayBlackClock(blackTimeRemaining - fudge,
16875 !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
16877 if (CheckFlags()) return;
16879 if(twoBoards) { // count down secondary board's clocks as well
16880 activePartnerTime -= lastTickLength;
16882 if(activePartner == 'W')
16883 DisplayWhiteClock(activePartnerTime, TRUE); // the counting clock is always the highlighted one!
16885 DisplayBlackClock(activePartnerTime, TRUE);
16890 intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
16891 StartClockTimer(intendedTickLength);
16893 /* if the time remaining has fallen below the alarm threshold, sound the
16894 * alarm. if the alarm has sounded and (due to a takeback or time control
16895 * with increment) the time remaining has increased to a level above the
16896 * threshold, reset the alarm so it can sound again.
16899 if (appData.icsActive && appData.icsAlarm) {
16901 /* make sure we are dealing with the user's clock */
16902 if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
16903 ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
16906 if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
16907 alarmSounded = FALSE;
16908 } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
16910 alarmSounded = TRUE;
16916 /* A player has just moved, so stop the previously running
16917 clock and (if in clock mode) start the other one.
16918 We redisplay both clocks in case we're in ICS mode, because
16919 ICS gives us an update to both clocks after every move.
16920 Note that this routine is called *after* forwardMostMove
16921 is updated, so the last fractional tick must be subtracted
16922 from the color that is *not* on move now.
16925 SwitchClocks (int newMoveNr)
16927 long lastTickLength;
16929 int flagged = FALSE;
16933 if (StopClockTimer() && appData.clockMode) {
16934 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16935 if (!WhiteOnMove(forwardMostMove)) {
16936 if(blackNPS >= 0) lastTickLength = 0;
16937 blackTimeRemaining -= lastTickLength;
16938 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
16939 // if(pvInfoList[forwardMostMove].time == -1)
16940 pvInfoList[forwardMostMove].time = // use GUI time
16941 (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
16943 if(whiteNPS >= 0) lastTickLength = 0;
16944 whiteTimeRemaining -= lastTickLength;
16945 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
16946 // if(pvInfoList[forwardMostMove].time == -1)
16947 pvInfoList[forwardMostMove].time =
16948 (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
16950 flagged = CheckFlags();
16952 forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
16953 CheckTimeControl();
16955 if (flagged || !appData.clockMode) return;
16957 switch (gameMode) {
16958 case MachinePlaysBlack:
16959 case MachinePlaysWhite:
16960 case BeginningOfGame:
16961 if (pausing) return;
16965 case PlayFromGameFile:
16973 if (searchTime) { // [HGM] st: set clock of player that has to move to max time
16974 if(WhiteOnMove(forwardMostMove))
16975 whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
16976 else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
16980 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
16981 whiteTimeRemaining : blackTimeRemaining);
16982 StartClockTimer(intendedTickLength);
16986 /* Stop both clocks */
16990 long lastTickLength;
16993 if (!StopClockTimer()) return;
16994 if (!appData.clockMode) return;
16998 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16999 if (WhiteOnMove(forwardMostMove)) {
17000 if(whiteNPS >= 0) lastTickLength = 0;
17001 whiteTimeRemaining -= lastTickLength;
17002 DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
17004 if(blackNPS >= 0) lastTickLength = 0;
17005 blackTimeRemaining -= lastTickLength;
17006 DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
17011 /* Start clock of player on move. Time may have been reset, so
17012 if clock is already running, stop and restart it. */
17016 (void) StopClockTimer(); /* in case it was running already */
17017 DisplayBothClocks();
17018 if (CheckFlags()) return;
17020 if (!appData.clockMode) return;
17021 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
17023 GetTimeMark(&tickStartTM);
17024 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
17025 whiteTimeRemaining : blackTimeRemaining);
17027 /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
17028 whiteNPS = blackNPS = -1;
17029 if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
17030 || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
17031 whiteNPS = first.nps;
17032 if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
17033 || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
17034 blackNPS = first.nps;
17035 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
17036 whiteNPS = second.nps;
17037 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
17038 blackNPS = second.nps;
17039 if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
17041 StartClockTimer(intendedTickLength);
17045 TimeString (long ms)
17047 long second, minute, hour, day;
17049 static char buf[32];
17051 if (ms > 0 && ms <= 9900) {
17052 /* convert milliseconds to tenths, rounding up */
17053 double tenths = floor( ((double)(ms + 99L)) / 100.00 );
17055 snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
17059 /* convert milliseconds to seconds, rounding up */
17060 /* use floating point to avoid strangeness of integer division
17061 with negative dividends on many machines */
17062 second = (long) floor(((double) (ms + 999L)) / 1000.0);
17069 day = second / (60 * 60 * 24);
17070 second = second % (60 * 60 * 24);
17071 hour = second / (60 * 60);
17072 second = second % (60 * 60);
17073 minute = second / 60;
17074 second = second % 60;
17077 snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
17078 sign, day, hour, minute, second);
17080 snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
17082 snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
17089 * This is necessary because some C libraries aren't ANSI C compliant yet.
17092 StrStr (char *string, char *match)
17096 length = strlen(match);
17098 for (i = strlen(string) - length; i >= 0; i--, string++)
17099 if (!strncmp(match, string, length))
17106 StrCaseStr (char *string, char *match)
17110 length = strlen(match);
17112 for (i = strlen(string) - length; i >= 0; i--, string++) {
17113 for (j = 0; j < length; j++) {
17114 if (ToLower(match[j]) != ToLower(string[j]))
17117 if (j == length) return string;
17125 StrCaseCmp (char *s1, char *s2)
17130 c1 = ToLower(*s1++);
17131 c2 = ToLower(*s2++);
17132 if (c1 > c2) return 1;
17133 if (c1 < c2) return -1;
17134 if (c1 == NULLCHAR) return 0;
17142 return isupper(c) ? tolower(c) : c;
17149 return islower(c) ? toupper(c) : c;
17151 #endif /* !_amigados */
17158 if ((ret = (char *) malloc(strlen(s) + 1)))
17160 safeStrCpy(ret, s, strlen(s)+1);
17166 StrSavePtr (char *s, char **savePtr)
17171 if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
17172 safeStrCpy(*savePtr, s, strlen(s)+1);
17184 clock = time((time_t *)NULL);
17185 tm = localtime(&clock);
17186 snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
17187 tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
17188 return StrSave(buf);
17193 PositionToFEN (int move, char *overrideCastling, int moveCounts)
17195 int i, j, fromX, fromY, toX, toY;
17202 whiteToPlay = (gameMode == EditPosition) ?
17203 !blackPlaysFirst : (move % 2 == 0);
17206 /* Piece placement data */
17207 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
17208 if(MSG_SIZ - (p - buf) < BOARD_RGHT - BOARD_LEFT + 20) { *p = 0; return StrSave(buf); }
17210 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
17211 if (boards[move][i][j] == EmptySquare) {
17213 } else { ChessSquare piece = boards[move][i][j];
17214 if (emptycount > 0) {
17215 if(emptycount<10) /* [HGM] can be >= 10 */
17216 *p++ = '0' + emptycount;
17217 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
17220 if(PieceToChar(piece) == '+') {
17221 /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
17223 piece = (ChessSquare)(DEMOTED piece);
17225 *p++ = (piece == DarkSquare ? '*' : PieceToChar(piece));
17227 /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
17228 p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
17233 if (emptycount > 0) {
17234 if(emptycount<10) /* [HGM] can be >= 10 */
17235 *p++ = '0' + emptycount;
17236 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
17243 /* [HGM] print Crazyhouse or Shogi holdings */
17244 if( gameInfo.holdingsWidth ) {
17245 *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
17247 for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
17248 piece = boards[move][i][BOARD_WIDTH-1];
17249 if( piece != EmptySquare )
17250 for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
17251 *p++ = PieceToChar(piece);
17253 for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
17254 piece = boards[move][BOARD_HEIGHT-i-1][0];
17255 if( piece != EmptySquare )
17256 for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
17257 *p++ = PieceToChar(piece);
17260 if( q == p ) *p++ = '-';
17266 *p++ = whiteToPlay ? 'w' : 'b';
17269 if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
17270 while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
17272 if(nrCastlingRights) {
17274 if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
17275 /* [HGM] write directly from rights */
17276 if(boards[move][CASTLING][2] != NoRights &&
17277 boards[move][CASTLING][0] != NoRights )
17278 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
17279 if(boards[move][CASTLING][2] != NoRights &&
17280 boards[move][CASTLING][1] != NoRights )
17281 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
17282 if(boards[move][CASTLING][5] != NoRights &&
17283 boards[move][CASTLING][3] != NoRights )
17284 *p++ = boards[move][CASTLING][3] + AAA;
17285 if(boards[move][CASTLING][5] != NoRights &&
17286 boards[move][CASTLING][4] != NoRights )
17287 *p++ = boards[move][CASTLING][4] + AAA;
17290 /* [HGM] write true castling rights */
17291 if( nrCastlingRights == 6 ) {
17293 if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
17294 boards[move][CASTLING][2] != NoRights ) k = 1, *p++ = 'K';
17295 q = (boards[move][CASTLING][1] == BOARD_LEFT &&
17296 boards[move][CASTLING][2] != NoRights );
17297 if(gameInfo.variant == VariantSChess) { // for S-Chess, indicate all vrgin backrank pieces
17298 for(i=j=0; i<BOARD_HEIGHT; i++) j += boards[move][i][BOARD_RGHT]; // count white held pieces
17299 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q && j; i--)
17300 if((boards[move][0][i] != WhiteKing || k+q == 0) &&
17301 boards[move][VIRGIN][i] & VIRGIN_W) *p++ = i + AAA + 'A' - 'a';
17305 if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
17306 boards[move][CASTLING][5] != NoRights ) k = 1, *p++ = 'k';
17307 q = (boards[move][CASTLING][4] == BOARD_LEFT &&
17308 boards[move][CASTLING][5] != NoRights );
17309 if(gameInfo.variant == VariantSChess) {
17310 for(i=j=0; i<BOARD_HEIGHT; i++) j += boards[move][i][BOARD_LEFT-1]; // count black held pieces
17311 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q && j; i--)
17312 if((boards[move][BOARD_HEIGHT-1][i] != BlackKing || k+q == 0) &&
17313 boards[move][VIRGIN][i] & VIRGIN_B) *p++ = i + AAA;
17318 if (q == p) *p++ = '-'; /* No castling rights */
17322 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
17323 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier &&
17324 gameInfo.variant != VariantMakruk && gameInfo.variant != VariantASEAN ) {
17325 /* En passant target square */
17326 if (move > backwardMostMove) {
17327 fromX = moveList[move - 1][0] - AAA;
17328 fromY = moveList[move - 1][1] - ONE;
17329 toX = moveList[move - 1][2] - AAA;
17330 toY = moveList[move - 1][3] - ONE;
17331 if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
17332 toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
17333 boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
17335 /* 2-square pawn move just happened */
17337 *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
17341 } else if(move == backwardMostMove) {
17342 // [HGM] perhaps we should always do it like this, and forget the above?
17343 if((signed char)boards[move][EP_STATUS] >= 0) {
17344 *p++ = boards[move][EP_STATUS] + AAA;
17345 *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
17357 { int i = 0, j=move;
17359 /* [HGM] find reversible plies */
17360 if (appData.debugMode) { int k;
17361 fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
17362 for(k=backwardMostMove; k<=forwardMostMove; k++)
17363 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
17367 while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
17368 if( j == backwardMostMove ) i += initialRulePlies;
17369 sprintf(p, "%d ", i);
17370 p += i>=100 ? 4 : i >= 10 ? 3 : 2;
17372 /* Fullmove number */
17373 sprintf(p, "%d", (move / 2) + 1);
17374 } else *--p = NULLCHAR;
17376 return StrSave(buf);
17380 ParseFEN (Board board, int *blackPlaysFirst, char *fen)
17384 int emptycount, virgin[BOARD_FILES];
17389 /* [HGM] by default clear Crazyhouse holdings, if present */
17390 if(gameInfo.holdingsWidth) {
17391 for(i=0; i<BOARD_HEIGHT; i++) {
17392 board[i][0] = EmptySquare; /* black holdings */
17393 board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
17394 board[i][1] = (ChessSquare) 0; /* black counts */
17395 board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
17399 /* Piece placement data */
17400 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
17403 if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
17404 if (*p == '/') p++;
17405 emptycount = gameInfo.boardWidth - j;
17406 while (emptycount--)
17407 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
17409 #if(BOARD_FILES >= 10)
17410 } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
17411 p++; emptycount=10;
17412 if (j + emptycount > gameInfo.boardWidth) return FALSE;
17413 while (emptycount--)
17414 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
17416 } else if (*p == '*') {
17417 board[i][(j++)+gameInfo.holdingsWidth] = DarkSquare; p++;
17418 } else if (isdigit(*p)) {
17419 emptycount = *p++ - '0';
17420 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
17421 if (j + emptycount > gameInfo.boardWidth) return FALSE;
17422 while (emptycount--)
17423 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
17424 } else if (*p == '+' || isalpha(*p)) {
17425 if (j >= gameInfo.boardWidth) return FALSE;
17427 piece = CharToPiece(*++p);
17428 if(piece == EmptySquare) return FALSE; /* unknown piece */
17429 piece = (ChessSquare) (PROMOTED piece ); p++;
17430 if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
17431 } else piece = CharToPiece(*p++);
17433 if(piece==EmptySquare) return FALSE; /* unknown piece */
17434 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
17435 piece = (ChessSquare) (PROMOTED piece);
17436 if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
17439 board[i][(j++)+gameInfo.holdingsWidth] = piece;
17445 while (*p == '/' || *p == ' ') p++;
17447 /* [HGM] look for Crazyhouse holdings here */
17448 while(*p==' ') p++;
17449 if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
17451 if(*p == '-' ) p++; /* empty holdings */ else {
17452 if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
17453 /* if we would allow FEN reading to set board size, we would */
17454 /* have to add holdings and shift the board read so far here */
17455 while( (piece = CharToPiece(*p) ) != EmptySquare ) {
17457 if((int) piece >= (int) BlackPawn ) {
17458 i = (int)piece - (int)BlackPawn;
17459 i = PieceToNumber((ChessSquare)i);
17460 if( i >= gameInfo.holdingsSize ) return FALSE;
17461 board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
17462 board[BOARD_HEIGHT-1-i][1]++; /* black counts */
17464 i = (int)piece - (int)WhitePawn;
17465 i = PieceToNumber((ChessSquare)i);
17466 if( i >= gameInfo.holdingsSize ) return FALSE;
17467 board[i][BOARD_WIDTH-1] = piece; /* white holdings */
17468 board[i][BOARD_WIDTH-2]++; /* black holdings */
17475 while(*p == ' ') p++;
17479 if(appData.colorNickNames) {
17480 if( c == appData.colorNickNames[0] ) c = 'w'; else
17481 if( c == appData.colorNickNames[1] ) c = 'b';
17485 *blackPlaysFirst = FALSE;
17488 *blackPlaysFirst = TRUE;
17494 /* [HGM] We NO LONGER ignore the rest of the FEN notation */
17495 /* return the extra info in global variiables */
17497 /* set defaults in case FEN is incomplete */
17498 board[EP_STATUS] = EP_UNKNOWN;
17499 for(i=0; i<nrCastlingRights; i++ ) {
17500 board[CASTLING][i] =
17501 gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
17502 } /* assume possible unless obviously impossible */
17503 if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
17504 if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
17505 if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
17506 && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
17507 if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
17508 if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
17509 if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
17510 && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
17513 while(*p==' ') p++;
17514 if(nrCastlingRights) {
17515 if(gameInfo.variant == VariantSChess) for(i=0; i<BOARD_FILES; i++) virgin[i] = 0;
17516 if(*p >= 'A' && *p <= 'Z' || *p >= 'a' && *p <= 'z' || *p=='-') {
17517 /* castling indicator present, so default becomes no castlings */
17518 for(i=0; i<nrCastlingRights; i++ ) {
17519 board[CASTLING][i] = NoRights;
17522 while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
17523 (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess) &&
17524 ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
17525 ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth) ) {
17526 int c = *p++, whiteKingFile=NoRights, blackKingFile=NoRights;
17528 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
17529 if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
17530 if(board[0 ][i] == WhiteKing) whiteKingFile = i;
17532 if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
17533 whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
17534 if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
17535 && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
17536 if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
17537 && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
17540 for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
17541 board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
17542 board[CASTLING][2] = whiteKingFile;
17543 if(board[CASTLING][0] != NoRights) virgin[board[CASTLING][0]] |= VIRGIN_W;
17544 if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
17547 for(i=BOARD_LEFT; i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
17548 board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
17549 board[CASTLING][2] = whiteKingFile;
17550 if(board[CASTLING][1] != NoRights) virgin[board[CASTLING][1]] |= VIRGIN_W;
17551 if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
17554 for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
17555 board[CASTLING][3] = i != blackKingFile ? i : NoRights;
17556 board[CASTLING][5] = blackKingFile;
17557 if(board[CASTLING][3] != NoRights) virgin[board[CASTLING][3]] |= VIRGIN_B;
17558 if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
17561 for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
17562 board[CASTLING][4] = i != blackKingFile ? i : NoRights;
17563 board[CASTLING][5] = blackKingFile;
17564 if(board[CASTLING][4] != NoRights) virgin[board[CASTLING][4]] |= VIRGIN_B;
17565 if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
17568 default: /* FRC castlings */
17569 if(c >= 'a') { /* black rights */
17570 if(gameInfo.variant == VariantSChess) { virgin[c-AAA] |= VIRGIN_B; break; } // in S-Chess castlings are always kq, so just virginity
17571 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
17572 if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
17573 if(i == BOARD_RGHT) break;
17574 board[CASTLING][5] = i;
17576 if(board[BOARD_HEIGHT-1][c] < BlackPawn ||
17577 board[BOARD_HEIGHT-1][c] >= BlackKing ) break;
17579 board[CASTLING][3] = c;
17581 board[CASTLING][4] = c;
17582 } else { /* white rights */
17583 if(gameInfo.variant == VariantSChess) { virgin[c-AAA-'A'+'a'] |= VIRGIN_W; break; } // in S-Chess castlings are always KQ
17584 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
17585 if(board[0][i] == WhiteKing) break;
17586 if(i == BOARD_RGHT) break;
17587 board[CASTLING][2] = i;
17588 c -= AAA - 'a' + 'A';
17589 if(board[0][c] >= WhiteKing) break;
17591 board[CASTLING][0] = c;
17593 board[CASTLING][1] = c;
17597 for(i=0; i<nrCastlingRights; i++)
17598 if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
17599 if(gameInfo.variant == VariantSChess) for(i=0; i<BOARD_FILES; i++) board[VIRGIN][i] = virgin[i];
17600 if (appData.debugMode) {
17601 fprintf(debugFP, "FEN castling rights:");
17602 for(i=0; i<nrCastlingRights; i++)
17603 fprintf(debugFP, " %d", board[CASTLING][i]);
17604 fprintf(debugFP, "\n");
17607 while(*p==' ') p++;
17610 /* read e.p. field in games that know e.p. capture */
17611 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
17612 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier &&
17613 gameInfo.variant != VariantMakruk && gameInfo.variant != VariantASEAN ) {
17615 p++; board[EP_STATUS] = EP_NONE;
17617 char c = *p++ - AAA;
17619 if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
17620 if(*p >= '0' && *p <='9') p++;
17621 board[EP_STATUS] = c;
17626 if(sscanf(p, "%d", &i) == 1) {
17627 FENrulePlies = i; /* 50-move ply counter */
17628 /* (The move number is still ignored) */
17635 EditPositionPasteFEN (char *fen)
17638 Board initial_position;
17640 if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
17641 DisplayError(_("Bad FEN position in clipboard"), 0);
17644 int savedBlackPlaysFirst = blackPlaysFirst;
17645 EditPositionEvent();
17646 blackPlaysFirst = savedBlackPlaysFirst;
17647 CopyBoard(boards[0], initial_position);
17648 initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
17649 EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
17650 DisplayBothClocks();
17651 DrawPosition(FALSE, boards[currentMove]);
17656 static char cseq[12] = "\\ ";
17659 set_cont_sequence (char *new_seq)
17664 // handle bad attempts to set the sequence
17666 return 0; // acceptable error - no debug
17668 len = strlen(new_seq);
17669 ret = (len > 0) && (len < sizeof(cseq));
17671 safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
17672 else if (appData.debugMode)
17673 fprintf(debugFP, "Invalid continuation sequence \"%s\" (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
17678 reformat a source message so words don't cross the width boundary. internal
17679 newlines are not removed. returns the wrapped size (no null character unless
17680 included in source message). If dest is NULL, only calculate the size required
17681 for the dest buffer. lp argument indicats line position upon entry, and it's
17682 passed back upon exit.
17685 wrap (char *dest, char *src, int count, int width, int *lp)
17687 int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
17689 cseq_len = strlen(cseq);
17690 old_line = line = *lp;
17691 ansi = len = clen = 0;
17693 for (i=0; i < count; i++)
17695 if (src[i] == '\033')
17698 // if we hit the width, back up
17699 if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
17701 // store i & len in case the word is too long
17702 old_i = i, old_len = len;
17704 // find the end of the last word
17705 while (i && src[i] != ' ' && src[i] != '\n')
17711 // word too long? restore i & len before splitting it
17712 if ((old_i-i+clen) >= width)
17719 if (i && src[i-1] == ' ')
17722 if (src[i] != ' ' && src[i] != '\n')
17729 // now append the newline and continuation sequence
17734 strncpy(dest+len, cseq, cseq_len);
17742 dest[len] = src[i];
17746 if (src[i] == '\n')
17751 if (dest && appData.debugMode)
17753 fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
17754 count, width, line, len, *lp);
17755 show_bytes(debugFP, src, count);
17756 fprintf(debugFP, "\ndest: ");
17757 show_bytes(debugFP, dest, len);
17758 fprintf(debugFP, "\n");
17760 *lp = dest ? line : old_line;
17765 // [HGM] vari: routines for shelving variations
17766 Boolean modeRestore = FALSE;
17769 PushInner (int firstMove, int lastMove)
17771 int i, j, nrMoves = lastMove - firstMove;
17773 // push current tail of game on stack
17774 savedResult[storedGames] = gameInfo.result;
17775 savedDetails[storedGames] = gameInfo.resultDetails;
17776 gameInfo.resultDetails = NULL;
17777 savedFirst[storedGames] = firstMove;
17778 savedLast [storedGames] = lastMove;
17779 savedFramePtr[storedGames] = framePtr;
17780 framePtr -= nrMoves; // reserve space for the boards
17781 for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
17782 CopyBoard(boards[framePtr+i], boards[firstMove+i]);
17783 for(j=0; j<MOVE_LEN; j++)
17784 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
17785 for(j=0; j<2*MOVE_LEN; j++)
17786 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
17787 timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
17788 timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
17789 pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
17790 pvInfoList[firstMove+i-1].depth = 0;
17791 commentList[framePtr+i] = commentList[firstMove+i];
17792 commentList[firstMove+i] = NULL;
17796 forwardMostMove = firstMove; // truncate game so we can start variation
17800 PushTail (int firstMove, int lastMove)
17802 if(appData.icsActive) { // only in local mode
17803 forwardMostMove = currentMove; // mimic old ICS behavior
17806 if(storedGames >= MAX_VARIATIONS-2) return; // leave one for PV-walk
17808 PushInner(firstMove, lastMove);
17809 if(storedGames == 1) GreyRevert(FALSE);
17810 if(gameMode == PlayFromGameFile) gameMode = EditGame, modeRestore = TRUE;
17814 PopInner (Boolean annotate)
17817 char buf[8000], moveBuf[20];
17819 ToNrEvent(savedFirst[storedGames-1]); // sets currentMove
17820 storedGames--; // do this after ToNrEvent, to make sure HistorySet will refresh entire game after PopInner returns
17821 nrMoves = savedLast[storedGames] - currentMove;
17824 if(!WhiteOnMove(currentMove))
17825 snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
17826 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
17827 for(i=currentMove; i<forwardMostMove; i++) {
17829 snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
17830 else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
17831 strcat(buf, moveBuf);
17832 if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
17833 if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
17837 for(i=1; i<=nrMoves; i++) { // copy last variation back
17838 CopyBoard(boards[currentMove+i], boards[framePtr+i]);
17839 for(j=0; j<MOVE_LEN; j++)
17840 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
17841 for(j=0; j<2*MOVE_LEN; j++)
17842 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
17843 timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
17844 timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
17845 pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
17846 if(commentList[currentMove+i]) free(commentList[currentMove+i]);
17847 commentList[currentMove+i] = commentList[framePtr+i];
17848 commentList[framePtr+i] = NULL;
17850 if(annotate) AppendComment(currentMove+1, buf, FALSE);
17851 framePtr = savedFramePtr[storedGames];
17852 gameInfo.result = savedResult[storedGames];
17853 if(gameInfo.resultDetails != NULL) {
17854 free(gameInfo.resultDetails);
17856 gameInfo.resultDetails = savedDetails[storedGames];
17857 forwardMostMove = currentMove + nrMoves;
17861 PopTail (Boolean annotate)
17863 if(appData.icsActive) return FALSE; // only in local mode
17864 if(!storedGames) return FALSE; // sanity
17865 CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
17867 PopInner(annotate);
17868 if(currentMove < forwardMostMove) ForwardEvent(); else
17869 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
17871 if(storedGames == 0) { GreyRevert(TRUE); if(modeRestore) modeRestore = FALSE, gameMode = PlayFromGameFile; }
17877 { // remove all shelved variations
17879 for(i=0; i<storedGames; i++) {
17880 if(savedDetails[i])
17881 free(savedDetails[i]);
17882 savedDetails[i] = NULL;
17884 for(i=framePtr; i<MAX_MOVES; i++) {
17885 if(commentList[i]) free(commentList[i]);
17886 commentList[i] = NULL;
17888 framePtr = MAX_MOVES-1;
17893 LoadVariation (int index, char *text)
17894 { // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
17895 char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
17896 int level = 0, move;
17898 if(gameMode != EditGame && gameMode != AnalyzeMode && gameMode != PlayFromGameFile) return;
17899 // first find outermost bracketing variation
17900 while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
17901 if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
17902 if(*p == '{') wait = '}'; else
17903 if(*p == '[') wait = ']'; else
17904 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
17905 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
17907 if(*p == wait) wait = NULLCHAR; // closing ]} found
17910 if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
17911 if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
17912 end[1] = NULLCHAR; // clip off comment beyond variation
17913 ToNrEvent(currentMove-1);
17914 PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
17915 // kludge: use ParsePV() to append variation to game
17916 move = currentMove;
17917 ParsePV(start, TRUE, TRUE);
17918 forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
17919 ClearPremoveHighlights();
17921 ToNrEvent(currentMove+1);
17927 char *p, *q, buf[MSG_SIZ];
17928 if(engineLine && engineLine[0]) { // a theme was selected from the listbox
17929 snprintf(buf, MSG_SIZ, "-theme %s", engineLine);
17930 ParseArgsFromString(buf);
17931 ActivateTheme(TRUE); // also redo colors
17935 if(*p && !strchr(p, '"')) // theme name specified and well-formed; add settings to theme list
17938 q = appData.themeNames;
17939 snprintf(buf, MSG_SIZ, "\"%s\"", nickName);
17940 if(appData.useBitmaps) {
17941 snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt true -lbtf \"%s\" -dbtf \"%s\" -lbtm %d -dbtm %d",
17942 appData.liteBackTextureFile, appData.darkBackTextureFile,
17943 appData.liteBackTextureMode,
17944 appData.darkBackTextureMode );
17946 snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt false -lsc %s -dsc %s",
17947 Col2Text(2), // lightSquareColor
17948 Col2Text(3) ); // darkSquareColor
17950 if(appData.useBorder) {
17951 snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub true -border \"%s\"",
17954 snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub false");
17956 if(appData.useFont) {
17957 snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf true -pf \"%s\" -fptc \"%s\" -fpfcw %s -fpbcb %s",
17958 appData.renderPiecesWithFont,
17959 appData.fontToPieceTable,
17960 Col2Text(9), // appData.fontBackColorWhite
17961 Col2Text(10) ); // appData.fontForeColorBlack
17963 snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf false -pid \"%s\"",
17964 appData.pieceDirectory);
17965 if(!appData.pieceDirectory[0])
17966 snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -wpc %s -bpc %s",
17967 Col2Text(0), // whitePieceColor
17968 Col2Text(1) ); // blackPieceColor
17970 snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -hsc %s -phc %s\n",
17971 Col2Text(4), // highlightSquareColor
17972 Col2Text(5) ); // premoveHighlightColor
17973 appData.themeNames = malloc(len = strlen(q) + strlen(buf) + 1);
17974 if(insert != q) insert[-1] = NULLCHAR;
17975 snprintf(appData.themeNames, len, "%s\n%s%s", q, buf, insert);
17978 ActivateTheme(FALSE);