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 void GameEnds P((ChessMove result, char *resultDetails, int whosays));
171 void EditPositionDone P((Boolean fakeRights));
172 void PrintOpponents P((FILE *fp));
173 void PrintPosition P((FILE *fp, int move));
174 void StartChessProgram P((ChessProgramState *cps));
175 void SendToProgram P((char *message, ChessProgramState *cps));
176 void SendMoveToProgram P((int moveNum, ChessProgramState *cps));
177 void ReceiveFromProgram P((InputSourceRef isr, VOIDSTAR closure,
178 char *buf, int count, int error));
179 void SendTimeControl P((ChessProgramState *cps,
180 int mps, long tc, int inc, int sd, int st));
181 char *TimeControlTagValue P((void));
182 void Attention P((ChessProgramState *cps));
183 void FeedMovesToProgram P((ChessProgramState *cps, int upto));
184 void ResurrectChessProgram P((void));
185 void DisplayComment P((int moveNumber, char *text));
186 void DisplayMove P((int moveNumber));
188 void ParseGameHistory P((char *game));
189 void ParseBoard12 P((char *string));
190 void KeepAlive P((void));
191 void StartClocks P((void));
192 void SwitchClocks P((void));
193 void StopClocks P((void));
194 void ResetClocks P((void));
195 char *PGNDate P((void));
196 void SetGameInfo P((void));
197 Boolean ParseFEN P((Board board, int *blackPlaysFirst, char *fen));
198 int RegisterMove P((void));
199 void MakeRegisteredMove P((void));
200 void TruncateGame P((void));
201 int looking_at P((char *, int *, char *));
202 void CopyPlayerNameIntoFileName P((char **, char *));
203 char *SavePart P((char *));
204 int SaveGameOldStyle P((FILE *));
205 int SaveGamePGN P((FILE *));
206 void GetTimeMark P((TimeMark *));
207 long SubtractTimeMarks P((TimeMark *, TimeMark *));
208 int CheckFlags P((void));
209 long NextTickLength P((long));
210 void CheckTimeControl P((void));
211 void show_bytes P((FILE *, char *, int));
212 int string_to_rating P((char *str));
213 void ParseFeatures P((char* args, ChessProgramState *cps));
214 void InitBackEnd3 P((void));
215 void FeatureDone P((ChessProgramState* cps, int val));
216 void InitChessProgram P((ChessProgramState *cps, int setup));
217 void OutputKibitz(int window, char *text);
218 int PerpetualChase(int first, int last);
219 int EngineOutputIsUp();
220 void InitDrawingSizes(int x, int y);
223 extern void ConsoleCreate();
226 ChessProgramState *WhitePlayer();
227 void InsertIntoMemo P((int which, char *text)); // [HGM] kibitz: in engineo.c
228 int VerifyDisplayMode P(());
230 char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment
231 void InitEngineUCI( const char * iniDir, ChessProgramState * cps ); // [HGM] moved here from winboard.c
232 char *ProbeBook P((int moveNr, char *book)); // [HGM] book: returns a book move
233 char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book
234 void ics_update_width P((int new_width));
235 extern char installDir[MSG_SIZ];
237 extern int tinyLayout, smallLayout;
238 ChessProgramStats programStats;
239 char lastPV[2][2*MSG_SIZ]; /* [HGM] pv: last PV in thinking output of each engine */
241 static int exiting = 0; /* [HGM] moved to top */
242 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
243 int startedFromPositionFile = FALSE; Board filePosition; /* [HGM] loadPos */
244 char endingGame = 0; /* [HGM] crash: flag to prevent recursion of GameEnds() */
245 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS */
246 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
247 int lastIndex = 0; /* [HGM] autoinc: last game/position used in match mode */
248 Boolean connectionAlive;/* [HGM] alive: ICS connection status from probing */
249 int opponentKibitzes;
250 int lastSavedGame; /* [HGM] save: ID of game */
251 char chatPartner[MAX_CHAT][MSG_SIZ]; /* [HGM] chat: list of chatting partners */
252 extern int chatCount;
254 char marker[BOARD_RANKS][BOARD_FILES]; /* [HGM] marks for target squares */
256 /* States for ics_getting_history */
258 #define H_REQUESTED 1
259 #define H_GOT_REQ_HEADER 2
260 #define H_GOT_UNREQ_HEADER 3
261 #define H_GETTING_MOVES 4
262 #define H_GOT_UNWANTED_HEADER 5
264 /* whosays values for GameEnds */
273 /* Maximum number of games in a cmail message */
274 #define CMAIL_MAX_GAMES 20
276 /* Different types of move when calling RegisterMove */
278 #define CMAIL_RESIGN 1
280 #define CMAIL_ACCEPT 3
282 /* Different types of result to remember for each game */
283 #define CMAIL_NOT_RESULT 0
284 #define CMAIL_OLD_RESULT 1
285 #define CMAIL_NEW_RESULT 2
287 /* Telnet protocol constants */
298 static char * safeStrCpy( char * dst, const char * src, size_t count )
300 assert( dst != NULL );
301 assert( src != NULL );
304 strncpy( dst, src, count );
305 dst[ count-1 ] = '\0';
309 /* Some compiler can't cast u64 to double
310 * This function do the job for us:
312 * We use the highest bit for cast, this only
313 * works if the highest bit is not
314 * in use (This should not happen)
316 * We used this for all compiler
319 u64ToDouble(u64 value)
322 u64 tmp = value & u64Const(0x7fffffffffffffff);
323 r = (double)(s64)tmp;
324 if (value & u64Const(0x8000000000000000))
325 r += 9.2233720368547758080e18; /* 2^63 */
329 /* Fake up flags for now, as we aren't keeping track of castling
330 availability yet. [HGM] Change of logic: the flag now only
331 indicates the type of castlings allowed by the rule of the game.
332 The actual rights themselves are maintained in the array
333 castlingRights, as part of the game history, and are not probed
339 int flags = F_ALL_CASTLE_OK;
340 if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
341 switch (gameInfo.variant) {
343 flags &= ~F_ALL_CASTLE_OK;
344 case VariantGiveaway: // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
345 flags |= F_IGNORE_CHECK;
347 flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
350 flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
352 case VariantKriegspiel:
353 flags |= F_KRIEGSPIEL_CAPTURE;
355 case VariantCapaRandom:
356 case VariantFischeRandom:
357 flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
358 case VariantNoCastle:
359 case VariantShatranj:
362 flags &= ~F_ALL_CASTLE_OK;
370 FILE *gameFileFP, *debugFP;
373 [AS] Note: sometimes, the sscanf() function is used to parse the input
374 into a fixed-size buffer. Because of this, we must be prepared to
375 receive strings as long as the size of the input buffer, which is currently
376 set to 4K for Windows and 8K for the rest.
377 So, we must either allocate sufficiently large buffers here, or
378 reduce the size of the input buffer in the input reading part.
381 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
382 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
383 char thinkOutput1[MSG_SIZ*10];
385 ChessProgramState first, second;
387 /* premove variables */
390 int premoveFromX = 0;
391 int premoveFromY = 0;
392 int premovePromoChar = 0;
394 Boolean alarmSounded;
395 /* end premove variables */
397 char *ics_prefix = "$";
398 int ics_type = ICS_GENERIC;
400 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
401 int pauseExamForwardMostMove = 0;
402 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
403 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
404 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
405 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
406 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
407 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
408 int whiteFlag = FALSE, blackFlag = FALSE;
409 int userOfferedDraw = FALSE;
410 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
411 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
412 int cmailMoveType[CMAIL_MAX_GAMES];
413 long ics_clock_paused = 0;
414 ProcRef icsPR = NoProc, cmailPR = NoProc;
415 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
416 GameMode gameMode = BeginningOfGame;
417 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
418 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
419 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
420 int hiddenThinkOutputState = 0; /* [AS] */
421 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
422 int adjudicateLossPlies = 6;
423 char white_holding[64], black_holding[64];
424 TimeMark lastNodeCountTime;
425 long lastNodeCount=0;
426 int have_sent_ICS_logon = 0;
428 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement;
429 long timeControl_2; /* [AS] Allow separate time controls */
430 char *fullTimeControlString = NULL; /* [HGM] secondary TC: merge of MPS, TC and inc */
431 long timeRemaining[2][MAX_MOVES];
433 TimeMark programStartTime;
434 char ics_handle[MSG_SIZ];
435 int have_set_title = 0;
437 /* animateTraining preserves the state of appData.animate
438 * when Training mode is activated. This allows the
439 * response to be animated when appData.animate == TRUE and
440 * appData.animateDragging == TRUE.
442 Boolean animateTraining;
448 Board boards[MAX_MOVES];
449 /* [HGM] Following 7 needed for accurate legality tests: */
450 signed char castlingRank[BOARD_FILES]; // and corresponding ranks
451 signed char initialRights[BOARD_FILES];
452 int nrCastlingRights; // For TwoKings, or to implement castling-unknown status
453 int initialRulePlies, FENrulePlies;
454 FILE *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
457 int mute; // mute all sounds
459 // [HGM] vari: next 12 to save and restore variations
460 #define MAX_VARIATIONS 10
461 int framePtr = MAX_MOVES-1; // points to free stack entry
463 int savedFirst[MAX_VARIATIONS];
464 int savedLast[MAX_VARIATIONS];
465 int savedFramePtr[MAX_VARIATIONS];
466 char *savedDetails[MAX_VARIATIONS];
467 ChessMove savedResult[MAX_VARIATIONS];
469 void PushTail P((int firstMove, int lastMove));
470 Boolean PopTail P((Boolean annotate));
471 void CleanupTail P((void));
473 ChessSquare FIDEArray[2][BOARD_FILES] = {
474 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
475 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
476 { BlackRook, BlackKnight, BlackBishop, BlackQueen,
477 BlackKing, BlackBishop, BlackKnight, BlackRook }
480 ChessSquare twoKingsArray[2][BOARD_FILES] = {
481 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
482 WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
483 { BlackRook, BlackKnight, BlackBishop, BlackQueen,
484 BlackKing, BlackKing, BlackKnight, BlackRook }
487 ChessSquare KnightmateArray[2][BOARD_FILES] = {
488 { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
489 WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
490 { BlackRook, BlackMan, BlackBishop, BlackQueen,
491 BlackUnicorn, BlackBishop, BlackMan, BlackRook }
494 ChessSquare fairyArray[2][BOARD_FILES] = { /* [HGM] Queen side differs from King side */
495 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
496 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
497 { BlackLance, BlackAlfil, BlackMarshall, BlackAngel,
498 BlackKing, BlackMarshall, BlackAlfil, BlackLance }
501 ChessSquare ShatranjArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
502 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
503 WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
504 { BlackRook, BlackKnight, BlackAlfil, BlackKing,
505 BlackFerz, BlackAlfil, BlackKnight, BlackRook }
508 ChessSquare makrukArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
509 { WhiteRook, WhiteKnight, WhiteMan, WhiteKing,
510 WhiteFerz, WhiteMan, WhiteKnight, WhiteRook },
511 { BlackRook, BlackKnight, BlackMan, BlackFerz,
512 BlackKing, BlackMan, BlackKnight, BlackRook }
516 #if (BOARD_FILES>=10)
517 ChessSquare ShogiArray[2][BOARD_FILES] = {
518 { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
519 WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
520 { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
521 BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
524 ChessSquare XiangqiArray[2][BOARD_FILES] = {
525 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
526 WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
527 { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
528 BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
531 ChessSquare CapablancaArray[2][BOARD_FILES] = {
532 { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
533 WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
534 { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
535 BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
538 ChessSquare GreatArray[2][BOARD_FILES] = {
539 { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
540 WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
541 { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
542 BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
545 ChessSquare JanusArray[2][BOARD_FILES] = {
546 { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
547 WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
548 { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
549 BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
553 ChessSquare GothicArray[2][BOARD_FILES] = {
554 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
555 WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
556 { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
557 BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
560 #define GothicArray CapablancaArray
564 ChessSquare FalconArray[2][BOARD_FILES] = {
565 { WhiteRook, WhiteKnight, WhiteBishop, WhiteLance, WhiteQueen,
566 WhiteKing, WhiteLance, WhiteBishop, WhiteKnight, WhiteRook },
567 { BlackRook, BlackKnight, BlackBishop, BlackLance, BlackQueen,
568 BlackKing, BlackLance, BlackBishop, BlackKnight, BlackRook }
571 #define FalconArray CapablancaArray
574 #else // !(BOARD_FILES>=10)
575 #define XiangqiPosition FIDEArray
576 #define CapablancaArray FIDEArray
577 #define GothicArray FIDEArray
578 #define GreatArray FIDEArray
579 #endif // !(BOARD_FILES>=10)
581 #if (BOARD_FILES>=12)
582 ChessSquare CourierArray[2][BOARD_FILES] = {
583 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
584 WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
585 { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
586 BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
588 #else // !(BOARD_FILES>=12)
589 #define CourierArray CapablancaArray
590 #endif // !(BOARD_FILES>=12)
593 Board initialPosition;
596 /* Convert str to a rating. Checks for special cases of "----",
598 "++++", etc. Also strips ()'s */
600 string_to_rating(str)
603 while(*str && !isdigit(*str)) ++str;
605 return 0; /* One of the special "no rating" cases */
613 /* Init programStats */
614 programStats.movelist[0] = 0;
615 programStats.depth = 0;
616 programStats.nr_moves = 0;
617 programStats.moves_left = 0;
618 programStats.nodes = 0;
619 programStats.time = -1; // [HGM] PGNtime: make invalid to recognize engine output
620 programStats.score = 0;
621 programStats.got_only_move = 0;
622 programStats.got_fail = 0;
623 programStats.line_is_book = 0;
629 int matched, min, sec;
631 ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
633 GetTimeMark(&programStartTime);
634 srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
637 programStats.ok_to_send = 1;
638 programStats.seen_stat = 0;
641 * Initialize game list
647 * Internet chess server status
649 if (appData.icsActive) {
650 appData.matchMode = FALSE;
651 appData.matchGames = 0;
653 appData.noChessProgram = !appData.zippyPlay;
655 appData.zippyPlay = FALSE;
656 appData.zippyTalk = FALSE;
657 appData.noChessProgram = TRUE;
659 if (*appData.icsHelper != NULLCHAR) {
660 appData.useTelnet = TRUE;
661 appData.telnetProgram = appData.icsHelper;
664 appData.zippyTalk = appData.zippyPlay = FALSE;
667 /* [AS] Initialize pv info list [HGM] and game state */
671 for( i=0; i<=framePtr; i++ ) {
672 pvInfoList[i].depth = -1;
673 boards[i][EP_STATUS] = EP_NONE;
674 for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
679 * Parse timeControl resource
681 if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
682 appData.movesPerSession)) {
684 snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
685 DisplayFatalError(buf, 0, 2);
689 * Parse searchTime resource
691 if (*appData.searchTime != NULLCHAR) {
692 matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
694 searchTime = min * 60;
695 } else if (matched == 2) {
696 searchTime = min * 60 + sec;
699 snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
700 DisplayFatalError(buf, 0, 2);
704 /* [AS] Adjudication threshold */
705 adjudicateLossThreshold = appData.adjudicateLossThreshold;
707 first.which = "first";
708 second.which = "second";
709 first.maybeThinking = second.maybeThinking = FALSE;
710 first.pr = second.pr = NoProc;
711 first.isr = second.isr = NULL;
712 first.sendTime = second.sendTime = 2;
713 first.sendDrawOffers = 1;
714 if (appData.firstPlaysBlack) {
715 first.twoMachinesColor = "black\n";
716 second.twoMachinesColor = "white\n";
718 first.twoMachinesColor = "white\n";
719 second.twoMachinesColor = "black\n";
721 first.program = appData.firstChessProgram;
722 second.program = appData.secondChessProgram;
723 first.host = appData.firstHost;
724 second.host = appData.secondHost;
725 first.dir = appData.firstDirectory;
726 second.dir = appData.secondDirectory;
727 first.other = &second;
728 second.other = &first;
729 first.initString = appData.initString;
730 second.initString = appData.secondInitString;
731 first.computerString = appData.firstComputerString;
732 second.computerString = appData.secondComputerString;
733 first.useSigint = second.useSigint = TRUE;
734 first.useSigterm = second.useSigterm = TRUE;
735 first.reuse = appData.reuseFirst;
736 second.reuse = appData.reuseSecond;
737 first.nps = appData.firstNPS; // [HGM] nps: copy nodes per second
738 second.nps = appData.secondNPS;
739 first.useSetboard = second.useSetboard = FALSE;
740 first.useSAN = second.useSAN = FALSE;
741 first.usePing = second.usePing = FALSE;
742 first.lastPing = second.lastPing = 0;
743 first.lastPong = second.lastPong = 0;
744 first.usePlayother = second.usePlayother = FALSE;
745 first.useColors = second.useColors = TRUE;
746 first.useUsermove = second.useUsermove = FALSE;
747 first.sendICS = second.sendICS = FALSE;
748 first.sendName = second.sendName = appData.icsActive;
749 first.sdKludge = second.sdKludge = FALSE;
750 first.stKludge = second.stKludge = FALSE;
751 TidyProgramName(first.program, first.host, first.tidy);
752 TidyProgramName(second.program, second.host, second.tidy);
753 first.matchWins = second.matchWins = 0;
754 strcpy(first.variants, appData.variant);
755 strcpy(second.variants, appData.variant);
756 first.analysisSupport = second.analysisSupport = 2; /* detect */
757 first.analyzing = second.analyzing = FALSE;
758 first.initDone = second.initDone = FALSE;
760 /* New features added by Tord: */
761 first.useFEN960 = FALSE; second.useFEN960 = FALSE;
762 first.useOOCastle = TRUE; second.useOOCastle = TRUE;
763 /* End of new features added by Tord. */
764 first.fenOverride = appData.fenOverride1;
765 second.fenOverride = appData.fenOverride2;
767 /* [HGM] time odds: set factor for each machine */
768 first.timeOdds = appData.firstTimeOdds;
769 second.timeOdds = appData.secondTimeOdds;
771 if(appData.timeOddsMode) {
772 norm = first.timeOdds;
773 if(norm > second.timeOdds) norm = second.timeOdds;
775 first.timeOdds /= norm;
776 second.timeOdds /= norm;
779 /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
780 first.accumulateTC = appData.firstAccumulateTC;
781 second.accumulateTC = appData.secondAccumulateTC;
782 first.maxNrOfSessions = second.maxNrOfSessions = 1;
785 first.debug = second.debug = FALSE;
786 first.supportsNPS = second.supportsNPS = UNKNOWN;
789 first.optionSettings = appData.firstOptions;
790 second.optionSettings = appData.secondOptions;
792 first.scoreIsAbsolute = appData.firstScoreIsAbsolute; /* [AS] */
793 second.scoreIsAbsolute = appData.secondScoreIsAbsolute; /* [AS] */
794 first.isUCI = appData.firstIsUCI; /* [AS] */
795 second.isUCI = appData.secondIsUCI; /* [AS] */
796 first.hasOwnBookUCI = appData.firstHasOwnBookUCI; /* [AS] */
797 second.hasOwnBookUCI = appData.secondHasOwnBookUCI; /* [AS] */
799 if (appData.firstProtocolVersion > PROTOVER ||
800 appData.firstProtocolVersion < 1) {
802 sprintf(buf, _("protocol version %d not supported"),
803 appData.firstProtocolVersion);
804 DisplayFatalError(buf, 0, 2);
806 first.protocolVersion = appData.firstProtocolVersion;
809 if (appData.secondProtocolVersion > PROTOVER ||
810 appData.secondProtocolVersion < 1) {
812 sprintf(buf, _("protocol version %d not supported"),
813 appData.secondProtocolVersion);
814 DisplayFatalError(buf, 0, 2);
816 second.protocolVersion = appData.secondProtocolVersion;
819 if (appData.icsActive) {
820 appData.clockMode = TRUE; /* changes dynamically in ICS mode */
821 // } else if (*appData.searchTime != NULLCHAR || appData.noChessProgram) {
822 } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
823 appData.clockMode = FALSE;
824 first.sendTime = second.sendTime = 0;
828 /* Override some settings from environment variables, for backward
829 compatibility. Unfortunately it's not feasible to have the env
830 vars just set defaults, at least in xboard. Ugh.
832 if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
837 if (appData.noChessProgram) {
838 programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
839 sprintf(programVersion, "%s", PACKAGE_STRING);
841 /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
842 programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
843 sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
846 if (!appData.icsActive) {
848 /* Check for variants that are supported only in ICS mode,
849 or not at all. Some that are accepted here nevertheless
850 have bugs; see comments below.
852 VariantClass variant = StringToVariant(appData.variant);
854 case VariantBughouse: /* need four players and two boards */
855 case VariantKriegspiel: /* need to hide pieces and move details */
856 /* case VariantFischeRandom: (Fabien: moved below) */
857 sprintf(buf, _("Variant %s supported only in ICS mode"), appData.variant);
858 DisplayFatalError(buf, 0, 2);
862 case VariantLoadable:
872 sprintf(buf, _("Unknown variant name %s"), appData.variant);
873 DisplayFatalError(buf, 0, 2);
876 case VariantXiangqi: /* [HGM] repetition rules not implemented */
877 case VariantFairy: /* [HGM] TestLegality definitely off! */
878 case VariantGothic: /* [HGM] should work */
879 case VariantCapablanca: /* [HGM] should work */
880 case VariantCourier: /* [HGM] initial forced moves not implemented */
881 case VariantShogi: /* [HGM] drops not tested for legality */
882 case VariantKnightmate: /* [HGM] should work */
883 case VariantCylinder: /* [HGM] untested */
884 case VariantFalcon: /* [HGM] untested */
885 case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
886 offboard interposition not understood */
887 case VariantNormal: /* definitely works! */
888 case VariantWildCastle: /* pieces not automatically shuffled */
889 case VariantNoCastle: /* pieces not automatically shuffled */
890 case VariantFischeRandom: /* [HGM] works and shuffles pieces */
891 case VariantLosers: /* should work except for win condition,
892 and doesn't know captures are mandatory */
893 case VariantSuicide: /* should work except for win condition,
894 and doesn't know captures are mandatory */
895 case VariantGiveaway: /* should work except for win condition,
896 and doesn't know captures are mandatory */
897 case VariantTwoKings: /* should work */
898 case VariantAtomic: /* should work except for win condition */
899 case Variant3Check: /* should work except for win condition */
900 case VariantShatranj: /* should work except for all win conditions */
901 case VariantMakruk: /* should work except for daw countdown */
902 case VariantBerolina: /* might work if TestLegality is off */
903 case VariantCapaRandom: /* should work */
904 case VariantJanus: /* should work */
905 case VariantSuper: /* experimental */
906 case VariantGreat: /* experimental, requires legality testing to be off */
911 InitEngineUCI( installDir, &first ); // [HGM] moved here from winboard.c, to make available in xboard
912 InitEngineUCI( installDir, &second );
915 int NextIntegerFromString( char ** str, long * value )
920 while( *s == ' ' || *s == '\t' ) {
926 if( *s >= '0' && *s <= '9' ) {
927 while( *s >= '0' && *s <= '9' ) {
928 *value = *value * 10 + (*s - '0');
940 int NextTimeControlFromString( char ** str, long * value )
943 int result = NextIntegerFromString( str, &temp );
946 *value = temp * 60; /* Minutes */
949 result = NextIntegerFromString( str, &temp );
950 *value += temp; /* Seconds */
957 int NextSessionFromString( char ** str, int *moves, long * tc, long *inc)
958 { /* [HGM] routine added to read '+moves/time' for secondary time control */
959 int result = -1; long temp, temp2;
961 if(**str != '+') return -1; // old params remain in force!
963 if( NextTimeControlFromString( str, &temp ) ) return -1;
966 /* time only: incremental or sudden-death time control */
967 if(**str == '+') { /* increment follows; read it */
969 if(result = NextIntegerFromString( str, &temp2)) return -1;
972 *moves = 0; *tc = temp * 1000;
974 } else if(temp % 60 != 0) return -1; /* moves was given as min:sec */
976 (*str)++; /* classical time control */
977 result = NextTimeControlFromString( str, &temp2);
986 int GetTimeQuota(int movenr)
987 { /* [HGM] get time to add from the multi-session time-control string */
988 int moves=1; /* kludge to force reading of first session */
989 long time, increment;
990 char *s = fullTimeControlString;
992 if(appData.debugMode) fprintf(debugFP, "TC string = '%s'\n", fullTimeControlString);
994 if(moves) NextSessionFromString(&s, &moves, &time, &increment);
995 if(appData.debugMode) fprintf(debugFP, "mps=%d tc=%d inc=%d\n", moves, (int) time, (int) increment);
996 if(movenr == -1) return time; /* last move before new session */
997 if(!moves) return increment; /* current session is incremental */
998 if(movenr >= 0) movenr -= moves; /* we already finished this session */
999 } while(movenr >= -1); /* try again for next session */
1001 return 0; // no new time quota on this move
1005 ParseTimeControl(tc, ti, mps)
1014 if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1017 sprintf(buf, "+%d/%s+%d", mps, tc, ti);
1018 else sprintf(buf, "+%s+%d", tc, ti);
1021 sprintf(buf, "+%d/%s", mps, tc);
1022 else sprintf(buf, "+%s", tc);
1024 fullTimeControlString = StrSave(buf);
1026 if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1031 /* Parse second time control */
1034 if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1042 timeControl_2 = tc2 * 1000;
1052 timeControl = tc1 * 1000;
1055 timeIncrement = ti * 1000; /* convert to ms */
1056 movesPerSession = 0;
1059 movesPerSession = mps;
1067 if (appData.debugMode) {
1068 fprintf(debugFP, "%s\n", programVersion);
1071 set_cont_sequence(appData.wrapContSeq);
1072 if (appData.matchGames > 0) {
1073 appData.matchMode = TRUE;
1074 } else if (appData.matchMode) {
1075 appData.matchGames = 1;
1077 if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1078 appData.matchGames = appData.sameColorGames;
1079 if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1080 if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1081 if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1084 if (appData.noChessProgram || first.protocolVersion == 1) {
1087 /* kludge: allow timeout for initial "feature" commands */
1089 DisplayMessage("", _("Starting chess program"));
1090 ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1095 InitBackEnd3 P((void))
1097 GameMode initialMode;
1101 InitChessProgram(&first, startedFromSetupPosition);
1104 if (appData.icsActive) {
1106 /* [DM] Make a console window if needed [HGM] merged ifs */
1111 if (*appData.icsCommPort != NULLCHAR) {
1112 sprintf(buf, _("Could not open comm port %s"),
1113 appData.icsCommPort);
1115 snprintf(buf, sizeof(buf), _("Could not connect to host %s, port %s"),
1116 appData.icsHost, appData.icsPort);
1118 DisplayFatalError(buf, err, 1);
1123 AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1125 AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1126 if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1127 ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1128 } else if (appData.noChessProgram) {
1134 if (*appData.cmailGameName != NULLCHAR) {
1136 OpenLoopback(&cmailPR);
1138 AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1142 DisplayMessage("", "");
1143 if (StrCaseCmp(appData.initialMode, "") == 0) {
1144 initialMode = BeginningOfGame;
1145 } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1146 initialMode = TwoMachinesPlay;
1147 } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1148 initialMode = AnalyzeFile;
1149 } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1150 initialMode = AnalyzeMode;
1151 } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1152 initialMode = MachinePlaysWhite;
1153 } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1154 initialMode = MachinePlaysBlack;
1155 } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1156 initialMode = EditGame;
1157 } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1158 initialMode = EditPosition;
1159 } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1160 initialMode = Training;
1162 sprintf(buf, _("Unknown initialMode %s"), appData.initialMode);
1163 DisplayFatalError(buf, 0, 2);
1167 if (appData.matchMode) {
1168 /* Set up machine vs. machine match */
1169 if (appData.noChessProgram) {
1170 DisplayFatalError(_("Can't have a match with no chess programs"),
1176 if (*appData.loadGameFile != NULLCHAR) {
1177 int index = appData.loadGameIndex; // [HGM] autoinc
1178 if(index<0) lastIndex = index = 1;
1179 if (!LoadGameFromFile(appData.loadGameFile,
1181 appData.loadGameFile, FALSE)) {
1182 DisplayFatalError(_("Bad game file"), 0, 1);
1185 } else if (*appData.loadPositionFile != NULLCHAR) {
1186 int index = appData.loadPositionIndex; // [HGM] autoinc
1187 if(index<0) lastIndex = index = 1;
1188 if (!LoadPositionFromFile(appData.loadPositionFile,
1190 appData.loadPositionFile)) {
1191 DisplayFatalError(_("Bad position file"), 0, 1);
1196 } else if (*appData.cmailGameName != NULLCHAR) {
1197 /* Set up cmail mode */
1198 ReloadCmailMsgEvent(TRUE);
1200 /* Set up other modes */
1201 if (initialMode == AnalyzeFile) {
1202 if (*appData.loadGameFile == NULLCHAR) {
1203 DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1207 if (*appData.loadGameFile != NULLCHAR) {
1208 (void) LoadGameFromFile(appData.loadGameFile,
1209 appData.loadGameIndex,
1210 appData.loadGameFile, TRUE);
1211 } else if (*appData.loadPositionFile != NULLCHAR) {
1212 (void) LoadPositionFromFile(appData.loadPositionFile,
1213 appData.loadPositionIndex,
1214 appData.loadPositionFile);
1215 /* [HGM] try to make self-starting even after FEN load */
1216 /* to allow automatic setup of fairy variants with wtm */
1217 if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1218 gameMode = BeginningOfGame;
1219 setboardSpoiledMachineBlack = 1;
1221 /* [HGM] loadPos: make that every new game uses the setup */
1222 /* from file as long as we do not switch variant */
1223 if(!blackPlaysFirst) {
1224 startedFromPositionFile = TRUE;
1225 CopyBoard(filePosition, boards[0]);
1228 if (initialMode == AnalyzeMode) {
1229 if (appData.noChessProgram) {
1230 DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1233 if (appData.icsActive) {
1234 DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1238 } else if (initialMode == AnalyzeFile) {
1239 appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1240 ShowThinkingEvent();
1242 AnalysisPeriodicEvent(1);
1243 } else if (initialMode == MachinePlaysWhite) {
1244 if (appData.noChessProgram) {
1245 DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1249 if (appData.icsActive) {
1250 DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1254 MachineWhiteEvent();
1255 } else if (initialMode == MachinePlaysBlack) {
1256 if (appData.noChessProgram) {
1257 DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1261 if (appData.icsActive) {
1262 DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1266 MachineBlackEvent();
1267 } else if (initialMode == TwoMachinesPlay) {
1268 if (appData.noChessProgram) {
1269 DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1273 if (appData.icsActive) {
1274 DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1279 } else if (initialMode == EditGame) {
1281 } else if (initialMode == EditPosition) {
1282 EditPositionEvent();
1283 } else if (initialMode == Training) {
1284 if (*appData.loadGameFile == NULLCHAR) {
1285 DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1294 * Establish will establish a contact to a remote host.port.
1295 * Sets icsPR to a ProcRef for a process (or pseudo-process)
1296 * used to talk to the host.
1297 * Returns 0 if okay, error code if not.
1304 if (*appData.icsCommPort != NULLCHAR) {
1305 /* Talk to the host through a serial comm port */
1306 return OpenCommPort(appData.icsCommPort, &icsPR);
1308 } else if (*appData.gateway != NULLCHAR) {
1309 if (*appData.remoteShell == NULLCHAR) {
1310 /* Use the rcmd protocol to run telnet program on a gateway host */
1311 snprintf(buf, sizeof(buf), "%s %s %s",
1312 appData.telnetProgram, appData.icsHost, appData.icsPort);
1313 return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1316 /* Use the rsh program to run telnet program on a gateway host */
1317 if (*appData.remoteUser == NULLCHAR) {
1318 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1319 appData.gateway, appData.telnetProgram,
1320 appData.icsHost, appData.icsPort);
1322 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1323 appData.remoteShell, appData.gateway,
1324 appData.remoteUser, appData.telnetProgram,
1325 appData.icsHost, appData.icsPort);
1327 return StartChildProcess(buf, "", &icsPR);
1330 } else if (appData.useTelnet) {
1331 return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1334 /* TCP socket interface differs somewhat between
1335 Unix and NT; handle details in the front end.
1337 return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1342 show_bytes(fp, buf, count)
1348 if (*buf < 040 || *(unsigned char *) buf > 0177) {
1349 fprintf(fp, "\\%03o", *buf & 0xff);
1358 /* Returns an errno value */
1360 OutputMaybeTelnet(pr, message, count, outError)
1366 char buf[8192], *p, *q, *buflim;
1367 int left, newcount, outcount;
1369 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1370 *appData.gateway != NULLCHAR) {
1371 if (appData.debugMode) {
1372 fprintf(debugFP, ">ICS: ");
1373 show_bytes(debugFP, message, count);
1374 fprintf(debugFP, "\n");
1376 return OutputToProcess(pr, message, count, outError);
1379 buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1386 if (appData.debugMode) {
1387 fprintf(debugFP, ">ICS: ");
1388 show_bytes(debugFP, buf, newcount);
1389 fprintf(debugFP, "\n");
1391 outcount = OutputToProcess(pr, buf, newcount, outError);
1392 if (outcount < newcount) return -1; /* to be sure */
1399 } else if (((unsigned char) *p) == TN_IAC) {
1400 *q++ = (char) TN_IAC;
1407 if (appData.debugMode) {
1408 fprintf(debugFP, ">ICS: ");
1409 show_bytes(debugFP, buf, newcount);
1410 fprintf(debugFP, "\n");
1412 outcount = OutputToProcess(pr, buf, newcount, outError);
1413 if (outcount < newcount) return -1; /* to be sure */
1418 read_from_player(isr, closure, message, count, error)
1425 int outError, outCount;
1426 static int gotEof = 0;
1428 /* Pass data read from player on to ICS */
1431 outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1432 if (outCount < count) {
1433 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1435 } else if (count < 0) {
1436 RemoveInputSource(isr);
1437 DisplayFatalError(_("Error reading from keyboard"), error, 1);
1438 } else if (gotEof++ > 0) {
1439 RemoveInputSource(isr);
1440 DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1446 { // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1447 if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1448 connectionAlive = FALSE; // only sticks if no response to 'date' command.
1449 SendToICS("date\n");
1450 if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1453 /* added routine for printf style output to ics */
1454 void ics_printf(char *format, ...)
1456 char buffer[MSG_SIZ];
1459 va_start(args, format);
1460 vsnprintf(buffer, sizeof(buffer), format, args);
1461 buffer[sizeof(buffer)-1] = '\0';
1470 int count, outCount, outError;
1472 if (icsPR == NULL) return;
1475 outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1476 if (outCount < count) {
1477 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1481 /* This is used for sending logon scripts to the ICS. Sending
1482 without a delay causes problems when using timestamp on ICC
1483 (at least on my machine). */
1485 SendToICSDelayed(s,msdelay)
1489 int count, outCount, outError;
1491 if (icsPR == NULL) return;
1494 if (appData.debugMode) {
1495 fprintf(debugFP, ">ICS: ");
1496 show_bytes(debugFP, s, count);
1497 fprintf(debugFP, "\n");
1499 outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1501 if (outCount < count) {
1502 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1507 /* Remove all highlighting escape sequences in s
1508 Also deletes any suffix starting with '('
1511 StripHighlightAndTitle(s)
1514 static char retbuf[MSG_SIZ];
1517 while (*s != NULLCHAR) {
1518 while (*s == '\033') {
1519 while (*s != NULLCHAR && !isalpha(*s)) s++;
1520 if (*s != NULLCHAR) s++;
1522 while (*s != NULLCHAR && *s != '\033') {
1523 if (*s == '(' || *s == '[') {
1534 /* Remove all highlighting escape sequences in s */
1539 static char retbuf[MSG_SIZ];
1542 while (*s != NULLCHAR) {
1543 while (*s == '\033') {
1544 while (*s != NULLCHAR && !isalpha(*s)) s++;
1545 if (*s != NULLCHAR) s++;
1547 while (*s != NULLCHAR && *s != '\033') {
1555 char *variantNames[] = VARIANT_NAMES;
1560 return variantNames[v];
1564 /* Identify a variant from the strings the chess servers use or the
1565 PGN Variant tag names we use. */
1572 VariantClass v = VariantNormal;
1573 int i, found = FALSE;
1578 /* [HGM] skip over optional board-size prefixes */
1579 if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1580 sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1581 while( *e++ != '_');
1584 if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
1588 for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
1589 if (StrCaseStr(e, variantNames[i])) {
1590 v = (VariantClass) i;
1597 if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
1598 || StrCaseStr(e, "wild/fr")
1599 || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
1600 v = VariantFischeRandom;
1601 } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
1602 (i = 1, p = StrCaseStr(e, "w"))) {
1604 while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
1611 case 0: /* FICS only, actually */
1613 /* Castling legal even if K starts on d-file */
1614 v = VariantWildCastle;
1619 /* Castling illegal even if K & R happen to start in
1620 normal positions. */
1621 v = VariantNoCastle;
1634 /* Castling legal iff K & R start in normal positions */
1640 /* Special wilds for position setup; unclear what to do here */
1641 v = VariantLoadable;
1644 /* Bizarre ICC game */
1645 v = VariantTwoKings;
1648 v = VariantKriegspiel;
1654 v = VariantFischeRandom;
1657 v = VariantCrazyhouse;
1660 v = VariantBughouse;
1666 /* Not quite the same as FICS suicide! */
1667 v = VariantGiveaway;
1673 v = VariantShatranj;
1676 /* Temporary names for future ICC types. The name *will* change in
1677 the next xboard/WinBoard release after ICC defines it. */
1715 v = VariantCapablanca;
1718 v = VariantKnightmate;
1724 v = VariantCylinder;
1730 v = VariantCapaRandom;
1733 v = VariantBerolina;
1745 /* Found "wild" or "w" in the string but no number;
1746 must assume it's normal chess. */
1750 sprintf(buf, _("Unknown wild type %d"), wnum);
1751 DisplayError(buf, 0);
1757 if (appData.debugMode) {
1758 fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
1759 e, wnum, VariantName(v));
1764 static int leftover_start = 0, leftover_len = 0;
1765 char star_match[STAR_MATCH_N][MSG_SIZ];
1767 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
1768 advance *index beyond it, and set leftover_start to the new value of
1769 *index; else return FALSE. If pattern contains the character '*', it
1770 matches any sequence of characters not containing '\r', '\n', or the
1771 character following the '*' (if any), and the matched sequence(s) are
1772 copied into star_match.
1775 looking_at(buf, index, pattern)
1780 char *bufp = &buf[*index], *patternp = pattern;
1782 char *matchp = star_match[0];
1785 if (*patternp == NULLCHAR) {
1786 *index = leftover_start = bufp - buf;
1790 if (*bufp == NULLCHAR) return FALSE;
1791 if (*patternp == '*') {
1792 if (*bufp == *(patternp + 1)) {
1794 matchp = star_match[++star_count];
1798 } else if (*bufp == '\n' || *bufp == '\r') {
1800 if (*patternp == NULLCHAR)
1805 *matchp++ = *bufp++;
1809 if (*patternp != *bufp) return FALSE;
1816 SendToPlayer(data, length)
1820 int error, outCount;
1821 outCount = OutputToProcess(NoProc, data, length, &error);
1822 if (outCount < length) {
1823 DisplayFatalError(_("Error writing to display"), error, 1);
1828 PackHolding(packed, holding)
1840 switch (runlength) {
1851 sprintf(q, "%d", runlength);
1863 /* Telnet protocol requests from the front end */
1865 TelnetRequest(ddww, option)
1866 unsigned char ddww, option;
1868 unsigned char msg[3];
1869 int outCount, outError;
1871 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
1873 if (appData.debugMode) {
1874 char buf1[8], buf2[8], *ddwwStr, *optionStr;
1890 sprintf(buf1, "%d", ddww);
1899 sprintf(buf2, "%d", option);
1902 fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
1907 outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
1909 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1916 if (!appData.icsActive) return;
1917 TelnetRequest(TN_DO, TN_ECHO);
1923 if (!appData.icsActive) return;
1924 TelnetRequest(TN_DONT, TN_ECHO);
1928 CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece)
1930 /* put the holdings sent to us by the server on the board holdings area */
1931 int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
1935 if(gameInfo.holdingsWidth < 2) return;
1936 if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
1937 return; // prevent overwriting by pre-board holdings
1939 if( (int)lowestPiece >= BlackPawn ) {
1942 holdingsStartRow = BOARD_HEIGHT-1;
1945 holdingsColumn = BOARD_WIDTH-1;
1946 countsColumn = BOARD_WIDTH-2;
1947 holdingsStartRow = 0;
1951 for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
1952 board[i][holdingsColumn] = EmptySquare;
1953 board[i][countsColumn] = (ChessSquare) 0;
1955 while( (p=*holdings++) != NULLCHAR ) {
1956 piece = CharToPiece( ToUpper(p) );
1957 if(piece == EmptySquare) continue;
1958 /*j = (int) piece - (int) WhitePawn;*/
1959 j = PieceToNumber(piece);
1960 if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
1961 if(j < 0) continue; /* should not happen */
1962 piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
1963 board[holdingsStartRow+j*direction][holdingsColumn] = piece;
1964 board[holdingsStartRow+j*direction][countsColumn]++;
1970 VariantSwitch(Board board, VariantClass newVariant)
1972 int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
1975 startedFromPositionFile = FALSE;
1976 if(gameInfo.variant == newVariant) return;
1978 /* [HGM] This routine is called each time an assignment is made to
1979 * gameInfo.variant during a game, to make sure the board sizes
1980 * are set to match the new variant. If that means adding or deleting
1981 * holdings, we shift the playing board accordingly
1982 * This kludge is needed because in ICS observe mode, we get boards
1983 * of an ongoing game without knowing the variant, and learn about the
1984 * latter only later. This can be because of the move list we requested,
1985 * in which case the game history is refilled from the beginning anyway,
1986 * but also when receiving holdings of a crazyhouse game. In the latter
1987 * case we want to add those holdings to the already received position.
1991 if (appData.debugMode) {
1992 fprintf(debugFP, "Switch board from %s to %s\n",
1993 VariantName(gameInfo.variant), VariantName(newVariant));
1994 setbuf(debugFP, NULL);
1996 shuffleOpenings = 0; /* [HGM] shuffle */
1997 gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2001 newWidth = 9; newHeight = 9;
2002 gameInfo.holdingsSize = 7;
2003 case VariantBughouse:
2004 case VariantCrazyhouse:
2005 newHoldingsWidth = 2; break;
2009 newHoldingsWidth = 2;
2010 gameInfo.holdingsSize = 8;
2013 case VariantCapablanca:
2014 case VariantCapaRandom:
2017 newHoldingsWidth = gameInfo.holdingsSize = 0;
2020 if(newWidth != gameInfo.boardWidth ||
2021 newHeight != gameInfo.boardHeight ||
2022 newHoldingsWidth != gameInfo.holdingsWidth ) {
2024 /* shift position to new playing area, if needed */
2025 if(newHoldingsWidth > gameInfo.holdingsWidth) {
2026 for(i=0; i<BOARD_HEIGHT; i++)
2027 for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2028 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2030 for(i=0; i<newHeight; i++) {
2031 board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2032 board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2034 } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2035 for(i=0; i<BOARD_HEIGHT; i++)
2036 for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2037 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2040 gameInfo.boardWidth = newWidth;
2041 gameInfo.boardHeight = newHeight;
2042 gameInfo.holdingsWidth = newHoldingsWidth;
2043 gameInfo.variant = newVariant;
2044 InitDrawingSizes(-2, 0);
2045 } else gameInfo.variant = newVariant;
2046 CopyBoard(oldBoard, board); // remember correctly formatted board
2047 InitPosition(FALSE); /* this sets up board[0], but also other stuff */
2048 DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2051 static int loggedOn = FALSE;
2053 /*-- Game start info cache: --*/
2055 char gs_kind[MSG_SIZ];
2056 static char player1Name[128] = "";
2057 static char player2Name[128] = "";
2058 static char cont_seq[] = "\n\\ ";
2059 static int player1Rating = -1;
2060 static int player2Rating = -1;
2061 /*----------------------------*/
2063 ColorClass curColor = ColorNormal;
2064 int suppressKibitz = 0;
2067 read_from_ics(isr, closure, data, count, error)
2074 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2075 #define STARTED_NONE 0
2076 #define STARTED_MOVES 1
2077 #define STARTED_BOARD 2
2078 #define STARTED_OBSERVE 3
2079 #define STARTED_HOLDINGS 4
2080 #define STARTED_CHATTER 5
2081 #define STARTED_COMMENT 6
2082 #define STARTED_MOVES_NOHIDE 7
2084 static int started = STARTED_NONE;
2085 static char parse[20000];
2086 static int parse_pos = 0;
2087 static char buf[BUF_SIZE + 1];
2088 static int firstTime = TRUE, intfSet = FALSE;
2089 static ColorClass prevColor = ColorNormal;
2090 static int savingComment = FALSE;
2091 static int cmatch = 0; // continuation sequence match
2098 int backup; /* [DM] For zippy color lines */
2100 char talker[MSG_SIZ]; // [HGM] chat
2103 connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2105 if (appData.debugMode) {
2107 fprintf(debugFP, "<ICS: ");
2108 show_bytes(debugFP, data, count);
2109 fprintf(debugFP, "\n");
2113 if (appData.debugMode) { int f = forwardMostMove;
2114 fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2115 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2116 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2119 /* If last read ended with a partial line that we couldn't parse,
2120 prepend it to the new read and try again. */
2121 if (leftover_len > 0) {
2122 for (i=0; i<leftover_len; i++)
2123 buf[i] = buf[leftover_start + i];
2126 /* copy new characters into the buffer */
2127 bp = buf + leftover_len;
2128 buf_len=leftover_len;
2129 for (i=0; i<count; i++)
2132 if (data[i] == '\r')
2135 // join lines split by ICS?
2136 if (!appData.noJoin)
2139 Joining just consists of finding matches against the
2140 continuation sequence, and discarding that sequence
2141 if found instead of copying it. So, until a match
2142 fails, there's nothing to do since it might be the
2143 complete sequence, and thus, something we don't want
2146 if (data[i] == cont_seq[cmatch])
2149 if (cmatch == strlen(cont_seq))
2151 cmatch = 0; // complete match. just reset the counter
2154 it's possible for the ICS to not include the space
2155 at the end of the last word, making our [correct]
2156 join operation fuse two separate words. the server
2157 does this when the space occurs at the width setting.
2159 if (!buf_len || buf[buf_len-1] != ' ')
2170 match failed, so we have to copy what matched before
2171 falling through and copying this character. In reality,
2172 this will only ever be just the newline character, but
2173 it doesn't hurt to be precise.
2175 strncpy(bp, cont_seq, cmatch);
2187 buf[buf_len] = NULLCHAR;
2188 // next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2193 while (i < buf_len) {
2194 /* Deal with part of the TELNET option negotiation
2195 protocol. We refuse to do anything beyond the
2196 defaults, except that we allow the WILL ECHO option,
2197 which ICS uses to turn off password echoing when we are
2198 directly connected to it. We reject this option
2199 if localLineEditing mode is on (always on in xboard)
2200 and we are talking to port 23, which might be a real
2201 telnet server that will try to keep WILL ECHO on permanently.
2203 if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2204 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2205 unsigned char option;
2207 switch ((unsigned char) buf[++i]) {
2209 if (appData.debugMode)
2210 fprintf(debugFP, "\n<WILL ");
2211 switch (option = (unsigned char) buf[++i]) {
2213 if (appData.debugMode)
2214 fprintf(debugFP, "ECHO ");
2215 /* Reply only if this is a change, according
2216 to the protocol rules. */
2217 if (remoteEchoOption) break;
2218 if (appData.localLineEditing &&
2219 atoi(appData.icsPort) == TN_PORT) {
2220 TelnetRequest(TN_DONT, TN_ECHO);
2223 TelnetRequest(TN_DO, TN_ECHO);
2224 remoteEchoOption = TRUE;
2228 if (appData.debugMode)
2229 fprintf(debugFP, "%d ", option);
2230 /* Whatever this is, we don't want it. */
2231 TelnetRequest(TN_DONT, option);
2236 if (appData.debugMode)
2237 fprintf(debugFP, "\n<WONT ");
2238 switch (option = (unsigned char) buf[++i]) {
2240 if (appData.debugMode)
2241 fprintf(debugFP, "ECHO ");
2242 /* Reply only if this is a change, according
2243 to the protocol rules. */
2244 if (!remoteEchoOption) break;
2246 TelnetRequest(TN_DONT, TN_ECHO);
2247 remoteEchoOption = FALSE;
2250 if (appData.debugMode)
2251 fprintf(debugFP, "%d ", (unsigned char) option);
2252 /* Whatever this is, it must already be turned
2253 off, because we never agree to turn on
2254 anything non-default, so according to the
2255 protocol rules, we don't reply. */
2260 if (appData.debugMode)
2261 fprintf(debugFP, "\n<DO ");
2262 switch (option = (unsigned char) buf[++i]) {
2264 /* Whatever this is, we refuse to do it. */
2265 if (appData.debugMode)
2266 fprintf(debugFP, "%d ", option);
2267 TelnetRequest(TN_WONT, option);
2272 if (appData.debugMode)
2273 fprintf(debugFP, "\n<DONT ");
2274 switch (option = (unsigned char) buf[++i]) {
2276 if (appData.debugMode)
2277 fprintf(debugFP, "%d ", option);
2278 /* Whatever this is, we are already not doing
2279 it, because we never agree to do anything
2280 non-default, so according to the protocol
2281 rules, we don't reply. */
2286 if (appData.debugMode)
2287 fprintf(debugFP, "\n<IAC ");
2288 /* Doubled IAC; pass it through */
2292 if (appData.debugMode)
2293 fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2294 /* Drop all other telnet commands on the floor */
2297 if (oldi > next_out)
2298 SendToPlayer(&buf[next_out], oldi - next_out);
2304 /* OK, this at least will *usually* work */
2305 if (!loggedOn && looking_at(buf, &i, "ics%")) {
2309 if (loggedOn && !intfSet) {
2310 if (ics_type == ICS_ICC) {
2312 "/set-quietly interface %s\n/set-quietly style 12\n",
2314 } else if (ics_type == ICS_CHESSNET) {
2315 sprintf(str, "/style 12\n");
2317 strcpy(str, "alias $ @\n$set interface ");
2318 strcat(str, programVersion);
2319 strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2321 strcat(str, "$iset nohighlight 1\n");
2323 strcat(str, "$iset lock 1\n$style 12\n");
2326 NotifyFrontendLogin();
2330 if (started == STARTED_COMMENT) {
2331 /* Accumulate characters in comment */
2332 parse[parse_pos++] = buf[i];
2333 if (buf[i] == '\n') {
2334 parse[parse_pos] = NULLCHAR;
2335 if(chattingPartner>=0) {
2337 sprintf(mess, "%s%s", talker, parse);
2338 OutputChatMessage(chattingPartner, mess);
2339 chattingPartner = -1;
2341 if(!suppressKibitz) // [HGM] kibitz
2342 AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
2343 else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2344 int nrDigit = 0, nrAlph = 0, j;
2345 if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2346 { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2347 parse[parse_pos] = NULLCHAR;
2348 // try to be smart: if it does not look like search info, it should go to
2349 // ICS interaction window after all, not to engine-output window.
2350 for(j=0; j<parse_pos; j++) { // count letters and digits
2351 nrDigit += (parse[j] >= '0' && parse[j] <= '9');
2352 nrAlph += (parse[j] >= 'a' && parse[j] <= 'z');
2353 nrAlph += (parse[j] >= 'A' && parse[j] <= 'Z');
2355 if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2356 int depth=0; float score;
2357 if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2358 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2359 pvInfoList[forwardMostMove-1].depth = depth;
2360 pvInfoList[forwardMostMove-1].score = 100*score;
2362 OutputKibitz(suppressKibitz, parse);
2363 next_out = i+1; // [HGM] suppress printing in ICS window
2366 sprintf(tmp, _("your opponent kibitzes: %s"), parse);
2367 SendToPlayer(tmp, strlen(tmp));
2370 started = STARTED_NONE;
2372 /* Don't match patterns against characters in comment */
2377 if (started == STARTED_CHATTER) {
2378 if (buf[i] != '\n') {
2379 /* Don't match patterns against characters in chatter */
2383 started = STARTED_NONE;
2386 /* Kludge to deal with rcmd protocol */
2387 if (firstTime && looking_at(buf, &i, "\001*")) {
2388 DisplayFatalError(&buf[1], 0, 1);
2394 if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2397 if (appData.debugMode)
2398 fprintf(debugFP, "ics_type %d\n", ics_type);
2401 if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
2402 ics_type = ICS_FICS;
2404 if (appData.debugMode)
2405 fprintf(debugFP, "ics_type %d\n", ics_type);
2408 if (!loggedOn && looking_at(buf, &i, "chess.net")) {
2409 ics_type = ICS_CHESSNET;
2411 if (appData.debugMode)
2412 fprintf(debugFP, "ics_type %d\n", ics_type);
2417 (looking_at(buf, &i, "\"*\" is *a registered name") ||
2418 looking_at(buf, &i, "Logging you in as \"*\"") ||
2419 looking_at(buf, &i, "will be \"*\""))) {
2420 strcpy(ics_handle, star_match[0]);
2424 if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
2426 snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
2427 DisplayIcsInteractionTitle(buf);
2428 have_set_title = TRUE;
2431 /* skip finger notes */
2432 if (started == STARTED_NONE &&
2433 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
2434 (buf[i] == '1' && buf[i+1] == '0')) &&
2435 buf[i+2] == ':' && buf[i+3] == ' ') {
2436 started = STARTED_CHATTER;
2441 /* skip formula vars */
2442 if (started == STARTED_NONE &&
2443 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
2444 started = STARTED_CHATTER;
2450 // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
2451 if (appData.autoKibitz && started == STARTED_NONE &&
2452 !appData.icsEngineAnalyze && // [HGM] [DM] ICS analyze
2453 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
2454 if(looking_at(buf, &i, "* kibitzes: ") &&
2455 (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
2456 StrStr(star_match[0], gameInfo.black) == star_match[0] )) { // kibitz of self or opponent
2457 suppressKibitz = TRUE;
2458 if((StrStr(star_match[0], gameInfo.white) == star_match[0]
2459 && (gameMode == IcsPlayingWhite)) ||
2460 (StrStr(star_match[0], gameInfo.black) == star_match[0]
2461 && (gameMode == IcsPlayingBlack)) ) // opponent kibitz
2462 started = STARTED_CHATTER; // own kibitz we simply discard
2464 started = STARTED_COMMENT; // make sure it will be collected in parse[]
2465 parse_pos = 0; parse[0] = NULLCHAR;
2466 savingComment = TRUE;
2467 suppressKibitz = gameMode != IcsObserving ? 2 :
2468 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
2472 if(looking_at(buf, &i, "kibitzed to *\n") && atoi(star_match[0])) {
2473 // suppress the acknowledgements of our own autoKibitz
2474 SendToPlayer(star_match[0], strlen(star_match[0]));
2475 looking_at(buf, &i, "*% "); // eat prompt
2478 } // [HGM] kibitz: end of patch
2480 //if(appData.debugMode) fprintf(debugFP, "hunt for tell, buf = %s\n", buf+i);
2482 // [HGM] chat: intercept tells by users for which we have an open chat window
2484 if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
2485 looking_at(buf, &i, "* whispers:") ||
2486 looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
2487 looking_at(buf, &i, "*(*)(*):") && sscanf(star_match[2], "%d", &channel) == 1 )) {
2489 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
2490 chattingPartner = -1;
2492 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
2493 for(p=0; p<MAX_CHAT; p++) {
2494 if(channel == atoi(chatPartner[p])) {
2495 talker[0] = '['; strcat(talker, "]");
2496 chattingPartner = p; break;
2499 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
2500 for(p=0; p<MAX_CHAT; p++) {
2501 if(!strcmp("WHISPER", chatPartner[p])) {
2502 talker[0] = '['; strcat(talker, "]");
2503 chattingPartner = p; break;
2506 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
2507 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
2509 chattingPartner = p; break;
2511 if(chattingPartner<0) i = oldi; else {
2512 started = STARTED_COMMENT;
2513 parse_pos = 0; parse[0] = NULLCHAR;
2514 savingComment = TRUE;
2515 suppressKibitz = TRUE;
2517 } // [HGM] chat: end of patch
2519 if (appData.zippyTalk || appData.zippyPlay) {
2520 /* [DM] Backup address for color zippy lines */
2524 if (loggedOn == TRUE)
2525 if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
2526 (appData.zippyPlay && ZippyMatch(buf, &backup)));
2528 if (ZippyControl(buf, &i) ||
2529 ZippyConverse(buf, &i) ||
2530 (appData.zippyPlay && ZippyMatch(buf, &i))) {
2532 if (!appData.colorize) continue;
2536 } // [DM] 'else { ' deleted
2538 /* Regular tells and says */
2539 (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
2540 looking_at(buf, &i, "* (your partner) tells you: ") ||
2541 looking_at(buf, &i, "* says: ") ||
2542 /* Don't color "message" or "messages" output */
2543 (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
2544 looking_at(buf, &i, "*. * at *:*: ") ||
2545 looking_at(buf, &i, "--* (*:*): ") ||
2546 /* Message notifications (same color as tells) */
2547 looking_at(buf, &i, "* has left a message ") ||
2548 looking_at(buf, &i, "* just sent you a message:\n") ||
2549 /* Whispers and kibitzes */
2550 (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
2551 looking_at(buf, &i, "* kibitzes: ") ||
2553 (tkind = 3, looking_at(buf, &i, "*(*: "))) {
2555 if (tkind == 1 && strchr(star_match[0], ':')) {
2556 /* Avoid "tells you:" spoofs in channels */
2559 if (star_match[0][0] == NULLCHAR ||
2560 strchr(star_match[0], ' ') ||
2561 (tkind == 3 && strchr(star_match[1], ' '))) {
2562 /* Reject bogus matches */
2565 if (appData.colorize) {
2566 if (oldi > next_out) {
2567 SendToPlayer(&buf[next_out], oldi - next_out);
2572 Colorize(ColorTell, FALSE);
2573 curColor = ColorTell;
2576 Colorize(ColorKibitz, FALSE);
2577 curColor = ColorKibitz;
2580 p = strrchr(star_match[1], '(');
2587 Colorize(ColorChannel1, FALSE);
2588 curColor = ColorChannel1;
2590 Colorize(ColorChannel, FALSE);
2591 curColor = ColorChannel;
2595 curColor = ColorNormal;
2599 if (started == STARTED_NONE && appData.autoComment &&
2600 (gameMode == IcsObserving ||
2601 gameMode == IcsPlayingWhite ||
2602 gameMode == IcsPlayingBlack)) {
2603 parse_pos = i - oldi;
2604 memcpy(parse, &buf[oldi], parse_pos);
2605 parse[parse_pos] = NULLCHAR;
2606 started = STARTED_COMMENT;
2607 savingComment = TRUE;
2609 started = STARTED_CHATTER;
2610 savingComment = FALSE;
2617 if (looking_at(buf, &i, "* s-shouts: ") ||
2618 looking_at(buf, &i, "* c-shouts: ")) {
2619 if (appData.colorize) {
2620 if (oldi > next_out) {
2621 SendToPlayer(&buf[next_out], oldi - next_out);
2624 Colorize(ColorSShout, FALSE);
2625 curColor = ColorSShout;
2628 started = STARTED_CHATTER;
2632 if (looking_at(buf, &i, "--->")) {
2637 if (looking_at(buf, &i, "* shouts: ") ||
2638 looking_at(buf, &i, "--> ")) {
2639 if (appData.colorize) {
2640 if (oldi > next_out) {
2641 SendToPlayer(&buf[next_out], oldi - next_out);
2644 Colorize(ColorShout, FALSE);
2645 curColor = ColorShout;
2648 started = STARTED_CHATTER;
2652 if (looking_at( buf, &i, "Challenge:")) {
2653 if (appData.colorize) {
2654 if (oldi > next_out) {
2655 SendToPlayer(&buf[next_out], oldi - next_out);
2658 Colorize(ColorChallenge, FALSE);
2659 curColor = ColorChallenge;
2665 if (looking_at(buf, &i, "* offers you") ||
2666 looking_at(buf, &i, "* offers to be") ||
2667 looking_at(buf, &i, "* would like to") ||
2668 looking_at(buf, &i, "* requests to") ||
2669 looking_at(buf, &i, "Your opponent offers") ||
2670 looking_at(buf, &i, "Your opponent requests")) {
2672 if (appData.colorize) {
2673 if (oldi > next_out) {
2674 SendToPlayer(&buf[next_out], oldi - next_out);
2677 Colorize(ColorRequest, FALSE);
2678 curColor = ColorRequest;
2683 if (looking_at(buf, &i, "* (*) seeking")) {
2684 if (appData.colorize) {
2685 if (oldi > next_out) {
2686 SendToPlayer(&buf[next_out], oldi - next_out);
2689 Colorize(ColorSeek, FALSE);
2690 curColor = ColorSeek;
2695 if (looking_at(buf, &i, "\\ ")) {
2696 if (prevColor != ColorNormal) {
2697 if (oldi > next_out) {
2698 SendToPlayer(&buf[next_out], oldi - next_out);
2701 Colorize(prevColor, TRUE);
2702 curColor = prevColor;
2704 if (savingComment) {
2705 parse_pos = i - oldi;
2706 memcpy(parse, &buf[oldi], parse_pos);
2707 parse[parse_pos] = NULLCHAR;
2708 started = STARTED_COMMENT;
2710 started = STARTED_CHATTER;
2715 if (looking_at(buf, &i, "Black Strength :") ||
2716 looking_at(buf, &i, "<<< style 10 board >>>") ||
2717 looking_at(buf, &i, "<10>") ||
2718 looking_at(buf, &i, "#@#")) {
2719 /* Wrong board style */
2721 SendToICS(ics_prefix);
2722 SendToICS("set style 12\n");
2723 SendToICS(ics_prefix);
2724 SendToICS("refresh\n");
2728 if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
2730 have_sent_ICS_logon = 1;
2734 if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
2735 (looking_at(buf, &i, "\n<12> ") ||
2736 looking_at(buf, &i, "<12> "))) {
2738 if (oldi > next_out) {
2739 SendToPlayer(&buf[next_out], oldi - next_out);
2742 started = STARTED_BOARD;
2747 if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
2748 looking_at(buf, &i, "<b1> ")) {
2749 if (oldi > next_out) {
2750 SendToPlayer(&buf[next_out], oldi - next_out);
2753 started = STARTED_HOLDINGS;
2758 if (looking_at(buf, &i, "* *vs. * *--- *")) {
2760 /* Header for a move list -- first line */
2762 switch (ics_getting_history) {
2766 case BeginningOfGame:
2767 /* User typed "moves" or "oldmoves" while we
2768 were idle. Pretend we asked for these
2769 moves and soak them up so user can step
2770 through them and/or save them.
2773 gameMode = IcsObserving;
2776 ics_getting_history = H_GOT_UNREQ_HEADER;
2778 case EditGame: /*?*/
2779 case EditPosition: /*?*/
2780 /* Should above feature work in these modes too? */
2781 /* For now it doesn't */
2782 ics_getting_history = H_GOT_UNWANTED_HEADER;
2785 ics_getting_history = H_GOT_UNWANTED_HEADER;
2790 /* Is this the right one? */
2791 if (gameInfo.white && gameInfo.black &&
2792 strcmp(gameInfo.white, star_match[0]) == 0 &&
2793 strcmp(gameInfo.black, star_match[2]) == 0) {
2795 ics_getting_history = H_GOT_REQ_HEADER;
2798 case H_GOT_REQ_HEADER:
2799 case H_GOT_UNREQ_HEADER:
2800 case H_GOT_UNWANTED_HEADER:
2801 case H_GETTING_MOVES:
2802 /* Should not happen */
2803 DisplayError(_("Error gathering move list: two headers"), 0);
2804 ics_getting_history = H_FALSE;
2808 /* Save player ratings into gameInfo if needed */
2809 if ((ics_getting_history == H_GOT_REQ_HEADER ||
2810 ics_getting_history == H_GOT_UNREQ_HEADER) &&
2811 (gameInfo.whiteRating == -1 ||
2812 gameInfo.blackRating == -1)) {
2814 gameInfo.whiteRating = string_to_rating(star_match[1]);
2815 gameInfo.blackRating = string_to_rating(star_match[3]);
2816 if (appData.debugMode)
2817 fprintf(debugFP, _("Ratings from header: W %d, B %d\n"),
2818 gameInfo.whiteRating, gameInfo.blackRating);
2823 if (looking_at(buf, &i,
2824 "* * match, initial time: * minute*, increment: * second")) {
2825 /* Header for a move list -- second line */
2826 /* Initial board will follow if this is a wild game */
2827 if (gameInfo.event != NULL) free(gameInfo.event);
2828 sprintf(str, "ICS %s %s match", star_match[0], star_match[1]);
2829 gameInfo.event = StrSave(str);
2830 /* [HGM] we switched variant. Translate boards if needed. */
2831 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
2835 if (looking_at(buf, &i, "Move ")) {
2836 /* Beginning of a move list */
2837 switch (ics_getting_history) {
2839 /* Normally should not happen */
2840 /* Maybe user hit reset while we were parsing */
2843 /* Happens if we are ignoring a move list that is not
2844 * the one we just requested. Common if the user
2845 * tries to observe two games without turning off
2848 case H_GETTING_MOVES:
2849 /* Should not happen */
2850 DisplayError(_("Error gathering move list: nested"), 0);
2851 ics_getting_history = H_FALSE;
2853 case H_GOT_REQ_HEADER:
2854 ics_getting_history = H_GETTING_MOVES;
2855 started = STARTED_MOVES;
2857 if (oldi > next_out) {
2858 SendToPlayer(&buf[next_out], oldi - next_out);
2861 case H_GOT_UNREQ_HEADER:
2862 ics_getting_history = H_GETTING_MOVES;
2863 started = STARTED_MOVES_NOHIDE;
2866 case H_GOT_UNWANTED_HEADER:
2867 ics_getting_history = H_FALSE;
2873 if (looking_at(buf, &i, "% ") ||
2874 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
2875 && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
2876 if(suppressKibitz) next_out = i;
2877 savingComment = FALSE;
2881 case STARTED_MOVES_NOHIDE:
2882 memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
2883 parse[parse_pos + i - oldi] = NULLCHAR;
2884 ParseGameHistory(parse);
2886 if (appData.zippyPlay && first.initDone) {
2887 FeedMovesToProgram(&first, forwardMostMove);
2888 if (gameMode == IcsPlayingWhite) {
2889 if (WhiteOnMove(forwardMostMove)) {
2890 if (first.sendTime) {
2891 if (first.useColors) {
2892 SendToProgram("black\n", &first);
2894 SendTimeRemaining(&first, TRUE);
2896 if (first.useColors) {
2897 SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
2899 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
2900 first.maybeThinking = TRUE;
2902 if (first.usePlayother) {
2903 if (first.sendTime) {
2904 SendTimeRemaining(&first, TRUE);
2906 SendToProgram("playother\n", &first);
2912 } else if (gameMode == IcsPlayingBlack) {
2913 if (!WhiteOnMove(forwardMostMove)) {
2914 if (first.sendTime) {
2915 if (first.useColors) {
2916 SendToProgram("white\n", &first);
2918 SendTimeRemaining(&first, FALSE);
2920 if (first.useColors) {
2921 SendToProgram("black\n", &first);
2923 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
2924 first.maybeThinking = TRUE;
2926 if (first.usePlayother) {
2927 if (first.sendTime) {
2928 SendTimeRemaining(&first, FALSE);
2930 SendToProgram("playother\n", &first);
2939 if (gameMode == IcsObserving && ics_gamenum == -1) {
2940 /* Moves came from oldmoves or moves command
2941 while we weren't doing anything else.
2943 currentMove = forwardMostMove;
2944 ClearHighlights();/*!!could figure this out*/
2945 flipView = appData.flipView;
2946 DrawPosition(TRUE, boards[currentMove]);
2947 DisplayBothClocks();
2948 sprintf(str, "%s vs. %s",
2949 gameInfo.white, gameInfo.black);
2953 /* Moves were history of an active game */
2954 if (gameInfo.resultDetails != NULL) {
2955 free(gameInfo.resultDetails);
2956 gameInfo.resultDetails = NULL;
2959 HistorySet(parseList, backwardMostMove,
2960 forwardMostMove, currentMove-1);
2961 DisplayMove(currentMove - 1);
2962 if (started == STARTED_MOVES) next_out = i;
2963 started = STARTED_NONE;
2964 ics_getting_history = H_FALSE;
2967 case STARTED_OBSERVE:
2968 started = STARTED_NONE;
2969 SendToICS(ics_prefix);
2970 SendToICS("refresh\n");
2976 if(bookHit) { // [HGM] book: simulate book reply
2977 static char bookMove[MSG_SIZ]; // a bit generous?
2979 programStats.nodes = programStats.depth = programStats.time =
2980 programStats.score = programStats.got_only_move = 0;
2981 sprintf(programStats.movelist, "%s (xbook)", bookHit);
2983 strcpy(bookMove, "move ");
2984 strcat(bookMove, bookHit);
2985 HandleMachineMove(bookMove, &first);
2990 if ((started == STARTED_MOVES || started == STARTED_BOARD ||
2991 started == STARTED_HOLDINGS ||
2992 started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
2993 /* Accumulate characters in move list or board */
2994 parse[parse_pos++] = buf[i];
2997 /* Start of game messages. Mostly we detect start of game
2998 when the first board image arrives. On some versions
2999 of the ICS, though, we need to do a "refresh" after starting
3000 to observe in order to get the current board right away. */
3001 if (looking_at(buf, &i, "Adding game * to observation list")) {
3002 started = STARTED_OBSERVE;
3006 /* Handle auto-observe */
3007 if (appData.autoObserve &&
3008 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3009 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3011 /* Choose the player that was highlighted, if any. */
3012 if (star_match[0][0] == '\033' ||
3013 star_match[1][0] != '\033') {
3014 player = star_match[0];
3016 player = star_match[2];
3018 sprintf(str, "%sobserve %s\n",
3019 ics_prefix, StripHighlightAndTitle(player));
3022 /* Save ratings from notify string */
3023 strcpy(player1Name, star_match[0]);
3024 player1Rating = string_to_rating(star_match[1]);
3025 strcpy(player2Name, star_match[2]);
3026 player2Rating = string_to_rating(star_match[3]);
3028 if (appData.debugMode)
3030 "Ratings from 'Game notification:' %s %d, %s %d\n",
3031 player1Name, player1Rating,
3032 player2Name, player2Rating);
3037 /* Deal with automatic examine mode after a game,
3038 and with IcsObserving -> IcsExamining transition */
3039 if (looking_at(buf, &i, "Entering examine mode for game *") ||
3040 looking_at(buf, &i, "has made you an examiner of game *")) {
3042 int gamenum = atoi(star_match[0]);
3043 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3044 gamenum == ics_gamenum) {
3045 /* We were already playing or observing this game;
3046 no need to refetch history */
3047 gameMode = IcsExamining;
3049 pauseExamForwardMostMove = forwardMostMove;
3050 } else if (currentMove < forwardMostMove) {
3051 ForwardInner(forwardMostMove);
3054 /* I don't think this case really can happen */
3055 SendToICS(ics_prefix);
3056 SendToICS("refresh\n");
3061 /* Error messages */
3062 // if (ics_user_moved) {
3063 if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3064 if (looking_at(buf, &i, "Illegal move") ||
3065 looking_at(buf, &i, "Not a legal move") ||
3066 looking_at(buf, &i, "Your king is in check") ||
3067 looking_at(buf, &i, "It isn't your turn") ||
3068 looking_at(buf, &i, "It is not your move")) {
3070 if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3071 currentMove = --forwardMostMove;
3072 DisplayMove(currentMove - 1); /* before DMError */
3073 DrawPosition(FALSE, boards[currentMove]);
3075 DisplayBothClocks();
3077 DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3083 if (looking_at(buf, &i, "still have time") ||
3084 looking_at(buf, &i, "not out of time") ||
3085 looking_at(buf, &i, "either player is out of time") ||
3086 looking_at(buf, &i, "has timeseal; checking")) {
3087 /* We must have called his flag a little too soon */
3088 whiteFlag = blackFlag = FALSE;
3092 if (looking_at(buf, &i, "added * seconds to") ||
3093 looking_at(buf, &i, "seconds were added to")) {
3094 /* Update the clocks */
3095 SendToICS(ics_prefix);
3096 SendToICS("refresh\n");
3100 if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3101 ics_clock_paused = TRUE;
3106 if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3107 ics_clock_paused = FALSE;
3112 /* Grab player ratings from the Creating: message.
3113 Note we have to check for the special case when
3114 the ICS inserts things like [white] or [black]. */
3115 if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3116 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3118 0 player 1 name (not necessarily white)
3120 2 empty, white, or black (IGNORED)
3121 3 player 2 name (not necessarily black)
3124 The names/ratings are sorted out when the game
3125 actually starts (below).
3127 strcpy(player1Name, StripHighlightAndTitle(star_match[0]));
3128 player1Rating = string_to_rating(star_match[1]);
3129 strcpy(player2Name, StripHighlightAndTitle(star_match[3]));
3130 player2Rating = string_to_rating(star_match[4]);
3132 if (appData.debugMode)
3134 "Ratings from 'Creating:' %s %d, %s %d\n",
3135 player1Name, player1Rating,
3136 player2Name, player2Rating);
3141 /* Improved generic start/end-of-game messages */
3142 if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3143 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3144 /* If tkind == 0: */
3145 /* star_match[0] is the game number */
3146 /* [1] is the white player's name */
3147 /* [2] is the black player's name */
3148 /* For end-of-game: */
3149 /* [3] is the reason for the game end */
3150 /* [4] is a PGN end game-token, preceded by " " */
3151 /* For start-of-game: */
3152 /* [3] begins with "Creating" or "Continuing" */
3153 /* [4] is " *" or empty (don't care). */
3154 int gamenum = atoi(star_match[0]);
3155 char *whitename, *blackname, *why, *endtoken;
3156 ChessMove endtype = (ChessMove) 0;
3159 whitename = star_match[1];
3160 blackname = star_match[2];
3161 why = star_match[3];
3162 endtoken = star_match[4];
3164 whitename = star_match[1];
3165 blackname = star_match[3];
3166 why = star_match[5];
3167 endtoken = star_match[6];
3170 /* Game start messages */
3171 if (strncmp(why, "Creating ", 9) == 0 ||
3172 strncmp(why, "Continuing ", 11) == 0) {
3173 gs_gamenum = gamenum;
3174 strcpy(gs_kind, strchr(why, ' ') + 1);
3175 VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
3177 if (appData.zippyPlay) {
3178 ZippyGameStart(whitename, blackname);
3184 /* Game end messages */
3185 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3186 ics_gamenum != gamenum) {
3189 while (endtoken[0] == ' ') endtoken++;
3190 switch (endtoken[0]) {
3193 endtype = GameUnfinished;
3196 endtype = BlackWins;
3199 if (endtoken[1] == '/')
3200 endtype = GameIsDrawn;
3202 endtype = WhiteWins;
3205 GameEnds(endtype, why, GE_ICS);
3207 if (appData.zippyPlay && first.initDone) {
3208 ZippyGameEnd(endtype, why);
3209 if (first.pr == NULL) {
3210 /* Start the next process early so that we'll
3211 be ready for the next challenge */
3212 StartChessProgram(&first);
3214 /* Send "new" early, in case this command takes
3215 a long time to finish, so that we'll be ready
3216 for the next challenge. */
3217 gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3224 if (looking_at(buf, &i, "Removing game * from observation") ||
3225 looking_at(buf, &i, "no longer observing game *") ||
3226 looking_at(buf, &i, "Game * (*) has no examiners")) {
3227 if (gameMode == IcsObserving &&
3228 atoi(star_match[0]) == ics_gamenum)
3230 /* icsEngineAnalyze */
3231 if (appData.icsEngineAnalyze) {
3238 ics_user_moved = FALSE;
3243 if (looking_at(buf, &i, "no longer examining game *")) {
3244 if (gameMode == IcsExamining &&
3245 atoi(star_match[0]) == ics_gamenum)
3249 ics_user_moved = FALSE;
3254 /* Advance leftover_start past any newlines we find,
3255 so only partial lines can get reparsed */
3256 if (looking_at(buf, &i, "\n")) {
3257 prevColor = curColor;
3258 if (curColor != ColorNormal) {
3259 if (oldi > next_out) {
3260 SendToPlayer(&buf[next_out], oldi - next_out);
3263 Colorize(ColorNormal, FALSE);
3264 curColor = ColorNormal;
3266 if (started == STARTED_BOARD) {
3267 started = STARTED_NONE;
3268 parse[parse_pos] = NULLCHAR;
3269 ParseBoard12(parse);
3272 /* Send premove here */
3273 if (appData.premove) {
3275 if (currentMove == 0 &&
3276 gameMode == IcsPlayingWhite &&
3277 appData.premoveWhite) {
3278 sprintf(str, "%s\n", appData.premoveWhiteText);
3279 if (appData.debugMode)
3280 fprintf(debugFP, "Sending premove:\n");
3282 } else if (currentMove == 1 &&
3283 gameMode == IcsPlayingBlack &&
3284 appData.premoveBlack) {
3285 sprintf(str, "%s\n", appData.premoveBlackText);
3286 if (appData.debugMode)
3287 fprintf(debugFP, "Sending premove:\n");
3289 } else if (gotPremove) {
3291 ClearPremoveHighlights();
3292 if (appData.debugMode)
3293 fprintf(debugFP, "Sending premove:\n");
3294 UserMoveEvent(premoveFromX, premoveFromY,
3295 premoveToX, premoveToY,
3300 /* Usually suppress following prompt */
3301 if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
3302 while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
3303 if (looking_at(buf, &i, "*% ")) {
3304 savingComment = FALSE;
3309 } else if (started == STARTED_HOLDINGS) {
3311 char new_piece[MSG_SIZ];
3312 started = STARTED_NONE;
3313 parse[parse_pos] = NULLCHAR;
3314 if (appData.debugMode)
3315 fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
3316 parse, currentMove);
3317 if (sscanf(parse, " game %d", &gamenum) == 1 &&
3318 gamenum == ics_gamenum) {
3319 if (gameInfo.variant == VariantNormal) {
3320 /* [HGM] We seem to switch variant during a game!
3321 * Presumably no holdings were displayed, so we have
3322 * to move the position two files to the right to
3323 * create room for them!
3325 VariantClass newVariant;
3326 switch(gameInfo.boardWidth) { // base guess on board width
3327 case 9: newVariant = VariantShogi; break;
3328 case 10: newVariant = VariantGreat; break;
3329 default: newVariant = VariantCrazyhouse; break;
3331 VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3332 /* Get a move list just to see the header, which
3333 will tell us whether this is really bug or zh */
3334 if (ics_getting_history == H_FALSE) {
3335 ics_getting_history = H_REQUESTED;
3336 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3340 new_piece[0] = NULLCHAR;
3341 sscanf(parse, "game %d white [%s black [%s <- %s",
3342 &gamenum, white_holding, black_holding,
3344 white_holding[strlen(white_holding)-1] = NULLCHAR;
3345 black_holding[strlen(black_holding)-1] = NULLCHAR;
3346 /* [HGM] copy holdings to board holdings area */
3347 CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
3348 CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
3349 boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
3351 if (appData.zippyPlay && first.initDone) {
3352 ZippyHoldings(white_holding, black_holding,
3356 if (tinyLayout || smallLayout) {
3357 char wh[16], bh[16];
3358 PackHolding(wh, white_holding);
3359 PackHolding(bh, black_holding);
3360 sprintf(str, "[%s-%s] %s-%s", wh, bh,
3361 gameInfo.white, gameInfo.black);
3363 sprintf(str, "%s [%s] vs. %s [%s]",
3364 gameInfo.white, white_holding,
3365 gameInfo.black, black_holding);
3368 DrawPosition(FALSE, boards[currentMove]);
3371 /* Suppress following prompt */
3372 if (looking_at(buf, &i, "*% ")) {
3373 if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
3374 savingComment = FALSE;
3382 i++; /* skip unparsed character and loop back */
3385 if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
3386 // started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
3387 // SendToPlayer(&buf[next_out], i - next_out);
3388 started != STARTED_HOLDINGS && leftover_start > next_out) {
3389 SendToPlayer(&buf[next_out], leftover_start - next_out);
3393 leftover_len = buf_len - leftover_start;
3394 /* if buffer ends with something we couldn't parse,
3395 reparse it after appending the next read */
3397 } else if (count == 0) {
3398 RemoveInputSource(isr);
3399 DisplayFatalError(_("Connection closed by ICS"), 0, 0);
3401 DisplayFatalError(_("Error reading from ICS"), error, 1);
3406 /* Board style 12 looks like this:
3408 <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
3410 * The "<12> " is stripped before it gets to this routine. The two
3411 * trailing 0's (flip state and clock ticking) are later addition, and
3412 * some chess servers may not have them, or may have only the first.
3413 * Additional trailing fields may be added in the future.
3416 #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"
3418 #define RELATION_OBSERVING_PLAYED 0
3419 #define RELATION_OBSERVING_STATIC -2 /* examined, oldmoves, or smoves */
3420 #define RELATION_PLAYING_MYMOVE 1
3421 #define RELATION_PLAYING_NOTMYMOVE -1
3422 #define RELATION_EXAMINING 2
3423 #define RELATION_ISOLATED_BOARD -3
3424 #define RELATION_STARTING_POSITION -4 /* FICS only */
3427 ParseBoard12(string)
3430 GameMode newGameMode;
3431 int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
3432 int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
3433 int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
3434 char to_play, board_chars[200];
3435 char move_str[500], str[500], elapsed_time[500];
3436 char black[32], white[32];
3438 int prevMove = currentMove;
3441 int fromX, fromY, toX, toY;
3443 int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
3444 char *bookHit = NULL; // [HGM] book
3445 Boolean weird = FALSE, reqFlag = FALSE;
3447 fromX = fromY = toX = toY = -1;
3451 if (appData.debugMode)
3452 fprintf(debugFP, _("Parsing board: %s\n"), string);
3454 move_str[0] = NULLCHAR;
3455 elapsed_time[0] = NULLCHAR;
3456 { /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
3458 while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
3459 if(string[i] == ' ') { ranks++; files = 0; }
3461 if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
3464 for(j = 0; j <i; j++) board_chars[j] = string[j];
3465 board_chars[i] = '\0';
3468 n = sscanf(string, PATTERN, &to_play, &double_push,
3469 &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
3470 &gamenum, white, black, &relation, &basetime, &increment,
3471 &white_stren, &black_stren, &white_time, &black_time,
3472 &moveNum, str, elapsed_time, move_str, &ics_flip,
3476 snprintf(str, sizeof(str), _("Failed to parse board string:\n\"%s\""), string);
3477 DisplayError(str, 0);
3481 /* Convert the move number to internal form */
3482 moveNum = (moveNum - 1) * 2;
3483 if (to_play == 'B') moveNum++;
3484 if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
3485 DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
3491 case RELATION_OBSERVING_PLAYED:
3492 case RELATION_OBSERVING_STATIC:
3493 if (gamenum == -1) {
3494 /* Old ICC buglet */
3495 relation = RELATION_OBSERVING_STATIC;
3497 newGameMode = IcsObserving;
3499 case RELATION_PLAYING_MYMOVE:
3500 case RELATION_PLAYING_NOTMYMOVE:
3502 ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
3503 IcsPlayingWhite : IcsPlayingBlack;
3505 case RELATION_EXAMINING:
3506 newGameMode = IcsExamining;
3508 case RELATION_ISOLATED_BOARD:
3510 /* Just display this board. If user was doing something else,
3511 we will forget about it until the next board comes. */
3512 newGameMode = IcsIdle;
3514 case RELATION_STARTING_POSITION:
3515 newGameMode = gameMode;
3519 /* Modify behavior for initial board display on move listing
3522 switch (ics_getting_history) {
3526 case H_GOT_REQ_HEADER:
3527 case H_GOT_UNREQ_HEADER:
3528 /* This is the initial position of the current game */
3529 gamenum = ics_gamenum;
3530 moveNum = 0; /* old ICS bug workaround */
3531 if (to_play == 'B') {
3532 startedFromSetupPosition = TRUE;
3533 blackPlaysFirst = TRUE;
3535 if (forwardMostMove == 0) forwardMostMove = 1;
3536 if (backwardMostMove == 0) backwardMostMove = 1;
3537 if (currentMove == 0) currentMove = 1;
3539 newGameMode = gameMode;
3540 relation = RELATION_STARTING_POSITION; /* ICC needs this */
3542 case H_GOT_UNWANTED_HEADER:
3543 /* This is an initial board that we don't want */
3545 case H_GETTING_MOVES:
3546 /* Should not happen */
3547 DisplayError(_("Error gathering move list: extra board"), 0);
3548 ics_getting_history = H_FALSE;
3552 if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
3553 weird && (int)gameInfo.variant <= (int)VariantShogi) {
3554 /* [HGM] We seem to have switched variant unexpectedly
3555 * Try to guess new variant from board size
3557 VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
3558 if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
3559 if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
3560 if(ranks == 8 && files == 12) newVariant = VariantCourier; else
3561 if(ranks == 9 && files == 9) newVariant = VariantShogi; else
3562 if(!weird) newVariant = VariantNormal;
3563 VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3564 /* Get a move list just to see the header, which
3565 will tell us whether this is really bug or zh */
3566 if (ics_getting_history == H_FALSE) {
3567 ics_getting_history = H_REQUESTED; reqFlag = TRUE;
3568 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3573 /* Take action if this is the first board of a new game, or of a
3574 different game than is currently being displayed. */
3575 if (gamenum != ics_gamenum || newGameMode != gameMode ||
3576 relation == RELATION_ISOLATED_BOARD) {
3578 /* Forget the old game and get the history (if any) of the new one */
3579 if (gameMode != BeginningOfGame) {
3583 if (appData.autoRaiseBoard) BoardToTop();
3585 if (gamenum == -1) {
3586 newGameMode = IcsIdle;
3587 } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
3588 appData.getMoveList && !reqFlag) {
3589 /* Need to get game history */
3590 ics_getting_history = H_REQUESTED;
3591 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3595 /* Initially flip the board to have black on the bottom if playing
3596 black or if the ICS flip flag is set, but let the user change
3597 it with the Flip View button. */
3598 flipView = appData.autoFlipView ?
3599 (newGameMode == IcsPlayingBlack) || ics_flip :
3602 /* Done with values from previous mode; copy in new ones */
3603 gameMode = newGameMode;
3605 ics_gamenum = gamenum;
3606 if (gamenum == gs_gamenum) {
3607 int klen = strlen(gs_kind);
3608 if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
3609 sprintf(str, "ICS %s", gs_kind);
3610 gameInfo.event = StrSave(str);
3612 gameInfo.event = StrSave("ICS game");
3614 gameInfo.site = StrSave(appData.icsHost);
3615 gameInfo.date = PGNDate();
3616 gameInfo.round = StrSave("-");
3617 gameInfo.white = StrSave(white);
3618 gameInfo.black = StrSave(black);
3619 timeControl = basetime * 60 * 1000;
3621 timeIncrement = increment * 1000;
3622 movesPerSession = 0;
3623 gameInfo.timeControl = TimeControlTagValue();
3624 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
3625 if (appData.debugMode) {
3626 fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
3627 fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
3628 setbuf(debugFP, NULL);
3631 gameInfo.outOfBook = NULL;
3633 /* Do we have the ratings? */
3634 if (strcmp(player1Name, white) == 0 &&
3635 strcmp(player2Name, black) == 0) {
3636 if (appData.debugMode)
3637 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3638 player1Rating, player2Rating);
3639 gameInfo.whiteRating = player1Rating;
3640 gameInfo.blackRating = player2Rating;
3641 } else if (strcmp(player2Name, white) == 0 &&
3642 strcmp(player1Name, black) == 0) {
3643 if (appData.debugMode)
3644 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3645 player2Rating, player1Rating);
3646 gameInfo.whiteRating = player2Rating;
3647 gameInfo.blackRating = player1Rating;
3649 player1Name[0] = player2Name[0] = NULLCHAR;
3651 /* Silence shouts if requested */
3652 if (appData.quietPlay &&
3653 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
3654 SendToICS(ics_prefix);
3655 SendToICS("set shout 0\n");
3659 /* Deal with midgame name changes */
3661 if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
3662 if (gameInfo.white) free(gameInfo.white);
3663 gameInfo.white = StrSave(white);
3665 if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
3666 if (gameInfo.black) free(gameInfo.black);
3667 gameInfo.black = StrSave(black);
3671 /* Throw away game result if anything actually changes in examine mode */
3672 if (gameMode == IcsExamining && !newGame) {
3673 gameInfo.result = GameUnfinished;
3674 if (gameInfo.resultDetails != NULL) {
3675 free(gameInfo.resultDetails);
3676 gameInfo.resultDetails = NULL;
3680 /* In pausing && IcsExamining mode, we ignore boards coming
3681 in if they are in a different variation than we are. */
3682 if (pauseExamInvalid) return;
3683 if (pausing && gameMode == IcsExamining) {
3684 if (moveNum <= pauseExamForwardMostMove) {
3685 pauseExamInvalid = TRUE;
3686 forwardMostMove = pauseExamForwardMostMove;
3691 if (appData.debugMode) {
3692 fprintf(debugFP, "load %dx%d board\n", files, ranks);
3694 /* Parse the board */
3695 for (k = 0; k < ranks; k++) {
3696 for (j = 0; j < files; j++)
3697 board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
3698 if(gameInfo.holdingsWidth > 1) {
3699 board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
3700 board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
3703 CopyBoard(boards[moveNum], board);
3704 boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
3706 startedFromSetupPosition =
3707 !CompareBoards(board, initialPosition);
3708 if(startedFromSetupPosition)
3709 initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
3712 /* [HGM] Set castling rights. Take the outermost Rooks,
3713 to make it also work for FRC opening positions. Note that board12
3714 is really defective for later FRC positions, as it has no way to
3715 indicate which Rook can castle if they are on the same side of King.
3716 For the initial position we grant rights to the outermost Rooks,
3717 and remember thos rights, and we then copy them on positions
3718 later in an FRC game. This means WB might not recognize castlings with
3719 Rooks that have moved back to their original position as illegal,
3720 but in ICS mode that is not its job anyway.
3722 if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
3723 { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
3725 for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
3726 if(board[0][i] == WhiteRook) j = i;
3727 initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
3728 for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
3729 if(board[0][i] == WhiteRook) j = i;
3730 initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
3731 for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
3732 if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
3733 initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
3734 for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
3735 if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
3736 initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
3738 if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
3739 for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
3740 if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
3741 for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
3742 if(board[BOARD_HEIGHT-1][k] == bKing)
3743 initialRights[5] = boards[moveNum][CASTLING][5] = k;
3744 if(gameInfo.variant == VariantTwoKings) {
3745 // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
3746 if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
3747 if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
3750 r = boards[moveNum][CASTLING][0] = initialRights[0];
3751 if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
3752 r = boards[moveNum][CASTLING][1] = initialRights[1];
3753 if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
3754 r = boards[moveNum][CASTLING][3] = initialRights[3];
3755 if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
3756 r = boards[moveNum][CASTLING][4] = initialRights[4];
3757 if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
3758 /* wildcastle kludge: always assume King has rights */
3759 r = boards[moveNum][CASTLING][2] = initialRights[2];
3760 r = boards[moveNum][CASTLING][5] = initialRights[5];
3762 /* [HGM] e.p. rights. Assume that ICS sends file number here? */
3763 boards[moveNum][EP_STATUS] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
3766 if (ics_getting_history == H_GOT_REQ_HEADER ||
3767 ics_getting_history == H_GOT_UNREQ_HEADER) {
3768 /* This was an initial position from a move list, not
3769 the current position */
3773 /* Update currentMove and known move number limits */
3774 newMove = newGame || moveNum > forwardMostMove;
3777 forwardMostMove = backwardMostMove = currentMove = moveNum;
3778 if (gameMode == IcsExamining && moveNum == 0) {
3779 /* Workaround for ICS limitation: we are not told the wild
3780 type when starting to examine a game. But if we ask for
3781 the move list, the move list header will tell us */
3782 ics_getting_history = H_REQUESTED;
3783 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3786 } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
3787 || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
3789 /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
3790 /* [HGM] applied this also to an engine that is silently watching */
3791 if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
3792 (gameMode == IcsObserving || gameMode == IcsExamining) &&
3793 gameInfo.variant == currentlyInitializedVariant) {
3794 takeback = forwardMostMove - moveNum;
3795 for (i = 0; i < takeback; i++) {
3796 if (appData.debugMode) fprintf(debugFP, "take back move\n");
3797 SendToProgram("undo\n", &first);
3802 forwardMostMove = moveNum;
3803 if (!pausing || currentMove > forwardMostMove)
3804 currentMove = forwardMostMove;
3806 /* New part of history that is not contiguous with old part */
3807 if (pausing && gameMode == IcsExamining) {
3808 pauseExamInvalid = TRUE;
3809 forwardMostMove = pauseExamForwardMostMove;
3812 if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
3814 if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
3815 // [HGM] when we will receive the move list we now request, it will be
3816 // fed to the engine from the first move on. So if the engine is not
3817 // in the initial position now, bring it there.
3818 InitChessProgram(&first, 0);
3821 ics_getting_history = H_REQUESTED;
3822 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3825 forwardMostMove = backwardMostMove = currentMove = moveNum;
3828 /* Update the clocks */
3829 if (strchr(elapsed_time, '.')) {
3831 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
3832 timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
3834 /* Time is in seconds */
3835 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
3836 timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
3841 if (appData.zippyPlay && newGame &&
3842 gameMode != IcsObserving && gameMode != IcsIdle &&
3843 gameMode != IcsExamining)
3844 ZippyFirstBoard(moveNum, basetime, increment);
3847 /* Put the move on the move list, first converting
3848 to canonical algebraic form. */
3850 if (appData.debugMode) {
3851 if (appData.debugMode) { int f = forwardMostMove;
3852 fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
3853 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
3854 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
3856 fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
3857 fprintf(debugFP, "moveNum = %d\n", moveNum);
3858 fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
3859 setbuf(debugFP, NULL);
3861 if (moveNum <= backwardMostMove) {
3862 /* We don't know what the board looked like before
3864 strcpy(parseList[moveNum - 1], move_str);
3865 strcat(parseList[moveNum - 1], " ");
3866 strcat(parseList[moveNum - 1], elapsed_time);
3867 moveList[moveNum - 1][0] = NULLCHAR;
3868 } else if (strcmp(move_str, "none") == 0) {
3869 // [HGM] long SAN: swapped order; test for 'none' before parsing move
3870 /* Again, we don't know what the board looked like;
3871 this is really the start of the game. */
3872 parseList[moveNum - 1][0] = NULLCHAR;
3873 moveList[moveNum - 1][0] = NULLCHAR;
3874 backwardMostMove = moveNum;
3875 startedFromSetupPosition = TRUE;
3876 fromX = fromY = toX = toY = -1;
3878 // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
3879 // So we parse the long-algebraic move string in stead of the SAN move
3880 int valid; char buf[MSG_SIZ], *prom;
3882 // str looks something like "Q/a1-a2"; kill the slash
3884 sprintf(buf, "%c%s", str[0], str+2);
3885 else strcpy(buf, str); // might be castling
3886 if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
3887 strcat(buf, prom); // long move lacks promo specification!
3888 if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
3889 if(appData.debugMode)
3890 fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
3891 strcpy(move_str, buf);
3893 valid = ParseOneMove(move_str, moveNum - 1, &moveType,
3894 &fromX, &fromY, &toX, &toY, &promoChar)
3895 || ParseOneMove(buf, moveNum - 1, &moveType,
3896 &fromX, &fromY, &toX, &toY, &promoChar);
3897 // end of long SAN patch
3899 (void) CoordsToAlgebraic(boards[moveNum - 1],
3900 PosFlags(moveNum - 1),
3901 fromY, fromX, toY, toX, promoChar,
3902 parseList[moveNum-1]);
3903 switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
3909 if(gameInfo.variant != VariantShogi)
3910 strcat(parseList[moveNum - 1], "+");
3913 case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
3914 strcat(parseList[moveNum - 1], "#");
3917 strcat(parseList[moveNum - 1], " ");
3918 strcat(parseList[moveNum - 1], elapsed_time);
3919 /* currentMoveString is set as a side-effect of ParseOneMove */
3920 strcpy(moveList[moveNum - 1], currentMoveString);
3921 strcat(moveList[moveNum - 1], "\n");
3923 /* Move from ICS was illegal!? Punt. */
3924 if (appData.debugMode) {
3925 fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
3926 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
3928 strcpy(parseList[moveNum - 1], move_str);
3929 strcat(parseList[moveNum - 1], " ");
3930 strcat(parseList[moveNum - 1], elapsed_time);
3931 moveList[moveNum - 1][0] = NULLCHAR;
3932 fromX = fromY = toX = toY = -1;
3935 if (appData.debugMode) {
3936 fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
3937 setbuf(debugFP, NULL);
3941 /* Send move to chess program (BEFORE animating it). */
3942 if (appData.zippyPlay && !newGame && newMove &&
3943 (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
3945 if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
3946 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
3947 if (moveList[moveNum - 1][0] == NULLCHAR) {
3948 sprintf(str, _("Couldn't parse move \"%s\" from ICS"),
3950 DisplayError(str, 0);
3952 if (first.sendTime) {
3953 SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
3955 bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
3956 if (firstMove && !bookHit) {
3958 if (first.useColors) {
3959 SendToProgram(gameMode == IcsPlayingWhite ?
3961 "black\ngo\n", &first);
3963 SendToProgram("go\n", &first);
3965 first.maybeThinking = TRUE;
3968 } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
3969 if (moveList[moveNum - 1][0] == NULLCHAR) {
3970 sprintf(str, _("Couldn't parse move \"%s\" from ICS"), move_str);
3971 DisplayError(str, 0);
3973 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
3974 SendMoveToProgram(moveNum - 1, &first);
3981 if (moveNum > 0 && !gotPremove && !appData.noGUI) {
3982 /* If move comes from a remote source, animate it. If it
3983 isn't remote, it will have already been animated. */
3984 if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
3985 AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
3987 if (!pausing && appData.highlightLastMove) {
3988 SetHighlights(fromX, fromY, toX, toY);
3992 /* Start the clocks */
3993 whiteFlag = blackFlag = FALSE;
3994 appData.clockMode = !(basetime == 0 && increment == 0);
3996 ics_clock_paused = TRUE;
3998 } else if (ticking == 1) {
3999 ics_clock_paused = FALSE;
4001 if (gameMode == IcsIdle ||
4002 relation == RELATION_OBSERVING_STATIC ||
4003 relation == RELATION_EXAMINING ||
4005 DisplayBothClocks();
4009 /* Display opponents and material strengths */
4010 if (gameInfo.variant != VariantBughouse &&
4011 gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4012 if (tinyLayout || smallLayout) {
4013 if(gameInfo.variant == VariantNormal)
4014 sprintf(str, "%s(%d) %s(%d) {%d %d}",
4015 gameInfo.white, white_stren, gameInfo.black, black_stren,
4016 basetime, increment);
4018 sprintf(str, "%s(%d) %s(%d) {%d %d w%d}",
4019 gameInfo.white, white_stren, gameInfo.black, black_stren,
4020 basetime, increment, (int) gameInfo.variant);
4022 if(gameInfo.variant == VariantNormal)
4023 sprintf(str, "%s (%d) vs. %s (%d) {%d %d}",
4024 gameInfo.white, white_stren, gameInfo.black, black_stren,
4025 basetime, increment);
4027 sprintf(str, "%s (%d) vs. %s (%d) {%d %d %s}",
4028 gameInfo.white, white_stren, gameInfo.black, black_stren,
4029 basetime, increment, VariantName(gameInfo.variant));
4032 if (appData.debugMode) {
4033 fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
4038 /* Display the board */
4039 if (!pausing && !appData.noGUI) {
4041 if (appData.premove)
4043 ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
4044 ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
4045 ClearPremoveHighlights();
4047 DrawPosition(FALSE, boards[currentMove]);
4048 DisplayMove(moveNum - 1);
4049 if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4050 !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4051 (gameMode == IcsPlayingBlack) && (WhiteOnMove(moveNum)) ) ) {
4052 if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4056 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4058 if(bookHit) { // [HGM] book: simulate book reply
4059 static char bookMove[MSG_SIZ]; // a bit generous?
4061 programStats.nodes = programStats.depth = programStats.time =
4062 programStats.score = programStats.got_only_move = 0;
4063 sprintf(programStats.movelist, "%s (xbook)", bookHit);
4065 strcpy(bookMove, "move ");
4066 strcat(bookMove, bookHit);
4067 HandleMachineMove(bookMove, &first);
4076 if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4077 ics_getting_history = H_REQUESTED;
4078 sprintf(buf, "%smoves %d\n", ics_prefix, ics_gamenum);
4084 AnalysisPeriodicEvent(force)
4087 if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4088 && !force) || !appData.periodicUpdates)
4091 /* Send . command to Crafty to collect stats */
4092 SendToProgram(".\n", &first);
4094 /* Don't send another until we get a response (this makes
4095 us stop sending to old Crafty's which don't understand
4096 the "." command (sending illegal cmds resets node count & time,
4097 which looks bad)) */
4098 programStats.ok_to_send = 0;
4101 void ics_update_width(new_width)
4104 ics_printf("set width %d\n", new_width);
4108 SendMoveToProgram(moveNum, cps)
4110 ChessProgramState *cps;
4114 if (cps->useUsermove) {
4115 SendToProgram("usermove ", cps);
4119 if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4120 int len = space - parseList[moveNum];
4121 memcpy(buf, parseList[moveNum], len);
4123 buf[len] = NULLCHAR;
4125 sprintf(buf, "%s\n", parseList[moveNum]);
4127 SendToProgram(buf, cps);
4129 if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4130 AlphaRank(moveList[moveNum], 4);
4131 SendToProgram(moveList[moveNum], cps);
4132 AlphaRank(moveList[moveNum], 4); // and back
4134 /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
4135 * the engine. It would be nice to have a better way to identify castle
4137 if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
4138 && cps->useOOCastle) {
4139 int fromX = moveList[moveNum][0] - AAA;
4140 int fromY = moveList[moveNum][1] - ONE;
4141 int toX = moveList[moveNum][2] - AAA;
4142 int toY = moveList[moveNum][3] - ONE;
4143 if((boards[moveNum][fromY][fromX] == WhiteKing
4144 && boards[moveNum][toY][toX] == WhiteRook)
4145 || (boards[moveNum][fromY][fromX] == BlackKing
4146 && boards[moveNum][toY][toX] == BlackRook)) {
4147 if(toX > fromX) SendToProgram("O-O\n", cps);
4148 else SendToProgram("O-O-O\n", cps);
4150 else SendToProgram(moveList[moveNum], cps);
4152 else SendToProgram(moveList[moveNum], cps);
4153 /* End of additions by Tord */
4156 /* [HGM] setting up the opening has brought engine in force mode! */
4157 /* Send 'go' if we are in a mode where machine should play. */
4158 if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4159 (gameMode == TwoMachinesPlay ||
4161 gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite ||
4163 gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4164 SendToProgram("go\n", cps);
4165 if (appData.debugMode) {
4166 fprintf(debugFP, "(extra)\n");
4169 setboardSpoiledMachineBlack = 0;
4173 SendMoveToICS(moveType, fromX, fromY, toX, toY)
4175 int fromX, fromY, toX, toY;
4177 char user_move[MSG_SIZ];
4181 sprintf(user_move, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4182 (int)moveType, fromX, fromY, toX, toY);
4183 DisplayError(user_move + strlen("say "), 0);
4185 case WhiteKingSideCastle:
4186 case BlackKingSideCastle:
4187 case WhiteQueenSideCastleWild:
4188 case BlackQueenSideCastleWild:
4190 case WhiteHSideCastleFR:
4191 case BlackHSideCastleFR:
4193 sprintf(user_move, "o-o\n");
4195 case WhiteQueenSideCastle:
4196 case BlackQueenSideCastle:
4197 case WhiteKingSideCastleWild:
4198 case BlackKingSideCastleWild:
4200 case WhiteASideCastleFR:
4201 case BlackASideCastleFR:
4203 sprintf(user_move, "o-o-o\n");
4205 case WhitePromotionQueen:
4206 case BlackPromotionQueen:
4207 case WhitePromotionRook:
4208 case BlackPromotionRook:
4209 case WhitePromotionBishop:
4210 case BlackPromotionBishop:
4211 case WhitePromotionKnight:
4212 case BlackPromotionKnight:
4213 case WhitePromotionKing:
4214 case BlackPromotionKing:
4215 case WhitePromotionChancellor:
4216 case BlackPromotionChancellor:
4217 case WhitePromotionArchbishop:
4218 case BlackPromotionArchbishop:
4219 if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
4220 sprintf(user_move, "%c%c%c%c=%c\n",
4221 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4222 PieceToChar(WhiteFerz));
4223 else if(gameInfo.variant == VariantGreat)
4224 sprintf(user_move, "%c%c%c%c=%c\n",
4225 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4226 PieceToChar(WhiteMan));
4228 sprintf(user_move, "%c%c%c%c=%c\n",
4229 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4230 PieceToChar(PromoPiece(moveType)));
4234 sprintf(user_move, "%c@%c%c\n",
4235 ToUpper(PieceToChar((ChessSquare) fromX)),
4236 AAA + toX, ONE + toY);
4239 case WhiteCapturesEnPassant:
4240 case BlackCapturesEnPassant:
4241 case IllegalMove: /* could be a variant we don't quite understand */
4242 sprintf(user_move, "%c%c%c%c\n",
4243 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4246 SendToICS(user_move);
4247 if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
4248 ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
4252 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
4257 if (rf == DROP_RANK) {
4258 sprintf(move, "%c@%c%c\n",
4259 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
4261 if (promoChar == 'x' || promoChar == NULLCHAR) {
4262 sprintf(move, "%c%c%c%c\n",
4263 AAA + ff, ONE + rf, AAA + ft, ONE + rt);
4265 sprintf(move, "%c%c%c%c%c\n",
4266 AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
4272 ProcessICSInitScript(f)
4277 while (fgets(buf, MSG_SIZ, f)) {
4278 SendToICSDelayed(buf,(long)appData.msLoginDelay);
4285 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
4287 AlphaRank(char *move, int n)
4289 // char *p = move, c; int x, y;
4291 if (appData.debugMode) {
4292 fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
4296 move[2]>='0' && move[2]<='9' &&
4297 move[3]>='a' && move[3]<='x' ) {
4299 move[2] = BOARD_RGHT -1 - (move[2]-'1') + AAA;
4300 move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4302 if(move[0]>='0' && move[0]<='9' &&
4303 move[1]>='a' && move[1]<='x' &&
4304 move[2]>='0' && move[2]<='9' &&
4305 move[3]>='a' && move[3]<='x' ) {
4306 /* input move, Shogi -> normal */
4307 move[0] = BOARD_RGHT -1 - (move[0]-'1') + AAA;
4308 move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
4309 move[2] = BOARD_RGHT -1 - (move[2]-'1') + AAA;
4310 move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4313 move[3]>='0' && move[3]<='9' &&
4314 move[2]>='a' && move[2]<='x' ) {
4316 move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4317 move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4320 move[0]>='a' && move[0]<='x' &&
4321 move[3]>='0' && move[3]<='9' &&
4322 move[2]>='a' && move[2]<='x' ) {
4323 /* output move, normal -> Shogi */
4324 move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
4325 move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
4326 move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4327 move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4328 if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
4330 if (appData.debugMode) {
4331 fprintf(debugFP, " out = '%s'\n", move);
4335 /* Parser for moves from gnuchess, ICS, or user typein box */
4337 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
4340 ChessMove *moveType;
4341 int *fromX, *fromY, *toX, *toY;
4344 if (appData.debugMode) {
4345 fprintf(debugFP, "move to parse: %s\n", move);
4347 *moveType = yylexstr(moveNum, move);
4349 switch (*moveType) {
4350 case WhitePromotionChancellor:
4351 case BlackPromotionChancellor:
4352 case WhitePromotionArchbishop:
4353 case BlackPromotionArchbishop:
4354 case WhitePromotionQueen:
4355 case BlackPromotionQueen:
4356 case WhitePromotionRook:
4357 case BlackPromotionRook:
4358 case WhitePromotionBishop:
4359 case BlackPromotionBishop:
4360 case WhitePromotionKnight:
4361 case BlackPromotionKnight:
4362 case WhitePromotionKing:
4363 case BlackPromotionKing:
4365 case WhiteCapturesEnPassant:
4366 case BlackCapturesEnPassant:
4367 case WhiteKingSideCastle:
4368 case WhiteQueenSideCastle:
4369 case BlackKingSideCastle:
4370 case BlackQueenSideCastle:
4371 case WhiteKingSideCastleWild:
4372 case WhiteQueenSideCastleWild:
4373 case BlackKingSideCastleWild:
4374 case BlackQueenSideCastleWild:
4375 /* Code added by Tord: */
4376 case WhiteHSideCastleFR:
4377 case WhiteASideCastleFR:
4378 case BlackHSideCastleFR:
4379 case BlackASideCastleFR:
4380 /* End of code added by Tord */
4381 case IllegalMove: /* bug or odd chess variant */
4382 *fromX = currentMoveString[0] - AAA;
4383 *fromY = currentMoveString[1] - ONE;
4384 *toX = currentMoveString[2] - AAA;
4385 *toY = currentMoveString[3] - ONE;
4386 *promoChar = currentMoveString[4];
4387 if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
4388 *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
4389 if (appData.debugMode) {
4390 fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
4392 *fromX = *fromY = *toX = *toY = 0;
4395 if (appData.testLegality) {
4396 return (*moveType != IllegalMove);
4398 return !(*fromX == *toX && *fromY == *toY) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
4399 WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
4404 *fromX = *moveType == WhiteDrop ?
4405 (int) CharToPiece(ToUpper(currentMoveString[0])) :
4406 (int) CharToPiece(ToLower(currentMoveString[0]));
4408 *toX = currentMoveString[2] - AAA;
4409 *toY = currentMoveString[3] - ONE;
4410 *promoChar = NULLCHAR;
4414 case ImpossibleMove:
4415 case (ChessMove) 0: /* end of file */
4424 if (appData.debugMode) {
4425 fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
4428 *fromX = *fromY = *toX = *toY = 0;
4429 *promoChar = NULLCHAR;
4437 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
4438 int fromX, fromY, toX, toY; char promoChar;
4443 endPV = forwardMostMove;
4445 while(*pv == ' ') pv++;
4446 if(*pv == '(') pv++; // first (ponder) move can be in parentheses
4447 valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
4448 if(appData.debugMode){
4449 fprintf(debugFP,"parsePV: %d %c%c%c%c '%s'\n", valid, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, pv);
4451 if(!valid && nr == 0 &&
4452 ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
4453 nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
4455 while(*pv && *pv++ != ' '); // skip what we parsed; assume space separators
4456 if(moveType == Comment) { valid++; continue; } // allow comments in PV
4458 if(endPV+1 > framePtr) break; // no space, truncate
4461 CopyBoard(boards[endPV], boards[endPV-1]);
4462 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
4463 moveList[endPV-1][0] = fromX + AAA;
4464 moveList[endPV-1][1] = fromY + ONE;
4465 moveList[endPV-1][2] = toX + AAA;
4466 moveList[endPV-1][3] = toY + ONE;
4467 parseList[endPV-1][0] = NULLCHAR;
4469 currentMove = endPV;
4470 if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
4471 SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
4472 moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
4473 DrawPosition(TRUE, boards[currentMove]);
4476 static int lastX, lastY;
4479 LoadMultiPV(int x, int y, char *buf, int index, int *start, int *end)
4483 if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
4484 lastX = x; lastY = y;
4485 while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
4487 while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
4489 while(buf[index] && buf[index] != '\n') index++;
4491 ParsePV(buf+startPV);
4492 *start = startPV; *end = index-1;
4497 LoadPV(int x, int y)
4498 { // called on right mouse click to load PV
4499 int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
4500 lastX = x; lastY = y;
4501 ParsePV(lastPV[which]); // load the PV of the thinking engine in the boards array.
4508 if(endPV < 0) return;
4510 currentMove = forwardMostMove;
4511 ClearPremoveHighlights();
4512 DrawPosition(TRUE, boards[currentMove]);
4516 MovePV(int x, int y, int h)
4517 { // step through PV based on mouse coordinates (called on mouse move)
4518 int margin = h>>3, step = 0;
4520 if(endPV < 0) return;
4521 // we must somehow check if right button is still down (might be released off board!)
4522 if(y < margin && (abs(x - lastX) > 6 || abs(y - lastY) > 6)) step = 1; else
4523 if(y > h - margin && (abs(x - lastX) > 6 || abs(y - lastY) > 6)) step = -1; else
4524 if( y > lastY + 6 ) step = -1; else if(y < lastY - 6) step = 1;
4526 lastX = x; lastY = y;
4527 if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
4528 currentMove += step;
4529 if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
4530 SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
4531 moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
4532 DrawPosition(FALSE, boards[currentMove]);
4536 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
4537 // All positions will have equal probability, but the current method will not provide a unique
4538 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
4544 int piecesLeft[(int)BlackPawn];
4545 int seed, nrOfShuffles;
4547 void GetPositionNumber()
4548 { // sets global variable seed
4551 seed = appData.defaultFrcPosition;
4552 if(seed < 0) { // randomize based on time for negative FRC position numbers
4553 for(i=0; i<50; i++) seed += random();
4554 seed = random() ^ random() >> 8 ^ random() << 8;
4555 if(seed<0) seed = -seed;
4559 int put(Board board, int pieceType, int rank, int n, int shade)
4560 // put the piece on the (n-1)-th empty squares of the given shade
4564 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
4565 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
4566 board[rank][i] = (ChessSquare) pieceType;
4567 squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
4569 piecesLeft[pieceType]--;
4577 void AddOnePiece(Board board, int pieceType, int rank, int shade)
4578 // calculate where the next piece goes, (any empty square), and put it there
4582 i = seed % squaresLeft[shade];
4583 nrOfShuffles *= squaresLeft[shade];
4584 seed /= squaresLeft[shade];
4585 put(board, pieceType, rank, i, shade);
4588 void AddTwoPieces(Board board, int pieceType, int rank)
4589 // calculate where the next 2 identical pieces go, (any empty square), and put it there
4591 int i, n=squaresLeft[ANY], j=n-1, k;
4593 k = n*(n-1)/2; // nr of possibilities, not counting permutations
4594 i = seed % k; // pick one
4597 while(i >= j) i -= j--;
4598 j = n - 1 - j; i += j;
4599 put(board, pieceType, rank, j, ANY);
4600 put(board, pieceType, rank, i, ANY);
4603 void SetUpShuffle(Board board, int number)
4607 GetPositionNumber(); nrOfShuffles = 1;
4609 squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
4610 squaresLeft[ANY] = BOARD_RGHT - BOARD_LEFT;
4611 squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
4613 for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
4615 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
4616 p = (int) board[0][i];
4617 if(p < (int) BlackPawn) piecesLeft[p] ++;
4618 board[0][i] = EmptySquare;
4621 if(PosFlags(0) & F_ALL_CASTLE_OK) {
4622 // shuffles restricted to allow normal castling put KRR first
4623 if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
4624 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
4625 else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
4626 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
4627 if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
4628 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
4629 if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
4630 put(board, WhiteRook, 0, 0, ANY);
4631 // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
4634 if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
4635 // only for even boards make effort to put pairs of colorbound pieces on opposite colors
4636 for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
4637 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
4638 while(piecesLeft[p] >= 2) {
4639 AddOnePiece(board, p, 0, LITE);
4640 AddOnePiece(board, p, 0, DARK);
4642 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
4645 for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
4646 // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
4647 // but we leave King and Rooks for last, to possibly obey FRC restriction
4648 if(p == (int)WhiteRook) continue;
4649 while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
4650 if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY); // add the odd piece
4653 // now everything is placed, except perhaps King (Unicorn) and Rooks
4655 if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
4656 // Last King gets castling rights
4657 while(piecesLeft[(int)WhiteUnicorn]) {
4658 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
4659 initialRights[2] = initialRights[5] = board[CASTLING][2] = board[CASTLING][5] = i;
4662 while(piecesLeft[(int)WhiteKing]) {
4663 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
4664 initialRights[2] = initialRights[5] = board[CASTLING][2] = board[CASTLING][5] = i;
4669 while(piecesLeft[(int)WhiteKing]) AddOnePiece(board, WhiteKing, 0, ANY);
4670 while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
4673 // Only Rooks can be left; simply place them all
4674 while(piecesLeft[(int)WhiteRook]) {
4675 i = put(board, WhiteRook, 0, 0, ANY);
4676 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
4679 initialRights[1] = initialRights[4] = board[CASTLING][1] = board[CASTLING][4] = i;
4681 initialRights[0] = initialRights[3] = board[CASTLING][0] = board[CASTLING][3] = i;
4684 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
4685 board[BOARD_HEIGHT-1][i] = (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
4688 if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
4691 int SetCharTable( char *table, const char * map )
4692 /* [HGM] moved here from winboard.c because of its general usefulness */
4693 /* Basically a safe strcpy that uses the last character as King */
4695 int result = FALSE; int NrPieces;
4697 if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
4698 && NrPieces >= 12 && !(NrPieces&1)) {
4699 int i; /* [HGM] Accept even length from 12 to 34 */
4701 for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
4702 for( i=0; i<NrPieces/2-1; i++ ) {
4704 table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
4706 table[(int) WhiteKing] = map[NrPieces/2-1];
4707 table[(int) BlackKing] = map[NrPieces-1];
4715 void Prelude(Board board)
4716 { // [HGM] superchess: random selection of exo-pieces
4717 int i, j, k; ChessSquare p;
4718 static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
4720 GetPositionNumber(); // use FRC position number
4722 if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
4723 SetCharTable(pieceToChar, appData.pieceToCharTable);
4724 for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
4725 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
4728 j = seed%4; seed /= 4;
4729 p = board[0][BOARD_LEFT+j]; board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
4730 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
4731 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
4732 j = seed%3 + (seed%3 >= j); seed /= 3;
4733 p = board[0][BOARD_LEFT+j]; board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
4734 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
4735 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
4736 j = seed%3; seed /= 3;
4737 p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
4738 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
4739 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
4740 j = seed%2 + (seed%2 >= j); seed /= 2;
4741 p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
4742 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
4743 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
4744 j = seed%4; seed /= 4; put(board, exoPieces[3], 0, j, ANY);
4745 j = seed%3; seed /= 3; put(board, exoPieces[2], 0, j, ANY);
4746 j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
4747 put(board, exoPieces[0], 0, 0, ANY);
4748 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
4752 InitPosition(redraw)
4755 ChessSquare (* pieces)[BOARD_FILES];
4756 int i, j, pawnRow, overrule,
4757 oldx = gameInfo.boardWidth,
4758 oldy = gameInfo.boardHeight,
4759 oldh = gameInfo.holdingsWidth,
4760 oldv = gameInfo.variant;
4762 if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
4764 /* [AS] Initialize pv info list [HGM] and game status */
4766 for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
4767 pvInfoList[i].depth = 0;
4768 boards[i][EP_STATUS] = EP_NONE;
4769 for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
4772 initialRulePlies = 0; /* 50-move counter start */
4774 castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
4775 castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
4779 /* [HGM] logic here is completely changed. In stead of full positions */
4780 /* the initialized data only consist of the two backranks. The switch */
4781 /* selects which one we will use, which is than copied to the Board */
4782 /* initialPosition, which for the rest is initialized by Pawns and */
4783 /* empty squares. This initial position is then copied to boards[0], */
4784 /* possibly after shuffling, so that it remains available. */
4786 gameInfo.holdingsWidth = 0; /* default board sizes */
4787 gameInfo.boardWidth = 8;
4788 gameInfo.boardHeight = 8;
4789 gameInfo.holdingsSize = 0;
4790 nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
4791 for(i=0; i<BOARD_FILES-2; i++)
4792 initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
4793 initialPosition[EP_STATUS] = EP_NONE;
4794 SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
4796 switch (gameInfo.variant) {
4797 case VariantFischeRandom:
4798 shuffleOpenings = TRUE;
4802 case VariantShatranj:
4803 pieces = ShatranjArray;
4804 nrCastlingRights = 0;
4805 SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
4808 pieces = makrukArray;
4809 nrCastlingRights = 0;
4810 startedFromSetupPosition = TRUE;
4811 SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
4813 case VariantTwoKings:
4814 pieces = twoKingsArray;
4816 case VariantCapaRandom:
4817 shuffleOpenings = TRUE;
4818 case VariantCapablanca:
4819 pieces = CapablancaArray;
4820 gameInfo.boardWidth = 10;
4821 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
4824 pieces = GothicArray;
4825 gameInfo.boardWidth = 10;
4826 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
4829 pieces = JanusArray;
4830 gameInfo.boardWidth = 10;
4831 SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
4832 nrCastlingRights = 6;
4833 initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
4834 initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
4835 initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
4836 initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
4837 initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
4838 initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
4841 pieces = FalconArray;
4842 gameInfo.boardWidth = 10;
4843 SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
4845 case VariantXiangqi:
4846 pieces = XiangqiArray;
4847 gameInfo.boardWidth = 9;
4848 gameInfo.boardHeight = 10;
4849 nrCastlingRights = 0;
4850 SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
4853 pieces = ShogiArray;
4854 gameInfo.boardWidth = 9;
4855 gameInfo.boardHeight = 9;
4856 gameInfo.holdingsSize = 7;
4857 nrCastlingRights = 0;
4858 SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
4860 case VariantCourier:
4861 pieces = CourierArray;
4862 gameInfo.boardWidth = 12;
4863 nrCastlingRights = 0;
4864 SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
4866 case VariantKnightmate:
4867 pieces = KnightmateArray;
4868 SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
4871 pieces = fairyArray;
4872 SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
4875 pieces = GreatArray;
4876 gameInfo.boardWidth = 10;
4877 SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
4878 gameInfo.holdingsSize = 8;
4882 SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
4883 gameInfo.holdingsSize = 8;
4884 startedFromSetupPosition = TRUE;
4886 case VariantCrazyhouse:
4887 case VariantBughouse:
4889 SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
4890 gameInfo.holdingsSize = 5;
4892 case VariantWildCastle:
4894 /* !!?shuffle with kings guaranteed to be on d or e file */
4895 shuffleOpenings = 1;
4897 case VariantNoCastle:
4899 nrCastlingRights = 0;
4900 /* !!?unconstrained back-rank shuffle */
4901 shuffleOpenings = 1;
4906 if(appData.NrFiles >= 0) {
4907 if(gameInfo.boardWidth != appData.NrFiles) overrule++;
4908 gameInfo.boardWidth = appData.NrFiles;
4910 if(appData.NrRanks >= 0) {
4911 gameInfo.boardHeight = appData.NrRanks;
4913 if(appData.holdingsSize >= 0) {
4914 i = appData.holdingsSize;
4915 if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
4916 gameInfo.holdingsSize = i;
4918 if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
4919 if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
4920 DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
4922 pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
4923 if(pawnRow < 1) pawnRow = 1;
4924 if(gameInfo.variant == VariantMakruk) pawnRow = 2;
4926 /* User pieceToChar list overrules defaults */
4927 if(appData.pieceToCharTable != NULL)
4928 SetCharTable(pieceToChar, appData.pieceToCharTable);
4930 for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
4932 if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
4933 s = (ChessSquare) 0; /* account holding counts in guard band */
4934 for( i=0; i<BOARD_HEIGHT; i++ )
4935 initialPosition[i][j] = s;
4937 if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
4938 initialPosition[0][j] = pieces[0][j-gameInfo.holdingsWidth];
4939 initialPosition[pawnRow][j] = WhitePawn;
4940 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = BlackPawn;
4941 if(gameInfo.variant == VariantXiangqi) {
4943 initialPosition[pawnRow][j] =
4944 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
4945 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
4946 initialPosition[2][j] = WhiteCannon;
4947 initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
4951 initialPosition[BOARD_HEIGHT-1][j] = pieces[1][j-gameInfo.holdingsWidth];
4953 if( (gameInfo.variant == VariantShogi) && !overrule ) {
4956 initialPosition[1][j] = WhiteBishop;
4957 initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
4959 initialPosition[1][j] = WhiteRook;
4960 initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
4963 if( nrCastlingRights == -1) {
4964 /* [HGM] Build normal castling rights (must be done after board sizing!) */
4965 /* This sets default castling rights from none to normal corners */
4966 /* Variants with other castling rights must set them themselves above */
4967 nrCastlingRights = 6;
4969 initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
4970 initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
4971 initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
4972 initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
4973 initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
4974 initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
4977 if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
4978 if(gameInfo.variant == VariantGreat) { // promotion commoners
4979 initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
4980 initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
4981 initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
4982 initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
4984 if (appData.debugMode) {
4985 fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
4987 if(shuffleOpenings) {
4988 SetUpShuffle(initialPosition, appData.defaultFrcPosition);
4989 startedFromSetupPosition = TRUE;
4991 if(startedFromPositionFile) {
4992 /* [HGM] loadPos: use PositionFile for every new game */
4993 CopyBoard(initialPosition, filePosition);
4994 for(i=0; i<nrCastlingRights; i++)
4995 initialRights[i] = filePosition[CASTLING][i];
4996 startedFromSetupPosition = TRUE;
4999 CopyBoard(boards[0], initialPosition);
5001 if(oldx != gameInfo.boardWidth ||
5002 oldy != gameInfo.boardHeight ||
5003 oldh != gameInfo.holdingsWidth
5005 || oldv == VariantGothic || // For licensing popups
5006 gameInfo.variant == VariantGothic
5009 || oldv == VariantFalcon ||
5010 gameInfo.variant == VariantFalcon
5013 InitDrawingSizes(-2 ,0);
5016 DrawPosition(TRUE, boards[currentMove]);
5020 SendBoard(cps, moveNum)
5021 ChessProgramState *cps;
5024 char message[MSG_SIZ];
5026 if (cps->useSetboard) {
5027 char* fen = PositionToFEN(moveNum, cps->fenOverride);
5028 sprintf(message, "setboard %s\n", fen);
5029 SendToProgram(message, cps);
5035 /* Kludge to set black to move, avoiding the troublesome and now
5036 * deprecated "black" command.
5038 if (!WhiteOnMove(moveNum)) SendToProgram("a2a3\n", cps);
5040 SendToProgram("edit\n", cps);
5041 SendToProgram("#\n", cps);
5042 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
5043 bp = &boards[moveNum][i][BOARD_LEFT];
5044 for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
5045 if ((int) *bp < (int) BlackPawn) {
5046 sprintf(message, "%c%c%c\n", PieceToChar(*bp),
5048 if(message[0] == '+' || message[0] == '~') {
5049 sprintf(message, "%c%c%c+\n",
5050 PieceToChar((ChessSquare)(DEMOTED *bp)),
5053 if(cps->alphaRank) { /* [HGM] shogi: translate coords */
5054 message[1] = BOARD_RGHT - 1 - j + '1';
5055 message[2] = BOARD_HEIGHT - 1 - i + 'a';
5057 SendToProgram(message, cps);
5062 SendToProgram("c\n", cps);
5063 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
5064 bp = &boards[moveNum][i][BOARD_LEFT];
5065 for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
5066 if (((int) *bp != (int) EmptySquare)
5067 && ((int) *bp >= (int) BlackPawn)) {
5068 sprintf(message, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
5070 if(message[0] == '+' || message[0] == '~') {
5071 sprintf(message, "%c%c%c+\n",
5072 PieceToChar((ChessSquare)(DEMOTED *bp)),
5075 if(cps->alphaRank) { /* [HGM] shogi: translate coords */
5076 message[1] = BOARD_RGHT - 1 - j + '1';
5077 message[2] = BOARD_HEIGHT - 1 - i + 'a';
5079 SendToProgram(message, cps);
5084 SendToProgram(".\n", cps);
5086 setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
5090 HasPromotionChoice(int fromX, int fromY, int toX, int toY, char *promoChoice)
5092 /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
5093 /* [HGM] add Shogi promotions */
5094 int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
5099 if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
5100 if(toX < BOARD_LEFT || toX >= BOARD_RGHT) return FALSE; // move into holdings
5102 if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
5103 !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
5106 piece = boards[currentMove][fromY][fromX];
5107 if(gameInfo.variant == VariantShogi) {
5108 promotionZoneSize = 3;
5109 highestPromotingPiece = (int)WhiteFerz;
5110 } else if(gameInfo.variant == VariantMakruk) {
5111 promotionZoneSize = 3;
5114 // next weed out all moves that do not touch the promotion zone at all
5115 if((int)piece >= BlackPawn) {
5116 if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
5118 highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
5120 if( toY < BOARD_HEIGHT - promotionZoneSize &&
5121 fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
5124 if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
5126 // weed out mandatory Shogi promotions
5127 if(gameInfo.variant == VariantShogi) {
5128 if(piece >= BlackPawn) {
5129 if(toY == 0 && piece == BlackPawn ||
5130 toY == 0 && piece == BlackQueen ||
5131 toY <= 1 && piece == BlackKnight) {
5136 if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
5137 toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
5138 toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
5145 // weed out obviously illegal Pawn moves
5146 if(appData.testLegality && (piece == WhitePawn || piece == BlackPawn) ) {
5147 if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
5148 if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
5149 if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
5150 if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
5151 // note we are not allowed to test for valid (non-)capture, due to premove
5154 // we either have a choice what to promote to, or (in Shogi) whether to promote
5155 if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk) {
5156 *promoChoice = PieceToChar(BlackFerz); // no choice
5159 if(appData.alwaysPromoteToQueen) { // predetermined
5160 if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantLosers)
5161 *promoChoice = PieceToChar(BlackKing); // in Suicide Q is the last thing we want
5162 else *promoChoice = PieceToChar(BlackQueen);
5166 // suppress promotion popup on illegal moves that are not premoves
5167 premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
5168 gameMode == IcsPlayingBlack && WhiteOnMove(currentMove);
5169 if(appData.testLegality && !premove) {
5170 moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5171 fromY, fromX, toY, toX, NULLCHAR);
5172 if(moveType != WhitePromotionQueen && moveType != BlackPromotionQueen &&
5173 moveType != WhitePromotionKnight && moveType != BlackPromotionKnight)
5181 InPalace(row, column)
5183 { /* [HGM] for Xiangqi */
5184 if( (row < 3 || row > BOARD_HEIGHT-4) &&
5185 column < (BOARD_WIDTH + 4)/2 &&
5186 column > (BOARD_WIDTH - 5)/2 ) return TRUE;
5191 PieceForSquare (x, y)
5195 if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
5198 return boards[currentMove][y][x];
5202 OKToStartUserMove(x, y)
5205 ChessSquare from_piece;
5208 if (matchMode) return FALSE;
5209 if (gameMode == EditPosition) return TRUE;
5211 if (x >= 0 && y >= 0)
5212 from_piece = boards[currentMove][y][x];
5214 from_piece = EmptySquare;
5216 if (from_piece == EmptySquare) return FALSE;
5218 white_piece = (int)from_piece >= (int)WhitePawn &&
5219 (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
5222 case PlayFromGameFile:
5224 case TwoMachinesPlay:
5232 case MachinePlaysWhite:
5233 case IcsPlayingBlack:
5234 if (appData.zippyPlay) return FALSE;
5236 DisplayMoveError(_("You are playing Black"));
5241 case MachinePlaysBlack:
5242 case IcsPlayingWhite:
5243 if (appData.zippyPlay) return FALSE;
5245 DisplayMoveError(_("You are playing White"));
5251 if (!white_piece && WhiteOnMove(currentMove)) {
5252 DisplayMoveError(_("It is White's turn"));
5255 if (white_piece && !WhiteOnMove(currentMove)) {
5256 DisplayMoveError(_("It is Black's turn"));
5259 if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
5260 /* Editing correspondence game history */
5261 /* Could disallow this or prompt for confirmation */
5266 case BeginningOfGame:
5267 if (appData.icsActive) return FALSE;
5268 if (!appData.noChessProgram) {
5270 DisplayMoveError(_("You are playing White"));
5277 if (!white_piece && WhiteOnMove(currentMove)) {
5278 DisplayMoveError(_("It is White's turn"));
5281 if (white_piece && !WhiteOnMove(currentMove)) {
5282 DisplayMoveError(_("It is Black's turn"));
5291 if (currentMove != forwardMostMove && gameMode != AnalyzeMode
5292 && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
5293 && gameMode != AnalyzeFile && gameMode != Training) {
5294 DisplayMoveError(_("Displayed position is not current"));
5300 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
5301 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
5302 int lastLoadGameUseList = FALSE;
5303 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
5304 ChessMove lastLoadGameStart = (ChessMove) 0;
5307 UserMoveTest(fromX, fromY, toX, toY, promoChar, captureOwn)
5308 int fromX, fromY, toX, toY;
5313 ChessSquare pdown, pup;
5315 /* Check if the user is playing in turn. This is complicated because we
5316 let the user "pick up" a piece before it is his turn. So the piece he
5317 tried to pick up may have been captured by the time he puts it down!
5318 Therefore we use the color the user is supposed to be playing in this
5319 test, not the color of the piece that is currently on the starting
5320 square---except in EditGame mode, where the user is playing both
5321 sides; fortunately there the capture race can't happen. (It can
5322 now happen in IcsExamining mode, but that's just too bad. The user
5323 will get a somewhat confusing message in that case.)
5327 case PlayFromGameFile:
5329 case TwoMachinesPlay:
5333 /* We switched into a game mode where moves are not accepted,
5334 perhaps while the mouse button was down. */
5335 return ImpossibleMove;
5337 case MachinePlaysWhite:
5338 /* User is moving for Black */
5339 if (WhiteOnMove(currentMove)) {
5340 DisplayMoveError(_("It is White's turn"));
5341 return ImpossibleMove;
5345 case MachinePlaysBlack:
5346 /* User is moving for White */
5347 if (!WhiteOnMove(currentMove)) {
5348 DisplayMoveError(_("It is Black's turn"));
5349 return ImpossibleMove;
5355 case BeginningOfGame:
5358 if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
5359 (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
5360 /* User is moving for Black */
5361 if (WhiteOnMove(currentMove)) {
5362 DisplayMoveError(_("It is White's turn"));
5363 return ImpossibleMove;
5366 /* User is moving for White */
5367 if (!WhiteOnMove(currentMove)) {
5368 DisplayMoveError(_("It is Black's turn"));
5369 return ImpossibleMove;
5374 case IcsPlayingBlack:
5375 /* User is moving for Black */
5376 if (WhiteOnMove(currentMove)) {
5377 if (!appData.premove) {
5378 DisplayMoveError(_("It is White's turn"));
5379 } else if (toX >= 0 && toY >= 0) {
5382 premoveFromX = fromX;
5383 premoveFromY = fromY;
5384 premovePromoChar = promoChar;
5386 if (appData.debugMode)
5387 fprintf(debugFP, "Got premove: fromX %d,"
5388 "fromY %d, toX %d, toY %d\n",
5389 fromX, fromY, toX, toY);
5391 return ImpossibleMove;
5395 case IcsPlayingWhite:
5396 /* User is moving for White */
5397 if (!WhiteOnMove(currentMove)) {
5398 if (!appData.premove) {
5399 DisplayMoveError(_("It is Black's turn"));
5400 } else if (toX >= 0 && toY >= 0) {
5403 premoveFromX = fromX;
5404 premoveFromY = fromY;
5405 premovePromoChar = promoChar;
5407 if (appData.debugMode)
5408 fprintf(debugFP, "Got premove: fromX %d,"
5409 "fromY %d, toX %d, toY %d\n",
5410 fromX, fromY, toX, toY);
5412 return ImpossibleMove;
5420 /* EditPosition, empty square, or different color piece;
5421 click-click move is possible */
5422 if (toX == -2 || toY == -2) {
5423 boards[0][fromY][fromX] = EmptySquare;
5424 return AmbiguousMove;
5425 } else if (toX >= 0 && toY >= 0) {
5426 boards[0][toY][toX] = boards[0][fromY][fromX];
5427 if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
5428 if(boards[0][fromY][0] != EmptySquare) {
5429 if(boards[0][fromY][1]) boards[0][fromY][1]--;
5430 if(boards[0][fromY][1] == 0) boards[0][fromY][0] = EmptySquare;
5433 if(fromX == BOARD_RGHT+1) {
5434 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
5435 if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
5436 if(boards[0][fromY][BOARD_WIDTH-2] == 0) boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
5439 boards[0][fromY][fromX] = EmptySquare;
5440 return AmbiguousMove;
5442 return ImpossibleMove;
5445 if(toX < 0 || toY < 0) return ImpossibleMove;
5446 pdown = boards[currentMove][fromY][fromX];
5447 pup = boards[currentMove][toY][toX];
5449 /* [HGM] If move started in holdings, it means a drop */
5450 if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) {
5451 if( pup != EmptySquare ) return ImpossibleMove;
5452 if(appData.testLegality) {
5453 /* it would be more logical if LegalityTest() also figured out
5454 * which drops are legal. For now we forbid pawns on back rank.
5455 * Shogi is on its own here...
5457 if( (pdown == WhitePawn || pdown == BlackPawn) &&
5458 (toY == 0 || toY == BOARD_HEIGHT -1 ) )
5459 return(ImpossibleMove); /* no pawn drops on 1st/8th */
5461 return WhiteDrop; /* Not needed to specify white or black yet */
5464 userOfferedDraw = FALSE;
5466 /* [HGM] always test for legality, to get promotion info */
5467 moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5468 fromY, fromX, toY, toX, promoChar);
5469 /* [HGM] but possibly ignore an IllegalMove result */
5470 if (appData.testLegality) {
5471 if (moveType == IllegalMove || moveType == ImpossibleMove) {
5472 DisplayMoveError(_("Illegal move"));
5473 return ImpossibleMove;
5478 /* [HGM] <popupFix> in stead of calling FinishMove directly, this
5479 function is made into one that returns an OK move type if FinishMove
5480 should be called. This to give the calling driver routine the
5481 opportunity to finish the userMove input with a promotion popup,
5482 without bothering the user with this for invalid or illegal moves */
5484 /* FinishMove(moveType, fromX, fromY, toX, toY, promoChar); */
5487 /* Common tail of UserMoveEvent and DropMenuEvent */
5489 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
5491 int fromX, fromY, toX, toY;
5492 /*char*/int promoChar;
5496 if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) && promoChar != NULLCHAR) {
5497 // [HGM] superchess: suppress promotions to non-available piece
5498 int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
5499 if(WhiteOnMove(currentMove)) {
5500 if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
5502 if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
5506 /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
5507 move type in caller when we know the move is a legal promotion */
5508 if(moveType == NormalMove && promoChar)
5509 moveType = PromoCharToMoveType(WhiteOnMove(currentMove), promoChar);
5511 /* [HGM] convert drag-and-drop piece drops to standard form */
5512 if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ){
5513 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
5514 if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n",
5515 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
5516 // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
5517 if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
5518 fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
5519 while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++;
5523 /* [HGM] <popupFix> The following if has been moved here from
5524 UserMoveEvent(). Because it seemed to belong here (why not allow
5525 piece drops in training games?), and because it can only be
5526 performed after it is known to what we promote. */
5527 if (gameMode == Training) {
5528 /* compare the move played on the board to the next move in the
5529 * game. If they match, display the move and the opponent's response.
5530 * If they don't match, display an error message.
5534 CopyBoard(testBoard, boards[currentMove]);
5535 ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
5537 if (CompareBoards(testBoard, boards[currentMove+1])) {
5538 ForwardInner(currentMove+1);
5540 /* Autoplay the opponent's response.
5541 * if appData.animate was TRUE when Training mode was entered,
5542 * the response will be animated.
5544 saveAnimate = appData.animate;
5545 appData.animate = animateTraining;
5546 ForwardInner(currentMove+1);
5547 appData.animate = saveAnimate;
5549 /* check for the end of the game */
5550 if (currentMove >= forwardMostMove) {
5551 gameMode = PlayFromGameFile;
5553 SetTrainingModeOff();
5554 DisplayInformation(_("End of game"));
5557 DisplayError(_("Incorrect move"), 0);
5562 /* Ok, now we know that the move is good, so we can kill
5563 the previous line in Analysis Mode */
5564 if ((gameMode == AnalyzeMode || gameMode == EditGame)
5565 && currentMove < forwardMostMove) {
5566 PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
5569 /* If we need the chess program but it's dead, restart it */
5570 ResurrectChessProgram();
5572 /* A user move restarts a paused game*/
5576 thinkOutput[0] = NULLCHAR;
5578 MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
5580 if (gameMode == BeginningOfGame) {
5581 if (appData.noChessProgram) {
5582 gameMode = EditGame;
5586 gameMode = MachinePlaysBlack;
5589 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
5591 if (first.sendName) {
5592 sprintf(buf, "name %s\n", gameInfo.white);
5593 SendToProgram(buf, &first);
5600 /* Relay move to ICS or chess engine */
5601 if (appData.icsActive) {
5602 if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
5603 gameMode == IcsExamining) {
5604 SendMoveToICS(moveType, fromX, fromY, toX, toY);
5608 if (first.sendTime && (gameMode == BeginningOfGame ||
5609 gameMode == MachinePlaysWhite ||
5610 gameMode == MachinePlaysBlack)) {
5611 SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
5613 if (gameMode != EditGame && gameMode != PlayFromGameFile) {
5614 // [HGM] book: if program might be playing, let it use book
5615 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
5616 first.maybeThinking = TRUE;
5617 } else SendMoveToProgram(forwardMostMove-1, &first);
5618 if (currentMove == cmailOldMove + 1) {
5619 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
5623 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5627 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
5633 if (WhiteOnMove(currentMove)) {
5634 GameEnds(BlackWins, "Black mates", GE_PLAYER);
5636 GameEnds(WhiteWins, "White mates", GE_PLAYER);
5640 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
5645 case MachinePlaysBlack:
5646 case MachinePlaysWhite:
5647 /* disable certain menu options while machine is thinking */
5648 SetMachineThinkingEnables();
5655 if(bookHit) { // [HGM] book: simulate book reply
5656 static char bookMove[MSG_SIZ]; // a bit generous?
5658 programStats.nodes = programStats.depth = programStats.time =
5659 programStats.score = programStats.got_only_move = 0;
5660 sprintf(programStats.movelist, "%s (xbook)", bookHit);
5662 strcpy(bookMove, "move ");
5663 strcat(bookMove, bookHit);
5664 HandleMachineMove(bookMove, &first);
5670 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
5671 int fromX, fromY, toX, toY;
5674 /* [HGM] This routine was added to allow calling of its two logical
5675 parts from other modules in the old way. Before, UserMoveEvent()
5676 automatically called FinishMove() if the move was OK, and returned
5677 otherwise. I separated the two, in order to make it possible to
5678 slip a promotion popup in between. But that it always needs two
5679 calls, to the first part, (now called UserMoveTest() ), and to
5680 FinishMove if the first part succeeded. Calls that do not need
5681 to do anything in between, can call this routine the old way.
5683 ChessMove moveType = UserMoveTest(fromX, fromY, toX, toY, promoChar, FALSE);
5684 if(appData.debugMode) fprintf(debugFP, "moveType 4 = %d, promochar = %x\n", moveType, promoChar);
5685 if(moveType == AmbiguousMove)
5686 DrawPosition(FALSE, boards[currentMove]);
5687 else if(moveType != ImpossibleMove && moveType != Comment)
5688 FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
5692 Mark(board, flags, kind, rf, ff, rt, ft, closure)
5699 typedef char Markers[BOARD_RANKS][BOARD_FILES];
5700 Markers *m = (Markers *) closure;
5701 if(rf == fromY && ff == fromX)
5702 (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
5703 || kind == WhiteCapturesEnPassant
5704 || kind == BlackCapturesEnPassant);
5705 else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
5709 MarkTargetSquares(int clear)
5712 if(!appData.markers || !appData.highlightDragging ||
5713 !appData.testLegality || gameMode == EditPosition) return;
5715 for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) marker[y][x] = 0;
5718 GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker);
5719 if(PosFlags(0) & F_MANDATORY_CAPTURE) {
5720 for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
5722 for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
5725 DrawPosition(TRUE, NULL);
5728 void LeftClick(ClickType clickType, int xPix, int yPix)
5731 Boolean saveAnimate;
5732 static int second = 0, promotionChoice = 0;
5733 char promoChoice = NULLCHAR;
5735 if (clickType == Press) ErrorPopDown();
5736 MarkTargetSquares(1);
5738 x = EventToSquare(xPix, BOARD_WIDTH);
5739 y = EventToSquare(yPix, BOARD_HEIGHT);
5740 if (!flipView && y >= 0) {
5741 y = BOARD_HEIGHT - 1 - y;
5743 if (flipView && x >= 0) {
5744 x = BOARD_WIDTH - 1 - x;
5747 if(promotionChoice) { // we are waiting for a click to indicate promotion piece
5748 if(clickType == Release) return; // ignore upclick of click-click destination
5749 promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
5750 if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
5751 if(gameInfo.holdingsWidth &&
5752 (WhiteOnMove(currentMove)
5753 ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y > 0
5754 : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT-1) ) {
5755 // click in right holdings, for determining promotion piece
5756 ChessSquare p = boards[currentMove][y][x];
5757 if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
5758 if(p != EmptySquare) {
5759 FinishMove(NormalMove, fromX, fromY, toX, toY, ToLower(PieceToChar(p)));
5764 DrawPosition(FALSE, boards[currentMove]);
5768 /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
5769 if(clickType == Press
5770 && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
5771 || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
5772 || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
5776 if (clickType == Press) {
5778 if (OKToStartUserMove(x, y)) {
5782 MarkTargetSquares(0);
5783 DragPieceBegin(xPix, yPix);
5784 if (appData.highlightDragging) {
5785 SetHighlights(x, y, -1, -1);
5793 if (clickType == Press && gameMode != EditPosition) {
5798 // ignore off-board to clicks
5799 if(y < 0 || x < 0) return;
5801 /* Check if clicking again on the same color piece */
5802 fromP = boards[currentMove][fromY][fromX];
5803 toP = boards[currentMove][y][x];
5804 frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom;
5805 if ((WhitePawn <= fromP && fromP <= WhiteKing &&
5806 WhitePawn <= toP && toP <= WhiteKing &&
5807 !(fromP == WhiteKing && toP == WhiteRook && frc) &&
5808 !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
5809 (BlackPawn <= fromP && fromP <= BlackKing &&
5810 BlackPawn <= toP && toP <= BlackKing &&
5811 !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
5812 !(fromP == BlackKing && toP == BlackRook && frc))) {
5813 /* Clicked again on same color piece -- changed his mind */
5814 second = (x == fromX && y == fromY);
5815 if (appData.highlightDragging) {
5816 SetHighlights(x, y, -1, -1);
5820 if (OKToStartUserMove(x, y)) {
5823 MarkTargetSquares(0);
5824 DragPieceBegin(xPix, yPix);
5828 // ignore clicks on holdings
5829 if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
5832 if (clickType == Release && x == fromX && y == fromY) {
5833 DragPieceEnd(xPix, yPix);
5834 if (appData.animateDragging) {
5835 /* Undo animation damage if any */
5836 DrawPosition(FALSE, NULL);
5839 /* Second up/down in same square; just abort move */
5844 ClearPremoveHighlights();
5846 /* First upclick in same square; start click-click mode */
5847 SetHighlights(x, y, -1, -1);
5852 /* we now have a different from- and (possibly off-board) to-square */
5853 /* Completed move */
5856 saveAnimate = appData.animate;
5857 if (clickType == Press) {
5858 /* Finish clickclick move */
5859 if (appData.animate || appData.highlightLastMove) {
5860 SetHighlights(fromX, fromY, toX, toY);
5865 /* Finish drag move */
5866 if (appData.highlightLastMove) {
5867 SetHighlights(fromX, fromY, toX, toY);
5871 DragPieceEnd(xPix, yPix);
5872 /* Don't animate move and drag both */
5873 appData.animate = FALSE;
5876 // moves into holding are invalid for now (except in EditPosition, adapting to-square)
5877 if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
5878 ChessSquare piece = boards[currentMove][fromY][fromX];
5879 if(gameMode == EditPosition && piece != EmptySquare &&
5880 fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
5883 if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
5884 n = PieceToNumber(piece - (int)BlackPawn);
5885 if(n > gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
5886 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
5887 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
5889 if(x == BOARD_RGHT+1 && piece < BlackPawn) {
5890 n = PieceToNumber(piece);
5891 if(n > gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
5892 boards[currentMove][n][BOARD_WIDTH-1] = piece;
5893 boards[currentMove][n][BOARD_WIDTH-2]++;
5895 boards[currentMove][fromY][fromX] = EmptySquare;
5899 DrawPosition(TRUE, boards[currentMove]);
5903 // off-board moves should not be highlighted
5904 if(x < 0 || x < 0) ClearHighlights();
5906 if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice)) {
5907 SetHighlights(fromX, fromY, toX, toY);
5908 if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
5909 // [HGM] super: promotion to captured piece selected from holdings
5910 ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
5911 promotionChoice = TRUE;
5912 // kludge follows to temporarily execute move on display, without promoting yet
5913 boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
5914 boards[currentMove][toY][toX] = p;
5915 DrawPosition(FALSE, boards[currentMove]);
5916 boards[currentMove][fromY][fromX] = p; // take back, but display stays
5917 boards[currentMove][toY][toX] = q;
5918 DisplayMessage("Click in holdings to choose piece", "");
5923 UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
5924 if (!appData.highlightLastMove || gotPremove) ClearHighlights();
5925 if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
5928 appData.animate = saveAnimate;
5929 if (appData.animate || appData.animateDragging) {
5930 /* Undo animation damage if needed */
5931 DrawPosition(FALSE, NULL);
5935 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
5937 // char * hint = lastHint;
5938 FrontEndProgramStats stats;
5940 stats.which = cps == &first ? 0 : 1;
5941 stats.depth = cpstats->depth;
5942 stats.nodes = cpstats->nodes;
5943 stats.score = cpstats->score;
5944 stats.time = cpstats->time;
5945 stats.pv = cpstats->movelist;
5946 stats.hint = lastHint;
5947 stats.an_move_index = 0;
5948 stats.an_move_count = 0;
5950 if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
5951 stats.hint = cpstats->move_name;
5952 stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
5953 stats.an_move_count = cpstats->nr_moves;
5956 if(stats.pv && stats.pv[0]) strcpy(lastPV[stats.which], stats.pv); // [HGM] pv: remember last PV of each
5958 SetProgramStats( &stats );
5961 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
5962 { // [HGM] book: this routine intercepts moves to simulate book replies
5963 char *bookHit = NULL;
5965 //first determine if the incoming move brings opponent into his book
5966 if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
5967 bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
5968 if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
5969 if(bookHit != NULL && !cps->bookSuspend) {
5970 // make sure opponent is not going to reply after receiving move to book position
5971 SendToProgram("force\n", cps);
5972 cps->bookSuspend = TRUE; // flag indicating it has to be restarted
5974 if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
5975 // now arrange restart after book miss
5977 // after a book hit we never send 'go', and the code after the call to this routine
5978 // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
5980 if (cps->useUsermove) sprintf(buf, "usermove "); // sorry, no SAN yet :(
5981 sprintf(buf, "%s\n", bookHit); // force book move into program supposed to play it
5982 SendToProgram(buf, cps);
5983 if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
5984 } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
5985 SendToProgram("go\n", cps);
5986 cps->bookSuspend = FALSE; // after a 'go' we are never suspended
5987 } else { // 'go' might be sent based on 'firstMove' after this routine returns
5988 if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
5989 SendToProgram("go\n", cps);
5990 cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
5992 return bookHit; // notify caller of hit, so it can take action to send move to opponent
5996 ChessProgramState *savedState;
5997 void DeferredBookMove(void)
5999 if(savedState->lastPing != savedState->lastPong)
6000 ScheduleDelayedEvent(DeferredBookMove, 10);
6002 HandleMachineMove(savedMessage, savedState);
6006 HandleMachineMove(message, cps)
6008 ChessProgramState *cps;
6010 char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
6011 char realname[MSG_SIZ];
6012 int fromX, fromY, toX, toY;
6021 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
6023 * Kludge to ignore BEL characters
6025 while (*message == '\007') message++;
6028 * [HGM] engine debug message: ignore lines starting with '#' character
6030 if(cps->debug && *message == '#') return;
6033 * Look for book output
6035 if (cps == &first && bookRequested) {
6036 if (message[0] == '\t' || message[0] == ' ') {
6037 /* Part of the book output is here; append it */
6038 strcat(bookOutput, message);
6039 strcat(bookOutput, " \n");
6041 } else if (bookOutput[0] != NULLCHAR) {
6042 /* All of book output has arrived; display it */
6043 char *p = bookOutput;
6044 while (*p != NULLCHAR) {
6045 if (*p == '\t') *p = ' ';
6048 DisplayInformation(bookOutput);
6049 bookRequested = FALSE;
6050 /* Fall through to parse the current output */
6055 * Look for machine move.
6057 if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
6058 (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
6060 /* This method is only useful on engines that support ping */
6061 if (cps->lastPing != cps->lastPong) {
6062 if (gameMode == BeginningOfGame) {
6063 /* Extra move from before last new; ignore */
6064 if (appData.debugMode) {
6065 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
6068 if (appData.debugMode) {
6069 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
6070 cps->which, gameMode);
6073 SendToProgram("undo\n", cps);
6079 case BeginningOfGame:
6080 /* Extra move from before last reset; ignore */
6081 if (appData.debugMode) {
6082 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
6089 /* Extra move after we tried to stop. The mode test is
6090 not a reliable way of detecting this problem, but it's
6091 the best we can do on engines that don't support ping.
6093 if (appData.debugMode) {
6094 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
6095 cps->which, gameMode);
6097 SendToProgram("undo\n", cps);
6100 case MachinePlaysWhite:
6101 case IcsPlayingWhite:
6102 machineWhite = TRUE;
6105 case MachinePlaysBlack:
6106 case IcsPlayingBlack:
6107 machineWhite = FALSE;
6110 case TwoMachinesPlay:
6111 machineWhite = (cps->twoMachinesColor[0] == 'w');
6114 if (WhiteOnMove(forwardMostMove) != machineWhite) {
6115 if (appData.debugMode) {
6117 "Ignoring move out of turn by %s, gameMode %d"
6118 ", forwardMost %d\n",
6119 cps->which, gameMode, forwardMostMove);
6124 if (appData.debugMode) { int f = forwardMostMove;
6125 fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
6126 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
6127 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
6129 if(cps->alphaRank) AlphaRank(machineMove, 4);
6130 if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
6131 &fromX, &fromY, &toX, &toY, &promoChar)) {
6132 /* Machine move could not be parsed; ignore it. */
6133 sprintf(buf1, _("Illegal move \"%s\" from %s machine"),
6134 machineMove, cps->which);
6135 DisplayError(buf1, 0);
6136 sprintf(buf1, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
6137 machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
6138 if (gameMode == TwoMachinesPlay) {
6139 GameEnds(machineWhite ? BlackWins : WhiteWins,
6145 /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
6146 /* So we have to redo legality test with true e.p. status here, */
6147 /* to make sure an illegal e.p. capture does not slip through, */
6148 /* to cause a forfeit on a justified illegal-move complaint */
6149 /* of the opponent. */
6150 if( gameMode==TwoMachinesPlay && appData.testLegality
6151 && fromY != DROP_RANK /* [HGM] temporary; should still add legality test for drops */
6154 moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
6155 fromY, fromX, toY, toX, promoChar);
6156 if (appData.debugMode) {
6158 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
6159 boards[forwardMostMove][CASTLING][i], castlingRank[i]);
6160 fprintf(debugFP, "castling rights\n");
6162 if(moveType == IllegalMove) {
6163 sprintf(buf1, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
6164 machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
6165 GameEnds(machineWhite ? BlackWins : WhiteWins,
6168 } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
6169 /* [HGM] Kludge to handle engines that send FRC-style castling
6170 when they shouldn't (like TSCP-Gothic) */
6172 case WhiteASideCastleFR:
6173 case BlackASideCastleFR:
6175 currentMoveString[2]++;
6177 case WhiteHSideCastleFR:
6178 case BlackHSideCastleFR:
6180 currentMoveString[2]--;
6182 default: ; // nothing to do, but suppresses warning of pedantic compilers
6185 hintRequested = FALSE;
6186 lastHint[0] = NULLCHAR;
6187 bookRequested = FALSE;
6188 /* Program may be pondering now */
6189 cps->maybeThinking = TRUE;
6190 if (cps->sendTime == 2) cps->sendTime = 1;
6191 if (cps->offeredDraw) cps->offeredDraw--;
6194 if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
6196 SendMoveToICS(moveType, fromX, fromY, toX, toY);
6198 if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
6199 char buf[3*MSG_SIZ];
6201 sprintf(buf, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
6202 programStats.score / 100.,
6204 programStats.time / 100.,
6205 (unsigned int)programStats.nodes,
6206 (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
6207 programStats.movelist);
6209 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
6213 /* currentMoveString is set as a side-effect of ParseOneMove */
6214 strcpy(machineMove, currentMoveString);
6215 strcat(machineMove, "\n");
6216 strcpy(moveList[forwardMostMove], machineMove);
6218 /* [AS] Save move info and clear stats for next move */
6219 pvInfoList[ forwardMostMove ].score = programStats.score;
6220 pvInfoList[ forwardMostMove ].depth = programStats.depth;
6221 pvInfoList[ forwardMostMove ].time = programStats.time; // [HGM] PGNtime: take time from engine stats
6222 ClearProgramStats();
6223 thinkOutput[0] = NULLCHAR;
6224 hiddenThinkOutputState = 0;
6226 MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
6228 /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
6229 if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
6232 while( count < adjudicateLossPlies ) {
6233 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
6236 score = -score; /* Flip score for winning side */
6239 if( score > adjudicateLossThreshold ) {
6246 if( count >= adjudicateLossPlies ) {
6247 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6249 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
6250 "Xboard adjudication",
6257 if( gameMode == TwoMachinesPlay ) {
6258 // [HGM] some adjudications useful with buggy engines
6259 int k, count = 0; static int bare = 1;
6260 if(gameInfo.holdingsSize == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
6263 if( appData.testLegality )
6264 { /* [HGM] Some more adjudications for obstinate engines */
6265 int NrWN=0, NrBN=0, NrWB=0, NrBB=0, NrWR=0, NrBR=0,
6266 NrWQ=0, NrBQ=0, NrW=0, NrK=0, bishopsColor = 0,
6267 NrPieces=0, NrPawns=0, PawnAdvance=0, i, j;
6268 static int moveCount = 6;
6270 char *reason = NULL;
6272 /* Count what is on board. */
6273 for(i=0; i<BOARD_HEIGHT; i++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
6274 { ChessSquare p = boards[forwardMostMove][i][j];
6278 { /* count B,N,R and other of each side */
6281 NrK++; break; // [HGM] atomic: count Kings
6285 case WhiteFerz: // [HGM] shatranj: kludge to mke it work in shatranj
6286 bishopsColor |= 1 << ((i^j)&1);
6291 case BlackFerz: // [HGM] shatranj: kludge to mke it work in shatranj
6292 bishopsColor |= 1 << ((i^j)&1);
6307 PawnAdvance += m; NrPawns++;
6309 NrPieces += (p != EmptySquare);
6310 NrW += ((int)p < (int)BlackPawn);
6311 if(gameInfo.variant == VariantXiangqi &&
6312 (p == WhiteFerz || p == WhiteAlfil || p == BlackFerz || p == BlackAlfil)) {
6313 NrPieces--; // [HGM] XQ: do not count purely defensive pieces
6314 NrW -= ((int)p < (int)BlackPawn);
6318 /* Some material-based adjudications that have to be made before stalemate test */
6319 if(gameInfo.variant == VariantAtomic && NrK < 2) {
6320 // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
6321 boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
6322 if(appData.checkMates) {
6323 SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets move
6324 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6325 GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
6326 "Xboard adjudication: King destroyed", GE_XBOARD );
6331 /* Bare King in Shatranj (loses) or Losers (wins) */
6332 if( NrW == 1 || NrPieces - NrW == 1) {
6333 if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
6334 boards[forwardMostMove][EP_STATUS] = EP_WINS; // mark as win, so it becomes claimable
6335 if(appData.checkMates) {
6336 SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets to see move
6337 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6338 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
6339 "Xboard adjudication: Bare king", GE_XBOARD );
6343 if( gameInfo.variant == VariantShatranj && --bare < 0)
6345 boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
6346 if(appData.checkMates) {
6347 /* but only adjudicate if adjudication enabled */
6348 SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets move
6349 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6350 GameEnds( NrW > 1 ? WhiteWins : NrPieces - NrW > 1 ? BlackWins : GameIsDrawn,
6351 "Xboard adjudication: Bare king", GE_XBOARD );
6358 // don't wait for engine to announce game end if we can judge ourselves
6359 switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
6361 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
6362 int i, checkCnt = 0; // (should really be done by making nr of checks part of game state)
6363 for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
6364 if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
6367 reason = "Xboard adjudication: 3rd check";
6368 boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
6378 reason = "Xboard adjudication: Stalemate";
6379 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
6380 boards[forwardMostMove][EP_STATUS] = EP_STALEMATE; // default result for stalemate is draw
6381 if(gameInfo.variant == VariantLosers || gameInfo.variant == VariantGiveaway) // [HGM] losers:
6382 boards[forwardMostMove][EP_STATUS] = EP_WINS; // in these variants stalemated is always a win
6383 else if(gameInfo.variant == VariantSuicide) // in suicide it depends
6384 boards[forwardMostMove][EP_STATUS] = NrW == NrPieces-NrW ? EP_STALEMATE :
6385 ((NrW < NrPieces-NrW) != WhiteOnMove(forwardMostMove) ?
6386 EP_CHECKMATE : EP_WINS);
6387 else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
6388 boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
6392 reason = "Xboard adjudication: Checkmate";
6393 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
6397 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
6399 result = GameIsDrawn; break;
6401 result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
6403 result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
6405 result = (ChessMove) 0;
6407 if(appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
6408 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6409 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6410 GameEnds( result, reason, GE_XBOARD );
6414 /* Next absolutely insufficient mating material. */
6415 if( NrPieces == 2 || gameInfo.variant != VariantXiangqi &&
6416 gameInfo.variant != VariantShatranj && // [HGM] baring will remain possible
6417 (NrPieces == 3 && NrWN+NrBN+NrWB+NrBB == 1 ||
6418 NrPieces == NrBB+NrWB+2 && bishopsColor != 3)) // [HGM] all Bishops (Ferz!) same color
6419 { /* KBK, KNK, KK of KBKB with like Bishops */
6421 /* always flag draws, for judging claims */
6422 boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
6424 if(appData.materialDraws) {
6425 /* but only adjudicate them if adjudication enabled */
6426 SendToProgram("force\n", cps->other); // suppress reply
6427 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see last move */
6428 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6429 GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
6434 /* Then some trivial draws (only adjudicate, cannot be claimed) */
6436 ( NrWR == 1 && NrBR == 1 /* KRKR */
6437 || NrWQ==1 && NrBQ==1 /* KQKQ */
6438 || NrWN==2 || NrBN==2 /* KNNK */
6439 || NrWN+NrWB == 1 && NrBN+NrBB == 1 /* KBKN, KBKB, KNKN */
6441 if(--moveCount < 0 && appData.trivialDraws)
6442 { /* if the first 3 moves do not show a tactical win, declare draw */
6443 SendToProgram("force\n", cps->other); // suppress reply
6444 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6445 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6446 GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
6449 } else moveCount = 6;
6453 if (appData.debugMode) { int i;
6454 fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
6455 forwardMostMove, backwardMostMove, boards[backwardMostMove][EP_STATUS],
6456 appData.drawRepeats);
6457 for( i=forwardMostMove; i>=backwardMostMove; i-- )
6458 fprintf(debugFP, "%d ep=%d\n", i, (signed char)boards[i][EP_STATUS]);
6462 /* Check for rep-draws */
6464 for(k = forwardMostMove-2;
6465 k>=backwardMostMove && k>=forwardMostMove-100 &&
6466 (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
6467 (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE;
6470 if(CompareBoards(boards[k], boards[forwardMostMove])) {
6471 /* compare castling rights */
6472 if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
6473 (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
6474 rights++; /* King lost rights, while rook still had them */
6475 if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
6476 if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
6477 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
6478 rights++; /* but at least one rook lost them */
6480 if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
6481 (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
6483 if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
6484 if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
6485 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
6488 if( rights == 0 && ++count > appData.drawRepeats-2
6489 && appData.drawRepeats > 1) {
6490 /* adjudicate after user-specified nr of repeats */
6491 SendToProgram("force\n", cps->other); // suppress reply
6492 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6493 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6494 if(gameInfo.variant == VariantXiangqi && appData.testLegality) {
6495 // [HGM] xiangqi: check for forbidden perpetuals
6496 int m, ourPerpetual = 1, hisPerpetual = 1;
6497 for(m=forwardMostMove; m>k; m-=2) {
6498 if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
6499 ourPerpetual = 0; // the current mover did not always check
6500 if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
6501 hisPerpetual = 0; // the opponent did not always check
6503 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
6504 ourPerpetual, hisPerpetual);
6505 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
6506 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
6507 "Xboard adjudication: perpetual checking", GE_XBOARD );
6510 if(hisPerpetual && !ourPerpetual) // he is checking us, but did not repeat yet
6511 break; // (or we would have caught him before). Abort repetition-checking loop.
6512 // Now check for perpetual chases
6513 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
6514 hisPerpetual = PerpetualChase(k, forwardMostMove);
6515 ourPerpetual = PerpetualChase(k+1, forwardMostMove);
6516 if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
6517 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
6518 "Xboard adjudication: perpetual chasing", GE_XBOARD );
6521 if(hisPerpetual && !ourPerpetual) // he is chasing us, but did not repeat yet
6522 break; // Abort repetition-checking loop.
6524 // if neither of us is checking or chasing all the time, or both are, it is draw
6526 GameEnds( GameIsDrawn, "Xboard adjudication: repetition draw", GE_XBOARD );
6529 if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
6530 boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
6534 /* Now we test for 50-move draws. Determine ply count */
6535 count = forwardMostMove;
6536 /* look for last irreversble move */
6537 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
6539 /* if we hit starting position, add initial plies */
6540 if( count == backwardMostMove )
6541 count -= initialRulePlies;
6542 count = forwardMostMove - count;
6544 boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
6545 /* this is used to judge if draw claims are legal */
6546 if(appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
6547 SendToProgram("force\n", cps->other); // suppress reply
6548 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6549 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6550 GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
6554 /* if draw offer is pending, treat it as a draw claim
6555 * when draw condition present, to allow engines a way to
6556 * claim draws before making their move to avoid a race
6557 * condition occurring after their move
6559 if( cps->other->offeredDraw || cps->offeredDraw ) {
6561 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
6562 p = "Draw claim: 50-move rule";
6563 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
6564 p = "Draw claim: 3-fold repetition";
6565 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
6566 p = "Draw claim: insufficient mating material";
6568 SendToProgram("force\n", cps->other); // suppress reply
6569 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6570 GameEnds( GameIsDrawn, p, GE_XBOARD );
6571 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6577 if( appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
6578 SendToProgram("force\n", cps->other); // suppress reply
6579 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6580 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6582 GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
6589 if (gameMode == TwoMachinesPlay) {
6590 /* [HGM] relaying draw offers moved to after reception of move */
6591 /* and interpreting offer as claim if it brings draw condition */
6592 if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
6593 SendToProgram("draw\n", cps->other);
6595 if (cps->other->sendTime) {
6596 SendTimeRemaining(cps->other,
6597 cps->other->twoMachinesColor[0] == 'w');
6599 bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
6600 if (firstMove && !bookHit) {
6602 if (cps->other->useColors) {
6603 SendToProgram(cps->other->twoMachinesColor, cps->other);
6605 SendToProgram("go\n", cps->other);
6607 cps->other->maybeThinking = TRUE;
6610 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6612 if (!pausing && appData.ringBellAfterMoves) {
6617 * Reenable menu items that were disabled while
6618 * machine was thinking
6620 if (gameMode != TwoMachinesPlay)
6621 SetUserThinkingEnables();
6623 // [HGM] book: after book hit opponent has received move and is now in force mode
6624 // force the book reply into it, and then fake that it outputted this move by jumping
6625 // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
6627 static char bookMove[MSG_SIZ]; // a bit generous?
6629 strcpy(bookMove, "move ");
6630 strcat(bookMove, bookHit);
6633 programStats.nodes = programStats.depth = programStats.time =
6634 programStats.score = programStats.got_only_move = 0;
6635 sprintf(programStats.movelist, "%s (xbook)", bookHit);
6637 if(cps->lastPing != cps->lastPong) {
6638 savedMessage = message; // args for deferred call
6640 ScheduleDelayedEvent(DeferredBookMove, 10);
6649 /* Set special modes for chess engines. Later something general
6650 * could be added here; for now there is just one kludge feature,
6651 * needed because Crafty 15.10 and earlier don't ignore SIGINT
6652 * when "xboard" is given as an interactive command.
6654 if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
6655 cps->useSigint = FALSE;
6656 cps->useSigterm = FALSE;
6658 if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
6659 ParseFeatures(message+8, cps);
6660 return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
6663 /* [HGM] Allow engine to set up a position. Don't ask me why one would
6664 * want this, I was asked to put it in, and obliged.
6666 if (!strncmp(message, "setboard ", 9)) {
6667 Board initial_position;
6669 GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
6671 if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
6672 DisplayError(_("Bad FEN received from engine"), 0);
6676 CopyBoard(boards[0], initial_position);
6677 initialRulePlies = FENrulePlies;
6678 if(blackPlaysFirst) gameMode = MachinePlaysWhite;
6679 else gameMode = MachinePlaysBlack;
6680 DrawPosition(FALSE, boards[currentMove]);
6686 * Look for communication commands
6688 if (!strncmp(message, "telluser ", 9)) {
6689 DisplayNote(message + 9);
6692 if (!strncmp(message, "tellusererror ", 14)) {
6694 DisplayError(message + 14, 0);
6697 if (!strncmp(message, "tellopponent ", 13)) {
6698 if (appData.icsActive) {
6700 snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
6704 DisplayNote(message + 13);
6708 if (!strncmp(message, "tellothers ", 11)) {
6709 if (appData.icsActive) {
6711 snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
6717 if (!strncmp(message, "tellall ", 8)) {
6718 if (appData.icsActive) {
6720 snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
6724 DisplayNote(message + 8);
6728 if (strncmp(message, "warning", 7) == 0) {
6729 /* Undocumented feature, use tellusererror in new code */
6730 DisplayError(message, 0);
6733 if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
6734 strcpy(realname, cps->tidy);
6735 strcat(realname, " query");
6736 AskQuestion(realname, buf2, buf1, cps->pr);
6739 /* Commands from the engine directly to ICS. We don't allow these to be
6740 * sent until we are logged on. Crafty kibitzes have been known to
6741 * interfere with the login process.
6744 if (!strncmp(message, "tellics ", 8)) {
6745 SendToICS(message + 8);
6749 if (!strncmp(message, "tellicsnoalias ", 15)) {
6750 SendToICS(ics_prefix);
6751 SendToICS(message + 15);
6755 /* The following are for backward compatibility only */
6756 if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
6757 !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
6758 SendToICS(ics_prefix);
6764 if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
6768 * If the move is illegal, cancel it and redraw the board.
6769 * Also deal with other error cases. Matching is rather loose
6770 * here to accommodate engines written before the spec.
6772 if (strncmp(message + 1, "llegal move", 11) == 0 ||
6773 strncmp(message, "Error", 5) == 0) {
6774 if (StrStr(message, "name") ||
6775 StrStr(message, "rating") || StrStr(message, "?") ||
6776 StrStr(message, "result") || StrStr(message, "board") ||
6777 StrStr(message, "bk") || StrStr(message, "computer") ||
6778 StrStr(message, "variant") || StrStr(message, "hint") ||
6779 StrStr(message, "random") || StrStr(message, "depth") ||
6780 StrStr(message, "accepted")) {
6783 if (StrStr(message, "protover")) {
6784 /* Program is responding to input, so it's apparently done
6785 initializing, and this error message indicates it is
6786 protocol version 1. So we don't need to wait any longer
6787 for it to initialize and send feature commands. */
6788 FeatureDone(cps, 1);
6789 cps->protocolVersion = 1;
6792 cps->maybeThinking = FALSE;
6794 if (StrStr(message, "draw")) {
6795 /* Program doesn't have "draw" command */
6796 cps->sendDrawOffers = 0;
6799 if (cps->sendTime != 1 &&
6800 (StrStr(message, "time") || StrStr(message, "otim"))) {
6801 /* Program apparently doesn't have "time" or "otim" command */
6805 if (StrStr(message, "analyze")) {
6806 cps->analysisSupport = FALSE;
6807 cps->analyzing = FALSE;
6809 sprintf(buf2, _("%s does not support analysis"), cps->tidy);
6810 DisplayError(buf2, 0);
6813 if (StrStr(message, "(no matching move)st")) {
6814 /* Special kludge for GNU Chess 4 only */
6815 cps->stKludge = TRUE;
6816 SendTimeControl(cps, movesPerSession, timeControl,
6817 timeIncrement, appData.searchDepth,
6821 if (StrStr(message, "(no matching move)sd")) {
6822 /* Special kludge for GNU Chess 4 only */
6823 cps->sdKludge = TRUE;
6824 SendTimeControl(cps, movesPerSession, timeControl,
6825 timeIncrement, appData.searchDepth,
6829 if (!StrStr(message, "llegal")) {
6832 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
6833 gameMode == IcsIdle) return;
6834 if (forwardMostMove <= backwardMostMove) return;
6835 if (pausing) PauseEvent();
6836 if(appData.forceIllegal) {
6837 // [HGM] illegal: machine refused move; force position after move into it
6838 SendToProgram("force\n", cps);
6839 if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
6840 // we have a real problem now, as SendBoard will use the a2a3 kludge
6841 // when black is to move, while there might be nothing on a2 or black
6842 // might already have the move. So send the board as if white has the move.
6843 // But first we must change the stm of the engine, as it refused the last move
6844 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
6845 if(WhiteOnMove(forwardMostMove)) {
6846 SendToProgram("a7a6\n", cps); // for the engine black still had the move
6847 SendBoard(cps, forwardMostMove); // kludgeless board
6849 SendToProgram("a2a3\n", cps); // for the engine white still had the move
6850 CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
6851 SendBoard(cps, forwardMostMove+1); // kludgeless board
6853 } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
6854 if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
6855 gameMode == TwoMachinesPlay)
6856 SendToProgram("go\n", cps);
6859 if (gameMode == PlayFromGameFile) {
6860 /* Stop reading this game file */
6861 gameMode = EditGame;
6864 currentMove = --forwardMostMove;
6865 DisplayMove(currentMove-1); /* before DisplayMoveError */
6867 DisplayBothClocks();
6868 sprintf(buf1, _("Illegal move \"%s\" (rejected by %s chess program)"),
6869 parseList[currentMove], cps->which);
6870 DisplayMoveError(buf1);
6871 DrawPosition(FALSE, boards[currentMove]);
6873 /* [HGM] illegal-move claim should forfeit game when Xboard */
6874 /* only passes fully legal moves */
6875 if( appData.testLegality && gameMode == TwoMachinesPlay ) {
6876 GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
6877 "False illegal-move claim", GE_XBOARD );
6881 if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
6882 /* Program has a broken "time" command that
6883 outputs a string not ending in newline.
6889 * If chess program startup fails, exit with an error message.
6890 * Attempts to recover here are futile.
6892 if ((StrStr(message, "unknown host") != NULL)
6893 || (StrStr(message, "No remote directory") != NULL)
6894 || (StrStr(message, "not found") != NULL)
6895 || (StrStr(message, "No such file") != NULL)
6896 || (StrStr(message, "can't alloc") != NULL)
6897 || (StrStr(message, "Permission denied") != NULL)) {
6899 cps->maybeThinking = FALSE;
6900 snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
6901 cps->which, cps->program, cps->host, message);
6902 RemoveInputSource(cps->isr);
6903 DisplayFatalError(buf1, 0, 1);
6908 * Look for hint output
6910 if (sscanf(message, "Hint: %s", buf1) == 1) {
6911 if (cps == &first && hintRequested) {
6912 hintRequested = FALSE;
6913 if (ParseOneMove(buf1, forwardMostMove, &moveType,
6914 &fromX, &fromY, &toX, &toY, &promoChar)) {
6915 (void) CoordsToAlgebraic(boards[forwardMostMove],
6916 PosFlags(forwardMostMove),
6917 fromY, fromX, toY, toX, promoChar, buf1);
6918 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
6919 DisplayInformation(buf2);
6921 /* Hint move could not be parsed!? */
6922 snprintf(buf2, sizeof(buf2),
6923 _("Illegal hint move \"%s\"\nfrom %s chess program"),
6925 DisplayError(buf2, 0);
6928 strcpy(lastHint, buf1);
6934 * Ignore other messages if game is not in progress
6936 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
6937 gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
6940 * look for win, lose, draw, or draw offer
6942 if (strncmp(message, "1-0", 3) == 0) {
6943 char *p, *q, *r = "";
6944 p = strchr(message, '{');
6952 GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
6954 } else if (strncmp(message, "0-1", 3) == 0) {
6955 char *p, *q, *r = "";
6956 p = strchr(message, '{');
6964 /* Kludge for Arasan 4.1 bug */
6965 if (strcmp(r, "Black resigns") == 0) {
6966 GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
6969 GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
6971 } else if (strncmp(message, "1/2", 3) == 0) {
6972 char *p, *q, *r = "";
6973 p = strchr(message, '{');
6982 GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
6985 } else if (strncmp(message, "White resign", 12) == 0) {
6986 GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
6988 } else if (strncmp(message, "Black resign", 12) == 0) {
6989 GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
6991 } else if (strncmp(message, "White matches", 13) == 0 ||
6992 strncmp(message, "Black matches", 13) == 0 ) {
6993 /* [HGM] ignore GNUShogi noises */
6995 } else if (strncmp(message, "White", 5) == 0 &&
6996 message[5] != '(' &&
6997 StrStr(message, "Black") == NULL) {
6998 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7000 } else if (strncmp(message, "Black", 5) == 0 &&
7001 message[5] != '(') {
7002 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7004 } else if (strcmp(message, "resign") == 0 ||
7005 strcmp(message, "computer resigns") == 0) {
7007 case MachinePlaysBlack:
7008 case IcsPlayingBlack:
7009 GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
7011 case MachinePlaysWhite:
7012 case IcsPlayingWhite:
7013 GameEnds(BlackWins, "White resigns", GE_ENGINE);
7015 case TwoMachinesPlay:
7016 if (cps->twoMachinesColor[0] == 'w')
7017 GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
7019 GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
7026 } else if (strncmp(message, "opponent mates", 14) == 0) {
7028 case MachinePlaysBlack:
7029 case IcsPlayingBlack:
7030 GameEnds(WhiteWins, "White mates", GE_ENGINE);
7032 case MachinePlaysWhite:
7033 case IcsPlayingWhite:
7034 GameEnds(BlackWins, "Black mates", GE_ENGINE);
7036 case TwoMachinesPlay:
7037 if (cps->twoMachinesColor[0] == 'w')
7038 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7040 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7047 } else if (strncmp(message, "computer mates", 14) == 0) {
7049 case MachinePlaysBlack:
7050 case IcsPlayingBlack:
7051 GameEnds(BlackWins, "Black mates", GE_ENGINE1);
7053 case MachinePlaysWhite:
7054 case IcsPlayingWhite:
7055 GameEnds(WhiteWins, "White mates", GE_ENGINE);
7057 case TwoMachinesPlay:
7058 if (cps->twoMachinesColor[0] == 'w')
7059 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7061 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7068 } else if (strncmp(message, "checkmate", 9) == 0) {
7069 if (WhiteOnMove(forwardMostMove)) {
7070 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7072 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7075 } else if (strstr(message, "Draw") != NULL ||
7076 strstr(message, "game is a draw") != NULL) {
7077 GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
7079 } else if (strstr(message, "offer") != NULL &&
7080 strstr(message, "draw") != NULL) {
7082 if (appData.zippyPlay && first.initDone) {
7083 /* Relay offer to ICS */
7084 SendToICS(ics_prefix);
7085 SendToICS("draw\n");
7088 cps->offeredDraw = 2; /* valid until this engine moves twice */
7089 if (gameMode == TwoMachinesPlay) {
7090 if (cps->other->offeredDraw) {
7091 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
7092 /* [HGM] in two-machine mode we delay relaying draw offer */
7093 /* until after we also have move, to see if it is really claim */
7095 } else if (gameMode == MachinePlaysWhite ||
7096 gameMode == MachinePlaysBlack) {
7097 if (userOfferedDraw) {
7098 DisplayInformation(_("Machine accepts your draw offer"));
7099 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
7101 DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
7108 * Look for thinking output
7110 if ( appData.showThinking // [HGM] thinking: test all options that cause this output
7111 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
7113 int plylev, mvleft, mvtot, curscore, time;
7114 char mvname[MOVE_LEN];
7118 int prefixHint = FALSE;
7119 mvname[0] = NULLCHAR;
7122 case MachinePlaysBlack:
7123 case IcsPlayingBlack:
7124 if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
7126 case MachinePlaysWhite:
7127 case IcsPlayingWhite:
7128 if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
7133 case IcsObserving: /* [DM] icsEngineAnalyze */
7134 if (!appData.icsEngineAnalyze) ignore = TRUE;
7136 case TwoMachinesPlay:
7137 if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
7148 if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
7149 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
7151 if (plyext != ' ' && plyext != '\t') {
7155 /* [AS] Negate score if machine is playing black and reporting absolute scores */
7156 if( cps->scoreIsAbsolute &&
7157 ( gameMode == MachinePlaysBlack ||
7158 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
7159 gameMode == IcsPlayingBlack || // [HGM] also add other situations where engine should report black POV
7160 (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
7161 !WhiteOnMove(currentMove)
7164 curscore = -curscore;
7168 programStats.depth = plylev;
7169 programStats.nodes = nodes;
7170 programStats.time = time;
7171 programStats.score = curscore;
7172 programStats.got_only_move = 0;
7174 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
7177 if(cps->nps == 0) ticklen = 10*time; // use engine reported time
7178 else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
7179 if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
7180 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
7181 whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
7182 if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
7183 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
7184 blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
7187 /* Buffer overflow protection */
7188 if (buf1[0] != NULLCHAR) {
7189 if (strlen(buf1) >= sizeof(programStats.movelist)
7190 && appData.debugMode) {
7192 "PV is too long; using the first %u bytes.\n",
7193 (unsigned) sizeof(programStats.movelist) - 1);
7196 safeStrCpy( programStats.movelist, buf1, sizeof(programStats.movelist) );
7198 sprintf(programStats.movelist, " no PV\n");
7201 if (programStats.seen_stat) {
7202 programStats.ok_to_send = 1;
7205 if (strchr(programStats.movelist, '(') != NULL) {
7206 programStats.line_is_book = 1;
7207 programStats.nr_moves = 0;
7208 programStats.moves_left = 0;
7210 programStats.line_is_book = 0;
7213 SendProgramStatsToFrontend( cps, &programStats );
7216 [AS] Protect the thinkOutput buffer from overflow... this
7217 is only useful if buf1 hasn't overflowed first!
7219 sprintf(thinkOutput, "[%d]%c%+.2f %s%s",
7221 (gameMode == TwoMachinesPlay ?
7222 ToUpper(cps->twoMachinesColor[0]) : ' '),
7223 ((double) curscore) / 100.0,
7224 prefixHint ? lastHint : "",
7225 prefixHint ? " " : "" );
7227 if( buf1[0] != NULLCHAR ) {
7228 unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
7230 if( strlen(buf1) > max_len ) {
7231 if( appData.debugMode) {
7232 fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
7234 buf1[max_len+1] = '\0';
7237 strcat( thinkOutput, buf1 );
7240 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
7241 || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7242 DisplayMove(currentMove - 1);
7246 } else if ((p=StrStr(message, "(only move)")) != NULL) {
7247 /* crafty (9.25+) says "(only move) <move>"
7248 * if there is only 1 legal move
7250 sscanf(p, "(only move) %s", buf1);
7251 sprintf(thinkOutput, "%s (only move)", buf1);
7252 sprintf(programStats.movelist, "%s (only move)", buf1);
7253 programStats.depth = 1;
7254 programStats.nr_moves = 1;
7255 programStats.moves_left = 1;
7256 programStats.nodes = 1;
7257 programStats.time = 1;
7258 programStats.got_only_move = 1;
7260 /* Not really, but we also use this member to
7261 mean "line isn't going to change" (Crafty
7262 isn't searching, so stats won't change) */
7263 programStats.line_is_book = 1;
7265 SendProgramStatsToFrontend( cps, &programStats );
7267 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
7268 gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7269 DisplayMove(currentMove - 1);
7272 } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
7273 &time, &nodes, &plylev, &mvleft,
7274 &mvtot, mvname) >= 5) {
7275 /* The stat01: line is from Crafty (9.29+) in response
7276 to the "." command */
7277 programStats.seen_stat = 1;
7278 cps->maybeThinking = TRUE;
7280 if (programStats.got_only_move || !appData.periodicUpdates)
7283 programStats.depth = plylev;
7284 programStats.time = time;
7285 programStats.nodes = nodes;
7286 programStats.moves_left = mvleft;
7287 programStats.nr_moves = mvtot;
7288 strcpy(programStats.move_name, mvname);
7289 programStats.ok_to_send = 1;
7290 programStats.movelist[0] = '\0';
7292 SendProgramStatsToFrontend( cps, &programStats );
7296 } else if (strncmp(message,"++",2) == 0) {
7297 /* Crafty 9.29+ outputs this */
7298 programStats.got_fail = 2;
7301 } else if (strncmp(message,"--",2) == 0) {
7302 /* Crafty 9.29+ outputs this */
7303 programStats.got_fail = 1;
7306 } else if (thinkOutput[0] != NULLCHAR &&
7307 strncmp(message, " ", 4) == 0) {
7308 unsigned message_len;
7311 while (*p && *p == ' ') p++;
7313 message_len = strlen( p );
7315 /* [AS] Avoid buffer overflow */
7316 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
7317 strcat(thinkOutput, " ");
7318 strcat(thinkOutput, p);
7321 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
7322 strcat(programStats.movelist, " ");
7323 strcat(programStats.movelist, p);
7326 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
7327 gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7328 DisplayMove(currentMove - 1);
7336 if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
7337 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
7339 ChessProgramStats cpstats;
7341 if (plyext != ' ' && plyext != '\t') {
7345 /* [AS] Negate score if machine is playing black and reporting absolute scores */
7346 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
7347 curscore = -curscore;
7350 cpstats.depth = plylev;
7351 cpstats.nodes = nodes;
7352 cpstats.time = time;
7353 cpstats.score = curscore;
7354 cpstats.got_only_move = 0;
7355 cpstats.movelist[0] = '\0';
7357 if (buf1[0] != NULLCHAR) {
7358 safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist) );
7361 cpstats.ok_to_send = 0;
7362 cpstats.line_is_book = 0;
7363 cpstats.nr_moves = 0;
7364 cpstats.moves_left = 0;
7366 SendProgramStatsToFrontend( cps, &cpstats );
7373 /* Parse a game score from the character string "game", and
7374 record it as the history of the current game. The game
7375 score is NOT assumed to start from the standard position.
7376 The display is not updated in any way.
7379 ParseGameHistory(game)
7383 int fromX, fromY, toX, toY, boardIndex;
7388 if (appData.debugMode)
7389 fprintf(debugFP, "Parsing game history: %s\n", game);
7391 if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
7392 gameInfo.site = StrSave(appData.icsHost);
7393 gameInfo.date = PGNDate();
7394 gameInfo.round = StrSave("-");
7396 /* Parse out names of players */
7397 while (*game == ' ') game++;
7399 while (*game != ' ') *p++ = *game++;
7401 gameInfo.white = StrSave(buf);
7402 while (*game == ' ') game++;
7404 while (*game != ' ' && *game != '\n') *p++ = *game++;
7406 gameInfo.black = StrSave(buf);
7409 boardIndex = blackPlaysFirst ? 1 : 0;
7412 yyboardindex = boardIndex;
7413 moveType = (ChessMove) yylex();
7415 case IllegalMove: /* maybe suicide chess, etc. */
7416 if (appData.debugMode) {
7417 fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
7418 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7419 setbuf(debugFP, NULL);
7421 case WhitePromotionChancellor:
7422 case BlackPromotionChancellor:
7423 case WhitePromotionArchbishop:
7424 case BlackPromotionArchbishop:
7425 case WhitePromotionQueen:
7426 case BlackPromotionQueen:
7427 case WhitePromotionRook:
7428 case BlackPromotionRook:
7429 case WhitePromotionBishop:
7430 case BlackPromotionBishop:
7431 case WhitePromotionKnight:
7432 case BlackPromotionKnight:
7433 case WhitePromotionKing:
7434 case BlackPromotionKing:
7436 case WhiteCapturesEnPassant:
7437 case BlackCapturesEnPassant:
7438 case WhiteKingSideCastle:
7439 case WhiteQueenSideCastle:
7440 case BlackKingSideCastle:
7441 case BlackQueenSideCastle:
7442 case WhiteKingSideCastleWild:
7443 case WhiteQueenSideCastleWild:
7444 case BlackKingSideCastleWild:
7445 case BlackQueenSideCastleWild:
7447 case WhiteHSideCastleFR:
7448 case WhiteASideCastleFR:
7449 case BlackHSideCastleFR:
7450 case BlackASideCastleFR:
7452 fromX = currentMoveString[0] - AAA;
7453 fromY = currentMoveString[1] - ONE;
7454 toX = currentMoveString[2] - AAA;
7455 toY = currentMoveString[3] - ONE;
7456 promoChar = currentMoveString[4];
7460 fromX = moveType == WhiteDrop ?
7461 (int) CharToPiece(ToUpper(currentMoveString[0])) :
7462 (int) CharToPiece(ToLower(currentMoveString[0]));
7464 toX = currentMoveString[2] - AAA;
7465 toY = currentMoveString[3] - ONE;
7466 promoChar = NULLCHAR;
7470 sprintf(buf, _("Ambiguous move in ICS output: \"%s\""), yy_text);
7471 if (appData.debugMode) {
7472 fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
7473 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7474 setbuf(debugFP, NULL);
7476 DisplayError(buf, 0);
7478 case ImpossibleMove:
7480 sprintf(buf, _("Illegal move in ICS output: \"%s\""), yy_text);
7481 if (appData.debugMode) {
7482 fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
7483 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7484 setbuf(debugFP, NULL);
7486 DisplayError(buf, 0);
7488 case (ChessMove) 0: /* end of file */
7489 if (boardIndex < backwardMostMove) {
7490 /* Oops, gap. How did that happen? */
7491 DisplayError(_("Gap in move list"), 0);
7494 backwardMostMove = blackPlaysFirst ? 1 : 0;
7495 if (boardIndex > forwardMostMove) {
7496 forwardMostMove = boardIndex;
7500 if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
7501 strcat(parseList[boardIndex-1], " ");
7502 strcat(parseList[boardIndex-1], yy_text);
7514 case GameUnfinished:
7515 if (gameMode == IcsExamining) {
7516 if (boardIndex < backwardMostMove) {
7517 /* Oops, gap. How did that happen? */
7520 backwardMostMove = blackPlaysFirst ? 1 : 0;
7523 gameInfo.result = moveType;
7524 p = strchr(yy_text, '{');
7525 if (p == NULL) p = strchr(yy_text, '(');
7528 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
7530 q = strchr(p, *p == '{' ? '}' : ')');
7531 if (q != NULL) *q = NULLCHAR;
7534 gameInfo.resultDetails = StrSave(p);
7537 if (boardIndex >= forwardMostMove &&
7538 !(gameMode == IcsObserving && ics_gamenum == -1)) {
7539 backwardMostMove = blackPlaysFirst ? 1 : 0;
7542 (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
7543 fromY, fromX, toY, toX, promoChar,
7544 parseList[boardIndex]);
7545 CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
7546 /* currentMoveString is set as a side-effect of yylex */
7547 strcpy(moveList[boardIndex], currentMoveString);
7548 strcat(moveList[boardIndex], "\n");
7550 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
7551 switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
7557 if(gameInfo.variant != VariantShogi)
7558 strcat(parseList[boardIndex - 1], "+");
7562 strcat(parseList[boardIndex - 1], "#");
7569 /* Apply a move to the given board */
7571 ApplyMove(fromX, fromY, toX, toY, promoChar, board)
7572 int fromX, fromY, toX, toY;
7576 ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
7577 int promoRank = gameInfo.variant == VariantMakruk ? 3 : 1;
7579 /* [HGM] compute & store e.p. status and castling rights for new position */
7580 /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
7583 if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
7584 oldEP = (signed char)board[EP_STATUS];
7585 board[EP_STATUS] = EP_NONE;
7587 if( board[toY][toX] != EmptySquare )
7588 board[EP_STATUS] = EP_CAPTURE;
7590 if( board[fromY][fromX] == WhitePawn ) {
7591 if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
7592 board[EP_STATUS] = EP_PAWN_MOVE;
7594 if(toX>BOARD_LEFT && board[toY][toX-1] == BlackPawn &&
7595 gameInfo.variant != VariantBerolina || toX < fromX)
7596 board[EP_STATUS] = toX | berolina;
7597 if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
7598 gameInfo.variant != VariantBerolina || toX > fromX)
7599 board[EP_STATUS] = toX;
7602 if( board[fromY][fromX] == BlackPawn ) {
7603 if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
7604 board[EP_STATUS] = EP_PAWN_MOVE;
7605 if( toY-fromY== -2) {
7606 if(toX>BOARD_LEFT && board[toY][toX-1] == WhitePawn &&
7607 gameInfo.variant != VariantBerolina || toX < fromX)
7608 board[EP_STATUS] = toX | berolina;
7609 if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
7610 gameInfo.variant != VariantBerolina || toX > fromX)
7611 board[EP_STATUS] = toX;
7615 for(i=0; i<nrCastlingRights; i++) {
7616 if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
7617 board[CASTLING][i] == toX && castlingRank[i] == toY
7618 ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
7623 /* [HGM] In Shatranj and Courier all promotions are to Ferz */
7624 if((gameInfo.variant==VariantShatranj || gameInfo.variant==VariantCourier || gameInfo.variant == VariantMakruk)
7625 && promoChar != 0) promoChar = PieceToChar(WhiteFerz);
7627 if (fromX == toX && fromY == toY) return;
7629 if (fromY == DROP_RANK) {
7631 piece = board[toY][toX] = (ChessSquare) fromX;
7633 piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
7634 king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
7635 if(gameInfo.variant == VariantKnightmate)
7636 king += (int) WhiteUnicorn - (int) WhiteKing;
7638 /* Code added by Tord: */
7639 /* FRC castling assumed when king captures friendly rook. */
7640 if (board[fromY][fromX] == WhiteKing &&
7641 board[toY][toX] == WhiteRook) {
7642 board[fromY][fromX] = EmptySquare;
7643 board[toY][toX] = EmptySquare;
7645 board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
7647 board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
7649 } else if (board[fromY][fromX] == BlackKing &&
7650 board[toY][toX] == BlackRook) {
7651 board[fromY][fromX] = EmptySquare;
7652 board[toY][toX] = EmptySquare;
7654 board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
7656 board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
7658 /* End of code added by Tord */
7660 } else if (board[fromY][fromX] == king
7661 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7662 && toY == fromY && toX > fromX+1) {
7663 board[fromY][fromX] = EmptySquare;
7664 board[toY][toX] = king;
7665 board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
7666 board[fromY][BOARD_RGHT-1] = EmptySquare;
7667 } else if (board[fromY][fromX] == king
7668 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7669 && toY == fromY && toX < fromX-1) {
7670 board[fromY][fromX] = EmptySquare;
7671 board[toY][toX] = king;
7672 board[toY][toX+1] = board[fromY][BOARD_LEFT];
7673 board[fromY][BOARD_LEFT] = EmptySquare;
7674 } else if (board[fromY][fromX] == WhitePawn
7675 && toY >= BOARD_HEIGHT-promoRank
7676 && gameInfo.variant != VariantXiangqi
7678 /* white pawn promotion */
7679 board[toY][toX] = CharToPiece(ToUpper(promoChar));
7680 if (board[toY][toX] == EmptySquare) {
7681 board[toY][toX] = WhiteQueen;
7683 if(gameInfo.variant==VariantBughouse ||
7684 gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
7685 board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
7686 board[fromY][fromX] = EmptySquare;
7687 } else if ((fromY == BOARD_HEIGHT-4)
7689 && gameInfo.variant != VariantXiangqi
7690 && gameInfo.variant != VariantBerolina
7691 && (board[fromY][fromX] == WhitePawn)
7692 && (board[toY][toX] == EmptySquare)) {
7693 board[fromY][fromX] = EmptySquare;
7694 board[toY][toX] = WhitePawn;
7695 captured = board[toY - 1][toX];
7696 board[toY - 1][toX] = EmptySquare;
7697 } else if ((fromY == BOARD_HEIGHT-4)
7699 && gameInfo.variant == VariantBerolina
7700 && (board[fromY][fromX] == WhitePawn)
7701 && (board[toY][toX] == EmptySquare)) {
7702 board[fromY][fromX] = EmptySquare;
7703 board[toY][toX] = WhitePawn;
7704 if(oldEP & EP_BEROLIN_A) {
7705 captured = board[fromY][fromX-1];
7706 board[fromY][fromX-1] = EmptySquare;
7707 }else{ captured = board[fromY][fromX+1];
7708 board[fromY][fromX+1] = EmptySquare;
7710 } else if (board[fromY][fromX] == king
7711 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7712 && toY == fromY && toX > fromX+1) {
7713 board[fromY][fromX] = EmptySquare;
7714 board[toY][toX] = king;
7715 board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
7716 board[fromY][BOARD_RGHT-1] = EmptySquare;
7717 } else if (board[fromY][fromX] == king
7718 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7719 && toY == fromY && toX < fromX-1) {
7720 board[fromY][fromX] = EmptySquare;
7721 board[toY][toX] = king;
7722 board[toY][toX+1] = board[fromY][BOARD_LEFT];
7723 board[fromY][BOARD_LEFT] = EmptySquare;
7724 } else if (fromY == 7 && fromX == 3
7725 && board[fromY][fromX] == BlackKing
7726 && toY == 7 && toX == 5) {
7727 board[fromY][fromX] = EmptySquare;
7728 board[toY][toX] = BlackKing;
7729 board[fromY][7] = EmptySquare;
7730 board[toY][4] = BlackRook;
7731 } else if (fromY == 7 && fromX == 3
7732 && board[fromY][fromX] == BlackKing
7733 && toY == 7 && toX == 1) {
7734 board[fromY][fromX] = EmptySquare;
7735 board[toY][toX] = BlackKing;
7736 board[fromY][0] = EmptySquare;
7737 board[toY][2] = BlackRook;
7738 } else if (board[fromY][fromX] == BlackPawn
7740 && gameInfo.variant != VariantXiangqi
7742 /* black pawn promotion */
7743 board[toY][toX] = CharToPiece(ToLower(promoChar));
7744 if (board[toY][toX] == EmptySquare) {
7745 board[toY][toX] = BlackQueen;
7747 if(gameInfo.variant==VariantBughouse ||
7748 gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
7749 board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
7750 board[fromY][fromX] = EmptySquare;
7751 } else if ((fromY == 3)
7753 && gameInfo.variant != VariantXiangqi
7754 && gameInfo.variant != VariantBerolina
7755 && (board[fromY][fromX] == BlackPawn)
7756 && (board[toY][toX] == EmptySquare)) {
7757 board[fromY][fromX] = EmptySquare;
7758 board[toY][toX] = BlackPawn;
7759 captured = board[toY + 1][toX];
7760 board[toY + 1][toX] = EmptySquare;
7761 } else if ((fromY == 3)
7763 && gameInfo.variant == VariantBerolina
7764 && (board[fromY][fromX] == BlackPawn)
7765 && (board[toY][toX] == EmptySquare)) {
7766 board[fromY][fromX] = EmptySquare;
7767 board[toY][toX] = BlackPawn;
7768 if(oldEP & EP_BEROLIN_A) {
7769 captured = board[fromY][fromX-1];
7770 board[fromY][fromX-1] = EmptySquare;
7771 }else{ captured = board[fromY][fromX+1];
7772 board[fromY][fromX+1] = EmptySquare;
7775 board[toY][toX] = board[fromY][fromX];
7776 board[fromY][fromX] = EmptySquare;
7779 /* [HGM] now we promote for Shogi, if needed */
7780 if(gameInfo.variant == VariantShogi && promoChar == 'q')
7781 board[toY][toX] = (ChessSquare) (PROMOTED piece);
7784 if (gameInfo.holdingsWidth != 0) {
7786 /* !!A lot more code needs to be written to support holdings */
7787 /* [HGM] OK, so I have written it. Holdings are stored in the */
7788 /* penultimate board files, so they are automaticlly stored */
7789 /* in the game history. */
7790 if (fromY == DROP_RANK) {
7791 /* Delete from holdings, by decreasing count */
7792 /* and erasing image if necessary */
7794 if(p < (int) BlackPawn) { /* white drop */
7795 p -= (int)WhitePawn;
7796 p = PieceToNumber((ChessSquare)p);
7797 if(p >= gameInfo.holdingsSize) p = 0;
7798 if(--board[p][BOARD_WIDTH-2] <= 0)
7799 board[p][BOARD_WIDTH-1] = EmptySquare;
7800 if((int)board[p][BOARD_WIDTH-2] < 0)
7801 board[p][BOARD_WIDTH-2] = 0;
7802 } else { /* black drop */
7803 p -= (int)BlackPawn;
7804 p = PieceToNumber((ChessSquare)p);
7805 if(p >= gameInfo.holdingsSize) p = 0;
7806 if(--board[BOARD_HEIGHT-1-p][1] <= 0)
7807 board[BOARD_HEIGHT-1-p][0] = EmptySquare;
7808 if((int)board[BOARD_HEIGHT-1-p][1] < 0)
7809 board[BOARD_HEIGHT-1-p][1] = 0;
7812 if (captured != EmptySquare && gameInfo.holdingsSize > 0
7813 && gameInfo.variant != VariantBughouse ) {
7814 /* [HGM] holdings: Add to holdings, if holdings exist */
7815 if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
7816 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
7817 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
7820 if (p >= (int) BlackPawn) {
7821 p -= (int)BlackPawn;
7822 if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
7823 /* in Shogi restore piece to its original first */
7824 captured = (ChessSquare) (DEMOTED captured);
7827 p = PieceToNumber((ChessSquare)p);
7828 if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
7829 board[p][BOARD_WIDTH-2]++;
7830 board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
7832 p -= (int)WhitePawn;
7833 if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
7834 captured = (ChessSquare) (DEMOTED captured);
7837 p = PieceToNumber((ChessSquare)p);
7838 if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
7839 board[BOARD_HEIGHT-1-p][1]++;
7840 board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
7843 } else if (gameInfo.variant == VariantAtomic) {
7844 if (captured != EmptySquare) {
7846 for (y = toY-1; y <= toY+1; y++) {
7847 for (x = toX-1; x <= toX+1; x++) {
7848 if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
7849 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
7850 board[y][x] = EmptySquare;
7854 board[toY][toX] = EmptySquare;
7857 if(gameInfo.variant == VariantShogi && promoChar != NULLCHAR && promoChar != '=') {
7858 /* [HGM] Shogi promotions */
7859 board[toY][toX] = (ChessSquare) (PROMOTED piece);
7862 if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
7863 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
7864 // [HGM] superchess: take promotion piece out of holdings
7865 int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
7866 if((int)piece < (int)BlackPawn) { // determine stm from piece color
7867 if(!--board[k][BOARD_WIDTH-2])
7868 board[k][BOARD_WIDTH-1] = EmptySquare;
7870 if(!--board[BOARD_HEIGHT-1-k][1])
7871 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
7877 /* Updates forwardMostMove */
7879 MakeMove(fromX, fromY, toX, toY, promoChar)
7880 int fromX, fromY, toX, toY;
7883 // forwardMostMove++; // [HGM] bare: moved downstream
7885 if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
7886 int timeLeft; static int lastLoadFlag=0; int king, piece;
7887 piece = boards[forwardMostMove][fromY][fromX];
7888 king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
7889 if(gameInfo.variant == VariantKnightmate)
7890 king += (int) WhiteUnicorn - (int) WhiteKing;
7891 if(forwardMostMove == 0) {
7893 fprintf(serverMoves, "%s;", second.tidy);
7894 fprintf(serverMoves, "%s;", first.tidy);
7895 if(!blackPlaysFirst)
7896 fprintf(serverMoves, "%s;", second.tidy);
7897 } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
7898 lastLoadFlag = loadFlag;
7900 fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
7901 // print castling suffix
7902 if( toY == fromY && piece == king ) {
7904 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
7906 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
7909 if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
7910 boards[forwardMostMove][fromY][fromX] == BlackPawn ) &&
7911 boards[forwardMostMove][toY][toX] == EmptySquare
7913 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
7915 if(promoChar != NULLCHAR)
7916 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);
7918 fprintf(serverMoves, "/%d/%d",
7919 pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
7920 if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
7921 else timeLeft = blackTimeRemaining/1000;
7922 fprintf(serverMoves, "/%d", timeLeft);
7924 fflush(serverMoves);
7927 if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations
7928 DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
7932 UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
7933 if (commentList[forwardMostMove+1] != NULL) {
7934 free(commentList[forwardMostMove+1]);
7935 commentList[forwardMostMove+1] = NULL;
7937 CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
7938 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
7939 forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
7940 SwitchClocks(); // uses forwardMostMove, so must be done after incrementing it !
7941 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
7942 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
7943 gameInfo.result = GameUnfinished;
7944 if (gameInfo.resultDetails != NULL) {
7945 free(gameInfo.resultDetails);
7946 gameInfo.resultDetails = NULL;
7948 CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
7949 moveList[forwardMostMove - 1]);
7950 (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
7951 PosFlags(forwardMostMove - 1),
7952 fromY, fromX, toY, toX, promoChar,
7953 parseList[forwardMostMove - 1]);
7954 switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
7960 if(gameInfo.variant != VariantShogi)
7961 strcat(parseList[forwardMostMove - 1], "+");
7965 strcat(parseList[forwardMostMove - 1], "#");
7968 if (appData.debugMode) {
7969 fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
7974 /* Updates currentMove if not pausing */
7976 ShowMove(fromX, fromY, toX, toY)
7978 int instant = (gameMode == PlayFromGameFile) ?
7979 (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
7980 if(appData.noGUI) return;
7981 if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
7983 if (forwardMostMove == currentMove + 1) {
7984 AnimateMove(boards[forwardMostMove - 1],
7985 fromX, fromY, toX, toY);
7987 if (appData.highlightLastMove) {
7988 SetHighlights(fromX, fromY, toX, toY);
7991 currentMove = forwardMostMove;
7994 if (instant) return;
7996 DisplayMove(currentMove - 1);
7997 DrawPosition(FALSE, boards[currentMove]);
7998 DisplayBothClocks();
7999 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
8002 void SendEgtPath(ChessProgramState *cps)
8003 { /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
8004 char buf[MSG_SIZ], name[MSG_SIZ], *p;
8006 if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
8009 char c, *q = name+1, *r, *s;
8011 name[0] = ','; // extract next format name from feature and copy with prefixed ','
8012 while(*p && *p != ',') *q++ = *p++;
8014 if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
8015 strcmp(name, ",nalimov:") == 0 ) {
8016 // take nalimov path from the menu-changeable option first, if it is defined
8017 sprintf(buf, "egtpath nalimov %s\n", appData.defaultPathEGTB);
8018 SendToProgram(buf,cps); // send egtbpath command for nalimov
8020 if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
8021 (s = StrStr(appData.egtFormats, name)) != NULL) {
8022 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
8023 s = r = StrStr(s, ":") + 1; // beginning of path info
8024 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
8025 c = *r; *r = 0; // temporarily null-terminate path info
8026 *--q = 0; // strip of trailig ':' from name
8027 sprintf(buf, "egtpath %s %s\n", name+1, s);
8029 SendToProgram(buf,cps); // send egtbpath command for this format
8031 if(*p == ',') p++; // read away comma to position for next format name
8036 InitChessProgram(cps, setup)
8037 ChessProgramState *cps;
8038 int setup; /* [HGM] needed to setup FRC opening position */
8040 char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
8041 if (appData.noChessProgram) return;
8042 hintRequested = FALSE;
8043 bookRequested = FALSE;
8045 /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
8046 /* moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
8047 if(cps->memSize) { /* [HGM] memory */
8048 sprintf(buf, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
8049 SendToProgram(buf, cps);
8051 SendEgtPath(cps); /* [HGM] EGT */
8052 if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
8053 sprintf(buf, "cores %d\n", appData.smpCores);
8054 SendToProgram(buf, cps);
8057 SendToProgram(cps->initString, cps);
8058 if (gameInfo.variant != VariantNormal &&
8059 gameInfo.variant != VariantLoadable
8060 /* [HGM] also send variant if board size non-standard */
8061 || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
8063 char *v = VariantName(gameInfo.variant);
8064 if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
8065 /* [HGM] in protocol 1 we have to assume all variants valid */
8066 sprintf(buf, _("Variant %s not supported by %s"), v, cps->tidy);
8067 DisplayFatalError(buf, 0, 1);
8071 /* [HGM] make prefix for non-standard board size. Awkward testing... */
8072 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8073 if( gameInfo.variant == VariantXiangqi )
8074 overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
8075 if( gameInfo.variant == VariantShogi )
8076 overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
8077 if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
8078 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
8079 if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
8080 gameInfo.variant == VariantGothic || gameInfo.variant == VariantFalcon )
8081 overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8082 if( gameInfo.variant == VariantCourier )
8083 overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8084 if( gameInfo.variant == VariantSuper )
8085 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
8086 if( gameInfo.variant == VariantGreat )
8087 overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
8090 sprintf(b, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
8091 gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
8092 /* [HGM] varsize: try first if this defiant size variant is specifically known */
8093 if(StrStr(cps->variants, b) == NULL) {
8094 // specific sized variant not known, check if general sizing allowed
8095 if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
8096 if(StrStr(cps->variants, "boardsize") == NULL) {
8097 sprintf(buf, "Board size %dx%d+%d not supported by %s",
8098 gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
8099 DisplayFatalError(buf, 0, 1);
8102 /* [HGM] here we really should compare with the maximum supported board size */
8105 } else sprintf(b, "%s", VariantName(gameInfo.variant));
8106 sprintf(buf, "variant %s\n", b);
8107 SendToProgram(buf, cps);
8109 currentlyInitializedVariant = gameInfo.variant;
8111 /* [HGM] send opening position in FRC to first engine */
8113 SendToProgram("force\n", cps);
8115 /* engine is now in force mode! Set flag to wake it up after first move. */
8116 setboardSpoiledMachineBlack = 1;
8120 snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
8121 SendToProgram(buf, cps);
8123 cps->maybeThinking = FALSE;
8124 cps->offeredDraw = 0;
8125 if (!appData.icsActive) {
8126 SendTimeControl(cps, movesPerSession, timeControl,
8127 timeIncrement, appData.searchDepth,
8130 if (appData.showThinking
8131 // [HGM] thinking: four options require thinking output to be sent
8132 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
8134 SendToProgram("post\n", cps);
8136 SendToProgram("hard\n", cps);
8137 if (!appData.ponderNextMove) {
8138 /* Warning: "easy" is a toggle in GNU Chess, so don't send
8139 it without being sure what state we are in first. "hard"
8140 is not a toggle, so that one is OK.
8142 SendToProgram("easy\n", cps);
8145 sprintf(buf, "ping %d\n", ++cps->lastPing);
8146 SendToProgram(buf, cps);
8148 cps->initDone = TRUE;
8153 StartChessProgram(cps)
8154 ChessProgramState *cps;
8159 if (appData.noChessProgram) return;
8160 cps->initDone = FALSE;
8162 if (strcmp(cps->host, "localhost") == 0) {
8163 err = StartChildProcess(cps->program, cps->dir, &cps->pr);
8164 } else if (*appData.remoteShell == NULLCHAR) {
8165 err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
8167 if (*appData.remoteUser == NULLCHAR) {
8168 snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
8171 snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
8172 cps->host, appData.remoteUser, cps->program);
8174 err = StartChildProcess(buf, "", &cps->pr);
8178 sprintf(buf, _("Startup failure on '%s'"), cps->program);
8179 DisplayFatalError(buf, err, 1);
8185 cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
8186 if (cps->protocolVersion > 1) {
8187 sprintf(buf, "xboard\nprotover %d\n", cps->protocolVersion);
8188 cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
8189 cps->comboCnt = 0; // and values of combo boxes
8190 SendToProgram(buf, cps);
8192 SendToProgram("xboard\n", cps);
8198 TwoMachinesEventIfReady P((void))
8200 if (first.lastPing != first.lastPong) {
8201 DisplayMessage("", _("Waiting for first chess program"));
8202 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
8205 if (second.lastPing != second.lastPong) {
8206 DisplayMessage("", _("Waiting for second chess program"));
8207 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
8215 NextMatchGame P((void))
8217 int index; /* [HGM] autoinc: step load index during match */
8219 if (*appData.loadGameFile != NULLCHAR) {
8220 index = appData.loadGameIndex;
8221 if(index < 0) { // [HGM] autoinc
8222 lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
8223 if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
8225 LoadGameFromFile(appData.loadGameFile,
8227 appData.loadGameFile, FALSE);
8228 } else if (*appData.loadPositionFile != NULLCHAR) {
8229 index = appData.loadPositionIndex;
8230 if(index < 0) { // [HGM] autoinc
8231 lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
8232 if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
8234 LoadPositionFromFile(appData.loadPositionFile,
8236 appData.loadPositionFile);
8238 TwoMachinesEventIfReady();
8241 void UserAdjudicationEvent( int result )
8243 ChessMove gameResult = GameIsDrawn;
8246 gameResult = WhiteWins;
8248 else if( result < 0 ) {
8249 gameResult = BlackWins;
8252 if( gameMode == TwoMachinesPlay ) {
8253 GameEnds( gameResult, "User adjudication", GE_XBOARD );
8258 // [HGM] save: calculate checksum of game to make games easily identifiable
8259 int StringCheckSum(char *s)
8262 if(s==NULL) return 0;
8263 while(*s) i = i*259 + *s++;
8270 for(i=backwardMostMove; i<forwardMostMove; i++) {
8271 sum += pvInfoList[i].depth;
8272 sum += StringCheckSum(parseList[i]);
8273 sum += StringCheckSum(commentList[i]);
8276 if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
8277 return sum + StringCheckSum(commentList[i]);
8278 } // end of save patch
8281 GameEnds(result, resultDetails, whosays)
8283 char *resultDetails;
8286 GameMode nextGameMode;
8290 if(endingGame) return; /* [HGM] crash: forbid recursion */
8293 if (appData.debugMode) {
8294 fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
8295 result, resultDetails ? resultDetails : "(null)", whosays);
8298 if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
8299 /* If we are playing on ICS, the server decides when the
8300 game is over, but the engine can offer to draw, claim
8304 if (appData.zippyPlay && first.initDone) {
8305 if (result == GameIsDrawn) {
8306 /* In case draw still needs to be claimed */
8307 SendToICS(ics_prefix);
8308 SendToICS("draw\n");
8309 } else if (StrCaseStr(resultDetails, "resign")) {
8310 SendToICS(ics_prefix);
8311 SendToICS("resign\n");
8315 endingGame = 0; /* [HGM] crash */
8319 /* If we're loading the game from a file, stop */
8320 if (whosays == GE_FILE) {
8321 (void) StopLoadGameTimer();
8325 /* Cancel draw offers */
8326 first.offeredDraw = second.offeredDraw = 0;
8328 /* If this is an ICS game, only ICS can really say it's done;
8329 if not, anyone can. */
8330 isIcsGame = (gameMode == IcsPlayingWhite ||
8331 gameMode == IcsPlayingBlack ||
8332 gameMode == IcsObserving ||
8333 gameMode == IcsExamining);
8335 if (!isIcsGame || whosays == GE_ICS) {
8336 /* OK -- not an ICS game, or ICS said it was done */
8338 if (!isIcsGame && !appData.noChessProgram)
8339 SetUserThinkingEnables();
8341 /* [HGM] if a machine claims the game end we verify this claim */
8342 if(gameMode == TwoMachinesPlay && appData.testClaims) {
8343 if(appData.testLegality && whosays >= GE_ENGINE1 ) {
8345 ChessMove trueResult = (ChessMove) -1;
8347 claimer = whosays == GE_ENGINE1 ? /* color of claimer */
8348 first.twoMachinesColor[0] :
8349 second.twoMachinesColor[0] ;
8351 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
8352 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
8353 /* [HGM] verify: engine mate claims accepted if they were flagged */
8354 trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
8356 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
8357 /* [HGM] verify: engine mate claims accepted if they were flagged */
8358 trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8360 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
8361 trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
8364 // now verify win claims, but not in drop games, as we don't understand those yet
8365 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
8366 || gameInfo.variant == VariantGreat) &&
8367 (result == WhiteWins && claimer == 'w' ||
8368 result == BlackWins && claimer == 'b' ) ) { // case to verify: engine claims own win
8369 if (appData.debugMode) {
8370 fprintf(debugFP, "result=%d sp=%d move=%d\n",
8371 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
8373 if(result != trueResult) {
8374 sprintf(buf, "False win claim: '%s'", resultDetails);
8375 result = claimer == 'w' ? BlackWins : WhiteWins;
8376 resultDetails = buf;
8379 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
8380 && (forwardMostMove <= backwardMostMove ||
8381 (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
8382 (claimer=='b')==(forwardMostMove&1))
8384 /* [HGM] verify: draws that were not flagged are false claims */
8385 sprintf(buf, "False draw claim: '%s'", resultDetails);
8386 result = claimer == 'w' ? BlackWins : WhiteWins;
8387 resultDetails = buf;
8389 /* (Claiming a loss is accepted no questions asked!) */
8391 /* [HGM] bare: don't allow bare King to win */
8392 if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
8393 && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
8394 && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
8395 && result != GameIsDrawn)
8396 { int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
8397 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
8398 int p = (signed char)boards[forwardMostMove][i][j] - color;
8399 if(p >= 0 && p <= (int)WhiteKing) k++;
8401 if (appData.debugMode) {
8402 fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
8403 result, resultDetails ? resultDetails : "(null)", whosays, k, color);
8406 result = GameIsDrawn;
8407 sprintf(buf, "%s but bare king", resultDetails);
8408 resultDetails = buf;
8414 if(serverMoves != NULL && !loadFlag) { char c = '=';
8415 if(result==WhiteWins) c = '+';
8416 if(result==BlackWins) c = '-';
8417 if(resultDetails != NULL)
8418 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
8420 if (resultDetails != NULL) {
8421 gameInfo.result = result;
8422 gameInfo.resultDetails = StrSave(resultDetails);
8424 /* display last move only if game was not loaded from file */
8425 if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
8426 DisplayMove(currentMove - 1);
8428 if (forwardMostMove != 0) {
8429 if (gameMode != PlayFromGameFile && gameMode != EditGame
8430 && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
8432 if (*appData.saveGameFile != NULLCHAR) {
8433 SaveGameToFile(appData.saveGameFile, TRUE);
8434 } else if (appData.autoSaveGames) {
8437 if (*appData.savePositionFile != NULLCHAR) {
8438 SavePositionToFile(appData.savePositionFile);
8443 /* Tell program how game ended in case it is learning */
8444 /* [HGM] Moved this to after saving the PGN, just in case */
8445 /* engine died and we got here through time loss. In that */
8446 /* case we will get a fatal error writing the pipe, which */
8447 /* would otherwise lose us the PGN. */
8448 /* [HGM] crash: not needed anymore, but doesn't hurt; */
8449 /* output during GameEnds should never be fatal anymore */
8450 if (gameMode == MachinePlaysWhite ||
8451 gameMode == MachinePlaysBlack ||
8452 gameMode == TwoMachinesPlay ||
8453 gameMode == IcsPlayingWhite ||
8454 gameMode == IcsPlayingBlack ||
8455 gameMode == BeginningOfGame) {
8457 sprintf(buf, "result %s {%s}\n", PGNResult(result),
8459 if (first.pr != NoProc) {
8460 SendToProgram(buf, &first);
8462 if (second.pr != NoProc &&
8463 gameMode == TwoMachinesPlay) {
8464 SendToProgram(buf, &second);
8469 if (appData.icsActive) {
8470 if (appData.quietPlay &&
8471 (gameMode == IcsPlayingWhite ||
8472 gameMode == IcsPlayingBlack)) {
8473 SendToICS(ics_prefix);
8474 SendToICS("set shout 1\n");
8476 nextGameMode = IcsIdle;
8477 ics_user_moved = FALSE;
8478 /* clean up premove. It's ugly when the game has ended and the
8479 * premove highlights are still on the board.
8483 ClearPremoveHighlights();
8484 DrawPosition(FALSE, boards[currentMove]);
8486 if (whosays == GE_ICS) {
8489 if (gameMode == IcsPlayingWhite)
8491 else if(gameMode == IcsPlayingBlack)
8495 if (gameMode == IcsPlayingBlack)
8497 else if(gameMode == IcsPlayingWhite)
8504 PlayIcsUnfinishedSound();
8507 } else if (gameMode == EditGame ||
8508 gameMode == PlayFromGameFile ||
8509 gameMode == AnalyzeMode ||
8510 gameMode == AnalyzeFile) {
8511 nextGameMode = gameMode;
8513 nextGameMode = EndOfGame;
8518 nextGameMode = gameMode;
8521 if (appData.noChessProgram) {
8522 gameMode = nextGameMode;
8524 endingGame = 0; /* [HGM] crash */
8529 /* Put first chess program into idle state */
8530 if (first.pr != NoProc &&
8531 (gameMode == MachinePlaysWhite ||
8532 gameMode == MachinePlaysBlack ||
8533 gameMode == TwoMachinesPlay ||
8534 gameMode == IcsPlayingWhite ||
8535 gameMode == IcsPlayingBlack ||
8536 gameMode == BeginningOfGame)) {
8537 SendToProgram("force\n", &first);
8538 if (first.usePing) {
8540 sprintf(buf, "ping %d\n", ++first.lastPing);
8541 SendToProgram(buf, &first);
8544 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
8545 /* Kill off first chess program */
8546 if (first.isr != NULL)
8547 RemoveInputSource(first.isr);
8550 if (first.pr != NoProc) {
8552 DoSleep( appData.delayBeforeQuit );
8553 SendToProgram("quit\n", &first);
8554 DoSleep( appData.delayAfterQuit );
8555 DestroyChildProcess(first.pr, first.useSigterm);
8560 /* Put second chess program into idle state */
8561 if (second.pr != NoProc &&
8562 gameMode == TwoMachinesPlay) {
8563 SendToProgram("force\n", &second);
8564 if (second.usePing) {
8566 sprintf(buf, "ping %d\n", ++second.lastPing);
8567 SendToProgram(buf, &second);
8570 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
8571 /* Kill off second chess program */
8572 if (second.isr != NULL)
8573 RemoveInputSource(second.isr);
8576 if (second.pr != NoProc) {
8577 DoSleep( appData.delayBeforeQuit );
8578 SendToProgram("quit\n", &second);
8579 DoSleep( appData.delayAfterQuit );
8580 DestroyChildProcess(second.pr, second.useSigterm);
8585 if (matchMode && gameMode == TwoMachinesPlay) {
8588 if (first.twoMachinesColor[0] == 'w') {
8595 if (first.twoMachinesColor[0] == 'b') {
8604 if (matchGame < appData.matchGames) {
8606 if(appData.sameColorGames <= 1) { /* [HGM] alternate: suppress color swap */
8607 tmp = first.twoMachinesColor;
8608 first.twoMachinesColor = second.twoMachinesColor;
8609 second.twoMachinesColor = tmp;
8611 gameMode = nextGameMode;
8613 if(appData.matchPause>10000 || appData.matchPause<10)
8614 appData.matchPause = 10000; /* [HGM] make pause adjustable */
8615 ScheduleDelayedEvent(NextMatchGame, appData.matchPause);
8616 endingGame = 0; /* [HGM] crash */
8620 gameMode = nextGameMode;
8621 sprintf(buf, _("Match %s vs. %s: final score %d-%d-%d"),
8622 first.tidy, second.tidy,
8623 first.matchWins, second.matchWins,
8624 appData.matchGames - (first.matchWins + second.matchWins));
8625 DisplayFatalError(buf, 0, 0);
8628 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
8629 !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
8631 gameMode = nextGameMode;
8633 endingGame = 0; /* [HGM] crash */
8636 /* Assumes program was just initialized (initString sent).
8637 Leaves program in force mode. */
8639 FeedMovesToProgram(cps, upto)
8640 ChessProgramState *cps;
8645 if (appData.debugMode)
8646 fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
8647 startedFromSetupPosition ? "position and " : "",
8648 backwardMostMove, upto, cps->which);
8649 if(currentlyInitializedVariant != gameInfo.variant) { char buf[MSG_SIZ];
8650 // [HGM] variantswitch: make engine aware of new variant
8651 if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
8652 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
8653 sprintf(buf, "variant %s\n", VariantName(gameInfo.variant));
8654 SendToProgram(buf, cps);
8655 currentlyInitializedVariant = gameInfo.variant;
8657 SendToProgram("force\n", cps);
8658 if (startedFromSetupPosition) {
8659 SendBoard(cps, backwardMostMove);
8660 if (appData.debugMode) {
8661 fprintf(debugFP, "feedMoves\n");
8664 for (i = backwardMostMove; i < upto; i++) {
8665 SendMoveToProgram(i, cps);
8671 ResurrectChessProgram()
8673 /* The chess program may have exited.
8674 If so, restart it and feed it all the moves made so far. */
8676 if (appData.noChessProgram || first.pr != NoProc) return;
8678 StartChessProgram(&first);
8679 InitChessProgram(&first, FALSE);
8680 FeedMovesToProgram(&first, currentMove);
8682 if (!first.sendTime) {
8683 /* can't tell gnuchess what its clock should read,
8684 so we bow to its notion. */
8686 timeRemaining[0][currentMove] = whiteTimeRemaining;
8687 timeRemaining[1][currentMove] = blackTimeRemaining;
8690 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
8691 appData.icsEngineAnalyze) && first.analysisSupport) {
8692 SendToProgram("analyze\n", &first);
8693 first.analyzing = TRUE;
8706 if (appData.debugMode) {
8707 fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
8708 redraw, init, gameMode);
8710 CleanupTail(); // [HGM] vari: delete any stored variations
8711 pausing = pauseExamInvalid = FALSE;
8712 startedFromSetupPosition = blackPlaysFirst = FALSE;
8714 whiteFlag = blackFlag = FALSE;
8715 userOfferedDraw = FALSE;
8716 hintRequested = bookRequested = FALSE;
8717 first.maybeThinking = FALSE;
8718 second.maybeThinking = FALSE;
8719 first.bookSuspend = FALSE; // [HGM] book
8720 second.bookSuspend = FALSE;
8721 thinkOutput[0] = NULLCHAR;
8722 lastHint[0] = NULLCHAR;
8723 ClearGameInfo(&gameInfo);
8724 gameInfo.variant = StringToVariant(appData.variant);
8725 ics_user_moved = ics_clock_paused = FALSE;
8726 ics_getting_history = H_FALSE;
8728 white_holding[0] = black_holding[0] = NULLCHAR;
8729 ClearProgramStats();
8730 opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
8734 flipView = appData.flipView;
8735 ClearPremoveHighlights();
8737 alarmSounded = FALSE;
8739 GameEnds((ChessMove) 0, NULL, GE_PLAYER);
8740 if(appData.serverMovesName != NULL) {
8741 /* [HGM] prepare to make moves file for broadcasting */
8742 clock_t t = clock();
8743 if(serverMoves != NULL) fclose(serverMoves);
8744 serverMoves = fopen(appData.serverMovesName, "r");
8745 if(serverMoves != NULL) {
8746 fclose(serverMoves);
8747 /* delay 15 sec before overwriting, so all clients can see end */
8748 while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
8750 serverMoves = fopen(appData.serverMovesName, "w");
8754 gameMode = BeginningOfGame;
8756 if(appData.icsActive) gameInfo.variant = VariantNormal;
8757 currentMove = forwardMostMove = backwardMostMove = 0;
8758 InitPosition(redraw);
8759 for (i = 0; i < MAX_MOVES; i++) {
8760 if (commentList[i] != NULL) {
8761 free(commentList[i]);
8762 commentList[i] = NULL;
8766 timeRemaining[0][0] = whiteTimeRemaining;
8767 timeRemaining[1][0] = blackTimeRemaining;
8768 if (first.pr == NULL) {
8769 StartChessProgram(&first);
8772 InitChessProgram(&first, startedFromSetupPosition);
8775 DisplayMessage("", "");
8776 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
8777 lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
8784 if (!AutoPlayOneMove())
8786 if (matchMode || appData.timeDelay == 0)
8788 if (appData.timeDelay < 0 || gameMode == AnalyzeFile)
8790 StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
8799 int fromX, fromY, toX, toY;
8801 if (appData.debugMode) {
8802 fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
8805 if (gameMode != PlayFromGameFile)
8808 if (currentMove >= forwardMostMove) {
8809 gameMode = EditGame;
8812 /* [AS] Clear current move marker at the end of a game */
8813 /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
8818 toX = moveList[currentMove][2] - AAA;
8819 toY = moveList[currentMove][3] - ONE;
8821 if (moveList[currentMove][1] == '@') {
8822 if (appData.highlightLastMove) {
8823 SetHighlights(-1, -1, toX, toY);
8826 fromX = moveList[currentMove][0] - AAA;
8827 fromY = moveList[currentMove][1] - ONE;
8829 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
8831 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
8833 if (appData.highlightLastMove) {
8834 SetHighlights(fromX, fromY, toX, toY);
8837 DisplayMove(currentMove);
8838 SendMoveToProgram(currentMove++, &first);
8839 DisplayBothClocks();
8840 DrawPosition(FALSE, boards[currentMove]);
8841 // [HGM] PV info: always display, routine tests if empty
8842 DisplayComment(currentMove - 1, commentList[currentMove]);
8848 LoadGameOneMove(readAhead)
8849 ChessMove readAhead;
8851 int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
8852 char promoChar = NULLCHAR;
8857 if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
8858 gameMode != AnalyzeMode && gameMode != Training) {
8863 yyboardindex = forwardMostMove;
8864 if (readAhead != (ChessMove)0) {
8865 moveType = readAhead;
8867 if (gameFileFP == NULL)
8869 moveType = (ChessMove) yylex();
8875 if (appData.debugMode)
8876 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
8879 /* append the comment but don't display it */
8880 AppendComment(currentMove, p, FALSE);
8883 case WhiteCapturesEnPassant:
8884 case BlackCapturesEnPassant:
8885 case WhitePromotionChancellor:
8886 case BlackPromotionChancellor:
8887 case WhitePromotionArchbishop:
8888 case BlackPromotionArchbishop:
8889 case WhitePromotionCentaur:
8890 case BlackPromotionCentaur:
8891 case WhitePromotionQueen:
8892 case BlackPromotionQueen:
8893 case WhitePromotionRook:
8894 case BlackPromotionRook:
8895 case WhitePromotionBishop:
8896 case BlackPromotionBishop:
8897 case WhitePromotionKnight:
8898 case BlackPromotionKnight:
8899 case WhitePromotionKing:
8900 case BlackPromotionKing:
8902 case WhiteKingSideCastle:
8903 case WhiteQueenSideCastle:
8904 case BlackKingSideCastle:
8905 case BlackQueenSideCastle:
8906 case WhiteKingSideCastleWild:
8907 case WhiteQueenSideCastleWild:
8908 case BlackKingSideCastleWild:
8909 case BlackQueenSideCastleWild:
8911 case WhiteHSideCastleFR:
8912 case WhiteASideCastleFR:
8913 case BlackHSideCastleFR:
8914 case BlackASideCastleFR:
8916 if (appData.debugMode)
8917 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
8918 fromX = currentMoveString[0] - AAA;
8919 fromY = currentMoveString[1] - ONE;
8920 toX = currentMoveString[2] - AAA;
8921 toY = currentMoveString[3] - ONE;
8922 promoChar = currentMoveString[4];
8927 if (appData.debugMode)
8928 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
8929 fromX = moveType == WhiteDrop ?
8930 (int) CharToPiece(ToUpper(currentMoveString[0])) :
8931 (int) CharToPiece(ToLower(currentMoveString[0]));
8933 toX = currentMoveString[2] - AAA;
8934 toY = currentMoveString[3] - ONE;
8940 case GameUnfinished:
8941 if (appData.debugMode)
8942 fprintf(debugFP, "Parsed game end: %s\n", yy_text);
8943 p = strchr(yy_text, '{');
8944 if (p == NULL) p = strchr(yy_text, '(');
8947 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
8949 q = strchr(p, *p == '{' ? '}' : ')');
8950 if (q != NULL) *q = NULLCHAR;
8953 GameEnds(moveType, p, GE_FILE);
8955 if (cmailMsgLoaded) {
8957 flipView = WhiteOnMove(currentMove);
8958 if (moveType == GameUnfinished) flipView = !flipView;
8959 if (appData.debugMode)
8960 fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
8964 case (ChessMove) 0: /* end of file */
8965 if (appData.debugMode)
8966 fprintf(debugFP, "Parser hit end of file\n");
8967 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
8973 if (WhiteOnMove(currentMove)) {
8974 GameEnds(BlackWins, "Black mates", GE_FILE);
8976 GameEnds(WhiteWins, "White mates", GE_FILE);
8980 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
8987 if (lastLoadGameStart == GNUChessGame) {
8988 /* GNUChessGames have numbers, but they aren't move numbers */
8989 if (appData.debugMode)
8990 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
8991 yy_text, (int) moveType);
8992 return LoadGameOneMove((ChessMove)0); /* tail recursion */
8994 /* else fall thru */
8999 /* Reached start of next game in file */
9000 if (appData.debugMode)
9001 fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
9002 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9008 if (WhiteOnMove(currentMove)) {
9009 GameEnds(BlackWins, "Black mates", GE_FILE);
9011 GameEnds(WhiteWins, "White mates", GE_FILE);
9015 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
9021 case PositionDiagram: /* should not happen; ignore */
9022 case ElapsedTime: /* ignore */
9023 case NAG: /* ignore */
9024 if (appData.debugMode)
9025 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
9026 yy_text, (int) moveType);
9027 return LoadGameOneMove((ChessMove)0); /* tail recursion */
9030 if (appData.testLegality) {
9031 if (appData.debugMode)
9032 fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
9033 sprintf(move, _("Illegal move: %d.%s%s"),
9034 (forwardMostMove / 2) + 1,
9035 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9036 DisplayError(move, 0);
9039 if (appData.debugMode)
9040 fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
9041 yy_text, currentMoveString);
9042 fromX = currentMoveString[0] - AAA;
9043 fromY = currentMoveString[1] - ONE;
9044 toX = currentMoveString[2] - AAA;
9045 toY = currentMoveString[3] - ONE;
9046 promoChar = currentMoveString[4];
9051 if (appData.debugMode)
9052 fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
9053 sprintf(move, _("Ambiguous move: %d.%s%s"),
9054 (forwardMostMove / 2) + 1,
9055 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9056 DisplayError(move, 0);
9061 case ImpossibleMove:
9062 if (appData.debugMode)
9063 fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
9064 sprintf(move, _("Illegal move: %d.%s%s"),
9065 (forwardMostMove / 2) + 1,
9066 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9067 DisplayError(move, 0);
9073 if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
9074 DrawPosition(FALSE, boards[currentMove]);
9075 DisplayBothClocks();
9076 if (!appData.matchMode) // [HGM] PV info: routine tests if empty
9077 DisplayComment(currentMove - 1, commentList[currentMove]);
9079 (void) StopLoadGameTimer();
9081 cmailOldMove = forwardMostMove;
9084 /* currentMoveString is set as a side-effect of yylex */
9085 strcat(currentMoveString, "\n");
9086 strcpy(moveList[forwardMostMove], currentMoveString);
9088 thinkOutput[0] = NULLCHAR;
9089 MakeMove(fromX, fromY, toX, toY, promoChar);
9090 currentMove = forwardMostMove;
9095 /* Load the nth game from the given file */
9097 LoadGameFromFile(filename, n, title, useList)
9101 /*Boolean*/ int useList;
9106 if (strcmp(filename, "-") == 0) {
9110 f = fopen(filename, "rb");
9112 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9113 DisplayError(buf, errno);
9117 if (fseek(f, 0, 0) == -1) {
9118 /* f is not seekable; probably a pipe */
9121 if (useList && n == 0) {
9122 int error = GameListBuild(f);
9124 DisplayError(_("Cannot build game list"), error);
9125 } else if (!ListEmpty(&gameList) &&
9126 ((ListGame *) gameList.tailPred)->number > 1) {
9127 GameListPopUp(f, title);
9134 return LoadGame(f, n, title, FALSE);
9139 MakeRegisteredMove()
9141 int fromX, fromY, toX, toY;
9143 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
9144 switch (cmailMoveType[lastLoadGameNumber - 1]) {
9147 if (appData.debugMode)
9148 fprintf(debugFP, "Restoring %s for game %d\n",
9149 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
9151 thinkOutput[0] = NULLCHAR;
9152 strcpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1]);
9153 fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
9154 fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
9155 toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
9156 toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
9157 promoChar = cmailMove[lastLoadGameNumber - 1][4];
9158 MakeMove(fromX, fromY, toX, toY, promoChar);
9159 ShowMove(fromX, fromY, toX, toY);
9161 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9168 if (WhiteOnMove(currentMove)) {
9169 GameEnds(BlackWins, "Black mates", GE_PLAYER);
9171 GameEnds(WhiteWins, "White mates", GE_PLAYER);
9176 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
9183 if (WhiteOnMove(currentMove)) {
9184 GameEnds(BlackWins, "White resigns", GE_PLAYER);
9186 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
9191 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
9202 /* Wrapper around LoadGame for use when a Cmail message is loaded */
9204 CmailLoadGame(f, gameNumber, title, useList)
9212 if (gameNumber > nCmailGames) {
9213 DisplayError(_("No more games in this message"), 0);
9216 if (f == lastLoadGameFP) {
9217 int offset = gameNumber - lastLoadGameNumber;
9219 cmailMsg[0] = NULLCHAR;
9220 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
9221 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
9222 nCmailMovesRegistered--;
9224 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
9225 if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
9226 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
9229 if (! RegisterMove()) return FALSE;
9233 retVal = LoadGame(f, gameNumber, title, useList);
9235 /* Make move registered during previous look at this game, if any */
9236 MakeRegisteredMove();
9238 if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
9239 commentList[currentMove]
9240 = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
9241 DisplayComment(currentMove - 1, commentList[currentMove]);
9247 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
9252 int gameNumber = lastLoadGameNumber + offset;
9253 if (lastLoadGameFP == NULL) {
9254 DisplayError(_("No game has been loaded yet"), 0);
9257 if (gameNumber <= 0) {
9258 DisplayError(_("Can't back up any further"), 0);
9261 if (cmailMsgLoaded) {
9262 return CmailLoadGame(lastLoadGameFP, gameNumber,
9263 lastLoadGameTitle, lastLoadGameUseList);
9265 return LoadGame(lastLoadGameFP, gameNumber,
9266 lastLoadGameTitle, lastLoadGameUseList);
9272 /* Load the nth game from open file f */
9274 LoadGame(f, gameNumber, title, useList)
9282 int gn = gameNumber;
9283 ListGame *lg = NULL;
9286 GameMode oldGameMode;
9287 VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
9289 if (appData.debugMode)
9290 fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
9292 if (gameMode == Training )
9293 SetTrainingModeOff();
9295 oldGameMode = gameMode;
9296 if (gameMode != BeginningOfGame) {
9301 if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
9302 fclose(lastLoadGameFP);
9306 lg = (ListGame *) ListElem(&gameList, gameNumber-1);
9309 fseek(f, lg->offset, 0);
9310 GameListHighlight(gameNumber);
9314 DisplayError(_("Game number out of range"), 0);
9319 if (fseek(f, 0, 0) == -1) {
9320 if (f == lastLoadGameFP ?
9321 gameNumber == lastLoadGameNumber + 1 :
9325 DisplayError(_("Can't seek on game file"), 0);
9331 lastLoadGameNumber = gameNumber;
9332 strcpy(lastLoadGameTitle, title);
9333 lastLoadGameUseList = useList;
9337 if (lg && lg->gameInfo.white && lg->gameInfo.black) {
9338 snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
9339 lg->gameInfo.black);
9341 } else if (*title != NULLCHAR) {
9342 if (gameNumber > 1) {
9343 sprintf(buf, "%s %d", title, gameNumber);
9346 DisplayTitle(title);
9350 if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
9351 gameMode = PlayFromGameFile;
9355 currentMove = forwardMostMove = backwardMostMove = 0;
9356 CopyBoard(boards[0], initialPosition);
9360 * Skip the first gn-1 games in the file.
9361 * Also skip over anything that precedes an identifiable
9362 * start of game marker, to avoid being confused by
9363 * garbage at the start of the file. Currently
9364 * recognized start of game markers are the move number "1",
9365 * the pattern "gnuchess .* game", the pattern
9366 * "^[#;%] [^ ]* game file", and a PGN tag block.
9367 * A game that starts with one of the latter two patterns
9368 * will also have a move number 1, possibly
9369 * following a position diagram.
9370 * 5-4-02: Let's try being more lenient and allowing a game to
9371 * start with an unnumbered move. Does that break anything?
9373 cm = lastLoadGameStart = (ChessMove) 0;
9375 yyboardindex = forwardMostMove;
9376 cm = (ChessMove) yylex();
9379 if (cmailMsgLoaded) {
9380 nCmailGames = CMAIL_MAX_GAMES - gn;
9383 DisplayError(_("Game not found in file"), 0);
9390 lastLoadGameStart = cm;
9394 switch (lastLoadGameStart) {
9401 gn--; /* count this game */
9402 lastLoadGameStart = cm;
9411 switch (lastLoadGameStart) {
9416 gn--; /* count this game */
9417 lastLoadGameStart = cm;
9420 lastLoadGameStart = cm; /* game counted already */
9428 yyboardindex = forwardMostMove;
9429 cm = (ChessMove) yylex();
9430 } while (cm == PGNTag || cm == Comment);
9437 if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
9438 if ( cmailResult[CMAIL_MAX_GAMES - gn - 1]
9439 != CMAIL_OLD_RESULT) {
9441 cmailResult[ CMAIL_MAX_GAMES
9442 - gn - 1] = CMAIL_OLD_RESULT;
9448 /* Only a NormalMove can be at the start of a game
9449 * without a position diagram. */
9450 if (lastLoadGameStart == (ChessMove) 0) {
9452 lastLoadGameStart = MoveNumberOne;
9461 if (appData.debugMode)
9462 fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
9464 if (cm == XBoardGame) {
9465 /* Skip any header junk before position diagram and/or move 1 */
9467 yyboardindex = forwardMostMove;
9468 cm = (ChessMove) yylex();
9470 if (cm == (ChessMove) 0 ||
9471 cm == GNUChessGame || cm == XBoardGame) {
9472 /* Empty game; pretend end-of-file and handle later */
9477 if (cm == MoveNumberOne || cm == PositionDiagram ||
9478 cm == PGNTag || cm == Comment)
9481 } else if (cm == GNUChessGame) {
9482 if (gameInfo.event != NULL) {
9483 free(gameInfo.event);
9485 gameInfo.event = StrSave(yy_text);
9488 startedFromSetupPosition = FALSE;
9489 while (cm == PGNTag) {
9490 if (appData.debugMode)
9491 fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
9492 err = ParsePGNTag(yy_text, &gameInfo);
9493 if (!err) numPGNTags++;
9495 /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
9496 if(gameInfo.variant != oldVariant) {
9497 startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
9499 oldVariant = gameInfo.variant;
9500 if (appData.debugMode)
9501 fprintf(debugFP, "New variant %d\n", (int) oldVariant);
9505 if (gameInfo.fen != NULL) {
9506 Board initial_position;
9507 startedFromSetupPosition = TRUE;
9508 if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
9510 DisplayError(_("Bad FEN position in file"), 0);
9513 CopyBoard(boards[0], initial_position);
9514 if (blackPlaysFirst) {
9515 currentMove = forwardMostMove = backwardMostMove = 1;
9516 CopyBoard(boards[1], initial_position);
9517 strcpy(moveList[0], "");
9518 strcpy(parseList[0], "");
9519 timeRemaining[0][1] = whiteTimeRemaining;
9520 timeRemaining[1][1] = blackTimeRemaining;
9521 if (commentList[0] != NULL) {
9522 commentList[1] = commentList[0];
9523 commentList[0] = NULL;
9526 currentMove = forwardMostMove = backwardMostMove = 0;
9528 /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
9530 initialRulePlies = FENrulePlies;
9531 for( i=0; i< nrCastlingRights; i++ )
9532 initialRights[i] = initial_position[CASTLING][i];
9534 yyboardindex = forwardMostMove;
9536 gameInfo.fen = NULL;
9539 yyboardindex = forwardMostMove;
9540 cm = (ChessMove) yylex();
9542 /* Handle comments interspersed among the tags */
9543 while (cm == Comment) {
9545 if (appData.debugMode)
9546 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9548 AppendComment(currentMove, p, FALSE);
9549 yyboardindex = forwardMostMove;
9550 cm = (ChessMove) yylex();
9554 /* don't rely on existence of Event tag since if game was
9555 * pasted from clipboard the Event tag may not exist
9557 if (numPGNTags > 0){
9559 if (gameInfo.variant == VariantNormal) {
9560 gameInfo.variant = StringToVariant(gameInfo.event);
9563 if( appData.autoDisplayTags ) {
9564 tags = PGNTags(&gameInfo);
9565 TagsPopUp(tags, CmailMsg());
9570 /* Make something up, but don't display it now */
9575 if (cm == PositionDiagram) {
9578 Board initial_position;
9580 if (appData.debugMode)
9581 fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
9583 if (!startedFromSetupPosition) {
9585 for (i = BOARD_HEIGHT - 1; i >= 0; i--)
9586 for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
9596 initial_position[i][j++] = CharToPiece(*p);
9599 while (*p == ' ' || *p == '\t' ||
9600 *p == '\n' || *p == '\r') p++;
9602 if (strncmp(p, "black", strlen("black"))==0)
9603 blackPlaysFirst = TRUE;
9605 blackPlaysFirst = FALSE;
9606 startedFromSetupPosition = TRUE;
9608 CopyBoard(boards[0], initial_position);
9609 if (blackPlaysFirst) {
9610 currentMove = forwardMostMove = backwardMostMove = 1;
9611 CopyBoard(boards[1], initial_position);
9612 strcpy(moveList[0], "");
9613 strcpy(parseList[0], "");
9614 timeRemaining[0][1] = whiteTimeRemaining;
9615 timeRemaining[1][1] = blackTimeRemaining;
9616 if (commentList[0] != NULL) {
9617 commentList[1] = commentList[0];
9618 commentList[0] = NULL;
9621 currentMove = forwardMostMove = backwardMostMove = 0;
9624 yyboardindex = forwardMostMove;
9625 cm = (ChessMove) yylex();
9628 if (first.pr == NoProc) {
9629 StartChessProgram(&first);
9631 InitChessProgram(&first, FALSE);
9632 SendToProgram("force\n", &first);
9633 if (startedFromSetupPosition) {
9634 SendBoard(&first, forwardMostMove);
9635 if (appData.debugMode) {
9636 fprintf(debugFP, "Load Game\n");
9638 DisplayBothClocks();
9641 /* [HGM] server: flag to write setup moves in broadcast file as one */
9642 loadFlag = appData.suppressLoadMoves;
9644 while (cm == Comment) {
9646 if (appData.debugMode)
9647 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9649 AppendComment(currentMove, p, FALSE);
9650 yyboardindex = forwardMostMove;
9651 cm = (ChessMove) yylex();
9654 if ((cm == (ChessMove) 0 && lastLoadGameStart != (ChessMove) 0) ||
9655 cm == WhiteWins || cm == BlackWins ||
9656 cm == GameIsDrawn || cm == GameUnfinished) {
9657 DisplayMessage("", _("No moves in game"));
9658 if (cmailMsgLoaded) {
9659 if (appData.debugMode)
9660 fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
9664 DrawPosition(FALSE, boards[currentMove]);
9665 DisplayBothClocks();
9666 gameMode = EditGame;
9673 // [HGM] PV info: routine tests if comment empty
9674 if (!matchMode && (pausing || appData.timeDelay != 0)) {
9675 DisplayComment(currentMove - 1, commentList[currentMove]);
9677 if (!matchMode && appData.timeDelay != 0)
9678 DrawPosition(FALSE, boards[currentMove]);
9680 if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
9681 programStats.ok_to_send = 1;
9684 /* if the first token after the PGN tags is a move
9685 * and not move number 1, retrieve it from the parser
9687 if (cm != MoveNumberOne)
9688 LoadGameOneMove(cm);
9690 /* load the remaining moves from the file */
9691 while (LoadGameOneMove((ChessMove)0)) {
9692 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
9693 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
9696 /* rewind to the start of the game */
9697 currentMove = backwardMostMove;
9699 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
9701 if (oldGameMode == AnalyzeFile ||
9702 oldGameMode == AnalyzeMode) {
9706 if (matchMode || appData.timeDelay == 0) {
9708 gameMode = EditGame;
9710 } else if (appData.timeDelay > 0) {
9714 if (appData.debugMode)
9715 fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
9717 loadFlag = 0; /* [HGM] true game starts */
9721 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
9723 ReloadPosition(offset)
9726 int positionNumber = lastLoadPositionNumber + offset;
9727 if (lastLoadPositionFP == NULL) {
9728 DisplayError(_("No position has been loaded yet"), 0);
9731 if (positionNumber <= 0) {
9732 DisplayError(_("Can't back up any further"), 0);
9735 return LoadPosition(lastLoadPositionFP, positionNumber,
9736 lastLoadPositionTitle);
9739 /* Load the nth position from the given file */
9741 LoadPositionFromFile(filename, n, title)
9749 if (strcmp(filename, "-") == 0) {
9750 return LoadPosition(stdin, n, "stdin");
9752 f = fopen(filename, "rb");
9754 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9755 DisplayError(buf, errno);
9758 return LoadPosition(f, n, title);
9763 /* Load the nth position from the given open file, and close it */
9765 LoadPosition(f, positionNumber, title)
9770 char *p, line[MSG_SIZ];
9771 Board initial_position;
9772 int i, j, fenMode, pn;
9774 if (gameMode == Training )
9775 SetTrainingModeOff();
9777 if (gameMode != BeginningOfGame) {
9780 if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
9781 fclose(lastLoadPositionFP);
9783 if (positionNumber == 0) positionNumber = 1;
9784 lastLoadPositionFP = f;
9785 lastLoadPositionNumber = positionNumber;
9786 strcpy(lastLoadPositionTitle, title);
9787 if (first.pr == NoProc) {
9788 StartChessProgram(&first);
9789 InitChessProgram(&first, FALSE);
9791 pn = positionNumber;
9792 if (positionNumber < 0) {
9793 /* Negative position number means to seek to that byte offset */
9794 if (fseek(f, -positionNumber, 0) == -1) {
9795 DisplayError(_("Can't seek on position file"), 0);
9800 if (fseek(f, 0, 0) == -1) {
9801 if (f == lastLoadPositionFP ?
9802 positionNumber == lastLoadPositionNumber + 1 :
9803 positionNumber == 1) {
9806 DisplayError(_("Can't seek on position file"), 0);
9811 /* See if this file is FEN or old-style xboard */
9812 if (fgets(line, MSG_SIZ, f) == NULL) {
9813 DisplayError(_("Position not found in file"), 0);
9816 // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
9817 fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
9820 if (fenMode || line[0] == '#') pn--;
9822 /* skip positions before number pn */
9823 if (fgets(line, MSG_SIZ, f) == NULL) {
9825 DisplayError(_("Position not found in file"), 0);
9828 if (fenMode || line[0] == '#') pn--;
9833 if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
9834 DisplayError(_("Bad FEN position in file"), 0);
9838 (void) fgets(line, MSG_SIZ, f);
9839 (void) fgets(line, MSG_SIZ, f);
9841 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
9842 (void) fgets(line, MSG_SIZ, f);
9843 for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
9846 initial_position[i][j++] = CharToPiece(*p);
9850 blackPlaysFirst = FALSE;
9852 (void) fgets(line, MSG_SIZ, f);
9853 if (strncmp(line, "black", strlen("black"))==0)
9854 blackPlaysFirst = TRUE;
9857 startedFromSetupPosition = TRUE;
9859 SendToProgram("force\n", &first);
9860 CopyBoard(boards[0], initial_position);
9861 if (blackPlaysFirst) {
9862 currentMove = forwardMostMove = backwardMostMove = 1;
9863 strcpy(moveList[0], "");
9864 strcpy(parseList[0], "");
9865 CopyBoard(boards[1], initial_position);
9866 DisplayMessage("", _("Black to play"));
9868 currentMove = forwardMostMove = backwardMostMove = 0;
9869 DisplayMessage("", _("White to play"));
9871 initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
9872 SendBoard(&first, forwardMostMove);
9873 if (appData.debugMode) {
9875 for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
9876 for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
9877 fprintf(debugFP, "Load Position\n");
9880 if (positionNumber > 1) {
9881 sprintf(line, "%s %d", title, positionNumber);
9884 DisplayTitle(title);
9886 gameMode = EditGame;
9889 timeRemaining[0][1] = whiteTimeRemaining;
9890 timeRemaining[1][1] = blackTimeRemaining;
9891 DrawPosition(FALSE, boards[currentMove]);
9898 CopyPlayerNameIntoFileName(dest, src)
9901 while (*src != NULLCHAR && *src != ',') {
9906 *(*dest)++ = *src++;
9911 char *DefaultFileName(ext)
9914 static char def[MSG_SIZ];
9917 if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
9919 CopyPlayerNameIntoFileName(&p, gameInfo.white);
9921 CopyPlayerNameIntoFileName(&p, gameInfo.black);
9930 /* Save the current game to the given file */
9932 SaveGameToFile(filename, append)
9939 if (strcmp(filename, "-") == 0) {
9940 return SaveGame(stdout, 0, NULL);
9942 f = fopen(filename, append ? "a" : "w");
9944 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9945 DisplayError(buf, errno);
9948 return SaveGame(f, 0, NULL);
9957 static char buf[MSG_SIZ];
9960 p = strchr(str, ' ');
9961 if (p == NULL) return str;
9962 strncpy(buf, str, p - str);
9963 buf[p - str] = NULLCHAR;
9967 #define PGN_MAX_LINE 75
9969 #define PGN_SIDE_WHITE 0
9970 #define PGN_SIDE_BLACK 1
9973 static int FindFirstMoveOutOfBook( int side )
9977 if( backwardMostMove == 0 && ! startedFromSetupPosition) {
9978 int index = backwardMostMove;
9979 int has_book_hit = 0;
9981 if( (index % 2) != side ) {
9985 while( index < forwardMostMove ) {
9986 /* Check to see if engine is in book */
9987 int depth = pvInfoList[index].depth;
9988 int score = pvInfoList[index].score;
9994 else if( score == 0 && depth == 63 ) {
9995 in_book = 1; /* Zappa */
9997 else if( score == 2 && depth == 99 ) {
9998 in_book = 1; /* Abrok */
10001 has_book_hit += in_book;
10017 void GetOutOfBookInfo( char * buf )
10021 int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10023 oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
10024 oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
10028 if( oob[0] >= 0 || oob[1] >= 0 ) {
10029 for( i=0; i<2; i++ ) {
10033 if( i > 0 && oob[0] >= 0 ) {
10034 strcat( buf, " " );
10037 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
10038 sprintf( buf+strlen(buf), "%s%.2f",
10039 pvInfoList[idx].score >= 0 ? "+" : "",
10040 pvInfoList[idx].score / 100.0 );
10046 /* Save game in PGN style and close the file */
10051 int i, offset, linelen, newblock;
10055 int movelen, numlen, blank;
10056 char move_buffer[100]; /* [AS] Buffer for move+PV info */
10058 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10060 tm = time((time_t *) NULL);
10062 PrintPGNTags(f, &gameInfo);
10064 if (backwardMostMove > 0 || startedFromSetupPosition) {
10065 char *fen = PositionToFEN(backwardMostMove, NULL);
10066 fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
10067 fprintf(f, "\n{--------------\n");
10068 PrintPosition(f, backwardMostMove);
10069 fprintf(f, "--------------}\n");
10073 /* [AS] Out of book annotation */
10074 if( appData.saveOutOfBookInfo ) {
10077 GetOutOfBookInfo( buf );
10079 if( buf[0] != '\0' ) {
10080 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
10087 i = backwardMostMove;
10091 while (i < forwardMostMove) {
10092 /* Print comments preceding this move */
10093 if (commentList[i] != NULL) {
10094 if (linelen > 0) fprintf(f, "\n");
10095 fprintf(f, "%s", commentList[i]);
10100 /* Format move number */
10101 if ((i % 2) == 0) {
10102 sprintf(numtext, "%d.", (i - offset)/2 + 1);
10105 sprintf(numtext, "%d...", (i - offset)/2 + 1);
10107 numtext[0] = NULLCHAR;
10110 numlen = strlen(numtext);
10113 /* Print move number */
10114 blank = linelen > 0 && numlen > 0;
10115 if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
10124 fprintf(f, "%s", numtext);
10128 strcpy(move_buffer, SavePart(parseList[i])); // [HGM] pgn: print move via buffer, so it can be edited
10129 movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
10132 blank = linelen > 0 && movelen > 0;
10133 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
10142 fprintf(f, "%s", move_buffer);
10143 linelen += movelen;
10145 /* [AS] Add PV info if present */
10146 if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
10147 /* [HGM] add time */
10148 char buf[MSG_SIZ]; int seconds;
10150 seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
10152 if( seconds <= 0) buf[0] = 0; else
10153 if( seconds < 30 ) sprintf(buf, " %3.1f%c", seconds/10., 0); else {
10154 seconds = (seconds + 4)/10; // round to full seconds
10155 if( seconds < 60 ) sprintf(buf, " %d%c", seconds, 0); else
10156 sprintf(buf, " %d:%02d%c", seconds/60, seconds%60, 0);
10159 sprintf( move_buffer, "{%s%.2f/%d%s}",
10160 pvInfoList[i].score >= 0 ? "+" : "",
10161 pvInfoList[i].score / 100.0,
10162 pvInfoList[i].depth,
10165 movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
10167 /* Print score/depth */
10168 blank = linelen > 0 && movelen > 0;
10169 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
10178 fprintf(f, "%s", move_buffer);
10179 linelen += movelen;
10185 /* Start a new line */
10186 if (linelen > 0) fprintf(f, "\n");
10188 /* Print comments after last move */
10189 if (commentList[i] != NULL) {
10190 fprintf(f, "%s\n", commentList[i]);
10194 if (gameInfo.resultDetails != NULL &&
10195 gameInfo.resultDetails[0] != NULLCHAR) {
10196 fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
10197 PGNResult(gameInfo.result));
10199 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
10203 lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
10207 /* Save game in old style and close the file */
10209 SaveGameOldStyle(f)
10215 tm = time((time_t *) NULL);
10217 fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
10220 if (backwardMostMove > 0 || startedFromSetupPosition) {
10221 fprintf(f, "\n[--------------\n");
10222 PrintPosition(f, backwardMostMove);
10223 fprintf(f, "--------------]\n");
10228 i = backwardMostMove;
10229 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10231 while (i < forwardMostMove) {
10232 if (commentList[i] != NULL) {
10233 fprintf(f, "[%s]\n", commentList[i]);
10236 if ((i % 2) == 1) {
10237 fprintf(f, "%d. ... %s\n", (i - offset)/2 + 1, parseList[i]);
10240 fprintf(f, "%d. %s ", (i - offset)/2 + 1, parseList[i]);
10242 if (commentList[i] != NULL) {
10246 if (i >= forwardMostMove) {
10250 fprintf(f, "%s\n", parseList[i]);
10255 if (commentList[i] != NULL) {
10256 fprintf(f, "[%s]\n", commentList[i]);
10259 /* This isn't really the old style, but it's close enough */
10260 if (gameInfo.resultDetails != NULL &&
10261 gameInfo.resultDetails[0] != NULLCHAR) {
10262 fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
10263 gameInfo.resultDetails);
10265 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
10272 /* Save the current game to open file f and close the file */
10274 SaveGame(f, dummy, dummy2)
10279 if (gameMode == EditPosition) EditPositionDone(TRUE);
10280 lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
10281 if (appData.oldSaveStyle)
10282 return SaveGameOldStyle(f);
10284 return SaveGamePGN(f);
10287 /* Save the current position to the given file */
10289 SavePositionToFile(filename)
10295 if (strcmp(filename, "-") == 0) {
10296 return SavePosition(stdout, 0, NULL);
10298 f = fopen(filename, "a");
10300 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10301 DisplayError(buf, errno);
10304 SavePosition(f, 0, NULL);
10310 /* Save the current position to the given open file and close the file */
10312 SavePosition(f, dummy, dummy2)
10320 if (gameMode == EditPosition) EditPositionDone(TRUE);
10321 if (appData.oldSaveStyle) {
10322 tm = time((time_t *) NULL);
10324 fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
10326 fprintf(f, "[--------------\n");
10327 PrintPosition(f, currentMove);
10328 fprintf(f, "--------------]\n");
10330 fen = PositionToFEN(currentMove, NULL);
10331 fprintf(f, "%s\n", fen);
10339 ReloadCmailMsgEvent(unregister)
10343 static char *inFilename = NULL;
10344 static char *outFilename;
10346 struct stat inbuf, outbuf;
10349 /* Any registered moves are unregistered if unregister is set, */
10350 /* i.e. invoked by the signal handler */
10352 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
10353 cmailMoveRegistered[i] = FALSE;
10354 if (cmailCommentList[i] != NULL) {
10355 free(cmailCommentList[i]);
10356 cmailCommentList[i] = NULL;
10359 nCmailMovesRegistered = 0;
10362 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
10363 cmailResult[i] = CMAIL_NOT_RESULT;
10367 if (inFilename == NULL) {
10368 /* Because the filenames are static they only get malloced once */
10369 /* and they never get freed */
10370 inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
10371 sprintf(inFilename, "%s.game.in", appData.cmailGameName);
10373 outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
10374 sprintf(outFilename, "%s.out", appData.cmailGameName);
10377 status = stat(outFilename, &outbuf);
10379 cmailMailedMove = FALSE;
10381 status = stat(inFilename, &inbuf);
10382 cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
10385 /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
10386 counts the games, notes how each one terminated, etc.
10388 It would be nice to remove this kludge and instead gather all
10389 the information while building the game list. (And to keep it
10390 in the game list nodes instead of having a bunch of fixed-size
10391 parallel arrays.) Note this will require getting each game's
10392 termination from the PGN tags, as the game list builder does
10393 not process the game moves. --mann
10395 cmailMsgLoaded = TRUE;
10396 LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
10398 /* Load first game in the file or popup game menu */
10399 LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
10401 #endif /* !WIN32 */
10409 char string[MSG_SIZ];
10411 if ( cmailMailedMove
10412 || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
10413 return TRUE; /* Allow free viewing */
10416 /* Unregister move to ensure that we don't leave RegisterMove */
10417 /* with the move registered when the conditions for registering no */
10419 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
10420 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
10421 nCmailMovesRegistered --;
10423 if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
10425 free(cmailCommentList[lastLoadGameNumber - 1]);
10426 cmailCommentList[lastLoadGameNumber - 1] = NULL;
10430 if (cmailOldMove == -1) {
10431 DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
10435 if (currentMove > cmailOldMove + 1) {
10436 DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
10440 if (currentMove < cmailOldMove) {
10441 DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
10445 if (forwardMostMove > currentMove) {
10446 /* Silently truncate extra moves */
10450 if ( (currentMove == cmailOldMove + 1)
10451 || ( (currentMove == cmailOldMove)
10452 && ( (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
10453 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
10454 if (gameInfo.result != GameUnfinished) {
10455 cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
10458 if (commentList[currentMove] != NULL) {
10459 cmailCommentList[lastLoadGameNumber - 1]
10460 = StrSave(commentList[currentMove]);
10462 strcpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1]);
10464 if (appData.debugMode)
10465 fprintf(debugFP, "Saving %s for game %d\n",
10466 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
10469 "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
10471 f = fopen(string, "w");
10472 if (appData.oldSaveStyle) {
10473 SaveGameOldStyle(f); /* also closes the file */
10475 sprintf(string, "%s.pos.out", appData.cmailGameName);
10476 f = fopen(string, "w");
10477 SavePosition(f, 0, NULL); /* also closes the file */
10479 fprintf(f, "{--------------\n");
10480 PrintPosition(f, currentMove);
10481 fprintf(f, "--------------}\n\n");
10483 SaveGame(f, 0, NULL); /* also closes the file*/
10486 cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
10487 nCmailMovesRegistered ++;
10488 } else if (nCmailGames == 1) {
10489 DisplayError(_("You have not made a move yet"), 0);
10500 static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
10501 FILE *commandOutput;
10502 char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
10503 int nBytes = 0; /* Suppress warnings on uninitialized variables */
10509 if (! cmailMsgLoaded) {
10510 DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
10514 if (nCmailGames == nCmailResults) {
10515 DisplayError(_("No unfinished games"), 0);
10519 #if CMAIL_PROHIBIT_REMAIL
10520 if (cmailMailedMove) {
10521 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);
10522 DisplayError(msg, 0);
10527 if (! (cmailMailedMove || RegisterMove())) return;
10529 if ( cmailMailedMove
10530 || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
10531 sprintf(string, partCommandString,
10532 appData.debugMode ? " -v" : "", appData.cmailGameName);
10533 commandOutput = popen(string, "r");
10535 if (commandOutput == NULL) {
10536 DisplayError(_("Failed to invoke cmail"), 0);
10538 for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
10539 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
10541 if (nBuffers > 1) {
10542 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
10543 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
10544 nBytes = MSG_SIZ - 1;
10546 (void) memcpy(msg, buffer, nBytes);
10548 *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
10550 if(StrStr(msg, "Mailed cmail message to ") != NULL) {
10551 cmailMailedMove = TRUE; /* Prevent >1 moves */
10554 for (i = 0; i < nCmailGames; i ++) {
10555 if (cmailResult[i] == CMAIL_NOT_RESULT) {
10560 && ( (arcDir = (char *) getenv("CMAIL_ARCDIR"))
10562 sprintf(buffer, "%s/%s.%s.archive",
10564 appData.cmailGameName,
10566 LoadGameFromFile(buffer, 1, buffer, FALSE);
10567 cmailMsgLoaded = FALSE;
10571 DisplayInformation(msg);
10572 pclose(commandOutput);
10575 if ((*cmailMsg) != '\0') {
10576 DisplayInformation(cmailMsg);
10581 #endif /* !WIN32 */
10590 int prependComma = 0;
10592 char string[MSG_SIZ]; /* Space for game-list */
10595 if (!cmailMsgLoaded) return "";
10597 if (cmailMailedMove) {
10598 sprintf(cmailMsg, _("Waiting for reply from opponent\n"));
10600 /* Create a list of games left */
10601 sprintf(string, "[");
10602 for (i = 0; i < nCmailGames; i ++) {
10603 if (! ( cmailMoveRegistered[i]
10604 || (cmailResult[i] == CMAIL_OLD_RESULT))) {
10605 if (prependComma) {
10606 sprintf(number, ",%d", i + 1);
10608 sprintf(number, "%d", i + 1);
10612 strcat(string, number);
10615 strcat(string, "]");
10617 if (nCmailMovesRegistered + nCmailResults == 0) {
10618 switch (nCmailGames) {
10621 _("Still need to make move for game\n"));
10626 _("Still need to make moves for both games\n"));
10631 _("Still need to make moves for all %d games\n"),
10636 switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
10639 _("Still need to make a move for game %s\n"),
10644 if (nCmailResults == nCmailGames) {
10645 sprintf(cmailMsg, _("No unfinished games\n"));
10647 sprintf(cmailMsg, _("Ready to send mail\n"));
10653 _("Still need to make moves for games %s\n"),
10665 if (gameMode == Training)
10666 SetTrainingModeOff();
10669 cmailMsgLoaded = FALSE;
10670 if (appData.icsActive) {
10671 SendToICS(ics_prefix);
10672 SendToICS("refresh\n");
10682 /* Give up on clean exit */
10686 /* Keep trying for clean exit */
10690 if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
10692 if (telnetISR != NULL) {
10693 RemoveInputSource(telnetISR);
10695 if (icsPR != NoProc) {
10696 DestroyChildProcess(icsPR, TRUE);
10699 /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
10700 GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
10702 /* [HGM] crash: the above GameEnds() is a dud if another one was running */
10703 /* make sure this other one finishes before killing it! */
10704 if(endingGame) { int count = 0;
10705 if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
10706 while(endingGame && count++ < 10) DoSleep(1);
10707 if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
10710 /* Kill off chess programs */
10711 if (first.pr != NoProc) {
10714 DoSleep( appData.delayBeforeQuit );
10715 SendToProgram("quit\n", &first);
10716 DoSleep( appData.delayAfterQuit );
10717 DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
10719 if (second.pr != NoProc) {
10720 DoSleep( appData.delayBeforeQuit );
10721 SendToProgram("quit\n", &second);
10722 DoSleep( appData.delayAfterQuit );
10723 DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
10725 if (first.isr != NULL) {
10726 RemoveInputSource(first.isr);
10728 if (second.isr != NULL) {
10729 RemoveInputSource(second.isr);
10732 ShutDownFrontEnd();
10739 if (appData.debugMode)
10740 fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
10744 if (gameMode == MachinePlaysWhite ||
10745 gameMode == MachinePlaysBlack) {
10748 DisplayBothClocks();
10750 if (gameMode == PlayFromGameFile) {
10751 if (appData.timeDelay >= 0)
10752 AutoPlayGameLoop();
10753 } else if (gameMode == IcsExamining && pauseExamInvalid) {
10754 Reset(FALSE, TRUE);
10755 SendToICS(ics_prefix);
10756 SendToICS("refresh\n");
10757 } else if (currentMove < forwardMostMove) {
10758 ForwardInner(forwardMostMove);
10760 pauseExamInvalid = FALSE;
10762 switch (gameMode) {
10766 pauseExamForwardMostMove = forwardMostMove;
10767 pauseExamInvalid = FALSE;
10770 case IcsPlayingWhite:
10771 case IcsPlayingBlack:
10775 case PlayFromGameFile:
10776 (void) StopLoadGameTimer();
10780 case BeginningOfGame:
10781 if (appData.icsActive) return;
10782 /* else fall through */
10783 case MachinePlaysWhite:
10784 case MachinePlaysBlack:
10785 case TwoMachinesPlay:
10786 if (forwardMostMove == 0)
10787 return; /* don't pause if no one has moved */
10788 if ((gameMode == MachinePlaysWhite &&
10789 !WhiteOnMove(forwardMostMove)) ||
10790 (gameMode == MachinePlaysBlack &&
10791 WhiteOnMove(forwardMostMove))) {
10804 char title[MSG_SIZ];
10806 if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
10807 strcpy(title, _("Edit comment"));
10809 sprintf(title, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
10810 WhiteOnMove(currentMove - 1) ? " " : ".. ",
10811 parseList[currentMove - 1]);
10814 EditCommentPopUp(currentMove, title, commentList[currentMove]);
10821 char *tags = PGNTags(&gameInfo);
10822 EditTagsPopUp(tags);
10829 if (appData.noChessProgram || gameMode == AnalyzeMode)
10832 if (gameMode != AnalyzeFile) {
10833 if (!appData.icsEngineAnalyze) {
10835 if (gameMode != EditGame) return;
10837 ResurrectChessProgram();
10838 SendToProgram("analyze\n", &first);
10839 first.analyzing = TRUE;
10840 /*first.maybeThinking = TRUE;*/
10841 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
10842 EngineOutputPopUp();
10844 if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
10849 StartAnalysisClock();
10850 GetTimeMark(&lastNodeCountTime);
10857 if (appData.noChessProgram || gameMode == AnalyzeFile)
10860 if (gameMode != AnalyzeMode) {
10862 if (gameMode != EditGame) return;
10863 ResurrectChessProgram();
10864 SendToProgram("analyze\n", &first);
10865 first.analyzing = TRUE;
10866 /*first.maybeThinking = TRUE;*/
10867 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
10868 EngineOutputPopUp();
10870 gameMode = AnalyzeFile;
10875 StartAnalysisClock();
10876 GetTimeMark(&lastNodeCountTime);
10881 MachineWhiteEvent()
10884 char *bookHit = NULL;
10886 if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
10890 if (gameMode == PlayFromGameFile ||
10891 gameMode == TwoMachinesPlay ||
10892 gameMode == Training ||
10893 gameMode == AnalyzeMode ||
10894 gameMode == EndOfGame)
10897 if (gameMode == EditPosition)
10898 EditPositionDone(TRUE);
10900 if (!WhiteOnMove(currentMove)) {
10901 DisplayError(_("It is not White's turn"), 0);
10905 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
10908 if (gameMode == EditGame || gameMode == AnalyzeMode ||
10909 gameMode == AnalyzeFile)
10912 ResurrectChessProgram(); /* in case it isn't running */
10913 if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
10914 gameMode = MachinePlaysWhite;
10917 gameMode = MachinePlaysWhite;
10921 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10923 if (first.sendName) {
10924 sprintf(buf, "name %s\n", gameInfo.black);
10925 SendToProgram(buf, &first);
10927 if (first.sendTime) {
10928 if (first.useColors) {
10929 SendToProgram("black\n", &first); /*gnu kludge*/
10931 SendTimeRemaining(&first, TRUE);
10933 if (first.useColors) {
10934 SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
10936 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
10937 SetMachineThinkingEnables();
10938 first.maybeThinking = TRUE;
10942 if (appData.autoFlipView && !flipView) {
10943 flipView = !flipView;
10944 DrawPosition(FALSE, NULL);
10945 DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;
10948 if(bookHit) { // [HGM] book: simulate book reply
10949 static char bookMove[MSG_SIZ]; // a bit generous?
10951 programStats.nodes = programStats.depth = programStats.time =
10952 programStats.score = programStats.got_only_move = 0;
10953 sprintf(programStats.movelist, "%s (xbook)", bookHit);
10955 strcpy(bookMove, "move ");
10956 strcat(bookMove, bookHit);
10957 HandleMachineMove(bookMove, &first);
10962 MachineBlackEvent()
10965 char *bookHit = NULL;
10967 if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
10971 if (gameMode == PlayFromGameFile ||
10972 gameMode == TwoMachinesPlay ||
10973 gameMode == Training ||
10974 gameMode == AnalyzeMode ||
10975 gameMode == EndOfGame)
10978 if (gameMode == EditPosition)
10979 EditPositionDone(TRUE);
10981 if (WhiteOnMove(currentMove)) {
10982 DisplayError(_("It is not Black's turn"), 0);
10986 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
10989 if (gameMode == EditGame || gameMode == AnalyzeMode ||
10990 gameMode == AnalyzeFile)
10993 ResurrectChessProgram(); /* in case it isn't running */
10994 gameMode = MachinePlaysBlack;
10998 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
11000 if (first.sendName) {
11001 sprintf(buf, "name %s\n", gameInfo.white);
11002 SendToProgram(buf, &first);
11004 if (first.sendTime) {
11005 if (first.useColors) {
11006 SendToProgram("white\n", &first); /*gnu kludge*/
11008 SendTimeRemaining(&first, FALSE);
11010 if (first.useColors) {
11011 SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
11013 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
11014 SetMachineThinkingEnables();
11015 first.maybeThinking = TRUE;
11018 if (appData.autoFlipView && flipView) {
11019 flipView = !flipView;
11020 DrawPosition(FALSE, NULL);
11021 DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;
11023 if(bookHit) { // [HGM] book: simulate book reply
11024 static char bookMove[MSG_SIZ]; // a bit generous?
11026 programStats.nodes = programStats.depth = programStats.time =
11027 programStats.score = programStats.got_only_move = 0;
11028 sprintf(programStats.movelist, "%s (xbook)", bookHit);
11030 strcpy(bookMove, "move ");
11031 strcat(bookMove, bookHit);
11032 HandleMachineMove(bookMove, &first);
11038 DisplayTwoMachinesTitle()
11041 if (appData.matchGames > 0) {
11042 if (first.twoMachinesColor[0] == 'w') {
11043 sprintf(buf, "%s vs. %s (%d-%d-%d)",
11044 gameInfo.white, gameInfo.black,
11045 first.matchWins, second.matchWins,
11046 matchGame - 1 - (first.matchWins + second.matchWins));
11048 sprintf(buf, "%s vs. %s (%d-%d-%d)",
11049 gameInfo.white, gameInfo.black,
11050 second.matchWins, first.matchWins,
11051 matchGame - 1 - (first.matchWins + second.matchWins));
11054 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
11060 TwoMachinesEvent P((void))
11064 ChessProgramState *onmove;
11065 char *bookHit = NULL;
11067 if (appData.noChessProgram) return;
11069 switch (gameMode) {
11070 case TwoMachinesPlay:
11072 case MachinePlaysWhite:
11073 case MachinePlaysBlack:
11074 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
11075 DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
11079 case BeginningOfGame:
11080 case PlayFromGameFile:
11083 if (gameMode != EditGame) return;
11086 EditPositionDone(TRUE);
11097 // forwardMostMove = currentMove;
11098 TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
11099 ResurrectChessProgram(); /* in case first program isn't running */
11101 if (second.pr == NULL) {
11102 StartChessProgram(&second);
11103 if (second.protocolVersion == 1) {
11104 TwoMachinesEventIfReady();
11106 /* kludge: allow timeout for initial "feature" command */
11108 DisplayMessage("", _("Starting second chess program"));
11109 ScheduleDelayedEvent(TwoMachinesEventIfReady, FEATURE_TIMEOUT);
11113 DisplayMessage("", "");
11114 InitChessProgram(&second, FALSE);
11115 SendToProgram("force\n", &second);
11116 if (startedFromSetupPosition) {
11117 SendBoard(&second, backwardMostMove);
11118 if (appData.debugMode) {
11119 fprintf(debugFP, "Two Machines\n");
11122 for (i = backwardMostMove; i < forwardMostMove; i++) {
11123 SendMoveToProgram(i, &second);
11126 gameMode = TwoMachinesPlay;
11130 DisplayTwoMachinesTitle();
11132 if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
11138 SendToProgram(first.computerString, &first);
11139 if (first.sendName) {
11140 sprintf(buf, "name %s\n", second.tidy);
11141 SendToProgram(buf, &first);
11143 SendToProgram(second.computerString, &second);
11144 if (second.sendName) {
11145 sprintf(buf, "name %s\n", first.tidy);
11146 SendToProgram(buf, &second);
11150 if (!first.sendTime || !second.sendTime) {
11151 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11152 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11154 if (onmove->sendTime) {
11155 if (onmove->useColors) {
11156 SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
11158 SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
11160 if (onmove->useColors) {
11161 SendToProgram(onmove->twoMachinesColor, onmove);
11163 bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
11164 // SendToProgram("go\n", onmove);
11165 onmove->maybeThinking = TRUE;
11166 SetMachineThinkingEnables();
11170 if(bookHit) { // [HGM] book: simulate book reply
11171 static char bookMove[MSG_SIZ]; // a bit generous?
11173 programStats.nodes = programStats.depth = programStats.time =
11174 programStats.score = programStats.got_only_move = 0;
11175 sprintf(programStats.movelist, "%s (xbook)", bookHit);
11177 strcpy(bookMove, "move ");
11178 strcat(bookMove, bookHit);
11179 savedMessage = bookMove; // args for deferred call
11180 savedState = onmove;
11181 ScheduleDelayedEvent(DeferredBookMove, 1);
11188 if (gameMode == Training) {
11189 SetTrainingModeOff();
11190 gameMode = PlayFromGameFile;
11191 DisplayMessage("", _("Training mode off"));
11193 gameMode = Training;
11194 animateTraining = appData.animate;
11196 /* make sure we are not already at the end of the game */
11197 if (currentMove < forwardMostMove) {
11198 SetTrainingModeOn();
11199 DisplayMessage("", _("Training mode on"));
11201 gameMode = PlayFromGameFile;
11202 DisplayError(_("Already at end of game"), 0);
11211 if (!appData.icsActive) return;
11212 switch (gameMode) {
11213 case IcsPlayingWhite:
11214 case IcsPlayingBlack:
11217 case BeginningOfGame:
11225 EditPositionDone(TRUE);
11238 gameMode = IcsIdle;
11249 switch (gameMode) {
11251 SetTrainingModeOff();
11253 case MachinePlaysWhite:
11254 case MachinePlaysBlack:
11255 case BeginningOfGame:
11256 SendToProgram("force\n", &first);
11257 SetUserThinkingEnables();
11259 case PlayFromGameFile:
11260 (void) StopLoadGameTimer();
11261 if (gameFileFP != NULL) {
11266 EditPositionDone(TRUE);
11271 SendToProgram("force\n", &first);
11273 case TwoMachinesPlay:
11274 GameEnds((ChessMove) 0, NULL, GE_PLAYER);
11275 ResurrectChessProgram();
11276 SetUserThinkingEnables();
11279 ResurrectChessProgram();
11281 case IcsPlayingBlack:
11282 case IcsPlayingWhite:
11283 DisplayError(_("Warning: You are still playing a game"), 0);
11286 DisplayError(_("Warning: You are still observing a game"), 0);
11289 DisplayError(_("Warning: You are still examining a game"), 0);
11300 first.offeredDraw = second.offeredDraw = 0;
11302 if (gameMode == PlayFromGameFile) {
11303 whiteTimeRemaining = timeRemaining[0][currentMove];
11304 blackTimeRemaining = timeRemaining[1][currentMove];
11308 if (gameMode == MachinePlaysWhite ||
11309 gameMode == MachinePlaysBlack ||
11310 gameMode == TwoMachinesPlay ||
11311 gameMode == EndOfGame) {
11312 i = forwardMostMove;
11313 while (i > currentMove) {
11314 SendToProgram("undo\n", &first);
11317 whiteTimeRemaining = timeRemaining[0][currentMove];
11318 blackTimeRemaining = timeRemaining[1][currentMove];
11319 DisplayBothClocks();
11320 if (whiteFlag || blackFlag) {
11321 whiteFlag = blackFlag = 0;
11326 gameMode = EditGame;
11333 EditPositionEvent()
11335 if (gameMode == EditPosition) {
11341 if (gameMode != EditGame) return;
11343 gameMode = EditPosition;
11346 if (currentMove > 0)
11347 CopyBoard(boards[0], boards[currentMove]);
11349 blackPlaysFirst = !WhiteOnMove(currentMove);
11351 currentMove = forwardMostMove = backwardMostMove = 0;
11352 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11359 /* [DM] icsEngineAnalyze - possible call from other functions */
11360 if (appData.icsEngineAnalyze) {
11361 appData.icsEngineAnalyze = FALSE;
11363 DisplayMessage("",_("Close ICS engine analyze..."));
11365 if (first.analysisSupport && first.analyzing) {
11366 SendToProgram("exit\n", &first);
11367 first.analyzing = FALSE;
11369 thinkOutput[0] = NULLCHAR;
11373 EditPositionDone(Boolean fakeRights)
11375 int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
11377 startedFromSetupPosition = TRUE;
11378 InitChessProgram(&first, FALSE);
11379 if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
11380 boards[0][EP_STATUS] = EP_NONE;
11381 boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
11382 if(boards[0][0][BOARD_WIDTH>>1] == king) {
11383 boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : NoRights;
11384 boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
11385 } else boards[0][CASTLING][2] = NoRights;
11386 if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
11387 boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : NoRights;
11388 boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
11389 } else boards[0][CASTLING][5] = NoRights;
11391 SendToProgram("force\n", &first);
11392 if (blackPlaysFirst) {
11393 strcpy(moveList[0], "");
11394 strcpy(parseList[0], "");
11395 currentMove = forwardMostMove = backwardMostMove = 1;
11396 CopyBoard(boards[1], boards[0]);
11398 currentMove = forwardMostMove = backwardMostMove = 0;
11400 SendBoard(&first, forwardMostMove);
11401 if (appData.debugMode) {
11402 fprintf(debugFP, "EditPosDone\n");
11405 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11406 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11407 gameMode = EditGame;
11409 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11410 ClearHighlights(); /* [AS] */
11413 /* Pause for `ms' milliseconds */
11414 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
11424 } while (SubtractTimeMarks(&m2, &m1) < ms);
11427 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
11429 SendMultiLineToICS(buf)
11432 char temp[MSG_SIZ+1], *p;
11439 strncpy(temp, buf, len);
11444 if (*p == '\n' || *p == '\r')
11449 strcat(temp, "\n");
11451 SendToPlayer(temp, strlen(temp));
11455 SetWhiteToPlayEvent()
11457 if (gameMode == EditPosition) {
11458 blackPlaysFirst = FALSE;
11459 DisplayBothClocks(); /* works because currentMove is 0 */
11460 } else if (gameMode == IcsExamining) {
11461 SendToICS(ics_prefix);
11462 SendToICS("tomove white\n");
11467 SetBlackToPlayEvent()
11469 if (gameMode == EditPosition) {
11470 blackPlaysFirst = TRUE;
11471 currentMove = 1; /* kludge */
11472 DisplayBothClocks();
11474 } else if (gameMode == IcsExamining) {
11475 SendToICS(ics_prefix);
11476 SendToICS("tomove black\n");
11481 EditPositionMenuEvent(selection, x, y)
11482 ChessSquare selection;
11486 ChessSquare piece = boards[0][y][x];
11488 if (gameMode != EditPosition && gameMode != IcsExamining) return;
11490 switch (selection) {
11492 if (gameMode == IcsExamining && ics_type == ICS_FICS) {
11493 SendToICS(ics_prefix);
11494 SendToICS("bsetup clear\n");
11495 } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
11496 SendToICS(ics_prefix);
11497 SendToICS("clearboard\n");
11499 for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
11500 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
11501 for (y = 0; y < BOARD_HEIGHT; y++) {
11502 if (gameMode == IcsExamining) {
11503 if (boards[currentMove][y][x] != EmptySquare) {
11504 sprintf(buf, "%sx@%c%c\n", ics_prefix,
11509 boards[0][y][x] = p;
11514 if (gameMode == EditPosition) {
11515 DrawPosition(FALSE, boards[0]);
11520 SetWhiteToPlayEvent();
11524 SetBlackToPlayEvent();
11528 if (gameMode == IcsExamining) {
11529 if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
11530 sprintf(buf, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
11533 if(x < BOARD_LEFT || x >= BOARD_RGHT) {
11534 if(x == BOARD_LEFT-2) {
11535 if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
11536 boards[0][y][1] = 0;
11538 if(x == BOARD_RGHT+1) {
11539 if(y >= gameInfo.holdingsSize) break;
11540 boards[0][y][BOARD_WIDTH-2] = 0;
11543 boards[0][y][x] = EmptySquare;
11544 DrawPosition(FALSE, boards[0]);
11549 if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
11550 piece >= (int)BlackPawn && piece < (int)BlackMan ) {
11551 selection = (ChessSquare) (PROMOTED piece);
11552 } else if(piece == EmptySquare) selection = WhiteSilver;
11553 else selection = (ChessSquare)((int)piece - 1);
11557 if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
11558 piece > (int)BlackMan && piece <= (int)BlackKing ) {
11559 selection = (ChessSquare) (DEMOTED piece);
11560 } else if(piece == EmptySquare) selection = BlackSilver;
11561 else selection = (ChessSquare)((int)piece + 1);
11566 if(gameInfo.variant == VariantShatranj ||
11567 gameInfo.variant == VariantXiangqi ||
11568 gameInfo.variant == VariantCourier ||
11569 gameInfo.variant == VariantMakruk )
11570 selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
11575 if(gameInfo.variant == VariantXiangqi)
11576 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
11577 if(gameInfo.variant == VariantKnightmate)
11578 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
11581 if (gameMode == IcsExamining) {
11582 if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
11583 sprintf(buf, "%s%c@%c%c\n", ics_prefix,
11584 PieceToChar(selection), AAA + x, ONE + y);
11587 if(x < BOARD_LEFT || x >= BOARD_RGHT) {
11589 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
11590 n = PieceToNumber(selection - BlackPawn);
11591 if(n > gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
11592 boards[0][BOARD_HEIGHT-1-n][0] = selection;
11593 boards[0][BOARD_HEIGHT-1-n][1]++;
11595 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
11596 n = PieceToNumber(selection);
11597 if(n > gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
11598 boards[0][n][BOARD_WIDTH-1] = selection;
11599 boards[0][n][BOARD_WIDTH-2]++;
11602 boards[0][y][x] = selection;
11603 DrawPosition(TRUE, boards[0]);
11611 DropMenuEvent(selection, x, y)
11612 ChessSquare selection;
11615 ChessMove moveType;
11617 switch (gameMode) {
11618 case IcsPlayingWhite:
11619 case MachinePlaysBlack:
11620 if (!WhiteOnMove(currentMove)) {
11621 DisplayMoveError(_("It is Black's turn"));
11624 moveType = WhiteDrop;
11626 case IcsPlayingBlack:
11627 case MachinePlaysWhite:
11628 if (WhiteOnMove(currentMove)) {
11629 DisplayMoveError(_("It is White's turn"));
11632 moveType = BlackDrop;
11635 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
11641 if (moveType == BlackDrop && selection < BlackPawn) {
11642 selection = (ChessSquare) ((int) selection
11643 + (int) BlackPawn - (int) WhitePawn);
11645 if (boards[currentMove][y][x] != EmptySquare) {
11646 DisplayMoveError(_("That square is occupied"));
11650 FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
11656 /* Accept a pending offer of any kind from opponent */
11658 if (appData.icsActive) {
11659 SendToICS(ics_prefix);
11660 SendToICS("accept\n");
11661 } else if (cmailMsgLoaded) {
11662 if (currentMove == cmailOldMove &&
11663 commentList[cmailOldMove] != NULL &&
11664 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11665 "Black offers a draw" : "White offers a draw")) {
11667 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11668 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
11670 DisplayError(_("There is no pending offer on this move"), 0);
11671 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11674 /* Not used for offers from chess program */
11681 /* Decline a pending offer of any kind from opponent */
11683 if (appData.icsActive) {
11684 SendToICS(ics_prefix);
11685 SendToICS("decline\n");
11686 } else if (cmailMsgLoaded) {
11687 if (currentMove == cmailOldMove &&
11688 commentList[cmailOldMove] != NULL &&
11689 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11690 "Black offers a draw" : "White offers a draw")) {
11692 AppendComment(cmailOldMove, "Draw declined", TRUE);
11693 DisplayComment(cmailOldMove - 1, "Draw declined");
11696 DisplayError(_("There is no pending offer on this move"), 0);
11699 /* Not used for offers from chess program */
11706 /* Issue ICS rematch command */
11707 if (appData.icsActive) {
11708 SendToICS(ics_prefix);
11709 SendToICS("rematch\n");
11716 /* Call your opponent's flag (claim a win on time) */
11717 if (appData.icsActive) {
11718 SendToICS(ics_prefix);
11719 SendToICS("flag\n");
11721 switch (gameMode) {
11724 case MachinePlaysWhite:
11727 GameEnds(GameIsDrawn, "Both players ran out of time",
11730 GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
11732 DisplayError(_("Your opponent is not out of time"), 0);
11735 case MachinePlaysBlack:
11738 GameEnds(GameIsDrawn, "Both players ran out of time",
11741 GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
11743 DisplayError(_("Your opponent is not out of time"), 0);
11753 /* Offer draw or accept pending draw offer from opponent */
11755 if (appData.icsActive) {
11756 /* Note: tournament rules require draw offers to be
11757 made after you make your move but before you punch
11758 your clock. Currently ICS doesn't let you do that;
11759 instead, you immediately punch your clock after making
11760 a move, but you can offer a draw at any time. */
11762 SendToICS(ics_prefix);
11763 SendToICS("draw\n");
11764 } else if (cmailMsgLoaded) {
11765 if (currentMove == cmailOldMove &&
11766 commentList[cmailOldMove] != NULL &&
11767 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11768 "Black offers a draw" : "White offers a draw")) {
11769 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11770 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
11771 } else if (currentMove == cmailOldMove + 1) {
11772 char *offer = WhiteOnMove(cmailOldMove) ?
11773 "White offers a draw" : "Black offers a draw";
11774 AppendComment(currentMove, offer, TRUE);
11775 DisplayComment(currentMove - 1, offer);
11776 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
11778 DisplayError(_("You must make your move before offering a draw"), 0);
11779 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11781 } else if (first.offeredDraw) {
11782 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
11784 if (first.sendDrawOffers) {
11785 SendToProgram("draw\n", &first);
11786 userOfferedDraw = TRUE;
11794 /* Offer Adjourn or accept pending Adjourn offer from opponent */
11796 if (appData.icsActive) {
11797 SendToICS(ics_prefix);
11798 SendToICS("adjourn\n");
11800 /* Currently GNU Chess doesn't offer or accept Adjourns */
11808 /* Offer Abort or accept pending Abort offer from opponent */
11810 if (appData.icsActive) {
11811 SendToICS(ics_prefix);
11812 SendToICS("abort\n");
11814 GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
11821 /* Resign. You can do this even if it's not your turn. */
11823 if (appData.icsActive) {
11824 SendToICS(ics_prefix);
11825 SendToICS("resign\n");
11827 switch (gameMode) {
11828 case MachinePlaysWhite:
11829 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11831 case MachinePlaysBlack:
11832 GameEnds(BlackWins, "White resigns", GE_PLAYER);
11835 if (cmailMsgLoaded) {
11837 if (WhiteOnMove(cmailOldMove)) {
11838 GameEnds(BlackWins, "White resigns", GE_PLAYER);
11840 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11842 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
11853 StopObservingEvent()
11855 /* Stop observing current games */
11856 SendToICS(ics_prefix);
11857 SendToICS("unobserve\n");
11861 StopExaminingEvent()
11863 /* Stop observing current game */
11864 SendToICS(ics_prefix);
11865 SendToICS("unexamine\n");
11869 ForwardInner(target)
11874 if (appData.debugMode)
11875 fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
11876 target, currentMove, forwardMostMove);
11878 if (gameMode == EditPosition)
11881 if (gameMode == PlayFromGameFile && !pausing)
11884 if (gameMode == IcsExamining && pausing)
11885 limit = pauseExamForwardMostMove;
11887 limit = forwardMostMove;
11889 if (target > limit) target = limit;
11891 if (target > 0 && moveList[target - 1][0]) {
11892 int fromX, fromY, toX, toY;
11893 toX = moveList[target - 1][2] - AAA;
11894 toY = moveList[target - 1][3] - ONE;
11895 if (moveList[target - 1][1] == '@') {
11896 if (appData.highlightLastMove) {
11897 SetHighlights(-1, -1, toX, toY);
11900 fromX = moveList[target - 1][0] - AAA;
11901 fromY = moveList[target - 1][1] - ONE;
11902 if (target == currentMove + 1) {
11903 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
11905 if (appData.highlightLastMove) {
11906 SetHighlights(fromX, fromY, toX, toY);
11910 if (gameMode == EditGame || gameMode == AnalyzeMode ||
11911 gameMode == Training || gameMode == PlayFromGameFile ||
11912 gameMode == AnalyzeFile) {
11913 while (currentMove < target) {
11914 SendMoveToProgram(currentMove++, &first);
11917 currentMove = target;
11920 if (gameMode == EditGame || gameMode == EndOfGame) {
11921 whiteTimeRemaining = timeRemaining[0][currentMove];
11922 blackTimeRemaining = timeRemaining[1][currentMove];
11924 DisplayBothClocks();
11925 DisplayMove(currentMove - 1);
11926 DrawPosition(FALSE, boards[currentMove]);
11927 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
11928 if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
11929 DisplayComment(currentMove - 1, commentList[currentMove]);
11937 if (gameMode == IcsExamining && !pausing) {
11938 SendToICS(ics_prefix);
11939 SendToICS("forward\n");
11941 ForwardInner(currentMove + 1);
11948 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11949 /* to optimze, we temporarily turn off analysis mode while we feed
11950 * the remaining moves to the engine. Otherwise we get analysis output
11953 if (first.analysisSupport) {
11954 SendToProgram("exit\nforce\n", &first);
11955 first.analyzing = FALSE;
11959 if (gameMode == IcsExamining && !pausing) {
11960 SendToICS(ics_prefix);
11961 SendToICS("forward 999999\n");
11963 ForwardInner(forwardMostMove);
11966 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11967 /* we have fed all the moves, so reactivate analysis mode */
11968 SendToProgram("analyze\n", &first);
11969 first.analyzing = TRUE;
11970 /*first.maybeThinking = TRUE;*/
11971 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11976 BackwardInner(target)
11979 int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
11981 if (appData.debugMode)
11982 fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
11983 target, currentMove, forwardMostMove);
11985 if (gameMode == EditPosition) return;
11986 if (currentMove <= backwardMostMove) {
11988 DrawPosition(full_redraw, boards[currentMove]);
11991 if (gameMode == PlayFromGameFile && !pausing)
11994 if (moveList[target][0]) {
11995 int fromX, fromY, toX, toY;
11996 toX = moveList[target][2] - AAA;
11997 toY = moveList[target][3] - ONE;
11998 if (moveList[target][1] == '@') {
11999 if (appData.highlightLastMove) {
12000 SetHighlights(-1, -1, toX, toY);
12003 fromX = moveList[target][0] - AAA;
12004 fromY = moveList[target][1] - ONE;
12005 if (target == currentMove - 1) {
12006 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
12008 if (appData.highlightLastMove) {
12009 SetHighlights(fromX, fromY, toX, toY);
12013 if (gameMode == EditGame || gameMode==AnalyzeMode ||
12014 gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
12015 while (currentMove > target) {
12016 SendToProgram("undo\n", &first);
12020 currentMove = target;
12023 if (gameMode == EditGame || gameMode == EndOfGame) {
12024 whiteTimeRemaining = timeRemaining[0][currentMove];
12025 blackTimeRemaining = timeRemaining[1][currentMove];
12027 DisplayBothClocks();
12028 DisplayMove(currentMove - 1);
12029 DrawPosition(full_redraw, boards[currentMove]);
12030 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
12031 // [HGM] PV info: routine tests if comment empty
12032 DisplayComment(currentMove - 1, commentList[currentMove]);
12038 if (gameMode == IcsExamining && !pausing) {
12039 SendToICS(ics_prefix);
12040 SendToICS("backward\n");
12042 BackwardInner(currentMove - 1);
12049 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12050 /* to optimize, we temporarily turn off analysis mode while we undo
12051 * all the moves. Otherwise we get analysis output after each undo.
12053 if (first.analysisSupport) {
12054 SendToProgram("exit\nforce\n", &first);
12055 first.analyzing = FALSE;
12059 if (gameMode == IcsExamining && !pausing) {
12060 SendToICS(ics_prefix);
12061 SendToICS("backward 999999\n");
12063 BackwardInner(backwardMostMove);
12066 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12067 /* we have fed all the moves, so reactivate analysis mode */
12068 SendToProgram("analyze\n", &first);
12069 first.analyzing = TRUE;
12070 /*first.maybeThinking = TRUE;*/
12071 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12078 if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
12079 if (to >= forwardMostMove) to = forwardMostMove;
12080 if (to <= backwardMostMove) to = backwardMostMove;
12081 if (to < currentMove) {
12091 if(PopTail(TRUE)) { // [HGM] vari: restore old game tail
12094 if (gameMode != IcsExamining) {
12095 DisplayError(_("You are not examining a game"), 0);
12099 DisplayError(_("You can't revert while pausing"), 0);
12102 SendToICS(ics_prefix);
12103 SendToICS("revert\n");
12109 switch (gameMode) {
12110 case MachinePlaysWhite:
12111 case MachinePlaysBlack:
12112 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
12113 DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
12116 if (forwardMostMove < 2) return;
12117 currentMove = forwardMostMove = forwardMostMove - 2;
12118 whiteTimeRemaining = timeRemaining[0][currentMove];
12119 blackTimeRemaining = timeRemaining[1][currentMove];
12120 DisplayBothClocks();
12121 DisplayMove(currentMove - 1);
12122 ClearHighlights();/*!! could figure this out*/
12123 DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
12124 SendToProgram("remove\n", &first);
12125 /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
12128 case BeginningOfGame:
12132 case IcsPlayingWhite:
12133 case IcsPlayingBlack:
12134 if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
12135 SendToICS(ics_prefix);
12136 SendToICS("takeback 2\n");
12138 SendToICS(ics_prefix);
12139 SendToICS("takeback 1\n");
12148 ChessProgramState *cps;
12150 switch (gameMode) {
12151 case MachinePlaysWhite:
12152 if (!WhiteOnMove(forwardMostMove)) {
12153 DisplayError(_("It is your turn"), 0);
12158 case MachinePlaysBlack:
12159 if (WhiteOnMove(forwardMostMove)) {
12160 DisplayError(_("It is your turn"), 0);
12165 case TwoMachinesPlay:
12166 if (WhiteOnMove(forwardMostMove) ==
12167 (first.twoMachinesColor[0] == 'w')) {
12173 case BeginningOfGame:
12177 SendToProgram("?\n", cps);
12181 TruncateGameEvent()
12184 if (gameMode != EditGame) return;
12191 CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
12192 if (forwardMostMove > currentMove) {
12193 if (gameInfo.resultDetails != NULL) {
12194 free(gameInfo.resultDetails);
12195 gameInfo.resultDetails = NULL;
12196 gameInfo.result = GameUnfinished;
12198 forwardMostMove = currentMove;
12199 HistorySet(parseList, backwardMostMove, forwardMostMove,
12207 if (appData.noChessProgram) return;
12208 switch (gameMode) {
12209 case MachinePlaysWhite:
12210 if (WhiteOnMove(forwardMostMove)) {
12211 DisplayError(_("Wait until your turn"), 0);
12215 case BeginningOfGame:
12216 case MachinePlaysBlack:
12217 if (!WhiteOnMove(forwardMostMove)) {
12218 DisplayError(_("Wait until your turn"), 0);
12223 DisplayError(_("No hint available"), 0);
12226 SendToProgram("hint\n", &first);
12227 hintRequested = TRUE;
12233 if (appData.noChessProgram) return;
12234 switch (gameMode) {
12235 case MachinePlaysWhite:
12236 if (WhiteOnMove(forwardMostMove)) {
12237 DisplayError(_("Wait until your turn"), 0);
12241 case BeginningOfGame:
12242 case MachinePlaysBlack:
12243 if (!WhiteOnMove(forwardMostMove)) {
12244 DisplayError(_("Wait until your turn"), 0);
12249 EditPositionDone(TRUE);
12251 case TwoMachinesPlay:
12256 SendToProgram("bk\n", &first);
12257 bookOutput[0] = NULLCHAR;
12258 bookRequested = TRUE;
12264 char *tags = PGNTags(&gameInfo);
12265 TagsPopUp(tags, CmailMsg());
12269 /* end button procedures */
12272 PrintPosition(fp, move)
12278 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
12279 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
12280 char c = PieceToChar(boards[move][i][j]);
12281 fputc(c == 'x' ? '.' : c, fp);
12282 fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
12285 if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
12286 fprintf(fp, "white to play\n");
12288 fprintf(fp, "black to play\n");
12295 if (gameInfo.white != NULL) {
12296 fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
12302 /* Find last component of program's own name, using some heuristics */
12304 TidyProgramName(prog, host, buf)
12305 char *prog, *host, buf[MSG_SIZ];
12308 int local = (strcmp(host, "localhost") == 0);
12309 while (!local && (p = strchr(prog, ';')) != NULL) {
12311 while (*p == ' ') p++;
12314 if (*prog == '"' || *prog == '\'') {
12315 q = strchr(prog + 1, *prog);
12317 q = strchr(prog, ' ');
12319 if (q == NULL) q = prog + strlen(prog);
12321 while (p >= prog && *p != '/' && *p != '\\') p--;
12323 if(p == prog && *p == '"') p++;
12324 if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
12325 memcpy(buf, p, q - p);
12326 buf[q - p] = NULLCHAR;
12334 TimeControlTagValue()
12337 if (!appData.clockMode) {
12339 } else if (movesPerSession > 0) {
12340 sprintf(buf, "%d/%ld", movesPerSession, timeControl/1000);
12341 } else if (timeIncrement == 0) {
12342 sprintf(buf, "%ld", timeControl/1000);
12344 sprintf(buf, "%ld+%ld", timeControl/1000, timeIncrement/1000);
12346 return StrSave(buf);
12352 /* This routine is used only for certain modes */
12353 VariantClass v = gameInfo.variant;
12354 ChessMove r = GameUnfinished;
12357 if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
12358 r = gameInfo.result;
12359 p = gameInfo.resultDetails;
12360 gameInfo.resultDetails = NULL;
12362 ClearGameInfo(&gameInfo);
12363 gameInfo.variant = v;
12365 switch (gameMode) {
12366 case MachinePlaysWhite:
12367 gameInfo.event = StrSave( appData.pgnEventHeader );
12368 gameInfo.site = StrSave(HostName());
12369 gameInfo.date = PGNDate();
12370 gameInfo.round = StrSave("-");
12371 gameInfo.white = StrSave(first.tidy);
12372 gameInfo.black = StrSave(UserName());
12373 gameInfo.timeControl = TimeControlTagValue();
12376 case MachinePlaysBlack:
12377 gameInfo.event = StrSave( appData.pgnEventHeader );
12378 gameInfo.site = StrSave(HostName());
12379 gameInfo.date = PGNDate();
12380 gameInfo.round = StrSave("-");
12381 gameInfo.white = StrSave(UserName());
12382 gameInfo.black = StrSave(first.tidy);
12383 gameInfo.timeControl = TimeControlTagValue();
12386 case TwoMachinesPlay:
12387 gameInfo.event = StrSave( appData.pgnEventHeader );
12388 gameInfo.site = StrSave(HostName());
12389 gameInfo.date = PGNDate();
12390 if (matchGame > 0) {
12392 sprintf(buf, "%d", matchGame);
12393 gameInfo.round = StrSave(buf);
12395 gameInfo.round = StrSave("-");
12397 if (first.twoMachinesColor[0] == 'w') {
12398 gameInfo.white = StrSave(first.tidy);
12399 gameInfo.black = StrSave(second.tidy);
12401 gameInfo.white = StrSave(second.tidy);
12402 gameInfo.black = StrSave(first.tidy);
12404 gameInfo.timeControl = TimeControlTagValue();
12408 gameInfo.event = StrSave("Edited game");
12409 gameInfo.site = StrSave(HostName());
12410 gameInfo.date = PGNDate();
12411 gameInfo.round = StrSave("-");
12412 gameInfo.white = StrSave("-");
12413 gameInfo.black = StrSave("-");
12414 gameInfo.result = r;
12415 gameInfo.resultDetails = p;
12419 gameInfo.event = StrSave("Edited position");
12420 gameInfo.site = StrSave(HostName());
12421 gameInfo.date = PGNDate();
12422 gameInfo.round = StrSave("-");
12423 gameInfo.white = StrSave("-");
12424 gameInfo.black = StrSave("-");
12427 case IcsPlayingWhite:
12428 case IcsPlayingBlack:
12433 case PlayFromGameFile:
12434 gameInfo.event = StrSave("Game from non-PGN file");
12435 gameInfo.site = StrSave(HostName());
12436 gameInfo.date = PGNDate();
12437 gameInfo.round = StrSave("-");
12438 gameInfo.white = StrSave("?");
12439 gameInfo.black = StrSave("?");
12448 ReplaceComment(index, text)
12454 while (*text == '\n') text++;
12455 len = strlen(text);
12456 while (len > 0 && text[len - 1] == '\n') len--;
12458 if (commentList[index] != NULL)
12459 free(commentList[index]);
12462 commentList[index] = NULL;
12465 if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
12466 *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
12467 *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
12468 commentList[index] = (char *) malloc(len + 2);
12469 strncpy(commentList[index], text, len);
12470 commentList[index][len] = '\n';
12471 commentList[index][len + 1] = NULLCHAR;
12473 // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
12475 commentList[index] = (char *) malloc(len + 6);
12476 strcpy(commentList[index], "{\n");
12477 strncpy(commentList[index]+2, text, len);
12478 commentList[index][len+2] = NULLCHAR;
12479 while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
12480 strcat(commentList[index], "\n}\n");
12494 if (ch == '\r') continue;
12496 } while (ch != '\0');
12500 AppendComment(index, text, addBraces)
12503 Boolean addBraces; // [HGM] braces: tells if we should add {}
12508 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
12509 text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
12512 while (*text == '\n') text++;
12513 len = strlen(text);
12514 while (len > 0 && text[len - 1] == '\n') len--;
12516 if (len == 0) return;
12518 if (commentList[index] != NULL) {
12519 old = commentList[index];
12520 oldlen = strlen(old);
12521 while(commentList[index][oldlen-1] == '\n')
12522 commentList[index][--oldlen] = NULLCHAR;
12523 commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
12524 strcpy(commentList[index], old);
12526 // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
12527 if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces)) {
12528 if(addBraces) addBraces = FALSE; else { text++; len--; }
12529 while (*text == '\n') { text++; len--; }
12530 commentList[index][--oldlen] = NULLCHAR;
12532 if(addBraces) strcat(commentList[index], "\n{\n");
12533 else strcat(commentList[index], "\n");
12534 strcat(commentList[index], text);
12535 if(addBraces) strcat(commentList[index], "\n}\n");
12536 else strcat(commentList[index], "\n");
12538 commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
12540 strcpy(commentList[index], "{\n");
12541 else commentList[index][0] = NULLCHAR;
12542 strcat(commentList[index], text);
12543 strcat(commentList[index], "\n");
12544 if(addBraces) strcat(commentList[index], "}\n");
12548 static char * FindStr( char * text, char * sub_text )
12550 char * result = strstr( text, sub_text );
12552 if( result != NULL ) {
12553 result += strlen( sub_text );
12559 /* [AS] Try to extract PV info from PGN comment */
12560 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
12561 char *GetInfoFromComment( int index, char * text )
12565 if( text != NULL && index > 0 ) {
12568 int time = -1, sec = 0, deci;
12569 char * s_eval = FindStr( text, "[%eval " );
12570 char * s_emt = FindStr( text, "[%emt " );
12572 if( s_eval != NULL || s_emt != NULL ) {
12576 if( s_eval != NULL ) {
12577 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
12581 if( delim != ']' ) {
12586 if( s_emt != NULL ) {
12591 /* We expect something like: [+|-]nnn.nn/dd */
12594 if(*text != '{') return text; // [HGM] braces: must be normal comment
12596 sep = strchr( text, '/' );
12597 if( sep == NULL || sep < (text+4) ) {
12601 time = -1; sec = -1; deci = -1;
12602 if( sscanf( text+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
12603 sscanf( text+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
12604 sscanf( text+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
12605 sscanf( text+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3 ) {
12609 if( score_lo < 0 || score_lo >= 100 ) {
12613 if(sec >= 0) time = 600*time + 10*sec; else
12614 if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
12616 score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
12618 /* [HGM] PV time: now locate end of PV info */
12619 while( *++sep >= '0' && *sep <= '9'); // strip depth
12621 while( *++sep >= '0' && *sep <= '9'); // strip time
12623 while( *++sep >= '0' && *sep <= '9'); // strip seconds
12625 while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
12626 while(*sep == ' ') sep++;
12637 pvInfoList[index-1].depth = depth;
12638 pvInfoList[index-1].score = score;
12639 pvInfoList[index-1].time = 10*time; // centi-sec
12640 if(*sep == '}') *sep = 0; else *--sep = '{';
12646 SendToProgram(message, cps)
12648 ChessProgramState *cps;
12650 int count, outCount, error;
12653 if (cps->pr == NULL) return;
12656 if (appData.debugMode) {
12659 fprintf(debugFP, "%ld >%-6s: %s",
12660 SubtractTimeMarks(&now, &programStartTime),
12661 cps->which, message);
12664 count = strlen(message);
12665 outCount = OutputToProcess(cps->pr, message, count, &error);
12666 if (outCount < count && !exiting
12667 && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
12668 sprintf(buf, _("Error writing to %s chess program"), cps->which);
12669 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
12670 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
12671 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
12672 sprintf(buf, "%s program exits in draw position (%s)", cps->which, cps->program);
12674 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
12676 gameInfo.resultDetails = StrSave(buf);
12678 if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
12683 ReceiveFromProgram(isr, closure, message, count, error)
12684 InputSourceRef isr;
12692 ChessProgramState *cps = (ChessProgramState *)closure;
12694 if (isr != cps->isr) return; /* Killed intentionally */
12698 _("Error: %s chess program (%s) exited unexpectedly"),
12699 cps->which, cps->program);
12700 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
12701 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
12702 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
12703 sprintf(buf, _("%s program exits in draw position (%s)"), cps->which, cps->program);
12705 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
12707 gameInfo.resultDetails = StrSave(buf);
12709 RemoveInputSource(cps->isr);
12710 if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
12713 _("Error reading from %s chess program (%s)"),
12714 cps->which, cps->program);
12715 RemoveInputSource(cps->isr);
12717 /* [AS] Program is misbehaving badly... kill it */
12718 if( count == -2 ) {
12719 DestroyChildProcess( cps->pr, 9 );
12723 if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
12728 if ((end_str = strchr(message, '\r')) != NULL)
12729 *end_str = NULLCHAR;
12730 if ((end_str = strchr(message, '\n')) != NULL)
12731 *end_str = NULLCHAR;
12733 if (appData.debugMode) {
12734 TimeMark now; int print = 1;
12735 char *quote = ""; char c; int i;
12737 if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
12738 char start = message[0];
12739 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
12740 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
12741 sscanf(message, "move %c", &c)!=1 && sscanf(message, "offer%c", &c)!=1 &&
12742 sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
12743 sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
12744 sscanf(message, "tell%c", &c)!=1 && sscanf(message, "0-1 %c", &c)!=1 &&
12745 sscanf(message, "1-0 %c", &c)!=1 && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
12746 sscanf(message, "pong %c", &c)!=1 && start != '#')
12747 { quote = "# "; print = (appData.engineComments == 2); }
12748 message[0] = start; // restore original message
12752 fprintf(debugFP, "%ld <%-6s: %s%s\n",
12753 SubtractTimeMarks(&now, &programStartTime), cps->which,
12759 /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
12760 if (appData.icsEngineAnalyze) {
12761 if (strstr(message, "whisper") != NULL ||
12762 strstr(message, "kibitz") != NULL ||
12763 strstr(message, "tellics") != NULL) return;
12766 HandleMachineMove(message, cps);
12771 SendTimeControl(cps, mps, tc, inc, sd, st)
12772 ChessProgramState *cps;
12773 int mps, inc, sd, st;
12779 if( timeControl_2 > 0 ) {
12780 if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
12781 tc = timeControl_2;
12784 tc /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
12785 inc /= cps->timeOdds;
12786 st /= cps->timeOdds;
12788 seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
12791 /* Set exact time per move, normally using st command */
12792 if (cps->stKludge) {
12793 /* GNU Chess 4 has no st command; uses level in a nonstandard way */
12795 if (seconds == 0) {
12796 sprintf(buf, "level 1 %d\n", st/60);
12798 sprintf(buf, "level 1 %d:%02d\n", st/60, seconds);
12801 sprintf(buf, "st %d\n", st);
12804 /* Set conventional or incremental time control, using level command */
12805 if (seconds == 0) {
12806 /* Note old gnuchess bug -- minutes:seconds used to not work.
12807 Fixed in later versions, but still avoid :seconds
12808 when seconds is 0. */
12809 sprintf(buf, "level %d %ld %d\n", mps, tc/60000, inc/1000);
12811 sprintf(buf, "level %d %ld:%02d %d\n", mps, tc/60000,
12812 seconds, inc/1000);
12815 SendToProgram(buf, cps);
12817 /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
12818 /* Orthogonally, limit search to given depth */
12820 if (cps->sdKludge) {
12821 sprintf(buf, "depth\n%d\n", sd);
12823 sprintf(buf, "sd %d\n", sd);
12825 SendToProgram(buf, cps);
12828 if(cps->nps > 0) { /* [HGM] nps */
12829 if(cps->supportsNPS == FALSE) cps->nps = -1; // don't use if engine explicitly says not supported!
12831 sprintf(buf, "nps %d\n", cps->nps);
12832 SendToProgram(buf, cps);
12837 ChessProgramState *WhitePlayer()
12838 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
12840 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
12841 gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
12847 SendTimeRemaining(cps, machineWhite)
12848 ChessProgramState *cps;
12849 int /*boolean*/ machineWhite;
12851 char message[MSG_SIZ];
12854 /* Note: this routine must be called when the clocks are stopped
12855 or when they have *just* been set or switched; otherwise
12856 it will be off by the time since the current tick started.
12858 if (machineWhite) {
12859 time = whiteTimeRemaining / 10;
12860 otime = blackTimeRemaining / 10;
12862 time = blackTimeRemaining / 10;
12863 otime = whiteTimeRemaining / 10;
12865 /* [HGM] translate opponent's time by time-odds factor */
12866 otime = (otime * cps->other->timeOdds) / cps->timeOdds;
12867 if (appData.debugMode) {
12868 fprintf(debugFP, "time odds: %f %f \n", cps->timeOdds, cps->other->timeOdds);
12871 if (time <= 0) time = 1;
12872 if (otime <= 0) otime = 1;
12874 sprintf(message, "time %ld\n", time);
12875 SendToProgram(message, cps);
12877 sprintf(message, "otim %ld\n", otime);
12878 SendToProgram(message, cps);
12882 BoolFeature(p, name, loc, cps)
12886 ChessProgramState *cps;
12889 int len = strlen(name);
12891 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
12893 sscanf(*p, "%d", &val);
12895 while (**p && **p != ' ') (*p)++;
12896 sprintf(buf, "accepted %s\n", name);
12897 SendToProgram(buf, cps);
12904 IntFeature(p, name, loc, cps)
12908 ChessProgramState *cps;
12911 int len = strlen(name);
12912 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
12914 sscanf(*p, "%d", loc);
12915 while (**p && **p != ' ') (*p)++;
12916 sprintf(buf, "accepted %s\n", name);
12917 SendToProgram(buf, cps);
12924 StringFeature(p, name, loc, cps)
12928 ChessProgramState *cps;
12931 int len = strlen(name);
12932 if (strncmp((*p), name, len) == 0
12933 && (*p)[len] == '=' && (*p)[len+1] == '\"') {
12935 sscanf(*p, "%[^\"]", loc);
12936 while (**p && **p != '\"') (*p)++;
12937 if (**p == '\"') (*p)++;
12938 sprintf(buf, "accepted %s\n", name);
12939 SendToProgram(buf, cps);
12946 ParseOption(Option *opt, ChessProgramState *cps)
12947 // [HGM] options: process the string that defines an engine option, and determine
12948 // name, type, default value, and allowed value range
12950 char *p, *q, buf[MSG_SIZ];
12951 int n, min = (-1)<<31, max = 1<<31, def;
12953 if(p = strstr(opt->name, " -spin ")) {
12954 if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
12955 if(max < min) max = min; // enforce consistency
12956 if(def < min) def = min;
12957 if(def > max) def = max;
12962 } else if((p = strstr(opt->name, " -slider "))) {
12963 // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
12964 if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
12965 if(max < min) max = min; // enforce consistency
12966 if(def < min) def = min;
12967 if(def > max) def = max;
12971 opt->type = Spin; // Slider;
12972 } else if((p = strstr(opt->name, " -string "))) {
12973 opt->textValue = p+9;
12974 opt->type = TextBox;
12975 } else if((p = strstr(opt->name, " -file "))) {
12976 // for now -file is a synonym for -string, to already provide compatibility with future polyglots
12977 opt->textValue = p+7;
12978 opt->type = TextBox; // FileName;
12979 } else if((p = strstr(opt->name, " -path "))) {
12980 // for now -file is a synonym for -string, to already provide compatibility with future polyglots
12981 opt->textValue = p+7;
12982 opt->type = TextBox; // PathName;
12983 } else if(p = strstr(opt->name, " -check ")) {
12984 if(sscanf(p, " -check %d", &def) < 1) return FALSE;
12985 opt->value = (def != 0);
12986 opt->type = CheckBox;
12987 } else if(p = strstr(opt->name, " -combo ")) {
12988 opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
12989 cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
12990 if(*q == '*') cps->comboList[cps->comboCnt-1]++;
12991 opt->value = n = 0;
12992 while(q = StrStr(q, " /// ")) {
12993 n++; *q = 0; // count choices, and null-terminate each of them
12995 if(*q == '*') { // remember default, which is marked with * prefix
12999 cps->comboList[cps->comboCnt++] = q;
13001 cps->comboList[cps->comboCnt++] = NULL;
13003 opt->type = ComboBox;
13004 } else if(p = strstr(opt->name, " -button")) {
13005 opt->type = Button;
13006 } else if(p = strstr(opt->name, " -save")) {
13007 opt->type = SaveButton;
13008 } else return FALSE;
13009 *p = 0; // terminate option name
13010 // now look if the command-line options define a setting for this engine option.
13011 if(cps->optionSettings && cps->optionSettings[0])
13012 p = strstr(cps->optionSettings, opt->name); else p = NULL;
13013 if(p && (p == cps->optionSettings || p[-1] == ',')) {
13014 sprintf(buf, "option %s", p);
13015 if(p = strstr(buf, ",")) *p = 0;
13017 SendToProgram(buf, cps);
13023 FeatureDone(cps, val)
13024 ChessProgramState* cps;
13027 DelayedEventCallback cb = GetDelayedEvent();
13028 if ((cb == InitBackEnd3 && cps == &first) ||
13029 (cb == TwoMachinesEventIfReady && cps == &second)) {
13030 CancelDelayedEvent();
13031 ScheduleDelayedEvent(cb, val ? 1 : 3600000);
13033 cps->initDone = val;
13036 /* Parse feature command from engine */
13038 ParseFeatures(args, cps)
13040 ChessProgramState *cps;
13048 while (*p == ' ') p++;
13049 if (*p == NULLCHAR) return;
13051 if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
13052 if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
13053 if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
13054 if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
13055 if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
13056 if (BoolFeature(&p, "reuse", &val, cps)) {
13057 /* Engine can disable reuse, but can't enable it if user said no */
13058 if (!val) cps->reuse = FALSE;
13061 if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
13062 if (StringFeature(&p, "myname", &cps->tidy, cps)) {
13063 if (gameMode == TwoMachinesPlay) {
13064 DisplayTwoMachinesTitle();
13070 if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
13071 if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
13072 if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
13073 if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
13074 if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
13075 if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
13076 if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
13077 if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
13078 if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
13079 if (IntFeature(&p, "done", &val, cps)) {
13080 FeatureDone(cps, val);
13083 /* Added by Tord: */
13084 if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
13085 if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
13086 /* End of additions by Tord */
13088 /* [HGM] added features: */
13089 if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
13090 if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
13091 if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
13092 if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
13093 if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
13094 if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
13095 if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
13096 if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
13097 sprintf(buf, "rejected option %s\n", cps->option[--cps->nrOptions].name);
13098 SendToProgram(buf, cps);
13101 if(cps->nrOptions >= MAX_OPTIONS) {
13103 sprintf(buf, "%s engine has too many options\n", cps->which);
13104 DisplayError(buf, 0);
13108 if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
13109 /* End of additions by HGM */
13111 /* unknown feature: complain and skip */
13113 while (*q && *q != '=') q++;
13114 sprintf(buf, "rejected %.*s\n", (int)(q-p), p);
13115 SendToProgram(buf, cps);
13121 while (*p && *p != '\"') p++;
13122 if (*p == '\"') p++;
13124 while (*p && *p != ' ') p++;
13132 PeriodicUpdatesEvent(newState)
13135 if (newState == appData.periodicUpdates)
13138 appData.periodicUpdates=newState;
13140 /* Display type changes, so update it now */
13141 // DisplayAnalysis();
13143 /* Get the ball rolling again... */
13145 AnalysisPeriodicEvent(1);
13146 StartAnalysisClock();
13151 PonderNextMoveEvent(newState)
13154 if (newState == appData.ponderNextMove) return;
13155 if (gameMode == EditPosition) EditPositionDone(TRUE);
13157 SendToProgram("hard\n", &first);
13158 if (gameMode == TwoMachinesPlay) {
13159 SendToProgram("hard\n", &second);
13162 SendToProgram("easy\n", &first);
13163 thinkOutput[0] = NULLCHAR;
13164 if (gameMode == TwoMachinesPlay) {
13165 SendToProgram("easy\n", &second);
13168 appData.ponderNextMove = newState;
13172 NewSettingEvent(option, command, value)
13178 if (gameMode == EditPosition) EditPositionDone(TRUE);
13179 sprintf(buf, "%s%s %d\n", (option ? "option ": ""), command, value);
13180 SendToProgram(buf, &first);
13181 if (gameMode == TwoMachinesPlay) {
13182 SendToProgram(buf, &second);
13187 ShowThinkingEvent()
13188 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
13190 static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
13191 int newState = appData.showThinking
13192 // [HGM] thinking: other features now need thinking output as well
13193 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
13195 if (oldState == newState) return;
13196 oldState = newState;
13197 if (gameMode == EditPosition) EditPositionDone(TRUE);
13199 SendToProgram("post\n", &first);
13200 if (gameMode == TwoMachinesPlay) {
13201 SendToProgram("post\n", &second);
13204 SendToProgram("nopost\n", &first);
13205 thinkOutput[0] = NULLCHAR;
13206 if (gameMode == TwoMachinesPlay) {
13207 SendToProgram("nopost\n", &second);
13210 // appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
13214 AskQuestionEvent(title, question, replyPrefix, which)
13215 char *title; char *question; char *replyPrefix; char *which;
13217 ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
13218 if (pr == NoProc) return;
13219 AskQuestion(title, question, replyPrefix, pr);
13223 DisplayMove(moveNumber)
13226 char message[MSG_SIZ];
13228 char cpThinkOutput[MSG_SIZ];
13230 if(appData.noGUI) return; // [HGM] fast: suppress display of moves
13232 if (moveNumber == forwardMostMove - 1 ||
13233 gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13235 safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput));
13237 if (strchr(cpThinkOutput, '\n')) {
13238 *strchr(cpThinkOutput, '\n') = NULLCHAR;
13241 *cpThinkOutput = NULLCHAR;
13244 /* [AS] Hide thinking from human user */
13245 if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
13246 *cpThinkOutput = NULLCHAR;
13247 if( thinkOutput[0] != NULLCHAR ) {
13250 for( i=0; i<=hiddenThinkOutputState; i++ ) {
13251 cpThinkOutput[i] = '.';
13253 cpThinkOutput[i] = NULLCHAR;
13254 hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
13258 if (moveNumber == forwardMostMove - 1 &&
13259 gameInfo.resultDetails != NULL) {
13260 if (gameInfo.resultDetails[0] == NULLCHAR) {
13261 sprintf(res, " %s", PGNResult(gameInfo.result));
13263 sprintf(res, " {%s} %s",
13264 gameInfo.resultDetails, PGNResult(gameInfo.result));
13270 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
13271 DisplayMessage(res, cpThinkOutput);
13273 sprintf(message, "%d.%s%s%s", moveNumber / 2 + 1,
13274 WhiteOnMove(moveNumber) ? " " : ".. ",
13275 parseList[moveNumber], res);
13276 DisplayMessage(message, cpThinkOutput);
13281 DisplayComment(moveNumber, text)
13285 char title[MSG_SIZ];
13286 char buf[8000]; // comment can be long!
13289 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
13290 strcpy(title, "Comment");
13292 sprintf(title, "Comment on %d.%s%s", moveNumber / 2 + 1,
13293 WhiteOnMove(moveNumber) ? " " : ".. ",
13294 parseList[moveNumber]);
13296 // [HGM] PV info: display PV info together with (or as) comment
13297 if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {
13298 if(text == NULL) text = "";
13299 score = pvInfoList[moveNumber].score;
13300 sprintf(buf, "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,
13301 depth, (pvInfoList[moveNumber].time+50)/100, text);
13304 if (text != NULL && (appData.autoDisplayComment || commentUp))
13305 CommentPopUp(title, text);
13308 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
13309 * might be busy thinking or pondering. It can be omitted if your
13310 * gnuchess is configured to stop thinking immediately on any user
13311 * input. However, that gnuchess feature depends on the FIONREAD
13312 * ioctl, which does not work properly on some flavors of Unix.
13316 ChessProgramState *cps;
13319 if (!cps->useSigint) return;
13320 if (appData.noChessProgram || (cps->pr == NoProc)) return;
13321 switch (gameMode) {
13322 case MachinePlaysWhite:
13323 case MachinePlaysBlack:
13324 case TwoMachinesPlay:
13325 case IcsPlayingWhite:
13326 case IcsPlayingBlack:
13329 /* Skip if we know it isn't thinking */
13330 if (!cps->maybeThinking) return;
13331 if (appData.debugMode)
13332 fprintf(debugFP, "Interrupting %s\n", cps->which);
13333 InterruptChildProcess(cps->pr);
13334 cps->maybeThinking = FALSE;
13339 #endif /*ATTENTION*/
13345 if (whiteTimeRemaining <= 0) {
13348 if (appData.icsActive) {
13349 if (appData.autoCallFlag &&
13350 gameMode == IcsPlayingBlack && !blackFlag) {
13351 SendToICS(ics_prefix);
13352 SendToICS("flag\n");
13356 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
13358 if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
13359 if (appData.autoCallFlag) {
13360 GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
13367 if (blackTimeRemaining <= 0) {
13370 if (appData.icsActive) {
13371 if (appData.autoCallFlag &&
13372 gameMode == IcsPlayingWhite && !whiteFlag) {
13373 SendToICS(ics_prefix);
13374 SendToICS("flag\n");
13378 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
13380 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
13381 if (appData.autoCallFlag) {
13382 GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
13395 if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
13396 gameMode == PlayFromGameFile || forwardMostMove == 0) return;
13399 * add time to clocks when time control is achieved ([HGM] now also used for increment)
13401 if ( !WhiteOnMove(forwardMostMove) )
13402 /* White made time control */
13403 whiteTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
13404 /* [HGM] time odds: correct new time quota for time odds! */
13405 / WhitePlayer()->timeOdds;
13407 /* Black made time control */
13408 blackTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
13409 / WhitePlayer()->other->timeOdds;
13413 DisplayBothClocks()
13415 int wom = gameMode == EditPosition ?
13416 !blackPlaysFirst : WhiteOnMove(currentMove);
13417 DisplayWhiteClock(whiteTimeRemaining, wom);
13418 DisplayBlackClock(blackTimeRemaining, !wom);
13422 /* Timekeeping seems to be a portability nightmare. I think everyone
13423 has ftime(), but I'm really not sure, so I'm including some ifdefs
13424 to use other calls if you don't. Clocks will be less accurate if
13425 you have neither ftime nor gettimeofday.
13428 /* VS 2008 requires the #include outside of the function */
13429 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
13430 #include <sys/timeb.h>
13433 /* Get the current time as a TimeMark */
13438 #if HAVE_GETTIMEOFDAY
13440 struct timeval timeVal;
13441 struct timezone timeZone;
13443 gettimeofday(&timeVal, &timeZone);
13444 tm->sec = (long) timeVal.tv_sec;
13445 tm->ms = (int) (timeVal.tv_usec / 1000L);
13447 #else /*!HAVE_GETTIMEOFDAY*/
13450 // include <sys/timeb.h> / moved to just above start of function
13451 struct timeb timeB;
13454 tm->sec = (long) timeB.time;
13455 tm->ms = (int) timeB.millitm;
13457 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
13458 tm->sec = (long) time(NULL);
13464 /* Return the difference in milliseconds between two
13465 time marks. We assume the difference will fit in a long!
13468 SubtractTimeMarks(tm2, tm1)
13469 TimeMark *tm2, *tm1;
13471 return 1000L*(tm2->sec - tm1->sec) +
13472 (long) (tm2->ms - tm1->ms);
13477 * Code to manage the game clocks.
13479 * In tournament play, black starts the clock and then white makes a move.
13480 * We give the human user a slight advantage if he is playing white---the
13481 * clocks don't run until he makes his first move, so it takes zero time.
13482 * Also, we don't account for network lag, so we could get out of sync
13483 * with GNU Chess's clock -- but then, referees are always right.
13486 static TimeMark tickStartTM;
13487 static long intendedTickLength;
13490 NextTickLength(timeRemaining)
13491 long timeRemaining;
13493 long nominalTickLength, nextTickLength;
13495 if (timeRemaining > 0L && timeRemaining <= 10000L)
13496 nominalTickLength = 100L;
13498 nominalTickLength = 1000L;
13499 nextTickLength = timeRemaining % nominalTickLength;
13500 if (nextTickLength <= 0) nextTickLength += nominalTickLength;
13502 return nextTickLength;
13505 /* Adjust clock one minute up or down */
13507 AdjustClock(Boolean which, int dir)
13509 if(which) blackTimeRemaining += 60000*dir;
13510 else whiteTimeRemaining += 60000*dir;
13511 DisplayBothClocks();
13514 /* Stop clocks and reset to a fresh time control */
13518 (void) StopClockTimer();
13519 if (appData.icsActive) {
13520 whiteTimeRemaining = blackTimeRemaining = 0;
13521 } else if (searchTime) {
13522 whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
13523 blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
13524 } else { /* [HGM] correct new time quote for time odds */
13525 whiteTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->timeOdds;
13526 blackTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->other->timeOdds;
13528 if (whiteFlag || blackFlag) {
13530 whiteFlag = blackFlag = FALSE;
13532 DisplayBothClocks();
13535 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
13537 /* Decrement running clock by amount of time that has passed */
13541 long timeRemaining;
13542 long lastTickLength, fudge;
13545 if (!appData.clockMode) return;
13546 if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
13550 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13552 /* Fudge if we woke up a little too soon */
13553 fudge = intendedTickLength - lastTickLength;
13554 if (fudge < 0 || fudge > FUDGE) fudge = 0;
13556 if (WhiteOnMove(forwardMostMove)) {
13557 if(whiteNPS >= 0) lastTickLength = 0;
13558 timeRemaining = whiteTimeRemaining -= lastTickLength;
13559 DisplayWhiteClock(whiteTimeRemaining - fudge,
13560 WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
13562 if(blackNPS >= 0) lastTickLength = 0;
13563 timeRemaining = blackTimeRemaining -= lastTickLength;
13564 DisplayBlackClock(blackTimeRemaining - fudge,
13565 !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
13568 if (CheckFlags()) return;
13571 intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
13572 StartClockTimer(intendedTickLength);
13574 /* if the time remaining has fallen below the alarm threshold, sound the
13575 * alarm. if the alarm has sounded and (due to a takeback or time control
13576 * with increment) the time remaining has increased to a level above the
13577 * threshold, reset the alarm so it can sound again.
13580 if (appData.icsActive && appData.icsAlarm) {
13582 /* make sure we are dealing with the user's clock */
13583 if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
13584 ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
13587 if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
13588 alarmSounded = FALSE;
13589 } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
13591 alarmSounded = TRUE;
13597 /* A player has just moved, so stop the previously running
13598 clock and (if in clock mode) start the other one.
13599 We redisplay both clocks in case we're in ICS mode, because
13600 ICS gives us an update to both clocks after every move.
13601 Note that this routine is called *after* forwardMostMove
13602 is updated, so the last fractional tick must be subtracted
13603 from the color that is *not* on move now.
13608 long lastTickLength;
13610 int flagged = FALSE;
13614 if (StopClockTimer() && appData.clockMode) {
13615 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13616 if (WhiteOnMove(forwardMostMove)) {
13617 if(blackNPS >= 0) lastTickLength = 0;
13618 blackTimeRemaining -= lastTickLength;
13619 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
13620 // if(pvInfoList[forwardMostMove-1].time == -1)
13621 pvInfoList[forwardMostMove-1].time = // use GUI time
13622 (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
13624 if(whiteNPS >= 0) lastTickLength = 0;
13625 whiteTimeRemaining -= lastTickLength;
13626 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
13627 // if(pvInfoList[forwardMostMove-1].time == -1)
13628 pvInfoList[forwardMostMove-1].time =
13629 (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
13631 flagged = CheckFlags();
13633 CheckTimeControl();
13635 if (flagged || !appData.clockMode) return;
13637 switch (gameMode) {
13638 case MachinePlaysBlack:
13639 case MachinePlaysWhite:
13640 case BeginningOfGame:
13641 if (pausing) return;
13645 case PlayFromGameFile:
13653 if (searchTime) { // [HGM] st: set clock of player that has to move to max time
13654 if(WhiteOnMove(forwardMostMove))
13655 whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
13656 else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
13660 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
13661 whiteTimeRemaining : blackTimeRemaining);
13662 StartClockTimer(intendedTickLength);
13666 /* Stop both clocks */
13670 long lastTickLength;
13673 if (!StopClockTimer()) return;
13674 if (!appData.clockMode) return;
13678 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13679 if (WhiteOnMove(forwardMostMove)) {
13680 if(whiteNPS >= 0) lastTickLength = 0;
13681 whiteTimeRemaining -= lastTickLength;
13682 DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
13684 if(blackNPS >= 0) lastTickLength = 0;
13685 blackTimeRemaining -= lastTickLength;
13686 DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
13691 /* Start clock of player on move. Time may have been reset, so
13692 if clock is already running, stop and restart it. */
13696 (void) StopClockTimer(); /* in case it was running already */
13697 DisplayBothClocks();
13698 if (CheckFlags()) return;
13700 if (!appData.clockMode) return;
13701 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
13703 GetTimeMark(&tickStartTM);
13704 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
13705 whiteTimeRemaining : blackTimeRemaining);
13707 /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
13708 whiteNPS = blackNPS = -1;
13709 if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
13710 || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
13711 whiteNPS = first.nps;
13712 if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
13713 || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
13714 blackNPS = first.nps;
13715 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
13716 whiteNPS = second.nps;
13717 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
13718 blackNPS = second.nps;
13719 if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
13721 StartClockTimer(intendedTickLength);
13728 long second, minute, hour, day;
13730 static char buf[32];
13732 if (ms > 0 && ms <= 9900) {
13733 /* convert milliseconds to tenths, rounding up */
13734 double tenths = floor( ((double)(ms + 99L)) / 100.00 );
13736 sprintf(buf, " %03.1f ", tenths/10.0);
13740 /* convert milliseconds to seconds, rounding up */
13741 /* use floating point to avoid strangeness of integer division
13742 with negative dividends on many machines */
13743 second = (long) floor(((double) (ms + 999L)) / 1000.0);
13750 day = second / (60 * 60 * 24);
13751 second = second % (60 * 60 * 24);
13752 hour = second / (60 * 60);
13753 second = second % (60 * 60);
13754 minute = second / 60;
13755 second = second % 60;
13758 sprintf(buf, " %s%ld:%02ld:%02ld:%02ld ",
13759 sign, day, hour, minute, second);
13761 sprintf(buf, " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
13763 sprintf(buf, " %s%2ld:%02ld ", sign, minute, second);
13770 * This is necessary because some C libraries aren't ANSI C compliant yet.
13773 StrStr(string, match)
13774 char *string, *match;
13778 length = strlen(match);
13780 for (i = strlen(string) - length; i >= 0; i--, string++)
13781 if (!strncmp(match, string, length))
13788 StrCaseStr(string, match)
13789 char *string, *match;
13793 length = strlen(match);
13795 for (i = strlen(string) - length; i >= 0; i--, string++) {
13796 for (j = 0; j < length; j++) {
13797 if (ToLower(match[j]) != ToLower(string[j]))
13800 if (j == length) return string;
13814 c1 = ToLower(*s1++);
13815 c2 = ToLower(*s2++);
13816 if (c1 > c2) return 1;
13817 if (c1 < c2) return -1;
13818 if (c1 == NULLCHAR) return 0;
13827 return isupper(c) ? tolower(c) : c;
13835 return islower(c) ? toupper(c) : c;
13837 #endif /* !_amigados */
13845 if ((ret = (char *) malloc(strlen(s) + 1))) {
13852 StrSavePtr(s, savePtr)
13853 char *s, **savePtr;
13858 if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
13859 strcpy(*savePtr, s);
13871 clock = time((time_t *)NULL);
13872 tm = localtime(&clock);
13873 sprintf(buf, "%04d.%02d.%02d",
13874 tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
13875 return StrSave(buf);
13880 PositionToFEN(move, overrideCastling)
13882 char *overrideCastling;
13884 int i, j, fromX, fromY, toX, toY;
13891 whiteToPlay = (gameMode == EditPosition) ?
13892 !blackPlaysFirst : (move % 2 == 0);
13895 /* Piece placement data */
13896 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13898 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
13899 if (boards[move][i][j] == EmptySquare) {
13901 } else { ChessSquare piece = boards[move][i][j];
13902 if (emptycount > 0) {
13903 if(emptycount<10) /* [HGM] can be >= 10 */
13904 *p++ = '0' + emptycount;
13905 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
13908 if(PieceToChar(piece) == '+') {
13909 /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
13911 piece = (ChessSquare)(DEMOTED piece);
13913 *p++ = PieceToChar(piece);
13915 /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
13916 p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
13921 if (emptycount > 0) {
13922 if(emptycount<10) /* [HGM] can be >= 10 */
13923 *p++ = '0' + emptycount;
13924 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
13931 /* [HGM] print Crazyhouse or Shogi holdings */
13932 if( gameInfo.holdingsWidth ) {
13933 *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
13935 for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
13936 piece = boards[move][i][BOARD_WIDTH-1];
13937 if( piece != EmptySquare )
13938 for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
13939 *p++ = PieceToChar(piece);
13941 for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
13942 piece = boards[move][BOARD_HEIGHT-i-1][0];
13943 if( piece != EmptySquare )
13944 for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
13945 *p++ = PieceToChar(piece);
13948 if( q == p ) *p++ = '-';
13954 *p++ = whiteToPlay ? 'w' : 'b';
13957 if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
13958 while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
13960 if(nrCastlingRights) {
13962 if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
13963 /* [HGM] write directly from rights */
13964 if(boards[move][CASTLING][2] != NoRights &&
13965 boards[move][CASTLING][0] != NoRights )
13966 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
13967 if(boards[move][CASTLING][2] != NoRights &&
13968 boards[move][CASTLING][1] != NoRights )
13969 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
13970 if(boards[move][CASTLING][5] != NoRights &&
13971 boards[move][CASTLING][3] != NoRights )
13972 *p++ = boards[move][CASTLING][3] + AAA;
13973 if(boards[move][CASTLING][5] != NoRights &&
13974 boards[move][CASTLING][4] != NoRights )
13975 *p++ = boards[move][CASTLING][4] + AAA;
13978 /* [HGM] write true castling rights */
13979 if( nrCastlingRights == 6 ) {
13980 if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
13981 boards[move][CASTLING][2] != NoRights ) *p++ = 'K';
13982 if(boards[move][CASTLING][1] == BOARD_LEFT &&
13983 boards[move][CASTLING][2] != NoRights ) *p++ = 'Q';
13984 if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
13985 boards[move][CASTLING][5] != NoRights ) *p++ = 'k';
13986 if(boards[move][CASTLING][4] == BOARD_LEFT &&
13987 boards[move][CASTLING][5] != NoRights ) *p++ = 'q';
13990 if (q == p) *p++ = '-'; /* No castling rights */
13994 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
13995 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
13996 /* En passant target square */
13997 if (move > backwardMostMove) {
13998 fromX = moveList[move - 1][0] - AAA;
13999 fromY = moveList[move - 1][1] - ONE;
14000 toX = moveList[move - 1][2] - AAA;
14001 toY = moveList[move - 1][3] - ONE;
14002 if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
14003 toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
14004 boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
14006 /* 2-square pawn move just happened */
14008 *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
14012 } else if(move == backwardMostMove) {
14013 // [HGM] perhaps we should always do it like this, and forget the above?
14014 if((signed char)boards[move][EP_STATUS] >= 0) {
14015 *p++ = boards[move][EP_STATUS] + AAA;
14016 *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
14027 /* [HGM] find reversible plies */
14028 { int i = 0, j=move;
14030 if (appData.debugMode) { int k;
14031 fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
14032 for(k=backwardMostMove; k<=forwardMostMove; k++)
14033 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
14037 while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
14038 if( j == backwardMostMove ) i += initialRulePlies;
14039 sprintf(p, "%d ", i);
14040 p += i>=100 ? 4 : i >= 10 ? 3 : 2;
14042 /* Fullmove number */
14043 sprintf(p, "%d", (move / 2) + 1);
14045 return StrSave(buf);
14049 ParseFEN(board, blackPlaysFirst, fen)
14051 int *blackPlaysFirst;
14061 /* [HGM] by default clear Crazyhouse holdings, if present */
14062 if(gameInfo.holdingsWidth) {
14063 for(i=0; i<BOARD_HEIGHT; i++) {
14064 board[i][0] = EmptySquare; /* black holdings */
14065 board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
14066 board[i][1] = (ChessSquare) 0; /* black counts */
14067 board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
14071 /* Piece placement data */
14072 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
14075 if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
14076 if (*p == '/') p++;
14077 emptycount = gameInfo.boardWidth - j;
14078 while (emptycount--)
14079 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
14081 #if(BOARD_FILES >= 10)
14082 } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
14083 p++; emptycount=10;
14084 if (j + emptycount > gameInfo.boardWidth) return FALSE;
14085 while (emptycount--)
14086 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
14088 } else if (isdigit(*p)) {
14089 emptycount = *p++ - '0';
14090 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
14091 if (j + emptycount > gameInfo.boardWidth) return FALSE;
14092 while (emptycount--)
14093 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
14094 } else if (*p == '+' || isalpha(*p)) {
14095 if (j >= gameInfo.boardWidth) return FALSE;
14097 piece = CharToPiece(*++p);
14098 if(piece == EmptySquare) return FALSE; /* unknown piece */
14099 piece = (ChessSquare) (PROMOTED piece ); p++;
14100 if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
14101 } else piece = CharToPiece(*p++);
14103 if(piece==EmptySquare) return FALSE; /* unknown piece */
14104 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
14105 piece = (ChessSquare) (PROMOTED piece);
14106 if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
14109 board[i][(j++)+gameInfo.holdingsWidth] = piece;
14115 while (*p == '/' || *p == ' ') p++;
14117 /* [HGM] look for Crazyhouse holdings here */
14118 while(*p==' ') p++;
14119 if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
14121 if(*p == '-' ) *p++; /* empty holdings */ else {
14122 if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
14123 /* if we would allow FEN reading to set board size, we would */
14124 /* have to add holdings and shift the board read so far here */
14125 while( (piece = CharToPiece(*p) ) != EmptySquare ) {
14127 if((int) piece >= (int) BlackPawn ) {
14128 i = (int)piece - (int)BlackPawn;
14129 i = PieceToNumber((ChessSquare)i);
14130 if( i >= gameInfo.holdingsSize ) return FALSE;
14131 board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
14132 board[BOARD_HEIGHT-1-i][1]++; /* black counts */
14134 i = (int)piece - (int)WhitePawn;
14135 i = PieceToNumber((ChessSquare)i);
14136 if( i >= gameInfo.holdingsSize ) return FALSE;
14137 board[i][BOARD_WIDTH-1] = piece; /* white holdings */
14138 board[i][BOARD_WIDTH-2]++; /* black holdings */
14142 if(*p == ']') *p++;
14145 while(*p == ' ') p++;
14150 *blackPlaysFirst = FALSE;
14153 *blackPlaysFirst = TRUE;
14159 /* [HGM] We NO LONGER ignore the rest of the FEN notation */
14160 /* return the extra info in global variiables */
14162 /* set defaults in case FEN is incomplete */
14163 board[EP_STATUS] = EP_UNKNOWN;
14164 for(i=0; i<nrCastlingRights; i++ ) {
14165 board[CASTLING][i] =
14166 gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
14167 } /* assume possible unless obviously impossible */
14168 if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
14169 if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
14170 if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
14171 && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
14172 if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
14173 if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
14174 if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
14175 && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
14178 while(*p==' ') p++;
14179 if(nrCastlingRights) {
14180 if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
14181 /* castling indicator present, so default becomes no castlings */
14182 for(i=0; i<nrCastlingRights; i++ ) {
14183 board[CASTLING][i] = NoRights;
14186 while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
14187 (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
14188 ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
14189 ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth) ) {
14190 char c = *p++; int whiteKingFile=NoRights, blackKingFile=NoRights;
14192 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
14193 if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
14194 if(board[0 ][i] == WhiteKing) whiteKingFile = i;
14196 if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
14197 whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
14198 if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
14199 && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
14200 if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
14201 && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
14204 for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
14205 board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
14206 board[CASTLING][2] = whiteKingFile;
14209 for(i=BOARD_LEFT; i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
14210 board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
14211 board[CASTLING][2] = whiteKingFile;
14214 for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
14215 board[CASTLING][3] = i != blackKingFile ? i : NoRights;
14216 board[CASTLING][5] = blackKingFile;
14219 for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
14220 board[CASTLING][4] = i != blackKingFile ? i : NoRights;
14221 board[CASTLING][5] = blackKingFile;
14224 default: /* FRC castlings */
14225 if(c >= 'a') { /* black rights */
14226 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
14227 if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
14228 if(i == BOARD_RGHT) break;
14229 board[CASTLING][5] = i;
14231 if(board[BOARD_HEIGHT-1][c] < BlackPawn ||
14232 board[BOARD_HEIGHT-1][c] >= BlackKing ) break;
14234 board[CASTLING][3] = c;
14236 board[CASTLING][4] = c;
14237 } else { /* white rights */
14238 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
14239 if(board[0][i] == WhiteKing) break;
14240 if(i == BOARD_RGHT) break;
14241 board[CASTLING][2] = i;
14242 c -= AAA - 'a' + 'A';
14243 if(board[0][c] >= WhiteKing) break;
14245 board[CASTLING][0] = c;
14247 board[CASTLING][1] = c;
14251 for(i=0; i<nrCastlingRights; i++)
14252 if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
14253 if (appData.debugMode) {
14254 fprintf(debugFP, "FEN castling rights:");
14255 for(i=0; i<nrCastlingRights; i++)
14256 fprintf(debugFP, " %d", board[CASTLING][i]);
14257 fprintf(debugFP, "\n");
14260 while(*p==' ') p++;
14263 /* read e.p. field in games that know e.p. capture */
14264 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
14265 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
14267 p++; board[EP_STATUS] = EP_NONE;
14269 char c = *p++ - AAA;
14271 if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
14272 if(*p >= '0' && *p <='9') *p++;
14273 board[EP_STATUS] = c;
14278 if(sscanf(p, "%d", &i) == 1) {
14279 FENrulePlies = i; /* 50-move ply counter */
14280 /* (The move number is still ignored) */
14287 EditPositionPasteFEN(char *fen)
14290 Board initial_position;
14292 if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
14293 DisplayError(_("Bad FEN position in clipboard"), 0);
14296 int savedBlackPlaysFirst = blackPlaysFirst;
14297 EditPositionEvent();
14298 blackPlaysFirst = savedBlackPlaysFirst;
14299 CopyBoard(boards[0], initial_position);
14300 initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
14301 EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
14302 DisplayBothClocks();
14303 DrawPosition(FALSE, boards[currentMove]);
14308 static char cseq[12] = "\\ ";
14310 Boolean set_cont_sequence(char *new_seq)
14315 // handle bad attempts to set the sequence
14317 return 0; // acceptable error - no debug
14319 len = strlen(new_seq);
14320 ret = (len > 0) && (len < sizeof(cseq));
14322 strcpy(cseq, new_seq);
14323 else if (appData.debugMode)
14324 fprintf(debugFP, "Invalid continuation sequence \"%s\" (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
14329 reformat a source message so words don't cross the width boundary. internal
14330 newlines are not removed. returns the wrapped size (no null character unless
14331 included in source message). If dest is NULL, only calculate the size required
14332 for the dest buffer. lp argument indicats line position upon entry, and it's
14333 passed back upon exit.
14335 int wrap(char *dest, char *src, int count, int width, int *lp)
14337 int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
14339 cseq_len = strlen(cseq);
14340 old_line = line = *lp;
14341 ansi = len = clen = 0;
14343 for (i=0; i < count; i++)
14345 if (src[i] == '\033')
14348 // if we hit the width, back up
14349 if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
14351 // store i & len in case the word is too long
14352 old_i = i, old_len = len;
14354 // find the end of the last word
14355 while (i && src[i] != ' ' && src[i] != '\n')
14361 // word too long? restore i & len before splitting it
14362 if ((old_i-i+clen) >= width)
14369 if (i && src[i-1] == ' ')
14372 if (src[i] != ' ' && src[i] != '\n')
14379 // now append the newline and continuation sequence
14384 strncpy(dest+len, cseq, cseq_len);
14392 dest[len] = src[i];
14396 if (src[i] == '\n')
14401 if (dest && appData.debugMode)
14403 fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
14404 count, width, line, len, *lp);
14405 show_bytes(debugFP, src, count);
14406 fprintf(debugFP, "\ndest: ");
14407 show_bytes(debugFP, dest, len);
14408 fprintf(debugFP, "\n");
14410 *lp = dest ? line : old_line;
14415 // [HGM] vari: routines for shelving variations
14418 PushTail(int firstMove, int lastMove)
14420 int i, j, nrMoves = lastMove - firstMove;
14422 if(appData.icsActive) { // only in local mode
14423 forwardMostMove = currentMove; // mimic old ICS behavior
14426 if(storedGames >= MAX_VARIATIONS-1) return;
14428 // push current tail of game on stack
14429 savedResult[storedGames] = gameInfo.result;
14430 savedDetails[storedGames] = gameInfo.resultDetails;
14431 gameInfo.resultDetails = NULL;
14432 savedFirst[storedGames] = firstMove;
14433 savedLast [storedGames] = lastMove;
14434 savedFramePtr[storedGames] = framePtr;
14435 framePtr -= nrMoves; // reserve space for the boards
14436 for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
14437 CopyBoard(boards[framePtr+i], boards[firstMove+i]);
14438 for(j=0; j<MOVE_LEN; j++)
14439 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
14440 for(j=0; j<2*MOVE_LEN; j++)
14441 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
14442 timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
14443 timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
14444 pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
14445 pvInfoList[firstMove+i-1].depth = 0;
14446 commentList[framePtr+i] = commentList[firstMove+i];
14447 commentList[firstMove+i] = NULL;
14451 forwardMostMove = currentMove; // truncte game so we can start variation
14452 if(storedGames == 1) GreyRevert(FALSE);
14456 PopTail(Boolean annotate)
14459 char buf[8000], moveBuf[20];
14461 if(appData.icsActive) return FALSE; // only in local mode
14462 if(!storedGames) return FALSE; // sanity
14465 ToNrEvent(savedFirst[storedGames]); // sets currentMove
14466 nrMoves = savedLast[storedGames] - currentMove;
14469 if(!WhiteOnMove(currentMove)) sprintf(buf, "(%d...", currentMove+2>>1);
14470 else strcpy(buf, "(");
14471 for(i=currentMove; i<forwardMostMove; i++) {
14473 sprintf(moveBuf, " %d. %s", i+2>>1, SavePart(parseList[i]));
14474 else sprintf(moveBuf, " %s", SavePart(parseList[i]));
14475 strcat(buf, moveBuf);
14476 if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
14480 for(i=1; i<nrMoves; i++) { // copy last variation back
14481 CopyBoard(boards[currentMove+i], boards[framePtr+i]);
14482 for(j=0; j<MOVE_LEN; j++)
14483 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
14484 for(j=0; j<2*MOVE_LEN; j++)
14485 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
14486 timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
14487 timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
14488 pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
14489 if(commentList[currentMove+i]) free(commentList[currentMove+i]);
14490 commentList[currentMove+i] = commentList[framePtr+i];
14491 commentList[framePtr+i] = NULL;
14493 if(annotate) AppendComment(currentMove+1, buf, FALSE);
14494 framePtr = savedFramePtr[storedGames];
14495 gameInfo.result = savedResult[storedGames];
14496 if(gameInfo.resultDetails != NULL) {
14497 free(gameInfo.resultDetails);
14499 gameInfo.resultDetails = savedDetails[storedGames];
14500 forwardMostMove = currentMove + nrMoves;
14501 if(storedGames == 0) GreyRevert(TRUE);
14507 { // remove all shelved variations
14509 for(i=0; i<storedGames; i++) {
14510 if(savedDetails[i])
14511 free(savedDetails[i]);
14512 savedDetails[i] = NULL;
14514 for(i=framePtr; i<MAX_MOVES; i++) {
14515 if(commentList[i]) free(commentList[i]);
14516 commentList[i] = NULL;
14518 framePtr = MAX_MOVES-1;