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 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"
135 # define _(s) gettext (s)
136 # define N_(s) gettext_noop (s)
137 # define T_(s) gettext(s)
150 int establish P((void));
151 void read_from_player P((InputSourceRef isr, VOIDSTAR closure,
152 char *buf, int count, int error));
153 void read_from_ics P((InputSourceRef isr, VOIDSTAR closure,
154 char *buf, int count, int error));
155 void ics_printf P((char *format, ...));
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(());
227 extern void ConsoleCreate();
230 ChessProgramState *WhitePlayer();
231 void InsertIntoMemo P((int which, char *text)); // [HGM] kibitz: in engineo.c
232 int VerifyDisplayMode P(());
234 char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment
235 void InitEngineUCI( const char * iniDir, ChessProgramState * cps ); // [HGM] moved here from winboard.c
236 char *ProbeBook P((int moveNr, char *book)); // [HGM] book: returns a book move
237 char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book
238 void ics_update_width P((int new_width));
239 extern char installDir[MSG_SIZ];
240 VariantClass startVariant; /* [HGM] nicks: initial variant */
243 extern int tinyLayout, smallLayout;
244 ChessProgramStats programStats;
245 char lastPV[2][2*MSG_SIZ]; /* [HGM] pv: last PV in thinking output of each engine */
247 static int exiting = 0; /* [HGM] moved to top */
248 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
249 int startedFromPositionFile = FALSE; Board filePosition; /* [HGM] loadPos */
250 Board partnerBoard; /* [HGM] bughouse: for peeking at partner game */
251 int partnerHighlight[2];
252 Boolean partnerBoardValid = 0;
253 char partnerStatus[MSG_SIZ];
255 Boolean originalFlip;
256 Boolean twoBoards = 0;
257 char endingGame = 0; /* [HGM] crash: flag to prevent recursion of GameEnds() */
258 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS */
259 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
260 int lastIndex = 0; /* [HGM] autoinc: last game/position used in match mode */
261 Boolean connectionAlive;/* [HGM] alive: ICS connection status from probing */
262 int opponentKibitzes;
263 int lastSavedGame; /* [HGM] save: ID of game */
264 char chatPartner[MAX_CHAT][MSG_SIZ]; /* [HGM] chat: list of chatting partners */
265 extern int chatCount;
267 char marker[BOARD_RANKS][BOARD_FILES]; /* [HGM] marks for target squares */
268 char lastMsg[MSG_SIZ];
269 ChessSquare pieceSweep = EmptySquare;
270 ChessSquare promoSweep = EmptySquare, defaultPromoChoice;
271 int promoDefaultAltered;
273 /* States for ics_getting_history */
275 #define H_REQUESTED 1
276 #define H_GOT_REQ_HEADER 2
277 #define H_GOT_UNREQ_HEADER 3
278 #define H_GETTING_MOVES 4
279 #define H_GOT_UNWANTED_HEADER 5
281 /* whosays values for GameEnds */
290 /* Maximum number of games in a cmail message */
291 #define CMAIL_MAX_GAMES 20
293 /* Different types of move when calling RegisterMove */
295 #define CMAIL_RESIGN 1
297 #define CMAIL_ACCEPT 3
299 /* Different types of result to remember for each game */
300 #define CMAIL_NOT_RESULT 0
301 #define CMAIL_OLD_RESULT 1
302 #define CMAIL_NEW_RESULT 2
304 /* Telnet protocol constants */
315 safeStrCpy (char *dst, const char *src, size_t count)
318 assert( dst != NULL );
319 assert( src != NULL );
322 for(i=0; i<count; i++) if((dst[i] = src[i]) == NULLCHAR) break;
323 if( i == count && dst[count-1] != NULLCHAR)
325 dst[ count-1 ] = '\0'; // make sure incomplete copy still null-terminated
326 if(appData.debugMode)
327 fprintf(debugFP, "safeStrCpy: copying %s into %s didn't work, not enough space %d\n",src,dst, (int)count);
333 /* Some compiler can't cast u64 to double
334 * This function do the job for us:
336 * We use the highest bit for cast, this only
337 * works if the highest bit is not
338 * in use (This should not happen)
340 * We used this for all compiler
343 u64ToDouble (u64 value)
346 u64 tmp = value & u64Const(0x7fffffffffffffff);
347 r = (double)(s64)tmp;
348 if (value & u64Const(0x8000000000000000))
349 r += 9.2233720368547758080e18; /* 2^63 */
353 /* Fake up flags for now, as we aren't keeping track of castling
354 availability yet. [HGM] Change of logic: the flag now only
355 indicates the type of castlings allowed by the rule of the game.
356 The actual rights themselves are maintained in the array
357 castlingRights, as part of the game history, and are not probed
363 int flags = F_ALL_CASTLE_OK;
364 if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
365 switch (gameInfo.variant) {
367 flags &= ~F_ALL_CASTLE_OK;
368 case VariantGiveaway: // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
369 flags |= F_IGNORE_CHECK;
371 flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
374 flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
376 case VariantKriegspiel:
377 flags |= F_KRIEGSPIEL_CAPTURE;
379 case VariantCapaRandom:
380 case VariantFischeRandom:
381 flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
382 case VariantNoCastle:
383 case VariantShatranj:
387 flags &= ~F_ALL_CASTLE_OK;
395 FILE *gameFileFP, *debugFP;
398 [AS] Note: sometimes, the sscanf() function is used to parse the input
399 into a fixed-size buffer. Because of this, we must be prepared to
400 receive strings as long as the size of the input buffer, which is currently
401 set to 4K for Windows and 8K for the rest.
402 So, we must either allocate sufficiently large buffers here, or
403 reduce the size of the input buffer in the input reading part.
406 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
407 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
408 char thinkOutput1[MSG_SIZ*10];
410 ChessProgramState first, second, pairing;
412 /* premove variables */
415 int premoveFromX = 0;
416 int premoveFromY = 0;
417 int premovePromoChar = 0;
419 Boolean alarmSounded;
420 /* end premove variables */
422 char *ics_prefix = "$";
423 int ics_type = ICS_GENERIC;
425 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
426 int pauseExamForwardMostMove = 0;
427 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
428 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
429 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
430 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
431 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
432 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
433 int whiteFlag = FALSE, blackFlag = FALSE;
434 int userOfferedDraw = FALSE;
435 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
436 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
437 int cmailMoveType[CMAIL_MAX_GAMES];
438 long ics_clock_paused = 0;
439 ProcRef icsPR = NoProc, cmailPR = NoProc;
440 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
441 GameMode gameMode = BeginningOfGame;
442 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
443 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
444 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
445 int hiddenThinkOutputState = 0; /* [AS] */
446 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
447 int adjudicateLossPlies = 6;
448 char white_holding[64], black_holding[64];
449 TimeMark lastNodeCountTime;
450 long lastNodeCount=0;
451 int shiftKey; // [HGM] set by mouse handler
453 int have_sent_ICS_logon = 0;
455 int suddenDeath, whiteStartMove, blackStartMove; /* [HGM] for implementation of 'any per time' sessions, as in first part of byoyomi TC */
456 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement, lastWhite, lastBlack;
457 Boolean adjustedClock;
458 long timeControl_2; /* [AS] Allow separate time controls */
459 char *fullTimeControlString = NULL, *nextSession, *whiteTC, *blackTC; /* [HGM] secondary TC: merge of MPS, TC and inc */
460 long timeRemaining[2][MAX_MOVES];
461 int matchGame = 0, nextGame = 0, roundNr = 0;
462 Boolean waitingForGame = FALSE;
463 TimeMark programStartTime, pauseStart;
464 char ics_handle[MSG_SIZ];
465 int have_set_title = 0;
467 /* animateTraining preserves the state of appData.animate
468 * when Training mode is activated. This allows the
469 * response to be animated when appData.animate == TRUE and
470 * appData.animateDragging == TRUE.
472 Boolean animateTraining;
478 Board boards[MAX_MOVES];
479 /* [HGM] Following 7 needed for accurate legality tests: */
480 signed char castlingRank[BOARD_FILES]; // and corresponding ranks
481 signed char initialRights[BOARD_FILES];
482 int nrCastlingRights; // For TwoKings, or to implement castling-unknown status
483 int initialRulePlies, FENrulePlies;
484 FILE *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
486 Boolean shuffleOpenings;
487 int mute; // mute all sounds
489 // [HGM] vari: next 12 to save and restore variations
490 #define MAX_VARIATIONS 10
491 int framePtr = MAX_MOVES-1; // points to free stack entry
493 int savedFirst[MAX_VARIATIONS];
494 int savedLast[MAX_VARIATIONS];
495 int savedFramePtr[MAX_VARIATIONS];
496 char *savedDetails[MAX_VARIATIONS];
497 ChessMove savedResult[MAX_VARIATIONS];
499 void PushTail P((int firstMove, int lastMove));
500 Boolean PopTail P((Boolean annotate));
501 void PushInner P((int firstMove, int lastMove));
502 void PopInner P((Boolean annotate));
503 void CleanupTail P((void));
505 ChessSquare FIDEArray[2][BOARD_FILES] = {
506 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
507 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
508 { BlackRook, BlackKnight, BlackBishop, BlackQueen,
509 BlackKing, BlackBishop, BlackKnight, BlackRook }
512 ChessSquare twoKingsArray[2][BOARD_FILES] = {
513 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
514 WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
515 { BlackRook, BlackKnight, BlackBishop, BlackQueen,
516 BlackKing, BlackKing, BlackKnight, BlackRook }
519 ChessSquare KnightmateArray[2][BOARD_FILES] = {
520 { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
521 WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
522 { BlackRook, BlackMan, BlackBishop, BlackQueen,
523 BlackUnicorn, BlackBishop, BlackMan, BlackRook }
526 ChessSquare SpartanArray[2][BOARD_FILES] = {
527 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
528 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
529 { BlackAlfil, BlackMarshall, BlackKing, BlackDragon,
530 BlackDragon, BlackKing, BlackAngel, BlackAlfil }
533 ChessSquare fairyArray[2][BOARD_FILES] = { /* [HGM] Queen side differs from King side */
534 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
535 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
536 { BlackCardinal, BlackAlfil, BlackMarshall, BlackAngel,
537 BlackKing, BlackMarshall, BlackAlfil, BlackCardinal }
540 ChessSquare ShatranjArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
541 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
542 WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
543 { BlackRook, BlackKnight, BlackAlfil, BlackKing,
544 BlackFerz, BlackAlfil, BlackKnight, BlackRook }
547 ChessSquare makrukArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
548 { WhiteRook, WhiteKnight, WhiteMan, WhiteKing,
549 WhiteFerz, WhiteMan, WhiteKnight, WhiteRook },
550 { BlackRook, BlackKnight, BlackMan, BlackFerz,
551 BlackKing, BlackMan, BlackKnight, BlackRook }
555 #if (BOARD_FILES>=10)
556 ChessSquare ShogiArray[2][BOARD_FILES] = {
557 { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
558 WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
559 { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
560 BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
563 ChessSquare XiangqiArray[2][BOARD_FILES] = {
564 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
565 WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
566 { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
567 BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
570 ChessSquare CapablancaArray[2][BOARD_FILES] = {
571 { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
572 WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
573 { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
574 BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
577 ChessSquare GreatArray[2][BOARD_FILES] = {
578 { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
579 WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
580 { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
581 BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
584 ChessSquare JanusArray[2][BOARD_FILES] = {
585 { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
586 WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
587 { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
588 BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
591 ChessSquare GrandArray[2][BOARD_FILES] = {
592 { EmptySquare, WhiteKnight, WhiteBishop, WhiteQueen, WhiteKing,
593 WhiteMarshall, WhiteAngel, WhiteBishop, WhiteKnight, EmptySquare },
594 { EmptySquare, BlackKnight, BlackBishop, BlackQueen, BlackKing,
595 BlackMarshall, BlackAngel, BlackBishop, BlackKnight, EmptySquare }
599 ChessSquare GothicArray[2][BOARD_FILES] = {
600 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
601 WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
602 { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
603 BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
606 #define GothicArray CapablancaArray
610 ChessSquare FalconArray[2][BOARD_FILES] = {
611 { WhiteRook, WhiteKnight, WhiteBishop, WhiteFalcon, WhiteQueen,
612 WhiteKing, WhiteFalcon, WhiteBishop, WhiteKnight, WhiteRook },
613 { BlackRook, BlackKnight, BlackBishop, BlackFalcon, BlackQueen,
614 BlackKing, BlackFalcon, BlackBishop, BlackKnight, BlackRook }
617 #define FalconArray CapablancaArray
620 #else // !(BOARD_FILES>=10)
621 #define XiangqiPosition FIDEArray
622 #define CapablancaArray FIDEArray
623 #define GothicArray FIDEArray
624 #define GreatArray FIDEArray
625 #endif // !(BOARD_FILES>=10)
627 #if (BOARD_FILES>=12)
628 ChessSquare CourierArray[2][BOARD_FILES] = {
629 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
630 WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
631 { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
632 BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
634 #else // !(BOARD_FILES>=12)
635 #define CourierArray CapablancaArray
636 #endif // !(BOARD_FILES>=12)
639 Board initialPosition;
642 /* Convert str to a rating. Checks for special cases of "----",
644 "++++", etc. Also strips ()'s */
646 string_to_rating (char *str)
648 while(*str && !isdigit(*str)) ++str;
650 return 0; /* One of the special "no rating" cases */
658 /* Init programStats */
659 programStats.movelist[0] = 0;
660 programStats.depth = 0;
661 programStats.nr_moves = 0;
662 programStats.moves_left = 0;
663 programStats.nodes = 0;
664 programStats.time = -1; // [HGM] PGNtime: make invalid to recognize engine output
665 programStats.score = 0;
666 programStats.got_only_move = 0;
667 programStats.got_fail = 0;
668 programStats.line_is_book = 0;
673 { // [HGM] moved some code here from InitBackend1 that has to be done after both engines have contributed their settings
674 if (appData.firstPlaysBlack) {
675 first.twoMachinesColor = "black\n";
676 second.twoMachinesColor = "white\n";
678 first.twoMachinesColor = "white\n";
679 second.twoMachinesColor = "black\n";
682 first.other = &second;
683 second.other = &first;
686 if(appData.timeOddsMode) {
687 norm = appData.timeOdds[0];
688 if(norm > appData.timeOdds[1]) norm = appData.timeOdds[1];
690 first.timeOdds = appData.timeOdds[0]/norm;
691 second.timeOdds = appData.timeOdds[1]/norm;
694 if(programVersion) free(programVersion);
695 if (appData.noChessProgram) {
696 programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
697 sprintf(programVersion, "%s", PACKAGE_STRING);
699 /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
700 programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
701 sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
706 UnloadEngine (ChessProgramState *cps)
708 /* Kill off first chess program */
709 if (cps->isr != NULL)
710 RemoveInputSource(cps->isr);
713 if (cps->pr != NoProc) {
715 DoSleep( appData.delayBeforeQuit );
716 SendToProgram("quit\n", cps);
717 DoSleep( appData.delayAfterQuit );
718 DestroyChildProcess(cps->pr, cps->useSigterm);
721 if(appData.debugMode) fprintf(debugFP, "Unload %s\n", cps->which);
725 ClearOptions (ChessProgramState *cps)
728 cps->nrOptions = cps->comboCnt = 0;
729 for(i=0; i<MAX_OPTIONS; i++) {
730 cps->option[i].min = cps->option[i].max = cps->option[i].value = 0;
731 cps->option[i].textValue = 0;
735 char *engineNames[] = {
741 InitEngine (ChessProgramState *cps, int n)
742 { // [HGM] all engine initialiation put in a function that does one engine
746 cps->which = engineNames[n];
747 cps->maybeThinking = FALSE;
751 cps->sendDrawOffers = 1;
753 cps->program = appData.chessProgram[n];
754 cps->host = appData.host[n];
755 cps->dir = appData.directory[n];
756 cps->initString = appData.engInitString[n];
757 cps->computerString = appData.computerString[n];
758 cps->useSigint = TRUE;
759 cps->useSigterm = TRUE;
760 cps->reuse = appData.reuse[n];
761 cps->nps = appData.NPS[n]; // [HGM] nps: copy nodes per second
762 cps->useSetboard = FALSE;
764 cps->usePing = FALSE;
767 cps->usePlayother = FALSE;
768 cps->useColors = TRUE;
769 cps->useUsermove = FALSE;
770 cps->sendICS = FALSE;
771 cps->sendName = appData.icsActive;
772 cps->sdKludge = FALSE;
773 cps->stKludge = FALSE;
774 TidyProgramName(cps->program, cps->host, cps->tidy);
776 safeStrCpy(cps->variants, appData.variant, MSG_SIZ);
777 cps->analysisSupport = 2; /* detect */
778 cps->analyzing = FALSE;
779 cps->initDone = FALSE;
781 /* New features added by Tord: */
782 cps->useFEN960 = FALSE;
783 cps->useOOCastle = TRUE;
784 /* End of new features added by Tord. */
785 cps->fenOverride = appData.fenOverride[n];
787 /* [HGM] time odds: set factor for each machine */
788 cps->timeOdds = appData.timeOdds[n];
790 /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
791 cps->accumulateTC = appData.accumulateTC[n];
792 cps->maxNrOfSessions = 1;
797 cps->supportsNPS = UNKNOWN;
798 cps->memSize = FALSE;
799 cps->maxCores = FALSE;
800 cps->egtFormats[0] = NULLCHAR;
803 cps->optionSettings = appData.engOptions[n];
805 cps->scoreIsAbsolute = appData.scoreIsAbsolute[n]; /* [AS] */
806 cps->isUCI = appData.isUCI[n]; /* [AS] */
807 cps->hasOwnBookUCI = appData.hasOwnBookUCI[n]; /* [AS] */
809 if (appData.protocolVersion[n] > PROTOVER
810 || appData.protocolVersion[n] < 1)
815 len = snprintf(buf, MSG_SIZ, _("protocol version %d not supported"),
816 appData.protocolVersion[n]);
817 if( (len >= MSG_SIZ) && appData.debugMode )
818 fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
820 DisplayFatalError(buf, 0, 2);
824 cps->protocolVersion = appData.protocolVersion[n];
827 InitEngineUCI( installDir, cps ); // [HGM] moved here from winboard.c, to make available in xboard
828 ParseFeatures(appData.featureDefaults, cps);
831 ChessProgramState *savCps;
837 if(WaitForEngine(savCps, LoadEngine)) return;
838 CommonEngineInit(); // recalculate time odds
839 if(gameInfo.variant != StringToVariant(appData.variant)) {
840 // we changed variant when loading the engine; this forces us to reset
841 Reset(TRUE, savCps != &first);
842 EditGameEvent(); // for consistency with other path, as Reset changes mode
844 InitChessProgram(savCps, FALSE);
845 SendToProgram("force\n", savCps);
846 DisplayMessage("", "");
847 if (startedFromSetupPosition) SendBoard(savCps, backwardMostMove);
848 for (i = backwardMostMove; i < forwardMostMove; i++) SendMoveToProgram(i, savCps);
854 ReplaceEngine (ChessProgramState *cps, int n)
858 appData.noChessProgram = FALSE;
859 appData.clockMode = TRUE;
862 if(n) return; // only startup first engine immediately; second can wait
863 savCps = cps; // parameter to LoadEngine passed as globals, to allow scheduled calling :-(
867 extern char *engineName, *engineDir, *engineChoice, *engineLine, *nickName, *params;
868 extern Boolean isUCI, hasBook, storeVariant, v1, addToList, useNick;
870 static char resetOptions[] =
871 "-reuse -firstIsUCI false -firstHasOwnBookUCI true -firstTimeOdds 1 "
872 "-firstInitString \"" INIT_STRING "\" -firstComputerString \"" COMPUTER_STRING "\" "
873 "-firstOptions \"\" -firstNPS -1 -fn \"\"";
876 Load (ChessProgramState *cps, int i)
878 char *p, *q, buf[MSG_SIZ], command[MSG_SIZ], buf2[MSG_SIZ];
879 if(engineLine && engineLine[0]) { // an engine was selected from the combo box
880 snprintf(buf, MSG_SIZ, "-fcp %s", engineLine);
881 SwapEngines(i); // kludge to parse -f* / -first* like it is -s* / -second*
882 ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL; appData.pvSAN[0] = FALSE;
883 ParseArgsFromString(buf);
885 ReplaceEngine(cps, i);
889 while(q = strchr(p, SLASH)) p = q+1;
890 if(*p== NULLCHAR) { DisplayError(_("You did not specify the engine executable"), 0); return; }
891 if(engineDir[0] != NULLCHAR)
892 appData.directory[i] = engineDir;
893 else if(p != engineName) { // derive directory from engine path, when not given
895 appData.directory[i] = strdup(engineName);
897 } else appData.directory[i] = ".";
899 if(strchr(p, ' ') && !strchr(p, '"')) snprintf(buf2, MSG_SIZ, "\"%s\"", p), p = buf2; // quote if it contains spaces
900 snprintf(command, MSG_SIZ, "%s %s", p, params);
903 appData.chessProgram[i] = strdup(p);
904 appData.isUCI[i] = isUCI;
905 appData.protocolVersion[i] = v1 ? 1 : PROTOVER;
906 appData.hasOwnBookUCI[i] = hasBook;
907 if(!nickName[0]) useNick = FALSE;
908 if(useNick) ASSIGN(appData.pgnName[i], nickName);
912 q = firstChessProgramNames;
913 if(nickName[0]) snprintf(buf, MSG_SIZ, "\"%s\" -fcp ", nickName); else buf[0] = NULLCHAR;
914 quote = strchr(p, '"') ? '\'' : '"'; // use single quotes around engine command if it contains double quotes
915 snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), "%c%s%c -fd \"%s\"%s%s%s%s%s%s%s%s\n",
916 quote, p, quote, appData.directory[i],
917 useNick ? " -fn \"" : "",
918 useNick ? nickName : "",
920 v1 ? " -firstProtocolVersion 1" : "",
921 hasBook ? "" : " -fNoOwnBookUCI",
922 isUCI ? (isUCI == TRUE ? " -fUCI" : gameInfo.variant == VariantShogi ? " -fUSI" : " -fUCCI") : "",
923 storeVariant ? " -variant " : "",
924 storeVariant ? VariantName(gameInfo.variant) : "");
925 firstChessProgramNames = malloc(len = strlen(q) + strlen(buf) + 1);
926 snprintf(firstChessProgramNames, len, "%s%s", q, buf);
929 ReplaceEngine(cps, i);
935 int matched, min, sec;
937 * Parse timeControl resource
939 if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
940 appData.movesPerSession)) {
942 snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
943 DisplayFatalError(buf, 0, 2);
947 * Parse searchTime resource
949 if (*appData.searchTime != NULLCHAR) {
950 matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
952 searchTime = min * 60;
953 } else if (matched == 2) {
954 searchTime = min * 60 + sec;
957 snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
958 DisplayFatalError(buf, 0, 2);
967 ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
968 startVariant = StringToVariant(appData.variant); // [HGM] nicks: remember original variant
970 GetTimeMark(&programStartTime);
971 srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
972 appData.seedBase = random() + (random()<<15);
973 pauseStart = programStartTime; pauseStart.sec -= 100; // [HGM] matchpause: fake a pause that has long since ended
976 programStats.ok_to_send = 1;
977 programStats.seen_stat = 0;
980 * Initialize game list
986 * Internet chess server status
988 if (appData.icsActive) {
989 appData.matchMode = FALSE;
990 appData.matchGames = 0;
992 appData.noChessProgram = !appData.zippyPlay;
994 appData.zippyPlay = FALSE;
995 appData.zippyTalk = FALSE;
996 appData.noChessProgram = TRUE;
998 if (*appData.icsHelper != NULLCHAR) {
999 appData.useTelnet = TRUE;
1000 appData.telnetProgram = appData.icsHelper;
1003 appData.zippyTalk = appData.zippyPlay = FALSE;
1006 /* [AS] Initialize pv info list [HGM] and game state */
1010 for( i=0; i<=framePtr; i++ ) {
1011 pvInfoList[i].depth = -1;
1012 boards[i][EP_STATUS] = EP_NONE;
1013 for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
1019 /* [AS] Adjudication threshold */
1020 adjudicateLossThreshold = appData.adjudicateLossThreshold;
1022 InitEngine(&first, 0);
1023 InitEngine(&second, 1);
1026 pairing.which = "pairing"; // pairing engine
1027 pairing.pr = NoProc;
1029 pairing.program = appData.pairingEngine;
1030 pairing.host = "localhost";
1033 if (appData.icsActive) {
1034 appData.clockMode = TRUE; /* changes dynamically in ICS mode */
1035 } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
1036 appData.clockMode = FALSE;
1037 first.sendTime = second.sendTime = 0;
1041 /* Override some settings from environment variables, for backward
1042 compatibility. Unfortunately it's not feasible to have the env
1043 vars just set defaults, at least in xboard. Ugh.
1045 if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
1050 if (!appData.icsActive) {
1054 /* Check for variants that are supported only in ICS mode,
1055 or not at all. Some that are accepted here nevertheless
1056 have bugs; see comments below.
1058 VariantClass variant = StringToVariant(appData.variant);
1060 case VariantBughouse: /* need four players and two boards */
1061 case VariantKriegspiel: /* need to hide pieces and move details */
1062 /* case VariantFischeRandom: (Fabien: moved below) */
1063 len = snprintf(buf,MSG_SIZ, _("Variant %s supported only in ICS mode"), appData.variant);
1064 if( (len >= MSG_SIZ) && appData.debugMode )
1065 fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1067 DisplayFatalError(buf, 0, 2);
1070 case VariantUnknown:
1071 case VariantLoadable:
1081 len = snprintf(buf, MSG_SIZ, _("Unknown variant name %s"), appData.variant);
1082 if( (len >= MSG_SIZ) && appData.debugMode )
1083 fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1085 DisplayFatalError(buf, 0, 2);
1088 case VariantXiangqi: /* [HGM] repetition rules not implemented */
1089 case VariantFairy: /* [HGM] TestLegality definitely off! */
1090 case VariantGothic: /* [HGM] should work */
1091 case VariantCapablanca: /* [HGM] should work */
1092 case VariantCourier: /* [HGM] initial forced moves not implemented */
1093 case VariantShogi: /* [HGM] could still mate with pawn drop */
1094 case VariantKnightmate: /* [HGM] should work */
1095 case VariantCylinder: /* [HGM] untested */
1096 case VariantFalcon: /* [HGM] untested */
1097 case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
1098 offboard interposition not understood */
1099 case VariantNormal: /* definitely works! */
1100 case VariantWildCastle: /* pieces not automatically shuffled */
1101 case VariantNoCastle: /* pieces not automatically shuffled */
1102 case VariantFischeRandom: /* [HGM] works and shuffles pieces */
1103 case VariantLosers: /* should work except for win condition,
1104 and doesn't know captures are mandatory */
1105 case VariantSuicide: /* should work except for win condition,
1106 and doesn't know captures are mandatory */
1107 case VariantGiveaway: /* should work except for win condition,
1108 and doesn't know captures are mandatory */
1109 case VariantTwoKings: /* should work */
1110 case VariantAtomic: /* should work except for win condition */
1111 case Variant3Check: /* should work except for win condition */
1112 case VariantShatranj: /* should work except for all win conditions */
1113 case VariantMakruk: /* should work except for draw countdown */
1114 case VariantBerolina: /* might work if TestLegality is off */
1115 case VariantCapaRandom: /* should work */
1116 case VariantJanus: /* should work */
1117 case VariantSuper: /* experimental */
1118 case VariantGreat: /* experimental, requires legality testing to be off */
1119 case VariantSChess: /* S-Chess, should work */
1120 case VariantGrand: /* should work */
1121 case VariantSpartan: /* should work */
1129 NextIntegerFromString (char ** str, long * value)
1134 while( *s == ' ' || *s == '\t' ) {
1140 if( *s >= '0' && *s <= '9' ) {
1141 while( *s >= '0' && *s <= '9' ) {
1142 *value = *value * 10 + (*s - '0');
1155 NextTimeControlFromString (char ** str, long * value)
1158 int result = NextIntegerFromString( str, &temp );
1161 *value = temp * 60; /* Minutes */
1162 if( **str == ':' ) {
1164 result = NextIntegerFromString( str, &temp );
1165 *value += temp; /* Seconds */
1173 NextSessionFromString (char ** str, int *moves, long * tc, long *inc, int *incType)
1174 { /* [HGM] routine added to read '+moves/time' for secondary time control. */
1175 int result = -1, type = 0; long temp, temp2;
1177 if(**str != ':') return -1; // old params remain in force!
1179 if(**str == '*') type = *(*str)++, temp = 0; // sandclock TC
1180 if( NextIntegerFromString( str, &temp ) ) return -1;
1181 if(type) { *moves = 0; *tc = temp * 500; *inc = temp * 1000; *incType = '*'; return 0; }
1184 /* time only: incremental or sudden-death time control */
1185 if(**str == '+') { /* increment follows; read it */
1187 if(**str == '!') type = *(*str)++; // Bronstein TC
1188 if(result = NextIntegerFromString( str, &temp2)) return -1;
1189 *inc = temp2 * 1000;
1190 if(**str == '.') { // read fraction of increment
1191 char *start = ++(*str);
1192 if(result = NextIntegerFromString( str, &temp2)) return -1;
1194 while(start++ < *str) temp2 /= 10;
1198 *moves = 0; *tc = temp * 1000; *incType = type;
1202 (*str)++; /* classical time control */
1203 result = NextIntegerFromString( str, &temp2); // NOTE: already converted to seconds by ParseTimeControl()
1215 GetTimeQuota (int movenr, int lastUsed, char *tcString)
1216 { /* [HGM] get time to add from the multi-session time-control string */
1217 int incType, moves=1; /* kludge to force reading of first session */
1218 long time, increment;
1221 if(!*s) return 0; // empty TC string means we ran out of the last sudden-death version
1222 if(appData.debugMode) fprintf(debugFP, "TC string = '%s'\n", tcString);
1224 if(moves) NextSessionFromString(&s, &moves, &time, &increment, &incType);
1225 nextSession = s; suddenDeath = moves == 0 && increment == 0;
1226 if(appData.debugMode) fprintf(debugFP, "mps=%d tc=%d inc=%d\n", moves, (int) time, (int) increment);
1227 if(movenr == -1) return time; /* last move before new session */
1228 if(incType == '*') increment = 0; else // for sandclock, time is added while not thinking
1229 if(incType == '!' && lastUsed < increment) increment = lastUsed;
1230 if(!moves) return increment; /* current session is incremental */
1231 if(movenr >= 0) movenr -= moves; /* we already finished this session */
1232 } while(movenr >= -1); /* try again for next session */
1234 return 0; // no new time quota on this move
1238 ParseTimeControl (char *tc, float ti, int mps)
1242 char buf[MSG_SIZ], buf2[MSG_SIZ], *mytc = tc;
1245 if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1246 if(!strchr(tc, '+') && !strchr(tc, '/') && sscanf(tc, "%d:%d", &min, &sec) >= 1)
1247 sprintf(mytc=buf2, "%d", 60*min+sec); // convert 'classical' min:sec tc string to seconds
1251 snprintf(buf, MSG_SIZ, ":%d/%s+%g", mps, mytc, ti);
1253 snprintf(buf, MSG_SIZ, ":%s+%g", mytc, ti);
1256 snprintf(buf, MSG_SIZ, ":%d/%s", mps, mytc);
1258 snprintf(buf, MSG_SIZ, ":%s", mytc);
1260 fullTimeControlString = StrSave(buf); // this should now be in PGN format
1262 if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1267 /* Parse second time control */
1270 if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1278 timeControl_2 = tc2 * 1000;
1288 timeControl = tc1 * 1000;
1291 timeIncrement = ti * 1000; /* convert to ms */
1292 movesPerSession = 0;
1295 movesPerSession = mps;
1303 if (appData.debugMode) {
1304 fprintf(debugFP, "%s\n", programVersion);
1307 set_cont_sequence(appData.wrapContSeq);
1308 if (appData.matchGames > 0) {
1309 appData.matchMode = TRUE;
1310 } else if (appData.matchMode) {
1311 appData.matchGames = 1;
1313 if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1314 appData.matchGames = appData.sameColorGames;
1315 if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1316 if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1317 if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1320 if (appData.noChessProgram || first.protocolVersion == 1) {
1323 /* kludge: allow timeout for initial "feature" commands */
1325 DisplayMessage("", _("Starting chess program"));
1326 ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1331 CalculateIndex (int index, int gameNr)
1332 { // [HGM] autoinc: absolute way to determine load index from game number (taking auto-inc and rewind into account)
1334 if(index > 0) return index; // fixed nmber
1335 if(index == 0) return 1;
1336 res = (index == -1 ? gameNr : (gameNr-1)/2 + 1); // autoinc
1337 if(appData.rewindIndex > 0) res = (res-1) % appData.rewindIndex + 1; // rewind
1342 LoadGameOrPosition (int gameNr)
1343 { // [HGM] taken out of MatchEvent and NextMatchGame (to combine it)
1344 if (*appData.loadGameFile != NULLCHAR) {
1345 if (!LoadGameFromFile(appData.loadGameFile,
1346 CalculateIndex(appData.loadGameIndex, gameNr),
1347 appData.loadGameFile, FALSE)) {
1348 DisplayFatalError(_("Bad game file"), 0, 1);
1351 } else if (*appData.loadPositionFile != NULLCHAR) {
1352 if (!LoadPositionFromFile(appData.loadPositionFile,
1353 CalculateIndex(appData.loadPositionIndex, gameNr),
1354 appData.loadPositionFile)) {
1355 DisplayFatalError(_("Bad position file"), 0, 1);
1363 ReserveGame (int gameNr, char resChar)
1365 FILE *tf = fopen(appData.tourneyFile, "r+");
1366 char *p, *q, c, buf[MSG_SIZ];
1367 if(tf == NULL) { nextGame = appData.matchGames + 1; return; } // kludge to terminate match
1368 safeStrCpy(buf, lastMsg, MSG_SIZ);
1369 DisplayMessage(_("Pick new game"), "");
1370 flock(fileno(tf), LOCK_EX); // lock the tourney file while we are messing with it
1371 ParseArgsFromFile(tf);
1372 p = q = appData.results;
1373 if(appData.debugMode) {
1374 char *r = appData.participants;
1375 fprintf(debugFP, "results = '%s'\n", p);
1376 while(*r) fprintf(debugFP, *r >= ' ' ? "%c" : "\\%03o", *r), r++;
1377 fprintf(debugFP, "\n");
1379 while(*q && *q != ' ') q++; // get first un-played game (could be beyond end!)
1381 q = malloc(strlen(p) + 2); // could be arbitrary long, but allow to extend by one!
1382 safeStrCpy(q, p, strlen(p) + 2);
1383 if(gameNr >= 0) q[gameNr] = resChar; // replace '*' with result
1384 if(appData.debugMode) fprintf(debugFP, "pick next game from '%s': %d\n", q, nextGame);
1385 if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch) { // reserve next game if tourney not yet done
1386 if(q[nextGame] == NULLCHAR) q[nextGame+1] = NULLCHAR; // append one char
1389 fseek(tf, -(strlen(p)+4), SEEK_END);
1391 if(c != '"') // depending on DOS or Unix line endings we can be one off
1392 fseek(tf, -(strlen(p)+2), SEEK_END);
1393 else fseek(tf, -(strlen(p)+3), SEEK_END);
1394 fprintf(tf, "%s\"\n", q); fclose(tf); // update, and flush by closing
1395 DisplayMessage(buf, "");
1396 free(p); appData.results = q;
1397 if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch &&
1398 (gameNr < 0 || nextGame / appData.defaultMatchGames != gameNr / appData.defaultMatchGames)) {
1399 UnloadEngine(&first); // next game belongs to other pairing;
1400 UnloadEngine(&second); // already unload the engines, so TwoMachinesEvent will load new ones.
1405 MatchEvent (int mode)
1406 { // [HGM] moved out of InitBackend3, to make it callable when match starts through menu
1408 if(matchMode) { // already in match mode: switch it off
1410 if(!appData.tourneyFile[0]) appData.matchGames = matchGame; // kludge to let match terminate after next game.
1413 // if(gameMode != BeginningOfGame) {
1414 // DisplayError(_("You can only start a match from the initial position."), 0);
1418 if(mode == 2) appData.matchGames = appData.defaultMatchGames;
1419 /* Set up machine vs. machine match */
1421 NextTourneyGame(-1, &dummy); // sets appData.matchGames if this is tourney, to make sure ReserveGame knows it
1422 if(appData.tourneyFile[0]) {
1424 if(nextGame > appData.matchGames) {
1426 if(strchr(appData.results, '*') == NULL) {
1428 appData.tourneyCycles++;
1429 if(f = WriteTourneyFile(appData.results, NULL)) { // make a tourney file with increased number of cycles
1431 NextTourneyGame(-1, &dummy);
1433 if(nextGame <= appData.matchGames) {
1434 DisplayNote(_("You restarted an already completed tourney\nOne more cycle will now be added to it\nGames commence in 10 sec"));
1436 ScheduleDelayedEvent(NextMatchGame, 10000);
1441 snprintf(buf, MSG_SIZ, _("All games in tourney '%s' are already played or playing"), appData.tourneyFile);
1442 DisplayError(buf, 0);
1443 appData.tourneyFile[0] = 0;
1447 if (appData.noChessProgram) { // [HGM] in tourney engines are loaded automatically
1448 DisplayFatalError(_("Can't have a match with no chess programs"),
1453 matchGame = roundNr = 1;
1454 first.matchWins = second.matchWins = 0; // [HGM] match: needed in later matches
1459 InitBackEnd3 P((void))
1461 GameMode initialMode;
1465 InitChessProgram(&first, startedFromSetupPosition);
1467 if(!appData.noChessProgram) { /* [HGM] tidy: redo program version to use name from myname feature */
1468 free(programVersion);
1469 programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1470 sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1473 if (appData.icsActive) {
1475 /* [DM] Make a console window if needed [HGM] merged ifs */
1481 if (*appData.icsCommPort != NULLCHAR)
1482 len = snprintf(buf, MSG_SIZ, _("Could not open comm port %s"),
1483 appData.icsCommPort);
1485 len = snprintf(buf, MSG_SIZ, _("Could not connect to host %s, port %s"),
1486 appData.icsHost, appData.icsPort);
1488 if( (len >= MSG_SIZ) && appData.debugMode )
1489 fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1491 DisplayFatalError(buf, err, 1);
1496 AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1498 AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1499 if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1500 ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1501 } else if (appData.noChessProgram) {
1507 if (*appData.cmailGameName != NULLCHAR) {
1509 OpenLoopback(&cmailPR);
1511 AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1515 DisplayMessage("", "");
1516 if (StrCaseCmp(appData.initialMode, "") == 0) {
1517 initialMode = BeginningOfGame;
1518 if(!appData.icsActive && appData.noChessProgram) { // [HGM] could be fall-back
1519 gameMode = MachinePlaysBlack; // "Machine Black" might have been implicitly highlighted
1520 ModeHighlight(); // make sure XBoard knows it is highlighted, so it will un-highlight it
1521 gameMode = BeginningOfGame; // in case BeginningOfGame now means "Edit Position"
1524 } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1525 initialMode = TwoMachinesPlay;
1526 } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1527 initialMode = AnalyzeFile;
1528 } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1529 initialMode = AnalyzeMode;
1530 } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1531 initialMode = MachinePlaysWhite;
1532 } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1533 initialMode = MachinePlaysBlack;
1534 } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1535 initialMode = EditGame;
1536 } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1537 initialMode = EditPosition;
1538 } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1539 initialMode = Training;
1541 len = snprintf(buf, MSG_SIZ, _("Unknown initialMode %s"), appData.initialMode);
1542 if( (len >= MSG_SIZ) && appData.debugMode )
1543 fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1545 DisplayFatalError(buf, 0, 2);
1549 if (appData.matchMode) {
1550 if(appData.tourneyFile[0]) { // start tourney from command line
1552 if(f = fopen(appData.tourneyFile, "r")) {
1553 ParseArgsFromFile(f); // make sure tourney parmeters re known
1555 appData.clockMode = TRUE;
1557 } else appData.tourneyFile[0] = NULLCHAR; // for now ignore bad tourney file
1560 } else if (*appData.cmailGameName != NULLCHAR) {
1561 /* Set up cmail mode */
1562 ReloadCmailMsgEvent(TRUE);
1564 /* Set up other modes */
1565 if (initialMode == AnalyzeFile) {
1566 if (*appData.loadGameFile == NULLCHAR) {
1567 DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1571 if (*appData.loadGameFile != NULLCHAR) {
1572 (void) LoadGameFromFile(appData.loadGameFile,
1573 appData.loadGameIndex,
1574 appData.loadGameFile, TRUE);
1575 } else if (*appData.loadPositionFile != NULLCHAR) {
1576 (void) LoadPositionFromFile(appData.loadPositionFile,
1577 appData.loadPositionIndex,
1578 appData.loadPositionFile);
1579 /* [HGM] try to make self-starting even after FEN load */
1580 /* to allow automatic setup of fairy variants with wtm */
1581 if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1582 gameMode = BeginningOfGame;
1583 setboardSpoiledMachineBlack = 1;
1585 /* [HGM] loadPos: make that every new game uses the setup */
1586 /* from file as long as we do not switch variant */
1587 if(!blackPlaysFirst) {
1588 startedFromPositionFile = TRUE;
1589 CopyBoard(filePosition, boards[0]);
1592 if (initialMode == AnalyzeMode) {
1593 if (appData.noChessProgram) {
1594 DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1597 if (appData.icsActive) {
1598 DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1602 } else if (initialMode == AnalyzeFile) {
1603 appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1604 ShowThinkingEvent();
1606 AnalysisPeriodicEvent(1);
1607 } else if (initialMode == MachinePlaysWhite) {
1608 if (appData.noChessProgram) {
1609 DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1613 if (appData.icsActive) {
1614 DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1618 MachineWhiteEvent();
1619 } else if (initialMode == MachinePlaysBlack) {
1620 if (appData.noChessProgram) {
1621 DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1625 if (appData.icsActive) {
1626 DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1630 MachineBlackEvent();
1631 } else if (initialMode == TwoMachinesPlay) {
1632 if (appData.noChessProgram) {
1633 DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1637 if (appData.icsActive) {
1638 DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1643 } else if (initialMode == EditGame) {
1645 } else if (initialMode == EditPosition) {
1646 EditPositionEvent();
1647 } else if (initialMode == Training) {
1648 if (*appData.loadGameFile == NULLCHAR) {
1649 DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1658 HistorySet (char movelist[][2*MOVE_LEN], int first, int last, int current)
1660 DisplayBook(current+1);
1662 MoveHistorySet( movelist, first, last, current, pvInfoList );
1664 EvalGraphSet( first, last, current, pvInfoList );
1666 MakeEngineOutputTitle();
1670 * Establish will establish a contact to a remote host.port.
1671 * Sets icsPR to a ProcRef for a process (or pseudo-process)
1672 * used to talk to the host.
1673 * Returns 0 if okay, error code if not.
1680 if (*appData.icsCommPort != NULLCHAR) {
1681 /* Talk to the host through a serial comm port */
1682 return OpenCommPort(appData.icsCommPort, &icsPR);
1684 } else if (*appData.gateway != NULLCHAR) {
1685 if (*appData.remoteShell == NULLCHAR) {
1686 /* Use the rcmd protocol to run telnet program on a gateway host */
1687 snprintf(buf, sizeof(buf), "%s %s %s",
1688 appData.telnetProgram, appData.icsHost, appData.icsPort);
1689 return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1692 /* Use the rsh program to run telnet program on a gateway host */
1693 if (*appData.remoteUser == NULLCHAR) {
1694 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1695 appData.gateway, appData.telnetProgram,
1696 appData.icsHost, appData.icsPort);
1698 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1699 appData.remoteShell, appData.gateway,
1700 appData.remoteUser, appData.telnetProgram,
1701 appData.icsHost, appData.icsPort);
1703 return StartChildProcess(buf, "", &icsPR);
1706 } else if (appData.useTelnet) {
1707 return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1710 /* TCP socket interface differs somewhat between
1711 Unix and NT; handle details in the front end.
1713 return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1718 EscapeExpand (char *p, char *q)
1719 { // [HGM] initstring: routine to shape up string arguments
1720 while(*p++ = *q++) if(p[-1] == '\\')
1722 case 'n': p[-1] = '\n'; break;
1723 case 'r': p[-1] = '\r'; break;
1724 case 't': p[-1] = '\t'; break;
1725 case '\\': p[-1] = '\\'; break;
1726 case 0: *p = 0; return;
1727 default: p[-1] = q[-1]; break;
1732 show_bytes (FILE *fp, char *buf, int count)
1735 if (*buf < 040 || *(unsigned char *) buf > 0177) {
1736 fprintf(fp, "\\%03o", *buf & 0xff);
1745 /* Returns an errno value */
1747 OutputMaybeTelnet (ProcRef pr, char *message, int count, int *outError)
1749 char buf[8192], *p, *q, *buflim;
1750 int left, newcount, outcount;
1752 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1753 *appData.gateway != NULLCHAR) {
1754 if (appData.debugMode) {
1755 fprintf(debugFP, ">ICS: ");
1756 show_bytes(debugFP, message, count);
1757 fprintf(debugFP, "\n");
1759 return OutputToProcess(pr, message, count, outError);
1762 buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1769 if (appData.debugMode) {
1770 fprintf(debugFP, ">ICS: ");
1771 show_bytes(debugFP, buf, newcount);
1772 fprintf(debugFP, "\n");
1774 outcount = OutputToProcess(pr, buf, newcount, outError);
1775 if (outcount < newcount) return -1; /* to be sure */
1782 } else if (((unsigned char) *p) == TN_IAC) {
1783 *q++ = (char) TN_IAC;
1790 if (appData.debugMode) {
1791 fprintf(debugFP, ">ICS: ");
1792 show_bytes(debugFP, buf, newcount);
1793 fprintf(debugFP, "\n");
1795 outcount = OutputToProcess(pr, buf, newcount, outError);
1796 if (outcount < newcount) return -1; /* to be sure */
1801 read_from_player (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
1803 int outError, outCount;
1804 static int gotEof = 0;
1806 /* Pass data read from player on to ICS */
1809 outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1810 if (outCount < count) {
1811 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1813 } else if (count < 0) {
1814 RemoveInputSource(isr);
1815 DisplayFatalError(_("Error reading from keyboard"), error, 1);
1816 } else if (gotEof++ > 0) {
1817 RemoveInputSource(isr);
1818 DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1824 { // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1825 if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1826 connectionAlive = FALSE; // only sticks if no response to 'date' command.
1827 SendToICS("date\n");
1828 if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1831 /* added routine for printf style output to ics */
1833 ics_printf (char *format, ...)
1835 char buffer[MSG_SIZ];
1838 va_start(args, format);
1839 vsnprintf(buffer, sizeof(buffer), format, args);
1840 buffer[sizeof(buffer)-1] = '\0';
1848 int count, outCount, outError;
1850 if (icsPR == NoProc) return;
1853 outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1854 if (outCount < count) {
1855 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1859 /* This is used for sending logon scripts to the ICS. Sending
1860 without a delay causes problems when using timestamp on ICC
1861 (at least on my machine). */
1863 SendToICSDelayed (char *s, long msdelay)
1865 int count, outCount, outError;
1867 if (icsPR == NoProc) return;
1870 if (appData.debugMode) {
1871 fprintf(debugFP, ">ICS: ");
1872 show_bytes(debugFP, s, count);
1873 fprintf(debugFP, "\n");
1875 outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1877 if (outCount < count) {
1878 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1883 /* Remove all highlighting escape sequences in s
1884 Also deletes any suffix starting with '('
1887 StripHighlightAndTitle (char *s)
1889 static char retbuf[MSG_SIZ];
1892 while (*s != NULLCHAR) {
1893 while (*s == '\033') {
1894 while (*s != NULLCHAR && !isalpha(*s)) s++;
1895 if (*s != NULLCHAR) s++;
1897 while (*s != NULLCHAR && *s != '\033') {
1898 if (*s == '(' || *s == '[') {
1909 /* Remove all highlighting escape sequences in s */
1911 StripHighlight (char *s)
1913 static char retbuf[MSG_SIZ];
1916 while (*s != NULLCHAR) {
1917 while (*s == '\033') {
1918 while (*s != NULLCHAR && !isalpha(*s)) s++;
1919 if (*s != NULLCHAR) s++;
1921 while (*s != NULLCHAR && *s != '\033') {
1929 char *variantNames[] = VARIANT_NAMES;
1931 VariantName (VariantClass v)
1933 return variantNames[v];
1937 /* Identify a variant from the strings the chess servers use or the
1938 PGN Variant tag names we use. */
1940 StringToVariant (char *e)
1944 VariantClass v = VariantNormal;
1945 int i, found = FALSE;
1951 /* [HGM] skip over optional board-size prefixes */
1952 if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1953 sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1954 while( *e++ != '_');
1957 if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
1961 for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
1962 if (StrCaseStr(e, variantNames[i])) {
1963 v = (VariantClass) i;
1970 if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
1971 || StrCaseStr(e, "wild/fr")
1972 || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
1973 v = VariantFischeRandom;
1974 } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
1975 (i = 1, p = StrCaseStr(e, "w"))) {
1977 while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
1984 case 0: /* FICS only, actually */
1986 /* Castling legal even if K starts on d-file */
1987 v = VariantWildCastle;
1992 /* Castling illegal even if K & R happen to start in
1993 normal positions. */
1994 v = VariantNoCastle;
2007 /* Castling legal iff K & R start in normal positions */
2013 /* Special wilds for position setup; unclear what to do here */
2014 v = VariantLoadable;
2017 /* Bizarre ICC game */
2018 v = VariantTwoKings;
2021 v = VariantKriegspiel;
2027 v = VariantFischeRandom;
2030 v = VariantCrazyhouse;
2033 v = VariantBughouse;
2039 /* Not quite the same as FICS suicide! */
2040 v = VariantGiveaway;
2046 v = VariantShatranj;
2049 /* Temporary names for future ICC types. The name *will* change in
2050 the next xboard/WinBoard release after ICC defines it. */
2088 v = VariantCapablanca;
2091 v = VariantKnightmate;
2097 v = VariantCylinder;
2103 v = VariantCapaRandom;
2106 v = VariantBerolina;
2118 /* Found "wild" or "w" in the string but no number;
2119 must assume it's normal chess. */
2123 len = snprintf(buf, MSG_SIZ, _("Unknown wild type %d"), wnum);
2124 if( (len >= MSG_SIZ) && appData.debugMode )
2125 fprintf(debugFP, "StringToVariant: buffer truncated.\n");
2127 DisplayError(buf, 0);
2133 if (appData.debugMode) {
2134 fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
2135 e, wnum, VariantName(v));
2140 static int leftover_start = 0, leftover_len = 0;
2141 char star_match[STAR_MATCH_N][MSG_SIZ];
2143 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
2144 advance *index beyond it, and set leftover_start to the new value of
2145 *index; else return FALSE. If pattern contains the character '*', it
2146 matches any sequence of characters not containing '\r', '\n', or the
2147 character following the '*' (if any), and the matched sequence(s) are
2148 copied into star_match.
2151 looking_at ( char *buf, int *index, char *pattern)
2153 char *bufp = &buf[*index], *patternp = pattern;
2155 char *matchp = star_match[0];
2158 if (*patternp == NULLCHAR) {
2159 *index = leftover_start = bufp - buf;
2163 if (*bufp == NULLCHAR) return FALSE;
2164 if (*patternp == '*') {
2165 if (*bufp == *(patternp + 1)) {
2167 matchp = star_match[++star_count];
2171 } else if (*bufp == '\n' || *bufp == '\r') {
2173 if (*patternp == NULLCHAR)
2178 *matchp++ = *bufp++;
2182 if (*patternp != *bufp) return FALSE;
2189 SendToPlayer (char *data, int length)
2191 int error, outCount;
2192 outCount = OutputToProcess(NoProc, data, length, &error);
2193 if (outCount < length) {
2194 DisplayFatalError(_("Error writing to display"), error, 1);
2199 PackHolding (char packed[], char *holding)
2209 switch (runlength) {
2220 sprintf(q, "%d", runlength);
2232 /* Telnet protocol requests from the front end */
2234 TelnetRequest (unsigned char ddww, unsigned char option)
2236 unsigned char msg[3];
2237 int outCount, outError;
2239 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
2241 if (appData.debugMode) {
2242 char buf1[8], buf2[8], *ddwwStr, *optionStr;
2258 snprintf(buf1,sizeof(buf1)/sizeof(buf1[0]), "%d", ddww);
2267 snprintf(buf2,sizeof(buf2)/sizeof(buf2[0]), "%d", option);
2270 fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
2275 outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
2277 DisplayFatalError(_("Error writing to ICS"), outError, 1);
2284 if (!appData.icsActive) return;
2285 TelnetRequest(TN_DO, TN_ECHO);
2291 if (!appData.icsActive) return;
2292 TelnetRequest(TN_DONT, TN_ECHO);
2296 CopyHoldings (Board board, char *holdings, ChessSquare lowestPiece)
2298 /* put the holdings sent to us by the server on the board holdings area */
2299 int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2303 if(gameInfo.holdingsWidth < 2) return;
2304 if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2305 return; // prevent overwriting by pre-board holdings
2307 if( (int)lowestPiece >= BlackPawn ) {
2310 holdingsStartRow = BOARD_HEIGHT-1;
2313 holdingsColumn = BOARD_WIDTH-1;
2314 countsColumn = BOARD_WIDTH-2;
2315 holdingsStartRow = 0;
2319 for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
2320 board[i][holdingsColumn] = EmptySquare;
2321 board[i][countsColumn] = (ChessSquare) 0;
2323 while( (p=*holdings++) != NULLCHAR ) {
2324 piece = CharToPiece( ToUpper(p) );
2325 if(piece == EmptySquare) continue;
2326 /*j = (int) piece - (int) WhitePawn;*/
2327 j = PieceToNumber(piece);
2328 if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2329 if(j < 0) continue; /* should not happen */
2330 piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2331 board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2332 board[holdingsStartRow+j*direction][countsColumn]++;
2338 VariantSwitch (Board board, VariantClass newVariant)
2340 int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2341 static Board oldBoard;
2343 startedFromPositionFile = FALSE;
2344 if(gameInfo.variant == newVariant) return;
2346 /* [HGM] This routine is called each time an assignment is made to
2347 * gameInfo.variant during a game, to make sure the board sizes
2348 * are set to match the new variant. If that means adding or deleting
2349 * holdings, we shift the playing board accordingly
2350 * This kludge is needed because in ICS observe mode, we get boards
2351 * of an ongoing game without knowing the variant, and learn about the
2352 * latter only later. This can be because of the move list we requested,
2353 * in which case the game history is refilled from the beginning anyway,
2354 * but also when receiving holdings of a crazyhouse game. In the latter
2355 * case we want to add those holdings to the already received position.
2359 if (appData.debugMode) {
2360 fprintf(debugFP, "Switch board from %s to %s\n",
2361 VariantName(gameInfo.variant), VariantName(newVariant));
2362 setbuf(debugFP, NULL);
2364 shuffleOpenings = 0; /* [HGM] shuffle */
2365 gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2369 newWidth = 9; newHeight = 9;
2370 gameInfo.holdingsSize = 7;
2371 case VariantBughouse:
2372 case VariantCrazyhouse:
2373 newHoldingsWidth = 2; break;
2377 newHoldingsWidth = 2;
2378 gameInfo.holdingsSize = 8;
2381 case VariantCapablanca:
2382 case VariantCapaRandom:
2385 newHoldingsWidth = gameInfo.holdingsSize = 0;
2388 if(newWidth != gameInfo.boardWidth ||
2389 newHeight != gameInfo.boardHeight ||
2390 newHoldingsWidth != gameInfo.holdingsWidth ) {
2392 /* shift position to new playing area, if needed */
2393 if(newHoldingsWidth > gameInfo.holdingsWidth) {
2394 for(i=0; i<BOARD_HEIGHT; i++)
2395 for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2396 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2398 for(i=0; i<newHeight; i++) {
2399 board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2400 board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2402 } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2403 for(i=0; i<BOARD_HEIGHT; i++)
2404 for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2405 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2408 gameInfo.boardWidth = newWidth;
2409 gameInfo.boardHeight = newHeight;
2410 gameInfo.holdingsWidth = newHoldingsWidth;
2411 gameInfo.variant = newVariant;
2412 InitDrawingSizes(-2, 0);
2413 } else gameInfo.variant = newVariant;
2414 CopyBoard(oldBoard, board); // remember correctly formatted board
2415 InitPosition(FALSE); /* this sets up board[0], but also other stuff */
2416 DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2419 static int loggedOn = FALSE;
2421 /*-- Game start info cache: --*/
2423 char gs_kind[MSG_SIZ];
2424 static char player1Name[128] = "";
2425 static char player2Name[128] = "";
2426 static char cont_seq[] = "\n\\ ";
2427 static int player1Rating = -1;
2428 static int player2Rating = -1;
2429 /*----------------------------*/
2431 ColorClass curColor = ColorNormal;
2432 int suppressKibitz = 0;
2435 Boolean soughtPending = FALSE;
2436 Boolean seekGraphUp;
2437 #define MAX_SEEK_ADS 200
2439 char *seekAdList[MAX_SEEK_ADS];
2440 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2441 float tcList[MAX_SEEK_ADS];
2442 char colorList[MAX_SEEK_ADS];
2443 int nrOfSeekAds = 0;
2444 int minRating = 1010, maxRating = 2800;
2445 int hMargin = 10, vMargin = 20, h, w;
2446 extern int squareSize, lineGap;
2451 int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2452 xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2453 if(r < minRating+100 && r >=0 ) r = minRating+100;
2454 if(r > maxRating) r = maxRating;
2455 if(tc < 1.) tc = 1.;
2456 if(tc > 95.) tc = 95.;
2457 x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2458 y = ((double)r - minRating)/(maxRating - minRating)
2459 * (h-vMargin-squareSize/8-1) + vMargin;
2460 if(ratingList[i] < 0) y = vMargin + squareSize/4;
2461 if(strstr(seekAdList[i], " u ")) color = 1;
2462 if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2463 !strstr(seekAdList[i], "bullet") &&
2464 !strstr(seekAdList[i], "blitz") &&
2465 !strstr(seekAdList[i], "standard") ) color = 2;
2466 if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2467 DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2471 AddAd (char *handle, char *rating, int base, int inc, char rated, char *type, int nr, Boolean plot)
2473 char buf[MSG_SIZ], *ext = "";
2474 VariantClass v = StringToVariant(type);
2475 if(strstr(type, "wild")) {
2476 ext = type + 4; // append wild number
2477 if(v == VariantFischeRandom) type = "chess960"; else
2478 if(v == VariantLoadable) type = "setup"; else
2479 type = VariantName(v);
2481 snprintf(buf, MSG_SIZ, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2482 if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2483 if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2484 ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2485 sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2486 tcList[nrOfSeekAds] = base + (2./3.)*inc;
2487 seekNrList[nrOfSeekAds] = nr;
2488 zList[nrOfSeekAds] = 0;
2489 seekAdList[nrOfSeekAds++] = StrSave(buf);
2490 if(plot) PlotSeekAd(nrOfSeekAds-1);
2495 EraseSeekDot (int i)
2497 int x = xList[i], y = yList[i], d=squareSize/4, k;
2498 DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2499 if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2500 // now replot every dot that overlapped
2501 for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2502 int xx = xList[k], yy = yList[k];
2503 if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2504 DrawSeekDot(xx, yy, colorList[k]);
2509 RemoveSeekAd (int nr)
2512 for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2514 if(seekAdList[i]) free(seekAdList[i]);
2515 seekAdList[i] = seekAdList[--nrOfSeekAds];
2516 seekNrList[i] = seekNrList[nrOfSeekAds];
2517 ratingList[i] = ratingList[nrOfSeekAds];
2518 colorList[i] = colorList[nrOfSeekAds];
2519 tcList[i] = tcList[nrOfSeekAds];
2520 xList[i] = xList[nrOfSeekAds];
2521 yList[i] = yList[nrOfSeekAds];
2522 zList[i] = zList[nrOfSeekAds];
2523 seekAdList[nrOfSeekAds] = NULL;
2529 MatchSoughtLine (char *line)
2531 char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2532 int nr, base, inc, u=0; char dummy;
2534 if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2535 sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2537 (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2538 sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7) ) {
2539 // match: compact and save the line
2540 AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2550 if(!seekGraphUp) return FALSE;
2551 h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
2552 w = BOARD_WIDTH * (squareSize + lineGap) + lineGap;
2554 DrawSeekBackground(0, 0, w, h);
2555 DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2556 DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2557 for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2558 int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2560 DrawSeekAxis(hMargin+5*(i%500==0), yy, hMargin-5, yy); // rating ticks
2563 snprintf(buf, MSG_SIZ, "%d", i);
2564 DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2567 DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2568 for(i=1; i<100; i+=(i<10?1:5)) {
2569 int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2570 DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2571 if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2573 snprintf(buf, MSG_SIZ, "%d", i);
2574 DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2577 for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2582 SeekGraphClick (ClickType click, int x, int y, int moving)
2584 static int lastDown = 0, displayed = 0, lastSecond;
2585 if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2586 if(click == Release || moving) return FALSE;
2588 soughtPending = TRUE;
2589 SendToICS(ics_prefix);
2590 SendToICS("sought\n"); // should this be "sought all"?
2591 } else { // issue challenge based on clicked ad
2592 int dist = 10000; int i, closest = 0, second = 0;
2593 for(i=0; i<nrOfSeekAds; i++) {
2594 int d = (x-xList[i])*(x-xList[i]) + (y-yList[i])*(y-yList[i]) + zList[i];
2595 if(d < dist) { dist = d; closest = i; }
2596 second += (d - zList[i] < 120); // count in-range ads
2597 if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2601 second = (second > 1);
2602 if(displayed != closest || second != lastSecond) {
2603 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2604 lastSecond = second; displayed = closest;
2606 if(click == Press) {
2607 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2610 } // on press 'hit', only show info
2611 if(moving == 2) return TRUE; // ignore right up-clicks on dot
2612 snprintf(buf, MSG_SIZ, "play %d\n", seekNrList[closest]);
2613 SendToICS(ics_prefix);
2615 return TRUE; // let incoming board of started game pop down the graph
2616 } else if(click == Release) { // release 'miss' is ignored
2617 zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2618 if(moving == 2) { // right up-click
2619 nrOfSeekAds = 0; // refresh graph
2620 soughtPending = TRUE;
2621 SendToICS(ics_prefix);
2622 SendToICS("sought\n"); // should this be "sought all"?
2625 } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2626 // press miss or release hit 'pop down' seek graph
2627 seekGraphUp = FALSE;
2628 DrawPosition(TRUE, NULL);
2634 read_from_ics (InputSourceRef isr, VOIDSTAR closure, char *data, int count, int error)
2636 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2637 #define STARTED_NONE 0
2638 #define STARTED_MOVES 1
2639 #define STARTED_BOARD 2
2640 #define STARTED_OBSERVE 3
2641 #define STARTED_HOLDINGS 4
2642 #define STARTED_CHATTER 5
2643 #define STARTED_COMMENT 6
2644 #define STARTED_MOVES_NOHIDE 7
2646 static int started = STARTED_NONE;
2647 static char parse[20000];
2648 static int parse_pos = 0;
2649 static char buf[BUF_SIZE + 1];
2650 static int firstTime = TRUE, intfSet = FALSE;
2651 static ColorClass prevColor = ColorNormal;
2652 static int savingComment = FALSE;
2653 static int cmatch = 0; // continuation sequence match
2660 int backup; /* [DM] For zippy color lines */
2662 char talker[MSG_SIZ]; // [HGM] chat
2665 connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2667 if (appData.debugMode) {
2669 fprintf(debugFP, "<ICS: ");
2670 show_bytes(debugFP, data, count);
2671 fprintf(debugFP, "\n");
2675 if (appData.debugMode) { int f = forwardMostMove;
2676 fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2677 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2678 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2681 /* If last read ended with a partial line that we couldn't parse,
2682 prepend it to the new read and try again. */
2683 if (leftover_len > 0) {
2684 for (i=0; i<leftover_len; i++)
2685 buf[i] = buf[leftover_start + i];
2688 /* copy new characters into the buffer */
2689 bp = buf + leftover_len;
2690 buf_len=leftover_len;
2691 for (i=0; i<count; i++)
2694 if (data[i] == '\r')
2697 // join lines split by ICS?
2698 if (!appData.noJoin)
2701 Joining just consists of finding matches against the
2702 continuation sequence, and discarding that sequence
2703 if found instead of copying it. So, until a match
2704 fails, there's nothing to do since it might be the
2705 complete sequence, and thus, something we don't want
2708 if (data[i] == cont_seq[cmatch])
2711 if (cmatch == strlen(cont_seq))
2713 cmatch = 0; // complete match. just reset the counter
2716 it's possible for the ICS to not include the space
2717 at the end of the last word, making our [correct]
2718 join operation fuse two separate words. the server
2719 does this when the space occurs at the width setting.
2721 if (!buf_len || buf[buf_len-1] != ' ')
2732 match failed, so we have to copy what matched before
2733 falling through and copying this character. In reality,
2734 this will only ever be just the newline character, but
2735 it doesn't hurt to be precise.
2737 strncpy(bp, cont_seq, cmatch);
2749 buf[buf_len] = NULLCHAR;
2750 // next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2755 while (i < buf_len) {
2756 /* Deal with part of the TELNET option negotiation
2757 protocol. We refuse to do anything beyond the
2758 defaults, except that we allow the WILL ECHO option,
2759 which ICS uses to turn off password echoing when we are
2760 directly connected to it. We reject this option
2761 if localLineEditing mode is on (always on in xboard)
2762 and we are talking to port 23, which might be a real
2763 telnet server that will try to keep WILL ECHO on permanently.
2765 if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2766 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2767 unsigned char option;
2769 switch ((unsigned char) buf[++i]) {
2771 if (appData.debugMode)
2772 fprintf(debugFP, "\n<WILL ");
2773 switch (option = (unsigned char) buf[++i]) {
2775 if (appData.debugMode)
2776 fprintf(debugFP, "ECHO ");
2777 /* Reply only if this is a change, according
2778 to the protocol rules. */
2779 if (remoteEchoOption) break;
2780 if (appData.localLineEditing &&
2781 atoi(appData.icsPort) == TN_PORT) {
2782 TelnetRequest(TN_DONT, TN_ECHO);
2785 TelnetRequest(TN_DO, TN_ECHO);
2786 remoteEchoOption = TRUE;
2790 if (appData.debugMode)
2791 fprintf(debugFP, "%d ", option);
2792 /* Whatever this is, we don't want it. */
2793 TelnetRequest(TN_DONT, option);
2798 if (appData.debugMode)
2799 fprintf(debugFP, "\n<WONT ");
2800 switch (option = (unsigned char) buf[++i]) {
2802 if (appData.debugMode)
2803 fprintf(debugFP, "ECHO ");
2804 /* Reply only if this is a change, according
2805 to the protocol rules. */
2806 if (!remoteEchoOption) break;
2808 TelnetRequest(TN_DONT, TN_ECHO);
2809 remoteEchoOption = FALSE;
2812 if (appData.debugMode)
2813 fprintf(debugFP, "%d ", (unsigned char) option);
2814 /* Whatever this is, it must already be turned
2815 off, because we never agree to turn on
2816 anything non-default, so according to the
2817 protocol rules, we don't reply. */
2822 if (appData.debugMode)
2823 fprintf(debugFP, "\n<DO ");
2824 switch (option = (unsigned char) buf[++i]) {
2826 /* Whatever this is, we refuse to do it. */
2827 if (appData.debugMode)
2828 fprintf(debugFP, "%d ", option);
2829 TelnetRequest(TN_WONT, option);
2834 if (appData.debugMode)
2835 fprintf(debugFP, "\n<DONT ");
2836 switch (option = (unsigned char) buf[++i]) {
2838 if (appData.debugMode)
2839 fprintf(debugFP, "%d ", option);
2840 /* Whatever this is, we are already not doing
2841 it, because we never agree to do anything
2842 non-default, so according to the protocol
2843 rules, we don't reply. */
2848 if (appData.debugMode)
2849 fprintf(debugFP, "\n<IAC ");
2850 /* Doubled IAC; pass it through */
2854 if (appData.debugMode)
2855 fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2856 /* Drop all other telnet commands on the floor */
2859 if (oldi > next_out)
2860 SendToPlayer(&buf[next_out], oldi - next_out);
2866 /* OK, this at least will *usually* work */
2867 if (!loggedOn && looking_at(buf, &i, "ics%")) {
2871 if (loggedOn && !intfSet) {
2872 if (ics_type == ICS_ICC) {
2873 snprintf(str, MSG_SIZ,
2874 "/set-quietly interface %s\n/set-quietly style 12\n",
2876 if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2877 strcat(str, "/set-2 51 1\n/set seek 1\n");
2878 } else if (ics_type == ICS_CHESSNET) {
2879 snprintf(str, MSG_SIZ, "/style 12\n");
2881 safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
2882 strcat(str, programVersion);
2883 strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2884 if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2885 strcat(str, "$iset seekremove 1\n$set seek 1\n");
2887 strcat(str, "$iset nohighlight 1\n");
2889 strcat(str, "$iset lock 1\n$style 12\n");
2892 NotifyFrontendLogin();
2896 if (started == STARTED_COMMENT) {
2897 /* Accumulate characters in comment */
2898 parse[parse_pos++] = buf[i];
2899 if (buf[i] == '\n') {
2900 parse[parse_pos] = NULLCHAR;
2901 if(chattingPartner>=0) {
2903 snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
2904 OutputChatMessage(chattingPartner, mess);
2905 chattingPartner = -1;
2906 next_out = i+1; // [HGM] suppress printing in ICS window
2908 if(!suppressKibitz) // [HGM] kibitz
2909 AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
2910 else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2911 int nrDigit = 0, nrAlph = 0, j;
2912 if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2913 { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2914 parse[parse_pos] = NULLCHAR;
2915 // try to be smart: if it does not look like search info, it should go to
2916 // ICS interaction window after all, not to engine-output window.
2917 for(j=0; j<parse_pos; j++) { // count letters and digits
2918 nrDigit += (parse[j] >= '0' && parse[j] <= '9');
2919 nrAlph += (parse[j] >= 'a' && parse[j] <= 'z');
2920 nrAlph += (parse[j] >= 'A' && parse[j] <= 'Z');
2922 if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2923 int depth=0; float score;
2924 if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2925 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2926 pvInfoList[forwardMostMove-1].depth = depth;
2927 pvInfoList[forwardMostMove-1].score = 100*score;
2929 OutputKibitz(suppressKibitz, parse);
2932 if(gameMode == IcsObserving) // restore original ICS messages
2933 snprintf(tmp, MSG_SIZ, "%s kibitzes: %s", star_match[0], parse);
2935 snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
2936 SendToPlayer(tmp, strlen(tmp));
2938 next_out = i+1; // [HGM] suppress printing in ICS window
2940 started = STARTED_NONE;
2942 /* Don't match patterns against characters in comment */
2947 if (started == STARTED_CHATTER) {
2948 if (buf[i] != '\n') {
2949 /* Don't match patterns against characters in chatter */
2953 started = STARTED_NONE;
2954 if(suppressKibitz) next_out = i+1;
2957 /* Kludge to deal with rcmd protocol */
2958 if (firstTime && looking_at(buf, &i, "\001*")) {
2959 DisplayFatalError(&buf[1], 0, 1);
2965 if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2968 if (appData.debugMode)
2969 fprintf(debugFP, "ics_type %d\n", ics_type);
2972 if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
2973 ics_type = ICS_FICS;
2975 if (appData.debugMode)
2976 fprintf(debugFP, "ics_type %d\n", ics_type);
2979 if (!loggedOn && looking_at(buf, &i, "chess.net")) {
2980 ics_type = ICS_CHESSNET;
2982 if (appData.debugMode)
2983 fprintf(debugFP, "ics_type %d\n", ics_type);
2988 (looking_at(buf, &i, "\"*\" is *a registered name") ||
2989 looking_at(buf, &i, "Logging you in as \"*\"") ||
2990 looking_at(buf, &i, "will be \"*\""))) {
2991 safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
2995 if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
2997 snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
2998 DisplayIcsInteractionTitle(buf);
2999 have_set_title = TRUE;
3002 /* skip finger notes */
3003 if (started == STARTED_NONE &&
3004 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
3005 (buf[i] == '1' && buf[i+1] == '0')) &&
3006 buf[i+2] == ':' && buf[i+3] == ' ') {
3007 started = STARTED_CHATTER;
3013 // [HGM] seekgraph: recognize sought lines and end-of-sought message
3014 if(appData.seekGraph) {
3015 if(soughtPending && MatchSoughtLine(buf+i)) {
3016 i = strstr(buf+i, "rated") - buf;
3017 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3018 next_out = leftover_start = i;
3019 started = STARTED_CHATTER;
3020 suppressKibitz = TRUE;
3023 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
3024 && looking_at(buf, &i, "* ads displayed")) {
3025 soughtPending = FALSE;
3030 if(appData.autoRefresh) {
3031 if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
3032 int s = (ics_type == ICS_ICC); // ICC format differs
3034 AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
3035 star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
3036 looking_at(buf, &i, "*% "); // eat prompt
3037 if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
3038 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3039 next_out = i; // suppress
3042 if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
3043 char *p = star_match[0];
3045 if(seekGraphUp) RemoveSeekAd(atoi(p));
3046 while(*p && *p++ != ' '); // next
3048 looking_at(buf, &i, "*% "); // eat prompt
3049 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3056 /* skip formula vars */
3057 if (started == STARTED_NONE &&
3058 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
3059 started = STARTED_CHATTER;
3064 // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
3065 if (appData.autoKibitz && started == STARTED_NONE &&
3066 !appData.icsEngineAnalyze && // [HGM] [DM] ICS analyze
3067 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
3068 if((looking_at(buf, &i, "\n* kibitzes: ") || looking_at(buf, &i, "\n* whispers: ") ||
3069 looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
3070 (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
3071 StrStr(star_match[0], gameInfo.black) == star_match[0] )) { // kibitz of self or opponent
3072 suppressKibitz = TRUE;
3073 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3075 if((StrStr(star_match[0], gameInfo.white) == star_match[0]
3076 && (gameMode == IcsPlayingWhite)) ||
3077 (StrStr(star_match[0], gameInfo.black) == star_match[0]
3078 && (gameMode == IcsPlayingBlack)) ) // opponent kibitz
3079 started = STARTED_CHATTER; // own kibitz we simply discard
3081 started = STARTED_COMMENT; // make sure it will be collected in parse[]
3082 parse_pos = 0; parse[0] = NULLCHAR;
3083 savingComment = TRUE;
3084 suppressKibitz = gameMode != IcsObserving ? 2 :
3085 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
3089 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
3090 looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
3091 && atoi(star_match[0])) {
3092 // suppress the acknowledgements of our own autoKibitz
3094 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3095 if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
3096 SendToPlayer(star_match[0], strlen(star_match[0]));
3097 if(looking_at(buf, &i, "*% ")) // eat prompt
3098 suppressKibitz = FALSE;
3102 } // [HGM] kibitz: end of patch
3104 if(looking_at(buf, &i, "* rating adjustment: * --> *\n")) continue;
3106 // [HGM] chat: intercept tells by users for which we have an open chat window
3108 if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
3109 looking_at(buf, &i, "* whispers:") ||
3110 looking_at(buf, &i, "* kibitzes:") ||
3111 looking_at(buf, &i, "* shouts:") ||
3112 looking_at(buf, &i, "* c-shouts:") ||
3113 looking_at(buf, &i, "--> * ") ||
3114 looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
3115 looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
3116 looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
3117 looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
3119 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
3120 chattingPartner = -1;
3122 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
3123 for(p=0; p<MAX_CHAT; p++) {
3124 if(chatPartner[p][0] >= '0' && chatPartner[p][0] <= '9' && channel == atoi(chatPartner[p])) {
3125 talker[0] = '['; strcat(talker, "] ");
3126 Colorize(channel == 1 ? ColorChannel1 : ColorChannel, FALSE);
3127 chattingPartner = p; break;
3130 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
3131 for(p=0; p<MAX_CHAT; p++) {
3132 if(!strcmp("kibitzes", chatPartner[p])) {
3133 talker[0] = '['; strcat(talker, "] ");
3134 chattingPartner = p; break;
3137 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
3138 for(p=0; p<MAX_CHAT; p++) {
3139 if(!strcmp("whispers", chatPartner[p])) {
3140 talker[0] = '['; strcat(talker, "] ");
3141 chattingPartner = p; break;
3144 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
3145 if(buf[i-8] == '-' && buf[i-3] == 't')
3146 for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
3147 if(!strcmp("c-shouts", chatPartner[p])) {
3148 talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
3149 chattingPartner = p; break;
3152 if(chattingPartner < 0)
3153 for(p=0; p<MAX_CHAT; p++) {
3154 if(!strcmp("shouts", chatPartner[p])) {
3155 if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
3156 else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
3157 else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
3158 chattingPartner = p; break;
3162 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
3163 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3164 talker[0] = 0; Colorize(ColorTell, FALSE);
3165 chattingPartner = p; break;
3167 if(chattingPartner<0) i = oldi; else {
3168 Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
3169 if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
3170 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3171 started = STARTED_COMMENT;
3172 parse_pos = 0; parse[0] = NULLCHAR;
3173 savingComment = 3 + chattingPartner; // counts as TRUE
3174 suppressKibitz = TRUE;
3177 } // [HGM] chat: end of patch
3180 if (appData.zippyTalk || appData.zippyPlay) {
3181 /* [DM] Backup address for color zippy lines */
3183 if (loggedOn == TRUE)
3184 if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
3185 (appData.zippyPlay && ZippyMatch(buf, &backup)));
3187 } // [DM] 'else { ' deleted
3189 /* Regular tells and says */
3190 (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
3191 looking_at(buf, &i, "* (your partner) tells you: ") ||
3192 looking_at(buf, &i, "* says: ") ||
3193 /* Don't color "message" or "messages" output */
3194 (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
3195 looking_at(buf, &i, "*. * at *:*: ") ||
3196 looking_at(buf, &i, "--* (*:*): ") ||
3197 /* Message notifications (same color as tells) */
3198 looking_at(buf, &i, "* has left a message ") ||
3199 looking_at(buf, &i, "* just sent you a message:\n") ||
3200 /* Whispers and kibitzes */
3201 (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
3202 looking_at(buf, &i, "* kibitzes: ") ||
3204 (tkind = 3, looking_at(buf, &i, "*(*: "))) {
3206 if (tkind == 1 && strchr(star_match[0], ':')) {
3207 /* Avoid "tells you:" spoofs in channels */
3210 if (star_match[0][0] == NULLCHAR ||
3211 strchr(star_match[0], ' ') ||
3212 (tkind == 3 && strchr(star_match[1], ' '))) {
3213 /* Reject bogus matches */
3216 if (appData.colorize) {
3217 if (oldi > next_out) {
3218 SendToPlayer(&buf[next_out], oldi - next_out);
3223 Colorize(ColorTell, FALSE);
3224 curColor = ColorTell;
3227 Colorize(ColorKibitz, FALSE);
3228 curColor = ColorKibitz;
3231 p = strrchr(star_match[1], '(');
3238 Colorize(ColorChannel1, FALSE);
3239 curColor = ColorChannel1;
3241 Colorize(ColorChannel, FALSE);
3242 curColor = ColorChannel;
3246 curColor = ColorNormal;
3250 if (started == STARTED_NONE && appData.autoComment &&
3251 (gameMode == IcsObserving ||
3252 gameMode == IcsPlayingWhite ||
3253 gameMode == IcsPlayingBlack)) {
3254 parse_pos = i - oldi;
3255 memcpy(parse, &buf[oldi], parse_pos);
3256 parse[parse_pos] = NULLCHAR;
3257 started = STARTED_COMMENT;
3258 savingComment = TRUE;
3260 started = STARTED_CHATTER;
3261 savingComment = FALSE;
3268 if (looking_at(buf, &i, "* s-shouts: ") ||
3269 looking_at(buf, &i, "* c-shouts: ")) {
3270 if (appData.colorize) {
3271 if (oldi > next_out) {
3272 SendToPlayer(&buf[next_out], oldi - next_out);
3275 Colorize(ColorSShout, FALSE);
3276 curColor = ColorSShout;
3279 started = STARTED_CHATTER;
3283 if (looking_at(buf, &i, "--->")) {
3288 if (looking_at(buf, &i, "* shouts: ") ||
3289 looking_at(buf, &i, "--> ")) {
3290 if (appData.colorize) {
3291 if (oldi > next_out) {
3292 SendToPlayer(&buf[next_out], oldi - next_out);
3295 Colorize(ColorShout, FALSE);
3296 curColor = ColorShout;
3299 started = STARTED_CHATTER;
3303 if (looking_at( buf, &i, "Challenge:")) {
3304 if (appData.colorize) {
3305 if (oldi > next_out) {
3306 SendToPlayer(&buf[next_out], oldi - next_out);
3309 Colorize(ColorChallenge, FALSE);
3310 curColor = ColorChallenge;
3316 if (looking_at(buf, &i, "* offers you") ||
3317 looking_at(buf, &i, "* offers to be") ||
3318 looking_at(buf, &i, "* would like to") ||
3319 looking_at(buf, &i, "* requests to") ||
3320 looking_at(buf, &i, "Your opponent offers") ||
3321 looking_at(buf, &i, "Your opponent requests")) {
3323 if (appData.colorize) {
3324 if (oldi > next_out) {
3325 SendToPlayer(&buf[next_out], oldi - next_out);
3328 Colorize(ColorRequest, FALSE);
3329 curColor = ColorRequest;
3334 if (looking_at(buf, &i, "* (*) seeking")) {
3335 if (appData.colorize) {
3336 if (oldi > next_out) {
3337 SendToPlayer(&buf[next_out], oldi - next_out);
3340 Colorize(ColorSeek, FALSE);
3341 curColor = ColorSeek;
3346 if(i < backup) { i = backup; continue; } // [HGM] for if ZippyControl matches, but the colorie code doesn't
3348 if (looking_at(buf, &i, "\\ ")) {
3349 if (prevColor != ColorNormal) {
3350 if (oldi > next_out) {
3351 SendToPlayer(&buf[next_out], oldi - next_out);
3354 Colorize(prevColor, TRUE);
3355 curColor = prevColor;
3357 if (savingComment) {
3358 parse_pos = i - oldi;
3359 memcpy(parse, &buf[oldi], parse_pos);
3360 parse[parse_pos] = NULLCHAR;
3361 started = STARTED_COMMENT;
3362 if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3363 chattingPartner = savingComment - 3; // kludge to remember the box
3365 started = STARTED_CHATTER;
3370 if (looking_at(buf, &i, "Black Strength :") ||
3371 looking_at(buf, &i, "<<< style 10 board >>>") ||
3372 looking_at(buf, &i, "<10>") ||
3373 looking_at(buf, &i, "#@#")) {
3374 /* Wrong board style */
3376 SendToICS(ics_prefix);
3377 SendToICS("set style 12\n");
3378 SendToICS(ics_prefix);
3379 SendToICS("refresh\n");
3383 if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
3385 have_sent_ICS_logon = 1;
3389 if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3390 (looking_at(buf, &i, "\n<12> ") ||
3391 looking_at(buf, &i, "<12> "))) {
3393 if (oldi > next_out) {
3394 SendToPlayer(&buf[next_out], oldi - next_out);
3397 started = STARTED_BOARD;
3402 if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3403 looking_at(buf, &i, "<b1> ")) {
3404 if (oldi > next_out) {
3405 SendToPlayer(&buf[next_out], oldi - next_out);
3408 started = STARTED_HOLDINGS;
3413 if (looking_at(buf, &i, "* *vs. * *--- *")) {
3415 /* Header for a move list -- first line */
3417 switch (ics_getting_history) {
3421 case BeginningOfGame:
3422 /* User typed "moves" or "oldmoves" while we
3423 were idle. Pretend we asked for these
3424 moves and soak them up so user can step
3425 through them and/or save them.
3428 gameMode = IcsObserving;
3431 ics_getting_history = H_GOT_UNREQ_HEADER;
3433 case EditGame: /*?*/
3434 case EditPosition: /*?*/
3435 /* Should above feature work in these modes too? */
3436 /* For now it doesn't */
3437 ics_getting_history = H_GOT_UNWANTED_HEADER;
3440 ics_getting_history = H_GOT_UNWANTED_HEADER;
3445 /* Is this the right one? */
3446 if (gameInfo.white && gameInfo.black &&
3447 strcmp(gameInfo.white, star_match[0]) == 0 &&
3448 strcmp(gameInfo.black, star_match[2]) == 0) {
3450 ics_getting_history = H_GOT_REQ_HEADER;
3453 case H_GOT_REQ_HEADER:
3454 case H_GOT_UNREQ_HEADER:
3455 case H_GOT_UNWANTED_HEADER:
3456 case H_GETTING_MOVES:
3457 /* Should not happen */
3458 DisplayError(_("Error gathering move list: two headers"), 0);
3459 ics_getting_history = H_FALSE;
3463 /* Save player ratings into gameInfo if needed */
3464 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3465 ics_getting_history == H_GOT_UNREQ_HEADER) &&
3466 (gameInfo.whiteRating == -1 ||
3467 gameInfo.blackRating == -1)) {
3469 gameInfo.whiteRating = string_to_rating(star_match[1]);
3470 gameInfo.blackRating = string_to_rating(star_match[3]);
3471 if (appData.debugMode)
3472 fprintf(debugFP, _("Ratings from header: W %d, B %d\n"),
3473 gameInfo.whiteRating, gameInfo.blackRating);
3478 if (looking_at(buf, &i,
3479 "* * match, initial time: * minute*, increment: * second")) {
3480 /* Header for a move list -- second line */
3481 /* Initial board will follow if this is a wild game */
3482 if (gameInfo.event != NULL) free(gameInfo.event);
3483 snprintf(str, MSG_SIZ, "ICS %s %s match", star_match[0], star_match[1]);
3484 gameInfo.event = StrSave(str);
3485 /* [HGM] we switched variant. Translate boards if needed. */
3486 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3490 if (looking_at(buf, &i, "Move ")) {
3491 /* Beginning of a move list */
3492 switch (ics_getting_history) {
3494 /* Normally should not happen */
3495 /* Maybe user hit reset while we were parsing */
3498 /* Happens if we are ignoring a move list that is not
3499 * the one we just requested. Common if the user
3500 * tries to observe two games without turning off
3503 case H_GETTING_MOVES:
3504 /* Should not happen */
3505 DisplayError(_("Error gathering move list: nested"), 0);
3506 ics_getting_history = H_FALSE;
3508 case H_GOT_REQ_HEADER:
3509 ics_getting_history = H_GETTING_MOVES;
3510 started = STARTED_MOVES;
3512 if (oldi > next_out) {
3513 SendToPlayer(&buf[next_out], oldi - next_out);
3516 case H_GOT_UNREQ_HEADER:
3517 ics_getting_history = H_GETTING_MOVES;
3518 started = STARTED_MOVES_NOHIDE;
3521 case H_GOT_UNWANTED_HEADER:
3522 ics_getting_history = H_FALSE;
3528 if (looking_at(buf, &i, "% ") ||
3529 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3530 && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3531 if(soughtPending) { // [HGM] seekgraph: on ICC sought-list has no termination line
3532 soughtPending = FALSE;
3536 if(suppressKibitz) next_out = i;
3537 savingComment = FALSE;
3541 case STARTED_MOVES_NOHIDE:
3542 memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3543 parse[parse_pos + i - oldi] = NULLCHAR;
3544 ParseGameHistory(parse);
3546 if (appData.zippyPlay && first.initDone) {
3547 FeedMovesToProgram(&first, forwardMostMove);
3548 if (gameMode == IcsPlayingWhite) {
3549 if (WhiteOnMove(forwardMostMove)) {
3550 if (first.sendTime) {
3551 if (first.useColors) {
3552 SendToProgram("black\n", &first);
3554 SendTimeRemaining(&first, TRUE);
3556 if (first.useColors) {
3557 SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3559 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3560 first.maybeThinking = TRUE;
3562 if (first.usePlayother) {
3563 if (first.sendTime) {
3564 SendTimeRemaining(&first, TRUE);
3566 SendToProgram("playother\n", &first);
3572 } else if (gameMode == IcsPlayingBlack) {
3573 if (!WhiteOnMove(forwardMostMove)) {
3574 if (first.sendTime) {
3575 if (first.useColors) {
3576 SendToProgram("white\n", &first);
3578 SendTimeRemaining(&first, FALSE);
3580 if (first.useColors) {
3581 SendToProgram("black\n", &first);
3583 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3584 first.maybeThinking = TRUE;
3586 if (first.usePlayother) {
3587 if (first.sendTime) {
3588 SendTimeRemaining(&first, FALSE);
3590 SendToProgram("playother\n", &first);
3599 if (gameMode == IcsObserving && ics_gamenum == -1) {
3600 /* Moves came from oldmoves or moves command
3601 while we weren't doing anything else.
3603 currentMove = forwardMostMove;
3604 ClearHighlights();/*!!could figure this out*/
3605 flipView = appData.flipView;
3606 DrawPosition(TRUE, boards[currentMove]);
3607 DisplayBothClocks();
3608 snprintf(str, MSG_SIZ, "%s %s %s",
3609 gameInfo.white, _("vs."), gameInfo.black);
3613 /* Moves were history of an active game */
3614 if (gameInfo.resultDetails != NULL) {
3615 free(gameInfo.resultDetails);
3616 gameInfo.resultDetails = NULL;
3619 HistorySet(parseList, backwardMostMove,
3620 forwardMostMove, currentMove-1);
3621 DisplayMove(currentMove - 1);
3622 if (started == STARTED_MOVES) next_out = i;
3623 started = STARTED_NONE;
3624 ics_getting_history = H_FALSE;
3627 case STARTED_OBSERVE:
3628 started = STARTED_NONE;
3629 SendToICS(ics_prefix);
3630 SendToICS("refresh\n");
3636 if(bookHit) { // [HGM] book: simulate book reply
3637 static char bookMove[MSG_SIZ]; // a bit generous?
3639 programStats.nodes = programStats.depth = programStats.time =
3640 programStats.score = programStats.got_only_move = 0;
3641 sprintf(programStats.movelist, "%s (xbook)", bookHit);
3643 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
3644 strcat(bookMove, bookHit);
3645 HandleMachineMove(bookMove, &first);
3650 if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3651 started == STARTED_HOLDINGS ||
3652 started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3653 /* Accumulate characters in move list or board */
3654 parse[parse_pos++] = buf[i];
3657 /* Start of game messages. Mostly we detect start of game
3658 when the first board image arrives. On some versions
3659 of the ICS, though, we need to do a "refresh" after starting
3660 to observe in order to get the current board right away. */
3661 if (looking_at(buf, &i, "Adding game * to observation list")) {
3662 started = STARTED_OBSERVE;
3666 /* Handle auto-observe */
3667 if (appData.autoObserve &&
3668 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3669 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3671 /* Choose the player that was highlighted, if any. */
3672 if (star_match[0][0] == '\033' ||
3673 star_match[1][0] != '\033') {
3674 player = star_match[0];
3676 player = star_match[2];
3678 snprintf(str, MSG_SIZ, "%sobserve %s\n",
3679 ics_prefix, StripHighlightAndTitle(player));
3682 /* Save ratings from notify string */
3683 safeStrCpy(player1Name, star_match[0], sizeof(player1Name)/sizeof(player1Name[0]));
3684 player1Rating = string_to_rating(star_match[1]);
3685 safeStrCpy(player2Name, star_match[2], sizeof(player2Name)/sizeof(player2Name[0]));
3686 player2Rating = string_to_rating(star_match[3]);
3688 if (appData.debugMode)
3690 "Ratings from 'Game notification:' %s %d, %s %d\n",
3691 player1Name, player1Rating,
3692 player2Name, player2Rating);
3697 /* Deal with automatic examine mode after a game,
3698 and with IcsObserving -> IcsExamining transition */
3699 if (looking_at(buf, &i, "Entering examine mode for game *") ||
3700 looking_at(buf, &i, "has made you an examiner of game *")) {
3702 int gamenum = atoi(star_match[0]);
3703 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3704 gamenum == ics_gamenum) {
3705 /* We were already playing or observing this game;
3706 no need to refetch history */
3707 gameMode = IcsExamining;
3709 pauseExamForwardMostMove = forwardMostMove;
3710 } else if (currentMove < forwardMostMove) {
3711 ForwardInner(forwardMostMove);
3714 /* I don't think this case really can happen */
3715 SendToICS(ics_prefix);
3716 SendToICS("refresh\n");
3721 /* Error messages */
3722 // if (ics_user_moved) {
3723 if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3724 if (looking_at(buf, &i, "Illegal move") ||
3725 looking_at(buf, &i, "Not a legal move") ||
3726 looking_at(buf, &i, "Your king is in check") ||
3727 looking_at(buf, &i, "It isn't your turn") ||
3728 looking_at(buf, &i, "It is not your move")) {
3730 if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3731 currentMove = forwardMostMove-1;
3732 DisplayMove(currentMove - 1); /* before DMError */
3733 DrawPosition(FALSE, boards[currentMove]);
3734 SwitchClocks(forwardMostMove-1); // [HGM] race
3735 DisplayBothClocks();
3737 DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3743 if (looking_at(buf, &i, "still have time") ||
3744 looking_at(buf, &i, "not out of time") ||
3745 looking_at(buf, &i, "either player is out of time") ||
3746 looking_at(buf, &i, "has timeseal; checking")) {
3747 /* We must have called his flag a little too soon */
3748 whiteFlag = blackFlag = FALSE;
3752 if (looking_at(buf, &i, "added * seconds to") ||
3753 looking_at(buf, &i, "seconds were added to")) {
3754 /* Update the clocks */
3755 SendToICS(ics_prefix);
3756 SendToICS("refresh\n");
3760 if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3761 ics_clock_paused = TRUE;
3766 if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3767 ics_clock_paused = FALSE;
3772 /* Grab player ratings from the Creating: message.
3773 Note we have to check for the special case when
3774 the ICS inserts things like [white] or [black]. */
3775 if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3776 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3778 0 player 1 name (not necessarily white)
3780 2 empty, white, or black (IGNORED)
3781 3 player 2 name (not necessarily black)
3784 The names/ratings are sorted out when the game
3785 actually starts (below).
3787 safeStrCpy(player1Name, StripHighlightAndTitle(star_match[0]), sizeof(player1Name)/sizeof(player1Name[0]));
3788 player1Rating = string_to_rating(star_match[1]);
3789 safeStrCpy(player2Name, StripHighlightAndTitle(star_match[3]), sizeof(player2Name)/sizeof(player2Name[0]));
3790 player2Rating = string_to_rating(star_match[4]);
3792 if (appData.debugMode)
3794 "Ratings from 'Creating:' %s %d, %s %d\n",
3795 player1Name, player1Rating,
3796 player2Name, player2Rating);
3801 /* Improved generic start/end-of-game messages */
3802 if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3803 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3804 /* If tkind == 0: */
3805 /* star_match[0] is the game number */
3806 /* [1] is the white player's name */
3807 /* [2] is the black player's name */
3808 /* For end-of-game: */
3809 /* [3] is the reason for the game end */
3810 /* [4] is a PGN end game-token, preceded by " " */
3811 /* For start-of-game: */
3812 /* [3] begins with "Creating" or "Continuing" */
3813 /* [4] is " *" or empty (don't care). */
3814 int gamenum = atoi(star_match[0]);
3815 char *whitename, *blackname, *why, *endtoken;
3816 ChessMove endtype = EndOfFile;
3819 whitename = star_match[1];
3820 blackname = star_match[2];
3821 why = star_match[3];
3822 endtoken = star_match[4];
3824 whitename = star_match[1];
3825 blackname = star_match[3];
3826 why = star_match[5];
3827 endtoken = star_match[6];
3830 /* Game start messages */
3831 if (strncmp(why, "Creating ", 9) == 0 ||
3832 strncmp(why, "Continuing ", 11) == 0) {
3833 gs_gamenum = gamenum;
3834 safeStrCpy(gs_kind, strchr(why, ' ') + 1,sizeof(gs_kind)/sizeof(gs_kind[0]));
3835 if(ics_gamenum == -1) // [HGM] only if we are not already involved in a game (because gin=1 sends us such messages)
3836 VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
3838 if (appData.zippyPlay) {
3839 ZippyGameStart(whitename, blackname);
3842 partnerBoardValid = FALSE; // [HGM] bughouse
3846 /* Game end messages */
3847 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3848 ics_gamenum != gamenum) {
3851 while (endtoken[0] == ' ') endtoken++;
3852 switch (endtoken[0]) {
3855 endtype = GameUnfinished;
3858 endtype = BlackWins;
3861 if (endtoken[1] == '/')
3862 endtype = GameIsDrawn;
3864 endtype = WhiteWins;
3867 GameEnds(endtype, why, GE_ICS);
3869 if (appData.zippyPlay && first.initDone) {
3870 ZippyGameEnd(endtype, why);
3871 if (first.pr == NoProc) {
3872 /* Start the next process early so that we'll
3873 be ready for the next challenge */
3874 StartChessProgram(&first);
3876 /* Send "new" early, in case this command takes
3877 a long time to finish, so that we'll be ready
3878 for the next challenge. */
3879 gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3883 if(appData.bgObserve && partnerBoardValid) DrawPosition(TRUE, partnerBoard);
3887 if (looking_at(buf, &i, "Removing game * from observation") ||
3888 looking_at(buf, &i, "no longer observing game *") ||
3889 looking_at(buf, &i, "Game * (*) has no examiners")) {
3890 if (gameMode == IcsObserving &&
3891 atoi(star_match[0]) == ics_gamenum)
3893 /* icsEngineAnalyze */
3894 if (appData.icsEngineAnalyze) {
3901 ics_user_moved = FALSE;
3906 if (looking_at(buf, &i, "no longer examining game *")) {
3907 if (gameMode == IcsExamining &&
3908 atoi(star_match[0]) == ics_gamenum)
3912 ics_user_moved = FALSE;
3917 /* Advance leftover_start past any newlines we find,
3918 so only partial lines can get reparsed */
3919 if (looking_at(buf, &i, "\n")) {
3920 prevColor = curColor;
3921 if (curColor != ColorNormal) {
3922 if (oldi > next_out) {
3923 SendToPlayer(&buf[next_out], oldi - next_out);
3926 Colorize(ColorNormal, FALSE);
3927 curColor = ColorNormal;
3929 if (started == STARTED_BOARD) {
3930 started = STARTED_NONE;
3931 parse[parse_pos] = NULLCHAR;
3932 ParseBoard12(parse);
3935 /* Send premove here */
3936 if (appData.premove) {
3938 if (currentMove == 0 &&
3939 gameMode == IcsPlayingWhite &&
3940 appData.premoveWhite) {
3941 snprintf(str, MSG_SIZ, "%s\n", appData.premoveWhiteText);
3942 if (appData.debugMode)
3943 fprintf(debugFP, "Sending premove:\n");
3945 } else if (currentMove == 1 &&
3946 gameMode == IcsPlayingBlack &&
3947 appData.premoveBlack) {
3948 snprintf(str, MSG_SIZ, "%s\n", appData.premoveBlackText);
3949 if (appData.debugMode)
3950 fprintf(debugFP, "Sending premove:\n");
3952 } else if (gotPremove) {
3954 ClearPremoveHighlights();
3955 if (appData.debugMode)
3956 fprintf(debugFP, "Sending premove:\n");
3957 UserMoveEvent(premoveFromX, premoveFromY,
3958 premoveToX, premoveToY,
3963 /* Usually suppress following prompt */
3964 if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
3965 while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
3966 if (looking_at(buf, &i, "*% ")) {
3967 savingComment = FALSE;
3972 } else if (started == STARTED_HOLDINGS) {
3974 char new_piece[MSG_SIZ];
3975 started = STARTED_NONE;
3976 parse[parse_pos] = NULLCHAR;
3977 if (appData.debugMode)
3978 fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
3979 parse, currentMove);
3980 if (sscanf(parse, " game %d", &gamenum) == 1) {
3981 if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game
3982 if (gameInfo.variant == VariantNormal) {
3983 /* [HGM] We seem to switch variant during a game!
3984 * Presumably no holdings were displayed, so we have
3985 * to move the position two files to the right to
3986 * create room for them!
3988 VariantClass newVariant;
3989 switch(gameInfo.boardWidth) { // base guess on board width
3990 case 9: newVariant = VariantShogi; break;
3991 case 10: newVariant = VariantGreat; break;
3992 default: newVariant = VariantCrazyhouse; break;
3994 VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3995 /* Get a move list just to see the header, which
3996 will tell us whether this is really bug or zh */
3997 if (ics_getting_history == H_FALSE) {
3998 ics_getting_history = H_REQUESTED;
3999 snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4003 new_piece[0] = NULLCHAR;
4004 sscanf(parse, "game %d white [%s black [%s <- %s",
4005 &gamenum, white_holding, black_holding,
4007 white_holding[strlen(white_holding)-1] = NULLCHAR;
4008 black_holding[strlen(black_holding)-1] = NULLCHAR;
4009 /* [HGM] copy holdings to board holdings area */
4010 CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
4011 CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
4012 boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
4014 if (appData.zippyPlay && first.initDone) {
4015 ZippyHoldings(white_holding, black_holding,
4019 if (tinyLayout || smallLayout) {
4020 char wh[16], bh[16];
4021 PackHolding(wh, white_holding);
4022 PackHolding(bh, black_holding);
4023 snprintf(str, MSG_SIZ, "[%s-%s] %s-%s", wh, bh,
4024 gameInfo.white, gameInfo.black);
4026 snprintf(str, MSG_SIZ, "%s [%s] %s %s [%s]",
4027 gameInfo.white, white_holding, _("vs."),
4028 gameInfo.black, black_holding);
4030 if(!partnerUp) // [HGM] bughouse: when peeking at partner game we already know what he captured...
4031 DrawPosition(FALSE, boards[currentMove]);
4033 } else if(appData.bgObserve) { // [HGM] bughouse: holdings of other game => background
4034 sscanf(parse, "game %d white [%s black [%s <- %s",
4035 &gamenum, white_holding, black_holding,
4037 white_holding[strlen(white_holding)-1] = NULLCHAR;
4038 black_holding[strlen(black_holding)-1] = NULLCHAR;
4039 /* [HGM] copy holdings to partner-board holdings area */
4040 CopyHoldings(partnerBoard, white_holding, WhitePawn);
4041 CopyHoldings(partnerBoard, black_holding, BlackPawn);
4042 if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual: always draw
4043 if(partnerUp) DrawPosition(FALSE, partnerBoard);
4044 if(twoBoards) { partnerUp = 0; flipView = !flipView; }
4047 /* Suppress following prompt */
4048 if (looking_at(buf, &i, "*% ")) {
4049 if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
4050 savingComment = FALSE;
4058 i++; /* skip unparsed character and loop back */
4061 if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
4062 // started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
4063 // SendToPlayer(&buf[next_out], i - next_out);
4064 started != STARTED_HOLDINGS && leftover_start > next_out) {
4065 SendToPlayer(&buf[next_out], leftover_start - next_out);
4069 leftover_len = buf_len - leftover_start;
4070 /* if buffer ends with something we couldn't parse,
4071 reparse it after appending the next read */
4073 } else if (count == 0) {
4074 RemoveInputSource(isr);
4075 DisplayFatalError(_("Connection closed by ICS"), 0, 0);
4077 DisplayFatalError(_("Error reading from ICS"), error, 1);
4082 /* Board style 12 looks like this:
4084 <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
4086 * The "<12> " is stripped before it gets to this routine. The two
4087 * trailing 0's (flip state and clock ticking) are later addition, and
4088 * some chess servers may not have them, or may have only the first.
4089 * Additional trailing fields may be added in the future.
4092 #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"
4094 #define RELATION_OBSERVING_PLAYED 0
4095 #define RELATION_OBSERVING_STATIC -2 /* examined, oldmoves, or smoves */
4096 #define RELATION_PLAYING_MYMOVE 1
4097 #define RELATION_PLAYING_NOTMYMOVE -1
4098 #define RELATION_EXAMINING 2
4099 #define RELATION_ISOLATED_BOARD -3
4100 #define RELATION_STARTING_POSITION -4 /* FICS only */
4103 ParseBoard12 (char *string)
4105 GameMode newGameMode;
4106 int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
4107 int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
4108 int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
4109 char to_play, board_chars[200];
4110 char move_str[MSG_SIZ], str[MSG_SIZ], elapsed_time[MSG_SIZ];
4111 char black[32], white[32];
4113 int prevMove = currentMove;
4116 int fromX, fromY, toX, toY;
4118 int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
4119 char *bookHit = NULL; // [HGM] book
4120 Boolean weird = FALSE, reqFlag = FALSE;
4122 fromX = fromY = toX = toY = -1;
4126 if (appData.debugMode)
4127 fprintf(debugFP, _("Parsing board: %s\n"), string);
4129 move_str[0] = NULLCHAR;
4130 elapsed_time[0] = NULLCHAR;
4131 { /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
4133 while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
4134 if(string[i] == ' ') { ranks++; files = 0; }
4136 if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
4139 for(j = 0; j <i; j++) board_chars[j] = string[j];
4140 board_chars[i] = '\0';
4143 n = sscanf(string, PATTERN, &to_play, &double_push,
4144 &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
4145 &gamenum, white, black, &relation, &basetime, &increment,
4146 &white_stren, &black_stren, &white_time, &black_time,
4147 &moveNum, str, elapsed_time, move_str, &ics_flip,
4151 snprintf(str, MSG_SIZ, _("Failed to parse board string:\n\"%s\""), string);
4152 DisplayError(str, 0);
4156 /* Convert the move number to internal form */
4157 moveNum = (moveNum - 1) * 2;
4158 if (to_play == 'B') moveNum++;
4159 if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
4160 DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
4166 case RELATION_OBSERVING_PLAYED:
4167 case RELATION_OBSERVING_STATIC:
4168 if (gamenum == -1) {
4169 /* Old ICC buglet */
4170 relation = RELATION_OBSERVING_STATIC;
4172 newGameMode = IcsObserving;
4174 case RELATION_PLAYING_MYMOVE:
4175 case RELATION_PLAYING_NOTMYMOVE:
4177 ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
4178 IcsPlayingWhite : IcsPlayingBlack;
4180 case RELATION_EXAMINING:
4181 newGameMode = IcsExamining;
4183 case RELATION_ISOLATED_BOARD:
4185 /* Just display this board. If user was doing something else,
4186 we will forget about it until the next board comes. */
4187 newGameMode = IcsIdle;
4189 case RELATION_STARTING_POSITION:
4190 newGameMode = gameMode;
4194 if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
4195 && newGameMode == IcsObserving && gamenum != ics_gamenum && appData.bgObserve) {
4196 // [HGM] bughouse: don't act on alien boards while we play. Just parse the board and save it */
4198 for (k = 0; k < ranks; k++) {
4199 for (j = 0; j < files; j++)
4200 board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4201 if(gameInfo.holdingsWidth > 1) {
4202 board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4203 board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4206 CopyBoard(partnerBoard, board);
4207 if(toSqr = strchr(str, '/')) { // extract highlights from long move
4208 partnerBoard[EP_STATUS-3] = toSqr[1] - AAA; // kludge: hide highlighting info in board
4209 partnerBoard[EP_STATUS-4] = toSqr[2] - ONE;
4210 } else partnerBoard[EP_STATUS-4] = partnerBoard[EP_STATUS-3] = -1;
4211 if(toSqr = strchr(str, '-')) {
4212 partnerBoard[EP_STATUS-1] = toSqr[1] - AAA;
4213 partnerBoard[EP_STATUS-2] = toSqr[2] - ONE;
4214 } else partnerBoard[EP_STATUS-1] = partnerBoard[EP_STATUS-2] = -1;
4215 if(appData.dualBoard && !twoBoards) { twoBoards = 1; InitDrawingSizes(-2,0); }
4216 if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual
4217 if(partnerUp) DrawPosition(FALSE, partnerBoard);
4218 if(twoBoards) { partnerUp = 0; flipView = !flipView; } // [HGM] dual
4219 snprintf(partnerStatus, MSG_SIZ,"W: %d:%02d B: %d:%02d (%d-%d) %c", white_time/60000, (white_time%60000)/1000,
4220 (black_time/60000), (black_time%60000)/1000, white_stren, black_stren, to_play);
4221 DisplayMessage(partnerStatus, "");
4222 partnerBoardValid = TRUE;
4226 /* Modify behavior for initial board display on move listing
4229 switch (ics_getting_history) {
4233 case H_GOT_REQ_HEADER:
4234 case H_GOT_UNREQ_HEADER:
4235 /* This is the initial position of the current game */
4236 gamenum = ics_gamenum;
4237 moveNum = 0; /* old ICS bug workaround */
4238 if (to_play == 'B') {
4239 startedFromSetupPosition = TRUE;
4240 blackPlaysFirst = TRUE;
4242 if (forwardMostMove == 0) forwardMostMove = 1;
4243 if (backwardMostMove == 0) backwardMostMove = 1;
4244 if (currentMove == 0) currentMove = 1;
4246 newGameMode = gameMode;
4247 relation = RELATION_STARTING_POSITION; /* ICC needs this */
4249 case H_GOT_UNWANTED_HEADER:
4250 /* This is an initial board that we don't want */
4252 case H_GETTING_MOVES:
4253 /* Should not happen */
4254 DisplayError(_("Error gathering move list: extra board"), 0);
4255 ics_getting_history = H_FALSE;
4259 if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
4260 weird && (int)gameInfo.variant < (int)VariantShogi) {
4261 /* [HGM] We seem to have switched variant unexpectedly
4262 * Try to guess new variant from board size
4264 VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
4265 if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
4266 if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
4267 if(ranks == 8 && files == 12) newVariant = VariantCourier; else
4268 if(ranks == 9 && files == 9) newVariant = VariantShogi; else
4269 if(!weird) newVariant = VariantNormal;
4270 VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4271 /* Get a move list just to see the header, which
4272 will tell us whether this is really bug or zh */
4273 if (ics_getting_history == H_FALSE) {
4274 ics_getting_history = H_REQUESTED; reqFlag = TRUE;
4275 snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4280 /* Take action if this is the first board of a new game, or of a
4281 different game than is currently being displayed. */
4282 if (gamenum != ics_gamenum || newGameMode != gameMode ||
4283 relation == RELATION_ISOLATED_BOARD) {
4285 /* Forget the old game and get the history (if any) of the new one */
4286 if (gameMode != BeginningOfGame) {
4290 if (appData.autoRaiseBoard) BoardToTop();
4292 if (gamenum == -1) {
4293 newGameMode = IcsIdle;
4294 } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
4295 appData.getMoveList && !reqFlag) {
4296 /* Need to get game history */
4297 ics_getting_history = H_REQUESTED;
4298 snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4302 /* Initially flip the board to have black on the bottom if playing
4303 black or if the ICS flip flag is set, but let the user change
4304 it with the Flip View button. */
4305 flipView = appData.autoFlipView ?
4306 (newGameMode == IcsPlayingBlack) || ics_flip :
4309 /* Done with values from previous mode; copy in new ones */
4310 gameMode = newGameMode;
4312 ics_gamenum = gamenum;
4313 if (gamenum == gs_gamenum) {
4314 int klen = strlen(gs_kind);
4315 if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
4316 snprintf(str, MSG_SIZ, "ICS %s", gs_kind);
4317 gameInfo.event = StrSave(str);
4319 gameInfo.event = StrSave("ICS game");
4321 gameInfo.site = StrSave(appData.icsHost);
4322 gameInfo.date = PGNDate();
4323 gameInfo.round = StrSave("-");
4324 gameInfo.white = StrSave(white);
4325 gameInfo.black = StrSave(black);
4326 timeControl = basetime * 60 * 1000;
4328 timeIncrement = increment * 1000;
4329 movesPerSession = 0;
4330 gameInfo.timeControl = TimeControlTagValue();
4331 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
4332 if (appData.debugMode) {
4333 fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
4334 fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
4335 setbuf(debugFP, NULL);
4338 gameInfo.outOfBook = NULL;
4340 /* Do we have the ratings? */
4341 if (strcmp(player1Name, white) == 0 &&
4342 strcmp(player2Name, black) == 0) {
4343 if (appData.debugMode)
4344 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4345 player1Rating, player2Rating);
4346 gameInfo.whiteRating = player1Rating;
4347 gameInfo.blackRating = player2Rating;
4348 } else if (strcmp(player2Name, white) == 0 &&
4349 strcmp(player1Name, black) == 0) {
4350 if (appData.debugMode)
4351 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4352 player2Rating, player1Rating);
4353 gameInfo.whiteRating = player2Rating;
4354 gameInfo.blackRating = player1Rating;
4356 player1Name[0] = player2Name[0] = NULLCHAR;
4358 /* Silence shouts if requested */
4359 if (appData.quietPlay &&
4360 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
4361 SendToICS(ics_prefix);
4362 SendToICS("set shout 0\n");
4366 /* Deal with midgame name changes */
4368 if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
4369 if (gameInfo.white) free(gameInfo.white);
4370 gameInfo.white = StrSave(white);
4372 if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
4373 if (gameInfo.black) free(gameInfo.black);
4374 gameInfo.black = StrSave(black);
4378 /* Throw away game result if anything actually changes in examine mode */
4379 if (gameMode == IcsExamining && !newGame) {
4380 gameInfo.result = GameUnfinished;
4381 if (gameInfo.resultDetails != NULL) {
4382 free(gameInfo.resultDetails);
4383 gameInfo.resultDetails = NULL;
4387 /* In pausing && IcsExamining mode, we ignore boards coming
4388 in if they are in a different variation than we are. */
4389 if (pauseExamInvalid) return;
4390 if (pausing && gameMode == IcsExamining) {
4391 if (moveNum <= pauseExamForwardMostMove) {
4392 pauseExamInvalid = TRUE;
4393 forwardMostMove = pauseExamForwardMostMove;
4398 if (appData.debugMode) {
4399 fprintf(debugFP, "load %dx%d board\n", files, ranks);
4401 /* Parse the board */
4402 for (k = 0; k < ranks; k++) {
4403 for (j = 0; j < files; j++)
4404 board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4405 if(gameInfo.holdingsWidth > 1) {
4406 board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4407 board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4410 if(moveNum==0 && gameInfo.variant == VariantSChess) {
4411 board[5][BOARD_RGHT+1] = WhiteAngel;
4412 board[6][BOARD_RGHT+1] = WhiteMarshall;
4413 board[1][0] = BlackMarshall;
4414 board[2][0] = BlackAngel;
4415 board[1][1] = board[2][1] = board[5][BOARD_RGHT] = board[6][BOARD_RGHT] = 1;
4417 CopyBoard(boards[moveNum], board);
4418 boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
4420 startedFromSetupPosition =
4421 !CompareBoards(board, initialPosition);
4422 if(startedFromSetupPosition)
4423 initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
4426 /* [HGM] Set castling rights. Take the outermost Rooks,
4427 to make it also work for FRC opening positions. Note that board12
4428 is really defective for later FRC positions, as it has no way to
4429 indicate which Rook can castle if they are on the same side of King.
4430 For the initial position we grant rights to the outermost Rooks,
4431 and remember thos rights, and we then copy them on positions
4432 later in an FRC game. This means WB might not recognize castlings with
4433 Rooks that have moved back to their original position as illegal,
4434 but in ICS mode that is not its job anyway.
4436 if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
4437 { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
4439 for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4440 if(board[0][i] == WhiteRook) j = i;
4441 initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4442 for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4443 if(board[0][i] == WhiteRook) j = i;
4444 initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4445 for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4446 if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4447 initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4448 for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4449 if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4450 initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4452 boards[moveNum][CASTLING][2] = boards[moveNum][CASTLING][5] = NoRights;
4453 if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
4454 for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4455 if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
4456 for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4457 if(board[BOARD_HEIGHT-1][k] == bKing)
4458 initialRights[5] = boards[moveNum][CASTLING][5] = k;
4459 if(gameInfo.variant == VariantTwoKings) {
4460 // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
4461 if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
4462 if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
4465 r = boards[moveNum][CASTLING][0] = initialRights[0];
4466 if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4467 r = boards[moveNum][CASTLING][1] = initialRights[1];
4468 if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4469 r = boards[moveNum][CASTLING][3] = initialRights[3];
4470 if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4471 r = boards[moveNum][CASTLING][4] = initialRights[4];
4472 if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4473 /* wildcastle kludge: always assume King has rights */
4474 r = boards[moveNum][CASTLING][2] = initialRights[2];
4475 r = boards[moveNum][CASTLING][5] = initialRights[5];
4477 /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4478 boards[moveNum][EP_STATUS] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
4481 if (ics_getting_history == H_GOT_REQ_HEADER ||
4482 ics_getting_history == H_GOT_UNREQ_HEADER) {
4483 /* This was an initial position from a move list, not
4484 the current position */
4488 /* Update currentMove and known move number limits */
4489 newMove = newGame || moveNum > forwardMostMove;
4492 forwardMostMove = backwardMostMove = currentMove = moveNum;
4493 if (gameMode == IcsExamining && moveNum == 0) {
4494 /* Workaround for ICS limitation: we are not told the wild
4495 type when starting to examine a game. But if we ask for
4496 the move list, the move list header will tell us */
4497 ics_getting_history = H_REQUESTED;
4498 snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4501 } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4502 || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4504 /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4505 /* [HGM] applied this also to an engine that is silently watching */
4506 if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4507 (gameMode == IcsObserving || gameMode == IcsExamining) &&
4508 gameInfo.variant == currentlyInitializedVariant) {
4509 takeback = forwardMostMove - moveNum;
4510 for (i = 0; i < takeback; i++) {
4511 if (appData.debugMode) fprintf(debugFP, "take back move\n");
4512 SendToProgram("undo\n", &first);
4517 forwardMostMove = moveNum;
4518 if (!pausing || currentMove > forwardMostMove)
4519 currentMove = forwardMostMove;
4521 /* New part of history that is not contiguous with old part */
4522 if (pausing && gameMode == IcsExamining) {
4523 pauseExamInvalid = TRUE;
4524 forwardMostMove = pauseExamForwardMostMove;
4527 if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4529 if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4530 // [HGM] when we will receive the move list we now request, it will be
4531 // fed to the engine from the first move on. So if the engine is not
4532 // in the initial position now, bring it there.
4533 InitChessProgram(&first, 0);
4536 ics_getting_history = H_REQUESTED;
4537 snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4540 forwardMostMove = backwardMostMove = currentMove = moveNum;
4543 /* Update the clocks */
4544 if (strchr(elapsed_time, '.')) {
4546 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4547 timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4549 /* Time is in seconds */
4550 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4551 timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4556 if (appData.zippyPlay && newGame &&
4557 gameMode != IcsObserving && gameMode != IcsIdle &&
4558 gameMode != IcsExamining)
4559 ZippyFirstBoard(moveNum, basetime, increment);
4562 /* Put the move on the move list, first converting
4563 to canonical algebraic form. */
4565 if (appData.debugMode) {
4566 if (appData.debugMode) { int f = forwardMostMove;
4567 fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4568 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4569 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4571 fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4572 fprintf(debugFP, "moveNum = %d\n", moveNum);
4573 fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4574 setbuf(debugFP, NULL);
4576 if (moveNum <= backwardMostMove) {
4577 /* We don't know what the board looked like before
4579 safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4580 strcat(parseList[moveNum - 1], " ");
4581 strcat(parseList[moveNum - 1], elapsed_time);
4582 moveList[moveNum - 1][0] = NULLCHAR;
4583 } else if (strcmp(move_str, "none") == 0) {
4584 // [HGM] long SAN: swapped order; test for 'none' before parsing move
4585 /* Again, we don't know what the board looked like;
4586 this is really the start of the game. */
4587 parseList[moveNum - 1][0] = NULLCHAR;
4588 moveList[moveNum - 1][0] = NULLCHAR;
4589 backwardMostMove = moveNum;
4590 startedFromSetupPosition = TRUE;
4591 fromX = fromY = toX = toY = -1;
4593 // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
4594 // So we parse the long-algebraic move string in stead of the SAN move
4595 int valid; char buf[MSG_SIZ], *prom;
4597 if(gameInfo.variant == VariantShogi && !strchr(move_str, '=') && !strchr(move_str, '@'))
4598 strcat(move_str, "="); // if ICS does not say 'promote' on non-drop, we defer.
4599 // str looks something like "Q/a1-a2"; kill the slash
4601 snprintf(buf, MSG_SIZ,"%c%s", str[0], str+2);
4602 else safeStrCpy(buf, str, sizeof(buf)/sizeof(buf[0])); // might be castling
4603 if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
4604 strcat(buf, prom); // long move lacks promo specification!
4605 if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4606 if(appData.debugMode)
4607 fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4608 safeStrCpy(move_str, buf, MSG_SIZ);
4610 valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4611 &fromX, &fromY, &toX, &toY, &promoChar)
4612 || ParseOneMove(buf, moveNum - 1, &moveType,
4613 &fromX, &fromY, &toX, &toY, &promoChar);
4614 // end of long SAN patch
4616 (void) CoordsToAlgebraic(boards[moveNum - 1],
4617 PosFlags(moveNum - 1),
4618 fromY, fromX, toY, toX, promoChar,
4619 parseList[moveNum-1]);
4620 switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4626 if(gameInfo.variant != VariantShogi)
4627 strcat(parseList[moveNum - 1], "+");
4630 case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4631 strcat(parseList[moveNum - 1], "#");
4634 strcat(parseList[moveNum - 1], " ");
4635 strcat(parseList[moveNum - 1], elapsed_time);
4636 /* currentMoveString is set as a side-effect of ParseOneMove */
4637 if(gameInfo.variant == VariantShogi && currentMoveString[4]) currentMoveString[4] = '^';
4638 safeStrCpy(moveList[moveNum - 1], currentMoveString, sizeof(moveList[moveNum - 1])/sizeof(moveList[moveNum - 1][0]));
4639 strcat(moveList[moveNum - 1], "\n");
4641 if(gameInfo.holdingsWidth && !appData.disguise && gameInfo.variant != VariantSuper && gameInfo.variant != VariantGreat
4642 && gameInfo.variant != VariantGrand&& gameInfo.variant != VariantSChess) // inherit info that ICS does not give from previous board
4643 for(k=0; k<ranks; k++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
4644 ChessSquare old, new = boards[moveNum][k][j];
4645 if(fromY == DROP_RANK && k==toY && j==toX) continue; // dropped pieces always stand for themselves
4646 old = (k==toY && j==toX) ? boards[moveNum-1][fromY][fromX] : boards[moveNum-1][k][j]; // trace back mover
4647 if(old == new) continue;
4648 if(old == PROMOTED new) boards[moveNum][k][j] = old; // prevent promoted pieces to revert to primordial ones
4649 else if(new == WhiteWazir || new == BlackWazir) {
4650 if(old < WhiteCannon || old >= BlackPawn && old < BlackCannon)
4651 boards[moveNum][k][j] = PROMOTED old; // choose correct type of Gold in promotion
4652 else boards[moveNum][k][j] = old; // preserve type of Gold
4653 } else if((old == WhitePawn || old == BlackPawn) && new != EmptySquare) // Pawn promotions (but not e.p.capture!)
4654 boards[moveNum][k][j] = PROMOTED new; // use non-primordial representation of chosen piece
4657 /* Move from ICS was illegal!? Punt. */
4658 if (appData.debugMode) {
4659 fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4660 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4662 safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4663 strcat(parseList[moveNum - 1], " ");
4664 strcat(parseList[moveNum - 1], elapsed_time);
4665 moveList[moveNum - 1][0] = NULLCHAR;
4666 fromX = fromY = toX = toY = -1;
4669 if (appData.debugMode) {
4670 fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4671 setbuf(debugFP, NULL);
4675 /* Send move to chess program (BEFORE animating it). */
4676 if (appData.zippyPlay && !newGame && newMove &&
4677 (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4679 if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4680 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4681 if (moveList[moveNum - 1][0] == NULLCHAR) {
4682 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"),
4684 DisplayError(str, 0);
4686 if (first.sendTime) {
4687 SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4689 bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4690 if (firstMove && !bookHit) {
4692 if (first.useColors) {
4693 SendToProgram(gameMode == IcsPlayingWhite ?
4695 "black\ngo\n", &first);
4697 SendToProgram("go\n", &first);
4699 first.maybeThinking = TRUE;
4702 } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4703 if (moveList[moveNum - 1][0] == NULLCHAR) {
4704 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"), move_str);
4705 DisplayError(str, 0);
4707 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4708 SendMoveToProgram(moveNum - 1, &first);
4715 if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4716 /* If move comes from a remote source, animate it. If it
4717 isn't remote, it will have already been animated. */
4718 if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4719 AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4721 if (!pausing && appData.highlightLastMove) {
4722 SetHighlights(fromX, fromY, toX, toY);
4726 /* Start the clocks */
4727 whiteFlag = blackFlag = FALSE;
4728 appData.clockMode = !(basetime == 0 && increment == 0);
4730 ics_clock_paused = TRUE;
4732 } else if (ticking == 1) {
4733 ics_clock_paused = FALSE;
4735 if (gameMode == IcsIdle ||
4736 relation == RELATION_OBSERVING_STATIC ||
4737 relation == RELATION_EXAMINING ||
4739 DisplayBothClocks();
4743 /* Display opponents and material strengths */
4744 if (gameInfo.variant != VariantBughouse &&
4745 gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4746 if (tinyLayout || smallLayout) {
4747 if(gameInfo.variant == VariantNormal)
4748 snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d}",
4749 gameInfo.white, white_stren, gameInfo.black, black_stren,
4750 basetime, increment);
4752 snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d w%d}",
4753 gameInfo.white, white_stren, gameInfo.black, black_stren,
4754 basetime, increment, (int) gameInfo.variant);
4756 if(gameInfo.variant == VariantNormal)
4757 snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d}",
4758 gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
4759 basetime, increment);
4761 snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d %s}",
4762 gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
4763 basetime, increment, VariantName(gameInfo.variant));
4766 if (appData.debugMode) {
4767 fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
4772 /* Display the board */
4773 if (!pausing && !appData.noGUI) {
4775 if (appData.premove)
4777 ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
4778 ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
4779 ClearPremoveHighlights();
4781 j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
4782 if(partnerUp) { flipView = originalFlip; partnerUp = FALSE; j = TRUE; } // [HGM] bughouse: restore view
4783 DrawPosition(j, boards[currentMove]);
4785 DisplayMove(moveNum - 1);
4786 if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4787 !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4788 (gameMode == IcsPlayingBlack) && (WhiteOnMove(moveNum)) ) ) {
4789 if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4793 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4795 if(bookHit) { // [HGM] book: simulate book reply
4796 static char bookMove[MSG_SIZ]; // a bit generous?
4798 programStats.nodes = programStats.depth = programStats.time =
4799 programStats.score = programStats.got_only_move = 0;
4800 sprintf(programStats.movelist, "%s (xbook)", bookHit);
4802 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
4803 strcat(bookMove, bookHit);
4804 HandleMachineMove(bookMove, &first);
4813 if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4814 ics_getting_history = H_REQUESTED;
4815 snprintf(buf, MSG_SIZ, "%smoves %d\n", ics_prefix, ics_gamenum);
4821 AnalysisPeriodicEvent (int force)
4823 if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4824 && !force) || !appData.periodicUpdates)
4827 /* Send . command to Crafty to collect stats */
4828 SendToProgram(".\n", &first);
4830 /* Don't send another until we get a response (this makes
4831 us stop sending to old Crafty's which don't understand
4832 the "." command (sending illegal cmds resets node count & time,
4833 which looks bad)) */
4834 programStats.ok_to_send = 0;
4838 ics_update_width (int new_width)
4840 ics_printf("set width %d\n", new_width);
4844 SendMoveToProgram (int moveNum, ChessProgramState *cps)
4848 if(moveList[moveNum][1] == '@' && moveList[moveNum][0] == '@') {
4849 // null move in variant where engine does not understand it (for analysis purposes)
4850 SendBoard(cps, moveNum + 1); // send position after move in stead.
4853 if (cps->useUsermove) {
4854 SendToProgram("usermove ", cps);
4858 if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4859 int len = space - parseList[moveNum];
4860 memcpy(buf, parseList[moveNum], len);
4862 buf[len] = NULLCHAR;
4864 snprintf(buf, MSG_SIZ,"%s\n", parseList[moveNum]);
4866 SendToProgram(buf, cps);
4868 if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4869 AlphaRank(moveList[moveNum], 4);
4870 SendToProgram(moveList[moveNum], cps);
4871 AlphaRank(moveList[moveNum], 4); // and back
4873 /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
4874 * the engine. It would be nice to have a better way to identify castle
4876 if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
4877 && cps->useOOCastle) {
4878 int fromX = moveList[moveNum][0] - AAA;
4879 int fromY = moveList[moveNum][1] - ONE;
4880 int toX = moveList[moveNum][2] - AAA;
4881 int toY = moveList[moveNum][3] - ONE;
4882 if((boards[moveNum][fromY][fromX] == WhiteKing
4883 && boards[moveNum][toY][toX] == WhiteRook)
4884 || (boards[moveNum][fromY][fromX] == BlackKing
4885 && boards[moveNum][toY][toX] == BlackRook)) {
4886 if(toX > fromX) SendToProgram("O-O\n", cps);
4887 else SendToProgram("O-O-O\n", cps);
4889 else SendToProgram(moveList[moveNum], cps);
4891 if(BOARD_HEIGHT > 10) { // [HGM] big: convert ranks to double-digit where needed
4892 if(moveList[moveNum][1] == '@' && (BOARD_HEIGHT < 16 || moveList[moveNum][0] <= 'Z')) { // drop move
4893 if(moveList[moveNum][0]== '@') snprintf(buf, MSG_SIZ, "@@@@\n"); else
4894 snprintf(buf, MSG_SIZ, "%c@%c%d%s", moveList[moveNum][0],
4895 moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
4897 snprintf(buf, MSG_SIZ, "%c%d%c%d%s", moveList[moveNum][0], moveList[moveNum][1] - '0',
4898 moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
4899 SendToProgram(buf, cps);
4901 else SendToProgram(moveList[moveNum], cps);
4902 /* End of additions by Tord */
4905 /* [HGM] setting up the opening has brought engine in force mode! */
4906 /* Send 'go' if we are in a mode where machine should play. */
4907 if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4908 (gameMode == TwoMachinesPlay ||
4910 gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite ||
4912 gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4913 SendToProgram("go\n", cps);
4914 if (appData.debugMode) {
4915 fprintf(debugFP, "(extra)\n");
4918 setboardSpoiledMachineBlack = 0;
4922 SendMoveToICS (ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar)
4924 char user_move[MSG_SIZ];
4927 if(gameInfo.variant == VariantSChess && promoChar) {
4928 snprintf(suffix, 4, "=%c", toX == BOARD_WIDTH<<1 ? ToUpper(promoChar) : ToLower(promoChar));
4929 if(moveType == NormalMove) moveType = WhitePromotion; // kludge to do gating
4930 } else suffix[0] = NULLCHAR;
4934 snprintf(user_move, MSG_SIZ, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4935 (int)moveType, fromX, fromY, toX, toY);
4936 DisplayError(user_move + strlen("say "), 0);
4938 case WhiteKingSideCastle:
4939 case BlackKingSideCastle:
4940 case WhiteQueenSideCastleWild:
4941 case BlackQueenSideCastleWild:
4943 case WhiteHSideCastleFR:
4944 case BlackHSideCastleFR:
4946 snprintf(user_move, MSG_SIZ, "o-o%s\n", suffix);
4948 case WhiteQueenSideCastle:
4949 case BlackQueenSideCastle:
4950 case WhiteKingSideCastleWild:
4951 case BlackKingSideCastleWild:
4953 case WhiteASideCastleFR:
4954 case BlackASideCastleFR:
4956 snprintf(user_move, MSG_SIZ, "o-o-o%s\n",suffix);
4958 case WhiteNonPromotion:
4959 case BlackNonPromotion:
4960 sprintf(user_move, "%c%c%c%c==\n", AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4962 case WhitePromotion:
4963 case BlackPromotion:
4964 if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
4965 snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
4966 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4967 PieceToChar(WhiteFerz));
4968 else if(gameInfo.variant == VariantGreat)
4969 snprintf(user_move, MSG_SIZ,"%c%c%c%c=%c\n",
4970 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4971 PieceToChar(WhiteMan));
4973 snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
4974 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4980 snprintf(user_move, MSG_SIZ, "%c@%c%c\n",
4981 ToUpper(PieceToChar((ChessSquare) fromX)),
4982 AAA + toX, ONE + toY);
4984 case IllegalMove: /* could be a variant we don't quite understand */
4985 if(fromY == DROP_RANK) goto drop; // We need 'IllegalDrop' move type?
4987 case WhiteCapturesEnPassant:
4988 case BlackCapturesEnPassant:
4989 snprintf(user_move, MSG_SIZ,"%c%c%c%c\n",
4990 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4993 SendToICS(user_move);
4994 if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
4995 ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
5000 { // [HGM] upload: send entire stored game to ICS as long-algebraic moves.
5001 int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
5002 static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
5003 if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
5004 DisplayError(_("You cannot do this while you are playing or observing"), 0);
5007 if(gameMode != IcsExamining) { // is this ever not the case?
5008 char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
5010 if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
5011 snprintf(command,MSG_SIZ, "match %s", ics_handle);
5012 } else { // on FICS we must first go to general examine mode
5013 safeStrCpy(command, "examine\nbsetup", sizeof(command)/sizeof(command[0])); // and specify variant within it with bsetups
5015 if(gameInfo.variant != VariantNormal) {
5016 // try figure out wild number, as xboard names are not always valid on ICS
5017 for(i=1; i<=36; i++) {
5018 snprintf(buf, MSG_SIZ, "wild/%d", i);
5019 if(StringToVariant(buf) == gameInfo.variant) break;
5021 if(i<=36 && ics_type == ICS_ICC) snprintf(buf, MSG_SIZ,"%s w%d\n", command, i);
5022 else if(i == 22) snprintf(buf,MSG_SIZ, "%s fr\n", command);
5023 else snprintf(buf, MSG_SIZ,"%s %s\n", command, VariantName(gameInfo.variant));
5024 } else snprintf(buf, MSG_SIZ,"%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
5025 SendToICS(ics_prefix);
5027 if(startedFromSetupPosition || backwardMostMove != 0) {
5028 fen = PositionToFEN(backwardMostMove, NULL);
5029 if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
5030 snprintf(buf, MSG_SIZ,"loadfen %s\n", fen);
5032 } else { // FICS: everything has to set by separate bsetup commands
5033 p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board
5034 snprintf(buf, MSG_SIZ,"bsetup fen %s\n", fen);
5036 if(!WhiteOnMove(backwardMostMove)) {
5037 SendToICS("bsetup tomove black\n");
5039 i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL);
5040 snprintf(buf, MSG_SIZ,"bsetup wcastle %s\n", castlingStrings[i]);
5042 i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
5043 snprintf(buf, MSG_SIZ, "bsetup bcastle %s\n", castlingStrings[i]);
5045 i = boards[backwardMostMove][EP_STATUS];
5046 if(i >= 0) { // set e.p.
5047 snprintf(buf, MSG_SIZ,"bsetup eppos %c\n", i+AAA);
5053 if(bsetup || ics_type != ICS_ICC && gameInfo.variant != VariantNormal)
5054 SendToICS("bsetup done\n"); // switch to normal examining.
5056 for(i = backwardMostMove; i<last; i++) {
5058 snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s\n", parseList[i]);
5061 SendToICS(ics_prefix);
5062 SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
5066 CoordsToComputerAlgebraic (int rf, int ff, int rt, int ft, char promoChar, char move[7])
5068 if (rf == DROP_RANK) {
5069 if(ff == EmptySquare) sprintf(move, "@@@@\n"); else // [HGM] pass
5070 sprintf(move, "%c@%c%c\n",
5071 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
5073 if (promoChar == 'x' || promoChar == NULLCHAR) {
5074 sprintf(move, "%c%c%c%c\n",
5075 AAA + ff, ONE + rf, AAA + ft, ONE + rt);
5077 sprintf(move, "%c%c%c%c%c\n",
5078 AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
5084 ProcessICSInitScript (FILE *f)
5088 while (fgets(buf, MSG_SIZ, f)) {
5089 SendToICSDelayed(buf,(long)appData.msLoginDelay);
5096 static int lastX, lastY, selectFlag, dragging;
5101 ChessSquare king = WhiteKing, pawn = WhitePawn, last = promoSweep;
5102 if(gameInfo.variant == VariantKnightmate) king = WhiteUnicorn;
5103 if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway) king = EmptySquare;
5104 if(promoSweep >= BlackPawn) king = WHITE_TO_BLACK king, pawn = WHITE_TO_BLACK pawn;
5105 if(gameInfo.variant == VariantSpartan && pawn == BlackPawn) pawn = BlackLance, king = EmptySquare;
5106 if(fromY != BOARD_HEIGHT-2 && fromY != 1) pawn = EmptySquare;
5109 if(promoSweep == EmptySquare) promoSweep = BlackPawn; // wrap
5110 else if((int)promoSweep == -1) promoSweep = WhiteKing;
5111 else if(promoSweep == BlackPawn && step < 0) promoSweep = WhitePawn;
5112 else if(promoSweep == WhiteKing && step > 0) promoSweep = BlackKing;
5113 if(!step) step = -1;
5114 } while(PieceToChar(promoSweep) == '.' || PieceToChar(promoSweep) == '~' || promoSweep == pawn ||
5115 appData.testLegality && (promoSweep == king ||
5116 gameInfo.variant == VariantShogi && promoSweep != PROMOTED last && last != PROMOTED promoSweep && last != promoSweep));
5117 ChangeDragPiece(promoSweep);
5121 PromoScroll (int x, int y)
5125 if(promoSweep == EmptySquare || !appData.sweepSelect) return FALSE;
5126 if(abs(x - lastX) < 25 && abs(y - lastY) < 25) return FALSE;
5127 if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5128 if(!step) return FALSE;
5129 lastX = x; lastY = y;
5130 if((promoSweep < BlackPawn) == flipView) step = -step;
5131 if(step > 0) selectFlag = 1;
5132 if(!selectFlag) Sweep(step);
5137 NextPiece (int step)
5139 ChessSquare piece = boards[currentMove][toY][toX];
5142 if(pieceSweep == EmptySquare) pieceSweep = WhitePawn; // wrap
5143 if((int)pieceSweep == -1) pieceSweep = BlackKing;
5144 if(!step) step = -1;
5145 } while(PieceToChar(pieceSweep) == '.');
5146 boards[currentMove][toY][toX] = pieceSweep;
5147 DrawPosition(FALSE, boards[currentMove]);
5148 boards[currentMove][toY][toX] = piece;
5150 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
5152 AlphaRank (char *move, int n)
5154 // char *p = move, c; int x, y;
5156 if (appData.debugMode) {
5157 fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
5161 move[2]>='0' && move[2]<='9' &&
5162 move[3]>='a' && move[3]<='x' ) {
5164 move[2] = BOARD_RGHT -1 - (move[2]-'1') + AAA;
5165 move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5167 if(move[0]>='0' && move[0]<='9' &&
5168 move[1]>='a' && move[1]<='x' &&
5169 move[2]>='0' && move[2]<='9' &&
5170 move[3]>='a' && move[3]<='x' ) {
5171 /* input move, Shogi -> normal */
5172 move[0] = BOARD_RGHT -1 - (move[0]-'1') + AAA;
5173 move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
5174 move[2] = BOARD_RGHT -1 - (move[2]-'1') + AAA;
5175 move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5178 move[3]>='0' && move[3]<='9' &&
5179 move[2]>='a' && move[2]<='x' ) {
5181 move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5182 move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5185 move[0]>='a' && move[0]<='x' &&
5186 move[3]>='0' && move[3]<='9' &&
5187 move[2]>='a' && move[2]<='x' ) {
5188 /* output move, normal -> Shogi */
5189 move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
5190 move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
5191 move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5192 move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5193 if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
5195 if (appData.debugMode) {
5196 fprintf(debugFP, " out = '%s'\n", move);
5200 char yy_textstr[8000];
5202 /* Parser for moves from gnuchess, ICS, or user typein box */
5204 ParseOneMove (char *move, int moveNum, ChessMove *moveType, int *fromX, int *fromY, int *toX, int *toY, char *promoChar)
5206 *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
5208 switch (*moveType) {
5209 case WhitePromotion:
5210 case BlackPromotion:
5211 case WhiteNonPromotion:
5212 case BlackNonPromotion:
5214 case WhiteCapturesEnPassant:
5215 case BlackCapturesEnPassant:
5216 case WhiteKingSideCastle:
5217 case WhiteQueenSideCastle:
5218 case BlackKingSideCastle:
5219 case BlackQueenSideCastle:
5220 case WhiteKingSideCastleWild:
5221 case WhiteQueenSideCastleWild:
5222 case BlackKingSideCastleWild:
5223 case BlackQueenSideCastleWild:
5224 /* Code added by Tord: */
5225 case WhiteHSideCastleFR:
5226 case WhiteASideCastleFR:
5227 case BlackHSideCastleFR:
5228 case BlackASideCastleFR:
5229 /* End of code added by Tord */
5230 case IllegalMove: /* bug or odd chess variant */
5231 *fromX = currentMoveString[0] - AAA;
5232 *fromY = currentMoveString[1] - ONE;
5233 *toX = currentMoveString[2] - AAA;
5234 *toY = currentMoveString[3] - ONE;
5235 *promoChar = currentMoveString[4];
5236 if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
5237 *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
5238 if (appData.debugMode) {
5239 fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
5241 *fromX = *fromY = *toX = *toY = 0;
5244 if (appData.testLegality) {
5245 return (*moveType != IllegalMove);
5247 return !(*fromX == *toX && *fromY == *toY) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
5248 WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
5253 *fromX = *moveType == WhiteDrop ?
5254 (int) CharToPiece(ToUpper(currentMoveString[0])) :
5255 (int) CharToPiece(ToLower(currentMoveString[0]));
5257 *toX = currentMoveString[2] - AAA;
5258 *toY = currentMoveString[3] - ONE;
5259 *promoChar = NULLCHAR;
5263 case ImpossibleMove:
5273 if (appData.debugMode) {
5274 fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
5277 *fromX = *fromY = *toX = *toY = 0;
5278 *promoChar = NULLCHAR;
5283 Boolean pushed = FALSE;
5284 char *lastParseAttempt;
5287 ParsePV (char *pv, Boolean storeComments, Boolean atEnd)
5288 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
5289 int fromX, fromY, toX, toY; char promoChar;
5294 if (gameMode == AnalyzeMode && currentMove < forwardMostMove) {
5295 PushInner(currentMove, forwardMostMove); // [HGM] engine might not be thinking on forwardMost position!
5298 endPV = forwardMostMove;
5300 while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
5301 if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
5302 lastParseAttempt = pv;
5303 valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
5304 if(appData.debugMode){
5305 fprintf(debugFP,"parsePV: %d %c%c%c%c yy='%s'\nPV = '%s'\n", valid, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, yy_textstr, pv);
5307 if(!valid && nr == 0 &&
5308 ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
5309 nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
5310 // Hande case where played move is different from leading PV move
5311 CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
5312 CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
5313 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
5314 if(!CompareBoards(boards[endPV], boards[endPV+2])) {
5315 endPV += 2; // if position different, keep this
5316 moveList[endPV-1][0] = fromX + AAA;
5317 moveList[endPV-1][1] = fromY + ONE;
5318 moveList[endPV-1][2] = toX + AAA;
5319 moveList[endPV-1][3] = toY + ONE;
5320 parseList[endPV-1][0] = NULLCHAR;
5321 safeStrCpy(moveList[endPV-2], "_0_0", sizeof(moveList[endPV-2])/sizeof(moveList[endPV-2][0])); // suppress premove highlight on takeback move
5324 pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
5325 if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
5326 if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
5327 if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
5328 valid++; // allow comments in PV
5332 if(endPV+1 > framePtr) break; // no space, truncate
5335 CopyBoard(boards[endPV], boards[endPV-1]);
5336 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
5337 CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, moveList[endPV - 1]);
5338 strncat(moveList[endPV-1], "\n", MOVE_LEN);
5339 CoordsToAlgebraic(boards[endPV - 1],
5340 PosFlags(endPV - 1),
5341 fromY, fromX, toY, toX, promoChar,
5342 parseList[endPV - 1]);
5344 if(atEnd == 2) return; // used hidden, for PV conversion
5345 currentMove = (atEnd || endPV == forwardMostMove) ? endPV : forwardMostMove + 1;
5346 if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5347 SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5348 moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5349 DrawPosition(TRUE, boards[currentMove]);
5353 MultiPV (ChessProgramState *cps)
5354 { // check if engine supports MultiPV, and if so, return the number of the option that sets it
5356 for(i=0; i<cps->nrOptions; i++)
5357 if(!strcmp(cps->option[i].name, "MultiPV") && cps->option[i].type == Spin)
5363 LoadMultiPV (int x, int y, char *buf, int index, int *start, int *end)
5365 int startPV, multi, lineStart, origIndex = index;
5366 char *p, buf2[MSG_SIZ];
5368 if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
5369 lastX = x; lastY = y;
5370 while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
5371 lineStart = startPV = index;
5372 while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
5373 if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
5375 do{ while(buf[index] && buf[index] != '\n') index++;
5376 } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
5378 if(lineStart == 0 && gameMode == AnalyzeMode && (multi = MultiPV(&first)) >= 0) {
5379 int n = first.option[multi].value;
5380 if(origIndex > 17 && origIndex < 24) { if(n>1) n--; } else if(origIndex > index - 6) n++;
5381 snprintf(buf2, MSG_SIZ, "option MultiPV=%d\n", n);
5382 if(first.option[multi].value != n) SendToProgram(buf2, &first);
5383 first.option[multi].value = n;
5387 ParsePV(buf+startPV, FALSE, gameMode != AnalyzeMode);
5388 *start = startPV; *end = index-1;
5395 static char buf[10*MSG_SIZ];
5396 int i, k=0, savedEnd=endPV, saveFMM = forwardMostMove;
5398 if(forwardMostMove < endPV) PushInner(forwardMostMove, endPV);
5399 ParsePV(pv, FALSE, 2); // this appends PV to game, suppressing any display of it
5400 for(i = forwardMostMove; i<endPV; i++){
5401 if(i&1) snprintf(buf+k, 10*MSG_SIZ-k, "%s ", parseList[i]);
5402 else snprintf(buf+k, 10*MSG_SIZ-k, "%d. %s ", i/2 + 1, parseList[i]);
5405 snprintf(buf+k, 10*MSG_SIZ-k, "%s", lastParseAttempt); // if we ran into stuff that could not be parsed, print it verbatim
5406 if(forwardMostMove < savedEnd) { PopInner(0); forwardMostMove = saveFMM; } // PopInner would set fmm to endPV!
5412 LoadPV (int x, int y)
5413 { // called on right mouse click to load PV
5414 int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
5415 lastX = x; lastY = y;
5416 ParsePV(lastPV[which], FALSE, TRUE); // load the PV of the thinking engine in the boards array.
5423 int oldFMM = forwardMostMove; // N.B.: this was currentMove before PV was loaded!
5424 if(endPV < 0) return;
5426 if(gameMode == AnalyzeMode && currentMove > forwardMostMove) {
5427 Boolean saveAnimate = appData.animate;
5429 if(shiftKey && storedGames < MAX_VARIATIONS-2) { // wants to start variation, and there is space
5430 if(storedGames == 1) GreyRevert(FALSE); // we already pushed the tail, so just make it official
5431 } else storedGames--; // abandon shelved tail of original game
5434 forwardMostMove = currentMove;
5435 currentMove = oldFMM;
5436 appData.animate = FALSE;
5437 ToNrEvent(forwardMostMove);
5438 appData.animate = saveAnimate;
5440 currentMove = forwardMostMove;
5441 if(pushed) { PopInner(0); pushed = FALSE; } // restore shelved game continuation
5442 ClearPremoveHighlights();
5443 DrawPosition(TRUE, boards[currentMove]);
5447 MovePV (int x, int y, int h)
5448 { // step through PV based on mouse coordinates (called on mouse move)
5449 int margin = h>>3, step = 0, threshold = (pieceSweep == EmptySquare ? 10 : 15);
5451 // we must somehow check if right button is still down (might be released off board!)
5452 if(endPV < 0 && pieceSweep == EmptySquare) return; // needed in XBoard because lastX/Y is shared :-(
5453 if(abs(x - lastX) < threshold && abs(y - lastY) < threshold) return;
5454 if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5456 lastX = x; lastY = y;
5458 if(pieceSweep != EmptySquare) { NextPiece(step); return; }
5459 if(endPV < 0) return;
5460 if(y < margin) step = 1; else
5461 if(y > h - margin) step = -1;
5462 if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
5463 currentMove += step;
5464 if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5465 SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5466 moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5467 DrawPosition(FALSE, boards[currentMove]);
5471 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
5472 // All positions will have equal probability, but the current method will not provide a unique
5473 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
5479 int piecesLeft[(int)BlackPawn];
5480 int seed, nrOfShuffles;
5483 GetPositionNumber ()
5484 { // sets global variable seed
5487 seed = appData.defaultFrcPosition;
5488 if(seed < 0) { // randomize based on time for negative FRC position numbers
5489 for(i=0; i<50; i++) seed += random();
5490 seed = random() ^ random() >> 8 ^ random() << 8;
5491 if(seed<0) seed = -seed;
5496 put (Board board, int pieceType, int rank, int n, int shade)
5497 // put the piece on the (n-1)-th empty squares of the given shade
5501 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5502 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5503 board[rank][i] = (ChessSquare) pieceType;
5504 squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5506 piecesLeft[pieceType]--;
5515 AddOnePiece (Board board, int pieceType, int rank, int shade)
5516 // calculate where the next piece goes, (any empty square), and put it there
5520 i = seed % squaresLeft[shade];
5521 nrOfShuffles *= squaresLeft[shade];
5522 seed /= squaresLeft[shade];
5523 put(board, pieceType, rank, i, shade);
5527 AddTwoPieces (Board board, int pieceType, int rank)
5528 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5530 int i, n=squaresLeft[ANY], j=n-1, k;
5532 k = n*(n-1)/2; // nr of possibilities, not counting permutations
5533 i = seed % k; // pick one
5536 while(i >= j) i -= j--;
5537 j = n - 1 - j; i += j;
5538 put(board, pieceType, rank, j, ANY);
5539 put(board, pieceType, rank, i, ANY);
5543 SetUpShuffle (Board board, int number)
5547 GetPositionNumber(); nrOfShuffles = 1;
5549 squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5550 squaresLeft[ANY] = BOARD_RGHT - BOARD_LEFT;
5551 squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5553 for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5555 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5556 p = (int) board[0][i];
5557 if(p < (int) BlackPawn) piecesLeft[p] ++;
5558 board[0][i] = EmptySquare;
5561 if(PosFlags(0) & F_ALL_CASTLE_OK) {
5562 // shuffles restricted to allow normal castling put KRR first
5563 if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5564 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5565 else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5566 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5567 if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5568 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5569 if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5570 put(board, WhiteRook, 0, 0, ANY);
5571 // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5574 if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5575 // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5576 for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5577 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5578 while(piecesLeft[p] >= 2) {
5579 AddOnePiece(board, p, 0, LITE);
5580 AddOnePiece(board, p, 0, DARK);
5582 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5585 for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5586 // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5587 // but we leave King and Rooks for last, to possibly obey FRC restriction
5588 if(p == (int)WhiteRook) continue;
5589 while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5590 if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY); // add the odd piece
5593 // now everything is placed, except perhaps King (Unicorn) and Rooks
5595 if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5596 // Last King gets castling rights
5597 while(piecesLeft[(int)WhiteUnicorn]) {
5598 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5599 initialRights[2] = initialRights[5] = board[CASTLING][2] = board[CASTLING][5] = i;
5602 while(piecesLeft[(int)WhiteKing]) {
5603 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5604 initialRights[2] = initialRights[5] = board[CASTLING][2] = board[CASTLING][5] = i;
5609 while(piecesLeft[(int)WhiteKing]) AddOnePiece(board, WhiteKing, 0, ANY);
5610 while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
5613 // Only Rooks can be left; simply place them all
5614 while(piecesLeft[(int)WhiteRook]) {
5615 i = put(board, WhiteRook, 0, 0, ANY);
5616 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
5619 initialRights[1] = initialRights[4] = board[CASTLING][1] = board[CASTLING][4] = i;
5621 initialRights[0] = initialRights[3] = board[CASTLING][0] = board[CASTLING][3] = i;
5624 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
5625 board[BOARD_HEIGHT-1][i] = (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
5628 if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
5632 SetCharTable (char *table, const char * map)
5633 /* [HGM] moved here from winboard.c because of its general usefulness */
5634 /* Basically a safe strcpy that uses the last character as King */
5636 int result = FALSE; int NrPieces;
5638 if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
5639 && NrPieces >= 12 && !(NrPieces&1)) {
5640 int i; /* [HGM] Accept even length from 12 to 34 */
5642 for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
5643 for( i=0; i<NrPieces/2-1; i++ ) {
5645 table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
5647 table[(int) WhiteKing] = map[NrPieces/2-1];
5648 table[(int) BlackKing] = map[NrPieces-1];
5657 Prelude (Board board)
5658 { // [HGM] superchess: random selection of exo-pieces
5659 int i, j, k; ChessSquare p;
5660 static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
5662 GetPositionNumber(); // use FRC position number
5664 if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
5665 SetCharTable(pieceToChar, appData.pieceToCharTable);
5666 for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
5667 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
5670 j = seed%4; seed /= 4;
5671 p = board[0][BOARD_LEFT+j]; board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5672 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
5673 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
5674 j = seed%3 + (seed%3 >= j); seed /= 3;
5675 p = board[0][BOARD_LEFT+j]; board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5676 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
5677 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
5678 j = seed%3; seed /= 3;
5679 p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5680 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
5681 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
5682 j = seed%2 + (seed%2 >= j); seed /= 2;
5683 p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5684 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
5685 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
5686 j = seed%4; seed /= 4; put(board, exoPieces[3], 0, j, ANY);
5687 j = seed%3; seed /= 3; put(board, exoPieces[2], 0, j, ANY);
5688 j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
5689 put(board, exoPieces[0], 0, 0, ANY);
5690 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
5694 InitPosition (int redraw)
5696 ChessSquare (* pieces)[BOARD_FILES];
5697 int i, j, pawnRow, overrule,
5698 oldx = gameInfo.boardWidth,
5699 oldy = gameInfo.boardHeight,
5700 oldh = gameInfo.holdingsWidth;
5703 if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
5705 /* [AS] Initialize pv info list [HGM] and game status */
5707 for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
5708 pvInfoList[i].depth = 0;
5709 boards[i][EP_STATUS] = EP_NONE;
5710 for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
5713 initialRulePlies = 0; /* 50-move counter start */
5715 castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
5716 castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
5720 /* [HGM] logic here is completely changed. In stead of full positions */
5721 /* the initialized data only consist of the two backranks. The switch */
5722 /* selects which one we will use, which is than copied to the Board */
5723 /* initialPosition, which for the rest is initialized by Pawns and */
5724 /* empty squares. This initial position is then copied to boards[0], */
5725 /* possibly after shuffling, so that it remains available. */
5727 gameInfo.holdingsWidth = 0; /* default board sizes */
5728 gameInfo.boardWidth = 8;
5729 gameInfo.boardHeight = 8;
5730 gameInfo.holdingsSize = 0;
5731 nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
5732 for(i=0; i<BOARD_FILES-2; i++)
5733 initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
5734 initialPosition[EP_STATUS] = EP_NONE;
5735 SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
5736 if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
5737 SetCharTable(pieceNickName, appData.pieceNickNames);
5738 else SetCharTable(pieceNickName, "............");
5741 switch (gameInfo.variant) {
5742 case VariantFischeRandom:
5743 shuffleOpenings = TRUE;
5746 case VariantShatranj:
5747 pieces = ShatranjArray;
5748 nrCastlingRights = 0;
5749 SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
5752 pieces = makrukArray;
5753 nrCastlingRights = 0;
5754 startedFromSetupPosition = TRUE;
5755 SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
5757 case VariantTwoKings:
5758 pieces = twoKingsArray;
5761 pieces = GrandArray;
5762 nrCastlingRights = 0;
5763 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5764 gameInfo.boardWidth = 10;
5765 gameInfo.boardHeight = 10;
5766 gameInfo.holdingsSize = 7;
5768 case VariantCapaRandom:
5769 shuffleOpenings = TRUE;
5770 case VariantCapablanca:
5771 pieces = CapablancaArray;
5772 gameInfo.boardWidth = 10;
5773 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5776 pieces = GothicArray;
5777 gameInfo.boardWidth = 10;
5778 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5781 SetCharTable(pieceToChar, "PNBRQ..HEKpnbrq..hek");
5782 gameInfo.holdingsSize = 7;
5785 pieces = JanusArray;
5786 gameInfo.boardWidth = 10;
5787 SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
5788 nrCastlingRights = 6;
5789 initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5790 initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5791 initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
5792 initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5793 initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5794 initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
5797 pieces = FalconArray;
5798 gameInfo.boardWidth = 10;
5799 SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
5801 case VariantXiangqi:
5802 pieces = XiangqiArray;
5803 gameInfo.boardWidth = 9;
5804 gameInfo.boardHeight = 10;
5805 nrCastlingRights = 0;
5806 SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
5809 pieces = ShogiArray;
5810 gameInfo.boardWidth = 9;
5811 gameInfo.boardHeight = 9;
5812 gameInfo.holdingsSize = 7;
5813 nrCastlingRights = 0;
5814 SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
5816 case VariantCourier:
5817 pieces = CourierArray;
5818 gameInfo.boardWidth = 12;
5819 nrCastlingRights = 0;
5820 SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
5822 case VariantKnightmate:
5823 pieces = KnightmateArray;
5824 SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
5826 case VariantSpartan:
5827 pieces = SpartanArray;
5828 SetCharTable(pieceToChar, "PNBRQ................K......lwg.....c...h..k");
5831 pieces = fairyArray;
5832 SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
5835 pieces = GreatArray;
5836 gameInfo.boardWidth = 10;
5837 SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
5838 gameInfo.holdingsSize = 8;
5842 SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
5843 gameInfo.holdingsSize = 8;
5844 startedFromSetupPosition = TRUE;
5846 case VariantCrazyhouse:
5847 case VariantBughouse:
5849 SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
5850 gameInfo.holdingsSize = 5;
5852 case VariantWildCastle:
5854 /* !!?shuffle with kings guaranteed to be on d or e file */
5855 shuffleOpenings = 1;
5857 case VariantNoCastle:
5859 nrCastlingRights = 0;
5860 /* !!?unconstrained back-rank shuffle */
5861 shuffleOpenings = 1;
5866 if(appData.NrFiles >= 0) {
5867 if(gameInfo.boardWidth != appData.NrFiles) overrule++;
5868 gameInfo.boardWidth = appData.NrFiles;
5870 if(appData.NrRanks >= 0) {
5871 gameInfo.boardHeight = appData.NrRanks;
5873 if(appData.holdingsSize >= 0) {
5874 i = appData.holdingsSize;
5875 if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
5876 gameInfo.holdingsSize = i;
5878 if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
5879 if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
5880 DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
5882 pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
5883 if(pawnRow < 1) pawnRow = 1;
5884 if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand) pawnRow = 2;
5886 /* User pieceToChar list overrules defaults */
5887 if(appData.pieceToCharTable != NULL)
5888 SetCharTable(pieceToChar, appData.pieceToCharTable);
5890 for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
5892 if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
5893 s = (ChessSquare) 0; /* account holding counts in guard band */
5894 for( i=0; i<BOARD_HEIGHT; i++ )
5895 initialPosition[i][j] = s;
5897 if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
5898 initialPosition[gameInfo.variant == VariantGrand][j] = pieces[0][j-gameInfo.holdingsWidth];
5899 initialPosition[pawnRow][j] = WhitePawn;
5900 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = gameInfo.variant == VariantSpartan ? BlackLance : BlackPawn;
5901 if(gameInfo.variant == VariantXiangqi) {
5903 initialPosition[pawnRow][j] =
5904 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
5905 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
5906 initialPosition[2][j] = WhiteCannon;
5907 initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
5911 if(gameInfo.variant == VariantGrand) {
5912 if(j==BOARD_LEFT || j>=BOARD_RGHT-1) {
5913 initialPosition[0][j] = WhiteRook;
5914 initialPosition[BOARD_HEIGHT-1][j] = BlackRook;
5917 initialPosition[BOARD_HEIGHT-1-(gameInfo.variant == VariantGrand)][j] = pieces[1][j-gameInfo.holdingsWidth];
5919 if( (gameInfo.variant == VariantShogi) && !overrule ) {
5922 initialPosition[1][j] = WhiteBishop;
5923 initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
5925 initialPosition[1][j] = WhiteRook;
5926 initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
5929 if( nrCastlingRights == -1) {
5930 /* [HGM] Build normal castling rights (must be done after board sizing!) */
5931 /* This sets default castling rights from none to normal corners */
5932 /* Variants with other castling rights must set them themselves above */
5933 nrCastlingRights = 6;
5935 initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5936 initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5937 initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
5938 initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5939 initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5940 initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
5943 if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
5944 if(gameInfo.variant == VariantGreat) { // promotion commoners
5945 initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
5946 initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
5947 initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
5948 initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
5950 if( gameInfo.variant == VariantSChess ) {
5951 initialPosition[1][0] = BlackMarshall;
5952 initialPosition[2][0] = BlackAngel;
5953 initialPosition[6][BOARD_WIDTH-1] = WhiteMarshall;
5954 initialPosition[5][BOARD_WIDTH-1] = WhiteAngel;
5955 initialPosition[1][1] = initialPosition[2][1] =
5956 initialPosition[6][BOARD_WIDTH-2] = initialPosition[5][BOARD_WIDTH-2] = 1;
5958 if (appData.debugMode) {
5959 fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
5961 if(shuffleOpenings) {
5962 SetUpShuffle(initialPosition, appData.defaultFrcPosition);
5963 startedFromSetupPosition = TRUE;
5965 if(startedFromPositionFile) {
5966 /* [HGM] loadPos: use PositionFile for every new game */
5967 CopyBoard(initialPosition, filePosition);
5968 for(i=0; i<nrCastlingRights; i++)
5969 initialRights[i] = filePosition[CASTLING][i];
5970 startedFromSetupPosition = TRUE;
5973 CopyBoard(boards[0], initialPosition);
5975 if(oldx != gameInfo.boardWidth ||
5976 oldy != gameInfo.boardHeight ||
5977 oldv != gameInfo.variant ||
5978 oldh != gameInfo.holdingsWidth
5980 InitDrawingSizes(-2 ,0);
5982 oldv = gameInfo.variant;
5984 DrawPosition(TRUE, boards[currentMove]);
5988 SendBoard (ChessProgramState *cps, int moveNum)
5990 char message[MSG_SIZ];
5992 if (cps->useSetboard) {
5993 char* fen = PositionToFEN(moveNum, cps->fenOverride);
5994 snprintf(message, MSG_SIZ,"setboard %s\n", fen);
5995 SendToProgram(message, cps);
6000 int i, j, left=0, right=BOARD_WIDTH;
6001 /* Kludge to set black to move, avoiding the troublesome and now
6002 * deprecated "black" command.
6004 if (!WhiteOnMove(moveNum)) // [HGM] but better a deprecated command than an illegal move...
6005 SendToProgram(boards[0][1][BOARD_LEFT] == WhitePawn ? "a2a3\n" : "black\n", cps);
6007 if(!cps->extendedEdit) left = BOARD_LEFT, right = BOARD_RGHT; // only board proper
6009 SendToProgram("edit\n", cps);
6010 SendToProgram("#\n", cps);
6011 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6012 bp = &boards[moveNum][i][left];
6013 for (j = left; j < right; j++, bp++) {
6014 if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6015 if ((int) *bp < (int) BlackPawn) {
6016 if(j == BOARD_RGHT+1)
6017 snprintf(message, MSG_SIZ, "%c@%d\n", PieceToChar(*bp), bp[-1]);
6018 else snprintf(message, MSG_SIZ, "%c%c%c\n", PieceToChar(*bp), AAA + j, ONE + i);
6019 if(message[0] == '+' || message[0] == '~') {
6020 snprintf(message, MSG_SIZ,"%c%c%c+\n",
6021 PieceToChar((ChessSquare)(DEMOTED *bp)),
6024 if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6025 message[1] = BOARD_RGHT - 1 - j + '1';
6026 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6028 SendToProgram(message, cps);
6033 SendToProgram("c\n", cps);
6034 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6035 bp = &boards[moveNum][i][left];
6036 for (j = left; j < right; j++, bp++) {
6037 if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6038 if (((int) *bp != (int) EmptySquare)
6039 && ((int) *bp >= (int) BlackPawn)) {
6040 if(j == BOARD_LEFT-2)
6041 snprintf(message, MSG_SIZ, "%c@%d\n", ToUpper(PieceToChar(*bp)), bp[1]);
6042 else snprintf(message,MSG_SIZ, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
6044 if(message[0] == '+' || message[0] == '~') {
6045 snprintf(message, MSG_SIZ,"%c%c%c+\n",
6046 PieceToChar((ChessSquare)(DEMOTED *bp)),
6049 if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6050 message[1] = BOARD_RGHT - 1 - j + '1';
6051 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6053 SendToProgram(message, cps);
6058 SendToProgram(".\n", cps);
6060 setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
6064 DefaultPromoChoice (int white)
6067 if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
6068 result = WhiteFerz; // no choice
6069 else if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway)
6070 result= WhiteKing; // in Suicide Q is the last thing we want
6071 else if(gameInfo.variant == VariantSpartan)
6072 result = white ? WhiteQueen : WhiteAngel;
6073 else result = WhiteQueen;
6074 if(!white) result = WHITE_TO_BLACK result;
6078 static int autoQueen; // [HGM] oneclick
6081 HasPromotionChoice (int fromX, int fromY, int toX, int toY, char *promoChoice, int sweepSelect)
6083 /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
6084 /* [HGM] add Shogi promotions */
6085 int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
6090 if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
6091 if(toX < BOARD_LEFT || toX >= BOARD_RGHT) return FALSE; // move into holdings
6093 if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
6094 !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
6097 piece = boards[currentMove][fromY][fromX];
6098 if(gameInfo.variant == VariantShogi) {
6099 promotionZoneSize = BOARD_HEIGHT/3;
6100 highestPromotingPiece = (int)WhiteFerz;
6101 } else if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand) {
6102 promotionZoneSize = 3;
6105 // Treat Lance as Pawn when it is not representing Amazon
6106 if(gameInfo.variant != VariantSuper) {
6107 if(piece == WhiteLance) piece = WhitePawn; else
6108 if(piece == BlackLance) piece = BlackPawn;
6111 // next weed out all moves that do not touch the promotion zone at all
6112 if((int)piece >= BlackPawn) {
6113 if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
6115 highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
6117 if( toY < BOARD_HEIGHT - promotionZoneSize &&
6118 fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
6121 if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
6123 // weed out mandatory Shogi promotions
6124 if(gameInfo.variant == VariantShogi) {
6125 if(piece >= BlackPawn) {
6126 if(toY == 0 && piece == BlackPawn ||
6127 toY == 0 && piece == BlackQueen ||
6128 toY <= 1 && piece == BlackKnight) {
6133 if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
6134 toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
6135 toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
6142 // weed out obviously illegal Pawn moves
6143 if(appData.testLegality && (piece == WhitePawn || piece == BlackPawn) ) {
6144 if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
6145 if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
6146 if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
6147 if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
6148 // note we are not allowed to test for valid (non-)capture, due to premove
6151 // we either have a choice what to promote to, or (in Shogi) whether to promote
6152 if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk) {
6153 *promoChoice = PieceToChar(BlackFerz); // no choice
6156 // no sense asking what we must promote to if it is going to explode...
6157 if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) {
6158 *promoChoice = PieceToChar(BlackQueen); // Queen as good as any
6161 // give caller the default choice even if we will not make it
6162 *promoChoice = ToLower(PieceToChar(defaultPromoChoice));
6163 if(gameInfo.variant == VariantShogi) *promoChoice = (defaultPromoChoice == piece ? '=' : '+');
6164 if( sweepSelect && gameInfo.variant != VariantGreat
6165 && gameInfo.variant != VariantGrand
6166 && gameInfo.variant != VariantSuper) return FALSE;
6167 if(autoQueen) return FALSE; // predetermined
6169 // suppress promotion popup on illegal moves that are not premoves
6170 premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
6171 gameMode == IcsPlayingBlack && WhiteOnMove(currentMove);
6172 if(appData.testLegality && !premove) {
6173 moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6174 fromY, fromX, toY, toX, gameInfo.variant == VariantShogi ? '+' : NULLCHAR);
6175 if(moveType != WhitePromotion && moveType != BlackPromotion)
6183 InPalace (int row, int column)
6184 { /* [HGM] for Xiangqi */
6185 if( (row < 3 || row > BOARD_HEIGHT-4) &&
6186 column < (BOARD_WIDTH + 4)/2 &&
6187 column > (BOARD_WIDTH - 5)/2 ) return TRUE;
6192 PieceForSquare (int x, int y)
6194 if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
6197 return boards[currentMove][y][x];
6201 OKToStartUserMove (int x, int y)
6203 ChessSquare from_piece;
6206 if (matchMode) return FALSE;
6207 if (gameMode == EditPosition) return TRUE;
6209 if (x >= 0 && y >= 0)
6210 from_piece = boards[currentMove][y][x];
6212 from_piece = EmptySquare;
6214 if (from_piece == EmptySquare) return FALSE;
6216 white_piece = (int)from_piece >= (int)WhitePawn &&
6217 (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
6221 case TwoMachinesPlay:
6229 case MachinePlaysWhite:
6230 case IcsPlayingBlack:
6231 if (appData.zippyPlay) return FALSE;
6233 DisplayMoveError(_("You are playing Black"));
6238 case MachinePlaysBlack:
6239 case IcsPlayingWhite:
6240 if (appData.zippyPlay) return FALSE;
6242 DisplayMoveError(_("You are playing White"));
6247 case PlayFromGameFile:
6248 if(!shiftKey || !appData.variations) return FALSE; // [HGM] allow starting variation in this mode
6250 if (!white_piece && WhiteOnMove(currentMove)) {
6251 DisplayMoveError(_("It is White's turn"));
6254 if (white_piece && !WhiteOnMove(currentMove)) {
6255 DisplayMoveError(_("It is Black's turn"));
6258 if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
6259 /* Editing correspondence game history */
6260 /* Could disallow this or prompt for confirmation */
6265 case BeginningOfGame:
6266 if (appData.icsActive) return FALSE;
6267 if (!appData.noChessProgram) {
6269 DisplayMoveError(_("You are playing White"));
6276 if (!white_piece && WhiteOnMove(currentMove)) {
6277 DisplayMoveError(_("It is White's turn"));
6280 if (white_piece && !WhiteOnMove(currentMove)) {
6281 DisplayMoveError(_("It is Black's turn"));
6290 if (currentMove != forwardMostMove && gameMode != AnalyzeMode
6291 && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
6292 && gameMode != PlayFromGameFile // [HGM] as EditGame, with protected main line
6293 && gameMode != AnalyzeFile && gameMode != Training) {
6294 DisplayMoveError(_("Displayed position is not current"));
6301 OnlyMove (int *x, int *y, Boolean captures)
6303 DisambiguateClosure cl;
6304 if (appData.zippyPlay || !appData.testLegality) return FALSE;
6306 case MachinePlaysBlack:
6307 case IcsPlayingWhite:
6308 case BeginningOfGame:
6309 if(!WhiteOnMove(currentMove)) return FALSE;
6311 case MachinePlaysWhite:
6312 case IcsPlayingBlack:
6313 if(WhiteOnMove(currentMove)) return FALSE;
6320 cl.pieceIn = EmptySquare;
6325 cl.promoCharIn = NULLCHAR;
6326 Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6327 if( cl.kind == NormalMove ||
6328 cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6329 cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6330 cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6337 if(cl.kind != ImpossibleMove) return FALSE;
6338 cl.pieceIn = EmptySquare;
6343 cl.promoCharIn = NULLCHAR;
6344 Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6345 if( cl.kind == NormalMove ||
6346 cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6347 cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6348 cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6353 autoQueen = TRUE; // act as if autoQueen on when we click to-square
6359 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
6360 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
6361 int lastLoadGameUseList = FALSE;
6362 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
6363 ChessMove lastLoadGameStart = EndOfFile;
6366 UserMoveEvent(int fromX, int fromY, int toX, int toY, int promoChar)
6369 ChessSquare pdown, pup;
6371 /* Check if the user is playing in turn. This is complicated because we
6372 let the user "pick up" a piece before it is his turn. So the piece he
6373 tried to pick up may have been captured by the time he puts it down!
6374 Therefore we use the color the user is supposed to be playing in this
6375 test, not the color of the piece that is currently on the starting
6376 square---except in EditGame mode, where the user is playing both
6377 sides; fortunately there the capture race can't happen. (It can
6378 now happen in IcsExamining mode, but that's just too bad. The user
6379 will get a somewhat confusing message in that case.)
6384 case TwoMachinesPlay:
6388 /* We switched into a game mode where moves are not accepted,
6389 perhaps while the mouse button was down. */
6392 case MachinePlaysWhite:
6393 /* User is moving for Black */
6394 if (WhiteOnMove(currentMove)) {
6395 DisplayMoveError(_("It is White's turn"));
6400 case MachinePlaysBlack:
6401 /* User is moving for White */
6402 if (!WhiteOnMove(currentMove)) {
6403 DisplayMoveError(_("It is Black's turn"));
6408 case PlayFromGameFile:
6409 if(!shiftKey ||!appData.variations) return; // [HGM] only variations
6412 case BeginningOfGame:
6415 if(fromY == DROP_RANK) break; // [HGM] drop moves (entered through move type-in) are automatically assigned to side-to-move
6416 if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
6417 (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
6418 /* User is moving for Black */
6419 if (WhiteOnMove(currentMove)) {
6420 DisplayMoveError(_("It is White's turn"));
6424 /* User is moving for White */
6425 if (!WhiteOnMove(currentMove)) {
6426 DisplayMoveError(_("It is Black's turn"));
6432 case IcsPlayingBlack:
6433 /* User is moving for Black */
6434 if (WhiteOnMove(currentMove)) {
6435 if (!appData.premove) {
6436 DisplayMoveError(_("It is White's turn"));
6437 } else if (toX >= 0 && toY >= 0) {
6440 premoveFromX = fromX;
6441 premoveFromY = fromY;
6442 premovePromoChar = promoChar;
6444 if (appData.debugMode)
6445 fprintf(debugFP, "Got premove: fromX %d,"
6446 "fromY %d, toX %d, toY %d\n",
6447 fromX, fromY, toX, toY);
6453 case IcsPlayingWhite:
6454 /* User is moving for White */
6455 if (!WhiteOnMove(currentMove)) {
6456 if (!appData.premove) {
6457 DisplayMoveError(_("It is Black's turn"));
6458 } else if (toX >= 0 && toY >= 0) {
6461 premoveFromX = fromX;
6462 premoveFromY = fromY;
6463 premovePromoChar = promoChar;
6465 if (appData.debugMode)
6466 fprintf(debugFP, "Got premove: fromX %d,"
6467 "fromY %d, toX %d, toY %d\n",
6468 fromX, fromY, toX, toY);
6478 /* EditPosition, empty square, or different color piece;
6479 click-click move is possible */
6480 if (toX == -2 || toY == -2) {
6481 boards[0][fromY][fromX] = EmptySquare;
6482 DrawPosition(FALSE, boards[currentMove]);
6484 } else if (toX >= 0 && toY >= 0) {
6485 boards[0][toY][toX] = boards[0][fromY][fromX];
6486 if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
6487 if(boards[0][fromY][0] != EmptySquare) {
6488 if(boards[0][fromY][1]) boards[0][fromY][1]--;
6489 if(boards[0][fromY][1] == 0) boards[0][fromY][0] = EmptySquare;
6492 if(fromX == BOARD_RGHT+1) {
6493 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
6494 if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
6495 if(boards[0][fromY][BOARD_WIDTH-2] == 0) boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
6498 boards[0][fromY][fromX] = EmptySquare;
6499 DrawPosition(FALSE, boards[currentMove]);
6505 if(toX < 0 || toY < 0) return;
6506 pdown = boards[currentMove][fromY][fromX];
6507 pup = boards[currentMove][toY][toX];
6509 /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
6510 if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ) {
6511 if( pup != EmptySquare ) return;
6512 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
6513 if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n",
6514 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
6515 // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
6516 if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
6517 fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
6518 while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++;
6522 /* [HGM] always test for legality, to get promotion info */
6523 moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6524 fromY, fromX, toY, toX, promoChar);
6526 if(fromY == DROP_RANK && fromX == EmptySquare && (gameMode == AnalyzeMode || gameMode == EditGame)) moveType = NormalMove;
6528 /* [HGM] but possibly ignore an IllegalMove result */
6529 if (appData.testLegality) {
6530 if (moveType == IllegalMove || moveType == ImpossibleMove) {
6531 DisplayMoveError(_("Illegal move"));
6536 FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
6539 /* Common tail of UserMoveEvent and DropMenuEvent */
6541 FinishMove (ChessMove moveType, int fromX, int fromY, int toX, int toY, int promoChar)
6545 if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) && promoChar != NULLCHAR) {
6546 // [HGM] superchess: suppress promotions to non-available piece (but P always allowed)
6547 int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
6548 if(WhiteOnMove(currentMove)) {
6549 if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
6551 if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
6555 /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
6556 move type in caller when we know the move is a legal promotion */
6557 if(moveType == NormalMove && promoChar)
6558 moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
6560 /* [HGM] <popupFix> The following if has been moved here from
6561 UserMoveEvent(). Because it seemed to belong here (why not allow
6562 piece drops in training games?), and because it can only be
6563 performed after it is known to what we promote. */
6564 if (gameMode == Training) {
6565 /* compare the move played on the board to the next move in the
6566 * game. If they match, display the move and the opponent's response.
6567 * If they don't match, display an error message.
6571 CopyBoard(testBoard, boards[currentMove]);
6572 ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
6574 if (CompareBoards(testBoard, boards[currentMove+1])) {
6575 ForwardInner(currentMove+1);
6577 /* Autoplay the opponent's response.
6578 * if appData.animate was TRUE when Training mode was entered,
6579 * the response will be animated.
6581 saveAnimate = appData.animate;
6582 appData.animate = animateTraining;
6583 ForwardInner(currentMove+1);
6584 appData.animate = saveAnimate;
6586 /* check for the end of the game */
6587 if (currentMove >= forwardMostMove) {
6588 gameMode = PlayFromGameFile;
6590 SetTrainingModeOff();
6591 DisplayInformation(_("End of game"));
6594 DisplayError(_("Incorrect move"), 0);
6599 /* Ok, now we know that the move is good, so we can kill
6600 the previous line in Analysis Mode */
6601 if ((gameMode == AnalyzeMode || gameMode == EditGame || gameMode == PlayFromGameFile && appData.variations && shiftKey)
6602 && currentMove < forwardMostMove) {
6603 if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
6604 else forwardMostMove = currentMove;
6607 /* If we need the chess program but it's dead, restart it */
6608 ResurrectChessProgram();
6610 /* A user move restarts a paused game*/
6614 thinkOutput[0] = NULLCHAR;
6616 MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
6618 if(Adjudicate(NULL)) { // [HGM] adjudicate: take care of automatic game end
6619 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6623 if (gameMode == BeginningOfGame) {
6624 if (appData.noChessProgram) {
6625 gameMode = EditGame;
6629 gameMode = MachinePlaysBlack;
6632 snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
6634 if (first.sendName) {
6635 snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
6636 SendToProgram(buf, &first);
6643 /* Relay move to ICS or chess engine */
6644 if (appData.icsActive) {
6645 if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
6646 gameMode == IcsExamining) {
6647 if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
6648 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
6650 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6652 // also send plain move, in case ICS does not understand atomic claims
6653 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6657 if (first.sendTime && (gameMode == BeginningOfGame ||
6658 gameMode == MachinePlaysWhite ||
6659 gameMode == MachinePlaysBlack)) {
6660 SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
6662 if (gameMode != EditGame && gameMode != PlayFromGameFile) {
6663 // [HGM] book: if program might be playing, let it use book
6664 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
6665 first.maybeThinking = TRUE;
6666 } else if(fromY == DROP_RANK && fromX == EmptySquare) {
6667 if(!first.useSetboard) SendToProgram("undo\n", &first); // kludge to change stm in engines that do not support setboard
6668 SendBoard(&first, currentMove+1);
6669 } else SendMoveToProgram(forwardMostMove-1, &first);
6670 if (currentMove == cmailOldMove + 1) {
6671 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
6675 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6679 if(appData.testLegality)
6680 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
6686 if (WhiteOnMove(currentMove)) {
6687 GameEnds(BlackWins, "Black mates", GE_PLAYER);
6689 GameEnds(WhiteWins, "White mates", GE_PLAYER);
6693 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
6698 case MachinePlaysBlack:
6699 case MachinePlaysWhite:
6700 /* disable certain menu options while machine is thinking */
6701 SetMachineThinkingEnables();
6708 userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
6709 promoDefaultAltered = FALSE; // [HGM] fall back on default choice
6711 if(bookHit) { // [HGM] book: simulate book reply
6712 static char bookMove[MSG_SIZ]; // a bit generous?
6714 programStats.nodes = programStats.depth = programStats.time =
6715 programStats.score = programStats.got_only_move = 0;
6716 sprintf(programStats.movelist, "%s (xbook)", bookHit);
6718 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
6719 strcat(bookMove, bookHit);
6720 HandleMachineMove(bookMove, &first);
6726 Mark (Board board, int flags, ChessMove kind, int rf, int ff, int rt, int ft, VOIDSTAR closure)
6728 typedef char Markers[BOARD_RANKS][BOARD_FILES];
6729 Markers *m = (Markers *) closure;
6730 if(rf == fromY && ff == fromX)
6731 (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
6732 || kind == WhiteCapturesEnPassant
6733 || kind == BlackCapturesEnPassant);
6734 else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
6738 MarkTargetSquares (int clear)
6741 if(!appData.markers || !appData.highlightDragging || appData.icsActive && gameInfo.variant < VariantShogi ||
6742 !appData.testLegality || gameMode == EditPosition) return;
6744 for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) marker[y][x] = 0;
6747 GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker, EmptySquare);
6748 if(PosFlags(0) & F_MANDATORY_CAPTURE) {
6749 for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
6751 for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
6754 DrawPosition(TRUE, NULL);
6758 Explode (Board board, int fromX, int fromY, int toX, int toY)
6760 if(gameInfo.variant == VariantAtomic &&
6761 (board[toY][toX] != EmptySquare || // capture?
6762 toX != fromX && (board[fromY][fromX] == WhitePawn || // e.p. ?
6763 board[fromY][fromX] == BlackPawn )
6765 AnimateAtomicCapture(board, fromX, fromY, toX, toY);
6771 ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
6774 CanPromote (ChessSquare piece, int y)
6776 if(gameMode == EditPosition) return FALSE; // no promotions when editing position
6777 // some variants have fixed promotion piece, no promotion at all, or another selection mechanism
6778 if(gameInfo.variant == VariantShogi || gameInfo.variant == VariantXiangqi ||
6779 gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat ||
6780 gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6781 gameInfo.variant == VariantMakruk) return FALSE;
6782 return (piece == BlackPawn && y == 1 ||
6783 piece == WhitePawn && y == BOARD_HEIGHT-2 ||
6784 piece == BlackLance && y == 1 ||
6785 piece == WhiteLance && y == BOARD_HEIGHT-2 );
6789 LeftClick (ClickType clickType, int xPix, int yPix)
6792 Boolean saveAnimate;
6793 static int second = 0, promotionChoice = 0, clearFlag = 0;
6794 char promoChoice = NULLCHAR;
6797 if(appData.seekGraph && appData.icsActive && loggedOn &&
6798 (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
6799 SeekGraphClick(clickType, xPix, yPix, 0);
6803 if (clickType == Press) ErrorPopDown();
6805 x = EventToSquare(xPix, BOARD_WIDTH);
6806 y = EventToSquare(yPix, BOARD_HEIGHT);
6807 if (!flipView && y >= 0) {
6808 y = BOARD_HEIGHT - 1 - y;
6810 if (flipView && x >= 0) {
6811 x = BOARD_WIDTH - 1 - x;
6814 if(promoSweep != EmptySquare) { // up-click during sweep-select of promo-piece
6815 defaultPromoChoice = promoSweep;
6816 promoSweep = EmptySquare; // terminate sweep
6817 promoDefaultAltered = TRUE;
6818 if(!selectFlag && (x != toX || y != toY)) x = fromX, y = fromY; // and fake up-click on same square if we were still selecting
6821 if(promotionChoice) { // we are waiting for a click to indicate promotion piece
6822 if(clickType == Release) return; // ignore upclick of click-click destination
6823 promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
6824 if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
6825 if(gameInfo.holdingsWidth &&
6826 (WhiteOnMove(currentMove)
6827 ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y >= 0
6828 : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT) ) {
6829 // click in right holdings, for determining promotion piece
6830 ChessSquare p = boards[currentMove][y][x];
6831 if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
6832 if(p == WhitePawn || p == BlackPawn) p = EmptySquare; // [HGM] Pawns could be valid as deferral
6833 if(p != EmptySquare || gameInfo.variant == VariantGrand && toY != 0 && toY != BOARD_HEIGHT-1) { // [HGM] grand: empty square means defer
6834 FinishMove(NormalMove, fromX, fromY, toX, toY, p==EmptySquare ? NULLCHAR : ToLower(PieceToChar(p)));
6839 DrawPosition(FALSE, boards[currentMove]);
6843 /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
6844 if(clickType == Press
6845 && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
6846 || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
6847 || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
6850 if(clickType == Press && fromX == x && fromY == y && promoDefaultAltered)
6851 fromX = fromY = -1; // second click on piece after altering default promo piece treated as first click
6853 if(!promoDefaultAltered) { // determine default promotion piece, based on the side the user is moving for
6854 int side = (gameMode == IcsPlayingWhite || gameMode == MachinePlaysBlack ||
6855 gameMode != MachinePlaysWhite && gameMode != IcsPlayingBlack && WhiteOnMove(currentMove));
6856 defaultPromoChoice = DefaultPromoChoice(side);
6859 autoQueen = appData.alwaysPromoteToQueen;
6863 gatingPiece = EmptySquare;
6864 if (clickType != Press) {
6865 if(dragging) { // [HGM] from-square must have been reset due to game end since last press
6866 DragPieceEnd(xPix, yPix); dragging = 0;
6867 DrawPosition(FALSE, NULL);
6871 fromX = x; fromY = y; toX = toY = -1;
6872 if(!appData.oneClick || !OnlyMove(&x, &y, FALSE) ||
6873 // even if only move, we treat as normal when this would trigger a promotion popup, to allow sweep selection
6874 appData.sweepSelect && CanPromote(boards[currentMove][fromY][fromX], fromY) && originalY != y) {
6876 if (OKToStartUserMove(fromX, fromY)) {
6878 MarkTargetSquares(0);
6879 DragPieceBegin(xPix, yPix, FALSE); dragging = 1;
6880 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][fromY][fromX], fromY)) {
6881 promoSweep = defaultPromoChoice;
6882 selectFlag = 0; lastX = xPix; lastY = yPix;
6883 Sweep(0); // Pawn that is going to promote: preview promotion piece
6884 DisplayMessage("", _("Pull pawn backwards to under-promote"));
6886 if (appData.highlightDragging) {
6887 SetHighlights(fromX, fromY, -1, -1);
6889 } else fromX = fromY = -1;
6895 if (clickType == Press && gameMode != EditPosition) {
6900 // ignore off-board to clicks
6901 if(y < 0 || x < 0) return;
6903 /* Check if clicking again on the same color piece */
6904 fromP = boards[currentMove][fromY][fromX];
6905 toP = boards[currentMove][y][x];
6906 frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess;
6907 if ((WhitePawn <= fromP && fromP <= WhiteKing &&
6908 WhitePawn <= toP && toP <= WhiteKing &&
6909 !(fromP == WhiteKing && toP == WhiteRook && frc) &&
6910 !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
6911 (BlackPawn <= fromP && fromP <= BlackKing &&
6912 BlackPawn <= toP && toP <= BlackKing &&
6913 !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
6914 !(fromP == BlackKing && toP == BlackRook && frc))) {
6915 /* Clicked again on same color piece -- changed his mind */
6916 second = (x == fromX && y == fromY);
6917 promoDefaultAltered = FALSE;
6918 MarkTargetSquares(1);
6919 if(!second || appData.oneClick && !OnlyMove(&x, &y, TRUE)) {
6920 if (appData.highlightDragging) {
6921 SetHighlights(x, y, -1, -1);
6925 if (OKToStartUserMove(x, y)) {
6926 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
6927 (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
6928 y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
6929 gatingPiece = boards[currentMove][fromY][fromX];
6930 else gatingPiece = EmptySquare;
6932 fromY = y; dragging = 1;
6933 MarkTargetSquares(0);
6934 DragPieceBegin(xPix, yPix, FALSE);
6935 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) {
6936 promoSweep = defaultPromoChoice;
6937 selectFlag = 0; lastX = xPix; lastY = yPix;
6938 Sweep(0); // Pawn that is going to promote: preview promotion piece
6942 if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
6945 // ignore clicks on holdings
6946 if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
6949 if (clickType == Release && x == fromX && y == fromY) {
6950 DragPieceEnd(xPix, yPix); dragging = 0;
6952 // a deferred attempt to click-click move an empty square on top of a piece
6953 boards[currentMove][y][x] = EmptySquare;
6955 DrawPosition(FALSE, boards[currentMove]);
6956 fromX = fromY = -1; clearFlag = 0;
6959 if (appData.animateDragging) {
6960 /* Undo animation damage if any */
6961 DrawPosition(FALSE, NULL);
6964 /* Second up/down in same square; just abort move */
6967 gatingPiece = EmptySquare;
6970 ClearPremoveHighlights();
6972 /* First upclick in same square; start click-click mode */
6973 SetHighlights(x, y, -1, -1);
6980 /* we now have a different from- and (possibly off-board) to-square */
6981 /* Completed move */
6984 saveAnimate = appData.animate;
6985 MarkTargetSquares(1);
6986 if (clickType == Press) {
6987 if(gameMode == EditPosition && boards[currentMove][fromY][fromX] == EmptySquare) {
6988 // must be Edit Position mode with empty-square selected
6989 fromX = x; fromY = y; DragPieceBegin(xPix, yPix, FALSE); dragging = 1; // consider this a new attempt to drag
6990 if(x >= BOARD_LEFT && x < BOARD_RGHT) clearFlag = 1; // and defer click-click move of empty-square to up-click
6993 if(appData.sweepSelect && HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, FALSE)) {
6994 ChessSquare piece = boards[currentMove][fromY][fromX];
6995 DragPieceBegin(xPix, yPix, TRUE); dragging = 1;
6996 promoSweep = defaultPromoChoice;
6997 if(PieceToChar(PROMOTED piece) == '+') promoSweep = PROMOTED piece;
6998 selectFlag = 0; lastX = xPix; lastY = yPix;
6999 Sweep(0); // Pawn that is going to promote: preview promotion piece
7000 DisplayMessage("", _("Pull pawn backwards to under-promote"));
7001 DrawPosition(FALSE, boards[currentMove]);
7004 /* Finish clickclick move */
7005 if (appData.animate || appData.highlightLastMove) {
7006 SetHighlights(fromX, fromY, toX, toY);
7011 /* Finish drag move */
7012 if (appData.highlightLastMove) {
7013 SetHighlights(fromX, fromY, toX, toY);
7017 DragPieceEnd(xPix, yPix); dragging = 0;
7018 /* Don't animate move and drag both */
7019 appData.animate = FALSE;
7022 // moves into holding are invalid for now (except in EditPosition, adapting to-square)
7023 if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
7024 ChessSquare piece = boards[currentMove][fromY][fromX];
7025 if(gameMode == EditPosition && piece != EmptySquare &&
7026 fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
7029 if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
7030 n = PieceToNumber(piece - (int)BlackPawn);
7031 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
7032 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
7033 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
7035 if(x == BOARD_RGHT+1 && piece < BlackPawn) {
7036 n = PieceToNumber(piece);
7037 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
7038 boards[currentMove][n][BOARD_WIDTH-1] = piece;
7039 boards[currentMove][n][BOARD_WIDTH-2]++;
7041 boards[currentMove][fromY][fromX] = EmptySquare;
7045 DrawPosition(TRUE, boards[currentMove]);
7049 // off-board moves should not be highlighted
7050 if(x < 0 || y < 0) ClearHighlights();
7052 if(gatingPiece != EmptySquare) promoChoice = ToLower(PieceToChar(gatingPiece));
7054 if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, appData.sweepSelect)) {
7055 SetHighlights(fromX, fromY, toX, toY);
7056 if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
7057 // [HGM] super: promotion to captured piece selected from holdings
7058 ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
7059 promotionChoice = TRUE;
7060 // kludge follows to temporarily execute move on display, without promoting yet
7061 boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
7062 boards[currentMove][toY][toX] = p;
7063 DrawPosition(FALSE, boards[currentMove]);
7064 boards[currentMove][fromY][fromX] = p; // take back, but display stays
7065 boards[currentMove][toY][toX] = q;
7066 DisplayMessage("Click in holdings to choose piece", "");
7071 int oldMove = currentMove;
7072 UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
7073 if (!appData.highlightLastMove || gotPremove) ClearHighlights();
7074 if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
7075 if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
7076 Explode(boards[currentMove-1], fromX, fromY, toX, toY))
7077 DrawPosition(TRUE, boards[currentMove]);
7080 appData.animate = saveAnimate;
7081 if (appData.animate || appData.animateDragging) {
7082 /* Undo animation damage if needed */
7083 DrawPosition(FALSE, NULL);
7088 RightClick (ClickType action, int x, int y, int *fromX, int *fromY)
7089 { // front-end-free part taken out of PieceMenuPopup
7090 int whichMenu; int xSqr, ySqr;
7092 if(seekGraphUp) { // [HGM] seekgraph
7093 if(action == Press) SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
7094 if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
7098 if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
7099 && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
7100 if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
7101 if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
7102 if(action == Press) {
7103 originalFlip = flipView;
7104 flipView = !flipView; // temporarily flip board to see game from partners perspective
7105 DrawPosition(TRUE, partnerBoard);
7106 DisplayMessage(partnerStatus, "");
7108 } else if(action == Release) {
7109 flipView = originalFlip;
7110 DrawPosition(TRUE, boards[currentMove]);
7116 xSqr = EventToSquare(x, BOARD_WIDTH);
7117 ySqr = EventToSquare(y, BOARD_HEIGHT);
7118 if (action == Release) {
7119 if(pieceSweep != EmptySquare) {
7120 EditPositionMenuEvent(pieceSweep, toX, toY);
7121 pieceSweep = EmptySquare;
7122 } else UnLoadPV(); // [HGM] pv
7124 if (action != Press) return -2; // return code to be ignored
7127 if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;
7129 if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;
7130 if (xSqr < 0 || ySqr < 0) return -1;
7131 if(appData.pieceMenu) { whichMenu = 0; break; } // edit-position menu
7132 pieceSweep = shiftKey ? BlackPawn : WhitePawn; // [HGM] sweep: prepare selecting piece by mouse sweep
7133 toX = xSqr; toY = ySqr; lastX = x, lastY = y;
7134 if(flipView) toX = BOARD_WIDTH - 1 - toX; else toY = BOARD_HEIGHT - 1 - toY;
7138 if(!appData.icsEngineAnalyze) return -1;
7139 case IcsPlayingWhite:
7140 case IcsPlayingBlack:
7141 if(!appData.zippyPlay) goto noZip;
7144 case MachinePlaysWhite:
7145 case MachinePlaysBlack:
7146 case TwoMachinesPlay: // [HGM] pv: use for showing PV
7147 if (!appData.dropMenu) {
7149 return 2; // flag front-end to grab mouse events
7151 if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
7152 gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
7155 if (xSqr < 0 || ySqr < 0) return -1;
7156 if (!appData.dropMenu || appData.testLegality &&
7157 gameInfo.variant != VariantBughouse &&
7158 gameInfo.variant != VariantCrazyhouse) return -1;
7159 whichMenu = 1; // drop menu
7165 if (((*fromX = xSqr) < 0) ||
7166 ((*fromY = ySqr) < 0)) {
7167 *fromX = *fromY = -1;
7171 *fromX = BOARD_WIDTH - 1 - *fromX;
7173 *fromY = BOARD_HEIGHT - 1 - *fromY;
7179 SendProgramStatsToFrontend (ChessProgramState * cps, ChessProgramStats * cpstats)
7181 // char * hint = lastHint;
7182 FrontEndProgramStats stats;
7184 stats.which = cps == &first ? 0 : 1;
7185 stats.depth = cpstats->depth;
7186 stats.nodes = cpstats->nodes;
7187 stats.score = cpstats->score;
7188 stats.time = cpstats->time;
7189 stats.pv = cpstats->movelist;
7190 stats.hint = lastHint;
7191 stats.an_move_index = 0;
7192 stats.an_move_count = 0;
7194 if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
7195 stats.hint = cpstats->move_name;
7196 stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
7197 stats.an_move_count = cpstats->nr_moves;
7200 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
7202 SetProgramStats( &stats );
7206 ClearEngineOutputPane (int which)
7208 static FrontEndProgramStats dummyStats;
7209 dummyStats.which = which;
7210 dummyStats.pv = "#";
7211 SetProgramStats( &dummyStats );
7214 #define MAXPLAYERS 500
7217 TourneyStandings (int display)
7219 int i, w, b, color, wScore, bScore, dummy, nr=0, nPlayers=0;
7220 int score[MAXPLAYERS], ranking[MAXPLAYERS], points[MAXPLAYERS], games[MAXPLAYERS];
7221 char result, *p, *names[MAXPLAYERS];
7223 if(appData.tourneyType < 0 && !strchr(appData.results, '*'))
7224 return strdup(_("Swiss tourney finished")); // standings of Swiss yet TODO
7225 names[0] = p = strdup(appData.participants);
7226 while(p = strchr(p, '\n')) *p++ = NULLCHAR, names[++nPlayers] = p; // count participants
7228 for(i=0; i<nPlayers; i++) score[i] = games[i] = 0;
7230 while(result = appData.results[nr]) {
7231 color = Pairing(nr, nPlayers, &w, &b, &dummy);
7232 if(!(color ^ matchGame & 1)) { dummy = w; w = b; b = dummy; }
7233 wScore = bScore = 0;
7235 case '+': wScore = 2; break;
7236 case '-': bScore = 2; break;
7237 case '=': wScore = bScore = 1; break;
7239 case '*': return strdup("busy"); // tourney not finished
7247 if(appData.tourneyType > 0) nPlayers = appData.tourneyType; // in gauntlet, list only gauntlet engine(s)
7248 for(w=0; w<nPlayers; w++) {
7250 for(i=0; i<nPlayers; i++) if(score[i] > bScore) bScore = score[i], b = i;
7251 ranking[w] = b; points[w] = bScore; score[b] = -2;
7253 p = malloc(nPlayers*34+1);
7254 for(w=0; w<nPlayers && w<display; w++)
7255 sprintf(p+34*w, "%2d. %5.1f/%-3d %-19.19s\n", w+1, points[w]/2., games[ranking[w]], names[ranking[w]]);
7261 Count (Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
7262 { // count all piece types
7264 *nB = *nW = *wStale = *bStale = *bishopColor = 0;
7265 for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
7266 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
7269 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
7270 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
7271 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
7272 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
7273 p == BlackBishop || p == BlackFerz || p == BlackAlfil )
7274 *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
7279 SufficientDefence (int pCnt[], int side, int nMine, int nHis)
7281 int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
7282 int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
7284 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
7285 if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
7286 if(myPawns == 2 && nMine == 3) // KPP
7287 return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
7288 if(myPawns == 1 && nMine == 2) // KP
7289 return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
7290 if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
7291 return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
7292 if(myPawns) return FALSE;
7293 if(pCnt[WhiteRook+side])
7294 return pCnt[BlackRook-side] ||
7295 pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
7296 pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
7297 pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
7298 if(pCnt[WhiteCannon+side]) {
7299 if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
7300 return majorDefense || pCnt[BlackAlfil-side] >= 2;
7302 if(pCnt[WhiteKnight+side])
7303 return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
7308 MatingPotential (int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
7310 VariantClass v = gameInfo.variant;
7312 if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
7313 if(v == VariantShatranj) return TRUE; // always winnable through baring
7314 if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
7315 if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
7317 if(v == VariantXiangqi) {
7318 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
7320 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
7321 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
7322 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
7323 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
7324 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
7325 if(stale) // we have at least one last-rank P plus perhaps C
7326 return majors // KPKX
7327 || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
7329 return pCnt[WhiteFerz+side] // KCAK
7330 || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
7331 || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
7332 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
7334 } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
7335 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
7337 if(nMine == 1) return FALSE; // bare King
7338 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
7339 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
7340 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
7341 // by now we have King + 1 piece (or multiple Bishops on the same color)
7342 if(pCnt[WhiteKnight+side])
7343 return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
7344 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
7345 || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
7347 return (pCnt[BlackKnight-side]); // KBKN, KFKN
7348 if(pCnt[WhiteAlfil+side])
7349 return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
7350 if(pCnt[WhiteWazir+side])
7351 return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
7358 CompareWithRights (Board b1, Board b2)
7361 if(!CompareBoards(b1, b2)) return FALSE;
7362 if(b1[EP_STATUS] != b2[EP_STATUS]) return FALSE;
7363 /* compare castling rights */
7364 if( b1[CASTLING][2] != b2[CASTLING][2] && (b2[CASTLING][0] != NoRights || b2[CASTLING][1] != NoRights) )
7365 rights++; /* King lost rights, while rook still had them */
7366 if( b1[CASTLING][2] != NoRights ) { /* king has rights */
7367 if( b1[CASTLING][0] != b2[CASTLING][0] || b1[CASTLING][1] != b2[CASTLING][1] )
7368 rights++; /* but at least one rook lost them */
7370 if( b1[CASTLING][5] != b1[CASTLING][5] && (b2[CASTLING][3] != NoRights || b2[CASTLING][4] != NoRights) )
7372 if( b1[CASTLING][5] != NoRights ) {
7373 if( b1[CASTLING][3] != b2[CASTLING][3] || b1[CASTLING][4] != b2[CASTLING][4] )
7380 Adjudicate (ChessProgramState *cps)
7381 { // [HGM] some adjudications useful with buggy engines
7382 // [HGM] adjudicate: made into separate routine, which now can be called after every move
7383 // In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
7384 // Actually ending the game is now based on the additional internal condition canAdjudicate.
7385 // Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
7386 int k, count = 0; static int bare = 1;
7387 ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
7388 Boolean canAdjudicate = !appData.icsActive;
7390 // most tests only when we understand the game, i.e. legality-checking on
7391 if( appData.testLegality )
7392 { /* [HGM] Some more adjudications for obstinate engines */
7393 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+1], i;
7394 static int moveCount = 6;
7396 char *reason = NULL;
7398 /* Count what is on board. */
7399 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
7401 /* Some material-based adjudications that have to be made before stalemate test */
7402 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
7403 // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
7404 boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
7405 if(canAdjudicate && appData.checkMates) {
7407 SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7408 GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
7409 "Xboard adjudication: King destroyed", GE_XBOARD );
7414 /* Bare King in Shatranj (loses) or Losers (wins) */
7415 if( nrW == 1 || nrB == 1) {
7416 if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
7417 boards[forwardMostMove][EP_STATUS] = EP_WINS; // mark as win, so it becomes claimable
7418 if(canAdjudicate && appData.checkMates) {
7420 SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
7421 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7422 "Xboard adjudication: Bare king", GE_XBOARD );
7426 if( gameInfo.variant == VariantShatranj && --bare < 0)
7428 boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
7429 if(canAdjudicate && appData.checkMates) {
7430 /* but only adjudicate if adjudication enabled */
7432 SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7433 GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
7434 "Xboard adjudication: Bare king", GE_XBOARD );
7441 // don't wait for engine to announce game end if we can judge ourselves
7442 switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
7444 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
7445 int i, checkCnt = 0; // (should really be done by making nr of checks part of game state)
7446 for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
7447 if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
7450 reason = "Xboard adjudication: 3rd check";
7451 boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
7461 reason = "Xboard adjudication: Stalemate";
7462 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
7463 boards[forwardMostMove][EP_STATUS] = EP_STALEMATE; // default result for stalemate is draw
7464 if(gameInfo.variant == VariantLosers || gameInfo.variant == VariantGiveaway) // [HGM] losers:
7465 boards[forwardMostMove][EP_STATUS] = EP_WINS; // in these variants stalemated is always a win
7466 else if(gameInfo.variant == VariantSuicide) // in suicide it depends
7467 boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
7468 ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
7469 EP_CHECKMATE : EP_WINS);
7470 else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
7471 boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
7475 reason = "Xboard adjudication: Checkmate";
7476 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
7480 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
7482 result = GameIsDrawn; break;
7484 result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
7486 result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
7490 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
7492 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7493 GameEnds( result, reason, GE_XBOARD );
7497 /* Next absolutely insufficient mating material. */
7498 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
7499 !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
7500 { /* includes KBK, KNK, KK of KBKB with like Bishops */
7502 /* always flag draws, for judging claims */
7503 boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
7505 if(canAdjudicate && appData.materialDraws) {
7506 /* but only adjudicate them if adjudication enabled */
7507 if(engineOpponent) {
7508 SendToProgram("force\n", engineOpponent); // suppress reply
7509 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
7511 GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
7516 /* Then some trivial draws (only adjudicate, cannot be claimed) */
7517 if(gameInfo.variant == VariantXiangqi ?
7518 SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
7520 ( nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
7521 || nr[WhiteQueen] && nr[BlackQueen]==1 /* KQKQ */
7522 || nr[WhiteKnight]==2 || nr[BlackKnight]==2 /* KNNK */
7523 || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
7525 if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
7526 { /* if the first 3 moves do not show a tactical win, declare draw */
7527 if(engineOpponent) {
7528 SendToProgram("force\n", engineOpponent); // suppress reply
7529 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7531 GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
7534 } else moveCount = 6;
7536 if (appData.debugMode) { int i;
7537 fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
7538 forwardMostMove, backwardMostMove, boards[backwardMostMove][EP_STATUS],
7539 appData.drawRepeats);
7540 for( i=forwardMostMove; i>=backwardMostMove; i-- )
7541 fprintf(debugFP, "%d ep=%d\n", i, (signed char)boards[i][EP_STATUS]);
7545 // Repetition draws and 50-move rule can be applied independently of legality testing
7547 /* Check for rep-draws */
7549 for(k = forwardMostMove-2;
7550 k>=backwardMostMove && k>=forwardMostMove-100 &&
7551 (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
7552 (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE;
7555 if(CompareBoards(boards[k], boards[forwardMostMove])) {
7556 /* compare castling rights */
7557 if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
7558 (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
7559 rights++; /* King lost rights, while rook still had them */
7560 if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
7561 if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
7562 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
7563 rights++; /* but at least one rook lost them */
7565 if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
7566 (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
7568 if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
7569 if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
7570 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
7573 if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
7574 && appData.drawRepeats > 1) {
7575 /* adjudicate after user-specified nr of repeats */
7576 int result = GameIsDrawn;
7577 char *details = "XBoard adjudication: repetition draw";
7578 if(gameInfo.variant == VariantXiangqi && appData.testLegality) {
7579 // [HGM] xiangqi: check for forbidden perpetuals
7580 int m, ourPerpetual = 1, hisPerpetual = 1;
7581 for(m=forwardMostMove; m>k; m-=2) {
7582 if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
7583 ourPerpetual = 0; // the current mover did not always check
7584 if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
7585 hisPerpetual = 0; // the opponent did not always check
7587 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
7588 ourPerpetual, hisPerpetual);
7589 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
7590 result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7591 details = "Xboard adjudication: perpetual checking";
7593 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
7594 break; // (or we would have caught him before). Abort repetition-checking loop.
7596 // Now check for perpetual chases
7597 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
7598 hisPerpetual = PerpetualChase(k, forwardMostMove);
7599 ourPerpetual = PerpetualChase(k+1, forwardMostMove);
7600 if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
7601 static char resdet[MSG_SIZ];
7602 result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7604 snprintf(resdet, MSG_SIZ, "Xboard adjudication: perpetual chasing of %c%c", ourPerpetual>>8, ourPerpetual&255);
7606 if(hisPerpetual && !ourPerpetual) // he is chasing us, but did not repeat yet
7607 break; // Abort repetition-checking loop.
7609 // if neither of us is checking or chasing all the time, or both are, it is draw
7611 if(engineOpponent) {
7612 SendToProgram("force\n", engineOpponent); // suppress reply
7613 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7615 GameEnds( result, details, GE_XBOARD );
7618 if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
7619 boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
7623 /* Now we test for 50-move draws. Determine ply count */
7624 count = forwardMostMove;
7625 /* look for last irreversble move */
7626 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
7628 /* if we hit starting position, add initial plies */
7629 if( count == backwardMostMove )
7630 count -= initialRulePlies;
7631 count = forwardMostMove - count;
7632 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
7633 // adjust reversible move counter for checks in Xiangqi
7634 int i = forwardMostMove - count, inCheck = 0, lastCheck;
7635 if(i < backwardMostMove) i = backwardMostMove;
7636 while(i <= forwardMostMove) {
7637 lastCheck = inCheck; // check evasion does not count
7638 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
7639 if(inCheck || lastCheck) count--; // check does not count
7644 boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
7645 /* this is used to judge if draw claims are legal */
7646 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
7647 if(engineOpponent) {
7648 SendToProgram("force\n", engineOpponent); // suppress reply
7649 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7651 GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
7655 /* if draw offer is pending, treat it as a draw claim
7656 * when draw condition present, to allow engines a way to
7657 * claim draws before making their move to avoid a race
7658 * condition occurring after their move
7660 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
7662 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
7663 p = "Draw claim: 50-move rule";
7664 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
7665 p = "Draw claim: 3-fold repetition";
7666 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
7667 p = "Draw claim: insufficient mating material";
7668 if( p != NULL && canAdjudicate) {
7669 if(engineOpponent) {
7670 SendToProgram("force\n", engineOpponent); // suppress reply
7671 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7673 GameEnds( GameIsDrawn, p, GE_XBOARD );
7678 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
7679 if(engineOpponent) {
7680 SendToProgram("force\n", engineOpponent); // suppress reply
7681 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7683 GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
7690 SendMoveToBookUser (int moveNr, ChessProgramState *cps, int initial)
7691 { // [HGM] book: this routine intercepts moves to simulate book replies
7692 char *bookHit = NULL;
7694 //first determine if the incoming move brings opponent into his book
7695 if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
7696 bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
7697 if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
7698 if(bookHit != NULL && !cps->bookSuspend) {
7699 // make sure opponent is not going to reply after receiving move to book position
7700 SendToProgram("force\n", cps);
7701 cps->bookSuspend = TRUE; // flag indicating it has to be restarted
7703 if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
7704 // now arrange restart after book miss
7706 // after a book hit we never send 'go', and the code after the call to this routine
7707 // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
7708 char buf[MSG_SIZ], *move = bookHit;
7710 int fromX, fromY, toX, toY;
7714 if (ParseOneMove(bookHit, forwardMostMove, &moveType,
7715 &fromX, &fromY, &toX, &toY, &promoChar)) {
7716 (void) CoordsToAlgebraic(boards[forwardMostMove],
7717 PosFlags(forwardMostMove),
7718 fromY, fromX, toY, toX, promoChar, move);
7720 if(appData.debugMode) fprintf(debugFP, "Book move could not be parsed\n");
7724 snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), move); // force book move into program supposed to play it
7725 SendToProgram(buf, cps);
7726 if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
7727 } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
7728 SendToProgram("go\n", cps);
7729 cps->bookSuspend = FALSE; // after a 'go' we are never suspended
7730 } else { // 'go' might be sent based on 'firstMove' after this routine returns
7731 if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
7732 SendToProgram("go\n", cps);
7733 cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
7735 return bookHit; // notify caller of hit, so it can take action to send move to opponent
7739 ChessProgramState *savedState;
7741 DeferredBookMove (void)
7743 if(savedState->lastPing != savedState->lastPong)
7744 ScheduleDelayedEvent(DeferredBookMove, 10);
7746 HandleMachineMove(savedMessage, savedState);
7749 static int savedWhitePlayer, savedBlackPlayer, pairingReceived;
7752 HandleMachineMove (char *message, ChessProgramState *cps)
7754 char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
7755 char realname[MSG_SIZ];
7756 int fromX, fromY, toX, toY;
7763 if(cps == &pairing && sscanf(message, "%d-%d", &savedWhitePlayer, &savedBlackPlayer) == 2) {
7764 // [HGM] pairing: Mega-hack! Pairing engine also uses this routine (so it could give other WB commands).
7765 if(savedWhitePlayer == 0 || savedBlackPlayer == 0) {
7766 DisplayError(_("Invalid pairing from pairing engine"), 0);
7769 pairingReceived = 1;
7771 return; // Skim the pairing messages here.
7776 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
7778 * Kludge to ignore BEL characters
7780 while (*message == '\007') message++;
7783 * [HGM] engine debug message: ignore lines starting with '#' character
7785 if(cps->debug && *message == '#') return;
7788 * Look for book output
7790 if (cps == &first && bookRequested) {
7791 if (message[0] == '\t' || message[0] == ' ') {
7792 /* Part of the book output is here; append it */
7793 strcat(bookOutput, message);
7794 strcat(bookOutput, " \n");
7796 } else if (bookOutput[0] != NULLCHAR) {
7797 /* All of book output has arrived; display it */
7798 char *p = bookOutput;
7799 while (*p != NULLCHAR) {
7800 if (*p == '\t') *p = ' ';
7803 DisplayInformation(bookOutput);
7804 bookRequested = FALSE;
7805 /* Fall through to parse the current output */
7810 * Look for machine move.
7812 if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
7813 (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
7815 /* This method is only useful on engines that support ping */
7816 if (cps->lastPing != cps->lastPong) {
7817 if (gameMode == BeginningOfGame) {
7818 /* Extra move from before last new; ignore */
7819 if (appData.debugMode) {
7820 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7823 if (appData.debugMode) {
7824 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7825 cps->which, gameMode);
7828 SendToProgram("undo\n", cps);
7834 case BeginningOfGame:
7835 /* Extra move from before last reset; ignore */
7836 if (appData.debugMode) {
7837 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7844 /* Extra move after we tried to stop. The mode test is
7845 not a reliable way of detecting this problem, but it's
7846 the best we can do on engines that don't support ping.
7848 if (appData.debugMode) {
7849 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7850 cps->which, gameMode);
7852 SendToProgram("undo\n", cps);
7855 case MachinePlaysWhite:
7856 case IcsPlayingWhite:
7857 machineWhite = TRUE;
7860 case MachinePlaysBlack:
7861 case IcsPlayingBlack:
7862 machineWhite = FALSE;
7865 case TwoMachinesPlay:
7866 machineWhite = (cps->twoMachinesColor[0] == 'w');
7869 if (WhiteOnMove(forwardMostMove) != machineWhite) {
7870 if (appData.debugMode) {
7872 "Ignoring move out of turn by %s, gameMode %d"
7873 ", forwardMost %d\n",
7874 cps->which, gameMode, forwardMostMove);
7879 if (appData.debugMode) { int f = forwardMostMove;
7880 fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
7881 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
7882 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
7884 if(cps->alphaRank) AlphaRank(machineMove, 4);
7885 if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
7886 &fromX, &fromY, &toX, &toY, &promoChar)) {
7887 /* Machine move could not be parsed; ignore it. */
7888 snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
7889 machineMove, _(cps->which));
7890 DisplayError(buf1, 0);
7891 snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
7892 machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
7893 if (gameMode == TwoMachinesPlay) {
7894 GameEnds(machineWhite ? BlackWins : WhiteWins,
7900 /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
7901 /* So we have to redo legality test with true e.p. status here, */
7902 /* to make sure an illegal e.p. capture does not slip through, */
7903 /* to cause a forfeit on a justified illegal-move complaint */
7904 /* of the opponent. */
7905 if( gameMode==TwoMachinesPlay && appData.testLegality ) {
7907 moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
7908 fromY, fromX, toY, toX, promoChar);
7909 if (appData.debugMode) {
7911 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
7912 boards[forwardMostMove][CASTLING][i], castlingRank[i]);
7913 fprintf(debugFP, "castling rights\n");
7915 if(moveType == IllegalMove) {
7916 snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
7917 machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
7918 GameEnds(machineWhite ? BlackWins : WhiteWins,
7921 } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
7922 /* [HGM] Kludge to handle engines that send FRC-style castling
7923 when they shouldn't (like TSCP-Gothic) */
7925 case WhiteASideCastleFR:
7926 case BlackASideCastleFR:
7928 currentMoveString[2]++;
7930 case WhiteHSideCastleFR:
7931 case BlackHSideCastleFR:
7933 currentMoveString[2]--;
7935 default: ; // nothing to do, but suppresses warning of pedantic compilers
7938 hintRequested = FALSE;
7939 lastHint[0] = NULLCHAR;
7940 bookRequested = FALSE;
7941 /* Program may be pondering now */
7942 cps->maybeThinking = TRUE;
7943 if (cps->sendTime == 2) cps->sendTime = 1;
7944 if (cps->offeredDraw) cps->offeredDraw--;
7946 /* [AS] Save move info*/
7947 pvInfoList[ forwardMostMove ].score = programStats.score;
7948 pvInfoList[ forwardMostMove ].depth = programStats.depth;
7949 pvInfoList[ forwardMostMove ].time = programStats.time; // [HGM] PGNtime: take time from engine stats
7951 MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
7953 /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
7954 if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
7957 while( count < adjudicateLossPlies ) {
7958 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
7961 score = -score; /* Flip score for winning side */
7964 if( score > adjudicateLossThreshold ) {
7971 if( count >= adjudicateLossPlies ) {
7972 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7974 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7975 "Xboard adjudication",
7982 if(Adjudicate(cps)) {
7983 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7984 return; // [HGM] adjudicate: for all automatic game ends
7988 if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
7990 if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
7991 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
7993 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7995 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7997 if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
7998 char buf[3*MSG_SIZ];
8000 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
8001 programStats.score / 100.,
8003 programStats.time / 100.,
8004 (unsigned int)programStats.nodes,
8005 (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
8006 programStats.movelist);
8008 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
8013 /* [AS] Clear stats for next move */
8014 ClearProgramStats();
8015 thinkOutput[0] = NULLCHAR;
8016 hiddenThinkOutputState = 0;
8019 if (gameMode == TwoMachinesPlay) {
8020 /* [HGM] relaying draw offers moved to after reception of move */
8021 /* and interpreting offer as claim if it brings draw condition */
8022 if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
8023 SendToProgram("draw\n", cps->other);
8025 if (cps->other->sendTime) {
8026 SendTimeRemaining(cps->other,
8027 cps->other->twoMachinesColor[0] == 'w');
8029 bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
8030 if (firstMove && !bookHit) {
8032 if (cps->other->useColors) {
8033 SendToProgram(cps->other->twoMachinesColor, cps->other);
8035 SendToProgram("go\n", cps->other);
8037 cps->other->maybeThinking = TRUE;
8040 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8042 if (!pausing && appData.ringBellAfterMoves) {
8047 * Reenable menu items that were disabled while
8048 * machine was thinking
8050 if (gameMode != TwoMachinesPlay)
8051 SetUserThinkingEnables();
8053 // [HGM] book: after book hit opponent has received move and is now in force mode
8054 // force the book reply into it, and then fake that it outputted this move by jumping
8055 // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
8057 static char bookMove[MSG_SIZ]; // a bit generous?
8059 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
8060 strcat(bookMove, bookHit);
8063 programStats.nodes = programStats.depth = programStats.time =
8064 programStats.score = programStats.got_only_move = 0;
8065 sprintf(programStats.movelist, "%s (xbook)", bookHit);
8067 if(cps->lastPing != cps->lastPong) {
8068 savedMessage = message; // args for deferred call
8070 ScheduleDelayedEvent(DeferredBookMove, 10);
8079 /* Set special modes for chess engines. Later something general
8080 * could be added here; for now there is just one kludge feature,
8081 * needed because Crafty 15.10 and earlier don't ignore SIGINT
8082 * when "xboard" is given as an interactive command.
8084 if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
8085 cps->useSigint = FALSE;
8086 cps->useSigterm = FALSE;
8088 if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
8089 ParseFeatures(message+8, cps);
8090 return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
8093 if ((!appData.testLegality || gameInfo.variant == VariantFairy) &&
8094 !strncmp(message, "setup ", 6)) { // [HGM] allow first engine to define opening position
8095 int dummy, s=6; char buf[MSG_SIZ];
8096 if(appData.icsActive || forwardMostMove != 0 || cps != &first) return;
8097 if(sscanf(message, "setup (%s", buf) == 1) s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTable(pieceToChar, buf);
8098 if(startedFromSetupPosition) return;
8099 ParseFEN(boards[0], &dummy, message+s);
8100 DrawPosition(TRUE, boards[0]);
8101 startedFromSetupPosition = TRUE;
8104 /* [HGM] Allow engine to set up a position. Don't ask me why one would
8105 * want this, I was asked to put it in, and obliged.
8107 if (!strncmp(message, "setboard ", 9)) {
8108 Board initial_position;
8110 GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
8112 if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
8113 DisplayError(_("Bad FEN received from engine"), 0);
8117 CopyBoard(boards[0], initial_position);
8118 initialRulePlies = FENrulePlies;
8119 if(blackPlaysFirst) gameMode = MachinePlaysWhite;
8120 else gameMode = MachinePlaysBlack;
8121 DrawPosition(FALSE, boards[currentMove]);
8127 * Look for communication commands
8129 if (!strncmp(message, "telluser ", 9)) {
8130 if(message[9] == '\\' && message[10] == '\\')
8131 EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
8133 DisplayNote(message + 9);
8136 if (!strncmp(message, "tellusererror ", 14)) {
8138 if(message[14] == '\\' && message[15] == '\\')
8139 EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
8141 DisplayError(message + 14, 0);
8144 if (!strncmp(message, "tellopponent ", 13)) {
8145 if (appData.icsActive) {
8147 snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
8151 DisplayNote(message + 13);
8155 if (!strncmp(message, "tellothers ", 11)) {
8156 if (appData.icsActive) {
8158 snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
8164 if (!strncmp(message, "tellall ", 8)) {
8165 if (appData.icsActive) {
8167 snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
8171 DisplayNote(message + 8);
8175 if (strncmp(message, "warning", 7) == 0) {
8176 /* Undocumented feature, use tellusererror in new code */
8177 DisplayError(message, 0);
8180 if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
8181 safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
8182 strcat(realname, " query");
8183 AskQuestion(realname, buf2, buf1, cps->pr);
8186 /* Commands from the engine directly to ICS. We don't allow these to be
8187 * sent until we are logged on. Crafty kibitzes have been known to
8188 * interfere with the login process.
8191 if (!strncmp(message, "tellics ", 8)) {
8192 SendToICS(message + 8);
8196 if (!strncmp(message, "tellicsnoalias ", 15)) {
8197 SendToICS(ics_prefix);
8198 SendToICS(message + 15);
8202 /* The following are for backward compatibility only */
8203 if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
8204 !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
8205 SendToICS(ics_prefix);
8211 if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
8215 * If the move is illegal, cancel it and redraw the board.
8216 * Also deal with other error cases. Matching is rather loose
8217 * here to accommodate engines written before the spec.
8219 if (strncmp(message + 1, "llegal move", 11) == 0 ||
8220 strncmp(message, "Error", 5) == 0) {
8221 if (StrStr(message, "name") ||
8222 StrStr(message, "rating") || StrStr(message, "?") ||
8223 StrStr(message, "result") || StrStr(message, "board") ||
8224 StrStr(message, "bk") || StrStr(message, "computer") ||
8225 StrStr(message, "variant") || StrStr(message, "hint") ||
8226 StrStr(message, "random") || StrStr(message, "depth") ||
8227 StrStr(message, "accepted")) {
8230 if (StrStr(message, "protover")) {
8231 /* Program is responding to input, so it's apparently done
8232 initializing, and this error message indicates it is
8233 protocol version 1. So we don't need to wait any longer
8234 for it to initialize and send feature commands. */
8235 FeatureDone(cps, 1);
8236 cps->protocolVersion = 1;
8239 cps->maybeThinking = FALSE;
8241 if (StrStr(message, "draw")) {
8242 /* Program doesn't have "draw" command */
8243 cps->sendDrawOffers = 0;
8246 if (cps->sendTime != 1 &&
8247 (StrStr(message, "time") || StrStr(message, "otim"))) {
8248 /* Program apparently doesn't have "time" or "otim" command */
8252 if (StrStr(message, "analyze")) {
8253 cps->analysisSupport = FALSE;
8254 cps->analyzing = FALSE;
8255 // Reset(FALSE, TRUE); // [HGM] this caused discrepancy between display and internal state!
8256 EditGameEvent(); // [HGM] try to preserve loaded game
8257 snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
8258 DisplayError(buf2, 0);
8261 if (StrStr(message, "(no matching move)st")) {
8262 /* Special kludge for GNU Chess 4 only */
8263 cps->stKludge = TRUE;
8264 SendTimeControl(cps, movesPerSession, timeControl,
8265 timeIncrement, appData.searchDepth,
8269 if (StrStr(message, "(no matching move)sd")) {
8270 /* Special kludge for GNU Chess 4 only */
8271 cps->sdKludge = TRUE;
8272 SendTimeControl(cps, movesPerSession, timeControl,
8273 timeIncrement, appData.searchDepth,
8277 if (!StrStr(message, "llegal")) {
8280 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8281 gameMode == IcsIdle) return;
8282 if (forwardMostMove <= backwardMostMove) return;
8283 if (pausing) PauseEvent();
8284 if(appData.forceIllegal) {
8285 // [HGM] illegal: machine refused move; force position after move into it
8286 SendToProgram("force\n", cps);
8287 if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
8288 // we have a real problem now, as SendBoard will use the a2a3 kludge
8289 // when black is to move, while there might be nothing on a2 or black
8290 // might already have the move. So send the board as if white has the move.
8291 // But first we must change the stm of the engine, as it refused the last move
8292 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
8293 if(WhiteOnMove(forwardMostMove)) {
8294 SendToProgram("a7a6\n", cps); // for the engine black still had the move
8295 SendBoard(cps, forwardMostMove); // kludgeless board
8297 SendToProgram("a2a3\n", cps); // for the engine white still had the move
8298 CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
8299 SendBoard(cps, forwardMostMove+1); // kludgeless board
8301 } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
8302 if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
8303 gameMode == TwoMachinesPlay)
8304 SendToProgram("go\n", cps);
8307 if (gameMode == PlayFromGameFile) {
8308 /* Stop reading this game file */
8309 gameMode = EditGame;
8312 /* [HGM] illegal-move claim should forfeit game when Xboard */
8313 /* only passes fully legal moves */
8314 if( appData.testLegality && gameMode == TwoMachinesPlay ) {
8315 GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
8316 "False illegal-move claim", GE_XBOARD );
8317 return; // do not take back move we tested as valid
8319 currentMove = forwardMostMove-1;
8320 DisplayMove(currentMove-1); /* before DisplayMoveError */
8321 SwitchClocks(forwardMostMove-1); // [HGM] race
8322 DisplayBothClocks();
8323 snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
8324 parseList[currentMove], _(cps->which));
8325 DisplayMoveError(buf1);
8326 DrawPosition(FALSE, boards[currentMove]);
8328 SetUserThinkingEnables();
8331 if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
8332 /* Program has a broken "time" command that
8333 outputs a string not ending in newline.
8339 * If chess program startup fails, exit with an error message.
8340 * Attempts to recover here are futile.
8342 if ((StrStr(message, "unknown host") != NULL)
8343 || (StrStr(message, "No remote directory") != NULL)
8344 || (StrStr(message, "not found") != NULL)
8345 || (StrStr(message, "No such file") != NULL)
8346 || (StrStr(message, "can't alloc") != NULL)
8347 || (StrStr(message, "Permission denied") != NULL)) {
8349 cps->maybeThinking = FALSE;
8350 snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
8351 _(cps->which), cps->program, cps->host, message);
8352 RemoveInputSource(cps->isr);
8353 if(appData.icsActive) DisplayFatalError(buf1, 0, 1); else {
8354 if(cps == &first) appData.noChessProgram = TRUE;
8355 DisplayError(buf1, 0);
8361 * Look for hint output
8363 if (sscanf(message, "Hint: %s", buf1) == 1) {
8364 if (cps == &first && hintRequested) {
8365 hintRequested = FALSE;
8366 if (ParseOneMove(buf1, forwardMostMove, &moveType,
8367 &fromX, &fromY, &toX, &toY, &promoChar)) {
8368 (void) CoordsToAlgebraic(boards[forwardMostMove],
8369 PosFlags(forwardMostMove),
8370 fromY, fromX, toY, toX, promoChar, buf1);
8371 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
8372 DisplayInformation(buf2);
8374 /* Hint move could not be parsed!? */
8375 snprintf(buf2, sizeof(buf2),
8376 _("Illegal hint move \"%s\"\nfrom %s chess program"),
8377 buf1, _(cps->which));
8378 DisplayError(buf2, 0);
8381 safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
8387 * Ignore other messages if game is not in progress
8389 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8390 gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
8393 * look for win, lose, draw, or draw offer
8395 if (strncmp(message, "1-0", 3) == 0) {
8396 char *p, *q, *r = "";
8397 p = strchr(message, '{');
8405 GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
8407 } else if (strncmp(message, "0-1", 3) == 0) {
8408 char *p, *q, *r = "";
8409 p = strchr(message, '{');
8417 /* Kludge for Arasan 4.1 bug */
8418 if (strcmp(r, "Black resigns") == 0) {
8419 GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
8422 GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
8424 } else if (strncmp(message, "1/2", 3) == 0) {
8425 char *p, *q, *r = "";
8426 p = strchr(message, '{');
8435 GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
8438 } else if (strncmp(message, "White resign", 12) == 0) {
8439 GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8441 } else if (strncmp(message, "Black resign", 12) == 0) {
8442 GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8444 } else if (strncmp(message, "White matches", 13) == 0 ||
8445 strncmp(message, "Black matches", 13) == 0 ) {
8446 /* [HGM] ignore GNUShogi noises */
8448 } else if (strncmp(message, "White", 5) == 0 &&
8449 message[5] != '(' &&
8450 StrStr(message, "Black") == NULL) {
8451 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8453 } else if (strncmp(message, "Black", 5) == 0 &&
8454 message[5] != '(') {
8455 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8457 } else if (strcmp(message, "resign") == 0 ||
8458 strcmp(message, "computer resigns") == 0) {
8460 case MachinePlaysBlack:
8461 case IcsPlayingBlack:
8462 GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
8464 case MachinePlaysWhite:
8465 case IcsPlayingWhite:
8466 GameEnds(BlackWins, "White resigns", GE_ENGINE);
8468 case TwoMachinesPlay:
8469 if (cps->twoMachinesColor[0] == 'w')
8470 GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8472 GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8479 } else if (strncmp(message, "opponent mates", 14) == 0) {
8481 case MachinePlaysBlack:
8482 case IcsPlayingBlack:
8483 GameEnds(WhiteWins, "White mates", GE_ENGINE);
8485 case MachinePlaysWhite:
8486 case IcsPlayingWhite:
8487 GameEnds(BlackWins, "Black mates", GE_ENGINE);
8489 case TwoMachinesPlay:
8490 if (cps->twoMachinesColor[0] == 'w')
8491 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8493 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8500 } else if (strncmp(message, "computer mates", 14) == 0) {
8502 case MachinePlaysBlack:
8503 case IcsPlayingBlack:
8504 GameEnds(BlackWins, "Black mates", GE_ENGINE1);
8506 case MachinePlaysWhite:
8507 case IcsPlayingWhite:
8508 GameEnds(WhiteWins, "White mates", GE_ENGINE);
8510 case TwoMachinesPlay:
8511 if (cps->twoMachinesColor[0] == 'w')
8512 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8514 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8521 } else if (strncmp(message, "checkmate", 9) == 0) {
8522 if (WhiteOnMove(forwardMostMove)) {
8523 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8525 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8528 } else if (strstr(message, "Draw") != NULL ||
8529 strstr(message, "game is a draw") != NULL) {
8530 GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
8532 } else if (strstr(message, "offer") != NULL &&
8533 strstr(message, "draw") != NULL) {
8535 if (appData.zippyPlay && first.initDone) {
8536 /* Relay offer to ICS */
8537 SendToICS(ics_prefix);
8538 SendToICS("draw\n");
8541 cps->offeredDraw = 2; /* valid until this engine moves twice */
8542 if (gameMode == TwoMachinesPlay) {
8543 if (cps->other->offeredDraw) {
8544 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8545 /* [HGM] in two-machine mode we delay relaying draw offer */
8546 /* until after we also have move, to see if it is really claim */
8548 } else if (gameMode == MachinePlaysWhite ||
8549 gameMode == MachinePlaysBlack) {
8550 if (userOfferedDraw) {
8551 DisplayInformation(_("Machine accepts your draw offer"));
8552 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8554 DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
8561 * Look for thinking output
8563 if ( appData.showThinking // [HGM] thinking: test all options that cause this output
8564 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
8566 int plylev, mvleft, mvtot, curscore, time;
8567 char mvname[MOVE_LEN];
8571 int prefixHint = FALSE;
8572 mvname[0] = NULLCHAR;
8575 case MachinePlaysBlack:
8576 case IcsPlayingBlack:
8577 if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8579 case MachinePlaysWhite:
8580 case IcsPlayingWhite:
8581 if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8586 case IcsObserving: /* [DM] icsEngineAnalyze */
8587 if (!appData.icsEngineAnalyze) ignore = TRUE;
8589 case TwoMachinesPlay:
8590 if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
8600 ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
8602 if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8603 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
8605 if (plyext != ' ' && plyext != '\t') {
8609 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8610 if( cps->scoreIsAbsolute &&
8611 ( gameMode == MachinePlaysBlack ||
8612 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
8613 gameMode == IcsPlayingBlack || // [HGM] also add other situations where engine should report black POV
8614 (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
8615 !WhiteOnMove(currentMove)
8618 curscore = -curscore;
8621 if(appData.pvSAN[cps==&second]) pv = PvToSAN(buf1);
8623 if(serverMoves && (time > 100 || time == 0 && plylev > 7)) {
8626 snprintf(buf, MSG_SIZ, "%s", appData.serverMovesName);
8627 buf[strlen(buf)-1] = gameMode == MachinePlaysWhite ? 'w' :
8628 gameMode == MachinePlaysBlack ? 'b' : cps->twoMachinesColor[0];
8629 if(appData.debugMode) fprintf(debugFP, "write PV on file '%s'\n", buf);
8630 if(f = fopen(buf, "w")) { // export PV to applicable PV file
8631 fprintf(f, "%5.2f/%-2d %s", curscore/100., plylev, pv);
8633 } else DisplayError(_("failed writing PV"), 0);
8636 tempStats.depth = plylev;
8637 tempStats.nodes = nodes;
8638 tempStats.time = time;
8639 tempStats.score = curscore;
8640 tempStats.got_only_move = 0;
8642 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
8645 if(cps->nps == 0) ticklen = 10*time; // use engine reported time
8646 else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
8647 if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
8648 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
8649 whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
8650 if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
8651 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
8652 blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
8655 /* Buffer overflow protection */
8656 if (pv[0] != NULLCHAR) {
8657 if (strlen(pv) >= sizeof(tempStats.movelist)
8658 && appData.debugMode) {
8660 "PV is too long; using the first %u bytes.\n",
8661 (unsigned) sizeof(tempStats.movelist) - 1);
8664 safeStrCpy( tempStats.movelist, pv, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
8666 sprintf(tempStats.movelist, " no PV\n");
8669 if (tempStats.seen_stat) {
8670 tempStats.ok_to_send = 1;
8673 if (strchr(tempStats.movelist, '(') != NULL) {
8674 tempStats.line_is_book = 1;
8675 tempStats.nr_moves = 0;
8676 tempStats.moves_left = 0;
8678 tempStats.line_is_book = 0;
8681 if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
8682 programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
8684 SendProgramStatsToFrontend( cps, &tempStats );
8687 [AS] Protect the thinkOutput buffer from overflow... this
8688 is only useful if buf1 hasn't overflowed first!
8690 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%+.2f %s%s",
8692 (gameMode == TwoMachinesPlay ?
8693 ToUpper(cps->twoMachinesColor[0]) : ' '),
8694 ((double) curscore) / 100.0,
8695 prefixHint ? lastHint : "",
8696 prefixHint ? " " : "" );
8698 if( buf1[0] != NULLCHAR ) {
8699 unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
8701 if( strlen(pv) > max_len ) {
8702 if( appData.debugMode) {
8703 fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
8705 pv[max_len+1] = '\0';
8708 strcat( thinkOutput, pv);
8711 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
8712 || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8713 DisplayMove(currentMove - 1);
8717 } else if ((p=StrStr(message, "(only move)")) != NULL) {
8718 /* crafty (9.25+) says "(only move) <move>"
8719 * if there is only 1 legal move
8721 sscanf(p, "(only move) %s", buf1);
8722 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
8723 sprintf(programStats.movelist, "%s (only move)", buf1);
8724 programStats.depth = 1;
8725 programStats.nr_moves = 1;
8726 programStats.moves_left = 1;
8727 programStats.nodes = 1;
8728 programStats.time = 1;
8729 programStats.got_only_move = 1;
8731 /* Not really, but we also use this member to
8732 mean "line isn't going to change" (Crafty
8733 isn't searching, so stats won't change) */
8734 programStats.line_is_book = 1;
8736 SendProgramStatsToFrontend( cps, &programStats );
8738 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8739 gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8740 DisplayMove(currentMove - 1);
8743 } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
8744 &time, &nodes, &plylev, &mvleft,
8745 &mvtot, mvname) >= 5) {
8746 /* The stat01: line is from Crafty (9.29+) in response
8747 to the "." command */
8748 programStats.seen_stat = 1;
8749 cps->maybeThinking = TRUE;
8751 if (programStats.got_only_move || !appData.periodicUpdates)
8754 programStats.depth = plylev;
8755 programStats.time = time;
8756 programStats.nodes = nodes;
8757 programStats.moves_left = mvleft;
8758 programStats.nr_moves = mvtot;
8759 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
8760 programStats.ok_to_send = 1;
8761 programStats.movelist[0] = '\0';
8763 SendProgramStatsToFrontend( cps, &programStats );
8767 } else if (strncmp(message,"++",2) == 0) {
8768 /* Crafty 9.29+ outputs this */
8769 programStats.got_fail = 2;
8772 } else if (strncmp(message,"--",2) == 0) {
8773 /* Crafty 9.29+ outputs this */
8774 programStats.got_fail = 1;
8777 } else if (thinkOutput[0] != NULLCHAR &&
8778 strncmp(message, " ", 4) == 0) {
8779 unsigned message_len;
8782 while (*p && *p == ' ') p++;
8784 message_len = strlen( p );
8786 /* [AS] Avoid buffer overflow */
8787 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
8788 strcat(thinkOutput, " ");
8789 strcat(thinkOutput, p);
8792 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
8793 strcat(programStats.movelist, " ");
8794 strcat(programStats.movelist, p);
8797 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8798 gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8799 DisplayMove(currentMove - 1);
8807 if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8808 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
8810 ChessProgramStats cpstats;
8812 if (plyext != ' ' && plyext != '\t') {
8816 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8817 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
8818 curscore = -curscore;
8821 cpstats.depth = plylev;
8822 cpstats.nodes = nodes;
8823 cpstats.time = time;
8824 cpstats.score = curscore;
8825 cpstats.got_only_move = 0;
8826 cpstats.movelist[0] = '\0';
8828 if (buf1[0] != NULLCHAR) {
8829 safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
8832 cpstats.ok_to_send = 0;
8833 cpstats.line_is_book = 0;
8834 cpstats.nr_moves = 0;
8835 cpstats.moves_left = 0;
8837 SendProgramStatsToFrontend( cps, &cpstats );
8844 /* Parse a game score from the character string "game", and
8845 record it as the history of the current game. The game
8846 score is NOT assumed to start from the standard position.
8847 The display is not updated in any way.
8850 ParseGameHistory (char *game)
8853 int fromX, fromY, toX, toY, boardIndex;
8858 if (appData.debugMode)
8859 fprintf(debugFP, "Parsing game history: %s\n", game);
8861 if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
8862 gameInfo.site = StrSave(appData.icsHost);
8863 gameInfo.date = PGNDate();
8864 gameInfo.round = StrSave("-");
8866 /* Parse out names of players */
8867 while (*game == ' ') game++;
8869 while (*game != ' ') *p++ = *game++;
8871 gameInfo.white = StrSave(buf);
8872 while (*game == ' ') game++;
8874 while (*game != ' ' && *game != '\n') *p++ = *game++;
8876 gameInfo.black = StrSave(buf);
8879 boardIndex = blackPlaysFirst ? 1 : 0;
8882 yyboardindex = boardIndex;
8883 moveType = (ChessMove) Myylex();
8885 case IllegalMove: /* maybe suicide chess, etc. */
8886 if (appData.debugMode) {
8887 fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
8888 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8889 setbuf(debugFP, NULL);
8891 case WhitePromotion:
8892 case BlackPromotion:
8893 case WhiteNonPromotion:
8894 case BlackNonPromotion:
8896 case WhiteCapturesEnPassant:
8897 case BlackCapturesEnPassant:
8898 case WhiteKingSideCastle:
8899 case WhiteQueenSideCastle:
8900 case BlackKingSideCastle:
8901 case BlackQueenSideCastle:
8902 case WhiteKingSideCastleWild:
8903 case WhiteQueenSideCastleWild:
8904 case BlackKingSideCastleWild:
8905 case BlackQueenSideCastleWild:
8907 case WhiteHSideCastleFR:
8908 case WhiteASideCastleFR:
8909 case BlackHSideCastleFR:
8910 case BlackASideCastleFR:
8912 fromX = currentMoveString[0] - AAA;
8913 fromY = currentMoveString[1] - ONE;
8914 toX = currentMoveString[2] - AAA;
8915 toY = currentMoveString[3] - ONE;
8916 promoChar = currentMoveString[4];
8920 if(currentMoveString[0] == '@') continue; // no null moves in ICS mode!
8921 fromX = moveType == WhiteDrop ?
8922 (int) CharToPiece(ToUpper(currentMoveString[0])) :
8923 (int) CharToPiece(ToLower(currentMoveString[0]));
8925 toX = currentMoveString[2] - AAA;
8926 toY = currentMoveString[3] - ONE;
8927 promoChar = NULLCHAR;
8931 snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
8932 if (appData.debugMode) {
8933 fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
8934 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8935 setbuf(debugFP, NULL);
8937 DisplayError(buf, 0);
8939 case ImpossibleMove:
8941 snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
8942 if (appData.debugMode) {
8943 fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
8944 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8945 setbuf(debugFP, NULL);
8947 DisplayError(buf, 0);
8950 if (boardIndex < backwardMostMove) {
8951 /* Oops, gap. How did that happen? */
8952 DisplayError(_("Gap in move list"), 0);
8955 backwardMostMove = blackPlaysFirst ? 1 : 0;
8956 if (boardIndex > forwardMostMove) {
8957 forwardMostMove = boardIndex;
8961 if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
8962 strcat(parseList[boardIndex-1], " ");
8963 strcat(parseList[boardIndex-1], yy_text);
8975 case GameUnfinished:
8976 if (gameMode == IcsExamining) {
8977 if (boardIndex < backwardMostMove) {
8978 /* Oops, gap. How did that happen? */
8981 backwardMostMove = blackPlaysFirst ? 1 : 0;
8984 gameInfo.result = moveType;
8985 p = strchr(yy_text, '{');
8986 if (p == NULL) p = strchr(yy_text, '(');
8989 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
8991 q = strchr(p, *p == '{' ? '}' : ')');
8992 if (q != NULL) *q = NULLCHAR;
8995 while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
8996 gameInfo.resultDetails = StrSave(p);
8999 if (boardIndex >= forwardMostMove &&
9000 !(gameMode == IcsObserving && ics_gamenum == -1)) {
9001 backwardMostMove = blackPlaysFirst ? 1 : 0;
9004 (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
9005 fromY, fromX, toY, toX, promoChar,
9006 parseList[boardIndex]);
9007 CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
9008 /* currentMoveString is set as a side-effect of yylex */
9009 safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
9010 strcat(moveList[boardIndex], "\n");
9012 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
9013 switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
9019 if(gameInfo.variant != VariantShogi)
9020 strcat(parseList[boardIndex - 1], "+");
9024 strcat(parseList[boardIndex - 1], "#");
9031 /* Apply a move to the given board */
9033 ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board)
9035 ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
9036 int promoRank = gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand ? 3 : 1;
9038 /* [HGM] compute & store e.p. status and castling rights for new position */
9039 /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
9041 if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
9042 oldEP = (signed char)board[EP_STATUS];
9043 board[EP_STATUS] = EP_NONE;
9045 if (fromY == DROP_RANK) {
9047 if(fromX == EmptySquare) { // [HGM] pass: empty drop encodes null move; nothing to change.
9048 board[EP_STATUS] = EP_CAPTURE; // null move considered irreversible
9051 piece = board[toY][toX] = (ChessSquare) fromX;
9055 if( board[toY][toX] != EmptySquare )
9056 board[EP_STATUS] = EP_CAPTURE;
9058 if( board[fromY][fromX] == WhiteLance || board[fromY][fromX] == BlackLance ) {
9059 if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi )
9060 board[EP_STATUS] = EP_PAWN_MOVE; // Lance is Pawn-like in most variants
9062 if( board[fromY][fromX] == WhitePawn ) {
9063 if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9064 board[EP_STATUS] = EP_PAWN_MOVE;
9066 if(toX>BOARD_LEFT && board[toY][toX-1] == BlackPawn &&
9067 gameInfo.variant != VariantBerolina || toX < fromX)
9068 board[EP_STATUS] = toX | berolina;
9069 if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
9070 gameInfo.variant != VariantBerolina || toX > fromX)
9071 board[EP_STATUS] = toX;
9074 if( board[fromY][fromX] == BlackPawn ) {
9075 if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9076 board[EP_STATUS] = EP_PAWN_MOVE;
9077 if( toY-fromY== -2) {
9078 if(toX>BOARD_LEFT && board[toY][toX-1] == WhitePawn &&
9079 gameInfo.variant != VariantBerolina || toX < fromX)
9080 board[EP_STATUS] = toX | berolina;
9081 if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
9082 gameInfo.variant != VariantBerolina || toX > fromX)
9083 board[EP_STATUS] = toX;
9087 for(i=0; i<nrCastlingRights; i++) {
9088 if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
9089 board[CASTLING][i] == toX && castlingRank[i] == toY
9090 ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
9093 if (fromX == toX && fromY == toY) return;
9095 piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
9096 king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
9097 if(gameInfo.variant == VariantKnightmate)
9098 king += (int) WhiteUnicorn - (int) WhiteKing;
9100 /* Code added by Tord: */
9101 /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
9102 if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
9103 board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
9104 board[fromY][fromX] = EmptySquare;
9105 board[toY][toX] = EmptySquare;
9106 if((toX > fromX) != (piece == WhiteRook)) {
9107 board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
9109 board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
9111 } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
9112 board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
9113 board[fromY][fromX] = EmptySquare;
9114 board[toY][toX] = EmptySquare;
9115 if((toX > fromX) != (piece == BlackRook)) {
9116 board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
9118 board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
9120 /* End of code added by Tord */
9122 } else if (board[fromY][fromX] == king
9123 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9124 && toY == fromY && toX > fromX+1) {
9125 board[fromY][fromX] = EmptySquare;
9126 board[toY][toX] = king;
9127 board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9128 board[fromY][BOARD_RGHT-1] = EmptySquare;
9129 } else if (board[fromY][fromX] == king
9130 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9131 && toY == fromY && toX < fromX-1) {
9132 board[fromY][fromX] = EmptySquare;
9133 board[toY][toX] = king;
9134 board[toY][toX+1] = board[fromY][BOARD_LEFT];
9135 board[fromY][BOARD_LEFT] = EmptySquare;
9136 } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
9137 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9138 && toY >= BOARD_HEIGHT-promoRank && promoChar // defaulting to Q is done elsewhere
9140 /* white pawn promotion */
9141 board[toY][toX] = CharToPiece(ToUpper(promoChar));
9142 if(gameInfo.variant==VariantBughouse ||
9143 gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
9144 board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9145 board[fromY][fromX] = EmptySquare;
9146 } else if ((fromY >= BOARD_HEIGHT>>1)
9148 && gameInfo.variant != VariantXiangqi
9149 && gameInfo.variant != VariantBerolina
9150 && (board[fromY][fromX] == WhitePawn)
9151 && (board[toY][toX] == EmptySquare)) {
9152 board[fromY][fromX] = EmptySquare;
9153 board[toY][toX] = WhitePawn;
9154 captured = board[toY - 1][toX];
9155 board[toY - 1][toX] = EmptySquare;
9156 } else if ((fromY == BOARD_HEIGHT-4)
9158 && gameInfo.variant == VariantBerolina
9159 && (board[fromY][fromX] == WhitePawn)
9160 && (board[toY][toX] == EmptySquare)) {
9161 board[fromY][fromX] = EmptySquare;
9162 board[toY][toX] = WhitePawn;
9163 if(oldEP & EP_BEROLIN_A) {
9164 captured = board[fromY][fromX-1];
9165 board[fromY][fromX-1] = EmptySquare;
9166 }else{ captured = board[fromY][fromX+1];
9167 board[fromY][fromX+1] = EmptySquare;
9169 } else if (board[fromY][fromX] == king
9170 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9171 && toY == fromY && toX > fromX+1) {
9172 board[fromY][fromX] = EmptySquare;
9173 board[toY][toX] = king;
9174 board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9175 board[fromY][BOARD_RGHT-1] = EmptySquare;
9176 } else if (board[fromY][fromX] == king
9177 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9178 && toY == fromY && toX < fromX-1) {
9179 board[fromY][fromX] = EmptySquare;
9180 board[toY][toX] = king;
9181 board[toY][toX+1] = board[fromY][BOARD_LEFT];
9182 board[fromY][BOARD_LEFT] = EmptySquare;
9183 } else if (fromY == 7 && fromX == 3
9184 && board[fromY][fromX] == BlackKing
9185 && toY == 7 && toX == 5) {
9186 board[fromY][fromX] = EmptySquare;
9187 board[toY][toX] = BlackKing;
9188 board[fromY][7] = EmptySquare;
9189 board[toY][4] = BlackRook;
9190 } else if (fromY == 7 && fromX == 3
9191 && board[fromY][fromX] == BlackKing
9192 && toY == 7 && toX == 1) {
9193 board[fromY][fromX] = EmptySquare;
9194 board[toY][toX] = BlackKing;
9195 board[fromY][0] = EmptySquare;
9196 board[toY][2] = BlackRook;
9197 } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
9198 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9199 && toY < promoRank && promoChar
9201 /* black pawn promotion */
9202 board[toY][toX] = CharToPiece(ToLower(promoChar));
9203 if(gameInfo.variant==VariantBughouse ||
9204 gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
9205 board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9206 board[fromY][fromX] = EmptySquare;
9207 } else if ((fromY < BOARD_HEIGHT>>1)
9209 && gameInfo.variant != VariantXiangqi
9210 && gameInfo.variant != VariantBerolina
9211 && (board[fromY][fromX] == BlackPawn)
9212 && (board[toY][toX] == EmptySquare)) {
9213 board[fromY][fromX] = EmptySquare;
9214 board[toY][toX] = BlackPawn;
9215 captured = board[toY + 1][toX];
9216 board[toY + 1][toX] = EmptySquare;
9217 } else if ((fromY == 3)
9219 && gameInfo.variant == VariantBerolina
9220 && (board[fromY][fromX] == BlackPawn)
9221 && (board[toY][toX] == EmptySquare)) {
9222 board[fromY][fromX] = EmptySquare;
9223 board[toY][toX] = BlackPawn;
9224 if(oldEP & EP_BEROLIN_A) {
9225 captured = board[fromY][fromX-1];
9226 board[fromY][fromX-1] = EmptySquare;
9227 }else{ captured = board[fromY][fromX+1];
9228 board[fromY][fromX+1] = EmptySquare;
9231 board[toY][toX] = board[fromY][fromX];
9232 board[fromY][fromX] = EmptySquare;
9236 if (gameInfo.holdingsWidth != 0) {
9238 /* !!A lot more code needs to be written to support holdings */
9239 /* [HGM] OK, so I have written it. Holdings are stored in the */
9240 /* penultimate board files, so they are automaticlly stored */
9241 /* in the game history. */
9242 if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
9243 && promoChar && piece != WhitePawn && piece != BlackPawn) {
9244 /* Delete from holdings, by decreasing count */
9245 /* and erasing image if necessary */
9246 p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
9247 if(p < (int) BlackPawn) { /* white drop */
9248 p -= (int)WhitePawn;
9249 p = PieceToNumber((ChessSquare)p);
9250 if(p >= gameInfo.holdingsSize) p = 0;
9251 if(--board[p][BOARD_WIDTH-2] <= 0)
9252 board[p][BOARD_WIDTH-1] = EmptySquare;
9253 if((int)board[p][BOARD_WIDTH-2] < 0)
9254 board[p][BOARD_WIDTH-2] = 0;
9255 } else { /* black drop */
9256 p -= (int)BlackPawn;
9257 p = PieceToNumber((ChessSquare)p);
9258 if(p >= gameInfo.holdingsSize) p = 0;
9259 if(--board[BOARD_HEIGHT-1-p][1] <= 0)
9260 board[BOARD_HEIGHT-1-p][0] = EmptySquare;
9261 if((int)board[BOARD_HEIGHT-1-p][1] < 0)
9262 board[BOARD_HEIGHT-1-p][1] = 0;
9265 if (captured != EmptySquare && gameInfo.holdingsSize > 0
9266 && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess ) {
9267 /* [HGM] holdings: Add to holdings, if holdings exist */
9268 if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
9269 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
9270 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
9273 if (p >= (int) BlackPawn) {
9274 p -= (int)BlackPawn;
9275 if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9276 /* in Shogi restore piece to its original first */
9277 captured = (ChessSquare) (DEMOTED captured);
9280 p = PieceToNumber((ChessSquare)p);
9281 if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
9282 board[p][BOARD_WIDTH-2]++;
9283 board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
9285 p -= (int)WhitePawn;
9286 if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9287 captured = (ChessSquare) (DEMOTED captured);
9290 p = PieceToNumber((ChessSquare)p);
9291 if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
9292 board[BOARD_HEIGHT-1-p][1]++;
9293 board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
9296 } else if (gameInfo.variant == VariantAtomic) {
9297 if (captured != EmptySquare) {
9299 for (y = toY-1; y <= toY+1; y++) {
9300 for (x = toX-1; x <= toX+1; x++) {
9301 if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
9302 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
9303 board[y][x] = EmptySquare;
9307 board[toY][toX] = EmptySquare;
9310 if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
9311 board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
9313 if(promoChar == '+') {
9314 /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite orinary Pawn promotion) */
9315 board[toY][toX] = (ChessSquare) (PROMOTED piece);
9316 } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
9317 board[toY][toX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
9319 if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
9320 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
9321 // [HGM] superchess: take promotion piece out of holdings
9322 int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
9323 if((int)piece < (int)BlackPawn) { // determine stm from piece color
9324 if(!--board[k][BOARD_WIDTH-2])
9325 board[k][BOARD_WIDTH-1] = EmptySquare;
9327 if(!--board[BOARD_HEIGHT-1-k][1])
9328 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
9334 /* Updates forwardMostMove */
9336 MakeMove (int fromX, int fromY, int toX, int toY, int promoChar)
9338 // forwardMostMove++; // [HGM] bare: moved downstream
9340 (void) CoordsToAlgebraic(boards[forwardMostMove],
9341 PosFlags(forwardMostMove),
9342 fromY, fromX, toY, toX, promoChar,
9343 parseList[forwardMostMove]);
9345 if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
9346 int timeLeft; static int lastLoadFlag=0; int king, piece;
9347 piece = boards[forwardMostMove][fromY][fromX];
9348 king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
9349 if(gameInfo.variant == VariantKnightmate)
9350 king += (int) WhiteUnicorn - (int) WhiteKing;
9351 if(forwardMostMove == 0) {
9352 if(gameMode == MachinePlaysBlack || gameMode == BeginningOfGame)
9353 fprintf(serverMoves, "%s;", UserName());
9354 else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b')
9355 fprintf(serverMoves, "%s;", second.tidy);
9356 fprintf(serverMoves, "%s;", first.tidy);
9357 if(gameMode == MachinePlaysWhite)
9358 fprintf(serverMoves, "%s;", UserName());
9359 else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
9360 fprintf(serverMoves, "%s;", second.tidy);
9361 } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
9362 lastLoadFlag = loadFlag;
9364 fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
9365 // print castling suffix
9366 if( toY == fromY && piece == king ) {
9368 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
9370 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
9373 if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
9374 boards[forwardMostMove][fromY][fromX] == BlackPawn ) &&
9375 boards[forwardMostMove][toY][toX] == EmptySquare
9376 && fromX != toX && fromY != toY)
9377 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
9379 if(promoChar != NULLCHAR)
9380 fprintf(serverMoves, ":%c:%c%c", ToLower(promoChar), AAA+toX, ONE+toY);
9382 char buf[MOVE_LEN*2], *p; int len;
9383 fprintf(serverMoves, "/%d/%d",
9384 pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
9385 if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
9386 else timeLeft = blackTimeRemaining/1000;
9387 fprintf(serverMoves, "/%d", timeLeft);
9388 strncpy(buf, parseList[forwardMostMove], MOVE_LEN*2);
9389 if(p = strchr(buf, '=')) *p = NULLCHAR;
9390 len = strlen(buf); if(len > 1 && buf[len-2] != '-') buf[len-2] = NULLCHAR; // strip to-square
9391 fprintf(serverMoves, "/%s", buf);
9393 fflush(serverMoves);
9396 if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations..
9397 GameEnds(GameUnfinished, _("Game too long; increase MAX_MOVES and recompile"), GE_XBOARD);
9400 UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
9401 if (commentList[forwardMostMove+1] != NULL) {
9402 free(commentList[forwardMostMove+1]);
9403 commentList[forwardMostMove+1] = NULL;
9405 CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
9406 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
9407 // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
9408 SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
9409 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
9410 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
9411 adjustedClock = FALSE;
9412 gameInfo.result = GameUnfinished;
9413 if (gameInfo.resultDetails != NULL) {
9414 free(gameInfo.resultDetails);
9415 gameInfo.resultDetails = NULL;
9417 CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
9418 moveList[forwardMostMove - 1]);
9419 switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
9425 if(gameInfo.variant != VariantShogi)
9426 strcat(parseList[forwardMostMove - 1], "+");
9430 strcat(parseList[forwardMostMove - 1], "#");
9433 if (appData.debugMode) {
9434 fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
9439 /* Updates currentMove if not pausing */
9441 ShowMove (int fromX, int fromY, int toX, int toY)
9443 int instant = (gameMode == PlayFromGameFile) ?
9444 (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
9445 if(appData.noGUI) return;
9446 if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
9448 if (forwardMostMove == currentMove + 1) {
9449 AnimateMove(boards[forwardMostMove - 1],
9450 fromX, fromY, toX, toY);
9452 if (appData.highlightLastMove) {
9453 SetHighlights(fromX, fromY, toX, toY);
9456 currentMove = forwardMostMove;
9459 if (instant) return;
9461 DisplayMove(currentMove - 1);
9462 DrawPosition(FALSE, boards[currentMove]);
9463 DisplayBothClocks();
9464 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
9468 SendEgtPath (ChessProgramState *cps)
9469 { /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
9470 char buf[MSG_SIZ], name[MSG_SIZ], *p;
9472 if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
9475 char c, *q = name+1, *r, *s;
9477 name[0] = ','; // extract next format name from feature and copy with prefixed ','
9478 while(*p && *p != ',') *q++ = *p++;
9480 if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
9481 strcmp(name, ",nalimov:") == 0 ) {
9482 // take nalimov path from the menu-changeable option first, if it is defined
9483 snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
9484 SendToProgram(buf,cps); // send egtbpath command for nalimov
9486 if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
9487 (s = StrStr(appData.egtFormats, name)) != NULL) {
9488 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
9489 s = r = StrStr(s, ":") + 1; // beginning of path info
9490 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
9491 c = *r; *r = 0; // temporarily null-terminate path info
9492 *--q = 0; // strip of trailig ':' from name
9493 snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
9495 SendToProgram(buf,cps); // send egtbpath command for this format
9497 if(*p == ',') p++; // read away comma to position for next format name
9502 InitChessProgram (ChessProgramState *cps, int setup)
9503 /* setup needed to setup FRC opening position */
9505 char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
9506 if (appData.noChessProgram) return;
9507 hintRequested = FALSE;
9508 bookRequested = FALSE;
9510 ParseFeatures(appData.features[cps == &second], cps); // [HGM] allow user to overrule features
9511 /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
9512 /* moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
9513 if(cps->memSize) { /* [HGM] memory */
9514 snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
9515 SendToProgram(buf, cps);
9517 SendEgtPath(cps); /* [HGM] EGT */
9518 if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
9519 snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
9520 SendToProgram(buf, cps);
9523 SendToProgram(cps->initString, cps);
9524 if (gameInfo.variant != VariantNormal &&
9525 gameInfo.variant != VariantLoadable
9526 /* [HGM] also send variant if board size non-standard */
9527 || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
9529 char *v = VariantName(gameInfo.variant);
9530 if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
9531 /* [HGM] in protocol 1 we have to assume all variants valid */
9532 snprintf(buf, MSG_SIZ, _("Variant %s not supported by %s"), v, cps->tidy);
9533 DisplayFatalError(buf, 0, 1);
9537 /* [HGM] make prefix for non-standard board size. Awkward testing... */
9538 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9539 if( gameInfo.variant == VariantXiangqi )
9540 overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
9541 if( gameInfo.variant == VariantShogi )
9542 overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
9543 if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
9544 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
9545 if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
9546 gameInfo.variant == VariantGothic || gameInfo.variant == VariantFalcon || gameInfo.variant == VariantJanus )
9547 overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9548 if( gameInfo.variant == VariantCourier )
9549 overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9550 if( gameInfo.variant == VariantSuper )
9551 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9552 if( gameInfo.variant == VariantGreat )
9553 overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9554 if( gameInfo.variant == VariantSChess )
9555 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 7;
9556 if( gameInfo.variant == VariantGrand )
9557 overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 7;
9560 snprintf(b, MSG_SIZ, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
9561 gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
9562 /* [HGM] varsize: try first if this defiant size variant is specifically known */
9563 if(StrStr(cps->variants, b) == NULL) {
9564 // specific sized variant not known, check if general sizing allowed
9565 if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
9566 if(StrStr(cps->variants, "boardsize") == NULL) {
9567 snprintf(buf, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
9568 gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
9569 DisplayFatalError(buf, 0, 1);
9572 /* [HGM] here we really should compare with the maximum supported board size */
9575 } else snprintf(b, MSG_SIZ,"%s", VariantName(gameInfo.variant));
9576 snprintf(buf, MSG_SIZ, "variant %s\n", b);
9577 SendToProgram(buf, cps);
9579 currentlyInitializedVariant = gameInfo.variant;
9581 /* [HGM] send opening position in FRC to first engine */
9583 SendToProgram("force\n", cps);
9585 /* engine is now in force mode! Set flag to wake it up after first move. */
9586 setboardSpoiledMachineBlack = 1;
9590 snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
9591 SendToProgram(buf, cps);
9593 cps->maybeThinking = FALSE;
9594 cps->offeredDraw = 0;
9595 if (!appData.icsActive) {
9596 SendTimeControl(cps, movesPerSession, timeControl,
9597 timeIncrement, appData.searchDepth,
9600 if (appData.showThinking
9601 // [HGM] thinking: four options require thinking output to be sent
9602 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
9604 SendToProgram("post\n", cps);
9606 SendToProgram("hard\n", cps);
9607 if (!appData.ponderNextMove) {
9608 /* Warning: "easy" is a toggle in GNU Chess, so don't send
9609 it without being sure what state we are in first. "hard"
9610 is not a toggle, so that one is OK.
9612 SendToProgram("easy\n", cps);
9615 snprintf(buf, MSG_SIZ, "ping %d\n", ++cps->lastPing);
9616 SendToProgram(buf, cps);
9618 cps->initDone = TRUE;
9619 ClearEngineOutputPane(cps == &second);
9624 StartChessProgram (ChessProgramState *cps)
9629 if (appData.noChessProgram) return;
9630 cps->initDone = FALSE;
9632 if (strcmp(cps->host, "localhost") == 0) {
9633 err = StartChildProcess(cps->program, cps->dir, &cps->pr);
9634 } else if (*appData.remoteShell == NULLCHAR) {
9635 err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
9637 if (*appData.remoteUser == NULLCHAR) {
9638 snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
9641 snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
9642 cps->host, appData.remoteUser, cps->program);
9644 err = StartChildProcess(buf, "", &cps->pr);
9648 snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
9649 DisplayError(buf, err); // [HGM] bit of a rough kludge: ignore failure, (which XBoard would do anyway), and let I/O discover it
9650 if(cps != &first) return;
9651 appData.noChessProgram = TRUE;
9654 // DisplayFatalError(buf, err, 1);
9655 // cps->pr = NoProc;
9660 cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
9661 if (cps->protocolVersion > 1) {
9662 snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
9663 cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
9664 cps->comboCnt = 0; // and values of combo boxes
9665 SendToProgram(buf, cps);
9667 SendToProgram("xboard\n", cps);
9672 TwoMachinesEventIfReady P((void))
9674 static int curMess = 0;
9675 if (first.lastPing != first.lastPong) {
9676 if(curMess != 1) DisplayMessage("", _("Waiting for first chess program")); curMess = 1;
9677 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9680 if (second.lastPing != second.lastPong) {
9681 if(curMess != 2) DisplayMessage("", _("Waiting for second chess program")); curMess = 2;
9682 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9685 DisplayMessage("", ""); curMess = 0;
9691 MakeName (char *template)
9695 static char buf[MSG_SIZ];
9699 clock = time((time_t *)NULL);
9700 tm = localtime(&clock);
9702 while(*p++ = *template++) if(p[-1] == '%') {
9703 switch(*template++) {
9704 case 0: *p = 0; return buf;
9705 case 'Y': i = tm->tm_year+1900; break;
9706 case 'y': i = tm->tm_year-100; break;
9707 case 'M': i = tm->tm_mon+1; break;
9708 case 'd': i = tm->tm_mday; break;
9709 case 'h': i = tm->tm_hour; break;
9710 case 'm': i = tm->tm_min; break;
9711 case 's': i = tm->tm_sec; break;
9714 snprintf(p-1, MSG_SIZ-10 - (p - buf), "%02d", i); p += strlen(p);
9720 CountPlayers (char *p)
9723 while(p = strchr(p, '\n')) p++, n++; // count participants
9728 WriteTourneyFile (char *results, FILE *f)
9729 { // write tournament parameters on tourneyFile; on success return the stream pointer for closing
9730 if(f == NULL) f = fopen(appData.tourneyFile, "w");
9731 if(f == NULL) DisplayError(_("Could not write on tourney file"), 0); else {
9732 // create a file with tournament description
9733 fprintf(f, "-participants {%s}\n", appData.participants);
9734 fprintf(f, "-seedBase %d\n", appData.seedBase);
9735 fprintf(f, "-tourneyType %d\n", appData.tourneyType);
9736 fprintf(f, "-tourneyCycles %d\n", appData.tourneyCycles);
9737 fprintf(f, "-defaultMatchGames %d\n", appData.defaultMatchGames);
9738 fprintf(f, "-syncAfterRound %s\n", appData.roundSync ? "true" : "false");
9739 fprintf(f, "-syncAfterCycle %s\n", appData.cycleSync ? "true" : "false");
9740 fprintf(f, "-saveGameFile \"%s\"\n", appData.saveGameFile);
9741 fprintf(f, "-loadGameFile \"%s\"\n", appData.loadGameFile);
9742 fprintf(f, "-loadGameIndex %d\n", appData.loadGameIndex);
9743 fprintf(f, "-loadPositionFile \"%s\"\n", appData.loadPositionFile);
9744 fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex);
9745 fprintf(f, "-rewindIndex %d\n", appData.rewindIndex);
9746 fprintf(f, "-discourageOwnBooks %s\n", appData.defNoBook ? "true" : "false");
9748 fprintf(f, "-searchTime \"%d:%02d\"\n", searchTime/60, searchTime%60);
9750 fprintf(f, "-mps %d\n", appData.movesPerSession);
9751 fprintf(f, "-tc %s\n", appData.timeControl);
9752 fprintf(f, "-inc %.2f\n", appData.timeIncrement);
9754 fprintf(f, "-results \"%s\"\n", results);
9759 #define MAXENGINES 1000
9760 char *command[MAXENGINES], *mnemonic[MAXENGINES];
9763 Substitute (char *participants, int expunge)
9765 int i, changed, changes=0, nPlayers=0;
9766 char *p, *q, *r, buf[MSG_SIZ];
9767 if(participants == NULL) return;
9768 if(appData.tourneyFile[0] == NULLCHAR) { free(participants); return; }
9769 r = p = participants; q = appData.participants;
9770 while(*p && *p == *q) {
9771 if(*p == '\n') r = p+1, nPlayers++;
9774 if(*p) { // difference
9775 while(*p && *p++ != '\n');
9776 while(*q && *q++ != '\n');
9778 changes = 1 + (strcmp(p, q) != 0);
9780 if(changes == 1) { // a single engine mnemonic was changed
9781 q = r; while(*q) nPlayers += (*q++ == '\n');
9782 p = buf; while(*r && (*p = *r++) != '\n') p++;
9784 NamesToList(firstChessProgramNames, command, mnemonic);
9785 for(i=1; mnemonic[i]; i++) if(!strcmp(buf, mnemonic[i])) break;
9786 if(mnemonic[i]) { // The substitute is valid
9788 if(appData.tourneyFile[0] && (f = fopen(appData.tourneyFile, "r+")) ) {
9789 flock(fileno(f), LOCK_EX);
9790 ParseArgsFromFile(f);
9791 fseek(f, 0, SEEK_SET);
9792 FREE(appData.participants); appData.participants = participants;
9793 if(expunge) { // erase results of replaced engine
9794 int len = strlen(appData.results), w, b, dummy;
9795 for(i=0; i<len; i++) {
9796 Pairing(i, nPlayers, &w, &b, &dummy);
9797 if((w == changed || b == changed) && appData.results[i] == '*') {
9798 DisplayError(_("You cannot replace an engine while it is engaged!\nTerminate its game first."), 0);
9803 for(i=0; i<len; i++) {
9804 Pairing(i, nPlayers, &w, &b, &dummy);
9805 if(w == changed || b == changed) appData.results[i] = ' '; // mark as not played
9808 WriteTourneyFile(appData.results, f);
9809 fclose(f); // release lock
9812 } else DisplayError(_("No engine with the name you gave is installed"), 0);
9814 if(changes == 0) DisplayError(_("First change an engine by editing the participants list\nof the Tournament Options dialog"), 0);
9815 if(changes > 1) DisplayError(_("You can only change one engine at the time"), 0);
9821 CreateTourney (char *name)
9824 if(matchMode && strcmp(name, appData.tourneyFile)) {
9825 ASSIGN(name, appData.tourneyFile); //do not allow change of tourneyfile while playing
9827 if(name[0] == NULLCHAR) {
9828 if(appData.participants[0])
9829 DisplayError(_("You must supply a tournament file,\nfor storing the tourney progress"), 0);
9832 f = fopen(name, "r");
9833 if(f) { // file exists
9834 ASSIGN(appData.tourneyFile, name);
9835 ParseArgsFromFile(f); // parse it
9837 if(!appData.participants[0]) return 0; // ignore tourney file if non-existing & no participants
9838 if(CountPlayers(appData.participants) < (appData.tourneyType>0 ? appData.tourneyType+1 : 2)) {
9839 DisplayError(_("Not enough participants"), 0);
9842 ASSIGN(appData.tourneyFile, name);
9843 if(appData.tourneyType < 0) appData.defaultMatchGames = 1; // Swiss forces games/pairing = 1
9844 if((f = WriteTourneyFile("", NULL)) == NULL) return 0;
9847 appData.noChessProgram = FALSE;
9848 appData.clockMode = TRUE;
9854 NamesToList (char *names, char **engineList, char **engineMnemonic)
9856 char buf[MSG_SIZ], *p, *q;
9860 while(*p && *p != '\n') *q++ = *p++;
9862 if(engineList[i]) free(engineList[i]);
9863 engineList[i] = strdup(buf);
9865 TidyProgramName(engineList[i], "localhost", buf);
9866 if(engineMnemonic[i]) free(engineMnemonic[i]);
9867 if((q = strstr(engineList[i]+2, "variant")) && q[-2]== ' ' && (q[-1]=='/' || q[-1]=='-') && (q[7]==' ' || q[7]=='=')) {
9869 sscanf(q + 8, "%s", buf + strlen(buf));
9872 engineMnemonic[i] = strdup(buf);
9874 if(i > MAXENGINES - 2) break;
9876 engineList[i] = engineMnemonic[i] = NULL;
9879 // following implemented as macro to avoid type limitations
9880 #define SWAP(item, temp) temp = appData.item[0]; appData.item[0] = appData.item[n]; appData.item[n] = temp;
9884 { // swap settings for first engine and other engine (so far only some selected options)
9889 SWAP(chessProgram, p)
9891 SWAP(hasOwnBookUCI, h)
9892 SWAP(protocolVersion, h)
9894 SWAP(scoreIsAbsolute, h)
9903 SetPlayer (int player)
9904 { // [HGM] find the engine line of the partcipant given by number, and parse its options.
9906 char buf[MSG_SIZ], *engineName, *p = appData.participants;
9907 for(i=0; i<player; i++) p = strchr(p, '\n') + 1;
9908 engineName = strdup(p); if(p = strchr(engineName, '\n')) *p = NULLCHAR;
9909 for(i=1; command[i]; i++) if(!strcmp(mnemonic[i], engineName)) break;
9911 snprintf(buf, MSG_SIZ, "-fcp %s", command[i]);
9912 ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL; appData.pvSAN[0] = FALSE;
9913 appData.firstHasOwnBookUCI = !appData.defNoBook;
9914 ParseArgsFromString(buf);
9920 Pairing (int nr, int nPlayers, int *whitePlayer, int *blackPlayer, int *syncInterval)
9921 { // determine players from game number
9922 int curCycle, curRound, curPairing, gamesPerCycle, gamesPerRound, roundsPerCycle=1, pairingsPerRound=1;
9924 if(appData.tourneyType == 0) {
9925 roundsPerCycle = (nPlayers - 1) | 1;
9926 pairingsPerRound = nPlayers / 2;
9927 } else if(appData.tourneyType > 0) {
9928 roundsPerCycle = nPlayers - appData.tourneyType;
9929 pairingsPerRound = appData.tourneyType;
9931 gamesPerRound = pairingsPerRound * appData.defaultMatchGames;
9932 gamesPerCycle = gamesPerRound * roundsPerCycle;
9933 appData.matchGames = gamesPerCycle * appData.tourneyCycles - 1; // fake like all games are one big match
9934 curCycle = nr / gamesPerCycle; nr %= gamesPerCycle;
9935 curRound = nr / gamesPerRound; nr %= gamesPerRound;
9936 curPairing = nr / appData.defaultMatchGames; nr %= appData.defaultMatchGames;
9937 matchGame = nr + curCycle * appData.defaultMatchGames + 1; // fake game nr that loads correct game or position from file
9938 roundNr = (curCycle * roundsPerCycle + curRound) * appData.defaultMatchGames + nr + 1;
9940 if(appData.cycleSync) *syncInterval = gamesPerCycle;
9941 if(appData.roundSync) *syncInterval = gamesPerRound;
9943 if(appData.debugMode) fprintf(debugFP, "cycle=%d, round=%d, pairing=%d curGame=%d\n", curCycle, curRound, curPairing, matchGame);
9945 if(appData.tourneyType == 0) {
9946 if(curPairing == (nPlayers-1)/2 ) {
9947 *whitePlayer = curRound;
9948 *blackPlayer = nPlayers - 1; // this is the 'bye' when nPlayer is odd
9950 *whitePlayer = curRound - (nPlayers-1)/2 + curPairing;
9951 if(*whitePlayer < 0) *whitePlayer += nPlayers-1+(nPlayers&1);
9952 *blackPlayer = curRound + (nPlayers-1)/2 - curPairing;
9953 if(*blackPlayer >= nPlayers-1+(nPlayers&1)) *blackPlayer -= nPlayers-1+(nPlayers&1);
9955 } else if(appData.tourneyType > 0) {
9956 *whitePlayer = curPairing;
9957 *blackPlayer = curRound + appData.tourneyType;
9960 // take care of white/black alternation per round.
9961 // For cycles and games this is already taken care of by default, derived from matchGame!
9962 return curRound & 1;
9966 NextTourneyGame (int nr, int *swapColors)
9967 { // !!!major kludge!!! fiddle appData settings to get everything in order for next tourney game
9969 int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers;
9971 if(appData.tourneyFile[0] == NULLCHAR) return 1; // no tourney, always allow next game
9972 tf = fopen(appData.tourneyFile, "r");
9973 if(tf == NULL) { DisplayFatalError(_("Bad tournament file"), 0, 1); return 0; }
9974 ParseArgsFromFile(tf); fclose(tf);
9975 InitTimeControls(); // TC might be altered from tourney file
9977 nPlayers = CountPlayers(appData.participants); // count participants
9978 if(appData.tourneyType < 0) syncInterval = nPlayers/2; else
9979 *swapColors = Pairing(nr<0 ? 0 : nr, nPlayers, &whitePlayer, &blackPlayer, &syncInterval);
9982 p = q = appData.results;
9983 while(*q) if(*q++ == '*' || q[-1] == ' ') { firstBusy = q - p - 1; break; }
9984 if(firstBusy/syncInterval < (nextGame/syncInterval)) {
9985 DisplayMessage(_("Waiting for other game(s)"),"");
9986 waitingForGame = TRUE;
9987 ScheduleDelayedEvent(NextMatchGame, 1000); // wait for all games of previous round to finish
9990 waitingForGame = FALSE;
9993 if(appData.tourneyType < 0) {
9994 if(nr>=0 && !pairingReceived) {
9996 if(pairing.pr == NoProc) {
9997 if(!appData.pairingEngine[0]) {
9998 DisplayFatalError(_("No pairing engine specified"), 0, 1);
10001 StartChessProgram(&pairing); // starts the pairing engine
10003 snprintf(buf, 1<<16, "results %d %s\n", nPlayers, appData.results);
10004 SendToProgram(buf, &pairing);
10005 snprintf(buf, 1<<16, "pairing %d\n", nr+1);
10006 SendToProgram(buf, &pairing);
10007 return 0; // wait for pairing engine to answer (which causes NextTourneyGame to be called again...
10009 pairingReceived = 0; // ... so we continue here
10011 appData.matchGames = appData.tourneyCycles * syncInterval - 1;
10012 whitePlayer = savedWhitePlayer-1; blackPlayer = savedBlackPlayer-1;
10013 matchGame = 1; roundNr = nr / syncInterval + 1;
10016 if(first.pr != NoProc || second.pr != NoProc) return 1; // engines already loaded
10018 // redefine engines, engine dir, etc.
10019 NamesToList(firstChessProgramNames, command, mnemonic); // get mnemonics of installed engines
10020 SetPlayer(whitePlayer); // find white player amongst it, and parse its engine line
10022 SetPlayer(blackPlayer); // find black player amongst it, and parse its engine line
10023 SwapEngines(1); // and make that valid for second engine by swapping
10024 InitEngine(&first, 0); // initialize ChessProgramStates based on new settings.
10025 InitEngine(&second, 1);
10026 CommonEngineInit(); // after this TwoMachinesEvent will create correct engine processes
10027 UpdateLogos(FALSE); // leave display to ModeHiglight()
10033 { // performs game initialization that does not invoke engines, and then tries to start the game
10034 int res, firstWhite, swapColors = 0;
10035 if(!NextTourneyGame(nextGame, &swapColors)) return; // this sets matchGame, -fcp / -scp and other options for next game, if needed
10036 firstWhite = appData.firstPlaysBlack ^ (matchGame & 1 | appData.sameColorGames > 1); // non-incremental default
10037 firstWhite ^= swapColors; // reverses if NextTourneyGame says we are in an odd round
10038 first.twoMachinesColor = firstWhite ? "white\n" : "black\n"; // perform actual color assignement
10039 second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
10040 appData.noChessProgram = (first.pr == NoProc); // kludge to prevent Reset from starting up chess program
10041 if(appData.loadGameIndex == -2) srandom(appData.seedBase + 68163*(nextGame & ~1)); // deterministic seed to force same opening
10042 Reset(FALSE, first.pr != NoProc);
10043 res = LoadGameOrPosition(matchGame); // setup game
10044 appData.noChessProgram = FALSE; // LoadGameOrPosition might call Reset too!
10045 if(!res) return; // abort when bad game/pos file
10046 TwoMachinesEvent();
10050 UserAdjudicationEvent (int result)
10052 ChessMove gameResult = GameIsDrawn;
10055 gameResult = WhiteWins;
10057 else if( result < 0 ) {
10058 gameResult = BlackWins;
10061 if( gameMode == TwoMachinesPlay ) {
10062 GameEnds( gameResult, "User adjudication", GE_XBOARD );
10067 // [HGM] save: calculate checksum of game to make games easily identifiable
10069 StringCheckSum (char *s)
10072 if(s==NULL) return 0;
10073 while(*s) i = i*259 + *s++;
10081 for(i=backwardMostMove; i<forwardMostMove; i++) {
10082 sum += pvInfoList[i].depth;
10083 sum += StringCheckSum(parseList[i]);
10084 sum += StringCheckSum(commentList[i]);
10087 if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
10088 return sum + StringCheckSum(commentList[i]);
10089 } // end of save patch
10092 GameEnds (ChessMove result, char *resultDetails, int whosays)
10094 GameMode nextGameMode;
10096 char buf[MSG_SIZ], popupRequested = 0, *ranking = NULL;
10098 if(endingGame) return; /* [HGM] crash: forbid recursion */
10100 if(twoBoards) { // [HGM] dual: switch back to one board
10101 twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
10102 DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
10104 if (appData.debugMode) {
10105 fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
10106 result, resultDetails ? resultDetails : "(null)", whosays);
10109 fromX = fromY = -1; // [HGM] abort any move the user is entering.
10111 if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
10112 /* If we are playing on ICS, the server decides when the
10113 game is over, but the engine can offer to draw, claim
10117 if (appData.zippyPlay && first.initDone) {
10118 if (result == GameIsDrawn) {
10119 /* In case draw still needs to be claimed */
10120 SendToICS(ics_prefix);
10121 SendToICS("draw\n");
10122 } else if (StrCaseStr(resultDetails, "resign")) {
10123 SendToICS(ics_prefix);
10124 SendToICS("resign\n");
10128 endingGame = 0; /* [HGM] crash */
10132 /* If we're loading the game from a file, stop */
10133 if (whosays == GE_FILE) {
10134 (void) StopLoadGameTimer();
10138 /* Cancel draw offers */
10139 first.offeredDraw = second.offeredDraw = 0;
10141 /* If this is an ICS game, only ICS can really say it's done;
10142 if not, anyone can. */
10143 isIcsGame = (gameMode == IcsPlayingWhite ||
10144 gameMode == IcsPlayingBlack ||
10145 gameMode == IcsObserving ||
10146 gameMode == IcsExamining);
10148 if (!isIcsGame || whosays == GE_ICS) {
10149 /* OK -- not an ICS game, or ICS said it was done */
10151 if (!isIcsGame && !appData.noChessProgram)
10152 SetUserThinkingEnables();
10154 /* [HGM] if a machine claims the game end we verify this claim */
10155 if(gameMode == TwoMachinesPlay && appData.testClaims) {
10156 if(appData.testLegality && whosays >= GE_ENGINE1 ) {
10158 ChessMove trueResult = (ChessMove) -1;
10160 claimer = whosays == GE_ENGINE1 ? /* color of claimer */
10161 first.twoMachinesColor[0] :
10162 second.twoMachinesColor[0] ;
10164 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
10165 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
10166 /* [HGM] verify: engine mate claims accepted if they were flagged */
10167 trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
10169 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
10170 /* [HGM] verify: engine mate claims accepted if they were flagged */
10171 trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
10173 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
10174 trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
10177 // now verify win claims, but not in drop games, as we don't understand those yet
10178 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
10179 || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) &&
10180 (result == WhiteWins && claimer == 'w' ||
10181 result == BlackWins && claimer == 'b' ) ) { // case to verify: engine claims own win
10182 if (appData.debugMode) {
10183 fprintf(debugFP, "result=%d sp=%d move=%d\n",
10184 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
10186 if(result != trueResult) {
10187 snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
10188 result = claimer == 'w' ? BlackWins : WhiteWins;
10189 resultDetails = buf;
10192 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
10193 && (forwardMostMove <= backwardMostMove ||
10194 (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
10195 (claimer=='b')==(forwardMostMove&1))
10197 /* [HGM] verify: draws that were not flagged are false claims */
10198 snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
10199 result = claimer == 'w' ? BlackWins : WhiteWins;
10200 resultDetails = buf;
10202 /* (Claiming a loss is accepted no questions asked!) */
10204 /* [HGM] bare: don't allow bare King to win */
10205 if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
10206 || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
10207 && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
10208 && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
10209 && result != GameIsDrawn)
10210 { int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
10211 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
10212 int p = (signed char)boards[forwardMostMove][i][j] - color;
10213 if(p >= 0 && p <= (int)WhiteKing) k++;
10215 if (appData.debugMode) {
10216 fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
10217 result, resultDetails ? resultDetails : "(null)", whosays, k, color);
10220 result = GameIsDrawn;
10221 snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
10222 resultDetails = buf;
10228 if(serverMoves != NULL && !loadFlag) { char c = '=';
10229 if(result==WhiteWins) c = '+';
10230 if(result==BlackWins) c = '-';
10231 if(resultDetails != NULL)
10232 fprintf(serverMoves, ";%c;%s\n", c, resultDetails), fflush(serverMoves);
10234 if (resultDetails != NULL) {
10235 gameInfo.result = result;
10236 gameInfo.resultDetails = StrSave(resultDetails);
10238 /* display last move only if game was not loaded from file */
10239 if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
10240 DisplayMove(currentMove - 1);
10242 if (forwardMostMove != 0) {
10243 if (gameMode != PlayFromGameFile && gameMode != EditGame
10244 && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
10246 if (*appData.saveGameFile != NULLCHAR) {
10247 SaveGameToFile(appData.saveGameFile, TRUE);
10248 } else if (appData.autoSaveGames) {
10251 if (*appData.savePositionFile != NULLCHAR) {
10252 SavePositionToFile(appData.savePositionFile);
10257 /* Tell program how game ended in case it is learning */
10258 /* [HGM] Moved this to after saving the PGN, just in case */
10259 /* engine died and we got here through time loss. In that */
10260 /* case we will get a fatal error writing the pipe, which */
10261 /* would otherwise lose us the PGN. */
10262 /* [HGM] crash: not needed anymore, but doesn't hurt; */
10263 /* output during GameEnds should never be fatal anymore */
10264 if (gameMode == MachinePlaysWhite ||
10265 gameMode == MachinePlaysBlack ||
10266 gameMode == TwoMachinesPlay ||
10267 gameMode == IcsPlayingWhite ||
10268 gameMode == IcsPlayingBlack ||
10269 gameMode == BeginningOfGame) {
10271 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
10273 if (first.pr != NoProc) {
10274 SendToProgram(buf, &first);
10276 if (second.pr != NoProc &&
10277 gameMode == TwoMachinesPlay) {
10278 SendToProgram(buf, &second);
10283 if (appData.icsActive) {
10284 if (appData.quietPlay &&
10285 (gameMode == IcsPlayingWhite ||
10286 gameMode == IcsPlayingBlack)) {
10287 SendToICS(ics_prefix);
10288 SendToICS("set shout 1\n");
10290 nextGameMode = IcsIdle;
10291 ics_user_moved = FALSE;
10292 /* clean up premove. It's ugly when the game has ended and the
10293 * premove highlights are still on the board.
10296 gotPremove = FALSE;
10297 ClearPremoveHighlights();
10298 DrawPosition(FALSE, boards[currentMove]);
10300 if (whosays == GE_ICS) {
10303 if (gameMode == IcsPlayingWhite)
10305 else if(gameMode == IcsPlayingBlack)
10306 PlayIcsLossSound();
10309 if (gameMode == IcsPlayingBlack)
10311 else if(gameMode == IcsPlayingWhite)
10312 PlayIcsLossSound();
10315 PlayIcsDrawSound();
10318 PlayIcsUnfinishedSound();
10321 } else if (gameMode == EditGame ||
10322 gameMode == PlayFromGameFile ||
10323 gameMode == AnalyzeMode ||
10324 gameMode == AnalyzeFile) {
10325 nextGameMode = gameMode;
10327 nextGameMode = EndOfGame;
10332 nextGameMode = gameMode;
10335 if (appData.noChessProgram) {
10336 gameMode = nextGameMode;
10338 endingGame = 0; /* [HGM] crash */
10343 /* Put first chess program into idle state */
10344 if (first.pr != NoProc &&
10345 (gameMode == MachinePlaysWhite ||
10346 gameMode == MachinePlaysBlack ||
10347 gameMode == TwoMachinesPlay ||
10348 gameMode == IcsPlayingWhite ||
10349 gameMode == IcsPlayingBlack ||
10350 gameMode == BeginningOfGame)) {
10351 SendToProgram("force\n", &first);
10352 if (first.usePing) {
10354 snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
10355 SendToProgram(buf, &first);
10358 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
10359 /* Kill off first chess program */
10360 if (first.isr != NULL)
10361 RemoveInputSource(first.isr);
10364 if (first.pr != NoProc) {
10366 DoSleep( appData.delayBeforeQuit );
10367 SendToProgram("quit\n", &first);
10368 DoSleep( appData.delayAfterQuit );
10369 DestroyChildProcess(first.pr, first.useSigterm);
10373 if (second.reuse) {
10374 /* Put second chess program into idle state */
10375 if (second.pr != NoProc &&
10376 gameMode == TwoMachinesPlay) {
10377 SendToProgram("force\n", &second);
10378 if (second.usePing) {
10380 snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
10381 SendToProgram(buf, &second);
10384 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
10385 /* Kill off second chess program */
10386 if (second.isr != NULL)
10387 RemoveInputSource(second.isr);
10390 if (second.pr != NoProc) {
10391 DoSleep( appData.delayBeforeQuit );
10392 SendToProgram("quit\n", &second);
10393 DoSleep( appData.delayAfterQuit );
10394 DestroyChildProcess(second.pr, second.useSigterm);
10396 second.pr = NoProc;
10399 if (matchMode && (gameMode == TwoMachinesPlay || waitingForGame && exiting)) {
10400 char resChar = '=';
10404 if (first.twoMachinesColor[0] == 'w') {
10407 second.matchWins++;
10412 if (first.twoMachinesColor[0] == 'b') {
10415 second.matchWins++;
10418 case GameUnfinished:
10424 if(waitingForGame) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game
10425 if(appData.tourneyFile[0]){ // [HGM] we are in a tourney; update tourney file with game result
10426 if(appData.afterGame && appData.afterGame[0]) RunCommand(appData.afterGame);
10427 ReserveGame(nextGame, resChar); // sets nextGame
10428 if(nextGame > appData.matchGames) appData.tourneyFile[0] = 0, ranking = TourneyStandings(3); // tourney is done
10429 else ranking = strdup("busy"); //suppress popup when aborted but not finished
10430 } else roundNr = nextGame = matchGame + 1; // normal match, just increment; round equals matchGame
10432 if (nextGame <= appData.matchGames && !abortMatch) {
10433 gameMode = nextGameMode;
10434 matchGame = nextGame; // this will be overruled in tourney mode!
10435 GetTimeMark(&pauseStart); // [HGM] matchpause: stipulate a pause
10436 ScheduleDelayedEvent(NextMatchGame, 10); // but start game immediately (as it will wait out the pause itself)
10437 endingGame = 0; /* [HGM] crash */
10440 gameMode = nextGameMode;
10441 snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
10442 first.tidy, second.tidy,
10443 first.matchWins, second.matchWins,
10444 appData.matchGames - (first.matchWins + second.matchWins));
10445 if(!appData.tourneyFile[0]) matchGame++, DisplayTwoMachinesTitle(); // [HGM] update result in window title
10446 if(ranking && strcmp(ranking, "busy") && appData.afterTourney && appData.afterTourney[0]) RunCommand(appData.afterTourney);
10447 popupRequested++; // [HGM] crash: postpone to after resetting endingGame
10448 if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
10449 first.twoMachinesColor = "black\n";
10450 second.twoMachinesColor = "white\n";
10452 first.twoMachinesColor = "white\n";
10453 second.twoMachinesColor = "black\n";
10457 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
10458 !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
10460 gameMode = nextGameMode;
10462 endingGame = 0; /* [HGM] crash */
10463 if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
10464 if(matchMode == TRUE) { // match through command line: exit with or without popup
10466 ToNrEvent(forwardMostMove);
10467 if(strcmp(ranking, "busy")) DisplayFatalError(ranking, 0, 0);
10469 } else DisplayFatalError(buf, 0, 0);
10470 } else { // match through menu; just stop, with or without popup
10471 matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
10474 if(strcmp(ranking, "busy")) DisplayNote(ranking);
10475 } else DisplayNote(buf);
10477 if(ranking) free(ranking);
10481 /* Assumes program was just initialized (initString sent).
10482 Leaves program in force mode. */
10484 FeedMovesToProgram (ChessProgramState *cps, int upto)
10488 if (appData.debugMode)
10489 fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
10490 startedFromSetupPosition ? "position and " : "",
10491 backwardMostMove, upto, cps->which);
10492 if(currentlyInitializedVariant != gameInfo.variant) {
10494 // [HGM] variantswitch: make engine aware of new variant
10495 if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
10496 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
10497 snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
10498 SendToProgram(buf, cps);
10499 currentlyInitializedVariant = gameInfo.variant;
10501 SendToProgram("force\n", cps);
10502 if (startedFromSetupPosition) {
10503 SendBoard(cps, backwardMostMove);
10504 if (appData.debugMode) {
10505 fprintf(debugFP, "feedMoves\n");
10508 for (i = backwardMostMove; i < upto; i++) {
10509 SendMoveToProgram(i, cps);
10515 ResurrectChessProgram ()
10517 /* The chess program may have exited.
10518 If so, restart it and feed it all the moves made so far. */
10519 static int doInit = 0;
10521 if (appData.noChessProgram) return 1;
10523 if(matchMode && appData.tourneyFile[0]) { // [HGM] tourney: make sure we get features after engine replacement. (Should we always do this?)
10524 if(WaitForEngine(&first, TwoMachinesEventIfReady)) { doInit = 1; return 0; } // request to do init on next visit
10525 if(!doInit) return 1; // this replaces testing first.pr != NoProc, which is true when we get here, but first time no reason to abort
10526 doInit = 0; // we fell through (first time after starting the engine); make sure it doesn't happen again
10528 if (first.pr != NoProc) return 1;
10529 StartChessProgram(&first);
10531 InitChessProgram(&first, FALSE);
10532 FeedMovesToProgram(&first, currentMove);
10534 if (!first.sendTime) {
10535 /* can't tell gnuchess what its clock should read,
10536 so we bow to its notion. */
10538 timeRemaining[0][currentMove] = whiteTimeRemaining;
10539 timeRemaining[1][currentMove] = blackTimeRemaining;
10542 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
10543 appData.icsEngineAnalyze) && first.analysisSupport) {
10544 SendToProgram("analyze\n", &first);
10545 first.analyzing = TRUE;
10551 * Button procedures
10554 Reset (int redraw, int init)
10558 if (appData.debugMode) {
10559 fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
10560 redraw, init, gameMode);
10562 CleanupTail(); // [HGM] vari: delete any stored variations
10563 CommentPopDown(); // [HGM] make sure no comments to the previous game keep hanging on
10564 pausing = pauseExamInvalid = FALSE;
10565 startedFromSetupPosition = blackPlaysFirst = FALSE;
10567 whiteFlag = blackFlag = FALSE;
10568 userOfferedDraw = FALSE;
10569 hintRequested = bookRequested = FALSE;
10570 first.maybeThinking = FALSE;
10571 second.maybeThinking = FALSE;
10572 first.bookSuspend = FALSE; // [HGM] book
10573 second.bookSuspend = FALSE;
10574 thinkOutput[0] = NULLCHAR;
10575 lastHint[0] = NULLCHAR;
10576 ClearGameInfo(&gameInfo);
10577 gameInfo.variant = StringToVariant(appData.variant);
10578 ics_user_moved = ics_clock_paused = FALSE;
10579 ics_getting_history = H_FALSE;
10581 white_holding[0] = black_holding[0] = NULLCHAR;
10582 ClearProgramStats();
10583 opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
10587 flipView = appData.flipView;
10588 ClearPremoveHighlights();
10589 gotPremove = FALSE;
10590 alarmSounded = FALSE;
10592 GameEnds(EndOfFile, NULL, GE_PLAYER);
10593 if(appData.serverMovesName != NULL) {
10594 /* [HGM] prepare to make moves file for broadcasting */
10595 clock_t t = clock();
10596 if(serverMoves != NULL) fclose(serverMoves);
10597 serverMoves = fopen(appData.serverMovesName, "r");
10598 if(serverMoves != NULL) {
10599 fclose(serverMoves);
10600 /* delay 15 sec before overwriting, so all clients can see end */
10601 while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
10603 serverMoves = fopen(appData.serverMovesName, "w");
10607 gameMode = BeginningOfGame;
10609 if(appData.icsActive) gameInfo.variant = VariantNormal;
10610 currentMove = forwardMostMove = backwardMostMove = 0;
10611 MarkTargetSquares(1);
10612 InitPosition(redraw);
10613 for (i = 0; i < MAX_MOVES; i++) {
10614 if (commentList[i] != NULL) {
10615 free(commentList[i]);
10616 commentList[i] = NULL;
10620 timeRemaining[0][0] = whiteTimeRemaining;
10621 timeRemaining[1][0] = blackTimeRemaining;
10623 if (first.pr == NoProc) {
10624 StartChessProgram(&first);
10627 InitChessProgram(&first, startedFromSetupPosition);
10630 DisplayMessage("", "");
10631 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10632 lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
10636 AutoPlayGameLoop ()
10639 if (!AutoPlayOneMove())
10641 if (matchMode || appData.timeDelay == 0)
10643 if (appData.timeDelay < 0)
10645 StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
10654 int fromX, fromY, toX, toY;
10656 if (appData.debugMode) {
10657 fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
10660 if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
10663 if (gameMode == AnalyzeFile && currentMove > backwardMostMove) {
10664 pvInfoList[currentMove].depth = programStats.depth;
10665 pvInfoList[currentMove].score = programStats.score;
10666 pvInfoList[currentMove].time = 0;
10667 if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
10670 if (currentMove >= forwardMostMove) {
10671 if(gameMode == AnalyzeFile) { ExitAnalyzeMode(); SendToProgram("force\n", &first); }
10672 // gameMode = EndOfGame;
10673 // ModeHighlight();
10675 /* [AS] Clear current move marker at the end of a game */
10676 /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
10681 toX = moveList[currentMove][2] - AAA;
10682 toY = moveList[currentMove][3] - ONE;
10684 if (moveList[currentMove][1] == '@') {
10685 if (appData.highlightLastMove) {
10686 SetHighlights(-1, -1, toX, toY);
10689 fromX = moveList[currentMove][0] - AAA;
10690 fromY = moveList[currentMove][1] - ONE;
10692 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
10694 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
10696 if (appData.highlightLastMove) {
10697 SetHighlights(fromX, fromY, toX, toY);
10700 DisplayMove(currentMove);
10701 SendMoveToProgram(currentMove++, &first);
10702 DisplayBothClocks();
10703 DrawPosition(FALSE, boards[currentMove]);
10704 // [HGM] PV info: always display, routine tests if empty
10705 DisplayComment(currentMove - 1, commentList[currentMove]);
10711 LoadGameOneMove (ChessMove readAhead)
10713 int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
10714 char promoChar = NULLCHAR;
10715 ChessMove moveType;
10716 char move[MSG_SIZ];
10719 if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
10720 gameMode != AnalyzeMode && gameMode != Training) {
10725 yyboardindex = forwardMostMove;
10726 if (readAhead != EndOfFile) {
10727 moveType = readAhead;
10729 if (gameFileFP == NULL)
10731 moveType = (ChessMove) Myylex();
10735 switch (moveType) {
10737 if (appData.debugMode)
10738 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10741 /* append the comment but don't display it */
10742 AppendComment(currentMove, p, FALSE);
10745 case WhiteCapturesEnPassant:
10746 case BlackCapturesEnPassant:
10747 case WhitePromotion:
10748 case BlackPromotion:
10749 case WhiteNonPromotion:
10750 case BlackNonPromotion:
10752 case WhiteKingSideCastle:
10753 case WhiteQueenSideCastle:
10754 case BlackKingSideCastle:
10755 case BlackQueenSideCastle:
10756 case WhiteKingSideCastleWild:
10757 case WhiteQueenSideCastleWild:
10758 case BlackKingSideCastleWild:
10759 case BlackQueenSideCastleWild:
10761 case WhiteHSideCastleFR:
10762 case WhiteASideCastleFR:
10763 case BlackHSideCastleFR:
10764 case BlackASideCastleFR:
10766 if (appData.debugMode)
10767 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
10768 fromX = currentMoveString[0] - AAA;
10769 fromY = currentMoveString[1] - ONE;
10770 toX = currentMoveString[2] - AAA;
10771 toY = currentMoveString[3] - ONE;
10772 promoChar = currentMoveString[4];
10777 if (appData.debugMode)
10778 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
10779 fromX = moveType == WhiteDrop ?
10780 (int) CharToPiece(ToUpper(currentMoveString[0])) :
10781 (int) CharToPiece(ToLower(currentMoveString[0]));
10783 toX = currentMoveString[2] - AAA;
10784 toY = currentMoveString[3] - ONE;
10790 case GameUnfinished:
10791 if (appData.debugMode)
10792 fprintf(debugFP, "Parsed game end: %s\n", yy_text);
10793 p = strchr(yy_text, '{');
10794 if (p == NULL) p = strchr(yy_text, '(');
10797 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
10799 q = strchr(p, *p == '{' ? '}' : ')');
10800 if (q != NULL) *q = NULLCHAR;
10803 while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
10804 GameEnds(moveType, p, GE_FILE);
10806 if (cmailMsgLoaded) {
10808 flipView = WhiteOnMove(currentMove);
10809 if (moveType == GameUnfinished) flipView = !flipView;
10810 if (appData.debugMode)
10811 fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
10816 if (appData.debugMode)
10817 fprintf(debugFP, "Parser hit end of file\n");
10818 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
10824 if (WhiteOnMove(currentMove)) {
10825 GameEnds(BlackWins, "Black mates", GE_FILE);
10827 GameEnds(WhiteWins, "White mates", GE_FILE);
10831 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
10837 case MoveNumberOne:
10838 if (lastLoadGameStart == GNUChessGame) {
10839 /* GNUChessGames have numbers, but they aren't move numbers */
10840 if (appData.debugMode)
10841 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
10842 yy_text, (int) moveType);
10843 return LoadGameOneMove(EndOfFile); /* tail recursion */
10845 /* else fall thru */
10850 /* Reached start of next game in file */
10851 if (appData.debugMode)
10852 fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
10853 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
10859 if (WhiteOnMove(currentMove)) {
10860 GameEnds(BlackWins, "Black mates", GE_FILE);
10862 GameEnds(WhiteWins, "White mates", GE_FILE);
10866 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
10872 case PositionDiagram: /* should not happen; ignore */
10873 case ElapsedTime: /* ignore */
10874 case NAG: /* ignore */
10875 if (appData.debugMode)
10876 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
10877 yy_text, (int) moveType);
10878 return LoadGameOneMove(EndOfFile); /* tail recursion */
10881 if (appData.testLegality) {
10882 if (appData.debugMode)
10883 fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
10884 snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
10885 (forwardMostMove / 2) + 1,
10886 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10887 DisplayError(move, 0);
10890 if (appData.debugMode)
10891 fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
10892 yy_text, currentMoveString);
10893 fromX = currentMoveString[0] - AAA;
10894 fromY = currentMoveString[1] - ONE;
10895 toX = currentMoveString[2] - AAA;
10896 toY = currentMoveString[3] - ONE;
10897 promoChar = currentMoveString[4];
10901 case AmbiguousMove:
10902 if (appData.debugMode)
10903 fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
10904 snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
10905 (forwardMostMove / 2) + 1,
10906 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10907 DisplayError(move, 0);
10912 case ImpossibleMove:
10913 if (appData.debugMode)
10914 fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
10915 snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
10916 (forwardMostMove / 2) + 1,
10917 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10918 DisplayError(move, 0);
10924 if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
10925 DrawPosition(FALSE, boards[currentMove]);
10926 DisplayBothClocks();
10927 if (!appData.matchMode) // [HGM] PV info: routine tests if empty
10928 DisplayComment(currentMove - 1, commentList[currentMove]);
10930 (void) StopLoadGameTimer();
10932 cmailOldMove = forwardMostMove;
10935 /* currentMoveString is set as a side-effect of yylex */
10937 thinkOutput[0] = NULLCHAR;
10938 MakeMove(fromX, fromY, toX, toY, promoChar);
10939 currentMove = forwardMostMove;
10944 /* Load the nth game from the given file */
10946 LoadGameFromFile (char *filename, int n, char *title, int useList)
10951 if (strcmp(filename, "-") == 0) {
10955 f = fopen(filename, "rb");
10957 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10958 DisplayError(buf, errno);
10962 if (fseek(f, 0, 0) == -1) {
10963 /* f is not seekable; probably a pipe */
10966 if (useList && n == 0) {
10967 int error = GameListBuild(f);
10969 DisplayError(_("Cannot build game list"), error);
10970 } else if (!ListEmpty(&gameList) &&
10971 ((ListGame *) gameList.tailPred)->number > 1) {
10972 GameListPopUp(f, title);
10979 return LoadGame(f, n, title, FALSE);
10984 MakeRegisteredMove ()
10986 int fromX, fromY, toX, toY;
10988 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
10989 switch (cmailMoveType[lastLoadGameNumber - 1]) {
10992 if (appData.debugMode)
10993 fprintf(debugFP, "Restoring %s for game %d\n",
10994 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
10996 thinkOutput[0] = NULLCHAR;
10997 safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
10998 fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
10999 fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
11000 toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
11001 toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
11002 promoChar = cmailMove[lastLoadGameNumber - 1][4];
11003 MakeMove(fromX, fromY, toX, toY, promoChar);
11004 ShowMove(fromX, fromY, toX, toY);
11006 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11013 if (WhiteOnMove(currentMove)) {
11014 GameEnds(BlackWins, "Black mates", GE_PLAYER);
11016 GameEnds(WhiteWins, "White mates", GE_PLAYER);
11021 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
11028 if (WhiteOnMove(currentMove)) {
11029 GameEnds(BlackWins, "White resigns", GE_PLAYER);
11031 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11036 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11047 /* Wrapper around LoadGame for use when a Cmail message is loaded */
11049 CmailLoadGame (FILE *f, int gameNumber, char *title, int useList)
11053 if (gameNumber > nCmailGames) {
11054 DisplayError(_("No more games in this message"), 0);
11057 if (f == lastLoadGameFP) {
11058 int offset = gameNumber - lastLoadGameNumber;
11060 cmailMsg[0] = NULLCHAR;
11061 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11062 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
11063 nCmailMovesRegistered--;
11065 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11066 if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
11067 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
11070 if (! RegisterMove()) return FALSE;
11074 retVal = LoadGame(f, gameNumber, title, useList);
11076 /* Make move registered during previous look at this game, if any */
11077 MakeRegisteredMove();
11079 if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
11080 commentList[currentMove]
11081 = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
11082 DisplayComment(currentMove - 1, commentList[currentMove]);
11088 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
11090 ReloadGame (int offset)
11092 int gameNumber = lastLoadGameNumber + offset;
11093 if (lastLoadGameFP == NULL) {
11094 DisplayError(_("No game has been loaded yet"), 0);
11097 if (gameNumber <= 0) {
11098 DisplayError(_("Can't back up any further"), 0);
11101 if (cmailMsgLoaded) {
11102 return CmailLoadGame(lastLoadGameFP, gameNumber,
11103 lastLoadGameTitle, lastLoadGameUseList);
11105 return LoadGame(lastLoadGameFP, gameNumber,
11106 lastLoadGameTitle, lastLoadGameUseList);
11110 int keys[EmptySquare+1];
11113 PositionMatches (Board b1, Board b2)
11116 switch(appData.searchMode) {
11117 case 1: return CompareWithRights(b1, b2);
11119 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11120 if(b2[r][f] != EmptySquare && b1[r][f] != b2[r][f]) return FALSE;
11124 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11125 if((b2[r][f] == WhitePawn || b2[r][f] == BlackPawn) && b1[r][f] != b2[r][f]) return FALSE;
11126 sum += keys[b1[r][f]] - keys[b2[r][f]];
11130 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11131 sum += keys[b1[r][f]] - keys[b2[r][f]];
11143 int pieceList[256], quickBoard[256];
11144 ChessSquare pieceType[256] = { EmptySquare };
11145 Board soughtBoard, reverseBoard, flipBoard, rotateBoard;
11146 int counts[EmptySquare], minSought[EmptySquare], minReverse[EmptySquare], maxSought[EmptySquare], maxReverse[EmptySquare];
11147 int soughtTotal, turn;
11148 Boolean epOK, flipSearch;
11151 unsigned char piece, to;
11154 #define DSIZE (250000)
11156 Move initialSpace[DSIZE+1000]; // gamble on that game will not be more than 500 moves
11157 Move *moveDatabase = initialSpace;
11158 unsigned int movePtr, dataSize = DSIZE;
11161 MakePieceList (Board board, int *counts)
11163 int r, f, n=Q_PROMO, total=0;
11164 for(r=0;r<EmptySquare;r++) counts[r] = 0; // piece-type counts
11165 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11166 int sq = f + (r<<4);
11167 if(board[r][f] == EmptySquare) quickBoard[sq] = 0; else {
11168 quickBoard[sq] = ++n;
11170 pieceType[n] = board[r][f];
11171 counts[board[r][f]]++;
11172 if(board[r][f] == WhiteKing) pieceList[1] = n; else
11173 if(board[r][f] == BlackKing) pieceList[2] = n; // remember which are Kings, for castling
11177 epOK = gameInfo.variant != VariantXiangqi && gameInfo.variant != VariantBerolina;
11182 PackMove (int fromX, int fromY, int toX, int toY, ChessSquare promoPiece)
11184 int sq = fromX + (fromY<<4);
11185 int piece = quickBoard[sq];
11186 quickBoard[sq] = 0;
11187 moveDatabase[movePtr].to = pieceList[piece] = sq = toX + (toY<<4);
11188 if(piece == pieceList[1] && fromY == toY && (toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
11189 int from = toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT;
11190 moveDatabase[movePtr++].piece = Q_WCASTL;
11191 quickBoard[sq] = piece;
11192 piece = quickBoard[from]; quickBoard[from] = 0;
11193 moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
11195 if(piece == pieceList[2] && fromY == toY && (toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
11196 int from = (toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT) + (BOARD_HEIGHT-1 <<4);
11197 moveDatabase[movePtr++].piece = Q_BCASTL;
11198 quickBoard[sq] = piece;
11199 piece = quickBoard[from]; quickBoard[from] = 0;
11200 moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
11202 if(epOK && (pieceType[piece] == WhitePawn || pieceType[piece] == BlackPawn) && fromX != toX && quickBoard[sq] == 0) {
11203 quickBoard[(fromY<<4)+toX] = 0;
11204 moveDatabase[movePtr].piece = Q_EP;
11205 moveDatabase[movePtr++].to = (fromY<<4)+toX;
11206 moveDatabase[movePtr].to = sq;
11208 if(promoPiece != pieceType[piece]) {
11209 moveDatabase[movePtr++].piece = Q_PROMO;
11210 moveDatabase[movePtr].to = pieceType[piece] = (int) promoPiece;
11212 moveDatabase[movePtr].piece = piece;
11213 quickBoard[sq] = piece;
11218 PackGame (Board board)
11220 Move *newSpace = NULL;
11221 moveDatabase[movePtr].piece = 0; // terminate previous game
11222 if(movePtr > dataSize) {
11223 if(appData.debugMode) fprintf(debugFP, "move-cache overflow, enlarge to %d MB\n", dataSize/128);
11224 dataSize *= 8; // increase size by factor 8 (512KB -> 4MB -> 32MB -> 256MB -> 2GB)
11225 if(dataSize) newSpace = (Move*) calloc(dataSize + 1000, sizeof(Move));
11228 Move *p = moveDatabase, *q = newSpace;
11229 for(i=0; i<movePtr; i++) *q++ = *p++; // copy to newly allocated space
11230 if(dataSize > 8*DSIZE) free(moveDatabase); // and free old space (if it was allocated)
11231 moveDatabase = newSpace;
11232 } else { // calloc failed, we must be out of memory. Too bad...
11233 dataSize = 0; // prevent calloc events for all subsequent games
11234 return 0; // and signal this one isn't cached
11238 MakePieceList(board, counts);
11243 QuickCompare (Board board, int *minCounts, int *maxCounts)
11244 { // compare according to search mode
11246 switch(appData.searchMode)
11248 case 1: // exact position match
11249 if(!(turn & board[EP_STATUS-1])) return FALSE; // wrong side to move
11250 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11251 if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11254 case 2: // can have extra material on empty squares
11255 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11256 if(board[r][f] == EmptySquare) continue;
11257 if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11260 case 3: // material with exact Pawn structure
11261 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11262 if(board[r][f] != WhitePawn && board[r][f] != BlackPawn) continue;
11263 if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11264 } // fall through to material comparison
11265 case 4: // exact material
11266 for(r=0; r<EmptySquare; r++) if(counts[r] != maxCounts[r]) return FALSE;
11268 case 6: // material range with given imbalance
11269 for(r=0; r<BlackPawn; r++) if(counts[r] - minCounts[r] != counts[r+BlackPawn] - minCounts[r+BlackPawn]) return FALSE;
11270 // fall through to range comparison
11271 case 5: // material range
11272 for(r=0; r<EmptySquare; r++) if(counts[r] < minCounts[r] || counts[r] > maxCounts[r]) return FALSE;
11278 QuickScan (Board board, Move *move)
11279 { // reconstruct game,and compare all positions in it
11280 int cnt=0, stretch=0, total = MakePieceList(board, counts);
11282 int piece = move->piece;
11283 int to = move->to, from = pieceList[piece];
11284 if(piece <= Q_PROMO) { // special moves encoded by otherwise invalid piece numbers 1-4
11285 if(!piece) return -1;
11286 if(piece == Q_PROMO) { // promotion, encoded as (Q_PROMO, to) + (piece, promoType)
11287 piece = (++move)->piece;
11288 from = pieceList[piece];
11289 counts[pieceType[piece]]--;
11290 pieceType[piece] = (ChessSquare) move->to;
11291 counts[move->to]++;
11292 } else if(piece == Q_EP) { // e.p. capture, encoded as (Q_EP, ep-sqr) + (piece, to)
11293 counts[pieceType[quickBoard[to]]]--;
11294 quickBoard[to] = 0; total--;
11297 } else if(piece <= Q_BCASTL) { // castling, encoded as (Q_XCASTL, king-to) + (rook, rook-to)
11298 piece = pieceList[piece]; // first two elements of pieceList contain King numbers
11299 from = pieceList[piece]; // so this must be King
11300 quickBoard[from] = 0;
11301 quickBoard[to] = piece;
11302 pieceList[piece] = to;
11307 if(appData.searchMode > 2) counts[pieceType[quickBoard[to]]]--; // account capture
11308 if((total -= (quickBoard[to] != 0)) < soughtTotal) return -1; // piece count dropped below what we search for
11309 quickBoard[from] = 0;
11310 quickBoard[to] = piece;
11311 pieceList[piece] = to;
11313 if(QuickCompare(soughtBoard, minSought, maxSought) ||
11314 appData.ignoreColors && QuickCompare(reverseBoard, minReverse, maxReverse) ||
11315 flipSearch && (QuickCompare(flipBoard, minSought, maxSought) ||
11316 appData.ignoreColors && QuickCompare(rotateBoard, minReverse, maxReverse))
11318 static int lastCounts[EmptySquare+1];
11320 if(stretch) for(i=0; i<EmptySquare; i++) if(lastCounts[i] != counts[i]) { stretch = 0; break; } // reset if material changes
11321 if(stretch++ == 0) for(i=0; i<EmptySquare; i++) lastCounts[i] = counts[i]; // remember actual material
11322 } else stretch = 0;
11323 if(stretch && (appData.searchMode == 1 || stretch >= appData.stretch)) return cnt + 1 - stretch;
11332 flipSearch = FALSE;
11333 CopyBoard(soughtBoard, boards[currentMove]);
11334 soughtTotal = MakePieceList(soughtBoard, maxSought);
11335 soughtBoard[EP_STATUS-1] = (currentMove & 1) + 1;
11336 if(currentMove == 0 && gameMode == EditPosition) soughtBoard[EP_STATUS-1] = blackPlaysFirst + 1; // (!)
11337 CopyBoard(reverseBoard, boards[currentMove]);
11338 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11339 int piece = boards[currentMove][BOARD_HEIGHT-1-r][f];
11340 if(piece < BlackPawn) piece += BlackPawn; else if(piece < EmptySquare) piece -= BlackPawn; // color-flip
11341 reverseBoard[r][f] = piece;
11343 reverseBoard[EP_STATUS-1] = soughtBoard[EP_STATUS-1] ^ 3;
11344 for(r=0; r<6; r++) reverseBoard[CASTLING][r] = boards[currentMove][CASTLING][(r+3)%6];
11345 if(appData.findMirror && appData.searchMode <= 3 && (!nrCastlingRights
11346 || (boards[currentMove][CASTLING][2] == NoRights ||
11347 boards[currentMove][CASTLING][0] == NoRights && boards[currentMove][CASTLING][1] == NoRights )
11348 && (boards[currentMove][CASTLING][5] == NoRights ||
11349 boards[currentMove][CASTLING][3] == NoRights && boards[currentMove][CASTLING][4] == NoRights ) )
11352 CopyBoard(flipBoard, soughtBoard);
11353 CopyBoard(rotateBoard, reverseBoard);
11354 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11355 flipBoard[r][f] = soughtBoard[r][BOARD_WIDTH-1-f];
11356 rotateBoard[r][f] = reverseBoard[r][BOARD_WIDTH-1-f];
11359 for(r=0; r<BlackPawn; r++) maxReverse[r] = maxSought[r+BlackPawn], maxReverse[r+BlackPawn] = maxSought[r];
11360 if(appData.searchMode >= 5) {
11361 for(r=BOARD_HEIGHT/2; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) soughtBoard[r][f] = EmptySquare;
11362 MakePieceList(soughtBoard, minSought);
11363 for(r=0; r<BlackPawn; r++) minReverse[r] = minSought[r+BlackPawn], minReverse[r+BlackPawn] = minSought[r];
11365 if(gameInfo.variant == VariantCrazyhouse || gameInfo.variant == VariantShogi || gameInfo.variant == VariantBughouse)
11366 soughtTotal = 0; // in drop games nr of pieces does not fall monotonously
11369 GameInfo dummyInfo;
11372 GameContainsPosition (FILE *f, ListGame *lg)
11374 int next, btm=0, plyNr=0, scratch=forwardMostMove+2&~1;
11375 int fromX, fromY, toX, toY;
11377 static int initDone=FALSE;
11379 // weed out games based on numerical tag comparison
11380 if(lg->gameInfo.variant != gameInfo.variant) return -1; // wrong variant
11381 if(appData.eloThreshold1 && (lg->gameInfo.whiteRating < appData.eloThreshold1 && lg->gameInfo.blackRating < appData.eloThreshold1)) return -1;
11382 if(appData.eloThreshold2 && (lg->gameInfo.whiteRating < appData.eloThreshold2 || lg->gameInfo.blackRating < appData.eloThreshold2)) return -1;
11383 if(appData.dateThreshold && (!lg->gameInfo.date || atoi(lg->gameInfo.date) < appData.dateThreshold)) return -1;
11385 for(next = WhitePawn; next<EmptySquare; next++) keys[next] = random()>>8 ^ random()<<6 ^random()<<20;
11388 if(lg->gameInfo.fen) ParseFEN(boards[scratch], &btm, lg->gameInfo.fen);
11389 else CopyBoard(boards[scratch], initialPosition); // default start position
11392 if((next = QuickScan( boards[scratch], &moveDatabase[lg->moves] )) < 0) return -1; // quick scan rules out it is there
11393 if(appData.searchMode >= 4) return next; // for material searches, trust QuickScan.
11396 if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
11397 fseek(f, lg->offset, 0);
11400 yyboardindex = scratch;
11401 quickFlag = plyNr+1;
11406 if(plyNr) return -1; // after we have seen moves, any tags will be start of next game
11412 if(plyNr) return -1; // after we have seen moves, this is for new game
11415 case AmbiguousMove: // we cannot reconstruct the game beyond these two
11416 case ImpossibleMove:
11417 case WhiteWins: // game ends here with these four
11420 case GameUnfinished:
11424 if(appData.testLegality) return -1;
11425 case WhiteCapturesEnPassant:
11426 case BlackCapturesEnPassant:
11427 case WhitePromotion:
11428 case BlackPromotion:
11429 case WhiteNonPromotion:
11430 case BlackNonPromotion:
11432 case WhiteKingSideCastle:
11433 case WhiteQueenSideCastle:
11434 case BlackKingSideCastle:
11435 case BlackQueenSideCastle:
11436 case WhiteKingSideCastleWild:
11437 case WhiteQueenSideCastleWild:
11438 case BlackKingSideCastleWild:
11439 case BlackQueenSideCastleWild:
11440 case WhiteHSideCastleFR:
11441 case WhiteASideCastleFR:
11442 case BlackHSideCastleFR:
11443 case BlackASideCastleFR:
11444 fromX = currentMoveString[0] - AAA;
11445 fromY = currentMoveString[1] - ONE;
11446 toX = currentMoveString[2] - AAA;
11447 toY = currentMoveString[3] - ONE;
11448 promoChar = currentMoveString[4];
11452 fromX = next == WhiteDrop ?
11453 (int) CharToPiece(ToUpper(currentMoveString[0])) :
11454 (int) CharToPiece(ToLower(currentMoveString[0]));
11456 toX = currentMoveString[2] - AAA;
11457 toY = currentMoveString[3] - ONE;
11461 // Move encountered; peform it. We need to shuttle between two boards, as even/odd index determines side to move
11463 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[scratch]);
11464 if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
11465 if(appData.ignoreColors && PositionMatches(boards[scratch], reverseBoard)) return plyNr;
11466 if(appData.findMirror) {
11467 if(PositionMatches(boards[scratch], flipBoard)) return plyNr;
11468 if(appData.ignoreColors && PositionMatches(boards[scratch], rotateBoard)) return plyNr;
11473 /* Load the nth game from open file f */
11475 LoadGame (FILE *f, int gameNumber, char *title, int useList)
11479 int gn = gameNumber;
11480 ListGame *lg = NULL;
11481 int numPGNTags = 0;
11483 GameMode oldGameMode;
11484 VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
11486 if (appData.debugMode)
11487 fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
11489 if (gameMode == Training )
11490 SetTrainingModeOff();
11492 oldGameMode = gameMode;
11493 if (gameMode != BeginningOfGame) {
11494 Reset(FALSE, TRUE);
11498 if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
11499 fclose(lastLoadGameFP);
11503 lg = (ListGame *) ListElem(&gameList, gameNumber-1);
11506 fseek(f, lg->offset, 0);
11507 GameListHighlight(gameNumber);
11508 pos = lg->position;
11512 DisplayError(_("Game number out of range"), 0);
11517 if (fseek(f, 0, 0) == -1) {
11518 if (f == lastLoadGameFP ?
11519 gameNumber == lastLoadGameNumber + 1 :
11523 DisplayError(_("Can't seek on game file"), 0);
11528 lastLoadGameFP = f;
11529 lastLoadGameNumber = gameNumber;
11530 safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
11531 lastLoadGameUseList = useList;
11535 if (lg && lg->gameInfo.white && lg->gameInfo.black) {
11536 snprintf(buf, sizeof(buf), "%s %s %s", lg->gameInfo.white, _("vs."),
11537 lg->gameInfo.black);
11539 } else if (*title != NULLCHAR) {
11540 if (gameNumber > 1) {
11541 snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
11544 DisplayTitle(title);
11548 if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
11549 gameMode = PlayFromGameFile;
11553 currentMove = forwardMostMove = backwardMostMove = 0;
11554 CopyBoard(boards[0], initialPosition);
11558 * Skip the first gn-1 games in the file.
11559 * Also skip over anything that precedes an identifiable
11560 * start of game marker, to avoid being confused by
11561 * garbage at the start of the file. Currently
11562 * recognized start of game markers are the move number "1",
11563 * the pattern "gnuchess .* game", the pattern
11564 * "^[#;%] [^ ]* game file", and a PGN tag block.
11565 * A game that starts with one of the latter two patterns
11566 * will also have a move number 1, possibly
11567 * following a position diagram.
11568 * 5-4-02: Let's try being more lenient and allowing a game to
11569 * start with an unnumbered move. Does that break anything?
11571 cm = lastLoadGameStart = EndOfFile;
11573 yyboardindex = forwardMostMove;
11574 cm = (ChessMove) Myylex();
11577 if (cmailMsgLoaded) {
11578 nCmailGames = CMAIL_MAX_GAMES - gn;
11581 DisplayError(_("Game not found in file"), 0);
11588 lastLoadGameStart = cm;
11591 case MoveNumberOne:
11592 switch (lastLoadGameStart) {
11597 case MoveNumberOne:
11599 gn--; /* count this game */
11600 lastLoadGameStart = cm;
11609 switch (lastLoadGameStart) {
11612 case MoveNumberOne:
11614 gn--; /* count this game */
11615 lastLoadGameStart = cm;
11618 lastLoadGameStart = cm; /* game counted already */
11626 yyboardindex = forwardMostMove;
11627 cm = (ChessMove) Myylex();
11628 } while (cm == PGNTag || cm == Comment);
11635 if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
11636 if ( cmailResult[CMAIL_MAX_GAMES - gn - 1]
11637 != CMAIL_OLD_RESULT) {
11639 cmailResult[ CMAIL_MAX_GAMES
11640 - gn - 1] = CMAIL_OLD_RESULT;
11646 /* Only a NormalMove can be at the start of a game
11647 * without a position diagram. */
11648 if (lastLoadGameStart == EndOfFile ) {
11650 lastLoadGameStart = MoveNumberOne;
11659 if (appData.debugMode)
11660 fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
11662 if (cm == XBoardGame) {
11663 /* Skip any header junk before position diagram and/or move 1 */
11665 yyboardindex = forwardMostMove;
11666 cm = (ChessMove) Myylex();
11668 if (cm == EndOfFile ||
11669 cm == GNUChessGame || cm == XBoardGame) {
11670 /* Empty game; pretend end-of-file and handle later */
11675 if (cm == MoveNumberOne || cm == PositionDiagram ||
11676 cm == PGNTag || cm == Comment)
11679 } else if (cm == GNUChessGame) {
11680 if (gameInfo.event != NULL) {
11681 free(gameInfo.event);
11683 gameInfo.event = StrSave(yy_text);
11686 startedFromSetupPosition = FALSE;
11687 while (cm == PGNTag) {
11688 if (appData.debugMode)
11689 fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
11690 err = ParsePGNTag(yy_text, &gameInfo);
11691 if (!err) numPGNTags++;
11693 /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
11694 if(gameInfo.variant != oldVariant) {
11695 startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
11696 ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
11697 InitPosition(TRUE);
11698 oldVariant = gameInfo.variant;
11699 if (appData.debugMode)
11700 fprintf(debugFP, "New variant %d\n", (int) oldVariant);
11704 if (gameInfo.fen != NULL) {
11705 Board initial_position;
11706 startedFromSetupPosition = TRUE;
11707 if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
11709 DisplayError(_("Bad FEN position in file"), 0);
11712 CopyBoard(boards[0], initial_position);
11713 if (blackPlaysFirst) {
11714 currentMove = forwardMostMove = backwardMostMove = 1;
11715 CopyBoard(boards[1], initial_position);
11716 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
11717 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
11718 timeRemaining[0][1] = whiteTimeRemaining;
11719 timeRemaining[1][1] = blackTimeRemaining;
11720 if (commentList[0] != NULL) {
11721 commentList[1] = commentList[0];
11722 commentList[0] = NULL;
11725 currentMove = forwardMostMove = backwardMostMove = 0;
11727 /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
11729 initialRulePlies = FENrulePlies;
11730 for( i=0; i< nrCastlingRights; i++ )
11731 initialRights[i] = initial_position[CASTLING][i];
11733 yyboardindex = forwardMostMove;
11734 free(gameInfo.fen);
11735 gameInfo.fen = NULL;
11738 yyboardindex = forwardMostMove;
11739 cm = (ChessMove) Myylex();
11741 /* Handle comments interspersed among the tags */
11742 while (cm == Comment) {
11744 if (appData.debugMode)
11745 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11747 AppendComment(currentMove, p, FALSE);
11748 yyboardindex = forwardMostMove;
11749 cm = (ChessMove) Myylex();
11753 /* don't rely on existence of Event tag since if game was
11754 * pasted from clipboard the Event tag may not exist
11756 if (numPGNTags > 0){
11758 if (gameInfo.variant == VariantNormal) {
11759 VariantClass v = StringToVariant(gameInfo.event);
11760 // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
11761 if(v < VariantShogi) gameInfo.variant = v;
11764 if( appData.autoDisplayTags ) {
11765 tags = PGNTags(&gameInfo);
11766 TagsPopUp(tags, CmailMsg());
11771 /* Make something up, but don't display it now */
11776 if (cm == PositionDiagram) {
11779 Board initial_position;
11781 if (appData.debugMode)
11782 fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
11784 if (!startedFromSetupPosition) {
11786 for (i = BOARD_HEIGHT - 1; i >= 0; i--)
11787 for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
11798 initial_position[i][j++] = CharToPiece(*p);
11801 while (*p == ' ' || *p == '\t' ||
11802 *p == '\n' || *p == '\r') p++;
11804 if (strncmp(p, "black", strlen("black"))==0)
11805 blackPlaysFirst = TRUE;
11807 blackPlaysFirst = FALSE;
11808 startedFromSetupPosition = TRUE;
11810 CopyBoard(boards[0], initial_position);
11811 if (blackPlaysFirst) {
11812 currentMove = forwardMostMove = backwardMostMove = 1;
11813 CopyBoard(boards[1], initial_position);
11814 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
11815 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
11816 timeRemaining[0][1] = whiteTimeRemaining;
11817 timeRemaining[1][1] = blackTimeRemaining;
11818 if (commentList[0] != NULL) {
11819 commentList[1] = commentList[0];
11820 commentList[0] = NULL;
11823 currentMove = forwardMostMove = backwardMostMove = 0;
11826 yyboardindex = forwardMostMove;
11827 cm = (ChessMove) Myylex();
11830 if (first.pr == NoProc) {
11831 StartChessProgram(&first);
11833 InitChessProgram(&first, FALSE);
11834 SendToProgram("force\n", &first);
11835 if (startedFromSetupPosition) {
11836 SendBoard(&first, forwardMostMove);
11837 if (appData.debugMode) {
11838 fprintf(debugFP, "Load Game\n");
11840 DisplayBothClocks();
11843 /* [HGM] server: flag to write setup moves in broadcast file as one */
11844 loadFlag = appData.suppressLoadMoves;
11846 while (cm == Comment) {
11848 if (appData.debugMode)
11849 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11851 AppendComment(currentMove, p, FALSE);
11852 yyboardindex = forwardMostMove;
11853 cm = (ChessMove) Myylex();
11856 if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
11857 cm == WhiteWins || cm == BlackWins ||
11858 cm == GameIsDrawn || cm == GameUnfinished) {
11859 DisplayMessage("", _("No moves in game"));
11860 if (cmailMsgLoaded) {
11861 if (appData.debugMode)
11862 fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
11866 DrawPosition(FALSE, boards[currentMove]);
11867 DisplayBothClocks();
11868 gameMode = EditGame;
11875 // [HGM] PV info: routine tests if comment empty
11876 if (!matchMode && (pausing || appData.timeDelay != 0)) {
11877 DisplayComment(currentMove - 1, commentList[currentMove]);
11879 if (!matchMode && appData.timeDelay != 0)
11880 DrawPosition(FALSE, boards[currentMove]);
11882 if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
11883 programStats.ok_to_send = 1;
11886 /* if the first token after the PGN tags is a move
11887 * and not move number 1, retrieve it from the parser
11889 if (cm != MoveNumberOne)
11890 LoadGameOneMove(cm);
11892 /* load the remaining moves from the file */
11893 while (LoadGameOneMove(EndOfFile)) {
11894 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11895 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11898 /* rewind to the start of the game */
11899 currentMove = backwardMostMove;
11901 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11903 if (oldGameMode == AnalyzeFile ||
11904 oldGameMode == AnalyzeMode) {
11905 AnalyzeFileEvent();
11908 if (!matchMode && pos >= 0) {
11909 ToNrEvent(pos); // [HGM] no autoplay if selected on position
11911 if (matchMode || appData.timeDelay == 0) {
11913 } else if (appData.timeDelay > 0) {
11914 AutoPlayGameLoop();
11917 if (appData.debugMode)
11918 fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
11920 loadFlag = 0; /* [HGM] true game starts */
11924 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
11926 ReloadPosition (int offset)
11928 int positionNumber = lastLoadPositionNumber + offset;
11929 if (lastLoadPositionFP == NULL) {
11930 DisplayError(_("No position has been loaded yet"), 0);
11933 if (positionNumber <= 0) {
11934 DisplayError(_("Can't back up any further"), 0);
11937 return LoadPosition(lastLoadPositionFP, positionNumber,
11938 lastLoadPositionTitle);
11941 /* Load the nth position from the given file */
11943 LoadPositionFromFile (char *filename, int n, char *title)
11948 if (strcmp(filename, "-") == 0) {
11949 return LoadPosition(stdin, n, "stdin");
11951 f = fopen(filename, "rb");
11953 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
11954 DisplayError(buf, errno);
11957 return LoadPosition(f, n, title);
11962 /* Load the nth position from the given open file, and close it */
11964 LoadPosition (FILE *f, int positionNumber, char *title)
11966 char *p, line[MSG_SIZ];
11967 Board initial_position;
11968 int i, j, fenMode, pn;
11970 if (gameMode == Training )
11971 SetTrainingModeOff();
11973 if (gameMode != BeginningOfGame) {
11974 Reset(FALSE, TRUE);
11976 if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
11977 fclose(lastLoadPositionFP);
11979 if (positionNumber == 0) positionNumber = 1;
11980 lastLoadPositionFP = f;
11981 lastLoadPositionNumber = positionNumber;
11982 safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
11983 if (first.pr == NoProc && !appData.noChessProgram) {
11984 StartChessProgram(&first);
11985 InitChessProgram(&first, FALSE);
11987 pn = positionNumber;
11988 if (positionNumber < 0) {
11989 /* Negative position number means to seek to that byte offset */
11990 if (fseek(f, -positionNumber, 0) == -1) {
11991 DisplayError(_("Can't seek on position file"), 0);
11996 if (fseek(f, 0, 0) == -1) {
11997 if (f == lastLoadPositionFP ?
11998 positionNumber == lastLoadPositionNumber + 1 :
11999 positionNumber == 1) {
12002 DisplayError(_("Can't seek on position file"), 0);
12007 /* See if this file is FEN or old-style xboard */
12008 if (fgets(line, MSG_SIZ, f) == NULL) {
12009 DisplayError(_("Position not found in file"), 0);
12012 // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
12013 fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
12016 if (fenMode || line[0] == '#') pn--;
12018 /* skip positions before number pn */
12019 if (fgets(line, MSG_SIZ, f) == NULL) {
12021 DisplayError(_("Position not found in file"), 0);
12024 if (fenMode || line[0] == '#') pn--;
12029 if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
12030 DisplayError(_("Bad FEN position in file"), 0);
12034 (void) fgets(line, MSG_SIZ, f);
12035 (void) fgets(line, MSG_SIZ, f);
12037 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
12038 (void) fgets(line, MSG_SIZ, f);
12039 for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
12042 initial_position[i][j++] = CharToPiece(*p);
12046 blackPlaysFirst = FALSE;
12048 (void) fgets(line, MSG_SIZ, f);
12049 if (strncmp(line, "black", strlen("black"))==0)
12050 blackPlaysFirst = TRUE;
12053 startedFromSetupPosition = TRUE;
12055 CopyBoard(boards[0], initial_position);
12056 if (blackPlaysFirst) {
12057 currentMove = forwardMostMove = backwardMostMove = 1;
12058 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12059 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12060 CopyBoard(boards[1], initial_position);
12061 DisplayMessage("", _("Black to play"));
12063 currentMove = forwardMostMove = backwardMostMove = 0;
12064 DisplayMessage("", _("White to play"));
12066 initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
12067 if(first.pr != NoProc) { // [HGM] in tourney-mode a position can be loaded before the chess engine is installed
12068 SendToProgram("force\n", &first);
12069 SendBoard(&first, forwardMostMove);
12071 if (appData.debugMode) {
12073 for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
12074 for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
12075 fprintf(debugFP, "Load Position\n");
12078 if (positionNumber > 1) {
12079 snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
12080 DisplayTitle(line);
12082 DisplayTitle(title);
12084 gameMode = EditGame;
12087 timeRemaining[0][1] = whiteTimeRemaining;
12088 timeRemaining[1][1] = blackTimeRemaining;
12089 DrawPosition(FALSE, boards[currentMove]);
12096 CopyPlayerNameIntoFileName (char **dest, char *src)
12098 while (*src != NULLCHAR && *src != ',') {
12103 *(*dest)++ = *src++;
12109 DefaultFileName (char *ext)
12111 static char def[MSG_SIZ];
12114 if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
12116 CopyPlayerNameIntoFileName(&p, gameInfo.white);
12118 CopyPlayerNameIntoFileName(&p, gameInfo.black);
12120 safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
12127 /* Save the current game to the given file */
12129 SaveGameToFile (char *filename, int append)
12133 int result, i, t,tot=0;
12135 if (strcmp(filename, "-") == 0) {
12136 return SaveGame(stdout, 0, NULL);
12138 for(i=0; i<10; i++) { // upto 10 tries
12139 f = fopen(filename, append ? "a" : "w");
12140 if(f && i) fprintf(f, "[Delay \"%d retries, %d msec\"]\n",i,tot);
12141 if(f || errno != 13) break;
12142 DoSleep(t = 5 + random()%11); // wait 5-15 msec
12146 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12147 DisplayError(buf, errno);
12150 safeStrCpy(buf, lastMsg, MSG_SIZ);
12151 DisplayMessage(_("Waiting for access to save file"), "");
12152 flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing
12153 DisplayMessage(_("Saving game"), "");
12154 if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError(_("Bad Seek"), errno); // better safe than sorry...
12155 result = SaveGame(f, 0, NULL);
12156 DisplayMessage(buf, "");
12163 SavePart (char *str)
12165 static char buf[MSG_SIZ];
12168 p = strchr(str, ' ');
12169 if (p == NULL) return str;
12170 strncpy(buf, str, p - str);
12171 buf[p - str] = NULLCHAR;
12175 #define PGN_MAX_LINE 75
12177 #define PGN_SIDE_WHITE 0
12178 #define PGN_SIDE_BLACK 1
12181 FindFirstMoveOutOfBook (int side)
12185 if( backwardMostMove == 0 && ! startedFromSetupPosition) {
12186 int index = backwardMostMove;
12187 int has_book_hit = 0;
12189 if( (index % 2) != side ) {
12193 while( index < forwardMostMove ) {
12194 /* Check to see if engine is in book */
12195 int depth = pvInfoList[index].depth;
12196 int score = pvInfoList[index].score;
12202 else if( score == 0 && depth == 63 ) {
12203 in_book = 1; /* Zappa */
12205 else if( score == 2 && depth == 99 ) {
12206 in_book = 1; /* Abrok */
12209 has_book_hit += in_book;
12225 GetOutOfBookInfo (char * buf)
12229 int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12231 oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
12232 oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
12236 if( oob[0] >= 0 || oob[1] >= 0 ) {
12237 for( i=0; i<2; i++ ) {
12241 if( i > 0 && oob[0] >= 0 ) {
12242 strcat( buf, " " );
12245 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
12246 sprintf( buf+strlen(buf), "%s%.2f",
12247 pvInfoList[idx].score >= 0 ? "+" : "",
12248 pvInfoList[idx].score / 100.0 );
12254 /* Save game in PGN style and close the file */
12256 SaveGamePGN (FILE *f)
12258 int i, offset, linelen, newblock;
12262 int movelen, numlen, blank;
12263 char move_buffer[100]; /* [AS] Buffer for move+PV info */
12265 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12267 tm = time((time_t *) NULL);
12269 PrintPGNTags(f, &gameInfo);
12271 if (backwardMostMove > 0 || startedFromSetupPosition) {
12272 char *fen = PositionToFEN(backwardMostMove, NULL);
12273 fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
12274 fprintf(f, "\n{--------------\n");
12275 PrintPosition(f, backwardMostMove);
12276 fprintf(f, "--------------}\n");
12280 /* [AS] Out of book annotation */
12281 if( appData.saveOutOfBookInfo ) {
12284 GetOutOfBookInfo( buf );
12286 if( buf[0] != '\0' ) {
12287 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
12294 i = backwardMostMove;
12298 while (i < forwardMostMove) {
12299 /* Print comments preceding this move */
12300 if (commentList[i] != NULL) {
12301 if (linelen > 0) fprintf(f, "\n");
12302 fprintf(f, "%s", commentList[i]);
12307 /* Format move number */
12309 snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
12312 snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
12314 numtext[0] = NULLCHAR;
12316 numlen = strlen(numtext);
12319 /* Print move number */
12320 blank = linelen > 0 && numlen > 0;
12321 if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
12330 fprintf(f, "%s", numtext);
12334 safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
12335 movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
12338 blank = linelen > 0 && movelen > 0;
12339 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
12348 fprintf(f, "%s", move_buffer);
12349 linelen += movelen;
12351 /* [AS] Add PV info if present */
12352 if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
12353 /* [HGM] add time */
12354 char buf[MSG_SIZ]; int seconds;
12356 seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
12362 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
12365 seconds = (seconds + 4)/10; // round to full seconds
12367 snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
12369 snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
12372 snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
12373 pvInfoList[i].score >= 0 ? "+" : "",
12374 pvInfoList[i].score / 100.0,
12375 pvInfoList[i].depth,
12378 movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
12380 /* Print score/depth */
12381 blank = linelen > 0 && movelen > 0;
12382 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
12391 fprintf(f, "%s", move_buffer);
12392 linelen += movelen;
12398 /* Start a new line */
12399 if (linelen > 0) fprintf(f, "\n");
12401 /* Print comments after last move */
12402 if (commentList[i] != NULL) {
12403 fprintf(f, "%s\n", commentList[i]);
12407 if (gameInfo.resultDetails != NULL &&
12408 gameInfo.resultDetails[0] != NULLCHAR) {
12409 fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
12410 PGNResult(gameInfo.result));
12412 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
12416 lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
12420 /* Save game in old style and close the file */
12422 SaveGameOldStyle (FILE *f)
12427 tm = time((time_t *) NULL);
12429 fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
12432 if (backwardMostMove > 0 || startedFromSetupPosition) {
12433 fprintf(f, "\n[--------------\n");
12434 PrintPosition(f, backwardMostMove);
12435 fprintf(f, "--------------]\n");
12440 i = backwardMostMove;
12441 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12443 while (i < forwardMostMove) {
12444 if (commentList[i] != NULL) {
12445 fprintf(f, "[%s]\n", commentList[i]);
12448 if ((i % 2) == 1) {
12449 fprintf(f, "%d. ... %s\n", (i - offset)/2 + 1, parseList[i]);
12452 fprintf(f, "%d. %s ", (i - offset)/2 + 1, parseList[i]);
12454 if (commentList[i] != NULL) {
12458 if (i >= forwardMostMove) {
12462 fprintf(f, "%s\n", parseList[i]);
12467 if (commentList[i] != NULL) {
12468 fprintf(f, "[%s]\n", commentList[i]);
12471 /* This isn't really the old style, but it's close enough */
12472 if (gameInfo.resultDetails != NULL &&
12473 gameInfo.resultDetails[0] != NULLCHAR) {
12474 fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
12475 gameInfo.resultDetails);
12477 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
12484 /* Save the current game to open file f and close the file */
12486 SaveGame (FILE *f, int dummy, char *dummy2)
12488 if (gameMode == EditPosition) EditPositionDone(TRUE);
12489 lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
12490 if (appData.oldSaveStyle)
12491 return SaveGameOldStyle(f);
12493 return SaveGamePGN(f);
12496 /* Save the current position to the given file */
12498 SavePositionToFile (char *filename)
12503 if (strcmp(filename, "-") == 0) {
12504 return SavePosition(stdout, 0, NULL);
12506 f = fopen(filename, "a");
12508 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12509 DisplayError(buf, errno);
12512 safeStrCpy(buf, lastMsg, MSG_SIZ);
12513 DisplayMessage(_("Waiting for access to save file"), "");
12514 flock(fileno(f), LOCK_EX); // [HGM] lock
12515 DisplayMessage(_("Saving position"), "");
12516 lseek(fileno(f), 0, SEEK_END); // better safe than sorry...
12517 SavePosition(f, 0, NULL);
12518 DisplayMessage(buf, "");
12524 /* Save the current position to the given open file and close the file */
12526 SavePosition (FILE *f, int dummy, char *dummy2)
12531 if (gameMode == EditPosition) EditPositionDone(TRUE);
12532 if (appData.oldSaveStyle) {
12533 tm = time((time_t *) NULL);
12535 fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
12537 fprintf(f, "[--------------\n");
12538 PrintPosition(f, currentMove);
12539 fprintf(f, "--------------]\n");
12541 fen = PositionToFEN(currentMove, NULL);
12542 fprintf(f, "%s\n", fen);
12550 ReloadCmailMsgEvent (int unregister)
12553 static char *inFilename = NULL;
12554 static char *outFilename;
12556 struct stat inbuf, outbuf;
12559 /* Any registered moves are unregistered if unregister is set, */
12560 /* i.e. invoked by the signal handler */
12562 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
12563 cmailMoveRegistered[i] = FALSE;
12564 if (cmailCommentList[i] != NULL) {
12565 free(cmailCommentList[i]);
12566 cmailCommentList[i] = NULL;
12569 nCmailMovesRegistered = 0;
12572 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
12573 cmailResult[i] = CMAIL_NOT_RESULT;
12577 if (inFilename == NULL) {
12578 /* Because the filenames are static they only get malloced once */
12579 /* and they never get freed */
12580 inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
12581 sprintf(inFilename, "%s.game.in", appData.cmailGameName);
12583 outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
12584 sprintf(outFilename, "%s.out", appData.cmailGameName);
12587 status = stat(outFilename, &outbuf);
12589 cmailMailedMove = FALSE;
12591 status = stat(inFilename, &inbuf);
12592 cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
12595 /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
12596 counts the games, notes how each one terminated, etc.
12598 It would be nice to remove this kludge and instead gather all
12599 the information while building the game list. (And to keep it
12600 in the game list nodes instead of having a bunch of fixed-size
12601 parallel arrays.) Note this will require getting each game's
12602 termination from the PGN tags, as the game list builder does
12603 not process the game moves. --mann
12605 cmailMsgLoaded = TRUE;
12606 LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
12608 /* Load first game in the file or popup game menu */
12609 LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
12611 #endif /* !WIN32 */
12619 char string[MSG_SIZ];
12621 if ( cmailMailedMove
12622 || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
12623 return TRUE; /* Allow free viewing */
12626 /* Unregister move to ensure that we don't leave RegisterMove */
12627 /* with the move registered when the conditions for registering no */
12629 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
12630 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
12631 nCmailMovesRegistered --;
12633 if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
12635 free(cmailCommentList[lastLoadGameNumber - 1]);
12636 cmailCommentList[lastLoadGameNumber - 1] = NULL;
12640 if (cmailOldMove == -1) {
12641 DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
12645 if (currentMove > cmailOldMove + 1) {
12646 DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
12650 if (currentMove < cmailOldMove) {
12651 DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
12655 if (forwardMostMove > currentMove) {
12656 /* Silently truncate extra moves */
12660 if ( (currentMove == cmailOldMove + 1)
12661 || ( (currentMove == cmailOldMove)
12662 && ( (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
12663 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
12664 if (gameInfo.result != GameUnfinished) {
12665 cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
12668 if (commentList[currentMove] != NULL) {
12669 cmailCommentList[lastLoadGameNumber - 1]
12670 = StrSave(commentList[currentMove]);
12672 safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
12674 if (appData.debugMode)
12675 fprintf(debugFP, "Saving %s for game %d\n",
12676 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
12678 snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
12680 f = fopen(string, "w");
12681 if (appData.oldSaveStyle) {
12682 SaveGameOldStyle(f); /* also closes the file */
12684 snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
12685 f = fopen(string, "w");
12686 SavePosition(f, 0, NULL); /* also closes the file */
12688 fprintf(f, "{--------------\n");
12689 PrintPosition(f, currentMove);
12690 fprintf(f, "--------------}\n\n");
12692 SaveGame(f, 0, NULL); /* also closes the file*/
12695 cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
12696 nCmailMovesRegistered ++;
12697 } else if (nCmailGames == 1) {
12698 DisplayError(_("You have not made a move yet"), 0);
12709 static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
12710 FILE *commandOutput;
12711 char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
12712 int nBytes = 0; /* Suppress warnings on uninitialized variables */
12718 if (! cmailMsgLoaded) {
12719 DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
12723 if (nCmailGames == nCmailResults) {
12724 DisplayError(_("No unfinished games"), 0);
12728 #if CMAIL_PROHIBIT_REMAIL
12729 if (cmailMailedMove) {
12730 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);
12731 DisplayError(msg, 0);
12736 if (! (cmailMailedMove || RegisterMove())) return;
12738 if ( cmailMailedMove
12739 || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
12740 snprintf(string, MSG_SIZ, partCommandString,
12741 appData.debugMode ? " -v" : "", appData.cmailGameName);
12742 commandOutput = popen(string, "r");
12744 if (commandOutput == NULL) {
12745 DisplayError(_("Failed to invoke cmail"), 0);
12747 for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
12748 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
12750 if (nBuffers > 1) {
12751 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
12752 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
12753 nBytes = MSG_SIZ - 1;
12755 (void) memcpy(msg, buffer, nBytes);
12757 *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
12759 if(StrStr(msg, "Mailed cmail message to ") != NULL) {
12760 cmailMailedMove = TRUE; /* Prevent >1 moves */
12763 for (i = 0; i < nCmailGames; i ++) {
12764 if (cmailResult[i] == CMAIL_NOT_RESULT) {
12769 && ( (arcDir = (char *) getenv("CMAIL_ARCDIR"))
12771 snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
12773 appData.cmailGameName,
12775 LoadGameFromFile(buffer, 1, buffer, FALSE);
12776 cmailMsgLoaded = FALSE;
12780 DisplayInformation(msg);
12781 pclose(commandOutput);
12784 if ((*cmailMsg) != '\0') {
12785 DisplayInformation(cmailMsg);
12790 #endif /* !WIN32 */
12799 int prependComma = 0;
12801 char string[MSG_SIZ]; /* Space for game-list */
12804 if (!cmailMsgLoaded) return "";
12806 if (cmailMailedMove) {
12807 snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
12809 /* Create a list of games left */
12810 snprintf(string, MSG_SIZ, "[");
12811 for (i = 0; i < nCmailGames; i ++) {
12812 if (! ( cmailMoveRegistered[i]
12813 || (cmailResult[i] == CMAIL_OLD_RESULT))) {
12814 if (prependComma) {
12815 snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
12817 snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
12821 strcat(string, number);
12824 strcat(string, "]");
12826 if (nCmailMovesRegistered + nCmailResults == 0) {
12827 switch (nCmailGames) {
12829 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
12833 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
12837 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
12842 switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
12844 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
12849 if (nCmailResults == nCmailGames) {
12850 snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
12852 snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
12857 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
12869 if (gameMode == Training)
12870 SetTrainingModeOff();
12873 cmailMsgLoaded = FALSE;
12874 if (appData.icsActive) {
12875 SendToICS(ics_prefix);
12876 SendToICS("refresh\n");
12881 ExitEvent (int status)
12885 /* Give up on clean exit */
12889 /* Keep trying for clean exit */
12893 if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
12895 if (telnetISR != NULL) {
12896 RemoveInputSource(telnetISR);
12898 if (icsPR != NoProc) {
12899 DestroyChildProcess(icsPR, TRUE);
12902 /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
12903 GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
12905 /* [HGM] crash: the above GameEnds() is a dud if another one was running */
12906 /* make sure this other one finishes before killing it! */
12907 if(endingGame) { int count = 0;
12908 if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
12909 while(endingGame && count++ < 10) DoSleep(1);
12910 if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
12913 /* Kill off chess programs */
12914 if (first.pr != NoProc) {
12917 DoSleep( appData.delayBeforeQuit );
12918 SendToProgram("quit\n", &first);
12919 DoSleep( appData.delayAfterQuit );
12920 DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
12922 if (second.pr != NoProc) {
12923 DoSleep( appData.delayBeforeQuit );
12924 SendToProgram("quit\n", &second);
12925 DoSleep( appData.delayAfterQuit );
12926 DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
12928 if (first.isr != NULL) {
12929 RemoveInputSource(first.isr);
12931 if (second.isr != NULL) {
12932 RemoveInputSource(second.isr);
12935 if (pairing.pr != NoProc) SendToProgram("quit\n", &pairing);
12936 if (pairing.isr != NULL) RemoveInputSource(pairing.isr);
12938 ShutDownFrontEnd();
12945 if (appData.debugMode)
12946 fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
12950 if (gameMode == MachinePlaysWhite ||
12951 gameMode == MachinePlaysBlack) {
12954 DisplayBothClocks();
12956 if (gameMode == PlayFromGameFile) {
12957 if (appData.timeDelay >= 0)
12958 AutoPlayGameLoop();
12959 } else if (gameMode == IcsExamining && pauseExamInvalid) {
12960 Reset(FALSE, TRUE);
12961 SendToICS(ics_prefix);
12962 SendToICS("refresh\n");
12963 } else if (currentMove < forwardMostMove) {
12964 ForwardInner(forwardMostMove);
12966 pauseExamInvalid = FALSE;
12968 switch (gameMode) {
12972 pauseExamForwardMostMove = forwardMostMove;
12973 pauseExamInvalid = FALSE;
12976 case IcsPlayingWhite:
12977 case IcsPlayingBlack:
12981 case PlayFromGameFile:
12982 (void) StopLoadGameTimer();
12986 case BeginningOfGame:
12987 if (appData.icsActive) return;
12988 /* else fall through */
12989 case MachinePlaysWhite:
12990 case MachinePlaysBlack:
12991 case TwoMachinesPlay:
12992 if (forwardMostMove == 0)
12993 return; /* don't pause if no one has moved */
12994 if ((gameMode == MachinePlaysWhite &&
12995 !WhiteOnMove(forwardMostMove)) ||
12996 (gameMode == MachinePlaysBlack &&
12997 WhiteOnMove(forwardMostMove))) {
13008 EditCommentEvent ()
13010 char title[MSG_SIZ];
13012 if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
13013 safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
13015 snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
13016 WhiteOnMove(currentMove - 1) ? " " : ".. ",
13017 parseList[currentMove - 1]);
13020 EditCommentPopUp(currentMove, title, commentList[currentMove]);
13027 char *tags = PGNTags(&gameInfo);
13029 EditTagsPopUp(tags, NULL);
13034 AnalyzeModeEvent ()
13036 if (appData.noChessProgram || gameMode == AnalyzeMode)
13039 if (gameMode != AnalyzeFile) {
13040 if (!appData.icsEngineAnalyze) {
13042 if (gameMode != EditGame) return;
13044 ResurrectChessProgram();
13045 SendToProgram("analyze\n", &first);
13046 first.analyzing = TRUE;
13047 /*first.maybeThinking = TRUE;*/
13048 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13049 EngineOutputPopUp();
13051 if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
13056 StartAnalysisClock();
13057 GetTimeMark(&lastNodeCountTime);
13062 AnalyzeFileEvent ()
13064 if (appData.noChessProgram || gameMode == AnalyzeFile)
13067 if (gameMode != AnalyzeMode) {
13069 if (gameMode != EditGame) return;
13070 ResurrectChessProgram();
13071 SendToProgram("analyze\n", &first);
13072 first.analyzing = TRUE;
13073 /*first.maybeThinking = TRUE;*/
13074 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13075 EngineOutputPopUp();
13077 gameMode = AnalyzeFile;
13082 StartAnalysisClock();
13083 GetTimeMark(&lastNodeCountTime);
13085 if(appData.timeDelay > 0) StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
13089 MachineWhiteEvent ()
13092 char *bookHit = NULL;
13094 if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
13098 if (gameMode == PlayFromGameFile ||
13099 gameMode == TwoMachinesPlay ||
13100 gameMode == Training ||
13101 gameMode == AnalyzeMode ||
13102 gameMode == EndOfGame)
13105 if (gameMode == EditPosition)
13106 EditPositionDone(TRUE);
13108 if (!WhiteOnMove(currentMove)) {
13109 DisplayError(_("It is not White's turn"), 0);
13113 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
13116 if (gameMode == EditGame || gameMode == AnalyzeMode ||
13117 gameMode == AnalyzeFile)
13120 ResurrectChessProgram(); /* in case it isn't running */
13121 if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
13122 gameMode = MachinePlaysWhite;
13125 gameMode = MachinePlaysWhite;
13129 snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
13131 if (first.sendName) {
13132 snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
13133 SendToProgram(buf, &first);
13135 if (first.sendTime) {
13136 if (first.useColors) {
13137 SendToProgram("black\n", &first); /*gnu kludge*/
13139 SendTimeRemaining(&first, TRUE);
13141 if (first.useColors) {
13142 SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
13144 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
13145 SetMachineThinkingEnables();
13146 first.maybeThinking = TRUE;
13150 if (appData.autoFlipView && !flipView) {
13151 flipView = !flipView;
13152 DrawPosition(FALSE, NULL);
13153 DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;
13156 if(bookHit) { // [HGM] book: simulate book reply
13157 static char bookMove[MSG_SIZ]; // a bit generous?
13159 programStats.nodes = programStats.depth = programStats.time =
13160 programStats.score = programStats.got_only_move = 0;
13161 sprintf(programStats.movelist, "%s (xbook)", bookHit);
13163 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13164 strcat(bookMove, bookHit);
13165 HandleMachineMove(bookMove, &first);
13170 MachineBlackEvent ()
13173 char *bookHit = NULL;
13175 if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
13179 if (gameMode == PlayFromGameFile ||
13180 gameMode == TwoMachinesPlay ||
13181 gameMode == Training ||
13182 gameMode == AnalyzeMode ||
13183 gameMode == EndOfGame)
13186 if (gameMode == EditPosition)
13187 EditPositionDone(TRUE);
13189 if (WhiteOnMove(currentMove)) {
13190 DisplayError(_("It is not Black's turn"), 0);
13194 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
13197 if (gameMode == EditGame || gameMode == AnalyzeMode ||
13198 gameMode == AnalyzeFile)
13201 ResurrectChessProgram(); /* in case it isn't running */
13202 gameMode = MachinePlaysBlack;
13206 snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
13208 if (first.sendName) {
13209 snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
13210 SendToProgram(buf, &first);
13212 if (first.sendTime) {
13213 if (first.useColors) {
13214 SendToProgram("white\n", &first); /*gnu kludge*/
13216 SendTimeRemaining(&first, FALSE);
13218 if (first.useColors) {
13219 SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
13221 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
13222 SetMachineThinkingEnables();
13223 first.maybeThinking = TRUE;
13226 if (appData.autoFlipView && flipView) {
13227 flipView = !flipView;
13228 DrawPosition(FALSE, NULL);
13229 DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;
13231 if(bookHit) { // [HGM] book: simulate book reply
13232 static char bookMove[MSG_SIZ]; // a bit generous?
13234 programStats.nodes = programStats.depth = programStats.time =
13235 programStats.score = programStats.got_only_move = 0;
13236 sprintf(programStats.movelist, "%s (xbook)", bookHit);
13238 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13239 strcat(bookMove, bookHit);
13240 HandleMachineMove(bookMove, &first);
13246 DisplayTwoMachinesTitle ()
13249 if (appData.matchGames > 0) {
13250 if(appData.tourneyFile[0]) {
13251 snprintf(buf, MSG_SIZ, "%s %s %s (%d/%d%s)",
13252 gameInfo.white, _("vs."), gameInfo.black,
13253 nextGame+1, appData.matchGames+1,
13254 appData.tourneyType>0 ? "gt" : appData.tourneyType<0 ? "sw" : "rr");
13256 if (first.twoMachinesColor[0] == 'w') {
13257 snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
13258 gameInfo.white, _("vs."), gameInfo.black,
13259 first.matchWins, second.matchWins,
13260 matchGame - 1 - (first.matchWins + second.matchWins));
13262 snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
13263 gameInfo.white, _("vs."), gameInfo.black,
13264 second.matchWins, first.matchWins,
13265 matchGame - 1 - (first.matchWins + second.matchWins));
13268 snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
13274 SettingsMenuIfReady ()
13276 if (second.lastPing != second.lastPong) {
13277 DisplayMessage("", _("Waiting for second chess program"));
13278 ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
13282 DisplayMessage("", "");
13283 SettingsPopUp(&second);
13287 WaitForEngine (ChessProgramState *cps, DelayedEventCallback retry)
13290 if (cps->pr == NoProc) {
13291 StartChessProgram(cps);
13292 if (cps->protocolVersion == 1) {
13295 /* kludge: allow timeout for initial "feature" command */
13297 snprintf(buf, MSG_SIZ, _("Starting %s chess program"), cps->which);
13298 DisplayMessage("", buf);
13299 ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
13307 TwoMachinesEvent P((void))
13311 ChessProgramState *onmove;
13312 char *bookHit = NULL;
13313 static int stalling = 0;
13317 if (appData.noChessProgram) return;
13319 switch (gameMode) {
13320 case TwoMachinesPlay:
13322 case MachinePlaysWhite:
13323 case MachinePlaysBlack:
13324 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
13325 DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
13329 case BeginningOfGame:
13330 case PlayFromGameFile:
13333 if (gameMode != EditGame) return;
13336 EditPositionDone(TRUE);
13347 // forwardMostMove = currentMove;
13348 TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
13350 if(!ResurrectChessProgram()) return; /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
13352 if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
13353 if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
13354 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
13358 InitChessProgram(&second, FALSE); // unbalances ping of second engine
13359 SendToProgram("force\n", &second);
13361 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
13364 GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
13365 if(appData.matchPause>10000 || appData.matchPause<10)
13366 appData.matchPause = 10000; /* [HGM] make pause adjustable */
13367 wait = SubtractTimeMarks(&now, &pauseStart);
13368 if(wait < appData.matchPause) {
13369 ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
13373 DisplayMessage("", "");
13374 if (startedFromSetupPosition) {
13375 SendBoard(&second, backwardMostMove);
13376 if (appData.debugMode) {
13377 fprintf(debugFP, "Two Machines\n");
13380 for (i = backwardMostMove; i < forwardMostMove; i++) {
13381 SendMoveToProgram(i, &second);
13384 gameMode = TwoMachinesPlay;
13386 ModeHighlight(); // [HGM] logo: this triggers display update of logos
13388 DisplayTwoMachinesTitle();
13390 if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
13395 if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
13396 SendToProgram(first.computerString, &first);
13397 if (first.sendName) {
13398 snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
13399 SendToProgram(buf, &first);
13401 SendToProgram(second.computerString, &second);
13402 if (second.sendName) {
13403 snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
13404 SendToProgram(buf, &second);
13408 if (!first.sendTime || !second.sendTime) {
13409 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
13410 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
13412 if (onmove->sendTime) {
13413 if (onmove->useColors) {
13414 SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
13416 SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
13418 if (onmove->useColors) {
13419 SendToProgram(onmove->twoMachinesColor, onmove);
13421 bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
13422 // SendToProgram("go\n", onmove);
13423 onmove->maybeThinking = TRUE;
13424 SetMachineThinkingEnables();
13428 if(bookHit) { // [HGM] book: simulate book reply
13429 static char bookMove[MSG_SIZ]; // a bit generous?
13431 programStats.nodes = programStats.depth = programStats.time =
13432 programStats.score = programStats.got_only_move = 0;
13433 sprintf(programStats.movelist, "%s (xbook)", bookHit);
13435 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13436 strcat(bookMove, bookHit);
13437 savedMessage = bookMove; // args for deferred call
13438 savedState = onmove;
13439 ScheduleDelayedEvent(DeferredBookMove, 1);
13446 if (gameMode == Training) {
13447 SetTrainingModeOff();
13448 gameMode = PlayFromGameFile;
13449 DisplayMessage("", _("Training mode off"));
13451 gameMode = Training;
13452 animateTraining = appData.animate;
13454 /* make sure we are not already at the end of the game */
13455 if (currentMove < forwardMostMove) {
13456 SetTrainingModeOn();
13457 DisplayMessage("", _("Training mode on"));
13459 gameMode = PlayFromGameFile;
13460 DisplayError(_("Already at end of game"), 0);
13469 if (!appData.icsActive) return;
13470 switch (gameMode) {
13471 case IcsPlayingWhite:
13472 case IcsPlayingBlack:
13475 case BeginningOfGame:
13483 EditPositionDone(TRUE);
13496 gameMode = IcsIdle;
13506 switch (gameMode) {
13508 SetTrainingModeOff();
13510 case MachinePlaysWhite:
13511 case MachinePlaysBlack:
13512 case BeginningOfGame:
13513 SendToProgram("force\n", &first);
13514 SetUserThinkingEnables();
13516 case PlayFromGameFile:
13517 (void) StopLoadGameTimer();
13518 if (gameFileFP != NULL) {
13523 EditPositionDone(TRUE);
13528 SendToProgram("force\n", &first);
13530 case TwoMachinesPlay:
13531 GameEnds(EndOfFile, NULL, GE_PLAYER);
13532 ResurrectChessProgram();
13533 SetUserThinkingEnables();
13536 ResurrectChessProgram();
13538 case IcsPlayingBlack:
13539 case IcsPlayingWhite:
13540 DisplayError(_("Warning: You are still playing a game"), 0);
13543 DisplayError(_("Warning: You are still observing a game"), 0);
13546 DisplayError(_("Warning: You are still examining a game"), 0);
13557 first.offeredDraw = second.offeredDraw = 0;
13559 if (gameMode == PlayFromGameFile) {
13560 whiteTimeRemaining = timeRemaining[0][currentMove];
13561 blackTimeRemaining = timeRemaining[1][currentMove];
13565 if (gameMode == MachinePlaysWhite ||
13566 gameMode == MachinePlaysBlack ||
13567 gameMode == TwoMachinesPlay ||
13568 gameMode == EndOfGame) {
13569 i = forwardMostMove;
13570 while (i > currentMove) {
13571 SendToProgram("undo\n", &first);
13574 if(!adjustedClock) {
13575 whiteTimeRemaining = timeRemaining[0][currentMove];
13576 blackTimeRemaining = timeRemaining[1][currentMove];
13577 DisplayBothClocks();
13579 if (whiteFlag || blackFlag) {
13580 whiteFlag = blackFlag = 0;
13585 gameMode = EditGame;
13592 EditPositionEvent ()
13594 if (gameMode == EditPosition) {
13600 if (gameMode != EditGame) return;
13602 gameMode = EditPosition;
13605 if (currentMove > 0)
13606 CopyBoard(boards[0], boards[currentMove]);
13608 blackPlaysFirst = !WhiteOnMove(currentMove);
13610 currentMove = forwardMostMove = backwardMostMove = 0;
13611 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
13618 /* [DM] icsEngineAnalyze - possible call from other functions */
13619 if (appData.icsEngineAnalyze) {
13620 appData.icsEngineAnalyze = FALSE;
13622 DisplayMessage("",_("Close ICS engine analyze..."));
13624 if (first.analysisSupport && first.analyzing) {
13625 SendToProgram("exit\n", &first);
13626 first.analyzing = FALSE;
13628 thinkOutput[0] = NULLCHAR;
13632 EditPositionDone (Boolean fakeRights)
13634 int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
13636 startedFromSetupPosition = TRUE;
13637 InitChessProgram(&first, FALSE);
13638 if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
13639 boards[0][EP_STATUS] = EP_NONE;
13640 boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
13641 if(boards[0][0][BOARD_WIDTH>>1] == king) {
13642 boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : NoRights;
13643 boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
13644 } else boards[0][CASTLING][2] = NoRights;
13645 if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
13646 boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : NoRights;
13647 boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
13648 } else boards[0][CASTLING][5] = NoRights;
13650 SendToProgram("force\n", &first);
13651 if (blackPlaysFirst) {
13652 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13653 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13654 currentMove = forwardMostMove = backwardMostMove = 1;
13655 CopyBoard(boards[1], boards[0]);
13657 currentMove = forwardMostMove = backwardMostMove = 0;
13659 SendBoard(&first, forwardMostMove);
13660 if (appData.debugMode) {
13661 fprintf(debugFP, "EditPosDone\n");
13664 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
13665 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
13666 gameMode = EditGame;
13668 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
13669 ClearHighlights(); /* [AS] */
13672 /* Pause for `ms' milliseconds */
13673 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
13675 TimeDelay (long ms)
13682 } while (SubtractTimeMarks(&m2, &m1) < ms);
13685 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
13687 SendMultiLineToICS (char *buf)
13689 char temp[MSG_SIZ+1], *p;
13696 strncpy(temp, buf, len);
13701 if (*p == '\n' || *p == '\r')
13706 strcat(temp, "\n");
13708 SendToPlayer(temp, strlen(temp));
13712 SetWhiteToPlayEvent ()
13714 if (gameMode == EditPosition) {
13715 blackPlaysFirst = FALSE;
13716 DisplayBothClocks(); /* works because currentMove is 0 */
13717 } else if (gameMode == IcsExamining) {
13718 SendToICS(ics_prefix);
13719 SendToICS("tomove white\n");
13724 SetBlackToPlayEvent ()
13726 if (gameMode == EditPosition) {
13727 blackPlaysFirst = TRUE;
13728 currentMove = 1; /* kludge */
13729 DisplayBothClocks();
13731 } else if (gameMode == IcsExamining) {
13732 SendToICS(ics_prefix);
13733 SendToICS("tomove black\n");
13738 EditPositionMenuEvent (ChessSquare selection, int x, int y)
13741 ChessSquare piece = boards[0][y][x];
13743 if (gameMode != EditPosition && gameMode != IcsExamining) return;
13745 switch (selection) {
13747 if (gameMode == IcsExamining && ics_type == ICS_FICS) {
13748 SendToICS(ics_prefix);
13749 SendToICS("bsetup clear\n");
13750 } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
13751 SendToICS(ics_prefix);
13752 SendToICS("clearboard\n");
13754 for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
13755 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
13756 for (y = 0; y < BOARD_HEIGHT; y++) {
13757 if (gameMode == IcsExamining) {
13758 if (boards[currentMove][y][x] != EmptySquare) {
13759 snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
13764 boards[0][y][x] = p;
13769 if (gameMode == EditPosition) {
13770 DrawPosition(FALSE, boards[0]);
13775 SetWhiteToPlayEvent();
13779 SetBlackToPlayEvent();
13783 if (gameMode == IcsExamining) {
13784 if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
13785 snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
13788 if(x < BOARD_LEFT || x >= BOARD_RGHT) {
13789 if(x == BOARD_LEFT-2) {
13790 if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
13791 boards[0][y][1] = 0;
13793 if(x == BOARD_RGHT+1) {
13794 if(y >= gameInfo.holdingsSize) break;
13795 boards[0][y][BOARD_WIDTH-2] = 0;
13798 boards[0][y][x] = EmptySquare;
13799 DrawPosition(FALSE, boards[0]);
13804 if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
13805 piece >= (int)BlackPawn && piece < (int)BlackMan ) {
13806 selection = (ChessSquare) (PROMOTED piece);
13807 } else if(piece == EmptySquare) selection = WhiteSilver;
13808 else selection = (ChessSquare)((int)piece - 1);
13812 if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
13813 piece > (int)BlackMan && piece <= (int)BlackKing ) {
13814 selection = (ChessSquare) (DEMOTED piece);
13815 } else if(piece == EmptySquare) selection = BlackSilver;
13816 else selection = (ChessSquare)((int)piece + 1);
13821 if(gameInfo.variant == VariantShatranj ||
13822 gameInfo.variant == VariantXiangqi ||
13823 gameInfo.variant == VariantCourier ||
13824 gameInfo.variant == VariantMakruk )
13825 selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
13830 if(gameInfo.variant == VariantXiangqi)
13831 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
13832 if(gameInfo.variant == VariantKnightmate)
13833 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
13836 if (gameMode == IcsExamining) {
13837 if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
13838 snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
13839 PieceToChar(selection), AAA + x, ONE + y);
13842 if(x < BOARD_LEFT || x >= BOARD_RGHT) {
13844 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
13845 n = PieceToNumber(selection - BlackPawn);
13846 if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
13847 boards[0][BOARD_HEIGHT-1-n][0] = selection;
13848 boards[0][BOARD_HEIGHT-1-n][1]++;
13850 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
13851 n = PieceToNumber(selection);
13852 if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
13853 boards[0][n][BOARD_WIDTH-1] = selection;
13854 boards[0][n][BOARD_WIDTH-2]++;
13857 boards[0][y][x] = selection;
13858 DrawPosition(TRUE, boards[0]);
13866 DropMenuEvent (ChessSquare selection, int x, int y)
13868 ChessMove moveType;
13870 switch (gameMode) {
13871 case IcsPlayingWhite:
13872 case MachinePlaysBlack:
13873 if (!WhiteOnMove(currentMove)) {
13874 DisplayMoveError(_("It is Black's turn"));
13877 moveType = WhiteDrop;
13879 case IcsPlayingBlack:
13880 case MachinePlaysWhite:
13881 if (WhiteOnMove(currentMove)) {
13882 DisplayMoveError(_("It is White's turn"));
13885 moveType = BlackDrop;
13888 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
13894 if (moveType == BlackDrop && selection < BlackPawn) {
13895 selection = (ChessSquare) ((int) selection
13896 + (int) BlackPawn - (int) WhitePawn);
13898 if (boards[currentMove][y][x] != EmptySquare) {
13899 DisplayMoveError(_("That square is occupied"));
13903 FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
13909 /* Accept a pending offer of any kind from opponent */
13911 if (appData.icsActive) {
13912 SendToICS(ics_prefix);
13913 SendToICS("accept\n");
13914 } else if (cmailMsgLoaded) {
13915 if (currentMove == cmailOldMove &&
13916 commentList[cmailOldMove] != NULL &&
13917 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
13918 "Black offers a draw" : "White offers a draw")) {
13920 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
13921 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
13923 DisplayError(_("There is no pending offer on this move"), 0);
13924 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
13927 /* Not used for offers from chess program */
13934 /* Decline a pending offer of any kind from opponent */
13936 if (appData.icsActive) {
13937 SendToICS(ics_prefix);
13938 SendToICS("decline\n");
13939 } else if (cmailMsgLoaded) {
13940 if (currentMove == cmailOldMove &&
13941 commentList[cmailOldMove] != NULL &&
13942 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
13943 "Black offers a draw" : "White offers a draw")) {
13945 AppendComment(cmailOldMove, "Draw declined", TRUE);
13946 DisplayComment(cmailOldMove - 1, "Draw declined");
13949 DisplayError(_("There is no pending offer on this move"), 0);
13952 /* Not used for offers from chess program */
13959 /* Issue ICS rematch command */
13960 if (appData.icsActive) {
13961 SendToICS(ics_prefix);
13962 SendToICS("rematch\n");
13969 /* Call your opponent's flag (claim a win on time) */
13970 if (appData.icsActive) {
13971 SendToICS(ics_prefix);
13972 SendToICS("flag\n");
13974 switch (gameMode) {
13977 case MachinePlaysWhite:
13980 GameEnds(GameIsDrawn, "Both players ran out of time",
13983 GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
13985 DisplayError(_("Your opponent is not out of time"), 0);
13988 case MachinePlaysBlack:
13991 GameEnds(GameIsDrawn, "Both players ran out of time",
13994 GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
13996 DisplayError(_("Your opponent is not out of time"), 0);
14004 ClockClick (int which)
14005 { // [HGM] code moved to back-end from winboard.c
14006 if(which) { // black clock
14007 if (gameMode == EditPosition || gameMode == IcsExamining) {
14008 if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
14009 SetBlackToPlayEvent();
14010 } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !blackFlag && WhiteOnMove(currentMove)) {
14011 UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move: if not out of time, enters null move
14012 } else if (shiftKey) {
14013 AdjustClock(which, -1);
14014 } else if (gameMode == IcsPlayingWhite ||
14015 gameMode == MachinePlaysBlack) {
14018 } else { // white clock
14019 if (gameMode == EditPosition || gameMode == IcsExamining) {
14020 if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
14021 SetWhiteToPlayEvent();
14022 } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !whiteFlag && !WhiteOnMove(currentMove)) {
14023 UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move
14024 } else if (shiftKey) {
14025 AdjustClock(which, -1);
14026 } else if (gameMode == IcsPlayingBlack ||
14027 gameMode == MachinePlaysWhite) {
14036 /* Offer draw or accept pending draw offer from opponent */
14038 if (appData.icsActive) {
14039 /* Note: tournament rules require draw offers to be
14040 made after you make your move but before you punch
14041 your clock. Currently ICS doesn't let you do that;
14042 instead, you immediately punch your clock after making
14043 a move, but you can offer a draw at any time. */
14045 SendToICS(ics_prefix);
14046 SendToICS("draw\n");
14047 userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
14048 } else if (cmailMsgLoaded) {
14049 if (currentMove == cmailOldMove &&
14050 commentList[cmailOldMove] != NULL &&
14051 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14052 "Black offers a draw" : "White offers a draw")) {
14053 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
14054 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
14055 } else if (currentMove == cmailOldMove + 1) {
14056 char *offer = WhiteOnMove(cmailOldMove) ?
14057 "White offers a draw" : "Black offers a draw";
14058 AppendComment(currentMove, offer, TRUE);
14059 DisplayComment(currentMove - 1, offer);
14060 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
14062 DisplayError(_("You must make your move before offering a draw"), 0);
14063 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
14065 } else if (first.offeredDraw) {
14066 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
14068 if (first.sendDrawOffers) {
14069 SendToProgram("draw\n", &first);
14070 userOfferedDraw = TRUE;
14078 /* Offer Adjourn or accept pending Adjourn offer from opponent */
14080 if (appData.icsActive) {
14081 SendToICS(ics_prefix);
14082 SendToICS("adjourn\n");
14084 /* Currently GNU Chess doesn't offer or accept Adjourns */
14092 /* Offer Abort or accept pending Abort offer from opponent */
14094 if (appData.icsActive) {
14095 SendToICS(ics_prefix);
14096 SendToICS("abort\n");
14098 GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
14105 /* Resign. You can do this even if it's not your turn. */
14107 if (appData.icsActive) {
14108 SendToICS(ics_prefix);
14109 SendToICS("resign\n");
14111 switch (gameMode) {
14112 case MachinePlaysWhite:
14113 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
14115 case MachinePlaysBlack:
14116 GameEnds(BlackWins, "White resigns", GE_PLAYER);
14119 if (cmailMsgLoaded) {
14121 if (WhiteOnMove(cmailOldMove)) {
14122 GameEnds(BlackWins, "White resigns", GE_PLAYER);
14124 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
14126 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
14137 StopObservingEvent ()
14139 /* Stop observing current games */
14140 SendToICS(ics_prefix);
14141 SendToICS("unobserve\n");
14145 StopExaminingEvent ()
14147 /* Stop observing current game */
14148 SendToICS(ics_prefix);
14149 SendToICS("unexamine\n");
14153 ForwardInner (int target)
14157 if (appData.debugMode)
14158 fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
14159 target, currentMove, forwardMostMove);
14161 if (gameMode == EditPosition)
14164 MarkTargetSquares(1);
14166 if (gameMode == PlayFromGameFile && !pausing)
14169 if (gameMode == IcsExamining && pausing)
14170 limit = pauseExamForwardMostMove;
14172 limit = forwardMostMove;
14174 if (target > limit) target = limit;
14176 if (target > 0 && moveList[target - 1][0]) {
14177 int fromX, fromY, toX, toY;
14178 toX = moveList[target - 1][2] - AAA;
14179 toY = moveList[target - 1][3] - ONE;
14180 if (moveList[target - 1][1] == '@') {
14181 if (appData.highlightLastMove) {
14182 SetHighlights(-1, -1, toX, toY);
14185 fromX = moveList[target - 1][0] - AAA;
14186 fromY = moveList[target - 1][1] - ONE;
14187 if (target == currentMove + 1) {
14188 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
14190 if (appData.highlightLastMove) {
14191 SetHighlights(fromX, fromY, toX, toY);
14195 if (gameMode == EditGame || gameMode == AnalyzeMode ||
14196 gameMode == Training || gameMode == PlayFromGameFile ||
14197 gameMode == AnalyzeFile) {
14198 while (currentMove < target) {
14199 SendMoveToProgram(currentMove++, &first);
14202 currentMove = target;
14205 if (gameMode == EditGame || gameMode == EndOfGame) {
14206 whiteTimeRemaining = timeRemaining[0][currentMove];
14207 blackTimeRemaining = timeRemaining[1][currentMove];
14209 DisplayBothClocks();
14210 DisplayMove(currentMove - 1);
14211 DrawPosition(FALSE, boards[currentMove]);
14212 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
14213 if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
14214 DisplayComment(currentMove - 1, commentList[currentMove]);
14222 if (gameMode == IcsExamining && !pausing) {
14223 SendToICS(ics_prefix);
14224 SendToICS("forward\n");
14226 ForwardInner(currentMove + 1);
14233 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14234 /* to optimze, we temporarily turn off analysis mode while we feed
14235 * the remaining moves to the engine. Otherwise we get analysis output
14238 if (first.analysisSupport) {
14239 SendToProgram("exit\nforce\n", &first);
14240 first.analyzing = FALSE;
14244 if (gameMode == IcsExamining && !pausing) {
14245 SendToICS(ics_prefix);
14246 SendToICS("forward 999999\n");
14248 ForwardInner(forwardMostMove);
14251 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14252 /* we have fed all the moves, so reactivate analysis mode */
14253 SendToProgram("analyze\n", &first);
14254 first.analyzing = TRUE;
14255 /*first.maybeThinking = TRUE;*/
14256 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14261 BackwardInner (int target)
14263 int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
14265 if (appData.debugMode)
14266 fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
14267 target, currentMove, forwardMostMove);
14269 if (gameMode == EditPosition) return;
14270 MarkTargetSquares(1);
14271 if (currentMove <= backwardMostMove) {
14273 DrawPosition(full_redraw, boards[currentMove]);
14276 if (gameMode == PlayFromGameFile && !pausing)
14279 if (moveList[target][0]) {
14280 int fromX, fromY, toX, toY;
14281 toX = moveList[target][2] - AAA;
14282 toY = moveList[target][3] - ONE;
14283 if (moveList[target][1] == '@') {
14284 if (appData.highlightLastMove) {
14285 SetHighlights(-1, -1, toX, toY);
14288 fromX = moveList[target][0] - AAA;
14289 fromY = moveList[target][1] - ONE;
14290 if (target == currentMove - 1) {
14291 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
14293 if (appData.highlightLastMove) {
14294 SetHighlights(fromX, fromY, toX, toY);
14298 if (gameMode == EditGame || gameMode==AnalyzeMode ||
14299 gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
14300 while (currentMove > target) {
14301 if(moveList[currentMove-1][1] == '@' && moveList[currentMove-1][0] == '@') {
14302 // null move cannot be undone. Reload program with move history before it.
14304 for(i=target; i>backwardMostMove; i--) { // seek back to start or previous null move
14305 if(moveList[i-1][1] == '@' && moveList[i-1][0] == '@') break;
14307 SendBoard(&first, i);
14308 for(currentMove=i; currentMove<target; currentMove++) SendMoveToProgram(currentMove, &first);
14311 SendToProgram("undo\n", &first);
14315 currentMove = target;
14318 if (gameMode == EditGame || gameMode == EndOfGame) {
14319 whiteTimeRemaining = timeRemaining[0][currentMove];
14320 blackTimeRemaining = timeRemaining[1][currentMove];
14322 DisplayBothClocks();
14323 DisplayMove(currentMove - 1);
14324 DrawPosition(full_redraw, boards[currentMove]);
14325 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
14326 // [HGM] PV info: routine tests if comment empty
14327 DisplayComment(currentMove - 1, commentList[currentMove]);
14333 if (gameMode == IcsExamining && !pausing) {
14334 SendToICS(ics_prefix);
14335 SendToICS("backward\n");
14337 BackwardInner(currentMove - 1);
14344 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14345 /* to optimize, we temporarily turn off analysis mode while we undo
14346 * all the moves. Otherwise we get analysis output after each undo.
14348 if (first.analysisSupport) {
14349 SendToProgram("exit\nforce\n", &first);
14350 first.analyzing = FALSE;
14354 if (gameMode == IcsExamining && !pausing) {
14355 SendToICS(ics_prefix);
14356 SendToICS("backward 999999\n");
14358 BackwardInner(backwardMostMove);
14361 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14362 /* we have fed all the moves, so reactivate analysis mode */
14363 SendToProgram("analyze\n", &first);
14364 first.analyzing = TRUE;
14365 /*first.maybeThinking = TRUE;*/
14366 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14373 if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
14374 if (to >= forwardMostMove) to = forwardMostMove;
14375 if (to <= backwardMostMove) to = backwardMostMove;
14376 if (to < currentMove) {
14384 RevertEvent (Boolean annotate)
14386 if(PopTail(annotate)) { // [HGM] vari: restore old game tail
14389 if (gameMode != IcsExamining) {
14390 DisplayError(_("You are not examining a game"), 0);
14394 DisplayError(_("You can't revert while pausing"), 0);
14397 SendToICS(ics_prefix);
14398 SendToICS("revert\n");
14402 RetractMoveEvent ()
14404 switch (gameMode) {
14405 case MachinePlaysWhite:
14406 case MachinePlaysBlack:
14407 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
14408 DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
14411 if (forwardMostMove < 2) return;
14412 currentMove = forwardMostMove = forwardMostMove - 2;
14413 whiteTimeRemaining = timeRemaining[0][currentMove];
14414 blackTimeRemaining = timeRemaining[1][currentMove];
14415 DisplayBothClocks();
14416 DisplayMove(currentMove - 1);
14417 ClearHighlights();/*!! could figure this out*/
14418 DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
14419 SendToProgram("remove\n", &first);
14420 /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
14423 case BeginningOfGame:
14427 case IcsPlayingWhite:
14428 case IcsPlayingBlack:
14429 if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
14430 SendToICS(ics_prefix);
14431 SendToICS("takeback 2\n");
14433 SendToICS(ics_prefix);
14434 SendToICS("takeback 1\n");
14443 ChessProgramState *cps;
14445 switch (gameMode) {
14446 case MachinePlaysWhite:
14447 if (!WhiteOnMove(forwardMostMove)) {
14448 DisplayError(_("It is your turn"), 0);
14453 case MachinePlaysBlack:
14454 if (WhiteOnMove(forwardMostMove)) {
14455 DisplayError(_("It is your turn"), 0);
14460 case TwoMachinesPlay:
14461 if (WhiteOnMove(forwardMostMove) ==
14462 (first.twoMachinesColor[0] == 'w')) {
14468 case BeginningOfGame:
14472 SendToProgram("?\n", cps);
14476 TruncateGameEvent ()
14479 if (gameMode != EditGame) return;
14486 CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
14487 if (forwardMostMove > currentMove) {
14488 if (gameInfo.resultDetails != NULL) {
14489 free(gameInfo.resultDetails);
14490 gameInfo.resultDetails = NULL;
14491 gameInfo.result = GameUnfinished;
14493 forwardMostMove = currentMove;
14494 HistorySet(parseList, backwardMostMove, forwardMostMove,
14502 if (appData.noChessProgram) return;
14503 switch (gameMode) {
14504 case MachinePlaysWhite:
14505 if (WhiteOnMove(forwardMostMove)) {
14506 DisplayError(_("Wait until your turn"), 0);
14510 case BeginningOfGame:
14511 case MachinePlaysBlack:
14512 if (!WhiteOnMove(forwardMostMove)) {
14513 DisplayError(_("Wait until your turn"), 0);
14518 DisplayError(_("No hint available"), 0);
14521 SendToProgram("hint\n", &first);
14522 hintRequested = TRUE;
14528 if (appData.noChessProgram) return;
14529 switch (gameMode) {
14530 case MachinePlaysWhite:
14531 if (WhiteOnMove(forwardMostMove)) {
14532 DisplayError(_("Wait until your turn"), 0);
14536 case BeginningOfGame:
14537 case MachinePlaysBlack:
14538 if (!WhiteOnMove(forwardMostMove)) {
14539 DisplayError(_("Wait until your turn"), 0);
14544 EditPositionDone(TRUE);
14546 case TwoMachinesPlay:
14551 SendToProgram("bk\n", &first);
14552 bookOutput[0] = NULLCHAR;
14553 bookRequested = TRUE;
14559 char *tags = PGNTags(&gameInfo);
14560 TagsPopUp(tags, CmailMsg());
14564 /* end button procedures */
14567 PrintPosition (FILE *fp, int move)
14571 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
14572 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
14573 char c = PieceToChar(boards[move][i][j]);
14574 fputc(c == 'x' ? '.' : c, fp);
14575 fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
14578 if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
14579 fprintf(fp, "white to play\n");
14581 fprintf(fp, "black to play\n");
14585 PrintOpponents (FILE *fp)
14587 if (gameInfo.white != NULL) {
14588 fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
14594 /* Find last component of program's own name, using some heuristics */
14596 TidyProgramName (char *prog, char *host, char buf[MSG_SIZ])
14599 int local = (strcmp(host, "localhost") == 0);
14600 while (!local && (p = strchr(prog, ';')) != NULL) {
14602 while (*p == ' ') p++;
14605 if (*prog == '"' || *prog == '\'') {
14606 q = strchr(prog + 1, *prog);
14608 q = strchr(prog, ' ');
14610 if (q == NULL) q = prog + strlen(prog);
14612 while (p >= prog && *p != '/' && *p != '\\') p--;
14614 if(p == prog && *p == '"') p++;
14615 if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
14616 memcpy(buf, p, q - p);
14617 buf[q - p] = NULLCHAR;
14625 TimeControlTagValue ()
14628 if (!appData.clockMode) {
14629 safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
14630 } else if (movesPerSession > 0) {
14631 snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
14632 } else if (timeIncrement == 0) {
14633 snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
14635 snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
14637 return StrSave(buf);
14643 /* This routine is used only for certain modes */
14644 VariantClass v = gameInfo.variant;
14645 ChessMove r = GameUnfinished;
14648 if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
14649 r = gameInfo.result;
14650 p = gameInfo.resultDetails;
14651 gameInfo.resultDetails = NULL;
14653 ClearGameInfo(&gameInfo);
14654 gameInfo.variant = v;
14656 switch (gameMode) {
14657 case MachinePlaysWhite:
14658 gameInfo.event = StrSave( appData.pgnEventHeader );
14659 gameInfo.site = StrSave(HostName());
14660 gameInfo.date = PGNDate();
14661 gameInfo.round = StrSave("-");
14662 gameInfo.white = StrSave(first.tidy);
14663 gameInfo.black = StrSave(UserName());
14664 gameInfo.timeControl = TimeControlTagValue();
14667 case MachinePlaysBlack:
14668 gameInfo.event = StrSave( appData.pgnEventHeader );
14669 gameInfo.site = StrSave(HostName());
14670 gameInfo.date = PGNDate();
14671 gameInfo.round = StrSave("-");
14672 gameInfo.white = StrSave(UserName());
14673 gameInfo.black = StrSave(first.tidy);
14674 gameInfo.timeControl = TimeControlTagValue();
14677 case TwoMachinesPlay:
14678 gameInfo.event = StrSave( appData.pgnEventHeader );
14679 gameInfo.site = StrSave(HostName());
14680 gameInfo.date = PGNDate();
14683 snprintf(buf, MSG_SIZ, "%d", roundNr);
14684 gameInfo.round = StrSave(buf);
14686 gameInfo.round = StrSave("-");
14688 if (first.twoMachinesColor[0] == 'w') {
14689 gameInfo.white = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
14690 gameInfo.black = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
14692 gameInfo.white = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
14693 gameInfo.black = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
14695 gameInfo.timeControl = TimeControlTagValue();
14699 gameInfo.event = StrSave("Edited game");
14700 gameInfo.site = StrSave(HostName());
14701 gameInfo.date = PGNDate();
14702 gameInfo.round = StrSave("-");
14703 gameInfo.white = StrSave("-");
14704 gameInfo.black = StrSave("-");
14705 gameInfo.result = r;
14706 gameInfo.resultDetails = p;
14710 gameInfo.event = StrSave("Edited position");
14711 gameInfo.site = StrSave(HostName());
14712 gameInfo.date = PGNDate();
14713 gameInfo.round = StrSave("-");
14714 gameInfo.white = StrSave("-");
14715 gameInfo.black = StrSave("-");
14718 case IcsPlayingWhite:
14719 case IcsPlayingBlack:
14724 case PlayFromGameFile:
14725 gameInfo.event = StrSave("Game from non-PGN file");
14726 gameInfo.site = StrSave(HostName());
14727 gameInfo.date = PGNDate();
14728 gameInfo.round = StrSave("-");
14729 gameInfo.white = StrSave("?");
14730 gameInfo.black = StrSave("?");
14739 ReplaceComment (int index, char *text)
14745 if(index && sscanf(text, "%f/%d", &score, &len) == 2 &&
14746 pvInfoList[index-1].depth == len &&
14747 fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
14748 (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
14749 while (*text == '\n') text++;
14750 len = strlen(text);
14751 while (len > 0 && text[len - 1] == '\n') len--;
14753 if (commentList[index] != NULL)
14754 free(commentList[index]);
14757 commentList[index] = NULL;
14760 if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
14761 *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
14762 *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
14763 commentList[index] = (char *) malloc(len + 2);
14764 strncpy(commentList[index], text, len);
14765 commentList[index][len] = '\n';
14766 commentList[index][len + 1] = NULLCHAR;
14768 // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
14770 commentList[index] = (char *) malloc(len + 7);
14771 safeStrCpy(commentList[index], "{\n", 3);
14772 safeStrCpy(commentList[index]+2, text, len+1);
14773 commentList[index][len+2] = NULLCHAR;
14774 while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
14775 strcat(commentList[index], "\n}\n");
14780 CrushCRs (char *text)
14788 if (ch == '\r') continue;
14790 } while (ch != '\0');
14794 AppendComment (int index, char *text, Boolean addBraces)
14795 /* addBraces tells if we should add {} */
14800 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
14801 text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
14804 while (*text == '\n') text++;
14805 len = strlen(text);
14806 while (len > 0 && text[len - 1] == '\n') len--;
14807 text[len] = NULLCHAR;
14809 if (len == 0) return;
14811 if (commentList[index] != NULL) {
14812 Boolean addClosingBrace = addBraces;
14813 old = commentList[index];
14814 oldlen = strlen(old);
14815 while(commentList[index][oldlen-1] == '\n')
14816 commentList[index][--oldlen] = NULLCHAR;
14817 commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
14818 safeStrCpy(commentList[index], old, oldlen + len + 6);
14820 // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
14821 if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
14822 if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
14823 while (*text == '\n') { text++; len--; }
14824 commentList[index][--oldlen] = NULLCHAR;
14826 if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
14827 else strcat(commentList[index], "\n");
14828 strcat(commentList[index], text);
14829 if(addClosingBrace) strcat(commentList[index], addClosingBrace == 2 ? ")\n" : "\n}\n");
14830 else strcat(commentList[index], "\n");
14832 commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
14834 safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
14835 else commentList[index][0] = NULLCHAR;
14836 strcat(commentList[index], text);
14837 strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
14838 if(addBraces == TRUE) strcat(commentList[index], "}\n");
14843 FindStr (char * text, char * sub_text)
14845 char * result = strstr( text, sub_text );
14847 if( result != NULL ) {
14848 result += strlen( sub_text );
14854 /* [AS] Try to extract PV info from PGN comment */
14855 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
14857 GetInfoFromComment (int index, char * text)
14859 char * sep = text, *p;
14861 if( text != NULL && index > 0 ) {
14864 int time = -1, sec = 0, deci;
14865 char * s_eval = FindStr( text, "[%eval " );
14866 char * s_emt = FindStr( text, "[%emt " );
14868 if( s_eval != NULL || s_emt != NULL ) {
14872 if( s_eval != NULL ) {
14873 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
14877 if( delim != ']' ) {
14882 if( s_emt != NULL ) {
14887 /* We expect something like: [+|-]nnn.nn/dd */
14890 if(*text != '{') return text; // [HGM] braces: must be normal comment
14892 sep = strchr( text, '/' );
14893 if( sep == NULL || sep < (text+4) ) {
14898 if(p[1] == '(') { // comment starts with PV
14899 p = strchr(p, ')'); // locate end of PV
14900 if(p == NULL || sep < p+5) return text;
14901 // at this point we have something like "{(.*) +0.23/6 ..."
14902 p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
14903 *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
14904 // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
14906 time = -1; sec = -1; deci = -1;
14907 if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
14908 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
14909 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
14910 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3 ) {
14914 if( score_lo < 0 || score_lo >= 100 ) {
14918 if(sec >= 0) time = 600*time + 10*sec; else
14919 if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
14921 score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
14923 /* [HGM] PV time: now locate end of PV info */
14924 while( *++sep >= '0' && *sep <= '9'); // strip depth
14926 while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
14928 while( *++sep >= '0' && *sep <= '9'); // strip seconds
14930 while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
14931 while(*sep == ' ' || *sep == '\n' || *sep == '\r') sep++;
14942 pvInfoList[index-1].depth = depth;
14943 pvInfoList[index-1].score = score;
14944 pvInfoList[index-1].time = 10*time; // centi-sec
14945 if(*sep == '}') *sep = 0; else *--sep = '{';
14946 if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
14952 SendToProgram (char *message, ChessProgramState *cps)
14954 int count, outCount, error;
14957 if (cps->pr == NoProc) return;
14960 if (appData.debugMode) {
14963 fprintf(debugFP, "%ld >%-6s: %s",
14964 SubtractTimeMarks(&now, &programStartTime),
14965 cps->which, message);
14968 count = strlen(message);
14969 outCount = OutputToProcess(cps->pr, message, count, &error);
14970 if (outCount < count && !exiting
14971 && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
14972 if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
14973 snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
14974 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
14975 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
14976 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
14977 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
14978 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
14980 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
14981 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
14982 gameInfo.result = res;
14984 gameInfo.resultDetails = StrSave(buf);
14986 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
14987 if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
14992 ReceiveFromProgram (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
14996 ChessProgramState *cps = (ChessProgramState *)closure;
14998 if (isr != cps->isr) return; /* Killed intentionally */
15001 RemoveInputSource(cps->isr);
15002 if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
15003 snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
15004 _(cps->which), cps->program);
15005 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
15006 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
15007 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
15008 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
15009 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
15011 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
15012 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
15013 gameInfo.result = res;
15015 gameInfo.resultDetails = StrSave(buf);
15017 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
15018 if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
15020 snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
15021 _(cps->which), cps->program);
15022 RemoveInputSource(cps->isr);
15024 /* [AS] Program is misbehaving badly... kill it */
15025 if( count == -2 ) {
15026 DestroyChildProcess( cps->pr, 9 );
15030 if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
15035 if ((end_str = strchr(message, '\r')) != NULL)
15036 *end_str = NULLCHAR;
15037 if ((end_str = strchr(message, '\n')) != NULL)
15038 *end_str = NULLCHAR;
15040 if (appData.debugMode) {
15041 TimeMark now; int print = 1;
15042 char *quote = ""; char c; int i;
15044 if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
15045 char start = message[0];
15046 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
15047 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
15048 sscanf(message, "move %c", &c)!=1 && sscanf(message, "offer%c", &c)!=1 &&
15049 sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
15050 sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
15051 sscanf(message, "tell%c", &c)!=1 && sscanf(message, "0-1 %c", &c)!=1 &&
15052 sscanf(message, "1-0 %c", &c)!=1 && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
15053 sscanf(message, "setboard %c", &c)!=1 && sscanf(message, "setup %c", &c)!=1 &&
15054 sscanf(message, "hint: %c", &c)!=1 &&
15055 sscanf(message, "pong %c", &c)!=1 && start != '#') {
15056 quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
15057 print = (appData.engineComments >= 2);
15059 message[0] = start; // restore original message
15063 fprintf(debugFP, "%ld <%-6s: %s%s\n",
15064 SubtractTimeMarks(&now, &programStartTime), cps->which,
15070 /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
15071 if (appData.icsEngineAnalyze) {
15072 if (strstr(message, "whisper") != NULL ||
15073 strstr(message, "kibitz") != NULL ||
15074 strstr(message, "tellics") != NULL) return;
15077 HandleMachineMove(message, cps);
15082 SendTimeControl (ChessProgramState *cps, int mps, long tc, int inc, int sd, int st)
15087 if( timeControl_2 > 0 ) {
15088 if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
15089 tc = timeControl_2;
15092 tc /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
15093 inc /= cps->timeOdds;
15094 st /= cps->timeOdds;
15096 seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
15099 /* Set exact time per move, normally using st command */
15100 if (cps->stKludge) {
15101 /* GNU Chess 4 has no st command; uses level in a nonstandard way */
15103 if (seconds == 0) {
15104 snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
15106 snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
15109 snprintf(buf, MSG_SIZ, "st %d\n", st);
15112 /* Set conventional or incremental time control, using level command */
15113 if (seconds == 0) {
15114 /* Note old gnuchess bug -- minutes:seconds used to not work.
15115 Fixed in later versions, but still avoid :seconds
15116 when seconds is 0. */
15117 snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
15119 snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
15120 seconds, inc/1000.);
15123 SendToProgram(buf, cps);
15125 /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
15126 /* Orthogonally, limit search to given depth */
15128 if (cps->sdKludge) {
15129 snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
15131 snprintf(buf, MSG_SIZ, "sd %d\n", sd);
15133 SendToProgram(buf, cps);
15136 if(cps->nps >= 0) { /* [HGM] nps */
15137 if(cps->supportsNPS == FALSE)
15138 cps->nps = -1; // don't use if engine explicitly says not supported!
15140 snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
15141 SendToProgram(buf, cps);
15146 ChessProgramState *
15148 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
15150 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
15151 gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
15157 SendTimeRemaining (ChessProgramState *cps, int machineWhite)
15159 char message[MSG_SIZ];
15162 /* Note: this routine must be called when the clocks are stopped
15163 or when they have *just* been set or switched; otherwise
15164 it will be off by the time since the current tick started.
15166 if (machineWhite) {
15167 time = whiteTimeRemaining / 10;
15168 otime = blackTimeRemaining / 10;
15170 time = blackTimeRemaining / 10;
15171 otime = whiteTimeRemaining / 10;
15173 /* [HGM] translate opponent's time by time-odds factor */
15174 otime = (otime * cps->other->timeOdds) / cps->timeOdds;
15175 if (appData.debugMode) {
15176 fprintf(debugFP, "time odds: %f %f \n", cps->timeOdds, cps->other->timeOdds);
15179 if (time <= 0) time = 1;
15180 if (otime <= 0) otime = 1;
15182 snprintf(message, MSG_SIZ, "time %ld\n", time);
15183 SendToProgram(message, cps);
15185 snprintf(message, MSG_SIZ, "otim %ld\n", otime);
15186 SendToProgram(message, cps);
15190 BoolFeature (char **p, char *name, int *loc, ChessProgramState *cps)
15193 int len = strlen(name);
15196 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
15198 sscanf(*p, "%d", &val);
15200 while (**p && **p != ' ')
15202 snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15203 SendToProgram(buf, cps);
15210 IntFeature (char **p, char *name, int *loc, ChessProgramState *cps)
15213 int len = strlen(name);
15214 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
15216 sscanf(*p, "%d", loc);
15217 while (**p && **p != ' ') (*p)++;
15218 snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15219 SendToProgram(buf, cps);
15226 StringFeature (char **p, char *name, char loc[], ChessProgramState *cps)
15229 int len = strlen(name);
15230 if (strncmp((*p), name, len) == 0
15231 && (*p)[len] == '=' && (*p)[len+1] == '\"') {
15233 sscanf(*p, "%[^\"]", loc);
15234 while (**p && **p != '\"') (*p)++;
15235 if (**p == '\"') (*p)++;
15236 snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15237 SendToProgram(buf, cps);
15244 ParseOption (Option *opt, ChessProgramState *cps)
15245 // [HGM] options: process the string that defines an engine option, and determine
15246 // name, type, default value, and allowed value range
15248 char *p, *q, buf[MSG_SIZ];
15249 int n, min = (-1)<<31, max = 1<<31, def;
15251 if(p = strstr(opt->name, " -spin ")) {
15252 if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
15253 if(max < min) max = min; // enforce consistency
15254 if(def < min) def = min;
15255 if(def > max) def = max;
15260 } else if((p = strstr(opt->name, " -slider "))) {
15261 // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
15262 if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
15263 if(max < min) max = min; // enforce consistency
15264 if(def < min) def = min;
15265 if(def > max) def = max;
15269 opt->type = Spin; // Slider;
15270 } else if((p = strstr(opt->name, " -string "))) {
15271 opt->textValue = p+9;
15272 opt->type = TextBox;
15273 } else if((p = strstr(opt->name, " -file "))) {
15274 // for now -file is a synonym for -string, to already provide compatibility with future polyglots
15275 opt->textValue = p+7;
15276 opt->type = FileName; // FileName;
15277 } else if((p = strstr(opt->name, " -path "))) {
15278 // for now -file is a synonym for -string, to already provide compatibility with future polyglots
15279 opt->textValue = p+7;
15280 opt->type = PathName; // PathName;
15281 } else if(p = strstr(opt->name, " -check ")) {
15282 if(sscanf(p, " -check %d", &def) < 1) return FALSE;
15283 opt->value = (def != 0);
15284 opt->type = CheckBox;
15285 } else if(p = strstr(opt->name, " -combo ")) {
15286 opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
15287 cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
15288 if(*q == '*') cps->comboList[cps->comboCnt-1]++;
15289 opt->value = n = 0;
15290 while(q = StrStr(q, " /// ")) {
15291 n++; *q = 0; // count choices, and null-terminate each of them
15293 if(*q == '*') { // remember default, which is marked with * prefix
15297 cps->comboList[cps->comboCnt++] = q;
15299 cps->comboList[cps->comboCnt++] = NULL;
15301 opt->type = ComboBox;
15302 } else if(p = strstr(opt->name, " -button")) {
15303 opt->type = Button;
15304 } else if(p = strstr(opt->name, " -save")) {
15305 opt->type = SaveButton;
15306 } else return FALSE;
15307 *p = 0; // terminate option name
15308 // now look if the command-line options define a setting for this engine option.
15309 if(cps->optionSettings && cps->optionSettings[0])
15310 p = strstr(cps->optionSettings, opt->name); else p = NULL;
15311 if(p && (p == cps->optionSettings || p[-1] == ',')) {
15312 snprintf(buf, MSG_SIZ, "option %s", p);
15313 if(p = strstr(buf, ",")) *p = 0;
15314 if(q = strchr(buf, '=')) switch(opt->type) {
15316 for(n=0; n<opt->max; n++)
15317 if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
15320 safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
15324 opt->value = atoi(q+1);
15329 SendToProgram(buf, cps);
15335 FeatureDone (ChessProgramState *cps, int val)
15337 DelayedEventCallback cb = GetDelayedEvent();
15338 if ((cb == InitBackEnd3 && cps == &first) ||
15339 (cb == SettingsMenuIfReady && cps == &second) ||
15340 (cb == LoadEngine) ||
15341 (cb == TwoMachinesEventIfReady)) {
15342 CancelDelayedEvent();
15343 ScheduleDelayedEvent(cb, val ? 1 : 3600000);
15345 cps->initDone = val;
15348 /* Parse feature command from engine */
15350 ParseFeatures (char *args, ChessProgramState *cps)
15358 while (*p == ' ') p++;
15359 if (*p == NULLCHAR) return;
15361 if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
15362 if (BoolFeature(&p, "xedit", &cps->extendedEdit, cps)) continue;
15363 if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
15364 if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
15365 if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
15366 if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
15367 if (BoolFeature(&p, "reuse", &val, cps)) {
15368 /* Engine can disable reuse, but can't enable it if user said no */
15369 if (!val) cps->reuse = FALSE;
15372 if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
15373 if (StringFeature(&p, "myname", cps->tidy, cps)) {
15374 if (gameMode == TwoMachinesPlay) {
15375 DisplayTwoMachinesTitle();
15381 if (StringFeature(&p, "variants", cps->variants, cps)) continue;
15382 if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
15383 if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
15384 if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
15385 if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
15386 if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
15387 if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
15388 if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
15389 if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
15390 if (IntFeature(&p, "done", &val, cps)) {
15391 FeatureDone(cps, val);
15394 /* Added by Tord: */
15395 if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
15396 if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
15397 /* End of additions by Tord */
15399 /* [HGM] added features: */
15400 if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
15401 if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
15402 if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
15403 if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
15404 if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
15405 if (StringFeature(&p, "egt", cps->egtFormats, cps)) continue;
15406 if (StringFeature(&p, "option", cps->option[cps->nrOptions].name, cps)) {
15407 if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
15408 snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
15409 SendToProgram(buf, cps);
15412 if(cps->nrOptions >= MAX_OPTIONS) {
15414 snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
15415 DisplayError(buf, 0);
15419 /* End of additions by HGM */
15421 /* unknown feature: complain and skip */
15423 while (*q && *q != '=') q++;
15424 snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
15425 SendToProgram(buf, cps);
15431 while (*p && *p != '\"') p++;
15432 if (*p == '\"') p++;
15434 while (*p && *p != ' ') p++;
15442 PeriodicUpdatesEvent (int newState)
15444 if (newState == appData.periodicUpdates)
15447 appData.periodicUpdates=newState;
15449 /* Display type changes, so update it now */
15450 // DisplayAnalysis();
15452 /* Get the ball rolling again... */
15454 AnalysisPeriodicEvent(1);
15455 StartAnalysisClock();
15460 PonderNextMoveEvent (int newState)
15462 if (newState == appData.ponderNextMove) return;
15463 if (gameMode == EditPosition) EditPositionDone(TRUE);
15465 SendToProgram("hard\n", &first);
15466 if (gameMode == TwoMachinesPlay) {
15467 SendToProgram("hard\n", &second);
15470 SendToProgram("easy\n", &first);
15471 thinkOutput[0] = NULLCHAR;
15472 if (gameMode == TwoMachinesPlay) {
15473 SendToProgram("easy\n", &second);
15476 appData.ponderNextMove = newState;
15480 NewSettingEvent (int option, int *feature, char *command, int value)
15484 if (gameMode == EditPosition) EditPositionDone(TRUE);
15485 snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
15486 if(feature == NULL || *feature) SendToProgram(buf, &first);
15487 if (gameMode == TwoMachinesPlay) {
15488 if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
15493 ShowThinkingEvent ()
15494 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
15496 static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
15497 int newState = appData.showThinking
15498 // [HGM] thinking: other features now need thinking output as well
15499 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
15501 if (oldState == newState) return;
15502 oldState = newState;
15503 if (gameMode == EditPosition) EditPositionDone(TRUE);
15505 SendToProgram("post\n", &first);
15506 if (gameMode == TwoMachinesPlay) {
15507 SendToProgram("post\n", &second);
15510 SendToProgram("nopost\n", &first);
15511 thinkOutput[0] = NULLCHAR;
15512 if (gameMode == TwoMachinesPlay) {
15513 SendToProgram("nopost\n", &second);
15516 // appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
15520 AskQuestionEvent (char *title, char *question, char *replyPrefix, char *which)
15522 ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
15523 if (pr == NoProc) return;
15524 AskQuestion(title, question, replyPrefix, pr);
15528 TypeInEvent (char firstChar)
15530 if ((gameMode == BeginningOfGame && !appData.icsActive) ||
15531 gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
15532 gameMode == AnalyzeMode || gameMode == EditGame ||
15533 gameMode == EditPosition || gameMode == IcsExamining ||
15534 gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
15535 isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes
15536 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||
15537 gameMode == IcsObserving || gameMode == TwoMachinesPlay ) ||
15538 gameMode == Training) PopUpMoveDialog(firstChar);
15542 TypeInDoneEvent (char *move)
15545 int n, fromX, fromY, toX, toY;
15547 ChessMove moveType;
15550 if(gameMode == EditPosition && ParseFEN(board, &n, move) ) {
15551 EditPositionPasteFEN(move);
15554 // [HGM] movenum: allow move number to be typed in any mode
15555 if(sscanf(move, "%d", &n) == 1 && n != 0 ) {
15560 if (gameMode != EditGame && currentMove != forwardMostMove &&
15561 gameMode != Training) {
15562 DisplayMoveError(_("Displayed move is not current"));
15564 int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
15565 &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
15566 if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized
15567 if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
15568 &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {
15569 UserMoveEvent(fromX, fromY, toX, toY, promoChar);
15571 DisplayMoveError(_("Could not parse move"));
15577 DisplayMove (int moveNumber)
15579 char message[MSG_SIZ];
15581 char cpThinkOutput[MSG_SIZ];
15583 if(appData.noGUI) return; // [HGM] fast: suppress display of moves
15585 if (moveNumber == forwardMostMove - 1 ||
15586 gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15588 safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
15590 if (strchr(cpThinkOutput, '\n')) {
15591 *strchr(cpThinkOutput, '\n') = NULLCHAR;
15594 *cpThinkOutput = NULLCHAR;
15597 /* [AS] Hide thinking from human user */
15598 if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
15599 *cpThinkOutput = NULLCHAR;
15600 if( thinkOutput[0] != NULLCHAR ) {
15603 for( i=0; i<=hiddenThinkOutputState; i++ ) {
15604 cpThinkOutput[i] = '.';
15606 cpThinkOutput[i] = NULLCHAR;
15607 hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
15611 if (moveNumber == forwardMostMove - 1 &&
15612 gameInfo.resultDetails != NULL) {
15613 if (gameInfo.resultDetails[0] == NULLCHAR) {
15614 snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
15616 snprintf(res, MSG_SIZ, " {%s} %s",
15617 T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
15623 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
15624 DisplayMessage(res, cpThinkOutput);
15626 snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
15627 WhiteOnMove(moveNumber) ? " " : ".. ",
15628 parseList[moveNumber], res);
15629 DisplayMessage(message, cpThinkOutput);
15634 DisplayComment (int moveNumber, char *text)
15636 char title[MSG_SIZ];
15638 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
15639 safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
15641 snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
15642 WhiteOnMove(moveNumber) ? " " : ".. ",
15643 parseList[moveNumber]);
15645 if (text != NULL && (appData.autoDisplayComment || commentUp))
15646 CommentPopUp(title, text);
15649 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
15650 * might be busy thinking or pondering. It can be omitted if your
15651 * gnuchess is configured to stop thinking immediately on any user
15652 * input. However, that gnuchess feature depends on the FIONREAD
15653 * ioctl, which does not work properly on some flavors of Unix.
15656 Attention (ChessProgramState *cps)
15659 if (!cps->useSigint) return;
15660 if (appData.noChessProgram || (cps->pr == NoProc)) return;
15661 switch (gameMode) {
15662 case MachinePlaysWhite:
15663 case MachinePlaysBlack:
15664 case TwoMachinesPlay:
15665 case IcsPlayingWhite:
15666 case IcsPlayingBlack:
15669 /* Skip if we know it isn't thinking */
15670 if (!cps->maybeThinking) return;
15671 if (appData.debugMode)
15672 fprintf(debugFP, "Interrupting %s\n", cps->which);
15673 InterruptChildProcess(cps->pr);
15674 cps->maybeThinking = FALSE;
15679 #endif /*ATTENTION*/
15685 if (whiteTimeRemaining <= 0) {
15688 if (appData.icsActive) {
15689 if (appData.autoCallFlag &&
15690 gameMode == IcsPlayingBlack && !blackFlag) {
15691 SendToICS(ics_prefix);
15692 SendToICS("flag\n");
15696 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
15698 if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
15699 if (appData.autoCallFlag) {
15700 GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
15707 if (blackTimeRemaining <= 0) {
15710 if (appData.icsActive) {
15711 if (appData.autoCallFlag &&
15712 gameMode == IcsPlayingWhite && !whiteFlag) {
15713 SendToICS(ics_prefix);
15714 SendToICS("flag\n");
15718 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
15720 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
15721 if (appData.autoCallFlag) {
15722 GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
15733 CheckTimeControl ()
15735 if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
15736 gameMode == PlayFromGameFile || forwardMostMove == 0) return;
15739 * add time to clocks when time control is achieved ([HGM] now also used for increment)
15741 if ( !WhiteOnMove(forwardMostMove) ) {
15742 /* White made time control */
15743 lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
15744 whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
15745 /* [HGM] time odds: correct new time quota for time odds! */
15746 / WhitePlayer()->timeOdds;
15747 lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
15749 lastBlack -= blackTimeRemaining;
15750 /* Black made time control */
15751 blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
15752 / WhitePlayer()->other->timeOdds;
15753 lastWhite = whiteTimeRemaining;
15758 DisplayBothClocks ()
15760 int wom = gameMode == EditPosition ?
15761 !blackPlaysFirst : WhiteOnMove(currentMove);
15762 DisplayWhiteClock(whiteTimeRemaining, wom);
15763 DisplayBlackClock(blackTimeRemaining, !wom);
15767 /* Timekeeping seems to be a portability nightmare. I think everyone
15768 has ftime(), but I'm really not sure, so I'm including some ifdefs
15769 to use other calls if you don't. Clocks will be less accurate if
15770 you have neither ftime nor gettimeofday.
15773 /* VS 2008 requires the #include outside of the function */
15774 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
15775 #include <sys/timeb.h>
15778 /* Get the current time as a TimeMark */
15780 GetTimeMark (TimeMark *tm)
15782 #if HAVE_GETTIMEOFDAY
15784 struct timeval timeVal;
15785 struct timezone timeZone;
15787 gettimeofday(&timeVal, &timeZone);
15788 tm->sec = (long) timeVal.tv_sec;
15789 tm->ms = (int) (timeVal.tv_usec / 1000L);
15791 #else /*!HAVE_GETTIMEOFDAY*/
15794 // include <sys/timeb.h> / moved to just above start of function
15795 struct timeb timeB;
15798 tm->sec = (long) timeB.time;
15799 tm->ms = (int) timeB.millitm;
15801 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
15802 tm->sec = (long) time(NULL);
15808 /* Return the difference in milliseconds between two
15809 time marks. We assume the difference will fit in a long!
15812 SubtractTimeMarks (TimeMark *tm2, TimeMark *tm1)
15814 return 1000L*(tm2->sec - tm1->sec) +
15815 (long) (tm2->ms - tm1->ms);
15820 * Code to manage the game clocks.
15822 * In tournament play, black starts the clock and then white makes a move.
15823 * We give the human user a slight advantage if he is playing white---the
15824 * clocks don't run until he makes his first move, so it takes zero time.
15825 * Also, we don't account for network lag, so we could get out of sync
15826 * with GNU Chess's clock -- but then, referees are always right.
15829 static TimeMark tickStartTM;
15830 static long intendedTickLength;
15833 NextTickLength (long timeRemaining)
15835 long nominalTickLength, nextTickLength;
15837 if (timeRemaining > 0L && timeRemaining <= 10000L)
15838 nominalTickLength = 100L;
15840 nominalTickLength = 1000L;
15841 nextTickLength = timeRemaining % nominalTickLength;
15842 if (nextTickLength <= 0) nextTickLength += nominalTickLength;
15844 return nextTickLength;
15847 /* Adjust clock one minute up or down */
15849 AdjustClock (Boolean which, int dir)
15851 if(appData.autoCallFlag) { DisplayError(_("Clock adjustment not allowed in auto-flag mode"), 0); return; }
15852 if(which) blackTimeRemaining += 60000*dir;
15853 else whiteTimeRemaining += 60000*dir;
15854 DisplayBothClocks();
15855 adjustedClock = TRUE;
15858 /* Stop clocks and reset to a fresh time control */
15862 (void) StopClockTimer();
15863 if (appData.icsActive) {
15864 whiteTimeRemaining = blackTimeRemaining = 0;
15865 } else if (searchTime) {
15866 whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
15867 blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
15868 } else { /* [HGM] correct new time quote for time odds */
15869 whiteTC = blackTC = fullTimeControlString;
15870 whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
15871 blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
15873 if (whiteFlag || blackFlag) {
15875 whiteFlag = blackFlag = FALSE;
15877 lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
15878 DisplayBothClocks();
15879 adjustedClock = FALSE;
15882 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
15884 /* Decrement running clock by amount of time that has passed */
15888 long timeRemaining;
15889 long lastTickLength, fudge;
15892 if (!appData.clockMode) return;
15893 if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
15897 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
15899 /* Fudge if we woke up a little too soon */
15900 fudge = intendedTickLength - lastTickLength;
15901 if (fudge < 0 || fudge > FUDGE) fudge = 0;
15903 if (WhiteOnMove(forwardMostMove)) {
15904 if(whiteNPS >= 0) lastTickLength = 0;
15905 timeRemaining = whiteTimeRemaining -= lastTickLength;
15906 if(timeRemaining < 0 && !appData.icsActive) {
15907 GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
15908 if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
15909 whiteStartMove = forwardMostMove; whiteTC = nextSession;
15910 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
15913 DisplayWhiteClock(whiteTimeRemaining - fudge,
15914 WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
15916 if(blackNPS >= 0) lastTickLength = 0;
15917 timeRemaining = blackTimeRemaining -= lastTickLength;
15918 if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
15919 GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
15921 blackStartMove = forwardMostMove;
15922 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
15925 DisplayBlackClock(blackTimeRemaining - fudge,
15926 !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
15928 if (CheckFlags()) return;
15931 intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
15932 StartClockTimer(intendedTickLength);
15934 /* if the time remaining has fallen below the alarm threshold, sound the
15935 * alarm. if the alarm has sounded and (due to a takeback or time control
15936 * with increment) the time remaining has increased to a level above the
15937 * threshold, reset the alarm so it can sound again.
15940 if (appData.icsActive && appData.icsAlarm) {
15942 /* make sure we are dealing with the user's clock */
15943 if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
15944 ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
15947 if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
15948 alarmSounded = FALSE;
15949 } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
15951 alarmSounded = TRUE;
15957 /* A player has just moved, so stop the previously running
15958 clock and (if in clock mode) start the other one.
15959 We redisplay both clocks in case we're in ICS mode, because
15960 ICS gives us an update to both clocks after every move.
15961 Note that this routine is called *after* forwardMostMove
15962 is updated, so the last fractional tick must be subtracted
15963 from the color that is *not* on move now.
15966 SwitchClocks (int newMoveNr)
15968 long lastTickLength;
15970 int flagged = FALSE;
15974 if (StopClockTimer() && appData.clockMode) {
15975 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
15976 if (!WhiteOnMove(forwardMostMove)) {
15977 if(blackNPS >= 0) lastTickLength = 0;
15978 blackTimeRemaining -= lastTickLength;
15979 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
15980 // if(pvInfoList[forwardMostMove].time == -1)
15981 pvInfoList[forwardMostMove].time = // use GUI time
15982 (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
15984 if(whiteNPS >= 0) lastTickLength = 0;
15985 whiteTimeRemaining -= lastTickLength;
15986 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
15987 // if(pvInfoList[forwardMostMove].time == -1)
15988 pvInfoList[forwardMostMove].time =
15989 (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
15991 flagged = CheckFlags();
15993 forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
15994 CheckTimeControl();
15996 if (flagged || !appData.clockMode) return;
15998 switch (gameMode) {
15999 case MachinePlaysBlack:
16000 case MachinePlaysWhite:
16001 case BeginningOfGame:
16002 if (pausing) return;
16006 case PlayFromGameFile:
16014 if (searchTime) { // [HGM] st: set clock of player that has to move to max time
16015 if(WhiteOnMove(forwardMostMove))
16016 whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
16017 else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
16021 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
16022 whiteTimeRemaining : blackTimeRemaining);
16023 StartClockTimer(intendedTickLength);
16027 /* Stop both clocks */
16031 long lastTickLength;
16034 if (!StopClockTimer()) return;
16035 if (!appData.clockMode) return;
16039 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16040 if (WhiteOnMove(forwardMostMove)) {
16041 if(whiteNPS >= 0) lastTickLength = 0;
16042 whiteTimeRemaining -= lastTickLength;
16043 DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
16045 if(blackNPS >= 0) lastTickLength = 0;
16046 blackTimeRemaining -= lastTickLength;
16047 DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
16052 /* Start clock of player on move. Time may have been reset, so
16053 if clock is already running, stop and restart it. */
16057 (void) StopClockTimer(); /* in case it was running already */
16058 DisplayBothClocks();
16059 if (CheckFlags()) return;
16061 if (!appData.clockMode) return;
16062 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
16064 GetTimeMark(&tickStartTM);
16065 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
16066 whiteTimeRemaining : blackTimeRemaining);
16068 /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
16069 whiteNPS = blackNPS = -1;
16070 if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
16071 || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
16072 whiteNPS = first.nps;
16073 if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
16074 || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
16075 blackNPS = first.nps;
16076 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
16077 whiteNPS = second.nps;
16078 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
16079 blackNPS = second.nps;
16080 if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
16082 StartClockTimer(intendedTickLength);
16086 TimeString (long ms)
16088 long second, minute, hour, day;
16090 static char buf[32];
16092 if (ms > 0 && ms <= 9900) {
16093 /* convert milliseconds to tenths, rounding up */
16094 double tenths = floor( ((double)(ms + 99L)) / 100.00 );
16096 snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
16100 /* convert milliseconds to seconds, rounding up */
16101 /* use floating point to avoid strangeness of integer division
16102 with negative dividends on many machines */
16103 second = (long) floor(((double) (ms + 999L)) / 1000.0);
16110 day = second / (60 * 60 * 24);
16111 second = second % (60 * 60 * 24);
16112 hour = second / (60 * 60);
16113 second = second % (60 * 60);
16114 minute = second / 60;
16115 second = second % 60;
16118 snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
16119 sign, day, hour, minute, second);
16121 snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
16123 snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
16130 * This is necessary because some C libraries aren't ANSI C compliant yet.
16133 StrStr (char *string, char *match)
16137 length = strlen(match);
16139 for (i = strlen(string) - length; i >= 0; i--, string++)
16140 if (!strncmp(match, string, length))
16147 StrCaseStr (char *string, char *match)
16151 length = strlen(match);
16153 for (i = strlen(string) - length; i >= 0; i--, string++) {
16154 for (j = 0; j < length; j++) {
16155 if (ToLower(match[j]) != ToLower(string[j]))
16158 if (j == length) return string;
16166 StrCaseCmp (char *s1, char *s2)
16171 c1 = ToLower(*s1++);
16172 c2 = ToLower(*s2++);
16173 if (c1 > c2) return 1;
16174 if (c1 < c2) return -1;
16175 if (c1 == NULLCHAR) return 0;
16183 return isupper(c) ? tolower(c) : c;
16190 return islower(c) ? toupper(c) : c;
16192 #endif /* !_amigados */
16199 if ((ret = (char *) malloc(strlen(s) + 1)))
16201 safeStrCpy(ret, s, strlen(s)+1);
16207 StrSavePtr (char *s, char **savePtr)
16212 if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
16213 safeStrCpy(*savePtr, s, strlen(s)+1);
16225 clock = time((time_t *)NULL);
16226 tm = localtime(&clock);
16227 snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
16228 tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
16229 return StrSave(buf);
16234 PositionToFEN (int move, char *overrideCastling)
16236 int i, j, fromX, fromY, toX, toY;
16243 whiteToPlay = (gameMode == EditPosition) ?
16244 !blackPlaysFirst : (move % 2 == 0);
16247 /* Piece placement data */
16248 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
16249 if(MSG_SIZ - (p - buf) < BOARD_RGHT - BOARD_LEFT + 20) { *p = 0; return StrSave(buf); }
16251 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
16252 if (boards[move][i][j] == EmptySquare) {
16254 } else { ChessSquare piece = boards[move][i][j];
16255 if (emptycount > 0) {
16256 if(emptycount<10) /* [HGM] can be >= 10 */
16257 *p++ = '0' + emptycount;
16258 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
16261 if(PieceToChar(piece) == '+') {
16262 /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
16264 piece = (ChessSquare)(DEMOTED piece);
16266 *p++ = PieceToChar(piece);
16268 /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
16269 p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
16274 if (emptycount > 0) {
16275 if(emptycount<10) /* [HGM] can be >= 10 */
16276 *p++ = '0' + emptycount;
16277 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
16284 /* [HGM] print Crazyhouse or Shogi holdings */
16285 if( gameInfo.holdingsWidth ) {
16286 *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
16288 for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
16289 piece = boards[move][i][BOARD_WIDTH-1];
16290 if( piece != EmptySquare )
16291 for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
16292 *p++ = PieceToChar(piece);
16294 for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
16295 piece = boards[move][BOARD_HEIGHT-i-1][0];
16296 if( piece != EmptySquare )
16297 for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
16298 *p++ = PieceToChar(piece);
16301 if( q == p ) *p++ = '-';
16307 *p++ = whiteToPlay ? 'w' : 'b';
16310 if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
16311 while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
16313 if(nrCastlingRights) {
16315 if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
16316 /* [HGM] write directly from rights */
16317 if(boards[move][CASTLING][2] != NoRights &&
16318 boards[move][CASTLING][0] != NoRights )
16319 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
16320 if(boards[move][CASTLING][2] != NoRights &&
16321 boards[move][CASTLING][1] != NoRights )
16322 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
16323 if(boards[move][CASTLING][5] != NoRights &&
16324 boards[move][CASTLING][3] != NoRights )
16325 *p++ = boards[move][CASTLING][3] + AAA;
16326 if(boards[move][CASTLING][5] != NoRights &&
16327 boards[move][CASTLING][4] != NoRights )
16328 *p++ = boards[move][CASTLING][4] + AAA;
16331 /* [HGM] write true castling rights */
16332 if( nrCastlingRights == 6 ) {
16333 if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
16334 boards[move][CASTLING][2] != NoRights ) *p++ = 'K';
16335 if(boards[move][CASTLING][1] == BOARD_LEFT &&
16336 boards[move][CASTLING][2] != NoRights ) *p++ = 'Q';
16337 if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
16338 boards[move][CASTLING][5] != NoRights ) *p++ = 'k';
16339 if(boards[move][CASTLING][4] == BOARD_LEFT &&
16340 boards[move][CASTLING][5] != NoRights ) *p++ = 'q';
16343 if (q == p) *p++ = '-'; /* No castling rights */
16347 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
16348 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
16349 /* En passant target square */
16350 if (move > backwardMostMove) {
16351 fromX = moveList[move - 1][0] - AAA;
16352 fromY = moveList[move - 1][1] - ONE;
16353 toX = moveList[move - 1][2] - AAA;
16354 toY = moveList[move - 1][3] - ONE;
16355 if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
16356 toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
16357 boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
16359 /* 2-square pawn move just happened */
16361 *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
16365 } else if(move == backwardMostMove) {
16366 // [HGM] perhaps we should always do it like this, and forget the above?
16367 if((signed char)boards[move][EP_STATUS] >= 0) {
16368 *p++ = boards[move][EP_STATUS] + AAA;
16369 *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
16380 /* [HGM] find reversible plies */
16381 { int i = 0, j=move;
16383 if (appData.debugMode) { int k;
16384 fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
16385 for(k=backwardMostMove; k<=forwardMostMove; k++)
16386 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
16390 while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
16391 if( j == backwardMostMove ) i += initialRulePlies;
16392 sprintf(p, "%d ", i);
16393 p += i>=100 ? 4 : i >= 10 ? 3 : 2;
16395 /* Fullmove number */
16396 sprintf(p, "%d", (move / 2) + 1);
16398 return StrSave(buf);
16402 ParseFEN (Board board, int *blackPlaysFirst, char *fen)
16411 /* [HGM] by default clear Crazyhouse holdings, if present */
16412 if(gameInfo.holdingsWidth) {
16413 for(i=0; i<BOARD_HEIGHT; i++) {
16414 board[i][0] = EmptySquare; /* black holdings */
16415 board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
16416 board[i][1] = (ChessSquare) 0; /* black counts */
16417 board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
16421 /* Piece placement data */
16422 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
16425 if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
16426 if (*p == '/') p++;
16427 emptycount = gameInfo.boardWidth - j;
16428 while (emptycount--)
16429 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
16431 #if(BOARD_FILES >= 10)
16432 } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
16433 p++; emptycount=10;
16434 if (j + emptycount > gameInfo.boardWidth) return FALSE;
16435 while (emptycount--)
16436 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
16438 } else if (isdigit(*p)) {
16439 emptycount = *p++ - '0';
16440 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
16441 if (j + emptycount > gameInfo.boardWidth) return FALSE;
16442 while (emptycount--)
16443 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
16444 } else if (*p == '+' || isalpha(*p)) {
16445 if (j >= gameInfo.boardWidth) return FALSE;
16447 piece = CharToPiece(*++p);
16448 if(piece == EmptySquare) return FALSE; /* unknown piece */
16449 piece = (ChessSquare) (PROMOTED piece ); p++;
16450 if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
16451 } else piece = CharToPiece(*p++);
16453 if(piece==EmptySquare) return FALSE; /* unknown piece */
16454 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
16455 piece = (ChessSquare) (PROMOTED piece);
16456 if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
16459 board[i][(j++)+gameInfo.holdingsWidth] = piece;
16465 while (*p == '/' || *p == ' ') p++;
16467 /* [HGM] look for Crazyhouse holdings here */
16468 while(*p==' ') p++;
16469 if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
16471 if(*p == '-' ) p++; /* empty holdings */ else {
16472 if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
16473 /* if we would allow FEN reading to set board size, we would */
16474 /* have to add holdings and shift the board read so far here */
16475 while( (piece = CharToPiece(*p) ) != EmptySquare ) {
16477 if((int) piece >= (int) BlackPawn ) {
16478 i = (int)piece - (int)BlackPawn;
16479 i = PieceToNumber((ChessSquare)i);
16480 if( i >= gameInfo.holdingsSize ) return FALSE;
16481 board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
16482 board[BOARD_HEIGHT-1-i][1]++; /* black counts */
16484 i = (int)piece - (int)WhitePawn;
16485 i = PieceToNumber((ChessSquare)i);
16486 if( i >= gameInfo.holdingsSize ) return FALSE;
16487 board[i][BOARD_WIDTH-1] = piece; /* white holdings */
16488 board[i][BOARD_WIDTH-2]++; /* black holdings */
16495 while(*p == ' ') p++;
16499 if(appData.colorNickNames) {
16500 if( c == appData.colorNickNames[0] ) c = 'w'; else
16501 if( c == appData.colorNickNames[1] ) c = 'b';
16505 *blackPlaysFirst = FALSE;
16508 *blackPlaysFirst = TRUE;
16514 /* [HGM] We NO LONGER ignore the rest of the FEN notation */
16515 /* return the extra info in global variiables */
16517 /* set defaults in case FEN is incomplete */
16518 board[EP_STATUS] = EP_UNKNOWN;
16519 for(i=0; i<nrCastlingRights; i++ ) {
16520 board[CASTLING][i] =
16521 gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
16522 } /* assume possible unless obviously impossible */
16523 if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
16524 if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
16525 if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
16526 && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
16527 if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
16528 if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
16529 if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
16530 && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
16533 while(*p==' ') p++;
16534 if(nrCastlingRights) {
16535 if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
16536 /* castling indicator present, so default becomes no castlings */
16537 for(i=0; i<nrCastlingRights; i++ ) {
16538 board[CASTLING][i] = NoRights;
16541 while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
16542 (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
16543 ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
16544 ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth) ) {
16545 char c = *p++; int whiteKingFile=NoRights, blackKingFile=NoRights;
16547 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
16548 if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
16549 if(board[0 ][i] == WhiteKing) whiteKingFile = i;
16551 if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
16552 whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
16553 if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
16554 && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
16555 if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
16556 && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
16559 for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
16560 board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
16561 board[CASTLING][2] = whiteKingFile;
16564 for(i=BOARD_LEFT; i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
16565 board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
16566 board[CASTLING][2] = whiteKingFile;
16569 for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
16570 board[CASTLING][3] = i != blackKingFile ? i : NoRights;
16571 board[CASTLING][5] = blackKingFile;
16574 for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
16575 board[CASTLING][4] = i != blackKingFile ? i : NoRights;
16576 board[CASTLING][5] = blackKingFile;
16579 default: /* FRC castlings */
16580 if(c >= 'a') { /* black rights */
16581 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
16582 if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
16583 if(i == BOARD_RGHT) break;
16584 board[CASTLING][5] = i;
16586 if(board[BOARD_HEIGHT-1][c] < BlackPawn ||
16587 board[BOARD_HEIGHT-1][c] >= BlackKing ) break;
16589 board[CASTLING][3] = c;
16591 board[CASTLING][4] = c;
16592 } else { /* white rights */
16593 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
16594 if(board[0][i] == WhiteKing) break;
16595 if(i == BOARD_RGHT) break;
16596 board[CASTLING][2] = i;
16597 c -= AAA - 'a' + 'A';
16598 if(board[0][c] >= WhiteKing) break;
16600 board[CASTLING][0] = c;
16602 board[CASTLING][1] = c;
16606 for(i=0; i<nrCastlingRights; i++)
16607 if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
16608 if (appData.debugMode) {
16609 fprintf(debugFP, "FEN castling rights:");
16610 for(i=0; i<nrCastlingRights; i++)
16611 fprintf(debugFP, " %d", board[CASTLING][i]);
16612 fprintf(debugFP, "\n");
16615 while(*p==' ') p++;
16618 /* read e.p. field in games that know e.p. capture */
16619 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
16620 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
16622 p++; board[EP_STATUS] = EP_NONE;
16624 char c = *p++ - AAA;
16626 if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
16627 if(*p >= '0' && *p <='9') p++;
16628 board[EP_STATUS] = c;
16633 if(sscanf(p, "%d", &i) == 1) {
16634 FENrulePlies = i; /* 50-move ply counter */
16635 /* (The move number is still ignored) */
16642 EditPositionPasteFEN (char *fen)
16645 Board initial_position;
16647 if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
16648 DisplayError(_("Bad FEN position in clipboard"), 0);
16651 int savedBlackPlaysFirst = blackPlaysFirst;
16652 EditPositionEvent();
16653 blackPlaysFirst = savedBlackPlaysFirst;
16654 CopyBoard(boards[0], initial_position);
16655 initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
16656 EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
16657 DisplayBothClocks();
16658 DrawPosition(FALSE, boards[currentMove]);
16663 static char cseq[12] = "\\ ";
16666 set_cont_sequence (char *new_seq)
16671 // handle bad attempts to set the sequence
16673 return 0; // acceptable error - no debug
16675 len = strlen(new_seq);
16676 ret = (len > 0) && (len < sizeof(cseq));
16678 safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
16679 else if (appData.debugMode)
16680 fprintf(debugFP, "Invalid continuation sequence \"%s\" (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
16685 reformat a source message so words don't cross the width boundary. internal
16686 newlines are not removed. returns the wrapped size (no null character unless
16687 included in source message). If dest is NULL, only calculate the size required
16688 for the dest buffer. lp argument indicats line position upon entry, and it's
16689 passed back upon exit.
16692 wrap (char *dest, char *src, int count, int width, int *lp)
16694 int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
16696 cseq_len = strlen(cseq);
16697 old_line = line = *lp;
16698 ansi = len = clen = 0;
16700 for (i=0; i < count; i++)
16702 if (src[i] == '\033')
16705 // if we hit the width, back up
16706 if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
16708 // store i & len in case the word is too long
16709 old_i = i, old_len = len;
16711 // find the end of the last word
16712 while (i && src[i] != ' ' && src[i] != '\n')
16718 // word too long? restore i & len before splitting it
16719 if ((old_i-i+clen) >= width)
16726 if (i && src[i-1] == ' ')
16729 if (src[i] != ' ' && src[i] != '\n')
16736 // now append the newline and continuation sequence
16741 strncpy(dest+len, cseq, cseq_len);
16749 dest[len] = src[i];
16753 if (src[i] == '\n')
16758 if (dest && appData.debugMode)
16760 fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
16761 count, width, line, len, *lp);
16762 show_bytes(debugFP, src, count);
16763 fprintf(debugFP, "\ndest: ");
16764 show_bytes(debugFP, dest, len);
16765 fprintf(debugFP, "\n");
16767 *lp = dest ? line : old_line;
16772 // [HGM] vari: routines for shelving variations
16773 Boolean modeRestore = FALSE;
16776 PushInner (int firstMove, int lastMove)
16778 int i, j, nrMoves = lastMove - firstMove;
16780 // push current tail of game on stack
16781 savedResult[storedGames] = gameInfo.result;
16782 savedDetails[storedGames] = gameInfo.resultDetails;
16783 gameInfo.resultDetails = NULL;
16784 savedFirst[storedGames] = firstMove;
16785 savedLast [storedGames] = lastMove;
16786 savedFramePtr[storedGames] = framePtr;
16787 framePtr -= nrMoves; // reserve space for the boards
16788 for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
16789 CopyBoard(boards[framePtr+i], boards[firstMove+i]);
16790 for(j=0; j<MOVE_LEN; j++)
16791 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
16792 for(j=0; j<2*MOVE_LEN; j++)
16793 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
16794 timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
16795 timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
16796 pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
16797 pvInfoList[firstMove+i-1].depth = 0;
16798 commentList[framePtr+i] = commentList[firstMove+i];
16799 commentList[firstMove+i] = NULL;
16803 forwardMostMove = firstMove; // truncate game so we can start variation
16807 PushTail (int firstMove, int lastMove)
16809 if(appData.icsActive) { // only in local mode
16810 forwardMostMove = currentMove; // mimic old ICS behavior
16813 if(storedGames >= MAX_VARIATIONS-2) return; // leave one for PV-walk
16815 PushInner(firstMove, lastMove);
16816 if(storedGames == 1) GreyRevert(FALSE);
16817 if(gameMode == PlayFromGameFile) gameMode = EditGame, modeRestore = TRUE;
16821 PopInner (Boolean annotate)
16824 char buf[8000], moveBuf[20];
16826 ToNrEvent(savedFirst[storedGames-1]); // sets currentMove
16827 storedGames--; // do this after ToNrEvent, to make sure HistorySet will refresh entire game after PopInner returns
16828 nrMoves = savedLast[storedGames] - currentMove;
16831 if(!WhiteOnMove(currentMove))
16832 snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
16833 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
16834 for(i=currentMove; i<forwardMostMove; i++) {
16836 snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
16837 else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
16838 strcat(buf, moveBuf);
16839 if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
16840 if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
16844 for(i=1; i<=nrMoves; i++) { // copy last variation back
16845 CopyBoard(boards[currentMove+i], boards[framePtr+i]);
16846 for(j=0; j<MOVE_LEN; j++)
16847 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
16848 for(j=0; j<2*MOVE_LEN; j++)
16849 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
16850 timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
16851 timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
16852 pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
16853 if(commentList[currentMove+i]) free(commentList[currentMove+i]);
16854 commentList[currentMove+i] = commentList[framePtr+i];
16855 commentList[framePtr+i] = NULL;
16857 if(annotate) AppendComment(currentMove+1, buf, FALSE);
16858 framePtr = savedFramePtr[storedGames];
16859 gameInfo.result = savedResult[storedGames];
16860 if(gameInfo.resultDetails != NULL) {
16861 free(gameInfo.resultDetails);
16863 gameInfo.resultDetails = savedDetails[storedGames];
16864 forwardMostMove = currentMove + nrMoves;
16868 PopTail (Boolean annotate)
16870 if(appData.icsActive) return FALSE; // only in local mode
16871 if(!storedGames) return FALSE; // sanity
16872 CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
16874 PopInner(annotate);
16875 if(currentMove < forwardMostMove) ForwardEvent(); else
16876 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
16878 if(storedGames == 0) { GreyRevert(TRUE); if(modeRestore) modeRestore = FALSE, gameMode = PlayFromGameFile; }
16884 { // remove all shelved variations
16886 for(i=0; i<storedGames; i++) {
16887 if(savedDetails[i])
16888 free(savedDetails[i]);
16889 savedDetails[i] = NULL;
16891 for(i=framePtr; i<MAX_MOVES; i++) {
16892 if(commentList[i]) free(commentList[i]);
16893 commentList[i] = NULL;
16895 framePtr = MAX_MOVES-1;
16900 LoadVariation (int index, char *text)
16901 { // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
16902 char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
16903 int level = 0, move;
16905 if(gameMode != EditGame && gameMode != AnalyzeMode && gameMode != PlayFromGameFile) return;
16906 // first find outermost bracketing variation
16907 while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
16908 if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
16909 if(*p == '{') wait = '}'; else
16910 if(*p == '[') wait = ']'; else
16911 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
16912 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
16914 if(*p == wait) wait = NULLCHAR; // closing ]} found
16917 if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
16918 if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
16919 end[1] = NULLCHAR; // clip off comment beyond variation
16920 ToNrEvent(currentMove-1);
16921 PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
16922 // kludge: use ParsePV() to append variation to game
16923 move = currentMove;
16924 ParsePV(start, TRUE, TRUE);
16925 forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
16926 ClearPremoveHighlights();
16928 ToNrEvent(currentMove+1);