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 lastMsg[MSG_SIZ];
273 ChessSquare pieceSweep = EmptySquare;
274 ChessSquare promoSweep = EmptySquare, defaultPromoChoice;
275 int promoDefaultAltered;
276 int keepInfo = 0; /* [HGM] to protect PGN tags in auto-step game analysis */
278 /* States for ics_getting_history */
280 #define H_REQUESTED 1
281 #define H_GOT_REQ_HEADER 2
282 #define H_GOT_UNREQ_HEADER 3
283 #define H_GETTING_MOVES 4
284 #define H_GOT_UNWANTED_HEADER 5
286 /* whosays values for GameEnds */
295 /* Maximum number of games in a cmail message */
296 #define CMAIL_MAX_GAMES 20
298 /* Different types of move when calling RegisterMove */
300 #define CMAIL_RESIGN 1
302 #define CMAIL_ACCEPT 3
304 /* Different types of result to remember for each game */
305 #define CMAIL_NOT_RESULT 0
306 #define CMAIL_OLD_RESULT 1
307 #define CMAIL_NEW_RESULT 2
309 /* Telnet protocol constants */
320 safeStrCpy (char *dst, const char *src, size_t count)
323 assert( dst != NULL );
324 assert( src != NULL );
327 for(i=0; i<count; i++) if((dst[i] = src[i]) == NULLCHAR) break;
328 if( i == count && dst[count-1] != NULLCHAR)
330 dst[ count-1 ] = '\0'; // make sure incomplete copy still null-terminated
331 if(appData.debugMode)
332 fprintf(debugFP, "safeStrCpy: copying %s into %s didn't work, not enough space %d\n",src,dst, (int)count);
338 /* Some compiler can't cast u64 to double
339 * This function do the job for us:
341 * We use the highest bit for cast, this only
342 * works if the highest bit is not
343 * in use (This should not happen)
345 * We used this for all compiler
348 u64ToDouble (u64 value)
351 u64 tmp = value & u64Const(0x7fffffffffffffff);
352 r = (double)(s64)tmp;
353 if (value & u64Const(0x8000000000000000))
354 r += 9.2233720368547758080e18; /* 2^63 */
358 /* Fake up flags for now, as we aren't keeping track of castling
359 availability yet. [HGM] Change of logic: the flag now only
360 indicates the type of castlings allowed by the rule of the game.
361 The actual rights themselves are maintained in the array
362 castlingRights, as part of the game history, and are not probed
368 int flags = F_ALL_CASTLE_OK;
369 if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
370 switch (gameInfo.variant) {
372 flags &= ~F_ALL_CASTLE_OK;
373 case VariantGiveaway: // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
374 flags |= F_IGNORE_CHECK;
376 flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
379 flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
381 case VariantKriegspiel:
382 flags |= F_KRIEGSPIEL_CAPTURE;
384 case VariantCapaRandom:
385 case VariantFischeRandom:
386 flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
387 case VariantNoCastle:
388 case VariantShatranj:
393 flags &= ~F_ALL_CASTLE_OK;
401 FILE *gameFileFP, *debugFP, *serverFP;
402 char *currentDebugFile; // [HGM] debug split: to remember name
405 [AS] Note: sometimes, the sscanf() function is used to parse the input
406 into a fixed-size buffer. Because of this, we must be prepared to
407 receive strings as long as the size of the input buffer, which is currently
408 set to 4K for Windows and 8K for the rest.
409 So, we must either allocate sufficiently large buffers here, or
410 reduce the size of the input buffer in the input reading part.
413 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
414 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
415 char thinkOutput1[MSG_SIZ*10];
417 ChessProgramState first, second, pairing;
419 /* premove variables */
422 int premoveFromX = 0;
423 int premoveFromY = 0;
424 int premovePromoChar = 0;
426 Boolean alarmSounded;
427 /* end premove variables */
429 char *ics_prefix = "$";
430 enum ICS_TYPE ics_type = ICS_GENERIC;
432 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
433 int pauseExamForwardMostMove = 0;
434 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
435 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
436 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
437 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
438 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
439 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
440 int whiteFlag = FALSE, blackFlag = FALSE;
441 int userOfferedDraw = FALSE;
442 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
443 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
444 int cmailMoveType[CMAIL_MAX_GAMES];
445 long ics_clock_paused = 0;
446 ProcRef icsPR = NoProc, cmailPR = NoProc;
447 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
448 GameMode gameMode = BeginningOfGame;
449 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
450 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
451 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
452 int hiddenThinkOutputState = 0; /* [AS] */
453 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
454 int adjudicateLossPlies = 6;
455 char white_holding[64], black_holding[64];
456 TimeMark lastNodeCountTime;
457 long lastNodeCount=0;
458 int shiftKey, controlKey; // [HGM] set by mouse handler
460 int have_sent_ICS_logon = 0;
462 int suddenDeath, whiteStartMove, blackStartMove; /* [HGM] for implementation of 'any per time' sessions, as in first part of byoyomi TC */
463 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement, lastWhite, lastBlack, activePartnerTime;
464 Boolean adjustedClock;
465 long timeControl_2; /* [AS] Allow separate time controls */
466 char *fullTimeControlString = NULL, *nextSession, *whiteTC, *blackTC, activePartner; /* [HGM] secondary TC: merge of MPS, TC and inc */
467 long timeRemaining[2][MAX_MOVES];
468 int matchGame = 0, nextGame = 0, roundNr = 0;
469 Boolean waitingForGame = FALSE, startingEngine = FALSE;
470 TimeMark programStartTime, pauseStart;
471 char ics_handle[MSG_SIZ];
472 int have_set_title = 0;
474 /* animateTraining preserves the state of appData.animate
475 * when Training mode is activated. This allows the
476 * response to be animated when appData.animate == TRUE and
477 * appData.animateDragging == TRUE.
479 Boolean animateTraining;
485 Board boards[MAX_MOVES];
486 /* [HGM] Following 7 needed for accurate legality tests: */
487 signed char castlingRank[BOARD_FILES]; // and corresponding ranks
488 signed char initialRights[BOARD_FILES];
489 int nrCastlingRights; // For TwoKings, or to implement castling-unknown status
490 int initialRulePlies, FENrulePlies;
491 FILE *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
493 Boolean shuffleOpenings;
494 int mute; // mute all sounds
496 // [HGM] vari: next 12 to save and restore variations
497 #define MAX_VARIATIONS 10
498 int framePtr = MAX_MOVES-1; // points to free stack entry
500 int savedFirst[MAX_VARIATIONS];
501 int savedLast[MAX_VARIATIONS];
502 int savedFramePtr[MAX_VARIATIONS];
503 char *savedDetails[MAX_VARIATIONS];
504 ChessMove savedResult[MAX_VARIATIONS];
506 void PushTail P((int firstMove, int lastMove));
507 Boolean PopTail P((Boolean annotate));
508 void PushInner P((int firstMove, int lastMove));
509 void PopInner P((Boolean annotate));
510 void CleanupTail P((void));
512 ChessSquare FIDEArray[2][BOARD_FILES] = {
513 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
514 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
515 { BlackRook, BlackKnight, BlackBishop, BlackQueen,
516 BlackKing, BlackBishop, BlackKnight, BlackRook }
519 ChessSquare twoKingsArray[2][BOARD_FILES] = {
520 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
521 WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
522 { BlackRook, BlackKnight, BlackBishop, BlackQueen,
523 BlackKing, BlackKing, BlackKnight, BlackRook }
526 ChessSquare KnightmateArray[2][BOARD_FILES] = {
527 { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
528 WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
529 { BlackRook, BlackMan, BlackBishop, BlackQueen,
530 BlackUnicorn, BlackBishop, BlackMan, BlackRook }
533 ChessSquare SpartanArray[2][BOARD_FILES] = {
534 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
535 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
536 { BlackAlfil, BlackMarshall, BlackKing, BlackDragon,
537 BlackDragon, BlackKing, BlackAngel, BlackAlfil }
540 ChessSquare fairyArray[2][BOARD_FILES] = { /* [HGM] Queen side differs from King side */
541 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
542 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
543 { BlackCardinal, BlackAlfil, BlackMarshall, BlackAngel,
544 BlackKing, BlackMarshall, BlackAlfil, BlackCardinal }
547 ChessSquare ShatranjArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
548 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
549 WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
550 { BlackRook, BlackKnight, BlackAlfil, BlackKing,
551 BlackFerz, BlackAlfil, BlackKnight, BlackRook }
554 ChessSquare makrukArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
555 { WhiteRook, WhiteKnight, WhiteMan, WhiteKing,
556 WhiteFerz, WhiteMan, WhiteKnight, WhiteRook },
557 { BlackRook, BlackKnight, BlackMan, BlackFerz,
558 BlackKing, BlackMan, BlackKnight, BlackRook }
561 ChessSquare aseanArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
562 { WhiteRook, WhiteKnight, WhiteMan, WhiteFerz,
563 WhiteKing, WhiteMan, WhiteKnight, WhiteRook },
564 { BlackRook, BlackKnight, BlackMan, BlackFerz,
565 BlackKing, BlackMan, BlackKnight, BlackRook }
569 #if (BOARD_FILES>=10)
570 ChessSquare ShogiArray[2][BOARD_FILES] = {
571 { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
572 WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
573 { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
574 BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
577 ChessSquare XiangqiArray[2][BOARD_FILES] = {
578 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
579 WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
580 { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
581 BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
584 ChessSquare CapablancaArray[2][BOARD_FILES] = {
585 { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
586 WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
587 { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
588 BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
591 ChessSquare GreatArray[2][BOARD_FILES] = {
592 { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
593 WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
594 { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
595 BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
598 ChessSquare JanusArray[2][BOARD_FILES] = {
599 { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
600 WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
601 { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
602 BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
605 ChessSquare GrandArray[2][BOARD_FILES] = {
606 { EmptySquare, WhiteKnight, WhiteBishop, WhiteQueen, WhiteKing,
607 WhiteMarshall, WhiteAngel, WhiteBishop, WhiteKnight, EmptySquare },
608 { EmptySquare, BlackKnight, BlackBishop, BlackQueen, BlackKing,
609 BlackMarshall, BlackAngel, BlackBishop, BlackKnight, EmptySquare }
613 ChessSquare GothicArray[2][BOARD_FILES] = {
614 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
615 WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
616 { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
617 BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
620 #define GothicArray CapablancaArray
624 ChessSquare FalconArray[2][BOARD_FILES] = {
625 { WhiteRook, WhiteKnight, WhiteBishop, WhiteFalcon, WhiteQueen,
626 WhiteKing, WhiteFalcon, WhiteBishop, WhiteKnight, WhiteRook },
627 { BlackRook, BlackKnight, BlackBishop, BlackFalcon, BlackQueen,
628 BlackKing, BlackFalcon, BlackBishop, BlackKnight, BlackRook }
631 #define FalconArray CapablancaArray
634 #else // !(BOARD_FILES>=10)
635 #define XiangqiPosition FIDEArray
636 #define CapablancaArray FIDEArray
637 #define GothicArray FIDEArray
638 #define GreatArray FIDEArray
639 #endif // !(BOARD_FILES>=10)
641 #if (BOARD_FILES>=12)
642 ChessSquare CourierArray[2][BOARD_FILES] = {
643 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
644 WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
645 { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
646 BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
648 #else // !(BOARD_FILES>=12)
649 #define CourierArray CapablancaArray
650 #endif // !(BOARD_FILES>=12)
653 Board initialPosition;
656 /* Convert str to a rating. Checks for special cases of "----",
658 "++++", etc. Also strips ()'s */
660 string_to_rating (char *str)
662 while(*str && !isdigit(*str)) ++str;
664 return 0; /* One of the special "no rating" cases */
672 /* Init programStats */
673 programStats.movelist[0] = 0;
674 programStats.depth = 0;
675 programStats.nr_moves = 0;
676 programStats.moves_left = 0;
677 programStats.nodes = 0;
678 programStats.time = -1; // [HGM] PGNtime: make invalid to recognize engine output
679 programStats.score = 0;
680 programStats.got_only_move = 0;
681 programStats.got_fail = 0;
682 programStats.line_is_book = 0;
687 { // [HGM] moved some code here from InitBackend1 that has to be done after both engines have contributed their settings
688 if (appData.firstPlaysBlack) {
689 first.twoMachinesColor = "black\n";
690 second.twoMachinesColor = "white\n";
692 first.twoMachinesColor = "white\n";
693 second.twoMachinesColor = "black\n";
696 first.other = &second;
697 second.other = &first;
700 if(appData.timeOddsMode) {
701 norm = appData.timeOdds[0];
702 if(norm > appData.timeOdds[1]) norm = appData.timeOdds[1];
704 first.timeOdds = appData.timeOdds[0]/norm;
705 second.timeOdds = appData.timeOdds[1]/norm;
708 if(programVersion) free(programVersion);
709 if (appData.noChessProgram) {
710 programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
711 sprintf(programVersion, "%s", PACKAGE_STRING);
713 /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
714 programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
715 sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
720 UnloadEngine (ChessProgramState *cps)
722 /* Kill off first chess program */
723 if (cps->isr != NULL)
724 RemoveInputSource(cps->isr);
727 if (cps->pr != NoProc) {
729 DoSleep( appData.delayBeforeQuit );
730 SendToProgram("quit\n", cps);
731 DoSleep( appData.delayAfterQuit );
732 DestroyChildProcess(cps->pr, cps->useSigterm);
735 if(appData.debugMode) fprintf(debugFP, "Unload %s\n", cps->which);
739 ClearOptions (ChessProgramState *cps)
742 cps->nrOptions = cps->comboCnt = 0;
743 for(i=0; i<MAX_OPTIONS; i++) {
744 cps->option[i].min = cps->option[i].max = cps->option[i].value = 0;
745 cps->option[i].textValue = 0;
749 char *engineNames[] = {
750 /* TRANSLATORS: "first" is the first of possible two chess engines. It is inserted into strings
751 such as "%s engine" / "%s chess program" / "%s machine" - all meaning the same thing */
753 /* TRANSLATORS: "second" is the second of possible two chess engines. It is inserted into strings
754 such as "%s engine" / "%s chess program" / "%s machine" - all meaning the same thing */
759 InitEngine (ChessProgramState *cps, int n)
760 { // [HGM] all engine initialiation put in a function that does one engine
764 cps->which = engineNames[n];
765 cps->maybeThinking = FALSE;
769 cps->sendDrawOffers = 1;
771 cps->program = appData.chessProgram[n];
772 cps->host = appData.host[n];
773 cps->dir = appData.directory[n];
774 cps->initString = appData.engInitString[n];
775 cps->computerString = appData.computerString[n];
776 cps->useSigint = TRUE;
777 cps->useSigterm = TRUE;
778 cps->reuse = appData.reuse[n];
779 cps->nps = appData.NPS[n]; // [HGM] nps: copy nodes per second
780 cps->useSetboard = FALSE;
782 cps->usePing = FALSE;
785 cps->usePlayother = FALSE;
786 cps->useColors = TRUE;
787 cps->useUsermove = FALSE;
788 cps->sendICS = FALSE;
789 cps->sendName = appData.icsActive;
790 cps->sdKludge = FALSE;
791 cps->stKludge = FALSE;
792 if(cps->tidy == NULL) cps->tidy = (char*) malloc(MSG_SIZ);
793 TidyProgramName(cps->program, cps->host, cps->tidy);
795 ASSIGN(cps->variants, appData.variant);
796 cps->analysisSupport = 2; /* detect */
797 cps->analyzing = FALSE;
798 cps->initDone = FALSE;
801 /* New features added by Tord: */
802 cps->useFEN960 = FALSE;
803 cps->useOOCastle = TRUE;
804 /* End of new features added by Tord. */
805 cps->fenOverride = appData.fenOverride[n];
807 /* [HGM] time odds: set factor for each machine */
808 cps->timeOdds = appData.timeOdds[n];
810 /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
811 cps->accumulateTC = appData.accumulateTC[n];
812 cps->maxNrOfSessions = 1;
817 cps->supportsNPS = UNKNOWN;
818 cps->memSize = FALSE;
819 cps->maxCores = FALSE;
820 ASSIGN(cps->egtFormats, "");
823 cps->optionSettings = appData.engOptions[n];
825 cps->scoreIsAbsolute = appData.scoreIsAbsolute[n]; /* [AS] */
826 cps->isUCI = appData.isUCI[n]; /* [AS] */
827 cps->hasOwnBookUCI = appData.hasOwnBookUCI[n]; /* [AS] */
829 if (appData.protocolVersion[n] > PROTOVER
830 || appData.protocolVersion[n] < 1)
835 len = snprintf(buf, MSG_SIZ, _("protocol version %d not supported"),
836 appData.protocolVersion[n]);
837 if( (len >= MSG_SIZ) && appData.debugMode )
838 fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
840 DisplayFatalError(buf, 0, 2);
844 cps->protocolVersion = appData.protocolVersion[n];
847 InitEngineUCI( installDir, cps ); // [HGM] moved here from winboard.c, to make available in xboard
848 ParseFeatures(appData.featureDefaults, cps);
851 ChessProgramState *savCps;
859 if(WaitForEngine(savCps, LoadEngine)) return;
860 CommonEngineInit(); // recalculate time odds
861 if(gameInfo.variant != StringToVariant(appData.variant)) {
862 // we changed variant when loading the engine; this forces us to reset
863 Reset(TRUE, savCps != &first);
864 oldMode = BeginningOfGame; // to prevent restoring old mode
866 InitChessProgram(savCps, FALSE);
867 if(gameMode == EditGame) SendToProgram("force\n", savCps); // in EditGame mode engine must be in force mode
868 DisplayMessage("", "");
869 if (startedFromSetupPosition) SendBoard(savCps, backwardMostMove);
870 for (i = backwardMostMove; i < currentMove; i++) SendMoveToProgram(i, savCps);
873 if(oldMode == AnalyzeMode) AnalyzeModeEvent();
877 ReplaceEngine (ChessProgramState *cps, int n)
879 oldMode = gameMode; // remember mode, so it can be restored after loading sequence is complete
881 if(oldMode != BeginningOfGame) EditGameEvent();
884 appData.noChessProgram = FALSE;
885 appData.clockMode = TRUE;
888 if(n) return; // only startup first engine immediately; second can wait
889 savCps = cps; // parameter to LoadEngine passed as globals, to allow scheduled calling :-(
893 extern char *engineName, *engineDir, *engineChoice, *engineLine, *nickName, *params;
894 extern Boolean isUCI, hasBook, storeVariant, v1, addToList, useNick;
896 static char resetOptions[] =
897 "-reuse -firstIsUCI false -firstHasOwnBookUCI true -firstTimeOdds 1 "
898 "-firstInitString \"" INIT_STRING "\" -firstComputerString \"" COMPUTER_STRING "\" "
899 "-firstFeatures \"\" -firstLogo \"\" -firstAccumulateTC 1 "
900 "-firstOptions \"\" -firstNPS -1 -fn \"\" -firstScoreAbs false";
903 FloatToFront(char **list, char *engineLine)
905 char buf[MSG_SIZ], tidy[MSG_SIZ], *p = buf, *q, *r = buf;
907 if(appData.recentEngines <= 0) return;
908 TidyProgramName(engineLine, "localhost", tidy+1);
909 tidy[0] = buf[0] = '\n'; strcat(tidy, "\n");
910 strncpy(buf+1, *list, MSG_SIZ-50);
911 if(p = strstr(buf, tidy)) { // tidy name appears in list
912 q = strchr(++p, '\n'); if(q == NULL) return; // malformed, don't touch
913 while(*p++ = *++q); // squeeze out
915 strcat(tidy, buf+1); // put list behind tidy name
916 p = tidy + 1; while(q = strchr(p, '\n')) i++, r = p, p = q + 1; // count entries in new list
917 if(i > appData.recentEngines) *r = NULLCHAR; // if maximum rached, strip off last
918 ASSIGN(*list, tidy+1);
921 char *insert, *wbOptions; // point in ChessProgramNames were we should insert new engine
924 Load (ChessProgramState *cps, int i)
926 char *p, *q, buf[MSG_SIZ], command[MSG_SIZ], buf2[MSG_SIZ];
927 if(engineLine && engineLine[0]) { // an engine was selected from the combo box
928 snprintf(buf, MSG_SIZ, "-fcp %s", engineLine);
929 SwapEngines(i); // kludge to parse -f* / -first* like it is -s* / -second*
930 ParseArgsFromString(resetOptions); appData.pvSAN[0] = FALSE;
931 FREE(appData.fenOverride[0]); appData.fenOverride[0] = NULL;
932 appData.firstProtocolVersion = PROTOVER;
933 ParseArgsFromString(buf);
935 ReplaceEngine(cps, i);
936 FloatToFront(&appData.recentEngineList, engineLine);
940 while(q = strchr(p, SLASH)) p = q+1;
941 if(*p== NULLCHAR) { DisplayError(_("You did not specify the engine executable"), 0); return; }
942 if(engineDir[0] != NULLCHAR) {
943 ASSIGN(appData.directory[i], engineDir); p = engineName;
944 } else if(p != engineName) { // derive directory from engine path, when not given
946 ASSIGN(appData.directory[i], engineName);
948 if(SLASH == '/' && p - engineName > 1) *(p -= 2) = '.'; // for XBoard use ./exeName as command after split!
949 } else { ASSIGN(appData.directory[i], "."); }
951 if(strchr(p, ' ') && !strchr(p, '"')) snprintf(buf2, MSG_SIZ, "\"%s\"", p), p = buf2; // quote if it contains spaces
952 snprintf(command, MSG_SIZ, "%s %s", p, params);
955 ASSIGN(appData.chessProgram[i], p);
956 appData.isUCI[i] = isUCI;
957 appData.protocolVersion[i] = v1 ? 1 : PROTOVER;
958 appData.hasOwnBookUCI[i] = hasBook;
959 if(!nickName[0]) useNick = FALSE;
960 if(useNick) ASSIGN(appData.pgnName[i], nickName);
964 q = firstChessProgramNames;
965 if(nickName[0]) snprintf(buf, MSG_SIZ, "\"%s\" -fcp ", nickName); else buf[0] = NULLCHAR;
966 quote = strchr(p, '"') ? '\'' : '"'; // use single quotes around engine command if it contains double quotes
967 snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), "%c%s%c -fd \"%s\"%s%s%s%s%s%s%s%s\n",
968 quote, p, quote, appData.directory[i],
969 useNick ? " -fn \"" : "",
970 useNick ? nickName : "",
972 v1 ? " -firstProtocolVersion 1" : "",
973 hasBook ? "" : " -fNoOwnBookUCI",
974 isUCI ? (isUCI == TRUE ? " -fUCI" : gameInfo.variant == VariantShogi ? " -fUSI" : " -fUCCI") : "",
975 storeVariant ? " -variant " : "",
976 storeVariant ? VariantName(gameInfo.variant) : "");
977 if(wbOptions && wbOptions[0]) snprintf(buf+strlen(buf)-1, MSG_SIZ-strlen(buf), " %s\n", wbOptions);
978 firstChessProgramNames = malloc(len = strlen(q) + strlen(buf) + 1);
979 if(insert != q) insert[-1] = NULLCHAR;
980 snprintf(firstChessProgramNames, len, "%s\n%s%s", q, buf, insert);
982 FloatToFront(&appData.recentEngineList, buf);
984 ReplaceEngine(cps, i);
990 int matched, min, sec;
992 * Parse timeControl resource
994 if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
995 appData.movesPerSession)) {
997 snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
998 DisplayFatalError(buf, 0, 2);
1002 * Parse searchTime resource
1004 if (*appData.searchTime != NULLCHAR) {
1005 matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
1007 searchTime = min * 60;
1008 } else if (matched == 2) {
1009 searchTime = min * 60 + sec;
1012 snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
1013 DisplayFatalError(buf, 0, 2);
1022 ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
1023 startVariant = StringToVariant(appData.variant); // [HGM] nicks: remember original variant
1025 GetTimeMark(&programStartTime);
1026 srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
1027 appData.seedBase = random() + (random()<<15);
1028 pauseStart = programStartTime; pauseStart.sec -= 100; // [HGM] matchpause: fake a pause that has long since ended
1030 ClearProgramStats();
1031 programStats.ok_to_send = 1;
1032 programStats.seen_stat = 0;
1035 * Initialize game list
1041 * Internet chess server status
1043 if (appData.icsActive) {
1044 appData.matchMode = FALSE;
1045 appData.matchGames = 0;
1047 appData.noChessProgram = !appData.zippyPlay;
1049 appData.zippyPlay = FALSE;
1050 appData.zippyTalk = FALSE;
1051 appData.noChessProgram = TRUE;
1053 if (*appData.icsHelper != NULLCHAR) {
1054 appData.useTelnet = TRUE;
1055 appData.telnetProgram = appData.icsHelper;
1058 appData.zippyTalk = appData.zippyPlay = FALSE;
1061 /* [AS] Initialize pv info list [HGM] and game state */
1065 for( i=0; i<=framePtr; i++ ) {
1066 pvInfoList[i].depth = -1;
1067 boards[i][EP_STATUS] = EP_NONE;
1068 for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
1074 /* [AS] Adjudication threshold */
1075 adjudicateLossThreshold = appData.adjudicateLossThreshold;
1077 InitEngine(&first, 0);
1078 InitEngine(&second, 1);
1081 pairing.which = "pairing"; // pairing engine
1082 pairing.pr = NoProc;
1084 pairing.program = appData.pairingEngine;
1085 pairing.host = "localhost";
1088 if (appData.icsActive) {
1089 appData.clockMode = TRUE; /* changes dynamically in ICS mode */
1090 } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
1091 appData.clockMode = FALSE;
1092 first.sendTime = second.sendTime = 0;
1096 /* Override some settings from environment variables, for backward
1097 compatibility. Unfortunately it's not feasible to have the env
1098 vars just set defaults, at least in xboard. Ugh.
1100 if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
1105 if (!appData.icsActive) {
1109 /* Check for variants that are supported only in ICS mode,
1110 or not at all. Some that are accepted here nevertheless
1111 have bugs; see comments below.
1113 VariantClass variant = StringToVariant(appData.variant);
1115 case VariantBughouse: /* need four players and two boards */
1116 case VariantKriegspiel: /* need to hide pieces and move details */
1117 /* case VariantFischeRandom: (Fabien: moved below) */
1118 len = snprintf(buf,MSG_SIZ, _("Variant %s supported only in ICS mode"), appData.variant);
1119 if( (len >= MSG_SIZ) && appData.debugMode )
1120 fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1122 DisplayFatalError(buf, 0, 2);
1125 case VariantUnknown:
1126 case VariantLoadable:
1136 len = snprintf(buf, MSG_SIZ, _("Unknown variant name %s"), appData.variant);
1137 if( (len >= MSG_SIZ) && appData.debugMode )
1138 fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1140 DisplayFatalError(buf, 0, 2);
1143 case VariantXiangqi: /* [HGM] repetition rules not implemented */
1144 case VariantFairy: /* [HGM] TestLegality definitely off! */
1145 case VariantGothic: /* [HGM] should work */
1146 case VariantCapablanca: /* [HGM] should work */
1147 case VariantCourier: /* [HGM] initial forced moves not implemented */
1148 case VariantShogi: /* [HGM] could still mate with pawn drop */
1149 case VariantKnightmate: /* [HGM] should work */
1150 case VariantCylinder: /* [HGM] untested */
1151 case VariantFalcon: /* [HGM] untested */
1152 case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
1153 offboard interposition not understood */
1154 case VariantNormal: /* definitely works! */
1155 case VariantWildCastle: /* pieces not automatically shuffled */
1156 case VariantNoCastle: /* pieces not automatically shuffled */
1157 case VariantFischeRandom: /* [HGM] works and shuffles pieces */
1158 case VariantLosers: /* should work except for win condition,
1159 and doesn't know captures are mandatory */
1160 case VariantSuicide: /* should work except for win condition,
1161 and doesn't know captures are mandatory */
1162 case VariantGiveaway: /* should work except for win condition,
1163 and doesn't know captures are mandatory */
1164 case VariantTwoKings: /* should work */
1165 case VariantAtomic: /* should work except for win condition */
1166 case Variant3Check: /* should work except for win condition */
1167 case VariantShatranj: /* should work except for all win conditions */
1168 case VariantMakruk: /* should work except for draw countdown */
1169 case VariantASEAN : /* should work except for draw countdown */
1170 case VariantBerolina: /* might work if TestLegality is off */
1171 case VariantCapaRandom: /* should work */
1172 case VariantJanus: /* should work */
1173 case VariantSuper: /* experimental */
1174 case VariantGreat: /* experimental, requires legality testing to be off */
1175 case VariantSChess: /* S-Chess, should work */
1176 case VariantGrand: /* should work */
1177 case VariantSpartan: /* should work */
1185 NextIntegerFromString (char ** str, long * value)
1190 while( *s == ' ' || *s == '\t' ) {
1196 if( *s >= '0' && *s <= '9' ) {
1197 while( *s >= '0' && *s <= '9' ) {
1198 *value = *value * 10 + (*s - '0');
1211 NextTimeControlFromString (char ** str, long * value)
1214 int result = NextIntegerFromString( str, &temp );
1217 *value = temp * 60; /* Minutes */
1218 if( **str == ':' ) {
1220 result = NextIntegerFromString( str, &temp );
1221 *value += temp; /* Seconds */
1229 NextSessionFromString (char ** str, int *moves, long * tc, long *inc, int *incType)
1230 { /* [HGM] routine added to read '+moves/time' for secondary time control. */
1231 int result = -1, type = 0; long temp, temp2;
1233 if(**str != ':') return -1; // old params remain in force!
1235 if(**str == '*') type = *(*str)++, temp = 0; // sandclock TC
1236 if( NextIntegerFromString( str, &temp ) ) return -1;
1237 if(type) { *moves = 0; *tc = temp * 500; *inc = temp * 1000; *incType = '*'; return 0; }
1240 /* time only: incremental or sudden-death time control */
1241 if(**str == '+') { /* increment follows; read it */
1243 if(**str == '!') type = *(*str)++; // Bronstein TC
1244 if(result = NextIntegerFromString( str, &temp2)) return -1;
1245 *inc = temp2 * 1000;
1246 if(**str == '.') { // read fraction of increment
1247 char *start = ++(*str);
1248 if(result = NextIntegerFromString( str, &temp2)) return -1;
1250 while(start++ < *str) temp2 /= 10;
1254 *moves = 0; *tc = temp * 1000; *incType = type;
1258 (*str)++; /* classical time control */
1259 result = NextIntegerFromString( str, &temp2); // NOTE: already converted to seconds by ParseTimeControl()
1271 GetTimeQuota (int movenr, int lastUsed, char *tcString)
1272 { /* [HGM] get time to add from the multi-session time-control string */
1273 int incType, moves=1; /* kludge to force reading of first session */
1274 long time, increment;
1277 if(!s || !*s) return 0; // empty TC string means we ran out of the last sudden-death version
1279 if(moves) NextSessionFromString(&s, &moves, &time, &increment, &incType);
1280 nextSession = s; suddenDeath = moves == 0 && increment == 0;
1281 if(movenr == -1) return time; /* last move before new session */
1282 if(incType == '*') increment = 0; else // for sandclock, time is added while not thinking
1283 if(incType == '!' && lastUsed < increment) increment = lastUsed;
1284 if(!moves) return increment; /* current session is incremental */
1285 if(movenr >= 0) movenr -= moves; /* we already finished this session */
1286 } while(movenr >= -1); /* try again for next session */
1288 return 0; // no new time quota on this move
1292 ParseTimeControl (char *tc, float ti, int mps)
1296 char buf[MSG_SIZ], buf2[MSG_SIZ], *mytc = tc;
1299 if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1300 if(!strchr(tc, '+') && !strchr(tc, '/') && sscanf(tc, "%d:%d", &min, &sec) >= 1)
1301 sprintf(mytc=buf2, "%d", 60*min+sec); // convert 'classical' min:sec tc string to seconds
1305 snprintf(buf, MSG_SIZ, ":%d/%s+%g", mps, mytc, ti);
1307 snprintf(buf, MSG_SIZ, ":%s+%g", mytc, ti);
1310 snprintf(buf, MSG_SIZ, ":%d/%s", mps, mytc);
1312 snprintf(buf, MSG_SIZ, ":%s", mytc);
1314 fullTimeControlString = StrSave(buf); // this should now be in PGN format
1316 if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1321 /* Parse second time control */
1324 if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1332 timeControl_2 = tc2 * 1000;
1342 timeControl = tc1 * 1000;
1345 timeIncrement = ti * 1000; /* convert to ms */
1346 movesPerSession = 0;
1349 movesPerSession = mps;
1357 if (appData.debugMode) {
1358 # ifdef __GIT_VERSION
1359 fprintf(debugFP, "Version: %s (%s)\n", programVersion, __GIT_VERSION);
1361 fprintf(debugFP, "Version: %s\n", programVersion);
1364 ASSIGN(currentDebugFile, appData.nameOfDebugFile); // [HGM] debug split: remember initial name in use
1366 set_cont_sequence(appData.wrapContSeq);
1367 if (appData.matchGames > 0) {
1368 appData.matchMode = TRUE;
1369 } else if (appData.matchMode) {
1370 appData.matchGames = 1;
1372 if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1373 appData.matchGames = appData.sameColorGames;
1374 if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1375 if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1376 if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1379 if (appData.noChessProgram || first.protocolVersion == 1) {
1382 /* kludge: allow timeout for initial "feature" commands */
1384 DisplayMessage("", _("Starting chess program"));
1385 ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1390 CalculateIndex (int index, int gameNr)
1391 { // [HGM] autoinc: absolute way to determine load index from game number (taking auto-inc and rewind into account)
1393 if(index > 0) return index; // fixed nmber
1394 if(index == 0) return 1;
1395 res = (index == -1 ? gameNr : (gameNr-1)/2 + 1); // autoinc
1396 if(appData.rewindIndex > 0) res = (res-1) % appData.rewindIndex + 1; // rewind
1401 LoadGameOrPosition (int gameNr)
1402 { // [HGM] taken out of MatchEvent and NextMatchGame (to combine it)
1403 if (*appData.loadGameFile != NULLCHAR) {
1404 if (!LoadGameFromFile(appData.loadGameFile,
1405 CalculateIndex(appData.loadGameIndex, gameNr),
1406 appData.loadGameFile, FALSE)) {
1407 DisplayFatalError(_("Bad game file"), 0, 1);
1410 } else if (*appData.loadPositionFile != NULLCHAR) {
1411 if (!LoadPositionFromFile(appData.loadPositionFile,
1412 CalculateIndex(appData.loadPositionIndex, gameNr),
1413 appData.loadPositionFile)) {
1414 DisplayFatalError(_("Bad position file"), 0, 1);
1422 ReserveGame (int gameNr, char resChar)
1424 FILE *tf = fopen(appData.tourneyFile, "r+");
1425 char *p, *q, c, buf[MSG_SIZ];
1426 if(tf == NULL) { nextGame = appData.matchGames + 1; return; } // kludge to terminate match
1427 safeStrCpy(buf, lastMsg, MSG_SIZ);
1428 DisplayMessage(_("Pick new game"), "");
1429 flock(fileno(tf), LOCK_EX); // lock the tourney file while we are messing with it
1430 ParseArgsFromFile(tf);
1431 p = q = appData.results;
1432 if(appData.debugMode) {
1433 char *r = appData.participants;
1434 fprintf(debugFP, "results = '%s'\n", p);
1435 while(*r) fprintf(debugFP, *r >= ' ' ? "%c" : "\\%03o", *r), r++;
1436 fprintf(debugFP, "\n");
1438 while(*q && *q != ' ') q++; // get first un-played game (could be beyond end!)
1440 q = malloc(strlen(p) + 2); // could be arbitrary long, but allow to extend by one!
1441 safeStrCpy(q, p, strlen(p) + 2);
1442 if(gameNr >= 0) q[gameNr] = resChar; // replace '*' with result
1443 if(appData.debugMode) fprintf(debugFP, "pick next game from '%s': %d\n", q, nextGame);
1444 if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch) { // reserve next game if tourney not yet done
1445 if(q[nextGame] == NULLCHAR) q[nextGame+1] = NULLCHAR; // append one char
1448 fseek(tf, -(strlen(p)+4), SEEK_END);
1450 if(c != '"') // depending on DOS or Unix line endings we can be one off
1451 fseek(tf, -(strlen(p)+2), SEEK_END);
1452 else fseek(tf, -(strlen(p)+3), SEEK_END);
1453 fprintf(tf, "%s\"\n", q); fclose(tf); // update, and flush by closing
1454 DisplayMessage(buf, "");
1455 free(p); appData.results = q;
1456 if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch &&
1457 (gameNr < 0 || nextGame / appData.defaultMatchGames != gameNr / appData.defaultMatchGames)) {
1458 int round = appData.defaultMatchGames * appData.tourneyType;
1459 if(gameNr < 0 || appData.tourneyType < 1 || // gauntlet engine can always stay loaded as first engine
1460 appData.tourneyType > 1 && nextGame/round != gameNr/round) // in multi-gauntlet change only after round
1461 UnloadEngine(&first); // next game belongs to other pairing;
1462 UnloadEngine(&second); // already unload the engines, so TwoMachinesEvent will load new ones.
1464 if(appData.debugMode) fprintf(debugFP, "Reserved, next=%d, nr=%d\n", nextGame, gameNr);
1468 MatchEvent (int mode)
1469 { // [HGM] moved out of InitBackend3, to make it callable when match starts through menu
1471 if(matchMode) { // already in match mode: switch it off
1473 if(!appData.tourneyFile[0]) appData.matchGames = matchGame; // kludge to let match terminate after next game.
1476 // if(gameMode != BeginningOfGame) {
1477 // DisplayError(_("You can only start a match from the initial position."), 0);
1481 if(mode == 2) appData.matchGames = appData.defaultMatchGames;
1482 /* Set up machine vs. machine match */
1484 NextTourneyGame(-1, &dummy); // sets appData.matchGames if this is tourney, to make sure ReserveGame knows it
1485 if(appData.tourneyFile[0]) {
1487 if(nextGame > appData.matchGames) {
1489 if(strchr(appData.results, '*') == NULL) {
1491 appData.tourneyCycles++;
1492 if(f = WriteTourneyFile(appData.results, NULL)) { // make a tourney file with increased number of cycles
1494 NextTourneyGame(-1, &dummy);
1496 if(nextGame <= appData.matchGames) {
1497 DisplayNote(_("You restarted an already completed tourney\nOne more cycle will now be added to it\nGames commence in 10 sec"));
1499 ScheduleDelayedEvent(NextMatchGame, 10000);
1504 snprintf(buf, MSG_SIZ, _("All games in tourney '%s' are already played or playing"), appData.tourneyFile);
1505 DisplayError(buf, 0);
1506 appData.tourneyFile[0] = 0;
1510 if (appData.noChessProgram) { // [HGM] in tourney engines are loaded automatically
1511 DisplayFatalError(_("Can't have a match with no chess programs"),
1516 matchGame = roundNr = 1;
1517 first.matchWins = second.matchWins = 0; // [HGM] match: needed in later matches
1521 char *comboLine = NULL; // [HGM] recent: WinBoard's first-engine combobox line
1524 InitBackEnd3 P((void))
1526 GameMode initialMode;
1530 InitChessProgram(&first, startedFromSetupPosition);
1532 if(!appData.noChessProgram) { /* [HGM] tidy: redo program version to use name from myname feature */
1533 free(programVersion);
1534 programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1535 sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1536 FloatToFront(&appData.recentEngineList, comboLine ? comboLine : appData.firstChessProgram);
1539 if (appData.icsActive) {
1541 /* [DM] Make a console window if needed [HGM] merged ifs */
1547 if (*appData.icsCommPort != NULLCHAR)
1548 len = snprintf(buf, MSG_SIZ, _("Could not open comm port %s"),
1549 appData.icsCommPort);
1551 len = snprintf(buf, MSG_SIZ, _("Could not connect to host %s, port %s"),
1552 appData.icsHost, appData.icsPort);
1554 if( (len >= MSG_SIZ) && appData.debugMode )
1555 fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1557 DisplayFatalError(buf, err, 1);
1562 AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1564 AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1565 if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1566 ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1567 } else if (appData.noChessProgram) {
1573 if (*appData.cmailGameName != NULLCHAR) {
1575 OpenLoopback(&cmailPR);
1577 AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1581 DisplayMessage("", "");
1582 if (StrCaseCmp(appData.initialMode, "") == 0) {
1583 initialMode = BeginningOfGame;
1584 if(!appData.icsActive && appData.noChessProgram) { // [HGM] could be fall-back
1585 gameMode = MachinePlaysBlack; // "Machine Black" might have been implicitly highlighted
1586 ModeHighlight(); // make sure XBoard knows it is highlighted, so it will un-highlight it
1587 gameMode = BeginningOfGame; // in case BeginningOfGame now means "Edit Position"
1590 } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1591 initialMode = TwoMachinesPlay;
1592 } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1593 initialMode = AnalyzeFile;
1594 } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1595 initialMode = AnalyzeMode;
1596 } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1597 initialMode = MachinePlaysWhite;
1598 } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1599 initialMode = MachinePlaysBlack;
1600 } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1601 initialMode = EditGame;
1602 } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1603 initialMode = EditPosition;
1604 } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1605 initialMode = Training;
1607 len = snprintf(buf, MSG_SIZ, _("Unknown initialMode %s"), appData.initialMode);
1608 if( (len >= MSG_SIZ) && appData.debugMode )
1609 fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1611 DisplayFatalError(buf, 0, 2);
1615 if (appData.matchMode) {
1616 if(appData.tourneyFile[0]) { // start tourney from command line
1618 if(f = fopen(appData.tourneyFile, "r")) {
1619 ParseArgsFromFile(f); // make sure tourney parmeters re known
1621 appData.clockMode = TRUE;
1623 } else appData.tourneyFile[0] = NULLCHAR; // for now ignore bad tourney file
1626 } else if (*appData.cmailGameName != NULLCHAR) {
1627 /* Set up cmail mode */
1628 ReloadCmailMsgEvent(TRUE);
1630 /* Set up other modes */
1631 if (initialMode == AnalyzeFile) {
1632 if (*appData.loadGameFile == NULLCHAR) {
1633 DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1637 if (*appData.loadGameFile != NULLCHAR) {
1638 (void) LoadGameFromFile(appData.loadGameFile,
1639 appData.loadGameIndex,
1640 appData.loadGameFile, TRUE);
1641 } else if (*appData.loadPositionFile != NULLCHAR) {
1642 (void) LoadPositionFromFile(appData.loadPositionFile,
1643 appData.loadPositionIndex,
1644 appData.loadPositionFile);
1645 /* [HGM] try to make self-starting even after FEN load */
1646 /* to allow automatic setup of fairy variants with wtm */
1647 if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1648 gameMode = BeginningOfGame;
1649 setboardSpoiledMachineBlack = 1;
1651 /* [HGM] loadPos: make that every new game uses the setup */
1652 /* from file as long as we do not switch variant */
1653 if(!blackPlaysFirst) {
1654 startedFromPositionFile = TRUE;
1655 CopyBoard(filePosition, boards[0]);
1658 if (initialMode == AnalyzeMode) {
1659 if (appData.noChessProgram) {
1660 DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1663 if (appData.icsActive) {
1664 DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1668 } else if (initialMode == AnalyzeFile) {
1669 appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1670 ShowThinkingEvent();
1672 AnalysisPeriodicEvent(1);
1673 } else if (initialMode == MachinePlaysWhite) {
1674 if (appData.noChessProgram) {
1675 DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1679 if (appData.icsActive) {
1680 DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1684 MachineWhiteEvent();
1685 } else if (initialMode == MachinePlaysBlack) {
1686 if (appData.noChessProgram) {
1687 DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1691 if (appData.icsActive) {
1692 DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1696 MachineBlackEvent();
1697 } else if (initialMode == TwoMachinesPlay) {
1698 if (appData.noChessProgram) {
1699 DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1703 if (appData.icsActive) {
1704 DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1709 } else if (initialMode == EditGame) {
1711 } else if (initialMode == EditPosition) {
1712 EditPositionEvent();
1713 } else if (initialMode == Training) {
1714 if (*appData.loadGameFile == NULLCHAR) {
1715 DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1724 HistorySet (char movelist[][2*MOVE_LEN], int first, int last, int current)
1726 DisplayBook(current+1);
1728 MoveHistorySet( movelist, first, last, current, pvInfoList );
1730 EvalGraphSet( first, last, current, pvInfoList );
1732 MakeEngineOutputTitle();
1736 * Establish will establish a contact to a remote host.port.
1737 * Sets icsPR to a ProcRef for a process (or pseudo-process)
1738 * used to talk to the host.
1739 * Returns 0 if okay, error code if not.
1746 if (*appData.icsCommPort != NULLCHAR) {
1747 /* Talk to the host through a serial comm port */
1748 return OpenCommPort(appData.icsCommPort, &icsPR);
1750 } else if (*appData.gateway != NULLCHAR) {
1751 if (*appData.remoteShell == NULLCHAR) {
1752 /* Use the rcmd protocol to run telnet program on a gateway host */
1753 snprintf(buf, sizeof(buf), "%s %s %s",
1754 appData.telnetProgram, appData.icsHost, appData.icsPort);
1755 return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1758 /* Use the rsh program to run telnet program on a gateway host */
1759 if (*appData.remoteUser == NULLCHAR) {
1760 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1761 appData.gateway, appData.telnetProgram,
1762 appData.icsHost, appData.icsPort);
1764 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1765 appData.remoteShell, appData.gateway,
1766 appData.remoteUser, appData.telnetProgram,
1767 appData.icsHost, appData.icsPort);
1769 return StartChildProcess(buf, "", &icsPR);
1772 } else if (appData.useTelnet) {
1773 return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1776 /* TCP socket interface differs somewhat between
1777 Unix and NT; handle details in the front end.
1779 return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1784 EscapeExpand (char *p, char *q)
1785 { // [HGM] initstring: routine to shape up string arguments
1786 while(*p++ = *q++) if(p[-1] == '\\')
1788 case 'n': p[-1] = '\n'; break;
1789 case 'r': p[-1] = '\r'; break;
1790 case 't': p[-1] = '\t'; break;
1791 case '\\': p[-1] = '\\'; break;
1792 case 0: *p = 0; return;
1793 default: p[-1] = q[-1]; break;
1798 show_bytes (FILE *fp, char *buf, int count)
1801 if (*buf < 040 || *(unsigned char *) buf > 0177) {
1802 fprintf(fp, "\\%03o", *buf & 0xff);
1811 /* Returns an errno value */
1813 OutputMaybeTelnet (ProcRef pr, char *message, int count, int *outError)
1815 char buf[8192], *p, *q, *buflim;
1816 int left, newcount, outcount;
1818 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1819 *appData.gateway != NULLCHAR) {
1820 if (appData.debugMode) {
1821 fprintf(debugFP, ">ICS: ");
1822 show_bytes(debugFP, message, count);
1823 fprintf(debugFP, "\n");
1825 return OutputToProcess(pr, message, count, outError);
1828 buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1835 if (appData.debugMode) {
1836 fprintf(debugFP, ">ICS: ");
1837 show_bytes(debugFP, buf, newcount);
1838 fprintf(debugFP, "\n");
1840 outcount = OutputToProcess(pr, buf, newcount, outError);
1841 if (outcount < newcount) return -1; /* to be sure */
1848 } else if (((unsigned char) *p) == TN_IAC) {
1849 *q++ = (char) TN_IAC;
1856 if (appData.debugMode) {
1857 fprintf(debugFP, ">ICS: ");
1858 show_bytes(debugFP, buf, newcount);
1859 fprintf(debugFP, "\n");
1861 outcount = OutputToProcess(pr, buf, newcount, outError);
1862 if (outcount < newcount) return -1; /* to be sure */
1867 read_from_player (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
1869 int outError, outCount;
1870 static int gotEof = 0;
1873 /* Pass data read from player on to ICS */
1876 outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1877 if (outCount < count) {
1878 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1880 if(have_sent_ICS_logon == 2) {
1881 if(ini = fopen(appData.icsLogon, "w")) { // save first two lines (presumably username & password) on init script file
1882 fprintf(ini, "%s", message);
1883 have_sent_ICS_logon = 3;
1885 have_sent_ICS_logon = 1;
1886 } else if(have_sent_ICS_logon == 3) {
1887 fprintf(ini, "%s", message);
1889 have_sent_ICS_logon = 1;
1891 } else if (count < 0) {
1892 RemoveInputSource(isr);
1893 DisplayFatalError(_("Error reading from keyboard"), error, 1);
1894 } else if (gotEof++ > 0) {
1895 RemoveInputSource(isr);
1896 DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1902 { // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1903 if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1904 connectionAlive = FALSE; // only sticks if no response to 'date' command.
1905 SendToICS("date\n");
1906 if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1909 /* added routine for printf style output to ics */
1911 ics_printf (char *format, ...)
1913 char buffer[MSG_SIZ];
1916 va_start(args, format);
1917 vsnprintf(buffer, sizeof(buffer), format, args);
1918 buffer[sizeof(buffer)-1] = '\0';
1926 int count, outCount, outError;
1928 if (icsPR == NoProc) return;
1931 outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1932 if (outCount < count) {
1933 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1937 /* This is used for sending logon scripts to the ICS. Sending
1938 without a delay causes problems when using timestamp on ICC
1939 (at least on my machine). */
1941 SendToICSDelayed (char *s, long msdelay)
1943 int count, outCount, outError;
1945 if (icsPR == NoProc) return;
1948 if (appData.debugMode) {
1949 fprintf(debugFP, ">ICS: ");
1950 show_bytes(debugFP, s, count);
1951 fprintf(debugFP, "\n");
1953 outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1955 if (outCount < count) {
1956 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1961 /* Remove all highlighting escape sequences in s
1962 Also deletes any suffix starting with '('
1965 StripHighlightAndTitle (char *s)
1967 static char retbuf[MSG_SIZ];
1970 while (*s != NULLCHAR) {
1971 while (*s == '\033') {
1972 while (*s != NULLCHAR && !isalpha(*s)) s++;
1973 if (*s != NULLCHAR) s++;
1975 while (*s != NULLCHAR && *s != '\033') {
1976 if (*s == '(' || *s == '[') {
1987 /* Remove all highlighting escape sequences in s */
1989 StripHighlight (char *s)
1991 static char retbuf[MSG_SIZ];
1994 while (*s != NULLCHAR) {
1995 while (*s == '\033') {
1996 while (*s != NULLCHAR && !isalpha(*s)) s++;
1997 if (*s != NULLCHAR) s++;
1999 while (*s != NULLCHAR && *s != '\033') {
2007 char *variantNames[] = VARIANT_NAMES;
2009 VariantName (VariantClass v)
2011 return variantNames[v];
2015 /* Identify a variant from the strings the chess servers use or the
2016 PGN Variant tag names we use. */
2018 StringToVariant (char *e)
2022 VariantClass v = VariantNormal;
2023 int i, found = FALSE;
2029 /* [HGM] skip over optional board-size prefixes */
2030 if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
2031 sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
2032 while( *e++ != '_');
2035 if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
2039 for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
2040 if (StrCaseStr(e, variantNames[i])) {
2041 v = (VariantClass) i;
2048 if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
2049 || StrCaseStr(e, "wild/fr")
2050 || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
2051 v = VariantFischeRandom;
2052 } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
2053 (i = 1, p = StrCaseStr(e, "w"))) {
2055 while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
2062 case 0: /* FICS only, actually */
2064 /* Castling legal even if K starts on d-file */
2065 v = VariantWildCastle;
2070 /* Castling illegal even if K & R happen to start in
2071 normal positions. */
2072 v = VariantNoCastle;
2085 /* Castling legal iff K & R start in normal positions */
2091 /* Special wilds for position setup; unclear what to do here */
2092 v = VariantLoadable;
2095 /* Bizarre ICC game */
2096 v = VariantTwoKings;
2099 v = VariantKriegspiel;
2105 v = VariantFischeRandom;
2108 v = VariantCrazyhouse;
2111 v = VariantBughouse;
2117 /* Not quite the same as FICS suicide! */
2118 v = VariantGiveaway;
2124 v = VariantShatranj;
2127 /* Temporary names for future ICC types. The name *will* change in
2128 the next xboard/WinBoard release after ICC defines it. */
2166 v = VariantCapablanca;
2169 v = VariantKnightmate;
2175 v = VariantCylinder;
2181 v = VariantCapaRandom;
2184 v = VariantBerolina;
2196 /* Found "wild" or "w" in the string but no number;
2197 must assume it's normal chess. */
2201 len = snprintf(buf, MSG_SIZ, _("Unknown wild type %d"), wnum);
2202 if( (len >= MSG_SIZ) && appData.debugMode )
2203 fprintf(debugFP, "StringToVariant: buffer truncated.\n");
2205 DisplayError(buf, 0);
2211 if (appData.debugMode) {
2212 fprintf(debugFP, "recognized '%s' (%d) as variant %s\n",
2213 e, wnum, VariantName(v));
2218 static int leftover_start = 0, leftover_len = 0;
2219 char star_match[STAR_MATCH_N][MSG_SIZ];
2221 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
2222 advance *index beyond it, and set leftover_start to the new value of
2223 *index; else return FALSE. If pattern contains the character '*', it
2224 matches any sequence of characters not containing '\r', '\n', or the
2225 character following the '*' (if any), and the matched sequence(s) are
2226 copied into star_match.
2229 looking_at ( char *buf, int *index, char *pattern)
2231 char *bufp = &buf[*index], *patternp = pattern;
2233 char *matchp = star_match[0];
2236 if (*patternp == NULLCHAR) {
2237 *index = leftover_start = bufp - buf;
2241 if (*bufp == NULLCHAR) return FALSE;
2242 if (*patternp == '*') {
2243 if (*bufp == *(patternp + 1)) {
2245 matchp = star_match[++star_count];
2249 } else if (*bufp == '\n' || *bufp == '\r') {
2251 if (*patternp == NULLCHAR)
2256 *matchp++ = *bufp++;
2260 if (*patternp != *bufp) return FALSE;
2267 SendToPlayer (char *data, int length)
2269 int error, outCount;
2270 outCount = OutputToProcess(NoProc, data, length, &error);
2271 if (outCount < length) {
2272 DisplayFatalError(_("Error writing to display"), error, 1);
2277 PackHolding (char packed[], char *holding)
2287 switch (runlength) {
2298 sprintf(q, "%d", runlength);
2310 /* Telnet protocol requests from the front end */
2312 TelnetRequest (unsigned char ddww, unsigned char option)
2314 unsigned char msg[3];
2315 int outCount, outError;
2317 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
2319 if (appData.debugMode) {
2320 char buf1[8], buf2[8], *ddwwStr, *optionStr;
2336 snprintf(buf1,sizeof(buf1)/sizeof(buf1[0]), "%d", ddww);
2345 snprintf(buf2,sizeof(buf2)/sizeof(buf2[0]), "%d", option);
2348 fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
2353 outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
2355 DisplayFatalError(_("Error writing to ICS"), outError, 1);
2362 if (!appData.icsActive) return;
2363 TelnetRequest(TN_DO, TN_ECHO);
2369 if (!appData.icsActive) return;
2370 TelnetRequest(TN_DONT, TN_ECHO);
2374 CopyHoldings (Board board, char *holdings, ChessSquare lowestPiece)
2376 /* put the holdings sent to us by the server on the board holdings area */
2377 int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2381 if(gameInfo.holdingsWidth < 2) return;
2382 if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2383 return; // prevent overwriting by pre-board holdings
2385 if( (int)lowestPiece >= BlackPawn ) {
2388 holdingsStartRow = BOARD_HEIGHT-1;
2391 holdingsColumn = BOARD_WIDTH-1;
2392 countsColumn = BOARD_WIDTH-2;
2393 holdingsStartRow = 0;
2397 for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
2398 board[i][holdingsColumn] = EmptySquare;
2399 board[i][countsColumn] = (ChessSquare) 0;
2401 while( (p=*holdings++) != NULLCHAR ) {
2402 piece = CharToPiece( ToUpper(p) );
2403 if(piece == EmptySquare) continue;
2404 /*j = (int) piece - (int) WhitePawn;*/
2405 j = PieceToNumber(piece);
2406 if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2407 if(j < 0) continue; /* should not happen */
2408 piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2409 board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2410 board[holdingsStartRow+j*direction][countsColumn]++;
2416 VariantSwitch (Board board, VariantClass newVariant)
2418 int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2419 static Board oldBoard;
2421 startedFromPositionFile = FALSE;
2422 if(gameInfo.variant == newVariant) return;
2424 /* [HGM] This routine is called each time an assignment is made to
2425 * gameInfo.variant during a game, to make sure the board sizes
2426 * are set to match the new variant. If that means adding or deleting
2427 * holdings, we shift the playing board accordingly
2428 * This kludge is needed because in ICS observe mode, we get boards
2429 * of an ongoing game without knowing the variant, and learn about the
2430 * latter only later. This can be because of the move list we requested,
2431 * in which case the game history is refilled from the beginning anyway,
2432 * but also when receiving holdings of a crazyhouse game. In the latter
2433 * case we want to add those holdings to the already received position.
2437 if (appData.debugMode) {
2438 fprintf(debugFP, "Switch board from %s to %s\n",
2439 VariantName(gameInfo.variant), VariantName(newVariant));
2440 setbuf(debugFP, NULL);
2442 shuffleOpenings = 0; /* [HGM] shuffle */
2443 gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2447 newWidth = 9; newHeight = 9;
2448 gameInfo.holdingsSize = 7;
2449 case VariantBughouse:
2450 case VariantCrazyhouse:
2451 newHoldingsWidth = 2; break;
2455 newHoldingsWidth = 2;
2456 gameInfo.holdingsSize = 8;
2459 case VariantCapablanca:
2460 case VariantCapaRandom:
2463 newHoldingsWidth = gameInfo.holdingsSize = 0;
2466 if(newWidth != gameInfo.boardWidth ||
2467 newHeight != gameInfo.boardHeight ||
2468 newHoldingsWidth != gameInfo.holdingsWidth ) {
2470 /* shift position to new playing area, if needed */
2471 if(newHoldingsWidth > gameInfo.holdingsWidth) {
2472 for(i=0; i<BOARD_HEIGHT; i++)
2473 for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2474 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2476 for(i=0; i<newHeight; i++) {
2477 board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2478 board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2480 } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2481 for(i=0; i<BOARD_HEIGHT; i++)
2482 for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2483 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2486 board[HOLDINGS_SET] = 0;
2487 gameInfo.boardWidth = newWidth;
2488 gameInfo.boardHeight = newHeight;
2489 gameInfo.holdingsWidth = newHoldingsWidth;
2490 gameInfo.variant = newVariant;
2491 InitDrawingSizes(-2, 0);
2492 } else gameInfo.variant = newVariant;
2493 CopyBoard(oldBoard, board); // remember correctly formatted board
2494 InitPosition(FALSE); /* this sets up board[0], but also other stuff */
2495 DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2498 static int loggedOn = FALSE;
2500 /*-- Game start info cache: --*/
2502 char gs_kind[MSG_SIZ];
2503 static char player1Name[128] = "";
2504 static char player2Name[128] = "";
2505 static char cont_seq[] = "\n\\ ";
2506 static int player1Rating = -1;
2507 static int player2Rating = -1;
2508 /*----------------------------*/
2510 ColorClass curColor = ColorNormal;
2511 int suppressKibitz = 0;
2514 Boolean soughtPending = FALSE;
2515 Boolean seekGraphUp;
2516 #define MAX_SEEK_ADS 200
2518 char *seekAdList[MAX_SEEK_ADS];
2519 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2520 float tcList[MAX_SEEK_ADS];
2521 char colorList[MAX_SEEK_ADS];
2522 int nrOfSeekAds = 0;
2523 int minRating = 1010, maxRating = 2800;
2524 int hMargin = 10, vMargin = 20, h, w;
2525 extern int squareSize, lineGap;
2530 int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2531 xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2532 if(r < minRating+100 && r >=0 ) r = minRating+100;
2533 if(r > maxRating) r = maxRating;
2534 if(tc < 1.f) tc = 1.f;
2535 if(tc > 95.f) tc = 95.f;
2536 x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2537 y = ((double)r - minRating)/(maxRating - minRating)
2538 * (h-vMargin-squareSize/8-1) + vMargin;
2539 if(ratingList[i] < 0) y = vMargin + squareSize/4;
2540 if(strstr(seekAdList[i], " u ")) color = 1;
2541 if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2542 !strstr(seekAdList[i], "bullet") &&
2543 !strstr(seekAdList[i], "blitz") &&
2544 !strstr(seekAdList[i], "standard") ) color = 2;
2545 if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2546 DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2550 PlotSingleSeekAd (int i)
2556 AddAd (char *handle, char *rating, int base, int inc, char rated, char *type, int nr, Boolean plot)
2558 char buf[MSG_SIZ], *ext = "";
2559 VariantClass v = StringToVariant(type);
2560 if(strstr(type, "wild")) {
2561 ext = type + 4; // append wild number
2562 if(v == VariantFischeRandom) type = "chess960"; else
2563 if(v == VariantLoadable) type = "setup"; else
2564 type = VariantName(v);
2566 snprintf(buf, MSG_SIZ, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2567 if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2568 if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2569 ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2570 sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2571 tcList[nrOfSeekAds] = base + (2./3.)*inc;
2572 seekNrList[nrOfSeekAds] = nr;
2573 zList[nrOfSeekAds] = 0;
2574 seekAdList[nrOfSeekAds++] = StrSave(buf);
2575 if(plot) PlotSingleSeekAd(nrOfSeekAds-1);
2580 EraseSeekDot (int i)
2582 int x = xList[i], y = yList[i], d=squareSize/4, k;
2583 DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2584 if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2585 // now replot every dot that overlapped
2586 for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2587 int xx = xList[k], yy = yList[k];
2588 if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2589 DrawSeekDot(xx, yy, colorList[k]);
2594 RemoveSeekAd (int nr)
2597 for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2599 if(seekAdList[i]) free(seekAdList[i]);
2600 seekAdList[i] = seekAdList[--nrOfSeekAds];
2601 seekNrList[i] = seekNrList[nrOfSeekAds];
2602 ratingList[i] = ratingList[nrOfSeekAds];
2603 colorList[i] = colorList[nrOfSeekAds];
2604 tcList[i] = tcList[nrOfSeekAds];
2605 xList[i] = xList[nrOfSeekAds];
2606 yList[i] = yList[nrOfSeekAds];
2607 zList[i] = zList[nrOfSeekAds];
2608 seekAdList[nrOfSeekAds] = NULL;
2614 MatchSoughtLine (char *line)
2616 char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2617 int nr, base, inc, u=0; char dummy;
2619 if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2620 sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2622 (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2623 sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7) ) {
2624 // match: compact and save the line
2625 AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2635 if(!seekGraphUp) return FALSE;
2636 h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
2637 w = BOARD_WIDTH * (squareSize + lineGap) + lineGap;
2639 DrawSeekBackground(0, 0, w, h);
2640 DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2641 DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2642 for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2643 int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2645 DrawSeekAxis(hMargin-5, yy, hMargin+5*(i%500==0), yy); // rating ticks
2648 snprintf(buf, MSG_SIZ, "%d", i);
2649 DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2652 DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2653 for(i=1; i<100; i+=(i<10?1:5)) {
2654 int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2655 DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2656 if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2658 snprintf(buf, MSG_SIZ, "%d", i);
2659 DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2662 for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2667 SeekGraphClick (ClickType click, int x, int y, int moving)
2669 static int lastDown = 0, displayed = 0, lastSecond;
2670 if(y < 0) return FALSE;
2671 if(!(appData.seekGraph && appData.icsActive && loggedOn &&
2672 (gameMode == BeginningOfGame || gameMode == IcsIdle))) {
2673 if(!seekGraphUp) return FALSE;
2674 seekGraphUp = FALSE; // seek graph is up when it shouldn't be: take it down
2675 DrawPosition(TRUE, NULL);
2678 if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2679 if(click == Release || moving) return FALSE;
2681 soughtPending = TRUE;
2682 SendToICS(ics_prefix);
2683 SendToICS("sought\n"); // should this be "sought all"?
2684 } else { // issue challenge based on clicked ad
2685 int dist = 10000; int i, closest = 0, second = 0;
2686 for(i=0; i<nrOfSeekAds; i++) {
2687 int d = (x-xList[i])*(x-xList[i]) + (y-yList[i])*(y-yList[i]) + zList[i];
2688 if(d < dist) { dist = d; closest = i; }
2689 second += (d - zList[i] < 120); // count in-range ads
2690 if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2694 second = (second > 1);
2695 if(displayed != closest || second != lastSecond) {
2696 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2697 lastSecond = second; displayed = closest;
2699 if(click == Press) {
2700 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2703 } // on press 'hit', only show info
2704 if(moving == 2) return TRUE; // ignore right up-clicks on dot
2705 snprintf(buf, MSG_SIZ, "play %d\n", seekNrList[closest]);
2706 SendToICS(ics_prefix);
2708 return TRUE; // let incoming board of started game pop down the graph
2709 } else if(click == Release) { // release 'miss' is ignored
2710 zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2711 if(moving == 2) { // right up-click
2712 nrOfSeekAds = 0; // refresh graph
2713 soughtPending = TRUE;
2714 SendToICS(ics_prefix);
2715 SendToICS("sought\n"); // should this be "sought all"?
2718 } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2719 // press miss or release hit 'pop down' seek graph
2720 seekGraphUp = FALSE;
2721 DrawPosition(TRUE, NULL);
2727 read_from_ics (InputSourceRef isr, VOIDSTAR closure, char *data, int count, int error)
2729 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2730 #define STARTED_NONE 0
2731 #define STARTED_MOVES 1
2732 #define STARTED_BOARD 2
2733 #define STARTED_OBSERVE 3
2734 #define STARTED_HOLDINGS 4
2735 #define STARTED_CHATTER 5
2736 #define STARTED_COMMENT 6
2737 #define STARTED_MOVES_NOHIDE 7
2739 static int started = STARTED_NONE;
2740 static char parse[20000];
2741 static int parse_pos = 0;
2742 static char buf[BUF_SIZE + 1];
2743 static int firstTime = TRUE, intfSet = FALSE;
2744 static ColorClass prevColor = ColorNormal;
2745 static int savingComment = FALSE;
2746 static int cmatch = 0; // continuation sequence match
2753 int backup; /* [DM] For zippy color lines */
2755 char talker[MSG_SIZ]; // [HGM] chat
2758 connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2760 if (appData.debugMode) {
2762 fprintf(debugFP, "<ICS: ");
2763 show_bytes(debugFP, data, count);
2764 fprintf(debugFP, "\n");
2768 if (appData.debugMode) { int f = forwardMostMove;
2769 fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2770 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2771 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2774 /* If last read ended with a partial line that we couldn't parse,
2775 prepend it to the new read and try again. */
2776 if (leftover_len > 0) {
2777 for (i=0; i<leftover_len; i++)
2778 buf[i] = buf[leftover_start + i];
2781 /* copy new characters into the buffer */
2782 bp = buf + leftover_len;
2783 buf_len=leftover_len;
2784 for (i=0; i<count; i++)
2787 if (data[i] == '\r')
2790 // join lines split by ICS?
2791 if (!appData.noJoin)
2794 Joining just consists of finding matches against the
2795 continuation sequence, and discarding that sequence
2796 if found instead of copying it. So, until a match
2797 fails, there's nothing to do since it might be the
2798 complete sequence, and thus, something we don't want
2801 if (data[i] == cont_seq[cmatch])
2804 if (cmatch == strlen(cont_seq))
2806 cmatch = 0; // complete match. just reset the counter
2809 it's possible for the ICS to not include the space
2810 at the end of the last word, making our [correct]
2811 join operation fuse two separate words. the server
2812 does this when the space occurs at the width setting.
2814 if (!buf_len || buf[buf_len-1] != ' ')
2825 match failed, so we have to copy what matched before
2826 falling through and copying this character. In reality,
2827 this will only ever be just the newline character, but
2828 it doesn't hurt to be precise.
2830 strncpy(bp, cont_seq, cmatch);
2842 buf[buf_len] = NULLCHAR;
2843 // next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2848 while (i < buf_len) {
2849 /* Deal with part of the TELNET option negotiation
2850 protocol. We refuse to do anything beyond the
2851 defaults, except that we allow the WILL ECHO option,
2852 which ICS uses to turn off password echoing when we are
2853 directly connected to it. We reject this option
2854 if localLineEditing mode is on (always on in xboard)
2855 and we are talking to port 23, which might be a real
2856 telnet server that will try to keep WILL ECHO on permanently.
2858 if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2859 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2860 unsigned char option;
2862 switch ((unsigned char) buf[++i]) {
2864 if (appData.debugMode)
2865 fprintf(debugFP, "\n<WILL ");
2866 switch (option = (unsigned char) buf[++i]) {
2868 if (appData.debugMode)
2869 fprintf(debugFP, "ECHO ");
2870 /* Reply only if this is a change, according
2871 to the protocol rules. */
2872 if (remoteEchoOption) break;
2873 if (appData.localLineEditing &&
2874 atoi(appData.icsPort) == TN_PORT) {
2875 TelnetRequest(TN_DONT, TN_ECHO);
2878 TelnetRequest(TN_DO, TN_ECHO);
2879 remoteEchoOption = TRUE;
2883 if (appData.debugMode)
2884 fprintf(debugFP, "%d ", option);
2885 /* Whatever this is, we don't want it. */
2886 TelnetRequest(TN_DONT, option);
2891 if (appData.debugMode)
2892 fprintf(debugFP, "\n<WONT ");
2893 switch (option = (unsigned char) buf[++i]) {
2895 if (appData.debugMode)
2896 fprintf(debugFP, "ECHO ");
2897 /* Reply only if this is a change, according
2898 to the protocol rules. */
2899 if (!remoteEchoOption) break;
2901 TelnetRequest(TN_DONT, TN_ECHO);
2902 remoteEchoOption = FALSE;
2905 if (appData.debugMode)
2906 fprintf(debugFP, "%d ", (unsigned char) option);
2907 /* Whatever this is, it must already be turned
2908 off, because we never agree to turn on
2909 anything non-default, so according to the
2910 protocol rules, we don't reply. */
2915 if (appData.debugMode)
2916 fprintf(debugFP, "\n<DO ");
2917 switch (option = (unsigned char) buf[++i]) {
2919 /* Whatever this is, we refuse to do it. */
2920 if (appData.debugMode)
2921 fprintf(debugFP, "%d ", option);
2922 TelnetRequest(TN_WONT, option);
2927 if (appData.debugMode)
2928 fprintf(debugFP, "\n<DONT ");
2929 switch (option = (unsigned char) buf[++i]) {
2931 if (appData.debugMode)
2932 fprintf(debugFP, "%d ", option);
2933 /* Whatever this is, we are already not doing
2934 it, because we never agree to do anything
2935 non-default, so according to the protocol
2936 rules, we don't reply. */
2941 if (appData.debugMode)
2942 fprintf(debugFP, "\n<IAC ");
2943 /* Doubled IAC; pass it through */
2947 if (appData.debugMode)
2948 fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2949 /* Drop all other telnet commands on the floor */
2952 if (oldi > next_out)
2953 SendToPlayer(&buf[next_out], oldi - next_out);
2959 /* OK, this at least will *usually* work */
2960 if (!loggedOn && looking_at(buf, &i, "ics%")) {
2964 if (loggedOn && !intfSet) {
2965 if (ics_type == ICS_ICC) {
2966 snprintf(str, MSG_SIZ,
2967 "/set-quietly interface %s\n/set-quietly style 12\n",
2969 if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2970 strcat(str, "/set-2 51 1\n/set seek 1\n");
2971 } else if (ics_type == ICS_CHESSNET) {
2972 snprintf(str, MSG_SIZ, "/style 12\n");
2974 safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
2975 strcat(str, programVersion);
2976 strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2977 if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2978 strcat(str, "$iset seekremove 1\n$set seek 1\n");
2980 strcat(str, "$iset nohighlight 1\n");
2982 strcat(str, "$iset lock 1\n$style 12\n");
2985 NotifyFrontendLogin();
2989 if (started == STARTED_COMMENT) {
2990 /* Accumulate characters in comment */
2991 parse[parse_pos++] = buf[i];
2992 if (buf[i] == '\n') {
2993 parse[parse_pos] = NULLCHAR;
2994 if(chattingPartner>=0) {
2996 snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
2997 OutputChatMessage(chattingPartner, mess);
2998 chattingPartner = -1;
2999 next_out = i+1; // [HGM] suppress printing in ICS window
3001 if(!suppressKibitz) // [HGM] kibitz
3002 AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
3003 else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
3004 int nrDigit = 0, nrAlph = 0, j;
3005 if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
3006 { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
3007 parse[parse_pos] = NULLCHAR;
3008 // try to be smart: if it does not look like search info, it should go to
3009 // ICS interaction window after all, not to engine-output window.
3010 for(j=0; j<parse_pos; j++) { // count letters and digits
3011 nrDigit += (parse[j] >= '0' && parse[j] <= '9');
3012 nrAlph += (parse[j] >= 'a' && parse[j] <= 'z');
3013 nrAlph += (parse[j] >= 'A' && parse[j] <= 'Z');
3015 if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
3016 int depth=0; float score;
3017 if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
3018 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
3019 pvInfoList[forwardMostMove-1].depth = depth;
3020 pvInfoList[forwardMostMove-1].score = 100*score;
3022 OutputKibitz(suppressKibitz, parse);
3025 if(gameMode == IcsObserving) // restore original ICS messages
3026 snprintf(tmp, MSG_SIZ, "%s kibitzes: %s", star_match[0], parse);
3028 snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
3029 SendToPlayer(tmp, strlen(tmp));
3031 next_out = i+1; // [HGM] suppress printing in ICS window
3033 started = STARTED_NONE;
3035 /* Don't match patterns against characters in comment */
3040 if (started == STARTED_CHATTER) {
3041 if (buf[i] != '\n') {
3042 /* Don't match patterns against characters in chatter */
3046 started = STARTED_NONE;
3047 if(suppressKibitz) next_out = i+1;
3050 /* Kludge to deal with rcmd protocol */
3051 if (firstTime && looking_at(buf, &i, "\001*")) {
3052 DisplayFatalError(&buf[1], 0, 1);
3058 if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
3061 if (appData.debugMode)
3062 fprintf(debugFP, "ics_type %d\n", ics_type);
3065 if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
3066 ics_type = ICS_FICS;
3068 if (appData.debugMode)
3069 fprintf(debugFP, "ics_type %d\n", ics_type);
3072 if (!loggedOn && looking_at(buf, &i, "chess.net")) {
3073 ics_type = ICS_CHESSNET;
3075 if (appData.debugMode)
3076 fprintf(debugFP, "ics_type %d\n", ics_type);
3081 (looking_at(buf, &i, "\"*\" is *a registered name") ||
3082 looking_at(buf, &i, "Logging you in as \"*\"") ||
3083 looking_at(buf, &i, "will be \"*\""))) {
3084 safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
3088 if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
3090 snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
3091 DisplayIcsInteractionTitle(buf);
3092 have_set_title = TRUE;
3095 /* skip finger notes */
3096 if (started == STARTED_NONE &&
3097 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
3098 (buf[i] == '1' && buf[i+1] == '0')) &&
3099 buf[i+2] == ':' && buf[i+3] == ' ') {
3100 started = STARTED_CHATTER;
3106 // [HGM] seekgraph: recognize sought lines and end-of-sought message
3107 if(appData.seekGraph) {
3108 if(soughtPending && MatchSoughtLine(buf+i)) {
3109 i = strstr(buf+i, "rated") - buf;
3110 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3111 next_out = leftover_start = i;
3112 started = STARTED_CHATTER;
3113 suppressKibitz = TRUE;
3116 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
3117 && looking_at(buf, &i, "* ads displayed")) {
3118 soughtPending = FALSE;
3123 if(appData.autoRefresh) {
3124 if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
3125 int s = (ics_type == ICS_ICC); // ICC format differs
3127 AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
3128 star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
3129 looking_at(buf, &i, "*% "); // eat prompt
3130 if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
3131 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3132 next_out = i; // suppress
3135 if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
3136 char *p = star_match[0];
3138 if(seekGraphUp) RemoveSeekAd(atoi(p));
3139 while(*p && *p++ != ' '); // next
3141 looking_at(buf, &i, "*% "); // eat prompt
3142 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3149 /* skip formula vars */
3150 if (started == STARTED_NONE &&
3151 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
3152 started = STARTED_CHATTER;
3157 // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
3158 if (appData.autoKibitz && started == STARTED_NONE &&
3159 !appData.icsEngineAnalyze && // [HGM] [DM] ICS analyze
3160 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
3161 if((looking_at(buf, &i, "\n* kibitzes: ") || looking_at(buf, &i, "\n* whispers: ") ||
3162 looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
3163 (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
3164 StrStr(star_match[0], gameInfo.black) == star_match[0] )) { // kibitz of self or opponent
3165 suppressKibitz = TRUE;
3166 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3168 if((StrStr(star_match[0], gameInfo.white) == star_match[0]
3169 && (gameMode == IcsPlayingWhite)) ||
3170 (StrStr(star_match[0], gameInfo.black) == star_match[0]
3171 && (gameMode == IcsPlayingBlack)) ) // opponent kibitz
3172 started = STARTED_CHATTER; // own kibitz we simply discard
3174 started = STARTED_COMMENT; // make sure it will be collected in parse[]
3175 parse_pos = 0; parse[0] = NULLCHAR;
3176 savingComment = TRUE;
3177 suppressKibitz = gameMode != IcsObserving ? 2 :
3178 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
3182 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
3183 looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
3184 && atoi(star_match[0])) {
3185 // suppress the acknowledgements of our own autoKibitz
3187 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3188 if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
3189 SendToPlayer(star_match[0], strlen(star_match[0]));
3190 if(looking_at(buf, &i, "*% ")) // eat prompt
3191 suppressKibitz = FALSE;
3195 } // [HGM] kibitz: end of patch
3197 if(looking_at(buf, &i, "* rating adjustment: * --> *\n")) continue;
3199 // [HGM] chat: intercept tells by users for which we have an open chat window
3201 if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
3202 looking_at(buf, &i, "* whispers:") ||
3203 looking_at(buf, &i, "* kibitzes:") ||
3204 looking_at(buf, &i, "* shouts:") ||
3205 looking_at(buf, &i, "* c-shouts:") ||
3206 looking_at(buf, &i, "--> * ") ||
3207 looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
3208 looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
3209 looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
3210 looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
3212 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
3213 chattingPartner = -1;
3215 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
3216 for(p=0; p<MAX_CHAT; p++) {
3217 if(chatPartner[p][0] >= '0' && chatPartner[p][0] <= '9' && channel == atoi(chatPartner[p])) {
3218 talker[0] = '['; strcat(talker, "] ");
3219 Colorize(channel == 1 ? ColorChannel1 : ColorChannel, FALSE);
3220 chattingPartner = p; break;
3223 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
3224 for(p=0; p<MAX_CHAT; p++) {
3225 if(!strcmp("kibitzes", chatPartner[p])) {
3226 talker[0] = '['; strcat(talker, "] ");
3227 chattingPartner = p; break;
3230 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
3231 for(p=0; p<MAX_CHAT; p++) {
3232 if(!strcmp("whispers", chatPartner[p])) {
3233 talker[0] = '['; strcat(talker, "] ");
3234 chattingPartner = p; break;
3237 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
3238 if(buf[i-8] == '-' && buf[i-3] == 't')
3239 for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
3240 if(!strcmp("c-shouts", chatPartner[p])) {
3241 talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
3242 chattingPartner = p; break;
3245 if(chattingPartner < 0)
3246 for(p=0; p<MAX_CHAT; p++) {
3247 if(!strcmp("shouts", chatPartner[p])) {
3248 if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
3249 else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
3250 else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
3251 chattingPartner = p; break;
3255 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
3256 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3257 talker[0] = 0; Colorize(ColorTell, FALSE);
3258 chattingPartner = p; break;
3260 if(chattingPartner<0) i = oldi; else {
3261 Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
3262 if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
3263 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3264 started = STARTED_COMMENT;
3265 parse_pos = 0; parse[0] = NULLCHAR;
3266 savingComment = 3 + chattingPartner; // counts as TRUE
3267 suppressKibitz = TRUE;
3270 } // [HGM] chat: end of patch
3273 if (appData.zippyTalk || appData.zippyPlay) {
3274 /* [DM] Backup address for color zippy lines */
3276 if (loggedOn == TRUE)
3277 if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
3278 (appData.zippyPlay && ZippyMatch(buf, &backup)));
3280 } // [DM] 'else { ' deleted
3282 /* Regular tells and says */
3283 (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
3284 looking_at(buf, &i, "* (your partner) tells you: ") ||
3285 looking_at(buf, &i, "* says: ") ||
3286 /* Don't color "message" or "messages" output */
3287 (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
3288 looking_at(buf, &i, "*. * at *:*: ") ||
3289 looking_at(buf, &i, "--* (*:*): ") ||
3290 /* Message notifications (same color as tells) */
3291 looking_at(buf, &i, "* has left a message ") ||
3292 looking_at(buf, &i, "* just sent you a message:\n") ||
3293 /* Whispers and kibitzes */
3294 (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
3295 looking_at(buf, &i, "* kibitzes: ") ||
3297 (tkind = 3, looking_at(buf, &i, "*(*: "))) {
3299 if (tkind == 1 && strchr(star_match[0], ':')) {
3300 /* Avoid "tells you:" spoofs in channels */
3303 if (star_match[0][0] == NULLCHAR ||
3304 strchr(star_match[0], ' ') ||
3305 (tkind == 3 && strchr(star_match[1], ' '))) {
3306 /* Reject bogus matches */
3309 if (appData.colorize) {
3310 if (oldi > next_out) {
3311 SendToPlayer(&buf[next_out], oldi - next_out);
3316 Colorize(ColorTell, FALSE);
3317 curColor = ColorTell;
3320 Colorize(ColorKibitz, FALSE);
3321 curColor = ColorKibitz;
3324 p = strrchr(star_match[1], '(');
3331 Colorize(ColorChannel1, FALSE);
3332 curColor = ColorChannel1;
3334 Colorize(ColorChannel, FALSE);
3335 curColor = ColorChannel;
3339 curColor = ColorNormal;
3343 if (started == STARTED_NONE && appData.autoComment &&
3344 (gameMode == IcsObserving ||
3345 gameMode == IcsPlayingWhite ||
3346 gameMode == IcsPlayingBlack)) {
3347 parse_pos = i - oldi;
3348 memcpy(parse, &buf[oldi], parse_pos);
3349 parse[parse_pos] = NULLCHAR;
3350 started = STARTED_COMMENT;
3351 savingComment = TRUE;
3353 started = STARTED_CHATTER;
3354 savingComment = FALSE;
3361 if (looking_at(buf, &i, "* s-shouts: ") ||
3362 looking_at(buf, &i, "* c-shouts: ")) {
3363 if (appData.colorize) {
3364 if (oldi > next_out) {
3365 SendToPlayer(&buf[next_out], oldi - next_out);
3368 Colorize(ColorSShout, FALSE);
3369 curColor = ColorSShout;
3372 started = STARTED_CHATTER;
3376 if (looking_at(buf, &i, "--->")) {
3381 if (looking_at(buf, &i, "* shouts: ") ||
3382 looking_at(buf, &i, "--> ")) {
3383 if (appData.colorize) {
3384 if (oldi > next_out) {
3385 SendToPlayer(&buf[next_out], oldi - next_out);
3388 Colorize(ColorShout, FALSE);
3389 curColor = ColorShout;
3392 started = STARTED_CHATTER;
3396 if (looking_at( buf, &i, "Challenge:")) {
3397 if (appData.colorize) {
3398 if (oldi > next_out) {
3399 SendToPlayer(&buf[next_out], oldi - next_out);
3402 Colorize(ColorChallenge, FALSE);
3403 curColor = ColorChallenge;
3409 if (looking_at(buf, &i, "* offers you") ||
3410 looking_at(buf, &i, "* offers to be") ||
3411 looking_at(buf, &i, "* would like to") ||
3412 looking_at(buf, &i, "* requests to") ||
3413 looking_at(buf, &i, "Your opponent offers") ||
3414 looking_at(buf, &i, "Your opponent requests")) {
3416 if (appData.colorize) {
3417 if (oldi > next_out) {
3418 SendToPlayer(&buf[next_out], oldi - next_out);
3421 Colorize(ColorRequest, FALSE);
3422 curColor = ColorRequest;
3427 if (looking_at(buf, &i, "* (*) seeking")) {
3428 if (appData.colorize) {
3429 if (oldi > next_out) {
3430 SendToPlayer(&buf[next_out], oldi - next_out);
3433 Colorize(ColorSeek, FALSE);
3434 curColor = ColorSeek;
3439 if(i < backup) { i = backup; continue; } // [HGM] for if ZippyControl matches, but the colorie code doesn't
3441 if (looking_at(buf, &i, "\\ ")) {
3442 if (prevColor != ColorNormal) {
3443 if (oldi > next_out) {
3444 SendToPlayer(&buf[next_out], oldi - next_out);
3447 Colorize(prevColor, TRUE);
3448 curColor = prevColor;
3450 if (savingComment) {
3451 parse_pos = i - oldi;
3452 memcpy(parse, &buf[oldi], parse_pos);
3453 parse[parse_pos] = NULLCHAR;
3454 started = STARTED_COMMENT;
3455 if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3456 chattingPartner = savingComment - 3; // kludge to remember the box
3458 started = STARTED_CHATTER;
3463 if (looking_at(buf, &i, "Black Strength :") ||
3464 looking_at(buf, &i, "<<< style 10 board >>>") ||
3465 looking_at(buf, &i, "<10>") ||
3466 looking_at(buf, &i, "#@#")) {
3467 /* Wrong board style */
3469 SendToICS(ics_prefix);
3470 SendToICS("set style 12\n");
3471 SendToICS(ics_prefix);
3472 SendToICS("refresh\n");
3476 if (looking_at(buf, &i, "login:")) {
3477 if (!have_sent_ICS_logon) {
3479 have_sent_ICS_logon = 1;
3480 else // no init script was found
3481 have_sent_ICS_logon = (appData.autoCreateLogon ? 2 : 1); // flag that we should capture username + password
3482 } else { // we have sent (or created) the InitScript, but apparently the ICS rejected it
3483 have_sent_ICS_logon = (appData.autoCreateLogon ? 2 : 1); // request creation of a new script
3488 if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3489 (looking_at(buf, &i, "\n<12> ") ||
3490 looking_at(buf, &i, "<12> "))) {
3492 if (oldi > next_out) {
3493 SendToPlayer(&buf[next_out], oldi - next_out);
3496 started = STARTED_BOARD;
3501 if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3502 looking_at(buf, &i, "<b1> ")) {
3503 if (oldi > next_out) {
3504 SendToPlayer(&buf[next_out], oldi - next_out);
3507 started = STARTED_HOLDINGS;
3512 if (looking_at(buf, &i, "* *vs. * *--- *")) {
3514 /* Header for a move list -- first line */
3516 switch (ics_getting_history) {
3520 case BeginningOfGame:
3521 /* User typed "moves" or "oldmoves" while we
3522 were idle. Pretend we asked for these
3523 moves and soak them up so user can step
3524 through them and/or save them.
3527 gameMode = IcsObserving;
3530 ics_getting_history = H_GOT_UNREQ_HEADER;
3532 case EditGame: /*?*/
3533 case EditPosition: /*?*/
3534 /* Should above feature work in these modes too? */
3535 /* For now it doesn't */
3536 ics_getting_history = H_GOT_UNWANTED_HEADER;
3539 ics_getting_history = H_GOT_UNWANTED_HEADER;
3544 /* Is this the right one? */
3545 if (gameInfo.white && gameInfo.black &&
3546 strcmp(gameInfo.white, star_match[0]) == 0 &&
3547 strcmp(gameInfo.black, star_match[2]) == 0) {
3549 ics_getting_history = H_GOT_REQ_HEADER;
3552 case H_GOT_REQ_HEADER:
3553 case H_GOT_UNREQ_HEADER:
3554 case H_GOT_UNWANTED_HEADER:
3555 case H_GETTING_MOVES:
3556 /* Should not happen */
3557 DisplayError(_("Error gathering move list: two headers"), 0);
3558 ics_getting_history = H_FALSE;
3562 /* Save player ratings into gameInfo if needed */
3563 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3564 ics_getting_history == H_GOT_UNREQ_HEADER) &&
3565 (gameInfo.whiteRating == -1 ||
3566 gameInfo.blackRating == -1)) {
3568 gameInfo.whiteRating = string_to_rating(star_match[1]);
3569 gameInfo.blackRating = string_to_rating(star_match[3]);
3570 if (appData.debugMode)
3571 fprintf(debugFP, "Ratings from header: W %d, B %d\n",
3572 gameInfo.whiteRating, gameInfo.blackRating);
3577 if (looking_at(buf, &i,
3578 "* * match, initial time: * minute*, increment: * second")) {
3579 /* Header for a move list -- second line */
3580 /* Initial board will follow if this is a wild game */
3581 if (gameInfo.event != NULL) free(gameInfo.event);
3582 snprintf(str, MSG_SIZ, "ICS %s %s match", star_match[0], star_match[1]);
3583 gameInfo.event = StrSave(str);
3584 /* [HGM] we switched variant. Translate boards if needed. */
3585 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3589 if (looking_at(buf, &i, "Move ")) {
3590 /* Beginning of a move list */
3591 switch (ics_getting_history) {
3593 /* Normally should not happen */
3594 /* Maybe user hit reset while we were parsing */
3597 /* Happens if we are ignoring a move list that is not
3598 * the one we just requested. Common if the user
3599 * tries to observe two games without turning off
3602 case H_GETTING_MOVES:
3603 /* Should not happen */
3604 DisplayError(_("Error gathering move list: nested"), 0);
3605 ics_getting_history = H_FALSE;
3607 case H_GOT_REQ_HEADER:
3608 ics_getting_history = H_GETTING_MOVES;
3609 started = STARTED_MOVES;
3611 if (oldi > next_out) {
3612 SendToPlayer(&buf[next_out], oldi - next_out);
3615 case H_GOT_UNREQ_HEADER:
3616 ics_getting_history = H_GETTING_MOVES;
3617 started = STARTED_MOVES_NOHIDE;
3620 case H_GOT_UNWANTED_HEADER:
3621 ics_getting_history = H_FALSE;
3627 if (looking_at(buf, &i, "% ") ||
3628 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3629 && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3630 if(soughtPending && nrOfSeekAds) { // [HGM] seekgraph: on ICC sought-list has no termination line
3631 soughtPending = FALSE;
3635 if(suppressKibitz) next_out = i;
3636 savingComment = FALSE;
3640 case STARTED_MOVES_NOHIDE:
3641 memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3642 parse[parse_pos + i - oldi] = NULLCHAR;
3643 ParseGameHistory(parse);
3645 if (appData.zippyPlay && first.initDone) {
3646 FeedMovesToProgram(&first, forwardMostMove);
3647 if (gameMode == IcsPlayingWhite) {
3648 if (WhiteOnMove(forwardMostMove)) {
3649 if (first.sendTime) {
3650 if (first.useColors) {
3651 SendToProgram("black\n", &first);
3653 SendTimeRemaining(&first, TRUE);
3655 if (first.useColors) {
3656 SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3658 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3659 first.maybeThinking = TRUE;
3661 if (first.usePlayother) {
3662 if (first.sendTime) {
3663 SendTimeRemaining(&first, TRUE);
3665 SendToProgram("playother\n", &first);
3671 } else if (gameMode == IcsPlayingBlack) {
3672 if (!WhiteOnMove(forwardMostMove)) {
3673 if (first.sendTime) {
3674 if (first.useColors) {
3675 SendToProgram("white\n", &first);
3677 SendTimeRemaining(&first, FALSE);
3679 if (first.useColors) {
3680 SendToProgram("black\n", &first);
3682 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3683 first.maybeThinking = TRUE;
3685 if (first.usePlayother) {
3686 if (first.sendTime) {
3687 SendTimeRemaining(&first, FALSE);
3689 SendToProgram("playother\n", &first);
3698 if (gameMode == IcsObserving && ics_gamenum == -1) {
3699 /* Moves came from oldmoves or moves command
3700 while we weren't doing anything else.
3702 currentMove = forwardMostMove;
3703 ClearHighlights();/*!!could figure this out*/
3704 flipView = appData.flipView;
3705 DrawPosition(TRUE, boards[currentMove]);
3706 DisplayBothClocks();
3707 snprintf(str, MSG_SIZ, "%s %s %s",
3708 gameInfo.white, _("vs."), gameInfo.black);
3712 /* Moves were history of an active game */
3713 if (gameInfo.resultDetails != NULL) {
3714 free(gameInfo.resultDetails);
3715 gameInfo.resultDetails = NULL;
3718 HistorySet(parseList, backwardMostMove,
3719 forwardMostMove, currentMove-1);
3720 DisplayMove(currentMove - 1);
3721 if (started == STARTED_MOVES) next_out = i;
3722 started = STARTED_NONE;
3723 ics_getting_history = H_FALSE;
3726 case STARTED_OBSERVE:
3727 started = STARTED_NONE;
3728 SendToICS(ics_prefix);
3729 SendToICS("refresh\n");
3735 if(bookHit) { // [HGM] book: simulate book reply
3736 static char bookMove[MSG_SIZ]; // a bit generous?
3738 programStats.nodes = programStats.depth = programStats.time =
3739 programStats.score = programStats.got_only_move = 0;
3740 sprintf(programStats.movelist, "%s (xbook)", bookHit);
3742 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
3743 strcat(bookMove, bookHit);
3744 HandleMachineMove(bookMove, &first);
3749 if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3750 started == STARTED_HOLDINGS ||
3751 started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3752 /* Accumulate characters in move list or board */
3753 parse[parse_pos++] = buf[i];
3756 /* Start of game messages. Mostly we detect start of game
3757 when the first board image arrives. On some versions
3758 of the ICS, though, we need to do a "refresh" after starting
3759 to observe in order to get the current board right away. */
3760 if (looking_at(buf, &i, "Adding game * to observation list")) {
3761 started = STARTED_OBSERVE;
3765 /* Handle auto-observe */
3766 if (appData.autoObserve &&
3767 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3768 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3770 /* Choose the player that was highlighted, if any. */
3771 if (star_match[0][0] == '\033' ||
3772 star_match[1][0] != '\033') {
3773 player = star_match[0];
3775 player = star_match[2];
3777 snprintf(str, MSG_SIZ, "%sobserve %s\n",
3778 ics_prefix, StripHighlightAndTitle(player));
3781 /* Save ratings from notify string */
3782 safeStrCpy(player1Name, star_match[0], sizeof(player1Name)/sizeof(player1Name[0]));
3783 player1Rating = string_to_rating(star_match[1]);
3784 safeStrCpy(player2Name, star_match[2], sizeof(player2Name)/sizeof(player2Name[0]));
3785 player2Rating = string_to_rating(star_match[3]);
3787 if (appData.debugMode)
3789 "Ratings from 'Game notification:' %s %d, %s %d\n",
3790 player1Name, player1Rating,
3791 player2Name, player2Rating);
3796 /* Deal with automatic examine mode after a game,
3797 and with IcsObserving -> IcsExamining transition */
3798 if (looking_at(buf, &i, "Entering examine mode for game *") ||
3799 looking_at(buf, &i, "has made you an examiner of game *")) {
3801 int gamenum = atoi(star_match[0]);
3802 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3803 gamenum == ics_gamenum) {
3804 /* We were already playing or observing this game;
3805 no need to refetch history */
3806 gameMode = IcsExamining;
3808 pauseExamForwardMostMove = forwardMostMove;
3809 } else if (currentMove < forwardMostMove) {
3810 ForwardInner(forwardMostMove);
3813 /* I don't think this case really can happen */
3814 SendToICS(ics_prefix);
3815 SendToICS("refresh\n");
3820 /* Error messages */
3821 // if (ics_user_moved) {
3822 if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3823 if (looking_at(buf, &i, "Illegal move") ||
3824 looking_at(buf, &i, "Not a legal move") ||
3825 looking_at(buf, &i, "Your king is in check") ||
3826 looking_at(buf, &i, "It isn't your turn") ||
3827 looking_at(buf, &i, "It is not your move")) {
3829 if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3830 currentMove = forwardMostMove-1;
3831 DisplayMove(currentMove - 1); /* before DMError */
3832 DrawPosition(FALSE, boards[currentMove]);
3833 SwitchClocks(forwardMostMove-1); // [HGM] race
3834 DisplayBothClocks();
3836 DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3842 if (looking_at(buf, &i, "still have time") ||
3843 looking_at(buf, &i, "not out of time") ||
3844 looking_at(buf, &i, "either player is out of time") ||
3845 looking_at(buf, &i, "has timeseal; checking")) {
3846 /* We must have called his flag a little too soon */
3847 whiteFlag = blackFlag = FALSE;
3851 if (looking_at(buf, &i, "added * seconds to") ||
3852 looking_at(buf, &i, "seconds were added to")) {
3853 /* Update the clocks */
3854 SendToICS(ics_prefix);
3855 SendToICS("refresh\n");
3859 if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3860 ics_clock_paused = TRUE;
3865 if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3866 ics_clock_paused = FALSE;
3871 /* Grab player ratings from the Creating: message.
3872 Note we have to check for the special case when
3873 the ICS inserts things like [white] or [black]. */
3874 if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3875 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3877 0 player 1 name (not necessarily white)
3879 2 empty, white, or black (IGNORED)
3880 3 player 2 name (not necessarily black)
3883 The names/ratings are sorted out when the game
3884 actually starts (below).
3886 safeStrCpy(player1Name, StripHighlightAndTitle(star_match[0]), sizeof(player1Name)/sizeof(player1Name[0]));
3887 player1Rating = string_to_rating(star_match[1]);
3888 safeStrCpy(player2Name, StripHighlightAndTitle(star_match[3]), sizeof(player2Name)/sizeof(player2Name[0]));
3889 player2Rating = string_to_rating(star_match[4]);
3891 if (appData.debugMode)
3893 "Ratings from 'Creating:' %s %d, %s %d\n",
3894 player1Name, player1Rating,
3895 player2Name, player2Rating);
3900 /* Improved generic start/end-of-game messages */
3901 if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3902 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3903 /* If tkind == 0: */
3904 /* star_match[0] is the game number */
3905 /* [1] is the white player's name */
3906 /* [2] is the black player's name */
3907 /* For end-of-game: */
3908 /* [3] is the reason for the game end */
3909 /* [4] is a PGN end game-token, preceded by " " */
3910 /* For start-of-game: */
3911 /* [3] begins with "Creating" or "Continuing" */
3912 /* [4] is " *" or empty (don't care). */
3913 int gamenum = atoi(star_match[0]);
3914 char *whitename, *blackname, *why, *endtoken;
3915 ChessMove endtype = EndOfFile;
3918 whitename = star_match[1];
3919 blackname = star_match[2];
3920 why = star_match[3];
3921 endtoken = star_match[4];
3923 whitename = star_match[1];
3924 blackname = star_match[3];
3925 why = star_match[5];
3926 endtoken = star_match[6];
3929 /* Game start messages */
3930 if (strncmp(why, "Creating ", 9) == 0 ||
3931 strncmp(why, "Continuing ", 11) == 0) {
3932 gs_gamenum = gamenum;
3933 safeStrCpy(gs_kind, strchr(why, ' ') + 1,sizeof(gs_kind)/sizeof(gs_kind[0]));
3934 if(ics_gamenum == -1) // [HGM] only if we are not already involved in a game (because gin=1 sends us such messages)
3935 VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
3937 if (appData.zippyPlay) {
3938 ZippyGameStart(whitename, blackname);
3941 partnerBoardValid = FALSE; // [HGM] bughouse
3945 /* Game end messages */
3946 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3947 ics_gamenum != gamenum) {
3950 while (endtoken[0] == ' ') endtoken++;
3951 switch (endtoken[0]) {
3954 endtype = GameUnfinished;
3957 endtype = BlackWins;
3960 if (endtoken[1] == '/')
3961 endtype = GameIsDrawn;
3963 endtype = WhiteWins;
3966 GameEnds(endtype, why, GE_ICS);
3968 if (appData.zippyPlay && first.initDone) {
3969 ZippyGameEnd(endtype, why);
3970 if (first.pr == NoProc) {
3971 /* Start the next process early so that we'll
3972 be ready for the next challenge */
3973 StartChessProgram(&first);
3975 /* Send "new" early, in case this command takes
3976 a long time to finish, so that we'll be ready
3977 for the next challenge. */
3978 gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3982 if(appData.bgObserve && partnerBoardValid) DrawPosition(TRUE, partnerBoard);
3986 if (looking_at(buf, &i, "Removing game * from observation") ||
3987 looking_at(buf, &i, "no longer observing game *") ||
3988 looking_at(buf, &i, "Game * (*) has no examiners")) {
3989 if (gameMode == IcsObserving &&
3990 atoi(star_match[0]) == ics_gamenum)
3992 /* icsEngineAnalyze */
3993 if (appData.icsEngineAnalyze) {
4000 ics_user_moved = FALSE;
4005 if (looking_at(buf, &i, "no longer examining game *")) {
4006 if (gameMode == IcsExamining &&
4007 atoi(star_match[0]) == ics_gamenum)
4011 ics_user_moved = FALSE;
4016 /* Advance leftover_start past any newlines we find,
4017 so only partial lines can get reparsed */
4018 if (looking_at(buf, &i, "\n")) {
4019 prevColor = curColor;
4020 if (curColor != ColorNormal) {
4021 if (oldi > next_out) {
4022 SendToPlayer(&buf[next_out], oldi - next_out);
4025 Colorize(ColorNormal, FALSE);
4026 curColor = ColorNormal;
4028 if (started == STARTED_BOARD) {
4029 started = STARTED_NONE;
4030 parse[parse_pos] = NULLCHAR;
4031 ParseBoard12(parse);
4034 /* Send premove here */
4035 if (appData.premove) {
4037 if (currentMove == 0 &&
4038 gameMode == IcsPlayingWhite &&
4039 appData.premoveWhite) {
4040 snprintf(str, MSG_SIZ, "%s\n", appData.premoveWhiteText);
4041 if (appData.debugMode)
4042 fprintf(debugFP, "Sending premove:\n");
4044 } else if (currentMove == 1 &&
4045 gameMode == IcsPlayingBlack &&
4046 appData.premoveBlack) {
4047 snprintf(str, MSG_SIZ, "%s\n", appData.premoveBlackText);
4048 if (appData.debugMode)
4049 fprintf(debugFP, "Sending premove:\n");
4051 } else if (gotPremove) {
4053 ClearPremoveHighlights();
4054 if (appData.debugMode)
4055 fprintf(debugFP, "Sending premove:\n");
4056 UserMoveEvent(premoveFromX, premoveFromY,
4057 premoveToX, premoveToY,
4062 /* Usually suppress following prompt */
4063 if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
4064 while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
4065 if (looking_at(buf, &i, "*% ")) {
4066 savingComment = FALSE;
4071 } else if (started == STARTED_HOLDINGS) {
4073 char new_piece[MSG_SIZ];
4074 started = STARTED_NONE;
4075 parse[parse_pos] = NULLCHAR;
4076 if (appData.debugMode)
4077 fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
4078 parse, currentMove);
4079 if (sscanf(parse, " game %d", &gamenum) == 1) {
4080 if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game
4081 if (gameInfo.variant == VariantNormal) {
4082 /* [HGM] We seem to switch variant during a game!
4083 * Presumably no holdings were displayed, so we have
4084 * to move the position two files to the right to
4085 * create room for them!
4087 VariantClass newVariant;
4088 switch(gameInfo.boardWidth) { // base guess on board width
4089 case 9: newVariant = VariantShogi; break;
4090 case 10: newVariant = VariantGreat; break;
4091 default: newVariant = VariantCrazyhouse; break;
4093 VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4094 /* Get a move list just to see the header, which
4095 will tell us whether this is really bug or zh */
4096 if (ics_getting_history == H_FALSE) {
4097 ics_getting_history = H_REQUESTED;
4098 snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4102 new_piece[0] = NULLCHAR;
4103 sscanf(parse, "game %d white [%s black [%s <- %s",
4104 &gamenum, white_holding, black_holding,
4106 white_holding[strlen(white_holding)-1] = NULLCHAR;
4107 black_holding[strlen(black_holding)-1] = NULLCHAR;
4108 /* [HGM] copy holdings to board holdings area */
4109 CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
4110 CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
4111 boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
4113 if (appData.zippyPlay && first.initDone) {
4114 ZippyHoldings(white_holding, black_holding,
4118 if (tinyLayout || smallLayout) {
4119 char wh[16], bh[16];
4120 PackHolding(wh, white_holding);
4121 PackHolding(bh, black_holding);
4122 snprintf(str, MSG_SIZ, "[%s-%s] %s-%s", wh, bh,
4123 gameInfo.white, gameInfo.black);
4125 snprintf(str, MSG_SIZ, "%s [%s] %s %s [%s]",
4126 gameInfo.white, white_holding, _("vs."),
4127 gameInfo.black, black_holding);
4129 if(!partnerUp) // [HGM] bughouse: when peeking at partner game we already know what he captured...
4130 DrawPosition(FALSE, boards[currentMove]);
4132 } else if(appData.bgObserve) { // [HGM] bughouse: holdings of other game => background
4133 sscanf(parse, "game %d white [%s black [%s <- %s",
4134 &gamenum, white_holding, black_holding,
4136 white_holding[strlen(white_holding)-1] = NULLCHAR;
4137 black_holding[strlen(black_holding)-1] = NULLCHAR;
4138 /* [HGM] copy holdings to partner-board holdings area */
4139 CopyHoldings(partnerBoard, white_holding, WhitePawn);
4140 CopyHoldings(partnerBoard, black_holding, BlackPawn);
4141 if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual: always draw
4142 if(partnerUp) DrawPosition(FALSE, partnerBoard);
4143 if(twoBoards) { partnerUp = 0; flipView = !flipView; }
4146 /* Suppress following prompt */
4147 if (looking_at(buf, &i, "*% ")) {
4148 if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
4149 savingComment = FALSE;
4157 i++; /* skip unparsed character and loop back */
4160 if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
4161 // started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
4162 // SendToPlayer(&buf[next_out], i - next_out);
4163 started != STARTED_HOLDINGS && leftover_start > next_out) {
4164 SendToPlayer(&buf[next_out], leftover_start - next_out);
4168 leftover_len = buf_len - leftover_start;
4169 /* if buffer ends with something we couldn't parse,
4170 reparse it after appending the next read */
4172 } else if (count == 0) {
4173 RemoveInputSource(isr);
4174 DisplayFatalError(_("Connection closed by ICS"), 0, 0);
4176 DisplayFatalError(_("Error reading from ICS"), error, 1);
4181 /* Board style 12 looks like this:
4183 <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
4185 * The "<12> " is stripped before it gets to this routine. The two
4186 * trailing 0's (flip state and clock ticking) are later addition, and
4187 * some chess servers may not have them, or may have only the first.
4188 * Additional trailing fields may be added in the future.
4191 #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"
4193 #define RELATION_OBSERVING_PLAYED 0
4194 #define RELATION_OBSERVING_STATIC -2 /* examined, oldmoves, or smoves */
4195 #define RELATION_PLAYING_MYMOVE 1
4196 #define RELATION_PLAYING_NOTMYMOVE -1
4197 #define RELATION_EXAMINING 2
4198 #define RELATION_ISOLATED_BOARD -3
4199 #define RELATION_STARTING_POSITION -4 /* FICS only */
4202 ParseBoard12 (char *string)
4206 char *bookHit = NULL; // [HGM] book
4208 GameMode newGameMode;
4209 int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0;
4210 int j, k, n, moveNum, white_stren, black_stren, white_time, black_time;
4211 int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
4212 char to_play, board_chars[200];
4213 char move_str[MSG_SIZ], str[MSG_SIZ], elapsed_time[MSG_SIZ];
4214 char black[32], white[32];
4216 int prevMove = currentMove;
4219 int fromX, fromY, toX, toY;
4221 int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
4222 Boolean weird = FALSE, reqFlag = FALSE;
4224 fromX = fromY = toX = toY = -1;
4228 if (appData.debugMode)
4229 fprintf(debugFP, "Parsing board: %s\n", string);
4231 move_str[0] = NULLCHAR;
4232 elapsed_time[0] = NULLCHAR;
4233 { /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
4235 while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
4236 if(string[i] == ' ') { ranks++; files = 0; }
4238 if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
4241 for(j = 0; j <i; j++) board_chars[j] = string[j];
4242 board_chars[i] = '\0';
4245 n = sscanf(string, PATTERN, &to_play, &double_push,
4246 &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
4247 &gamenum, white, black, &relation, &basetime, &increment,
4248 &white_stren, &black_stren, &white_time, &black_time,
4249 &moveNum, str, elapsed_time, move_str, &ics_flip,
4253 snprintf(str, MSG_SIZ, _("Failed to parse board string:\n\"%s\""), string);
4254 DisplayError(str, 0);
4258 /* Convert the move number to internal form */
4259 moveNum = (moveNum - 1) * 2;
4260 if (to_play == 'B') moveNum++;
4261 if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
4262 DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
4268 case RELATION_OBSERVING_PLAYED:
4269 case RELATION_OBSERVING_STATIC:
4270 if (gamenum == -1) {
4271 /* Old ICC buglet */
4272 relation = RELATION_OBSERVING_STATIC;
4274 newGameMode = IcsObserving;
4276 case RELATION_PLAYING_MYMOVE:
4277 case RELATION_PLAYING_NOTMYMOVE:
4279 ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
4280 IcsPlayingWhite : IcsPlayingBlack;
4281 soughtPending =FALSE; // [HGM] seekgraph: solve race condition
4283 case RELATION_EXAMINING:
4284 newGameMode = IcsExamining;
4286 case RELATION_ISOLATED_BOARD:
4288 /* Just display this board. If user was doing something else,
4289 we will forget about it until the next board comes. */
4290 newGameMode = IcsIdle;
4292 case RELATION_STARTING_POSITION:
4293 newGameMode = gameMode;
4297 if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
4298 gameMode == IcsObserving && appData.dualBoard) // also allow use of second board for observing two games
4299 && newGameMode == IcsObserving && gamenum != ics_gamenum && appData.bgObserve) {
4300 // [HGM] bughouse: don't act on alien boards while we play. Just parse the board and save it */
4301 int fac = strchr(elapsed_time, '.') ? 1 : 1000;
4302 static int lastBgGame = -1;
4304 for (k = 0; k < ranks; k++) {
4305 for (j = 0; j < files; j++)
4306 board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4307 if(gameInfo.holdingsWidth > 1) {
4308 board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4309 board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4312 CopyBoard(partnerBoard, board);
4313 if(toSqr = strchr(str, '/')) { // extract highlights from long move
4314 partnerBoard[EP_STATUS-3] = toSqr[1] - AAA; // kludge: hide highlighting info in board
4315 partnerBoard[EP_STATUS-4] = toSqr[2] - ONE;
4316 } else partnerBoard[EP_STATUS-4] = partnerBoard[EP_STATUS-3] = -1;
4317 if(toSqr = strchr(str, '-')) {
4318 partnerBoard[EP_STATUS-1] = toSqr[1] - AAA;
4319 partnerBoard[EP_STATUS-2] = toSqr[2] - ONE;
4320 } else partnerBoard[EP_STATUS-1] = partnerBoard[EP_STATUS-2] = -1;
4321 if(appData.dualBoard && !twoBoards) { twoBoards = 1; InitDrawingSizes(-2,0); }
4322 if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual
4323 if(partnerUp) DrawPosition(FALSE, partnerBoard);
4325 DisplayWhiteClock(white_time*fac, to_play == 'W');
4326 DisplayBlackClock(black_time*fac, to_play != 'W');
4327 activePartner = to_play;
4328 if(gamenum != lastBgGame) {
4330 snprintf(buf, MSG_SIZ, "%s %s %s", white, _("vs."), black);
4333 lastBgGame = gamenum;
4334 activePartnerTime = to_play == 'W' ? white_time*fac : black_time*fac;
4335 partnerUp = 0; flipView = !flipView; } // [HGM] dual
4336 snprintf(partnerStatus, MSG_SIZ,"W: %d:%02d B: %d:%02d (%d-%d) %c", white_time*fac/60000, (white_time*fac%60000)/1000,
4337 (black_time*fac/60000), (black_time*fac%60000)/1000, white_stren, black_stren, to_play);
4338 if(!twoBoards) DisplayMessage(partnerStatus, "");
4339 partnerBoardValid = TRUE;
4343 if(appData.dualBoard && appData.bgObserve) {
4344 if((newGameMode == IcsPlayingWhite || newGameMode == IcsPlayingBlack) && moveNum == 1)
4345 SendToICS(ics_prefix), SendToICS("pobserve\n");
4346 else if(newGameMode == IcsObserving && (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
4348 snprintf(buf, MSG_SIZ, "%spobserve %s\n", ics_prefix, white);
4353 /* Modify behavior for initial board display on move listing
4356 switch (ics_getting_history) {
4360 case H_GOT_REQ_HEADER:
4361 case H_GOT_UNREQ_HEADER:
4362 /* This is the initial position of the current game */
4363 gamenum = ics_gamenum;
4364 moveNum = 0; /* old ICS bug workaround */
4365 if (to_play == 'B') {
4366 startedFromSetupPosition = TRUE;
4367 blackPlaysFirst = TRUE;
4369 if (forwardMostMove == 0) forwardMostMove = 1;
4370 if (backwardMostMove == 0) backwardMostMove = 1;
4371 if (currentMove == 0) currentMove = 1;
4373 newGameMode = gameMode;
4374 relation = RELATION_STARTING_POSITION; /* ICC needs this */
4376 case H_GOT_UNWANTED_HEADER:
4377 /* This is an initial board that we don't want */
4379 case H_GETTING_MOVES:
4380 /* Should not happen */
4381 DisplayError(_("Error gathering move list: extra board"), 0);
4382 ics_getting_history = H_FALSE;
4386 if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
4387 move_str[1] == '@' && !gameInfo.holdingsWidth ||
4388 weird && (int)gameInfo.variant < (int)VariantShogi) {
4389 /* [HGM] We seem to have switched variant unexpectedly
4390 * Try to guess new variant from board size
4392 VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
4393 if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
4394 if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
4395 if(ranks == 8 && files == 12) newVariant = VariantCourier; else
4396 if(ranks == 9 && files == 9) newVariant = VariantShogi; else
4397 if(ranks == 10 && files == 10) newVariant = VariantGrand; else
4398 if(!weird) newVariant = move_str[1] == '@' ? VariantCrazyhouse : VariantNormal;
4399 VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4400 /* Get a move list just to see the header, which
4401 will tell us whether this is really bug or zh */
4402 if (ics_getting_history == H_FALSE) {
4403 ics_getting_history = H_REQUESTED; reqFlag = TRUE;
4404 snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4409 /* Take action if this is the first board of a new game, or of a
4410 different game than is currently being displayed. */
4411 if (gamenum != ics_gamenum || newGameMode != gameMode ||
4412 relation == RELATION_ISOLATED_BOARD) {
4414 /* Forget the old game and get the history (if any) of the new one */
4415 if (gameMode != BeginningOfGame) {
4419 if (appData.autoRaiseBoard) BoardToTop();
4421 if (gamenum == -1) {
4422 newGameMode = IcsIdle;
4423 } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
4424 appData.getMoveList && !reqFlag) {
4425 /* Need to get game history */
4426 ics_getting_history = H_REQUESTED;
4427 snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4431 /* Initially flip the board to have black on the bottom if playing
4432 black or if the ICS flip flag is set, but let the user change
4433 it with the Flip View button. */
4434 flipView = appData.autoFlipView ?
4435 (newGameMode == IcsPlayingBlack) || ics_flip :
4438 /* Done with values from previous mode; copy in new ones */
4439 gameMode = newGameMode;
4441 ics_gamenum = gamenum;
4442 if (gamenum == gs_gamenum) {
4443 int klen = strlen(gs_kind);
4444 if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
4445 snprintf(str, MSG_SIZ, "ICS %s", gs_kind);
4446 gameInfo.event = StrSave(str);
4448 gameInfo.event = StrSave("ICS game");
4450 gameInfo.site = StrSave(appData.icsHost);
4451 gameInfo.date = PGNDate();
4452 gameInfo.round = StrSave("-");
4453 gameInfo.white = StrSave(white);
4454 gameInfo.black = StrSave(black);
4455 timeControl = basetime * 60 * 1000;
4457 timeIncrement = increment * 1000;
4458 movesPerSession = 0;
4459 gameInfo.timeControl = TimeControlTagValue();
4460 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
4461 if (appData.debugMode) {
4462 fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
4463 fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
4464 setbuf(debugFP, NULL);
4467 gameInfo.outOfBook = NULL;
4469 /* Do we have the ratings? */
4470 if (strcmp(player1Name, white) == 0 &&
4471 strcmp(player2Name, black) == 0) {
4472 if (appData.debugMode)
4473 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4474 player1Rating, player2Rating);
4475 gameInfo.whiteRating = player1Rating;
4476 gameInfo.blackRating = player2Rating;
4477 } else if (strcmp(player2Name, white) == 0 &&
4478 strcmp(player1Name, black) == 0) {
4479 if (appData.debugMode)
4480 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4481 player2Rating, player1Rating);
4482 gameInfo.whiteRating = player2Rating;
4483 gameInfo.blackRating = player1Rating;
4485 player1Name[0] = player2Name[0] = NULLCHAR;
4487 /* Silence shouts if requested */
4488 if (appData.quietPlay &&
4489 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
4490 SendToICS(ics_prefix);
4491 SendToICS("set shout 0\n");
4495 /* Deal with midgame name changes */
4497 if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
4498 if (gameInfo.white) free(gameInfo.white);
4499 gameInfo.white = StrSave(white);
4501 if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
4502 if (gameInfo.black) free(gameInfo.black);
4503 gameInfo.black = StrSave(black);
4507 /* Throw away game result if anything actually changes in examine mode */
4508 if (gameMode == IcsExamining && !newGame) {
4509 gameInfo.result = GameUnfinished;
4510 if (gameInfo.resultDetails != NULL) {
4511 free(gameInfo.resultDetails);
4512 gameInfo.resultDetails = NULL;
4516 /* In pausing && IcsExamining mode, we ignore boards coming
4517 in if they are in a different variation than we are. */
4518 if (pauseExamInvalid) return;
4519 if (pausing && gameMode == IcsExamining) {
4520 if (moveNum <= pauseExamForwardMostMove) {
4521 pauseExamInvalid = TRUE;
4522 forwardMostMove = pauseExamForwardMostMove;
4527 if (appData.debugMode) {
4528 fprintf(debugFP, "load %dx%d board\n", files, ranks);
4530 /* Parse the board */
4531 for (k = 0; k < ranks; k++) {
4532 for (j = 0; j < files; j++)
4533 board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4534 if(gameInfo.holdingsWidth > 1) {
4535 board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4536 board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4539 if(moveNum==0 && gameInfo.variant == VariantSChess) {
4540 board[5][BOARD_RGHT+1] = WhiteAngel;
4541 board[6][BOARD_RGHT+1] = WhiteMarshall;
4542 board[1][0] = BlackMarshall;
4543 board[2][0] = BlackAngel;
4544 board[1][1] = board[2][1] = board[5][BOARD_RGHT] = board[6][BOARD_RGHT] = 1;
4546 CopyBoard(boards[moveNum], board);
4547 boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
4549 startedFromSetupPosition =
4550 !CompareBoards(board, initialPosition);
4551 if(startedFromSetupPosition)
4552 initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
4555 /* [HGM] Set castling rights. Take the outermost Rooks,
4556 to make it also work for FRC opening positions. Note that board12
4557 is really defective for later FRC positions, as it has no way to
4558 indicate which Rook can castle if they are on the same side of King.
4559 For the initial position we grant rights to the outermost Rooks,
4560 and remember thos rights, and we then copy them on positions
4561 later in an FRC game. This means WB might not recognize castlings with
4562 Rooks that have moved back to their original position as illegal,
4563 but in ICS mode that is not its job anyway.
4565 if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
4566 { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
4568 for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4569 if(board[0][i] == WhiteRook) j = i;
4570 initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4571 for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4572 if(board[0][i] == WhiteRook) j = i;
4573 initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4574 for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4575 if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4576 initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4577 for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4578 if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4579 initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4581 boards[moveNum][CASTLING][2] = boards[moveNum][CASTLING][5] = NoRights;
4582 if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
4583 for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4584 if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
4585 for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4586 if(board[BOARD_HEIGHT-1][k] == bKing)
4587 initialRights[5] = boards[moveNum][CASTLING][5] = k;
4588 if(gameInfo.variant == VariantTwoKings) {
4589 // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
4590 if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
4591 if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
4594 r = boards[moveNum][CASTLING][0] = initialRights[0];
4595 if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4596 r = boards[moveNum][CASTLING][1] = initialRights[1];
4597 if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4598 r = boards[moveNum][CASTLING][3] = initialRights[3];
4599 if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4600 r = boards[moveNum][CASTLING][4] = initialRights[4];
4601 if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4602 /* wildcastle kludge: always assume King has rights */
4603 r = boards[moveNum][CASTLING][2] = initialRights[2];
4604 r = boards[moveNum][CASTLING][5] = initialRights[5];
4606 /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4607 boards[moveNum][EP_STATUS] = EP_NONE;
4608 if(str[0] == 'P') boards[moveNum][EP_STATUS] = EP_PAWN_MOVE;
4609 if(strchr(move_str, 'x')) boards[moveNum][EP_STATUS] = EP_CAPTURE;
4610 if(double_push != -1) boards[moveNum][EP_STATUS] = double_push + BOARD_LEFT;
4613 if (ics_getting_history == H_GOT_REQ_HEADER ||
4614 ics_getting_history == H_GOT_UNREQ_HEADER) {
4615 /* This was an initial position from a move list, not
4616 the current position */
4620 /* Update currentMove and known move number limits */
4621 newMove = newGame || moveNum > forwardMostMove;
4624 forwardMostMove = backwardMostMove = currentMove = moveNum;
4625 if (gameMode == IcsExamining && moveNum == 0) {
4626 /* Workaround for ICS limitation: we are not told the wild
4627 type when starting to examine a game. But if we ask for
4628 the move list, the move list header will tell us */
4629 ics_getting_history = H_REQUESTED;
4630 snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4633 } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4634 || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4636 /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4637 /* [HGM] applied this also to an engine that is silently watching */
4638 if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4639 (gameMode == IcsObserving || gameMode == IcsExamining) &&
4640 gameInfo.variant == currentlyInitializedVariant) {
4641 takeback = forwardMostMove - moveNum;
4642 for (i = 0; i < takeback; i++) {
4643 if (appData.debugMode) fprintf(debugFP, "take back move\n");
4644 SendToProgram("undo\n", &first);
4649 forwardMostMove = moveNum;
4650 if (!pausing || currentMove > forwardMostMove)
4651 currentMove = forwardMostMove;
4653 /* New part of history that is not contiguous with old part */
4654 if (pausing && gameMode == IcsExamining) {
4655 pauseExamInvalid = TRUE;
4656 forwardMostMove = pauseExamForwardMostMove;
4659 if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4661 if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4662 // [HGM] when we will receive the move list we now request, it will be
4663 // fed to the engine from the first move on. So if the engine is not
4664 // in the initial position now, bring it there.
4665 InitChessProgram(&first, 0);
4668 ics_getting_history = H_REQUESTED;
4669 snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4672 forwardMostMove = backwardMostMove = currentMove = moveNum;
4675 /* Update the clocks */
4676 if (strchr(elapsed_time, '.')) {
4678 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4679 timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4681 /* Time is in seconds */
4682 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4683 timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4688 if (appData.zippyPlay && newGame &&
4689 gameMode != IcsObserving && gameMode != IcsIdle &&
4690 gameMode != IcsExamining)
4691 ZippyFirstBoard(moveNum, basetime, increment);
4694 /* Put the move on the move list, first converting
4695 to canonical algebraic form. */
4697 if (appData.debugMode) {
4698 int f = forwardMostMove;
4699 fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4700 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4701 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4702 fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4703 fprintf(debugFP, "moveNum = %d\n", moveNum);
4704 fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4705 setbuf(debugFP, NULL);
4707 if (moveNum <= backwardMostMove) {
4708 /* We don't know what the board looked like before
4710 safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4711 strcat(parseList[moveNum - 1], " ");
4712 strcat(parseList[moveNum - 1], elapsed_time);
4713 moveList[moveNum - 1][0] = NULLCHAR;
4714 } else if (strcmp(move_str, "none") == 0) {
4715 // [HGM] long SAN: swapped order; test for 'none' before parsing move
4716 /* Again, we don't know what the board looked like;
4717 this is really the start of the game. */
4718 parseList[moveNum - 1][0] = NULLCHAR;
4719 moveList[moveNum - 1][0] = NULLCHAR;
4720 backwardMostMove = moveNum;
4721 startedFromSetupPosition = TRUE;
4722 fromX = fromY = toX = toY = -1;
4724 // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
4725 // So we parse the long-algebraic move string in stead of the SAN move
4726 int valid; char buf[MSG_SIZ], *prom;
4728 if(gameInfo.variant == VariantShogi && !strchr(move_str, '=') && !strchr(move_str, '@'))
4729 strcat(move_str, "="); // if ICS does not say 'promote' on non-drop, we defer.
4730 // str looks something like "Q/a1-a2"; kill the slash
4732 snprintf(buf, MSG_SIZ,"%c%s", str[0], str+2);
4733 else safeStrCpy(buf, str, sizeof(buf)/sizeof(buf[0])); // might be castling
4734 if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
4735 strcat(buf, prom); // long move lacks promo specification!
4736 if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4737 if(appData.debugMode)
4738 fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4739 safeStrCpy(move_str, buf, MSG_SIZ);
4741 valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4742 &fromX, &fromY, &toX, &toY, &promoChar)
4743 || ParseOneMove(buf, moveNum - 1, &moveType,
4744 &fromX, &fromY, &toX, &toY, &promoChar);
4745 // end of long SAN patch
4747 (void) CoordsToAlgebraic(boards[moveNum - 1],
4748 PosFlags(moveNum - 1),
4749 fromY, fromX, toY, toX, promoChar,
4750 parseList[moveNum-1]);
4751 switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4757 if(gameInfo.variant != VariantShogi)
4758 strcat(parseList[moveNum - 1], "+");
4761 case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4762 strcat(parseList[moveNum - 1], "#");
4765 strcat(parseList[moveNum - 1], " ");
4766 strcat(parseList[moveNum - 1], elapsed_time);
4767 /* currentMoveString is set as a side-effect of ParseOneMove */
4768 if(gameInfo.variant == VariantShogi && currentMoveString[4]) currentMoveString[4] = '^';
4769 safeStrCpy(moveList[moveNum - 1], currentMoveString, sizeof(moveList[moveNum - 1])/sizeof(moveList[moveNum - 1][0]));
4770 strcat(moveList[moveNum - 1], "\n");
4772 if(gameInfo.holdingsWidth && !appData.disguise && gameInfo.variant != VariantSuper && gameInfo.variant != VariantGreat
4773 && gameInfo.variant != VariantGrand&& gameInfo.variant != VariantSChess) // inherit info that ICS does not give from previous board
4774 for(k=0; k<ranks; k++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
4775 ChessSquare old, new = boards[moveNum][k][j];
4776 if(fromY == DROP_RANK && k==toY && j==toX) continue; // dropped pieces always stand for themselves
4777 old = (k==toY && j==toX) ? boards[moveNum-1][fromY][fromX] : boards[moveNum-1][k][j]; // trace back mover
4778 if(old == new) continue;
4779 if(old == PROMOTED new) boards[moveNum][k][j] = old; // prevent promoted pieces to revert to primordial ones
4780 else if(new == WhiteWazir || new == BlackWazir) {
4781 if(old < WhiteCannon || old >= BlackPawn && old < BlackCannon)
4782 boards[moveNum][k][j] = PROMOTED old; // choose correct type of Gold in promotion
4783 else boards[moveNum][k][j] = old; // preserve type of Gold
4784 } else if((old == WhitePawn || old == BlackPawn) && new != EmptySquare) // Pawn promotions (but not e.p.capture!)
4785 boards[moveNum][k][j] = PROMOTED new; // use non-primordial representation of chosen piece
4788 /* Move from ICS was illegal!? Punt. */
4789 if (appData.debugMode) {
4790 fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4791 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4793 safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4794 strcat(parseList[moveNum - 1], " ");
4795 strcat(parseList[moveNum - 1], elapsed_time);
4796 moveList[moveNum - 1][0] = NULLCHAR;
4797 fromX = fromY = toX = toY = -1;
4800 if (appData.debugMode) {
4801 fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4802 setbuf(debugFP, NULL);
4806 /* Send move to chess program (BEFORE animating it). */
4807 if (appData.zippyPlay && !newGame && newMove &&
4808 (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4810 if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4811 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4812 if (moveList[moveNum - 1][0] == NULLCHAR) {
4813 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"),
4815 DisplayError(str, 0);
4817 if (first.sendTime) {
4818 SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4820 bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4821 if (firstMove && !bookHit) {
4823 if (first.useColors) {
4824 SendToProgram(gameMode == IcsPlayingWhite ?
4826 "black\ngo\n", &first);
4828 SendToProgram("go\n", &first);
4830 first.maybeThinking = TRUE;
4833 } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4834 if (moveList[moveNum - 1][0] == NULLCHAR) {
4835 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"), move_str);
4836 DisplayError(str, 0);
4838 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4839 SendMoveToProgram(moveNum - 1, &first);
4846 if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4847 /* If move comes from a remote source, animate it. If it
4848 isn't remote, it will have already been animated. */
4849 if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4850 AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4852 if (!pausing && appData.highlightLastMove) {
4853 SetHighlights(fromX, fromY, toX, toY);
4857 /* Start the clocks */
4858 whiteFlag = blackFlag = FALSE;
4859 appData.clockMode = !(basetime == 0 && increment == 0);
4861 ics_clock_paused = TRUE;
4863 } else if (ticking == 1) {
4864 ics_clock_paused = FALSE;
4866 if (gameMode == IcsIdle ||
4867 relation == RELATION_OBSERVING_STATIC ||
4868 relation == RELATION_EXAMINING ||
4870 DisplayBothClocks();
4874 /* Display opponents and material strengths */
4875 if (gameInfo.variant != VariantBughouse &&
4876 gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4877 if (tinyLayout || smallLayout) {
4878 if(gameInfo.variant == VariantNormal)
4879 snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d}",
4880 gameInfo.white, white_stren, gameInfo.black, black_stren,
4881 basetime, increment);
4883 snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d w%d}",
4884 gameInfo.white, white_stren, gameInfo.black, black_stren,
4885 basetime, increment, (int) gameInfo.variant);
4887 if(gameInfo.variant == VariantNormal)
4888 snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d}",
4889 gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
4890 basetime, increment);
4892 snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d %s}",
4893 gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
4894 basetime, increment, VariantName(gameInfo.variant));
4897 if (appData.debugMode) {
4898 fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
4903 /* Display the board */
4904 if (!pausing && !appData.noGUI) {
4906 if (appData.premove)
4908 ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
4909 ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
4910 ClearPremoveHighlights();
4912 j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
4913 if(partnerUp) { flipView = originalFlip; partnerUp = FALSE; j = TRUE; } // [HGM] bughouse: restore view
4914 DrawPosition(j, boards[currentMove]);
4916 DisplayMove(moveNum - 1);
4917 if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4918 !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4919 (gameMode == IcsPlayingBlack) && (WhiteOnMove(moveNum)) ) ) {
4920 if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4924 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4926 if(bookHit) { // [HGM] book: simulate book reply
4927 static char bookMove[MSG_SIZ]; // a bit generous?
4929 programStats.nodes = programStats.depth = programStats.time =
4930 programStats.score = programStats.got_only_move = 0;
4931 sprintf(programStats.movelist, "%s (xbook)", bookHit);
4933 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
4934 strcat(bookMove, bookHit);
4935 HandleMachineMove(bookMove, &first);
4944 if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4945 ics_getting_history = H_REQUESTED;
4946 snprintf(buf, MSG_SIZ, "%smoves %d\n", ics_prefix, ics_gamenum);
4952 SendToBoth (char *msg)
4953 { // to make it easy to keep two engines in step in dual analysis
4954 SendToProgram(msg, &first);
4955 if(second.analyzing) SendToProgram(msg, &second);
4959 AnalysisPeriodicEvent (int force)
4961 if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4962 && !force) || !appData.periodicUpdates)
4965 /* Send . command to Crafty to collect stats */
4968 /* Don't send another until we get a response (this makes
4969 us stop sending to old Crafty's which don't understand
4970 the "." command (sending illegal cmds resets node count & time,
4971 which looks bad)) */
4972 programStats.ok_to_send = 0;
4976 ics_update_width (int new_width)
4978 ics_printf("set width %d\n", new_width);
4982 SendMoveToProgram (int moveNum, ChessProgramState *cps)
4986 if(moveList[moveNum][1] == '@' && moveList[moveNum][0] == '@') {
4987 // null move in variant where engine does not understand it (for analysis purposes)
4988 SendBoard(cps, moveNum + 1); // send position after move in stead.
4991 if (cps->useUsermove) {
4992 SendToProgram("usermove ", cps);
4996 if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4997 int len = space - parseList[moveNum];
4998 memcpy(buf, parseList[moveNum], len);
5000 buf[len] = NULLCHAR;
5002 snprintf(buf, MSG_SIZ,"%s\n", parseList[moveNum]);
5004 SendToProgram(buf, cps);
5006 if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
5007 AlphaRank(moveList[moveNum], 4);
5008 SendToProgram(moveList[moveNum], cps);
5009 AlphaRank(moveList[moveNum], 4); // and back
5011 /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
5012 * the engine. It would be nice to have a better way to identify castle
5014 if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
5015 && cps->useOOCastle) {
5016 int fromX = moveList[moveNum][0] - AAA;
5017 int fromY = moveList[moveNum][1] - ONE;
5018 int toX = moveList[moveNum][2] - AAA;
5019 int toY = moveList[moveNum][3] - ONE;
5020 if((boards[moveNum][fromY][fromX] == WhiteKing
5021 && boards[moveNum][toY][toX] == WhiteRook)
5022 || (boards[moveNum][fromY][fromX] == BlackKing
5023 && boards[moveNum][toY][toX] == BlackRook)) {
5024 if(toX > fromX) SendToProgram("O-O\n", cps);
5025 else SendToProgram("O-O-O\n", cps);
5027 else SendToProgram(moveList[moveNum], cps);
5029 if(BOARD_HEIGHT > 10) { // [HGM] big: convert ranks to double-digit where needed
5030 if(moveList[moveNum][1] == '@' && (BOARD_HEIGHT < 16 || moveList[moveNum][0] <= 'Z')) { // drop move
5031 if(moveList[moveNum][0]== '@') snprintf(buf, MSG_SIZ, "@@@@\n"); else
5032 snprintf(buf, MSG_SIZ, "%c@%c%d%s", moveList[moveNum][0],
5033 moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
5035 snprintf(buf, MSG_SIZ, "%c%d%c%d%s", moveList[moveNum][0], moveList[moveNum][1] - '0',
5036 moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
5037 SendToProgram(buf, cps);
5039 else SendToProgram(moveList[moveNum], cps);
5040 /* End of additions by Tord */
5043 /* [HGM] setting up the opening has brought engine in force mode! */
5044 /* Send 'go' if we are in a mode where machine should play. */
5045 if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
5046 (gameMode == TwoMachinesPlay ||
5048 gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite ||
5050 gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
5051 SendToProgram("go\n", cps);
5052 if (appData.debugMode) {
5053 fprintf(debugFP, "(extra)\n");
5056 setboardSpoiledMachineBlack = 0;
5060 SendMoveToICS (ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar)
5062 char user_move[MSG_SIZ];
5065 if(gameInfo.variant == VariantSChess && promoChar) {
5066 snprintf(suffix, 4, "=%c", toX == BOARD_WIDTH<<1 ? ToUpper(promoChar) : ToLower(promoChar));
5067 if(moveType == NormalMove) moveType = WhitePromotion; // kludge to do gating
5068 } else suffix[0] = NULLCHAR;
5072 snprintf(user_move, MSG_SIZ, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
5073 (int)moveType, fromX, fromY, toX, toY);
5074 DisplayError(user_move + strlen("say "), 0);
5076 case WhiteKingSideCastle:
5077 case BlackKingSideCastle:
5078 case WhiteQueenSideCastleWild:
5079 case BlackQueenSideCastleWild:
5081 case WhiteHSideCastleFR:
5082 case BlackHSideCastleFR:
5084 snprintf(user_move, MSG_SIZ, "o-o%s\n", suffix);
5086 case WhiteQueenSideCastle:
5087 case BlackQueenSideCastle:
5088 case WhiteKingSideCastleWild:
5089 case BlackKingSideCastleWild:
5091 case WhiteASideCastleFR:
5092 case BlackASideCastleFR:
5094 snprintf(user_move, MSG_SIZ, "o-o-o%s\n",suffix);
5096 case WhiteNonPromotion:
5097 case BlackNonPromotion:
5098 sprintf(user_move, "%c%c%c%c==\n", AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5100 case WhitePromotion:
5101 case BlackPromotion:
5102 if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
5103 gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN)
5104 snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5105 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5106 PieceToChar(WhiteFerz));
5107 else if(gameInfo.variant == VariantGreat)
5108 snprintf(user_move, MSG_SIZ,"%c%c%c%c=%c\n",
5109 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5110 PieceToChar(WhiteMan));
5112 snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5113 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5119 snprintf(user_move, MSG_SIZ, "%c@%c%c\n",
5120 ToUpper(PieceToChar((ChessSquare) fromX)),
5121 AAA + toX, ONE + toY);
5123 case IllegalMove: /* could be a variant we don't quite understand */
5124 if(fromY == DROP_RANK) goto drop; // We need 'IllegalDrop' move type?
5126 case WhiteCapturesEnPassant:
5127 case BlackCapturesEnPassant:
5128 snprintf(user_move, MSG_SIZ,"%c%c%c%c\n",
5129 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5132 SendToICS(user_move);
5133 if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
5134 ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
5139 { // [HGM] upload: send entire stored game to ICS as long-algebraic moves.
5140 int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
5141 static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
5142 if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
5143 DisplayError(_("You cannot do this while you are playing or observing"), 0);
5146 if(gameMode != IcsExamining) { // is this ever not the case?
5147 char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
5149 if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
5150 snprintf(command,MSG_SIZ, "match %s", ics_handle);
5151 } else { // on FICS we must first go to general examine mode
5152 safeStrCpy(command, "examine\nbsetup", sizeof(command)/sizeof(command[0])); // and specify variant within it with bsetups
5154 if(gameInfo.variant != VariantNormal) {
5155 // try figure out wild number, as xboard names are not always valid on ICS
5156 for(i=1; i<=36; i++) {
5157 snprintf(buf, MSG_SIZ, "wild/%d", i);
5158 if(StringToVariant(buf) == gameInfo.variant) break;
5160 if(i<=36 && ics_type == ICS_ICC) snprintf(buf, MSG_SIZ,"%s w%d\n", command, i);
5161 else if(i == 22) snprintf(buf,MSG_SIZ, "%s fr\n", command);
5162 else snprintf(buf, MSG_SIZ,"%s %s\n", command, VariantName(gameInfo.variant));
5163 } else snprintf(buf, MSG_SIZ,"%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
5164 SendToICS(ics_prefix);
5166 if(startedFromSetupPosition || backwardMostMove != 0) {
5167 fen = PositionToFEN(backwardMostMove, NULL);
5168 if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
5169 snprintf(buf, MSG_SIZ,"loadfen %s\n", fen);
5171 } else { // FICS: everything has to set by separate bsetup commands
5172 p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board
5173 snprintf(buf, MSG_SIZ,"bsetup fen %s\n", fen);
5175 if(!WhiteOnMove(backwardMostMove)) {
5176 SendToICS("bsetup tomove black\n");
5178 i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL);
5179 snprintf(buf, MSG_SIZ,"bsetup wcastle %s\n", castlingStrings[i]);
5181 i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
5182 snprintf(buf, MSG_SIZ, "bsetup bcastle %s\n", castlingStrings[i]);
5184 i = boards[backwardMostMove][EP_STATUS];
5185 if(i >= 0) { // set e.p.
5186 snprintf(buf, MSG_SIZ,"bsetup eppos %c\n", i+AAA);
5192 if(bsetup || ics_type != ICS_ICC && gameInfo.variant != VariantNormal)
5193 SendToICS("bsetup done\n"); // switch to normal examining.
5195 for(i = backwardMostMove; i<last; i++) {
5197 snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s\n", parseList[i]);
5198 if((*buf == 'b' || *buf == 'B') && buf[1] == 'x') { // work-around for stupid FICS bug, which thinks bxc3 can be a Bishop move
5199 int len = strlen(moveList[i]);
5200 snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s", moveList[i]); // use long algebraic
5201 if(!isdigit(buf[len-2])) snprintf(buf+len-2, 20-len, "=%c\n", ToUpper(buf[len-2])); // promotion must have '=' in ICS format
5205 SendToICS(ics_prefix);
5206 SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
5210 CoordsToComputerAlgebraic (int rf, int ff, int rt, int ft, char promoChar, char move[7])
5212 if (rf == DROP_RANK) {
5213 if(ff == EmptySquare) sprintf(move, "@@@@\n"); else // [HGM] pass
5214 sprintf(move, "%c@%c%c\n",
5215 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
5217 if (promoChar == 'x' || promoChar == NULLCHAR) {
5218 sprintf(move, "%c%c%c%c\n",
5219 AAA + ff, ONE + rf, AAA + ft, ONE + rt);
5221 sprintf(move, "%c%c%c%c%c\n",
5222 AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
5228 ProcessICSInitScript (FILE *f)
5232 while (fgets(buf, MSG_SIZ, f)) {
5233 SendToICSDelayed(buf,(long)appData.msLoginDelay);
5240 static int lastX, lastY, selectFlag, dragging;
5245 ChessSquare king = WhiteKing, pawn = WhitePawn, last = promoSweep;
5246 if(gameInfo.variant == VariantKnightmate) king = WhiteUnicorn;
5247 if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway) king = EmptySquare;
5248 if(promoSweep >= BlackPawn) king = WHITE_TO_BLACK king, pawn = WHITE_TO_BLACK pawn;
5249 if(gameInfo.variant == VariantSpartan && pawn == BlackPawn) pawn = BlackLance, king = EmptySquare;
5250 if(fromY != BOARD_HEIGHT-2 && fromY != 1) pawn = EmptySquare;
5253 if(promoSweep == EmptySquare) promoSweep = BlackPawn; // wrap
5254 else if((int)promoSweep == -1) promoSweep = WhiteKing;
5255 else if(promoSweep == BlackPawn && step < 0) promoSweep = WhitePawn;
5256 else if(promoSweep == WhiteKing && step > 0) promoSweep = BlackKing;
5257 if(!step) step = -1;
5258 } while(PieceToChar(promoSweep) == '.' || PieceToChar(promoSweep) == '~' || promoSweep == pawn ||
5259 appData.testLegality && (promoSweep == king ||
5260 gameInfo.variant == VariantShogi && promoSweep != PROMOTED last && last != PROMOTED promoSweep && last != promoSweep));
5262 int victim = boards[currentMove][toY][toX];
5263 boards[currentMove][toY][toX] = promoSweep;
5264 DrawPosition(FALSE, boards[currentMove]);
5265 boards[currentMove][toY][toX] = victim;
5267 ChangeDragPiece(promoSweep);
5271 PromoScroll (int x, int y)
5275 if(promoSweep == EmptySquare || !appData.sweepSelect) return FALSE;
5276 if(abs(x - lastX) < 25 && abs(y - lastY) < 25) return FALSE;
5277 if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5278 if(!step) return FALSE;
5279 lastX = x; lastY = y;
5280 if((promoSweep < BlackPawn) == flipView) step = -step;
5281 if(step > 0) selectFlag = 1;
5282 if(!selectFlag) Sweep(step);
5287 NextPiece (int step)
5289 ChessSquare piece = boards[currentMove][toY][toX];
5292 if(pieceSweep == EmptySquare) pieceSweep = WhitePawn; // wrap
5293 if((int)pieceSweep == -1) pieceSweep = BlackKing;
5294 if(!step) step = -1;
5295 } while(PieceToChar(pieceSweep) == '.');
5296 boards[currentMove][toY][toX] = pieceSweep;
5297 DrawPosition(FALSE, boards[currentMove]);
5298 boards[currentMove][toY][toX] = piece;
5300 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
5302 AlphaRank (char *move, int n)
5304 // char *p = move, c; int x, y;
5306 if (appData.debugMode) {
5307 fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
5311 move[2]>='0' && move[2]<='9' &&
5312 move[3]>='a' && move[3]<='x' ) {
5314 move[2] = BOARD_RGHT -1 - (move[2]-'1') + AAA;
5315 move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5317 if(move[0]>='0' && move[0]<='9' &&
5318 move[1]>='a' && move[1]<='x' &&
5319 move[2]>='0' && move[2]<='9' &&
5320 move[3]>='a' && move[3]<='x' ) {
5321 /* input move, Shogi -> normal */
5322 move[0] = BOARD_RGHT -1 - (move[0]-'1') + AAA;
5323 move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
5324 move[2] = BOARD_RGHT -1 - (move[2]-'1') + AAA;
5325 move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5328 move[3]>='0' && move[3]<='9' &&
5329 move[2]>='a' && move[2]<='x' ) {
5331 move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5332 move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5335 move[0]>='a' && move[0]<='x' &&
5336 move[3]>='0' && move[3]<='9' &&
5337 move[2]>='a' && move[2]<='x' ) {
5338 /* output move, normal -> Shogi */
5339 move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
5340 move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
5341 move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5342 move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5343 if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
5345 if (appData.debugMode) {
5346 fprintf(debugFP, " out = '%s'\n", move);
5350 char yy_textstr[8000];
5352 /* Parser for moves from gnuchess, ICS, or user typein box */
5354 ParseOneMove (char *move, int moveNum, ChessMove *moveType, int *fromX, int *fromY, int *toX, int *toY, char *promoChar)
5356 *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
5358 switch (*moveType) {
5359 case WhitePromotion:
5360 case BlackPromotion:
5361 case WhiteNonPromotion:
5362 case BlackNonPromotion:
5364 case WhiteCapturesEnPassant:
5365 case BlackCapturesEnPassant:
5366 case WhiteKingSideCastle:
5367 case WhiteQueenSideCastle:
5368 case BlackKingSideCastle:
5369 case BlackQueenSideCastle:
5370 case WhiteKingSideCastleWild:
5371 case WhiteQueenSideCastleWild:
5372 case BlackKingSideCastleWild:
5373 case BlackQueenSideCastleWild:
5374 /* Code added by Tord: */
5375 case WhiteHSideCastleFR:
5376 case WhiteASideCastleFR:
5377 case BlackHSideCastleFR:
5378 case BlackASideCastleFR:
5379 /* End of code added by Tord */
5380 case IllegalMove: /* bug or odd chess variant */
5381 *fromX = currentMoveString[0] - AAA;
5382 *fromY = currentMoveString[1] - ONE;
5383 *toX = currentMoveString[2] - AAA;
5384 *toY = currentMoveString[3] - ONE;
5385 *promoChar = currentMoveString[4];
5386 if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
5387 *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
5388 if (appData.debugMode) {
5389 fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
5391 *fromX = *fromY = *toX = *toY = 0;
5394 if (appData.testLegality) {
5395 return (*moveType != IllegalMove);
5397 return !(*fromX == *toX && *fromY == *toY) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
5398 WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
5403 *fromX = *moveType == WhiteDrop ?
5404 (int) CharToPiece(ToUpper(currentMoveString[0])) :
5405 (int) CharToPiece(ToLower(currentMoveString[0]));
5407 *toX = currentMoveString[2] - AAA;
5408 *toY = currentMoveString[3] - ONE;
5409 *promoChar = NULLCHAR;
5413 case ImpossibleMove:
5423 if (appData.debugMode) {
5424 fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
5427 *fromX = *fromY = *toX = *toY = 0;
5428 *promoChar = NULLCHAR;
5433 Boolean pushed = FALSE;
5434 char *lastParseAttempt;
5437 ParsePV (char *pv, Boolean storeComments, Boolean atEnd)
5438 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
5439 int fromX, fromY, toX, toY; char promoChar;
5444 lastParseAttempt = pv; if(!*pv) return; // turns out we crash when we parse an empty PV
5445 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) && currentMove < forwardMostMove) {
5446 PushInner(currentMove, forwardMostMove); // [HGM] engine might not be thinking on forwardMost position!
5449 endPV = forwardMostMove;
5451 while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
5452 if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
5453 lastParseAttempt = pv;
5454 valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
5455 if(!valid && nr == 0 &&
5456 ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
5457 nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
5458 // Hande case where played move is different from leading PV move
5459 CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
5460 CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
5461 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
5462 if(!CompareBoards(boards[endPV], boards[endPV+2])) {
5463 endPV += 2; // if position different, keep this
5464 moveList[endPV-1][0] = fromX + AAA;
5465 moveList[endPV-1][1] = fromY + ONE;
5466 moveList[endPV-1][2] = toX + AAA;
5467 moveList[endPV-1][3] = toY + ONE;
5468 parseList[endPV-1][0] = NULLCHAR;
5469 safeStrCpy(moveList[endPV-2], "_0_0", sizeof(moveList[endPV-2])/sizeof(moveList[endPV-2][0])); // suppress premove highlight on takeback move
5472 pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
5473 if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
5474 if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
5475 if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
5476 valid++; // allow comments in PV
5480 if(endPV+1 > framePtr) break; // no space, truncate
5483 CopyBoard(boards[endPV], boards[endPV-1]);
5484 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
5485 CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, moveList[endPV - 1]);
5486 strncat(moveList[endPV-1], "\n", MOVE_LEN);
5487 CoordsToAlgebraic(boards[endPV - 1],
5488 PosFlags(endPV - 1),
5489 fromY, fromX, toY, toX, promoChar,
5490 parseList[endPV - 1]);
5492 if(atEnd == 2) return; // used hidden, for PV conversion
5493 currentMove = (atEnd || endPV == forwardMostMove) ? endPV : forwardMostMove + 1;
5494 if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5495 SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5496 moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5497 DrawPosition(TRUE, boards[currentMove]);
5501 MultiPV (ChessProgramState *cps)
5502 { // check if engine supports MultiPV, and if so, return the number of the option that sets it
5504 for(i=0; i<cps->nrOptions; i++)
5505 if(!strcmp(cps->option[i].name, "MultiPV") && cps->option[i].type == Spin)
5510 Boolean extendGame; // signals to UnLoadPV() if walked part of PV has to be appended to game
5513 LoadMultiPV (int x, int y, char *buf, int index, int *start, int *end, int pane)
5515 int startPV, multi, lineStart, origIndex = index;
5516 char *p, buf2[MSG_SIZ];
5517 ChessProgramState *cps = (pane ? &second : &first);
5519 if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
5520 lastX = x; lastY = y;
5521 while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
5522 lineStart = startPV = index;
5523 while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
5524 if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
5526 do{ while(buf[index] && buf[index] != '\n') index++;
5527 } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
5529 if(lineStart == 0 && gameMode == AnalyzeMode && (multi = MultiPV(cps)) >= 0) {
5530 int n = cps->option[multi].value;
5531 if(origIndex > 17 && origIndex < 24) { if(n>1) n--; } else if(origIndex > index - 6) n++;
5532 snprintf(buf2, MSG_SIZ, "option MultiPV=%d\n", n);
5533 if(cps->option[multi].value != n) SendToProgram(buf2, cps);
5534 cps->option[multi].value = n;
5537 } else if(strstr(buf+lineStart, "exclude:") == buf+lineStart) { // exclude moves clicked
5538 ExcludeClick(origIndex - lineStart);
5541 ParsePV(buf+startPV, FALSE, gameMode != AnalyzeMode);
5542 *start = startPV; *end = index-1;
5543 extendGame = (gameMode == AnalyzeMode && appData.autoExtend);
5550 static char buf[10*MSG_SIZ];
5551 int i, k=0, savedEnd=endPV, saveFMM = forwardMostMove;
5553 if(forwardMostMove < endPV) PushInner(forwardMostMove, endPV); // shelve PV of PV-walk
5554 ParsePV(pv, FALSE, 2); // this appends PV to game, suppressing any display of it
5555 for(i = forwardMostMove; i<endPV; i++){
5556 if(i&1) snprintf(buf+k, 10*MSG_SIZ-k, "%s ", parseList[i]);
5557 else snprintf(buf+k, 10*MSG_SIZ-k, "%d. %s ", i/2 + 1, parseList[i]);
5560 snprintf(buf+k, 10*MSG_SIZ-k, "%s", lastParseAttempt); // if we ran into stuff that could not be parsed, print it verbatim
5561 if(pushed) { PopInner(0); pushed = FALSE; } // restore game continuation shelved by ParsePV
5562 if(forwardMostMove < savedEnd) { PopInner(0); forwardMostMove = saveFMM; } // PopInner would set fmm to endPV!
5568 LoadPV (int x, int y)
5569 { // called on right mouse click to load PV
5570 int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
5571 lastX = x; lastY = y;
5572 ParsePV(lastPV[which], FALSE, TRUE); // load the PV of the thinking engine in the boards array.
5580 int oldFMM = forwardMostMove; // N.B.: this was currentMove before PV was loaded!
5581 if(endPV < 0) return;
5582 if(appData.autoCopyPV) CopyFENToClipboard();
5584 if(extendGame && currentMove > forwardMostMove) {
5585 Boolean saveAnimate = appData.animate;
5587 if(shiftKey && storedGames < MAX_VARIATIONS-2) { // wants to start variation, and there is space
5588 if(storedGames == 1) GreyRevert(FALSE); // we already pushed the tail, so just make it official
5589 } else storedGames--; // abandon shelved tail of original game
5592 forwardMostMove = currentMove;
5593 currentMove = oldFMM;
5594 appData.animate = FALSE;
5595 ToNrEvent(forwardMostMove);
5596 appData.animate = saveAnimate;
5598 currentMove = forwardMostMove;
5599 if(pushed) { PopInner(0); pushed = FALSE; } // restore shelved game continuation
5600 ClearPremoveHighlights();
5601 DrawPosition(TRUE, boards[currentMove]);
5605 MovePV (int x, int y, int h)
5606 { // step through PV based on mouse coordinates (called on mouse move)
5607 int margin = h>>3, step = 0, threshold = (pieceSweep == EmptySquare ? 10 : 15);
5609 // we must somehow check if right button is still down (might be released off board!)
5610 if(endPV < 0 && pieceSweep == EmptySquare) return; // needed in XBoard because lastX/Y is shared :-(
5611 if(abs(x - lastX) < threshold && abs(y - lastY) < threshold) return;
5612 if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5614 lastX = x; lastY = y;
5616 if(pieceSweep != EmptySquare) { NextPiece(step); return; }
5617 if(endPV < 0) return;
5618 if(y < margin) step = 1; else
5619 if(y > h - margin) step = -1;
5620 if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
5621 currentMove += step;
5622 if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5623 SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5624 moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5625 DrawPosition(FALSE, boards[currentMove]);
5629 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
5630 // All positions will have equal probability, but the current method will not provide a unique
5631 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
5637 int piecesLeft[(int)BlackPawn];
5638 int seed, nrOfShuffles;
5641 GetPositionNumber ()
5642 { // sets global variable seed
5645 seed = appData.defaultFrcPosition;
5646 if(seed < 0) { // randomize based on time for negative FRC position numbers
5647 for(i=0; i<50; i++) seed += random();
5648 seed = random() ^ random() >> 8 ^ random() << 8;
5649 if(seed<0) seed = -seed;
5654 put (Board board, int pieceType, int rank, int n, int shade)
5655 // put the piece on the (n-1)-th empty squares of the given shade
5659 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5660 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5661 board[rank][i] = (ChessSquare) pieceType;
5662 squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5664 piecesLeft[pieceType]--;
5673 AddOnePiece (Board board, int pieceType, int rank, int shade)
5674 // calculate where the next piece goes, (any empty square), and put it there
5678 i = seed % squaresLeft[shade];
5679 nrOfShuffles *= squaresLeft[shade];
5680 seed /= squaresLeft[shade];
5681 put(board, pieceType, rank, i, shade);
5685 AddTwoPieces (Board board, int pieceType, int rank)
5686 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5688 int i, n=squaresLeft[ANY], j=n-1, k;
5690 k = n*(n-1)/2; // nr of possibilities, not counting permutations
5691 i = seed % k; // pick one
5694 while(i >= j) i -= j--;
5695 j = n - 1 - j; i += j;
5696 put(board, pieceType, rank, j, ANY);
5697 put(board, pieceType, rank, i, ANY);
5701 SetUpShuffle (Board board, int number)
5705 GetPositionNumber(); nrOfShuffles = 1;
5707 squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5708 squaresLeft[ANY] = BOARD_RGHT - BOARD_LEFT;
5709 squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5711 for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5713 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5714 p = (int) board[0][i];
5715 if(p < (int) BlackPawn) piecesLeft[p] ++;
5716 board[0][i] = EmptySquare;
5719 if(PosFlags(0) & F_ALL_CASTLE_OK) {
5720 // shuffles restricted to allow normal castling put KRR first
5721 if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5722 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5723 else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5724 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5725 if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5726 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5727 if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5728 put(board, WhiteRook, 0, 0, ANY);
5729 // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5732 if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5733 // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5734 for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5735 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5736 while(piecesLeft[p] >= 2) {
5737 AddOnePiece(board, p, 0, LITE);
5738 AddOnePiece(board, p, 0, DARK);
5740 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5743 for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5744 // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5745 // but we leave King and Rooks for last, to possibly obey FRC restriction
5746 if(p == (int)WhiteRook) continue;
5747 while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5748 if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY); // add the odd piece
5751 // now everything is placed, except perhaps King (Unicorn) and Rooks
5753 if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5754 // Last King gets castling rights
5755 while(piecesLeft[(int)WhiteUnicorn]) {
5756 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5757 initialRights[2] = initialRights[5] = board[CASTLING][2] = board[CASTLING][5] = i;
5760 while(piecesLeft[(int)WhiteKing]) {
5761 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5762 initialRights[2] = initialRights[5] = board[CASTLING][2] = board[CASTLING][5] = i;
5767 while(piecesLeft[(int)WhiteKing]) AddOnePiece(board, WhiteKing, 0, ANY);
5768 while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
5771 // Only Rooks can be left; simply place them all
5772 while(piecesLeft[(int)WhiteRook]) {
5773 i = put(board, WhiteRook, 0, 0, ANY);
5774 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
5777 initialRights[1] = initialRights[4] = board[CASTLING][1] = board[CASTLING][4] = i;
5779 initialRights[0] = initialRights[3] = board[CASTLING][0] = board[CASTLING][3] = i;
5782 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
5783 board[BOARD_HEIGHT-1][i] = (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
5786 if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
5790 SetCharTable (char *table, const char * map)
5791 /* [HGM] moved here from winboard.c because of its general usefulness */
5792 /* Basically a safe strcpy that uses the last character as King */
5794 int result = FALSE; int NrPieces;
5796 if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
5797 && NrPieces >= 12 && !(NrPieces&1)) {
5798 int i; /* [HGM] Accept even length from 12 to 34 */
5800 for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
5801 for( i=0; i<NrPieces/2-1; i++ ) {
5803 table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
5805 table[(int) WhiteKing] = map[NrPieces/2-1];
5806 table[(int) BlackKing] = map[NrPieces-1];
5815 Prelude (Board board)
5816 { // [HGM] superchess: random selection of exo-pieces
5817 int i, j, k; ChessSquare p;
5818 static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
5820 GetPositionNumber(); // use FRC position number
5822 if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
5823 SetCharTable(pieceToChar, appData.pieceToCharTable);
5824 for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
5825 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
5828 j = seed%4; seed /= 4;
5829 p = board[0][BOARD_LEFT+j]; board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5830 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
5831 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
5832 j = seed%3 + (seed%3 >= j); seed /= 3;
5833 p = board[0][BOARD_LEFT+j]; board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5834 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
5835 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
5836 j = seed%3; seed /= 3;
5837 p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5838 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
5839 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
5840 j = seed%2 + (seed%2 >= j); seed /= 2;
5841 p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5842 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
5843 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
5844 j = seed%4; seed /= 4; put(board, exoPieces[3], 0, j, ANY);
5845 j = seed%3; seed /= 3; put(board, exoPieces[2], 0, j, ANY);
5846 j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
5847 put(board, exoPieces[0], 0, 0, ANY);
5848 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
5852 InitPosition (int redraw)
5854 ChessSquare (* pieces)[BOARD_FILES];
5855 int i, j, pawnRow, overrule,
5856 oldx = gameInfo.boardWidth,
5857 oldy = gameInfo.boardHeight,
5858 oldh = gameInfo.holdingsWidth;
5861 if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
5863 /* [AS] Initialize pv info list [HGM] and game status */
5865 for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
5866 pvInfoList[i].depth = 0;
5867 boards[i][EP_STATUS] = EP_NONE;
5868 for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
5871 initialRulePlies = 0; /* 50-move counter start */
5873 castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
5874 castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
5878 /* [HGM] logic here is completely changed. In stead of full positions */
5879 /* the initialized data only consist of the two backranks. The switch */
5880 /* selects which one we will use, which is than copied to the Board */
5881 /* initialPosition, which for the rest is initialized by Pawns and */
5882 /* empty squares. This initial position is then copied to boards[0], */
5883 /* possibly after shuffling, so that it remains available. */
5885 gameInfo.holdingsWidth = 0; /* default board sizes */
5886 gameInfo.boardWidth = 8;
5887 gameInfo.boardHeight = 8;
5888 gameInfo.holdingsSize = 0;
5889 nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
5890 for(i=0; i<BOARD_FILES-2; i++)
5891 initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
5892 initialPosition[EP_STATUS] = EP_NONE;
5893 SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
5894 if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
5895 SetCharTable(pieceNickName, appData.pieceNickNames);
5896 else SetCharTable(pieceNickName, "............");
5899 switch (gameInfo.variant) {
5900 case VariantFischeRandom:
5901 shuffleOpenings = TRUE;
5904 case VariantShatranj:
5905 pieces = ShatranjArray;
5906 nrCastlingRights = 0;
5907 SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
5910 pieces = makrukArray;
5911 nrCastlingRights = 0;
5912 startedFromSetupPosition = TRUE;
5913 SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
5916 pieces = aseanArray;
5917 nrCastlingRights = 0;
5918 startedFromSetupPosition = TRUE;
5919 SetCharTable(pieceToChar, "PN.R.Q....BKpn.r.q....bk");
5921 case VariantTwoKings:
5922 pieces = twoKingsArray;
5925 pieces = GrandArray;
5926 nrCastlingRights = 0;
5927 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5928 gameInfo.boardWidth = 10;
5929 gameInfo.boardHeight = 10;
5930 gameInfo.holdingsSize = 7;
5932 case VariantCapaRandom:
5933 shuffleOpenings = TRUE;
5934 case VariantCapablanca:
5935 pieces = CapablancaArray;
5936 gameInfo.boardWidth = 10;
5937 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5940 pieces = GothicArray;
5941 gameInfo.boardWidth = 10;
5942 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5945 SetCharTable(pieceToChar, "PNBRQ..HEKpnbrq..hek");
5946 gameInfo.holdingsSize = 7;
5947 for(i=0; i<BOARD_FILES; i++) initialPosition[VIRGIN][i] = VIRGIN_W | VIRGIN_B;
5950 pieces = JanusArray;
5951 gameInfo.boardWidth = 10;
5952 SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
5953 nrCastlingRights = 6;
5954 initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5955 initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5956 initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
5957 initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5958 initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5959 initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
5962 pieces = FalconArray;
5963 gameInfo.boardWidth = 10;
5964 SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
5966 case VariantXiangqi:
5967 pieces = XiangqiArray;
5968 gameInfo.boardWidth = 9;
5969 gameInfo.boardHeight = 10;
5970 nrCastlingRights = 0;
5971 SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
5974 pieces = ShogiArray;
5975 gameInfo.boardWidth = 9;
5976 gameInfo.boardHeight = 9;
5977 gameInfo.holdingsSize = 7;
5978 nrCastlingRights = 0;
5979 SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
5981 case VariantCourier:
5982 pieces = CourierArray;
5983 gameInfo.boardWidth = 12;
5984 nrCastlingRights = 0;
5985 SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
5987 case VariantKnightmate:
5988 pieces = KnightmateArray;
5989 SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
5991 case VariantSpartan:
5992 pieces = SpartanArray;
5993 SetCharTable(pieceToChar, "PNBRQ................K......lwg.....c...h..k");
5996 pieces = fairyArray;
5997 SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
6000 pieces = GreatArray;
6001 gameInfo.boardWidth = 10;
6002 SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
6003 gameInfo.holdingsSize = 8;
6007 SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
6008 gameInfo.holdingsSize = 8;
6009 startedFromSetupPosition = TRUE;
6011 case VariantCrazyhouse:
6012 case VariantBughouse:
6014 SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
6015 gameInfo.holdingsSize = 5;
6017 case VariantWildCastle:
6019 /* !!?shuffle with kings guaranteed to be on d or e file */
6020 shuffleOpenings = 1;
6022 case VariantNoCastle:
6024 nrCastlingRights = 0;
6025 /* !!?unconstrained back-rank shuffle */
6026 shuffleOpenings = 1;
6031 if(appData.NrFiles >= 0) {
6032 if(gameInfo.boardWidth != appData.NrFiles) overrule++;
6033 gameInfo.boardWidth = appData.NrFiles;
6035 if(appData.NrRanks >= 0) {
6036 gameInfo.boardHeight = appData.NrRanks;
6038 if(appData.holdingsSize >= 0) {
6039 i = appData.holdingsSize;
6040 if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
6041 gameInfo.holdingsSize = i;
6043 if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
6044 if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
6045 DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
6047 pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
6048 if(pawnRow < 1) pawnRow = 1;
6049 if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN || gameInfo.variant == VariantGrand) pawnRow = 2;
6051 /* User pieceToChar list overrules defaults */
6052 if(appData.pieceToCharTable != NULL)
6053 SetCharTable(pieceToChar, appData.pieceToCharTable);
6055 for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
6057 if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
6058 s = (ChessSquare) 0; /* account holding counts in guard band */
6059 for( i=0; i<BOARD_HEIGHT; i++ )
6060 initialPosition[i][j] = s;
6062 if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
6063 initialPosition[gameInfo.variant == VariantGrand][j] = pieces[0][j-gameInfo.holdingsWidth];
6064 initialPosition[pawnRow][j] = WhitePawn;
6065 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = gameInfo.variant == VariantSpartan ? BlackLance : BlackPawn;
6066 if(gameInfo.variant == VariantXiangqi) {
6068 initialPosition[pawnRow][j] =
6069 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
6070 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
6071 initialPosition[2][j] = WhiteCannon;
6072 initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
6076 if(gameInfo.variant == VariantGrand) {
6077 if(j==BOARD_LEFT || j>=BOARD_RGHT-1) {
6078 initialPosition[0][j] = WhiteRook;
6079 initialPosition[BOARD_HEIGHT-1][j] = BlackRook;
6082 initialPosition[BOARD_HEIGHT-1-(gameInfo.variant == VariantGrand)][j] = pieces[1][j-gameInfo.holdingsWidth];
6084 if( (gameInfo.variant == VariantShogi) && !overrule ) {
6087 initialPosition[1][j] = WhiteBishop;
6088 initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
6090 initialPosition[1][j] = WhiteRook;
6091 initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
6094 if( nrCastlingRights == -1) {
6095 /* [HGM] Build normal castling rights (must be done after board sizing!) */
6096 /* This sets default castling rights from none to normal corners */
6097 /* Variants with other castling rights must set them themselves above */
6098 nrCastlingRights = 6;
6100 initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
6101 initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
6102 initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
6103 initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
6104 initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
6105 initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
6108 if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
6109 if(gameInfo.variant == VariantGreat) { // promotion commoners
6110 initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
6111 initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
6112 initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
6113 initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
6115 if( gameInfo.variant == VariantSChess ) {
6116 initialPosition[1][0] = BlackMarshall;
6117 initialPosition[2][0] = BlackAngel;
6118 initialPosition[6][BOARD_WIDTH-1] = WhiteMarshall;
6119 initialPosition[5][BOARD_WIDTH-1] = WhiteAngel;
6120 initialPosition[1][1] = initialPosition[2][1] =
6121 initialPosition[6][BOARD_WIDTH-2] = initialPosition[5][BOARD_WIDTH-2] = 1;
6123 if (appData.debugMode) {
6124 fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
6126 if(shuffleOpenings) {
6127 SetUpShuffle(initialPosition, appData.defaultFrcPosition);
6128 startedFromSetupPosition = TRUE;
6130 if(startedFromPositionFile) {
6131 /* [HGM] loadPos: use PositionFile for every new game */
6132 CopyBoard(initialPosition, filePosition);
6133 for(i=0; i<nrCastlingRights; i++)
6134 initialRights[i] = filePosition[CASTLING][i];
6135 startedFromSetupPosition = TRUE;
6138 CopyBoard(boards[0], initialPosition);
6140 if(oldx != gameInfo.boardWidth ||
6141 oldy != gameInfo.boardHeight ||
6142 oldv != gameInfo.variant ||
6143 oldh != gameInfo.holdingsWidth
6145 InitDrawingSizes(-2 ,0);
6147 oldv = gameInfo.variant;
6149 DrawPosition(TRUE, boards[currentMove]);
6153 SendBoard (ChessProgramState *cps, int moveNum)
6155 char message[MSG_SIZ];
6157 if (cps->useSetboard) {
6158 char* fen = PositionToFEN(moveNum, cps->fenOverride);
6159 snprintf(message, MSG_SIZ,"setboard %s\n", fen);
6160 SendToProgram(message, cps);
6165 int i, j, left=0, right=BOARD_WIDTH;
6166 /* Kludge to set black to move, avoiding the troublesome and now
6167 * deprecated "black" command.
6169 if (!WhiteOnMove(moveNum)) // [HGM] but better a deprecated command than an illegal move...
6170 SendToProgram(boards[0][1][BOARD_LEFT] == WhitePawn ? "a2a3\n" : "black\n", cps);
6172 if(!cps->extendedEdit) left = BOARD_LEFT, right = BOARD_RGHT; // only board proper
6174 SendToProgram("edit\n", cps);
6175 SendToProgram("#\n", cps);
6176 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6177 bp = &boards[moveNum][i][left];
6178 for (j = left; j < right; j++, bp++) {
6179 if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6180 if ((int) *bp < (int) BlackPawn) {
6181 if(j == BOARD_RGHT+1)
6182 snprintf(message, MSG_SIZ, "%c@%d\n", PieceToChar(*bp), bp[-1]);
6183 else snprintf(message, MSG_SIZ, "%c%c%c\n", PieceToChar(*bp), AAA + j, ONE + i);
6184 if(message[0] == '+' || message[0] == '~') {
6185 snprintf(message, MSG_SIZ,"%c%c%c+\n",
6186 PieceToChar((ChessSquare)(DEMOTED *bp)),
6189 if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6190 message[1] = BOARD_RGHT - 1 - j + '1';
6191 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6193 SendToProgram(message, cps);
6198 SendToProgram("c\n", cps);
6199 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6200 bp = &boards[moveNum][i][left];
6201 for (j = left; j < right; j++, bp++) {
6202 if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6203 if (((int) *bp != (int) EmptySquare)
6204 && ((int) *bp >= (int) BlackPawn)) {
6205 if(j == BOARD_LEFT-2)
6206 snprintf(message, MSG_SIZ, "%c@%d\n", ToUpper(PieceToChar(*bp)), bp[1]);
6207 else snprintf(message,MSG_SIZ, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
6209 if(message[0] == '+' || message[0] == '~') {
6210 snprintf(message, MSG_SIZ,"%c%c%c+\n",
6211 PieceToChar((ChessSquare)(DEMOTED *bp)),
6214 if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6215 message[1] = BOARD_RGHT - 1 - j + '1';
6216 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6218 SendToProgram(message, cps);
6223 SendToProgram(".\n", cps);
6225 setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
6228 char exclusionHeader[MSG_SIZ];
6229 int exCnt, excludePtr;
6230 typedef struct { int ff, fr, tf, tr, pc, mark; } Exclusion;
6231 static Exclusion excluTab[200];
6232 static char excludeMap[(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8]; // [HGM] exclude: bitmap for excluced moves
6238 for(j=0; j<(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8; j++) excludeMap[j] = s;
6239 exclusionHeader[19] = s ? '-' : '+'; // update tail state
6245 safeStrCpy(exclusionHeader, "exclude: none best +tail \n", MSG_SIZ);
6246 excludePtr = 24; exCnt = 0;
6251 UpdateExcludeHeader (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6252 { // search given move in table of header moves, to know where it is listed (and add if not there), and update state
6253 char buf[2*MOVE_LEN], *p;
6254 Exclusion *e = excluTab;
6256 for(i=0; i<exCnt; i++)
6257 if(e[i].ff == fromX && e[i].fr == fromY &&
6258 e[i].tf == toX && e[i].tr == toY && e[i].pc == promoChar) break;
6259 if(i == exCnt) { // was not in exclude list; add it
6260 CoordsToAlgebraic(boards[currentMove], PosFlags(currentMove), fromY, fromX, toY, toX, promoChar, buf);
6261 if(strlen(exclusionHeader + excludePtr) < strlen(buf)) { // no space to write move
6262 if(state != exclusionHeader[19]) exclusionHeader[19] = '*'; // tail is now in mixed state
6265 e[i].ff = fromX; e[i].fr = fromY; e[i].tf = toX; e[i].tr = toY; e[i].pc = promoChar;
6266 excludePtr++; e[i].mark = excludePtr++;
6267 for(p=buf; *p; p++) exclusionHeader[excludePtr++] = *p; // copy move
6270 exclusionHeader[e[i].mark] = state;
6274 ExcludeOneMove (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6275 { // include or exclude the given move, as specified by state ('+' or '-'), or toggle
6279 if((signed char)promoChar == -1) { // kludge to indicate best move
6280 if(!ParseOneMove(lastPV[0], currentMove, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) // get current best move from last PV
6281 return 1; // if unparsable, abort
6283 // update exclusion map (resolving toggle by consulting existing state)
6284 k=(BOARD_FILES*fromY+fromX)*BOARD_RANKS*BOARD_FILES + (BOARD_FILES*toY+toX);
6286 if(state == '*') state = (excludeMap[k] & 1<<j ? '+' : '-'); // toggle
6287 if(state == '-' && !promoChar) // only non-promotions get marked as excluded, to allow exclusion of under-promotions
6288 excludeMap[k] |= 1<<j;
6289 else excludeMap[k] &= ~(1<<j);
6291 UpdateExcludeHeader(fromY, fromX, toY, toX, promoChar, state);
6293 snprintf(buf, MSG_SIZ, "%sclude ", state == '+' ? "in" : "ex");
6294 CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, buf+8);
6296 return (state == '+');
6300 ExcludeClick (int index)
6303 Exclusion *e = excluTab;
6304 if(index < 25) { // none, best or tail clicked
6305 if(index < 13) { // none: include all
6306 WriteMap(0); // clear map
6307 for(i=0; i<exCnt; i++) exclusionHeader[excluTab[i].mark] = '+'; // and moves
6308 SendToBoth("include all\n"); // and inform engine
6309 } else if(index > 18) { // tail
6310 if(exclusionHeader[19] == '-') { // tail was excluded
6311 SendToBoth("include all\n");
6312 WriteMap(0); // clear map completely
6313 // now re-exclude selected moves
6314 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '-')
6315 ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '-');
6316 } else { // tail was included or in mixed state
6317 SendToBoth("exclude all\n");
6318 WriteMap(0xFF); // fill map completely
6319 // now re-include selected moves
6320 j = 0; // count them
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, '+'), j++;
6323 if(!j) ExcludeOneMove(0, 0, 0, 0, -1, '+'); // if no moves were selected, keep best
6326 ExcludeOneMove(0, 0, 0, 0, -1, '-'); // exclude it
6329 for(i=0; i<exCnt; i++) if(i == exCnt-1 || excluTab[i+1].mark > index) {
6330 char *p=exclusionHeader + excluTab[i].mark; // do trust header more than map (promotions!)
6331 ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, *p == '+' ? '-' : '+');
6338 DefaultPromoChoice (int white)
6341 if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6342 gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN)
6343 result = WhiteFerz; // no choice
6344 else if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway)
6345 result= WhiteKing; // in Suicide Q is the last thing we want
6346 else if(gameInfo.variant == VariantSpartan)
6347 result = white ? WhiteQueen : WhiteAngel;
6348 else result = WhiteQueen;
6349 if(!white) result = WHITE_TO_BLACK result;
6353 static int autoQueen; // [HGM] oneclick
6356 HasPromotionChoice (int fromX, int fromY, int toX, int toY, char *promoChoice, int sweepSelect)
6358 /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
6359 /* [HGM] add Shogi promotions */
6360 int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
6365 if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
6366 if(toX < BOARD_LEFT || toX >= BOARD_RGHT) return FALSE; // move into holdings
6368 if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
6369 !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
6372 piece = boards[currentMove][fromY][fromX];
6373 if(gameInfo.variant == VariantShogi) {
6374 promotionZoneSize = BOARD_HEIGHT/3;
6375 highestPromotingPiece = (int)WhiteFerz;
6376 } else if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand) {
6377 promotionZoneSize = 3;
6380 // Treat Lance as Pawn when it is not representing Amazon
6381 if(gameInfo.variant != VariantSuper) {
6382 if(piece == WhiteLance) piece = WhitePawn; else
6383 if(piece == BlackLance) piece = BlackPawn;
6386 // next weed out all moves that do not touch the promotion zone at all
6387 if((int)piece >= BlackPawn) {
6388 if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
6390 highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
6392 if( toY < BOARD_HEIGHT - promotionZoneSize &&
6393 fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
6396 if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
6398 // weed out mandatory Shogi promotions
6399 if(gameInfo.variant == VariantShogi) {
6400 if(piece >= BlackPawn) {
6401 if(toY == 0 && piece == BlackPawn ||
6402 toY == 0 && piece == BlackQueen ||
6403 toY <= 1 && piece == BlackKnight) {
6408 if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
6409 toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
6410 toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
6417 // weed out obviously illegal Pawn moves
6418 if(appData.testLegality && (piece == WhitePawn || piece == BlackPawn) ) {
6419 if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
6420 if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
6421 if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
6422 if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
6423 // note we are not allowed to test for valid (non-)capture, due to premove
6426 // we either have a choice what to promote to, or (in Shogi) whether to promote
6427 if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6428 gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN) {
6429 *promoChoice = PieceToChar(BlackFerz); // no choice
6432 // no sense asking what we must promote to if it is going to explode...
6433 if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) {
6434 *promoChoice = PieceToChar(BlackQueen); // Queen as good as any
6437 // give caller the default choice even if we will not make it
6438 *promoChoice = ToLower(PieceToChar(defaultPromoChoice));
6439 if(gameInfo.variant == VariantShogi) *promoChoice = (defaultPromoChoice == piece ? '=' : '+');
6440 if( sweepSelect && gameInfo.variant != VariantGreat
6441 && gameInfo.variant != VariantGrand
6442 && gameInfo.variant != VariantSuper) return FALSE;
6443 if(autoQueen) return FALSE; // predetermined
6445 // suppress promotion popup on illegal moves that are not premoves
6446 premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
6447 gameMode == IcsPlayingBlack && WhiteOnMove(currentMove);
6448 if(appData.testLegality && !premove) {
6449 moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6450 fromY, fromX, toY, toX, gameInfo.variant == VariantShogi ? '+' : NULLCHAR);
6451 if(moveType != WhitePromotion && moveType != BlackPromotion)
6459 InPalace (int row, int column)
6460 { /* [HGM] for Xiangqi */
6461 if( (row < 3 || row > BOARD_HEIGHT-4) &&
6462 column < (BOARD_WIDTH + 4)/2 &&
6463 column > (BOARD_WIDTH - 5)/2 ) return TRUE;
6468 PieceForSquare (int x, int y)
6470 if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
6473 return boards[currentMove][y][x];
6477 OKToStartUserMove (int x, int y)
6479 ChessSquare from_piece;
6482 if (matchMode) return FALSE;
6483 if (gameMode == EditPosition) return TRUE;
6485 if (x >= 0 && y >= 0)
6486 from_piece = boards[currentMove][y][x];
6488 from_piece = EmptySquare;
6490 if (from_piece == EmptySquare) return FALSE;
6492 white_piece = (int)from_piece >= (int)WhitePawn &&
6493 (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
6497 case TwoMachinesPlay:
6505 case MachinePlaysWhite:
6506 case IcsPlayingBlack:
6507 if (appData.zippyPlay) return FALSE;
6509 DisplayMoveError(_("You are playing Black"));
6514 case MachinePlaysBlack:
6515 case IcsPlayingWhite:
6516 if (appData.zippyPlay) return FALSE;
6518 DisplayMoveError(_("You are playing White"));
6523 case PlayFromGameFile:
6524 if(!shiftKey || !appData.variations) return FALSE; // [HGM] allow starting variation in this mode
6526 if (!white_piece && WhiteOnMove(currentMove)) {
6527 DisplayMoveError(_("It is White's turn"));
6530 if (white_piece && !WhiteOnMove(currentMove)) {
6531 DisplayMoveError(_("It is Black's turn"));
6534 if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
6535 /* Editing correspondence game history */
6536 /* Could disallow this or prompt for confirmation */
6541 case BeginningOfGame:
6542 if (appData.icsActive) return FALSE;
6543 if (!appData.noChessProgram) {
6545 DisplayMoveError(_("You are playing White"));
6552 if (!white_piece && WhiteOnMove(currentMove)) {
6553 DisplayMoveError(_("It is White's turn"));
6556 if (white_piece && !WhiteOnMove(currentMove)) {
6557 DisplayMoveError(_("It is Black's turn"));
6566 if (currentMove != forwardMostMove && gameMode != AnalyzeMode
6567 && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
6568 && gameMode != PlayFromGameFile // [HGM] as EditGame, with protected main line
6569 && gameMode != AnalyzeFile && gameMode != Training) {
6570 DisplayMoveError(_("Displayed position is not current"));
6577 OnlyMove (int *x, int *y, Boolean captures)
6579 DisambiguateClosure cl;
6580 if (appData.zippyPlay || !appData.testLegality) return FALSE;
6582 case MachinePlaysBlack:
6583 case IcsPlayingWhite:
6584 case BeginningOfGame:
6585 if(!WhiteOnMove(currentMove)) return FALSE;
6587 case MachinePlaysWhite:
6588 case IcsPlayingBlack:
6589 if(WhiteOnMove(currentMove)) return FALSE;
6596 cl.pieceIn = EmptySquare;
6601 cl.promoCharIn = NULLCHAR;
6602 Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6603 if( cl.kind == NormalMove ||
6604 cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6605 cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6606 cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6613 if(cl.kind != ImpossibleMove) return FALSE;
6614 cl.pieceIn = EmptySquare;
6619 cl.promoCharIn = NULLCHAR;
6620 Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6621 if( cl.kind == NormalMove ||
6622 cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6623 cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6624 cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6629 autoQueen = TRUE; // act as if autoQueen on when we click to-square
6635 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
6636 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
6637 int lastLoadGameUseList = FALSE;
6638 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
6639 ChessMove lastLoadGameStart = EndOfFile;
6643 UserMoveEvent(int fromX, int fromY, int toX, int toY, int promoChar)
6647 int ff=fromX, rf=fromY, ft=toX, rt=toY;
6649 /* Check if the user is playing in turn. This is complicated because we
6650 let the user "pick up" a piece before it is his turn. So the piece he
6651 tried to pick up may have been captured by the time he puts it down!
6652 Therefore we use the color the user is supposed to be playing in this
6653 test, not the color of the piece that is currently on the starting
6654 square---except in EditGame mode, where the user is playing both
6655 sides; fortunately there the capture race can't happen. (It can
6656 now happen in IcsExamining mode, but that's just too bad. The user
6657 will get a somewhat confusing message in that case.)
6662 case TwoMachinesPlay:
6666 /* We switched into a game mode where moves are not accepted,
6667 perhaps while the mouse button was down. */
6670 case MachinePlaysWhite:
6671 /* User is moving for Black */
6672 if (WhiteOnMove(currentMove)) {
6673 DisplayMoveError(_("It is White's turn"));
6678 case MachinePlaysBlack:
6679 /* User is moving for White */
6680 if (!WhiteOnMove(currentMove)) {
6681 DisplayMoveError(_("It is Black's turn"));
6686 case PlayFromGameFile:
6687 if(!shiftKey ||!appData.variations) return; // [HGM] only variations
6690 case BeginningOfGame:
6693 if(fromY == DROP_RANK) break; // [HGM] drop moves (entered through move type-in) are automatically assigned to side-to-move
6694 if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
6695 (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
6696 /* User is moving for Black */
6697 if (WhiteOnMove(currentMove)) {
6698 DisplayMoveError(_("It is White's turn"));
6702 /* User is moving for White */
6703 if (!WhiteOnMove(currentMove)) {
6704 DisplayMoveError(_("It is Black's turn"));
6710 case IcsPlayingBlack:
6711 /* User is moving for Black */
6712 if (WhiteOnMove(currentMove)) {
6713 if (!appData.premove) {
6714 DisplayMoveError(_("It is White's turn"));
6715 } else if (toX >= 0 && toY >= 0) {
6718 premoveFromX = fromX;
6719 premoveFromY = fromY;
6720 premovePromoChar = promoChar;
6722 if (appData.debugMode)
6723 fprintf(debugFP, "Got premove: fromX %d,"
6724 "fromY %d, toX %d, toY %d\n",
6725 fromX, fromY, toX, toY);
6731 case IcsPlayingWhite:
6732 /* User is moving for White */
6733 if (!WhiteOnMove(currentMove)) {
6734 if (!appData.premove) {
6735 DisplayMoveError(_("It is Black's turn"));
6736 } else if (toX >= 0 && toY >= 0) {
6739 premoveFromX = fromX;
6740 premoveFromY = fromY;
6741 premovePromoChar = promoChar;
6743 if (appData.debugMode)
6744 fprintf(debugFP, "Got premove: fromX %d,"
6745 "fromY %d, toX %d, toY %d\n",
6746 fromX, fromY, toX, toY);
6756 /* EditPosition, empty square, or different color piece;
6757 click-click move is possible */
6758 if (toX == -2 || toY == -2) {
6759 boards[0][fromY][fromX] = EmptySquare;
6760 DrawPosition(FALSE, boards[currentMove]);
6762 } else if (toX >= 0 && toY >= 0) {
6763 boards[0][toY][toX] = boards[0][fromY][fromX];
6764 if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
6765 if(boards[0][fromY][0] != EmptySquare) {
6766 if(boards[0][fromY][1]) boards[0][fromY][1]--;
6767 if(boards[0][fromY][1] == 0) boards[0][fromY][0] = EmptySquare;
6770 if(fromX == BOARD_RGHT+1) {
6771 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
6772 if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
6773 if(boards[0][fromY][BOARD_WIDTH-2] == 0) boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
6776 boards[0][fromY][fromX] = gatingPiece;
6777 DrawPosition(FALSE, boards[currentMove]);
6783 if(toX < 0 || toY < 0) return;
6784 pup = boards[currentMove][toY][toX];
6786 /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
6787 if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ) {
6788 if( pup != EmptySquare ) return;
6789 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
6790 if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n",
6791 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
6792 // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
6793 if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
6794 fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
6795 while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++;
6799 /* [HGM] always test for legality, to get promotion info */
6800 moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6801 fromY, fromX, toY, toX, promoChar);
6803 if(fromY == DROP_RANK && fromX == EmptySquare && (gameMode == AnalyzeMode || gameMode == EditGame)) moveType = NormalMove;
6805 /* [HGM] but possibly ignore an IllegalMove result */
6806 if (appData.testLegality) {
6807 if (moveType == IllegalMove || moveType == ImpossibleMove) {
6808 DisplayMoveError(_("Illegal move"));
6813 if(doubleClick && gameMode == AnalyzeMode) { // [HGM] exclude: move entered with double-click on from square is for exclusion, not playing
6814 if(ExcludeOneMove(fromY, fromX, toY, toX, promoChar, '*')) // toggle
6815 ClearPremoveHighlights(); // was included
6816 else ClearHighlights(), SetPremoveHighlights(ff, rf, ft, rt); // exclusion indicated by premove highlights
6820 FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
6823 /* Common tail of UserMoveEvent and DropMenuEvent */
6825 FinishMove (ChessMove moveType, int fromX, int fromY, int toX, int toY, int promoChar)
6829 if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) && promoChar != NULLCHAR) {
6830 // [HGM] superchess: suppress promotions to non-available piece (but P always allowed)
6831 int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
6832 if(WhiteOnMove(currentMove)) {
6833 if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
6835 if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
6839 /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
6840 move type in caller when we know the move is a legal promotion */
6841 if(moveType == NormalMove && promoChar)
6842 moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
6844 /* [HGM] <popupFix> The following if has been moved here from
6845 UserMoveEvent(). Because it seemed to belong here (why not allow
6846 piece drops in training games?), and because it can only be
6847 performed after it is known to what we promote. */
6848 if (gameMode == Training) {
6849 /* compare the move played on the board to the next move in the
6850 * game. If they match, display the move and the opponent's response.
6851 * If they don't match, display an error message.
6855 CopyBoard(testBoard, boards[currentMove]);
6856 ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
6858 if (CompareBoards(testBoard, boards[currentMove+1])) {
6859 ForwardInner(currentMove+1);
6861 /* Autoplay the opponent's response.
6862 * if appData.animate was TRUE when Training mode was entered,
6863 * the response will be animated.
6865 saveAnimate = appData.animate;
6866 appData.animate = animateTraining;
6867 ForwardInner(currentMove+1);
6868 appData.animate = saveAnimate;
6870 /* check for the end of the game */
6871 if (currentMove >= forwardMostMove) {
6872 gameMode = PlayFromGameFile;
6874 SetTrainingModeOff();
6875 DisplayInformation(_("End of game"));
6878 DisplayError(_("Incorrect move"), 0);
6883 /* Ok, now we know that the move is good, so we can kill
6884 the previous line in Analysis Mode */
6885 if ((gameMode == AnalyzeMode || gameMode == EditGame || gameMode == PlayFromGameFile && appData.variations && shiftKey)
6886 && currentMove < forwardMostMove) {
6887 if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
6888 else forwardMostMove = currentMove;
6893 /* If we need the chess program but it's dead, restart it */
6894 ResurrectChessProgram();
6896 /* A user move restarts a paused game*/
6900 thinkOutput[0] = NULLCHAR;
6902 MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
6904 if(Adjudicate(NULL)) { // [HGM] adjudicate: take care of automatic game end
6905 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6909 if (gameMode == BeginningOfGame) {
6910 if (appData.noChessProgram) {
6911 gameMode = EditGame;
6915 gameMode = MachinePlaysBlack;
6918 snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
6920 if (first.sendName) {
6921 snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
6922 SendToProgram(buf, &first);
6929 /* Relay move to ICS or chess engine */
6930 if (appData.icsActive) {
6931 if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
6932 gameMode == IcsExamining) {
6933 if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
6934 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
6936 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6938 // also send plain move, in case ICS does not understand atomic claims
6939 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6943 if (first.sendTime && (gameMode == BeginningOfGame ||
6944 gameMode == MachinePlaysWhite ||
6945 gameMode == MachinePlaysBlack)) {
6946 SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
6948 if (gameMode != EditGame && gameMode != PlayFromGameFile && gameMode != AnalyzeMode) {
6949 // [HGM] book: if program might be playing, let it use book
6950 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
6951 first.maybeThinking = TRUE;
6952 } else if(fromY == DROP_RANK && fromX == EmptySquare) {
6953 if(!first.useSetboard) SendToProgram("undo\n", &first); // kludge to change stm in engines that do not support setboard
6954 SendBoard(&first, currentMove+1);
6955 if(second.analyzing) {
6956 if(!second.useSetboard) SendToProgram("undo\n", &second);
6957 SendBoard(&second, currentMove+1);
6960 SendMoveToProgram(forwardMostMove-1, &first);
6961 if(second.analyzing) SendMoveToProgram(forwardMostMove-1, &second);
6963 if (currentMove == cmailOldMove + 1) {
6964 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
6968 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6972 if(appData.testLegality)
6973 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
6979 if (WhiteOnMove(currentMove)) {
6980 GameEnds(BlackWins, "Black mates", GE_PLAYER);
6982 GameEnds(WhiteWins, "White mates", GE_PLAYER);
6986 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
6991 case MachinePlaysBlack:
6992 case MachinePlaysWhite:
6993 /* disable certain menu options while machine is thinking */
6994 SetMachineThinkingEnables();
7001 userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
7002 promoDefaultAltered = FALSE; // [HGM] fall back on default choice
7004 if(bookHit) { // [HGM] book: simulate book reply
7005 static char bookMove[MSG_SIZ]; // a bit generous?
7007 programStats.nodes = programStats.depth = programStats.time =
7008 programStats.score = programStats.got_only_move = 0;
7009 sprintf(programStats.movelist, "%s (xbook)", bookHit);
7011 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
7012 strcat(bookMove, bookHit);
7013 HandleMachineMove(bookMove, &first);
7019 Mark (Board board, int flags, ChessMove kind, int rf, int ff, int rt, int ft, VOIDSTAR closure)
7021 typedef char Markers[BOARD_RANKS][BOARD_FILES];
7022 Markers *m = (Markers *) closure;
7023 if(rf == fromY && ff == fromX)
7024 (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
7025 || kind == WhiteCapturesEnPassant
7026 || kind == BlackCapturesEnPassant);
7027 else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
7031 MarkTargetSquares (int clear)
7034 if(clear) // no reason to ever suppress clearing
7035 for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) marker[y][x] = 0;
7036 if(!appData.markers || !appData.highlightDragging || appData.icsActive && gameInfo.variant < VariantShogi ||
7037 !appData.testLegality || gameMode == EditPosition) return;
7040 GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker, EmptySquare);
7041 if(PosFlags(0) & F_MANDATORY_CAPTURE) {
7042 for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
7044 for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
7047 DrawPosition(FALSE, NULL);
7051 Explode (Board board, int fromX, int fromY, int toX, int toY)
7053 if(gameInfo.variant == VariantAtomic &&
7054 (board[toY][toX] != EmptySquare || // capture?
7055 toX != fromX && (board[fromY][fromX] == WhitePawn || // e.p. ?
7056 board[fromY][fromX] == BlackPawn )
7058 AnimateAtomicCapture(board, fromX, fromY, toX, toY);
7064 ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
7067 CanPromote (ChessSquare piece, int y)
7069 if(gameMode == EditPosition) return FALSE; // no promotions when editing position
7070 // some variants have fixed promotion piece, no promotion at all, or another selection mechanism
7071 if(gameInfo.variant == VariantShogi || gameInfo.variant == VariantXiangqi ||
7072 gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat ||
7073 gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
7074 gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN) return FALSE;
7075 return (piece == BlackPawn && y == 1 ||
7076 piece == WhitePawn && y == BOARD_HEIGHT-2 ||
7077 piece == BlackLance && y == 1 ||
7078 piece == WhiteLance && y == BOARD_HEIGHT-2 );
7082 LeftClick (ClickType clickType, int xPix, int yPix)
7085 Boolean saveAnimate;
7086 static int second = 0, promotionChoice = 0, clearFlag = 0, sweepSelecting = 0;
7087 char promoChoice = NULLCHAR;
7089 static TimeMark lastClickTime, prevClickTime;
7091 if(SeekGraphClick(clickType, xPix, yPix, 0)) return;
7093 prevClickTime = lastClickTime; GetTimeMark(&lastClickTime);
7095 if (clickType == Press) ErrorPopDown();
7097 x = EventToSquare(xPix, BOARD_WIDTH);
7098 y = EventToSquare(yPix, BOARD_HEIGHT);
7099 if (!flipView && y >= 0) {
7100 y = BOARD_HEIGHT - 1 - y;
7102 if (flipView && x >= 0) {
7103 x = BOARD_WIDTH - 1 - x;
7106 if(promoSweep != EmptySquare) { // up-click during sweep-select of promo-piece
7107 defaultPromoChoice = promoSweep;
7108 promoSweep = EmptySquare; // terminate sweep
7109 promoDefaultAltered = TRUE;
7110 if(!selectFlag && !sweepSelecting && (x != toX || y != toY)) x = fromX, y = fromY; // and fake up-click on same square if we were still selecting
7113 if(promotionChoice) { // we are waiting for a click to indicate promotion piece
7114 if(clickType == Release) return; // ignore upclick of click-click destination
7115 promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
7116 if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
7117 if(gameInfo.holdingsWidth &&
7118 (WhiteOnMove(currentMove)
7119 ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y >= 0
7120 : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT) ) {
7121 // click in right holdings, for determining promotion piece
7122 ChessSquare p = boards[currentMove][y][x];
7123 if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
7124 if(p == WhitePawn || p == BlackPawn) p = EmptySquare; // [HGM] Pawns could be valid as deferral
7125 if(p != EmptySquare || gameInfo.variant == VariantGrand && toY != 0 && toY != BOARD_HEIGHT-1) { // [HGM] grand: empty square means defer
7126 FinishMove(NormalMove, fromX, fromY, toX, toY, p==EmptySquare ? NULLCHAR : ToLower(PieceToChar(p)));
7131 DrawPosition(FALSE, boards[currentMove]);
7135 /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
7136 if(clickType == Press
7137 && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
7138 || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
7139 || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
7142 if(gotPremove && x == premoveFromX && y == premoveFromY && clickType == Release) {
7143 // could be static click on premove from-square: abort premove
7145 ClearPremoveHighlights();
7148 if(clickType == Press && fromX == x && fromY == y && promoDefaultAltered && SubtractTimeMarks(&lastClickTime, &prevClickTime) >= 200)
7149 fromX = fromY = -1; // second click on piece after altering default promo piece treated as first click
7151 if(!promoDefaultAltered) { // determine default promotion piece, based on the side the user is moving for
7152 int side = (gameMode == IcsPlayingWhite || gameMode == MachinePlaysBlack ||
7153 gameMode != MachinePlaysWhite && gameMode != IcsPlayingBlack && WhiteOnMove(currentMove));
7154 defaultPromoChoice = DefaultPromoChoice(side);
7157 autoQueen = appData.alwaysPromoteToQueen;
7161 gatingPiece = EmptySquare;
7162 if (clickType != Press) {
7163 if(dragging) { // [HGM] from-square must have been reset due to game end since last press
7164 DragPieceEnd(xPix, yPix); dragging = 0;
7165 DrawPosition(FALSE, NULL);
7169 doubleClick = FALSE;
7170 if(gameMode == AnalyzeMode && (pausing || controlKey) && first.excludeMoves) { // use pause state to exclude moves
7171 doubleClick = TRUE; gatingPiece = boards[currentMove][y][x];
7173 fromX = x; fromY = y; toX = toY = -1;
7174 if(!appData.oneClick || !OnlyMove(&x, &y, FALSE) ||
7175 // even if only move, we treat as normal when this would trigger a promotion popup, to allow sweep selection
7176 appData.sweepSelect && CanPromote(boards[currentMove][fromY][fromX], fromY) && originalY != y) {
7178 if (OKToStartUserMove(fromX, fromY)) {
7180 MarkTargetSquares(0);
7181 if(gameMode == EditPosition && controlKey) gatingPiece = boards[currentMove][fromY][fromX];
7182 DragPieceBegin(xPix, yPix, FALSE); dragging = 1;
7183 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][fromY][fromX], fromY)) {
7184 promoSweep = defaultPromoChoice;
7185 selectFlag = 0; lastX = xPix; lastY = yPix;
7186 Sweep(0); // Pawn that is going to promote: preview promotion piece
7187 DisplayMessage("", _("Pull pawn backwards to under-promote"));
7189 if (appData.highlightDragging) {
7190 SetHighlights(fromX, fromY, -1, -1);
7194 } else fromX = fromY = -1;
7200 if (clickType == Press && gameMode != EditPosition) {
7205 // ignore off-board to clicks
7206 if(y < 0 || x < 0) return;
7208 /* Check if clicking again on the same color piece */
7209 fromP = boards[currentMove][fromY][fromX];
7210 toP = boards[currentMove][y][x];
7211 frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess;
7212 if ((WhitePawn <= fromP && fromP <= WhiteKing &&
7213 WhitePawn <= toP && toP <= WhiteKing &&
7214 !(fromP == WhiteKing && toP == WhiteRook && frc) &&
7215 !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
7216 (BlackPawn <= fromP && fromP <= BlackKing &&
7217 BlackPawn <= toP && toP <= BlackKing &&
7218 !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
7219 !(fromP == BlackKing && toP == BlackRook && frc))) {
7220 /* Clicked again on same color piece -- changed his mind */
7221 second = (x == fromX && y == fromY);
7222 if(second && gameMode == AnalyzeMode && SubtractTimeMarks(&lastClickTime, &prevClickTime) < 200) {
7223 second = FALSE; // first double-click rather than scond click
7224 doubleClick = first.excludeMoves; // used by UserMoveEvent to recognize exclude moves
7226 promoDefaultAltered = FALSE;
7227 MarkTargetSquares(1);
7228 if(!second || appData.oneClick && !OnlyMove(&x, &y, TRUE)) {
7229 if (appData.highlightDragging) {
7230 SetHighlights(x, y, -1, -1);
7234 if (OKToStartUserMove(x, y)) {
7235 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
7236 (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
7237 y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
7238 gatingPiece = boards[currentMove][fromY][fromX];
7239 else gatingPiece = doubleClick ? fromP : EmptySquare;
7241 fromY = y; dragging = 1;
7242 MarkTargetSquares(0);
7243 DragPieceBegin(xPix, yPix, FALSE);
7244 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) {
7245 promoSweep = defaultPromoChoice;
7246 selectFlag = 0; lastX = xPix; lastY = yPix;
7247 Sweep(0); // Pawn that is going to promote: preview promotion piece
7251 if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
7254 // ignore clicks on holdings
7255 if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
7258 if (clickType == Release && x == fromX && y == fromY) {
7259 DragPieceEnd(xPix, yPix); dragging = 0;
7261 // a deferred attempt to click-click move an empty square on top of a piece
7262 boards[currentMove][y][x] = EmptySquare;
7264 DrawPosition(FALSE, boards[currentMove]);
7265 fromX = fromY = -1; clearFlag = 0;
7268 if (appData.animateDragging) {
7269 /* Undo animation damage if any */
7270 DrawPosition(FALSE, NULL);
7272 if (second || sweepSelecting) {
7273 /* Second up/down in same square; just abort move */
7274 if(sweepSelecting) DrawPosition(FALSE, boards[currentMove]);
7275 second = sweepSelecting = 0;
7277 gatingPiece = EmptySquare;
7280 ClearPremoveHighlights();
7282 /* First upclick in same square; start click-click mode */
7283 SetHighlights(x, y, -1, -1);
7290 /* we now have a different from- and (possibly off-board) to-square */
7291 /* Completed move */
7292 if(!sweepSelecting) {
7295 } else sweepSelecting = 0; // this must be the up-click corresponding to the down-click that started the sweep
7297 saveAnimate = appData.animate;
7298 if (clickType == Press) {
7299 if(gameMode == EditPosition && boards[currentMove][fromY][fromX] == EmptySquare) {
7300 // must be Edit Position mode with empty-square selected
7301 fromX = x; fromY = y; DragPieceBegin(xPix, yPix, FALSE); dragging = 1; // consider this a new attempt to drag
7302 if(x >= BOARD_LEFT && x < BOARD_RGHT) clearFlag = 1; // and defer click-click move of empty-square to up-click
7305 if(HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, FALSE)) {
7306 if(appData.sweepSelect) {
7307 ChessSquare piece = boards[currentMove][fromY][fromX];
7308 promoSweep = defaultPromoChoice;
7309 if(PieceToChar(PROMOTED piece) == '+') promoSweep = PROMOTED piece;
7310 selectFlag = 0; lastX = xPix; lastY = yPix;
7311 Sweep(0); // Pawn that is going to promote: preview promotion piece
7313 DisplayMessage("", _("Pull pawn backwards to under-promote"));
7314 MarkTargetSquares(1);
7316 return; // promo popup appears on up-click
7318 /* Finish clickclick move */
7319 if (appData.animate || appData.highlightLastMove) {
7320 SetHighlights(fromX, fromY, toX, toY);
7326 // [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
7327 /* Finish drag move */
7328 if (appData.highlightLastMove) {
7329 SetHighlights(fromX, fromY, toX, toY);
7334 DragPieceEnd(xPix, yPix); dragging = 0;
7335 /* Don't animate move and drag both */
7336 appData.animate = FALSE;
7339 // moves into holding are invalid for now (except in EditPosition, adapting to-square)
7340 if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
7341 ChessSquare piece = boards[currentMove][fromY][fromX];
7342 if(gameMode == EditPosition && piece != EmptySquare &&
7343 fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
7346 if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
7347 n = PieceToNumber(piece - (int)BlackPawn);
7348 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
7349 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
7350 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
7352 if(x == BOARD_RGHT+1 && piece < BlackPawn) {
7353 n = PieceToNumber(piece);
7354 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
7355 boards[currentMove][n][BOARD_WIDTH-1] = piece;
7356 boards[currentMove][n][BOARD_WIDTH-2]++;
7358 boards[currentMove][fromY][fromX] = EmptySquare;
7362 MarkTargetSquares(1);
7363 DrawPosition(TRUE, boards[currentMove]);
7367 // off-board moves should not be highlighted
7368 if(x < 0 || y < 0) ClearHighlights();
7370 if(gatingPiece != EmptySquare && gameInfo.variant == VariantSChess) promoChoice = ToLower(PieceToChar(gatingPiece));
7372 if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, appData.sweepSelect)) {
7373 SetHighlights(fromX, fromY, toX, toY);
7374 MarkTargetSquares(1);
7375 if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
7376 // [HGM] super: promotion to captured piece selected from holdings
7377 ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
7378 promotionChoice = TRUE;
7379 // kludge follows to temporarily execute move on display, without promoting yet
7380 boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
7381 boards[currentMove][toY][toX] = p;
7382 DrawPosition(FALSE, boards[currentMove]);
7383 boards[currentMove][fromY][fromX] = p; // take back, but display stays
7384 boards[currentMove][toY][toX] = q;
7385 DisplayMessage("Click in holdings to choose piece", "");
7390 int oldMove = currentMove;
7391 UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
7392 if (!appData.highlightLastMove || gotPremove) ClearHighlights();
7393 if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
7394 if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
7395 Explode(boards[currentMove-1], fromX, fromY, toX, toY))
7396 DrawPosition(TRUE, boards[currentMove]);
7397 MarkTargetSquares(1);
7400 appData.animate = saveAnimate;
7401 if (appData.animate || appData.animateDragging) {
7402 /* Undo animation damage if needed */
7403 DrawPosition(FALSE, NULL);
7408 RightClick (ClickType action, int x, int y, int *fromX, int *fromY)
7409 { // front-end-free part taken out of PieceMenuPopup
7410 int whichMenu; int xSqr, ySqr;
7412 if(seekGraphUp) { // [HGM] seekgraph
7413 if(action == Press) SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
7414 if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
7418 if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
7419 && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
7420 if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
7421 if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
7422 if(action == Press) {
7423 originalFlip = flipView;
7424 flipView = !flipView; // temporarily flip board to see game from partners perspective
7425 DrawPosition(TRUE, partnerBoard);
7426 DisplayMessage(partnerStatus, "");
7428 } else if(action == Release) {
7429 flipView = originalFlip;
7430 DrawPosition(TRUE, boards[currentMove]);
7436 xSqr = EventToSquare(x, BOARD_WIDTH);
7437 ySqr = EventToSquare(y, BOARD_HEIGHT);
7438 if (action == Release) {
7439 if(pieceSweep != EmptySquare) {
7440 EditPositionMenuEvent(pieceSweep, toX, toY);
7441 pieceSweep = EmptySquare;
7442 } else UnLoadPV(); // [HGM] pv
7444 if (action != Press) return -2; // return code to be ignored
7447 if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;
7449 if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;
7450 if (xSqr < 0 || ySqr < 0) return -1;
7451 if(appData.pieceMenu) { whichMenu = 0; break; } // edit-position menu
7452 pieceSweep = shiftKey ? BlackPawn : WhitePawn; // [HGM] sweep: prepare selecting piece by mouse sweep
7453 toX = xSqr; toY = ySqr; lastX = x, lastY = y;
7454 if(flipView) toX = BOARD_WIDTH - 1 - toX; else toY = BOARD_HEIGHT - 1 - toY;
7458 if(!appData.icsEngineAnalyze) return -1;
7459 case IcsPlayingWhite:
7460 case IcsPlayingBlack:
7461 if(!appData.zippyPlay) goto noZip;
7464 case MachinePlaysWhite:
7465 case MachinePlaysBlack:
7466 case TwoMachinesPlay: // [HGM] pv: use for showing PV
7467 if (!appData.dropMenu) {
7469 return 2; // flag front-end to grab mouse events
7471 if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
7472 gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
7475 if (xSqr < 0 || ySqr < 0) return -1;
7476 if (!appData.dropMenu || appData.testLegality &&
7477 gameInfo.variant != VariantBughouse &&
7478 gameInfo.variant != VariantCrazyhouse) return -1;
7479 whichMenu = 1; // drop menu
7485 if (((*fromX = xSqr) < 0) ||
7486 ((*fromY = ySqr) < 0)) {
7487 *fromX = *fromY = -1;
7491 *fromX = BOARD_WIDTH - 1 - *fromX;
7493 *fromY = BOARD_HEIGHT - 1 - *fromY;
7499 SendProgramStatsToFrontend (ChessProgramState * cps, ChessProgramStats * cpstats)
7501 // char * hint = lastHint;
7502 FrontEndProgramStats stats;
7504 stats.which = cps == &first ? 0 : 1;
7505 stats.depth = cpstats->depth;
7506 stats.nodes = cpstats->nodes;
7507 stats.score = cpstats->score;
7508 stats.time = cpstats->time;
7509 stats.pv = cpstats->movelist;
7510 stats.hint = lastHint;
7511 stats.an_move_index = 0;
7512 stats.an_move_count = 0;
7514 if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
7515 stats.hint = cpstats->move_name;
7516 stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
7517 stats.an_move_count = cpstats->nr_moves;
7520 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
7522 SetProgramStats( &stats );
7526 ClearEngineOutputPane (int which)
7528 static FrontEndProgramStats dummyStats;
7529 dummyStats.which = which;
7530 dummyStats.pv = "#";
7531 SetProgramStats( &dummyStats );
7534 #define MAXPLAYERS 500
7537 TourneyStandings (int display)
7539 int i, w, b, color, wScore, bScore, dummy, nr=0, nPlayers=0;
7540 int score[MAXPLAYERS], ranking[MAXPLAYERS], points[MAXPLAYERS], games[MAXPLAYERS];
7541 char result, *p, *names[MAXPLAYERS];
7543 if(appData.tourneyType < 0 && !strchr(appData.results, '*'))
7544 return strdup(_("Swiss tourney finished")); // standings of Swiss yet TODO
7545 names[0] = p = strdup(appData.participants);
7546 while(p = strchr(p, '\n')) *p++ = NULLCHAR, names[++nPlayers] = p; // count participants
7548 for(i=0; i<nPlayers; i++) score[i] = games[i] = 0;
7550 while(result = appData.results[nr]) {
7551 color = Pairing(nr, nPlayers, &w, &b, &dummy);
7552 if(!(color ^ matchGame & 1)) { dummy = w; w = b; b = dummy; }
7553 wScore = bScore = 0;
7555 case '+': wScore = 2; break;
7556 case '-': bScore = 2; break;
7557 case '=': wScore = bScore = 1; break;
7559 case '*': return strdup("busy"); // tourney not finished
7567 if(appData.tourneyType > 0) nPlayers = appData.tourneyType; // in gauntlet, list only gauntlet engine(s)
7568 for(w=0; w<nPlayers; w++) {
7570 for(i=0; i<nPlayers; i++) if(score[i] > bScore) bScore = score[i], b = i;
7571 ranking[w] = b; points[w] = bScore; score[b] = -2;
7573 p = malloc(nPlayers*34+1);
7574 for(w=0; w<nPlayers && w<display; w++)
7575 sprintf(p+34*w, "%2d. %5.1f/%-3d %-19.19s\n", w+1, points[w]/2., games[ranking[w]], names[ranking[w]]);
7581 Count (Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
7582 { // count all piece types
7584 *nB = *nW = *wStale = *bStale = *bishopColor = 0;
7585 for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
7586 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
7589 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
7590 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
7591 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
7592 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
7593 p == BlackBishop || p == BlackFerz || p == BlackAlfil )
7594 *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
7599 SufficientDefence (int pCnt[], int side, int nMine, int nHis)
7601 int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
7602 int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
7604 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
7605 if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
7606 if(myPawns == 2 && nMine == 3) // KPP
7607 return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
7608 if(myPawns == 1 && nMine == 2) // KP
7609 return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
7610 if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
7611 return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
7612 if(myPawns) return FALSE;
7613 if(pCnt[WhiteRook+side])
7614 return pCnt[BlackRook-side] ||
7615 pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
7616 pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
7617 pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
7618 if(pCnt[WhiteCannon+side]) {
7619 if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
7620 return majorDefense || pCnt[BlackAlfil-side] >= 2;
7622 if(pCnt[WhiteKnight+side])
7623 return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
7628 MatingPotential (int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
7630 VariantClass v = gameInfo.variant;
7632 if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
7633 if(v == VariantShatranj) return TRUE; // always winnable through baring
7634 if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
7635 if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
7637 if(v == VariantXiangqi) {
7638 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
7640 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
7641 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
7642 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
7643 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
7644 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
7645 if(stale) // we have at least one last-rank P plus perhaps C
7646 return majors // KPKX
7647 || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
7649 return pCnt[WhiteFerz+side] // KCAK
7650 || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
7651 || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
7652 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
7654 } else if(v == VariantKnightmate) {
7655 if(nMine == 1) return FALSE;
7656 if(nMine == 2 && nHis == 1 && pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side] + pCnt[WhiteKnight+side]) return FALSE; // KBK is only draw
7657 } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
7658 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
7660 if(nMine == 1) return FALSE; // bare King
7661 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
7662 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
7663 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
7664 // by now we have King + 1 piece (or multiple Bishops on the same color)
7665 if(pCnt[WhiteKnight+side])
7666 return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
7667 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
7668 || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
7670 return (pCnt[BlackKnight-side]); // KBKN, KFKN
7671 if(pCnt[WhiteAlfil+side])
7672 return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
7673 if(pCnt[WhiteWazir+side])
7674 return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
7681 CompareWithRights (Board b1, Board b2)
7684 if(!CompareBoards(b1, b2)) return FALSE;
7685 if(b1[EP_STATUS] != b2[EP_STATUS]) return FALSE;
7686 /* compare castling rights */
7687 if( b1[CASTLING][2] != b2[CASTLING][2] && (b2[CASTLING][0] != NoRights || b2[CASTLING][1] != NoRights) )
7688 rights++; /* King lost rights, while rook still had them */
7689 if( b1[CASTLING][2] != NoRights ) { /* king has rights */
7690 if( b1[CASTLING][0] != b2[CASTLING][0] || b1[CASTLING][1] != b2[CASTLING][1] )
7691 rights++; /* but at least one rook lost them */
7693 if( b1[CASTLING][5] != b1[CASTLING][5] && (b2[CASTLING][3] != NoRights || b2[CASTLING][4] != NoRights) )
7695 if( b1[CASTLING][5] != NoRights ) {
7696 if( b1[CASTLING][3] != b2[CASTLING][3] || b1[CASTLING][4] != b2[CASTLING][4] )
7703 Adjudicate (ChessProgramState *cps)
7704 { // [HGM] some adjudications useful with buggy engines
7705 // [HGM] adjudicate: made into separate routine, which now can be called after every move
7706 // In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
7707 // Actually ending the game is now based on the additional internal condition canAdjudicate.
7708 // Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
7709 int k, drop, count = 0; static int bare = 1;
7710 ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
7711 Boolean canAdjudicate = !appData.icsActive;
7713 // most tests only when we understand the game, i.e. legality-checking on
7714 if( appData.testLegality )
7715 { /* [HGM] Some more adjudications for obstinate engines */
7716 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+1], i;
7717 static int moveCount = 6;
7719 char *reason = NULL;
7721 /* Count what is on board. */
7722 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
7724 /* Some material-based adjudications that have to be made before stalemate test */
7725 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
7726 // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
7727 boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
7728 if(canAdjudicate && appData.checkMates) {
7730 SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7731 GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
7732 "Xboard adjudication: King destroyed", GE_XBOARD );
7737 /* Bare King in Shatranj (loses) or Losers (wins) */
7738 if( nrW == 1 || nrB == 1) {
7739 if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
7740 boards[forwardMostMove][EP_STATUS] = EP_WINS; // mark as win, so it becomes claimable
7741 if(canAdjudicate && appData.checkMates) {
7743 SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
7744 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7745 "Xboard adjudication: Bare king", GE_XBOARD );
7749 if( gameInfo.variant == VariantShatranj && --bare < 0)
7751 boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
7752 if(canAdjudicate && appData.checkMates) {
7753 /* but only adjudicate if adjudication enabled */
7755 SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7756 GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
7757 "Xboard adjudication: Bare king", GE_XBOARD );
7764 // don't wait for engine to announce game end if we can judge ourselves
7765 switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
7767 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
7768 int i, checkCnt = 0; // (should really be done by making nr of checks part of game state)
7769 for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
7770 if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
7773 reason = "Xboard adjudication: 3rd check";
7774 boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
7784 reason = "Xboard adjudication: Stalemate";
7785 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
7786 boards[forwardMostMove][EP_STATUS] = EP_STALEMATE; // default result for stalemate is draw
7787 if(gameInfo.variant == VariantLosers || gameInfo.variant == VariantGiveaway) // [HGM] losers:
7788 boards[forwardMostMove][EP_STATUS] = EP_WINS; // in these variants stalemated is always a win
7789 else if(gameInfo.variant == VariantSuicide) // in suicide it depends
7790 boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
7791 ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
7792 EP_CHECKMATE : EP_WINS);
7793 else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi)
7794 boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
7798 reason = "Xboard adjudication: Checkmate";
7799 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
7800 if(gameInfo.variant == VariantShogi) {
7801 if(forwardMostMove > backwardMostMove
7802 && moveList[forwardMostMove-1][1] == '@'
7803 && CharToPiece(ToUpper(moveList[forwardMostMove-1][0])) == WhitePawn) {
7804 reason = "XBoard adjudication: pawn-drop mate";
7805 boards[forwardMostMove][EP_STATUS] = EP_WINS;
7811 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
7813 result = GameIsDrawn; break;
7815 result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
7817 result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
7821 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
7823 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7824 GameEnds( result, reason, GE_XBOARD );
7828 /* Next absolutely insufficient mating material. */
7829 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
7830 !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
7831 { /* includes KBK, KNK, KK of KBKB with like Bishops */
7833 /* always flag draws, for judging claims */
7834 boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
7836 if(canAdjudicate && appData.materialDraws) {
7837 /* but only adjudicate them if adjudication enabled */
7838 if(engineOpponent) {
7839 SendToProgram("force\n", engineOpponent); // suppress reply
7840 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
7842 GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
7847 /* Then some trivial draws (only adjudicate, cannot be claimed) */
7848 if(gameInfo.variant == VariantXiangqi ?
7849 SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
7851 ( nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
7852 || nr[WhiteQueen] && nr[BlackQueen]==1 /* KQKQ */
7853 || nr[WhiteKnight]==2 || nr[BlackKnight]==2 /* KNNK */
7854 || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
7856 if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
7857 { /* if the first 3 moves do not show a tactical win, declare draw */
7858 if(engineOpponent) {
7859 SendToProgram("force\n", engineOpponent); // suppress reply
7860 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7862 GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
7865 } else moveCount = 6;
7868 // Repetition draws and 50-move rule can be applied independently of legality testing
7870 /* Check for rep-draws */
7872 drop = gameInfo.holdingsSize && (gameInfo.variant != VariantSuper && gameInfo.variant != VariantSChess
7873 && gameInfo.variant != VariantGreat && gameInfo.variant != VariantGrand);
7874 for(k = forwardMostMove-2;
7875 k>=backwardMostMove && k>=forwardMostMove-100 && (drop ||
7876 (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
7877 (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE);
7880 if(CompareBoards(boards[k], boards[forwardMostMove])) {
7881 /* compare castling rights */
7882 if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
7883 (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
7884 rights++; /* King lost rights, while rook still had them */
7885 if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
7886 if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
7887 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
7888 rights++; /* but at least one rook lost them */
7890 if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
7891 (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
7893 if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
7894 if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
7895 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
7898 if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
7899 && appData.drawRepeats > 1) {
7900 /* adjudicate after user-specified nr of repeats */
7901 int result = GameIsDrawn;
7902 char *details = "XBoard adjudication: repetition draw";
7903 if((gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi) && appData.testLegality) {
7904 // [HGM] xiangqi: check for forbidden perpetuals
7905 int m, ourPerpetual = 1, hisPerpetual = 1;
7906 for(m=forwardMostMove; m>k; m-=2) {
7907 if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
7908 ourPerpetual = 0; // the current mover did not always check
7909 if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
7910 hisPerpetual = 0; // the opponent did not always check
7912 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
7913 ourPerpetual, hisPerpetual);
7914 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
7915 result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7916 details = "Xboard adjudication: perpetual checking";
7918 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
7919 break; // (or we would have caught him before). Abort repetition-checking loop.
7921 if(gameInfo.variant == VariantShogi) { // in Shogi other repetitions are draws
7922 if(BOARD_HEIGHT == 5 && BOARD_RGHT - BOARD_LEFT == 5) { // but in mini-Shogi gote wins!
7924 details = "Xboard adjudication: repetition";
7926 } else // it must be XQ
7927 // Now check for perpetual chases
7928 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
7929 hisPerpetual = PerpetualChase(k, forwardMostMove);
7930 ourPerpetual = PerpetualChase(k+1, forwardMostMove);
7931 if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
7932 static char resdet[MSG_SIZ];
7933 result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7935 snprintf(resdet, MSG_SIZ, "Xboard adjudication: perpetual chasing of %c%c", ourPerpetual>>8, ourPerpetual&255);
7937 if(hisPerpetual && !ourPerpetual) // he is chasing us, but did not repeat yet
7938 break; // Abort repetition-checking loop.
7940 // if neither of us is checking or chasing all the time, or both are, it is draw
7942 if(engineOpponent) {
7943 SendToProgram("force\n", engineOpponent); // suppress reply
7944 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7946 GameEnds( result, details, GE_XBOARD );
7949 if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
7950 boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
7954 /* Now we test for 50-move draws. Determine ply count */
7955 count = forwardMostMove;
7956 /* look for last irreversble move */
7957 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
7959 /* if we hit starting position, add initial plies */
7960 if( count == backwardMostMove )
7961 count -= initialRulePlies;
7962 count = forwardMostMove - count;
7963 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
7964 // adjust reversible move counter for checks in Xiangqi
7965 int i = forwardMostMove - count, inCheck = 0, lastCheck;
7966 if(i < backwardMostMove) i = backwardMostMove;
7967 while(i <= forwardMostMove) {
7968 lastCheck = inCheck; // check evasion does not count
7969 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
7970 if(inCheck || lastCheck) count--; // check does not count
7975 boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
7976 /* this is used to judge if draw claims are legal */
7977 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
7978 if(engineOpponent) {
7979 SendToProgram("force\n", engineOpponent); // suppress reply
7980 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7982 GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
7986 /* if draw offer is pending, treat it as a draw claim
7987 * when draw condition present, to allow engines a way to
7988 * claim draws before making their move to avoid a race
7989 * condition occurring after their move
7991 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
7993 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
7994 p = "Draw claim: 50-move rule";
7995 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
7996 p = "Draw claim: 3-fold repetition";
7997 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
7998 p = "Draw claim: insufficient mating material";
7999 if( p != NULL && canAdjudicate) {
8000 if(engineOpponent) {
8001 SendToProgram("force\n", engineOpponent); // suppress reply
8002 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8004 GameEnds( GameIsDrawn, p, GE_XBOARD );
8009 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
8010 if(engineOpponent) {
8011 SendToProgram("force\n", engineOpponent); // suppress reply
8012 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8014 GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
8021 SendMoveToBookUser (int moveNr, ChessProgramState *cps, int initial)
8022 { // [HGM] book: this routine intercepts moves to simulate book replies
8023 char *bookHit = NULL;
8025 //first determine if the incoming move brings opponent into his book
8026 if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
8027 bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
8028 if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
8029 if(bookHit != NULL && !cps->bookSuspend) {
8030 // make sure opponent is not going to reply after receiving move to book position
8031 SendToProgram("force\n", cps);
8032 cps->bookSuspend = TRUE; // flag indicating it has to be restarted
8034 if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
8035 // now arrange restart after book miss
8037 // after a book hit we never send 'go', and the code after the call to this routine
8038 // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
8039 char buf[MSG_SIZ], *move = bookHit;
8041 int fromX, fromY, toX, toY;
8045 if (ParseOneMove(bookHit, forwardMostMove, &moveType,
8046 &fromX, &fromY, &toX, &toY, &promoChar)) {
8047 (void) CoordsToAlgebraic(boards[forwardMostMove],
8048 PosFlags(forwardMostMove),
8049 fromY, fromX, toY, toX, promoChar, move);
8051 if(appData.debugMode) fprintf(debugFP, "Book move could not be parsed\n");
8055 snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), move); // force book move into program supposed to play it
8056 SendToProgram(buf, cps);
8057 if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
8058 } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
8059 SendToProgram("go\n", cps);
8060 cps->bookSuspend = FALSE; // after a 'go' we are never suspended
8061 } else { // 'go' might be sent based on 'firstMove' after this routine returns
8062 if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
8063 SendToProgram("go\n", cps);
8064 cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
8066 return bookHit; // notify caller of hit, so it can take action to send move to opponent
8070 LoadError (char *errmess, ChessProgramState *cps)
8071 { // unloads engine and switches back to -ncp mode if it was first
8072 if(cps->initDone) return FALSE;
8073 cps->isr = NULL; // this should suppress further error popups from breaking pipes
8074 DestroyChildProcess(cps->pr, 9 ); // just to be sure
8077 appData.noChessProgram = TRUE;
8078 gameMode = MachinePlaysBlack; ModeHighlight(); // kludge to unmark Machine Black menu
8079 gameMode = BeginningOfGame; ModeHighlight();
8082 if(GetDelayedEvent()) CancelDelayedEvent(), ThawUI(); // [HGM] cancel remaining loading effort scheduled after feature timeout
8083 DisplayMessage("", ""); // erase waiting message
8084 if(errmess) DisplayError(errmess, 0); // announce reason, if given
8089 ChessProgramState *savedState;
8091 DeferredBookMove (void)
8093 if(savedState->lastPing != savedState->lastPong)
8094 ScheduleDelayedEvent(DeferredBookMove, 10);
8096 HandleMachineMove(savedMessage, savedState);
8099 static int savedWhitePlayer, savedBlackPlayer, pairingReceived;
8100 static ChessProgramState *stalledEngine;
8101 static char stashedInputMove[MSG_SIZ];
8104 HandleMachineMove (char *message, ChessProgramState *cps)
8106 char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
8107 char realname[MSG_SIZ];
8108 int fromX, fromY, toX, toY;
8112 int machineWhite, oldError;
8115 if(cps == &pairing && sscanf(message, "%d-%d", &savedWhitePlayer, &savedBlackPlayer) == 2) {
8116 // [HGM] pairing: Mega-hack! Pairing engine also uses this routine (so it could give other WB commands).
8117 if(savedWhitePlayer == 0 || savedBlackPlayer == 0) {
8118 DisplayError(_("Invalid pairing from pairing engine"), 0);
8121 pairingReceived = 1;
8123 return; // Skim the pairing messages here.
8126 oldError = cps->userError; cps->userError = 0;
8128 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
8130 * Kludge to ignore BEL characters
8132 while (*message == '\007') message++;
8135 * [HGM] engine debug message: ignore lines starting with '#' character
8137 if(cps->debug && *message == '#') return;
8140 * Look for book output
8142 if (cps == &first && bookRequested) {
8143 if (message[0] == '\t' || message[0] == ' ') {
8144 /* Part of the book output is here; append it */
8145 strcat(bookOutput, message);
8146 strcat(bookOutput, " \n");
8148 } else if (bookOutput[0] != NULLCHAR) {
8149 /* All of book output has arrived; display it */
8150 char *p = bookOutput;
8151 while (*p != NULLCHAR) {
8152 if (*p == '\t') *p = ' ';
8155 DisplayInformation(bookOutput);
8156 bookRequested = FALSE;
8157 /* Fall through to parse the current output */
8162 * Look for machine move.
8164 if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
8165 (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
8167 if(pausing && !cps->pause) { // for pausing engine that does not support 'pause', we stash its move for processing when we resume.
8168 if(appData.debugMode) fprintf(debugFP, "pause %s engine after move\n", cps->which);
8169 safeStrCpy(stashedInputMove, message, MSG_SIZ);
8170 stalledEngine = cps;
8171 if(appData.ponderNextMove) { // bring opponent out of ponder
8172 if(gameMode == TwoMachinesPlay) {
8173 if(cps->other->pause)
8174 PauseEngine(cps->other);
8176 SendToProgram("easy\n", cps->other);
8183 /* This method is only useful on engines that support ping */
8184 if (cps->lastPing != cps->lastPong) {
8185 if (gameMode == BeginningOfGame) {
8186 /* Extra move from before last new; ignore */
8187 if (appData.debugMode) {
8188 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8191 if (appData.debugMode) {
8192 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8193 cps->which, gameMode);
8196 SendToProgram("undo\n", cps);
8202 case BeginningOfGame:
8203 /* Extra move from before last reset; ignore */
8204 if (appData.debugMode) {
8205 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8212 /* Extra move after we tried to stop. The mode test is
8213 not a reliable way of detecting this problem, but it's
8214 the best we can do on engines that don't support ping.
8216 if (appData.debugMode) {
8217 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8218 cps->which, gameMode);
8220 SendToProgram("undo\n", cps);
8223 case MachinePlaysWhite:
8224 case IcsPlayingWhite:
8225 machineWhite = TRUE;
8228 case MachinePlaysBlack:
8229 case IcsPlayingBlack:
8230 machineWhite = FALSE;
8233 case TwoMachinesPlay:
8234 machineWhite = (cps->twoMachinesColor[0] == 'w');
8237 if (WhiteOnMove(forwardMostMove) != machineWhite) {
8238 if (appData.debugMode) {
8240 "Ignoring move out of turn by %s, gameMode %d"
8241 ", forwardMost %d\n",
8242 cps->which, gameMode, forwardMostMove);
8247 if(cps->alphaRank) AlphaRank(machineMove, 4);
8248 if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
8249 &fromX, &fromY, &toX, &toY, &promoChar)) {
8250 /* Machine move could not be parsed; ignore it. */
8251 snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
8252 machineMove, _(cps->which));
8253 DisplayMoveError(buf1);
8254 snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
8255 machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
8256 if (gameMode == TwoMachinesPlay) {
8257 GameEnds(machineWhite ? BlackWins : WhiteWins,
8263 /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
8264 /* So we have to redo legality test with true e.p. status here, */
8265 /* to make sure an illegal e.p. capture does not slip through, */
8266 /* to cause a forfeit on a justified illegal-move complaint */
8267 /* of the opponent. */
8268 if( gameMode==TwoMachinesPlay && appData.testLegality ) {
8270 moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
8271 fromY, fromX, toY, toX, promoChar);
8272 if(moveType == IllegalMove) {
8273 snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
8274 machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
8275 GameEnds(machineWhite ? BlackWins : WhiteWins,
8278 } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
8279 /* [HGM] Kludge to handle engines that send FRC-style castling
8280 when they shouldn't (like TSCP-Gothic) */
8282 case WhiteASideCastleFR:
8283 case BlackASideCastleFR:
8285 currentMoveString[2]++;
8287 case WhiteHSideCastleFR:
8288 case BlackHSideCastleFR:
8290 currentMoveString[2]--;
8292 default: ; // nothing to do, but suppresses warning of pedantic compilers
8295 hintRequested = FALSE;
8296 lastHint[0] = NULLCHAR;
8297 bookRequested = FALSE;
8298 /* Program may be pondering now */
8299 cps->maybeThinking = TRUE;
8300 if (cps->sendTime == 2) cps->sendTime = 1;
8301 if (cps->offeredDraw) cps->offeredDraw--;
8303 /* [AS] Save move info*/
8304 pvInfoList[ forwardMostMove ].score = programStats.score;
8305 pvInfoList[ forwardMostMove ].depth = programStats.depth;
8306 pvInfoList[ forwardMostMove ].time = programStats.time; // [HGM] PGNtime: take time from engine stats
8308 MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
8310 /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
8311 if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
8314 while( count < adjudicateLossPlies ) {
8315 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
8318 score = -score; /* Flip score for winning side */
8321 if( score > adjudicateLossThreshold ) {
8328 if( count >= adjudicateLossPlies ) {
8329 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8331 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
8332 "Xboard adjudication",
8339 if(Adjudicate(cps)) {
8340 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8341 return; // [HGM] adjudicate: for all automatic game ends
8345 if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
8347 if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
8348 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
8350 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8352 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8354 if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
8355 char buf[3*MSG_SIZ];
8357 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
8358 programStats.score / 100.,
8360 programStats.time / 100.,
8361 (unsigned int)programStats.nodes,
8362 (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
8363 programStats.movelist);
8365 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
8370 /* [AS] Clear stats for next move */
8371 ClearProgramStats();
8372 thinkOutput[0] = NULLCHAR;
8373 hiddenThinkOutputState = 0;
8376 if (gameMode == TwoMachinesPlay) {
8377 /* [HGM] relaying draw offers moved to after reception of move */
8378 /* and interpreting offer as claim if it brings draw condition */
8379 if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
8380 SendToProgram("draw\n", cps->other);
8382 if (cps->other->sendTime) {
8383 SendTimeRemaining(cps->other,
8384 cps->other->twoMachinesColor[0] == 'w');
8386 bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
8387 if (firstMove && !bookHit) {
8389 if (cps->other->useColors) {
8390 SendToProgram(cps->other->twoMachinesColor, cps->other);
8392 SendToProgram("go\n", cps->other);
8394 cps->other->maybeThinking = TRUE;
8397 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8399 if (!pausing && appData.ringBellAfterMoves) {
8404 * Reenable menu items that were disabled while
8405 * machine was thinking
8407 if (gameMode != TwoMachinesPlay)
8408 SetUserThinkingEnables();
8410 // [HGM] book: after book hit opponent has received move and is now in force mode
8411 // force the book reply into it, and then fake that it outputted this move by jumping
8412 // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
8414 static char bookMove[MSG_SIZ]; // a bit generous?
8416 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
8417 strcat(bookMove, bookHit);
8420 programStats.nodes = programStats.depth = programStats.time =
8421 programStats.score = programStats.got_only_move = 0;
8422 sprintf(programStats.movelist, "%s (xbook)", bookHit);
8424 if(cps->lastPing != cps->lastPong) {
8425 savedMessage = message; // args for deferred call
8427 ScheduleDelayedEvent(DeferredBookMove, 10);
8436 /* Set special modes for chess engines. Later something general
8437 * could be added here; for now there is just one kludge feature,
8438 * needed because Crafty 15.10 and earlier don't ignore SIGINT
8439 * when "xboard" is given as an interactive command.
8441 if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
8442 cps->useSigint = FALSE;
8443 cps->useSigterm = FALSE;
8445 if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
8446 ParseFeatures(message+8, cps);
8447 return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
8450 if (!strncmp(message, "setup ", 6) &&
8451 (!appData.testLegality || gameInfo.variant == VariantFairy || NonStandardBoardSize())
8452 ) { // [HGM] allow first engine to define opening position
8453 int dummy, s=6; char buf[MSG_SIZ];
8454 if(appData.icsActive || forwardMostMove != 0 || cps != &first) return;
8455 if(sscanf(message, "setup (%s", buf) == 1) s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTable(pieceToChar, buf);
8456 if(startedFromSetupPosition) return;
8457 if(sscanf(message+s, "%dx%d+%d", &dummy, &dummy, &dummy) == 3) while(message[s] && message[s++] != ' '); // for compatibility with Alien Edition
8458 ParseFEN(boards[0], &dummy, message+s);
8459 DrawPosition(TRUE, boards[0]);
8460 startedFromSetupPosition = TRUE;
8463 /* [HGM] Allow engine to set up a position. Don't ask me why one would
8464 * want this, I was asked to put it in, and obliged.
8466 if (!strncmp(message, "setboard ", 9)) {
8467 Board initial_position;
8469 GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
8471 if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
8472 DisplayError(_("Bad FEN received from engine"), 0);
8476 CopyBoard(boards[0], initial_position);
8477 initialRulePlies = FENrulePlies;
8478 if(blackPlaysFirst) gameMode = MachinePlaysWhite;
8479 else gameMode = MachinePlaysBlack;
8480 DrawPosition(FALSE, boards[currentMove]);
8486 * Look for communication commands
8488 if (!strncmp(message, "telluser ", 9)) {
8489 if(message[9] == '\\' && message[10] == '\\')
8490 EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
8492 DisplayNote(message + 9);
8495 if (!strncmp(message, "tellusererror ", 14)) {
8497 if(message[14] == '\\' && message[15] == '\\')
8498 EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
8500 DisplayError(message + 14, 0);
8503 if (!strncmp(message, "tellopponent ", 13)) {
8504 if (appData.icsActive) {
8506 snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
8510 DisplayNote(message + 13);
8514 if (!strncmp(message, "tellothers ", 11)) {
8515 if (appData.icsActive) {
8517 snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
8520 } else if(appData.autoComment) AppendComment (forwardMostMove, message + 11, 1); // in local mode, add as move comment
8523 if (!strncmp(message, "tellall ", 8)) {
8524 if (appData.icsActive) {
8526 snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
8530 DisplayNote(message + 8);
8534 if (strncmp(message, "warning", 7) == 0) {
8535 /* Undocumented feature, use tellusererror in new code */
8536 DisplayError(message, 0);
8539 if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
8540 safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
8541 strcat(realname, " query");
8542 AskQuestion(realname, buf2, buf1, cps->pr);
8545 /* Commands from the engine directly to ICS. We don't allow these to be
8546 * sent until we are logged on. Crafty kibitzes have been known to
8547 * interfere with the login process.
8550 if (!strncmp(message, "tellics ", 8)) {
8551 SendToICS(message + 8);
8555 if (!strncmp(message, "tellicsnoalias ", 15)) {
8556 SendToICS(ics_prefix);
8557 SendToICS(message + 15);
8561 /* The following are for backward compatibility only */
8562 if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
8563 !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
8564 SendToICS(ics_prefix);
8570 if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
8574 * If the move is illegal, cancel it and redraw the board.
8575 * Also deal with other error cases. Matching is rather loose
8576 * here to accommodate engines written before the spec.
8578 if (strncmp(message + 1, "llegal move", 11) == 0 ||
8579 strncmp(message, "Error", 5) == 0) {
8580 if (StrStr(message, "name") ||
8581 StrStr(message, "rating") || StrStr(message, "?") ||
8582 StrStr(message, "result") || StrStr(message, "board") ||
8583 StrStr(message, "bk") || StrStr(message, "computer") ||
8584 StrStr(message, "variant") || StrStr(message, "hint") ||
8585 StrStr(message, "random") || StrStr(message, "depth") ||
8586 StrStr(message, "accepted")) {
8589 if (StrStr(message, "protover")) {
8590 /* Program is responding to input, so it's apparently done
8591 initializing, and this error message indicates it is
8592 protocol version 1. So we don't need to wait any longer
8593 for it to initialize and send feature commands. */
8594 FeatureDone(cps, 1);
8595 cps->protocolVersion = 1;
8598 cps->maybeThinking = FALSE;
8600 if (StrStr(message, "draw")) {
8601 /* Program doesn't have "draw" command */
8602 cps->sendDrawOffers = 0;
8605 if (cps->sendTime != 1 &&
8606 (StrStr(message, "time") || StrStr(message, "otim"))) {
8607 /* Program apparently doesn't have "time" or "otim" command */
8611 if (StrStr(message, "analyze")) {
8612 cps->analysisSupport = FALSE;
8613 cps->analyzing = FALSE;
8614 // Reset(FALSE, TRUE); // [HGM] this caused discrepancy between display and internal state!
8615 EditGameEvent(); // [HGM] try to preserve loaded game
8616 snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
8617 DisplayError(buf2, 0);
8620 if (StrStr(message, "(no matching move)st")) {
8621 /* Special kludge for GNU Chess 4 only */
8622 cps->stKludge = TRUE;
8623 SendTimeControl(cps, movesPerSession, timeControl,
8624 timeIncrement, appData.searchDepth,
8628 if (StrStr(message, "(no matching move)sd")) {
8629 /* Special kludge for GNU Chess 4 only */
8630 cps->sdKludge = TRUE;
8631 SendTimeControl(cps, movesPerSession, timeControl,
8632 timeIncrement, appData.searchDepth,
8636 if (!StrStr(message, "llegal")) {
8639 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8640 gameMode == IcsIdle) return;
8641 if (forwardMostMove <= backwardMostMove) return;
8642 if (pausing) PauseEvent();
8643 if(appData.forceIllegal) {
8644 // [HGM] illegal: machine refused move; force position after move into it
8645 SendToProgram("force\n", cps);
8646 if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
8647 // we have a real problem now, as SendBoard will use the a2a3 kludge
8648 // when black is to move, while there might be nothing on a2 or black
8649 // might already have the move. So send the board as if white has the move.
8650 // But first we must change the stm of the engine, as it refused the last move
8651 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
8652 if(WhiteOnMove(forwardMostMove)) {
8653 SendToProgram("a7a6\n", cps); // for the engine black still had the move
8654 SendBoard(cps, forwardMostMove); // kludgeless board
8656 SendToProgram("a2a3\n", cps); // for the engine white still had the move
8657 CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
8658 SendBoard(cps, forwardMostMove+1); // kludgeless board
8660 } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
8661 if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
8662 gameMode == TwoMachinesPlay)
8663 SendToProgram("go\n", cps);
8666 if (gameMode == PlayFromGameFile) {
8667 /* Stop reading this game file */
8668 gameMode = EditGame;
8671 /* [HGM] illegal-move claim should forfeit game when Xboard */
8672 /* only passes fully legal moves */
8673 if( appData.testLegality && gameMode == TwoMachinesPlay ) {
8674 GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
8675 "False illegal-move claim", GE_XBOARD );
8676 return; // do not take back move we tested as valid
8678 currentMove = forwardMostMove-1;
8679 DisplayMove(currentMove-1); /* before DisplayMoveError */
8680 SwitchClocks(forwardMostMove-1); // [HGM] race
8681 DisplayBothClocks();
8682 snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
8683 parseList[currentMove], _(cps->which));
8684 DisplayMoveError(buf1);
8685 DrawPosition(FALSE, boards[currentMove]);
8687 SetUserThinkingEnables();
8690 if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
8691 /* Program has a broken "time" command that
8692 outputs a string not ending in newline.
8698 * If chess program startup fails, exit with an error message.
8699 * Attempts to recover here are futile. [HGM] Well, we try anyway
8701 if ((StrStr(message, "unknown host") != NULL)
8702 || (StrStr(message, "No remote directory") != NULL)
8703 || (StrStr(message, "not found") != NULL)
8704 || (StrStr(message, "No such file") != NULL)
8705 || (StrStr(message, "can't alloc") != NULL)
8706 || (StrStr(message, "Permission denied") != NULL)) {
8708 cps->maybeThinking = FALSE;
8709 snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
8710 _(cps->which), cps->program, cps->host, message);
8711 RemoveInputSource(cps->isr);
8712 if(appData.icsActive) DisplayFatalError(buf1, 0, 1); else {
8713 if(LoadError(oldError ? NULL : buf1, cps)) return; // error has then been handled by LoadError
8714 if(!oldError) DisplayError(buf1, 0); // if reason neatly announced, suppress general error popup
8720 * Look for hint output
8722 if (sscanf(message, "Hint: %s", buf1) == 1) {
8723 if (cps == &first && hintRequested) {
8724 hintRequested = FALSE;
8725 if (ParseOneMove(buf1, forwardMostMove, &moveType,
8726 &fromX, &fromY, &toX, &toY, &promoChar)) {
8727 (void) CoordsToAlgebraic(boards[forwardMostMove],
8728 PosFlags(forwardMostMove),
8729 fromY, fromX, toY, toX, promoChar, buf1);
8730 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
8731 DisplayInformation(buf2);
8733 /* Hint move could not be parsed!? */
8734 snprintf(buf2, sizeof(buf2),
8735 _("Illegal hint move \"%s\"\nfrom %s chess program"),
8736 buf1, _(cps->which));
8737 DisplayError(buf2, 0);
8740 safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
8746 * Ignore other messages if game is not in progress
8748 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8749 gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
8752 * look for win, lose, draw, or draw offer
8754 if (strncmp(message, "1-0", 3) == 0) {
8755 char *p, *q, *r = "";
8756 p = strchr(message, '{');
8764 GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
8766 } else if (strncmp(message, "0-1", 3) == 0) {
8767 char *p, *q, *r = "";
8768 p = strchr(message, '{');
8776 /* Kludge for Arasan 4.1 bug */
8777 if (strcmp(r, "Black resigns") == 0) {
8778 GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
8781 GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
8783 } else if (strncmp(message, "1/2", 3) == 0) {
8784 char *p, *q, *r = "";
8785 p = strchr(message, '{');
8794 GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
8797 } else if (strncmp(message, "White resign", 12) == 0) {
8798 GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8800 } else if (strncmp(message, "Black resign", 12) == 0) {
8801 GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8803 } else if (strncmp(message, "White matches", 13) == 0 ||
8804 strncmp(message, "Black matches", 13) == 0 ) {
8805 /* [HGM] ignore GNUShogi noises */
8807 } else if (strncmp(message, "White", 5) == 0 &&
8808 message[5] != '(' &&
8809 StrStr(message, "Black") == NULL) {
8810 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8812 } else if (strncmp(message, "Black", 5) == 0 &&
8813 message[5] != '(') {
8814 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8816 } else if (strcmp(message, "resign") == 0 ||
8817 strcmp(message, "computer resigns") == 0) {
8819 case MachinePlaysBlack:
8820 case IcsPlayingBlack:
8821 GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
8823 case MachinePlaysWhite:
8824 case IcsPlayingWhite:
8825 GameEnds(BlackWins, "White resigns", GE_ENGINE);
8827 case TwoMachinesPlay:
8828 if (cps->twoMachinesColor[0] == 'w')
8829 GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8831 GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8838 } else if (strncmp(message, "opponent mates", 14) == 0) {
8840 case MachinePlaysBlack:
8841 case IcsPlayingBlack:
8842 GameEnds(WhiteWins, "White mates", GE_ENGINE);
8844 case MachinePlaysWhite:
8845 case IcsPlayingWhite:
8846 GameEnds(BlackWins, "Black mates", GE_ENGINE);
8848 case TwoMachinesPlay:
8849 if (cps->twoMachinesColor[0] == 'w')
8850 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8852 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8859 } else if (strncmp(message, "computer mates", 14) == 0) {
8861 case MachinePlaysBlack:
8862 case IcsPlayingBlack:
8863 GameEnds(BlackWins, "Black mates", GE_ENGINE1);
8865 case MachinePlaysWhite:
8866 case IcsPlayingWhite:
8867 GameEnds(WhiteWins, "White mates", GE_ENGINE);
8869 case TwoMachinesPlay:
8870 if (cps->twoMachinesColor[0] == 'w')
8871 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8873 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8880 } else if (strncmp(message, "checkmate", 9) == 0) {
8881 if (WhiteOnMove(forwardMostMove)) {
8882 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8884 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8887 } else if (strstr(message, "Draw") != NULL ||
8888 strstr(message, "game is a draw") != NULL) {
8889 GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
8891 } else if (strstr(message, "offer") != NULL &&
8892 strstr(message, "draw") != NULL) {
8894 if (appData.zippyPlay && first.initDone) {
8895 /* Relay offer to ICS */
8896 SendToICS(ics_prefix);
8897 SendToICS("draw\n");
8900 cps->offeredDraw = 2; /* valid until this engine moves twice */
8901 if (gameMode == TwoMachinesPlay) {
8902 if (cps->other->offeredDraw) {
8903 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8904 /* [HGM] in two-machine mode we delay relaying draw offer */
8905 /* until after we also have move, to see if it is really claim */
8907 } else if (gameMode == MachinePlaysWhite ||
8908 gameMode == MachinePlaysBlack) {
8909 if (userOfferedDraw) {
8910 DisplayInformation(_("Machine accepts your draw offer"));
8911 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8913 DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
8920 * Look for thinking output
8922 if ( appData.showThinking // [HGM] thinking: test all options that cause this output
8923 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
8925 int plylev, mvleft, mvtot, curscore, time;
8926 char mvname[MOVE_LEN];
8930 int prefixHint = FALSE;
8931 mvname[0] = NULLCHAR;
8934 case MachinePlaysBlack:
8935 case IcsPlayingBlack:
8936 if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8938 case MachinePlaysWhite:
8939 case IcsPlayingWhite:
8940 if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8945 case IcsObserving: /* [DM] icsEngineAnalyze */
8946 if (!appData.icsEngineAnalyze) ignore = TRUE;
8948 case TwoMachinesPlay:
8949 if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
8959 ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
8961 if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8962 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
8964 if (plyext != ' ' && plyext != '\t') {
8968 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8969 if( cps->scoreIsAbsolute &&
8970 ( gameMode == MachinePlaysBlack ||
8971 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
8972 gameMode == IcsPlayingBlack || // [HGM] also add other situations where engine should report black POV
8973 (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
8974 !WhiteOnMove(currentMove)
8977 curscore = -curscore;
8980 if(appData.pvSAN[cps==&second]) pv = PvToSAN(buf1);
8982 if(serverMoves && (time > 100 || time == 0 && plylev > 7)) {
8985 snprintf(buf, MSG_SIZ, "%s", appData.serverMovesName);
8986 buf[strlen(buf)-1] = gameMode == MachinePlaysWhite ? 'w' :
8987 gameMode == MachinePlaysBlack ? 'b' : cps->twoMachinesColor[0];
8988 if(appData.debugMode) fprintf(debugFP, "write PV on file '%s'\n", buf);
8989 if(f = fopen(buf, "w")) { // export PV to applicable PV file
8990 fprintf(f, "%5.2f/%-2d %s", curscore/100., plylev, pv);
8992 } else DisplayError(_("failed writing PV"), 0);
8995 tempStats.depth = plylev;
8996 tempStats.nodes = nodes;
8997 tempStats.time = time;
8998 tempStats.score = curscore;
8999 tempStats.got_only_move = 0;
9001 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
9004 if(cps->nps == 0) ticklen = 10*time; // use engine reported time
9005 else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
9006 if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
9007 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
9008 whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
9009 if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
9010 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
9011 blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
9014 /* Buffer overflow protection */
9015 if (pv[0] != NULLCHAR) {
9016 if (strlen(pv) >= sizeof(tempStats.movelist)
9017 && appData.debugMode) {
9019 "PV is too long; using the first %u bytes.\n",
9020 (unsigned) sizeof(tempStats.movelist) - 1);
9023 safeStrCpy( tempStats.movelist, pv, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
9025 sprintf(tempStats.movelist, " no PV\n");
9028 if (tempStats.seen_stat) {
9029 tempStats.ok_to_send = 1;
9032 if (strchr(tempStats.movelist, '(') != NULL) {
9033 tempStats.line_is_book = 1;
9034 tempStats.nr_moves = 0;
9035 tempStats.moves_left = 0;
9037 tempStats.line_is_book = 0;
9040 if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
9041 programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
9043 SendProgramStatsToFrontend( cps, &tempStats );
9046 [AS] Protect the thinkOutput buffer from overflow... this
9047 is only useful if buf1 hasn't overflowed first!
9049 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%+.2f %s%s",
9051 (gameMode == TwoMachinesPlay ?
9052 ToUpper(cps->twoMachinesColor[0]) : ' '),
9053 ((double) curscore) / 100.0,
9054 prefixHint ? lastHint : "",
9055 prefixHint ? " " : "" );
9057 if( buf1[0] != NULLCHAR ) {
9058 unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
9060 if( strlen(pv) > max_len ) {
9061 if( appData.debugMode) {
9062 fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
9064 pv[max_len+1] = '\0';
9067 strcat( thinkOutput, pv);
9070 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
9071 || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9072 DisplayMove(currentMove - 1);
9076 } else if ((p=StrStr(message, "(only move)")) != NULL) {
9077 /* crafty (9.25+) says "(only move) <move>"
9078 * if there is only 1 legal move
9080 sscanf(p, "(only move) %s", buf1);
9081 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
9082 sprintf(programStats.movelist, "%s (only move)", buf1);
9083 programStats.depth = 1;
9084 programStats.nr_moves = 1;
9085 programStats.moves_left = 1;
9086 programStats.nodes = 1;
9087 programStats.time = 1;
9088 programStats.got_only_move = 1;
9090 /* Not really, but we also use this member to
9091 mean "line isn't going to change" (Crafty
9092 isn't searching, so stats won't change) */
9093 programStats.line_is_book = 1;
9095 SendProgramStatsToFrontend( cps, &programStats );
9097 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9098 gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9099 DisplayMove(currentMove - 1);
9102 } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
9103 &time, &nodes, &plylev, &mvleft,
9104 &mvtot, mvname) >= 5) {
9105 /* The stat01: line is from Crafty (9.29+) in response
9106 to the "." command */
9107 programStats.seen_stat = 1;
9108 cps->maybeThinking = TRUE;
9110 if (programStats.got_only_move || !appData.periodicUpdates)
9113 programStats.depth = plylev;
9114 programStats.time = time;
9115 programStats.nodes = nodes;
9116 programStats.moves_left = mvleft;
9117 programStats.nr_moves = mvtot;
9118 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
9119 programStats.ok_to_send = 1;
9120 programStats.movelist[0] = '\0';
9122 SendProgramStatsToFrontend( cps, &programStats );
9126 } else if (strncmp(message,"++",2) == 0) {
9127 /* Crafty 9.29+ outputs this */
9128 programStats.got_fail = 2;
9131 } else if (strncmp(message,"--",2) == 0) {
9132 /* Crafty 9.29+ outputs this */
9133 programStats.got_fail = 1;
9136 } else if (thinkOutput[0] != NULLCHAR &&
9137 strncmp(message, " ", 4) == 0) {
9138 unsigned message_len;
9141 while (*p && *p == ' ') p++;
9143 message_len = strlen( p );
9145 /* [AS] Avoid buffer overflow */
9146 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
9147 strcat(thinkOutput, " ");
9148 strcat(thinkOutput, p);
9151 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
9152 strcat(programStats.movelist, " ");
9153 strcat(programStats.movelist, p);
9156 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9157 gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9158 DisplayMove(currentMove - 1);
9166 if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
9167 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
9169 ChessProgramStats cpstats;
9171 if (plyext != ' ' && plyext != '\t') {
9175 /* [AS] Negate score if machine is playing black and reporting absolute scores */
9176 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
9177 curscore = -curscore;
9180 cpstats.depth = plylev;
9181 cpstats.nodes = nodes;
9182 cpstats.time = time;
9183 cpstats.score = curscore;
9184 cpstats.got_only_move = 0;
9185 cpstats.movelist[0] = '\0';
9187 if (buf1[0] != NULLCHAR) {
9188 safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
9191 cpstats.ok_to_send = 0;
9192 cpstats.line_is_book = 0;
9193 cpstats.nr_moves = 0;
9194 cpstats.moves_left = 0;
9196 SendProgramStatsToFrontend( cps, &cpstats );
9203 /* Parse a game score from the character string "game", and
9204 record it as the history of the current game. The game
9205 score is NOT assumed to start from the standard position.
9206 The display is not updated in any way.
9209 ParseGameHistory (char *game)
9212 int fromX, fromY, toX, toY, boardIndex;
9217 if (appData.debugMode)
9218 fprintf(debugFP, "Parsing game history: %s\n", game);
9220 if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
9221 gameInfo.site = StrSave(appData.icsHost);
9222 gameInfo.date = PGNDate();
9223 gameInfo.round = StrSave("-");
9225 /* Parse out names of players */
9226 while (*game == ' ') game++;
9228 while (*game != ' ') *p++ = *game++;
9230 gameInfo.white = StrSave(buf);
9231 while (*game == ' ') game++;
9233 while (*game != ' ' && *game != '\n') *p++ = *game++;
9235 gameInfo.black = StrSave(buf);
9238 boardIndex = blackPlaysFirst ? 1 : 0;
9241 yyboardindex = boardIndex;
9242 moveType = (ChessMove) Myylex();
9244 case IllegalMove: /* maybe suicide chess, etc. */
9245 if (appData.debugMode) {
9246 fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
9247 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9248 setbuf(debugFP, NULL);
9250 case WhitePromotion:
9251 case BlackPromotion:
9252 case WhiteNonPromotion:
9253 case BlackNonPromotion:
9255 case WhiteCapturesEnPassant:
9256 case BlackCapturesEnPassant:
9257 case WhiteKingSideCastle:
9258 case WhiteQueenSideCastle:
9259 case BlackKingSideCastle:
9260 case BlackQueenSideCastle:
9261 case WhiteKingSideCastleWild:
9262 case WhiteQueenSideCastleWild:
9263 case BlackKingSideCastleWild:
9264 case BlackQueenSideCastleWild:
9266 case WhiteHSideCastleFR:
9267 case WhiteASideCastleFR:
9268 case BlackHSideCastleFR:
9269 case BlackASideCastleFR:
9271 fromX = currentMoveString[0] - AAA;
9272 fromY = currentMoveString[1] - ONE;
9273 toX = currentMoveString[2] - AAA;
9274 toY = currentMoveString[3] - ONE;
9275 promoChar = currentMoveString[4];
9279 if(currentMoveString[0] == '@') continue; // no null moves in ICS mode!
9280 fromX = moveType == WhiteDrop ?
9281 (int) CharToPiece(ToUpper(currentMoveString[0])) :
9282 (int) CharToPiece(ToLower(currentMoveString[0]));
9284 toX = currentMoveString[2] - AAA;
9285 toY = currentMoveString[3] - ONE;
9286 promoChar = NULLCHAR;
9290 snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
9291 if (appData.debugMode) {
9292 fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
9293 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9294 setbuf(debugFP, NULL);
9296 DisplayError(buf, 0);
9298 case ImpossibleMove:
9300 snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
9301 if (appData.debugMode) {
9302 fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
9303 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9304 setbuf(debugFP, NULL);
9306 DisplayError(buf, 0);
9309 if (boardIndex < backwardMostMove) {
9310 /* Oops, gap. How did that happen? */
9311 DisplayError(_("Gap in move list"), 0);
9314 backwardMostMove = blackPlaysFirst ? 1 : 0;
9315 if (boardIndex > forwardMostMove) {
9316 forwardMostMove = boardIndex;
9320 if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
9321 strcat(parseList[boardIndex-1], " ");
9322 strcat(parseList[boardIndex-1], yy_text);
9334 case GameUnfinished:
9335 if (gameMode == IcsExamining) {
9336 if (boardIndex < backwardMostMove) {
9337 /* Oops, gap. How did that happen? */
9340 backwardMostMove = blackPlaysFirst ? 1 : 0;
9343 gameInfo.result = moveType;
9344 p = strchr(yy_text, '{');
9345 if (p == NULL) p = strchr(yy_text, '(');
9348 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
9350 q = strchr(p, *p == '{' ? '}' : ')');
9351 if (q != NULL) *q = NULLCHAR;
9354 while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
9355 gameInfo.resultDetails = StrSave(p);
9358 if (boardIndex >= forwardMostMove &&
9359 !(gameMode == IcsObserving && ics_gamenum == -1)) {
9360 backwardMostMove = blackPlaysFirst ? 1 : 0;
9363 (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
9364 fromY, fromX, toY, toX, promoChar,
9365 parseList[boardIndex]);
9366 CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
9367 /* currentMoveString is set as a side-effect of yylex */
9368 safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
9369 strcat(moveList[boardIndex], "\n");
9371 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
9372 switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
9378 if(gameInfo.variant != VariantShogi)
9379 strcat(parseList[boardIndex - 1], "+");
9383 strcat(parseList[boardIndex - 1], "#");
9390 /* Apply a move to the given board */
9392 ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board)
9394 ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
9395 int promoRank = gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand ? 3 : 1;
9397 /* [HGM] compute & store e.p. status and castling rights for new position */
9398 /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
9400 if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
9401 oldEP = (signed char)board[EP_STATUS];
9402 board[EP_STATUS] = EP_NONE;
9404 if (fromY == DROP_RANK) {
9406 if(fromX == EmptySquare) { // [HGM] pass: empty drop encodes null move; nothing to change.
9407 board[EP_STATUS] = EP_CAPTURE; // null move considered irreversible
9410 piece = board[toY][toX] = (ChessSquare) fromX;
9414 if( board[toY][toX] != EmptySquare )
9415 board[EP_STATUS] = EP_CAPTURE;
9417 if( board[fromY][fromX] == WhiteLance || board[fromY][fromX] == BlackLance ) {
9418 if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi )
9419 board[EP_STATUS] = EP_PAWN_MOVE; // Lance is Pawn-like in most variants
9421 if( board[fromY][fromX] == WhitePawn ) {
9422 if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9423 board[EP_STATUS] = EP_PAWN_MOVE;
9425 if(toX>BOARD_LEFT && board[toY][toX-1] == BlackPawn &&
9426 gameInfo.variant != VariantBerolina || toX < fromX)
9427 board[EP_STATUS] = toX | berolina;
9428 if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
9429 gameInfo.variant != VariantBerolina || toX > fromX)
9430 board[EP_STATUS] = toX;
9433 if( board[fromY][fromX] == BlackPawn ) {
9434 if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9435 board[EP_STATUS] = EP_PAWN_MOVE;
9436 if( toY-fromY== -2) {
9437 if(toX>BOARD_LEFT && board[toY][toX-1] == WhitePawn &&
9438 gameInfo.variant != VariantBerolina || toX < fromX)
9439 board[EP_STATUS] = toX | berolina;
9440 if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
9441 gameInfo.variant != VariantBerolina || toX > fromX)
9442 board[EP_STATUS] = toX;
9446 for(i=0; i<nrCastlingRights; i++) {
9447 if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
9448 board[CASTLING][i] == toX && castlingRank[i] == toY
9449 ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
9452 if(gameInfo.variant == VariantSChess) { // update virginity
9453 if(fromY == 0) board[VIRGIN][fromX] &= ~VIRGIN_W; // loss by moving
9454 if(fromY == BOARD_HEIGHT-1) board[VIRGIN][fromX] &= ~VIRGIN_B;
9455 if(toY == 0) board[VIRGIN][toX] &= ~VIRGIN_W; // loss by capture
9456 if(toY == BOARD_HEIGHT-1) board[VIRGIN][toX] &= ~VIRGIN_B;
9459 if (fromX == toX && fromY == toY) return;
9461 piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
9462 king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
9463 if(gameInfo.variant == VariantKnightmate)
9464 king += (int) WhiteUnicorn - (int) WhiteKing;
9466 /* Code added by Tord: */
9467 /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
9468 if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
9469 board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
9470 board[fromY][fromX] = EmptySquare;
9471 board[toY][toX] = EmptySquare;
9472 if((toX > fromX) != (piece == WhiteRook)) {
9473 board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
9475 board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
9477 } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
9478 board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
9479 board[fromY][fromX] = EmptySquare;
9480 board[toY][toX] = EmptySquare;
9481 if((toX > fromX) != (piece == BlackRook)) {
9482 board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
9484 board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
9486 /* End of code added by Tord */
9488 } else if (board[fromY][fromX] == king
9489 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9490 && toY == fromY && toX > fromX+1) {
9491 board[fromY][fromX] = EmptySquare;
9492 board[toY][toX] = king;
9493 board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9494 board[fromY][BOARD_RGHT-1] = EmptySquare;
9495 } else if (board[fromY][fromX] == king
9496 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9497 && toY == fromY && toX < fromX-1) {
9498 board[fromY][fromX] = EmptySquare;
9499 board[toY][toX] = king;
9500 board[toY][toX+1] = board[fromY][BOARD_LEFT];
9501 board[fromY][BOARD_LEFT] = EmptySquare;
9502 } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
9503 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9504 && toY >= BOARD_HEIGHT-promoRank && promoChar // defaulting to Q is done elsewhere
9506 /* white pawn promotion */
9507 board[toY][toX] = CharToPiece(ToUpper(promoChar));
9508 if(board[toY][toX] < WhiteCannon && PieceToChar(PROMOTED board[toY][toX]) == '~') /* [HGM] use shadow piece (if available) */
9509 board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9510 board[fromY][fromX] = EmptySquare;
9511 } else if ((fromY >= BOARD_HEIGHT>>1)
9512 && (oldEP == toX || oldEP == EP_UNKNOWN || appData.testLegality)
9514 && gameInfo.variant != VariantXiangqi
9515 && gameInfo.variant != VariantBerolina
9516 && (board[fromY][fromX] == WhitePawn)
9517 && (board[toY][toX] == EmptySquare)) {
9518 board[fromY][fromX] = EmptySquare;
9519 board[toY][toX] = WhitePawn;
9520 captured = board[toY - 1][toX];
9521 board[toY - 1][toX] = EmptySquare;
9522 } else if ((fromY == BOARD_HEIGHT-4)
9524 && gameInfo.variant == VariantBerolina
9525 && (board[fromY][fromX] == WhitePawn)
9526 && (board[toY][toX] == EmptySquare)) {
9527 board[fromY][fromX] = EmptySquare;
9528 board[toY][toX] = WhitePawn;
9529 if(oldEP & EP_BEROLIN_A) {
9530 captured = board[fromY][fromX-1];
9531 board[fromY][fromX-1] = EmptySquare;
9532 }else{ captured = board[fromY][fromX+1];
9533 board[fromY][fromX+1] = EmptySquare;
9535 } else if (board[fromY][fromX] == king
9536 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9537 && toY == fromY && toX > fromX+1) {
9538 board[fromY][fromX] = EmptySquare;
9539 board[toY][toX] = king;
9540 board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9541 board[fromY][BOARD_RGHT-1] = EmptySquare;
9542 } else if (board[fromY][fromX] == king
9543 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9544 && toY == fromY && toX < fromX-1) {
9545 board[fromY][fromX] = EmptySquare;
9546 board[toY][toX] = king;
9547 board[toY][toX+1] = board[fromY][BOARD_LEFT];
9548 board[fromY][BOARD_LEFT] = EmptySquare;
9549 } else if (fromY == 7 && fromX == 3
9550 && board[fromY][fromX] == BlackKing
9551 && toY == 7 && toX == 5) {
9552 board[fromY][fromX] = EmptySquare;
9553 board[toY][toX] = BlackKing;
9554 board[fromY][7] = EmptySquare;
9555 board[toY][4] = BlackRook;
9556 } else if (fromY == 7 && fromX == 3
9557 && board[fromY][fromX] == BlackKing
9558 && toY == 7 && toX == 1) {
9559 board[fromY][fromX] = EmptySquare;
9560 board[toY][toX] = BlackKing;
9561 board[fromY][0] = EmptySquare;
9562 board[toY][2] = BlackRook;
9563 } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
9564 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9565 && toY < promoRank && promoChar
9567 /* black pawn promotion */
9568 board[toY][toX] = CharToPiece(ToLower(promoChar));
9569 if(board[toY][toX] < BlackCannon && PieceToChar(PROMOTED board[toY][toX]) == '~') /* [HGM] use shadow piece (if available) */
9570 board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9571 board[fromY][fromX] = EmptySquare;
9572 } else if ((fromY < BOARD_HEIGHT>>1)
9573 && (oldEP == toX || oldEP == EP_UNKNOWN || appData.testLegality)
9575 && gameInfo.variant != VariantXiangqi
9576 && gameInfo.variant != VariantBerolina
9577 && (board[fromY][fromX] == BlackPawn)
9578 && (board[toY][toX] == EmptySquare)) {
9579 board[fromY][fromX] = EmptySquare;
9580 board[toY][toX] = BlackPawn;
9581 captured = board[toY + 1][toX];
9582 board[toY + 1][toX] = EmptySquare;
9583 } else if ((fromY == 3)
9585 && gameInfo.variant == VariantBerolina
9586 && (board[fromY][fromX] == BlackPawn)
9587 && (board[toY][toX] == EmptySquare)) {
9588 board[fromY][fromX] = EmptySquare;
9589 board[toY][toX] = BlackPawn;
9590 if(oldEP & EP_BEROLIN_A) {
9591 captured = board[fromY][fromX-1];
9592 board[fromY][fromX-1] = EmptySquare;
9593 }else{ captured = board[fromY][fromX+1];
9594 board[fromY][fromX+1] = EmptySquare;
9597 board[toY][toX] = board[fromY][fromX];
9598 board[fromY][fromX] = EmptySquare;
9602 if (gameInfo.holdingsWidth != 0) {
9604 /* !!A lot more code needs to be written to support holdings */
9605 /* [HGM] OK, so I have written it. Holdings are stored in the */
9606 /* penultimate board files, so they are automaticlly stored */
9607 /* in the game history. */
9608 if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
9609 && promoChar && piece != WhitePawn && piece != BlackPawn) {
9610 /* Delete from holdings, by decreasing count */
9611 /* and erasing image if necessary */
9612 p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
9613 if(p < (int) BlackPawn) { /* white drop */
9614 p -= (int)WhitePawn;
9615 p = PieceToNumber((ChessSquare)p);
9616 if(p >= gameInfo.holdingsSize) p = 0;
9617 if(--board[p][BOARD_WIDTH-2] <= 0)
9618 board[p][BOARD_WIDTH-1] = EmptySquare;
9619 if((int)board[p][BOARD_WIDTH-2] < 0)
9620 board[p][BOARD_WIDTH-2] = 0;
9621 } else { /* black drop */
9622 p -= (int)BlackPawn;
9623 p = PieceToNumber((ChessSquare)p);
9624 if(p >= gameInfo.holdingsSize) p = 0;
9625 if(--board[BOARD_HEIGHT-1-p][1] <= 0)
9626 board[BOARD_HEIGHT-1-p][0] = EmptySquare;
9627 if((int)board[BOARD_HEIGHT-1-p][1] < 0)
9628 board[BOARD_HEIGHT-1-p][1] = 0;
9631 if (captured != EmptySquare && gameInfo.holdingsSize > 0
9632 && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess ) {
9633 /* [HGM] holdings: Add to holdings, if holdings exist */
9634 if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
9635 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
9636 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
9639 if (p >= (int) BlackPawn) {
9640 p -= (int)BlackPawn;
9641 if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9642 /* in Shogi restore piece to its original first */
9643 captured = (ChessSquare) (DEMOTED captured);
9646 p = PieceToNumber((ChessSquare)p);
9647 if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
9648 board[p][BOARD_WIDTH-2]++;
9649 board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
9651 p -= (int)WhitePawn;
9652 if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9653 captured = (ChessSquare) (DEMOTED captured);
9656 p = PieceToNumber((ChessSquare)p);
9657 if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
9658 board[BOARD_HEIGHT-1-p][1]++;
9659 board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
9662 } else if (gameInfo.variant == VariantAtomic) {
9663 if (captured != EmptySquare) {
9665 for (y = toY-1; y <= toY+1; y++) {
9666 for (x = toX-1; x <= toX+1; x++) {
9667 if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
9668 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
9669 board[y][x] = EmptySquare;
9673 board[toY][toX] = EmptySquare;
9676 if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
9677 board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
9679 if(promoChar == '+') {
9680 /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite ordinary Pawn promotion) */
9681 board[toY][toX] = (ChessSquare) (PROMOTED piece);
9682 } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
9683 ChessSquare newPiece = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
9684 if((newPiece <= WhiteMan || newPiece >= BlackPawn && newPiece <= BlackMan) // unpromoted piece specified
9685 && pieceToChar[PROMOTED newPiece] == '~') newPiece = PROMOTED newPiece; // but promoted version available
9686 board[toY][toX] = newPiece;
9688 if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
9689 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
9690 // [HGM] superchess: take promotion piece out of holdings
9691 int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
9692 if((int)piece < (int)BlackPawn) { // determine stm from piece color
9693 if(!--board[k][BOARD_WIDTH-2])
9694 board[k][BOARD_WIDTH-1] = EmptySquare;
9696 if(!--board[BOARD_HEIGHT-1-k][1])
9697 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
9703 /* Updates forwardMostMove */
9705 MakeMove (int fromX, int fromY, int toX, int toY, int promoChar)
9707 // forwardMostMove++; // [HGM] bare: moved downstream
9709 (void) CoordsToAlgebraic(boards[forwardMostMove],
9710 PosFlags(forwardMostMove),
9711 fromY, fromX, toY, toX, promoChar,
9712 parseList[forwardMostMove]);
9714 if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
9715 int timeLeft; static int lastLoadFlag=0; int king, piece;
9716 piece = boards[forwardMostMove][fromY][fromX];
9717 king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
9718 if(gameInfo.variant == VariantKnightmate)
9719 king += (int) WhiteUnicorn - (int) WhiteKing;
9720 if(forwardMostMove == 0) {
9721 if(gameMode == MachinePlaysBlack || gameMode == BeginningOfGame)
9722 fprintf(serverMoves, "%s;", UserName());
9723 else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b')
9724 fprintf(serverMoves, "%s;", second.tidy);
9725 fprintf(serverMoves, "%s;", first.tidy);
9726 if(gameMode == MachinePlaysWhite)
9727 fprintf(serverMoves, "%s;", UserName());
9728 else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
9729 fprintf(serverMoves, "%s;", second.tidy);
9730 } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
9731 lastLoadFlag = loadFlag;
9733 fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
9734 // print castling suffix
9735 if( toY == fromY && piece == king ) {
9737 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
9739 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
9742 if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
9743 boards[forwardMostMove][fromY][fromX] == BlackPawn ) &&
9744 boards[forwardMostMove][toY][toX] == EmptySquare
9745 && fromX != toX && fromY != toY)
9746 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
9748 if(promoChar != NULLCHAR) {
9749 if(fromY == 0 || fromY == BOARD_HEIGHT-1)
9750 fprintf(serverMoves, ":%c%c:%c%c", WhiteOnMove(forwardMostMove) ? 'w' : 'b',
9751 ToLower(promoChar), AAA+fromX, ONE+fromY); // Seirawan gating
9752 else fprintf(serverMoves, ":%c:%c%c", ToLower(promoChar), AAA+toX, ONE+toY);
9755 char buf[MOVE_LEN*2], *p; int len;
9756 fprintf(serverMoves, "/%d/%d",
9757 pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
9758 if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
9759 else timeLeft = blackTimeRemaining/1000;
9760 fprintf(serverMoves, "/%d", timeLeft);
9761 strncpy(buf, parseList[forwardMostMove], MOVE_LEN*2);
9762 if(p = strchr(buf, '/')) *p = NULLCHAR; else
9763 if(p = strchr(buf, '=')) *p = NULLCHAR;
9764 len = strlen(buf); if(len > 1 && buf[len-2] != '-') buf[len-2] = NULLCHAR; // strip to-square
9765 fprintf(serverMoves, "/%s", buf);
9767 fflush(serverMoves);
9770 if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations..
9771 GameEnds(GameUnfinished, _("Game too long; increase MAX_MOVES and recompile"), GE_XBOARD);
9774 UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
9775 if (commentList[forwardMostMove+1] != NULL) {
9776 free(commentList[forwardMostMove+1]);
9777 commentList[forwardMostMove+1] = NULL;
9779 CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
9780 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
9781 // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
9782 SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
9783 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
9784 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
9785 adjustedClock = FALSE;
9786 gameInfo.result = GameUnfinished;
9787 if (gameInfo.resultDetails != NULL) {
9788 free(gameInfo.resultDetails);
9789 gameInfo.resultDetails = NULL;
9791 CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
9792 moveList[forwardMostMove - 1]);
9793 switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
9799 if(gameInfo.variant != VariantShogi)
9800 strcat(parseList[forwardMostMove - 1], "+");
9804 strcat(parseList[forwardMostMove - 1], "#");
9810 /* Updates currentMove if not pausing */
9812 ShowMove (int fromX, int fromY, int toX, int toY)
9814 int instant = (gameMode == PlayFromGameFile) ?
9815 (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
9816 if(appData.noGUI) return;
9817 if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
9819 if (forwardMostMove == currentMove + 1) {
9820 AnimateMove(boards[forwardMostMove - 1],
9821 fromX, fromY, toX, toY);
9824 currentMove = forwardMostMove;
9827 if (instant) return;
9829 DisplayMove(currentMove - 1);
9830 if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
9831 if (appData.highlightLastMove) { // [HGM] moved to after DrawPosition, as with arrow it could redraw old board
9832 SetHighlights(fromX, fromY, toX, toY);
9835 DrawPosition(FALSE, boards[currentMove]);
9836 DisplayBothClocks();
9837 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
9841 SendEgtPath (ChessProgramState *cps)
9842 { /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
9843 char buf[MSG_SIZ], name[MSG_SIZ], *p;
9845 if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
9848 char c, *q = name+1, *r, *s;
9850 name[0] = ','; // extract next format name from feature and copy with prefixed ','
9851 while(*p && *p != ',') *q++ = *p++;
9853 if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
9854 strcmp(name, ",nalimov:") == 0 ) {
9855 // take nalimov path from the menu-changeable option first, if it is defined
9856 snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
9857 SendToProgram(buf,cps); // send egtbpath command for nalimov
9859 if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
9860 (s = StrStr(appData.egtFormats, name)) != NULL) {
9861 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
9862 s = r = StrStr(s, ":") + 1; // beginning of path info
9863 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
9864 c = *r; *r = 0; // temporarily null-terminate path info
9865 *--q = 0; // strip of trailig ':' from name
9866 snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
9868 SendToProgram(buf,cps); // send egtbpath command for this format
9870 if(*p == ',') p++; // read away comma to position for next format name
9875 NonStandardBoardSize ()
9877 /* [HGM] Awkward testing. Should really be a table */
9878 int overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9879 if( gameInfo.variant == VariantXiangqi )
9880 overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
9881 if( gameInfo.variant == VariantShogi )
9882 overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
9883 if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
9884 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
9885 if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
9886 gameInfo.variant == VariantGothic || gameInfo.variant == VariantFalcon || gameInfo.variant == VariantJanus )
9887 overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9888 if( gameInfo.variant == VariantCourier )
9889 overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9890 if( gameInfo.variant == VariantSuper )
9891 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9892 if( gameInfo.variant == VariantGreat )
9893 overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9894 if( gameInfo.variant == VariantSChess )
9895 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 7;
9896 if( gameInfo.variant == VariantGrand )
9897 overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 7;
9902 InitChessProgram (ChessProgramState *cps, int setup)
9903 /* setup needed to setup FRC opening position */
9905 char buf[MSG_SIZ], b[MSG_SIZ];
9906 if (appData.noChessProgram) return;
9907 hintRequested = FALSE;
9908 bookRequested = FALSE;
9910 ParseFeatures(appData.features[cps == &second], cps); // [HGM] allow user to overrule features
9911 /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
9912 /* moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
9913 if(cps->memSize) { /* [HGM] memory */
9914 snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
9915 SendToProgram(buf, cps);
9917 SendEgtPath(cps); /* [HGM] EGT */
9918 if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
9919 snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
9920 SendToProgram(buf, cps);
9923 SendToProgram(cps->initString, cps);
9924 if (gameInfo.variant != VariantNormal &&
9925 gameInfo.variant != VariantLoadable
9926 /* [HGM] also send variant if board size non-standard */
9927 || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
9929 char *v = VariantName(gameInfo.variant);
9930 if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
9931 /* [HGM] in protocol 1 we have to assume all variants valid */
9932 snprintf(buf, MSG_SIZ, _("Variant %s not supported by %s"), v, cps->tidy);
9933 DisplayFatalError(buf, 0, 1);
9937 if(NonStandardBoardSize()) { /* [HGM] make prefix for non-standard board size. */
9938 snprintf(b, MSG_SIZ, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
9939 gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
9940 /* [HGM] varsize: try first if this defiant size variant is specifically known */
9941 if(StrStr(cps->variants, b) == NULL) {
9942 // specific sized variant not known, check if general sizing allowed
9943 if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
9944 if(StrStr(cps->variants, "boardsize") == NULL) {
9945 snprintf(buf, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
9946 gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
9947 DisplayFatalError(buf, 0, 1);
9950 /* [HGM] here we really should compare with the maximum supported board size */
9953 } else snprintf(b, MSG_SIZ,"%s", VariantName(gameInfo.variant));
9954 snprintf(buf, MSG_SIZ, "variant %s\n", b);
9955 SendToProgram(buf, cps);
9957 currentlyInitializedVariant = gameInfo.variant;
9959 /* [HGM] send opening position in FRC to first engine */
9961 SendToProgram("force\n", cps);
9963 /* engine is now in force mode! Set flag to wake it up after first move. */
9964 setboardSpoiledMachineBlack = 1;
9968 snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
9969 SendToProgram(buf, cps);
9971 cps->maybeThinking = FALSE;
9972 cps->offeredDraw = 0;
9973 if (!appData.icsActive) {
9974 SendTimeControl(cps, movesPerSession, timeControl,
9975 timeIncrement, appData.searchDepth,
9978 if (appData.showThinking
9979 // [HGM] thinking: four options require thinking output to be sent
9980 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
9982 SendToProgram("post\n", cps);
9984 SendToProgram("hard\n", cps);
9985 if (!appData.ponderNextMove) {
9986 /* Warning: "easy" is a toggle in GNU Chess, so don't send
9987 it without being sure what state we are in first. "hard"
9988 is not a toggle, so that one is OK.
9990 SendToProgram("easy\n", cps);
9993 snprintf(buf, MSG_SIZ, "ping %d\n", ++cps->lastPing);
9994 SendToProgram(buf, cps);
9996 cps->initDone = TRUE;
9997 ClearEngineOutputPane(cps == &second);
10002 ResendOptions (ChessProgramState *cps)
10003 { // send the stored value of the options
10006 Option *opt = cps->option;
10007 for(i=0; i<cps->nrOptions; i++, opt++) {
10008 switch(opt->type) {
10012 snprintf(buf, MSG_SIZ, "option %s=%d\n", opt->name, opt->value);
10015 snprintf(buf, MSG_SIZ, "option %s=%s\n", opt->name, opt->choice[opt->value]);
10018 snprintf(buf, MSG_SIZ, "option %s=%s\n", opt->name, opt->textValue);
10024 SendToProgram(buf, cps);
10029 StartChessProgram (ChessProgramState *cps)
10034 if (appData.noChessProgram) return;
10035 cps->initDone = FALSE;
10037 if (strcmp(cps->host, "localhost") == 0) {
10038 err = StartChildProcess(cps->program, cps->dir, &cps->pr);
10039 } else if (*appData.remoteShell == NULLCHAR) {
10040 err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
10042 if (*appData.remoteUser == NULLCHAR) {
10043 snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
10046 snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
10047 cps->host, appData.remoteUser, cps->program);
10049 err = StartChildProcess(buf, "", &cps->pr);
10053 snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
10054 DisplayError(buf, err); // [HGM] bit of a rough kludge: ignore failure, (which XBoard would do anyway), and let I/O discover it
10055 if(cps != &first) return;
10056 appData.noChessProgram = TRUE;
10059 // DisplayFatalError(buf, err, 1);
10060 // cps->pr = NoProc;
10061 // cps->isr = NULL;
10065 cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
10066 if (cps->protocolVersion > 1) {
10067 snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
10068 if(!cps->reload) { // do not clear options when reloading because of -xreuse
10069 cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
10070 cps->comboCnt = 0; // and values of combo boxes
10072 SendToProgram(buf, cps);
10073 if(cps->reload) ResendOptions(cps);
10075 SendToProgram("xboard\n", cps);
10080 TwoMachinesEventIfReady P((void))
10082 static int curMess = 0;
10083 if (first.lastPing != first.lastPong) {
10084 if(curMess != 1) DisplayMessage("", _("Waiting for first chess program")); curMess = 1;
10085 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
10088 if (second.lastPing != second.lastPong) {
10089 if(curMess != 2) DisplayMessage("", _("Waiting for second chess program")); curMess = 2;
10090 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
10093 DisplayMessage("", ""); curMess = 0;
10094 TwoMachinesEvent();
10098 MakeName (char *template)
10102 static char buf[MSG_SIZ];
10106 clock = time((time_t *)NULL);
10107 tm = localtime(&clock);
10109 while(*p++ = *template++) if(p[-1] == '%') {
10110 switch(*template++) {
10111 case 0: *p = 0; return buf;
10112 case 'Y': i = tm->tm_year+1900; break;
10113 case 'y': i = tm->tm_year-100; break;
10114 case 'M': i = tm->tm_mon+1; break;
10115 case 'd': i = tm->tm_mday; break;
10116 case 'h': i = tm->tm_hour; break;
10117 case 'm': i = tm->tm_min; break;
10118 case 's': i = tm->tm_sec; break;
10121 snprintf(p-1, MSG_SIZ-10 - (p - buf), "%02d", i); p += strlen(p);
10127 CountPlayers (char *p)
10130 while(p = strchr(p, '\n')) p++, n++; // count participants
10135 WriteTourneyFile (char *results, FILE *f)
10136 { // write tournament parameters on tourneyFile; on success return the stream pointer for closing
10137 if(f == NULL) f = fopen(appData.tourneyFile, "w");
10138 if(f == NULL) DisplayError(_("Could not write on tourney file"), 0); else {
10139 // create a file with tournament description
10140 fprintf(f, "-participants {%s}\n", appData.participants);
10141 fprintf(f, "-seedBase %d\n", appData.seedBase);
10142 fprintf(f, "-tourneyType %d\n", appData.tourneyType);
10143 fprintf(f, "-tourneyCycles %d\n", appData.tourneyCycles);
10144 fprintf(f, "-defaultMatchGames %d\n", appData.defaultMatchGames);
10145 fprintf(f, "-syncAfterRound %s\n", appData.roundSync ? "true" : "false");
10146 fprintf(f, "-syncAfterCycle %s\n", appData.cycleSync ? "true" : "false");
10147 fprintf(f, "-saveGameFile \"%s\"\n", appData.saveGameFile);
10148 fprintf(f, "-loadGameFile \"%s\"\n", appData.loadGameFile);
10149 fprintf(f, "-loadGameIndex %d\n", appData.loadGameIndex);
10150 fprintf(f, "-loadPositionFile \"%s\"\n", appData.loadPositionFile);
10151 fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex);
10152 fprintf(f, "-rewindIndex %d\n", appData.rewindIndex);
10153 fprintf(f, "-usePolyglotBook %s\n", appData.usePolyglotBook ? "true" : "false");
10154 fprintf(f, "-polyglotBook \"%s\"\n", appData.polyglotBook);
10155 fprintf(f, "-bookDepth %d\n", appData.bookDepth);
10156 fprintf(f, "-bookVariation %d\n", appData.bookStrength);
10157 fprintf(f, "-discourageOwnBooks %s\n", appData.defNoBook ? "true" : "false");
10158 fprintf(f, "-defaultHashSize %d\n", appData.defaultHashSize);
10159 fprintf(f, "-defaultCacheSizeEGTB %d\n", appData.defaultCacheSizeEGTB);
10160 fprintf(f, "-ponderNextMove %s\n", appData.ponderNextMove ? "true" : "false");
10161 fprintf(f, "-smpCores %d\n", appData.smpCores);
10163 fprintf(f, "-searchTime \"%d:%02d\"\n", searchTime/60, searchTime%60);
10165 fprintf(f, "-mps %d\n", appData.movesPerSession);
10166 fprintf(f, "-tc %s\n", appData.timeControl);
10167 fprintf(f, "-inc %.2f\n", appData.timeIncrement);
10169 fprintf(f, "-results \"%s\"\n", results);
10174 char *command[MAXENGINES], *mnemonic[MAXENGINES];
10177 Substitute (char *participants, int expunge)
10179 int i, changed, changes=0, nPlayers=0;
10180 char *p, *q, *r, buf[MSG_SIZ];
10181 if(participants == NULL) return;
10182 if(appData.tourneyFile[0] == NULLCHAR) { free(participants); return; }
10183 r = p = participants; q = appData.participants;
10184 while(*p && *p == *q) {
10185 if(*p == '\n') r = p+1, nPlayers++;
10188 if(*p) { // difference
10189 while(*p && *p++ != '\n');
10190 while(*q && *q++ != '\n');
10191 changed = nPlayers;
10192 changes = 1 + (strcmp(p, q) != 0);
10194 if(changes == 1) { // a single engine mnemonic was changed
10195 q = r; while(*q) nPlayers += (*q++ == '\n');
10196 p = buf; while(*r && (*p = *r++) != '\n') p++;
10198 NamesToList(firstChessProgramNames, command, mnemonic, "all");
10199 for(i=1; mnemonic[i]; i++) if(!strcmp(buf, mnemonic[i])) break;
10200 if(mnemonic[i]) { // The substitute is valid
10202 if(appData.tourneyFile[0] && (f = fopen(appData.tourneyFile, "r+")) ) {
10203 flock(fileno(f), LOCK_EX);
10204 ParseArgsFromFile(f);
10205 fseek(f, 0, SEEK_SET);
10206 FREE(appData.participants); appData.participants = participants;
10207 if(expunge) { // erase results of replaced engine
10208 int len = strlen(appData.results), w, b, dummy;
10209 for(i=0; i<len; i++) {
10210 Pairing(i, nPlayers, &w, &b, &dummy);
10211 if((w == changed || b == changed) && appData.results[i] == '*') {
10212 DisplayError(_("You cannot replace an engine while it is engaged!\nTerminate its game first."), 0);
10217 for(i=0; i<len; i++) {
10218 Pairing(i, nPlayers, &w, &b, &dummy);
10219 if(w == changed || b == changed) appData.results[i] = ' '; // mark as not played
10222 WriteTourneyFile(appData.results, f);
10223 fclose(f); // release lock
10226 } else DisplayError(_("No engine with the name you gave is installed"), 0);
10228 if(changes == 0) DisplayError(_("First change an engine by editing the participants list\nof the Tournament Options dialog"), 0);
10229 if(changes > 1) DisplayError(_("You can only change one engine at the time"), 0);
10230 free(participants);
10235 CheckPlayers (char *participants)
10238 char buf[MSG_SIZ], *p;
10239 NamesToList(firstChessProgramNames, command, mnemonic, "all");
10240 while(p = strchr(participants, '\n')) {
10242 for(i=1; mnemonic[i]; i++) if(!strcmp(participants, mnemonic[i])) break;
10244 snprintf(buf, MSG_SIZ, _("No engine %s is installed"), participants);
10246 DisplayError(buf, 0);
10250 participants = p + 1;
10256 CreateTourney (char *name)
10259 if(matchMode && strcmp(name, appData.tourneyFile)) {
10260 ASSIGN(name, appData.tourneyFile); //do not allow change of tourneyfile while playing
10262 if(name[0] == NULLCHAR) {
10263 if(appData.participants[0])
10264 DisplayError(_("You must supply a tournament file,\nfor storing the tourney progress"), 0);
10267 f = fopen(name, "r");
10268 if(f) { // file exists
10269 ASSIGN(appData.tourneyFile, name);
10270 ParseArgsFromFile(f); // parse it
10272 if(!appData.participants[0]) return 0; // ignore tourney file if non-existing & no participants
10273 if(CountPlayers(appData.participants) < (appData.tourneyType>0 ? appData.tourneyType+1 : 2)) {
10274 DisplayError(_("Not enough participants"), 0);
10277 if(CheckPlayers(appData.participants)) return 0;
10278 ASSIGN(appData.tourneyFile, name);
10279 if(appData.tourneyType < 0) appData.defaultMatchGames = 1; // Swiss forces games/pairing = 1
10280 if((f = WriteTourneyFile("", NULL)) == NULL) return 0;
10283 appData.noChessProgram = FALSE;
10284 appData.clockMode = TRUE;
10290 NamesToList (char *names, char **engineList, char **engineMnemonic, char *group)
10292 char buf[MSG_SIZ], *p, *q;
10293 int i=1, header, skip, all = !strcmp(group, "all"), depth = 0;
10294 insert = names; // afterwards, this global will point just after last retrieved engine line or group end in the 'names'
10295 skip = !all && group[0]; // if group requested, we start in skip mode
10296 for(;*names && depth >= 0 && i < MAXENGINES-1; names = p) {
10297 p = names; q = buf; header = 0;
10298 while(*p && *p != '\n') *q++ = *p++;
10300 if(*p == '\n') p++;
10301 if(buf[0] == '#') {
10302 if(strstr(buf, "# end") == buf) { if(!--depth) insert = p; continue; } // leave group, and suppress printing label
10303 depth++; // we must be entering a new group
10304 if(all) continue; // suppress printing group headers when complete list requested
10306 if(skip && !strcmp(group, buf)) { depth = 0; skip = FALSE; } // start when we reach requested group
10308 if(depth != header && !all || skip) continue; // skip contents of group (but print first-level header)
10309 if(engineList[i]) free(engineList[i]);
10310 engineList[i] = strdup(buf);
10311 if(buf[0] != '#') insert = p, TidyProgramName(engineList[i], "localhost", buf); // group headers not tidied
10312 if(engineMnemonic[i]) free(engineMnemonic[i]);
10313 if((q = strstr(engineList[i]+2, "variant")) && q[-2]== ' ' && (q[-1]=='/' || q[-1]=='-') && (q[7]==' ' || q[7]=='=')) {
10315 sscanf(q + 8, "%s", buf + strlen(buf));
10318 engineMnemonic[i] = strdup(buf);
10321 engineList[i] = engineMnemonic[i] = NULL;
10325 // following implemented as macro to avoid type limitations
10326 #define SWAP(item, temp) temp = appData.item[0]; appData.item[0] = appData.item[n]; appData.item[n] = temp;
10329 SwapEngines (int n)
10330 { // swap settings for first engine and other engine (so far only some selected options)
10335 SWAP(chessProgram, p)
10337 SWAP(hasOwnBookUCI, h)
10338 SWAP(protocolVersion, h)
10340 SWAP(scoreIsAbsolute, h)
10345 SWAP(engOptions, p)
10346 SWAP(engInitString, p)
10347 SWAP(computerString, p)
10349 SWAP(fenOverride, p)
10351 SWAP(accumulateTC, h)
10356 GetEngineLine (char *s, int n)
10360 extern char *icsNames;
10361 if(!s || !*s) return 0;
10362 NamesToList(n >= 10 ? icsNames : firstChessProgramNames, command, mnemonic, "all");
10363 for(i=1; mnemonic[i]; i++) if(!strcmp(s, mnemonic[i])) break;
10364 if(!mnemonic[i]) return 0;
10365 if(n == 11) return 1; // just testing if there was a match
10366 snprintf(buf, MSG_SIZ, "-%s %s", n == 10 ? "icshost" : "fcp", command[i]);
10367 if(n == 1) SwapEngines(n);
10368 ParseArgsFromString(buf);
10369 if(n == 1) SwapEngines(n);
10370 if(n == 0 && *appData.secondChessProgram == NULLCHAR) {
10371 SwapEngines(1); // set second same as first if not yet set (to suppress WB startup dialog)
10372 ParseArgsFromString(buf);
10378 SetPlayer (int player, char *p)
10379 { // [HGM] find the engine line of the partcipant given by number, and parse its options.
10381 char buf[MSG_SIZ], *engineName;
10382 for(i=0; i<player; i++) p = strchr(p, '\n') + 1;
10383 engineName = strdup(p); if(p = strchr(engineName, '\n')) *p = NULLCHAR;
10384 for(i=1; command[i]; i++) if(!strcmp(mnemonic[i], engineName)) break;
10386 snprintf(buf, MSG_SIZ, "-fcp %s", command[i]);
10387 ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL; appData.pvSAN[0] = FALSE;
10388 appData.firstHasOwnBookUCI = !appData.defNoBook; appData.protocolVersion[0] = PROTOVER;
10389 ParseArgsFromString(buf);
10390 } else { // no engine with this nickname is installed!
10391 snprintf(buf, MSG_SIZ, _("No engine %s is installed"), engineName);
10392 ReserveGame(nextGame, ' '); // unreserve game and drop out of match mode with error
10393 matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
10395 DisplayError(buf, 0);
10402 char *recentEngines;
10405 RecentEngineEvent (int nr)
10408 // SwapEngines(1); // bump first to second
10409 // ReplaceEngine(&second, 1); // and load it there
10410 NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
10411 n = SetPlayer(nr, recentEngines); // select new (using original menu order!)
10412 if(mnemonic[n]) { // if somehow the engine with the selected nickname is no longer found in the list, we skip
10413 ReplaceEngine(&first, 0);
10414 FloatToFront(&appData.recentEngineList, command[n]);
10419 Pairing (int nr, int nPlayers, int *whitePlayer, int *blackPlayer, int *syncInterval)
10420 { // determine players from game number
10421 int curCycle, curRound, curPairing, gamesPerCycle, gamesPerRound, roundsPerCycle=1, pairingsPerRound=1;
10423 if(appData.tourneyType == 0) {
10424 roundsPerCycle = (nPlayers - 1) | 1;
10425 pairingsPerRound = nPlayers / 2;
10426 } else if(appData.tourneyType > 0) {
10427 roundsPerCycle = nPlayers - appData.tourneyType;
10428 pairingsPerRound = appData.tourneyType;
10430 gamesPerRound = pairingsPerRound * appData.defaultMatchGames;
10431 gamesPerCycle = gamesPerRound * roundsPerCycle;
10432 appData.matchGames = gamesPerCycle * appData.tourneyCycles - 1; // fake like all games are one big match
10433 curCycle = nr / gamesPerCycle; nr %= gamesPerCycle;
10434 curRound = nr / gamesPerRound; nr %= gamesPerRound;
10435 curPairing = nr / appData.defaultMatchGames; nr %= appData.defaultMatchGames;
10436 matchGame = nr + curCycle * appData.defaultMatchGames + 1; // fake game nr that loads correct game or position from file
10437 roundNr = (curCycle * roundsPerCycle + curRound) * appData.defaultMatchGames + nr + 1;
10439 if(appData.cycleSync) *syncInterval = gamesPerCycle;
10440 if(appData.roundSync) *syncInterval = gamesPerRound;
10442 if(appData.debugMode) fprintf(debugFP, "cycle=%d, round=%d, pairing=%d curGame=%d\n", curCycle, curRound, curPairing, matchGame);
10444 if(appData.tourneyType == 0) {
10445 if(curPairing == (nPlayers-1)/2 ) {
10446 *whitePlayer = curRound;
10447 *blackPlayer = nPlayers - 1; // this is the 'bye' when nPlayer is odd
10449 *whitePlayer = curRound - (nPlayers-1)/2 + curPairing;
10450 if(*whitePlayer < 0) *whitePlayer += nPlayers-1+(nPlayers&1);
10451 *blackPlayer = curRound + (nPlayers-1)/2 - curPairing;
10452 if(*blackPlayer >= nPlayers-1+(nPlayers&1)) *blackPlayer -= nPlayers-1+(nPlayers&1);
10454 } else if(appData.tourneyType > 1) {
10455 *blackPlayer = curPairing; // in multi-gauntlet, assign gauntlet engines to second, so first an be kept loaded during round
10456 *whitePlayer = curRound + appData.tourneyType;
10457 } else if(appData.tourneyType > 0) {
10458 *whitePlayer = curPairing;
10459 *blackPlayer = curRound + appData.tourneyType;
10462 // take care of white/black alternation per round.
10463 // For cycles and games this is already taken care of by default, derived from matchGame!
10464 return curRound & 1;
10468 NextTourneyGame (int nr, int *swapColors)
10469 { // !!!major kludge!!! fiddle appData settings to get everything in order for next tourney game
10471 int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers, OK = 1;
10473 if(appData.tourneyFile[0] == NULLCHAR) return 1; // no tourney, always allow next game
10474 tf = fopen(appData.tourneyFile, "r");
10475 if(tf == NULL) { DisplayFatalError(_("Bad tournament file"), 0, 1); return 0; }
10476 ParseArgsFromFile(tf); fclose(tf);
10477 InitTimeControls(); // TC might be altered from tourney file
10479 nPlayers = CountPlayers(appData.participants); // count participants
10480 if(appData.tourneyType < 0) syncInterval = nPlayers/2; else
10481 *swapColors = Pairing(nr<0 ? 0 : nr, nPlayers, &whitePlayer, &blackPlayer, &syncInterval);
10484 p = q = appData.results;
10485 while(*q) if(*q++ == '*' || q[-1] == ' ') { firstBusy = q - p - 1; break; }
10486 if(firstBusy/syncInterval < (nextGame/syncInterval)) {
10487 DisplayMessage(_("Waiting for other game(s)"),"");
10488 waitingForGame = TRUE;
10489 ScheduleDelayedEvent(NextMatchGame, 1000); // wait for all games of previous round to finish
10492 waitingForGame = FALSE;
10495 if(appData.tourneyType < 0) {
10496 if(nr>=0 && !pairingReceived) {
10498 if(pairing.pr == NoProc) {
10499 if(!appData.pairingEngine[0]) {
10500 DisplayFatalError(_("No pairing engine specified"), 0, 1);
10503 StartChessProgram(&pairing); // starts the pairing engine
10505 snprintf(buf, 1<<16, "results %d %s\n", nPlayers, appData.results);
10506 SendToProgram(buf, &pairing);
10507 snprintf(buf, 1<<16, "pairing %d\n", nr+1);
10508 SendToProgram(buf, &pairing);
10509 return 0; // wait for pairing engine to answer (which causes NextTourneyGame to be called again...
10511 pairingReceived = 0; // ... so we continue here
10513 appData.matchGames = appData.tourneyCycles * syncInterval - 1;
10514 whitePlayer = savedWhitePlayer-1; blackPlayer = savedBlackPlayer-1;
10515 matchGame = 1; roundNr = nr / syncInterval + 1;
10518 if(first.pr != NoProc && second.pr != NoProc || nr<0) return 1; // engines already loaded
10520 // redefine engines, engine dir, etc.
10521 NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
10522 if(first.pr == NoProc) {
10523 if(!SetPlayer(whitePlayer, appData.participants)) OK = 0; // find white player amongst it, and parse its engine line
10524 InitEngine(&first, 0); // initialize ChessProgramStates based on new settings.
10526 if(second.pr == NoProc) {
10528 if(!SetPlayer(blackPlayer, appData.participants)) OK = 0; // find black player amongst it, and parse its engine line
10529 SwapEngines(1); // and make that valid for second engine by swapping
10530 InitEngine(&second, 1);
10532 CommonEngineInit(); // after this TwoMachinesEvent will create correct engine processes
10533 UpdateLogos(FALSE); // leave display to ModeHiglight()
10539 { // performs game initialization that does not invoke engines, and then tries to start the game
10540 int res, firstWhite, swapColors = 0;
10541 if(!NextTourneyGame(nextGame, &swapColors)) return; // this sets matchGame, -fcp / -scp and other options for next game, if needed
10542 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
10544 snprintf(buf, MSG_SIZ, appData.nameOfDebugFile, nextGame+1); // expand name of debug file with %d in it
10545 if(strcmp(buf, currentDebugFile)) { // name has changed
10546 FILE *f = fopen(buf, "w");
10547 if(f) { // if opening the new file failed, just keep using the old one
10548 ASSIGN(currentDebugFile, buf);
10552 if(appData.serverFileName) {
10553 if(serverFP) fclose(serverFP);
10554 serverFP = fopen(appData.serverFileName, "w");
10555 if(serverFP && first.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", first.tidy);
10556 if(serverFP && second.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", second.tidy);
10560 firstWhite = appData.firstPlaysBlack ^ (matchGame & 1 | appData.sameColorGames > 1); // non-incremental default
10561 firstWhite ^= swapColors; // reverses if NextTourneyGame says we are in an odd round
10562 first.twoMachinesColor = firstWhite ? "white\n" : "black\n"; // perform actual color assignement
10563 second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
10564 appData.noChessProgram = (first.pr == NoProc); // kludge to prevent Reset from starting up chess program
10565 if(appData.loadGameIndex == -2) srandom(appData.seedBase + 68163*(nextGame & ~1)); // deterministic seed to force same opening
10566 Reset(FALSE, first.pr != NoProc);
10567 res = LoadGameOrPosition(matchGame); // setup game
10568 appData.noChessProgram = FALSE; // LoadGameOrPosition might call Reset too!
10569 if(!res) return; // abort when bad game/pos file
10570 TwoMachinesEvent();
10574 UserAdjudicationEvent (int result)
10576 ChessMove gameResult = GameIsDrawn;
10579 gameResult = WhiteWins;
10581 else if( result < 0 ) {
10582 gameResult = BlackWins;
10585 if( gameMode == TwoMachinesPlay ) {
10586 GameEnds( gameResult, "User adjudication", GE_XBOARD );
10591 // [HGM] save: calculate checksum of game to make games easily identifiable
10593 StringCheckSum (char *s)
10596 if(s==NULL) return 0;
10597 while(*s) i = i*259 + *s++;
10605 for(i=backwardMostMove; i<forwardMostMove; i++) {
10606 sum += pvInfoList[i].depth;
10607 sum += StringCheckSum(parseList[i]);
10608 sum += StringCheckSum(commentList[i]);
10611 if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
10612 return sum + StringCheckSum(commentList[i]);
10613 } // end of save patch
10616 GameEnds (ChessMove result, char *resultDetails, int whosays)
10618 GameMode nextGameMode;
10620 char buf[MSG_SIZ], popupRequested = 0, *ranking = NULL;
10622 if(endingGame) return; /* [HGM] crash: forbid recursion */
10624 if(twoBoards) { // [HGM] dual: switch back to one board
10625 twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
10626 DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
10628 if (appData.debugMode) {
10629 fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
10630 result, resultDetails ? resultDetails : "(null)", whosays);
10633 fromX = fromY = -1; // [HGM] abort any move the user is entering.
10635 if(pausing) PauseEvent(); // can happen when we abort a paused game (New Game or Quit)
10637 if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
10638 /* If we are playing on ICS, the server decides when the
10639 game is over, but the engine can offer to draw, claim
10643 if (appData.zippyPlay && first.initDone) {
10644 if (result == GameIsDrawn) {
10645 /* In case draw still needs to be claimed */
10646 SendToICS(ics_prefix);
10647 SendToICS("draw\n");
10648 } else if (StrCaseStr(resultDetails, "resign")) {
10649 SendToICS(ics_prefix);
10650 SendToICS("resign\n");
10654 endingGame = 0; /* [HGM] crash */
10658 /* If we're loading the game from a file, stop */
10659 if (whosays == GE_FILE) {
10660 (void) StopLoadGameTimer();
10664 /* Cancel draw offers */
10665 first.offeredDraw = second.offeredDraw = 0;
10667 /* If this is an ICS game, only ICS can really say it's done;
10668 if not, anyone can. */
10669 isIcsGame = (gameMode == IcsPlayingWhite ||
10670 gameMode == IcsPlayingBlack ||
10671 gameMode == IcsObserving ||
10672 gameMode == IcsExamining);
10674 if (!isIcsGame || whosays == GE_ICS) {
10675 /* OK -- not an ICS game, or ICS said it was done */
10677 if (!isIcsGame && !appData.noChessProgram)
10678 SetUserThinkingEnables();
10680 /* [HGM] if a machine claims the game end we verify this claim */
10681 if(gameMode == TwoMachinesPlay && appData.testClaims) {
10682 if(appData.testLegality && whosays >= GE_ENGINE1 ) {
10684 ChessMove trueResult = (ChessMove) -1;
10686 claimer = whosays == GE_ENGINE1 ? /* color of claimer */
10687 first.twoMachinesColor[0] :
10688 second.twoMachinesColor[0] ;
10690 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
10691 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
10692 /* [HGM] verify: engine mate claims accepted if they were flagged */
10693 trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
10695 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
10696 /* [HGM] verify: engine mate claims accepted if they were flagged */
10697 trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
10699 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
10700 trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
10703 // now verify win claims, but not in drop games, as we don't understand those yet
10704 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
10705 || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) &&
10706 (result == WhiteWins && claimer == 'w' ||
10707 result == BlackWins && claimer == 'b' ) ) { // case to verify: engine claims own win
10708 if (appData.debugMode) {
10709 fprintf(debugFP, "result=%d sp=%d move=%d\n",
10710 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
10712 if(result != trueResult) {
10713 snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
10714 result = claimer == 'w' ? BlackWins : WhiteWins;
10715 resultDetails = buf;
10718 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
10719 && (forwardMostMove <= backwardMostMove ||
10720 (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
10721 (claimer=='b')==(forwardMostMove&1))
10723 /* [HGM] verify: draws that were not flagged are false claims */
10724 snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
10725 result = claimer == 'w' ? BlackWins : WhiteWins;
10726 resultDetails = buf;
10728 /* (Claiming a loss is accepted no questions asked!) */
10729 } else if(matchMode && result == GameIsDrawn && !strcmp(resultDetails, "Engine Abort Request")) {
10730 forwardMostMove = backwardMostMove; // [HGM] delete game to surpress saving
10731 result = GameUnfinished;
10732 if(!*appData.tourneyFile) matchGame--; // replay even in plain match
10734 /* [HGM] bare: don't allow bare King to win */
10735 if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
10736 || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
10737 && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
10738 && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
10739 && result != GameIsDrawn)
10740 { int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
10741 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
10742 int p = (signed char)boards[forwardMostMove][i][j] - color;
10743 if(p >= 0 && p <= (int)WhiteKing) k++;
10745 if (appData.debugMode) {
10746 fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
10747 result, resultDetails ? resultDetails : "(null)", whosays, k, color);
10750 result = GameIsDrawn;
10751 snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
10752 resultDetails = buf;
10758 if(serverMoves != NULL && !loadFlag) { char c = '=';
10759 if(result==WhiteWins) c = '+';
10760 if(result==BlackWins) c = '-';
10761 if(resultDetails != NULL)
10762 fprintf(serverMoves, ";%c;%s\n", c, resultDetails), fflush(serverMoves);
10764 if (resultDetails != NULL) {
10765 gameInfo.result = result;
10766 gameInfo.resultDetails = StrSave(resultDetails);
10768 /* display last move only if game was not loaded from file */
10769 if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
10770 DisplayMove(currentMove - 1);
10772 if (forwardMostMove != 0) {
10773 if (gameMode != PlayFromGameFile && gameMode != EditGame
10774 && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
10776 if (*appData.saveGameFile != NULLCHAR) {
10777 if(result == GameUnfinished && matchMode && *appData.tourneyFile)
10778 AutoSaveGame(); // [HGM] protect tourney PGN from aborted games, and prompt for name instead
10780 SaveGameToFile(appData.saveGameFile, TRUE);
10781 } else if (appData.autoSaveGames) {
10782 if(gameMode != IcsObserving || !appData.onlyOwn) AutoSaveGame();
10784 if (*appData.savePositionFile != NULLCHAR) {
10785 SavePositionToFile(appData.savePositionFile);
10787 AddGameToBook(FALSE); // Only does something during Monte-Carlo book building
10791 /* Tell program how game ended in case it is learning */
10792 /* [HGM] Moved this to after saving the PGN, just in case */
10793 /* engine died and we got here through time loss. In that */
10794 /* case we will get a fatal error writing the pipe, which */
10795 /* would otherwise lose us the PGN. */
10796 /* [HGM] crash: not needed anymore, but doesn't hurt; */
10797 /* output during GameEnds should never be fatal anymore */
10798 if (gameMode == MachinePlaysWhite ||
10799 gameMode == MachinePlaysBlack ||
10800 gameMode == TwoMachinesPlay ||
10801 gameMode == IcsPlayingWhite ||
10802 gameMode == IcsPlayingBlack ||
10803 gameMode == BeginningOfGame) {
10805 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
10807 if (first.pr != NoProc) {
10808 SendToProgram(buf, &first);
10810 if (second.pr != NoProc &&
10811 gameMode == TwoMachinesPlay) {
10812 SendToProgram(buf, &second);
10817 if (appData.icsActive) {
10818 if (appData.quietPlay &&
10819 (gameMode == IcsPlayingWhite ||
10820 gameMode == IcsPlayingBlack)) {
10821 SendToICS(ics_prefix);
10822 SendToICS("set shout 1\n");
10824 nextGameMode = IcsIdle;
10825 ics_user_moved = FALSE;
10826 /* clean up premove. It's ugly when the game has ended and the
10827 * premove highlights are still on the board.
10830 gotPremove = FALSE;
10831 ClearPremoveHighlights();
10832 DrawPosition(FALSE, boards[currentMove]);
10834 if (whosays == GE_ICS) {
10837 if (gameMode == IcsPlayingWhite)
10839 else if(gameMode == IcsPlayingBlack)
10840 PlayIcsLossSound();
10843 if (gameMode == IcsPlayingBlack)
10845 else if(gameMode == IcsPlayingWhite)
10846 PlayIcsLossSound();
10849 PlayIcsDrawSound();
10852 PlayIcsUnfinishedSound();
10855 } else if (gameMode == EditGame ||
10856 gameMode == PlayFromGameFile ||
10857 gameMode == AnalyzeMode ||
10858 gameMode == AnalyzeFile) {
10859 nextGameMode = gameMode;
10861 nextGameMode = EndOfGame;
10866 nextGameMode = gameMode;
10869 if (appData.noChessProgram) {
10870 gameMode = nextGameMode;
10872 endingGame = 0; /* [HGM] crash */
10877 /* Put first chess program into idle state */
10878 if (first.pr != NoProc &&
10879 (gameMode == MachinePlaysWhite ||
10880 gameMode == MachinePlaysBlack ||
10881 gameMode == TwoMachinesPlay ||
10882 gameMode == IcsPlayingWhite ||
10883 gameMode == IcsPlayingBlack ||
10884 gameMode == BeginningOfGame)) {
10885 SendToProgram("force\n", &first);
10886 if (first.usePing) {
10888 snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
10889 SendToProgram(buf, &first);
10892 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
10893 /* Kill off first chess program */
10894 if (first.isr != NULL)
10895 RemoveInputSource(first.isr);
10898 if (first.pr != NoProc) {
10900 DoSleep( appData.delayBeforeQuit );
10901 SendToProgram("quit\n", &first);
10902 DoSleep( appData.delayAfterQuit );
10903 DestroyChildProcess(first.pr, first.useSigterm);
10904 first.reload = TRUE;
10908 if (second.reuse) {
10909 /* Put second chess program into idle state */
10910 if (second.pr != NoProc &&
10911 gameMode == TwoMachinesPlay) {
10912 SendToProgram("force\n", &second);
10913 if (second.usePing) {
10915 snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
10916 SendToProgram(buf, &second);
10919 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
10920 /* Kill off second chess program */
10921 if (second.isr != NULL)
10922 RemoveInputSource(second.isr);
10925 if (second.pr != NoProc) {
10926 DoSleep( appData.delayBeforeQuit );
10927 SendToProgram("quit\n", &second);
10928 DoSleep( appData.delayAfterQuit );
10929 DestroyChildProcess(second.pr, second.useSigterm);
10930 second.reload = TRUE;
10932 second.pr = NoProc;
10935 if (matchMode && (gameMode == TwoMachinesPlay || (waitingForGame || startingEngine) && exiting)) {
10936 char resChar = '=';
10940 if (first.twoMachinesColor[0] == 'w') {
10943 second.matchWins++;
10948 if (first.twoMachinesColor[0] == 'b') {
10951 second.matchWins++;
10954 case GameUnfinished:
10960 if(exiting) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game
10961 if(appData.tourneyFile[0]){ // [HGM] we are in a tourney; update tourney file with game result
10962 if(appData.afterGame && appData.afterGame[0]) RunCommand(appData.afterGame);
10963 ReserveGame(nextGame, resChar); // sets nextGame
10964 if(nextGame > appData.matchGames) appData.tourneyFile[0] = 0, ranking = TourneyStandings(3); // tourney is done
10965 else ranking = strdup("busy"); //suppress popup when aborted but not finished
10966 } else roundNr = nextGame = matchGame + 1; // normal match, just increment; round equals matchGame
10968 if (nextGame <= appData.matchGames && !abortMatch) {
10969 gameMode = nextGameMode;
10970 matchGame = nextGame; // this will be overruled in tourney mode!
10971 GetTimeMark(&pauseStart); // [HGM] matchpause: stipulate a pause
10972 ScheduleDelayedEvent(NextMatchGame, 10); // but start game immediately (as it will wait out the pause itself)
10973 endingGame = 0; /* [HGM] crash */
10976 gameMode = nextGameMode;
10977 snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
10978 first.tidy, second.tidy,
10979 first.matchWins, second.matchWins,
10980 appData.matchGames - (first.matchWins + second.matchWins));
10981 if(!appData.tourneyFile[0]) matchGame++, DisplayTwoMachinesTitle(); // [HGM] update result in window title
10982 if(ranking && strcmp(ranking, "busy") && appData.afterTourney && appData.afterTourney[0]) RunCommand(appData.afterTourney);
10983 popupRequested++; // [HGM] crash: postpone to after resetting endingGame
10984 if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
10985 first.twoMachinesColor = "black\n";
10986 second.twoMachinesColor = "white\n";
10988 first.twoMachinesColor = "white\n";
10989 second.twoMachinesColor = "black\n";
10993 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
10994 !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
10996 gameMode = nextGameMode;
10998 endingGame = 0; /* [HGM] crash */
10999 if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
11000 if(matchMode == TRUE) { // match through command line: exit with or without popup
11002 ToNrEvent(forwardMostMove);
11003 if(strcmp(ranking, "busy")) DisplayFatalError(ranking, 0, 0);
11005 } else DisplayFatalError(buf, 0, 0);
11006 } else { // match through menu; just stop, with or without popup
11007 matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
11010 if(strcmp(ranking, "busy")) DisplayNote(ranking);
11011 } else DisplayNote(buf);
11013 if(ranking) free(ranking);
11017 /* Assumes program was just initialized (initString sent).
11018 Leaves program in force mode. */
11020 FeedMovesToProgram (ChessProgramState *cps, int upto)
11024 if (appData.debugMode)
11025 fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
11026 startedFromSetupPosition ? "position and " : "",
11027 backwardMostMove, upto, cps->which);
11028 if(currentlyInitializedVariant != gameInfo.variant) {
11030 // [HGM] variantswitch: make engine aware of new variant
11031 if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
11032 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
11033 snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
11034 SendToProgram(buf, cps);
11035 currentlyInitializedVariant = gameInfo.variant;
11037 SendToProgram("force\n", cps);
11038 if (startedFromSetupPosition) {
11039 SendBoard(cps, backwardMostMove);
11040 if (appData.debugMode) {
11041 fprintf(debugFP, "feedMoves\n");
11044 for (i = backwardMostMove; i < upto; i++) {
11045 SendMoveToProgram(i, cps);
11051 ResurrectChessProgram ()
11053 /* The chess program may have exited.
11054 If so, restart it and feed it all the moves made so far. */
11055 static int doInit = 0;
11057 if (appData.noChessProgram) return 1;
11059 if(matchMode /*&& appData.tourneyFile[0]*/) { // [HGM] tourney: make sure we get features after engine replacement. (Should we always do this?)
11060 if(WaitForEngine(&first, TwoMachinesEventIfReady)) { doInit = 1; return 0; } // request to do init on next visit, because we started engine
11061 if(!doInit) return 1; // this replaces testing first.pr != NoProc, which is true when we get here, but first time no reason to abort
11062 doInit = 0; // we fell through (first time after starting the engine); make sure it doesn't happen again
11064 if (first.pr != NoProc) return 1;
11065 StartChessProgram(&first);
11067 InitChessProgram(&first, FALSE);
11068 FeedMovesToProgram(&first, currentMove);
11070 if (!first.sendTime) {
11071 /* can't tell gnuchess what its clock should read,
11072 so we bow to its notion. */
11074 timeRemaining[0][currentMove] = whiteTimeRemaining;
11075 timeRemaining[1][currentMove] = blackTimeRemaining;
11078 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
11079 appData.icsEngineAnalyze) && first.analysisSupport) {
11080 SendToProgram("analyze\n", &first);
11081 first.analyzing = TRUE;
11087 * Button procedures
11090 Reset (int redraw, int init)
11094 if (appData.debugMode) {
11095 fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
11096 redraw, init, gameMode);
11098 CleanupTail(); // [HGM] vari: delete any stored variations
11099 CommentPopDown(); // [HGM] make sure no comments to the previous game keep hanging on
11100 pausing = pauseExamInvalid = FALSE;
11101 startedFromSetupPosition = blackPlaysFirst = FALSE;
11103 whiteFlag = blackFlag = FALSE;
11104 userOfferedDraw = FALSE;
11105 hintRequested = bookRequested = FALSE;
11106 first.maybeThinking = FALSE;
11107 second.maybeThinking = FALSE;
11108 first.bookSuspend = FALSE; // [HGM] book
11109 second.bookSuspend = FALSE;
11110 thinkOutput[0] = NULLCHAR;
11111 lastHint[0] = NULLCHAR;
11112 ClearGameInfo(&gameInfo);
11113 gameInfo.variant = StringToVariant(appData.variant);
11114 ics_user_moved = ics_clock_paused = FALSE;
11115 ics_getting_history = H_FALSE;
11117 white_holding[0] = black_holding[0] = NULLCHAR;
11118 ClearProgramStats();
11119 opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
11123 flipView = appData.flipView;
11124 ClearPremoveHighlights();
11125 gotPremove = FALSE;
11126 alarmSounded = FALSE;
11128 GameEnds(EndOfFile, NULL, GE_PLAYER);
11129 if(appData.serverMovesName != NULL) {
11130 /* [HGM] prepare to make moves file for broadcasting */
11131 clock_t t = clock();
11132 if(serverMoves != NULL) fclose(serverMoves);
11133 serverMoves = fopen(appData.serverMovesName, "r");
11134 if(serverMoves != NULL) {
11135 fclose(serverMoves);
11136 /* delay 15 sec before overwriting, so all clients can see end */
11137 while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
11139 serverMoves = fopen(appData.serverMovesName, "w");
11143 gameMode = BeginningOfGame;
11145 if(appData.icsActive) gameInfo.variant = VariantNormal;
11146 currentMove = forwardMostMove = backwardMostMove = 0;
11147 MarkTargetSquares(1);
11148 InitPosition(redraw);
11149 for (i = 0; i < MAX_MOVES; i++) {
11150 if (commentList[i] != NULL) {
11151 free(commentList[i]);
11152 commentList[i] = NULL;
11156 timeRemaining[0][0] = whiteTimeRemaining;
11157 timeRemaining[1][0] = blackTimeRemaining;
11159 if (first.pr == NoProc) {
11160 StartChessProgram(&first);
11163 InitChessProgram(&first, startedFromSetupPosition);
11166 DisplayMessage("", "");
11167 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11168 lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
11169 ClearMap(); // [HGM] exclude: invalidate map
11173 AutoPlayGameLoop ()
11176 if (!AutoPlayOneMove())
11178 if (matchMode || appData.timeDelay == 0)
11180 if (appData.timeDelay < 0)
11182 StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
11190 ReloadGame(1); // next game
11196 int fromX, fromY, toX, toY;
11198 if (appData.debugMode) {
11199 fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
11202 if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
11205 if (gameMode == AnalyzeFile && currentMove > backwardMostMove && programStats.depth) {
11206 pvInfoList[currentMove].depth = programStats.depth;
11207 pvInfoList[currentMove].score = programStats.score;
11208 pvInfoList[currentMove].time = 0;
11209 if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
11210 else { // append analysis of final position as comment
11212 snprintf(buf, MSG_SIZ, "{final score %+4.2f/%d}", programStats.score/100., programStats.depth);
11213 AppendComment(currentMove, buf, 3); // the 3 prevents stripping of the score/depth!
11215 programStats.depth = 0;
11218 if (currentMove >= forwardMostMove) {
11219 if(gameMode == AnalyzeFile) {
11220 if(appData.loadGameIndex == -1) {
11221 GameEnds(gameInfo.result, gameInfo.resultDetails ? gameInfo.resultDetails : "", GE_FILE);
11222 ScheduleDelayedEvent(AnalyzeNextGame, 10);
11224 ExitAnalyzeMode(); SendToProgram("force\n", &first);
11227 // gameMode = EndOfGame;
11228 // ModeHighlight();
11230 /* [AS] Clear current move marker at the end of a game */
11231 /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
11236 toX = moveList[currentMove][2] - AAA;
11237 toY = moveList[currentMove][3] - ONE;
11239 if (moveList[currentMove][1] == '@') {
11240 if (appData.highlightLastMove) {
11241 SetHighlights(-1, -1, toX, toY);
11244 fromX = moveList[currentMove][0] - AAA;
11245 fromY = moveList[currentMove][1] - ONE;
11247 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
11249 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
11251 if (appData.highlightLastMove) {
11252 SetHighlights(fromX, fromY, toX, toY);
11255 DisplayMove(currentMove);
11256 SendMoveToProgram(currentMove++, &first);
11257 DisplayBothClocks();
11258 DrawPosition(FALSE, boards[currentMove]);
11259 // [HGM] PV info: always display, routine tests if empty
11260 DisplayComment(currentMove - 1, commentList[currentMove]);
11266 LoadGameOneMove (ChessMove readAhead)
11268 int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
11269 char promoChar = NULLCHAR;
11270 ChessMove moveType;
11271 char move[MSG_SIZ];
11274 if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
11275 gameMode != AnalyzeMode && gameMode != Training) {
11280 yyboardindex = forwardMostMove;
11281 if (readAhead != EndOfFile) {
11282 moveType = readAhead;
11284 if (gameFileFP == NULL)
11286 moveType = (ChessMove) Myylex();
11290 switch (moveType) {
11292 if (appData.debugMode)
11293 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11296 /* append the comment but don't display it */
11297 AppendComment(currentMove, p, FALSE);
11300 case WhiteCapturesEnPassant:
11301 case BlackCapturesEnPassant:
11302 case WhitePromotion:
11303 case BlackPromotion:
11304 case WhiteNonPromotion:
11305 case BlackNonPromotion:
11307 case WhiteKingSideCastle:
11308 case WhiteQueenSideCastle:
11309 case BlackKingSideCastle:
11310 case BlackQueenSideCastle:
11311 case WhiteKingSideCastleWild:
11312 case WhiteQueenSideCastleWild:
11313 case BlackKingSideCastleWild:
11314 case BlackQueenSideCastleWild:
11316 case WhiteHSideCastleFR:
11317 case WhiteASideCastleFR:
11318 case BlackHSideCastleFR:
11319 case BlackASideCastleFR:
11321 if (appData.debugMode)
11322 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
11323 fromX = currentMoveString[0] - AAA;
11324 fromY = currentMoveString[1] - ONE;
11325 toX = currentMoveString[2] - AAA;
11326 toY = currentMoveString[3] - ONE;
11327 promoChar = currentMoveString[4];
11332 if (appData.debugMode)
11333 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
11334 fromX = moveType == WhiteDrop ?
11335 (int) CharToPiece(ToUpper(currentMoveString[0])) :
11336 (int) CharToPiece(ToLower(currentMoveString[0]));
11338 toX = currentMoveString[2] - AAA;
11339 toY = currentMoveString[3] - ONE;
11345 case GameUnfinished:
11346 if (appData.debugMode)
11347 fprintf(debugFP, "Parsed game end: %s\n", yy_text);
11348 p = strchr(yy_text, '{');
11349 if (p == NULL) p = strchr(yy_text, '(');
11352 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
11354 q = strchr(p, *p == '{' ? '}' : ')');
11355 if (q != NULL) *q = NULLCHAR;
11358 while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
11359 GameEnds(moveType, p, GE_FILE);
11361 if (cmailMsgLoaded) {
11363 flipView = WhiteOnMove(currentMove);
11364 if (moveType == GameUnfinished) flipView = !flipView;
11365 if (appData.debugMode)
11366 fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
11371 if (appData.debugMode)
11372 fprintf(debugFP, "Parser hit end of file\n");
11373 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11379 if (WhiteOnMove(currentMove)) {
11380 GameEnds(BlackWins, "Black mates", GE_FILE);
11382 GameEnds(WhiteWins, "White mates", GE_FILE);
11386 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
11392 case MoveNumberOne:
11393 if (lastLoadGameStart == GNUChessGame) {
11394 /* GNUChessGames have numbers, but they aren't move numbers */
11395 if (appData.debugMode)
11396 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
11397 yy_text, (int) moveType);
11398 return LoadGameOneMove(EndOfFile); /* tail recursion */
11400 /* else fall thru */
11405 /* Reached start of next game in file */
11406 if (appData.debugMode)
11407 fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
11408 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11414 if (WhiteOnMove(currentMove)) {
11415 GameEnds(BlackWins, "Black mates", GE_FILE);
11417 GameEnds(WhiteWins, "White mates", GE_FILE);
11421 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
11427 case PositionDiagram: /* should not happen; ignore */
11428 case ElapsedTime: /* ignore */
11429 case NAG: /* ignore */
11430 if (appData.debugMode)
11431 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
11432 yy_text, (int) moveType);
11433 return LoadGameOneMove(EndOfFile); /* tail recursion */
11436 if (appData.testLegality) {
11437 if (appData.debugMode)
11438 fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
11439 snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
11440 (forwardMostMove / 2) + 1,
11441 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11442 DisplayError(move, 0);
11445 if (appData.debugMode)
11446 fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
11447 yy_text, currentMoveString);
11448 fromX = currentMoveString[0] - AAA;
11449 fromY = currentMoveString[1] - ONE;
11450 toX = currentMoveString[2] - AAA;
11451 toY = currentMoveString[3] - ONE;
11452 promoChar = currentMoveString[4];
11456 case AmbiguousMove:
11457 if (appData.debugMode)
11458 fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
11459 snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
11460 (forwardMostMove / 2) + 1,
11461 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11462 DisplayError(move, 0);
11467 case ImpossibleMove:
11468 if (appData.debugMode)
11469 fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
11470 snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
11471 (forwardMostMove / 2) + 1,
11472 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11473 DisplayError(move, 0);
11479 if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
11480 DrawPosition(FALSE, boards[currentMove]);
11481 DisplayBothClocks();
11482 if (!appData.matchMode) // [HGM] PV info: routine tests if empty
11483 DisplayComment(currentMove - 1, commentList[currentMove]);
11485 (void) StopLoadGameTimer();
11487 cmailOldMove = forwardMostMove;
11490 /* currentMoveString is set as a side-effect of yylex */
11492 thinkOutput[0] = NULLCHAR;
11493 MakeMove(fromX, fromY, toX, toY, promoChar);
11494 currentMove = forwardMostMove;
11499 /* Load the nth game from the given file */
11501 LoadGameFromFile (char *filename, int n, char *title, int useList)
11506 if (strcmp(filename, "-") == 0) {
11510 f = fopen(filename, "rb");
11512 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
11513 DisplayError(buf, errno);
11517 if (fseek(f, 0, 0) == -1) {
11518 /* f is not seekable; probably a pipe */
11521 if (useList && n == 0) {
11522 int error = GameListBuild(f);
11524 DisplayError(_("Cannot build game list"), error);
11525 } else if (!ListEmpty(&gameList) &&
11526 ((ListGame *) gameList.tailPred)->number > 1) {
11527 GameListPopUp(f, title);
11534 return LoadGame(f, n, title, FALSE);
11539 MakeRegisteredMove ()
11541 int fromX, fromY, toX, toY;
11543 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11544 switch (cmailMoveType[lastLoadGameNumber - 1]) {
11547 if (appData.debugMode)
11548 fprintf(debugFP, "Restoring %s for game %d\n",
11549 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
11551 thinkOutput[0] = NULLCHAR;
11552 safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
11553 fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
11554 fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
11555 toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
11556 toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
11557 promoChar = cmailMove[lastLoadGameNumber - 1][4];
11558 MakeMove(fromX, fromY, toX, toY, promoChar);
11559 ShowMove(fromX, fromY, toX, toY);
11561 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11568 if (WhiteOnMove(currentMove)) {
11569 GameEnds(BlackWins, "Black mates", GE_PLAYER);
11571 GameEnds(WhiteWins, "White mates", GE_PLAYER);
11576 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
11583 if (WhiteOnMove(currentMove)) {
11584 GameEnds(BlackWins, "White resigns", GE_PLAYER);
11586 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11591 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11602 /* Wrapper around LoadGame for use when a Cmail message is loaded */
11604 CmailLoadGame (FILE *f, int gameNumber, char *title, int useList)
11608 if (gameNumber > nCmailGames) {
11609 DisplayError(_("No more games in this message"), 0);
11612 if (f == lastLoadGameFP) {
11613 int offset = gameNumber - lastLoadGameNumber;
11615 cmailMsg[0] = NULLCHAR;
11616 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11617 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
11618 nCmailMovesRegistered--;
11620 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11621 if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
11622 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
11625 if (! RegisterMove()) return FALSE;
11629 retVal = LoadGame(f, gameNumber, title, useList);
11631 /* Make move registered during previous look at this game, if any */
11632 MakeRegisteredMove();
11634 if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
11635 commentList[currentMove]
11636 = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
11637 DisplayComment(currentMove - 1, commentList[currentMove]);
11643 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
11645 ReloadGame (int offset)
11647 int gameNumber = lastLoadGameNumber + offset;
11648 if (lastLoadGameFP == NULL) {
11649 DisplayError(_("No game has been loaded yet"), 0);
11652 if (gameNumber <= 0) {
11653 DisplayError(_("Can't back up any further"), 0);
11656 if (cmailMsgLoaded) {
11657 return CmailLoadGame(lastLoadGameFP, gameNumber,
11658 lastLoadGameTitle, lastLoadGameUseList);
11660 return LoadGame(lastLoadGameFP, gameNumber,
11661 lastLoadGameTitle, lastLoadGameUseList);
11665 int keys[EmptySquare+1];
11668 PositionMatches (Board b1, Board b2)
11671 switch(appData.searchMode) {
11672 case 1: return CompareWithRights(b1, b2);
11674 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11675 if(b2[r][f] != EmptySquare && b1[r][f] != b2[r][f]) return FALSE;
11679 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11680 if((b2[r][f] == WhitePawn || b2[r][f] == BlackPawn) && b1[r][f] != b2[r][f]) return FALSE;
11681 sum += keys[b1[r][f]] - keys[b2[r][f]];
11685 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11686 sum += keys[b1[r][f]] - keys[b2[r][f]];
11698 int pieceList[256], quickBoard[256];
11699 ChessSquare pieceType[256] = { EmptySquare };
11700 Board soughtBoard, reverseBoard, flipBoard, rotateBoard;
11701 int counts[EmptySquare], minSought[EmptySquare], minReverse[EmptySquare], maxSought[EmptySquare], maxReverse[EmptySquare];
11702 int soughtTotal, turn;
11703 Boolean epOK, flipSearch;
11706 unsigned char piece, to;
11709 #define DSIZE (250000)
11711 Move initialSpace[DSIZE+1000]; // gamble on that game will not be more than 500 moves
11712 Move *moveDatabase = initialSpace;
11713 unsigned int movePtr, dataSize = DSIZE;
11716 MakePieceList (Board board, int *counts)
11718 int r, f, n=Q_PROMO, total=0;
11719 for(r=0;r<EmptySquare;r++) counts[r] = 0; // piece-type counts
11720 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11721 int sq = f + (r<<4);
11722 if(board[r][f] == EmptySquare) quickBoard[sq] = 0; else {
11723 quickBoard[sq] = ++n;
11725 pieceType[n] = board[r][f];
11726 counts[board[r][f]]++;
11727 if(board[r][f] == WhiteKing) pieceList[1] = n; else
11728 if(board[r][f] == BlackKing) pieceList[2] = n; // remember which are Kings, for castling
11732 epOK = gameInfo.variant != VariantXiangqi && gameInfo.variant != VariantBerolina;
11737 PackMove (int fromX, int fromY, int toX, int toY, ChessSquare promoPiece)
11739 int sq = fromX + (fromY<<4);
11740 int piece = quickBoard[sq];
11741 quickBoard[sq] = 0;
11742 moveDatabase[movePtr].to = pieceList[piece] = sq = toX + (toY<<4);
11743 if(piece == pieceList[1] && fromY == toY && (toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
11744 int from = toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT;
11745 moveDatabase[movePtr++].piece = Q_WCASTL;
11746 quickBoard[sq] = piece;
11747 piece = quickBoard[from]; quickBoard[from] = 0;
11748 moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
11750 if(piece == pieceList[2] && fromY == toY && (toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
11751 int from = (toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT) + (BOARD_HEIGHT-1 <<4);
11752 moveDatabase[movePtr++].piece = Q_BCASTL;
11753 quickBoard[sq] = piece;
11754 piece = quickBoard[from]; quickBoard[from] = 0;
11755 moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
11757 if(epOK && (pieceType[piece] == WhitePawn || pieceType[piece] == BlackPawn) && fromX != toX && quickBoard[sq] == 0) {
11758 quickBoard[(fromY<<4)+toX] = 0;
11759 moveDatabase[movePtr].piece = Q_EP;
11760 moveDatabase[movePtr++].to = (fromY<<4)+toX;
11761 moveDatabase[movePtr].to = sq;
11763 if(promoPiece != pieceType[piece]) {
11764 moveDatabase[movePtr++].piece = Q_PROMO;
11765 moveDatabase[movePtr].to = pieceType[piece] = (int) promoPiece;
11767 moveDatabase[movePtr].piece = piece;
11768 quickBoard[sq] = piece;
11773 PackGame (Board board)
11775 Move *newSpace = NULL;
11776 moveDatabase[movePtr].piece = 0; // terminate previous game
11777 if(movePtr > dataSize) {
11778 if(appData.debugMode) fprintf(debugFP, "move-cache overflow, enlarge to %d MB\n", dataSize/128);
11779 dataSize *= 8; // increase size by factor 8 (512KB -> 4MB -> 32MB -> 256MB -> 2GB)
11780 if(dataSize) newSpace = (Move*) calloc(dataSize + 1000, sizeof(Move));
11783 Move *p = moveDatabase, *q = newSpace;
11784 for(i=0; i<movePtr; i++) *q++ = *p++; // copy to newly allocated space
11785 if(dataSize > 8*DSIZE) free(moveDatabase); // and free old space (if it was allocated)
11786 moveDatabase = newSpace;
11787 } else { // calloc failed, we must be out of memory. Too bad...
11788 dataSize = 0; // prevent calloc events for all subsequent games
11789 return 0; // and signal this one isn't cached
11793 MakePieceList(board, counts);
11798 QuickCompare (Board board, int *minCounts, int *maxCounts)
11799 { // compare according to search mode
11801 switch(appData.searchMode)
11803 case 1: // exact position match
11804 if(!(turn & board[EP_STATUS-1])) return FALSE; // wrong side to move
11805 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11806 if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11809 case 2: // can have extra material on empty squares
11810 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11811 if(board[r][f] == EmptySquare) continue;
11812 if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11815 case 3: // material with exact Pawn structure
11816 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11817 if(board[r][f] != WhitePawn && board[r][f] != BlackPawn) continue;
11818 if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11819 } // fall through to material comparison
11820 case 4: // exact material
11821 for(r=0; r<EmptySquare; r++) if(counts[r] != maxCounts[r]) return FALSE;
11823 case 6: // material range with given imbalance
11824 for(r=0; r<BlackPawn; r++) if(counts[r] - minCounts[r] != counts[r+BlackPawn] - minCounts[r+BlackPawn]) return FALSE;
11825 // fall through to range comparison
11826 case 5: // material range
11827 for(r=0; r<EmptySquare; r++) if(counts[r] < minCounts[r] || counts[r] > maxCounts[r]) return FALSE;
11833 QuickScan (Board board, Move *move)
11834 { // reconstruct game,and compare all positions in it
11835 int cnt=0, stretch=0, total = MakePieceList(board, counts);
11837 int piece = move->piece;
11838 int to = move->to, from = pieceList[piece];
11839 if(piece <= Q_PROMO) { // special moves encoded by otherwise invalid piece numbers 1-4
11840 if(!piece) return -1;
11841 if(piece == Q_PROMO) { // promotion, encoded as (Q_PROMO, to) + (piece, promoType)
11842 piece = (++move)->piece;
11843 from = pieceList[piece];
11844 counts[pieceType[piece]]--;
11845 pieceType[piece] = (ChessSquare) move->to;
11846 counts[move->to]++;
11847 } else if(piece == Q_EP) { // e.p. capture, encoded as (Q_EP, ep-sqr) + (piece, to)
11848 counts[pieceType[quickBoard[to]]]--;
11849 quickBoard[to] = 0; total--;
11852 } else if(piece <= Q_BCASTL) { // castling, encoded as (Q_XCASTL, king-to) + (rook, rook-to)
11853 piece = pieceList[piece]; // first two elements of pieceList contain King numbers
11854 from = pieceList[piece]; // so this must be King
11855 quickBoard[from] = 0;
11856 pieceList[piece] = to;
11857 from = pieceList[(++move)->piece]; // for FRC this has to be done here
11858 quickBoard[from] = 0; // rook
11859 quickBoard[to] = piece;
11860 to = move->to; piece = move->piece;
11864 if(appData.searchMode > 2) counts[pieceType[quickBoard[to]]]--; // account capture
11865 if((total -= (quickBoard[to] != 0)) < soughtTotal) return -1; // piece count dropped below what we search for
11866 quickBoard[from] = 0;
11868 quickBoard[to] = piece;
11869 pieceList[piece] = to;
11871 if(QuickCompare(soughtBoard, minSought, maxSought) ||
11872 appData.ignoreColors && QuickCompare(reverseBoard, minReverse, maxReverse) ||
11873 flipSearch && (QuickCompare(flipBoard, minSought, maxSought) ||
11874 appData.ignoreColors && QuickCompare(rotateBoard, minReverse, maxReverse))
11876 static int lastCounts[EmptySquare+1];
11878 if(stretch) for(i=0; i<EmptySquare; i++) if(lastCounts[i] != counts[i]) { stretch = 0; break; } // reset if material changes
11879 if(stretch++ == 0) for(i=0; i<EmptySquare; i++) lastCounts[i] = counts[i]; // remember actual material
11880 } else stretch = 0;
11881 if(stretch && (appData.searchMode == 1 || stretch >= appData.stretch)) return cnt + 1 - stretch;
11890 flipSearch = FALSE;
11891 CopyBoard(soughtBoard, boards[currentMove]);
11892 soughtTotal = MakePieceList(soughtBoard, maxSought);
11893 soughtBoard[EP_STATUS-1] = (currentMove & 1) + 1;
11894 if(currentMove == 0 && gameMode == EditPosition) soughtBoard[EP_STATUS-1] = blackPlaysFirst + 1; // (!)
11895 CopyBoard(reverseBoard, boards[currentMove]);
11896 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11897 int piece = boards[currentMove][BOARD_HEIGHT-1-r][f];
11898 if(piece < BlackPawn) piece += BlackPawn; else if(piece < EmptySquare) piece -= BlackPawn; // color-flip
11899 reverseBoard[r][f] = piece;
11901 reverseBoard[EP_STATUS-1] = soughtBoard[EP_STATUS-1] ^ 3;
11902 for(r=0; r<6; r++) reverseBoard[CASTLING][r] = boards[currentMove][CASTLING][(r+3)%6];
11903 if(appData.findMirror && appData.searchMode <= 3 && (!nrCastlingRights
11904 || (boards[currentMove][CASTLING][2] == NoRights ||
11905 boards[currentMove][CASTLING][0] == NoRights && boards[currentMove][CASTLING][1] == NoRights )
11906 && (boards[currentMove][CASTLING][5] == NoRights ||
11907 boards[currentMove][CASTLING][3] == NoRights && boards[currentMove][CASTLING][4] == NoRights ) )
11910 CopyBoard(flipBoard, soughtBoard);
11911 CopyBoard(rotateBoard, reverseBoard);
11912 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11913 flipBoard[r][f] = soughtBoard[r][BOARD_WIDTH-1-f];
11914 rotateBoard[r][f] = reverseBoard[r][BOARD_WIDTH-1-f];
11917 for(r=0; r<BlackPawn; r++) maxReverse[r] = maxSought[r+BlackPawn], maxReverse[r+BlackPawn] = maxSought[r];
11918 if(appData.searchMode >= 5) {
11919 for(r=BOARD_HEIGHT/2; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) soughtBoard[r][f] = EmptySquare;
11920 MakePieceList(soughtBoard, minSought);
11921 for(r=0; r<BlackPawn; r++) minReverse[r] = minSought[r+BlackPawn], minReverse[r+BlackPawn] = minSought[r];
11923 if(gameInfo.variant == VariantCrazyhouse || gameInfo.variant == VariantShogi || gameInfo.variant == VariantBughouse)
11924 soughtTotal = 0; // in drop games nr of pieces does not fall monotonously
11927 GameInfo dummyInfo;
11928 static int creatingBook;
11931 GameContainsPosition (FILE *f, ListGame *lg)
11933 int next, btm=0, plyNr=0, scratch=forwardMostMove+2&~1;
11934 int fromX, fromY, toX, toY;
11936 static int initDone=FALSE;
11938 // weed out games based on numerical tag comparison
11939 if(lg->gameInfo.variant != gameInfo.variant) return -1; // wrong variant
11940 if(appData.eloThreshold1 && (lg->gameInfo.whiteRating < appData.eloThreshold1 && lg->gameInfo.blackRating < appData.eloThreshold1)) return -1;
11941 if(appData.eloThreshold2 && (lg->gameInfo.whiteRating < appData.eloThreshold2 || lg->gameInfo.blackRating < appData.eloThreshold2)) return -1;
11942 if(appData.dateThreshold && (!lg->gameInfo.date || atoi(lg->gameInfo.date) < appData.dateThreshold)) return -1;
11944 for(next = WhitePawn; next<EmptySquare; next++) keys[next] = random()>>8 ^ random()<<6 ^random()<<20;
11947 if(lg->gameInfo.fen) ParseFEN(boards[scratch], &btm, lg->gameInfo.fen);
11948 else CopyBoard(boards[scratch], initialPosition); // default start position
11951 if((next = QuickScan( boards[scratch], &moveDatabase[lg->moves] )) < 0) return -1; // quick scan rules out it is there
11952 if(appData.searchMode >= 4) return next; // for material searches, trust QuickScan.
11955 if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
11956 fseek(f, lg->offset, 0);
11959 yyboardindex = scratch;
11960 quickFlag = plyNr+1;
11965 if(plyNr) return -1; // after we have seen moves, any tags will be start of next game
11971 if(plyNr) return -1; // after we have seen moves, this is for new game
11974 case AmbiguousMove: // we cannot reconstruct the game beyond these two
11975 case ImpossibleMove:
11976 case WhiteWins: // game ends here with these four
11979 case GameUnfinished:
11983 if(appData.testLegality) return -1;
11984 case WhiteCapturesEnPassant:
11985 case BlackCapturesEnPassant:
11986 case WhitePromotion:
11987 case BlackPromotion:
11988 case WhiteNonPromotion:
11989 case BlackNonPromotion:
11991 case WhiteKingSideCastle:
11992 case WhiteQueenSideCastle:
11993 case BlackKingSideCastle:
11994 case BlackQueenSideCastle:
11995 case WhiteKingSideCastleWild:
11996 case WhiteQueenSideCastleWild:
11997 case BlackKingSideCastleWild:
11998 case BlackQueenSideCastleWild:
11999 case WhiteHSideCastleFR:
12000 case WhiteASideCastleFR:
12001 case BlackHSideCastleFR:
12002 case BlackASideCastleFR:
12003 fromX = currentMoveString[0] - AAA;
12004 fromY = currentMoveString[1] - ONE;
12005 toX = currentMoveString[2] - AAA;
12006 toY = currentMoveString[3] - ONE;
12007 promoChar = currentMoveString[4];
12011 fromX = next == WhiteDrop ?
12012 (int) CharToPiece(ToUpper(currentMoveString[0])) :
12013 (int) CharToPiece(ToLower(currentMoveString[0]));
12015 toX = currentMoveString[2] - AAA;
12016 toY = currentMoveString[3] - ONE;
12020 // Move encountered; peform it. We need to shuttle between two boards, as even/odd index determines side to move
12022 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[scratch]);
12023 if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
12024 if(appData.ignoreColors && PositionMatches(boards[scratch], reverseBoard)) return plyNr;
12025 if(appData.findMirror) {
12026 if(PositionMatches(boards[scratch], flipBoard)) return plyNr;
12027 if(appData.ignoreColors && PositionMatches(boards[scratch], rotateBoard)) return plyNr;
12032 /* Load the nth game from open file f */
12034 LoadGame (FILE *f, int gameNumber, char *title, int useList)
12038 int gn = gameNumber;
12039 ListGame *lg = NULL;
12040 int numPGNTags = 0;
12042 GameMode oldGameMode;
12043 VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
12045 if (appData.debugMode)
12046 fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
12048 if (gameMode == Training )
12049 SetTrainingModeOff();
12051 oldGameMode = gameMode;
12052 if (gameMode != BeginningOfGame) {
12053 Reset(FALSE, TRUE);
12057 if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
12058 fclose(lastLoadGameFP);
12062 lg = (ListGame *) ListElem(&gameList, gameNumber-1);
12065 fseek(f, lg->offset, 0);
12066 GameListHighlight(gameNumber);
12067 pos = lg->position;
12071 if(oldGameMode == AnalyzeFile && appData.loadGameIndex == -1)
12072 appData.loadGameIndex = 0; // [HGM] suppress error message if we reach file end after auto-stepping analysis
12074 DisplayError(_("Game number out of range"), 0);
12079 if (fseek(f, 0, 0) == -1) {
12080 if (f == lastLoadGameFP ?
12081 gameNumber == lastLoadGameNumber + 1 :
12085 DisplayError(_("Can't seek on game file"), 0);
12090 lastLoadGameFP = f;
12091 lastLoadGameNumber = gameNumber;
12092 safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
12093 lastLoadGameUseList = useList;
12097 if (lg && lg->gameInfo.white && lg->gameInfo.black) {
12098 snprintf(buf, sizeof(buf), "%s %s %s", lg->gameInfo.white, _("vs."),
12099 lg->gameInfo.black);
12101 } else if (*title != NULLCHAR) {
12102 if (gameNumber > 1) {
12103 snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
12106 DisplayTitle(title);
12110 if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
12111 gameMode = PlayFromGameFile;
12115 currentMove = forwardMostMove = backwardMostMove = 0;
12116 CopyBoard(boards[0], initialPosition);
12120 * Skip the first gn-1 games in the file.
12121 * Also skip over anything that precedes an identifiable
12122 * start of game marker, to avoid being confused by
12123 * garbage at the start of the file. Currently
12124 * recognized start of game markers are the move number "1",
12125 * the pattern "gnuchess .* game", the pattern
12126 * "^[#;%] [^ ]* game file", and a PGN tag block.
12127 * A game that starts with one of the latter two patterns
12128 * will also have a move number 1, possibly
12129 * following a position diagram.
12130 * 5-4-02: Let's try being more lenient and allowing a game to
12131 * start with an unnumbered move. Does that break anything?
12133 cm = lastLoadGameStart = EndOfFile;
12135 yyboardindex = forwardMostMove;
12136 cm = (ChessMove) Myylex();
12139 if (cmailMsgLoaded) {
12140 nCmailGames = CMAIL_MAX_GAMES - gn;
12143 DisplayError(_("Game not found in file"), 0);
12150 lastLoadGameStart = cm;
12153 case MoveNumberOne:
12154 switch (lastLoadGameStart) {
12159 case MoveNumberOne:
12161 gn--; /* count this game */
12162 lastLoadGameStart = cm;
12171 switch (lastLoadGameStart) {
12174 case MoveNumberOne:
12176 gn--; /* count this game */
12177 lastLoadGameStart = cm;
12180 lastLoadGameStart = cm; /* game counted already */
12188 yyboardindex = forwardMostMove;
12189 cm = (ChessMove) Myylex();
12190 } while (cm == PGNTag || cm == Comment);
12197 if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
12198 if ( cmailResult[CMAIL_MAX_GAMES - gn - 1]
12199 != CMAIL_OLD_RESULT) {
12201 cmailResult[ CMAIL_MAX_GAMES
12202 - gn - 1] = CMAIL_OLD_RESULT;
12208 /* Only a NormalMove can be at the start of a game
12209 * without a position diagram. */
12210 if (lastLoadGameStart == EndOfFile ) {
12212 lastLoadGameStart = MoveNumberOne;
12221 if (appData.debugMode)
12222 fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
12224 if (cm == XBoardGame) {
12225 /* Skip any header junk before position diagram and/or move 1 */
12227 yyboardindex = forwardMostMove;
12228 cm = (ChessMove) Myylex();
12230 if (cm == EndOfFile ||
12231 cm == GNUChessGame || cm == XBoardGame) {
12232 /* Empty game; pretend end-of-file and handle later */
12237 if (cm == MoveNumberOne || cm == PositionDiagram ||
12238 cm == PGNTag || cm == Comment)
12241 } else if (cm == GNUChessGame) {
12242 if (gameInfo.event != NULL) {
12243 free(gameInfo.event);
12245 gameInfo.event = StrSave(yy_text);
12248 startedFromSetupPosition = FALSE;
12249 while (cm == PGNTag) {
12250 if (appData.debugMode)
12251 fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
12252 err = ParsePGNTag(yy_text, &gameInfo);
12253 if (!err) numPGNTags++;
12255 /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
12256 if(gameInfo.variant != oldVariant) {
12257 startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
12258 ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
12259 InitPosition(TRUE);
12260 oldVariant = gameInfo.variant;
12261 if (appData.debugMode)
12262 fprintf(debugFP, "New variant %d\n", (int) oldVariant);
12266 if (gameInfo.fen != NULL) {
12267 Board initial_position;
12268 startedFromSetupPosition = TRUE;
12269 if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
12271 DisplayError(_("Bad FEN position in file"), 0);
12274 CopyBoard(boards[0], initial_position);
12275 if (blackPlaysFirst) {
12276 currentMove = forwardMostMove = backwardMostMove = 1;
12277 CopyBoard(boards[1], initial_position);
12278 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12279 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12280 timeRemaining[0][1] = whiteTimeRemaining;
12281 timeRemaining[1][1] = blackTimeRemaining;
12282 if (commentList[0] != NULL) {
12283 commentList[1] = commentList[0];
12284 commentList[0] = NULL;
12287 currentMove = forwardMostMove = backwardMostMove = 0;
12289 /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
12291 initialRulePlies = FENrulePlies;
12292 for( i=0; i< nrCastlingRights; i++ )
12293 initialRights[i] = initial_position[CASTLING][i];
12295 yyboardindex = forwardMostMove;
12296 free(gameInfo.fen);
12297 gameInfo.fen = NULL;
12300 yyboardindex = forwardMostMove;
12301 cm = (ChessMove) Myylex();
12303 /* Handle comments interspersed among the tags */
12304 while (cm == Comment) {
12306 if (appData.debugMode)
12307 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
12309 AppendComment(currentMove, p, FALSE);
12310 yyboardindex = forwardMostMove;
12311 cm = (ChessMove) Myylex();
12315 /* don't rely on existence of Event tag since if game was
12316 * pasted from clipboard the Event tag may not exist
12318 if (numPGNTags > 0){
12320 if (gameInfo.variant == VariantNormal) {
12321 VariantClass v = StringToVariant(gameInfo.event);
12322 // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
12323 if(v < VariantShogi) gameInfo.variant = v;
12326 if( appData.autoDisplayTags ) {
12327 tags = PGNTags(&gameInfo);
12328 TagsPopUp(tags, CmailMsg());
12333 /* Make something up, but don't display it now */
12338 if (cm == PositionDiagram) {
12341 Board initial_position;
12343 if (appData.debugMode)
12344 fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
12346 if (!startedFromSetupPosition) {
12348 for (i = BOARD_HEIGHT - 1; i >= 0; i--)
12349 for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
12360 initial_position[i][j++] = CharToPiece(*p);
12363 while (*p == ' ' || *p == '\t' ||
12364 *p == '\n' || *p == '\r') p++;
12366 if (strncmp(p, "black", strlen("black"))==0)
12367 blackPlaysFirst = TRUE;
12369 blackPlaysFirst = FALSE;
12370 startedFromSetupPosition = TRUE;
12372 CopyBoard(boards[0], initial_position);
12373 if (blackPlaysFirst) {
12374 currentMove = forwardMostMove = backwardMostMove = 1;
12375 CopyBoard(boards[1], initial_position);
12376 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12377 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12378 timeRemaining[0][1] = whiteTimeRemaining;
12379 timeRemaining[1][1] = blackTimeRemaining;
12380 if (commentList[0] != NULL) {
12381 commentList[1] = commentList[0];
12382 commentList[0] = NULL;
12385 currentMove = forwardMostMove = backwardMostMove = 0;
12388 yyboardindex = forwardMostMove;
12389 cm = (ChessMove) Myylex();
12392 if(!creatingBook) {
12393 if (first.pr == NoProc) {
12394 StartChessProgram(&first);
12396 InitChessProgram(&first, FALSE);
12397 SendToProgram("force\n", &first);
12398 if (startedFromSetupPosition) {
12399 SendBoard(&first, forwardMostMove);
12400 if (appData.debugMode) {
12401 fprintf(debugFP, "Load Game\n");
12403 DisplayBothClocks();
12407 /* [HGM] server: flag to write setup moves in broadcast file as one */
12408 loadFlag = appData.suppressLoadMoves;
12410 while (cm == Comment) {
12412 if (appData.debugMode)
12413 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
12415 AppendComment(currentMove, p, FALSE);
12416 yyboardindex = forwardMostMove;
12417 cm = (ChessMove) Myylex();
12420 if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
12421 cm == WhiteWins || cm == BlackWins ||
12422 cm == GameIsDrawn || cm == GameUnfinished) {
12423 DisplayMessage("", _("No moves in game"));
12424 if (cmailMsgLoaded) {
12425 if (appData.debugMode)
12426 fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
12430 DrawPosition(FALSE, boards[currentMove]);
12431 DisplayBothClocks();
12432 gameMode = EditGame;
12439 // [HGM] PV info: routine tests if comment empty
12440 if (!matchMode && (pausing || appData.timeDelay != 0)) {
12441 DisplayComment(currentMove - 1, commentList[currentMove]);
12443 if (!matchMode && appData.timeDelay != 0)
12444 DrawPosition(FALSE, boards[currentMove]);
12446 if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
12447 programStats.ok_to_send = 1;
12450 /* if the first token after the PGN tags is a move
12451 * and not move number 1, retrieve it from the parser
12453 if (cm != MoveNumberOne)
12454 LoadGameOneMove(cm);
12456 /* load the remaining moves from the file */
12457 while (LoadGameOneMove(EndOfFile)) {
12458 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12459 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12462 /* rewind to the start of the game */
12463 currentMove = backwardMostMove;
12465 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12467 if (oldGameMode == AnalyzeFile) {
12468 appData.loadGameIndex = -1; // [HGM] order auto-stepping through games
12469 AnalyzeFileEvent();
12471 if (oldGameMode == AnalyzeMode) {
12472 AnalyzeFileEvent();
12475 if(creatingBook) return TRUE;
12476 if (!matchMode && pos > 0) {
12477 ToNrEvent(pos); // [HGM] no autoplay if selected on position
12479 if (matchMode || appData.timeDelay == 0) {
12481 } else if (appData.timeDelay > 0) {
12482 AutoPlayGameLoop();
12485 if (appData.debugMode)
12486 fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
12488 loadFlag = 0; /* [HGM] true game starts */
12492 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
12494 ReloadPosition (int offset)
12496 int positionNumber = lastLoadPositionNumber + offset;
12497 if (lastLoadPositionFP == NULL) {
12498 DisplayError(_("No position has been loaded yet"), 0);
12501 if (positionNumber <= 0) {
12502 DisplayError(_("Can't back up any further"), 0);
12505 return LoadPosition(lastLoadPositionFP, positionNumber,
12506 lastLoadPositionTitle);
12509 /* Load the nth position from the given file */
12511 LoadPositionFromFile (char *filename, int n, char *title)
12516 if (strcmp(filename, "-") == 0) {
12517 return LoadPosition(stdin, n, "stdin");
12519 f = fopen(filename, "rb");
12521 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12522 DisplayError(buf, errno);
12525 return LoadPosition(f, n, title);
12530 /* Load the nth position from the given open file, and close it */
12532 LoadPosition (FILE *f, int positionNumber, char *title)
12534 char *p, line[MSG_SIZ];
12535 Board initial_position;
12536 int i, j, fenMode, pn;
12538 if (gameMode == Training )
12539 SetTrainingModeOff();
12541 if (gameMode != BeginningOfGame) {
12542 Reset(FALSE, TRUE);
12544 if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
12545 fclose(lastLoadPositionFP);
12547 if (positionNumber == 0) positionNumber = 1;
12548 lastLoadPositionFP = f;
12549 lastLoadPositionNumber = positionNumber;
12550 safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
12551 if (first.pr == NoProc && !appData.noChessProgram) {
12552 StartChessProgram(&first);
12553 InitChessProgram(&first, FALSE);
12555 pn = positionNumber;
12556 if (positionNumber < 0) {
12557 /* Negative position number means to seek to that byte offset */
12558 if (fseek(f, -positionNumber, 0) == -1) {
12559 DisplayError(_("Can't seek on position file"), 0);
12564 if (fseek(f, 0, 0) == -1) {
12565 if (f == lastLoadPositionFP ?
12566 positionNumber == lastLoadPositionNumber + 1 :
12567 positionNumber == 1) {
12570 DisplayError(_("Can't seek on position file"), 0);
12575 /* See if this file is FEN or old-style xboard */
12576 if (fgets(line, MSG_SIZ, f) == NULL) {
12577 DisplayError(_("Position not found in file"), 0);
12580 // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
12581 fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
12584 if (fenMode || line[0] == '#') pn--;
12586 /* skip positions before number pn */
12587 if (fgets(line, MSG_SIZ, f) == NULL) {
12589 DisplayError(_("Position not found in file"), 0);
12592 if (fenMode || line[0] == '#') pn--;
12597 if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
12598 DisplayError(_("Bad FEN position in file"), 0);
12602 (void) fgets(line, MSG_SIZ, f);
12603 (void) fgets(line, MSG_SIZ, f);
12605 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
12606 (void) fgets(line, MSG_SIZ, f);
12607 for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
12610 initial_position[i][j++] = CharToPiece(*p);
12614 blackPlaysFirst = FALSE;
12616 (void) fgets(line, MSG_SIZ, f);
12617 if (strncmp(line, "black", strlen("black"))==0)
12618 blackPlaysFirst = TRUE;
12621 startedFromSetupPosition = TRUE;
12623 CopyBoard(boards[0], initial_position);
12624 if (blackPlaysFirst) {
12625 currentMove = forwardMostMove = backwardMostMove = 1;
12626 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12627 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12628 CopyBoard(boards[1], initial_position);
12629 DisplayMessage("", _("Black to play"));
12631 currentMove = forwardMostMove = backwardMostMove = 0;
12632 DisplayMessage("", _("White to play"));
12634 initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
12635 if(first.pr != NoProc) { // [HGM] in tourney-mode a position can be loaded before the chess engine is installed
12636 SendToProgram("force\n", &first);
12637 SendBoard(&first, forwardMostMove);
12639 if (appData.debugMode) {
12641 for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
12642 for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
12643 fprintf(debugFP, "Load Position\n");
12646 if (positionNumber > 1) {
12647 snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
12648 DisplayTitle(line);
12650 DisplayTitle(title);
12652 gameMode = EditGame;
12655 timeRemaining[0][1] = whiteTimeRemaining;
12656 timeRemaining[1][1] = blackTimeRemaining;
12657 DrawPosition(FALSE, boards[currentMove]);
12664 CopyPlayerNameIntoFileName (char **dest, char *src)
12666 while (*src != NULLCHAR && *src != ',') {
12671 *(*dest)++ = *src++;
12677 DefaultFileName (char *ext)
12679 static char def[MSG_SIZ];
12682 if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
12684 CopyPlayerNameIntoFileName(&p, gameInfo.white);
12686 CopyPlayerNameIntoFileName(&p, gameInfo.black);
12688 safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
12695 /* Save the current game to the given file */
12697 SaveGameToFile (char *filename, int append)
12701 int result, i, t,tot=0;
12703 if (strcmp(filename, "-") == 0) {
12704 return SaveGame(stdout, 0, NULL);
12706 for(i=0; i<10; i++) { // upto 10 tries
12707 f = fopen(filename, append ? "a" : "w");
12708 if(f && i) fprintf(f, "[Delay \"%d retries, %d msec\"]\n",i,tot);
12709 if(f || errno != 13) break;
12710 DoSleep(t = 5 + random()%11); // wait 5-15 msec
12714 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12715 DisplayError(buf, errno);
12718 safeStrCpy(buf, lastMsg, MSG_SIZ);
12719 DisplayMessage(_("Waiting for access to save file"), "");
12720 flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing
12721 DisplayMessage(_("Saving game"), "");
12722 if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError(_("Bad Seek"), errno); // better safe than sorry...
12723 result = SaveGame(f, 0, NULL);
12724 DisplayMessage(buf, "");
12731 SavePart (char *str)
12733 static char buf[MSG_SIZ];
12736 p = strchr(str, ' ');
12737 if (p == NULL) return str;
12738 strncpy(buf, str, p - str);
12739 buf[p - str] = NULLCHAR;
12743 #define PGN_MAX_LINE 75
12745 #define PGN_SIDE_WHITE 0
12746 #define PGN_SIDE_BLACK 1
12749 FindFirstMoveOutOfBook (int side)
12753 if( backwardMostMove == 0 && ! startedFromSetupPosition) {
12754 int index = backwardMostMove;
12755 int has_book_hit = 0;
12757 if( (index % 2) != side ) {
12761 while( index < forwardMostMove ) {
12762 /* Check to see if engine is in book */
12763 int depth = pvInfoList[index].depth;
12764 int score = pvInfoList[index].score;
12770 else if( score == 0 && depth == 63 ) {
12771 in_book = 1; /* Zappa */
12773 else if( score == 2 && depth == 99 ) {
12774 in_book = 1; /* Abrok */
12777 has_book_hit += in_book;
12793 GetOutOfBookInfo (char * buf)
12797 int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12799 oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
12800 oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
12804 if( oob[0] >= 0 || oob[1] >= 0 ) {
12805 for( i=0; i<2; i++ ) {
12809 if( i > 0 && oob[0] >= 0 ) {
12810 strcat( buf, " " );
12813 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
12814 sprintf( buf+strlen(buf), "%s%.2f",
12815 pvInfoList[idx].score >= 0 ? "+" : "",
12816 pvInfoList[idx].score / 100.0 );
12822 /* Save game in PGN style and close the file */
12824 SaveGamePGN (FILE *f)
12826 int i, offset, linelen, newblock;
12829 int movelen, numlen, blank;
12830 char move_buffer[100]; /* [AS] Buffer for move+PV info */
12832 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12834 PrintPGNTags(f, &gameInfo);
12836 if(appData.numberTag && matchMode) fprintf(f, "[Number \"%d\"]\n", nextGame+1); // [HGM] number tag
12838 if (backwardMostMove > 0 || startedFromSetupPosition) {
12839 char *fen = PositionToFEN(backwardMostMove, NULL);
12840 fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
12841 fprintf(f, "\n{--------------\n");
12842 PrintPosition(f, backwardMostMove);
12843 fprintf(f, "--------------}\n");
12847 /* [AS] Out of book annotation */
12848 if( appData.saveOutOfBookInfo ) {
12851 GetOutOfBookInfo( buf );
12853 if( buf[0] != '\0' ) {
12854 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
12861 i = backwardMostMove;
12865 while (i < forwardMostMove) {
12866 /* Print comments preceding this move */
12867 if (commentList[i] != NULL) {
12868 if (linelen > 0) fprintf(f, "\n");
12869 fprintf(f, "%s", commentList[i]);
12874 /* Format move number */
12876 snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
12879 snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
12881 numtext[0] = NULLCHAR;
12883 numlen = strlen(numtext);
12886 /* Print move number */
12887 blank = linelen > 0 && numlen > 0;
12888 if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
12897 fprintf(f, "%s", numtext);
12901 safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
12902 movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
12905 blank = linelen > 0 && movelen > 0;
12906 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
12915 fprintf(f, "%s", move_buffer);
12916 linelen += movelen;
12918 /* [AS] Add PV info if present */
12919 if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
12920 /* [HGM] add time */
12921 char buf[MSG_SIZ]; int seconds;
12923 seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
12929 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
12932 seconds = (seconds + 4)/10; // round to full seconds
12934 snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
12936 snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
12939 snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
12940 pvInfoList[i].score >= 0 ? "+" : "",
12941 pvInfoList[i].score / 100.0,
12942 pvInfoList[i].depth,
12945 movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
12947 /* Print score/depth */
12948 blank = linelen > 0 && movelen > 0;
12949 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
12958 fprintf(f, "%s", move_buffer);
12959 linelen += movelen;
12965 /* Start a new line */
12966 if (linelen > 0) fprintf(f, "\n");
12968 /* Print comments after last move */
12969 if (commentList[i] != NULL) {
12970 fprintf(f, "%s\n", commentList[i]);
12974 if (gameInfo.resultDetails != NULL &&
12975 gameInfo.resultDetails[0] != NULLCHAR) {
12976 fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
12977 PGNResult(gameInfo.result));
12979 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
12983 lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
12987 /* Save game in old style and close the file */
12989 SaveGameOldStyle (FILE *f)
12994 tm = time((time_t *) NULL);
12996 fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
12999 if (backwardMostMove > 0 || startedFromSetupPosition) {
13000 fprintf(f, "\n[--------------\n");
13001 PrintPosition(f, backwardMostMove);
13002 fprintf(f, "--------------]\n");
13007 i = backwardMostMove;
13008 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13010 while (i < forwardMostMove) {
13011 if (commentList[i] != NULL) {
13012 fprintf(f, "[%s]\n", commentList[i]);
13015 if ((i % 2) == 1) {
13016 fprintf(f, "%d. ... %s\n", (i - offset)/2 + 1, parseList[i]);
13019 fprintf(f, "%d. %s ", (i - offset)/2 + 1, parseList[i]);
13021 if (commentList[i] != NULL) {
13025 if (i >= forwardMostMove) {
13029 fprintf(f, "%s\n", parseList[i]);
13034 if (commentList[i] != NULL) {
13035 fprintf(f, "[%s]\n", commentList[i]);
13038 /* This isn't really the old style, but it's close enough */
13039 if (gameInfo.resultDetails != NULL &&
13040 gameInfo.resultDetails[0] != NULLCHAR) {
13041 fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
13042 gameInfo.resultDetails);
13044 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
13051 /* Save the current game to open file f and close the file */
13053 SaveGame (FILE *f, int dummy, char *dummy2)
13055 if (gameMode == EditPosition) EditPositionDone(TRUE);
13056 lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
13057 if (appData.oldSaveStyle)
13058 return SaveGameOldStyle(f);
13060 return SaveGamePGN(f);
13063 /* Save the current position to the given file */
13065 SavePositionToFile (char *filename)
13070 if (strcmp(filename, "-") == 0) {
13071 return SavePosition(stdout, 0, NULL);
13073 f = fopen(filename, "a");
13075 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13076 DisplayError(buf, errno);
13079 safeStrCpy(buf, lastMsg, MSG_SIZ);
13080 DisplayMessage(_("Waiting for access to save file"), "");
13081 flock(fileno(f), LOCK_EX); // [HGM] lock
13082 DisplayMessage(_("Saving position"), "");
13083 lseek(fileno(f), 0, SEEK_END); // better safe than sorry...
13084 SavePosition(f, 0, NULL);
13085 DisplayMessage(buf, "");
13091 /* Save the current position to the given open file and close the file */
13093 SavePosition (FILE *f, int dummy, char *dummy2)
13098 if (gameMode == EditPosition) EditPositionDone(TRUE);
13099 if (appData.oldSaveStyle) {
13100 tm = time((time_t *) NULL);
13102 fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
13104 fprintf(f, "[--------------\n");
13105 PrintPosition(f, currentMove);
13106 fprintf(f, "--------------]\n");
13108 fen = PositionToFEN(currentMove, NULL);
13109 fprintf(f, "%s\n", fen);
13117 ReloadCmailMsgEvent (int unregister)
13120 static char *inFilename = NULL;
13121 static char *outFilename;
13123 struct stat inbuf, outbuf;
13126 /* Any registered moves are unregistered if unregister is set, */
13127 /* i.e. invoked by the signal handler */
13129 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
13130 cmailMoveRegistered[i] = FALSE;
13131 if (cmailCommentList[i] != NULL) {
13132 free(cmailCommentList[i]);
13133 cmailCommentList[i] = NULL;
13136 nCmailMovesRegistered = 0;
13139 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
13140 cmailResult[i] = CMAIL_NOT_RESULT;
13144 if (inFilename == NULL) {
13145 /* Because the filenames are static they only get malloced once */
13146 /* and they never get freed */
13147 inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
13148 sprintf(inFilename, "%s.game.in", appData.cmailGameName);
13150 outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
13151 sprintf(outFilename, "%s.out", appData.cmailGameName);
13154 status = stat(outFilename, &outbuf);
13156 cmailMailedMove = FALSE;
13158 status = stat(inFilename, &inbuf);
13159 cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
13162 /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
13163 counts the games, notes how each one terminated, etc.
13165 It would be nice to remove this kludge and instead gather all
13166 the information while building the game list. (And to keep it
13167 in the game list nodes instead of having a bunch of fixed-size
13168 parallel arrays.) Note this will require getting each game's
13169 termination from the PGN tags, as the game list builder does
13170 not process the game moves. --mann
13172 cmailMsgLoaded = TRUE;
13173 LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
13175 /* Load first game in the file or popup game menu */
13176 LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
13178 #endif /* !WIN32 */
13186 char string[MSG_SIZ];
13188 if ( cmailMailedMove
13189 || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
13190 return TRUE; /* Allow free viewing */
13193 /* Unregister move to ensure that we don't leave RegisterMove */
13194 /* with the move registered when the conditions for registering no */
13196 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
13197 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
13198 nCmailMovesRegistered --;
13200 if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
13202 free(cmailCommentList[lastLoadGameNumber - 1]);
13203 cmailCommentList[lastLoadGameNumber - 1] = NULL;
13207 if (cmailOldMove == -1) {
13208 DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
13212 if (currentMove > cmailOldMove + 1) {
13213 DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
13217 if (currentMove < cmailOldMove) {
13218 DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
13222 if (forwardMostMove > currentMove) {
13223 /* Silently truncate extra moves */
13227 if ( (currentMove == cmailOldMove + 1)
13228 || ( (currentMove == cmailOldMove)
13229 && ( (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
13230 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
13231 if (gameInfo.result != GameUnfinished) {
13232 cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
13235 if (commentList[currentMove] != NULL) {
13236 cmailCommentList[lastLoadGameNumber - 1]
13237 = StrSave(commentList[currentMove]);
13239 safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
13241 if (appData.debugMode)
13242 fprintf(debugFP, "Saving %s for game %d\n",
13243 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
13245 snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
13247 f = fopen(string, "w");
13248 if (appData.oldSaveStyle) {
13249 SaveGameOldStyle(f); /* also closes the file */
13251 snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
13252 f = fopen(string, "w");
13253 SavePosition(f, 0, NULL); /* also closes the file */
13255 fprintf(f, "{--------------\n");
13256 PrintPosition(f, currentMove);
13257 fprintf(f, "--------------}\n\n");
13259 SaveGame(f, 0, NULL); /* also closes the file*/
13262 cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
13263 nCmailMovesRegistered ++;
13264 } else if (nCmailGames == 1) {
13265 DisplayError(_("You have not made a move yet"), 0);
13276 static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
13277 FILE *commandOutput;
13278 char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
13279 int nBytes = 0; /* Suppress warnings on uninitialized variables */
13285 if (! cmailMsgLoaded) {
13286 DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
13290 if (nCmailGames == nCmailResults) {
13291 DisplayError(_("No unfinished games"), 0);
13295 #if CMAIL_PROHIBIT_REMAIL
13296 if (cmailMailedMove) {
13297 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);
13298 DisplayError(msg, 0);
13303 if (! (cmailMailedMove || RegisterMove())) return;
13305 if ( cmailMailedMove
13306 || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
13307 snprintf(string, MSG_SIZ, partCommandString,
13308 appData.debugMode ? " -v" : "", appData.cmailGameName);
13309 commandOutput = popen(string, "r");
13311 if (commandOutput == NULL) {
13312 DisplayError(_("Failed to invoke cmail"), 0);
13314 for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
13315 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
13317 if (nBuffers > 1) {
13318 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
13319 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
13320 nBytes = MSG_SIZ - 1;
13322 (void) memcpy(msg, buffer, nBytes);
13324 *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
13326 if(StrStr(msg, "Mailed cmail message to ") != NULL) {
13327 cmailMailedMove = TRUE; /* Prevent >1 moves */
13330 for (i = 0; i < nCmailGames; i ++) {
13331 if (cmailResult[i] == CMAIL_NOT_RESULT) {
13336 && ( (arcDir = (char *) getenv("CMAIL_ARCDIR"))
13338 snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
13340 appData.cmailGameName,
13342 LoadGameFromFile(buffer, 1, buffer, FALSE);
13343 cmailMsgLoaded = FALSE;
13347 DisplayInformation(msg);
13348 pclose(commandOutput);
13351 if ((*cmailMsg) != '\0') {
13352 DisplayInformation(cmailMsg);
13357 #endif /* !WIN32 */
13366 int prependComma = 0;
13368 char string[MSG_SIZ]; /* Space for game-list */
13371 if (!cmailMsgLoaded) return "";
13373 if (cmailMailedMove) {
13374 snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
13376 /* Create a list of games left */
13377 snprintf(string, MSG_SIZ, "[");
13378 for (i = 0; i < nCmailGames; i ++) {
13379 if (! ( cmailMoveRegistered[i]
13380 || (cmailResult[i] == CMAIL_OLD_RESULT))) {
13381 if (prependComma) {
13382 snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
13384 snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
13388 strcat(string, number);
13391 strcat(string, "]");
13393 if (nCmailMovesRegistered + nCmailResults == 0) {
13394 switch (nCmailGames) {
13396 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
13400 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
13404 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
13409 switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
13411 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
13416 if (nCmailResults == nCmailGames) {
13417 snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
13419 snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
13424 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
13436 if (gameMode == Training)
13437 SetTrainingModeOff();
13440 cmailMsgLoaded = FALSE;
13441 if (appData.icsActive) {
13442 SendToICS(ics_prefix);
13443 SendToICS("refresh\n");
13448 ExitEvent (int status)
13452 /* Give up on clean exit */
13456 /* Keep trying for clean exit */
13460 if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
13462 if (telnetISR != NULL) {
13463 RemoveInputSource(telnetISR);
13465 if (icsPR != NoProc) {
13466 DestroyChildProcess(icsPR, TRUE);
13469 /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
13470 GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
13472 /* [HGM] crash: the above GameEnds() is a dud if another one was running */
13473 /* make sure this other one finishes before killing it! */
13474 if(endingGame) { int count = 0;
13475 if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
13476 while(endingGame && count++ < 10) DoSleep(1);
13477 if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
13480 /* Kill off chess programs */
13481 if (first.pr != NoProc) {
13484 DoSleep( appData.delayBeforeQuit );
13485 SendToProgram("quit\n", &first);
13486 DoSleep( appData.delayAfterQuit );
13487 DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
13489 if (second.pr != NoProc) {
13490 DoSleep( appData.delayBeforeQuit );
13491 SendToProgram("quit\n", &second);
13492 DoSleep( appData.delayAfterQuit );
13493 DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
13495 if (first.isr != NULL) {
13496 RemoveInputSource(first.isr);
13498 if (second.isr != NULL) {
13499 RemoveInputSource(second.isr);
13502 if (pairing.pr != NoProc) SendToProgram("quit\n", &pairing);
13503 if (pairing.isr != NULL) RemoveInputSource(pairing.isr);
13505 ShutDownFrontEnd();
13510 PauseEngine (ChessProgramState *cps)
13512 SendToProgram("pause\n", cps);
13517 UnPauseEngine (ChessProgramState *cps)
13519 SendToProgram("resume\n", cps);
13526 if (appData.debugMode)
13527 fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
13531 if(stalledEngine) { // [HGM] pause: resume game by releasing withheld move
13533 if(gameMode == TwoMachinesPlay) { // we might have to make the opponent resume pondering
13534 if(stalledEngine->other->pause == 2) UnPauseEngine(stalledEngine->other);
13535 else if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine->other);
13537 if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine);
13538 HandleMachineMove(stashedInputMove, stalledEngine);
13539 stalledEngine = NULL;
13542 if (gameMode == MachinePlaysWhite ||
13543 gameMode == TwoMachinesPlay ||
13544 gameMode == MachinePlaysBlack) { // the thinking engine must have used pause mode, or it would have been stalledEngine
13545 if(first.pause) UnPauseEngine(&first);
13546 else if(appData.ponderNextMove) SendToProgram("hard\n", &first);
13547 if(second.pause) UnPauseEngine(&second);
13548 else if(gameMode == TwoMachinesPlay && appData.ponderNextMove) SendToProgram("hard\n", &second);
13551 DisplayBothClocks();
13553 if (gameMode == PlayFromGameFile) {
13554 if (appData.timeDelay >= 0)
13555 AutoPlayGameLoop();
13556 } else if (gameMode == IcsExamining && pauseExamInvalid) {
13557 Reset(FALSE, TRUE);
13558 SendToICS(ics_prefix);
13559 SendToICS("refresh\n");
13560 } else if (currentMove < forwardMostMove && gameMode != AnalyzeMode) {
13561 ForwardInner(forwardMostMove);
13563 pauseExamInvalid = FALSE;
13565 switch (gameMode) {
13569 pauseExamForwardMostMove = forwardMostMove;
13570 pauseExamInvalid = FALSE;
13573 case IcsPlayingWhite:
13574 case IcsPlayingBlack:
13578 case PlayFromGameFile:
13579 (void) StopLoadGameTimer();
13583 case BeginningOfGame:
13584 if (appData.icsActive) return;
13585 /* else fall through */
13586 case MachinePlaysWhite:
13587 case MachinePlaysBlack:
13588 case TwoMachinesPlay:
13589 if (forwardMostMove == 0)
13590 return; /* don't pause if no one has moved */
13591 if(gameMode == TwoMachinesPlay) { // [HGM] pause: stop clocks if engine can be paused immediately
13592 ChessProgramState *onMove = (WhiteOnMove(forwardMostMove) == (first.twoMachinesColor[0] == 'w') ? &first : &second);
13593 if(onMove->pause) { // thinking engine can be paused
13594 PauseEngine(onMove); // do it
13595 if(onMove->other->pause) // pondering opponent can always be paused immediately
13596 PauseEngine(onMove->other);
13598 SendToProgram("easy\n", onMove->other);
13600 } else if(appData.ponderNextMove) SendToProgram("easy\n", onMove); // pre-emptively bring out of ponder
13601 } else if(gameMode == (WhiteOnMove(forwardMostMove) ? MachinePlaysWhite : MachinePlaysBlack)) { // engine on move
13603 PauseEngine(&first);
13605 } else if(appData.ponderNextMove) SendToProgram("easy\n", &first); // pre-emptively bring out of ponder
13606 } else { // human on move, pause pondering by either method
13608 PauseEngine(&first);
13609 else if(appData.ponderNextMove)
13610 SendToProgram("easy\n", &first);
13613 // if no immediate pausing is possible, wait for engine to move, and stop clocks then
13623 EditCommentEvent ()
13625 char title[MSG_SIZ];
13627 if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
13628 safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
13630 snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
13631 WhiteOnMove(currentMove - 1) ? " " : ".. ",
13632 parseList[currentMove - 1]);
13635 EditCommentPopUp(currentMove, title, commentList[currentMove]);
13642 char *tags = PGNTags(&gameInfo);
13644 EditTagsPopUp(tags, NULL);
13651 if(second.analyzing) {
13652 SendToProgram("exit\n", &second);
13653 second.analyzing = FALSE;
13655 if (second.pr == NoProc) StartChessProgram(&second);
13656 InitChessProgram(&second, FALSE);
13657 FeedMovesToProgram(&second, currentMove);
13659 SendToProgram("analyze\n", &second);
13660 second.analyzing = TRUE;
13664 /* Toggle ShowThinking */
13666 ToggleShowThinking()
13668 appData.showThinking = !appData.showThinking;
13669 ShowThinkingEvent();
13673 AnalyzeModeEvent ()
13677 if (!first.analysisSupport) {
13678 snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
13679 DisplayError(buf, 0);
13682 /* [DM] icsEngineAnalyze [HGM] This is horrible code; reverse the gameMode and isEngineAnalyze tests! */
13683 if (appData.icsActive) {
13684 if (gameMode != IcsObserving) {
13685 snprintf(buf, MSG_SIZ, _("You are not observing a game"));
13686 DisplayError(buf, 0);
13688 if (appData.icsEngineAnalyze) {
13689 if (appData.debugMode)
13690 fprintf(debugFP, "Found unexpected active ICS engine analyze \n");
13696 /* if enable, user wants to disable icsEngineAnalyze */
13697 if (appData.icsEngineAnalyze) {
13702 appData.icsEngineAnalyze = TRUE;
13703 if (appData.debugMode)
13704 fprintf(debugFP, "ICS engine analyze starting... \n");
13707 if (gameMode == AnalyzeMode) { ToggleSecond(); return 0; }
13708 if (appData.noChessProgram || gameMode == AnalyzeMode)
13711 if (gameMode != AnalyzeFile) {
13712 if (!appData.icsEngineAnalyze) {
13714 if (gameMode != EditGame) return 0;
13716 if (!appData.showThinking) ToggleShowThinking();
13717 ResurrectChessProgram();
13718 SendToProgram("analyze\n", &first);
13719 first.analyzing = TRUE;
13720 /*first.maybeThinking = TRUE;*/
13721 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13722 EngineOutputPopUp();
13724 if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
13729 StartAnalysisClock();
13730 GetTimeMark(&lastNodeCountTime);
13736 AnalyzeFileEvent ()
13738 if (appData.noChessProgram || gameMode == AnalyzeFile)
13741 if (!first.analysisSupport) {
13743 snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
13744 DisplayError(buf, 0);
13748 if (gameMode != AnalyzeMode) {
13749 keepInfo = 1; // mere annotating should not alter PGN tags
13752 if (gameMode != EditGame) return;
13753 if (!appData.showThinking) ToggleShowThinking();
13754 ResurrectChessProgram();
13755 SendToProgram("analyze\n", &first);
13756 first.analyzing = TRUE;
13757 /*first.maybeThinking = TRUE;*/
13758 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13759 EngineOutputPopUp();
13761 gameMode = AnalyzeFile;
13765 StartAnalysisClock();
13766 GetTimeMark(&lastNodeCountTime);
13768 if(appData.timeDelay > 0) StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
13769 AnalysisPeriodicEvent(1);
13773 MachineWhiteEvent ()
13776 char *bookHit = NULL;
13778 if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
13782 if (gameMode == PlayFromGameFile ||
13783 gameMode == TwoMachinesPlay ||
13784 gameMode == Training ||
13785 gameMode == AnalyzeMode ||
13786 gameMode == EndOfGame)
13789 if (gameMode == EditPosition)
13790 EditPositionDone(TRUE);
13792 if (!WhiteOnMove(currentMove)) {
13793 DisplayError(_("It is not White's turn"), 0);
13797 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
13800 if (gameMode == EditGame || gameMode == AnalyzeMode ||
13801 gameMode == AnalyzeFile)
13804 ResurrectChessProgram(); /* in case it isn't running */
13805 if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
13806 gameMode = MachinePlaysWhite;
13809 gameMode = MachinePlaysWhite;
13813 snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
13815 if (first.sendName) {
13816 snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
13817 SendToProgram(buf, &first);
13819 if (first.sendTime) {
13820 if (first.useColors) {
13821 SendToProgram("black\n", &first); /*gnu kludge*/
13823 SendTimeRemaining(&first, TRUE);
13825 if (first.useColors) {
13826 SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
13828 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
13829 SetMachineThinkingEnables();
13830 first.maybeThinking = TRUE;
13834 if (appData.autoFlipView && !flipView) {
13835 flipView = !flipView;
13836 DrawPosition(FALSE, NULL);
13837 DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;
13840 if(bookHit) { // [HGM] book: simulate book reply
13841 static char bookMove[MSG_SIZ]; // a bit generous?
13843 programStats.nodes = programStats.depth = programStats.time =
13844 programStats.score = programStats.got_only_move = 0;
13845 sprintf(programStats.movelist, "%s (xbook)", bookHit);
13847 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13848 strcat(bookMove, bookHit);
13849 HandleMachineMove(bookMove, &first);
13854 MachineBlackEvent ()
13857 char *bookHit = NULL;
13859 if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
13863 if (gameMode == PlayFromGameFile ||
13864 gameMode == TwoMachinesPlay ||
13865 gameMode == Training ||
13866 gameMode == AnalyzeMode ||
13867 gameMode == EndOfGame)
13870 if (gameMode == EditPosition)
13871 EditPositionDone(TRUE);
13873 if (WhiteOnMove(currentMove)) {
13874 DisplayError(_("It is not Black's turn"), 0);
13878 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
13881 if (gameMode == EditGame || gameMode == AnalyzeMode ||
13882 gameMode == AnalyzeFile)
13885 ResurrectChessProgram(); /* in case it isn't running */
13886 gameMode = MachinePlaysBlack;
13890 snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
13892 if (first.sendName) {
13893 snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
13894 SendToProgram(buf, &first);
13896 if (first.sendTime) {
13897 if (first.useColors) {
13898 SendToProgram("white\n", &first); /*gnu kludge*/
13900 SendTimeRemaining(&first, FALSE);
13902 if (first.useColors) {
13903 SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
13905 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
13906 SetMachineThinkingEnables();
13907 first.maybeThinking = TRUE;
13910 if (appData.autoFlipView && flipView) {
13911 flipView = !flipView;
13912 DrawPosition(FALSE, NULL);
13913 DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;
13915 if(bookHit) { // [HGM] book: simulate book reply
13916 static char bookMove[MSG_SIZ]; // a bit generous?
13918 programStats.nodes = programStats.depth = programStats.time =
13919 programStats.score = programStats.got_only_move = 0;
13920 sprintf(programStats.movelist, "%s (xbook)", bookHit);
13922 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13923 strcat(bookMove, bookHit);
13924 HandleMachineMove(bookMove, &first);
13930 DisplayTwoMachinesTitle ()
13933 if (appData.matchGames > 0) {
13934 if(appData.tourneyFile[0]) {
13935 snprintf(buf, MSG_SIZ, "%s %s %s (%d/%d%s)",
13936 gameInfo.white, _("vs."), gameInfo.black,
13937 nextGame+1, appData.matchGames+1,
13938 appData.tourneyType>0 ? "gt" : appData.tourneyType<0 ? "sw" : "rr");
13940 if (first.twoMachinesColor[0] == 'w') {
13941 snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
13942 gameInfo.white, _("vs."), gameInfo.black,
13943 first.matchWins, second.matchWins,
13944 matchGame - 1 - (first.matchWins + second.matchWins));
13946 snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
13947 gameInfo.white, _("vs."), gameInfo.black,
13948 second.matchWins, first.matchWins,
13949 matchGame - 1 - (first.matchWins + second.matchWins));
13952 snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
13958 SettingsMenuIfReady ()
13960 if (second.lastPing != second.lastPong) {
13961 DisplayMessage("", _("Waiting for second chess program"));
13962 ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
13966 DisplayMessage("", "");
13967 SettingsPopUp(&second);
13971 WaitForEngine (ChessProgramState *cps, DelayedEventCallback retry)
13974 if (cps->pr == NoProc) {
13975 StartChessProgram(cps);
13976 if (cps->protocolVersion == 1) {
13978 ScheduleDelayedEvent(retry, 1); // Do this also through timeout to avoid recursive calling of 'retry'
13980 /* kludge: allow timeout for initial "feature" command */
13981 if(retry != TwoMachinesEventIfReady) FreezeUI();
13982 snprintf(buf, MSG_SIZ, _("Starting %s chess program"), _(cps->which));
13983 DisplayMessage("", buf);
13984 ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
13992 TwoMachinesEvent P((void))
13996 ChessProgramState *onmove;
13997 char *bookHit = NULL;
13998 static int stalling = 0;
14002 if (appData.noChessProgram) return;
14004 switch (gameMode) {
14005 case TwoMachinesPlay:
14007 case MachinePlaysWhite:
14008 case MachinePlaysBlack:
14009 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
14010 DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
14014 case BeginningOfGame:
14015 case PlayFromGameFile:
14018 if (gameMode != EditGame) return;
14021 EditPositionDone(TRUE);
14032 // forwardMostMove = currentMove;
14033 TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
14034 startingEngine = TRUE;
14036 if(!ResurrectChessProgram()) return; /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
14038 if(!first.initDone && GetDelayedEvent() == TwoMachinesEventIfReady) return; // [HGM] engine #1 still waiting for feature timeout
14039 if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
14040 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
14043 if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
14045 if(second.protocolVersion >= 2 && !strstr(second.variants, VariantName(gameInfo.variant))) {
14046 startingEngine = FALSE;
14047 DisplayError("second engine does not play this", 0);
14052 InitChessProgram(&second, FALSE); // unbalances ping of second engine
14053 SendToProgram("force\n", &second);
14055 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
14058 GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
14059 if(appData.matchPause>10000 || appData.matchPause<10)
14060 appData.matchPause = 10000; /* [HGM] make pause adjustable */
14061 wait = SubtractTimeMarks(&now, &pauseStart);
14062 if(wait < appData.matchPause) {
14063 ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
14066 // we are now committed to starting the game
14068 DisplayMessage("", "");
14069 if (startedFromSetupPosition) {
14070 SendBoard(&second, backwardMostMove);
14071 if (appData.debugMode) {
14072 fprintf(debugFP, "Two Machines\n");
14075 for (i = backwardMostMove; i < forwardMostMove; i++) {
14076 SendMoveToProgram(i, &second);
14079 gameMode = TwoMachinesPlay;
14080 pausing = startingEngine = FALSE;
14081 ModeHighlight(); // [HGM] logo: this triggers display update of logos
14083 DisplayTwoMachinesTitle();
14085 if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
14090 if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
14091 SendToProgram(first.computerString, &first);
14092 if (first.sendName) {
14093 snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
14094 SendToProgram(buf, &first);
14096 SendToProgram(second.computerString, &second);
14097 if (second.sendName) {
14098 snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
14099 SendToProgram(buf, &second);
14103 if (!first.sendTime || !second.sendTime) {
14104 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
14105 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
14107 if (onmove->sendTime) {
14108 if (onmove->useColors) {
14109 SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
14111 SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
14113 if (onmove->useColors) {
14114 SendToProgram(onmove->twoMachinesColor, onmove);
14116 bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
14117 // SendToProgram("go\n", onmove);
14118 onmove->maybeThinking = TRUE;
14119 SetMachineThinkingEnables();
14123 if(bookHit) { // [HGM] book: simulate book reply
14124 static char bookMove[MSG_SIZ]; // a bit generous?
14126 programStats.nodes = programStats.depth = programStats.time =
14127 programStats.score = programStats.got_only_move = 0;
14128 sprintf(programStats.movelist, "%s (xbook)", bookHit);
14130 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14131 strcat(bookMove, bookHit);
14132 savedMessage = bookMove; // args for deferred call
14133 savedState = onmove;
14134 ScheduleDelayedEvent(DeferredBookMove, 1);
14141 if (gameMode == Training) {
14142 SetTrainingModeOff();
14143 gameMode = PlayFromGameFile;
14144 DisplayMessage("", _("Training mode off"));
14146 gameMode = Training;
14147 animateTraining = appData.animate;
14149 /* make sure we are not already at the end of the game */
14150 if (currentMove < forwardMostMove) {
14151 SetTrainingModeOn();
14152 DisplayMessage("", _("Training mode on"));
14154 gameMode = PlayFromGameFile;
14155 DisplayError(_("Already at end of game"), 0);
14164 if (!appData.icsActive) return;
14165 switch (gameMode) {
14166 case IcsPlayingWhite:
14167 case IcsPlayingBlack:
14170 case BeginningOfGame:
14178 EditPositionDone(TRUE);
14191 gameMode = IcsIdle;
14201 switch (gameMode) {
14203 SetTrainingModeOff();
14205 case MachinePlaysWhite:
14206 case MachinePlaysBlack:
14207 case BeginningOfGame:
14208 SendToProgram("force\n", &first);
14209 SetUserThinkingEnables();
14211 case PlayFromGameFile:
14212 (void) StopLoadGameTimer();
14213 if (gameFileFP != NULL) {
14218 EditPositionDone(TRUE);
14223 SendToProgram("force\n", &first);
14225 case TwoMachinesPlay:
14226 GameEnds(EndOfFile, NULL, GE_PLAYER);
14227 ResurrectChessProgram();
14228 SetUserThinkingEnables();
14231 ResurrectChessProgram();
14233 case IcsPlayingBlack:
14234 case IcsPlayingWhite:
14235 DisplayError(_("Warning: You are still playing a game"), 0);
14238 DisplayError(_("Warning: You are still observing a game"), 0);
14241 DisplayError(_("Warning: You are still examining a game"), 0);
14252 first.offeredDraw = second.offeredDraw = 0;
14254 if (gameMode == PlayFromGameFile) {
14255 whiteTimeRemaining = timeRemaining[0][currentMove];
14256 blackTimeRemaining = timeRemaining[1][currentMove];
14260 if (gameMode == MachinePlaysWhite ||
14261 gameMode == MachinePlaysBlack ||
14262 gameMode == TwoMachinesPlay ||
14263 gameMode == EndOfGame) {
14264 i = forwardMostMove;
14265 while (i > currentMove) {
14266 SendToProgram("undo\n", &first);
14269 if(!adjustedClock) {
14270 whiteTimeRemaining = timeRemaining[0][currentMove];
14271 blackTimeRemaining = timeRemaining[1][currentMove];
14272 DisplayBothClocks();
14274 if (whiteFlag || blackFlag) {
14275 whiteFlag = blackFlag = 0;
14280 gameMode = EditGame;
14287 EditPositionEvent ()
14289 if (gameMode == EditPosition) {
14295 if (gameMode != EditGame) return;
14297 gameMode = EditPosition;
14300 if (currentMove > 0)
14301 CopyBoard(boards[0], boards[currentMove]);
14303 blackPlaysFirst = !WhiteOnMove(currentMove);
14305 currentMove = forwardMostMove = backwardMostMove = 0;
14306 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
14308 if(!appData.pieceMenu) DisplayMessage(_("Click clock to clear board"), "");
14314 /* [DM] icsEngineAnalyze - possible call from other functions */
14315 if (appData.icsEngineAnalyze) {
14316 appData.icsEngineAnalyze = FALSE;
14318 DisplayMessage("",_("Close ICS engine analyze..."));
14320 if (first.analysisSupport && first.analyzing) {
14321 SendToBoth("exit\n");
14322 first.analyzing = second.analyzing = FALSE;
14324 thinkOutput[0] = NULLCHAR;
14328 EditPositionDone (Boolean fakeRights)
14330 int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
14332 startedFromSetupPosition = TRUE;
14333 InitChessProgram(&first, FALSE);
14334 if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
14335 boards[0][EP_STATUS] = EP_NONE;
14336 boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
14337 if(boards[0][0][BOARD_WIDTH>>1] == king) {
14338 boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? BOARD_LEFT : NoRights;
14339 boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
14340 } else boards[0][CASTLING][2] = NoRights;
14341 if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
14342 boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? BOARD_LEFT : NoRights;
14343 boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
14344 } else boards[0][CASTLING][5] = NoRights;
14345 if(gameInfo.variant == VariantSChess) {
14347 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // pieces in their original position are assumed virgin
14348 boards[0][VIRGIN][i] = 0;
14349 if(boards[0][0][i] == FIDEArray[0][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_W;
14350 if(boards[0][BOARD_HEIGHT-1][i] == FIDEArray[1][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_B;
14354 SendToProgram("force\n", &first);
14355 if (blackPlaysFirst) {
14356 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
14357 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
14358 currentMove = forwardMostMove = backwardMostMove = 1;
14359 CopyBoard(boards[1], boards[0]);
14361 currentMove = forwardMostMove = backwardMostMove = 0;
14363 SendBoard(&first, forwardMostMove);
14364 if (appData.debugMode) {
14365 fprintf(debugFP, "EditPosDone\n");
14368 DisplayMessage("", "");
14369 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
14370 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
14371 gameMode = EditGame;
14373 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
14374 ClearHighlights(); /* [AS] */
14377 /* Pause for `ms' milliseconds */
14378 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
14380 TimeDelay (long ms)
14387 } while (SubtractTimeMarks(&m2, &m1) < ms);
14390 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
14392 SendMultiLineToICS (char *buf)
14394 char temp[MSG_SIZ+1], *p;
14401 strncpy(temp, buf, len);
14406 if (*p == '\n' || *p == '\r')
14411 strcat(temp, "\n");
14413 SendToPlayer(temp, strlen(temp));
14417 SetWhiteToPlayEvent ()
14419 if (gameMode == EditPosition) {
14420 blackPlaysFirst = FALSE;
14421 DisplayBothClocks(); /* works because currentMove is 0 */
14422 } else if (gameMode == IcsExamining) {
14423 SendToICS(ics_prefix);
14424 SendToICS("tomove white\n");
14429 SetBlackToPlayEvent ()
14431 if (gameMode == EditPosition) {
14432 blackPlaysFirst = TRUE;
14433 currentMove = 1; /* kludge */
14434 DisplayBothClocks();
14436 } else if (gameMode == IcsExamining) {
14437 SendToICS(ics_prefix);
14438 SendToICS("tomove black\n");
14443 EditPositionMenuEvent (ChessSquare selection, int x, int y)
14446 ChessSquare piece = boards[0][y][x];
14448 if (gameMode != EditPosition && gameMode != IcsExamining) return;
14450 switch (selection) {
14452 if (gameMode == IcsExamining && ics_type == ICS_FICS) {
14453 SendToICS(ics_prefix);
14454 SendToICS("bsetup clear\n");
14455 } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
14456 SendToICS(ics_prefix);
14457 SendToICS("clearboard\n");
14459 for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
14460 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
14461 for (y = 0; y < BOARD_HEIGHT; y++) {
14462 if (gameMode == IcsExamining) {
14463 if (boards[currentMove][y][x] != EmptySquare) {
14464 snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
14469 boards[0][y][x] = p;
14474 if (gameMode == EditPosition) {
14475 DrawPosition(FALSE, boards[0]);
14480 SetWhiteToPlayEvent();
14484 SetBlackToPlayEvent();
14488 if (gameMode == IcsExamining) {
14489 if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
14490 snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
14493 if(x < BOARD_LEFT || x >= BOARD_RGHT) {
14494 if(x == BOARD_LEFT-2) {
14495 if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
14496 boards[0][y][1] = 0;
14498 if(x == BOARD_RGHT+1) {
14499 if(y >= gameInfo.holdingsSize) break;
14500 boards[0][y][BOARD_WIDTH-2] = 0;
14503 boards[0][y][x] = EmptySquare;
14504 DrawPosition(FALSE, boards[0]);
14509 if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
14510 piece >= (int)BlackPawn && piece < (int)BlackMan ) {
14511 selection = (ChessSquare) (PROMOTED piece);
14512 } else if(piece == EmptySquare) selection = WhiteSilver;
14513 else selection = (ChessSquare)((int)piece - 1);
14517 if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
14518 piece > (int)BlackMan && piece <= (int)BlackKing ) {
14519 selection = (ChessSquare) (DEMOTED piece);
14520 } else if(piece == EmptySquare) selection = BlackSilver;
14521 else selection = (ChessSquare)((int)piece + 1);
14526 if(gameInfo.variant == VariantShatranj ||
14527 gameInfo.variant == VariantXiangqi ||
14528 gameInfo.variant == VariantCourier ||
14529 gameInfo.variant == VariantASEAN ||
14530 gameInfo.variant == VariantMakruk )
14531 selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
14536 if(gameInfo.variant == VariantXiangqi)
14537 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
14538 if(gameInfo.variant == VariantKnightmate)
14539 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
14542 if (gameMode == IcsExamining) {
14543 if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
14544 snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
14545 PieceToChar(selection), AAA + x, ONE + y);
14548 if(x < BOARD_LEFT || x >= BOARD_RGHT) {
14550 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
14551 n = PieceToNumber(selection - BlackPawn);
14552 if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
14553 boards[0][BOARD_HEIGHT-1-n][0] = selection;
14554 boards[0][BOARD_HEIGHT-1-n][1]++;
14556 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
14557 n = PieceToNumber(selection);
14558 if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
14559 boards[0][n][BOARD_WIDTH-1] = selection;
14560 boards[0][n][BOARD_WIDTH-2]++;
14563 boards[0][y][x] = selection;
14564 DrawPosition(TRUE, boards[0]);
14566 fromX = fromY = -1;
14574 DropMenuEvent (ChessSquare selection, int x, int y)
14576 ChessMove moveType;
14578 switch (gameMode) {
14579 case IcsPlayingWhite:
14580 case MachinePlaysBlack:
14581 if (!WhiteOnMove(currentMove)) {
14582 DisplayMoveError(_("It is Black's turn"));
14585 moveType = WhiteDrop;
14587 case IcsPlayingBlack:
14588 case MachinePlaysWhite:
14589 if (WhiteOnMove(currentMove)) {
14590 DisplayMoveError(_("It is White's turn"));
14593 moveType = BlackDrop;
14596 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
14602 if (moveType == BlackDrop && selection < BlackPawn) {
14603 selection = (ChessSquare) ((int) selection
14604 + (int) BlackPawn - (int) WhitePawn);
14606 if (boards[currentMove][y][x] != EmptySquare) {
14607 DisplayMoveError(_("That square is occupied"));
14611 FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
14617 /* Accept a pending offer of any kind from opponent */
14619 if (appData.icsActive) {
14620 SendToICS(ics_prefix);
14621 SendToICS("accept\n");
14622 } else if (cmailMsgLoaded) {
14623 if (currentMove == cmailOldMove &&
14624 commentList[cmailOldMove] != NULL &&
14625 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14626 "Black offers a draw" : "White offers a draw")) {
14628 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
14629 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
14631 DisplayError(_("There is no pending offer on this move"), 0);
14632 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
14635 /* Not used for offers from chess program */
14642 /* Decline a pending offer of any kind from opponent */
14644 if (appData.icsActive) {
14645 SendToICS(ics_prefix);
14646 SendToICS("decline\n");
14647 } else if (cmailMsgLoaded) {
14648 if (currentMove == cmailOldMove &&
14649 commentList[cmailOldMove] != NULL &&
14650 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14651 "Black offers a draw" : "White offers a draw")) {
14653 AppendComment(cmailOldMove, "Draw declined", TRUE);
14654 DisplayComment(cmailOldMove - 1, "Draw declined");
14657 DisplayError(_("There is no pending offer on this move"), 0);
14660 /* Not used for offers from chess program */
14667 /* Issue ICS rematch command */
14668 if (appData.icsActive) {
14669 SendToICS(ics_prefix);
14670 SendToICS("rematch\n");
14677 /* Call your opponent's flag (claim a win on time) */
14678 if (appData.icsActive) {
14679 SendToICS(ics_prefix);
14680 SendToICS("flag\n");
14682 switch (gameMode) {
14685 case MachinePlaysWhite:
14688 GameEnds(GameIsDrawn, "Both players ran out of time",
14691 GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
14693 DisplayError(_("Your opponent is not out of time"), 0);
14696 case MachinePlaysBlack:
14699 GameEnds(GameIsDrawn, "Both players ran out of time",
14702 GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
14704 DisplayError(_("Your opponent is not out of time"), 0);
14712 ClockClick (int which)
14713 { // [HGM] code moved to back-end from winboard.c
14714 if(which) { // black clock
14715 if (gameMode == EditPosition || gameMode == IcsExamining) {
14716 if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
14717 SetBlackToPlayEvent();
14718 } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !blackFlag && WhiteOnMove(currentMove)) {
14719 UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move: if not out of time, enters null move
14720 } else if (shiftKey) {
14721 AdjustClock(which, -1);
14722 } else if (gameMode == IcsPlayingWhite ||
14723 gameMode == MachinePlaysBlack) {
14726 } else { // white clock
14727 if (gameMode == EditPosition || gameMode == IcsExamining) {
14728 if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
14729 SetWhiteToPlayEvent();
14730 } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !whiteFlag && !WhiteOnMove(currentMove)) {
14731 UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move
14732 } else if (shiftKey) {
14733 AdjustClock(which, -1);
14734 } else if (gameMode == IcsPlayingBlack ||
14735 gameMode == MachinePlaysWhite) {
14744 /* Offer draw or accept pending draw offer from opponent */
14746 if (appData.icsActive) {
14747 /* Note: tournament rules require draw offers to be
14748 made after you make your move but before you punch
14749 your clock. Currently ICS doesn't let you do that;
14750 instead, you immediately punch your clock after making
14751 a move, but you can offer a draw at any time. */
14753 SendToICS(ics_prefix);
14754 SendToICS("draw\n");
14755 userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
14756 } else if (cmailMsgLoaded) {
14757 if (currentMove == cmailOldMove &&
14758 commentList[cmailOldMove] != NULL &&
14759 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14760 "Black offers a draw" : "White offers a draw")) {
14761 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
14762 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
14763 } else if (currentMove == cmailOldMove + 1) {
14764 char *offer = WhiteOnMove(cmailOldMove) ?
14765 "White offers a draw" : "Black offers a draw";
14766 AppendComment(currentMove, offer, TRUE);
14767 DisplayComment(currentMove - 1, offer);
14768 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
14770 DisplayError(_("You must make your move before offering a draw"), 0);
14771 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
14773 } else if (first.offeredDraw) {
14774 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
14776 if (first.sendDrawOffers) {
14777 SendToProgram("draw\n", &first);
14778 userOfferedDraw = TRUE;
14786 /* Offer Adjourn or accept pending Adjourn offer from opponent */
14788 if (appData.icsActive) {
14789 SendToICS(ics_prefix);
14790 SendToICS("adjourn\n");
14792 /* Currently GNU Chess doesn't offer or accept Adjourns */
14800 /* Offer Abort or accept pending Abort offer from opponent */
14802 if (appData.icsActive) {
14803 SendToICS(ics_prefix);
14804 SendToICS("abort\n");
14806 GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
14813 /* Resign. You can do this even if it's not your turn. */
14815 if (appData.icsActive) {
14816 SendToICS(ics_prefix);
14817 SendToICS("resign\n");
14819 switch (gameMode) {
14820 case MachinePlaysWhite:
14821 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
14823 case MachinePlaysBlack:
14824 GameEnds(BlackWins, "White resigns", GE_PLAYER);
14827 if (cmailMsgLoaded) {
14829 if (WhiteOnMove(cmailOldMove)) {
14830 GameEnds(BlackWins, "White resigns", GE_PLAYER);
14832 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
14834 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
14845 StopObservingEvent ()
14847 /* Stop observing current games */
14848 SendToICS(ics_prefix);
14849 SendToICS("unobserve\n");
14853 StopExaminingEvent ()
14855 /* Stop observing current game */
14856 SendToICS(ics_prefix);
14857 SendToICS("unexamine\n");
14861 ForwardInner (int target)
14863 int limit; int oldSeekGraphUp = seekGraphUp;
14865 if (appData.debugMode)
14866 fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
14867 target, currentMove, forwardMostMove);
14869 if (gameMode == EditPosition)
14872 seekGraphUp = FALSE;
14873 MarkTargetSquares(1);
14875 if (gameMode == PlayFromGameFile && !pausing)
14878 if (gameMode == IcsExamining && pausing)
14879 limit = pauseExamForwardMostMove;
14881 limit = forwardMostMove;
14883 if (target > limit) target = limit;
14885 if (target > 0 && moveList[target - 1][0]) {
14886 int fromX, fromY, toX, toY;
14887 toX = moveList[target - 1][2] - AAA;
14888 toY = moveList[target - 1][3] - ONE;
14889 if (moveList[target - 1][1] == '@') {
14890 if (appData.highlightLastMove) {
14891 SetHighlights(-1, -1, toX, toY);
14894 fromX = moveList[target - 1][0] - AAA;
14895 fromY = moveList[target - 1][1] - ONE;
14896 if (target == currentMove + 1) {
14897 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
14899 if (appData.highlightLastMove) {
14900 SetHighlights(fromX, fromY, toX, toY);
14904 if (gameMode == EditGame || gameMode == AnalyzeMode ||
14905 gameMode == Training || gameMode == PlayFromGameFile ||
14906 gameMode == AnalyzeFile) {
14907 while (currentMove < target) {
14908 if(second.analyzing) SendMoveToProgram(currentMove, &second);
14909 SendMoveToProgram(currentMove++, &first);
14912 currentMove = target;
14915 if (gameMode == EditGame || gameMode == EndOfGame) {
14916 whiteTimeRemaining = timeRemaining[0][currentMove];
14917 blackTimeRemaining = timeRemaining[1][currentMove];
14919 DisplayBothClocks();
14920 DisplayMove(currentMove - 1);
14921 DrawPosition(oldSeekGraphUp, boards[currentMove]);
14922 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
14923 if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
14924 DisplayComment(currentMove - 1, commentList[currentMove]);
14926 ClearMap(); // [HGM] exclude: invalidate map
14933 if (gameMode == IcsExamining && !pausing) {
14934 SendToICS(ics_prefix);
14935 SendToICS("forward\n");
14937 ForwardInner(currentMove + 1);
14944 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14945 /* to optimze, we temporarily turn off analysis mode while we feed
14946 * the remaining moves to the engine. Otherwise we get analysis output
14949 if (first.analysisSupport) {
14950 SendToProgram("exit\nforce\n", &first);
14951 first.analyzing = FALSE;
14955 if (gameMode == IcsExamining && !pausing) {
14956 SendToICS(ics_prefix);
14957 SendToICS("forward 999999\n");
14959 ForwardInner(forwardMostMove);
14962 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14963 /* we have fed all the moves, so reactivate analysis mode */
14964 SendToProgram("analyze\n", &first);
14965 first.analyzing = TRUE;
14966 /*first.maybeThinking = TRUE;*/
14967 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14972 BackwardInner (int target)
14974 int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
14976 if (appData.debugMode)
14977 fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
14978 target, currentMove, forwardMostMove);
14980 if (gameMode == EditPosition) return;
14981 seekGraphUp = FALSE;
14982 MarkTargetSquares(1);
14983 if (currentMove <= backwardMostMove) {
14985 DrawPosition(full_redraw, boards[currentMove]);
14988 if (gameMode == PlayFromGameFile && !pausing)
14991 if (moveList[target][0]) {
14992 int fromX, fromY, toX, toY;
14993 toX = moveList[target][2] - AAA;
14994 toY = moveList[target][3] - ONE;
14995 if (moveList[target][1] == '@') {
14996 if (appData.highlightLastMove) {
14997 SetHighlights(-1, -1, toX, toY);
15000 fromX = moveList[target][0] - AAA;
15001 fromY = moveList[target][1] - ONE;
15002 if (target == currentMove - 1) {
15003 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
15005 if (appData.highlightLastMove) {
15006 SetHighlights(fromX, fromY, toX, toY);
15010 if (gameMode == EditGame || gameMode==AnalyzeMode ||
15011 gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
15012 while (currentMove > target) {
15013 if(moveList[currentMove-1][1] == '@' && moveList[currentMove-1][0] == '@') {
15014 // null move cannot be undone. Reload program with move history before it.
15016 for(i=target; i>backwardMostMove; i--) { // seek back to start or previous null move
15017 if(moveList[i-1][1] == '@' && moveList[i-1][0] == '@') break;
15019 SendBoard(&first, i);
15020 if(second.analyzing) SendBoard(&second, i);
15021 for(currentMove=i; currentMove<target; currentMove++) {
15022 SendMoveToProgram(currentMove, &first);
15023 if(second.analyzing) SendMoveToProgram(currentMove, &second);
15027 SendToBoth("undo\n");
15031 currentMove = target;
15034 if (gameMode == EditGame || gameMode == EndOfGame) {
15035 whiteTimeRemaining = timeRemaining[0][currentMove];
15036 blackTimeRemaining = timeRemaining[1][currentMove];
15038 DisplayBothClocks();
15039 DisplayMove(currentMove - 1);
15040 DrawPosition(full_redraw, boards[currentMove]);
15041 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
15042 // [HGM] PV info: routine tests if comment empty
15043 DisplayComment(currentMove - 1, commentList[currentMove]);
15044 ClearMap(); // [HGM] exclude: invalidate map
15050 if (gameMode == IcsExamining && !pausing) {
15051 SendToICS(ics_prefix);
15052 SendToICS("backward\n");
15054 BackwardInner(currentMove - 1);
15061 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15062 /* to optimize, we temporarily turn off analysis mode while we undo
15063 * all the moves. Otherwise we get analysis output after each undo.
15065 if (first.analysisSupport) {
15066 SendToProgram("exit\nforce\n", &first);
15067 first.analyzing = FALSE;
15071 if (gameMode == IcsExamining && !pausing) {
15072 SendToICS(ics_prefix);
15073 SendToICS("backward 999999\n");
15075 BackwardInner(backwardMostMove);
15078 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15079 /* we have fed all the moves, so reactivate analysis mode */
15080 SendToProgram("analyze\n", &first);
15081 first.analyzing = TRUE;
15082 /*first.maybeThinking = TRUE;*/
15083 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
15090 if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
15091 if (to >= forwardMostMove) to = forwardMostMove;
15092 if (to <= backwardMostMove) to = backwardMostMove;
15093 if (to < currentMove) {
15101 RevertEvent (Boolean annotate)
15103 if(PopTail(annotate)) { // [HGM] vari: restore old game tail
15106 if (gameMode != IcsExamining) {
15107 DisplayError(_("You are not examining a game"), 0);
15111 DisplayError(_("You can't revert while pausing"), 0);
15114 SendToICS(ics_prefix);
15115 SendToICS("revert\n");
15119 RetractMoveEvent ()
15121 switch (gameMode) {
15122 case MachinePlaysWhite:
15123 case MachinePlaysBlack:
15124 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
15125 DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
15128 if (forwardMostMove < 2) return;
15129 currentMove = forwardMostMove = forwardMostMove - 2;
15130 whiteTimeRemaining = timeRemaining[0][currentMove];
15131 blackTimeRemaining = timeRemaining[1][currentMove];
15132 DisplayBothClocks();
15133 DisplayMove(currentMove - 1);
15134 ClearHighlights();/*!! could figure this out*/
15135 DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
15136 SendToProgram("remove\n", &first);
15137 /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
15140 case BeginningOfGame:
15144 case IcsPlayingWhite:
15145 case IcsPlayingBlack:
15146 if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
15147 SendToICS(ics_prefix);
15148 SendToICS("takeback 2\n");
15150 SendToICS(ics_prefix);
15151 SendToICS("takeback 1\n");
15160 ChessProgramState *cps;
15162 switch (gameMode) {
15163 case MachinePlaysWhite:
15164 if (!WhiteOnMove(forwardMostMove)) {
15165 DisplayError(_("It is your turn"), 0);
15170 case MachinePlaysBlack:
15171 if (WhiteOnMove(forwardMostMove)) {
15172 DisplayError(_("It is your turn"), 0);
15177 case TwoMachinesPlay:
15178 if (WhiteOnMove(forwardMostMove) ==
15179 (first.twoMachinesColor[0] == 'w')) {
15185 case BeginningOfGame:
15189 SendToProgram("?\n", cps);
15193 TruncateGameEvent ()
15196 if (gameMode != EditGame) return;
15203 CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
15204 if (forwardMostMove > currentMove) {
15205 if (gameInfo.resultDetails != NULL) {
15206 free(gameInfo.resultDetails);
15207 gameInfo.resultDetails = NULL;
15208 gameInfo.result = GameUnfinished;
15210 forwardMostMove = currentMove;
15211 HistorySet(parseList, backwardMostMove, forwardMostMove,
15219 if (appData.noChessProgram) return;
15220 switch (gameMode) {
15221 case MachinePlaysWhite:
15222 if (WhiteOnMove(forwardMostMove)) {
15223 DisplayError(_("Wait until your turn"), 0);
15227 case BeginningOfGame:
15228 case MachinePlaysBlack:
15229 if (!WhiteOnMove(forwardMostMove)) {
15230 DisplayError(_("Wait until your turn"), 0);
15235 DisplayError(_("No hint available"), 0);
15238 SendToProgram("hint\n", &first);
15239 hintRequested = TRUE;
15245 ListGame * lg = (ListGame *) gameList.head;
15248 static int secondTime = FALSE;
15250 if( !(f = GameFile()) || ((ListGame *) gameList.tailPred)->number <= 0 ) {
15251 DisplayError(_("Game list not loaded or empty"), 0);
15255 if(!secondTime && (f = fopen(appData.polyglotBook, "r"))) {
15258 DisplayNote(_("Book file exists! Try again for overwrite."));
15262 creatingBook = TRUE;
15263 secondTime = FALSE;
15265 /* Get list size */
15266 for (nItem = 1; nItem <= ((ListGame *) gameList.tailPred)->number; nItem++){
15267 LoadGame(f, nItem, "", TRUE);
15268 AddGameToBook(TRUE);
15269 lg = (ListGame *) lg->node.succ;
15272 creatingBook = FALSE;
15279 if (appData.noChessProgram) return;
15280 switch (gameMode) {
15281 case MachinePlaysWhite:
15282 if (WhiteOnMove(forwardMostMove)) {
15283 DisplayError(_("Wait until your turn"), 0);
15287 case BeginningOfGame:
15288 case MachinePlaysBlack:
15289 if (!WhiteOnMove(forwardMostMove)) {
15290 DisplayError(_("Wait until your turn"), 0);
15295 EditPositionDone(TRUE);
15297 case TwoMachinesPlay:
15302 SendToProgram("bk\n", &first);
15303 bookOutput[0] = NULLCHAR;
15304 bookRequested = TRUE;
15310 char *tags = PGNTags(&gameInfo);
15311 TagsPopUp(tags, CmailMsg());
15315 /* end button procedures */
15318 PrintPosition (FILE *fp, int move)
15322 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
15323 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
15324 char c = PieceToChar(boards[move][i][j]);
15325 fputc(c == 'x' ? '.' : c, fp);
15326 fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
15329 if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
15330 fprintf(fp, "white to play\n");
15332 fprintf(fp, "black to play\n");
15336 PrintOpponents (FILE *fp)
15338 if (gameInfo.white != NULL) {
15339 fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
15345 /* Find last component of program's own name, using some heuristics */
15347 TidyProgramName (char *prog, char *host, char buf[MSG_SIZ])
15350 int local = (strcmp(host, "localhost") == 0);
15351 while (!local && (p = strchr(prog, ';')) != NULL) {
15353 while (*p == ' ') p++;
15356 if (*prog == '"' || *prog == '\'') {
15357 q = strchr(prog + 1, *prog);
15359 q = strchr(prog, ' ');
15361 if (q == NULL) q = prog + strlen(prog);
15363 while (p >= prog && *p != '/' && *p != '\\') p--;
15365 if(p == prog && *p == '"') p++;
15367 if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) *q = c, q -= 4; else *q = c;
15368 memcpy(buf, p, q - p);
15369 buf[q - p] = NULLCHAR;
15377 TimeControlTagValue ()
15380 if (!appData.clockMode) {
15381 safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
15382 } else if (movesPerSession > 0) {
15383 snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
15384 } else if (timeIncrement == 0) {
15385 snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
15387 snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
15389 return StrSave(buf);
15395 /* This routine is used only for certain modes */
15396 VariantClass v = gameInfo.variant;
15397 ChessMove r = GameUnfinished;
15400 if(keepInfo) return;
15402 if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
15403 r = gameInfo.result;
15404 p = gameInfo.resultDetails;
15405 gameInfo.resultDetails = NULL;
15407 ClearGameInfo(&gameInfo);
15408 gameInfo.variant = v;
15410 switch (gameMode) {
15411 case MachinePlaysWhite:
15412 gameInfo.event = StrSave( appData.pgnEventHeader );
15413 gameInfo.site = StrSave(HostName());
15414 gameInfo.date = PGNDate();
15415 gameInfo.round = StrSave("-");
15416 gameInfo.white = StrSave(first.tidy);
15417 gameInfo.black = StrSave(UserName());
15418 gameInfo.timeControl = TimeControlTagValue();
15421 case MachinePlaysBlack:
15422 gameInfo.event = StrSave( appData.pgnEventHeader );
15423 gameInfo.site = StrSave(HostName());
15424 gameInfo.date = PGNDate();
15425 gameInfo.round = StrSave("-");
15426 gameInfo.white = StrSave(UserName());
15427 gameInfo.black = StrSave(first.tidy);
15428 gameInfo.timeControl = TimeControlTagValue();
15431 case TwoMachinesPlay:
15432 gameInfo.event = StrSave( appData.pgnEventHeader );
15433 gameInfo.site = StrSave(HostName());
15434 gameInfo.date = PGNDate();
15437 snprintf(buf, MSG_SIZ, "%d", roundNr);
15438 gameInfo.round = StrSave(buf);
15440 gameInfo.round = StrSave("-");
15442 if (first.twoMachinesColor[0] == 'w') {
15443 gameInfo.white = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
15444 gameInfo.black = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
15446 gameInfo.white = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
15447 gameInfo.black = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
15449 gameInfo.timeControl = TimeControlTagValue();
15453 gameInfo.event = StrSave("Edited game");
15454 gameInfo.site = StrSave(HostName());
15455 gameInfo.date = PGNDate();
15456 gameInfo.round = StrSave("-");
15457 gameInfo.white = StrSave("-");
15458 gameInfo.black = StrSave("-");
15459 gameInfo.result = r;
15460 gameInfo.resultDetails = p;
15464 gameInfo.event = StrSave("Edited position");
15465 gameInfo.site = StrSave(HostName());
15466 gameInfo.date = PGNDate();
15467 gameInfo.round = StrSave("-");
15468 gameInfo.white = StrSave("-");
15469 gameInfo.black = StrSave("-");
15472 case IcsPlayingWhite:
15473 case IcsPlayingBlack:
15478 case PlayFromGameFile:
15479 gameInfo.event = StrSave("Game from non-PGN file");
15480 gameInfo.site = StrSave(HostName());
15481 gameInfo.date = PGNDate();
15482 gameInfo.round = StrSave("-");
15483 gameInfo.white = StrSave("?");
15484 gameInfo.black = StrSave("?");
15493 ReplaceComment (int index, char *text)
15499 if(index && sscanf(text, "%f/%d", &score, &len) == 2 &&
15500 pvInfoList[index-1].depth == len &&
15501 fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
15502 (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
15503 while (*text == '\n') text++;
15504 len = strlen(text);
15505 while (len > 0 && text[len - 1] == '\n') len--;
15507 if (commentList[index] != NULL)
15508 free(commentList[index]);
15511 commentList[index] = NULL;
15514 if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
15515 *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
15516 *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
15517 commentList[index] = (char *) malloc(len + 2);
15518 strncpy(commentList[index], text, len);
15519 commentList[index][len] = '\n';
15520 commentList[index][len + 1] = NULLCHAR;
15522 // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
15524 commentList[index] = (char *) malloc(len + 7);
15525 safeStrCpy(commentList[index], "{\n", 3);
15526 safeStrCpy(commentList[index]+2, text, len+1);
15527 commentList[index][len+2] = NULLCHAR;
15528 while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
15529 strcat(commentList[index], "\n}\n");
15534 CrushCRs (char *text)
15542 if (ch == '\r') continue;
15544 } while (ch != '\0');
15548 AppendComment (int index, char *text, Boolean addBraces)
15549 /* addBraces tells if we should add {} */
15554 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces);
15555 if(addBraces == 3) addBraces = 0; else // force appending literally
15556 text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
15559 while (*text == '\n') text++;
15560 len = strlen(text);
15561 while (len > 0 && text[len - 1] == '\n') len--;
15562 text[len] = NULLCHAR;
15564 if (len == 0) return;
15566 if (commentList[index] != NULL) {
15567 Boolean addClosingBrace = addBraces;
15568 old = commentList[index];
15569 oldlen = strlen(old);
15570 while(commentList[index][oldlen-1] == '\n')
15571 commentList[index][--oldlen] = NULLCHAR;
15572 commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
15573 safeStrCpy(commentList[index], old, oldlen + len + 6);
15575 // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
15576 if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
15577 if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
15578 while (*text == '\n') { text++; len--; }
15579 commentList[index][--oldlen] = NULLCHAR;
15581 if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
15582 else strcat(commentList[index], "\n");
15583 strcat(commentList[index], text);
15584 if(addClosingBrace) strcat(commentList[index], addClosingBrace == 2 ? ")\n" : "\n}\n");
15585 else strcat(commentList[index], "\n");
15587 commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
15589 safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
15590 else commentList[index][0] = NULLCHAR;
15591 strcat(commentList[index], text);
15592 strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
15593 if(addBraces == TRUE) strcat(commentList[index], "}\n");
15598 FindStr (char * text, char * sub_text)
15600 char * result = strstr( text, sub_text );
15602 if( result != NULL ) {
15603 result += strlen( sub_text );
15609 /* [AS] Try to extract PV info from PGN comment */
15610 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
15612 GetInfoFromComment (int index, char * text)
15614 char * sep = text, *p;
15616 if( text != NULL && index > 0 ) {
15619 int time = -1, sec = 0, deci;
15620 char * s_eval = FindStr( text, "[%eval " );
15621 char * s_emt = FindStr( text, "[%emt " );
15623 if( s_eval != NULL || s_emt != NULL ) {
15625 if(0) { // [HGM] this code is not finished, and could actually be detrimental
15630 if( s_eval != NULL ) {
15631 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
15635 if( delim != ']' ) {
15640 if( s_emt != NULL ) {
15645 /* We expect something like: [+|-]nnn.nn/dd */
15648 if(*text != '{') return text; // [HGM] braces: must be normal comment
15650 sep = strchr( text, '/' );
15651 if( sep == NULL || sep < (text+4) ) {
15656 if(!strncmp(p+1, "final score ", 12)) p += 12, index++; else
15657 if(p[1] == '(') { // comment starts with PV
15658 p = strchr(p, ')'); // locate end of PV
15659 if(p == NULL || sep < p+5) return text;
15660 // at this point we have something like "{(.*) +0.23/6 ..."
15661 p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
15662 *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
15663 // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
15665 time = -1; sec = -1; deci = -1;
15666 if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
15667 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
15668 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
15669 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3 ) {
15673 if( score_lo < 0 || score_lo >= 100 ) {
15677 if(sec >= 0) time = 600*time + 10*sec; else
15678 if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
15680 score = score > 0 || !score & p[1] != '-' ? score*100 + score_lo : score*100 - score_lo;
15682 /* [HGM] PV time: now locate end of PV info */
15683 while( *++sep >= '0' && *sep <= '9'); // strip depth
15685 while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
15687 while( *++sep >= '0' && *sep <= '9'); // strip seconds
15689 while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
15690 while(*sep == ' ' || *sep == '\n' || *sep == '\r') sep++;
15701 pvInfoList[index-1].depth = depth;
15702 pvInfoList[index-1].score = score;
15703 pvInfoList[index-1].time = 10*time; // centi-sec
15704 if(*sep == '}') *sep = 0; else *--sep = '{';
15705 if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
15711 SendToProgram (char *message, ChessProgramState *cps)
15713 int count, outCount, error;
15716 if (cps->pr == NoProc) return;
15719 if (appData.debugMode) {
15722 fprintf(debugFP, "%ld >%-6s: %s",
15723 SubtractTimeMarks(&now, &programStartTime),
15724 cps->which, message);
15726 fprintf(serverFP, "%ld >%-6s: %s",
15727 SubtractTimeMarks(&now, &programStartTime),
15728 cps->which, message), fflush(serverFP);
15731 count = strlen(message);
15732 outCount = OutputToProcess(cps->pr, message, count, &error);
15733 if (outCount < count && !exiting
15734 && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
15735 if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
15736 snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
15737 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
15738 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
15739 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
15740 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
15741 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
15743 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
15744 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
15745 gameInfo.result = res;
15747 gameInfo.resultDetails = StrSave(buf);
15749 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
15750 if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
15755 ReceiveFromProgram (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
15759 ChessProgramState *cps = (ChessProgramState *)closure;
15761 if (isr != cps->isr) return; /* Killed intentionally */
15764 RemoveInputSource(cps->isr);
15765 snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
15766 _(cps->which), cps->program);
15767 if(LoadError(cps->userError ? NULL : buf, cps)) return; // [HGM] should not generate fatal error during engine load
15768 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
15769 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
15770 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
15771 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
15772 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
15774 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
15775 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
15776 gameInfo.result = res;
15778 gameInfo.resultDetails = StrSave(buf);
15780 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
15781 if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
15783 snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
15784 _(cps->which), cps->program);
15785 RemoveInputSource(cps->isr);
15787 /* [AS] Program is misbehaving badly... kill it */
15788 if( count == -2 ) {
15789 DestroyChildProcess( cps->pr, 9 );
15793 if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
15798 if ((end_str = strchr(message, '\r')) != NULL)
15799 *end_str = NULLCHAR;
15800 if ((end_str = strchr(message, '\n')) != NULL)
15801 *end_str = NULLCHAR;
15803 if (appData.debugMode) {
15804 TimeMark now; int print = 1;
15805 char *quote = ""; char c; int i;
15807 if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
15808 char start = message[0];
15809 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
15810 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
15811 sscanf(message, "move %c", &c)!=1 && sscanf(message, "offer%c", &c)!=1 &&
15812 sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
15813 sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
15814 sscanf(message, "tell%c", &c)!=1 && sscanf(message, "0-1 %c", &c)!=1 &&
15815 sscanf(message, "1-0 %c", &c)!=1 && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
15816 sscanf(message, "setboard %c", &c)!=1 && sscanf(message, "setup %c", &c)!=1 &&
15817 sscanf(message, "hint: %c", &c)!=1 &&
15818 sscanf(message, "pong %c", &c)!=1 && start != '#') {
15819 quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
15820 print = (appData.engineComments >= 2);
15822 message[0] = start; // restore original message
15826 fprintf(debugFP, "%ld <%-6s: %s%s\n",
15827 SubtractTimeMarks(&now, &programStartTime), cps->which,
15831 fprintf(serverFP, "%ld <%-6s: %s%s\n",
15832 SubtractTimeMarks(&now, &programStartTime), cps->which,
15834 message), fflush(serverFP);
15838 /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
15839 if (appData.icsEngineAnalyze) {
15840 if (strstr(message, "whisper") != NULL ||
15841 strstr(message, "kibitz") != NULL ||
15842 strstr(message, "tellics") != NULL) return;
15845 HandleMachineMove(message, cps);
15850 SendTimeControl (ChessProgramState *cps, int mps, long tc, int inc, int sd, int st)
15855 if( timeControl_2 > 0 ) {
15856 if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
15857 tc = timeControl_2;
15860 tc /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
15861 inc /= cps->timeOdds;
15862 st /= cps->timeOdds;
15864 seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
15867 /* Set exact time per move, normally using st command */
15868 if (cps->stKludge) {
15869 /* GNU Chess 4 has no st command; uses level in a nonstandard way */
15871 if (seconds == 0) {
15872 snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
15874 snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
15877 snprintf(buf, MSG_SIZ, "st %d\n", st);
15880 /* Set conventional or incremental time control, using level command */
15881 if (seconds == 0) {
15882 /* Note old gnuchess bug -- minutes:seconds used to not work.
15883 Fixed in later versions, but still avoid :seconds
15884 when seconds is 0. */
15885 snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
15887 snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
15888 seconds, inc/1000.);
15891 SendToProgram(buf, cps);
15893 /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
15894 /* Orthogonally, limit search to given depth */
15896 if (cps->sdKludge) {
15897 snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
15899 snprintf(buf, MSG_SIZ, "sd %d\n", sd);
15901 SendToProgram(buf, cps);
15904 if(cps->nps >= 0) { /* [HGM] nps */
15905 if(cps->supportsNPS == FALSE)
15906 cps->nps = -1; // don't use if engine explicitly says not supported!
15908 snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
15909 SendToProgram(buf, cps);
15914 ChessProgramState *
15916 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
15918 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
15919 gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
15925 SendTimeRemaining (ChessProgramState *cps, int machineWhite)
15927 char message[MSG_SIZ];
15930 /* Note: this routine must be called when the clocks are stopped
15931 or when they have *just* been set or switched; otherwise
15932 it will be off by the time since the current tick started.
15934 if (machineWhite) {
15935 time = whiteTimeRemaining / 10;
15936 otime = blackTimeRemaining / 10;
15938 time = blackTimeRemaining / 10;
15939 otime = whiteTimeRemaining / 10;
15941 /* [HGM] translate opponent's time by time-odds factor */
15942 otime = (otime * cps->other->timeOdds) / cps->timeOdds;
15944 if (time <= 0) time = 1;
15945 if (otime <= 0) otime = 1;
15947 snprintf(message, MSG_SIZ, "time %ld\n", time);
15948 SendToProgram(message, cps);
15950 snprintf(message, MSG_SIZ, "otim %ld\n", otime);
15951 SendToProgram(message, cps);
15955 BoolFeature (char **p, char *name, int *loc, ChessProgramState *cps)
15958 int len = strlen(name);
15961 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
15963 sscanf(*p, "%d", &val);
15965 while (**p && **p != ' ')
15967 snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15968 SendToProgram(buf, cps);
15975 IntFeature (char **p, char *name, int *loc, ChessProgramState *cps)
15978 int len = strlen(name);
15979 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
15981 sscanf(*p, "%d", loc);
15982 while (**p && **p != ' ') (*p)++;
15983 snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15984 SendToProgram(buf, cps);
15991 StringFeature (char **p, char *name, char **loc, ChessProgramState *cps)
15994 int len = strlen(name);
15995 if (strncmp((*p), name, len) == 0
15996 && (*p)[len] == '=' && (*p)[len+1] == '\"') {
15998 ASSIGN(*loc, *p); // kludge alert: assign rest of line just to be sure allocation is large enough so that sscanf below always fits
15999 sscanf(*p, "%[^\"]", *loc);
16000 while (**p && **p != '\"') (*p)++;
16001 if (**p == '\"') (*p)++;
16002 snprintf(buf, MSG_SIZ, "accepted %s\n", name);
16003 SendToProgram(buf, cps);
16010 ParseOption (Option *opt, ChessProgramState *cps)
16011 // [HGM] options: process the string that defines an engine option, and determine
16012 // name, type, default value, and allowed value range
16014 char *p, *q, buf[MSG_SIZ];
16015 int n, min = (-1)<<31, max = 1<<31, def;
16017 if(p = strstr(opt->name, " -spin ")) {
16018 if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
16019 if(max < min) max = min; // enforce consistency
16020 if(def < min) def = min;
16021 if(def > max) def = max;
16026 } else if((p = strstr(opt->name, " -slider "))) {
16027 // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
16028 if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
16029 if(max < min) max = min; // enforce consistency
16030 if(def < min) def = min;
16031 if(def > max) def = max;
16035 opt->type = Spin; // Slider;
16036 } else if((p = strstr(opt->name, " -string "))) {
16037 opt->textValue = p+9;
16038 opt->type = TextBox;
16039 } else if((p = strstr(opt->name, " -file "))) {
16040 // for now -file is a synonym for -string, to already provide compatibility with future polyglots
16041 opt->textValue = p+7;
16042 opt->type = FileName; // FileName;
16043 } else if((p = strstr(opt->name, " -path "))) {
16044 // for now -file is a synonym for -string, to already provide compatibility with future polyglots
16045 opt->textValue = p+7;
16046 opt->type = PathName; // PathName;
16047 } else if(p = strstr(opt->name, " -check ")) {
16048 if(sscanf(p, " -check %d", &def) < 1) return FALSE;
16049 opt->value = (def != 0);
16050 opt->type = CheckBox;
16051 } else if(p = strstr(opt->name, " -combo ")) {
16052 opt->textValue = (char*) (opt->choice = &cps->comboList[cps->comboCnt]); // cheat with pointer type
16053 cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
16054 if(*q == '*') cps->comboList[cps->comboCnt-1]++;
16055 opt->value = n = 0;
16056 while(q = StrStr(q, " /// ")) {
16057 n++; *q = 0; // count choices, and null-terminate each of them
16059 if(*q == '*') { // remember default, which is marked with * prefix
16063 cps->comboList[cps->comboCnt++] = q;
16065 cps->comboList[cps->comboCnt++] = NULL;
16067 opt->type = ComboBox;
16068 } else if(p = strstr(opt->name, " -button")) {
16069 opt->type = Button;
16070 } else if(p = strstr(opt->name, " -save")) {
16071 opt->type = SaveButton;
16072 } else return FALSE;
16073 *p = 0; // terminate option name
16074 // now look if the command-line options define a setting for this engine option.
16075 if(cps->optionSettings && cps->optionSettings[0])
16076 p = strstr(cps->optionSettings, opt->name); else p = NULL;
16077 if(p && (p == cps->optionSettings || p[-1] == ',')) {
16078 snprintf(buf, MSG_SIZ, "option %s", p);
16079 if(p = strstr(buf, ",")) *p = 0;
16080 if(q = strchr(buf, '=')) switch(opt->type) {
16082 for(n=0; n<opt->max; n++)
16083 if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
16086 safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
16090 opt->value = atoi(q+1);
16095 SendToProgram(buf, cps);
16101 FeatureDone (ChessProgramState *cps, int val)
16103 DelayedEventCallback cb = GetDelayedEvent();
16104 if ((cb == InitBackEnd3 && cps == &first) ||
16105 (cb == SettingsMenuIfReady && cps == &second) ||
16106 (cb == LoadEngine) ||
16107 (cb == TwoMachinesEventIfReady)) {
16108 CancelDelayedEvent();
16109 ScheduleDelayedEvent(cb, val ? 1 : 3600000);
16111 cps->initDone = val;
16112 if(val) cps->reload = FALSE;
16115 /* Parse feature command from engine */
16117 ParseFeatures (char *args, ChessProgramState *cps)
16125 while (*p == ' ') p++;
16126 if (*p == NULLCHAR) return;
16128 if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
16129 if (BoolFeature(&p, "xedit", &cps->extendedEdit, cps)) continue;
16130 if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
16131 if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
16132 if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
16133 if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
16134 if (BoolFeature(&p, "reuse", &val, cps)) {
16135 /* Engine can disable reuse, but can't enable it if user said no */
16136 if (!val) cps->reuse = FALSE;
16139 if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
16140 if (StringFeature(&p, "myname", &cps->tidy, cps)) {
16141 if (gameMode == TwoMachinesPlay) {
16142 DisplayTwoMachinesTitle();
16148 if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
16149 if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
16150 if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
16151 if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
16152 if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
16153 if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
16154 if (BoolFeature(&p, "exclude", &cps->excludeMoves, cps)) continue;
16155 if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
16156 if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
16157 if (BoolFeature(&p, "pause", &cps->pause, cps)) continue; // [HGM] pause
16158 if (IntFeature(&p, "done", &val, cps)) {
16159 FeatureDone(cps, val);
16162 /* Added by Tord: */
16163 if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
16164 if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
16165 /* End of additions by Tord */
16167 /* [HGM] added features: */
16168 if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
16169 if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
16170 if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
16171 if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
16172 if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
16173 if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
16174 if (StringFeature(&p, "option", &q, cps)) { // read to freshly allocated temp buffer first
16175 if(cps->reload) { FREE(q); q = NULL; continue; } // we are reloading because of xreuse
16176 FREE(cps->option[cps->nrOptions].name);
16177 cps->option[cps->nrOptions].name = q; q = NULL;
16178 if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
16179 snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
16180 SendToProgram(buf, cps);
16183 if(cps->nrOptions >= MAX_OPTIONS) {
16185 snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
16186 DisplayError(buf, 0);
16190 /* End of additions by HGM */
16192 /* unknown feature: complain and skip */
16194 while (*q && *q != '=') q++;
16195 snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
16196 SendToProgram(buf, cps);
16202 while (*p && *p != '\"') p++;
16203 if (*p == '\"') p++;
16205 while (*p && *p != ' ') p++;
16213 PeriodicUpdatesEvent (int newState)
16215 if (newState == appData.periodicUpdates)
16218 appData.periodicUpdates=newState;
16220 /* Display type changes, so update it now */
16221 // DisplayAnalysis();
16223 /* Get the ball rolling again... */
16225 AnalysisPeriodicEvent(1);
16226 StartAnalysisClock();
16231 PonderNextMoveEvent (int newState)
16233 if (newState == appData.ponderNextMove) return;
16234 if (gameMode == EditPosition) EditPositionDone(TRUE);
16236 SendToProgram("hard\n", &first);
16237 if (gameMode == TwoMachinesPlay) {
16238 SendToProgram("hard\n", &second);
16241 SendToProgram("easy\n", &first);
16242 thinkOutput[0] = NULLCHAR;
16243 if (gameMode == TwoMachinesPlay) {
16244 SendToProgram("easy\n", &second);
16247 appData.ponderNextMove = newState;
16251 NewSettingEvent (int option, int *feature, char *command, int value)
16255 if (gameMode == EditPosition) EditPositionDone(TRUE);
16256 snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
16257 if(feature == NULL || *feature) SendToProgram(buf, &first);
16258 if (gameMode == TwoMachinesPlay) {
16259 if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
16264 ShowThinkingEvent ()
16265 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
16267 static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
16268 int newState = appData.showThinking
16269 // [HGM] thinking: other features now need thinking output as well
16270 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
16272 if (oldState == newState) return;
16273 oldState = newState;
16274 if (gameMode == EditPosition) EditPositionDone(TRUE);
16276 SendToProgram("post\n", &first);
16277 if (gameMode == TwoMachinesPlay) {
16278 SendToProgram("post\n", &second);
16281 SendToProgram("nopost\n", &first);
16282 thinkOutput[0] = NULLCHAR;
16283 if (gameMode == TwoMachinesPlay) {
16284 SendToProgram("nopost\n", &second);
16287 // appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
16291 AskQuestionEvent (char *title, char *question, char *replyPrefix, char *which)
16293 ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
16294 if (pr == NoProc) return;
16295 AskQuestion(title, question, replyPrefix, pr);
16299 TypeInEvent (char firstChar)
16301 if ((gameMode == BeginningOfGame && !appData.icsActive) ||
16302 gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
16303 gameMode == AnalyzeMode || gameMode == EditGame ||
16304 gameMode == EditPosition || gameMode == IcsExamining ||
16305 gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
16306 isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes
16307 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||
16308 gameMode == IcsObserving || gameMode == TwoMachinesPlay ) ||
16309 gameMode == Training) PopUpMoveDialog(firstChar);
16313 TypeInDoneEvent (char *move)
16316 int n, fromX, fromY, toX, toY;
16318 ChessMove moveType;
16321 if(gameMode == EditPosition && ParseFEN(board, &n, move) ) {
16322 EditPositionPasteFEN(move);
16325 // [HGM] movenum: allow move number to be typed in any mode
16326 if(sscanf(move, "%d", &n) == 1 && n != 0 ) {
16330 // undocumented kludge: allow command-line option to be typed in!
16331 // (potentially fatal, and does not implement the effect of the option.)
16332 // should only be used for options that are values on which future decisions will be made,
16333 // and definitely not on options that would be used during initialization.
16334 if(strstr(move, "!!! -") == move) {
16335 ParseArgsFromString(move+4);
16339 if (gameMode != EditGame && currentMove != forwardMostMove &&
16340 gameMode != Training) {
16341 DisplayMoveError(_("Displayed move is not current"));
16343 int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
16344 &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
16345 if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized
16346 if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
16347 &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {
16348 UserMoveEvent(fromX, fromY, toX, toY, promoChar);
16350 DisplayMoveError(_("Could not parse move"));
16356 DisplayMove (int moveNumber)
16358 char message[MSG_SIZ];
16360 char cpThinkOutput[MSG_SIZ];
16362 if(appData.noGUI) return; // [HGM] fast: suppress display of moves
16364 if (moveNumber == forwardMostMove - 1 ||
16365 gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
16367 safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
16369 if (strchr(cpThinkOutput, '\n')) {
16370 *strchr(cpThinkOutput, '\n') = NULLCHAR;
16373 *cpThinkOutput = NULLCHAR;
16376 /* [AS] Hide thinking from human user */
16377 if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
16378 *cpThinkOutput = NULLCHAR;
16379 if( thinkOutput[0] != NULLCHAR ) {
16382 for( i=0; i<=hiddenThinkOutputState; i++ ) {
16383 cpThinkOutput[i] = '.';
16385 cpThinkOutput[i] = NULLCHAR;
16386 hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
16390 if (moveNumber == forwardMostMove - 1 &&
16391 gameInfo.resultDetails != NULL) {
16392 if (gameInfo.resultDetails[0] == NULLCHAR) {
16393 snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
16395 snprintf(res, MSG_SIZ, " {%s} %s",
16396 T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
16402 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
16403 DisplayMessage(res, cpThinkOutput);
16405 snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
16406 WhiteOnMove(moveNumber) ? " " : ".. ",
16407 parseList[moveNumber], res);
16408 DisplayMessage(message, cpThinkOutput);
16413 DisplayComment (int moveNumber, char *text)
16415 char title[MSG_SIZ];
16417 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
16418 safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
16420 snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
16421 WhiteOnMove(moveNumber) ? " " : ".. ",
16422 parseList[moveNumber]);
16424 if (text != NULL && (appData.autoDisplayComment || commentUp))
16425 CommentPopUp(title, text);
16428 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
16429 * might be busy thinking or pondering. It can be omitted if your
16430 * gnuchess is configured to stop thinking immediately on any user
16431 * input. However, that gnuchess feature depends on the FIONREAD
16432 * ioctl, which does not work properly on some flavors of Unix.
16435 Attention (ChessProgramState *cps)
16438 if (!cps->useSigint) return;
16439 if (appData.noChessProgram || (cps->pr == NoProc)) return;
16440 switch (gameMode) {
16441 case MachinePlaysWhite:
16442 case MachinePlaysBlack:
16443 case TwoMachinesPlay:
16444 case IcsPlayingWhite:
16445 case IcsPlayingBlack:
16448 /* Skip if we know it isn't thinking */
16449 if (!cps->maybeThinking) return;
16450 if (appData.debugMode)
16451 fprintf(debugFP, "Interrupting %s\n", cps->which);
16452 InterruptChildProcess(cps->pr);
16453 cps->maybeThinking = FALSE;
16458 #endif /*ATTENTION*/
16464 if (whiteTimeRemaining <= 0) {
16467 if (appData.icsActive) {
16468 if (appData.autoCallFlag &&
16469 gameMode == IcsPlayingBlack && !blackFlag) {
16470 SendToICS(ics_prefix);
16471 SendToICS("flag\n");
16475 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
16477 if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
16478 if (appData.autoCallFlag) {
16479 GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
16486 if (blackTimeRemaining <= 0) {
16489 if (appData.icsActive) {
16490 if (appData.autoCallFlag &&
16491 gameMode == IcsPlayingWhite && !whiteFlag) {
16492 SendToICS(ics_prefix);
16493 SendToICS("flag\n");
16497 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
16499 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
16500 if (appData.autoCallFlag) {
16501 GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
16512 CheckTimeControl ()
16514 if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
16515 gameMode == PlayFromGameFile || forwardMostMove == 0) return;
16518 * add time to clocks when time control is achieved ([HGM] now also used for increment)
16520 if ( !WhiteOnMove(forwardMostMove) ) {
16521 /* White made time control */
16522 lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
16523 whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
16524 /* [HGM] time odds: correct new time quota for time odds! */
16525 / WhitePlayer()->timeOdds;
16526 lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
16528 lastBlack -= blackTimeRemaining;
16529 /* Black made time control */
16530 blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
16531 / WhitePlayer()->other->timeOdds;
16532 lastWhite = whiteTimeRemaining;
16537 DisplayBothClocks ()
16539 int wom = gameMode == EditPosition ?
16540 !blackPlaysFirst : WhiteOnMove(currentMove);
16541 DisplayWhiteClock(whiteTimeRemaining, wom);
16542 DisplayBlackClock(blackTimeRemaining, !wom);
16546 /* Timekeeping seems to be a portability nightmare. I think everyone
16547 has ftime(), but I'm really not sure, so I'm including some ifdefs
16548 to use other calls if you don't. Clocks will be less accurate if
16549 you have neither ftime nor gettimeofday.
16552 /* VS 2008 requires the #include outside of the function */
16553 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
16554 #include <sys/timeb.h>
16557 /* Get the current time as a TimeMark */
16559 GetTimeMark (TimeMark *tm)
16561 #if HAVE_GETTIMEOFDAY
16563 struct timeval timeVal;
16564 struct timezone timeZone;
16566 gettimeofday(&timeVal, &timeZone);
16567 tm->sec = (long) timeVal.tv_sec;
16568 tm->ms = (int) (timeVal.tv_usec / 1000L);
16570 #else /*!HAVE_GETTIMEOFDAY*/
16573 // include <sys/timeb.h> / moved to just above start of function
16574 struct timeb timeB;
16577 tm->sec = (long) timeB.time;
16578 tm->ms = (int) timeB.millitm;
16580 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
16581 tm->sec = (long) time(NULL);
16587 /* Return the difference in milliseconds between two
16588 time marks. We assume the difference will fit in a long!
16591 SubtractTimeMarks (TimeMark *tm2, TimeMark *tm1)
16593 return 1000L*(tm2->sec - tm1->sec) +
16594 (long) (tm2->ms - tm1->ms);
16599 * Code to manage the game clocks.
16601 * In tournament play, black starts the clock and then white makes a move.
16602 * We give the human user a slight advantage if he is playing white---the
16603 * clocks don't run until he makes his first move, so it takes zero time.
16604 * Also, we don't account for network lag, so we could get out of sync
16605 * with GNU Chess's clock -- but then, referees are always right.
16608 static TimeMark tickStartTM;
16609 static long intendedTickLength;
16612 NextTickLength (long timeRemaining)
16614 long nominalTickLength, nextTickLength;
16616 if (timeRemaining > 0L && timeRemaining <= 10000L)
16617 nominalTickLength = 100L;
16619 nominalTickLength = 1000L;
16620 nextTickLength = timeRemaining % nominalTickLength;
16621 if (nextTickLength <= 0) nextTickLength += nominalTickLength;
16623 return nextTickLength;
16626 /* Adjust clock one minute up or down */
16628 AdjustClock (Boolean which, int dir)
16630 if(appData.autoCallFlag) { DisplayError(_("Clock adjustment not allowed in auto-flag mode"), 0); return; }
16631 if(which) blackTimeRemaining += 60000*dir;
16632 else whiteTimeRemaining += 60000*dir;
16633 DisplayBothClocks();
16634 adjustedClock = TRUE;
16637 /* Stop clocks and reset to a fresh time control */
16641 (void) StopClockTimer();
16642 if (appData.icsActive) {
16643 whiteTimeRemaining = blackTimeRemaining = 0;
16644 } else if (searchTime) {
16645 whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
16646 blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
16647 } else { /* [HGM] correct new time quote for time odds */
16648 whiteTC = blackTC = fullTimeControlString;
16649 whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
16650 blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
16652 if (whiteFlag || blackFlag) {
16654 whiteFlag = blackFlag = FALSE;
16656 lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
16657 DisplayBothClocks();
16658 adjustedClock = FALSE;
16661 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
16663 /* Decrement running clock by amount of time that has passed */
16667 long timeRemaining;
16668 long lastTickLength, fudge;
16671 if (!appData.clockMode) return;
16672 if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
16676 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16678 /* Fudge if we woke up a little too soon */
16679 fudge = intendedTickLength - lastTickLength;
16680 if (fudge < 0 || fudge > FUDGE) fudge = 0;
16682 if (WhiteOnMove(forwardMostMove)) {
16683 if(whiteNPS >= 0) lastTickLength = 0;
16684 timeRemaining = whiteTimeRemaining -= lastTickLength;
16685 if(timeRemaining < 0 && !appData.icsActive) {
16686 GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
16687 if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
16688 whiteStartMove = forwardMostMove; whiteTC = nextSession;
16689 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
16692 DisplayWhiteClock(whiteTimeRemaining - fudge,
16693 WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
16695 if(blackNPS >= 0) lastTickLength = 0;
16696 timeRemaining = blackTimeRemaining -= lastTickLength;
16697 if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
16698 GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
16700 blackStartMove = forwardMostMove;
16701 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
16704 DisplayBlackClock(blackTimeRemaining - fudge,
16705 !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
16707 if (CheckFlags()) return;
16709 if(twoBoards) { // count down secondary board's clocks as well
16710 activePartnerTime -= lastTickLength;
16712 if(activePartner == 'W')
16713 DisplayWhiteClock(activePartnerTime, TRUE); // the counting clock is always the highlighted one!
16715 DisplayBlackClock(activePartnerTime, TRUE);
16720 intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
16721 StartClockTimer(intendedTickLength);
16723 /* if the time remaining has fallen below the alarm threshold, sound the
16724 * alarm. if the alarm has sounded and (due to a takeback or time control
16725 * with increment) the time remaining has increased to a level above the
16726 * threshold, reset the alarm so it can sound again.
16729 if (appData.icsActive && appData.icsAlarm) {
16731 /* make sure we are dealing with the user's clock */
16732 if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
16733 ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
16736 if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
16737 alarmSounded = FALSE;
16738 } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
16740 alarmSounded = TRUE;
16746 /* A player has just moved, so stop the previously running
16747 clock and (if in clock mode) start the other one.
16748 We redisplay both clocks in case we're in ICS mode, because
16749 ICS gives us an update to both clocks after every move.
16750 Note that this routine is called *after* forwardMostMove
16751 is updated, so the last fractional tick must be subtracted
16752 from the color that is *not* on move now.
16755 SwitchClocks (int newMoveNr)
16757 long lastTickLength;
16759 int flagged = FALSE;
16763 if (StopClockTimer() && appData.clockMode) {
16764 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16765 if (!WhiteOnMove(forwardMostMove)) {
16766 if(blackNPS >= 0) lastTickLength = 0;
16767 blackTimeRemaining -= lastTickLength;
16768 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
16769 // if(pvInfoList[forwardMostMove].time == -1)
16770 pvInfoList[forwardMostMove].time = // use GUI time
16771 (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
16773 if(whiteNPS >= 0) lastTickLength = 0;
16774 whiteTimeRemaining -= lastTickLength;
16775 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
16776 // if(pvInfoList[forwardMostMove].time == -1)
16777 pvInfoList[forwardMostMove].time =
16778 (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
16780 flagged = CheckFlags();
16782 forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
16783 CheckTimeControl();
16785 if (flagged || !appData.clockMode) return;
16787 switch (gameMode) {
16788 case MachinePlaysBlack:
16789 case MachinePlaysWhite:
16790 case BeginningOfGame:
16791 if (pausing) return;
16795 case PlayFromGameFile:
16803 if (searchTime) { // [HGM] st: set clock of player that has to move to max time
16804 if(WhiteOnMove(forwardMostMove))
16805 whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
16806 else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
16810 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
16811 whiteTimeRemaining : blackTimeRemaining);
16812 StartClockTimer(intendedTickLength);
16816 /* Stop both clocks */
16820 long lastTickLength;
16823 if (!StopClockTimer()) return;
16824 if (!appData.clockMode) return;
16828 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16829 if (WhiteOnMove(forwardMostMove)) {
16830 if(whiteNPS >= 0) lastTickLength = 0;
16831 whiteTimeRemaining -= lastTickLength;
16832 DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
16834 if(blackNPS >= 0) lastTickLength = 0;
16835 blackTimeRemaining -= lastTickLength;
16836 DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
16841 /* Start clock of player on move. Time may have been reset, so
16842 if clock is already running, stop and restart it. */
16846 (void) StopClockTimer(); /* in case it was running already */
16847 DisplayBothClocks();
16848 if (CheckFlags()) return;
16850 if (!appData.clockMode) return;
16851 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
16853 GetTimeMark(&tickStartTM);
16854 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
16855 whiteTimeRemaining : blackTimeRemaining);
16857 /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
16858 whiteNPS = blackNPS = -1;
16859 if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
16860 || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
16861 whiteNPS = first.nps;
16862 if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
16863 || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
16864 blackNPS = first.nps;
16865 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
16866 whiteNPS = second.nps;
16867 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
16868 blackNPS = second.nps;
16869 if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
16871 StartClockTimer(intendedTickLength);
16875 TimeString (long ms)
16877 long second, minute, hour, day;
16879 static char buf[32];
16881 if (ms > 0 && ms <= 9900) {
16882 /* convert milliseconds to tenths, rounding up */
16883 double tenths = floor( ((double)(ms + 99L)) / 100.00 );
16885 snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
16889 /* convert milliseconds to seconds, rounding up */
16890 /* use floating point to avoid strangeness of integer division
16891 with negative dividends on many machines */
16892 second = (long) floor(((double) (ms + 999L)) / 1000.0);
16899 day = second / (60 * 60 * 24);
16900 second = second % (60 * 60 * 24);
16901 hour = second / (60 * 60);
16902 second = second % (60 * 60);
16903 minute = second / 60;
16904 second = second % 60;
16907 snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
16908 sign, day, hour, minute, second);
16910 snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
16912 snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
16919 * This is necessary because some C libraries aren't ANSI C compliant yet.
16922 StrStr (char *string, char *match)
16926 length = strlen(match);
16928 for (i = strlen(string) - length; i >= 0; i--, string++)
16929 if (!strncmp(match, string, length))
16936 StrCaseStr (char *string, char *match)
16940 length = strlen(match);
16942 for (i = strlen(string) - length; i >= 0; i--, string++) {
16943 for (j = 0; j < length; j++) {
16944 if (ToLower(match[j]) != ToLower(string[j]))
16947 if (j == length) return string;
16955 StrCaseCmp (char *s1, char *s2)
16960 c1 = ToLower(*s1++);
16961 c2 = ToLower(*s2++);
16962 if (c1 > c2) return 1;
16963 if (c1 < c2) return -1;
16964 if (c1 == NULLCHAR) return 0;
16972 return isupper(c) ? tolower(c) : c;
16979 return islower(c) ? toupper(c) : c;
16981 #endif /* !_amigados */
16988 if ((ret = (char *) malloc(strlen(s) + 1)))
16990 safeStrCpy(ret, s, strlen(s)+1);
16996 StrSavePtr (char *s, char **savePtr)
17001 if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
17002 safeStrCpy(*savePtr, s, strlen(s)+1);
17014 clock = time((time_t *)NULL);
17015 tm = localtime(&clock);
17016 snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
17017 tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
17018 return StrSave(buf);
17023 PositionToFEN (int move, char *overrideCastling)
17025 int i, j, fromX, fromY, toX, toY;
17032 whiteToPlay = (gameMode == EditPosition) ?
17033 !blackPlaysFirst : (move % 2 == 0);
17036 /* Piece placement data */
17037 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
17038 if(MSG_SIZ - (p - buf) < BOARD_RGHT - BOARD_LEFT + 20) { *p = 0; return StrSave(buf); }
17040 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
17041 if (boards[move][i][j] == EmptySquare) {
17043 } else { ChessSquare piece = boards[move][i][j];
17044 if (emptycount > 0) {
17045 if(emptycount<10) /* [HGM] can be >= 10 */
17046 *p++ = '0' + emptycount;
17047 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
17050 if(PieceToChar(piece) == '+') {
17051 /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
17053 piece = (ChessSquare)(DEMOTED piece);
17055 *p++ = PieceToChar(piece);
17057 /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
17058 p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
17063 if (emptycount > 0) {
17064 if(emptycount<10) /* [HGM] can be >= 10 */
17065 *p++ = '0' + emptycount;
17066 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
17073 /* [HGM] print Crazyhouse or Shogi holdings */
17074 if( gameInfo.holdingsWidth ) {
17075 *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
17077 for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
17078 piece = boards[move][i][BOARD_WIDTH-1];
17079 if( piece != EmptySquare )
17080 for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
17081 *p++ = PieceToChar(piece);
17083 for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
17084 piece = boards[move][BOARD_HEIGHT-i-1][0];
17085 if( piece != EmptySquare )
17086 for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
17087 *p++ = PieceToChar(piece);
17090 if( q == p ) *p++ = '-';
17096 *p++ = whiteToPlay ? 'w' : 'b';
17099 if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
17100 while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
17102 if(nrCastlingRights) {
17104 if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
17105 /* [HGM] write directly from rights */
17106 if(boards[move][CASTLING][2] != NoRights &&
17107 boards[move][CASTLING][0] != NoRights )
17108 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
17109 if(boards[move][CASTLING][2] != NoRights &&
17110 boards[move][CASTLING][1] != NoRights )
17111 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
17112 if(boards[move][CASTLING][5] != NoRights &&
17113 boards[move][CASTLING][3] != NoRights )
17114 *p++ = boards[move][CASTLING][3] + AAA;
17115 if(boards[move][CASTLING][5] != NoRights &&
17116 boards[move][CASTLING][4] != NoRights )
17117 *p++ = boards[move][CASTLING][4] + AAA;
17120 /* [HGM] write true castling rights */
17121 if( nrCastlingRights == 6 ) {
17123 if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
17124 boards[move][CASTLING][2] != NoRights ) k = 1, *p++ = 'K';
17125 q = (boards[move][CASTLING][1] == BOARD_LEFT &&
17126 boards[move][CASTLING][2] != NoRights );
17127 if(gameInfo.variant == VariantSChess) { // for S-Chess, indicate all vrgin backrank pieces
17128 for(i=j=0; i<BOARD_HEIGHT; i++) j += boards[move][i][BOARD_RGHT]; // count white held pieces
17129 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q && j; i--)
17130 if((boards[move][0][i] != WhiteKing || k+q == 0) &&
17131 boards[move][VIRGIN][i] & VIRGIN_W) *p++ = i + AAA + 'A' - 'a';
17135 if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
17136 boards[move][CASTLING][5] != NoRights ) k = 1, *p++ = 'k';
17137 q = (boards[move][CASTLING][4] == BOARD_LEFT &&
17138 boards[move][CASTLING][5] != NoRights );
17139 if(gameInfo.variant == VariantSChess) {
17140 for(i=j=0; i<BOARD_HEIGHT; i++) j += boards[move][i][BOARD_LEFT-1]; // count black held pieces
17141 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q && j; i--)
17142 if((boards[move][BOARD_HEIGHT-1][i] != BlackKing || k+q == 0) &&
17143 boards[move][VIRGIN][i] & VIRGIN_B) *p++ = i + AAA;
17148 if (q == p) *p++ = '-'; /* No castling rights */
17152 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
17153 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier &&
17154 gameInfo.variant != VariantMakruk && gameInfo.variant != VariantASEAN ) {
17155 /* En passant target square */
17156 if (move > backwardMostMove) {
17157 fromX = moveList[move - 1][0] - AAA;
17158 fromY = moveList[move - 1][1] - ONE;
17159 toX = moveList[move - 1][2] - AAA;
17160 toY = moveList[move - 1][3] - ONE;
17161 if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
17162 toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
17163 boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
17165 /* 2-square pawn move just happened */
17167 *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
17171 } else if(move == backwardMostMove) {
17172 // [HGM] perhaps we should always do it like this, and forget the above?
17173 if((signed char)boards[move][EP_STATUS] >= 0) {
17174 *p++ = boards[move][EP_STATUS] + AAA;
17175 *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
17186 /* [HGM] find reversible plies */
17187 { int i = 0, j=move;
17189 if (appData.debugMode) { int k;
17190 fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
17191 for(k=backwardMostMove; k<=forwardMostMove; k++)
17192 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
17196 while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
17197 if( j == backwardMostMove ) i += initialRulePlies;
17198 sprintf(p, "%d ", i);
17199 p += i>=100 ? 4 : i >= 10 ? 3 : 2;
17201 /* Fullmove number */
17202 sprintf(p, "%d", (move / 2) + 1);
17204 return StrSave(buf);
17208 ParseFEN (Board board, int *blackPlaysFirst, char *fen)
17212 int emptycount, virgin[BOARD_FILES];
17217 /* [HGM] by default clear Crazyhouse holdings, if present */
17218 if(gameInfo.holdingsWidth) {
17219 for(i=0; i<BOARD_HEIGHT; i++) {
17220 board[i][0] = EmptySquare; /* black holdings */
17221 board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
17222 board[i][1] = (ChessSquare) 0; /* black counts */
17223 board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
17227 /* Piece placement data */
17228 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
17231 if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
17232 if (*p == '/') p++;
17233 emptycount = gameInfo.boardWidth - j;
17234 while (emptycount--)
17235 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
17237 #if(BOARD_FILES >= 10)
17238 } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
17239 p++; emptycount=10;
17240 if (j + emptycount > gameInfo.boardWidth) return FALSE;
17241 while (emptycount--)
17242 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
17244 } else if (isdigit(*p)) {
17245 emptycount = *p++ - '0';
17246 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
17247 if (j + emptycount > gameInfo.boardWidth) return FALSE;
17248 while (emptycount--)
17249 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
17250 } else if (*p == '+' || isalpha(*p)) {
17251 if (j >= gameInfo.boardWidth) return FALSE;
17253 piece = CharToPiece(*++p);
17254 if(piece == EmptySquare) return FALSE; /* unknown piece */
17255 piece = (ChessSquare) (PROMOTED piece ); p++;
17256 if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
17257 } else piece = CharToPiece(*p++);
17259 if(piece==EmptySquare) return FALSE; /* unknown piece */
17260 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
17261 piece = (ChessSquare) (PROMOTED piece);
17262 if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
17265 board[i][(j++)+gameInfo.holdingsWidth] = piece;
17271 while (*p == '/' || *p == ' ') p++;
17273 /* [HGM] look for Crazyhouse holdings here */
17274 while(*p==' ') p++;
17275 if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
17277 if(*p == '-' ) p++; /* empty holdings */ else {
17278 if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
17279 /* if we would allow FEN reading to set board size, we would */
17280 /* have to add holdings and shift the board read so far here */
17281 while( (piece = CharToPiece(*p) ) != EmptySquare ) {
17283 if((int) piece >= (int) BlackPawn ) {
17284 i = (int)piece - (int)BlackPawn;
17285 i = PieceToNumber((ChessSquare)i);
17286 if( i >= gameInfo.holdingsSize ) return FALSE;
17287 board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
17288 board[BOARD_HEIGHT-1-i][1]++; /* black counts */
17290 i = (int)piece - (int)WhitePawn;
17291 i = PieceToNumber((ChessSquare)i);
17292 if( i >= gameInfo.holdingsSize ) return FALSE;
17293 board[i][BOARD_WIDTH-1] = piece; /* white holdings */
17294 board[i][BOARD_WIDTH-2]++; /* black holdings */
17301 while(*p == ' ') p++;
17305 if(appData.colorNickNames) {
17306 if( c == appData.colorNickNames[0] ) c = 'w'; else
17307 if( c == appData.colorNickNames[1] ) c = 'b';
17311 *blackPlaysFirst = FALSE;
17314 *blackPlaysFirst = TRUE;
17320 /* [HGM] We NO LONGER ignore the rest of the FEN notation */
17321 /* return the extra info in global variiables */
17323 /* set defaults in case FEN is incomplete */
17324 board[EP_STATUS] = EP_UNKNOWN;
17325 for(i=0; i<nrCastlingRights; i++ ) {
17326 board[CASTLING][i] =
17327 gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
17328 } /* assume possible unless obviously impossible */
17329 if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
17330 if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
17331 if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
17332 && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
17333 if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
17334 if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
17335 if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
17336 && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
17339 while(*p==' ') p++;
17340 if(nrCastlingRights) {
17341 if(gameInfo.variant == VariantSChess) for(i=0; i<BOARD_FILES; i++) virgin[i] = 0;
17342 if(*p >= 'A' && *p <= 'Z' || *p >= 'a' && *p <= 'z' || *p=='-') {
17343 /* castling indicator present, so default becomes no castlings */
17344 for(i=0; i<nrCastlingRights; i++ ) {
17345 board[CASTLING][i] = NoRights;
17348 while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
17349 (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess) &&
17350 ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
17351 ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth) ) {
17352 int c = *p++, whiteKingFile=NoRights, blackKingFile=NoRights;
17354 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
17355 if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
17356 if(board[0 ][i] == WhiteKing) whiteKingFile = i;
17358 if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
17359 whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
17360 if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
17361 && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
17362 if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
17363 && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
17366 for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
17367 board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
17368 board[CASTLING][2] = whiteKingFile;
17369 if(board[CASTLING][0] != NoRights) virgin[board[CASTLING][0]] |= VIRGIN_W;
17370 if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
17373 for(i=BOARD_LEFT; i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
17374 board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
17375 board[CASTLING][2] = whiteKingFile;
17376 if(board[CASTLING][1] != NoRights) virgin[board[CASTLING][1]] |= VIRGIN_W;
17377 if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
17380 for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
17381 board[CASTLING][3] = i != blackKingFile ? i : NoRights;
17382 board[CASTLING][5] = blackKingFile;
17383 if(board[CASTLING][3] != NoRights) virgin[board[CASTLING][3]] |= VIRGIN_B;
17384 if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
17387 for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
17388 board[CASTLING][4] = i != blackKingFile ? i : NoRights;
17389 board[CASTLING][5] = blackKingFile;
17390 if(board[CASTLING][4] != NoRights) virgin[board[CASTLING][4]] |= VIRGIN_B;
17391 if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
17394 default: /* FRC castlings */
17395 if(c >= 'a') { /* black rights */
17396 if(gameInfo.variant == VariantSChess) { virgin[c-AAA] |= VIRGIN_B; break; } // in S-Chess castlings are always kq, so just virginity
17397 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
17398 if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
17399 if(i == BOARD_RGHT) break;
17400 board[CASTLING][5] = i;
17402 if(board[BOARD_HEIGHT-1][c] < BlackPawn ||
17403 board[BOARD_HEIGHT-1][c] >= BlackKing ) break;
17405 board[CASTLING][3] = c;
17407 board[CASTLING][4] = c;
17408 } else { /* white rights */
17409 if(gameInfo.variant == VariantSChess) { virgin[c-AAA-'A'+'a'] |= VIRGIN_W; break; } // in S-Chess castlings are always KQ
17410 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
17411 if(board[0][i] == WhiteKing) break;
17412 if(i == BOARD_RGHT) break;
17413 board[CASTLING][2] = i;
17414 c -= AAA - 'a' + 'A';
17415 if(board[0][c] >= WhiteKing) break;
17417 board[CASTLING][0] = c;
17419 board[CASTLING][1] = c;
17423 for(i=0; i<nrCastlingRights; i++)
17424 if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
17425 if(gameInfo.variant == VariantSChess) for(i=0; i<BOARD_FILES; i++) board[VIRGIN][i] = virgin[i];
17426 if (appData.debugMode) {
17427 fprintf(debugFP, "FEN castling rights:");
17428 for(i=0; i<nrCastlingRights; i++)
17429 fprintf(debugFP, " %d", board[CASTLING][i]);
17430 fprintf(debugFP, "\n");
17433 while(*p==' ') p++;
17436 /* read e.p. field in games that know e.p. capture */
17437 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
17438 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier &&
17439 gameInfo.variant != VariantMakruk && gameInfo.variant != VariantASEAN ) {
17441 p++; board[EP_STATUS] = EP_NONE;
17443 char c = *p++ - AAA;
17445 if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
17446 if(*p >= '0' && *p <='9') p++;
17447 board[EP_STATUS] = c;
17452 if(sscanf(p, "%d", &i) == 1) {
17453 FENrulePlies = i; /* 50-move ply counter */
17454 /* (The move number is still ignored) */
17461 EditPositionPasteFEN (char *fen)
17464 Board initial_position;
17466 if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
17467 DisplayError(_("Bad FEN position in clipboard"), 0);
17470 int savedBlackPlaysFirst = blackPlaysFirst;
17471 EditPositionEvent();
17472 blackPlaysFirst = savedBlackPlaysFirst;
17473 CopyBoard(boards[0], initial_position);
17474 initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
17475 EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
17476 DisplayBothClocks();
17477 DrawPosition(FALSE, boards[currentMove]);
17482 static char cseq[12] = "\\ ";
17485 set_cont_sequence (char *new_seq)
17490 // handle bad attempts to set the sequence
17492 return 0; // acceptable error - no debug
17494 len = strlen(new_seq);
17495 ret = (len > 0) && (len < sizeof(cseq));
17497 safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
17498 else if (appData.debugMode)
17499 fprintf(debugFP, "Invalid continuation sequence \"%s\" (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
17504 reformat a source message so words don't cross the width boundary. internal
17505 newlines are not removed. returns the wrapped size (no null character unless
17506 included in source message). If dest is NULL, only calculate the size required
17507 for the dest buffer. lp argument indicats line position upon entry, and it's
17508 passed back upon exit.
17511 wrap (char *dest, char *src, int count, int width, int *lp)
17513 int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
17515 cseq_len = strlen(cseq);
17516 old_line = line = *lp;
17517 ansi = len = clen = 0;
17519 for (i=0; i < count; i++)
17521 if (src[i] == '\033')
17524 // if we hit the width, back up
17525 if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
17527 // store i & len in case the word is too long
17528 old_i = i, old_len = len;
17530 // find the end of the last word
17531 while (i && src[i] != ' ' && src[i] != '\n')
17537 // word too long? restore i & len before splitting it
17538 if ((old_i-i+clen) >= width)
17545 if (i && src[i-1] == ' ')
17548 if (src[i] != ' ' && src[i] != '\n')
17555 // now append the newline and continuation sequence
17560 strncpy(dest+len, cseq, cseq_len);
17568 dest[len] = src[i];
17572 if (src[i] == '\n')
17577 if (dest && appData.debugMode)
17579 fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
17580 count, width, line, len, *lp);
17581 show_bytes(debugFP, src, count);
17582 fprintf(debugFP, "\ndest: ");
17583 show_bytes(debugFP, dest, len);
17584 fprintf(debugFP, "\n");
17586 *lp = dest ? line : old_line;
17591 // [HGM] vari: routines for shelving variations
17592 Boolean modeRestore = FALSE;
17595 PushInner (int firstMove, int lastMove)
17597 int i, j, nrMoves = lastMove - firstMove;
17599 // push current tail of game on stack
17600 savedResult[storedGames] = gameInfo.result;
17601 savedDetails[storedGames] = gameInfo.resultDetails;
17602 gameInfo.resultDetails = NULL;
17603 savedFirst[storedGames] = firstMove;
17604 savedLast [storedGames] = lastMove;
17605 savedFramePtr[storedGames] = framePtr;
17606 framePtr -= nrMoves; // reserve space for the boards
17607 for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
17608 CopyBoard(boards[framePtr+i], boards[firstMove+i]);
17609 for(j=0; j<MOVE_LEN; j++)
17610 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
17611 for(j=0; j<2*MOVE_LEN; j++)
17612 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
17613 timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
17614 timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
17615 pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
17616 pvInfoList[firstMove+i-1].depth = 0;
17617 commentList[framePtr+i] = commentList[firstMove+i];
17618 commentList[firstMove+i] = NULL;
17622 forwardMostMove = firstMove; // truncate game so we can start variation
17626 PushTail (int firstMove, int lastMove)
17628 if(appData.icsActive) { // only in local mode
17629 forwardMostMove = currentMove; // mimic old ICS behavior
17632 if(storedGames >= MAX_VARIATIONS-2) return; // leave one for PV-walk
17634 PushInner(firstMove, lastMove);
17635 if(storedGames == 1) GreyRevert(FALSE);
17636 if(gameMode == PlayFromGameFile) gameMode = EditGame, modeRestore = TRUE;
17640 PopInner (Boolean annotate)
17643 char buf[8000], moveBuf[20];
17645 ToNrEvent(savedFirst[storedGames-1]); // sets currentMove
17646 storedGames--; // do this after ToNrEvent, to make sure HistorySet will refresh entire game after PopInner returns
17647 nrMoves = savedLast[storedGames] - currentMove;
17650 if(!WhiteOnMove(currentMove))
17651 snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
17652 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
17653 for(i=currentMove; i<forwardMostMove; i++) {
17655 snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
17656 else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
17657 strcat(buf, moveBuf);
17658 if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
17659 if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
17663 for(i=1; i<=nrMoves; i++) { // copy last variation back
17664 CopyBoard(boards[currentMove+i], boards[framePtr+i]);
17665 for(j=0; j<MOVE_LEN; j++)
17666 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
17667 for(j=0; j<2*MOVE_LEN; j++)
17668 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
17669 timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
17670 timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
17671 pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
17672 if(commentList[currentMove+i]) free(commentList[currentMove+i]);
17673 commentList[currentMove+i] = commentList[framePtr+i];
17674 commentList[framePtr+i] = NULL;
17676 if(annotate) AppendComment(currentMove+1, buf, FALSE);
17677 framePtr = savedFramePtr[storedGames];
17678 gameInfo.result = savedResult[storedGames];
17679 if(gameInfo.resultDetails != NULL) {
17680 free(gameInfo.resultDetails);
17682 gameInfo.resultDetails = savedDetails[storedGames];
17683 forwardMostMove = currentMove + nrMoves;
17687 PopTail (Boolean annotate)
17689 if(appData.icsActive) return FALSE; // only in local mode
17690 if(!storedGames) return FALSE; // sanity
17691 CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
17693 PopInner(annotate);
17694 if(currentMove < forwardMostMove) ForwardEvent(); else
17695 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
17697 if(storedGames == 0) { GreyRevert(TRUE); if(modeRestore) modeRestore = FALSE, gameMode = PlayFromGameFile; }
17703 { // remove all shelved variations
17705 for(i=0; i<storedGames; i++) {
17706 if(savedDetails[i])
17707 free(savedDetails[i]);
17708 savedDetails[i] = NULL;
17710 for(i=framePtr; i<MAX_MOVES; i++) {
17711 if(commentList[i]) free(commentList[i]);
17712 commentList[i] = NULL;
17714 framePtr = MAX_MOVES-1;
17719 LoadVariation (int index, char *text)
17720 { // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
17721 char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
17722 int level = 0, move;
17724 if(gameMode != EditGame && gameMode != AnalyzeMode && gameMode != PlayFromGameFile) return;
17725 // first find outermost bracketing variation
17726 while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
17727 if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
17728 if(*p == '{') wait = '}'; else
17729 if(*p == '[') wait = ']'; else
17730 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
17731 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
17733 if(*p == wait) wait = NULLCHAR; // closing ]} found
17736 if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
17737 if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
17738 end[1] = NULLCHAR; // clip off comment beyond variation
17739 ToNrEvent(currentMove-1);
17740 PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
17741 // kludge: use ParsePV() to append variation to game
17742 move = currentMove;
17743 ParsePV(start, TRUE, TRUE);
17744 forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
17745 ClearPremoveHighlights();
17747 ToNrEvent(currentMove+1);
17753 char *p, *q, buf[MSG_SIZ];
17754 if(engineLine && engineLine[0]) { // a theme was selected from the listbox
17755 snprintf(buf, MSG_SIZ, "-theme %s", engineLine);
17756 ParseArgsFromString(buf);
17757 ActivateTheme(TRUE); // also redo colors
17761 if(*p && !strchr(p, '"')) // theme name specified and well-formed; add settings to theme list
17764 q = appData.themeNames;
17765 snprintf(buf, MSG_SIZ, "\"%s\"", nickName);
17766 if(appData.useBitmaps) {
17767 snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt true -lbtf \"%s\" -dbtf \"%s\" -lbtm %d -dbtm %d",
17768 appData.liteBackTextureFile, appData.darkBackTextureFile,
17769 appData.liteBackTextureMode,
17770 appData.darkBackTextureMode );
17772 snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt false -lsc %s -dsc %s",
17773 Col2Text(2), // lightSquareColor
17774 Col2Text(3) ); // darkSquareColor
17776 if(appData.useBorder) {
17777 snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub true -border \"%s\"",
17780 snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub false");
17782 if(appData.useFont) {
17783 snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf true -pf \"%s\" -fptc \"%s\" -fpfcw %s -fpbcb %s",
17784 appData.renderPiecesWithFont,
17785 appData.fontToPieceTable,
17786 Col2Text(9), // appData.fontBackColorWhite
17787 Col2Text(10) ); // appData.fontForeColorBlack
17789 snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf false -pid \"%s\"",
17790 appData.pieceDirectory);
17791 if(!appData.pieceDirectory[0])
17792 snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -wpc %s -bpc %s",
17793 Col2Text(0), // whitePieceColor
17794 Col2Text(1) ); // blackPieceColor
17796 snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -hsc %s -phc %s\n",
17797 Col2Text(4), // highlightSquareColor
17798 Col2Text(5) ); // premoveHighlightColor
17799 appData.themeNames = malloc(len = strlen(q) + strlen(buf) + 1);
17800 if(insert != q) insert[-1] = NULLCHAR;
17801 snprintf(appData.themeNames, len, "%s\n%s%s", q, buf, insert);
17804 ActivateTheme(FALSE);