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 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 #define DoSleep( n ) if( (n) != 0 ) Sleep( (n) );
62 #define DoSleep( n ) if( (n) >= 0) sleep(n)
72 #include <sys/types.h>
81 #else /* not STDC_HEADERS */
84 # else /* not HAVE_STRING_H */
86 # endif /* not HAVE_STRING_H */
87 #endif /* not STDC_HEADERS */
90 # include <sys/fcntl.h>
91 #else /* not HAVE_SYS_FCNTL_H */
94 # endif /* HAVE_FCNTL_H */
95 #endif /* not HAVE_SYS_FCNTL_H */
97 #if TIME_WITH_SYS_TIME
98 # include <sys/time.h>
102 # include <sys/time.h>
108 #if defined(_amigados) && !defined(__GNUC__)
113 extern int gettimeofday(struct timeval *, struct timezone *);
121 #include "frontend.h"
128 #include "backendz.h"
132 # define _(s) gettext (s)
133 # define N_(s) gettext_noop (s)
140 /* A point in time */
142 long sec; /* Assuming this is >= 32 bits */
143 int ms; /* Assuming this is >= 16 bits */
146 int establish P((void));
147 void read_from_player P((InputSourceRef isr, VOIDSTAR closure,
148 char *buf, int count, int error));
149 void read_from_ics P((InputSourceRef isr, VOIDSTAR closure,
150 char *buf, int count, int error));
151 void ics_printf P((char *format, ...));
152 void SendToICS P((char *s));
153 void SendToICSDelayed P((char *s, long msdelay));
154 void SendMoveToICS P((ChessMove moveType, int fromX, int fromY,
156 void HandleMachineMove P((char *message, ChessProgramState *cps));
157 int AutoPlayOneMove P((void));
158 int LoadGameOneMove P((ChessMove readAhead));
159 int LoadGameFromFile P((char *filename, int n, char *title, int useList));
160 int LoadPositionFromFile P((char *filename, int n, char *title));
161 int SavePositionToFile P((char *filename));
162 void ApplyMove P((int fromX, int fromY, int toX, int toY, int promoChar,
164 void MakeMove P((int fromX, int fromY, int toX, int toY, int promoChar));
165 void ShowMove P((int fromX, int fromY, int toX, int toY));
166 int FinishMove P((ChessMove moveType, int fromX, int fromY, int toX, int toY,
167 /*char*/int promoChar));
168 void BackwardInner P((int target));
169 void ForwardInner P((int target));
170 int Adjudicate P((ChessProgramState *cps));
171 void GameEnds P((ChessMove result, char *resultDetails, int whosays));
172 void EditPositionDone P((Boolean fakeRights));
173 void PrintOpponents P((FILE *fp));
174 void PrintPosition P((FILE *fp, int move));
175 void StartChessProgram P((ChessProgramState *cps));
176 void SendToProgram P((char *message, ChessProgramState *cps));
177 void SendMoveToProgram P((int moveNum, ChessProgramState *cps));
178 void ReceiveFromProgram P((InputSourceRef isr, VOIDSTAR closure,
179 char *buf, int count, int error));
180 void SendTimeControl P((ChessProgramState *cps,
181 int mps, long tc, int inc, int sd, int st));
182 char *TimeControlTagValue P((void));
183 void Attention P((ChessProgramState *cps));
184 void FeedMovesToProgram P((ChessProgramState *cps, int upto));
185 void ResurrectChessProgram P((void));
186 void DisplayComment P((int moveNumber, char *text));
187 void DisplayMove P((int moveNumber));
189 void ParseGameHistory P((char *game));
190 void ParseBoard12 P((char *string));
191 void KeepAlive P((void));
192 void StartClocks P((void));
193 void SwitchClocks P((void));
194 void StopClocks P((void));
195 void ResetClocks P((void));
196 char *PGNDate P((void));
197 void SetGameInfo P((void));
198 Boolean ParseFEN P((Board board, int *blackPlaysFirst, char *fen));
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 void GetTimeMark P((TimeMark *));
208 long SubtractTimeMarks P((TimeMark *, TimeMark *));
209 int CheckFlags P((void));
210 long NextTickLength P((long));
211 void CheckTimeControl P((void));
212 void show_bytes P((FILE *, char *, int));
213 int string_to_rating P((char *str));
214 void ParseFeatures P((char* args, ChessProgramState *cps));
215 void InitBackEnd3 P((void));
216 void FeatureDone P((ChessProgramState* cps, int val));
217 void InitChessProgram P((ChessProgramState *cps, int setup));
218 void OutputKibitz(int window, char *text);
219 int PerpetualChase(int first, int last);
220 int EngineOutputIsUp();
221 void InitDrawingSizes(int x, int y);
224 extern void ConsoleCreate();
227 ChessProgramState *WhitePlayer();
228 void InsertIntoMemo P((int which, char *text)); // [HGM] kibitz: in engineo.c
229 int VerifyDisplayMode P(());
231 char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment
232 void InitEngineUCI( const char * iniDir, ChessProgramState * cps ); // [HGM] moved here from winboard.c
233 char *ProbeBook P((int moveNr, char *book)); // [HGM] book: returns a book move
234 char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book
235 void ics_update_width P((int new_width));
236 extern char installDir[MSG_SIZ];
238 extern int tinyLayout, smallLayout;
239 ChessProgramStats programStats;
240 char lastPV[2][2*MSG_SIZ]; /* [HGM] pv: last PV in thinking output of each engine */
242 static int exiting = 0; /* [HGM] moved to top */
243 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
244 int startedFromPositionFile = FALSE; Board filePosition; /* [HGM] loadPos */
245 char endingGame = 0; /* [HGM] crash: flag to prevent recursion of GameEnds() */
246 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS */
247 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
248 int lastIndex = 0; /* [HGM] autoinc: last game/position used in match mode */
249 Boolean connectionAlive;/* [HGM] alive: ICS connection status from probing */
250 int opponentKibitzes;
251 int lastSavedGame; /* [HGM] save: ID of game */
252 char chatPartner[MAX_CHAT][MSG_SIZ]; /* [HGM] chat: list of chatting partners */
253 extern int chatCount;
255 char marker[BOARD_RANKS][BOARD_FILES]; /* [HGM] marks for target squares */
257 /* States for ics_getting_history */
259 #define H_REQUESTED 1
260 #define H_GOT_REQ_HEADER 2
261 #define H_GOT_UNREQ_HEADER 3
262 #define H_GETTING_MOVES 4
263 #define H_GOT_UNWANTED_HEADER 5
265 /* whosays values for GameEnds */
274 /* Maximum number of games in a cmail message */
275 #define CMAIL_MAX_GAMES 20
277 /* Different types of move when calling RegisterMove */
279 #define CMAIL_RESIGN 1
281 #define CMAIL_ACCEPT 3
283 /* Different types of result to remember for each game */
284 #define CMAIL_NOT_RESULT 0
285 #define CMAIL_OLD_RESULT 1
286 #define CMAIL_NEW_RESULT 2
288 /* Telnet protocol constants */
299 static char * safeStrCpy( char * dst, const char * src, size_t count )
301 assert( dst != NULL );
302 assert( src != NULL );
305 strncpy( dst, src, count );
306 dst[ count-1 ] = '\0';
310 /* Some compiler can't cast u64 to double
311 * This function do the job for us:
313 * We use the highest bit for cast, this only
314 * works if the highest bit is not
315 * in use (This should not happen)
317 * We used this for all compiler
320 u64ToDouble(u64 value)
323 u64 tmp = value & u64Const(0x7fffffffffffffff);
324 r = (double)(s64)tmp;
325 if (value & u64Const(0x8000000000000000))
326 r += 9.2233720368547758080e18; /* 2^63 */
330 /* Fake up flags for now, as we aren't keeping track of castling
331 availability yet. [HGM] Change of logic: the flag now only
332 indicates the type of castlings allowed by the rule of the game.
333 The actual rights themselves are maintained in the array
334 castlingRights, as part of the game history, and are not probed
340 int flags = F_ALL_CASTLE_OK;
341 if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
342 switch (gameInfo.variant) {
344 flags &= ~F_ALL_CASTLE_OK;
345 case VariantGiveaway: // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
346 flags |= F_IGNORE_CHECK;
348 flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
351 flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
353 case VariantKriegspiel:
354 flags |= F_KRIEGSPIEL_CAPTURE;
356 case VariantCapaRandom:
357 case VariantFischeRandom:
358 flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
359 case VariantNoCastle:
360 case VariantShatranj:
363 flags &= ~F_ALL_CASTLE_OK;
371 FILE *gameFileFP, *debugFP;
374 [AS] Note: sometimes, the sscanf() function is used to parse the input
375 into a fixed-size buffer. Because of this, we must be prepared to
376 receive strings as long as the size of the input buffer, which is currently
377 set to 4K for Windows and 8K for the rest.
378 So, we must either allocate sufficiently large buffers here, or
379 reduce the size of the input buffer in the input reading part.
382 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
383 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
384 char thinkOutput1[MSG_SIZ*10];
386 ChessProgramState first, second;
388 /* premove variables */
391 int premoveFromX = 0;
392 int premoveFromY = 0;
393 int premovePromoChar = 0;
395 Boolean alarmSounded;
396 /* end premove variables */
398 char *ics_prefix = "$";
399 int ics_type = ICS_GENERIC;
401 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
402 int pauseExamForwardMostMove = 0;
403 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
404 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
405 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
406 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
407 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
408 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
409 int whiteFlag = FALSE, blackFlag = FALSE;
410 int userOfferedDraw = FALSE;
411 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
412 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
413 int cmailMoveType[CMAIL_MAX_GAMES];
414 long ics_clock_paused = 0;
415 ProcRef icsPR = NoProc, cmailPR = NoProc;
416 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
417 GameMode gameMode = BeginningOfGame;
418 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
419 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
420 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
421 int hiddenThinkOutputState = 0; /* [AS] */
422 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
423 int adjudicateLossPlies = 6;
424 char white_holding[64], black_holding[64];
425 TimeMark lastNodeCountTime;
426 long lastNodeCount=0;
427 int have_sent_ICS_logon = 0;
429 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement;
430 long timeControl_2; /* [AS] Allow separate time controls */
431 char *fullTimeControlString = NULL; /* [HGM] secondary TC: merge of MPS, TC and inc */
432 long timeRemaining[2][MAX_MOVES];
434 TimeMark programStartTime;
435 char ics_handle[MSG_SIZ];
436 int have_set_title = 0;
438 /* animateTraining preserves the state of appData.animate
439 * when Training mode is activated. This allows the
440 * response to be animated when appData.animate == TRUE and
441 * appData.animateDragging == TRUE.
443 Boolean animateTraining;
449 Board boards[MAX_MOVES];
450 /* [HGM] Following 7 needed for accurate legality tests: */
451 signed char castlingRank[BOARD_FILES]; // and corresponding ranks
452 signed char initialRights[BOARD_FILES];
453 int nrCastlingRights; // For TwoKings, or to implement castling-unknown status
454 int initialRulePlies, FENrulePlies;
455 FILE *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
458 int mute; // mute all sounds
460 // [HGM] vari: next 12 to save and restore variations
461 #define MAX_VARIATIONS 10
462 int framePtr = MAX_MOVES-1; // points to free stack entry
464 int savedFirst[MAX_VARIATIONS];
465 int savedLast[MAX_VARIATIONS];
466 int savedFramePtr[MAX_VARIATIONS];
467 char *savedDetails[MAX_VARIATIONS];
468 ChessMove savedResult[MAX_VARIATIONS];
470 void PushTail P((int firstMove, int lastMove));
471 Boolean PopTail P((Boolean annotate));
472 void CleanupTail P((void));
474 ChessSquare FIDEArray[2][BOARD_FILES] = {
475 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
476 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
477 { BlackRook, BlackKnight, BlackBishop, BlackQueen,
478 BlackKing, BlackBishop, BlackKnight, BlackRook }
481 ChessSquare twoKingsArray[2][BOARD_FILES] = {
482 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
483 WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
484 { BlackRook, BlackKnight, BlackBishop, BlackQueen,
485 BlackKing, BlackKing, BlackKnight, BlackRook }
488 ChessSquare KnightmateArray[2][BOARD_FILES] = {
489 { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
490 WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
491 { BlackRook, BlackMan, BlackBishop, BlackQueen,
492 BlackUnicorn, BlackBishop, BlackMan, BlackRook }
495 ChessSquare fairyArray[2][BOARD_FILES] = { /* [HGM] Queen side differs from King side */
496 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
497 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
498 { BlackLance, BlackAlfil, BlackMarshall, BlackAngel,
499 BlackKing, BlackMarshall, BlackAlfil, BlackLance }
502 ChessSquare ShatranjArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
503 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
504 WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
505 { BlackRook, BlackKnight, BlackAlfil, BlackKing,
506 BlackFerz, BlackAlfil, BlackKnight, BlackRook }
509 ChessSquare makrukArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
510 { WhiteRook, WhiteKnight, WhiteMan, WhiteKing,
511 WhiteFerz, WhiteMan, WhiteKnight, WhiteRook },
512 { BlackRook, BlackKnight, BlackMan, BlackFerz,
513 BlackKing, BlackMan, BlackKnight, BlackRook }
517 #if (BOARD_FILES>=10)
518 ChessSquare ShogiArray[2][BOARD_FILES] = {
519 { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
520 WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
521 { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
522 BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
525 ChessSquare XiangqiArray[2][BOARD_FILES] = {
526 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
527 WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
528 { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
529 BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
532 ChessSquare CapablancaArray[2][BOARD_FILES] = {
533 { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
534 WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
535 { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
536 BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
539 ChessSquare GreatArray[2][BOARD_FILES] = {
540 { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
541 WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
542 { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
543 BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
546 ChessSquare JanusArray[2][BOARD_FILES] = {
547 { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
548 WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
549 { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
550 BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
554 ChessSquare GothicArray[2][BOARD_FILES] = {
555 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
556 WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
557 { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
558 BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
561 #define GothicArray CapablancaArray
565 ChessSquare FalconArray[2][BOARD_FILES] = {
566 { WhiteRook, WhiteKnight, WhiteBishop, WhiteLance, WhiteQueen,
567 WhiteKing, WhiteLance, WhiteBishop, WhiteKnight, WhiteRook },
568 { BlackRook, BlackKnight, BlackBishop, BlackLance, BlackQueen,
569 BlackKing, BlackLance, BlackBishop, BlackKnight, BlackRook }
572 #define FalconArray CapablancaArray
575 #else // !(BOARD_FILES>=10)
576 #define XiangqiPosition FIDEArray
577 #define CapablancaArray FIDEArray
578 #define GothicArray FIDEArray
579 #define GreatArray FIDEArray
580 #endif // !(BOARD_FILES>=10)
582 #if (BOARD_FILES>=12)
583 ChessSquare CourierArray[2][BOARD_FILES] = {
584 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
585 WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
586 { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
587 BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
589 #else // !(BOARD_FILES>=12)
590 #define CourierArray CapablancaArray
591 #endif // !(BOARD_FILES>=12)
594 Board initialPosition;
597 /* Convert str to a rating. Checks for special cases of "----",
599 "++++", etc. Also strips ()'s */
601 string_to_rating(str)
604 while(*str && !isdigit(*str)) ++str;
606 return 0; /* One of the special "no rating" cases */
614 /* Init programStats */
615 programStats.movelist[0] = 0;
616 programStats.depth = 0;
617 programStats.nr_moves = 0;
618 programStats.moves_left = 0;
619 programStats.nodes = 0;
620 programStats.time = -1; // [HGM] PGNtime: make invalid to recognize engine output
621 programStats.score = 0;
622 programStats.got_only_move = 0;
623 programStats.got_fail = 0;
624 programStats.line_is_book = 0;
630 int matched, min, sec;
632 ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
634 GetTimeMark(&programStartTime);
635 srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
638 programStats.ok_to_send = 1;
639 programStats.seen_stat = 0;
642 * Initialize game list
648 * Internet chess server status
650 if (appData.icsActive) {
651 appData.matchMode = FALSE;
652 appData.matchGames = 0;
654 appData.noChessProgram = !appData.zippyPlay;
656 appData.zippyPlay = FALSE;
657 appData.zippyTalk = FALSE;
658 appData.noChessProgram = TRUE;
660 if (*appData.icsHelper != NULLCHAR) {
661 appData.useTelnet = TRUE;
662 appData.telnetProgram = appData.icsHelper;
665 appData.zippyTalk = appData.zippyPlay = FALSE;
668 /* [AS] Initialize pv info list [HGM] and game state */
672 for( i=0; i<=framePtr; i++ ) {
673 pvInfoList[i].depth = -1;
674 boards[i][EP_STATUS] = EP_NONE;
675 for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
680 * Parse timeControl resource
682 if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
683 appData.movesPerSession)) {
685 snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
686 DisplayFatalError(buf, 0, 2);
690 * Parse searchTime resource
692 if (*appData.searchTime != NULLCHAR) {
693 matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
695 searchTime = min * 60;
696 } else if (matched == 2) {
697 searchTime = min * 60 + sec;
700 snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
701 DisplayFatalError(buf, 0, 2);
705 /* [AS] Adjudication threshold */
706 adjudicateLossThreshold = appData.adjudicateLossThreshold;
708 first.which = "first";
709 second.which = "second";
710 first.maybeThinking = second.maybeThinking = FALSE;
711 first.pr = second.pr = NoProc;
712 first.isr = second.isr = NULL;
713 first.sendTime = second.sendTime = 2;
714 first.sendDrawOffers = 1;
715 if (appData.firstPlaysBlack) {
716 first.twoMachinesColor = "black\n";
717 second.twoMachinesColor = "white\n";
719 first.twoMachinesColor = "white\n";
720 second.twoMachinesColor = "black\n";
722 first.program = appData.firstChessProgram;
723 second.program = appData.secondChessProgram;
724 first.host = appData.firstHost;
725 second.host = appData.secondHost;
726 first.dir = appData.firstDirectory;
727 second.dir = appData.secondDirectory;
728 first.other = &second;
729 second.other = &first;
730 first.initString = appData.initString;
731 second.initString = appData.secondInitString;
732 first.computerString = appData.firstComputerString;
733 second.computerString = appData.secondComputerString;
734 first.useSigint = second.useSigint = TRUE;
735 first.useSigterm = second.useSigterm = TRUE;
736 first.reuse = appData.reuseFirst;
737 second.reuse = appData.reuseSecond;
738 first.nps = appData.firstNPS; // [HGM] nps: copy nodes per second
739 second.nps = appData.secondNPS;
740 first.useSetboard = second.useSetboard = FALSE;
741 first.useSAN = second.useSAN = FALSE;
742 first.usePing = second.usePing = FALSE;
743 first.lastPing = second.lastPing = 0;
744 first.lastPong = second.lastPong = 0;
745 first.usePlayother = second.usePlayother = FALSE;
746 first.useColors = second.useColors = TRUE;
747 first.useUsermove = second.useUsermove = FALSE;
748 first.sendICS = second.sendICS = FALSE;
749 first.sendName = second.sendName = appData.icsActive;
750 first.sdKludge = second.sdKludge = FALSE;
751 first.stKludge = second.stKludge = FALSE;
752 TidyProgramName(first.program, first.host, first.tidy);
753 TidyProgramName(second.program, second.host, second.tidy);
754 first.matchWins = second.matchWins = 0;
755 strcpy(first.variants, appData.variant);
756 strcpy(second.variants, appData.variant);
757 first.analysisSupport = second.analysisSupport = 2; /* detect */
758 first.analyzing = second.analyzing = FALSE;
759 first.initDone = second.initDone = FALSE;
761 /* New features added by Tord: */
762 first.useFEN960 = FALSE; second.useFEN960 = FALSE;
763 first.useOOCastle = TRUE; second.useOOCastle = TRUE;
764 /* End of new features added by Tord. */
765 first.fenOverride = appData.fenOverride1;
766 second.fenOverride = appData.fenOverride2;
768 /* [HGM] time odds: set factor for each machine */
769 first.timeOdds = appData.firstTimeOdds;
770 second.timeOdds = appData.secondTimeOdds;
772 if(appData.timeOddsMode) {
773 norm = first.timeOdds;
774 if(norm > second.timeOdds) norm = second.timeOdds;
776 first.timeOdds /= norm;
777 second.timeOdds /= norm;
780 /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
781 first.accumulateTC = appData.firstAccumulateTC;
782 second.accumulateTC = appData.secondAccumulateTC;
783 first.maxNrOfSessions = second.maxNrOfSessions = 1;
786 first.debug = second.debug = FALSE;
787 first.supportsNPS = second.supportsNPS = UNKNOWN;
790 first.optionSettings = appData.firstOptions;
791 second.optionSettings = appData.secondOptions;
793 first.scoreIsAbsolute = appData.firstScoreIsAbsolute; /* [AS] */
794 second.scoreIsAbsolute = appData.secondScoreIsAbsolute; /* [AS] */
795 first.isUCI = appData.firstIsUCI; /* [AS] */
796 second.isUCI = appData.secondIsUCI; /* [AS] */
797 first.hasOwnBookUCI = appData.firstHasOwnBookUCI; /* [AS] */
798 second.hasOwnBookUCI = appData.secondHasOwnBookUCI; /* [AS] */
800 if (appData.firstProtocolVersion > PROTOVER ||
801 appData.firstProtocolVersion < 1) {
803 sprintf(buf, _("protocol version %d not supported"),
804 appData.firstProtocolVersion);
805 DisplayFatalError(buf, 0, 2);
807 first.protocolVersion = appData.firstProtocolVersion;
810 if (appData.secondProtocolVersion > PROTOVER ||
811 appData.secondProtocolVersion < 1) {
813 sprintf(buf, _("protocol version %d not supported"),
814 appData.secondProtocolVersion);
815 DisplayFatalError(buf, 0, 2);
817 second.protocolVersion = appData.secondProtocolVersion;
820 if (appData.icsActive) {
821 appData.clockMode = TRUE; /* changes dynamically in ICS mode */
822 // } else if (*appData.searchTime != NULLCHAR || appData.noChessProgram) {
823 } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
824 appData.clockMode = FALSE;
825 first.sendTime = second.sendTime = 0;
829 /* Override some settings from environment variables, for backward
830 compatibility. Unfortunately it's not feasible to have the env
831 vars just set defaults, at least in xboard. Ugh.
833 if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
838 if (appData.noChessProgram) {
839 programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
840 sprintf(programVersion, "%s", PACKAGE_STRING);
842 /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
843 programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
844 sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
847 if (!appData.icsActive) {
849 /* Check for variants that are supported only in ICS mode,
850 or not at all. Some that are accepted here nevertheless
851 have bugs; see comments below.
853 VariantClass variant = StringToVariant(appData.variant);
855 case VariantBughouse: /* need four players and two boards */
856 case VariantKriegspiel: /* need to hide pieces and move details */
857 /* case VariantFischeRandom: (Fabien: moved below) */
858 sprintf(buf, _("Variant %s supported only in ICS mode"), appData.variant);
859 DisplayFatalError(buf, 0, 2);
863 case VariantLoadable:
873 sprintf(buf, _("Unknown variant name %s"), appData.variant);
874 DisplayFatalError(buf, 0, 2);
877 case VariantXiangqi: /* [HGM] repetition rules not implemented */
878 case VariantFairy: /* [HGM] TestLegality definitely off! */
879 case VariantGothic: /* [HGM] should work */
880 case VariantCapablanca: /* [HGM] should work */
881 case VariantCourier: /* [HGM] initial forced moves not implemented */
882 case VariantShogi: /* [HGM] drops not tested for legality */
883 case VariantKnightmate: /* [HGM] should work */
884 case VariantCylinder: /* [HGM] untested */
885 case VariantFalcon: /* [HGM] untested */
886 case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
887 offboard interposition not understood */
888 case VariantNormal: /* definitely works! */
889 case VariantWildCastle: /* pieces not automatically shuffled */
890 case VariantNoCastle: /* pieces not automatically shuffled */
891 case VariantFischeRandom: /* [HGM] works and shuffles pieces */
892 case VariantLosers: /* should work except for win condition,
893 and doesn't know captures are mandatory */
894 case VariantSuicide: /* should work except for win condition,
895 and doesn't know captures are mandatory */
896 case VariantGiveaway: /* should work except for win condition,
897 and doesn't know captures are mandatory */
898 case VariantTwoKings: /* should work */
899 case VariantAtomic: /* should work except for win condition */
900 case Variant3Check: /* should work except for win condition */
901 case VariantShatranj: /* should work except for all win conditions */
902 case VariantMakruk: /* should work except for daw countdown */
903 case VariantBerolina: /* might work if TestLegality is off */
904 case VariantCapaRandom: /* should work */
905 case VariantJanus: /* should work */
906 case VariantSuper: /* experimental */
907 case VariantGreat: /* experimental, requires legality testing to be off */
912 InitEngineUCI( installDir, &first ); // [HGM] moved here from winboard.c, to make available in xboard
913 InitEngineUCI( installDir, &second );
916 int NextIntegerFromString( char ** str, long * value )
921 while( *s == ' ' || *s == '\t' ) {
927 if( *s >= '0' && *s <= '9' ) {
928 while( *s >= '0' && *s <= '9' ) {
929 *value = *value * 10 + (*s - '0');
941 int NextTimeControlFromString( char ** str, long * value )
944 int result = NextIntegerFromString( str, &temp );
947 *value = temp * 60; /* Minutes */
950 result = NextIntegerFromString( str, &temp );
951 *value += temp; /* Seconds */
958 int NextSessionFromString( char ** str, int *moves, long * tc, long *inc)
959 { /* [HGM] routine added to read '+moves/time' for secondary time control */
960 int result = -1; long temp, temp2;
962 if(**str != '+') return -1; // old params remain in force!
964 if( NextTimeControlFromString( str, &temp ) ) return -1;
967 /* time only: incremental or sudden-death time control */
968 if(**str == '+') { /* increment follows; read it */
970 if(result = NextIntegerFromString( str, &temp2)) return -1;
973 *moves = 0; *tc = temp * 1000;
975 } else if(temp % 60 != 0) return -1; /* moves was given as min:sec */
977 (*str)++; /* classical time control */
978 result = NextTimeControlFromString( str, &temp2);
987 int GetTimeQuota(int movenr)
988 { /* [HGM] get time to add from the multi-session time-control string */
989 int moves=1; /* kludge to force reading of first session */
990 long time, increment;
991 char *s = fullTimeControlString;
993 if(appData.debugMode) fprintf(debugFP, "TC string = '%s'\n", fullTimeControlString);
995 if(moves) NextSessionFromString(&s, &moves, &time, &increment);
996 if(appData.debugMode) fprintf(debugFP, "mps=%d tc=%d inc=%d\n", moves, (int) time, (int) increment);
997 if(movenr == -1) return time; /* last move before new session */
998 if(!moves) return increment; /* current session is incremental */
999 if(movenr >= 0) movenr -= moves; /* we already finished this session */
1000 } while(movenr >= -1); /* try again for next session */
1002 return 0; // no new time quota on this move
1006 ParseTimeControl(tc, ti, mps)
1015 if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1018 sprintf(buf, "+%d/%s+%d", mps, tc, ti);
1019 else sprintf(buf, "+%s+%d", tc, ti);
1022 sprintf(buf, "+%d/%s", mps, tc);
1023 else sprintf(buf, "+%s", tc);
1025 fullTimeControlString = StrSave(buf);
1027 if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1032 /* Parse second time control */
1035 if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1043 timeControl_2 = tc2 * 1000;
1053 timeControl = tc1 * 1000;
1056 timeIncrement = ti * 1000; /* convert to ms */
1057 movesPerSession = 0;
1060 movesPerSession = mps;
1068 if (appData.debugMode) {
1069 fprintf(debugFP, "%s\n", programVersion);
1072 set_cont_sequence(appData.wrapContSeq);
1073 if (appData.matchGames > 0) {
1074 appData.matchMode = TRUE;
1075 } else if (appData.matchMode) {
1076 appData.matchGames = 1;
1078 if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1079 appData.matchGames = appData.sameColorGames;
1080 if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1081 if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1082 if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1085 if (appData.noChessProgram || first.protocolVersion == 1) {
1088 /* kludge: allow timeout for initial "feature" commands */
1090 DisplayMessage("", _("Starting chess program"));
1091 ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1096 InitBackEnd3 P((void))
1098 GameMode initialMode;
1102 InitChessProgram(&first, startedFromSetupPosition);
1105 if (appData.icsActive)
1110 if (*appData.icsCommPort != NULLCHAR)
1112 sprintf(buf, _("Could not open comm port %s"),
1113 appData.icsCommPort);
1117 snprintf(buf, sizeof(buf), _("Could not connect to host %s, port %s"),
1118 appData.icsHost, appData.icsPort);
1120 DisplayFatalError(buf, err, 1);
1126 AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1128 AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1129 if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1130 ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1132 else if (appData.noChessProgram)
1141 if (*appData.cmailGameName != NULLCHAR)
1144 OpenLoopback(&cmailPR);
1146 AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1150 DisplayMessage("", "");
1151 if (StrCaseCmp(appData.initialMode, "") == 0)
1153 initialMode = BeginningOfGame;
1155 else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0)
1157 initialMode = TwoMachinesPlay;
1159 else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0)
1161 initialMode = AnalyzeFile;
1163 else if (StrCaseCmp(appData.initialMode, "Analysis") == 0)
1165 initialMode = AnalyzeMode;
1167 else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0)
1169 initialMode = MachinePlaysWhite;
1171 else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0)
1173 initialMode = MachinePlaysBlack;
1175 else if (StrCaseCmp(appData.initialMode, "EditGame") == 0)
1177 initialMode = EditGame;
1179 else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0)
1181 initialMode = EditPosition;
1183 else if (StrCaseCmp(appData.initialMode, "Training") == 0)
1185 initialMode = Training;
1189 sprintf(buf, _("Unknown initialMode %s"), appData.initialMode);
1190 DisplayFatalError(buf, 0, 2);
1194 if (appData.matchMode)
1196 /* Set up machine vs. machine match */
1197 if (appData.noChessProgram)
1199 DisplayFatalError(_("Can't have a match with no chess programs"),
1205 if (*appData.loadGameFile != NULLCHAR)
1207 int index = appData.loadGameIndex; // [HGM] autoinc
1208 if(index<0) lastIndex = index = 1;
1209 if (!LoadGameFromFile(appData.loadGameFile,
1211 appData.loadGameFile, FALSE))
1213 DisplayFatalError(_("Bad game file"), 0, 1);
1217 else if (*appData.loadPositionFile != NULLCHAR)
1219 int index = appData.loadPositionIndex; // [HGM] autoinc
1220 if(index<0) lastIndex = index = 1;
1221 if (!LoadPositionFromFile(appData.loadPositionFile,
1223 appData.loadPositionFile))
1225 DisplayFatalError(_("Bad position file"), 0, 1);
1231 else if (*appData.cmailGameName != NULLCHAR)
1233 /* Set up cmail mode */
1234 ReloadCmailMsgEvent(TRUE);
1238 /* Set up other modes */
1239 if (initialMode == AnalyzeFile)
1241 if (*appData.loadGameFile == NULLCHAR)
1243 DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1247 if (*appData.loadGameFile != NULLCHAR)
1249 (void) LoadGameFromFile(appData.loadGameFile,
1250 appData.loadGameIndex,
1251 appData.loadGameFile, TRUE);
1253 else if (*appData.loadPositionFile != NULLCHAR)
1255 (void) LoadPositionFromFile(appData.loadPositionFile,
1256 appData.loadPositionIndex,
1257 appData.loadPositionFile);
1258 /* [HGM] try to make self-starting even after FEN load */
1259 /* to allow automatic setup of fairy variants with wtm */
1260 if(initialMode == BeginningOfGame && !blackPlaysFirst)
1262 gameMode = BeginningOfGame;
1263 setboardSpoiledMachineBlack = 1;
1265 /* [HGM] loadPos: make that every new game uses the setup */
1266 /* from file as long as we do not switch variant */
1267 if(!blackPlaysFirst)
1269 startedFromPositionFile = TRUE;
1270 CopyBoard(filePosition, boards[0]);
1273 if (initialMode == AnalyzeMode)
1275 if (appData.noChessProgram)
1277 DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1280 if (appData.icsActive)
1282 DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1287 else if (initialMode == AnalyzeFile)
1289 appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1290 ShowThinkingEvent();
1292 AnalysisPeriodicEvent(1);
1294 else if (initialMode == MachinePlaysWhite)
1296 if (appData.noChessProgram)
1298 DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1302 if (appData.icsActive)
1304 DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1308 MachineWhiteEvent();
1310 else if (initialMode == MachinePlaysBlack)
1312 if (appData.noChessProgram)
1314 DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1318 if (appData.icsActive)
1320 DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1324 MachineBlackEvent();
1326 else if (initialMode == TwoMachinesPlay)
1328 if (appData.noChessProgram)
1330 DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1334 if (appData.icsActive)
1336 DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1342 else if (initialMode == EditGame)
1346 else if (initialMode == EditPosition)
1348 EditPositionEvent();
1350 else if (initialMode == Training)
1352 if (*appData.loadGameFile == NULLCHAR)
1354 DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1365 * Establish will establish a contact to a remote host.port.
1366 * Sets icsPR to a ProcRef for a process (or pseudo-process)
1367 * used to talk to the host.
1368 * Returns 0 if okay, error code if not.
1375 if (*appData.icsCommPort != NULLCHAR) {
1376 /* Talk to the host through a serial comm port */
1377 return OpenCommPort(appData.icsCommPort, &icsPR);
1379 } else if (*appData.gateway != NULLCHAR) {
1380 if (*appData.remoteShell == NULLCHAR) {
1381 /* Use the rcmd protocol to run telnet program on a gateway host */
1382 snprintf(buf, sizeof(buf), "%s %s %s",
1383 appData.telnetProgram, appData.icsHost, appData.icsPort);
1384 return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1387 /* Use the rsh program to run telnet program on a gateway host */
1388 if (*appData.remoteUser == NULLCHAR) {
1389 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1390 appData.gateway, appData.telnetProgram,
1391 appData.icsHost, appData.icsPort);
1393 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1394 appData.remoteShell, appData.gateway,
1395 appData.remoteUser, appData.telnetProgram,
1396 appData.icsHost, appData.icsPort);
1398 return StartChildProcess(buf, "", &icsPR);
1401 } else if (appData.useTelnet) {
1402 return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1405 /* TCP socket interface differs somewhat between
1406 Unix and NT; handle details in the front end.
1408 return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1413 show_bytes(fp, buf, count)
1419 if (*buf < 040 || *(unsigned char *) buf > 0177) {
1420 fprintf(fp, "\\%03o", *buf & 0xff);
1429 /* Returns an errno value */
1431 OutputMaybeTelnet(pr, message, count, outError)
1437 char buf[8192], *p, *q, *buflim;
1438 int left, newcount, outcount;
1440 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1441 *appData.gateway != NULLCHAR) {
1442 if (appData.debugMode) {
1443 fprintf(debugFP, ">ICS: ");
1444 show_bytes(debugFP, message, count);
1445 fprintf(debugFP, "\n");
1447 return OutputToProcess(pr, message, count, outError);
1450 buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1457 if (appData.debugMode) {
1458 fprintf(debugFP, ">ICS: ");
1459 show_bytes(debugFP, buf, newcount);
1460 fprintf(debugFP, "\n");
1462 outcount = OutputToProcess(pr, buf, newcount, outError);
1463 if (outcount < newcount) return -1; /* to be sure */
1470 } else if (((unsigned char) *p) == TN_IAC) {
1471 *q++ = (char) TN_IAC;
1478 if (appData.debugMode) {
1479 fprintf(debugFP, ">ICS: ");
1480 show_bytes(debugFP, buf, newcount);
1481 fprintf(debugFP, "\n");
1483 outcount = OutputToProcess(pr, buf, newcount, outError);
1484 if (outcount < newcount) return -1; /* to be sure */
1489 read_from_player(isr, closure, message, count, error)
1496 int outError, outCount;
1497 static int gotEof = 0;
1499 /* Pass data read from player on to ICS */
1502 outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1503 if (outCount < count) {
1504 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1506 } else if (count < 0) {
1507 RemoveInputSource(isr);
1508 DisplayFatalError(_("Error reading from keyboard"), error, 1);
1509 } else if (gotEof++ > 0) {
1510 RemoveInputSource(isr);
1511 DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1517 { // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1518 if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1519 connectionAlive = FALSE; // only sticks if no response to 'date' command.
1520 SendToICS("date\n");
1521 if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1524 /* added routine for printf style output to ics */
1525 void ics_printf(char *format, ...)
1527 char buffer[MSG_SIZ];
1530 va_start(args, format);
1531 vsnprintf(buffer, sizeof(buffer), format, args);
1532 buffer[sizeof(buffer)-1] = '\0';
1541 int count, outCount, outError;
1543 if (icsPR == NULL) return;
1546 outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1547 if (outCount < count) {
1548 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1552 /* This is used for sending logon scripts to the ICS. Sending
1553 without a delay causes problems when using timestamp on ICC
1554 (at least on my machine). */
1556 SendToICSDelayed(s,msdelay)
1560 int count, outCount, outError;
1562 if (icsPR == NULL) return;
1565 if (appData.debugMode) {
1566 fprintf(debugFP, ">ICS: ");
1567 show_bytes(debugFP, s, count);
1568 fprintf(debugFP, "\n");
1570 outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1572 if (outCount < count) {
1573 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1578 /* Remove all highlighting escape sequences in s
1579 Also deletes any suffix starting with '('
1582 StripHighlightAndTitle(s)
1585 static char retbuf[MSG_SIZ];
1588 while (*s != NULLCHAR) {
1589 while (*s == '\033') {
1590 while (*s != NULLCHAR && !isalpha(*s)) s++;
1591 if (*s != NULLCHAR) s++;
1593 while (*s != NULLCHAR && *s != '\033') {
1594 if (*s == '(' || *s == '[') {
1605 /* Remove all highlighting escape sequences in s */
1610 static char retbuf[MSG_SIZ];
1613 while (*s != NULLCHAR) {
1614 while (*s == '\033') {
1615 while (*s != NULLCHAR && !isalpha(*s)) s++;
1616 if (*s != NULLCHAR) s++;
1618 while (*s != NULLCHAR && *s != '\033') {
1626 char *variantNames[] = VARIANT_NAMES;
1631 return variantNames[v];
1635 /* Identify a variant from the strings the chess servers use or the
1636 PGN Variant tag names we use. */
1643 VariantClass v = VariantNormal;
1644 int i, found = FALSE;
1649 /* [HGM] skip over optional board-size prefixes */
1650 if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1651 sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1652 while( *e++ != '_');
1655 if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
1659 for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
1660 if (StrCaseStr(e, variantNames[i])) {
1661 v = (VariantClass) i;
1668 if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
1669 || StrCaseStr(e, "wild/fr")
1670 || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
1671 v = VariantFischeRandom;
1672 } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
1673 (i = 1, p = StrCaseStr(e, "w"))) {
1675 while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
1682 case 0: /* FICS only, actually */
1684 /* Castling legal even if K starts on d-file */
1685 v = VariantWildCastle;
1690 /* Castling illegal even if K & R happen to start in
1691 normal positions. */
1692 v = VariantNoCastle;
1705 /* Castling legal iff K & R start in normal positions */
1711 /* Special wilds for position setup; unclear what to do here */
1712 v = VariantLoadable;
1715 /* Bizarre ICC game */
1716 v = VariantTwoKings;
1719 v = VariantKriegspiel;
1725 v = VariantFischeRandom;
1728 v = VariantCrazyhouse;
1731 v = VariantBughouse;
1737 /* Not quite the same as FICS suicide! */
1738 v = VariantGiveaway;
1744 v = VariantShatranj;
1747 /* Temporary names for future ICC types. The name *will* change in
1748 the next xboard/WinBoard release after ICC defines it. */
1786 v = VariantCapablanca;
1789 v = VariantKnightmate;
1795 v = VariantCylinder;
1801 v = VariantCapaRandom;
1804 v = VariantBerolina;
1816 /* Found "wild" or "w" in the string but no number;
1817 must assume it's normal chess. */
1821 sprintf(buf, _("Unknown wild type %d"), wnum);
1822 DisplayError(buf, 0);
1828 if (appData.debugMode) {
1829 fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
1830 e, wnum, VariantName(v));
1835 static int leftover_start = 0, leftover_len = 0;
1836 char star_match[STAR_MATCH_N][MSG_SIZ];
1838 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
1839 advance *index beyond it, and set leftover_start to the new value of
1840 *index; else return FALSE. If pattern contains the character '*', it
1841 matches any sequence of characters not containing '\r', '\n', or the
1842 character following the '*' (if any), and the matched sequence(s) are
1843 copied into star_match.
1846 looking_at(buf, index, pattern)
1851 char *bufp = &buf[*index], *patternp = pattern;
1853 char *matchp = star_match[0];
1856 if (*patternp == NULLCHAR) {
1857 *index = leftover_start = bufp - buf;
1861 if (*bufp == NULLCHAR) return FALSE;
1862 if (*patternp == '*') {
1863 if (*bufp == *(patternp + 1)) {
1865 matchp = star_match[++star_count];
1869 } else if (*bufp == '\n' || *bufp == '\r') {
1871 if (*patternp == NULLCHAR)
1876 *matchp++ = *bufp++;
1880 if (*patternp != *bufp) return FALSE;
1887 SendToPlayer(data, length)
1891 int error, outCount;
1892 outCount = OutputToProcess(NoProc, data, length, &error);
1893 if (outCount < length) {
1894 DisplayFatalError(_("Error writing to display"), error, 1);
1899 PackHolding(packed, holding)
1911 switch (runlength) {
1922 sprintf(q, "%d", runlength);
1934 /* Telnet protocol requests from the front end */
1936 TelnetRequest(ddww, option)
1937 unsigned char ddww, option;
1939 unsigned char msg[3];
1940 int outCount, outError;
1942 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
1944 if (appData.debugMode) {
1945 char buf1[8], buf2[8], *ddwwStr, *optionStr;
1961 sprintf(buf1, "%d", ddww);
1970 sprintf(buf2, "%d", option);
1973 fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
1978 outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
1980 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1987 if (!appData.icsActive) return;
1988 TelnetRequest(TN_DO, TN_ECHO);
1994 if (!appData.icsActive) return;
1995 TelnetRequest(TN_DONT, TN_ECHO);
1999 CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece)
2001 /* put the holdings sent to us by the server on the board holdings area */
2002 int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2006 if(gameInfo.holdingsWidth < 2) return;
2007 if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2008 return; // prevent overwriting by pre-board holdings
2010 if( (int)lowestPiece >= BlackPawn ) {
2013 holdingsStartRow = BOARD_HEIGHT-1;
2016 holdingsColumn = BOARD_WIDTH-1;
2017 countsColumn = BOARD_WIDTH-2;
2018 holdingsStartRow = 0;
2022 for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
2023 board[i][holdingsColumn] = EmptySquare;
2024 board[i][countsColumn] = (ChessSquare) 0;
2026 while( (p=*holdings++) != NULLCHAR ) {
2027 piece = CharToPiece( ToUpper(p) );
2028 if(piece == EmptySquare) continue;
2029 /*j = (int) piece - (int) WhitePawn;*/
2030 j = PieceToNumber(piece);
2031 if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2032 if(j < 0) continue; /* should not happen */
2033 piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2034 board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2035 board[holdingsStartRow+j*direction][countsColumn]++;
2041 VariantSwitch(Board board, VariantClass newVariant)
2043 int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2046 startedFromPositionFile = FALSE;
2047 if(gameInfo.variant == newVariant) return;
2049 /* [HGM] This routine is called each time an assignment is made to
2050 * gameInfo.variant during a game, to make sure the board sizes
2051 * are set to match the new variant. If that means adding or deleting
2052 * holdings, we shift the playing board accordingly
2053 * This kludge is needed because in ICS observe mode, we get boards
2054 * of an ongoing game without knowing the variant, and learn about the
2055 * latter only later. This can be because of the move list we requested,
2056 * in which case the game history is refilled from the beginning anyway,
2057 * but also when receiving holdings of a crazyhouse game. In the latter
2058 * case we want to add those holdings to the already received position.
2061 if (appData.debugMode) {
2062 fprintf(debugFP, "Switch board from %s to %s\n",
2063 VariantName(gameInfo.variant), VariantName(newVariant));
2064 setbuf(debugFP, NULL);
2066 shuffleOpenings = 0; /* [HGM] shuffle */
2067 gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2071 newWidth = 9; newHeight = 9;
2072 gameInfo.holdingsSize = 7;
2073 case VariantBughouse:
2074 case VariantCrazyhouse:
2075 newHoldingsWidth = 2; break;
2079 newHoldingsWidth = 2;
2080 gameInfo.holdingsSize = 8;
2083 case VariantCapablanca:
2084 case VariantCapaRandom:
2087 newHoldingsWidth = gameInfo.holdingsSize = 0;
2090 if(newWidth != gameInfo.boardWidth ||
2091 newHeight != gameInfo.boardHeight ||
2092 newHoldingsWidth != gameInfo.holdingsWidth ) {
2094 /* shift position to new playing area, if needed */
2095 if(newHoldingsWidth > gameInfo.holdingsWidth) {
2096 for(i=0; i<BOARD_HEIGHT; i++)
2097 for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2098 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2100 for(i=0; i<newHeight; i++) {
2101 board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2102 board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2104 } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2105 for(i=0; i<BOARD_HEIGHT; i++)
2106 for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2107 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2110 gameInfo.boardWidth = newWidth;
2111 gameInfo.boardHeight = newHeight;
2112 gameInfo.holdingsWidth = newHoldingsWidth;
2113 gameInfo.variant = newVariant;
2114 InitDrawingSizes(-2, 0);
2115 } else gameInfo.variant = newVariant;
2116 CopyBoard(oldBoard, board); // remember correctly formatted board
2117 InitPosition(FALSE); /* this sets up board[0], but also other stuff */
2118 DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2121 static int loggedOn = FALSE;
2123 /*-- Game start info cache: --*/
2125 char gs_kind[MSG_SIZ];
2126 static char player1Name[128] = "";
2127 static char player2Name[128] = "";
2128 static char cont_seq[] = "\n\\ ";
2129 static int player1Rating = -1;
2130 static int player2Rating = -1;
2131 /*----------------------------*/
2133 ColorClass curColor = ColorNormal;
2134 int suppressKibitz = 0;
2137 Boolean soughtPending = FALSE;
2138 Boolean seekGraphUp;
2139 #define MAX_SEEK_ADS 200
2141 char *seekAdList[MAX_SEEK_ADS];
2142 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2143 float tcList[MAX_SEEK_ADS];
2144 char colorList[MAX_SEEK_ADS];
2145 int nrOfSeekAds = 0;
2146 int minRating = 1010, maxRating = 2800;
2147 int hMargin = 10, vMargin = 20, h, w;
2148 extern int squareSize, lineGap;
2153 int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2154 xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2155 if(r < minRating+100 && r >=0 ) r = minRating+100;
2156 if(r > maxRating) r = maxRating;
2157 if(tc < 1.) tc = 1.;
2158 if(tc > 95.) tc = 95.;
2159 x = (w-hMargin)* log(tc)/log(100.) + hMargin;
2160 y = ((double)r - minRating)/(maxRating - minRating)
2161 * (h-vMargin-squareSize/8-1) + vMargin;
2162 if(ratingList[i] < 0) y = vMargin + squareSize/4;
2163 if(strstr(seekAdList[i], " u ")) color = 1;
2164 if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2165 !strstr(seekAdList[i], "bullet") &&
2166 !strstr(seekAdList[i], "blitz") &&
2167 !strstr(seekAdList[i], "standard") ) color = 2;
2168 if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2169 DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2173 AddAd(char *handle, char *rating, int base, int inc, char rated, char *type, int nr, Boolean plot)
2175 char buf[MSG_SIZ], *ext = "";
2176 VariantClass v = StringToVariant(type);
2177 if(strstr(type, "wild")) {
2178 ext = type + 4; // append wild number
2179 if(v == VariantFischeRandom) type = "chess960"; else
2180 if(v == VariantLoadable) type = "setup"; else
2181 type = VariantName(v);
2183 sprintf(buf, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2184 if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2185 if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2186 ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2187 sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2188 tcList[nrOfSeekAds] = base + (2./3.)*inc;
2189 seekNrList[nrOfSeekAds] = nr;
2190 zList[nrOfSeekAds] = 0;
2191 seekAdList[nrOfSeekAds++] = StrSave(buf);
2192 if(plot) PlotSeekAd(nrOfSeekAds-1);
2199 int x = xList[i], y = yList[i], d=squareSize/4, k;
2200 DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2201 if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2202 // now replot every dot that overlapped
2203 for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2204 int xx = xList[k], yy = yList[k];
2205 if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2206 DrawSeekDot(xx, yy, colorList[k]);
2211 RemoveSeekAd(int nr)
2214 for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2216 if(seekAdList[i]) free(seekAdList[i]);
2217 seekAdList[i] = seekAdList[--nrOfSeekAds];
2218 seekNrList[i] = seekNrList[nrOfSeekAds];
2219 ratingList[i] = ratingList[nrOfSeekAds];
2220 colorList[i] = colorList[nrOfSeekAds];
2221 tcList[i] = tcList[nrOfSeekAds];
2222 xList[i] = xList[nrOfSeekAds];
2223 yList[i] = yList[nrOfSeekAds];
2224 zList[i] = zList[nrOfSeekAds];
2225 seekAdList[nrOfSeekAds] = NULL;
2231 MatchSoughtLine(char *line)
2233 char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2234 int nr, base, inc, u=0; char dummy;
2236 if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2237 sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2239 (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2240 sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7) ) {
2241 // match: compact and save the line
2242 AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2251 if(!seekGraphUp) return FALSE;
2253 h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
2254 w = BOARD_WIDTH * (squareSize + lineGap) + lineGap;
2256 DrawSeekBackground(0, 0, w, h);
2257 DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2258 DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2259 for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2260 int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2262 DrawSeekAxis(hMargin+5*(i%500==0), yy, hMargin-5, yy); // rating ticks
2265 sprintf(buf, "%d", i);
2266 DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2269 DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2270 for(i=1; i<100; i+=(i<10?1:5)) {
2271 int xx = (w-hMargin)* log((double)i)/log(100.) + hMargin;
2272 DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2273 if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2275 sprintf(buf, "%d", i);
2276 DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2279 for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2283 int SeekGraphClick(ClickType click, int x, int y, int moving)
2285 static int lastDown = 0, displayed = 0, lastSecond;
2286 if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2287 if(click == Release || moving) return FALSE;
2289 soughtPending = TRUE;
2290 SendToICS(ics_prefix);
2291 SendToICS("sought\n"); // should this be "sought all"?
2292 } else { // issue challenge based on clicked ad
2293 int dist = 10000; int i, closest = 0, second = 0;
2294 for(i=0; i<nrOfSeekAds; i++) {
2295 int d = (x-xList[i])*(x-xList[i]) + (y-yList[i])*(y-yList[i]) + zList[i];
2296 if(d < dist) { dist = d; closest = i; }
2297 second += (d - zList[i] < 120); // count in-range ads
2298 if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2302 second = (second > 1);
2303 if(displayed != closest || second != lastSecond) {
2304 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2305 lastSecond = second; displayed = closest;
2307 sprintf(buf, "play %d\n", seekNrList[closest]);
2308 if(click == Press) {
2309 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2312 } // on press 'hit', only show info
2313 if(moving == 2) return TRUE; // ignore right up-clicks on dot
2314 SendToICS(ics_prefix);
2315 SendToICS(buf); // should this be "sought all"?
2316 } else if(click == Release) { // release 'miss' is ignored
2317 zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2318 if(moving == 2) { // right up-click
2319 nrOfSeekAds = 0; // refresh graph
2320 soughtPending = TRUE;
2321 SendToICS(ics_prefix);
2322 SendToICS("sought\n"); // should this be "sought all"?
2325 } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2326 // press miss or release hit 'pop down' seek graph
2327 seekGraphUp = FALSE;
2328 DrawPosition(TRUE, NULL);
2334 read_from_ics(isr, closure, data, count, error)
2341 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2342 #define STARTED_NONE 0
2343 #define STARTED_MOVES 1
2344 #define STARTED_BOARD 2
2345 #define STARTED_OBSERVE 3
2346 #define STARTED_HOLDINGS 4
2347 #define STARTED_CHATTER 5
2348 #define STARTED_COMMENT 6
2349 #define STARTED_MOVES_NOHIDE 7
2351 static int started = STARTED_NONE;
2352 static char parse[20000];
2353 static int parse_pos = 0;
2354 static char buf[BUF_SIZE + 1];
2355 static int firstTime = TRUE, intfSet = FALSE;
2356 static ColorClass prevColor = ColorNormal;
2357 static int savingComment = FALSE;
2358 static int cmatch = 0; // continuation sequence match
2365 int backup; /* [DM] For zippy color lines */
2367 char talker[MSG_SIZ]; // [HGM] chat
2370 connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2372 if (appData.debugMode) {
2374 fprintf(debugFP, "<ICS: ");
2375 show_bytes(debugFP, data, count);
2376 fprintf(debugFP, "\n");
2380 if (appData.debugMode) { int f = forwardMostMove;
2381 fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2382 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2383 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2386 /* If last read ended with a partial line that we couldn't parse,
2387 prepend it to the new read and try again. */
2388 if (leftover_len > 0) {
2389 for (i=0; i<leftover_len; i++)
2390 buf[i] = buf[leftover_start + i];
2393 /* copy new characters into the buffer */
2394 bp = buf + leftover_len;
2395 buf_len=leftover_len;
2396 for (i=0; i<count; i++)
2399 if (data[i] == '\r')
2402 // join lines split by ICS?
2403 if (!appData.noJoin)
2406 Joining just consists of finding matches against the
2407 continuation sequence, and discarding that sequence
2408 if found instead of copying it. So, until a match
2409 fails, there's nothing to do since it might be the
2410 complete sequence, and thus, something we don't want
2413 if (data[i] == cont_seq[cmatch])
2416 if (cmatch == strlen(cont_seq))
2418 cmatch = 0; // complete match. just reset the counter
2421 it's possible for the ICS to not include the space
2422 at the end of the last word, making our [correct]
2423 join operation fuse two separate words. the server
2424 does this when the space occurs at the width setting.
2426 if (!buf_len || buf[buf_len-1] != ' ')
2437 match failed, so we have to copy what matched before
2438 falling through and copying this character. In reality,
2439 this will only ever be just the newline character, but
2440 it doesn't hurt to be precise.
2442 strncpy(bp, cont_seq, cmatch);
2454 buf[buf_len] = NULLCHAR;
2455 // next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2460 while (i < buf_len) {
2461 /* Deal with part of the TELNET option negotiation
2462 protocol. We refuse to do anything beyond the
2463 defaults, except that we allow the WILL ECHO option,
2464 which ICS uses to turn off password echoing when we are
2465 directly connected to it. We reject this option
2466 if localLineEditing mode is on (always on in xboard)
2467 and we are talking to port 23, which might be a real
2468 telnet server that will try to keep WILL ECHO on permanently.
2470 if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2471 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2472 unsigned char option;
2474 switch ((unsigned char) buf[++i]) {
2476 if (appData.debugMode)
2477 fprintf(debugFP, "\n<WILL ");
2478 switch (option = (unsigned char) buf[++i]) {
2480 if (appData.debugMode)
2481 fprintf(debugFP, "ECHO ");
2482 /* Reply only if this is a change, according
2483 to the protocol rules. */
2484 if (remoteEchoOption) break;
2485 if (appData.localLineEditing &&
2486 atoi(appData.icsPort) == TN_PORT) {
2487 TelnetRequest(TN_DONT, TN_ECHO);
2490 TelnetRequest(TN_DO, TN_ECHO);
2491 remoteEchoOption = TRUE;
2495 if (appData.debugMode)
2496 fprintf(debugFP, "%d ", option);
2497 /* Whatever this is, we don't want it. */
2498 TelnetRequest(TN_DONT, option);
2503 if (appData.debugMode)
2504 fprintf(debugFP, "\n<WONT ");
2505 switch (option = (unsigned char) buf[++i]) {
2507 if (appData.debugMode)
2508 fprintf(debugFP, "ECHO ");
2509 /* Reply only if this is a change, according
2510 to the protocol rules. */
2511 if (!remoteEchoOption) break;
2513 TelnetRequest(TN_DONT, TN_ECHO);
2514 remoteEchoOption = FALSE;
2517 if (appData.debugMode)
2518 fprintf(debugFP, "%d ", (unsigned char) option);
2519 /* Whatever this is, it must already be turned
2520 off, because we never agree to turn on
2521 anything non-default, so according to the
2522 protocol rules, we don't reply. */
2527 if (appData.debugMode)
2528 fprintf(debugFP, "\n<DO ");
2529 switch (option = (unsigned char) buf[++i]) {
2531 /* Whatever this is, we refuse to do it. */
2532 if (appData.debugMode)
2533 fprintf(debugFP, "%d ", option);
2534 TelnetRequest(TN_WONT, option);
2539 if (appData.debugMode)
2540 fprintf(debugFP, "\n<DONT ");
2541 switch (option = (unsigned char) buf[++i]) {
2543 if (appData.debugMode)
2544 fprintf(debugFP, "%d ", option);
2545 /* Whatever this is, we are already not doing
2546 it, because we never agree to do anything
2547 non-default, so according to the protocol
2548 rules, we don't reply. */
2553 if (appData.debugMode)
2554 fprintf(debugFP, "\n<IAC ");
2555 /* Doubled IAC; pass it through */
2559 if (appData.debugMode)
2560 fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2561 /* Drop all other telnet commands on the floor */
2564 if (oldi > next_out)
2565 SendToPlayer(&buf[next_out], oldi - next_out);
2571 /* OK, this at least will *usually* work */
2572 if (!loggedOn && looking_at(buf, &i, "ics%")) {
2576 if (loggedOn && !intfSet) {
2577 if (ics_type == ICS_ICC) {
2579 "/set-quietly interface %s\n/set-quietly style 12\n",
2581 if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2582 strcat(str, "/set-2 51 1\n/set seek 1\n");
2583 } else if (ics_type == ICS_CHESSNET) {
2584 sprintf(str, "/style 12\n");
2586 strcpy(str, "alias $ @\n$set interface ");
2587 strcat(str, programVersion);
2588 strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2589 if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2590 strcat(str, "$iset seekremove 1\n$set seek 1\n");
2592 strcat(str, "$iset nohighlight 1\n");
2594 strcat(str, "$iset lock 1\n$style 12\n");
2597 NotifyFrontendLogin();
2601 if (started == STARTED_COMMENT) {
2602 /* Accumulate characters in comment */
2603 parse[parse_pos++] = buf[i];
2604 if (buf[i] == '\n') {
2605 parse[parse_pos] = NULLCHAR;
2606 if(chattingPartner>=0) {
2608 sprintf(mess, "%s%s", talker, parse);
2609 OutputChatMessage(chattingPartner, mess);
2610 chattingPartner = -1;
2612 if(!suppressKibitz) // [HGM] kibitz
2613 AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
2614 else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2615 int nrDigit = 0, nrAlph = 0, j;
2616 if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2617 { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2618 parse[parse_pos] = NULLCHAR;
2619 // try to be smart: if it does not look like search info, it should go to
2620 // ICS interaction window after all, not to engine-output window.
2621 for(j=0; j<parse_pos; j++) { // count letters and digits
2622 nrDigit += (parse[j] >= '0' && parse[j] <= '9');
2623 nrAlph += (parse[j] >= 'a' && parse[j] <= 'z');
2624 nrAlph += (parse[j] >= 'A' && parse[j] <= 'Z');
2626 if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2627 int depth=0; float score;
2628 if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2629 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2630 pvInfoList[forwardMostMove-1].depth = depth;
2631 pvInfoList[forwardMostMove-1].score = 100*score;
2633 OutputKibitz(suppressKibitz, parse);
2634 next_out = i+1; // [HGM] suppress printing in ICS window
2637 sprintf(tmp, _("your opponent kibitzes: %s"), parse);
2638 SendToPlayer(tmp, strlen(tmp));
2641 started = STARTED_NONE;
2643 /* Don't match patterns against characters in comment */
2648 if (started == STARTED_CHATTER) {
2649 if (buf[i] != '\n') {
2650 /* Don't match patterns against characters in chatter */
2654 started = STARTED_NONE;
2657 /* Kludge to deal with rcmd protocol */
2658 if (firstTime && looking_at(buf, &i, "\001*")) {
2659 DisplayFatalError(&buf[1], 0, 1);
2665 if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2668 if (appData.debugMode)
2669 fprintf(debugFP, "ics_type %d\n", ics_type);
2672 if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
2673 ics_type = ICS_FICS;
2675 if (appData.debugMode)
2676 fprintf(debugFP, "ics_type %d\n", ics_type);
2679 if (!loggedOn && looking_at(buf, &i, "chess.net")) {
2680 ics_type = ICS_CHESSNET;
2682 if (appData.debugMode)
2683 fprintf(debugFP, "ics_type %d\n", ics_type);
2688 (looking_at(buf, &i, "\"*\" is *a registered name") ||
2689 looking_at(buf, &i, "Logging you in as \"*\"") ||
2690 looking_at(buf, &i, "will be \"*\""))) {
2691 strcpy(ics_handle, star_match[0]);
2695 if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
2697 snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
2698 DisplayIcsInteractionTitle(buf);
2699 have_set_title = TRUE;
2702 /* skip finger notes */
2703 if (started == STARTED_NONE &&
2704 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
2705 (buf[i] == '1' && buf[i+1] == '0')) &&
2706 buf[i+2] == ':' && buf[i+3] == ' ') {
2707 started = STARTED_CHATTER;
2712 // [HGM] seekgraph: recognize sought lines and end-of-sought message
2713 if(appData.seekGraph) {
2714 if(soughtPending && MatchSoughtLine(buf+i)) {
2715 i = strstr(buf+i, "rated") - buf;
2716 next_out = leftover_start = i;
2717 started = STARTED_CHATTER;
2718 suppressKibitz = TRUE;
2721 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
2722 && looking_at(buf, &i, "* ads displayed")) {
2723 soughtPending = FALSE;
2728 if(appData.autoRefresh) {
2729 if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
2730 int s = (ics_type == ICS_ICC); // ICC format differs
2732 AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
2733 star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
2734 looking_at(buf, &i, "*% "); // eat prompt
2735 next_out = i; // suppress
2738 if(looking_at(buf, &i, "Ads removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
2739 char *p = star_match[0];
2741 if(seekGraphUp) RemoveSeekAd(atoi(p));
2742 while(*p && *p++ != ' '); // next
2744 looking_at(buf, &i, "*% "); // eat prompt
2751 /* skip formula vars */
2752 if (started == STARTED_NONE &&
2753 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
2754 started = STARTED_CHATTER;
2760 // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
2761 if (appData.autoKibitz && started == STARTED_NONE &&
2762 !appData.icsEngineAnalyze && // [HGM] [DM] ICS analyze
2763 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
2764 if(looking_at(buf, &i, "* kibitzes: ") &&
2765 (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
2766 StrStr(star_match[0], gameInfo.black) == star_match[0] )) { // kibitz of self or opponent
2767 suppressKibitz = TRUE;
2768 if((StrStr(star_match[0], gameInfo.white) == star_match[0]
2769 && (gameMode == IcsPlayingWhite)) ||
2770 (StrStr(star_match[0], gameInfo.black) == star_match[0]
2771 && (gameMode == IcsPlayingBlack)) ) // opponent kibitz
2772 started = STARTED_CHATTER; // own kibitz we simply discard
2774 started = STARTED_COMMENT; // make sure it will be collected in parse[]
2775 parse_pos = 0; parse[0] = NULLCHAR;
2776 savingComment = TRUE;
2777 suppressKibitz = gameMode != IcsObserving ? 2 :
2778 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
2782 if(looking_at(buf, &i, "kibitzed to *\n") && atoi(star_match[0])) {
2783 // suppress the acknowledgements of our own autoKibitz
2785 if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
2786 SendToPlayer(star_match[0], strlen(star_match[0]));
2787 looking_at(buf, &i, "*% "); // eat prompt
2790 } // [HGM] kibitz: end of patch
2792 //if(appData.debugMode) fprintf(debugFP, "hunt for tell, buf = %s\n", buf+i);
2794 // [HGM] chat: intercept tells by users for which we have an open chat window
2796 if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
2797 looking_at(buf, &i, "* whispers:") ||
2798 looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
2799 looking_at(buf, &i, "*(*)(*):") && sscanf(star_match[2], "%d", &channel) == 1 )) {
2801 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
2802 chattingPartner = -1;
2804 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
2805 for(p=0; p<MAX_CHAT; p++) {
2806 if(channel == atoi(chatPartner[p])) {
2807 talker[0] = '['; strcat(talker, "] ");
2808 chattingPartner = p; break;
2811 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
2812 for(p=0; p<MAX_CHAT; p++) {
2813 if(!strcmp("WHISPER", chatPartner[p])) {
2814 talker[0] = '['; strcat(talker, "] ");
2815 chattingPartner = p; break;
2818 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
2819 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
2821 chattingPartner = p; break;
2823 if(chattingPartner<0) i = oldi; else {
2824 started = STARTED_COMMENT;
2825 parse_pos = 0; parse[0] = NULLCHAR;
2826 savingComment = 3 + chattingPartner; // counts as TRUE
2827 suppressKibitz = TRUE;
2829 } // [HGM] chat: end of patch
2831 if (appData.zippyTalk || appData.zippyPlay) {
2832 /* [DM] Backup address for color zippy lines */
2836 if (loggedOn == TRUE)
2837 if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
2838 (appData.zippyPlay && ZippyMatch(buf, &backup)));
2840 if (ZippyControl(buf, &i) ||
2841 ZippyConverse(buf, &i) ||
2842 (appData.zippyPlay && ZippyMatch(buf, &i))) {
2844 if (!appData.colorize) continue;
2848 } // [DM] 'else { ' deleted
2850 /* Regular tells and says */
2851 (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
2852 looking_at(buf, &i, "* (your partner) tells you: ") ||
2853 looking_at(buf, &i, "* says: ") ||
2854 /* Don't color "message" or "messages" output */
2855 (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
2856 looking_at(buf, &i, "*. * at *:*: ") ||
2857 looking_at(buf, &i, "--* (*:*): ") ||
2858 /* Message notifications (same color as tells) */
2859 looking_at(buf, &i, "* has left a message ") ||
2860 looking_at(buf, &i, "* just sent you a message:\n") ||
2861 /* Whispers and kibitzes */
2862 (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
2863 looking_at(buf, &i, "* kibitzes: ") ||
2865 (tkind = 3, looking_at(buf, &i, "*(*: "))) {
2867 if (tkind == 1 && strchr(star_match[0], ':')) {
2868 /* Avoid "tells you:" spoofs in channels */
2871 if (star_match[0][0] == NULLCHAR ||
2872 strchr(star_match[0], ' ') ||
2873 (tkind == 3 && strchr(star_match[1], ' '))) {
2874 /* Reject bogus matches */
2877 if (appData.colorize) {
2878 if (oldi > next_out) {
2879 SendToPlayer(&buf[next_out], oldi - next_out);
2884 Colorize(ColorTell, FALSE);
2885 curColor = ColorTell;
2888 Colorize(ColorKibitz, FALSE);
2889 curColor = ColorKibitz;
2892 p = strrchr(star_match[1], '(');
2899 Colorize(ColorChannel1, FALSE);
2900 curColor = ColorChannel1;
2902 Colorize(ColorChannel, FALSE);
2903 curColor = ColorChannel;
2907 curColor = ColorNormal;
2911 if (started == STARTED_NONE && appData.autoComment &&
2912 (gameMode == IcsObserving ||
2913 gameMode == IcsPlayingWhite ||
2914 gameMode == IcsPlayingBlack)) {
2915 parse_pos = i - oldi;
2916 memcpy(parse, &buf[oldi], parse_pos);
2917 parse[parse_pos] = NULLCHAR;
2918 started = STARTED_COMMENT;
2919 savingComment = TRUE;
2921 started = STARTED_CHATTER;
2922 savingComment = FALSE;
2929 if (looking_at(buf, &i, "* s-shouts: ") ||
2930 looking_at(buf, &i, "* c-shouts: ")) {
2931 if (appData.colorize) {
2932 if (oldi > next_out) {
2933 SendToPlayer(&buf[next_out], oldi - next_out);
2936 Colorize(ColorSShout, FALSE);
2937 curColor = ColorSShout;
2940 started = STARTED_CHATTER;
2944 if (looking_at(buf, &i, "--->")) {
2949 if (looking_at(buf, &i, "* shouts: ") ||
2950 looking_at(buf, &i, "--> ")) {
2951 if (appData.colorize) {
2952 if (oldi > next_out) {
2953 SendToPlayer(&buf[next_out], oldi - next_out);
2956 Colorize(ColorShout, FALSE);
2957 curColor = ColorShout;
2960 started = STARTED_CHATTER;
2964 if (looking_at( buf, &i, "Challenge:")) {
2965 if (appData.colorize) {
2966 if (oldi > next_out) {
2967 SendToPlayer(&buf[next_out], oldi - next_out);
2970 Colorize(ColorChallenge, FALSE);
2971 curColor = ColorChallenge;
2977 if (looking_at(buf, &i, "* offers you") ||
2978 looking_at(buf, &i, "* offers to be") ||
2979 looking_at(buf, &i, "* would like to") ||
2980 looking_at(buf, &i, "* requests to") ||
2981 looking_at(buf, &i, "Your opponent offers") ||
2982 looking_at(buf, &i, "Your opponent requests")) {
2984 if (appData.colorize) {
2985 if (oldi > next_out) {
2986 SendToPlayer(&buf[next_out], oldi - next_out);
2989 Colorize(ColorRequest, FALSE);
2990 curColor = ColorRequest;
2995 if (looking_at(buf, &i, "* (*) seeking")) {
2996 if (appData.colorize) {
2997 if (oldi > next_out) {
2998 SendToPlayer(&buf[next_out], oldi - next_out);
3001 Colorize(ColorSeek, FALSE);
3002 curColor = ColorSeek;
3007 if (looking_at(buf, &i, "\\ ")) {
3008 if (prevColor != ColorNormal) {
3009 if (oldi > next_out) {
3010 SendToPlayer(&buf[next_out], oldi - next_out);
3013 Colorize(prevColor, TRUE);
3014 curColor = prevColor;
3016 if (savingComment) {
3017 parse_pos = i - oldi;
3018 memcpy(parse, &buf[oldi], parse_pos);
3019 parse[parse_pos] = NULLCHAR;
3020 started = STARTED_COMMENT;
3021 if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3022 chattingPartner = savingComment - 3; // kludge to remember the box
3024 started = STARTED_CHATTER;
3029 if (looking_at(buf, &i, "Black Strength :") ||
3030 looking_at(buf, &i, "<<< style 10 board >>>") ||
3031 looking_at(buf, &i, "<10>") ||
3032 looking_at(buf, &i, "#@#")) {
3033 /* Wrong board style */
3035 SendToICS(ics_prefix);
3036 SendToICS("set style 12\n");
3037 SendToICS(ics_prefix);
3038 SendToICS("refresh\n");
3042 if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
3044 have_sent_ICS_logon = 1;
3048 if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3049 (looking_at(buf, &i, "\n<12> ") ||
3050 looking_at(buf, &i, "<12> "))) {
3052 if (oldi > next_out) {
3053 SendToPlayer(&buf[next_out], oldi - next_out);
3056 started = STARTED_BOARD;
3061 if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3062 looking_at(buf, &i, "<b1> ")) {
3063 if (oldi > next_out) {
3064 SendToPlayer(&buf[next_out], oldi - next_out);
3067 started = STARTED_HOLDINGS;
3072 if (looking_at(buf, &i, "* *vs. * *--- *")) {
3074 /* Header for a move list -- first line */
3076 switch (ics_getting_history) {
3080 case BeginningOfGame:
3081 /* User typed "moves" or "oldmoves" while we
3082 were idle. Pretend we asked for these
3083 moves and soak them up so user can step
3084 through them and/or save them.
3087 gameMode = IcsObserving;
3090 ics_getting_history = H_GOT_UNREQ_HEADER;
3092 case EditGame: /*?*/
3093 case EditPosition: /*?*/
3094 /* Should above feature work in these modes too? */
3095 /* For now it doesn't */
3096 ics_getting_history = H_GOT_UNWANTED_HEADER;
3099 ics_getting_history = H_GOT_UNWANTED_HEADER;
3104 /* Is this the right one? */
3105 if (gameInfo.white && gameInfo.black &&
3106 strcmp(gameInfo.white, star_match[0]) == 0 &&
3107 strcmp(gameInfo.black, star_match[2]) == 0) {
3109 ics_getting_history = H_GOT_REQ_HEADER;
3112 case H_GOT_REQ_HEADER:
3113 case H_GOT_UNREQ_HEADER:
3114 case H_GOT_UNWANTED_HEADER:
3115 case H_GETTING_MOVES:
3116 /* Should not happen */
3117 DisplayError(_("Error gathering move list: two headers"), 0);
3118 ics_getting_history = H_FALSE;
3122 /* Save player ratings into gameInfo if needed */
3123 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3124 ics_getting_history == H_GOT_UNREQ_HEADER) &&
3125 (gameInfo.whiteRating == -1 ||
3126 gameInfo.blackRating == -1)) {
3128 gameInfo.whiteRating = string_to_rating(star_match[1]);
3129 gameInfo.blackRating = string_to_rating(star_match[3]);
3130 if (appData.debugMode)
3131 fprintf(debugFP, _("Ratings from header: W %d, B %d\n"),
3132 gameInfo.whiteRating, gameInfo.blackRating);
3137 if (looking_at(buf, &i,
3138 "* * match, initial time: * minute*, increment: * second")) {
3139 /* Header for a move list -- second line */
3140 /* Initial board will follow if this is a wild game */
3141 if (gameInfo.event != NULL) free(gameInfo.event);
3142 sprintf(str, "ICS %s %s match", star_match[0], star_match[1]);
3143 gameInfo.event = StrSave(str);
3144 /* [HGM] we switched variant. Translate boards if needed. */
3145 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3149 if (looking_at(buf, &i, "Move ")) {
3150 /* Beginning of a move list */
3151 switch (ics_getting_history) {
3153 /* Normally should not happen */
3154 /* Maybe user hit reset while we were parsing */
3157 /* Happens if we are ignoring a move list that is not
3158 * the one we just requested. Common if the user
3159 * tries to observe two games without turning off
3162 case H_GETTING_MOVES:
3163 /* Should not happen */
3164 DisplayError(_("Error gathering move list: nested"), 0);
3165 ics_getting_history = H_FALSE;
3167 case H_GOT_REQ_HEADER:
3168 ics_getting_history = H_GETTING_MOVES;
3169 started = STARTED_MOVES;
3171 if (oldi > next_out) {
3172 SendToPlayer(&buf[next_out], oldi - next_out);
3175 case H_GOT_UNREQ_HEADER:
3176 ics_getting_history = H_GETTING_MOVES;
3177 started = STARTED_MOVES_NOHIDE;
3180 case H_GOT_UNWANTED_HEADER:
3181 ics_getting_history = H_FALSE;
3187 if (looking_at(buf, &i, "% ") ||
3188 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3189 && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3190 if(ics_type == ICS_ICC && soughtPending) { // [HGM] seekgraph: on ICC sought-list has no termination line
3191 soughtPending = FALSE;
3195 if(suppressKibitz) next_out = i;
3196 savingComment = FALSE;
3200 case STARTED_MOVES_NOHIDE:
3201 memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3202 parse[parse_pos + i - oldi] = NULLCHAR;
3203 ParseGameHistory(parse);
3205 if (appData.zippyPlay && first.initDone) {
3206 FeedMovesToProgram(&first, forwardMostMove);
3207 if (gameMode == IcsPlayingWhite) {
3208 if (WhiteOnMove(forwardMostMove)) {
3209 if (first.sendTime) {
3210 if (first.useColors) {
3211 SendToProgram("black\n", &first);
3213 SendTimeRemaining(&first, TRUE);
3215 if (first.useColors) {
3216 SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3218 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3219 first.maybeThinking = TRUE;
3221 if (first.usePlayother) {
3222 if (first.sendTime) {
3223 SendTimeRemaining(&first, TRUE);
3225 SendToProgram("playother\n", &first);
3231 } else if (gameMode == IcsPlayingBlack) {
3232 if (!WhiteOnMove(forwardMostMove)) {
3233 if (first.sendTime) {
3234 if (first.useColors) {
3235 SendToProgram("white\n", &first);
3237 SendTimeRemaining(&first, FALSE);
3239 if (first.useColors) {
3240 SendToProgram("black\n", &first);
3242 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3243 first.maybeThinking = TRUE;
3245 if (first.usePlayother) {
3246 if (first.sendTime) {
3247 SendTimeRemaining(&first, FALSE);
3249 SendToProgram("playother\n", &first);
3258 if (gameMode == IcsObserving && ics_gamenum == -1) {
3259 /* Moves came from oldmoves or moves command
3260 while we weren't doing anything else.
3262 currentMove = forwardMostMove;
3263 ClearHighlights();/*!!could figure this out*/
3264 flipView = appData.flipView;
3265 DrawPosition(TRUE, boards[currentMove]);
3266 DisplayBothClocks();
3267 sprintf(str, "%s vs. %s",
3268 gameInfo.white, gameInfo.black);
3272 /* Moves were history of an active game */
3273 if (gameInfo.resultDetails != NULL) {
3274 free(gameInfo.resultDetails);
3275 gameInfo.resultDetails = NULL;
3278 HistorySet(parseList, backwardMostMove,
3279 forwardMostMove, currentMove-1);
3280 DisplayMove(currentMove - 1);
3281 if (started == STARTED_MOVES) next_out = i;
3282 started = STARTED_NONE;
3283 ics_getting_history = H_FALSE;
3286 case STARTED_OBSERVE:
3287 started = STARTED_NONE;
3288 SendToICS(ics_prefix);
3289 SendToICS("refresh\n");
3295 if(bookHit) { // [HGM] book: simulate book reply
3296 static char bookMove[MSG_SIZ]; // a bit generous?
3298 programStats.nodes = programStats.depth = programStats.time =
3299 programStats.score = programStats.got_only_move = 0;
3300 sprintf(programStats.movelist, "%s (xbook)", bookHit);
3302 strcpy(bookMove, "move ");
3303 strcat(bookMove, bookHit);
3304 HandleMachineMove(bookMove, &first);
3309 if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3310 started == STARTED_HOLDINGS ||
3311 started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3312 /* Accumulate characters in move list or board */
3313 parse[parse_pos++] = buf[i];
3316 /* Start of game messages. Mostly we detect start of game
3317 when the first board image arrives. On some versions
3318 of the ICS, though, we need to do a "refresh" after starting
3319 to observe in order to get the current board right away. */
3320 if (looking_at(buf, &i, "Adding game * to observation list")) {
3321 started = STARTED_OBSERVE;
3325 /* Handle auto-observe */
3326 if (appData.autoObserve &&
3327 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3328 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3330 /* Choose the player that was highlighted, if any. */
3331 if (star_match[0][0] == '\033' ||
3332 star_match[1][0] != '\033') {
3333 player = star_match[0];
3335 player = star_match[2];
3337 sprintf(str, "%sobserve %s\n",
3338 ics_prefix, StripHighlightAndTitle(player));
3341 /* Save ratings from notify string */
3342 strcpy(player1Name, star_match[0]);
3343 player1Rating = string_to_rating(star_match[1]);
3344 strcpy(player2Name, star_match[2]);
3345 player2Rating = string_to_rating(star_match[3]);
3347 if (appData.debugMode)
3349 "Ratings from 'Game notification:' %s %d, %s %d\n",
3350 player1Name, player1Rating,
3351 player2Name, player2Rating);
3356 /* Deal with automatic examine mode after a game,
3357 and with IcsObserving -> IcsExamining transition */
3358 if (looking_at(buf, &i, "Entering examine mode for game *") ||
3359 looking_at(buf, &i, "has made you an examiner of game *")) {
3361 int gamenum = atoi(star_match[0]);
3362 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3363 gamenum == ics_gamenum) {
3364 /* We were already playing or observing this game;
3365 no need to refetch history */
3366 gameMode = IcsExamining;
3368 pauseExamForwardMostMove = forwardMostMove;
3369 } else if (currentMove < forwardMostMove) {
3370 ForwardInner(forwardMostMove);
3373 /* I don't think this case really can happen */
3374 SendToICS(ics_prefix);
3375 SendToICS("refresh\n");
3380 /* Error messages */
3381 // if (ics_user_moved) {
3382 if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3383 if (looking_at(buf, &i, "Illegal move") ||
3384 looking_at(buf, &i, "Not a legal move") ||
3385 looking_at(buf, &i, "Your king is in check") ||
3386 looking_at(buf, &i, "It isn't your turn") ||
3387 looking_at(buf, &i, "It is not your move")) {
3389 if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3390 currentMove = --forwardMostMove;
3391 DisplayMove(currentMove - 1); /* before DMError */
3392 DrawPosition(FALSE, boards[currentMove]);
3394 DisplayBothClocks();
3396 DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3402 if (looking_at(buf, &i, "still have time") ||
3403 looking_at(buf, &i, "not out of time") ||
3404 looking_at(buf, &i, "either player is out of time") ||
3405 looking_at(buf, &i, "has timeseal; checking")) {
3406 /* We must have called his flag a little too soon */
3407 whiteFlag = blackFlag = FALSE;
3411 if (looking_at(buf, &i, "added * seconds to") ||
3412 looking_at(buf, &i, "seconds were added to")) {
3413 /* Update the clocks */
3414 SendToICS(ics_prefix);
3415 SendToICS("refresh\n");
3419 if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3420 ics_clock_paused = TRUE;
3425 if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3426 ics_clock_paused = FALSE;
3431 /* Grab player ratings from the Creating: message.
3432 Note we have to check for the special case when
3433 the ICS inserts things like [white] or [black]. */
3434 if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3435 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3437 0 player 1 name (not necessarily white)
3439 2 empty, white, or black (IGNORED)
3440 3 player 2 name (not necessarily black)
3443 The names/ratings are sorted out when the game
3444 actually starts (below).
3446 strcpy(player1Name, StripHighlightAndTitle(star_match[0]));
3447 player1Rating = string_to_rating(star_match[1]);
3448 strcpy(player2Name, StripHighlightAndTitle(star_match[3]));
3449 player2Rating = string_to_rating(star_match[4]);
3451 if (appData.debugMode)
3453 "Ratings from 'Creating:' %s %d, %s %d\n",
3454 player1Name, player1Rating,
3455 player2Name, player2Rating);
3460 /* Improved generic start/end-of-game messages */
3461 if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3462 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3463 /* If tkind == 0: */
3464 /* star_match[0] is the game number */
3465 /* [1] is the white player's name */
3466 /* [2] is the black player's name */
3467 /* For end-of-game: */
3468 /* [3] is the reason for the game end */
3469 /* [4] is a PGN end game-token, preceded by " " */
3470 /* For start-of-game: */
3471 /* [3] begins with "Creating" or "Continuing" */
3472 /* [4] is " *" or empty (don't care). */
3473 int gamenum = atoi(star_match[0]);
3474 char *whitename, *blackname, *why, *endtoken;
3475 ChessMove endtype = (ChessMove) 0;
3478 whitename = star_match[1];
3479 blackname = star_match[2];
3480 why = star_match[3];
3481 endtoken = star_match[4];
3483 whitename = star_match[1];
3484 blackname = star_match[3];
3485 why = star_match[5];
3486 endtoken = star_match[6];
3489 /* Game start messages */
3490 if (strncmp(why, "Creating ", 9) == 0 ||
3491 strncmp(why, "Continuing ", 11) == 0) {
3492 gs_gamenum = gamenum;
3493 strcpy(gs_kind, strchr(why, ' ') + 1);
3494 VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
3496 if (appData.zippyPlay) {
3497 ZippyGameStart(whitename, blackname);
3503 /* Game end messages */
3504 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3505 ics_gamenum != gamenum) {
3508 while (endtoken[0] == ' ') endtoken++;
3509 switch (endtoken[0]) {
3512 endtype = GameUnfinished;
3515 endtype = BlackWins;
3518 if (endtoken[1] == '/')
3519 endtype = GameIsDrawn;
3521 endtype = WhiteWins;
3524 GameEnds(endtype, why, GE_ICS);
3526 if (appData.zippyPlay && first.initDone) {
3527 ZippyGameEnd(endtype, why);
3528 if (first.pr == NULL) {
3529 /* Start the next process early so that we'll
3530 be ready for the next challenge */
3531 StartChessProgram(&first);
3533 /* Send "new" early, in case this command takes
3534 a long time to finish, so that we'll be ready
3535 for the next challenge. */
3536 gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3543 if (looking_at(buf, &i, "Removing game * from observation") ||
3544 looking_at(buf, &i, "no longer observing game *") ||
3545 looking_at(buf, &i, "Game * (*) has no examiners")) {
3546 if (gameMode == IcsObserving &&
3547 atoi(star_match[0]) == ics_gamenum)
3549 /* icsEngineAnalyze */
3550 if (appData.icsEngineAnalyze) {
3557 ics_user_moved = FALSE;
3562 if (looking_at(buf, &i, "no longer examining game *")) {
3563 if (gameMode == IcsExamining &&
3564 atoi(star_match[0]) == ics_gamenum)
3568 ics_user_moved = FALSE;
3573 /* Advance leftover_start past any newlines we find,
3574 so only partial lines can get reparsed */
3575 if (looking_at(buf, &i, "\n")) {
3576 prevColor = curColor;
3577 if (curColor != ColorNormal) {
3578 if (oldi > next_out) {
3579 SendToPlayer(&buf[next_out], oldi - next_out);
3582 Colorize(ColorNormal, FALSE);
3583 curColor = ColorNormal;
3585 if (started == STARTED_BOARD) {
3586 started = STARTED_NONE;
3587 parse[parse_pos] = NULLCHAR;
3588 ParseBoard12(parse);
3591 /* Send premove here */
3592 if (appData.premove) {
3594 if (currentMove == 0 &&
3595 gameMode == IcsPlayingWhite &&
3596 appData.premoveWhite) {
3597 sprintf(str, "%s\n", appData.premoveWhiteText);
3598 if (appData.debugMode)
3599 fprintf(debugFP, "Sending premove:\n");
3601 } else if (currentMove == 1 &&
3602 gameMode == IcsPlayingBlack &&
3603 appData.premoveBlack) {
3604 sprintf(str, "%s\n", appData.premoveBlackText);
3605 if (appData.debugMode)
3606 fprintf(debugFP, "Sending premove:\n");
3608 } else if (gotPremove) {
3610 ClearPremoveHighlights();
3611 if (appData.debugMode)
3612 fprintf(debugFP, "Sending premove:\n");
3613 UserMoveEvent(premoveFromX, premoveFromY,
3614 premoveToX, premoveToY,
3619 /* Usually suppress following prompt */
3620 if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
3621 while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
3622 if (looking_at(buf, &i, "*% ")) {
3623 savingComment = FALSE;
3628 } else if (started == STARTED_HOLDINGS) {
3630 char new_piece[MSG_SIZ];
3631 started = STARTED_NONE;
3632 parse[parse_pos] = NULLCHAR;
3633 if (appData.debugMode)
3634 fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
3635 parse, currentMove);
3636 if (sscanf(parse, " game %d", &gamenum) == 1 &&
3637 gamenum == ics_gamenum) {
3638 if (gameInfo.variant == VariantNormal) {
3639 /* [HGM] We seem to switch variant during a game!
3640 * Presumably no holdings were displayed, so we have
3641 * to move the position two files to the right to
3642 * create room for them!
3644 VariantClass newVariant;
3645 switch(gameInfo.boardWidth) { // base guess on board width
3646 case 9: newVariant = VariantShogi; break;
3647 case 10: newVariant = VariantGreat; break;
3648 default: newVariant = VariantCrazyhouse; break;
3650 VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3651 /* Get a move list just to see the header, which
3652 will tell us whether this is really bug or zh */
3653 if (ics_getting_history == H_FALSE) {
3654 ics_getting_history = H_REQUESTED;
3655 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3659 new_piece[0] = NULLCHAR;
3660 sscanf(parse, "game %d white [%s black [%s <- %s",
3661 &gamenum, white_holding, black_holding,
3663 white_holding[strlen(white_holding)-1] = NULLCHAR;
3664 black_holding[strlen(black_holding)-1] = NULLCHAR;
3665 /* [HGM] copy holdings to board holdings area */
3666 CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
3667 CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
3668 boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
3670 if (appData.zippyPlay && first.initDone) {
3671 ZippyHoldings(white_holding, black_holding,
3675 if (tinyLayout || smallLayout) {
3676 char wh[16], bh[16];
3677 PackHolding(wh, white_holding);
3678 PackHolding(bh, black_holding);
3679 sprintf(str, "[%s-%s] %s-%s", wh, bh,
3680 gameInfo.white, gameInfo.black);
3682 sprintf(str, "%s [%s] vs. %s [%s]",
3683 gameInfo.white, white_holding,
3684 gameInfo.black, black_holding);
3687 DrawPosition(FALSE, boards[currentMove]);
3690 /* Suppress following prompt */
3691 if (looking_at(buf, &i, "*% ")) {
3692 if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
3693 savingComment = FALSE;
3701 i++; /* skip unparsed character and loop back */
3704 if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
3705 // started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
3706 // SendToPlayer(&buf[next_out], i - next_out);
3707 started != STARTED_HOLDINGS && leftover_start > next_out) {
3708 SendToPlayer(&buf[next_out], leftover_start - next_out);
3712 leftover_len = buf_len - leftover_start;
3713 /* if buffer ends with something we couldn't parse,
3714 reparse it after appending the next read */
3716 } else if (count == 0) {
3717 RemoveInputSource(isr);
3718 DisplayFatalError(_("Connection closed by ICS"), 0, 0);
3720 DisplayFatalError(_("Error reading from ICS"), error, 1);
3725 /* Board style 12 looks like this:
3727 <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
3729 * The "<12> " is stripped before it gets to this routine. The two
3730 * trailing 0's (flip state and clock ticking) are later addition, and
3731 * some chess servers may not have them, or may have only the first.
3732 * Additional trailing fields may be added in the future.
3735 #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"
3737 #define RELATION_OBSERVING_PLAYED 0
3738 #define RELATION_OBSERVING_STATIC -2 /* examined, oldmoves, or smoves */
3739 #define RELATION_PLAYING_MYMOVE 1
3740 #define RELATION_PLAYING_NOTMYMOVE -1
3741 #define RELATION_EXAMINING 2
3742 #define RELATION_ISOLATED_BOARD -3
3743 #define RELATION_STARTING_POSITION -4 /* FICS only */
3746 ParseBoard12(string)
3749 GameMode newGameMode;
3750 int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
3751 int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
3752 int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
3753 char to_play, board_chars[200];
3754 char move_str[500], str[500], elapsed_time[500];
3755 char black[32], white[32];
3757 int prevMove = currentMove;
3760 int fromX, fromY, toX, toY;
3762 int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
3763 char *bookHit = NULL; // [HGM] book
3764 Boolean weird = FALSE, reqFlag = FALSE;
3766 fromX = fromY = toX = toY = -1;
3770 if (appData.debugMode)
3771 fprintf(debugFP, _("Parsing board: %s\n"), string);
3773 move_str[0] = NULLCHAR;
3774 elapsed_time[0] = NULLCHAR;
3775 { /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
3777 while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
3778 if(string[i] == ' ') { ranks++; files = 0; }
3780 if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
3783 for(j = 0; j <i; j++) board_chars[j] = string[j];
3784 board_chars[i] = '\0';
3787 n = sscanf(string, PATTERN, &to_play, &double_push,
3788 &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
3789 &gamenum, white, black, &relation, &basetime, &increment,
3790 &white_stren, &black_stren, &white_time, &black_time,
3791 &moveNum, str, elapsed_time, move_str, &ics_flip,
3795 snprintf(str, sizeof(str), _("Failed to parse board string:\n\"%s\""), string);
3796 DisplayError(str, 0);
3800 /* Convert the move number to internal form */
3801 moveNum = (moveNum - 1) * 2;
3802 if (to_play == 'B') moveNum++;
3803 if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
3804 DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
3810 case RELATION_OBSERVING_PLAYED:
3811 case RELATION_OBSERVING_STATIC:
3812 if (gamenum == -1) {
3813 /* Old ICC buglet */
3814 relation = RELATION_OBSERVING_STATIC;
3816 newGameMode = IcsObserving;
3818 case RELATION_PLAYING_MYMOVE:
3819 case RELATION_PLAYING_NOTMYMOVE:
3821 ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
3822 IcsPlayingWhite : IcsPlayingBlack;
3824 case RELATION_EXAMINING:
3825 newGameMode = IcsExamining;
3827 case RELATION_ISOLATED_BOARD:
3829 /* Just display this board. If user was doing something else,
3830 we will forget about it until the next board comes. */
3831 newGameMode = IcsIdle;
3833 case RELATION_STARTING_POSITION:
3834 newGameMode = gameMode;
3838 /* Modify behavior for initial board display on move listing
3841 switch (ics_getting_history) {
3845 case H_GOT_REQ_HEADER:
3846 case H_GOT_UNREQ_HEADER:
3847 /* This is the initial position of the current game */
3848 gamenum = ics_gamenum;
3849 moveNum = 0; /* old ICS bug workaround */
3850 if (to_play == 'B') {
3851 startedFromSetupPosition = TRUE;
3852 blackPlaysFirst = TRUE;
3854 if (forwardMostMove == 0) forwardMostMove = 1;
3855 if (backwardMostMove == 0) backwardMostMove = 1;
3856 if (currentMove == 0) currentMove = 1;
3858 newGameMode = gameMode;
3859 relation = RELATION_STARTING_POSITION; /* ICC needs this */
3861 case H_GOT_UNWANTED_HEADER:
3862 /* This is an initial board that we don't want */
3864 case H_GETTING_MOVES:
3865 /* Should not happen */
3866 DisplayError(_("Error gathering move list: extra board"), 0);
3867 ics_getting_history = H_FALSE;
3871 if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
3872 weird && (int)gameInfo.variant <= (int)VariantShogi) {
3873 /* [HGM] We seem to have switched variant unexpectedly
3874 * Try to guess new variant from board size
3876 VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
3877 if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
3878 if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
3879 if(ranks == 8 && files == 12) newVariant = VariantCourier; else
3880 if(ranks == 9 && files == 9) newVariant = VariantShogi; else
3881 if(!weird) newVariant = VariantNormal;
3882 VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3883 /* Get a move list just to see the header, which
3884 will tell us whether this is really bug or zh */
3885 if (ics_getting_history == H_FALSE) {
3886 ics_getting_history = H_REQUESTED; reqFlag = TRUE;
3887 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3892 /* Take action if this is the first board of a new game, or of a
3893 different game than is currently being displayed. */
3894 if (gamenum != ics_gamenum || newGameMode != gameMode ||
3895 relation == RELATION_ISOLATED_BOARD) {
3897 /* Forget the old game and get the history (if any) of the new one */
3898 if (gameMode != BeginningOfGame) {
3902 if (appData.autoRaiseBoard) BoardToTop();
3904 if (gamenum == -1) {
3905 newGameMode = IcsIdle;
3906 } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
3907 appData.getMoveList && !reqFlag) {
3908 /* Need to get game history */
3909 ics_getting_history = H_REQUESTED;
3910 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3914 /* Initially flip the board to have black on the bottom if playing
3915 black or if the ICS flip flag is set, but let the user change
3916 it with the Flip View button. */
3917 flipView = appData.autoFlipView ?
3918 (newGameMode == IcsPlayingBlack) || ics_flip :
3921 /* Done with values from previous mode; copy in new ones */
3922 gameMode = newGameMode;
3924 ics_gamenum = gamenum;
3925 if (gamenum == gs_gamenum) {
3926 int klen = strlen(gs_kind);
3927 if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
3928 sprintf(str, "ICS %s", gs_kind);
3929 gameInfo.event = StrSave(str);
3931 gameInfo.event = StrSave("ICS game");
3933 gameInfo.site = StrSave(appData.icsHost);
3934 gameInfo.date = PGNDate();
3935 gameInfo.round = StrSave("-");
3936 gameInfo.white = StrSave(white);
3937 gameInfo.black = StrSave(black);
3938 timeControl = basetime * 60 * 1000;
3940 timeIncrement = increment * 1000;
3941 movesPerSession = 0;
3942 gameInfo.timeControl = TimeControlTagValue();
3943 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
3944 if (appData.debugMode) {
3945 fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
3946 fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
3947 setbuf(debugFP, NULL);
3950 gameInfo.outOfBook = NULL;
3952 /* Do we have the ratings? */
3953 if (strcmp(player1Name, white) == 0 &&
3954 strcmp(player2Name, black) == 0) {
3955 if (appData.debugMode)
3956 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3957 player1Rating, player2Rating);
3958 gameInfo.whiteRating = player1Rating;
3959 gameInfo.blackRating = player2Rating;
3960 } else if (strcmp(player2Name, white) == 0 &&
3961 strcmp(player1Name, black) == 0) {
3962 if (appData.debugMode)
3963 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3964 player2Rating, player1Rating);
3965 gameInfo.whiteRating = player2Rating;
3966 gameInfo.blackRating = player1Rating;
3968 player1Name[0] = player2Name[0] = NULLCHAR;
3970 /* Silence shouts if requested */
3971 if (appData.quietPlay &&
3972 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
3973 SendToICS(ics_prefix);
3974 SendToICS("set shout 0\n");
3978 /* Deal with midgame name changes */
3980 if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
3981 if (gameInfo.white) free(gameInfo.white);
3982 gameInfo.white = StrSave(white);
3984 if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
3985 if (gameInfo.black) free(gameInfo.black);
3986 gameInfo.black = StrSave(black);
3990 /* Throw away game result if anything actually changes in examine mode */
3991 if (gameMode == IcsExamining && !newGame) {
3992 gameInfo.result = GameUnfinished;
3993 if (gameInfo.resultDetails != NULL) {
3994 free(gameInfo.resultDetails);
3995 gameInfo.resultDetails = NULL;
3999 /* In pausing && IcsExamining mode, we ignore boards coming
4000 in if they are in a different variation than we are. */
4001 if (pauseExamInvalid) return;
4002 if (pausing && gameMode == IcsExamining) {
4003 if (moveNum <= pauseExamForwardMostMove) {
4004 pauseExamInvalid = TRUE;
4005 forwardMostMove = pauseExamForwardMostMove;
4010 if (appData.debugMode) {
4011 fprintf(debugFP, "load %dx%d board\n", files, ranks);
4013 /* Parse the board */
4014 for (k = 0; k < ranks; k++) {
4015 for (j = 0; j < files; j++)
4016 board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4017 if(gameInfo.holdingsWidth > 1) {
4018 board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4019 board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4022 CopyBoard(boards[moveNum], board);
4023 boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
4025 startedFromSetupPosition =
4026 !CompareBoards(board, initialPosition);
4027 if(startedFromSetupPosition)
4028 initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
4031 /* [HGM] Set castling rights. Take the outermost Rooks,
4032 to make it also work for FRC opening positions. Note that board12
4033 is really defective for later FRC positions, as it has no way to
4034 indicate which Rook can castle if they are on the same side of King.
4035 For the initial position we grant rights to the outermost Rooks,
4036 and remember thos rights, and we then copy them on positions
4037 later in an FRC game. This means WB might not recognize castlings with
4038 Rooks that have moved back to their original position as illegal,
4039 but in ICS mode that is not its job anyway.
4041 if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
4042 { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
4044 for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4045 if(board[0][i] == WhiteRook) j = i;
4046 initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4047 for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4048 if(board[0][i] == WhiteRook) j = i;
4049 initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4050 for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4051 if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4052 initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4053 for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4054 if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4055 initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4057 if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
4058 for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4059 if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
4060 for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4061 if(board[BOARD_HEIGHT-1][k] == bKing)
4062 initialRights[5] = boards[moveNum][CASTLING][5] = k;
4063 if(gameInfo.variant == VariantTwoKings) {
4064 // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
4065 if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
4066 if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
4069 r = boards[moveNum][CASTLING][0] = initialRights[0];
4070 if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4071 r = boards[moveNum][CASTLING][1] = initialRights[1];
4072 if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4073 r = boards[moveNum][CASTLING][3] = initialRights[3];
4074 if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4075 r = boards[moveNum][CASTLING][4] = initialRights[4];
4076 if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4077 /* wildcastle kludge: always assume King has rights */
4078 r = boards[moveNum][CASTLING][2] = initialRights[2];
4079 r = boards[moveNum][CASTLING][5] = initialRights[5];
4081 /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4082 boards[moveNum][EP_STATUS] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
4085 if (ics_getting_history == H_GOT_REQ_HEADER ||
4086 ics_getting_history == H_GOT_UNREQ_HEADER) {
4087 /* This was an initial position from a move list, not
4088 the current position */
4092 /* Update currentMove and known move number limits */
4093 newMove = newGame || moveNum > forwardMostMove;
4096 forwardMostMove = backwardMostMove = currentMove = moveNum;
4097 if (gameMode == IcsExamining && moveNum == 0) {
4098 /* Workaround for ICS limitation: we are not told the wild
4099 type when starting to examine a game. But if we ask for
4100 the move list, the move list header will tell us */
4101 ics_getting_history = H_REQUESTED;
4102 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
4105 } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4106 || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4108 /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4109 /* [HGM] applied this also to an engine that is silently watching */
4110 if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4111 (gameMode == IcsObserving || gameMode == IcsExamining) &&
4112 gameInfo.variant == currentlyInitializedVariant) {
4113 takeback = forwardMostMove - moveNum;
4114 for (i = 0; i < takeback; i++) {
4115 if (appData.debugMode) fprintf(debugFP, "take back move\n");
4116 SendToProgram("undo\n", &first);
4121 forwardMostMove = moveNum;
4122 if (!pausing || currentMove > forwardMostMove)
4123 currentMove = forwardMostMove;
4125 /* New part of history that is not contiguous with old part */
4126 if (pausing && gameMode == IcsExamining) {
4127 pauseExamInvalid = TRUE;
4128 forwardMostMove = pauseExamForwardMostMove;
4131 if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4133 if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4134 // [HGM] when we will receive the move list we now request, it will be
4135 // fed to the engine from the first move on. So if the engine is not
4136 // in the initial position now, bring it there.
4137 InitChessProgram(&first, 0);
4140 ics_getting_history = H_REQUESTED;
4141 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
4144 forwardMostMove = backwardMostMove = currentMove = moveNum;
4147 /* Update the clocks */
4148 if (strchr(elapsed_time, '.')) {
4150 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4151 timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4153 /* Time is in seconds */
4154 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4155 timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4160 if (appData.zippyPlay && newGame &&
4161 gameMode != IcsObserving && gameMode != IcsIdle &&
4162 gameMode != IcsExamining)
4163 ZippyFirstBoard(moveNum, basetime, increment);
4166 /* Put the move on the move list, first converting
4167 to canonical algebraic form. */
4169 if (appData.debugMode) {
4170 if (appData.debugMode) { int f = forwardMostMove;
4171 fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4172 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4173 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4175 fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4176 fprintf(debugFP, "moveNum = %d\n", moveNum);
4177 fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4178 setbuf(debugFP, NULL);
4180 if (moveNum <= backwardMostMove) {
4181 /* We don't know what the board looked like before
4183 strcpy(parseList[moveNum - 1], move_str);
4184 strcat(parseList[moveNum - 1], " ");
4185 strcat(parseList[moveNum - 1], elapsed_time);
4186 moveList[moveNum - 1][0] = NULLCHAR;
4187 } else if (strcmp(move_str, "none") == 0) {
4188 // [HGM] long SAN: swapped order; test for 'none' before parsing move
4189 /* Again, we don't know what the board looked like;
4190 this is really the start of the game. */
4191 parseList[moveNum - 1][0] = NULLCHAR;
4192 moveList[moveNum - 1][0] = NULLCHAR;
4193 backwardMostMove = moveNum;
4194 startedFromSetupPosition = TRUE;
4195 fromX = fromY = toX = toY = -1;
4197 // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
4198 // So we parse the long-algebraic move string in stead of the SAN move
4199 int valid; char buf[MSG_SIZ], *prom;
4201 // str looks something like "Q/a1-a2"; kill the slash
4203 sprintf(buf, "%c%s", str[0], str+2);
4204 else strcpy(buf, str); // might be castling
4205 if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
4206 strcat(buf, prom); // long move lacks promo specification!
4207 if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4208 if(appData.debugMode)
4209 fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4210 strcpy(move_str, buf);
4212 valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4213 &fromX, &fromY, &toX, &toY, &promoChar)
4214 || ParseOneMove(buf, moveNum - 1, &moveType,
4215 &fromX, &fromY, &toX, &toY, &promoChar);
4216 // end of long SAN patch
4218 (void) CoordsToAlgebraic(boards[moveNum - 1],
4219 PosFlags(moveNum - 1),
4220 fromY, fromX, toY, toX, promoChar,
4221 parseList[moveNum-1]);
4222 switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4228 if(gameInfo.variant != VariantShogi)
4229 strcat(parseList[moveNum - 1], "+");
4232 case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4233 strcat(parseList[moveNum - 1], "#");
4236 strcat(parseList[moveNum - 1], " ");
4237 strcat(parseList[moveNum - 1], elapsed_time);
4238 /* currentMoveString is set as a side-effect of ParseOneMove */
4239 strcpy(moveList[moveNum - 1], currentMoveString);
4240 strcat(moveList[moveNum - 1], "\n");
4242 /* Move from ICS was illegal!? Punt. */
4243 if (appData.debugMode) {
4244 fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4245 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4247 strcpy(parseList[moveNum - 1], move_str);
4248 strcat(parseList[moveNum - 1], " ");
4249 strcat(parseList[moveNum - 1], elapsed_time);
4250 moveList[moveNum - 1][0] = NULLCHAR;
4251 fromX = fromY = toX = toY = -1;
4254 if (appData.debugMode) {
4255 fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4256 setbuf(debugFP, NULL);
4260 /* Send move to chess program (BEFORE animating it). */
4261 if (appData.zippyPlay && !newGame && newMove &&
4262 (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4264 if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4265 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4266 if (moveList[moveNum - 1][0] == NULLCHAR) {
4267 sprintf(str, _("Couldn't parse move \"%s\" from ICS"),
4269 DisplayError(str, 0);
4271 if (first.sendTime) {
4272 SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4274 bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4275 if (firstMove && !bookHit) {
4277 if (first.useColors) {
4278 SendToProgram(gameMode == IcsPlayingWhite ?
4280 "black\ngo\n", &first);
4282 SendToProgram("go\n", &first);
4284 first.maybeThinking = TRUE;
4287 } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4288 if (moveList[moveNum - 1][0] == NULLCHAR) {
4289 sprintf(str, _("Couldn't parse move \"%s\" from ICS"), move_str);
4290 DisplayError(str, 0);
4292 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4293 SendMoveToProgram(moveNum - 1, &first);
4300 if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4301 /* If move comes from a remote source, animate it. If it
4302 isn't remote, it will have already been animated. */
4303 if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4304 AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4306 if (!pausing && appData.highlightLastMove) {
4307 SetHighlights(fromX, fromY, toX, toY);
4311 /* Start the clocks */
4312 whiteFlag = blackFlag = FALSE;
4313 appData.clockMode = !(basetime == 0 && increment == 0);
4315 ics_clock_paused = TRUE;
4317 } else if (ticking == 1) {
4318 ics_clock_paused = FALSE;
4320 if (gameMode == IcsIdle ||
4321 relation == RELATION_OBSERVING_STATIC ||
4322 relation == RELATION_EXAMINING ||
4324 DisplayBothClocks();
4328 /* Display opponents and material strengths */
4329 if (gameInfo.variant != VariantBughouse &&
4330 gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4331 if (tinyLayout || smallLayout) {
4332 if(gameInfo.variant == VariantNormal)
4333 sprintf(str, "%s(%d) %s(%d) {%d %d}",
4334 gameInfo.white, white_stren, gameInfo.black, black_stren,
4335 basetime, increment);
4337 sprintf(str, "%s(%d) %s(%d) {%d %d w%d}",
4338 gameInfo.white, white_stren, gameInfo.black, black_stren,
4339 basetime, increment, (int) gameInfo.variant);
4341 if(gameInfo.variant == VariantNormal)
4342 sprintf(str, "%s (%d) vs. %s (%d) {%d %d}",
4343 gameInfo.white, white_stren, gameInfo.black, black_stren,
4344 basetime, increment);
4346 sprintf(str, "%s (%d) vs. %s (%d) {%d %d %s}",
4347 gameInfo.white, white_stren, gameInfo.black, black_stren,
4348 basetime, increment, VariantName(gameInfo.variant));
4351 if (appData.debugMode) {
4352 fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
4357 /* Display the board */
4358 if (!pausing && !appData.noGUI) {
4359 if (appData.premove)
4361 ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
4362 ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
4363 ClearPremoveHighlights();
4365 j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
4366 DrawPosition(j, boards[currentMove]);
4368 DisplayMove(moveNum - 1);
4369 if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4370 !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4371 (gameMode == IcsPlayingBlack) && (WhiteOnMove(moveNum)) ) ) {
4372 if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4376 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4378 if(bookHit) { // [HGM] book: simulate book reply
4379 static char bookMove[MSG_SIZ]; // a bit generous?
4381 programStats.nodes = programStats.depth = programStats.time =
4382 programStats.score = programStats.got_only_move = 0;
4383 sprintf(programStats.movelist, "%s (xbook)", bookHit);
4385 strcpy(bookMove, "move ");
4386 strcat(bookMove, bookHit);
4387 HandleMachineMove(bookMove, &first);
4396 if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4397 ics_getting_history = H_REQUESTED;
4398 sprintf(buf, "%smoves %d\n", ics_prefix, ics_gamenum);
4404 AnalysisPeriodicEvent(force)
4407 if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4408 && !force) || !appData.periodicUpdates)
4411 /* Send . command to Crafty to collect stats */
4412 SendToProgram(".\n", &first);
4414 /* Don't send another until we get a response (this makes
4415 us stop sending to old Crafty's which don't understand
4416 the "." command (sending illegal cmds resets node count & time,
4417 which looks bad)) */
4418 programStats.ok_to_send = 0;
4421 void ics_update_width(new_width)
4424 ics_printf("set width %d\n", new_width);
4428 SendMoveToProgram(moveNum, cps)
4430 ChessProgramState *cps;
4434 if (cps->useUsermove) {
4435 SendToProgram("usermove ", cps);
4439 if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4440 int len = space - parseList[moveNum];
4441 memcpy(buf, parseList[moveNum], len);
4443 buf[len] = NULLCHAR;
4445 sprintf(buf, "%s\n", parseList[moveNum]);
4447 SendToProgram(buf, cps);
4449 if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4450 AlphaRank(moveList[moveNum], 4);
4451 SendToProgram(moveList[moveNum], cps);
4452 AlphaRank(moveList[moveNum], 4); // and back
4454 /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
4455 * the engine. It would be nice to have a better way to identify castle
4457 if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
4458 && cps->useOOCastle) {
4459 int fromX = moveList[moveNum][0] - AAA;
4460 int fromY = moveList[moveNum][1] - ONE;
4461 int toX = moveList[moveNum][2] - AAA;
4462 int toY = moveList[moveNum][3] - ONE;
4463 if((boards[moveNum][fromY][fromX] == WhiteKing
4464 && boards[moveNum][toY][toX] == WhiteRook)
4465 || (boards[moveNum][fromY][fromX] == BlackKing
4466 && boards[moveNum][toY][toX] == BlackRook)) {
4467 if(toX > fromX) SendToProgram("O-O\n", cps);
4468 else SendToProgram("O-O-O\n", cps);
4470 else SendToProgram(moveList[moveNum], cps);
4472 else SendToProgram(moveList[moveNum], cps);
4473 /* End of additions by Tord */
4476 /* [HGM] setting up the opening has brought engine in force mode! */
4477 /* Send 'go' if we are in a mode where machine should play. */
4478 if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4479 (gameMode == TwoMachinesPlay ||
4481 gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite ||
4483 gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4484 SendToProgram("go\n", cps);
4485 if (appData.debugMode) {
4486 fprintf(debugFP, "(extra)\n");
4489 setboardSpoiledMachineBlack = 0;
4493 SendMoveToICS(moveType, fromX, fromY, toX, toY)
4495 int fromX, fromY, toX, toY;
4497 char user_move[MSG_SIZ];
4501 sprintf(user_move, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4502 (int)moveType, fromX, fromY, toX, toY);
4503 DisplayError(user_move + strlen("say "), 0);
4505 case WhiteKingSideCastle:
4506 case BlackKingSideCastle:
4507 case WhiteQueenSideCastleWild:
4508 case BlackQueenSideCastleWild:
4510 case WhiteHSideCastleFR:
4511 case BlackHSideCastleFR:
4513 sprintf(user_move, "o-o\n");
4515 case WhiteQueenSideCastle:
4516 case BlackQueenSideCastle:
4517 case WhiteKingSideCastleWild:
4518 case BlackKingSideCastleWild:
4520 case WhiteASideCastleFR:
4521 case BlackASideCastleFR:
4523 sprintf(user_move, "o-o-o\n");
4525 case WhitePromotionQueen:
4526 case BlackPromotionQueen:
4527 case WhitePromotionRook:
4528 case BlackPromotionRook:
4529 case WhitePromotionBishop:
4530 case BlackPromotionBishop:
4531 case WhitePromotionKnight:
4532 case BlackPromotionKnight:
4533 case WhitePromotionKing:
4534 case BlackPromotionKing:
4535 case WhitePromotionChancellor:
4536 case BlackPromotionChancellor:
4537 case WhitePromotionArchbishop:
4538 case BlackPromotionArchbishop:
4539 if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
4540 sprintf(user_move, "%c%c%c%c=%c\n",
4541 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4542 PieceToChar(WhiteFerz));
4543 else if(gameInfo.variant == VariantGreat)
4544 sprintf(user_move, "%c%c%c%c=%c\n",
4545 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4546 PieceToChar(WhiteMan));
4548 sprintf(user_move, "%c%c%c%c=%c\n",
4549 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4550 PieceToChar(PromoPiece(moveType)));
4554 sprintf(user_move, "%c@%c%c\n",
4555 ToUpper(PieceToChar((ChessSquare) fromX)),
4556 AAA + toX, ONE + toY);
4559 case WhiteCapturesEnPassant:
4560 case BlackCapturesEnPassant:
4561 case IllegalMove: /* could be a variant we don't quite understand */
4562 sprintf(user_move, "%c%c%c%c\n",
4563 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4566 SendToICS(user_move);
4567 if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
4568 ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
4572 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
4577 if (rf == DROP_RANK) {
4578 sprintf(move, "%c@%c%c\n",
4579 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
4581 if (promoChar == 'x' || promoChar == NULLCHAR) {
4582 sprintf(move, "%c%c%c%c\n",
4583 AAA + ff, ONE + rf, AAA + ft, ONE + rt);
4585 sprintf(move, "%c%c%c%c%c\n",
4586 AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
4592 ProcessICSInitScript(f)
4597 while (fgets(buf, MSG_SIZ, f)) {
4598 SendToICSDelayed(buf,(long)appData.msLoginDelay);
4605 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
4607 AlphaRank(char *move, int n)
4609 // char *p = move, c; int x, y;
4611 if (appData.debugMode) {
4612 fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
4616 move[2]>='0' && move[2]<='9' &&
4617 move[3]>='a' && move[3]<='x' ) {
4619 move[2] = BOARD_RGHT -1 - (move[2]-'1') + AAA;
4620 move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4622 if(move[0]>='0' && move[0]<='9' &&
4623 move[1]>='a' && move[1]<='x' &&
4624 move[2]>='0' && move[2]<='9' &&
4625 move[3]>='a' && move[3]<='x' ) {
4626 /* input move, Shogi -> normal */
4627 move[0] = BOARD_RGHT -1 - (move[0]-'1') + AAA;
4628 move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
4629 move[2] = BOARD_RGHT -1 - (move[2]-'1') + AAA;
4630 move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4633 move[3]>='0' && move[3]<='9' &&
4634 move[2]>='a' && move[2]<='x' ) {
4636 move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4637 move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4640 move[0]>='a' && move[0]<='x' &&
4641 move[3]>='0' && move[3]<='9' &&
4642 move[2]>='a' && move[2]<='x' ) {
4643 /* output move, normal -> Shogi */
4644 move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
4645 move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
4646 move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4647 move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4648 if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
4650 if (appData.debugMode) {
4651 fprintf(debugFP, " out = '%s'\n", move);
4655 /* Parser for moves from gnuchess, ICS, or user typein box */
4657 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
4660 ChessMove *moveType;
4661 int *fromX, *fromY, *toX, *toY;
4664 if (appData.debugMode) {
4665 fprintf(debugFP, "move to parse: %s\n", move);
4667 *moveType = yylexstr(moveNum, move);
4669 switch (*moveType) {
4670 case WhitePromotionChancellor:
4671 case BlackPromotionChancellor:
4672 case WhitePromotionArchbishop:
4673 case BlackPromotionArchbishop:
4674 case WhitePromotionQueen:
4675 case BlackPromotionQueen:
4676 case WhitePromotionRook:
4677 case BlackPromotionRook:
4678 case WhitePromotionBishop:
4679 case BlackPromotionBishop:
4680 case WhitePromotionKnight:
4681 case BlackPromotionKnight:
4682 case WhitePromotionKing:
4683 case BlackPromotionKing:
4685 case WhiteCapturesEnPassant:
4686 case BlackCapturesEnPassant:
4687 case WhiteKingSideCastle:
4688 case WhiteQueenSideCastle:
4689 case BlackKingSideCastle:
4690 case BlackQueenSideCastle:
4691 case WhiteKingSideCastleWild:
4692 case WhiteQueenSideCastleWild:
4693 case BlackKingSideCastleWild:
4694 case BlackQueenSideCastleWild:
4695 /* Code added by Tord: */
4696 case WhiteHSideCastleFR:
4697 case WhiteASideCastleFR:
4698 case BlackHSideCastleFR:
4699 case BlackASideCastleFR:
4700 /* End of code added by Tord */
4701 case IllegalMove: /* bug or odd chess variant */
4702 *fromX = currentMoveString[0] - AAA;
4703 *fromY = currentMoveString[1] - ONE;
4704 *toX = currentMoveString[2] - AAA;
4705 *toY = currentMoveString[3] - ONE;
4706 *promoChar = currentMoveString[4];
4707 if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
4708 *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
4709 if (appData.debugMode) {
4710 fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
4712 *fromX = *fromY = *toX = *toY = 0;
4715 if (appData.testLegality) {
4716 return (*moveType != IllegalMove);
4718 return !(*fromX == *toX && *fromY == *toY) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
4719 WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
4724 *fromX = *moveType == WhiteDrop ?
4725 (int) CharToPiece(ToUpper(currentMoveString[0])) :
4726 (int) CharToPiece(ToLower(currentMoveString[0]));
4728 *toX = currentMoveString[2] - AAA;
4729 *toY = currentMoveString[3] - ONE;
4730 *promoChar = NULLCHAR;
4734 case ImpossibleMove:
4735 case (ChessMove) 0: /* end of file */
4744 if (appData.debugMode) {
4745 fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
4748 *fromX = *fromY = *toX = *toY = 0;
4749 *promoChar = NULLCHAR;
4757 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
4758 int fromX, fromY, toX, toY; char promoChar;
4763 endPV = forwardMostMove;
4765 while(*pv == ' ') pv++;
4766 if(*pv == '(') pv++; // first (ponder) move can be in parentheses
4767 valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
4768 if(appData.debugMode){
4769 fprintf(debugFP,"parsePV: %d %c%c%c%c '%s'\n", valid, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, pv);
4771 if(!valid && nr == 0 &&
4772 ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
4773 nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
4775 while(*pv && *pv++ != ' '); // skip what we parsed; assume space separators
4776 if(moveType == Comment) { valid++; continue; } // allow comments in PV
4778 if(endPV+1 > framePtr) break; // no space, truncate
4781 CopyBoard(boards[endPV], boards[endPV-1]);
4782 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
4783 moveList[endPV-1][0] = fromX + AAA;
4784 moveList[endPV-1][1] = fromY + ONE;
4785 moveList[endPV-1][2] = toX + AAA;
4786 moveList[endPV-1][3] = toY + ONE;
4787 parseList[endPV-1][0] = NULLCHAR;
4789 currentMove = endPV;
4790 if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
4791 SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
4792 moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
4793 DrawPosition(TRUE, boards[currentMove]);
4796 static int lastX, lastY;
4799 LoadMultiPV(int x, int y, char *buf, int index, int *start, int *end)
4803 if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
4804 lastX = x; lastY = y;
4805 while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
4807 while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
4809 while(buf[index] && buf[index] != '\n') index++;
4811 ParsePV(buf+startPV);
4812 *start = startPV; *end = index-1;
4817 LoadPV(int x, int y)
4818 { // called on right mouse click to load PV
4819 int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
4820 lastX = x; lastY = y;
4821 ParsePV(lastPV[which]); // load the PV of the thinking engine in the boards array.
4828 if(endPV < 0) return;
4830 currentMove = forwardMostMove;
4831 ClearPremoveHighlights();
4832 DrawPosition(TRUE, boards[currentMove]);
4836 MovePV(int x, int y, int h)
4837 { // step through PV based on mouse coordinates (called on mouse move)
4838 int margin = h>>3, step = 0;
4840 if(endPV < 0) return;
4841 // we must somehow check if right button is still down (might be released off board!)
4842 if(y < margin && (abs(x - lastX) > 6 || abs(y - lastY) > 6)) step = 1; else
4843 if(y > h - margin && (abs(x - lastX) > 6 || abs(y - lastY) > 6)) step = -1; else
4844 if( y > lastY + 6 ) step = -1; else if(y < lastY - 6) step = 1;
4846 lastX = x; lastY = y;
4847 if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
4848 currentMove += step;
4849 if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
4850 SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
4851 moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
4852 DrawPosition(FALSE, boards[currentMove]);
4856 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
4857 // All positions will have equal probability, but the current method will not provide a unique
4858 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
4864 int piecesLeft[(int)BlackPawn];
4865 int seed, nrOfShuffles;
4867 void GetPositionNumber()
4868 { // sets global variable seed
4871 seed = appData.defaultFrcPosition;
4872 if(seed < 0) { // randomize based on time for negative FRC position numbers
4873 for(i=0; i<50; i++) seed += random();
4874 seed = random() ^ random() >> 8 ^ random() << 8;
4875 if(seed<0) seed = -seed;
4879 int put(Board board, int pieceType, int rank, int n, int shade)
4880 // put the piece on the (n-1)-th empty squares of the given shade
4884 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
4885 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
4886 board[rank][i] = (ChessSquare) pieceType;
4887 squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
4889 piecesLeft[pieceType]--;
4897 void AddOnePiece(Board board, int pieceType, int rank, int shade)
4898 // calculate where the next piece goes, (any empty square), and put it there
4902 i = seed % squaresLeft[shade];
4903 nrOfShuffles *= squaresLeft[shade];
4904 seed /= squaresLeft[shade];
4905 put(board, pieceType, rank, i, shade);
4908 void AddTwoPieces(Board board, int pieceType, int rank)
4909 // calculate where the next 2 identical pieces go, (any empty square), and put it there
4911 int i, n=squaresLeft[ANY], j=n-1, k;
4913 k = n*(n-1)/2; // nr of possibilities, not counting permutations
4914 i = seed % k; // pick one
4917 while(i >= j) i -= j--;
4918 j = n - 1 - j; i += j;
4919 put(board, pieceType, rank, j, ANY);
4920 put(board, pieceType, rank, i, ANY);
4923 void SetUpShuffle(Board board, int number)
4927 GetPositionNumber(); nrOfShuffles = 1;
4929 squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
4930 squaresLeft[ANY] = BOARD_RGHT - BOARD_LEFT;
4931 squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
4933 for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
4935 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
4936 p = (int) board[0][i];
4937 if(p < (int) BlackPawn) piecesLeft[p] ++;
4938 board[0][i] = EmptySquare;
4941 if(PosFlags(0) & F_ALL_CASTLE_OK) {
4942 // shuffles restricted to allow normal castling put KRR first
4943 if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
4944 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
4945 else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
4946 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
4947 if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
4948 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
4949 if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
4950 put(board, WhiteRook, 0, 0, ANY);
4951 // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
4954 if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
4955 // only for even boards make effort to put pairs of colorbound pieces on opposite colors
4956 for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
4957 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
4958 while(piecesLeft[p] >= 2) {
4959 AddOnePiece(board, p, 0, LITE);
4960 AddOnePiece(board, p, 0, DARK);
4962 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
4965 for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
4966 // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
4967 // but we leave King and Rooks for last, to possibly obey FRC restriction
4968 if(p == (int)WhiteRook) continue;
4969 while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
4970 if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY); // add the odd piece
4973 // now everything is placed, except perhaps King (Unicorn) and Rooks
4975 if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
4976 // Last King gets castling rights
4977 while(piecesLeft[(int)WhiteUnicorn]) {
4978 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
4979 initialRights[2] = initialRights[5] = board[CASTLING][2] = board[CASTLING][5] = i;
4982 while(piecesLeft[(int)WhiteKing]) {
4983 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
4984 initialRights[2] = initialRights[5] = board[CASTLING][2] = board[CASTLING][5] = i;
4989 while(piecesLeft[(int)WhiteKing]) AddOnePiece(board, WhiteKing, 0, ANY);
4990 while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
4993 // Only Rooks can be left; simply place them all
4994 while(piecesLeft[(int)WhiteRook]) {
4995 i = put(board, WhiteRook, 0, 0, ANY);
4996 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
4999 initialRights[1] = initialRights[4] = board[CASTLING][1] = board[CASTLING][4] = i;
5001 initialRights[0] = initialRights[3] = board[CASTLING][0] = board[CASTLING][3] = i;
5004 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
5005 board[BOARD_HEIGHT-1][i] = (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
5008 if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
5011 int SetCharTable( char *table, const char * map )
5012 /* [HGM] moved here from winboard.c because of its general usefulness */
5013 /* Basically a safe strcpy that uses the last character as King */
5015 int result = FALSE; int NrPieces;
5017 if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
5018 && NrPieces >= 12 && !(NrPieces&1)) {
5019 int i; /* [HGM] Accept even length from 12 to 34 */
5021 for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
5022 for( i=0; i<NrPieces/2-1; i++ ) {
5024 table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
5026 table[(int) WhiteKing] = map[NrPieces/2-1];
5027 table[(int) BlackKing] = map[NrPieces-1];
5035 void Prelude(Board board)
5036 { // [HGM] superchess: random selection of exo-pieces
5037 int i, j, k; ChessSquare p;
5038 static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
5040 GetPositionNumber(); // use FRC position number
5042 if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
5043 SetCharTable(pieceToChar, appData.pieceToCharTable);
5044 for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
5045 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
5048 j = seed%4; seed /= 4;
5049 p = board[0][BOARD_LEFT+j]; board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5050 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
5051 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
5052 j = seed%3 + (seed%3 >= j); seed /= 3;
5053 p = board[0][BOARD_LEFT+j]; board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5054 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
5055 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
5056 j = seed%3; seed /= 3;
5057 p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5058 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
5059 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
5060 j = seed%2 + (seed%2 >= j); seed /= 2;
5061 p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5062 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
5063 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
5064 j = seed%4; seed /= 4; put(board, exoPieces[3], 0, j, ANY);
5065 j = seed%3; seed /= 3; put(board, exoPieces[2], 0, j, ANY);
5066 j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
5067 put(board, exoPieces[0], 0, 0, ANY);
5068 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
5072 InitPosition(redraw)
5075 ChessSquare (* pieces)[BOARD_FILES];
5076 int i, j, pawnRow, overrule,
5077 oldx = gameInfo.boardWidth,
5078 oldy = gameInfo.boardHeight,
5079 oldh = gameInfo.holdingsWidth,
5080 oldv = gameInfo.variant;
5082 if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
5084 /* [AS] Initialize pv info list [HGM] and game status */
5086 for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
5087 pvInfoList[i].depth = 0;
5088 boards[i][EP_STATUS] = EP_NONE;
5089 for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
5092 initialRulePlies = 0; /* 50-move counter start */
5094 castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
5095 castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
5099 /* [HGM] logic here is completely changed. In stead of full positions */
5100 /* the initialized data only consist of the two backranks. The switch */
5101 /* selects which one we will use, which is than copied to the Board */
5102 /* initialPosition, which for the rest is initialized by Pawns and */
5103 /* empty squares. This initial position is then copied to boards[0], */
5104 /* possibly after shuffling, so that it remains available. */
5106 gameInfo.holdingsWidth = 0; /* default board sizes */
5107 gameInfo.boardWidth = 8;
5108 gameInfo.boardHeight = 8;
5109 gameInfo.holdingsSize = 0;
5110 nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
5111 for(i=0; i<BOARD_FILES-2; i++)
5112 initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
5113 initialPosition[EP_STATUS] = EP_NONE;
5114 SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
5116 switch (gameInfo.variant) {
5117 case VariantFischeRandom:
5118 shuffleOpenings = TRUE;
5122 case VariantShatranj:
5123 pieces = ShatranjArray;
5124 nrCastlingRights = 0;
5125 SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
5128 pieces = makrukArray;
5129 nrCastlingRights = 0;
5130 startedFromSetupPosition = TRUE;
5131 SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
5133 case VariantTwoKings:
5134 pieces = twoKingsArray;
5136 case VariantCapaRandom:
5137 shuffleOpenings = TRUE;
5138 case VariantCapablanca:
5139 pieces = CapablancaArray;
5140 gameInfo.boardWidth = 10;
5141 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5144 pieces = GothicArray;
5145 gameInfo.boardWidth = 10;
5146 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5149 pieces = JanusArray;
5150 gameInfo.boardWidth = 10;
5151 SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
5152 nrCastlingRights = 6;
5153 initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5154 initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5155 initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
5156 initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5157 initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5158 initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
5161 pieces = FalconArray;
5162 gameInfo.boardWidth = 10;
5163 SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
5165 case VariantXiangqi:
5166 pieces = XiangqiArray;
5167 gameInfo.boardWidth = 9;
5168 gameInfo.boardHeight = 10;
5169 nrCastlingRights = 0;
5170 SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
5173 pieces = ShogiArray;
5174 gameInfo.boardWidth = 9;
5175 gameInfo.boardHeight = 9;
5176 gameInfo.holdingsSize = 7;
5177 nrCastlingRights = 0;
5178 SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
5180 case VariantCourier:
5181 pieces = CourierArray;
5182 gameInfo.boardWidth = 12;
5183 nrCastlingRights = 0;
5184 SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
5186 case VariantKnightmate:
5187 pieces = KnightmateArray;
5188 SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
5191 pieces = fairyArray;
5192 SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
5195 pieces = GreatArray;
5196 gameInfo.boardWidth = 10;
5197 SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
5198 gameInfo.holdingsSize = 8;
5202 SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
5203 gameInfo.holdingsSize = 8;
5204 startedFromSetupPosition = TRUE;
5206 case VariantCrazyhouse:
5207 case VariantBughouse:
5209 SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
5210 gameInfo.holdingsSize = 5;
5212 case VariantWildCastle:
5214 /* !!?shuffle with kings guaranteed to be on d or e file */
5215 shuffleOpenings = 1;
5217 case VariantNoCastle:
5219 nrCastlingRights = 0;
5220 /* !!?unconstrained back-rank shuffle */
5221 shuffleOpenings = 1;
5226 if(appData.NrFiles >= 0) {
5227 if(gameInfo.boardWidth != appData.NrFiles) overrule++;
5228 gameInfo.boardWidth = appData.NrFiles;
5230 if(appData.NrRanks >= 0) {
5231 gameInfo.boardHeight = appData.NrRanks;
5233 if(appData.holdingsSize >= 0) {
5234 i = appData.holdingsSize;
5235 if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
5236 gameInfo.holdingsSize = i;
5238 if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
5239 if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
5240 DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
5242 pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
5243 if(pawnRow < 1) pawnRow = 1;
5244 if(gameInfo.variant == VariantMakruk) pawnRow = 2;
5246 /* User pieceToChar list overrules defaults */
5247 if(appData.pieceToCharTable != NULL)
5248 SetCharTable(pieceToChar, appData.pieceToCharTable);
5250 for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
5252 if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
5253 s = (ChessSquare) 0; /* account holding counts in guard band */
5254 for( i=0; i<BOARD_HEIGHT; i++ )
5255 initialPosition[i][j] = s;
5257 if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
5258 initialPosition[0][j] = pieces[0][j-gameInfo.holdingsWidth];
5259 initialPosition[pawnRow][j] = WhitePawn;
5260 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = BlackPawn;
5261 if(gameInfo.variant == VariantXiangqi) {
5263 initialPosition[pawnRow][j] =
5264 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
5265 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
5266 initialPosition[2][j] = WhiteCannon;
5267 initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
5271 initialPosition[BOARD_HEIGHT-1][j] = pieces[1][j-gameInfo.holdingsWidth];
5273 if( (gameInfo.variant == VariantShogi) && !overrule ) {
5276 initialPosition[1][j] = WhiteBishop;
5277 initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
5279 initialPosition[1][j] = WhiteRook;
5280 initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
5283 if( nrCastlingRights == -1) {
5284 /* [HGM] Build normal castling rights (must be done after board sizing!) */
5285 /* This sets default castling rights from none to normal corners */
5286 /* Variants with other castling rights must set them themselves above */
5287 nrCastlingRights = 6;
5288 initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5289 initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5290 initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
5291 initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5292 initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5293 initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
5296 if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
5297 if(gameInfo.variant == VariantGreat) { // promotion commoners
5298 initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
5299 initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
5300 initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
5301 initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
5303 if (appData.debugMode) {
5304 fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
5306 if(shuffleOpenings) {
5307 SetUpShuffle(initialPosition, appData.defaultFrcPosition);
5308 startedFromSetupPosition = TRUE;
5310 if(startedFromPositionFile) {
5311 /* [HGM] loadPos: use PositionFile for every new game */
5312 CopyBoard(initialPosition, filePosition);
5313 for(i=0; i<nrCastlingRights; i++)
5314 initialRights[i] = filePosition[CASTLING][i];
5315 startedFromSetupPosition = TRUE;
5318 CopyBoard(boards[0], initialPosition);
5319 if(oldx != gameInfo.boardWidth ||
5320 oldy != gameInfo.boardHeight ||
5321 oldh != gameInfo.holdingsWidth
5323 || oldv == VariantGothic || // For licensing popups
5324 gameInfo.variant == VariantGothic
5327 || oldv == VariantFalcon ||
5328 gameInfo.variant == VariantFalcon
5332 InitDrawingSizes(-2 ,0);
5336 DrawPosition(TRUE, boards[currentMove]);
5341 SendBoard(cps, moveNum)
5342 ChessProgramState *cps;
5345 char message[MSG_SIZ];
5347 if (cps->useSetboard) {
5348 char* fen = PositionToFEN(moveNum, cps->fenOverride);
5349 sprintf(message, "setboard %s\n", fen);
5350 SendToProgram(message, cps);
5356 /* Kludge to set black to move, avoiding the troublesome and now
5357 * deprecated "black" command.
5359 if (!WhiteOnMove(moveNum)) SendToProgram("a2a3\n", cps);
5361 SendToProgram("edit\n", cps);
5362 SendToProgram("#\n", cps);
5363 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
5364 bp = &boards[moveNum][i][BOARD_LEFT];
5365 for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
5366 if ((int) *bp < (int) BlackPawn) {
5367 sprintf(message, "%c%c%c\n", PieceToChar(*bp),
5369 if(message[0] == '+' || message[0] == '~') {
5370 sprintf(message, "%c%c%c+\n",
5371 PieceToChar((ChessSquare)(DEMOTED *bp)),
5374 if(cps->alphaRank) { /* [HGM] shogi: translate coords */
5375 message[1] = BOARD_RGHT - 1 - j + '1';
5376 message[2] = BOARD_HEIGHT - 1 - i + 'a';
5378 SendToProgram(message, cps);
5383 SendToProgram("c\n", cps);
5384 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
5385 bp = &boards[moveNum][i][BOARD_LEFT];
5386 for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
5387 if (((int) *bp != (int) EmptySquare)
5388 && ((int) *bp >= (int) BlackPawn)) {
5389 sprintf(message, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
5391 if(message[0] == '+' || message[0] == '~') {
5392 sprintf(message, "%c%c%c+\n",
5393 PieceToChar((ChessSquare)(DEMOTED *bp)),
5396 if(cps->alphaRank) { /* [HGM] shogi: translate coords */
5397 message[1] = BOARD_RGHT - 1 - j + '1';
5398 message[2] = BOARD_HEIGHT - 1 - i + 'a';
5400 SendToProgram(message, cps);
5405 SendToProgram(".\n", cps);
5407 setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
5411 HasPromotionChoice(int fromX, int fromY, int toX, int toY, char *promoChoice)
5413 /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
5414 /* [HGM] add Shogi promotions */
5415 int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
5420 if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
5421 if(toX < BOARD_LEFT || toX >= BOARD_RGHT) return FALSE; // move into holdings
5423 if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
5424 !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
5427 piece = boards[currentMove][fromY][fromX];
5428 if(gameInfo.variant == VariantShogi) {
5429 promotionZoneSize = 3;
5430 highestPromotingPiece = (int)WhiteFerz;
5431 } else if(gameInfo.variant == VariantMakruk) {
5432 promotionZoneSize = 3;
5435 // next weed out all moves that do not touch the promotion zone at all
5436 if((int)piece >= BlackPawn) {
5437 if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
5439 highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
5441 if( toY < BOARD_HEIGHT - promotionZoneSize &&
5442 fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
5445 if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
5447 // weed out mandatory Shogi promotions
5448 if(gameInfo.variant == VariantShogi) {
5449 if(piece >= BlackPawn) {
5450 if(toY == 0 && piece == BlackPawn ||
5451 toY == 0 && piece == BlackQueen ||
5452 toY <= 1 && piece == BlackKnight) {
5457 if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
5458 toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
5459 toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
5466 // weed out obviously illegal Pawn moves
5467 if(appData.testLegality && (piece == WhitePawn || piece == BlackPawn) ) {
5468 if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
5469 if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
5470 if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
5471 if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
5472 // note we are not allowed to test for valid (non-)capture, due to premove
5475 // we either have a choice what to promote to, or (in Shogi) whether to promote
5476 if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk) {
5477 *promoChoice = PieceToChar(BlackFerz); // no choice
5480 if(appData.alwaysPromoteToQueen) { // predetermined
5481 if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantLosers)
5482 *promoChoice = PieceToChar(BlackKing); // in Suicide Q is the last thing we want
5483 else *promoChoice = PieceToChar(BlackQueen);
5487 // suppress promotion popup on illegal moves that are not premoves
5488 premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
5489 gameMode == IcsPlayingBlack && WhiteOnMove(currentMove);
5490 if(appData.testLegality && !premove) {
5491 moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5492 fromY, fromX, toY, toX, NULLCHAR);
5493 if(moveType != WhitePromotionQueen && moveType != BlackPromotionQueen &&
5494 moveType != WhitePromotionKnight && moveType != BlackPromotionKnight)
5502 InPalace(row, column)
5504 { /* [HGM] for Xiangqi */
5505 if( (row < 3 || row > BOARD_HEIGHT-4) &&
5506 column < (BOARD_WIDTH + 4)/2 &&
5507 column > (BOARD_WIDTH - 5)/2 ) return TRUE;
5512 PieceForSquare (x, y)
5516 if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
5519 return boards[currentMove][y][x];
5523 OKToStartUserMove(x, y)
5526 ChessSquare from_piece;
5529 if (matchMode) return FALSE;
5530 if (gameMode == EditPosition) return TRUE;
5532 if (x >= 0 && y >= 0)
5533 from_piece = boards[currentMove][y][x];
5535 from_piece = EmptySquare;
5537 if (from_piece == EmptySquare) return FALSE;
5539 white_piece = (int)from_piece >= (int)WhitePawn &&
5540 (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
5543 case PlayFromGameFile:
5545 case TwoMachinesPlay:
5553 case MachinePlaysWhite:
5554 case IcsPlayingBlack:
5555 if (appData.zippyPlay) return FALSE;
5557 DisplayMoveError(_("You are playing Black"));
5562 case MachinePlaysBlack:
5563 case IcsPlayingWhite:
5564 if (appData.zippyPlay) return FALSE;
5566 DisplayMoveError(_("You are playing White"));
5572 if (!white_piece && WhiteOnMove(currentMove)) {
5573 DisplayMoveError(_("It is White's turn"));
5576 if (white_piece && !WhiteOnMove(currentMove)) {
5577 DisplayMoveError(_("It is Black's turn"));
5580 if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
5581 /* Editing correspondence game history */
5582 /* Could disallow this or prompt for confirmation */
5587 case BeginningOfGame:
5588 if (appData.icsActive) return FALSE;
5589 if (!appData.noChessProgram) {
5591 DisplayMoveError(_("You are playing White"));
5598 if (!white_piece && WhiteOnMove(currentMove)) {
5599 DisplayMoveError(_("It is White's turn"));
5602 if (white_piece && !WhiteOnMove(currentMove)) {
5603 DisplayMoveError(_("It is Black's turn"));
5612 if (currentMove != forwardMostMove && gameMode != AnalyzeMode
5613 && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
5614 && gameMode != AnalyzeFile && gameMode != Training) {
5615 DisplayMoveError(_("Displayed position is not current"));
5622 OnlyMove(int *x, int *y) {
5623 DisambiguateClosure cl;
5624 if (appData.zippyPlay) return FALSE;
5626 case MachinePlaysBlack:
5627 case IcsPlayingWhite:
5628 case BeginningOfGame:
5629 if(!WhiteOnMove(currentMove)) return FALSE;
5631 case MachinePlaysWhite:
5632 case IcsPlayingBlack:
5633 if(WhiteOnMove(currentMove)) return FALSE;
5638 cl.pieceIn = EmptySquare;
5643 cl.promoCharIn = NULLCHAR;
5644 Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
5645 if(cl.kind == NormalMove) {
5652 if(cl.kind != ImpossibleMove) return FALSE;
5653 cl.pieceIn = EmptySquare;
5658 cl.promoCharIn = NULLCHAR;
5659 Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
5660 if(cl.kind == NormalMove) {
5670 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
5671 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
5672 int lastLoadGameUseList = FALSE;
5673 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
5674 ChessMove lastLoadGameStart = (ChessMove) 0;
5677 UserMoveTest(fromX, fromY, toX, toY, promoChar, captureOwn)
5678 int fromX, fromY, toX, toY;
5683 ChessSquare pdown, pup;
5685 /* Check if the user is playing in turn. This is complicated because we
5686 let the user "pick up" a piece before it is his turn. So the piece he
5687 tried to pick up may have been captured by the time he puts it down!
5688 Therefore we use the color the user is supposed to be playing in this
5689 test, not the color of the piece that is currently on the starting
5690 square---except in EditGame mode, where the user is playing both
5691 sides; fortunately there the capture race can't happen. (It can
5692 now happen in IcsExamining mode, but that's just too bad. The user
5693 will get a somewhat confusing message in that case.)
5697 case PlayFromGameFile:
5699 case TwoMachinesPlay:
5703 /* We switched into a game mode where moves are not accepted,
5704 perhaps while the mouse button was down. */
5705 return ImpossibleMove;
5707 case MachinePlaysWhite:
5708 /* User is moving for Black */
5709 if (WhiteOnMove(currentMove)) {
5710 DisplayMoveError(_("It is White's turn"));
5711 return ImpossibleMove;
5715 case MachinePlaysBlack:
5716 /* User is moving for White */
5717 if (!WhiteOnMove(currentMove)) {
5718 DisplayMoveError(_("It is Black's turn"));
5719 return ImpossibleMove;
5725 case BeginningOfGame:
5728 if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
5729 (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
5730 /* User is moving for Black */
5731 if (WhiteOnMove(currentMove)) {
5732 DisplayMoveError(_("It is White's turn"));
5733 return ImpossibleMove;
5736 /* User is moving for White */
5737 if (!WhiteOnMove(currentMove)) {
5738 DisplayMoveError(_("It is Black's turn"));
5739 return ImpossibleMove;
5744 case IcsPlayingBlack:
5745 /* User is moving for Black */
5746 if (WhiteOnMove(currentMove)) {
5747 if (!appData.premove) {
5748 DisplayMoveError(_("It is White's turn"));
5749 } else if (toX >= 0 && toY >= 0) {
5752 premoveFromX = fromX;
5753 premoveFromY = fromY;
5754 premovePromoChar = promoChar;
5756 if (appData.debugMode)
5757 fprintf(debugFP, "Got premove: fromX %d,"
5758 "fromY %d, toX %d, toY %d\n",
5759 fromX, fromY, toX, toY);
5761 return ImpossibleMove;
5765 case IcsPlayingWhite:
5766 /* User is moving for White */
5767 if (!WhiteOnMove(currentMove)) {
5768 if (!appData.premove) {
5769 DisplayMoveError(_("It is Black's turn"));
5770 } else if (toX >= 0 && toY >= 0) {
5773 premoveFromX = fromX;
5774 premoveFromY = fromY;
5775 premovePromoChar = promoChar;
5777 if (appData.debugMode)
5778 fprintf(debugFP, "Got premove: fromX %d,"
5779 "fromY %d, toX %d, toY %d\n",
5780 fromX, fromY, toX, toY);
5782 return ImpossibleMove;
5790 /* EditPosition, empty square, or different color piece;
5791 click-click move is possible */
5792 if (toX == -2 || toY == -2) {
5793 boards[0][fromY][fromX] = EmptySquare;
5794 return AmbiguousMove;
5795 } else if (toX >= 0 && toY >= 0) {
5796 boards[0][toY][toX] = boards[0][fromY][fromX];
5797 if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
5798 if(boards[0][fromY][0] != EmptySquare) {
5799 if(boards[0][fromY][1]) boards[0][fromY][1]--;
5800 if(boards[0][fromY][1] == 0) boards[0][fromY][0] = EmptySquare;
5803 if(fromX == BOARD_RGHT+1) {
5804 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
5805 if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
5806 if(boards[0][fromY][BOARD_WIDTH-2] == 0) boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
5809 boards[0][fromY][fromX] = EmptySquare;
5810 return AmbiguousMove;
5812 return ImpossibleMove;
5815 if(toX < 0 || toY < 0) return ImpossibleMove;
5816 pdown = boards[currentMove][fromY][fromX];
5817 pup = boards[currentMove][toY][toX];
5819 /* [HGM] If move started in holdings, it means a drop */
5820 if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) {
5821 if( pup != EmptySquare ) return ImpossibleMove;
5822 if(appData.testLegality) {
5823 /* it would be more logical if LegalityTest() also figured out
5824 * which drops are legal. For now we forbid pawns on back rank.
5825 * Shogi is on its own here...
5827 if( (pdown == WhitePawn || pdown == BlackPawn) &&
5828 (toY == 0 || toY == BOARD_HEIGHT -1 ) )
5829 return(ImpossibleMove); /* no pawn drops on 1st/8th */
5831 return WhiteDrop; /* Not needed to specify white or black yet */
5835 /* [HGM] always test for legality, to get promotion info */
5836 moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5837 fromY, fromX, toY, toX, promoChar);
5838 /* [HGM] but possibly ignore an IllegalMove result */
5839 if (appData.testLegality) {
5840 if (moveType == IllegalMove || moveType == ImpossibleMove) {
5841 DisplayMoveError(_("Illegal move"));
5842 return ImpossibleMove;
5847 /* [HGM] <popupFix> in stead of calling FinishMove directly, this
5848 function is made into one that returns an OK move type if FinishMove
5849 should be called. This to give the calling driver routine the
5850 opportunity to finish the userMove input with a promotion popup,
5851 without bothering the user with this for invalid or illegal moves */
5853 /* FinishMove(moveType, fromX, fromY, toX, toY, promoChar); */
5856 /* Common tail of UserMoveEvent and DropMenuEvent */
5858 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
5860 int fromX, fromY, toX, toY;
5861 /*char*/int promoChar;
5865 if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) && promoChar != NULLCHAR)
5867 // [HGM] superchess: suppress promotions to non-available piece
5868 int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
5869 if(WhiteOnMove(currentMove))
5871 if(!boards[currentMove][k][BOARD_WIDTH-2])
5876 if(!boards[currentMove][BOARD_HEIGHT-1-k][1])
5881 /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
5882 move type in caller when we know the move is a legal promotion */
5883 if(moveType == NormalMove && promoChar)
5884 moveType = PromoCharToMoveType(WhiteOnMove(currentMove), promoChar);
5886 /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
5887 move type in caller when we know the move is a legal promotion */
5888 if(moveType == NormalMove && promoChar)
5889 moveType = PromoCharToMoveType(WhiteOnMove(currentMove), promoChar);
5891 /* [HGM] convert drag-and-drop piece drops to standard form */
5892 if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK )
5894 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
5895 if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n",
5896 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
5897 // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
5898 if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
5899 fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
5900 while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++;
5904 /* [HGM] <popupFix> The following if has been moved here from
5905 UserMoveEvent(). Because it seemed to belong here (why not allow
5906 piece drops in training games?), and because it can only be
5907 performed after it is known to what we promote. */
5908 if (gameMode == Training)
5910 /* compare the move played on the board to the next move in the
5911 * game. If they match, display the move and the opponent's response.
5912 * If they don't match, display an error message.
5916 CopyBoard(testBoard, boards[currentMove]);
5917 ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
5919 if (CompareBoards(testBoard, boards[currentMove+1]))
5921 ForwardInner(currentMove+1);
5923 /* Autoplay the opponent's response.
5924 * if appData.animate was TRUE when Training mode was entered,
5925 * the response will be animated.
5927 saveAnimate = appData.animate;
5928 appData.animate = animateTraining;
5929 ForwardInner(currentMove+1);
5930 appData.animate = saveAnimate;
5932 /* check for the end of the game */
5933 if (currentMove >= forwardMostMove)
5935 gameMode = PlayFromGameFile;
5937 SetTrainingModeOff();
5938 DisplayInformation(_("End of game"));
5943 DisplayError(_("Incorrect move"), 0);
5948 /* Ok, now we know that the move is good, so we can kill
5949 the previous line in Analysis Mode */
5950 if ((gameMode == AnalyzeMode || gameMode == EditGame)
5951 && currentMove < forwardMostMove) {
5952 PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
5955 /* If we need the chess program but it's dead, restart it */
5956 ResurrectChessProgram();
5958 /* A user move restarts a paused game*/
5962 thinkOutput[0] = NULLCHAR;
5964 MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
5967 if(Adjudicate(NULL)) return 1; // [HGM] adjudicate: take care of automtic game end
5969 if (gameMode == BeginningOfGame)
5971 if (appData.noChessProgram)
5973 gameMode = EditGame;
5979 gameMode = MachinePlaysBlack;
5982 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
5986 sprintf(buf, "name %s\n", gameInfo.white);
5987 SendToProgram(buf, &first);
5995 /* Relay move to ICS or chess engine */
5997 if (appData.icsActive) {
5998 if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
5999 gameMode == IcsExamining) {
6000 if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
6001 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
6003 SendMoveToICS(moveType, fromX, fromY, toX, toY);
6005 // also send plain move, in case ICS does not understand atomic claims
6006 SendMoveToICS(moveType, fromX, fromY, toX, toY);
6010 if (first.sendTime && (gameMode == BeginningOfGame ||
6011 gameMode == MachinePlaysWhite ||
6012 gameMode == MachinePlaysBlack)) {
6013 SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
6015 if (gameMode != EditGame && gameMode != PlayFromGameFile) {
6016 // [HGM] book: if program might be playing, let it use book
6017 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
6018 first.maybeThinking = TRUE;
6019 } else SendMoveToProgram(forwardMostMove-1, &first);
6020 if (currentMove == cmailOldMove + 1) {
6021 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
6025 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6030 switch (MateTest(boards[currentMove], PosFlags(currentMove)) )
6037 if (WhiteOnMove(currentMove)) {
6038 GameEnds(BlackWins, "Black mates", GE_PLAYER);
6040 GameEnds(WhiteWins, "White mates", GE_PLAYER);
6044 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
6049 case MachinePlaysBlack:
6050 case MachinePlaysWhite:
6051 /* disable certain menu options while machine is thinking */
6052 SetMachineThinkingEnables();
6058 userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
6061 { // [HGM] book: simulate book reply
6062 static char bookMove[MSG_SIZ]; // a bit generous?
6065 programStats.nodes = programStats.depth = programStats.time =
6066 programStats.score = programStats.got_only_move = 0;
6067 sprintf(programStats.movelist, "%s (xbook)", bookHit);
6069 strcpy(bookMove, "move ");
6070 strcat(bookMove, bookHit);
6071 HandleMachineMove(bookMove, &first);
6078 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
6079 int fromX, fromY, toX, toY;
6082 /* [HGM] This routine was added to allow calling of its two logical
6083 parts from other modules in the old way. Before, UserMoveEvent()
6084 automatically called FinishMove() if the move was OK, and returned
6085 otherwise. I separated the two, in order to make it possible to
6086 slip a promotion popup in between. But that it always needs two
6087 calls, to the first part, (now called UserMoveTest() ), and to
6088 FinishMove if the first part succeeded. Calls that do not need
6089 to do anything in between, can call this routine the old way.
6091 ChessMove moveType = UserMoveTest(fromX, fromY, toX, toY, promoChar, FALSE);
6092 if(appData.debugMode) fprintf(debugFP, "moveType 4 = %d, promochar = %x\n", moveType, promoChar);
6093 if(moveType == AmbiguousMove)
6094 DrawPosition(FALSE, boards[currentMove]);
6095 else if(moveType != ImpossibleMove && moveType != Comment)
6096 FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
6100 Mark(board, flags, kind, rf, ff, rt, ft, closure)
6107 typedef char Markers[BOARD_RANKS][BOARD_FILES];
6108 Markers *m = (Markers *) closure;
6109 if(rf == fromY && ff == fromX)
6110 (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
6111 || kind == WhiteCapturesEnPassant
6112 || kind == BlackCapturesEnPassant);
6113 else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
6117 MarkTargetSquares(int clear)
6120 if(!appData.markers || !appData.highlightDragging ||
6121 !appData.testLegality || gameMode == EditPosition) return;
6123 for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) marker[y][x] = 0;
6126 GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker);
6127 if(PosFlags(0) & F_MANDATORY_CAPTURE) {
6128 for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
6130 for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
6133 DrawPosition(TRUE, NULL);
6136 void LeftClick(ClickType clickType, int xPix, int yPix)
6139 Boolean saveAnimate;
6140 static int second = 0, promotionChoice = 0;
6141 char promoChoice = NULLCHAR;
6143 if(appData.seekGraph && appData.icsActive && loggedOn &&
6144 (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
6145 SeekGraphClick(clickType, xPix, yPix, 0);
6149 if (clickType == Press) ErrorPopDown();
6150 MarkTargetSquares(1);
6152 x = EventToSquare(xPix, BOARD_WIDTH);
6153 y = EventToSquare(yPix, BOARD_HEIGHT);
6154 if (!flipView && y >= 0) {
6155 y = BOARD_HEIGHT - 1 - y;
6157 if (flipView && x >= 0) {
6158 x = BOARD_WIDTH - 1 - x;
6161 if(promotionChoice) { // we are waiting for a click to indicate promotion piece
6162 if(clickType == Release) return; // ignore upclick of click-click destination
6163 promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
6164 if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
6165 if(gameInfo.holdingsWidth &&
6166 (WhiteOnMove(currentMove)
6167 ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y > 0
6168 : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT-1) ) {
6169 // click in right holdings, for determining promotion piece
6170 ChessSquare p = boards[currentMove][y][x];
6171 if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
6172 if(p != EmptySquare) {
6173 FinishMove(NormalMove, fromX, fromY, toX, toY, ToLower(PieceToChar(p)));
6178 DrawPosition(FALSE, boards[currentMove]);
6182 /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
6183 if(clickType == Press
6184 && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
6185 || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
6186 || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
6190 if(!appData.oneClick || !OnlyMove(&x, &y)) {
6191 if (clickType == Press) {
6193 if (OKToStartUserMove(x, y)) {
6197 MarkTargetSquares(0);
6198 DragPieceBegin(xPix, yPix);
6199 if (appData.highlightDragging) {
6200 SetHighlights(x, y, -1, -1);
6209 if (clickType == Press && gameMode != EditPosition) {
6214 // ignore off-board to clicks
6215 if(y < 0 || x < 0) return;
6217 /* Check if clicking again on the same color piece */
6218 fromP = boards[currentMove][fromY][fromX];
6219 toP = boards[currentMove][y][x];
6220 frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom;
6221 if ((WhitePawn <= fromP && fromP <= WhiteKing &&
6222 WhitePawn <= toP && toP <= WhiteKing &&
6223 !(fromP == WhiteKing && toP == WhiteRook && frc) &&
6224 !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
6225 (BlackPawn <= fromP && fromP <= BlackKing &&
6226 BlackPawn <= toP && toP <= BlackKing &&
6227 !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
6228 !(fromP == BlackKing && toP == BlackRook && frc))) {
6229 /* Clicked again on same color piece -- changed his mind */
6230 second = (x == fromX && y == fromY);
6231 if (appData.highlightDragging) {
6232 SetHighlights(x, y, -1, -1);
6236 if (OKToStartUserMove(x, y)) {
6239 MarkTargetSquares(0);
6240 DragPieceBegin(xPix, yPix);
6244 // ignore clicks on holdings
6245 if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
6248 if (clickType == Release && x == fromX && y == fromY) {
6249 DragPieceEnd(xPix, yPix);
6250 if (appData.animateDragging) {
6251 /* Undo animation damage if any */
6252 DrawPosition(FALSE, NULL);
6255 /* Second up/down in same square; just abort move */
6260 ClearPremoveHighlights();
6262 /* First upclick in same square; start click-click mode */
6263 SetHighlights(x, y, -1, -1);
6268 /* we now have a different from- and (possibly off-board) to-square */
6269 /* Completed move */
6272 saveAnimate = appData.animate;
6273 if (clickType == Press) {
6274 /* Finish clickclick move */
6275 if (appData.animate || appData.highlightLastMove) {
6276 SetHighlights(fromX, fromY, toX, toY);
6281 /* Finish drag move */
6282 if (appData.highlightLastMove) {
6283 SetHighlights(fromX, fromY, toX, toY);
6287 DragPieceEnd(xPix, yPix);
6288 /* Don't animate move and drag both */
6289 appData.animate = FALSE;
6292 // moves into holding are invalid for now (except in EditPosition, adapting to-square)
6293 if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
6294 ChessSquare piece = boards[currentMove][fromY][fromX];
6295 if(gameMode == EditPosition && piece != EmptySquare &&
6296 fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
6299 if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
6300 n = PieceToNumber(piece - (int)BlackPawn);
6301 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
6302 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
6303 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
6305 if(x == BOARD_RGHT+1 && piece < BlackPawn) {
6306 n = PieceToNumber(piece);
6307 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
6308 boards[currentMove][n][BOARD_WIDTH-1] = piece;
6309 boards[currentMove][n][BOARD_WIDTH-2]++;
6311 boards[currentMove][fromY][fromX] = EmptySquare;
6315 DrawPosition(TRUE, boards[currentMove]);
6319 // off-board moves should not be highlighted
6320 if(x < 0 || x < 0) ClearHighlights();
6322 if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice)) {
6323 SetHighlights(fromX, fromY, toX, toY);
6324 if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
6325 // [HGM] super: promotion to captured piece selected from holdings
6326 ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
6327 promotionChoice = TRUE;
6328 // kludge follows to temporarily execute move on display, without promoting yet
6329 boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
6330 boards[currentMove][toY][toX] = p;
6331 DrawPosition(FALSE, boards[currentMove]);
6332 boards[currentMove][fromY][fromX] = p; // take back, but display stays
6333 boards[currentMove][toY][toX] = q;
6334 DisplayMessage("Click in holdings to choose piece", "");
6339 UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
6340 if (!appData.highlightLastMove || gotPremove) ClearHighlights();
6341 if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
6344 appData.animate = saveAnimate;
6345 if (appData.animate || appData.animateDragging) {
6346 /* Undo animation damage if needed */
6347 DrawPosition(FALSE, NULL);
6351 int RightClick(ClickType action, int x, int y, int *fromX, int *fromY)
6352 { // front-end-free part taken out of PieceMenuPopup
6353 int whichMenu; int xSqr, ySqr;
6355 if(seekGraphUp) { // [HGM] seekgraph
6356 if(action == Press) SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
6357 if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
6361 xSqr = EventToSquare(x, BOARD_WIDTH);
6362 ySqr = EventToSquare(y, BOARD_HEIGHT);
6363 if (action == Release) UnLoadPV(); // [HGM] pv
6364 if (action != Press) return -2; // return code to be ignored
6367 if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;
6369 if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;
6370 if (xSqr < 0 || ySqr < 0) return -1;
6371 whichMenu = 0; // edit-position menu
6374 if(!appData.icsEngineAnalyze) return -1;
6375 case IcsPlayingWhite:
6376 case IcsPlayingBlack:
6377 if(!appData.zippyPlay) goto noZip;
6380 case MachinePlaysWhite:
6381 case MachinePlaysBlack:
6382 case TwoMachinesPlay: // [HGM] pv: use for showing PV
6383 if (!appData.dropMenu) {
6385 return 2; // flag front-end to grab mouse events
6387 if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
6388 gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
6391 if (xSqr < 0 || ySqr < 0) return -1;
6392 if (!appData.dropMenu || appData.testLegality &&
6393 gameInfo.variant != VariantBughouse &&
6394 gameInfo.variant != VariantCrazyhouse) return -1;
6395 whichMenu = 1; // drop menu
6401 if (((*fromX = xSqr) < 0) ||
6402 ((*fromY = ySqr) < 0)) {
6403 *fromX = *fromY = -1;
6407 *fromX = BOARD_WIDTH - 1 - *fromX;
6409 *fromY = BOARD_HEIGHT - 1 - *fromY;
6414 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
6416 // char * hint = lastHint;
6417 FrontEndProgramStats stats;
6419 stats.which = cps == &first ? 0 : 1;
6420 stats.depth = cpstats->depth;
6421 stats.nodes = cpstats->nodes;
6422 stats.score = cpstats->score;
6423 stats.time = cpstats->time;
6424 stats.pv = cpstats->movelist;
6425 stats.hint = lastHint;
6426 stats.an_move_index = 0;
6427 stats.an_move_count = 0;
6429 if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
6430 stats.hint = cpstats->move_name;
6431 stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
6432 stats.an_move_count = cpstats->nr_moves;
6435 if(stats.pv && stats.pv[0]) strcpy(lastPV[stats.which], stats.pv); // [HGM] pv: remember last PV of each
6437 SetProgramStats( &stats );
6441 Adjudicate(ChessProgramState *cps)
6442 { // [HGM] some adjudications useful with buggy engines
6443 // [HGM] adjudicate: made into separate routine, which now can be called after every move
6444 // In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
6445 // Actually ending the game is now based on the additional internal condition canAdjudicate.
6446 // Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
6447 int k, count = 0; static int bare = 1;
6448 ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
6449 Boolean canAdjudicate = !appData.icsActive;
6451 // most tests only when we understand the game, i.e. legality-checking on, and (for the time being) no piece drops
6452 if(gameInfo.holdingsSize == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
6453 if( appData.testLegality )
6454 { /* [HGM] Some more adjudications for obstinate engines */
6455 int NrWN=0, NrBN=0, NrWB=0, NrBB=0, NrWR=0, NrBR=0,
6456 NrWQ=0, NrBQ=0, NrW=0, NrK=0, bishopsColor = 0,
6457 NrPieces=0, NrPawns=0, PawnAdvance=0, i, j;
6458 static int moveCount = 6;
6460 char *reason = NULL;
6463 /* Count what is on board. */
6464 for(i=0; i<BOARD_HEIGHT; i++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
6465 { ChessSquare p = boards[forwardMostMove][i][j];
6469 { /* count B,N,R and other of each side */
6472 NrK++; break; // [HGM] atomic: count Kings
6476 case WhiteFerz: // [HGM] shatranj: kludge to mke it work in shatranj
6477 bishopsColor |= 1 << ((i^j)&1);
6482 case BlackFerz: // [HGM] shatranj: kludge to mke it work in shatranj
6483 bishopsColor |= 1 << ((i^j)&1);
6498 PawnAdvance += m; NrPawns++;
6500 NrPieces += (p != EmptySquare);
6501 NrW += ((int)p < (int)BlackPawn);
6502 if(gameInfo.variant == VariantXiangqi &&
6503 (p == WhiteFerz || p == WhiteAlfil || p == BlackFerz || p == BlackAlfil)) {
6504 NrPieces--; // [HGM] XQ: do not count purely defensive pieces
6505 NrW -= ((int)p < (int)BlackPawn);
6509 /* Some material-based adjudications that have to be made before stalemate test */
6510 if(gameInfo.variant == VariantAtomic && NrK < 2) {
6511 // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
6512 boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
6513 if(canAdjudicate && appData.checkMates) {
6515 SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
6516 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6517 GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
6518 "Xboard adjudication: King destroyed", GE_XBOARD );
6523 /* Bare King in Shatranj (loses) or Losers (wins) */
6524 if( NrW == 1 || NrPieces - NrW == 1) {
6525 if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
6526 boards[forwardMostMove][EP_STATUS] = EP_WINS; // mark as win, so it becomes claimable
6527 if(canAdjudicate && appData.checkMates) {
6529 SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
6530 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6531 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
6532 "Xboard adjudication: Bare king", GE_XBOARD );
6536 if( gameInfo.variant == VariantShatranj && --bare < 0)
6538 boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
6539 if(canAdjudicate && appData.checkMates) {
6540 /* but only adjudicate if adjudication enabled */
6542 SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
6543 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6544 GameEnds( NrW > 1 ? WhiteWins : NrPieces - NrW > 1 ? BlackWins : GameIsDrawn,
6545 "Xboard adjudication: Bare king", GE_XBOARD );
6552 // don't wait for engine to announce game end if we can judge ourselves
6553 switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
6555 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
6556 int i, checkCnt = 0; // (should really be done by making nr of checks part of game state)
6557 for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
6558 if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
6561 reason = "Xboard adjudication: 3rd check";
6562 boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
6572 reason = "Xboard adjudication: Stalemate";
6573 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
6574 boards[forwardMostMove][EP_STATUS] = EP_STALEMATE; // default result for stalemate is draw
6575 if(gameInfo.variant == VariantLosers || gameInfo.variant == VariantGiveaway) // [HGM] losers:
6576 boards[forwardMostMove][EP_STATUS] = EP_WINS; // in these variants stalemated is always a win
6577 else if(gameInfo.variant == VariantSuicide) // in suicide it depends
6578 boards[forwardMostMove][EP_STATUS] = NrW == NrPieces-NrW ? EP_STALEMATE :
6579 ((NrW < NrPieces-NrW) != WhiteOnMove(forwardMostMove) ?
6580 EP_CHECKMATE : EP_WINS);
6581 else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
6582 boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
6586 reason = "Xboard adjudication: Checkmate";
6587 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
6591 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
6593 result = GameIsDrawn; break;
6595 result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
6597 result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
6599 result = (ChessMove) 0;
6601 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
6603 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6604 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6605 GameEnds( result, reason, GE_XBOARD );
6609 /* Next absolutely insufficient mating material. */
6610 if( NrPieces == 2 || gameInfo.variant != VariantXiangqi &&
6611 gameInfo.variant != VariantShatranj && // [HGM] baring will remain possible
6612 (NrPieces == 3 && NrWN+NrBN+NrWB+NrBB == 1 ||
6613 NrPieces == NrBB+NrWB+2 && bishopsColor != 3)) // [HGM] all Bishops (Ferz!) same color
6614 { /* KBK, KNK, KK of KBKB with like Bishops */
6616 /* always flag draws, for judging claims */
6617 boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
6619 if(canAdjudicate && appData.materialDraws) {
6620 /* but only adjudicate them if adjudication enabled */
6621 if(engineOpponent) {
6622 SendToProgram("force\n", engineOpponent); // suppress reply
6623 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
6625 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6626 GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
6631 /* Then some trivial draws (only adjudicate, cannot be claimed) */
6633 ( NrWR == 1 && NrBR == 1 /* KRKR */
6634 || NrWQ==1 && NrBQ==1 /* KQKQ */
6635 || NrWN==2 || NrBN==2 /* KNNK */
6636 || NrWN+NrWB == 1 && NrBN+NrBB == 1 /* KBKN, KBKB, KNKN */
6638 if(canAdjudicate && --moveCount < 0 && appData.trivialDraws)
6639 { /* if the first 3 moves do not show a tactical win, declare draw */
6640 if(engineOpponent) {
6641 SendToProgram("force\n", engineOpponent); // suppress reply
6642 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6644 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6645 GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
6648 } else moveCount = 6;
6652 if (appData.debugMode) { int i;
6653 fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
6654 forwardMostMove, backwardMostMove, boards[backwardMostMove][EP_STATUS],
6655 appData.drawRepeats);
6656 for( i=forwardMostMove; i>=backwardMostMove; i-- )
6657 fprintf(debugFP, "%d ep=%d\n", i, (signed char)boards[i][EP_STATUS]);
6661 // Repetition draws and 50-move rule can be applied independently of legality testing
6663 /* Check for rep-draws */
6665 for(k = forwardMostMove-2;
6666 k>=backwardMostMove && k>=forwardMostMove-100 &&
6667 (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
6668 (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE;
6671 if(CompareBoards(boards[k], boards[forwardMostMove])) {
6672 /* compare castling rights */
6673 if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
6674 (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
6675 rights++; /* King lost rights, while rook still had them */
6676 if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
6677 if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
6678 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
6679 rights++; /* but at least one rook lost them */
6681 if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
6682 (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
6684 if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
6685 if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
6686 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
6689 if( canAdjudicate && rights == 0 && ++count > appData.drawRepeats-2
6690 && appData.drawRepeats > 1) {
6691 /* adjudicate after user-specified nr of repeats */
6692 if(engineOpponent) {
6693 SendToProgram("force\n", engineOpponent); // suppress reply
6694 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6696 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6697 if(gameInfo.variant == VariantXiangqi && appData.testLegality) {
6698 // [HGM] xiangqi: check for forbidden perpetuals
6699 int m, ourPerpetual = 1, hisPerpetual = 1;
6700 for(m=forwardMostMove; m>k; m-=2) {
6701 if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
6702 ourPerpetual = 0; // the current mover did not always check
6703 if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
6704 hisPerpetual = 0; // the opponent did not always check
6706 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
6707 ourPerpetual, hisPerpetual);
6708 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
6709 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
6710 "Xboard adjudication: perpetual checking", GE_XBOARD );
6713 if(hisPerpetual && !ourPerpetual) // he is checking us, but did not repeat yet
6714 break; // (or we would have caught him before). Abort repetition-checking loop.
6715 // Now check for perpetual chases
6716 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
6717 hisPerpetual = PerpetualChase(k, forwardMostMove);
6718 ourPerpetual = PerpetualChase(k+1, forwardMostMove);
6719 if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
6720 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
6721 "Xboard adjudication: perpetual chasing", GE_XBOARD );
6724 if(hisPerpetual && !ourPerpetual) // he is chasing us, but did not repeat yet
6725 break; // Abort repetition-checking loop.
6727 // if neither of us is checking or chasing all the time, or both are, it is draw
6729 GameEnds( GameIsDrawn, "Xboard adjudication: repetition draw", GE_XBOARD );
6732 if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
6733 boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
6737 /* Now we test for 50-move draws. Determine ply count */
6738 count = forwardMostMove;
6739 /* look for last irreversble move */
6740 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
6742 /* if we hit starting position, add initial plies */
6743 if( count == backwardMostMove )
6744 count -= initialRulePlies;
6745 count = forwardMostMove - count;
6747 boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
6748 /* this is used to judge if draw claims are legal */
6749 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
6750 if(engineOpponent) {
6751 SendToProgram("force\n", engineOpponent); // suppress reply
6752 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6754 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6755 GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
6759 /* if draw offer is pending, treat it as a draw claim
6760 * when draw condition present, to allow engines a way to
6761 * claim draws before making their move to avoid a race
6762 * condition occurring after their move
6764 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
6766 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
6767 p = "Draw claim: 50-move rule";
6768 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
6769 p = "Draw claim: 3-fold repetition";
6770 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
6771 p = "Draw claim: insufficient mating material";
6772 if( p != NULL && canAdjudicate) {
6773 if(engineOpponent) {
6774 SendToProgram("force\n", engineOpponent); // suppress reply
6775 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6777 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6778 GameEnds( GameIsDrawn, p, GE_XBOARD );
6783 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
6784 if(engineOpponent) {
6785 SendToProgram("force\n", engineOpponent); // suppress reply
6786 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6788 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6789 GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
6795 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
6796 { // [HGM] book: this routine intercepts moves to simulate book replies
6797 char *bookHit = NULL;
6799 //first determine if the incoming move brings opponent into his book
6800 if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
6801 bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
6802 if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
6803 if(bookHit != NULL && !cps->bookSuspend) {
6804 // make sure opponent is not going to reply after receiving move to book position
6805 SendToProgram("force\n", cps);
6806 cps->bookSuspend = TRUE; // flag indicating it has to be restarted
6808 if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
6809 // now arrange restart after book miss
6811 // after a book hit we never send 'go', and the code after the call to this routine
6812 // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
6814 if (cps->useUsermove) sprintf(buf, "usermove "); // sorry, no SAN yet :(
6815 sprintf(buf, "%s\n", bookHit); // force book move into program supposed to play it
6816 SendToProgram(buf, cps);
6817 if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
6818 } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
6819 SendToProgram("go\n", cps);
6820 cps->bookSuspend = FALSE; // after a 'go' we are never suspended
6821 } else { // 'go' might be sent based on 'firstMove' after this routine returns
6822 if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
6823 SendToProgram("go\n", cps);
6824 cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
6826 return bookHit; // notify caller of hit, so it can take action to send move to opponent
6830 ChessProgramState *savedState;
6831 void DeferredBookMove(void)
6833 if(savedState->lastPing != savedState->lastPong)
6834 ScheduleDelayedEvent(DeferredBookMove, 10);
6836 HandleMachineMove(savedMessage, savedState);
6840 HandleMachineMove(message, cps)
6842 ChessProgramState *cps;
6844 char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
6845 char realname[MSG_SIZ];
6846 int fromX, fromY, toX, toY;
6855 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
6857 * Kludge to ignore BEL characters
6859 while (*message == '\007') message++;
6862 * [HGM] engine debug message: ignore lines starting with '#' character
6864 if(cps->debug && *message == '#') return;
6867 * Look for book output
6869 if (cps == &first && bookRequested) {
6870 if (message[0] == '\t' || message[0] == ' ') {
6871 /* Part of the book output is here; append it */
6872 strcat(bookOutput, message);
6873 strcat(bookOutput, " \n");
6875 } else if (bookOutput[0] != NULLCHAR) {
6876 /* All of book output has arrived; display it */
6877 char *p = bookOutput;
6878 while (*p != NULLCHAR) {
6879 if (*p == '\t') *p = ' ';
6882 DisplayInformation(bookOutput);
6883 bookRequested = FALSE;
6884 /* Fall through to parse the current output */
6889 * Look for machine move.
6891 if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
6892 (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
6894 /* This method is only useful on engines that support ping */
6895 if (cps->lastPing != cps->lastPong) {
6896 if (gameMode == BeginningOfGame) {
6897 /* Extra move from before last new; ignore */
6898 if (appData.debugMode) {
6899 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
6902 if (appData.debugMode) {
6903 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
6904 cps->which, gameMode);
6907 SendToProgram("undo\n", cps);
6913 case BeginningOfGame:
6914 /* Extra move from before last reset; ignore */
6915 if (appData.debugMode) {
6916 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
6923 /* Extra move after we tried to stop. The mode test is
6924 not a reliable way of detecting this problem, but it's
6925 the best we can do on engines that don't support ping.
6927 if (appData.debugMode) {
6928 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
6929 cps->which, gameMode);
6931 SendToProgram("undo\n", cps);
6934 case MachinePlaysWhite:
6935 case IcsPlayingWhite:
6936 machineWhite = TRUE;
6939 case MachinePlaysBlack:
6940 case IcsPlayingBlack:
6941 machineWhite = FALSE;
6944 case TwoMachinesPlay:
6945 machineWhite = (cps->twoMachinesColor[0] == 'w');
6948 if (WhiteOnMove(forwardMostMove) != machineWhite) {
6949 if (appData.debugMode) {
6951 "Ignoring move out of turn by %s, gameMode %d"
6952 ", forwardMost %d\n",
6953 cps->which, gameMode, forwardMostMove);
6958 if (appData.debugMode) { int f = forwardMostMove;
6959 fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
6960 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
6961 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
6963 if(cps->alphaRank) AlphaRank(machineMove, 4);
6964 if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
6965 &fromX, &fromY, &toX, &toY, &promoChar)) {
6966 /* Machine move could not be parsed; ignore it. */
6967 sprintf(buf1, _("Illegal move \"%s\" from %s machine"),
6968 machineMove, cps->which);
6969 DisplayError(buf1, 0);
6970 sprintf(buf1, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
6971 machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
6972 if (gameMode == TwoMachinesPlay) {
6973 GameEnds(machineWhite ? BlackWins : WhiteWins,
6980 /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
6981 /* So we have to redo legality test with true e.p. status here, */
6982 /* to make sure an illegal e.p. capture does not slip through, */
6983 /* to cause a forfeit on a justified illegal-move complaint */
6984 /* of the opponent. */
6985 if( gameMode==TwoMachinesPlay && appData.testLegality
6986 && fromY != DROP_RANK /* [HGM] temporary; should still add legality test for drops */
6989 moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
6990 fromY, fromX, toY, toX, promoChar);
6991 if (appData.debugMode) {
6993 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
6994 boards[forwardMostMove][CASTLING][i], castlingRank[i]);
6995 fprintf(debugFP, "castling rights\n");
6997 if(moveType == IllegalMove) {
6998 sprintf(buf1, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
6999 machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
7000 GameEnds(machineWhite ? BlackWins : WhiteWins,
7003 } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
7004 /* [HGM] Kludge to handle engines that send FRC-style castling
7005 when they shouldn't (like TSCP-Gothic) */
7007 case WhiteASideCastleFR:
7008 case BlackASideCastleFR:
7010 currentMoveString[2]++;
7012 case WhiteHSideCastleFR:
7013 case BlackHSideCastleFR:
7015 currentMoveString[2]--;
7017 default: ; // nothing to do, but suppresses warning of pedantic compilers
7020 hintRequested = FALSE;
7021 lastHint[0] = NULLCHAR;
7022 bookRequested = FALSE;
7023 /* Program may be pondering now */
7024 cps->maybeThinking = TRUE;
7025 if (cps->sendTime == 2) cps->sendTime = 1;
7026 if (cps->offeredDraw) cps->offeredDraw--;
7029 if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
7031 SendMoveToICS(moveType, fromX, fromY, toX, toY);
7033 if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
7034 char buf[3*MSG_SIZ];
7036 sprintf(buf, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
7037 programStats.score / 100.,
7039 programStats.time / 100.,
7040 (unsigned int)programStats.nodes,
7041 (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
7042 programStats.movelist);
7044 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
7048 /* currentMoveString is set as a side-effect of ParseOneMove */
7049 strcpy(machineMove, currentMoveString);
7050 strcat(machineMove, "\n");
7051 strcpy(moveList[forwardMostMove], machineMove);
7053 /* [AS] Save move info and clear stats for next move */
7054 pvInfoList[ forwardMostMove ].score = programStats.score;
7055 pvInfoList[ forwardMostMove ].depth = programStats.depth;
7056 pvInfoList[ forwardMostMove ].time = programStats.time; // [HGM] PGNtime: take time from engine stats
7057 ClearProgramStats();
7058 thinkOutput[0] = NULLCHAR;
7059 hiddenThinkOutputState = 0;
7061 MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
7063 /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
7064 if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
7067 while( count < adjudicateLossPlies ) {
7068 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
7071 score = -score; /* Flip score for winning side */
7074 if( score > adjudicateLossThreshold ) {
7081 if( count >= adjudicateLossPlies ) {
7082 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7084 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7085 "Xboard adjudication",
7092 if( gameMode == TwoMachinesPlay ) {
7093 // [HGM] some adjudications useful with buggy engines
7094 int k, count = 0; static int bare = 1;
7095 if(gameInfo.holdingsSize == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
7098 if( appData.testLegality )
7099 { /* [HGM] Some more adjudications for obstinate engines */
7100 int NrWN=0, NrBN=0, NrWB=0, NrBB=0, NrWR=0, NrBR=0,
7101 NrWQ=0, NrBQ=0, NrW=0, NrK=0, bishopsColor = 0,
7102 NrPieces=0, NrPawns=0, PawnAdvance=0, i, j;
7103 static int moveCount = 6;
7105 char *reason = NULL;
7107 /* Count what is on board. */
7108 for(i=0; i<BOARD_HEIGHT; i++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
7109 { ChessSquare p = boards[forwardMostMove][i][j];
7113 { /* count B,N,R and other of each side */
7116 NrK++; break; // [HGM] atomic: count Kings
7120 case WhiteFerz: // [HGM] shatranj: kludge to mke it work in shatranj
7121 bishopsColor |= 1 << ((i^j)&1);
7126 case BlackFerz: // [HGM] shatranj: kludge to mke it work in shatranj
7127 bishopsColor |= 1 << ((i^j)&1);
7142 PawnAdvance += m; NrPawns++;
7144 NrPieces += (p != EmptySquare);
7145 NrW += ((int)p < (int)BlackPawn);
7146 if(gameInfo.variant == VariantXiangqi &&
7147 (p == WhiteFerz || p == WhiteAlfil || p == BlackFerz || p == BlackAlfil)) {
7148 NrPieces--; // [HGM] XQ: do not count purely defensive pieces
7149 NrW -= ((int)p < (int)BlackPawn);
7153 /* Some material-based adjudications that have to be made before stalemate test */
7154 if(gameInfo.variant == VariantAtomic && NrK < 2) {
7155 // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
7156 boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
7157 if(appData.checkMates) {
7158 SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets move
7159 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7160 GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
7161 "Xboard adjudication: King destroyed", GE_XBOARD );
7166 /* Bare King in Shatranj (loses) or Losers (wins) */
7167 if( NrW == 1 || NrPieces - NrW == 1) {
7168 if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
7169 boards[forwardMostMove][EP_STATUS] = EP_WINS; // mark as win, so it becomes claimable
7170 if(appData.checkMates) {
7171 SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets to see move
7172 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7173 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7174 "Xboard adjudication: Bare king", GE_XBOARD );
7178 if( gameInfo.variant == VariantShatranj && --bare < 0)
7180 boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
7181 if(appData.checkMates) {
7182 /* but only adjudicate if adjudication enabled */
7183 SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets move
7184 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7185 GameEnds( NrW > 1 ? WhiteWins : NrPieces - NrW > 1 ? BlackWins : GameIsDrawn,
7186 "Xboard adjudication: Bare king", GE_XBOARD );
7193 // don't wait for engine to announce game end if we can judge ourselves
7194 switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
7196 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
7197 int i, checkCnt = 0; // (should really be done by making nr of checks part of game state)
7198 for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
7199 if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
7202 reason = "Xboard adjudication: 3rd check";
7203 boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
7213 reason = "Xboard adjudication: Stalemate";
7214 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
7215 boards[forwardMostMove][EP_STATUS] = EP_STALEMATE; // default result for stalemate is draw
7216 if(gameInfo.variant == VariantLosers || gameInfo.variant == VariantGiveaway) // [HGM] losers:
7217 boards[forwardMostMove][EP_STATUS] = EP_WINS; // in these variants stalemated is always a win
7218 else if(gameInfo.variant == VariantSuicide) // in suicide it depends
7219 boards[forwardMostMove][EP_STATUS] = NrW == NrPieces-NrW ? EP_STALEMATE :
7220 ((NrW < NrPieces-NrW) != WhiteOnMove(forwardMostMove) ?
7221 EP_CHECKMATE : EP_WINS);
7222 else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
7223 boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
7227 reason = "Xboard adjudication: Checkmate";
7228 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
7237 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
7239 result = GameIsDrawn; break;
7241 result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
7243 result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
7245 result = (ChessMove) 0;
7247 if(appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
7248 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
7249 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7250 GameEnds( result, reason, GE_XBOARD );
7254 /* Next absolutely insufficient mating material. */
7255 if( NrPieces == 2 || gameInfo.variant != VariantXiangqi &&
7256 gameInfo.variant != VariantShatranj && // [HGM] baring will remain possible
7257 (NrPieces == 3 && NrWN+NrBN+NrWB+NrBB == 1 ||
7258 NrPieces == NrBB+NrWB+2 && bishopsColor != 3)) // [HGM] all Bishops (Ferz!) same color
7259 { /* KBK, KNK, KK of KBKB with like Bishops */
7261 /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
7262 /* So we have to redo legality test with true e.p. status here, */
7263 /* to make sure an illegal e.p. capture does not slip through, */
7264 /* to cause a forfeit on a justified illegal-move complaint */
7265 /* of the opponent. */
7266 if( gameMode==TwoMachinesPlay && appData.testLegality
7267 && fromY != DROP_RANK /* [HGM] temporary; should still add legality test for drops */
7270 moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
7271 fromY, fromX, toY, toX, promoChar);
7272 if (appData.debugMode) {
7274 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
7275 boards[forwardMostMove][CASTLING][i], castlingRank[i]);
7276 fprintf(debugFP, "castling rights\n");
7278 if(moveType == IllegalMove) {
7279 sprintf(buf1, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
7280 machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
7281 GameEnds(machineWhite ? BlackWins : WhiteWins,
7284 } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
7285 /* [HGM] Kludge to handle engines that send FRC-style castling
7286 when they shouldn't (like TSCP-Gothic) */
7288 case WhiteASideCastleFR:
7289 case BlackASideCastleFR:
7291 currentMoveString[2]++;
7293 case WhiteHSideCastleFR:
7294 case BlackHSideCastleFR:
7296 currentMoveString[2]--;
7298 default: ; // nothing to do, but suppresses warning of pedantic compilers
7301 hintRequested = FALSE;
7302 lastHint[0] = NULLCHAR;
7303 bookRequested = FALSE;
7304 /* Program may be pondering now */
7305 cps->maybeThinking = TRUE;
7306 if (cps->sendTime == 2) cps->sendTime = 1;
7307 if (cps->offeredDraw) cps->offeredDraw--;
7310 /* currentMoveString is set as a side-effect of ParseOneMove */
7311 strcpy(machineMove, currentMoveString);
7312 strcat(machineMove, "\n");
7313 strcpy(moveList[forwardMostMove], machineMove);
7315 MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
7318 /* Then some trivial draws (only adjudicate, cannot be claimed) */
7320 ( NrWR == 1 && NrBR == 1 /* KRKR */
7321 || NrWQ==1 && NrBQ==1 /* KQKQ */
7322 || NrWN==2 || NrBN==2 /* KNNK */
7323 || NrWN+NrWB == 1 && NrBN+NrBB == 1 /* KBKN, KBKB, KNKN */
7325 if(--moveCount < 0 && appData.trivialDraws)
7326 { /* if the first 3 moves do not show a tactical win, declare draw */
7327 SendToProgram("force\n", cps->other); // suppress reply
7328 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
7329 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7330 GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
7333 } else moveCount = 6;
7337 if (appData.debugMode) { int i;
7338 fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
7339 forwardMostMove, backwardMostMove, boards[backwardMostMove][EP_STATUS],
7340 appData.drawRepeats);
7341 for( i=forwardMostMove; i>=backwardMostMove; i-- )
7342 fprintf(debugFP, "%d ep=%d\n", i, (signed char)boards[i][EP_STATUS]);
7346 /* Check for rep-draws */
7348 for(k = forwardMostMove-2;
7349 k>=backwardMostMove && k>=forwardMostMove-100 &&
7350 (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
7351 (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE;
7354 if(CompareBoards(boards[k], boards[forwardMostMove])) {
7355 /* compare castling rights */
7356 if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
7357 (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
7358 rights++; /* King lost rights, while rook still had them */
7359 if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
7360 if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
7361 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
7362 rights++; /* but at least one rook lost them */
7364 if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
7365 (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
7367 if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
7368 if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
7369 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
7372 if( rights == 0 && ++count > appData.drawRepeats-2
7373 && appData.drawRepeats > 1) {
7374 /* adjudicate after user-specified nr of repeats */
7375 SendToProgram("force\n", cps->other); // suppress reply
7376 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
7377 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7378 if(gameInfo.variant == VariantXiangqi && appData.testLegality) {
7379 // [HGM] xiangqi: check for forbidden perpetuals
7380 int m, ourPerpetual = 1, hisPerpetual = 1;
7381 for(m=forwardMostMove; m>k; m-=2) {
7382 if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
7383 ourPerpetual = 0; // the current mover did not always check
7384 if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
7385 hisPerpetual = 0; // the opponent did not always check
7387 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
7388 ourPerpetual, hisPerpetual);
7389 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
7390 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7391 "Xboard adjudication: perpetual checking", GE_XBOARD );
7394 if(hisPerpetual && !ourPerpetual) // he is checking us, but did not repeat yet
7395 break; // (or we would have caught him before). Abort repetition-checking loop.
7396 // Now check for perpetual chases
7397 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
7398 hisPerpetual = PerpetualChase(k, forwardMostMove);
7399 ourPerpetual = PerpetualChase(k+1, forwardMostMove);
7400 if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
7401 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7402 "Xboard adjudication: perpetual chasing", GE_XBOARD );
7405 if(hisPerpetual && !ourPerpetual) // he is chasing us, but did not repeat yet
7406 break; // Abort repetition-checking loop.
7408 // if neither of us is checking or chasing all the time, or both are, it is draw
7410 GameEnds( GameIsDrawn, "Xboard adjudication: repetition draw", GE_XBOARD );
7413 if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
7414 boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
7418 /* Now we test for 50-move draws. Determine ply count */
7419 count = forwardMostMove;
7420 /* look for last irreversble move */
7421 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
7423 /* if we hit starting position, add initial plies */
7424 if( count == backwardMostMove )
7425 count -= initialRulePlies;
7426 count = forwardMostMove - count;
7428 boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
7429 /* this is used to judge if draw claims are legal */
7430 if(appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
7431 SendToProgram("force\n", cps->other); // suppress reply
7432 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
7433 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7434 GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
7437 /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
7438 if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
7441 while( count < adjudicateLossPlies ) {
7442 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
7445 score = -score; /* Flip score for winning side */
7449 if( score > adjudicateLossThreshold ) {
7456 if( count >= adjudicateLossPlies ) {
7457 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7459 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7460 "Xboard adjudication",
7467 if(Adjudicate(cps)) return; // [HGM] adjudicate: for all automatic game ends
7470 if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
7472 if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
7473 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
7475 SendMoveToICS(moveType, fromX, fromY, toX, toY);
7477 SendMoveToICS(moveType, fromX, fromY, toX, toY);
7479 if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
7480 char buf[3*MSG_SIZ];
7482 sprintf(buf, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
7483 programStats.score / 100.,
7485 programStats.time / 100.,
7486 (unsigned int)programStats.nodes,
7487 (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
7488 programStats.movelist);
7490 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
7495 /* [AS] Save move info and clear stats for next move */
7496 pvInfoList[ forwardMostMove-1 ].score = programStats.score;
7497 pvInfoList[ forwardMostMove-1 ].depth = programStats.depth;
7498 pvInfoList[ forwardMostMove-1 ].time = programStats.time; // [HGM] PGNtime: take time from engine stats
7499 ClearProgramStats();
7500 thinkOutput[0] = NULLCHAR;
7501 hiddenThinkOutputState = 0;
7504 if (gameMode == TwoMachinesPlay) {
7505 /* [HGM] relaying draw offers moved to after reception of move */
7506 /* and interpreting offer as claim if it brings draw condition */
7507 if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
7508 SendToProgram("draw\n", cps->other);
7510 if (cps->other->sendTime) {
7511 SendTimeRemaining(cps->other,
7512 cps->other->twoMachinesColor[0] == 'w');
7514 bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
7515 if (firstMove && !bookHit) {
7517 if (cps->other->useColors) {
7518 SendToProgram(cps->other->twoMachinesColor, cps->other);
7520 SendToProgram("go\n", cps->other);
7522 cps->other->maybeThinking = TRUE;
7525 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7527 if (!pausing && appData.ringBellAfterMoves) {
7532 * Reenable menu items that were disabled while
7533 * machine was thinking
7535 if (gameMode != TwoMachinesPlay)
7536 SetUserThinkingEnables();
7538 // [HGM] book: after book hit opponent has received move and is now in force mode
7539 // force the book reply into it, and then fake that it outputted this move by jumping
7540 // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
7542 static char bookMove[MSG_SIZ]; // a bit generous?
7544 strcpy(bookMove, "move ");
7545 strcat(bookMove, bookHit);
7548 programStats.nodes = programStats.depth = programStats.time =
7549 programStats.score = programStats.got_only_move = 0;
7550 sprintf(programStats.movelist, "%s (xbook)", bookHit);
7552 if(cps->lastPing != cps->lastPong) {
7553 savedMessage = message; // args for deferred call
7555 ScheduleDelayedEvent(DeferredBookMove, 10);
7564 /* Set special modes for chess engines. Later something general
7565 * could be added here; for now there is just one kludge feature,
7566 * needed because Crafty 15.10 and earlier don't ignore SIGINT
7567 * when "xboard" is given as an interactive command.
7569 if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
7570 cps->useSigint = FALSE;
7571 cps->useSigterm = FALSE;
7573 if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
7574 ParseFeatures(message+8, cps);
7575 return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
7578 /* [HGM] Allow engine to set up a position. Don't ask me why one would
7579 * want this, I was asked to put it in, and obliged.
7581 if (!strncmp(message, "setboard ", 9)) {
7582 Board initial_position;
7584 GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
7586 if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
7587 DisplayError(_("Bad FEN received from engine"), 0);
7591 CopyBoard(boards[0], initial_position);
7592 initialRulePlies = FENrulePlies;
7593 if(blackPlaysFirst) gameMode = MachinePlaysWhite;
7594 else gameMode = MachinePlaysBlack;
7595 DrawPosition(FALSE, boards[currentMove]);
7601 * Look for communication commands
7603 if (!strncmp(message, "telluser ", 9)) {
7604 DisplayNote(message + 9);
7607 if (!strncmp(message, "tellusererror ", 14)) {
7609 DisplayError(message + 14, 0);
7612 if (!strncmp(message, "tellopponent ", 13)) {
7613 if (appData.icsActive) {
7615 snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
7619 DisplayNote(message + 13);
7623 if (!strncmp(message, "tellothers ", 11)) {
7624 if (appData.icsActive) {
7626 snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
7632 if (!strncmp(message, "tellall ", 8)) {
7633 if (appData.icsActive) {
7635 snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
7639 DisplayNote(message + 8);
7643 if (strncmp(message, "warning", 7) == 0) {
7644 /* Undocumented feature, use tellusererror in new code */
7645 DisplayError(message, 0);
7648 if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
7649 strcpy(realname, cps->tidy);
7650 strcat(realname, " query");
7651 AskQuestion(realname, buf2, buf1, cps->pr);
7654 /* Commands from the engine directly to ICS. We don't allow these to be
7655 * sent until we are logged on. Crafty kibitzes have been known to
7656 * interfere with the login process.
7659 if (!strncmp(message, "tellics ", 8)) {
7660 SendToICS(message + 8);
7664 if (!strncmp(message, "tellicsnoalias ", 15)) {
7665 SendToICS(ics_prefix);
7666 SendToICS(message + 15);
7670 /* The following are for backward compatibility only */
7671 if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
7672 !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
7673 SendToICS(ics_prefix);
7679 if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
7683 * If the move is illegal, cancel it and redraw the board.
7684 * Also deal with other error cases. Matching is rather loose
7685 * here to accommodate engines written before the spec.
7687 if (strncmp(message + 1, "llegal move", 11) == 0 ||
7688 strncmp(message, "Error", 5) == 0) {
7689 if (StrStr(message, "name") ||
7690 StrStr(message, "rating") || StrStr(message, "?") ||
7691 StrStr(message, "result") || StrStr(message, "board") ||
7692 StrStr(message, "bk") || StrStr(message, "computer") ||
7693 StrStr(message, "variant") || StrStr(message, "hint") ||
7694 StrStr(message, "random") || StrStr(message, "depth") ||
7695 StrStr(message, "accepted")) {
7698 if (StrStr(message, "protover")) {
7699 /* Program is responding to input, so it's apparently done
7700 initializing, and this error message indicates it is
7701 protocol version 1. So we don't need to wait any longer
7702 for it to initialize and send feature commands. */
7703 FeatureDone(cps, 1);
7704 cps->protocolVersion = 1;
7707 cps->maybeThinking = FALSE;
7709 if (StrStr(message, "draw")) {
7710 /* Program doesn't have "draw" command */
7711 cps->sendDrawOffers = 0;
7714 if (cps->sendTime != 1 &&
7715 (StrStr(message, "time") || StrStr(message, "otim"))) {
7716 /* Program apparently doesn't have "time" or "otim" command */
7720 if (StrStr(message, "analyze")) {
7721 cps->analysisSupport = FALSE;
7722 cps->analyzing = FALSE;
7724 sprintf(buf2, _("%s does not support analysis"), cps->tidy);
7725 DisplayError(buf2, 0);
7728 if (StrStr(message, "(no matching move)st")) {
7729 /* Special kludge for GNU Chess 4 only */
7730 cps->stKludge = TRUE;
7731 SendTimeControl(cps, movesPerSession, timeControl,
7732 timeIncrement, appData.searchDepth,
7736 if (StrStr(message, "(no matching move)sd")) {
7737 /* Special kludge for GNU Chess 4 only */
7738 cps->sdKludge = TRUE;
7739 SendTimeControl(cps, movesPerSession, timeControl,
7740 timeIncrement, appData.searchDepth,
7744 if (!StrStr(message, "llegal")) {
7747 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
7748 gameMode == IcsIdle) return;
7749 if (forwardMostMove <= backwardMostMove) return;
7750 if (pausing) PauseEvent();
7751 if(appData.forceIllegal) {
7752 // [HGM] illegal: machine refused move; force position after move into it
7753 SendToProgram("force\n", cps);
7754 if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
7755 // we have a real problem now, as SendBoard will use the a2a3 kludge
7756 // when black is to move, while there might be nothing on a2 or black
7757 // might already have the move. So send the board as if white has the move.
7758 // But first we must change the stm of the engine, as it refused the last move
7759 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
7760 if(WhiteOnMove(forwardMostMove)) {
7761 SendToProgram("a7a6\n", cps); // for the engine black still had the move
7762 SendBoard(cps, forwardMostMove); // kludgeless board
7764 SendToProgram("a2a3\n", cps); // for the engine white still had the move
7765 CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
7766 SendBoard(cps, forwardMostMove+1); // kludgeless board
7768 } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
7769 if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
7770 gameMode == TwoMachinesPlay)
7771 SendToProgram("go\n", cps);
7774 if (gameMode == PlayFromGameFile) {
7775 /* Stop reading this game file */
7776 gameMode = EditGame;
7779 currentMove = --forwardMostMove;
7780 DisplayMove(currentMove-1); /* before DisplayMoveError */
7782 DisplayBothClocks();
7783 sprintf(buf1, _("Illegal move \"%s\" (rejected by %s chess program)"),
7784 parseList[currentMove], cps->which);
7785 DisplayMoveError(buf1);
7786 DrawPosition(FALSE, boards[currentMove]);
7788 /* [HGM] illegal-move claim should forfeit game when Xboard */
7789 /* only passes fully legal moves */
7790 if( appData.testLegality && gameMode == TwoMachinesPlay ) {
7791 GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
7792 "False illegal-move claim", GE_XBOARD );
7796 if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
7797 /* Program has a broken "time" command that
7798 outputs a string not ending in newline.
7804 * If chess program startup fails, exit with an error message.
7805 * Attempts to recover here are futile.
7807 if ((StrStr(message, "unknown host") != NULL)
7808 || (StrStr(message, "No remote directory") != NULL)
7809 || (StrStr(message, "not found") != NULL)
7810 || (StrStr(message, "No such file") != NULL)
7811 || (StrStr(message, "can't alloc") != NULL)
7812 || (StrStr(message, "Permission denied") != NULL)) {
7814 cps->maybeThinking = FALSE;
7815 snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
7816 cps->which, cps->program, cps->host, message);
7817 RemoveInputSource(cps->isr);
7818 DisplayFatalError(buf1, 0, 1);
7823 * Look for hint output
7825 if (sscanf(message, "Hint: %s", buf1) == 1) {
7826 if (cps == &first && hintRequested) {
7827 hintRequested = FALSE;
7828 if (ParseOneMove(buf1, forwardMostMove, &moveType,
7829 &fromX, &fromY, &toX, &toY, &promoChar)) {
7830 (void) CoordsToAlgebraic(boards[forwardMostMove],
7831 PosFlags(forwardMostMove),
7832 fromY, fromX, toY, toX, promoChar, buf1);
7833 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
7834 DisplayInformation(buf2);
7836 /* Hint move could not be parsed!? */
7837 snprintf(buf2, sizeof(buf2),
7838 _("Illegal hint move \"%s\"\nfrom %s chess program"),
7840 DisplayError(buf2, 0);
7843 strcpy(lastHint, buf1);
7849 * Ignore other messages if game is not in progress
7851 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
7852 gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
7855 * look for win, lose, draw, or draw offer
7857 if (strncmp(message, "1-0", 3) == 0) {
7858 char *p, *q, *r = "";
7859 p = strchr(message, '{');
7867 GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
7869 } else if (strncmp(message, "0-1", 3) == 0) {
7870 char *p, *q, *r = "";
7871 p = strchr(message, '{');
7879 /* Kludge for Arasan 4.1 bug */
7880 if (strcmp(r, "Black resigns") == 0) {
7881 GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
7884 GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
7886 } else if (strncmp(message, "1/2", 3) == 0) {
7887 char *p, *q, *r = "";
7888 p = strchr(message, '{');
7897 GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
7900 } else if (strncmp(message, "White resign", 12) == 0) {
7901 GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
7903 } else if (strncmp(message, "Black resign", 12) == 0) {
7904 GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
7906 } else if (strncmp(message, "White matches", 13) == 0 ||
7907 strncmp(message, "Black matches", 13) == 0 ) {
7908 /* [HGM] ignore GNUShogi noises */
7910 } else if (strncmp(message, "White", 5) == 0 &&
7911 message[5] != '(' &&
7912 StrStr(message, "Black") == NULL) {
7913 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7915 } else if (strncmp(message, "Black", 5) == 0 &&
7916 message[5] != '(') {
7917 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7919 } else if (strcmp(message, "resign") == 0 ||
7920 strcmp(message, "computer resigns") == 0) {
7922 case MachinePlaysBlack:
7923 case IcsPlayingBlack:
7924 GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
7926 case MachinePlaysWhite:
7927 case IcsPlayingWhite:
7928 GameEnds(BlackWins, "White resigns", GE_ENGINE);
7930 case TwoMachinesPlay:
7931 if (cps->twoMachinesColor[0] == 'w')
7932 GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
7934 GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
7941 } else if (strncmp(message, "opponent mates", 14) == 0) {
7943 case MachinePlaysBlack:
7944 case IcsPlayingBlack:
7945 GameEnds(WhiteWins, "White mates", GE_ENGINE);
7947 case MachinePlaysWhite:
7948 case IcsPlayingWhite:
7949 GameEnds(BlackWins, "Black mates", GE_ENGINE);
7951 case TwoMachinesPlay:
7952 if (cps->twoMachinesColor[0] == 'w')
7953 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7955 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7962 } else if (strncmp(message, "computer mates", 14) == 0) {
7964 case MachinePlaysBlack:
7965 case IcsPlayingBlack:
7966 GameEnds(BlackWins, "Black mates", GE_ENGINE1);
7968 case MachinePlaysWhite:
7969 case IcsPlayingWhite:
7970 GameEnds(WhiteWins, "White mates", GE_ENGINE);
7972 case TwoMachinesPlay:
7973 if (cps->twoMachinesColor[0] == 'w')
7974 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7976 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7983 } else if (strncmp(message, "checkmate", 9) == 0) {
7984 if (WhiteOnMove(forwardMostMove)) {
7985 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7987 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7990 } else if (strstr(message, "Draw") != NULL ||
7991 strstr(message, "game is a draw") != NULL) {
7992 GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
7994 } else if (strstr(message, "offer") != NULL &&
7995 strstr(message, "draw") != NULL) {
7997 if (appData.zippyPlay && first.initDone) {
7998 /* Relay offer to ICS */
7999 SendToICS(ics_prefix);
8000 SendToICS("draw\n");
8003 cps->offeredDraw = 2; /* valid until this engine moves twice */
8004 if (gameMode == TwoMachinesPlay) {
8005 if (cps->other->offeredDraw) {
8006 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8007 /* [HGM] in two-machine mode we delay relaying draw offer */
8008 /* until after we also have move, to see if it is really claim */
8010 } else if (gameMode == MachinePlaysWhite ||
8011 gameMode == MachinePlaysBlack) {
8012 if (userOfferedDraw) {
8013 DisplayInformation(_("Machine accepts your draw offer"));
8014 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8016 DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
8023 * Look for thinking output
8025 if ( appData.showThinking // [HGM] thinking: test all options that cause this output
8026 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
8028 int plylev, mvleft, mvtot, curscore, time;
8029 char mvname[MOVE_LEN];
8033 int prefixHint = FALSE;
8034 mvname[0] = NULLCHAR;
8037 case MachinePlaysBlack:
8038 case IcsPlayingBlack:
8039 if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8041 case MachinePlaysWhite:
8042 case IcsPlayingWhite:
8043 if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8048 case IcsObserving: /* [DM] icsEngineAnalyze */
8049 if (!appData.icsEngineAnalyze) ignore = TRUE;
8051 case TwoMachinesPlay:
8052 if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
8063 if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8064 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
8066 if (plyext != ' ' && plyext != '\t') {
8070 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8071 if( cps->scoreIsAbsolute &&
8072 ( gameMode == MachinePlaysBlack ||
8073 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
8074 gameMode == IcsPlayingBlack || // [HGM] also add other situations where engine should report black POV
8075 (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
8076 !WhiteOnMove(currentMove)
8079 curscore = -curscore;
8083 programStats.depth = plylev;
8084 programStats.nodes = nodes;
8085 programStats.time = time;
8086 programStats.score = curscore;
8087 programStats.got_only_move = 0;
8089 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
8092 if(cps->nps == 0) ticklen = 10*time; // use engine reported time
8093 else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
8094 if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
8095 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
8096 whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
8097 if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
8098 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
8099 blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
8102 /* Buffer overflow protection */
8103 if (buf1[0] != NULLCHAR) {
8104 if (strlen(buf1) >= sizeof(programStats.movelist)
8105 && appData.debugMode) {
8107 "PV is too long; using the first %u bytes.\n",
8108 (unsigned) sizeof(programStats.movelist) - 1);
8111 safeStrCpy( programStats.movelist, buf1, sizeof(programStats.movelist) );
8113 sprintf(programStats.movelist, " no PV\n");
8116 if (programStats.seen_stat) {
8117 programStats.ok_to_send = 1;
8120 if (strchr(programStats.movelist, '(') != NULL) {
8121 programStats.line_is_book = 1;
8122 programStats.nr_moves = 0;
8123 programStats.moves_left = 0;
8125 programStats.line_is_book = 0;
8128 SendProgramStatsToFrontend( cps, &programStats );
8131 [AS] Protect the thinkOutput buffer from overflow... this
8132 is only useful if buf1 hasn't overflowed first!
8134 sprintf(thinkOutput, "[%d]%c%+.2f %s%s",
8136 (gameMode == TwoMachinesPlay ?
8137 ToUpper(cps->twoMachinesColor[0]) : ' '),
8138 ((double) curscore) / 100.0,
8139 prefixHint ? lastHint : "",
8140 prefixHint ? " " : "" );
8142 if( buf1[0] != NULLCHAR ) {
8143 unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
8145 if( strlen(buf1) > max_len ) {
8146 if( appData.debugMode) {
8147 fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
8149 buf1[max_len+1] = '\0';
8152 strcat( thinkOutput, buf1 );
8155 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
8156 || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8157 DisplayMove(currentMove - 1);
8161 } else if ((p=StrStr(message, "(only move)")) != NULL) {
8162 /* crafty (9.25+) says "(only move) <move>"
8163 * if there is only 1 legal move
8165 sscanf(p, "(only move) %s", buf1);
8166 sprintf(thinkOutput, "%s (only move)", buf1);
8167 sprintf(programStats.movelist, "%s (only move)", buf1);
8168 programStats.depth = 1;
8169 programStats.nr_moves = 1;
8170 programStats.moves_left = 1;
8171 programStats.nodes = 1;
8172 programStats.time = 1;
8173 programStats.got_only_move = 1;
8175 /* Not really, but we also use this member to
8176 mean "line isn't going to change" (Crafty
8177 isn't searching, so stats won't change) */
8178 programStats.line_is_book = 1;
8180 SendProgramStatsToFrontend( cps, &programStats );
8182 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8183 gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8184 DisplayMove(currentMove - 1);
8187 } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
8188 &time, &nodes, &plylev, &mvleft,
8189 &mvtot, mvname) >= 5) {
8190 /* The stat01: line is from Crafty (9.29+) in response
8191 to the "." command */
8192 programStats.seen_stat = 1;
8193 cps->maybeThinking = TRUE;
8195 if (programStats.got_only_move || !appData.periodicUpdates)
8198 programStats.depth = plylev;
8199 programStats.time = time;
8200 programStats.nodes = nodes;
8201 programStats.moves_left = mvleft;
8202 programStats.nr_moves = mvtot;
8203 strcpy(programStats.move_name, mvname);
8204 programStats.ok_to_send = 1;
8205 programStats.movelist[0] = '\0';
8207 SendProgramStatsToFrontend( cps, &programStats );
8211 } else if (strncmp(message,"++",2) == 0) {
8212 /* Crafty 9.29+ outputs this */
8213 programStats.got_fail = 2;
8216 } else if (strncmp(message,"--",2) == 0) {
8217 /* Crafty 9.29+ outputs this */
8218 programStats.got_fail = 1;
8221 } else if (thinkOutput[0] != NULLCHAR &&
8222 strncmp(message, " ", 4) == 0) {
8223 unsigned message_len;
8226 while (*p && *p == ' ') p++;
8228 message_len = strlen( p );
8230 /* [AS] Avoid buffer overflow */
8231 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
8232 strcat(thinkOutput, " ");
8233 strcat(thinkOutput, p);
8236 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
8237 strcat(programStats.movelist, " ");
8238 strcat(programStats.movelist, p);
8241 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8242 gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8243 DisplayMove(currentMove - 1);
8251 if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8252 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
8254 ChessProgramStats cpstats;
8256 if (plyext != ' ' && plyext != '\t') {
8260 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8261 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
8262 curscore = -curscore;
8265 cpstats.depth = plylev;
8266 cpstats.nodes = nodes;
8267 cpstats.time = time;
8268 cpstats.score = curscore;
8269 cpstats.got_only_move = 0;
8270 cpstats.movelist[0] = '\0';
8272 if (buf1[0] != NULLCHAR) {
8273 safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist) );
8276 cpstats.ok_to_send = 0;
8277 cpstats.line_is_book = 0;
8278 cpstats.nr_moves = 0;
8279 cpstats.moves_left = 0;
8281 SendProgramStatsToFrontend( cps, &cpstats );
8288 /* Parse a game score from the character string "game", and
8289 record it as the history of the current game. The game
8290 score is NOT assumed to start from the standard position.
8291 The display is not updated in any way.
8294 ParseGameHistory(game)
8298 int fromX, fromY, toX, toY, boardIndex;
8303 if (appData.debugMode)
8304 fprintf(debugFP, "Parsing game history: %s\n", game);
8306 if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
8307 gameInfo.site = StrSave(appData.icsHost);
8308 gameInfo.date = PGNDate();
8309 gameInfo.round = StrSave("-");
8311 /* Parse out names of players */
8312 while (*game == ' ') game++;
8314 while (*game != ' ') *p++ = *game++;
8316 gameInfo.white = StrSave(buf);
8317 while (*game == ' ') game++;
8319 while (*game != ' ' && *game != '\n') *p++ = *game++;
8321 gameInfo.black = StrSave(buf);
8324 boardIndex = blackPlaysFirst ? 1 : 0;
8327 yyboardindex = boardIndex;
8328 moveType = (ChessMove) yylex();
8330 case IllegalMove: /* maybe suicide chess, etc. */
8331 if (appData.debugMode) {
8332 fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
8333 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8334 setbuf(debugFP, NULL);
8336 case WhitePromotionChancellor:
8337 case BlackPromotionChancellor:
8338 case WhitePromotionArchbishop:
8339 case BlackPromotionArchbishop:
8340 case WhitePromotionQueen:
8341 case BlackPromotionQueen:
8342 case WhitePromotionRook:
8343 case BlackPromotionRook:
8344 case WhitePromotionBishop:
8345 case BlackPromotionBishop:
8346 case WhitePromotionKnight:
8347 case BlackPromotionKnight:
8348 case WhitePromotionKing:
8349 case BlackPromotionKing:
8351 case WhiteCapturesEnPassant:
8352 case BlackCapturesEnPassant:
8353 case WhiteKingSideCastle:
8354 case WhiteQueenSideCastle:
8355 case BlackKingSideCastle:
8356 case BlackQueenSideCastle:
8357 case WhiteKingSideCastleWild:
8358 case WhiteQueenSideCastleWild:
8359 case BlackKingSideCastleWild:
8360 case BlackQueenSideCastleWild:
8362 case WhiteHSideCastleFR:
8363 case WhiteASideCastleFR:
8364 case BlackHSideCastleFR:
8365 case BlackASideCastleFR:
8367 fromX = currentMoveString[0] - AAA;
8368 fromY = currentMoveString[1] - ONE;
8369 toX = currentMoveString[2] - AAA;
8370 toY = currentMoveString[3] - ONE;
8371 promoChar = currentMoveString[4];
8375 fromX = moveType == WhiteDrop ?
8376 (int) CharToPiece(ToUpper(currentMoveString[0])) :
8377 (int) CharToPiece(ToLower(currentMoveString[0]));
8379 toX = currentMoveString[2] - AAA;
8380 toY = currentMoveString[3] - ONE;
8381 promoChar = NULLCHAR;
8385 sprintf(buf, _("Ambiguous move in ICS output: \"%s\""), yy_text);
8386 if (appData.debugMode) {
8387 fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
8388 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8389 setbuf(debugFP, NULL);
8391 DisplayError(buf, 0);
8393 case ImpossibleMove:
8395 sprintf(buf, _("Illegal move in ICS output: \"%s\""), yy_text);
8396 if (appData.debugMode) {
8397 fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
8398 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8399 setbuf(debugFP, NULL);
8401 DisplayError(buf, 0);
8403 case (ChessMove) 0: /* end of file */
8404 if (boardIndex < backwardMostMove) {
8405 /* Oops, gap. How did that happen? */
8406 DisplayError(_("Gap in move list"), 0);
8409 backwardMostMove = blackPlaysFirst ? 1 : 0;
8410 if (boardIndex > forwardMostMove) {
8411 forwardMostMove = boardIndex;
8415 if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
8416 strcat(parseList[boardIndex-1], " ");
8417 strcat(parseList[boardIndex-1], yy_text);
8429 case GameUnfinished:
8430 if (gameMode == IcsExamining) {
8431 if (boardIndex < backwardMostMove) {
8432 /* Oops, gap. How did that happen? */
8435 backwardMostMove = blackPlaysFirst ? 1 : 0;
8438 gameInfo.result = moveType;
8439 p = strchr(yy_text, '{');
8440 if (p == NULL) p = strchr(yy_text, '(');
8443 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
8445 q = strchr(p, *p == '{' ? '}' : ')');
8446 if (q != NULL) *q = NULLCHAR;
8449 gameInfo.resultDetails = StrSave(p);
8452 if (boardIndex >= forwardMostMove &&
8453 !(gameMode == IcsObserving && ics_gamenum == -1)) {
8454 backwardMostMove = blackPlaysFirst ? 1 : 0;
8457 (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
8458 fromY, fromX, toY, toX, promoChar,
8459 parseList[boardIndex]);
8460 CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
8461 /* currentMoveString is set as a side-effect of yylex */
8462 strcpy(moveList[boardIndex], currentMoveString);
8463 strcat(moveList[boardIndex], "\n");
8465 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
8466 switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
8472 if(gameInfo.variant != VariantShogi)
8473 strcat(parseList[boardIndex - 1], "+");
8477 strcat(parseList[boardIndex - 1], "#");
8484 /* Apply a move to the given board */
8486 ApplyMove(fromX, fromY, toX, toY, promoChar, board)
8487 int fromX, fromY, toX, toY;
8491 ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
8492 int promoRank = gameInfo.variant == VariantMakruk ? 3 : 1;
8494 /* [HGM] compute & store e.p. status and castling rights for new position */
8495 /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
8498 if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
8499 oldEP = (signed char)board[EP_STATUS];
8500 board[EP_STATUS] = EP_NONE;
8502 if( board[toY][toX] != EmptySquare )
8503 board[EP_STATUS] = EP_CAPTURE;
8505 if( board[fromY][fromX] == WhitePawn ) {
8506 if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
8507 board[EP_STATUS] = EP_PAWN_MOVE;
8509 if(toX>BOARD_LEFT && board[toY][toX-1] == BlackPawn &&
8510 gameInfo.variant != VariantBerolina || toX < fromX)
8511 board[EP_STATUS] = toX | berolina;
8512 if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
8513 gameInfo.variant != VariantBerolina || toX > fromX)
8514 board[EP_STATUS] = toX;
8517 if( board[fromY][fromX] == BlackPawn ) {
8518 if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
8519 board[EP_STATUS] = EP_PAWN_MOVE;
8520 if( toY-fromY== -2) {
8521 if(toX>BOARD_LEFT && board[toY][toX-1] == WhitePawn &&
8522 gameInfo.variant != VariantBerolina || toX < fromX)
8523 board[EP_STATUS] = toX | berolina;
8524 if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
8525 gameInfo.variant != VariantBerolina || toX > fromX)
8526 board[EP_STATUS] = toX;
8530 for(i=0; i<nrCastlingRights; i++) {
8531 if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
8532 board[CASTLING][i] == toX && castlingRank[i] == toY
8533 ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
8538 /* [HGM] In Shatranj and Courier all promotions are to Ferz */
8539 if((gameInfo.variant==VariantShatranj || gameInfo.variant==VariantCourier || gameInfo.variant == VariantMakruk)
8540 && promoChar != 0) promoChar = PieceToChar(WhiteFerz);
8542 if (fromX == toX && fromY == toY) return;
8544 if (fromY == DROP_RANK) {
8546 piece = board[toY][toX] = (ChessSquare) fromX;
8548 piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
8549 king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
8550 if(gameInfo.variant == VariantKnightmate)
8551 king += (int) WhiteUnicorn - (int) WhiteKing;
8553 /* Code added by Tord: */
8554 /* FRC castling assumed when king captures friendly rook. */
8555 if (board[fromY][fromX] == WhiteKing &&
8556 board[toY][toX] == WhiteRook) {
8557 board[fromY][fromX] = EmptySquare;
8558 board[toY][toX] = EmptySquare;
8560 board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
8562 board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
8564 } else if (board[fromY][fromX] == BlackKing &&
8565 board[toY][toX] == BlackRook) {
8566 board[fromY][fromX] = EmptySquare;
8567 board[toY][toX] = EmptySquare;
8569 board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
8571 board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
8573 /* End of code added by Tord */
8575 } else if (board[fromY][fromX] == king
8576 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8577 && toY == fromY && toX > fromX+1) {
8578 board[fromY][fromX] = EmptySquare;
8579 board[toY][toX] = king;
8580 board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
8581 board[fromY][BOARD_RGHT-1] = EmptySquare;
8582 } else if (board[fromY][fromX] == king
8583 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8584 && toY == fromY && toX < fromX-1) {
8585 board[fromY][fromX] = EmptySquare;
8586 board[toY][toX] = king;
8587 board[toY][toX+1] = board[fromY][BOARD_LEFT];
8588 board[fromY][BOARD_LEFT] = EmptySquare;
8589 } else if (board[fromY][fromX] == WhitePawn
8590 && toY >= BOARD_HEIGHT-promoRank
8591 && gameInfo.variant != VariantXiangqi
8593 /* white pawn promotion */
8594 board[toY][toX] = CharToPiece(ToUpper(promoChar));
8595 if (board[toY][toX] == EmptySquare) {
8596 board[toY][toX] = WhiteQueen;
8598 if(gameInfo.variant==VariantBughouse ||
8599 gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
8600 board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
8601 board[fromY][fromX] = EmptySquare;
8602 } else if ((fromY == BOARD_HEIGHT-4)
8604 && gameInfo.variant != VariantXiangqi
8605 && gameInfo.variant != VariantBerolina
8606 && (board[fromY][fromX] == WhitePawn)
8607 && (board[toY][toX] == EmptySquare)) {
8608 board[fromY][fromX] = EmptySquare;
8609 board[toY][toX] = WhitePawn;
8610 captured = board[toY - 1][toX];
8611 board[toY - 1][toX] = EmptySquare;
8612 } else if ((fromY == BOARD_HEIGHT-4)
8614 && gameInfo.variant == VariantBerolina
8615 && (board[fromY][fromX] == WhitePawn)
8616 && (board[toY][toX] == EmptySquare)) {
8617 board[fromY][fromX] = EmptySquare;
8618 board[toY][toX] = WhitePawn;
8619 if(oldEP & EP_BEROLIN_A) {
8620 captured = board[fromY][fromX-1];
8621 board[fromY][fromX-1] = EmptySquare;
8622 }else{ captured = board[fromY][fromX+1];
8623 board[fromY][fromX+1] = EmptySquare;
8625 } else if (board[fromY][fromX] == king
8626 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8627 && toY == fromY && toX > fromX+1) {
8628 board[fromY][fromX] = EmptySquare;
8629 board[toY][toX] = king;
8630 board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
8631 board[fromY][BOARD_RGHT-1] = EmptySquare;
8632 } else if (board[fromY][fromX] == king
8633 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8634 && toY == fromY && toX < fromX-1) {
8635 board[fromY][fromX] = EmptySquare;
8636 board[toY][toX] = king;
8637 board[toY][toX+1] = board[fromY][BOARD_LEFT];
8638 board[fromY][BOARD_LEFT] = EmptySquare;
8639 } else if (fromY == 7 && fromX == 3
8640 && board[fromY][fromX] == BlackKing
8641 && toY == 7 && toX == 5) {
8642 board[fromY][fromX] = EmptySquare;
8643 board[toY][toX] = BlackKing;
8644 board[fromY][7] = EmptySquare;
8645 board[toY][4] = BlackRook;
8646 } else if (fromY == 7 && fromX == 3
8647 && board[fromY][fromX] == BlackKing
8648 && toY == 7 && toX == 1) {
8649 board[fromY][fromX] = EmptySquare;
8650 board[toY][toX] = BlackKing;
8651 board[fromY][0] = EmptySquare;
8652 board[toY][2] = BlackRook;
8653 } else if (board[fromY][fromX] == BlackPawn
8655 && gameInfo.variant != VariantXiangqi
8657 /* black pawn promotion */
8658 board[toY][toX] = CharToPiece(ToLower(promoChar));
8659 if (board[toY][toX] == EmptySquare) {
8660 board[toY][toX] = BlackQueen;
8662 if(gameInfo.variant==VariantBughouse ||
8663 gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
8664 board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
8665 board[fromY][fromX] = EmptySquare;
8666 } else if ((fromY == 3)
8668 && gameInfo.variant != VariantXiangqi
8669 && gameInfo.variant != VariantBerolina
8670 && (board[fromY][fromX] == BlackPawn)
8671 && (board[toY][toX] == EmptySquare)) {
8672 board[fromY][fromX] = EmptySquare;
8673 board[toY][toX] = BlackPawn;
8674 captured = board[toY + 1][toX];
8675 board[toY + 1][toX] = EmptySquare;
8676 } else if ((fromY == 3)
8678 && gameInfo.variant == VariantBerolina
8679 && (board[fromY][fromX] == BlackPawn)
8680 && (board[toY][toX] == EmptySquare)) {
8681 board[fromY][fromX] = EmptySquare;
8682 board[toY][toX] = BlackPawn;
8683 if(oldEP & EP_BEROLIN_A) {
8684 captured = board[fromY][fromX-1];
8685 board[fromY][fromX-1] = EmptySquare;
8686 }else{ captured = board[fromY][fromX+1];
8687 board[fromY][fromX+1] = EmptySquare;
8690 board[toY][toX] = board[fromY][fromX];
8691 board[fromY][fromX] = EmptySquare;
8694 /* [HGM] now we promote for Shogi, if needed */
8695 if(gameInfo.variant == VariantShogi && promoChar == 'q')
8696 board[toY][toX] = (ChessSquare) (PROMOTED piece);
8699 if (gameInfo.holdingsWidth != 0) {
8701 /* !!A lot more code needs to be written to support holdings */
8702 /* [HGM] OK, so I have written it. Holdings are stored in the */
8703 /* penultimate board files, so they are automaticlly stored */
8704 /* in the game history. */
8705 if (fromY == DROP_RANK) {
8706 /* Delete from holdings, by decreasing count */
8707 /* and erasing image if necessary */
8709 if(p < (int) BlackPawn) { /* white drop */
8710 p -= (int)WhitePawn;
8711 p = PieceToNumber((ChessSquare)p);
8712 if(p >= gameInfo.holdingsSize) p = 0;
8713 if(--board[p][BOARD_WIDTH-2] <= 0)
8714 board[p][BOARD_WIDTH-1] = EmptySquare;
8715 if((int)board[p][BOARD_WIDTH-2] < 0)
8716 board[p][BOARD_WIDTH-2] = 0;
8717 } else { /* black drop */
8718 p -= (int)BlackPawn;
8719 p = PieceToNumber((ChessSquare)p);
8720 if(p >= gameInfo.holdingsSize) p = 0;
8721 if(--board[BOARD_HEIGHT-1-p][1] <= 0)
8722 board[BOARD_HEIGHT-1-p][0] = EmptySquare;
8723 if((int)board[BOARD_HEIGHT-1-p][1] < 0)
8724 board[BOARD_HEIGHT-1-p][1] = 0;
8727 if (captured != EmptySquare && gameInfo.holdingsSize > 0
8728 && gameInfo.variant != VariantBughouse ) {
8729 /* [HGM] holdings: Add to holdings, if holdings exist */
8730 if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
8731 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
8732 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
8735 if (p >= (int) BlackPawn) {
8736 p -= (int)BlackPawn;
8737 if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
8738 /* in Shogi restore piece to its original first */
8739 captured = (ChessSquare) (DEMOTED captured);
8742 p = PieceToNumber((ChessSquare)p);
8743 if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
8744 board[p][BOARD_WIDTH-2]++;
8745 board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
8747 p -= (int)WhitePawn;
8748 if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
8749 captured = (ChessSquare) (DEMOTED captured);
8752 p = PieceToNumber((ChessSquare)p);
8753 if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
8754 board[BOARD_HEIGHT-1-p][1]++;
8755 board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
8758 } else if (gameInfo.variant == VariantAtomic) {
8759 if (captured != EmptySquare) {
8761 for (y = toY-1; y <= toY+1; y++) {
8762 for (x = toX-1; x <= toX+1; x++) {
8763 if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
8764 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
8765 board[y][x] = EmptySquare;
8769 board[toY][toX] = EmptySquare;
8772 if(gameInfo.variant == VariantShogi && promoChar != NULLCHAR && promoChar != '=') {
8773 /* [HGM] Shogi promotions */
8774 board[toY][toX] = (ChessSquare) (PROMOTED piece);
8777 if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
8778 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
8779 // [HGM] superchess: take promotion piece out of holdings
8780 int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
8781 if((int)piece < (int)BlackPawn) { // determine stm from piece color
8782 if(!--board[k][BOARD_WIDTH-2])
8783 board[k][BOARD_WIDTH-1] = EmptySquare;
8785 if(!--board[BOARD_HEIGHT-1-k][1])
8786 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
8792 /* Updates forwardMostMove */
8794 MakeMove(fromX, fromY, toX, toY, promoChar)
8795 int fromX, fromY, toX, toY;
8798 // forwardMostMove++; // [HGM] bare: moved downstream
8800 if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
8801 int timeLeft; static int lastLoadFlag=0; int king, piece;
8802 piece = boards[forwardMostMove][fromY][fromX];
8803 king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
8804 if(gameInfo.variant == VariantKnightmate)
8805 king += (int) WhiteUnicorn - (int) WhiteKing;
8806 if(forwardMostMove == 0) {
8808 fprintf(serverMoves, "%s;", second.tidy);
8809 fprintf(serverMoves, "%s;", first.tidy);
8810 if(!blackPlaysFirst)
8811 fprintf(serverMoves, "%s;", second.tidy);
8812 } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
8813 lastLoadFlag = loadFlag;
8815 fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
8816 // print castling suffix
8817 if( toY == fromY && piece == king ) {
8819 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
8821 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
8824 if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
8825 boards[forwardMostMove][fromY][fromX] == BlackPawn ) &&
8826 boards[forwardMostMove][toY][toX] == EmptySquare
8828 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
8830 if(promoChar != NULLCHAR)
8831 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);
8833 fprintf(serverMoves, "/%d/%d",
8834 pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
8835 if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
8836 else timeLeft = blackTimeRemaining/1000;
8837 fprintf(serverMoves, "/%d", timeLeft);
8839 fflush(serverMoves);
8842 if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations
8843 DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
8847 UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
8848 if (commentList[forwardMostMove+1] != NULL) {
8849 free(commentList[forwardMostMove+1]);
8850 commentList[forwardMostMove+1] = NULL;
8852 CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
8853 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
8854 forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
8855 SwitchClocks(); // uses forwardMostMove, so must be done after incrementing it !
8856 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
8857 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
8858 gameInfo.result = GameUnfinished;
8859 if (gameInfo.resultDetails != NULL) {
8860 free(gameInfo.resultDetails);
8861 gameInfo.resultDetails = NULL;
8863 CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
8864 moveList[forwardMostMove - 1]);
8865 (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
8866 PosFlags(forwardMostMove - 1),
8867 fromY, fromX, toY, toX, promoChar,
8868 parseList[forwardMostMove - 1]);
8869 switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
8875 if(gameInfo.variant != VariantShogi)
8876 strcat(parseList[forwardMostMove - 1], "+");
8880 strcat(parseList[forwardMostMove - 1], "#");
8883 if (appData.debugMode) {
8884 fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
8889 /* Updates currentMove if not pausing */
8891 ShowMove(fromX, fromY, toX, toY)
8893 int instant = (gameMode == PlayFromGameFile) ?
8894 (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
8896 if(appData.noGUI) return;
8898 if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile)
8902 if (forwardMostMove == currentMove + 1)
8905 // AnimateMove(boards[forwardMostMove - 1],
8906 // fromX, fromY, toX, toY);
8908 if (appData.highlightLastMove)
8910 SetHighlights(fromX, fromY, toX, toY);
8913 currentMove = forwardMostMove;
8916 if (instant) return;
8918 DisplayMove(currentMove - 1);
8919 DrawPosition(FALSE, boards[currentMove]);
8920 DisplayBothClocks();
8921 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
8926 void SendEgtPath(ChessProgramState *cps)
8927 { /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
8928 char buf[MSG_SIZ], name[MSG_SIZ], *p;
8930 if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
8933 char c, *q = name+1, *r, *s;
8935 name[0] = ','; // extract next format name from feature and copy with prefixed ','
8936 while(*p && *p != ',') *q++ = *p++;
8938 if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
8939 strcmp(name, ",nalimov:") == 0 ) {
8940 // take nalimov path from the menu-changeable option first, if it is defined
8941 sprintf(buf, "egtpath nalimov %s\n", appData.defaultPathEGTB);
8942 SendToProgram(buf,cps); // send egtbpath command for nalimov
8944 if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
8945 (s = StrStr(appData.egtFormats, name)) != NULL) {
8946 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
8947 s = r = StrStr(s, ":") + 1; // beginning of path info
8948 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
8949 c = *r; *r = 0; // temporarily null-terminate path info
8950 *--q = 0; // strip of trailig ':' from name
8951 sprintf(buf, "egtpath %s %s\n", name+1, s);
8953 SendToProgram(buf,cps); // send egtbpath command for this format
8955 if(*p == ',') p++; // read away comma to position for next format name
8960 InitChessProgram(cps, setup)
8961 ChessProgramState *cps;
8962 int setup; /* [HGM] needed to setup FRC opening position */
8964 char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
8965 if (appData.noChessProgram) return;
8966 hintRequested = FALSE;
8967 bookRequested = FALSE;
8969 /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
8970 /* moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
8971 if(cps->memSize) { /* [HGM] memory */
8972 sprintf(buf, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
8973 SendToProgram(buf, cps);
8975 SendEgtPath(cps); /* [HGM] EGT */
8976 if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
8977 sprintf(buf, "cores %d\n", appData.smpCores);
8978 SendToProgram(buf, cps);
8981 SendToProgram(cps->initString, cps);
8982 if (gameInfo.variant != VariantNormal &&
8983 gameInfo.variant != VariantLoadable
8984 /* [HGM] also send variant if board size non-standard */
8985 || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
8987 char *v = VariantName(gameInfo.variant);
8988 if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
8989 /* [HGM] in protocol 1 we have to assume all variants valid */
8990 sprintf(buf, _("Variant %s not supported by %s"), v, cps->tidy);
8991 DisplayFatalError(buf, 0, 1);
8995 /* [HGM] make prefix for non-standard board size. Awkward testing... */
8996 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8997 if( gameInfo.variant == VariantXiangqi )
8998 overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
8999 if( gameInfo.variant == VariantShogi )
9000 overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
9001 if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
9002 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
9003 if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
9004 gameInfo.variant == VariantGothic || gameInfo.variant == VariantFalcon )
9005 overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9006 if( gameInfo.variant == VariantCourier )
9007 overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9008 if( gameInfo.variant == VariantSuper )
9009 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9010 if( gameInfo.variant == VariantGreat )
9011 overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9014 sprintf(b, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
9015 gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
9016 /* [HGM] varsize: try first if this defiant size variant is specifically known */
9017 if(StrStr(cps->variants, b) == NULL) {
9018 // specific sized variant not known, check if general sizing allowed
9019 if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
9020 if(StrStr(cps->variants, "boardsize") == NULL) {
9021 sprintf(buf, "Board size %dx%d+%d not supported by %s",
9022 gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
9023 DisplayFatalError(buf, 0, 1);
9026 /* [HGM] here we really should compare with the maximum supported board size */
9029 } else sprintf(b, "%s", VariantName(gameInfo.variant));
9030 sprintf(buf, "variant %s\n", b);
9031 SendToProgram(buf, cps);
9033 currentlyInitializedVariant = gameInfo.variant;
9035 /* [HGM] send opening position in FRC to first engine */
9037 SendToProgram("force\n", cps);
9039 /* engine is now in force mode! Set flag to wake it up after first move. */
9040 setboardSpoiledMachineBlack = 1;
9044 snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
9045 SendToProgram(buf, cps);
9047 cps->maybeThinking = FALSE;
9048 cps->offeredDraw = 0;
9049 if (!appData.icsActive) {
9050 SendTimeControl(cps, movesPerSession, timeControl,
9051 timeIncrement, appData.searchDepth,
9054 if (appData.showThinking
9055 // [HGM] thinking: four options require thinking output to be sent
9056 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
9058 SendToProgram("post\n", cps);
9060 SendToProgram("hard\n", cps);
9061 if (!appData.ponderNextMove) {
9062 /* Warning: "easy" is a toggle in GNU Chess, so don't send
9063 it without being sure what state we are in first. "hard"
9064 is not a toggle, so that one is OK.
9066 SendToProgram("easy\n", cps);
9069 sprintf(buf, "ping %d\n", ++cps->lastPing);
9070 SendToProgram(buf, cps);
9072 cps->initDone = TRUE;
9077 StartChessProgram(cps)
9078 ChessProgramState *cps;
9083 if (appData.noChessProgram) return;
9084 cps->initDone = FALSE;
9086 if (strcmp(cps->host, "localhost") == 0) {
9087 err = StartChildProcess(cps->program, cps->dir, &cps->pr);
9088 } else if (*appData.remoteShell == NULLCHAR) {
9089 err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
9091 if (*appData.remoteUser == NULLCHAR) {
9092 snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
9095 snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
9096 cps->host, appData.remoteUser, cps->program);
9098 err = StartChildProcess(buf, "", &cps->pr);
9102 sprintf(buf, _("Startup failure on '%s'"), cps->program);
9103 DisplayFatalError(buf, err, 1);
9109 cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
9110 if (cps->protocolVersion > 1) {
9111 sprintf(buf, "xboard\nprotover %d\n", cps->protocolVersion);
9112 cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
9113 cps->comboCnt = 0; // and values of combo boxes
9114 SendToProgram(buf, cps);
9116 SendToProgram("xboard\n", cps);
9122 TwoMachinesEventIfReady P((void))
9124 if (first.lastPing != first.lastPong) {
9125 DisplayMessage("", _("Waiting for first chess program"));
9126 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9129 if (second.lastPing != second.lastPong) {
9130 DisplayMessage("", _("Waiting for second chess program"));
9131 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9139 NextMatchGame P((void))
9141 int index; /* [HGM] autoinc: step load index during match */
9143 if (*appData.loadGameFile != NULLCHAR) {
9144 index = appData.loadGameIndex;
9145 if(index < 0) { // [HGM] autoinc
9146 lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
9147 if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
9149 LoadGameFromFile(appData.loadGameFile,
9151 appData.loadGameFile, FALSE);
9152 } else if (*appData.loadPositionFile != NULLCHAR) {
9153 index = appData.loadPositionIndex;
9154 if(index < 0) { // [HGM] autoinc
9155 lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
9156 if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
9158 LoadPositionFromFile(appData.loadPositionFile,
9160 appData.loadPositionFile);
9162 TwoMachinesEventIfReady();
9165 void UserAdjudicationEvent( int result )
9167 ChessMove gameResult = GameIsDrawn;
9170 gameResult = WhiteWins;
9172 else if( result < 0 ) {
9173 gameResult = BlackWins;
9176 if( gameMode == TwoMachinesPlay ) {
9177 GameEnds( gameResult, "User adjudication", GE_XBOARD );
9182 // [HGM] save: calculate checksum of game to make games easily identifiable
9183 int StringCheckSum(char *s)
9186 if(s==NULL) return 0;
9187 while(*s) i = i*259 + *s++;
9194 for(i=backwardMostMove; i<forwardMostMove; i++) {
9195 sum += pvInfoList[i].depth;
9196 sum += StringCheckSum(parseList[i]);
9197 sum += StringCheckSum(commentList[i]);
9200 if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
9201 return sum + StringCheckSum(commentList[i]);
9202 } // end of save patch
9205 GameEnds(result, resultDetails, whosays)
9207 char *resultDetails;
9210 GameMode nextGameMode;
9214 if(endingGame) return; /* [HGM] crash: forbid recursion */
9217 if (appData.debugMode) {
9218 fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
9219 result, resultDetails ? resultDetails : "(null)", whosays);
9222 if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
9223 /* If we are playing on ICS, the server decides when the
9224 game is over, but the engine can offer to draw, claim
9228 if (appData.zippyPlay && first.initDone) {
9229 if (result == GameIsDrawn) {
9230 /* In case draw still needs to be claimed */
9231 SendToICS(ics_prefix);
9232 SendToICS("draw\n");
9233 } else if (StrCaseStr(resultDetails, "resign")) {
9234 SendToICS(ics_prefix);
9235 SendToICS("resign\n");
9239 endingGame = 0; /* [HGM] crash */
9243 /* If we're loading the game from a file, stop */
9244 if (whosays == GE_FILE) {
9245 (void) StopLoadGameTimer();
9249 /* Cancel draw offers */
9250 first.offeredDraw = second.offeredDraw = 0;
9252 /* If this is an ICS game, only ICS can really say it's done;
9253 if not, anyone can. */
9254 isIcsGame = (gameMode == IcsPlayingWhite ||
9255 gameMode == IcsPlayingBlack ||
9256 gameMode == IcsObserving ||
9257 gameMode == IcsExamining);
9259 if (!isIcsGame || whosays == GE_ICS) {
9260 /* OK -- not an ICS game, or ICS said it was done */
9262 if (!isIcsGame && !appData.noChessProgram)
9263 SetUserThinkingEnables();
9265 /* [HGM] if a machine claims the game end we verify this claim */
9266 if(gameMode == TwoMachinesPlay && appData.testClaims) {
9267 if(appData.testLegality && whosays >= GE_ENGINE1 ) {
9269 ChessMove trueResult = (ChessMove) -1;
9271 claimer = whosays == GE_ENGINE1 ? /* color of claimer */
9272 first.twoMachinesColor[0] :
9273 second.twoMachinesColor[0] ;
9275 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
9276 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
9277 /* [HGM] verify: engine mate claims accepted if they were flagged */
9278 trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
9280 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
9281 /* [HGM] verify: engine mate claims accepted if they were flagged */
9282 trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
9284 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
9285 trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
9288 // now verify win claims, but not in drop games, as we don't understand those yet
9289 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
9290 || gameInfo.variant == VariantGreat) &&
9291 (result == WhiteWins && claimer == 'w' ||
9292 result == BlackWins && claimer == 'b' ) ) { // case to verify: engine claims own win
9293 if (appData.debugMode) {
9294 fprintf(debugFP, "result=%d sp=%d move=%d\n",
9295 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
9297 if(result != trueResult) {
9298 sprintf(buf, "False win claim: '%s'", resultDetails);
9299 result = claimer == 'w' ? BlackWins : WhiteWins;
9300 resultDetails = buf;
9303 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
9304 && (forwardMostMove <= backwardMostMove ||
9305 (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
9306 (claimer=='b')==(forwardMostMove&1))
9308 /* [HGM] verify: draws that were not flagged are false claims */
9309 sprintf(buf, "False draw claim: '%s'", resultDetails);
9310 result = claimer == 'w' ? BlackWins : WhiteWins;
9311 resultDetails = buf;
9313 /* (Claiming a loss is accepted no questions asked!) */
9316 /* [HGM] bare: don't allow bare King to win */
9317 if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
9318 && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
9319 && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
9320 && result != GameIsDrawn)
9321 { int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
9322 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
9323 int p = (signed char)boards[forwardMostMove][i][j] - color;
9324 if(p >= 0 && p <= (int)WhiteKing) k++;
9326 if (appData.debugMode) {
9327 fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
9328 result, resultDetails ? resultDetails : "(null)", whosays, k, color);
9331 result = GameIsDrawn;
9332 sprintf(buf, "%s but bare king", resultDetails);
9333 resultDetails = buf;
9338 if(serverMoves != NULL && !loadFlag) { char c = '=';
9339 if(result==WhiteWins) c = '+';
9340 if(result==BlackWins) c = '-';
9341 if(resultDetails != NULL)
9342 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
9344 if (resultDetails != NULL) {
9345 gameInfo.result = result;
9346 gameInfo.resultDetails = StrSave(resultDetails);
9348 /* display last move only if game was not loaded from file */
9349 if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
9350 DisplayMove(currentMove - 1);
9352 if (forwardMostMove != 0) {
9353 if (gameMode != PlayFromGameFile && gameMode != EditGame
9354 && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
9356 if (*appData.saveGameFile != NULLCHAR) {
9357 SaveGameToFile(appData.saveGameFile, TRUE);
9358 } else if (appData.autoSaveGames) {
9361 if (*appData.savePositionFile != NULLCHAR) {
9362 SavePositionToFile(appData.savePositionFile);
9367 /* Tell program how game ended in case it is learning */
9368 /* [HGM] Moved this to after saving the PGN, just in case */
9369 /* engine died and we got here through time loss. In that */
9370 /* case we will get a fatal error writing the pipe, which */
9371 /* would otherwise lose us the PGN. */
9372 /* [HGM] crash: not needed anymore, but doesn't hurt; */
9373 /* output during GameEnds should never be fatal anymore */
9374 if (gameMode == MachinePlaysWhite ||
9375 gameMode == MachinePlaysBlack ||
9376 gameMode == TwoMachinesPlay ||
9377 gameMode == IcsPlayingWhite ||
9378 gameMode == IcsPlayingBlack ||
9379 gameMode == BeginningOfGame) {
9381 sprintf(buf, "result %s {%s}\n", PGNResult(result),
9383 if (first.pr != NoProc) {
9384 SendToProgram(buf, &first);
9386 if (second.pr != NoProc &&
9387 gameMode == TwoMachinesPlay) {
9388 SendToProgram(buf, &second);
9393 if (appData.icsActive) {
9394 if (appData.quietPlay &&
9395 (gameMode == IcsPlayingWhite ||
9396 gameMode == IcsPlayingBlack)) {
9397 SendToICS(ics_prefix);
9398 SendToICS("set shout 1\n");
9400 nextGameMode = IcsIdle;
9401 ics_user_moved = FALSE;
9402 /* clean up premove. It's ugly when the game has ended and the
9403 * premove highlights are still on the board.
9407 ClearPremoveHighlights();
9408 DrawPosition(FALSE, boards[currentMove]);
9410 if (whosays == GE_ICS) {
9413 if (gameMode == IcsPlayingWhite)
9415 else if(gameMode == IcsPlayingBlack)
9419 if (gameMode == IcsPlayingBlack)
9421 else if(gameMode == IcsPlayingWhite)
9428 PlayIcsUnfinishedSound();
9431 } else if (gameMode == EditGame ||
9432 gameMode == PlayFromGameFile ||
9433 gameMode == AnalyzeMode ||
9434 gameMode == AnalyzeFile) {
9435 nextGameMode = gameMode;
9437 nextGameMode = EndOfGame;
9442 nextGameMode = gameMode;
9445 if (appData.noChessProgram) {
9446 gameMode = nextGameMode;
9448 endingGame = 0; /* [HGM] crash */
9453 /* Put first chess program into idle state */
9454 if (first.pr != NoProc &&
9455 (gameMode == MachinePlaysWhite ||
9456 gameMode == MachinePlaysBlack ||
9457 gameMode == TwoMachinesPlay ||
9458 gameMode == IcsPlayingWhite ||
9459 gameMode == IcsPlayingBlack ||
9460 gameMode == BeginningOfGame)) {
9461 SendToProgram("force\n", &first);
9462 if (first.usePing) {
9464 sprintf(buf, "ping %d\n", ++first.lastPing);
9465 SendToProgram(buf, &first);
9468 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
9469 /* Kill off first chess program */
9470 if (first.isr != NULL)
9471 RemoveInputSource(first.isr);
9474 if (first.pr != NoProc) {
9476 DoSleep( appData.delayBeforeQuit );
9477 SendToProgram("quit\n", &first);
9478 DoSleep( appData.delayAfterQuit );
9479 DestroyChildProcess(first.pr, first.useSigterm);
9484 /* Put second chess program into idle state */
9485 if (second.pr != NoProc &&
9486 gameMode == TwoMachinesPlay) {
9487 SendToProgram("force\n", &second);
9488 if (second.usePing) {
9490 sprintf(buf, "ping %d\n", ++second.lastPing);
9491 SendToProgram(buf, &second);
9494 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
9495 /* Kill off second chess program */
9496 if (second.isr != NULL)
9497 RemoveInputSource(second.isr);
9500 if (second.pr != NoProc) {
9501 DoSleep( appData.delayBeforeQuit );
9502 SendToProgram("quit\n", &second);
9503 DoSleep( appData.delayAfterQuit );
9504 DestroyChildProcess(second.pr, second.useSigterm);
9509 if (matchMode && gameMode == TwoMachinesPlay) {
9512 if (first.twoMachinesColor[0] == 'w') {
9519 if (first.twoMachinesColor[0] == 'b') {
9528 if (matchGame < appData.matchGames) {
9530 if(appData.sameColorGames <= 1) { /* [HGM] alternate: suppress color swap */
9531 tmp = first.twoMachinesColor;
9532 first.twoMachinesColor = second.twoMachinesColor;
9533 second.twoMachinesColor = tmp;
9535 gameMode = nextGameMode;
9537 if(appData.matchPause>10000 || appData.matchPause<10)
9538 appData.matchPause = 10000; /* [HGM] make pause adjustable */
9539 ScheduleDelayedEvent(NextMatchGame, appData.matchPause);
9540 endingGame = 0; /* [HGM] crash */
9544 gameMode = nextGameMode;
9545 sprintf(buf, _("Match %s vs. %s: final score %d-%d-%d"),
9546 first.tidy, second.tidy,
9547 first.matchWins, second.matchWins,
9548 appData.matchGames - (first.matchWins + second.matchWins));
9549 DisplayFatalError(buf, 0, 0);
9552 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
9553 !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
9555 gameMode = nextGameMode;
9557 endingGame = 0; /* [HGM] crash */
9560 /* Assumes program was just initialized (initString sent).
9561 Leaves program in force mode. */
9563 FeedMovesToProgram(cps, upto)
9564 ChessProgramState *cps;
9569 if (appData.debugMode)
9570 fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
9571 startedFromSetupPosition ? "position and " : "",
9572 backwardMostMove, upto, cps->which);
9573 if(currentlyInitializedVariant != gameInfo.variant) { char buf[MSG_SIZ];
9574 // [HGM] variantswitch: make engine aware of new variant
9575 if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
9576 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
9577 sprintf(buf, "variant %s\n", VariantName(gameInfo.variant));
9578 SendToProgram(buf, cps);
9579 currentlyInitializedVariant = gameInfo.variant;
9581 SendToProgram("force\n", cps);
9582 if (startedFromSetupPosition) {
9583 SendBoard(cps, backwardMostMove);
9584 if (appData.debugMode) {
9585 fprintf(debugFP, "feedMoves\n");
9588 for (i = backwardMostMove; i < upto; i++) {
9589 SendMoveToProgram(i, cps);
9595 ResurrectChessProgram()
9597 /* The chess program may have exited.
9598 If so, restart it and feed it all the moves made so far. */
9600 if (appData.noChessProgram || first.pr != NoProc) return;
9602 StartChessProgram(&first);
9603 InitChessProgram(&first, FALSE);
9604 FeedMovesToProgram(&first, currentMove);
9606 if (!first.sendTime) {
9607 /* can't tell gnuchess what its clock should read,
9608 so we bow to its notion. */
9610 timeRemaining[0][currentMove] = whiteTimeRemaining;
9611 timeRemaining[1][currentMove] = blackTimeRemaining;
9614 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
9615 appData.icsEngineAnalyze) && first.analysisSupport) {
9616 SendToProgram("analyze\n", &first);
9617 first.analyzing = TRUE;
9630 if (appData.debugMode) {
9631 fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
9632 redraw, init, gameMode);
9634 CleanupTail(); // [HGM] vari: delete any stored variations
9635 pausing = pauseExamInvalid = FALSE;
9636 startedFromSetupPosition = blackPlaysFirst = FALSE;
9638 whiteFlag = blackFlag = FALSE;
9639 userOfferedDraw = FALSE;
9640 hintRequested = bookRequested = FALSE;
9641 first.maybeThinking = FALSE;
9642 second.maybeThinking = FALSE;
9643 first.bookSuspend = FALSE; // [HGM] book
9644 second.bookSuspend = FALSE;
9645 thinkOutput[0] = NULLCHAR;
9646 lastHint[0] = NULLCHAR;
9647 ClearGameInfo(&gameInfo);
9648 gameInfo.variant = StringToVariant(appData.variant);
9649 ics_user_moved = ics_clock_paused = FALSE;
9650 ics_getting_history = H_FALSE;
9652 white_holding[0] = black_holding[0] = NULLCHAR;
9653 ClearProgramStats();
9654 opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
9658 flipView = appData.flipView;
9659 ClearPremoveHighlights();
9661 alarmSounded = FALSE;
9663 GameEnds((ChessMove) 0, NULL, GE_PLAYER);
9664 if(appData.serverMovesName != NULL) {
9665 /* [HGM] prepare to make moves file for broadcasting */
9666 clock_t t = clock();
9667 if(serverMoves != NULL) fclose(serverMoves);
9668 serverMoves = fopen(appData.serverMovesName, "r");
9669 if(serverMoves != NULL) {
9670 fclose(serverMoves);
9671 /* delay 15 sec before overwriting, so all clients can see end */
9672 while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
9674 serverMoves = fopen(appData.serverMovesName, "w");
9678 gameMode = BeginningOfGame;
9681 if(appData.icsActive) gameInfo.variant = VariantNormal;
9682 currentMove = forwardMostMove = backwardMostMove = 0;
9683 InitPosition(redraw);
9684 for (i = 0; i < MAX_MOVES; i++) {
9685 if (commentList[i] != NULL) {
9686 free(commentList[i]);
9687 commentList[i] = NULL;
9692 timeRemaining[0][0] = whiteTimeRemaining;
9693 timeRemaining[1][0] = blackTimeRemaining;
9694 if (first.pr == NULL) {
9695 StartChessProgram(&first);
9698 InitChessProgram(&first, startedFromSetupPosition);
9702 DisplayMessage("", "");
9703 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
9704 lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
9712 if (!AutoPlayOneMove())
9714 if (matchMode || appData.timeDelay == 0)
9716 if (appData.timeDelay < 0 || gameMode == AnalyzeFile)
9718 StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
9727 int fromX, fromY, toX, toY;
9729 if (appData.debugMode) {
9730 fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
9733 if (gameMode != PlayFromGameFile)
9736 if (currentMove >= forwardMostMove) {
9737 gameMode = EditGame;
9740 /* [AS] Clear current move marker at the end of a game */
9741 /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
9746 toX = moveList[currentMove][2] - AAA;
9747 toY = moveList[currentMove][3] - ONE;
9749 if (moveList[currentMove][1] == '@') {
9750 if (appData.highlightLastMove) {
9751 SetHighlights(-1, -1, toX, toY);
9754 fromX = moveList[currentMove][0] - AAA;
9755 fromY = moveList[currentMove][1] - ONE;
9757 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
9759 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
9761 if (appData.highlightLastMove) {
9762 SetHighlights(fromX, fromY, toX, toY);
9765 DisplayMove(currentMove);
9766 SendMoveToProgram(currentMove++, &first);
9767 DisplayBothClocks();
9768 DrawPosition(FALSE, boards[currentMove]);
9769 // [HGM] PV info: always display, routine tests if empty
9770 DisplayComment(currentMove - 1, commentList[currentMove]);
9776 LoadGameOneMove(readAhead)
9777 ChessMove readAhead;
9779 int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
9780 char promoChar = NULLCHAR;
9785 if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
9786 gameMode != AnalyzeMode && gameMode != Training) {
9791 yyboardindex = forwardMostMove;
9792 if (readAhead != (ChessMove)0) {
9793 moveType = readAhead;
9795 if (gameFileFP == NULL)
9797 moveType = (ChessMove) yylex();
9803 if (appData.debugMode)
9804 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9807 /* append the comment but don't display it */
9808 AppendComment(currentMove, p, FALSE);
9811 case WhiteCapturesEnPassant:
9812 case BlackCapturesEnPassant:
9813 case WhitePromotionChancellor:
9814 case BlackPromotionChancellor:
9815 case WhitePromotionArchbishop:
9816 case BlackPromotionArchbishop:
9817 case WhitePromotionCentaur:
9818 case BlackPromotionCentaur:
9819 case WhitePromotionQueen:
9820 case BlackPromotionQueen:
9821 case WhitePromotionRook:
9822 case BlackPromotionRook:
9823 case WhitePromotionBishop:
9824 case BlackPromotionBishop:
9825 case WhitePromotionKnight:
9826 case BlackPromotionKnight:
9827 case WhitePromotionKing:
9828 case BlackPromotionKing:
9830 case WhiteKingSideCastle:
9831 case WhiteQueenSideCastle:
9832 case BlackKingSideCastle:
9833 case BlackQueenSideCastle:
9834 case WhiteKingSideCastleWild:
9835 case WhiteQueenSideCastleWild:
9836 case BlackKingSideCastleWild:
9837 case BlackQueenSideCastleWild:
9839 case WhiteHSideCastleFR:
9840 case WhiteASideCastleFR:
9841 case BlackHSideCastleFR:
9842 case BlackASideCastleFR:
9844 if (appData.debugMode)
9845 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
9846 fromX = currentMoveString[0] - AAA;
9847 fromY = currentMoveString[1] - ONE;
9848 toX = currentMoveString[2] - AAA;
9849 toY = currentMoveString[3] - ONE;
9850 promoChar = currentMoveString[4];
9855 if (appData.debugMode)
9856 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
9857 fromX = moveType == WhiteDrop ?
9858 (int) CharToPiece(ToUpper(currentMoveString[0])) :
9859 (int) CharToPiece(ToLower(currentMoveString[0]));
9861 toX = currentMoveString[2] - AAA;
9862 toY = currentMoveString[3] - ONE;
9868 case GameUnfinished:
9869 if (appData.debugMode)
9870 fprintf(debugFP, "Parsed game end: %s\n", yy_text);
9871 p = strchr(yy_text, '{');
9872 if (p == NULL) p = strchr(yy_text, '(');
9875 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
9877 q = strchr(p, *p == '{' ? '}' : ')');
9878 if (q != NULL) *q = NULLCHAR;
9881 GameEnds(moveType, p, GE_FILE);
9883 if (cmailMsgLoaded) {
9885 flipView = WhiteOnMove(currentMove);
9886 if (moveType == GameUnfinished) flipView = !flipView;
9887 if (appData.debugMode)
9888 fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
9892 case (ChessMove) 0: /* end of file */
9893 if (appData.debugMode)
9894 fprintf(debugFP, "Parser hit end of file\n");
9895 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9901 if (WhiteOnMove(currentMove)) {
9902 GameEnds(BlackWins, "Black mates", GE_FILE);
9904 GameEnds(WhiteWins, "White mates", GE_FILE);
9908 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
9915 if (lastLoadGameStart == GNUChessGame) {
9916 /* GNUChessGames have numbers, but they aren't move numbers */
9917 if (appData.debugMode)
9918 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
9919 yy_text, (int) moveType);
9920 return LoadGameOneMove((ChessMove)0); /* tail recursion */
9922 /* else fall thru */
9927 /* Reached start of next game in file */
9928 if (appData.debugMode)
9929 fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
9930 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9936 if (WhiteOnMove(currentMove)) {
9937 GameEnds(BlackWins, "Black mates", GE_FILE);
9939 GameEnds(WhiteWins, "White mates", GE_FILE);
9943 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
9949 case PositionDiagram: /* should not happen; ignore */
9950 case ElapsedTime: /* ignore */
9951 case NAG: /* ignore */
9952 if (appData.debugMode)
9953 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
9954 yy_text, (int) moveType);
9955 return LoadGameOneMove((ChessMove)0); /* tail recursion */
9958 if (appData.testLegality) {
9959 if (appData.debugMode)
9960 fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
9961 sprintf(move, _("Illegal move: %d.%s%s"),
9962 (forwardMostMove / 2) + 1,
9963 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9964 DisplayError(move, 0);
9967 if (appData.debugMode)
9968 fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
9969 yy_text, currentMoveString);
9970 fromX = currentMoveString[0] - AAA;
9971 fromY = currentMoveString[1] - ONE;
9972 toX = currentMoveString[2] - AAA;
9973 toY = currentMoveString[3] - ONE;
9974 promoChar = currentMoveString[4];
9979 if (appData.debugMode)
9980 fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
9981 sprintf(move, _("Ambiguous move: %d.%s%s"),
9982 (forwardMostMove / 2) + 1,
9983 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9984 DisplayError(move, 0);
9989 case ImpossibleMove:
9990 if (appData.debugMode)
9991 fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
9992 sprintf(move, _("Illegal move: %d.%s%s"),
9993 (forwardMostMove / 2) + 1,
9994 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9995 DisplayError(move, 0);
10001 if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
10002 DrawPosition(FALSE, boards[currentMove]);
10003 DisplayBothClocks();
10004 if (!appData.matchMode) // [HGM] PV info: routine tests if empty
10005 DisplayComment(currentMove - 1, commentList[currentMove]);
10007 (void) StopLoadGameTimer();
10009 cmailOldMove = forwardMostMove;
10012 /* currentMoveString is set as a side-effect of yylex */
10013 strcat(currentMoveString, "\n");
10014 strcpy(moveList[forwardMostMove], currentMoveString);
10016 thinkOutput[0] = NULLCHAR;
10017 MakeMove(fromX, fromY, toX, toY, promoChar);
10018 currentMove = forwardMostMove;
10023 /* Load the nth game from the given file */
10025 LoadGameFromFile(filename, n, title, useList)
10029 /*Boolean*/ int useList;
10034 if (strcmp(filename, "-") == 0) {
10038 f = fopen(filename, "rb");
10040 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10041 DisplayError(buf, errno);
10045 if (fseek(f, 0, 0) == -1) {
10046 /* f is not seekable; probably a pipe */
10049 if (useList && n == 0) {
10050 int error = GameListBuild(f);
10052 DisplayError(_("Cannot build game list"), error);
10053 } else if (!ListEmpty(&gameList) &&
10054 ((ListGame *) gameList.tailPred)->number > 1) {
10055 // TODO convert to GTK
10056 // GameListPopUp(f, title);
10063 return LoadGame(f, n, title, FALSE);
10068 MakeRegisteredMove()
10070 int fromX, fromY, toX, toY;
10072 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
10073 switch (cmailMoveType[lastLoadGameNumber - 1]) {
10076 if (appData.debugMode)
10077 fprintf(debugFP, "Restoring %s for game %d\n",
10078 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
10080 thinkOutput[0] = NULLCHAR;
10081 strcpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1]);
10082 fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
10083 fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
10084 toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
10085 toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
10086 promoChar = cmailMove[lastLoadGameNumber - 1][4];
10087 MakeMove(fromX, fromY, toX, toY, promoChar);
10088 ShowMove(fromX, fromY, toX, toY);
10089 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
10096 if (WhiteOnMove(currentMove)) {
10097 GameEnds(BlackWins, "Black mates", GE_PLAYER);
10099 GameEnds(WhiteWins, "White mates", GE_PLAYER);
10104 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
10111 if (WhiteOnMove(currentMove)) {
10112 GameEnds(BlackWins, "White resigns", GE_PLAYER);
10114 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
10119 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
10130 /* Wrapper around LoadGame for use when a Cmail message is loaded */
10132 CmailLoadGame(f, gameNumber, title, useList)
10140 if (gameNumber > nCmailGames) {
10141 DisplayError(_("No more games in this message"), 0);
10144 if (f == lastLoadGameFP) {
10145 int offset = gameNumber - lastLoadGameNumber;
10147 cmailMsg[0] = NULLCHAR;
10148 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
10149 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
10150 nCmailMovesRegistered--;
10152 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
10153 if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
10154 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
10157 if (! RegisterMove()) return FALSE;
10161 retVal = LoadGame(f, gameNumber, title, useList);
10163 /* Make move registered during previous look at this game, if any */
10164 MakeRegisteredMove();
10166 if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
10167 commentList[currentMove]
10168 = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
10169 DisplayComment(currentMove - 1, commentList[currentMove]);
10175 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
10180 int gameNumber = lastLoadGameNumber + offset;
10181 if (lastLoadGameFP == NULL) {
10182 DisplayError(_("No game has been loaded yet"), 0);
10185 if (gameNumber <= 0) {
10186 DisplayError(_("Can't back up any further"), 0);
10189 if (cmailMsgLoaded) {
10190 return CmailLoadGame(lastLoadGameFP, gameNumber,
10191 lastLoadGameTitle, lastLoadGameUseList);
10193 return LoadGame(lastLoadGameFP, gameNumber,
10194 lastLoadGameTitle, lastLoadGameUseList);
10200 /* Load the nth game from open file f */
10202 LoadGame(f, gameNumber, title, useList)
10210 int gn = gameNumber;
10211 ListGame *lg = NULL;
10212 int numPGNTags = 0;
10214 GameMode oldGameMode;
10215 VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
10217 if (appData.debugMode)
10218 fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
10220 if (gameMode == Training )
10221 SetTrainingModeOff();
10223 oldGameMode = gameMode;
10224 if (gameMode != BeginningOfGame)
10226 Reset(FALSE, TRUE);
10230 if (lastLoadGameFP != NULL && lastLoadGameFP != f)
10232 fclose(lastLoadGameFP);
10237 lg = (ListGame *) ListElem(&gameList, gameNumber-1);
10241 fseek(f, lg->offset, 0);
10242 GameListHighlight(gameNumber);
10247 DisplayError(_("Game number out of range"), 0);
10254 if (fseek(f, 0, 0) == -1)
10256 if (f == lastLoadGameFP ?
10257 gameNumber == lastLoadGameNumber + 1 :
10264 DisplayError(_("Can't seek on game file"), 0);
10270 lastLoadGameFP = f;
10271 lastLoadGameNumber = gameNumber;
10272 strcpy(lastLoadGameTitle, title);
10273 lastLoadGameUseList = useList;
10277 if (lg && lg->gameInfo.white && lg->gameInfo.black)
10279 snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
10280 lg->gameInfo.black);
10283 else if (*title != NULLCHAR)
10285 if (gameNumber > 1)
10287 sprintf(buf, "%s %d", title, gameNumber);
10292 DisplayTitle(title);
10296 if (gameMode != AnalyzeFile && gameMode != AnalyzeMode)
10298 gameMode = PlayFromGameFile;
10302 currentMove = forwardMostMove = backwardMostMove = 0;
10303 CopyBoard(boards[0], initialPosition);
10307 * Skip the first gn-1 games in the file.
10308 * Also skip over anything that precedes an identifiable
10309 * start of game marker, to avoid being confused by
10310 * garbage at the start of the file. Currently
10311 * recognized start of game markers are the move number "1",
10312 * the pattern "gnuchess .* game", the pattern
10313 * "^[#;%] [^ ]* game file", and a PGN tag block.
10314 * A game that starts with one of the latter two patterns
10315 * will also have a move number 1, possibly
10316 * following a position diagram.
10317 * 5-4-02: Let's try being more lenient and allowing a game to
10318 * start with an unnumbered move. Does that break anything?
10320 cm = lastLoadGameStart = (ChessMove) 0;
10322 yyboardindex = forwardMostMove;
10323 cm = (ChessMove) yylex();
10325 case (ChessMove) 0:
10326 if (cmailMsgLoaded) {
10327 nCmailGames = CMAIL_MAX_GAMES - gn;
10330 DisplayError(_("Game not found in file"), 0);
10337 lastLoadGameStart = cm;
10340 case MoveNumberOne:
10341 switch (lastLoadGameStart) {
10346 case MoveNumberOne:
10347 case (ChessMove) 0:
10348 gn--; /* count this game */
10349 lastLoadGameStart = cm;
10358 switch (lastLoadGameStart) {
10361 case MoveNumberOne:
10362 case (ChessMove) 0:
10363 gn--; /* count this game */
10364 lastLoadGameStart = cm;
10367 lastLoadGameStart = cm; /* game counted already */
10375 yyboardindex = forwardMostMove;
10376 cm = (ChessMove) yylex();
10377 } while (cm == PGNTag || cm == Comment);
10384 if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
10385 if ( cmailResult[CMAIL_MAX_GAMES - gn - 1]
10386 != CMAIL_OLD_RESULT) {
10388 cmailResult[ CMAIL_MAX_GAMES
10389 - gn - 1] = CMAIL_OLD_RESULT;
10395 /* Only a NormalMove can be at the start of a game
10396 * without a position diagram. */
10397 if (lastLoadGameStart == (ChessMove) 0) {
10399 lastLoadGameStart = MoveNumberOne;
10408 if (appData.debugMode)
10409 fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
10411 if (cm == XBoardGame) {
10412 /* Skip any header junk before position diagram and/or move 1 */
10414 yyboardindex = forwardMostMove;
10415 cm = (ChessMove) yylex();
10417 if (cm == (ChessMove) 0 ||
10418 cm == GNUChessGame || cm == XBoardGame) {
10419 /* Empty game; pretend end-of-file and handle later */
10420 cm = (ChessMove) 0;
10424 if (cm == MoveNumberOne || cm == PositionDiagram ||
10425 cm == PGNTag || cm == Comment)
10428 } else if (cm == GNUChessGame) {
10429 if (gameInfo.event != NULL) {
10430 free(gameInfo.event);
10432 gameInfo.event = StrSave(yy_text);
10435 startedFromSetupPosition = FALSE;
10436 while (cm == PGNTag) {
10437 if (appData.debugMode)
10438 fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
10439 err = ParsePGNTag(yy_text, &gameInfo);
10440 if (!err) numPGNTags++;
10442 /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
10443 if(gameInfo.variant != oldVariant) {
10444 startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
10445 InitPosition(TRUE);
10446 oldVariant = gameInfo.variant;
10447 if (appData.debugMode)
10448 fprintf(debugFP, "New variant %d\n", (int) oldVariant);
10452 if (gameInfo.fen != NULL) {
10453 Board initial_position;
10454 startedFromSetupPosition = TRUE;
10455 if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
10457 DisplayError(_("Bad FEN position in file"), 0);
10460 CopyBoard(boards[0], initial_position);
10461 if (blackPlaysFirst) {
10462 currentMove = forwardMostMove = backwardMostMove = 1;
10463 CopyBoard(boards[1], initial_position);
10464 strcpy(moveList[0], "");
10465 strcpy(parseList[0], "");
10466 timeRemaining[0][1] = whiteTimeRemaining;
10467 timeRemaining[1][1] = blackTimeRemaining;
10468 if (commentList[0] != NULL) {
10469 commentList[1] = commentList[0];
10470 commentList[0] = NULL;
10473 currentMove = forwardMostMove = backwardMostMove = 0;
10475 /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
10477 initialRulePlies = FENrulePlies;
10478 for( i=0; i< nrCastlingRights; i++ )
10479 initialRights[i] = initial_position[CASTLING][i];
10481 yyboardindex = forwardMostMove;
10482 free(gameInfo.fen);
10483 gameInfo.fen = NULL;
10486 yyboardindex = forwardMostMove;
10487 cm = (ChessMove) yylex();
10489 /* Handle comments interspersed among the tags */
10490 while (cm == Comment) {
10492 if (appData.debugMode)
10493 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10495 AppendComment(currentMove, p, FALSE);
10496 yyboardindex = forwardMostMove;
10497 cm = (ChessMove) yylex();
10501 /* don't rely on existence of Event tag since if game was
10502 * pasted from clipboard the Event tag may not exist
10504 if (numPGNTags > 0){
10506 if (gameInfo.variant == VariantNormal) {
10507 gameInfo.variant = StringToVariant(gameInfo.event);
10510 if( appData.autoDisplayTags ) {
10511 tags = PGNTags(&gameInfo);
10512 TagsPopUp(tags, CmailMsg());
10517 /* Make something up, but don't display it now */
10522 if (cm == PositionDiagram) {
10525 Board initial_position;
10527 if (appData.debugMode)
10528 fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
10530 if (!startedFromSetupPosition) {
10532 for (i = BOARD_HEIGHT - 1; i >= 0; i--)
10533 for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
10543 initial_position[i][j++] = CharToPiece(*p);
10546 while (*p == ' ' || *p == '\t' ||
10547 *p == '\n' || *p == '\r') p++;
10549 if (strncmp(p, "black", strlen("black"))==0)
10550 blackPlaysFirst = TRUE;
10552 blackPlaysFirst = FALSE;
10553 startedFromSetupPosition = TRUE;
10555 CopyBoard(boards[0], initial_position);
10556 if (blackPlaysFirst) {
10557 currentMove = forwardMostMove = backwardMostMove = 1;
10558 CopyBoard(boards[1], initial_position);
10559 strcpy(moveList[0], "");
10560 strcpy(parseList[0], "");
10561 timeRemaining[0][1] = whiteTimeRemaining;
10562 timeRemaining[1][1] = blackTimeRemaining;
10563 if (commentList[0] != NULL) {
10564 commentList[1] = commentList[0];
10565 commentList[0] = NULL;
10568 currentMove = forwardMostMove = backwardMostMove = 0;
10571 yyboardindex = forwardMostMove;
10572 cm = (ChessMove) yylex();
10575 if (first.pr == NoProc) {
10576 StartChessProgram(&first);
10578 InitChessProgram(&first, FALSE);
10579 SendToProgram("force\n", &first);
10580 if (startedFromSetupPosition) {
10581 SendBoard(&first, forwardMostMove);
10582 if (appData.debugMode) {
10583 fprintf(debugFP, "Load Game\n");
10585 DisplayBothClocks();
10588 /* [HGM] server: flag to write setup moves in broadcast file as one */
10589 loadFlag = appData.suppressLoadMoves;
10591 while (cm == Comment) {
10593 if (appData.debugMode)
10594 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10596 AppendComment(currentMove, p, FALSE);
10597 yyboardindex = forwardMostMove;
10598 cm = (ChessMove) yylex();
10601 if ((cm == (ChessMove) 0 && lastLoadGameStart != (ChessMove) 0) ||
10602 cm == WhiteWins || cm == BlackWins ||
10603 cm == GameIsDrawn || cm == GameUnfinished) {
10604 DisplayMessage("", _("No moves in game"));
10605 if (cmailMsgLoaded) {
10606 if (appData.debugMode)
10607 fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
10611 DrawPosition(FALSE, boards[currentMove]);
10612 DisplayBothClocks();
10613 gameMode = EditGame;
10620 // [HGM] PV info: routine tests if comment empty
10621 if (!matchMode && (pausing || appData.timeDelay != 0)) {
10622 DisplayComment(currentMove - 1, commentList[currentMove]);
10624 if (!matchMode && appData.timeDelay != 0)
10625 DrawPosition(FALSE, boards[currentMove]);
10627 if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
10628 programStats.ok_to_send = 1;
10631 /* if the first token after the PGN tags is a move
10632 * and not move number 1, retrieve it from the parser
10634 if (cm != MoveNumberOne)
10635 LoadGameOneMove(cm);
10637 /* load the remaining moves from the file */
10638 while (LoadGameOneMove((ChessMove)0)) {
10639 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10640 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10643 /* rewind to the start of the game */
10644 currentMove = backwardMostMove;
10646 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10648 if (oldGameMode == AnalyzeFile ||
10649 oldGameMode == AnalyzeMode) {
10650 AnalyzeFileEvent();
10653 if (matchMode || appData.timeDelay == 0) {
10655 gameMode = EditGame;
10657 } else if (appData.timeDelay > 0) {
10658 AutoPlayGameLoop();
10661 if (appData.debugMode)
10662 fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
10664 loadFlag = 0; /* [HGM] true game starts */
10668 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
10670 ReloadPosition(offset)
10673 int positionNumber = lastLoadPositionNumber + offset;
10674 if (lastLoadPositionFP == NULL) {
10675 DisplayError(_("No position has been loaded yet"), 0);
10678 if (positionNumber <= 0) {
10679 DisplayError(_("Can't back up any further"), 0);
10682 return LoadPosition(lastLoadPositionFP, positionNumber,
10683 lastLoadPositionTitle);
10686 /* Load the nth position from the given file */
10688 LoadPositionFromFile(filename, n, title)
10696 if (strcmp(filename, "-") == 0) {
10697 return LoadPosition(stdin, n, "stdin");
10699 f = fopen(filename, "rb");
10701 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10702 DisplayError(buf, errno);
10705 return LoadPosition(f, n, title);
10710 /* Load the nth position from the given open file, and close it */
10712 LoadPosition(f, positionNumber, title)
10714 int positionNumber;
10717 char *p, line[MSG_SIZ];
10718 Board initial_position;
10719 int i, j, fenMode, pn;
10721 if (gameMode == Training )
10722 SetTrainingModeOff();
10724 if (gameMode != BeginningOfGame) {
10725 Reset(FALSE, TRUE);
10727 if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
10728 fclose(lastLoadPositionFP);
10730 if (positionNumber == 0) positionNumber = 1;
10731 lastLoadPositionFP = f;
10732 lastLoadPositionNumber = positionNumber;
10733 strcpy(lastLoadPositionTitle, title);
10734 if (first.pr == NoProc) {
10735 StartChessProgram(&first);
10736 InitChessProgram(&first, FALSE);
10738 pn = positionNumber;
10739 if (positionNumber < 0) {
10740 /* Negative position number means to seek to that byte offset */
10741 if (fseek(f, -positionNumber, 0) == -1) {
10742 DisplayError(_("Can't seek on position file"), 0);
10747 if (fseek(f, 0, 0) == -1) {
10748 if (f == lastLoadPositionFP ?
10749 positionNumber == lastLoadPositionNumber + 1 :
10750 positionNumber == 1) {
10753 DisplayError(_("Can't seek on position file"), 0);
10758 /* See if this file is FEN or old-style xboard */
10759 if (fgets(line, MSG_SIZ, f) == NULL) {
10760 DisplayError(_("Position not found in file"), 0);
10763 // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
10764 fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
10767 if (fenMode || line[0] == '#') pn--;
10769 /* skip positions before number pn */
10770 if (fgets(line, MSG_SIZ, f) == NULL) {
10772 DisplayError(_("Position not found in file"), 0);
10775 if (fenMode || line[0] == '#') pn--;
10780 if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
10781 DisplayError(_("Bad FEN position in file"), 0);
10785 (void) fgets(line, MSG_SIZ, f);
10786 (void) fgets(line, MSG_SIZ, f);
10788 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
10789 (void) fgets(line, MSG_SIZ, f);
10790 for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
10793 initial_position[i][j++] = CharToPiece(*p);
10797 blackPlaysFirst = FALSE;
10799 (void) fgets(line, MSG_SIZ, f);
10800 if (strncmp(line, "black", strlen("black"))==0)
10801 blackPlaysFirst = TRUE;
10804 startedFromSetupPosition = TRUE;
10806 SendToProgram("force\n", &first);
10807 CopyBoard(boards[0], initial_position);
10808 if (blackPlaysFirst) {
10809 currentMove = forwardMostMove = backwardMostMove = 1;
10810 strcpy(moveList[0], "");
10811 strcpy(parseList[0], "");
10812 CopyBoard(boards[1], initial_position);
10813 DisplayMessage("", _("Black to play"));
10815 currentMove = forwardMostMove = backwardMostMove = 0;
10816 DisplayMessage("", _("White to play"));
10818 initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
10819 SendBoard(&first, forwardMostMove);
10820 if (appData.debugMode) {
10822 for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
10823 for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
10824 fprintf(debugFP, "Load Position\n");
10827 if (positionNumber > 1) {
10828 sprintf(line, "%s %d", title, positionNumber);
10829 DisplayTitle(line);
10831 DisplayTitle(title);
10833 gameMode = EditGame;
10836 timeRemaining[0][1] = whiteTimeRemaining;
10837 timeRemaining[1][1] = blackTimeRemaining;
10838 DrawPosition(FALSE, boards[currentMove]);
10845 CopyPlayerNameIntoFileName(dest, src)
10848 while (*src != NULLCHAR && *src != ',') {
10853 *(*dest)++ = *src++;
10858 char *DefaultFileName(ext)
10861 static char def[MSG_SIZ];
10864 if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
10866 CopyPlayerNameIntoFileName(&p, gameInfo.white);
10868 CopyPlayerNameIntoFileName(&p, gameInfo.black);
10877 /* Save the current game to the given file */
10879 SaveGameToFile(filename, append)
10886 if (strcmp(filename, "-") == 0) {
10887 return SaveGame(stdout, 0, NULL);
10889 f = fopen(filename, append ? "a" : "w");
10891 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10892 DisplayError(buf, errno);
10895 return SaveGame(f, 0, NULL);
10904 static char buf[MSG_SIZ];
10907 p = strchr(str, ' ');
10908 if (p == NULL) return str;
10909 strncpy(buf, str, p - str);
10910 buf[p - str] = NULLCHAR;
10914 #define PGN_MAX_LINE 75
10916 #define PGN_SIDE_WHITE 0
10917 #define PGN_SIDE_BLACK 1
10920 static int FindFirstMoveOutOfBook( int side )
10924 if( backwardMostMove == 0 && ! startedFromSetupPosition) {
10925 int index = backwardMostMove;
10926 int has_book_hit = 0;
10928 if( (index % 2) != side ) {
10932 while( index < forwardMostMove ) {
10933 /* Check to see if engine is in book */
10934 int depth = pvInfoList[index].depth;
10935 int score = pvInfoList[index].score;
10941 else if( score == 0 && depth == 63 ) {
10942 in_book = 1; /* Zappa */
10944 else if( score == 2 && depth == 99 ) {
10945 in_book = 1; /* Abrok */
10948 has_book_hit += in_book;
10964 void GetOutOfBookInfo( char * buf )
10968 int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10970 oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
10971 oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
10975 if( oob[0] >= 0 || oob[1] >= 0 ) {
10976 for( i=0; i<2; i++ ) {
10980 if( i > 0 && oob[0] >= 0 ) {
10981 strcat( buf, " " );
10984 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
10985 sprintf( buf+strlen(buf), "%s%.2f",
10986 pvInfoList[idx].score >= 0 ? "+" : "",
10987 pvInfoList[idx].score / 100.0 );
10993 /* Save game in PGN style and close the file */
10998 int i, offset, linelen, newblock;
11002 int movelen, numlen, blank;
11003 char move_buffer[100]; /* [AS] Buffer for move+PV info */
11005 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
11007 tm = time((time_t *) NULL);
11009 PrintPGNTags(f, &gameInfo);
11011 if (backwardMostMove > 0 || startedFromSetupPosition) {
11012 char *fen = PositionToFEN(backwardMostMove, NULL);
11013 fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
11014 fprintf(f, "\n{--------------\n");
11015 PrintPosition(f, backwardMostMove);
11016 fprintf(f, "--------------}\n");
11020 /* [AS] Out of book annotation */
11021 if( appData.saveOutOfBookInfo ) {
11024 GetOutOfBookInfo( buf );
11026 if( buf[0] != '\0' ) {
11027 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
11034 i = backwardMostMove;
11038 while (i < forwardMostMove) {
11039 /* Print comments preceding this move */
11040 if (commentList[i] != NULL) {
11041 if (linelen > 0) fprintf(f, "\n");
11042 fprintf(f, "%s", commentList[i]);
11047 /* Format move number */
11048 if ((i % 2) == 0) {
11049 sprintf(numtext, "%d.", (i - offset)/2 + 1);
11052 sprintf(numtext, "%d...", (i - offset)/2 + 1);
11054 numtext[0] = NULLCHAR;
11057 numlen = strlen(numtext);
11060 /* Print move number */
11061 blank = linelen > 0 && numlen > 0;
11062 if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
11071 fprintf(f, "%s", numtext);
11075 strcpy(move_buffer, SavePart(parseList[i])); // [HGM] pgn: print move via buffer, so it can be edited
11076 movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
11079 blank = linelen > 0 && movelen > 0;
11080 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
11089 fprintf(f, "%s", move_buffer);
11090 linelen += movelen;
11092 /* [AS] Add PV info if present */
11093 if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
11094 /* [HGM] add time */
11095 char buf[MSG_SIZ]; int seconds;
11097 seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
11099 if( seconds <= 0) buf[0] = 0; else
11100 if( seconds < 30 ) sprintf(buf, " %3.1f%c", seconds/10., 0); else {
11101 seconds = (seconds + 4)/10; // round to full seconds
11102 if( seconds < 60 ) sprintf(buf, " %d%c", seconds, 0); else
11103 sprintf(buf, " %d:%02d%c", seconds/60, seconds%60, 0);
11106 sprintf( move_buffer, "{%s%.2f/%d%s}",
11107 pvInfoList[i].score >= 0 ? "+" : "",
11108 pvInfoList[i].score / 100.0,
11109 pvInfoList[i].depth,
11112 movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
11114 /* Print score/depth */
11115 blank = linelen > 0 && movelen > 0;
11116 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
11125 fprintf(f, "%s", move_buffer);
11126 linelen += movelen;
11132 /* Start a new line */
11133 if (linelen > 0) fprintf(f, "\n");
11135 /* Print comments after last move */
11136 if (commentList[i] != NULL) {
11137 fprintf(f, "%s\n", commentList[i]);
11141 if (gameInfo.resultDetails != NULL &&
11142 gameInfo.resultDetails[0] != NULLCHAR) {
11143 fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
11144 PGNResult(gameInfo.result));
11146 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
11150 lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
11154 /* Save game in old style and close the file */
11156 SaveGameOldStyle(f)
11162 tm = time((time_t *) NULL);
11164 fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
11167 if (backwardMostMove > 0 || startedFromSetupPosition) {
11168 fprintf(f, "\n[--------------\n");
11169 PrintPosition(f, backwardMostMove);
11170 fprintf(f, "--------------]\n");
11175 i = backwardMostMove;
11176 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
11178 while (i < forwardMostMove) {
11179 if (commentList[i] != NULL) {
11180 fprintf(f, "[%s]\n", commentList[i]);
11183 if ((i % 2) == 1) {
11184 fprintf(f, "%d. ... %s\n", (i - offset)/2 + 1, parseList[i]);
11187 fprintf(f, "%d. %s ", (i - offset)/2 + 1, parseList[i]);
11189 if (commentList[i] != NULL) {
11193 if (i >= forwardMostMove) {
11197 fprintf(f, "%s\n", parseList[i]);
11202 if (commentList[i] != NULL) {
11203 fprintf(f, "[%s]\n", commentList[i]);
11206 /* This isn't really the old style, but it's close enough */
11207 if (gameInfo.resultDetails != NULL &&
11208 gameInfo.resultDetails[0] != NULLCHAR) {
11209 fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
11210 gameInfo.resultDetails);
11212 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
11219 /* Save the current game to open file f and close the file */
11221 SaveGame(f, dummy, dummy2)
11226 if (gameMode == EditPosition) EditPositionDone(TRUE);
11227 lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
11228 if (appData.oldSaveStyle)
11229 return SaveGameOldStyle(f);
11231 return SaveGamePGN(f);
11234 /* Save the current position to the given file */
11236 SavePositionToFile(filename)
11242 if (strcmp(filename, "-") == 0) {
11243 return SavePosition(stdout, 0, NULL);
11245 f = fopen(filename, "a");
11247 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
11248 DisplayError(buf, errno);
11251 SavePosition(f, 0, NULL);
11257 /* Save the current position to the given open file and close the file */
11259 SavePosition(f, dummy, dummy2)
11266 if (gameMode == EditPosition) EditPositionDone(TRUE);
11267 if (appData.oldSaveStyle) {
11268 tm = time((time_t *) NULL);
11270 fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
11272 fprintf(f, "[--------------\n");
11273 PrintPosition(f, currentMove);
11274 fprintf(f, "--------------]\n");
11276 fen = PositionToFEN(currentMove, NULL);
11277 fprintf(f, "%s\n", fen);
11285 ReloadCmailMsgEvent(unregister)
11289 static char *inFilename = NULL;
11290 static char *outFilename;
11292 struct stat inbuf, outbuf;
11295 /* Any registered moves are unregistered if unregister is set, */
11296 /* i.e. invoked by the signal handler */
11298 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
11299 cmailMoveRegistered[i] = FALSE;
11300 if (cmailCommentList[i] != NULL) {
11301 free(cmailCommentList[i]);
11302 cmailCommentList[i] = NULL;
11305 nCmailMovesRegistered = 0;
11308 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
11309 cmailResult[i] = CMAIL_NOT_RESULT;
11313 if (inFilename == NULL) {
11314 /* Because the filenames are static they only get malloced once */
11315 /* and they never get freed */
11316 inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
11317 sprintf(inFilename, "%s.game.in", appData.cmailGameName);
11319 outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
11320 sprintf(outFilename, "%s.out", appData.cmailGameName);
11323 status = stat(outFilename, &outbuf);
11325 cmailMailedMove = FALSE;
11327 status = stat(inFilename, &inbuf);
11328 cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
11331 /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
11332 counts the games, notes how each one terminated, etc.
11334 It would be nice to remove this kludge and instead gather all
11335 the information while building the game list. (And to keep it
11336 in the game list nodes instead of having a bunch of fixed-size
11337 parallel arrays.) Note this will require getting each game's
11338 termination from the PGN tags, as the game list builder does
11339 not process the game moves. --mann
11341 cmailMsgLoaded = TRUE;
11342 LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
11344 /* Load first game in the file or popup game menu */
11345 LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
11347 #endif /* !WIN32 */
11355 char string[MSG_SIZ];
11357 if ( cmailMailedMove
11358 || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
11359 return TRUE; /* Allow free viewing */
11362 /* Unregister move to ensure that we don't leave RegisterMove */
11363 /* with the move registered when the conditions for registering no */
11365 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11366 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
11367 nCmailMovesRegistered --;
11369 if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
11371 free(cmailCommentList[lastLoadGameNumber - 1]);
11372 cmailCommentList[lastLoadGameNumber - 1] = NULL;
11376 if (cmailOldMove == -1) {
11377 DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
11381 if (currentMove > cmailOldMove + 1) {
11382 DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
11386 if (currentMove < cmailOldMove) {
11387 DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
11391 if (forwardMostMove > currentMove) {
11392 /* Silently truncate extra moves */
11396 if ( (currentMove == cmailOldMove + 1)
11397 || ( (currentMove == cmailOldMove)
11398 && ( (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
11399 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
11400 if (gameInfo.result != GameUnfinished) {
11401 cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
11404 if (commentList[currentMove] != NULL) {
11405 cmailCommentList[lastLoadGameNumber - 1]
11406 = StrSave(commentList[currentMove]);
11408 strcpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1]);
11410 if (appData.debugMode)
11411 fprintf(debugFP, "Saving %s for game %d\n",
11412 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
11415 "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
11417 f = fopen(string, "w");
11418 if (appData.oldSaveStyle) {
11419 SaveGameOldStyle(f); /* also closes the file */
11421 sprintf(string, "%s.pos.out", appData.cmailGameName);
11422 f = fopen(string, "w");
11423 SavePosition(f, 0, NULL); /* also closes the file */
11425 fprintf(f, "{--------------\n");
11426 PrintPosition(f, currentMove);
11427 fprintf(f, "--------------}\n\n");
11429 SaveGame(f, 0, NULL); /* also closes the file*/
11432 cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
11433 nCmailMovesRegistered ++;
11434 } else if (nCmailGames == 1) {
11435 DisplayError(_("You have not made a move yet"), 0);
11446 static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
11447 FILE *commandOutput;
11448 char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
11449 int nBytes = 0; /* Suppress warnings on uninitialized variables */
11455 if (! cmailMsgLoaded) {
11456 DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
11460 if (nCmailGames == nCmailResults) {
11461 DisplayError(_("No unfinished games"), 0);
11465 #if CMAIL_PROHIBIT_REMAIL
11466 if (cmailMailedMove) {
11467 sprintf(msg, _("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);
11468 DisplayError(msg, 0);
11473 if (! (cmailMailedMove || RegisterMove())) return;
11475 if ( cmailMailedMove
11476 || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
11477 sprintf(string, partCommandString,
11478 appData.debugMode ? " -v" : "", appData.cmailGameName);
11479 commandOutput = popen(string, "r");
11481 if (commandOutput == NULL) {
11482 DisplayError(_("Failed to invoke cmail"), 0);
11484 for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
11485 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
11487 if (nBuffers > 1) {
11488 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
11489 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
11490 nBytes = MSG_SIZ - 1;
11492 (void) memcpy(msg, buffer, nBytes);
11494 *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
11496 if(StrStr(msg, "Mailed cmail message to ") != NULL) {
11497 cmailMailedMove = TRUE; /* Prevent >1 moves */
11500 for (i = 0; i < nCmailGames; i ++) {
11501 if (cmailResult[i] == CMAIL_NOT_RESULT) {
11506 && ( (arcDir = (char *) getenv("CMAIL_ARCDIR"))
11508 sprintf(buffer, "%s/%s.%s.archive",
11510 appData.cmailGameName,
11512 LoadGameFromFile(buffer, 1, buffer, FALSE);
11513 cmailMsgLoaded = FALSE;
11517 DisplayInformation(msg);
11518 pclose(commandOutput);
11521 if ((*cmailMsg) != '\0') {
11522 DisplayInformation(cmailMsg);
11527 #endif /* !WIN32 */
11536 int prependComma = 0;
11538 char string[MSG_SIZ]; /* Space for game-list */
11541 if (!cmailMsgLoaded) return "";
11543 if (cmailMailedMove) {
11544 sprintf(cmailMsg, _("Waiting for reply from opponent\n"));
11546 /* Create a list of games left */
11547 sprintf(string, "[");
11548 for (i = 0; i < nCmailGames; i ++) {
11549 if (! ( cmailMoveRegistered[i]
11550 || (cmailResult[i] == CMAIL_OLD_RESULT))) {
11551 if (prependComma) {
11552 sprintf(number, ",%d", i + 1);
11554 sprintf(number, "%d", i + 1);
11558 strcat(string, number);
11561 strcat(string, "]");
11563 if (nCmailMovesRegistered + nCmailResults == 0) {
11564 switch (nCmailGames) {
11567 _("Still need to make move for game\n"));
11572 _("Still need to make moves for both games\n"));
11577 _("Still need to make moves for all %d games\n"),
11582 switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
11585 _("Still need to make a move for game %s\n"),
11590 if (nCmailResults == nCmailGames) {
11591 sprintf(cmailMsg, _("No unfinished games\n"));
11593 sprintf(cmailMsg, _("Ready to send mail\n"));
11599 _("Still need to make moves for games %s\n"),
11611 if (gameMode == Training)
11612 SetTrainingModeOff();
11615 cmailMsgLoaded = FALSE;
11616 if (appData.icsActive) {
11617 SendToICS(ics_prefix);
11618 SendToICS("refresh\n");
11628 /* Give up on clean exit */
11632 /* Keep trying for clean exit */
11636 if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
11638 if (telnetISR != NULL) {
11639 RemoveInputSource(telnetISR);
11641 if (icsPR != NoProc) {
11642 DestroyChildProcess(icsPR, TRUE);
11645 /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
11646 GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
11648 /* [HGM] crash: the above GameEnds() is a dud if another one was running */
11649 /* make sure this other one finishes before killing it! */
11650 if(endingGame) { int count = 0;
11651 if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
11652 while(endingGame && count++ < 10) DoSleep(1);
11653 if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
11656 /* Kill off chess programs */
11657 if (first.pr != NoProc) {
11660 DoSleep( appData.delayBeforeQuit );
11661 SendToProgram("quit\n", &first);
11662 DoSleep( appData.delayAfterQuit );
11663 DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
11665 if (second.pr != NoProc) {
11666 DoSleep( appData.delayBeforeQuit );
11667 SendToProgram("quit\n", &second);
11668 DoSleep( appData.delayAfterQuit );
11669 DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
11671 if (first.isr != NULL) {
11672 RemoveInputSource(first.isr);
11674 if (second.isr != NULL) {
11675 RemoveInputSource(second.isr);
11678 ShutDownFrontEnd();
11685 if (appData.debugMode)
11686 fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
11690 if (gameMode == MachinePlaysWhite ||
11691 gameMode == MachinePlaysBlack) {
11694 DisplayBothClocks();
11696 if (gameMode == PlayFromGameFile) {
11697 if (appData.timeDelay >= 0)
11698 AutoPlayGameLoop();
11699 } else if (gameMode == IcsExamining && pauseExamInvalid) {
11700 Reset(FALSE, TRUE);
11701 SendToICS(ics_prefix);
11702 SendToICS("refresh\n");
11703 } else if (currentMove < forwardMostMove) {
11704 ForwardInner(forwardMostMove);
11706 pauseExamInvalid = FALSE;
11708 switch (gameMode) {
11712 pauseExamForwardMostMove = forwardMostMove;
11713 pauseExamInvalid = FALSE;
11716 case IcsPlayingWhite:
11717 case IcsPlayingBlack:
11721 case PlayFromGameFile:
11722 (void) StopLoadGameTimer();
11726 case BeginningOfGame:
11727 if (appData.icsActive) return;
11728 /* else fall through */
11729 case MachinePlaysWhite:
11730 case MachinePlaysBlack:
11731 case TwoMachinesPlay:
11732 if (forwardMostMove == 0)
11733 return; /* don't pause if no one has moved */
11734 if ((gameMode == MachinePlaysWhite &&
11735 !WhiteOnMove(forwardMostMove)) ||
11736 (gameMode == MachinePlaysBlack &&
11737 WhiteOnMove(forwardMostMove))) {
11750 char title[MSG_SIZ];
11752 if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
11753 strcpy(title, _("Edit comment"));
11755 sprintf(title, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
11756 WhiteOnMove(currentMove - 1) ? " " : ".. ",
11757 parseList[currentMove - 1]);
11760 EditCommentPopUp(currentMove, title, commentList[currentMove]);
11767 char *tags = PGNTags(&gameInfo);
11768 EditTagsPopUp(tags);
11775 if (appData.noChessProgram || gameMode == AnalyzeMode)
11778 if (gameMode != AnalyzeFile) {
11779 if (!appData.icsEngineAnalyze) {
11781 if (gameMode != EditGame) return;
11783 ResurrectChessProgram();
11784 SendToProgram("analyze\n", &first);
11785 first.analyzing = TRUE;
11786 /*first.maybeThinking = TRUE;*/
11787 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11788 EngineOutputPopUp();
11790 if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
11795 StartAnalysisClock();
11796 GetTimeMark(&lastNodeCountTime);
11803 if (appData.noChessProgram || gameMode == AnalyzeFile)
11806 if (gameMode != AnalyzeMode) {
11808 if (gameMode != EditGame) return;
11809 ResurrectChessProgram();
11810 SendToProgram("analyze\n", &first);
11811 first.analyzing = TRUE;
11812 /*first.maybeThinking = TRUE;*/
11813 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11814 EngineOutputPopUp();
11816 gameMode = AnalyzeFile;
11821 StartAnalysisClock();
11822 GetTimeMark(&lastNodeCountTime);
11827 MachineWhiteEvent()
11830 char *bookHit = NULL;
11832 if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
11836 if (gameMode == PlayFromGameFile ||
11837 gameMode == TwoMachinesPlay ||
11838 gameMode == Training ||
11839 gameMode == AnalyzeMode ||
11840 gameMode == EndOfGame)
11843 if (gameMode == EditPosition)
11844 EditPositionDone(TRUE);
11846 if (!WhiteOnMove(currentMove)) {
11847 DisplayError(_("It is not White's turn"), 0);
11851 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
11854 if (gameMode == EditGame || gameMode == AnalyzeMode ||
11855 gameMode == AnalyzeFile)
11858 ResurrectChessProgram(); /* in case it isn't running */
11859 if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
11860 gameMode = MachinePlaysWhite;
11863 gameMode = MachinePlaysWhite;
11867 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
11869 if (first.sendName) {
11870 sprintf(buf, "name %s\n", gameInfo.black);
11871 SendToProgram(buf, &first);
11873 if (first.sendTime) {
11874 if (first.useColors) {
11875 SendToProgram("black\n", &first); /*gnu kludge*/
11877 SendTimeRemaining(&first, TRUE);
11879 if (first.useColors) {
11880 SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
11882 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
11883 SetMachineThinkingEnables();
11884 first.maybeThinking = TRUE;
11888 if (appData.autoFlipView && !flipView) {
11889 flipView = !flipView;
11890 DrawPosition(FALSE, NULL);
11891 DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;
11894 if(bookHit) { // [HGM] book: simulate book reply
11895 static char bookMove[MSG_SIZ]; // a bit generous?
11897 programStats.nodes = programStats.depth = programStats.time =
11898 programStats.score = programStats.got_only_move = 0;
11899 sprintf(programStats.movelist, "%s (xbook)", bookHit);
11901 strcpy(bookMove, "move ");
11902 strcat(bookMove, bookHit);
11903 HandleMachineMove(bookMove, &first);
11908 MachineBlackEvent()
11911 char *bookHit = NULL;
11913 if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
11917 if (gameMode == PlayFromGameFile
11918 || gameMode == TwoMachinesPlay
11919 || gameMode == Training
11920 || gameMode == AnalyzeMode
11921 || gameMode == EndOfGame)
11924 if (gameMode == EditPosition)
11925 EditPositionDone(TRUE);
11927 if (WhiteOnMove(currentMove))
11929 DisplayError(_("It is not Black's turn"), 0);
11933 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
11936 if (gameMode == EditGame || gameMode == AnalyzeMode
11937 || gameMode == AnalyzeFile)
11940 ResurrectChessProgram(); /* in case it isn't running */
11941 gameMode = MachinePlaysBlack;
11945 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
11947 if (first.sendName)
11949 sprintf(buf, "name %s\n", gameInfo.white);
11950 SendToProgram(buf, &first);
11952 if (first.sendTime)
11954 if (first.useColors)
11956 SendToProgram("white\n", &first); /*gnu kludge*/
11958 SendTimeRemaining(&first, FALSE);
11960 if (first.useColors)
11962 SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
11964 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
11965 SetMachineThinkingEnables();
11966 first.maybeThinking = TRUE;
11969 if (appData.autoFlipView && flipView)
11971 flipView = !flipView;
11972 DrawPosition(FALSE, NULL);
11973 DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;
11976 { // [HGM] book: simulate book reply
11977 static char bookMove[MSG_SIZ]; // a bit generous?
11979 programStats.nodes = programStats.depth = programStats.time
11980 = programStats.score = programStats.got_only_move = 0;
11981 sprintf(programStats.movelist, "%s (xbook)", bookHit);
11983 strcpy(bookMove, "move ");
11984 strcat(bookMove, bookHit);
11985 HandleMachineMove(bookMove, &first);
11992 DisplayTwoMachinesTitle()
11995 if (appData.matchGames > 0) {
11996 if (first.twoMachinesColor[0] == 'w') {
11997 sprintf(buf, "%s vs. %s (%d-%d-%d)",
11998 gameInfo.white, gameInfo.black,
11999 first.matchWins, second.matchWins,
12000 matchGame - 1 - (first.matchWins + second.matchWins));
12002 sprintf(buf, "%s vs. %s (%d-%d-%d)",
12003 gameInfo.white, gameInfo.black,
12004 second.matchWins, first.matchWins,
12005 matchGame - 1 - (first.matchWins + second.matchWins));
12008 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
12014 TwoMachinesEvent P((void))
12018 ChessProgramState *onmove;
12019 char *bookHit = NULL;
12021 if (appData.noChessProgram) return;
12023 switch (gameMode) {
12024 case TwoMachinesPlay:
12026 case MachinePlaysWhite:
12027 case MachinePlaysBlack:
12028 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
12029 DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
12033 case BeginningOfGame:
12034 case PlayFromGameFile:
12037 if (gameMode != EditGame) return;
12040 EditPositionDone(TRUE);
12051 // forwardMostMove = currentMove;
12052 TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
12053 ResurrectChessProgram(); /* in case first program isn't running */
12055 if (second.pr == NULL) {
12056 StartChessProgram(&second);
12057 if (second.protocolVersion == 1) {
12058 TwoMachinesEventIfReady();
12060 /* kludge: allow timeout for initial "feature" command */
12062 DisplayMessage("", _("Starting second chess program"));
12063 ScheduleDelayedEvent(TwoMachinesEventIfReady, FEATURE_TIMEOUT);
12067 DisplayMessage("", "");
12068 InitChessProgram(&second, FALSE);
12069 SendToProgram("force\n", &second);
12070 if (startedFromSetupPosition) {
12071 SendBoard(&second, backwardMostMove);
12072 if (appData.debugMode) {
12073 fprintf(debugFP, "Two Machines\n");
12076 for (i = backwardMostMove; i < forwardMostMove; i++) {
12077 SendMoveToProgram(i, &second);
12080 gameMode = TwoMachinesPlay;
12084 DisplayTwoMachinesTitle();
12086 if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
12092 SendToProgram(first.computerString, &first);
12093 if (first.sendName) {
12094 sprintf(buf, "name %s\n", second.tidy);
12095 SendToProgram(buf, &first);
12097 SendToProgram(second.computerString, &second);
12098 if (second.sendName) {
12099 sprintf(buf, "name %s\n", first.tidy);
12100 SendToProgram(buf, &second);
12104 if (!first.sendTime || !second.sendTime) {
12105 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12106 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12108 if (onmove->sendTime) {
12109 if (onmove->useColors) {
12110 SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
12112 SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
12114 if (onmove->useColors) {
12115 SendToProgram(onmove->twoMachinesColor, onmove);
12117 bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
12118 // SendToProgram("go\n", onmove);
12119 onmove->maybeThinking = TRUE;
12120 SetMachineThinkingEnables();
12124 if(bookHit) { // [HGM] book: simulate book reply
12125 static char bookMove[MSG_SIZ]; // a bit generous?
12127 programStats.nodes = programStats.depth = programStats.time =
12128 programStats.score = programStats.got_only_move = 0;
12129 sprintf(programStats.movelist, "%s (xbook)", bookHit);
12131 strcpy(bookMove, "move ");
12132 strcat(bookMove, bookHit);
12133 savedMessage = bookMove; // args for deferred call
12134 savedState = onmove;
12135 ScheduleDelayedEvent(DeferredBookMove, 1);
12142 if (gameMode == Training) {
12143 SetTrainingModeOff();
12144 gameMode = PlayFromGameFile;
12145 DisplayMessage("", _("Training mode off"));
12147 gameMode = Training;
12148 animateTraining = appData.animate;
12150 /* make sure we are not already at the end of the game */
12151 if (currentMove < forwardMostMove) {
12152 SetTrainingModeOn();
12153 DisplayMessage("", _("Training mode on"));
12155 gameMode = PlayFromGameFile;
12156 DisplayError(_("Already at end of game"), 0);
12165 if (!appData.icsActive) return;
12166 switch (gameMode) {
12167 case IcsPlayingWhite:
12168 case IcsPlayingBlack:
12171 case BeginningOfGame:
12179 EditPositionDone(TRUE);
12192 gameMode = IcsIdle;
12203 switch (gameMode) {
12205 SetTrainingModeOff();
12207 case MachinePlaysWhite:
12208 case MachinePlaysBlack:
12209 case BeginningOfGame:
12210 SendToProgram("force\n", &first);
12211 SetUserThinkingEnables();
12213 case PlayFromGameFile:
12214 (void) StopLoadGameTimer();
12215 if (gameFileFP != NULL) {
12220 EditPositionDone(TRUE);
12225 SendToProgram("force\n", &first);
12227 case TwoMachinesPlay:
12228 GameEnds((ChessMove) 0, NULL, GE_PLAYER);
12229 ResurrectChessProgram();
12230 SetUserThinkingEnables();
12233 ResurrectChessProgram();
12235 case IcsPlayingBlack:
12236 case IcsPlayingWhite:
12237 DisplayError(_("Warning: You are still playing a game"), 0);
12240 DisplayError(_("Warning: You are still observing a game"), 0);
12243 DisplayError(_("Warning: You are still examining a game"), 0);
12254 first.offeredDraw = second.offeredDraw = 0;
12256 if (gameMode == PlayFromGameFile) {
12257 whiteTimeRemaining = timeRemaining[0][currentMove];
12258 blackTimeRemaining = timeRemaining[1][currentMove];
12262 if (gameMode == MachinePlaysWhite ||
12263 gameMode == MachinePlaysBlack ||
12264 gameMode == TwoMachinesPlay ||
12265 gameMode == EndOfGame) {
12266 i = forwardMostMove;
12267 while (i > currentMove) {
12268 SendToProgram("undo\n", &first);
12271 whiteTimeRemaining = timeRemaining[0][currentMove];
12272 blackTimeRemaining = timeRemaining[1][currentMove];
12273 DisplayBothClocks();
12274 if (whiteFlag || blackFlag) {
12275 whiteFlag = blackFlag = 0;
12280 gameMode = EditGame;
12287 EditPositionEvent()
12289 if (gameMode == EditPosition) {
12295 if (gameMode != EditGame) return;
12297 gameMode = EditPosition;
12300 if (currentMove > 0)
12301 CopyBoard(boards[0], boards[currentMove]);
12303 blackPlaysFirst = !WhiteOnMove(currentMove);
12305 currentMove = forwardMostMove = backwardMostMove = 0;
12306 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12313 /* [DM] icsEngineAnalyze - possible call from other functions */
12314 if (appData.icsEngineAnalyze) {
12315 appData.icsEngineAnalyze = FALSE;
12317 DisplayMessage("",_("Close ICS engine analyze..."));
12319 if (first.analysisSupport && first.analyzing) {
12320 SendToProgram("exit\n", &first);
12321 first.analyzing = FALSE;
12323 thinkOutput[0] = NULLCHAR;
12327 EditPositionDone(Boolean fakeRights)
12329 int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
12331 startedFromSetupPosition = TRUE;
12332 InitChessProgram(&first, FALSE);
12333 if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
12334 boards[0][EP_STATUS] = EP_NONE;
12335 boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
12336 if(boards[0][0][BOARD_WIDTH>>1] == king) {
12337 boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : NoRights;
12338 boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
12339 } else boards[0][CASTLING][2] = NoRights;
12340 if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
12341 boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : NoRights;
12342 boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
12343 } else boards[0][CASTLING][5] = NoRights;
12345 SendToProgram("force\n", &first);
12346 if (blackPlaysFirst) {
12347 strcpy(moveList[0], "");
12348 strcpy(parseList[0], "");
12349 currentMove = forwardMostMove = backwardMostMove = 1;
12350 CopyBoard(boards[1], boards[0]);
12352 currentMove = forwardMostMove = backwardMostMove = 0;
12354 SendBoard(&first, forwardMostMove);
12355 if (appData.debugMode) {
12356 fprintf(debugFP, "EditPosDone\n");
12359 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12360 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12361 gameMode = EditGame;
12363 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12364 ClearHighlights(); /* [AS] */
12367 /* Pause for `ms' milliseconds */
12368 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
12378 } while (SubtractTimeMarks(&m2, &m1) < ms);
12381 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
12383 SendMultiLineToICS(buf)
12386 char temp[MSG_SIZ+1], *p;
12393 strncpy(temp, buf, len);
12398 if (*p == '\n' || *p == '\r')
12403 strcat(temp, "\n");
12405 SendToPlayer(temp, strlen(temp));
12409 SetWhiteToPlayEvent()
12411 if (gameMode == EditPosition) {
12412 blackPlaysFirst = FALSE;
12413 DisplayBothClocks(); /* works because currentMove is 0 */
12414 } else if (gameMode == IcsExamining) {
12415 SendToICS(ics_prefix);
12416 SendToICS("tomove white\n");
12421 SetBlackToPlayEvent()
12423 if (gameMode == EditPosition) {
12424 blackPlaysFirst = TRUE;
12425 currentMove = 1; /* kludge */
12426 DisplayBothClocks();
12428 } else if (gameMode == IcsExamining) {
12429 SendToICS(ics_prefix);
12430 SendToICS("tomove black\n");
12435 EditPositionMenuEvent(selection, x, y)
12436 ChessSquare selection;
12440 ChessSquare piece = boards[0][y][x];
12442 if (gameMode != EditPosition && gameMode != IcsExamining) return;
12444 switch (selection) {
12446 if (gameMode == IcsExamining && ics_type == ICS_FICS) {
12447 SendToICS(ics_prefix);
12448 SendToICS("bsetup clear\n");
12449 } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
12450 SendToICS(ics_prefix);
12451 SendToICS("clearboard\n");
12453 for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
12454 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
12455 for (y = 0; y < BOARD_HEIGHT; y++) {
12456 if (gameMode == IcsExamining) {
12457 if (boards[currentMove][y][x] != EmptySquare) {
12458 sprintf(buf, "%sx@%c%c\n", ics_prefix,
12463 boards[0][y][x] = p;
12468 if (gameMode == EditPosition) {
12469 DrawPosition(FALSE, boards[0]);
12474 SetWhiteToPlayEvent();
12478 SetBlackToPlayEvent();
12482 if (gameMode == IcsExamining) {
12483 if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
12484 sprintf(buf, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
12487 if(x < BOARD_LEFT || x >= BOARD_RGHT) {
12488 if(x == BOARD_LEFT-2) {
12489 if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
12490 boards[0][y][1] = 0;
12492 if(x == BOARD_RGHT+1) {
12493 if(y >= gameInfo.holdingsSize) break;
12494 boards[0][y][BOARD_WIDTH-2] = 0;
12497 boards[0][y][x] = EmptySquare;
12498 DrawPosition(FALSE, boards[0]);
12503 if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
12504 piece >= (int)BlackPawn && piece < (int)BlackMan ) {
12505 selection = (ChessSquare) (PROMOTED piece);
12506 } else if(piece == EmptySquare) selection = WhiteSilver;
12507 else selection = (ChessSquare)((int)piece - 1);
12511 if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
12512 piece > (int)BlackMan && piece <= (int)BlackKing ) {
12513 selection = (ChessSquare) (DEMOTED piece);
12514 } else if(piece == EmptySquare) selection = BlackSilver;
12515 else selection = (ChessSquare)((int)piece + 1);
12520 if(gameInfo.variant == VariantShatranj ||
12521 gameInfo.variant == VariantXiangqi ||
12522 gameInfo.variant == VariantCourier ||
12523 gameInfo.variant == VariantMakruk )
12524 selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
12529 if(gameInfo.variant == VariantXiangqi)
12530 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
12531 if(gameInfo.variant == VariantKnightmate)
12532 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
12535 if (gameMode == IcsExamining) {
12536 if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
12537 sprintf(buf, "%s%c@%c%c\n", ics_prefix,
12538 PieceToChar(selection), AAA + x, ONE + y);
12541 if(x < BOARD_LEFT || x >= BOARD_RGHT) {
12543 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
12544 n = PieceToNumber(selection - BlackPawn);
12545 if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
12546 boards[0][BOARD_HEIGHT-1-n][0] = selection;
12547 boards[0][BOARD_HEIGHT-1-n][1]++;
12549 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
12550 n = PieceToNumber(selection);
12551 if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
12552 boards[0][n][BOARD_WIDTH-1] = selection;
12553 boards[0][n][BOARD_WIDTH-2]++;
12556 boards[0][y][x] = selection;
12557 DrawPosition(TRUE, boards[0]);
12565 DropMenuEvent(selection, x, y)
12566 ChessSquare selection;
12569 ChessMove moveType;
12571 switch (gameMode) {
12572 case IcsPlayingWhite:
12573 case MachinePlaysBlack:
12574 if (!WhiteOnMove(currentMove)) {
12575 DisplayMoveError(_("It is Black's turn"));
12578 moveType = WhiteDrop;
12580 case IcsPlayingBlack:
12581 case MachinePlaysWhite:
12582 if (WhiteOnMove(currentMove)) {
12583 DisplayMoveError(_("It is White's turn"));
12586 moveType = BlackDrop;
12589 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
12595 if (moveType == BlackDrop && selection < BlackPawn) {
12596 selection = (ChessSquare) ((int) selection
12597 + (int) BlackPawn - (int) WhitePawn);
12599 if (boards[currentMove][y][x] != EmptySquare) {
12600 DisplayMoveError(_("That square is occupied"));
12604 FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
12610 /* Accept a pending offer of any kind from opponent */
12612 if (appData.icsActive) {
12613 SendToICS(ics_prefix);
12614 SendToICS("accept\n");
12615 } else if (cmailMsgLoaded) {
12616 if (currentMove == cmailOldMove &&
12617 commentList[cmailOldMove] != NULL &&
12618 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12619 "Black offers a draw" : "White offers a draw")) {
12621 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
12622 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
12624 DisplayError(_("There is no pending offer on this move"), 0);
12625 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12628 /* Not used for offers from chess program */
12635 /* Decline a pending offer of any kind from opponent */
12637 if (appData.icsActive) {
12638 SendToICS(ics_prefix);
12639 SendToICS("decline\n");
12640 } else if (cmailMsgLoaded) {
12641 if (currentMove == cmailOldMove &&
12642 commentList[cmailOldMove] != NULL &&
12643 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12644 "Black offers a draw" : "White offers a draw")) {
12646 AppendComment(cmailOldMove, "Draw declined", TRUE);
12647 DisplayComment(cmailOldMove - 1, "Draw declined");
12650 DisplayError(_("There is no pending offer on this move"), 0);
12653 /* Not used for offers from chess program */
12660 /* Issue ICS rematch command */
12661 if (appData.icsActive) {
12662 SendToICS(ics_prefix);
12663 SendToICS("rematch\n");
12670 /* Call your opponent's flag (claim a win on time) */
12671 if (appData.icsActive) {
12672 SendToICS(ics_prefix);
12673 SendToICS("flag\n");
12675 switch (gameMode) {
12678 case MachinePlaysWhite:
12681 GameEnds(GameIsDrawn, "Both players ran out of time",
12684 GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
12686 DisplayError(_("Your opponent is not out of time"), 0);
12689 case MachinePlaysBlack:
12692 GameEnds(GameIsDrawn, "Both players ran out of time",
12695 GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
12697 DisplayError(_("Your opponent is not out of time"), 0);
12707 /* Offer draw or accept pending draw offer from opponent */
12709 if (appData.icsActive) {
12710 /* Note: tournament rules require draw offers to be
12711 made after you make your move but before you punch
12712 your clock. Currently ICS doesn't let you do that;
12713 instead, you immediately punch your clock after making
12714 a move, but you can offer a draw at any time. */
12716 SendToICS(ics_prefix);
12717 SendToICS("draw\n");
12718 userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
12719 } else if (cmailMsgLoaded) {
12720 if (currentMove == cmailOldMove &&
12721 commentList[cmailOldMove] != NULL &&
12722 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12723 "Black offers a draw" : "White offers a draw")) {
12724 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
12725 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
12726 } else if (currentMove == cmailOldMove + 1) {
12727 char *offer = WhiteOnMove(cmailOldMove) ?
12728 "White offers a draw" : "Black offers a draw";
12729 AppendComment(currentMove, offer, TRUE);
12730 DisplayComment(currentMove - 1, offer);
12731 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
12733 DisplayError(_("You must make your move before offering a draw"), 0);
12734 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12736 } else if (first.offeredDraw) {
12737 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
12739 if (first.sendDrawOffers) {
12740 SendToProgram("draw\n", &first);
12741 userOfferedDraw = TRUE;
12749 /* Offer Adjourn or accept pending Adjourn offer from opponent */
12751 if (appData.icsActive) {
12752 SendToICS(ics_prefix);
12753 SendToICS("adjourn\n");
12755 /* Currently GNU Chess doesn't offer or accept Adjourns */
12763 /* Offer Abort or accept pending Abort offer from opponent */
12765 if (appData.icsActive) {
12766 SendToICS(ics_prefix);
12767 SendToICS("abort\n");
12769 GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
12776 /* Resign. You can do this even if it's not your turn. */
12778 if (appData.icsActive) {
12779 SendToICS(ics_prefix);
12780 SendToICS("resign\n");
12782 switch (gameMode) {
12783 case MachinePlaysWhite:
12784 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12786 case MachinePlaysBlack:
12787 GameEnds(BlackWins, "White resigns", GE_PLAYER);
12790 if (cmailMsgLoaded) {
12792 if (WhiteOnMove(cmailOldMove)) {
12793 GameEnds(BlackWins, "White resigns", GE_PLAYER);
12795 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12797 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
12808 StopObservingEvent()
12810 /* Stop observing current games */
12811 SendToICS(ics_prefix);
12812 SendToICS("unobserve\n");
12816 StopExaminingEvent()
12818 /* Stop observing current game */
12819 SendToICS(ics_prefix);
12820 SendToICS("unexamine\n");
12824 ForwardInner(target)
12829 if (appData.debugMode)
12830 fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
12831 target, currentMove, forwardMostMove);
12833 if (gameMode == EditPosition)
12836 if (gameMode == PlayFromGameFile && !pausing)
12839 if (gameMode == IcsExamining && pausing)
12840 limit = pauseExamForwardMostMove;
12842 limit = forwardMostMove;
12844 if (target > limit) target = limit;
12846 if (target > 0 && moveList[target - 1][0]) {
12847 int fromX, fromY, toX, toY;
12848 toX = moveList[target - 1][2] - AAA;
12849 toY = moveList[target - 1][3] - ONE;
12850 if (moveList[target - 1][1] == '@') {
12851 if (appData.highlightLastMove) {
12852 SetHighlights(-1, -1, toX, toY);
12855 fromX = moveList[target - 1][0] - AAA;
12856 fromY = moveList[target - 1][1] - ONE;
12857 if (target == currentMove + 1) {
12858 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
12860 if (appData.highlightLastMove) {
12861 SetHighlights(fromX, fromY, toX, toY);
12865 if (gameMode == EditGame || gameMode == AnalyzeMode ||
12866 gameMode == Training || gameMode == PlayFromGameFile ||
12867 gameMode == AnalyzeFile) {
12868 while (currentMove < target) {
12869 SendMoveToProgram(currentMove++, &first);
12872 currentMove = target;
12875 if (gameMode == EditGame || gameMode == EndOfGame) {
12876 whiteTimeRemaining = timeRemaining[0][currentMove];
12877 blackTimeRemaining = timeRemaining[1][currentMove];
12879 DisplayBothClocks();
12880 DisplayMove(currentMove - 1);
12881 DrawPosition(FALSE, boards[currentMove]);
12882 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
12883 if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
12884 DisplayComment(currentMove - 1, commentList[currentMove]);
12892 if (gameMode == IcsExamining && !pausing) {
12893 SendToICS(ics_prefix);
12894 SendToICS("forward\n");
12896 ForwardInner(currentMove + 1);
12903 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12904 /* to optimze, we temporarily turn off analysis mode while we feed
12905 * the remaining moves to the engine. Otherwise we get analysis output
12908 if (first.analysisSupport) {
12909 SendToProgram("exit\nforce\n", &first);
12910 first.analyzing = FALSE;
12914 if (gameMode == IcsExamining && !pausing) {
12915 SendToICS(ics_prefix);
12916 SendToICS("forward 999999\n");
12918 ForwardInner(forwardMostMove);
12921 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12922 /* we have fed all the moves, so reactivate analysis mode */
12923 SendToProgram("analyze\n", &first);
12924 first.analyzing = TRUE;
12925 /*first.maybeThinking = TRUE;*/
12926 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12931 BackwardInner(target)
12934 int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
12936 if (appData.debugMode)
12937 fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
12938 target, currentMove, forwardMostMove);
12940 if (gameMode == EditPosition) return;
12941 if (currentMove <= backwardMostMove) {
12943 DrawPosition(full_redraw, boards[currentMove]);
12946 if (gameMode == PlayFromGameFile && !pausing)
12949 if (moveList[target][0]) {
12950 int fromX, fromY, toX, toY;
12951 toX = moveList[target][2] - AAA;
12952 toY = moveList[target][3] - ONE;
12953 if (moveList[target][1] == '@') {
12954 if (appData.highlightLastMove) {
12955 SetHighlights(-1, -1, toX, toY);
12958 fromX = moveList[target][0] - AAA;
12959 fromY = moveList[target][1] - ONE;
12960 if (target == currentMove - 1) {
12961 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
12963 if (appData.highlightLastMove) {
12964 SetHighlights(fromX, fromY, toX, toY);
12968 if (gameMode == EditGame || gameMode==AnalyzeMode ||
12969 gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
12970 while (currentMove > target) {
12971 SendToProgram("undo\n", &first);
12975 currentMove = target;
12978 if (gameMode == EditGame || gameMode == EndOfGame) {
12979 whiteTimeRemaining = timeRemaining[0][currentMove];
12980 blackTimeRemaining = timeRemaining[1][currentMove];
12982 DisplayBothClocks();
12983 DisplayMove(currentMove - 1);
12984 DrawPosition(full_redraw, boards[currentMove]);
12985 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
12986 // [HGM] PV info: routine tests if comment empty
12987 DisplayComment(currentMove - 1, commentList[currentMove]);
12993 if (gameMode == IcsExamining && !pausing) {
12994 SendToICS(ics_prefix);
12995 SendToICS("backward\n");
12997 BackwardInner(currentMove - 1);
13004 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13005 /* to optimize, we temporarily turn off analysis mode while we undo
13006 * all the moves. Otherwise we get analysis output after each undo.
13008 if (first.analysisSupport) {
13009 SendToProgram("exit\nforce\n", &first);
13010 first.analyzing = FALSE;
13014 if (gameMode == IcsExamining && !pausing) {
13015 SendToICS(ics_prefix);
13016 SendToICS("backward 999999\n");
13018 BackwardInner(backwardMostMove);
13021 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13022 /* we have fed all the moves, so reactivate analysis mode */
13023 SendToProgram("analyze\n", &first);
13024 first.analyzing = TRUE;
13025 /*first.maybeThinking = TRUE;*/
13026 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13033 if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
13034 if (to >= forwardMostMove) to = forwardMostMove;
13035 if (to <= backwardMostMove) to = backwardMostMove;
13036 if (to < currentMove) {
13046 if(PopTail(TRUE)) { // [HGM] vari: restore old game tail
13049 if (gameMode != IcsExamining) {
13050 DisplayError(_("You are not examining a game"), 0);
13054 DisplayError(_("You can't revert while pausing"), 0);
13057 SendToICS(ics_prefix);
13058 SendToICS("revert\n");
13064 switch (gameMode) {
13065 case MachinePlaysWhite:
13066 case MachinePlaysBlack:
13067 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
13068 DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
13071 if (forwardMostMove < 2) return;
13072 currentMove = forwardMostMove = forwardMostMove - 2;
13073 whiteTimeRemaining = timeRemaining[0][currentMove];
13074 blackTimeRemaining = timeRemaining[1][currentMove];
13075 DisplayBothClocks();
13076 DisplayMove(currentMove - 1);
13077 ClearHighlights();/*!! could figure this out*/
13078 DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
13079 SendToProgram("remove\n", &first);
13080 /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
13083 case BeginningOfGame:
13087 case IcsPlayingWhite:
13088 case IcsPlayingBlack:
13089 if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
13090 SendToICS(ics_prefix);
13091 SendToICS("takeback 2\n");
13093 SendToICS(ics_prefix);
13094 SendToICS("takeback 1\n");
13103 ChessProgramState *cps;
13105 switch (gameMode) {
13106 case MachinePlaysWhite:
13107 if (!WhiteOnMove(forwardMostMove)) {
13108 DisplayError(_("It is your turn"), 0);
13113 case MachinePlaysBlack:
13114 if (WhiteOnMove(forwardMostMove)) {
13115 DisplayError(_("It is your turn"), 0);
13120 case TwoMachinesPlay:
13121 if (WhiteOnMove(forwardMostMove) ==
13122 (first.twoMachinesColor[0] == 'w')) {
13128 case BeginningOfGame:
13132 SendToProgram("?\n", cps);
13136 TruncateGameEvent()
13139 if (gameMode != EditGame) return;
13146 CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
13147 if (forwardMostMove > currentMove) {
13148 if (gameInfo.resultDetails != NULL) {
13149 free(gameInfo.resultDetails);
13150 gameInfo.resultDetails = NULL;
13151 gameInfo.result = GameUnfinished;
13153 forwardMostMove = currentMove;
13154 HistorySet(parseList, backwardMostMove, forwardMostMove,
13162 if (appData.noChessProgram) return;
13163 switch (gameMode) {
13164 case MachinePlaysWhite:
13165 if (WhiteOnMove(forwardMostMove)) {
13166 DisplayError(_("Wait until your turn"), 0);
13170 case BeginningOfGame:
13171 case MachinePlaysBlack:
13172 if (!WhiteOnMove(forwardMostMove)) {
13173 DisplayError(_("Wait until your turn"), 0);
13178 DisplayError(_("No hint available"), 0);
13181 SendToProgram("hint\n", &first);
13182 hintRequested = TRUE;
13188 if (appData.noChessProgram) return;
13189 switch (gameMode) {
13190 case MachinePlaysWhite:
13191 if (WhiteOnMove(forwardMostMove)) {
13192 DisplayError(_("Wait until your turn"), 0);
13196 case BeginningOfGame:
13197 case MachinePlaysBlack:
13198 if (!WhiteOnMove(forwardMostMove)) {
13199 DisplayError(_("Wait until your turn"), 0);
13204 EditPositionDone(TRUE);
13206 case TwoMachinesPlay:
13211 SendToProgram("bk\n", &first);
13212 bookOutput[0] = NULLCHAR;
13213 bookRequested = TRUE;
13219 char *tags = PGNTags(&gameInfo);
13220 TagsPopUp(tags, CmailMsg());
13224 /* end button procedures */
13227 PrintPosition(fp, move)
13233 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13234 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
13235 char c = PieceToChar(boards[move][i][j]);
13236 fputc(c == 'x' ? '.' : c, fp);
13237 fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
13240 if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
13241 fprintf(fp, "white to play\n");
13243 fprintf(fp, "black to play\n");
13250 if (gameInfo.white != NULL) {
13251 fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
13257 /* Find last component of program's own name, using some heuristics */
13259 TidyProgramName(prog, host, buf)
13260 char *prog, *host, buf[MSG_SIZ];
13263 int local = (strcmp(host, "localhost") == 0);
13264 while (!local && (p = strchr(prog, ';')) != NULL) {
13266 while (*p == ' ') p++;
13269 if (*prog == '"' || *prog == '\'') {
13270 q = strchr(prog + 1, *prog);
13272 q = strchr(prog, ' ');
13274 if (q == NULL) q = prog + strlen(prog);
13276 while (p >= prog && *p != '/' && *p != '\\') p--;
13278 if(p == prog && *p == '"') p++;
13279 if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
13280 memcpy(buf, p, q - p);
13281 buf[q - p] = NULLCHAR;
13289 TimeControlTagValue()
13292 if (!appData.clockMode) {
13294 } else if (movesPerSession > 0) {
13295 sprintf(buf, "%d/%ld", movesPerSession, timeControl/1000);
13296 } else if (timeIncrement == 0) {
13297 sprintf(buf, "%ld", timeControl/1000);
13299 sprintf(buf, "%ld+%ld", timeControl/1000, timeIncrement/1000);
13301 return StrSave(buf);
13307 /* This routine is used only for certain modes */
13308 VariantClass v = gameInfo.variant;
13309 ChessMove r = GameUnfinished;
13312 if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
13313 r = gameInfo.result;
13314 p = gameInfo.resultDetails;
13315 gameInfo.resultDetails = NULL;
13317 ClearGameInfo(&gameInfo);
13318 gameInfo.variant = v;
13320 switch (gameMode) {
13321 case MachinePlaysWhite:
13322 gameInfo.event = StrSave( appData.pgnEventHeader );
13323 gameInfo.site = StrSave(HostName());
13324 gameInfo.date = PGNDate();
13325 gameInfo.round = StrSave("-");
13326 gameInfo.white = StrSave(first.tidy);
13327 gameInfo.black = StrSave(UserName());
13328 gameInfo.timeControl = TimeControlTagValue();
13331 case MachinePlaysBlack:
13332 gameInfo.event = StrSave( appData.pgnEventHeader );
13333 gameInfo.site = StrSave(HostName());
13334 gameInfo.date = PGNDate();
13335 gameInfo.round = StrSave("-");
13336 gameInfo.white = StrSave(UserName());
13337 gameInfo.black = StrSave(first.tidy);
13338 gameInfo.timeControl = TimeControlTagValue();
13341 case TwoMachinesPlay:
13342 gameInfo.event = StrSave( appData.pgnEventHeader );
13343 gameInfo.site = StrSave(HostName());
13344 gameInfo.date = PGNDate();
13345 if (matchGame > 0) {
13347 sprintf(buf, "%d", matchGame);
13348 gameInfo.round = StrSave(buf);
13350 gameInfo.round = StrSave("-");
13352 if (first.twoMachinesColor[0] == 'w') {
13353 gameInfo.white = StrSave(first.tidy);
13354 gameInfo.black = StrSave(second.tidy);
13356 gameInfo.white = StrSave(second.tidy);
13357 gameInfo.black = StrSave(first.tidy);
13359 gameInfo.timeControl = TimeControlTagValue();
13363 gameInfo.event = StrSave("Edited game");
13364 gameInfo.site = StrSave(HostName());
13365 gameInfo.date = PGNDate();
13366 gameInfo.round = StrSave("-");
13367 gameInfo.white = StrSave("-");
13368 gameInfo.black = StrSave("-");
13369 gameInfo.result = r;
13370 gameInfo.resultDetails = p;
13374 gameInfo.event = StrSave("Edited position");
13375 gameInfo.site = StrSave(HostName());
13376 gameInfo.date = PGNDate();
13377 gameInfo.round = StrSave("-");
13378 gameInfo.white = StrSave("-");
13379 gameInfo.black = StrSave("-");
13382 case IcsPlayingWhite:
13383 case IcsPlayingBlack:
13388 case PlayFromGameFile:
13389 gameInfo.event = StrSave("Game from non-PGN file");
13390 gameInfo.site = StrSave(HostName());
13391 gameInfo.date = PGNDate();
13392 gameInfo.round = StrSave("-");
13393 gameInfo.white = StrSave("?");
13394 gameInfo.black = StrSave("?");
13403 ReplaceComment(index, text)
13409 while (*text == '\n') text++;
13410 len = strlen(text);
13411 while (len > 0 && text[len - 1] == '\n') len--;
13413 if (commentList[index] != NULL)
13414 free(commentList[index]);
13417 commentList[index] = NULL;
13420 if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
13421 *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
13422 *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
13423 commentList[index] = (char *) malloc(len + 2);
13424 strncpy(commentList[index], text, len);
13425 commentList[index][len] = '\n';
13426 commentList[index][len + 1] = NULLCHAR;
13428 // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
13430 commentList[index] = (char *) malloc(len + 6);
13431 strcpy(commentList[index], "{\n");
13432 strncpy(commentList[index]+2, text, len);
13433 commentList[index][len+2] = NULLCHAR;
13434 while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
13435 strcat(commentList[index], "\n}\n");
13449 if (ch == '\r') continue;
13451 } while (ch != '\0');
13455 AppendComment(index, text, addBraces)
13458 Boolean addBraces; // [HGM] braces: tells if we should add {}
13463 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
13464 text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
13467 while (*text == '\n') text++;
13468 len = strlen(text);
13469 while (len > 0 && text[len - 1] == '\n') len--;
13471 if (len == 0) return;
13473 if (commentList[index] != NULL) {
13474 old = commentList[index];
13475 oldlen = strlen(old);
13476 while(commentList[index][oldlen-1] == '\n')
13477 commentList[index][--oldlen] = NULLCHAR;
13478 commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
13479 strcpy(commentList[index], old);
13481 // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
13482 if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces)) {
13483 if(addBraces) addBraces = FALSE; else { text++; len--; }
13484 while (*text == '\n') { text++; len--; }
13485 commentList[index][--oldlen] = NULLCHAR;
13487 if(addBraces) strcat(commentList[index], "\n{\n");
13488 else strcat(commentList[index], "\n");
13489 strcat(commentList[index], text);
13490 if(addBraces) strcat(commentList[index], "\n}\n");
13491 else strcat(commentList[index], "\n");
13493 commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
13495 strcpy(commentList[index], "{\n");
13496 else commentList[index][0] = NULLCHAR;
13497 strcat(commentList[index], text);
13498 strcat(commentList[index], "\n");
13499 if(addBraces) strcat(commentList[index], "}\n");
13503 static char * FindStr( char * text, char * sub_text )
13505 char * result = strstr( text, sub_text );
13507 if( result != NULL ) {
13508 result += strlen( sub_text );
13514 /* [AS] Try to extract PV info from PGN comment */
13515 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
13516 char *GetInfoFromComment( int index, char * text )
13520 if( text != NULL && index > 0 ) {
13523 int time = -1, sec = 0, deci;
13524 char * s_eval = FindStr( text, "[%eval " );
13525 char * s_emt = FindStr( text, "[%emt " );
13527 if( s_eval != NULL || s_emt != NULL ) {
13531 if( s_eval != NULL ) {
13532 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
13536 if( delim != ']' ) {
13541 if( s_emt != NULL ) {
13546 /* We expect something like: [+|-]nnn.nn/dd */
13549 if(*text != '{') return text; // [HGM] braces: must be normal comment
13551 sep = strchr( text, '/' );
13552 if( sep == NULL || sep < (text+4) ) {
13556 time = -1; sec = -1; deci = -1;
13557 if( sscanf( text+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
13558 sscanf( text+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
13559 sscanf( text+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
13560 sscanf( text+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3 ) {
13564 if( score_lo < 0 || score_lo >= 100 ) {
13568 if(sec >= 0) time = 600*time + 10*sec; else
13569 if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
13571 score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
13573 /* [HGM] PV time: now locate end of PV info */
13574 while( *++sep >= '0' && *sep <= '9'); // strip depth
13576 while( *++sep >= '0' && *sep <= '9'); // strip time
13578 while( *++sep >= '0' && *sep <= '9'); // strip seconds
13580 while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
13581 while(*sep == ' ') sep++;
13592 pvInfoList[index-1].depth = depth;
13593 pvInfoList[index-1].score = score;
13594 pvInfoList[index-1].time = 10*time; // centi-sec
13595 if(*sep == '}') *sep = 0; else *--sep = '{';
13601 SendToProgram(message, cps)
13603 ChessProgramState *cps;
13605 int count, outCount, error;
13608 if (cps->pr == NULL) return;
13611 if (appData.debugMode) {
13614 fprintf(debugFP, "%ld >%-6s: %s",
13615 SubtractTimeMarks(&now, &programStartTime),
13616 cps->which, message);
13619 count = strlen(message);
13620 outCount = OutputToProcess(cps->pr, message, count, &error);
13621 if (outCount < count && !exiting
13622 && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
13623 sprintf(buf, _("Error writing to %s chess program"), cps->which);
13624 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
13625 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
13626 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
13627 sprintf(buf, "%s program exits in draw position (%s)", cps->which, cps->program);
13629 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
13631 gameInfo.resultDetails = StrSave(buf);
13633 if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
13638 ReceiveFromProgram(isr, closure, message, count, error)
13639 InputSourceRef isr;
13647 ChessProgramState *cps = (ChessProgramState *)closure;
13649 if (isr != cps->isr) return; /* Killed intentionally */
13653 _("Error: %s chess program (%s) exited unexpectedly"),
13654 cps->which, cps->program);
13655 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
13656 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
13657 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
13658 sprintf(buf, _("%s program exits in draw position (%s)"), cps->which, cps->program);
13660 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
13662 gameInfo.resultDetails = StrSave(buf);
13664 RemoveInputSource(cps->isr);
13665 if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
13668 _("Error reading from %s chess program (%s)"),
13669 cps->which, cps->program);
13670 RemoveInputSource(cps->isr);
13672 /* [AS] Program is misbehaving badly... kill it */
13673 if( count == -2 ) {
13674 DestroyChildProcess( cps->pr, 9 );
13678 if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
13683 if ((end_str = strchr(message, '\r')) != NULL)
13684 *end_str = NULLCHAR;
13685 if ((end_str = strchr(message, '\n')) != NULL)
13686 *end_str = NULLCHAR;
13688 if (appData.debugMode) {
13689 TimeMark now; int print = 1;
13690 char *quote = ""; char c; int i;
13692 if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
13693 char start = message[0];
13694 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
13695 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
13696 sscanf(message, "move %c", &c)!=1 && sscanf(message, "offer%c", &c)!=1 &&
13697 sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
13698 sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
13699 sscanf(message, "tell%c", &c)!=1 && sscanf(message, "0-1 %c", &c)!=1 &&
13700 sscanf(message, "1-0 %c", &c)!=1 && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
13701 sscanf(message, "pong %c", &c)!=1 && start != '#')
13702 { quote = "# "; print = (appData.engineComments == 2); }
13703 message[0] = start; // restore original message
13707 fprintf(debugFP, "%ld <%-6s: %s%s\n",
13708 SubtractTimeMarks(&now, &programStartTime), cps->which,
13714 /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
13715 if (appData.icsEngineAnalyze) {
13716 if (strstr(message, "whisper") != NULL ||
13717 strstr(message, "kibitz") != NULL ||
13718 strstr(message, "tellics") != NULL) return;
13721 HandleMachineMove(message, cps);
13726 SendTimeControl(cps, mps, tc, inc, sd, st)
13727 ChessProgramState *cps;
13728 int mps, inc, sd, st;
13734 if( timeControl_2 > 0 ) {
13735 if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
13736 tc = timeControl_2;
13739 tc /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
13740 inc /= cps->timeOdds;
13741 st /= cps->timeOdds;
13743 seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
13746 /* Set exact time per move, normally using st command */
13747 if (cps->stKludge) {
13748 /* GNU Chess 4 has no st command; uses level in a nonstandard way */
13750 if (seconds == 0) {
13751 sprintf(buf, "level 1 %d\n", st/60);
13753 sprintf(buf, "level 1 %d:%02d\n", st/60, seconds);
13756 sprintf(buf, "st %d\n", st);
13759 /* Set conventional or incremental time control, using level command */
13760 if (seconds == 0) {
13761 /* Note old gnuchess bug -- minutes:seconds used to not work.
13762 Fixed in later versions, but still avoid :seconds
13763 when seconds is 0. */
13764 sprintf(buf, "level %d %ld %d\n", mps, tc/60000, inc/1000);
13766 sprintf(buf, "level %d %ld:%02d %d\n", mps, tc/60000,
13767 seconds, inc/1000);
13770 SendToProgram(buf, cps);
13772 /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
13773 /* Orthogonally, limit search to given depth */
13775 if (cps->sdKludge) {
13776 sprintf(buf, "depth\n%d\n", sd);
13778 sprintf(buf, "sd %d\n", sd);
13780 SendToProgram(buf, cps);
13783 if(cps->nps > 0) { /* [HGM] nps */
13784 if(cps->supportsNPS == FALSE) cps->nps = -1; // don't use if engine explicitly says not supported!
13786 sprintf(buf, "nps %d\n", cps->nps);
13787 SendToProgram(buf, cps);
13792 ChessProgramState *WhitePlayer()
13793 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
13795 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
13796 gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
13802 SendTimeRemaining(cps, machineWhite)
13803 ChessProgramState *cps;
13804 int /*boolean*/ machineWhite;
13806 char message[MSG_SIZ];
13809 /* Note: this routine must be called when the clocks are stopped
13810 or when they have *just* been set or switched; otherwise
13811 it will be off by the time since the current tick started.
13813 if (machineWhite) {
13814 time = whiteTimeRemaining / 10;
13815 otime = blackTimeRemaining / 10;
13817 time = blackTimeRemaining / 10;
13818 otime = whiteTimeRemaining / 10;
13820 /* [HGM] translate opponent's time by time-odds factor */
13821 otime = (otime * cps->other->timeOdds) / cps->timeOdds;
13822 if (appData.debugMode) {
13823 fprintf(debugFP, "time odds: %f %f \n", cps->timeOdds, cps->other->timeOdds);
13826 if (time <= 0) time = 1;
13827 if (otime <= 0) otime = 1;
13829 sprintf(message, "time %ld\n", time);
13830 SendToProgram(message, cps);
13832 sprintf(message, "otim %ld\n", otime);
13833 SendToProgram(message, cps);
13837 BoolFeature(p, name, loc, cps)
13841 ChessProgramState *cps;
13844 int len = strlen(name);
13846 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
13848 sscanf(*p, "%d", &val);
13850 while (**p && **p != ' ') (*p)++;
13851 sprintf(buf, "accepted %s\n", name);
13852 SendToProgram(buf, cps);
13859 IntFeature(p, name, loc, cps)
13863 ChessProgramState *cps;
13866 int len = strlen(name);
13867 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
13869 sscanf(*p, "%d", loc);
13870 while (**p && **p != ' ') (*p)++;
13871 sprintf(buf, "accepted %s\n", name);
13872 SendToProgram(buf, cps);
13879 StringFeature(p, name, loc, cps)
13883 ChessProgramState *cps;
13886 int len = strlen(name);
13887 if (strncmp((*p), name, len) == 0
13888 && (*p)[len] == '=' && (*p)[len+1] == '\"') {
13890 sscanf(*p, "%[^\"]", loc);
13891 while (**p && **p != '\"') (*p)++;
13892 if (**p == '\"') (*p)++;
13893 sprintf(buf, "accepted %s\n", name);
13894 SendToProgram(buf, cps);
13901 ParseOption(Option *opt, ChessProgramState *cps)
13902 // [HGM] options: process the string that defines an engine option, and determine
13903 // name, type, default value, and allowed value range
13905 char *p, *q, buf[MSG_SIZ];
13906 int n, min = (-1)<<31, max = 1<<31, def;
13908 if(p = strstr(opt->name, " -spin ")) {
13909 if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
13910 if(max < min) max = min; // enforce consistency
13911 if(def < min) def = min;
13912 if(def > max) def = max;
13917 } else if((p = strstr(opt->name, " -slider "))) {
13918 // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
13919 if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
13920 if(max < min) max = min; // enforce consistency
13921 if(def < min) def = min;
13922 if(def > max) def = max;
13926 opt->type = Spin; // Slider;
13927 } else if((p = strstr(opt->name, " -string "))) {
13928 opt->textValue = p+9;
13929 opt->type = TextBox;
13930 } else if((p = strstr(opt->name, " -file "))) {
13931 // for now -file is a synonym for -string, to already provide compatibility with future polyglots
13932 opt->textValue = p+7;
13933 opt->type = TextBox; // FileName;
13934 } else if((p = strstr(opt->name, " -path "))) {
13935 // for now -file is a synonym for -string, to already provide compatibility with future polyglots
13936 opt->textValue = p+7;
13937 opt->type = TextBox; // PathName;
13938 } else if(p = strstr(opt->name, " -check ")) {
13939 if(sscanf(p, " -check %d", &def) < 1) return FALSE;
13940 opt->value = (def != 0);
13941 opt->type = CheckBox;
13942 } else if(p = strstr(opt->name, " -combo ")) {
13943 opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
13944 cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
13945 if(*q == '*') cps->comboList[cps->comboCnt-1]++;
13946 opt->value = n = 0;
13947 while(q = StrStr(q, " /// ")) {
13948 n++; *q = 0; // count choices, and null-terminate each of them
13950 if(*q == '*') { // remember default, which is marked with * prefix
13954 cps->comboList[cps->comboCnt++] = q;
13956 cps->comboList[cps->comboCnt++] = NULL;
13958 opt->type = ComboBox;
13959 } else if(p = strstr(opt->name, " -button")) {
13960 opt->type = Button;
13961 } else if(p = strstr(opt->name, " -save")) {
13962 opt->type = SaveButton;
13963 } else return FALSE;
13964 *p = 0; // terminate option name
13965 // now look if the command-line options define a setting for this engine option.
13966 if(cps->optionSettings && cps->optionSettings[0])
13967 p = strstr(cps->optionSettings, opt->name); else p = NULL;
13968 if(p && (p == cps->optionSettings || p[-1] == ',')) {
13969 sprintf(buf, "option %s", p);
13970 if(p = strstr(buf, ",")) *p = 0;
13972 SendToProgram(buf, cps);
13978 FeatureDone(cps, val)
13979 ChessProgramState* cps;
13982 DelayedEventCallback cb = GetDelayedEvent();
13983 if ((cb == InitBackEnd3 && cps == &first) ||
13984 (cb == TwoMachinesEventIfReady && cps == &second)) {
13985 CancelDelayedEvent();
13986 ScheduleDelayedEvent(cb, val ? 1 : 3600000);
13988 cps->initDone = val;
13991 /* Parse feature command from engine */
13993 ParseFeatures(args, cps)
13995 ChessProgramState *cps;
14003 while (*p == ' ') p++;
14004 if (*p == NULLCHAR) return;
14006 if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
14007 if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
14008 if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
14009 if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
14010 if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
14011 if (BoolFeature(&p, "reuse", &val, cps)) {
14012 /* Engine can disable reuse, but can't enable it if user said no */
14013 if (!val) cps->reuse = FALSE;
14016 if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
14017 if (StringFeature(&p, "myname", &cps->tidy, cps)) {
14018 if (gameMode == TwoMachinesPlay) {
14019 DisplayTwoMachinesTitle();
14025 if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
14026 if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
14027 if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
14028 if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
14029 if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
14030 if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
14031 if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
14032 if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
14033 if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
14034 if (IntFeature(&p, "done", &val, cps)) {
14035 FeatureDone(cps, val);
14038 /* Added by Tord: */
14039 if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
14040 if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
14041 /* End of additions by Tord */
14043 /* [HGM] added features: */
14044 if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
14045 if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
14046 if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
14047 if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
14048 if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
14049 if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
14050 if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
14051 if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
14052 sprintf(buf, "rejected option %s\n", cps->option[--cps->nrOptions].name);
14053 SendToProgram(buf, cps);
14056 if(cps->nrOptions >= MAX_OPTIONS) {
14058 sprintf(buf, "%s engine has too many options\n", cps->which);
14059 DisplayError(buf, 0);
14063 if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
14064 /* End of additions by HGM */
14066 /* unknown feature: complain and skip */
14068 while (*q && *q != '=') q++;
14069 sprintf(buf, "rejected %.*s\n", (int)(q-p), p);
14070 SendToProgram(buf, cps);
14076 while (*p && *p != '\"') p++;
14077 if (*p == '\"') p++;
14079 while (*p && *p != ' ') p++;
14087 PeriodicUpdatesEvent(newState)
14090 if (newState == appData.periodicUpdates)
14093 appData.periodicUpdates=newState;
14095 /* Display type changes, so update it now */
14096 // DisplayAnalysis();
14098 /* Get the ball rolling again... */
14100 AnalysisPeriodicEvent(1);
14101 StartAnalysisClock();
14106 PonderNextMoveEvent(newState)
14109 if (newState == appData.ponderNextMove) return;
14110 if (gameMode == EditPosition) EditPositionDone(TRUE);
14112 SendToProgram("hard\n", &first);
14113 if (gameMode == TwoMachinesPlay) {
14114 SendToProgram("hard\n", &second);
14117 SendToProgram("easy\n", &first);
14118 thinkOutput[0] = NULLCHAR;
14119 if (gameMode == TwoMachinesPlay) {
14120 SendToProgram("easy\n", &second);
14123 appData.ponderNextMove = newState;
14127 NewSettingEvent(option, command, value)
14133 if (gameMode == EditPosition) EditPositionDone(TRUE);
14134 sprintf(buf, "%s%s %d\n", (option ? "option ": ""), command, value);
14135 SendToProgram(buf, &first);
14136 if (gameMode == TwoMachinesPlay) {
14137 SendToProgram(buf, &second);
14142 ShowThinkingEvent()
14143 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
14145 static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
14146 int newState = appData.showThinking
14147 // [HGM] thinking: other features now need thinking output as well
14148 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
14150 if (oldState == newState) return;
14151 oldState = newState;
14152 if (gameMode == EditPosition) EditPositionDone(TRUE);
14154 SendToProgram("post\n", &first);
14155 if (gameMode == TwoMachinesPlay) {
14156 SendToProgram("post\n", &second);
14159 SendToProgram("nopost\n", &first);
14160 thinkOutput[0] = NULLCHAR;
14161 if (gameMode == TwoMachinesPlay) {
14162 SendToProgram("nopost\n", &second);
14165 // appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
14169 AskQuestionEvent(title, question, replyPrefix, which)
14170 char *title; char *question; char *replyPrefix; char *which;
14172 ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
14173 if (pr == NoProc) return;
14174 AskQuestion(title, question, replyPrefix, pr);
14178 DisplayMove(moveNumber)
14181 char message[MSG_SIZ];
14183 char cpThinkOutput[MSG_SIZ];
14185 if(appData.noGUI) return; // [HGM] fast: suppress display of moves
14187 if (moveNumber == forwardMostMove - 1 ||
14188 gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14190 safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput));
14192 if (strchr(cpThinkOutput, '\n')) {
14193 *strchr(cpThinkOutput, '\n') = NULLCHAR;
14196 *cpThinkOutput = NULLCHAR;
14199 /* [AS] Hide thinking from human user */
14200 if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
14201 *cpThinkOutput = NULLCHAR;
14202 if( thinkOutput[0] != NULLCHAR ) {
14205 for( i=0; i<=hiddenThinkOutputState; i++ ) {
14206 cpThinkOutput[i] = '.';
14208 cpThinkOutput[i] = NULLCHAR;
14209 hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
14213 if (moveNumber == forwardMostMove - 1 &&
14214 gameInfo.resultDetails != NULL) {
14215 if (gameInfo.resultDetails[0] == NULLCHAR) {
14216 sprintf(res, " %s", PGNResult(gameInfo.result));
14218 sprintf(res, " {%s} %s",
14219 gameInfo.resultDetails, PGNResult(gameInfo.result));
14225 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
14226 DisplayMessage(res, cpThinkOutput);
14228 sprintf(message, "%d.%s%s%s", moveNumber / 2 + 1,
14229 WhiteOnMove(moveNumber) ? " " : ".. ",
14230 parseList[moveNumber], res);
14231 DisplayMessage(message, cpThinkOutput);
14236 DisplayComment(moveNumber, text)
14240 char title[MSG_SIZ];
14241 char buf[8000]; // comment can be long!
14243 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
14244 strcpy(title, "Comment");
14246 sprintf(title, "Comment on %d.%s%s", moveNumber / 2 + 1,
14247 WhiteOnMove(moveNumber) ? " " : ".. ",
14248 parseList[moveNumber]);
14250 // [HGM] PV info: display PV info together with (or as) comment
14251 if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {
14252 if(text == NULL) text = "";
14253 score = pvInfoList[moveNumber].score;
14254 sprintf(buf, "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,
14255 depth, (pvInfoList[moveNumber].time+50)/100, text);
14258 if (text != NULL && (appData.autoDisplayComment || commentUp))
14259 CommentPopUp(title, text);
14262 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
14263 * might be busy thinking or pondering. It can be omitted if your
14264 * gnuchess is configured to stop thinking immediately on any user
14265 * input. However, that gnuchess feature depends on the FIONREAD
14266 * ioctl, which does not work properly on some flavors of Unix.
14270 ChessProgramState *cps;
14273 if (!cps->useSigint) return;
14274 if (appData.noChessProgram || (cps->pr == NoProc)) return;
14275 switch (gameMode) {
14276 case MachinePlaysWhite:
14277 case MachinePlaysBlack:
14278 case TwoMachinesPlay:
14279 case IcsPlayingWhite:
14280 case IcsPlayingBlack:
14283 /* Skip if we know it isn't thinking */
14284 if (!cps->maybeThinking) return;
14285 if (appData.debugMode)
14286 fprintf(debugFP, "Interrupting %s\n", cps->which);
14287 InterruptChildProcess(cps->pr);
14288 cps->maybeThinking = FALSE;
14293 #endif /*ATTENTION*/
14299 if (whiteTimeRemaining <= 0) {
14302 if (appData.icsActive) {
14303 if (appData.autoCallFlag &&
14304 gameMode == IcsPlayingBlack && !blackFlag) {
14305 SendToICS(ics_prefix);
14306 SendToICS("flag\n");
14310 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
14312 if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
14313 if (appData.autoCallFlag) {
14314 GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
14321 if (blackTimeRemaining <= 0) {
14324 if (appData.icsActive) {
14325 if (appData.autoCallFlag &&
14326 gameMode == IcsPlayingWhite && !whiteFlag) {
14327 SendToICS(ics_prefix);
14328 SendToICS("flag\n");
14332 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
14334 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
14335 if (appData.autoCallFlag) {
14336 GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
14349 if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
14350 gameMode == PlayFromGameFile || forwardMostMove == 0) return;
14353 * add time to clocks when time control is achieved ([HGM] now also used for increment)
14355 if ( !WhiteOnMove(forwardMostMove) )
14356 /* White made time control */
14357 whiteTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
14358 /* [HGM] time odds: correct new time quota for time odds! */
14359 / WhitePlayer()->timeOdds;
14361 /* Black made time control */
14362 blackTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
14363 / WhitePlayer()->other->timeOdds;
14367 DisplayBothClocks()
14369 int wom = gameMode == EditPosition ?
14370 !blackPlaysFirst : WhiteOnMove(currentMove);
14371 DisplayWhiteClock(whiteTimeRemaining, wom);
14372 DisplayBlackClock(blackTimeRemaining, !wom);
14376 /* Timekeeping seems to be a portability nightmare. I think everyone
14377 has ftime(), but I'm really not sure, so I'm including some ifdefs
14378 to use other calls if you don't. Clocks will be less accurate if
14379 you have neither ftime nor gettimeofday.
14382 /* VS 2008 requires the #include outside of the function */
14383 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
14384 #include <sys/timeb.h>
14387 /* Get the current time as a TimeMark */
14392 #if HAVE_GETTIMEOFDAY
14394 struct timeval timeVal;
14395 struct timezone timeZone;
14397 gettimeofday(&timeVal, &timeZone);
14398 tm->sec = (long) timeVal.tv_sec;
14399 tm->ms = (int) (timeVal.tv_usec / 1000L);
14401 #else /*!HAVE_GETTIMEOFDAY*/
14404 // include <sys/timeb.h> / moved to just above start of function
14405 struct timeb timeB;
14408 tm->sec = (long) timeB.time;
14409 tm->ms = (int) timeB.millitm;
14411 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
14412 tm->sec = (long) time(NULL);
14418 /* Return the difference in milliseconds between two
14419 time marks. We assume the difference will fit in a long!
14422 SubtractTimeMarks(tm2, tm1)
14423 TimeMark *tm2, *tm1;
14425 return 1000L*(tm2->sec - tm1->sec) +
14426 (long) (tm2->ms - tm1->ms);
14431 * Code to manage the game clocks.
14433 * In tournament play, black starts the clock and then white makes a move.
14434 * We give the human user a slight advantage if he is playing white---the
14435 * clocks don't run until he makes his first move, so it takes zero time.
14436 * Also, we don't account for network lag, so we could get out of sync
14437 * with GNU Chess's clock -- but then, referees are always right.
14440 static TimeMark tickStartTM;
14441 static long intendedTickLength;
14444 NextTickLength(timeRemaining)
14445 long timeRemaining;
14447 long nominalTickLength, nextTickLength;
14449 if (timeRemaining > 0L && timeRemaining <= 10000L)
14450 nominalTickLength = 100L;
14452 nominalTickLength = 1000L;
14453 nextTickLength = timeRemaining % nominalTickLength;
14454 if (nextTickLength <= 0) nextTickLength += nominalTickLength;
14456 return nextTickLength;
14459 /* Adjust clock one minute up or down */
14461 AdjustClock(Boolean which, int dir)
14463 if(which) blackTimeRemaining += 60000*dir;
14464 else whiteTimeRemaining += 60000*dir;
14465 DisplayBothClocks();
14468 /* Stop clocks and reset to a fresh time control */
14472 (void) StopClockTimer();
14473 if (appData.icsActive) {
14474 whiteTimeRemaining = blackTimeRemaining = 0;
14475 } else if (searchTime) {
14476 whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
14477 blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
14478 } else { /* [HGM] correct new time quote for time odds */
14479 whiteTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->timeOdds;
14480 blackTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->other->timeOdds;
14482 if (whiteFlag || blackFlag) {
14484 whiteFlag = blackFlag = FALSE;
14486 DisplayBothClocks();
14489 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
14491 /* Decrement running clock by amount of time that has passed */
14495 long timeRemaining;
14496 long lastTickLength, fudge;
14499 if (!appData.clockMode) return;
14500 if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
14504 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
14506 /* Fudge if we woke up a little too soon */
14507 fudge = intendedTickLength - lastTickLength;
14508 if (fudge < 0 || fudge > FUDGE) fudge = 0;
14510 if (WhiteOnMove(forwardMostMove)) {
14511 if(whiteNPS >= 0) lastTickLength = 0;
14512 timeRemaining = whiteTimeRemaining -= lastTickLength;
14513 DisplayWhiteClock(whiteTimeRemaining - fudge,
14514 WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
14516 if(blackNPS >= 0) lastTickLength = 0;
14517 timeRemaining = blackTimeRemaining -= lastTickLength;
14518 DisplayBlackClock(blackTimeRemaining - fudge,
14519 !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
14522 if (CheckFlags()) return;
14525 intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
14526 StartClockTimer(intendedTickLength);
14528 /* if the time remaining has fallen below the alarm threshold, sound the
14529 * alarm. if the alarm has sounded and (due to a takeback or time control
14530 * with increment) the time remaining has increased to a level above the
14531 * threshold, reset the alarm so it can sound again.
14534 if (appData.icsActive && appData.icsAlarm) {
14536 /* make sure we are dealing with the user's clock */
14537 if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
14538 ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
14541 if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
14542 alarmSounded = FALSE;
14543 } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
14545 alarmSounded = TRUE;
14551 /* A player has just moved, so stop the previously running
14552 clock and (if in clock mode) start the other one.
14553 We redisplay both clocks in case we're in ICS mode, because
14554 ICS gives us an update to both clocks after every move.
14555 Note that this routine is called *after* forwardMostMove
14556 is updated, so the last fractional tick must be subtracted
14557 from the color that is *not* on move now.
14562 long lastTickLength;
14564 int flagged = FALSE;
14568 if (StopClockTimer() && appData.clockMode) {
14569 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
14570 if (WhiteOnMove(forwardMostMove)) {
14571 if(blackNPS >= 0) lastTickLength = 0;
14572 blackTimeRemaining -= lastTickLength;
14573 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
14574 // if(pvInfoList[forwardMostMove-1].time == -1)
14575 pvInfoList[forwardMostMove-1].time = // use GUI time
14576 (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
14578 if(whiteNPS >= 0) lastTickLength = 0;
14579 whiteTimeRemaining -= lastTickLength;
14580 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
14581 // if(pvInfoList[forwardMostMove-1].time == -1)
14582 pvInfoList[forwardMostMove-1].time =
14583 (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
14585 flagged = CheckFlags();
14587 CheckTimeControl();
14589 if (flagged || !appData.clockMode) return;
14591 switch (gameMode) {
14592 case MachinePlaysBlack:
14593 case MachinePlaysWhite:
14594 case BeginningOfGame:
14595 if (pausing) return;
14599 case PlayFromGameFile:
14607 if (searchTime) { // [HGM] st: set clock of player that has to move to max time
14608 if(WhiteOnMove(forwardMostMove))
14609 whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
14610 else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
14614 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
14615 whiteTimeRemaining : blackTimeRemaining);
14616 StartClockTimer(intendedTickLength);
14620 /* Stop both clocks */
14624 long lastTickLength;
14627 if (!StopClockTimer()) return;
14628 if (!appData.clockMode) return;
14632 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
14633 if (WhiteOnMove(forwardMostMove)) {
14634 if(whiteNPS >= 0) lastTickLength = 0;
14635 whiteTimeRemaining -= lastTickLength;
14636 DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
14638 if(blackNPS >= 0) lastTickLength = 0;
14639 blackTimeRemaining -= lastTickLength;
14640 DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
14645 /* Start clock of player on move. Time may have been reset, so
14646 if clock is already running, stop and restart it. */
14650 (void) StopClockTimer(); /* in case it was running already */
14651 DisplayBothClocks();
14652 if (CheckFlags()) return;
14654 if (!appData.clockMode) return;
14655 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
14657 GetTimeMark(&tickStartTM);
14658 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
14659 whiteTimeRemaining : blackTimeRemaining);
14661 /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
14662 whiteNPS = blackNPS = -1;
14663 if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
14664 || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
14665 whiteNPS = first.nps;
14666 if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
14667 || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
14668 blackNPS = first.nps;
14669 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
14670 whiteNPS = second.nps;
14671 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
14672 blackNPS = second.nps;
14673 if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
14675 StartClockTimer(intendedTickLength);
14682 long second, minute, hour, day;
14684 static char buf[32];
14686 if (ms > 0 && ms <= 9900) {
14687 /* convert milliseconds to tenths, rounding up */
14688 double tenths = floor( ((double)(ms + 99L)) / 100.00 );
14690 sprintf(buf, " %03.1f ", tenths/10.0);
14694 /* convert milliseconds to seconds, rounding up */
14695 /* use floating point to avoid strangeness of integer division
14696 with negative dividends on many machines */
14697 second = (long) floor(((double) (ms + 999L)) / 1000.0);
14704 day = second / (60 * 60 * 24);
14705 second = second % (60 * 60 * 24);
14706 hour = second / (60 * 60);
14707 second = second % (60 * 60);
14708 minute = second / 60;
14709 second = second % 60;
14712 sprintf(buf, " %s%ld:%02ld:%02ld:%02ld ",
14713 sign, day, hour, minute, second);
14715 sprintf(buf, " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
14717 sprintf(buf, " %s%2ld:%02ld ", sign, minute, second);
14724 * This is necessary because some C libraries aren't ANSI C compliant yet.
14727 StrStr(string, match)
14728 char *string, *match;
14732 length = strlen(match);
14734 for (i = strlen(string) - length; i >= 0; i--, string++)
14735 if (!strncmp(match, string, length))
14742 StrCaseStr(string, match)
14743 char *string, *match;
14747 length = strlen(match);
14749 for (i = strlen(string) - length; i >= 0; i--, string++) {
14750 for (j = 0; j < length; j++) {
14751 if (ToLower(match[j]) != ToLower(string[j]))
14754 if (j == length) return string;
14768 c1 = ToLower(*s1++);
14769 c2 = ToLower(*s2++);
14770 if (c1 > c2) return 1;
14771 if (c1 < c2) return -1;
14772 if (c1 == NULLCHAR) return 0;
14781 return isupper(c) ? tolower(c) : c;
14789 return islower(c) ? toupper(c) : c;
14791 #endif /* !_amigados */
14799 if ((ret = (char *) malloc(strlen(s) + 1))) {
14806 StrSavePtr(s, savePtr)
14807 char *s, **savePtr;
14812 if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
14813 strcpy(*savePtr, s);
14825 clock = time((time_t *)NULL);
14826 tm = localtime(&clock);
14827 sprintf(buf, "%04d.%02d.%02d",
14828 tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
14829 return StrSave(buf);
14834 PositionToFEN(move, overrideCastling)
14836 char *overrideCastling;
14838 int i, j, fromX, fromY, toX, toY;
14845 whiteToPlay = (gameMode == EditPosition) ?
14846 !blackPlaysFirst : (move % 2 == 0);
14849 /* Piece placement data */
14850 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
14852 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
14853 if (boards[move][i][j] == EmptySquare) {
14855 } else { ChessSquare piece = boards[move][i][j];
14856 if (emptycount > 0) {
14857 if(emptycount<10) /* [HGM] can be >= 10 */
14858 *p++ = '0' + emptycount;
14859 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
14862 if(PieceToChar(piece) == '+') {
14863 /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
14865 piece = (ChessSquare)(DEMOTED piece);
14867 *p++ = PieceToChar(piece);
14869 /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
14870 p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
14875 if (emptycount > 0) {
14876 if(emptycount<10) /* [HGM] can be >= 10 */
14877 *p++ = '0' + emptycount;
14878 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
14885 /* [HGM] print Crazyhouse or Shogi holdings */
14886 if( gameInfo.holdingsWidth ) {
14887 *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
14889 for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
14890 piece = boards[move][i][BOARD_WIDTH-1];
14891 if( piece != EmptySquare )
14892 for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
14893 *p++ = PieceToChar(piece);
14895 for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
14896 piece = boards[move][BOARD_HEIGHT-i-1][0];
14897 if( piece != EmptySquare )
14898 for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
14899 *p++ = PieceToChar(piece);
14902 if( q == p ) *p++ = '-';
14908 *p++ = whiteToPlay ? 'w' : 'b';
14911 if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
14912 while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
14914 if(nrCastlingRights) {
14916 if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
14917 /* [HGM] write directly from rights */
14918 if(boards[move][CASTLING][2] != NoRights &&
14919 boards[move][CASTLING][0] != NoRights )
14920 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
14921 if(boards[move][CASTLING][2] != NoRights &&
14922 boards[move][CASTLING][1] != NoRights )
14923 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
14924 if(boards[move][CASTLING][5] != NoRights &&
14925 boards[move][CASTLING][3] != NoRights )
14926 *p++ = boards[move][CASTLING][3] + AAA;
14927 if(boards[move][CASTLING][5] != NoRights &&
14928 boards[move][CASTLING][4] != NoRights )
14929 *p++ = boards[move][CASTLING][4] + AAA;
14932 /* [HGM] write true castling rights */
14933 if( nrCastlingRights == 6 ) {
14934 if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
14935 boards[move][CASTLING][2] != NoRights ) *p++ = 'K';
14936 if(boards[move][CASTLING][1] == BOARD_LEFT &&
14937 boards[move][CASTLING][2] != NoRights ) *p++ = 'Q';
14938 if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
14939 boards[move][CASTLING][5] != NoRights ) *p++ = 'k';
14940 if(boards[move][CASTLING][4] == BOARD_LEFT &&
14941 boards[move][CASTLING][5] != NoRights ) *p++ = 'q';
14944 if (q == p) *p++ = '-'; /* No castling rights */
14948 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
14949 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
14950 /* En passant target square */
14951 if (move > backwardMostMove) {
14952 fromX = moveList[move - 1][0] - AAA;
14953 fromY = moveList[move - 1][1] - ONE;
14954 toX = moveList[move - 1][2] - AAA;
14955 toY = moveList[move - 1][3] - ONE;
14956 if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
14957 toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
14958 boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
14960 /* 2-square pawn move just happened */
14962 *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
14966 } else if(move == backwardMostMove) {
14967 // [HGM] perhaps we should always do it like this, and forget the above?
14968 if((signed char)boards[move][EP_STATUS] >= 0) {
14969 *p++ = boards[move][EP_STATUS] + AAA;
14970 *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
14981 /* [HGM] find reversible plies */
14982 { int i = 0, j=move;
14984 if (appData.debugMode) { int k;
14985 fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
14986 for(k=backwardMostMove; k<=forwardMostMove; k++)
14987 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
14991 while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
14992 if( j == backwardMostMove ) i += initialRulePlies;
14993 sprintf(p, "%d ", i);
14994 p += i>=100 ? 4 : i >= 10 ? 3 : 2;
14996 /* Fullmove number */
14997 sprintf(p, "%d", (move / 2) + 1);
14999 return StrSave(buf);
15003 ParseFEN(board, blackPlaysFirst, fen)
15005 int *blackPlaysFirst;
15015 /* [HGM] by default clear Crazyhouse holdings, if present */
15016 if(gameInfo.holdingsWidth) {
15017 for(i=0; i<BOARD_HEIGHT; i++) {
15018 board[i][0] = EmptySquare; /* black holdings */
15019 board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
15020 board[i][1] = (ChessSquare) 0; /* black counts */
15021 board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
15025 /* Piece placement data */
15026 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
15029 if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
15030 if (*p == '/') p++;
15031 emptycount = gameInfo.boardWidth - j;
15032 while (emptycount--)
15033 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
15035 #if(BOARD_FILES >= 10)
15036 } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
15037 p++; emptycount=10;
15038 if (j + emptycount > gameInfo.boardWidth) return FALSE;
15039 while (emptycount--)
15040 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
15042 } else if (isdigit(*p)) {
15043 emptycount = *p++ - '0';
15044 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
15045 if (j + emptycount > gameInfo.boardWidth) return FALSE;
15046 while (emptycount--)
15047 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
15048 } else if (*p == '+' || isalpha(*p)) {
15049 if (j >= gameInfo.boardWidth) return FALSE;
15051 piece = CharToPiece(*++p);
15052 if(piece == EmptySquare) return FALSE; /* unknown piece */
15053 piece = (ChessSquare) (PROMOTED piece ); p++;
15054 if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
15055 } else piece = CharToPiece(*p++);
15057 if(piece==EmptySquare) return FALSE; /* unknown piece */
15058 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
15059 piece = (ChessSquare) (PROMOTED piece);
15060 if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
15063 board[i][(j++)+gameInfo.holdingsWidth] = piece;
15069 while (*p == '/' || *p == ' ') p++;
15071 /* [HGM] look for Crazyhouse holdings here */
15072 while(*p==' ') p++;
15073 if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
15075 if(*p == '-' ) *p++; /* empty holdings */ else {
15076 if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
15077 /* if we would allow FEN reading to set board size, we would */
15078 /* have to add holdings and shift the board read so far here */
15079 while( (piece = CharToPiece(*p) ) != EmptySquare ) {
15081 if((int) piece >= (int) BlackPawn ) {
15082 i = (int)piece - (int)BlackPawn;
15083 i = PieceToNumber((ChessSquare)i);
15084 if( i >= gameInfo.holdingsSize ) return FALSE;
15085 board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
15086 board[BOARD_HEIGHT-1-i][1]++; /* black counts */
15088 i = (int)piece - (int)WhitePawn;
15089 i = PieceToNumber((ChessSquare)i);
15090 if( i >= gameInfo.holdingsSize ) return FALSE;
15091 board[i][BOARD_WIDTH-1] = piece; /* white holdings */
15092 board[i][BOARD_WIDTH-2]++; /* black holdings */
15096 if(*p == ']') *p++;
15099 while(*p == ' ') p++;
15104 *blackPlaysFirst = FALSE;
15107 *blackPlaysFirst = TRUE;
15113 /* [HGM] We NO LONGER ignore the rest of the FEN notation */
15114 /* return the extra info in global variiables */
15116 /* set defaults in case FEN is incomplete */
15117 board[EP_STATUS] = EP_UNKNOWN;
15118 for(i=0; i<nrCastlingRights; i++ ) {
15119 board[CASTLING][i] =
15120 gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
15121 } /* assume possible unless obviously impossible */
15122 if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
15123 if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
15124 if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
15125 && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
15126 if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
15127 if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
15128 if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
15129 && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
15132 while(*p==' ') p++;
15133 if(nrCastlingRights) {
15134 if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
15135 /* castling indicator present, so default becomes no castlings */
15136 for(i=0; i<nrCastlingRights; i++ ) {
15137 board[CASTLING][i] = NoRights;
15140 while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
15141 (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
15142 ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
15143 ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth) ) {
15144 char c = *p++; int whiteKingFile=NoRights, blackKingFile=NoRights;
15146 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
15147 if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
15148 if(board[0 ][i] == WhiteKing) whiteKingFile = i;
15150 if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
15151 whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
15152 if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
15153 && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
15154 if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
15155 && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
15158 for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
15159 board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
15160 board[CASTLING][2] = whiteKingFile;
15163 for(i=BOARD_LEFT; i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
15164 board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
15165 board[CASTLING][2] = whiteKingFile;
15168 for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
15169 board[CASTLING][3] = i != blackKingFile ? i : NoRights;
15170 board[CASTLING][5] = blackKingFile;
15173 for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
15174 board[CASTLING][4] = i != blackKingFile ? i : NoRights;
15175 board[CASTLING][5] = blackKingFile;
15178 default: /* FRC castlings */
15179 if(c >= 'a') { /* black rights */
15180 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
15181 if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
15182 if(i == BOARD_RGHT) break;
15183 board[CASTLING][5] = i;
15185 if(board[BOARD_HEIGHT-1][c] < BlackPawn ||
15186 board[BOARD_HEIGHT-1][c] >= BlackKing ) break;
15188 board[CASTLING][3] = c;
15190 board[CASTLING][4] = c;
15191 } else { /* white rights */
15192 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
15193 if(board[0][i] == WhiteKing) break;
15194 if(i == BOARD_RGHT) break;
15195 board[CASTLING][2] = i;
15196 c -= AAA - 'a' + 'A';
15197 if(board[0][c] >= WhiteKing) break;
15199 board[CASTLING][0] = c;
15201 board[CASTLING][1] = c;
15205 for(i=0; i<nrCastlingRights; i++)
15206 if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
15207 if (appData.debugMode) {
15208 fprintf(debugFP, "FEN castling rights:");
15209 for(i=0; i<nrCastlingRights; i++)
15210 fprintf(debugFP, " %d", board[CASTLING][i]);
15211 fprintf(debugFP, "\n");
15214 while(*p==' ') p++;
15217 /* read e.p. field in games that know e.p. capture */
15218 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
15219 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
15221 p++; board[EP_STATUS] = EP_NONE;
15223 char c = *p++ - AAA;
15225 if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
15226 if(*p >= '0' && *p <='9') *p++;
15227 board[EP_STATUS] = c;
15232 if(sscanf(p, "%d", &i) == 1) {
15233 FENrulePlies = i; /* 50-move ply counter */
15234 /* (The move number is still ignored) */
15241 EditPositionPasteFEN(char *fen)
15244 Board initial_position;
15246 if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
15247 DisplayError(_("Bad FEN position in clipboard"), 0);
15250 int savedBlackPlaysFirst = blackPlaysFirst;
15251 EditPositionEvent();
15252 blackPlaysFirst = savedBlackPlaysFirst;
15253 CopyBoard(boards[0], initial_position);
15254 initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
15255 EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
15256 DisplayBothClocks();
15257 DrawPosition(FALSE, boards[currentMove]);
15262 static char cseq[12] = "\\ ";
15264 Boolean set_cont_sequence(char *new_seq)
15269 // handle bad attempts to set the sequence
15271 return 0; // acceptable error - no debug
15273 len = strlen(new_seq);
15274 ret = (len > 0) && (len < sizeof(cseq));
15276 strcpy(cseq, new_seq);
15277 else if (appData.debugMode)
15278 fprintf(debugFP, "Invalid continuation sequence \"%s\" (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
15283 reformat a source message so words don't cross the width boundary. internal
15284 newlines are not removed. returns the wrapped size (no null character unless
15285 included in source message). If dest is NULL, only calculate the size required
15286 for the dest buffer. lp argument indicats line position upon entry, and it's
15287 passed back upon exit.
15289 int wrap(char *dest, char *src, int count, int width, int *lp)
15291 int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
15293 cseq_len = strlen(cseq);
15294 old_line = line = *lp;
15295 ansi = len = clen = 0;
15297 for (i=0; i < count; i++)
15299 if (src[i] == '\033')
15302 // if we hit the width, back up
15303 if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
15305 // store i & len in case the word is too long
15306 old_i = i, old_len = len;
15308 // find the end of the last word
15309 while (i && src[i] != ' ' && src[i] != '\n')
15315 // word too long? restore i & len before splitting it
15316 if ((old_i-i+clen) >= width)
15323 if (i && src[i-1] == ' ')
15326 if (src[i] != ' ' && src[i] != '\n')
15333 // now append the newline and continuation sequence
15338 strncpy(dest+len, cseq, cseq_len);
15346 dest[len] = src[i];
15350 if (src[i] == '\n')
15355 if (dest && appData.debugMode)
15357 fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
15358 count, width, line, len, *lp);
15359 show_bytes(debugFP, src, count);
15360 fprintf(debugFP, "\ndest: ");
15361 show_bytes(debugFP, dest, len);
15362 fprintf(debugFP, "\n");
15364 *lp = dest ? line : old_line;
15369 // [HGM] vari: routines for shelving variations
15372 PushTail(int firstMove, int lastMove)
15374 int i, j, nrMoves = lastMove - firstMove;
15376 if(appData.icsActive) { // only in local mode
15377 forwardMostMove = currentMove; // mimic old ICS behavior
15380 if(storedGames >= MAX_VARIATIONS-1) return;
15382 // push current tail of game on stack
15383 savedResult[storedGames] = gameInfo.result;
15384 savedDetails[storedGames] = gameInfo.resultDetails;
15385 gameInfo.resultDetails = NULL;
15386 savedFirst[storedGames] = firstMove;
15387 savedLast [storedGames] = lastMove;
15388 savedFramePtr[storedGames] = framePtr;
15389 framePtr -= nrMoves; // reserve space for the boards
15390 for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
15391 CopyBoard(boards[framePtr+i], boards[firstMove+i]);
15392 for(j=0; j<MOVE_LEN; j++)
15393 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
15394 for(j=0; j<2*MOVE_LEN; j++)
15395 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
15396 timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
15397 timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
15398 pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
15399 pvInfoList[firstMove+i-1].depth = 0;
15400 commentList[framePtr+i] = commentList[firstMove+i];
15401 commentList[firstMove+i] = NULL;
15405 forwardMostMove = currentMove; // truncte game so we can start variation
15406 if(storedGames == 1) GreyRevert(FALSE);
15410 PopTail(Boolean annotate)
15413 char buf[8000], moveBuf[20];
15415 if(appData.icsActive) return FALSE; // only in local mode
15416 if(!storedGames) return FALSE; // sanity
15419 ToNrEvent(savedFirst[storedGames]); // sets currentMove
15420 nrMoves = savedLast[storedGames] - currentMove;
15423 if(!WhiteOnMove(currentMove)) sprintf(buf, "(%d...", currentMove+2>>1);
15424 else strcpy(buf, "(");
15425 for(i=currentMove; i<forwardMostMove; i++) {
15427 sprintf(moveBuf, " %d. %s", i+2>>1, SavePart(parseList[i]));
15428 else sprintf(moveBuf, " %s", SavePart(parseList[i]));
15429 strcat(buf, moveBuf);
15430 if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
15434 for(i=1; i<nrMoves; i++) { // copy last variation back
15435 CopyBoard(boards[currentMove+i], boards[framePtr+i]);
15436 for(j=0; j<MOVE_LEN; j++)
15437 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
15438 for(j=0; j<2*MOVE_LEN; j++)
15439 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
15440 timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
15441 timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
15442 pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
15443 if(commentList[currentMove+i]) free(commentList[currentMove+i]);
15444 commentList[currentMove+i] = commentList[framePtr+i];
15445 commentList[framePtr+i] = NULL;
15447 if(annotate) AppendComment(currentMove+1, buf, FALSE);
15448 framePtr = savedFramePtr[storedGames];
15449 gameInfo.result = savedResult[storedGames];
15450 if(gameInfo.resultDetails != NULL) {
15451 free(gameInfo.resultDetails);
15453 gameInfo.resultDetails = savedDetails[storedGames];
15454 forwardMostMove = currentMove + nrMoves;
15455 if(storedGames == 0) GreyRevert(TRUE);
15461 { // remove all shelved variations
15463 for(i=0; i<storedGames; i++) {
15464 if(savedDetails[i])
15465 free(savedDetails[i]);
15466 savedDetails[i] = NULL;
15468 for(i=framePtr; i<MAX_MOVES; i++) {
15469 if(commentList[i]) free(commentList[i]);
15470 commentList[i] = NULL;
15472 framePtr = MAX_MOVES-1;