2 * backend.c -- Common back end for X and Windows NT versions of
4 * Copyright 1991 by Digital Equipment Corporation, Maynard,
7 * Enhancements Copyright 1992-2001, 2002, 2003, 2004, 2005, 2006,
8 * 2007, 2008, 2009 Free Software Foundation, Inc.
10 * Enhancements Copyright 2005 Alessandro Scotti
12 * The following terms apply to Digital Equipment Corporation's copyright
14 * ------------------------------------------------------------------------
17 * Permission to use, copy, modify, and distribute this software and its
18 * documentation for any purpose and without fee is hereby granted,
19 * provided that the above copyright notice appear in all copies and that
20 * both that copyright notice and this permission notice appear in
21 * supporting documentation, and that the name of Digital not be
22 * used in advertising or publicity pertaining to distribution of the
23 * software without specific, written prior permission.
25 * DIGITAL DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
26 * ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL
27 * DIGITAL BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR
28 * ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
29 * WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
30 * ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
32 * ------------------------------------------------------------------------
34 * The following terms apply to the enhanced version of XBoard
35 * distributed by the Free Software Foundation:
36 * ------------------------------------------------------------------------
38 * GNU XBoard is free software: you can redistribute it and/or modify
39 * it under the terms of the GNU General Public License as published by
40 * the Free Software Foundation, either version 3 of the License, or (at
41 * your option) any later version.
43 * GNU XBoard is distributed in the hope that it will be useful, but
44 * WITHOUT ANY WARRANTY; without even the implied warranty of
45 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
46 * General Public License for more details.
48 * You should have received a copy of the GNU General Public License
49 * along with this program. If not, see http://www.gnu.org/licenses/. *
51 *------------------------------------------------------------------------
52 ** See the file ChangeLog for a revision history. */
54 /* [AS] Also useful here for debugging */
58 #define DoSleep( n ) if( (n) != 0 ) Sleep( (n) );
62 #define DoSleep( n ) if( (n) >= 0) sleep(n)
72 #include <sys/types.h>
81 #else /* not STDC_HEADERS */
84 # else /* not HAVE_STRING_H */
86 # endif /* not HAVE_STRING_H */
87 #endif /* not STDC_HEADERS */
90 # include <sys/fcntl.h>
91 #else /* not HAVE_SYS_FCNTL_H */
94 # endif /* HAVE_FCNTL_H */
95 #endif /* not HAVE_SYS_FCNTL_H */
97 #if TIME_WITH_SYS_TIME
98 # include <sys/time.h>
102 # include <sys/time.h>
108 #if defined(_amigados) && !defined(__GNUC__)
113 extern int gettimeofday(struct timeval *, struct timezone *);
121 #include "frontend.h"
128 #include "backendz.h"
132 # define _(s) gettext (s)
133 # define N_(s) gettext_noop (s)
140 /* A point in time */
142 long sec; /* Assuming this is >= 32 bits */
143 int ms; /* Assuming this is >= 16 bits */
146 int establish P((void));
147 void read_from_player P((InputSourceRef isr, VOIDSTAR closure,
148 char *buf, int count, int error));
149 void read_from_ics P((InputSourceRef isr, VOIDSTAR closure,
150 char *buf, int count, int error));
151 void ics_printf P((char *format, ...));
152 void SendToICS P((char *s));
153 void SendToICSDelayed P((char *s, long msdelay));
154 void SendMoveToICS P((ChessMove moveType, int fromX, int fromY,
156 void HandleMachineMove P((char *message, ChessProgramState *cps));
157 int AutoPlayOneMove P((void));
158 int LoadGameOneMove P((ChessMove readAhead));
159 int LoadGameFromFile P((char *filename, int n, char *title, int useList));
160 int LoadPositionFromFile P((char *filename, int n, char *title));
161 int SavePositionToFile P((char *filename));
162 void ApplyMove P((int fromX, int fromY, int toX, int toY, int promoChar,
164 void MakeMove P((int fromX, int fromY, int toX, int toY, int promoChar));
165 void ShowMove P((int fromX, int fromY, int toX, int toY));
166 int FinishMove P((ChessMove moveType, int fromX, int fromY, int toX, int toY,
167 /*char*/int promoChar));
168 void BackwardInner P((int target));
169 void ForwardInner P((int target));
170 int Adjudicate P((ChessProgramState *cps));
171 void GameEnds P((ChessMove result, char *resultDetails, int whosays));
172 void EditPositionDone P((Boolean fakeRights));
173 void PrintOpponents P((FILE *fp));
174 void PrintPosition P((FILE *fp, int move));
175 void StartChessProgram P((ChessProgramState *cps));
176 void SendToProgram P((char *message, ChessProgramState *cps));
177 void SendMoveToProgram P((int moveNum, ChessProgramState *cps));
178 void ReceiveFromProgram P((InputSourceRef isr, VOIDSTAR closure,
179 char *buf, int count, int error));
180 void SendTimeControl P((ChessProgramState *cps,
181 int mps, long tc, int inc, int sd, int st));
182 char *TimeControlTagValue P((void));
183 void Attention P((ChessProgramState *cps));
184 void FeedMovesToProgram P((ChessProgramState *cps, int upto));
185 void ResurrectChessProgram P((void));
186 void DisplayComment P((int moveNumber, char *text));
187 void DisplayMove P((int moveNumber));
189 void ParseGameHistory P((char *game));
190 void ParseBoard12 P((char *string));
191 void KeepAlive P((void));
192 void StartClocks P((void));
193 void SwitchClocks P((void));
194 void StopClocks P((void));
195 void ResetClocks P((void));
196 char *PGNDate P((void));
197 void SetGameInfo P((void));
198 Boolean ParseFEN P((Board board, int *blackPlaysFirst, char *fen));
199 int RegisterMove P((void));
200 void MakeRegisteredMove P((void));
201 void TruncateGame P((void));
202 int looking_at P((char *, int *, char *));
203 void CopyPlayerNameIntoFileName P((char **, char *));
204 char *SavePart P((char *));
205 int SaveGameOldStyle P((FILE *));
206 int SaveGamePGN P((FILE *));
207 void GetTimeMark P((TimeMark *));
208 long SubtractTimeMarks P((TimeMark *, TimeMark *));
209 int CheckFlags P((void));
210 long NextTickLength P((long));
211 void CheckTimeControl P((void));
212 void show_bytes P((FILE *, char *, int));
213 int string_to_rating P((char *str));
214 void ParseFeatures P((char* args, ChessProgramState *cps));
215 void InitBackEnd3 P((void));
216 void FeatureDone P((ChessProgramState* cps, int val));
217 void InitChessProgram P((ChessProgramState *cps, int setup));
218 void OutputKibitz(int window, char *text);
219 int PerpetualChase(int first, int last);
220 int EngineOutputIsUp();
221 void InitDrawingSizes(int x, int y);
224 extern void ConsoleCreate();
227 ChessProgramState *WhitePlayer();
228 void InsertIntoMemo P((int which, char *text)); // [HGM] kibitz: in engineo.c
229 int VerifyDisplayMode P(());
231 char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment
232 void InitEngineUCI( const char * iniDir, ChessProgramState * cps ); // [HGM] moved here from winboard.c
233 char *ProbeBook P((int moveNr, char *book)); // [HGM] book: returns a book move
234 char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book
235 void ics_update_width P((int new_width));
236 extern char installDir[MSG_SIZ];
238 extern int tinyLayout, smallLayout;
239 ChessProgramStats programStats;
240 char lastPV[2][2*MSG_SIZ]; /* [HGM] pv: last PV in thinking output of each engine */
242 static int exiting = 0; /* [HGM] moved to top */
243 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
244 int startedFromPositionFile = FALSE; Board filePosition; /* [HGM] loadPos */
245 char endingGame = 0; /* [HGM] crash: flag to prevent recursion of GameEnds() */
246 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS */
247 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
248 int lastIndex = 0; /* [HGM] autoinc: last game/position used in match mode */
249 Boolean connectionAlive;/* [HGM] alive: ICS connection status from probing */
250 int opponentKibitzes;
251 int lastSavedGame; /* [HGM] save: ID of game */
252 char chatPartner[MAX_CHAT][MSG_SIZ]; /* [HGM] chat: list of chatting partners */
253 extern int chatCount;
255 char marker[BOARD_RANKS][BOARD_FILES]; /* [HGM] marks for target squares */
257 /* States for ics_getting_history */
259 #define H_REQUESTED 1
260 #define H_GOT_REQ_HEADER 2
261 #define H_GOT_UNREQ_HEADER 3
262 #define H_GETTING_MOVES 4
263 #define H_GOT_UNWANTED_HEADER 5
265 /* whosays values for GameEnds */
274 /* Maximum number of games in a cmail message */
275 #define CMAIL_MAX_GAMES 20
277 /* Different types of move when calling RegisterMove */
279 #define CMAIL_RESIGN 1
281 #define CMAIL_ACCEPT 3
283 /* Different types of result to remember for each game */
284 #define CMAIL_NOT_RESULT 0
285 #define CMAIL_OLD_RESULT 1
286 #define CMAIL_NEW_RESULT 2
288 /* Telnet protocol constants */
299 static char * safeStrCpy( char * dst, const char * src, size_t count )
301 assert( dst != NULL );
302 assert( src != NULL );
305 strncpy( dst, src, count );
306 dst[ count-1 ] = '\0';
310 /* Some compiler can't cast u64 to double
311 * This function do the job for us:
313 * We use the highest bit for cast, this only
314 * works if the highest bit is not
315 * in use (This should not happen)
317 * We used this for all compiler
320 u64ToDouble(u64 value)
323 u64 tmp = value & u64Const(0x7fffffffffffffff);
324 r = (double)(s64)tmp;
325 if (value & u64Const(0x8000000000000000))
326 r += 9.2233720368547758080e18; /* 2^63 */
330 /* Fake up flags for now, as we aren't keeping track of castling
331 availability yet. [HGM] Change of logic: the flag now only
332 indicates the type of castlings allowed by the rule of the game.
333 The actual rights themselves are maintained in the array
334 castlingRights, as part of the game history, and are not probed
340 int flags = F_ALL_CASTLE_OK;
341 if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
342 switch (gameInfo.variant) {
344 flags &= ~F_ALL_CASTLE_OK;
345 case VariantGiveaway: // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
346 flags |= F_IGNORE_CHECK;
348 flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
351 flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
353 case VariantKriegspiel:
354 flags |= F_KRIEGSPIEL_CAPTURE;
356 case VariantCapaRandom:
357 case VariantFischeRandom:
358 flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
359 case VariantNoCastle:
360 case VariantShatranj:
363 flags &= ~F_ALL_CASTLE_OK;
371 FILE *gameFileFP, *debugFP;
374 [AS] Note: sometimes, the sscanf() function is used to parse the input
375 into a fixed-size buffer. Because of this, we must be prepared to
376 receive strings as long as the size of the input buffer, which is currently
377 set to 4K for Windows and 8K for the rest.
378 So, we must either allocate sufficiently large buffers here, or
379 reduce the size of the input buffer in the input reading part.
382 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
383 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
384 char thinkOutput1[MSG_SIZ*10];
386 ChessProgramState first, second;
388 /* premove variables */
391 int premoveFromX = 0;
392 int premoveFromY = 0;
393 int premovePromoChar = 0;
395 Boolean alarmSounded;
396 /* end premove variables */
398 char *ics_prefix = "$";
399 int ics_type = ICS_GENERIC;
401 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
402 int pauseExamForwardMostMove = 0;
403 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
404 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
405 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
406 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
407 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
408 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
409 int whiteFlag = FALSE, blackFlag = FALSE;
410 int userOfferedDraw = FALSE;
411 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
412 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
413 int cmailMoveType[CMAIL_MAX_GAMES];
414 long ics_clock_paused = 0;
415 ProcRef icsPR = NoProc, cmailPR = NoProc;
416 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
417 GameMode gameMode = BeginningOfGame;
418 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
419 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
420 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
421 int hiddenThinkOutputState = 0; /* [AS] */
422 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
423 int adjudicateLossPlies = 6;
424 char white_holding[64], black_holding[64];
425 TimeMark lastNodeCountTime;
426 long lastNodeCount=0;
427 int have_sent_ICS_logon = 0;
429 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement;
430 long timeControl_2; /* [AS] Allow separate time controls */
431 char *fullTimeControlString = NULL; /* [HGM] secondary TC: merge of MPS, TC and inc */
432 long timeRemaining[2][MAX_MOVES];
434 TimeMark programStartTime;
435 char ics_handle[MSG_SIZ];
436 int have_set_title = 0;
438 /* animateTraining preserves the state of appData.animate
439 * when Training mode is activated. This allows the
440 * response to be animated when appData.animate == TRUE and
441 * appData.animateDragging == TRUE.
443 Boolean animateTraining;
449 Board boards[MAX_MOVES];
450 /* [HGM] Following 7 needed for accurate legality tests: */
451 signed char castlingRank[BOARD_FILES]; // and corresponding ranks
452 signed char initialRights[BOARD_FILES];
453 int nrCastlingRights; // For TwoKings, or to implement castling-unknown status
454 int initialRulePlies, FENrulePlies;
455 FILE *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
458 int mute; // mute all sounds
460 // [HGM] vari: next 12 to save and restore variations
461 #define MAX_VARIATIONS 10
462 int framePtr = MAX_MOVES-1; // points to free stack entry
464 int savedFirst[MAX_VARIATIONS];
465 int savedLast[MAX_VARIATIONS];
466 int savedFramePtr[MAX_VARIATIONS];
467 char *savedDetails[MAX_VARIATIONS];
468 ChessMove savedResult[MAX_VARIATIONS];
470 void PushTail P((int firstMove, int lastMove));
471 Boolean PopTail P((Boolean annotate));
472 void CleanupTail P((void));
474 ChessSquare FIDEArray[2][BOARD_FILES] = {
475 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
476 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
477 { BlackRook, BlackKnight, BlackBishop, BlackQueen,
478 BlackKing, BlackBishop, BlackKnight, BlackRook }
481 ChessSquare twoKingsArray[2][BOARD_FILES] = {
482 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
483 WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
484 { BlackRook, BlackKnight, BlackBishop, BlackQueen,
485 BlackKing, BlackKing, BlackKnight, BlackRook }
488 ChessSquare KnightmateArray[2][BOARD_FILES] = {
489 { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
490 WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
491 { BlackRook, BlackMan, BlackBishop, BlackQueen,
492 BlackUnicorn, BlackBishop, BlackMan, BlackRook }
495 ChessSquare fairyArray[2][BOARD_FILES] = { /* [HGM] Queen side differs from King side */
496 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
497 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
498 { BlackLance, BlackAlfil, BlackMarshall, BlackAngel,
499 BlackKing, BlackMarshall, BlackAlfil, BlackLance }
502 ChessSquare ShatranjArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
503 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
504 WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
505 { BlackRook, BlackKnight, BlackAlfil, BlackKing,
506 BlackFerz, BlackAlfil, BlackKnight, BlackRook }
509 ChessSquare makrukArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
510 { WhiteRook, WhiteKnight, WhiteMan, WhiteKing,
511 WhiteFerz, WhiteMan, WhiteKnight, WhiteRook },
512 { BlackRook, BlackKnight, BlackMan, BlackFerz,
513 BlackKing, BlackMan, BlackKnight, BlackRook }
517 #if (BOARD_FILES>=10)
518 ChessSquare ShogiArray[2][BOARD_FILES] = {
519 { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
520 WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
521 { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
522 BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
525 ChessSquare XiangqiArray[2][BOARD_FILES] = {
526 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
527 WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
528 { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
529 BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
532 ChessSquare CapablancaArray[2][BOARD_FILES] = {
533 { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
534 WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
535 { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
536 BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
539 ChessSquare GreatArray[2][BOARD_FILES] = {
540 { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
541 WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
542 { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
543 BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
546 ChessSquare JanusArray[2][BOARD_FILES] = {
547 { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
548 WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
549 { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
550 BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
554 ChessSquare GothicArray[2][BOARD_FILES] = {
555 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
556 WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
557 { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
558 BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
561 #define GothicArray CapablancaArray
565 ChessSquare FalconArray[2][BOARD_FILES] = {
566 { WhiteRook, WhiteKnight, WhiteBishop, WhiteLance, WhiteQueen,
567 WhiteKing, WhiteLance, WhiteBishop, WhiteKnight, WhiteRook },
568 { BlackRook, BlackKnight, BlackBishop, BlackLance, BlackQueen,
569 BlackKing, BlackLance, BlackBishop, BlackKnight, BlackRook }
572 #define FalconArray CapablancaArray
575 #else // !(BOARD_FILES>=10)
576 #define XiangqiPosition FIDEArray
577 #define CapablancaArray FIDEArray
578 #define GothicArray FIDEArray
579 #define GreatArray FIDEArray
580 #endif // !(BOARD_FILES>=10)
582 #if (BOARD_FILES>=12)
583 ChessSquare CourierArray[2][BOARD_FILES] = {
584 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
585 WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
586 { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
587 BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
589 #else // !(BOARD_FILES>=12)
590 #define CourierArray CapablancaArray
591 #endif // !(BOARD_FILES>=12)
594 Board initialPosition;
597 /* Convert str to a rating. Checks for special cases of "----",
599 "++++", etc. Also strips ()'s */
601 string_to_rating(str)
604 while(*str && !isdigit(*str)) ++str;
606 return 0; /* One of the special "no rating" cases */
614 /* Init programStats */
615 programStats.movelist[0] = 0;
616 programStats.depth = 0;
617 programStats.nr_moves = 0;
618 programStats.moves_left = 0;
619 programStats.nodes = 0;
620 programStats.time = -1; // [HGM] PGNtime: make invalid to recognize engine output
621 programStats.score = 0;
622 programStats.got_only_move = 0;
623 programStats.got_fail = 0;
624 programStats.line_is_book = 0;
630 int matched, min, sec;
632 ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
634 GetTimeMark(&programStartTime);
635 srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
638 programStats.ok_to_send = 1;
639 programStats.seen_stat = 0;
642 * Initialize game list
648 * Internet chess server status
650 if (appData.icsActive) {
651 appData.matchMode = FALSE;
652 appData.matchGames = 0;
654 appData.noChessProgram = !appData.zippyPlay;
656 appData.zippyPlay = FALSE;
657 appData.zippyTalk = FALSE;
658 appData.noChessProgram = TRUE;
660 if (*appData.icsHelper != NULLCHAR) {
661 appData.useTelnet = TRUE;
662 appData.telnetProgram = appData.icsHelper;
665 appData.zippyTalk = appData.zippyPlay = FALSE;
668 /* [AS] Initialize pv info list [HGM] and game state */
672 for( i=0; i<=framePtr; i++ ) {
673 pvInfoList[i].depth = -1;
674 boards[i][EP_STATUS] = EP_NONE;
675 for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
680 * Parse timeControl resource
682 if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
683 appData.movesPerSession)) {
685 snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
686 DisplayFatalError(buf, 0, 2);
690 * Parse searchTime resource
692 if (*appData.searchTime != NULLCHAR) {
693 matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
695 searchTime = min * 60;
696 } else if (matched == 2) {
697 searchTime = min * 60 + sec;
700 snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
701 DisplayFatalError(buf, 0, 2);
705 /* [AS] Adjudication threshold */
706 adjudicateLossThreshold = appData.adjudicateLossThreshold;
708 first.which = "first";
709 second.which = "second";
710 first.maybeThinking = second.maybeThinking = FALSE;
711 first.pr = second.pr = NoProc;
712 first.isr = second.isr = NULL;
713 first.sendTime = second.sendTime = 2;
714 first.sendDrawOffers = 1;
715 if (appData.firstPlaysBlack) {
716 first.twoMachinesColor = "black\n";
717 second.twoMachinesColor = "white\n";
719 first.twoMachinesColor = "white\n";
720 second.twoMachinesColor = "black\n";
722 first.program = appData.firstChessProgram;
723 second.program = appData.secondChessProgram;
724 first.host = appData.firstHost;
725 second.host = appData.secondHost;
726 first.dir = appData.firstDirectory;
727 second.dir = appData.secondDirectory;
728 first.other = &second;
729 second.other = &first;
730 first.initString = appData.initString;
731 second.initString = appData.secondInitString;
732 first.computerString = appData.firstComputerString;
733 second.computerString = appData.secondComputerString;
734 first.useSigint = second.useSigint = TRUE;
735 first.useSigterm = second.useSigterm = TRUE;
736 first.reuse = appData.reuseFirst;
737 second.reuse = appData.reuseSecond;
738 first.nps = appData.firstNPS; // [HGM] nps: copy nodes per second
739 second.nps = appData.secondNPS;
740 first.useSetboard = second.useSetboard = FALSE;
741 first.useSAN = second.useSAN = FALSE;
742 first.usePing = second.usePing = FALSE;
743 first.lastPing = second.lastPing = 0;
744 first.lastPong = second.lastPong = 0;
745 first.usePlayother = second.usePlayother = FALSE;
746 first.useColors = second.useColors = TRUE;
747 first.useUsermove = second.useUsermove = FALSE;
748 first.sendICS = second.sendICS = FALSE;
749 first.sendName = second.sendName = appData.icsActive;
750 first.sdKludge = second.sdKludge = FALSE;
751 first.stKludge = second.stKludge = FALSE;
752 TidyProgramName(first.program, first.host, first.tidy);
753 TidyProgramName(second.program, second.host, second.tidy);
754 first.matchWins = second.matchWins = 0;
755 strcpy(first.variants, appData.variant);
756 strcpy(second.variants, appData.variant);
757 first.analysisSupport = second.analysisSupport = 2; /* detect */
758 first.analyzing = second.analyzing = FALSE;
759 first.initDone = second.initDone = FALSE;
761 /* New features added by Tord: */
762 first.useFEN960 = FALSE; second.useFEN960 = FALSE;
763 first.useOOCastle = TRUE; second.useOOCastle = TRUE;
764 /* End of new features added by Tord. */
765 first.fenOverride = appData.fenOverride1;
766 second.fenOverride = appData.fenOverride2;
768 /* [HGM] time odds: set factor for each machine */
769 first.timeOdds = appData.firstTimeOdds;
770 second.timeOdds = appData.secondTimeOdds;
772 if(appData.timeOddsMode) {
773 norm = first.timeOdds;
774 if(norm > second.timeOdds) norm = second.timeOdds;
776 first.timeOdds /= norm;
777 second.timeOdds /= norm;
780 /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
781 first.accumulateTC = appData.firstAccumulateTC;
782 second.accumulateTC = appData.secondAccumulateTC;
783 first.maxNrOfSessions = second.maxNrOfSessions = 1;
786 first.debug = second.debug = FALSE;
787 first.supportsNPS = second.supportsNPS = UNKNOWN;
790 first.optionSettings = appData.firstOptions;
791 second.optionSettings = appData.secondOptions;
793 first.scoreIsAbsolute = appData.firstScoreIsAbsolute; /* [AS] */
794 second.scoreIsAbsolute = appData.secondScoreIsAbsolute; /* [AS] */
795 first.isUCI = appData.firstIsUCI; /* [AS] */
796 second.isUCI = appData.secondIsUCI; /* [AS] */
797 first.hasOwnBookUCI = appData.firstHasOwnBookUCI; /* [AS] */
798 second.hasOwnBookUCI = appData.secondHasOwnBookUCI; /* [AS] */
800 if (appData.firstProtocolVersion > PROTOVER ||
801 appData.firstProtocolVersion < 1) {
803 sprintf(buf, _("protocol version %d not supported"),
804 appData.firstProtocolVersion);
805 DisplayFatalError(buf, 0, 2);
807 first.protocolVersion = appData.firstProtocolVersion;
810 if (appData.secondProtocolVersion > PROTOVER ||
811 appData.secondProtocolVersion < 1) {
813 sprintf(buf, _("protocol version %d not supported"),
814 appData.secondProtocolVersion);
815 DisplayFatalError(buf, 0, 2);
817 second.protocolVersion = appData.secondProtocolVersion;
820 if (appData.icsActive) {
821 appData.clockMode = TRUE; /* changes dynamically in ICS mode */
822 // } else if (*appData.searchTime != NULLCHAR || appData.noChessProgram) {
823 } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
824 appData.clockMode = FALSE;
825 first.sendTime = second.sendTime = 0;
829 /* Override some settings from environment variables, for backward
830 compatibility. Unfortunately it's not feasible to have the env
831 vars just set defaults, at least in xboard. Ugh.
833 if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
838 if (appData.noChessProgram) {
839 programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
840 sprintf(programVersion, "%s", PACKAGE_STRING);
842 /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
843 programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
844 sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
847 if (!appData.icsActive) {
849 /* Check for variants that are supported only in ICS mode,
850 or not at all. Some that are accepted here nevertheless
851 have bugs; see comments below.
853 VariantClass variant = StringToVariant(appData.variant);
855 case VariantBughouse: /* need four players and two boards */
856 case VariantKriegspiel: /* need to hide pieces and move details */
857 /* case VariantFischeRandom: (Fabien: moved below) */
858 sprintf(buf, _("Variant %s supported only in ICS mode"), appData.variant);
859 DisplayFatalError(buf, 0, 2);
863 case VariantLoadable:
873 sprintf(buf, _("Unknown variant name %s"), appData.variant);
874 DisplayFatalError(buf, 0, 2);
877 case VariantXiangqi: /* [HGM] repetition rules not implemented */
878 case VariantFairy: /* [HGM] TestLegality definitely off! */
879 case VariantGothic: /* [HGM] should work */
880 case VariantCapablanca: /* [HGM] should work */
881 case VariantCourier: /* [HGM] initial forced moves not implemented */
882 case VariantShogi: /* [HGM] drops not tested for legality */
883 case VariantKnightmate: /* [HGM] should work */
884 case VariantCylinder: /* [HGM] untested */
885 case VariantFalcon: /* [HGM] untested */
886 case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
887 offboard interposition not understood */
888 case VariantNormal: /* definitely works! */
889 case VariantWildCastle: /* pieces not automatically shuffled */
890 case VariantNoCastle: /* pieces not automatically shuffled */
891 case VariantFischeRandom: /* [HGM] works and shuffles pieces */
892 case VariantLosers: /* should work except for win condition,
893 and doesn't know captures are mandatory */
894 case VariantSuicide: /* should work except for win condition,
895 and doesn't know captures are mandatory */
896 case VariantGiveaway: /* should work except for win condition,
897 and doesn't know captures are mandatory */
898 case VariantTwoKings: /* should work */
899 case VariantAtomic: /* should work except for win condition */
900 case Variant3Check: /* should work except for win condition */
901 case VariantShatranj: /* should work except for all win conditions */
902 case VariantMakruk: /* should work except for daw countdown */
903 case VariantBerolina: /* might work if TestLegality is off */
904 case VariantCapaRandom: /* should work */
905 case VariantJanus: /* should work */
906 case VariantSuper: /* experimental */
907 case VariantGreat: /* experimental, requires legality testing to be off */
912 InitEngineUCI( installDir, &first ); // [HGM] moved here from winboard.c, to make available in xboard
913 InitEngineUCI( installDir, &second );
916 int NextIntegerFromString( char ** str, long * value )
921 while( *s == ' ' || *s == '\t' ) {
927 if( *s >= '0' && *s <= '9' ) {
928 while( *s >= '0' && *s <= '9' ) {
929 *value = *value * 10 + (*s - '0');
941 int NextTimeControlFromString( char ** str, long * value )
944 int result = NextIntegerFromString( str, &temp );
947 *value = temp * 60; /* Minutes */
950 result = NextIntegerFromString( str, &temp );
951 *value += temp; /* Seconds */
958 int NextSessionFromString( char ** str, int *moves, long * tc, long *inc)
959 { /* [HGM] routine added to read '+moves/time' for secondary time control */
960 int result = -1; long temp, temp2;
962 if(**str != '+') return -1; // old params remain in force!
964 if( NextTimeControlFromString( str, &temp ) ) return -1;
967 /* time only: incremental or sudden-death time control */
968 if(**str == '+') { /* increment follows; read it */
970 if(result = NextIntegerFromString( str, &temp2)) return -1;
973 *moves = 0; *tc = temp * 1000;
975 } else if(temp % 60 != 0) return -1; /* moves was given as min:sec */
977 (*str)++; /* classical time control */
978 result = NextTimeControlFromString( str, &temp2);
987 int GetTimeQuota(int movenr)
988 { /* [HGM] get time to add from the multi-session time-control string */
989 int moves=1; /* kludge to force reading of first session */
990 long time, increment;
991 char *s = fullTimeControlString;
993 if(appData.debugMode) fprintf(debugFP, "TC string = '%s'\n", fullTimeControlString);
995 if(moves) NextSessionFromString(&s, &moves, &time, &increment);
996 if(appData.debugMode) fprintf(debugFP, "mps=%d tc=%d inc=%d\n", moves, (int) time, (int) increment);
997 if(movenr == -1) return time; /* last move before new session */
998 if(!moves) return increment; /* current session is incremental */
999 if(movenr >= 0) movenr -= moves; /* we already finished this session */
1000 } while(movenr >= -1); /* try again for next session */
1002 return 0; // no new time quota on this move
1006 ParseTimeControl(tc, ti, mps)
1015 if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1018 sprintf(buf, "+%d/%s+%d", mps, tc, ti);
1019 else sprintf(buf, "+%s+%d", tc, ti);
1022 sprintf(buf, "+%d/%s", mps, tc);
1023 else sprintf(buf, "+%s", tc);
1025 fullTimeControlString = StrSave(buf);
1027 if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1032 /* Parse second time control */
1035 if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1043 timeControl_2 = tc2 * 1000;
1053 timeControl = tc1 * 1000;
1056 timeIncrement = ti * 1000; /* convert to ms */
1057 movesPerSession = 0;
1060 movesPerSession = mps;
1068 if (appData.debugMode) {
1069 fprintf(debugFP, "%s\n", programVersion);
1072 set_cont_sequence(appData.wrapContSeq);
1073 if (appData.matchGames > 0) {
1074 appData.matchMode = TRUE;
1075 } else if (appData.matchMode) {
1076 appData.matchGames = 1;
1078 if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1079 appData.matchGames = appData.sameColorGames;
1080 if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1081 if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1082 if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1085 if (appData.noChessProgram || first.protocolVersion == 1) {
1088 /* kludge: allow timeout for initial "feature" commands */
1090 DisplayMessage("", _("Starting chess program"));
1091 ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1096 InitBackEnd3 P((void))
1098 GameMode initialMode;
1102 InitChessProgram(&first, startedFromSetupPosition);
1105 if (appData.icsActive) {
1107 /* [DM] Make a console window if needed [HGM] merged ifs */
1112 if (*appData.icsCommPort != NULLCHAR) {
1113 sprintf(buf, _("Could not open comm port %s"),
1114 appData.icsCommPort);
1116 snprintf(buf, sizeof(buf), _("Could not connect to host %s, port %s"),
1117 appData.icsHost, appData.icsPort);
1119 DisplayFatalError(buf, err, 1);
1124 AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1126 AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1127 if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1128 ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1129 } else if (appData.noChessProgram) {
1135 if (*appData.cmailGameName != NULLCHAR) {
1137 OpenLoopback(&cmailPR);
1139 AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1143 DisplayMessage("", "");
1144 if (StrCaseCmp(appData.initialMode, "") == 0) {
1145 initialMode = BeginningOfGame;
1146 } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1147 initialMode = TwoMachinesPlay;
1148 } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1149 initialMode = AnalyzeFile;
1150 } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1151 initialMode = AnalyzeMode;
1152 } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1153 initialMode = MachinePlaysWhite;
1154 } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1155 initialMode = MachinePlaysBlack;
1156 } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1157 initialMode = EditGame;
1158 } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1159 initialMode = EditPosition;
1160 } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1161 initialMode = Training;
1163 sprintf(buf, _("Unknown initialMode %s"), appData.initialMode);
1164 DisplayFatalError(buf, 0, 2);
1168 if (appData.matchMode) {
1169 /* Set up machine vs. machine match */
1170 if (appData.noChessProgram) {
1171 DisplayFatalError(_("Can't have a match with no chess programs"),
1177 if (*appData.loadGameFile != NULLCHAR) {
1178 int index = appData.loadGameIndex; // [HGM] autoinc
1179 if(index<0) lastIndex = index = 1;
1180 if (!LoadGameFromFile(appData.loadGameFile,
1182 appData.loadGameFile, FALSE)) {
1183 DisplayFatalError(_("Bad game file"), 0, 1);
1186 } else if (*appData.loadPositionFile != NULLCHAR) {
1187 int index = appData.loadPositionIndex; // [HGM] autoinc
1188 if(index<0) lastIndex = index = 1;
1189 if (!LoadPositionFromFile(appData.loadPositionFile,
1191 appData.loadPositionFile)) {
1192 DisplayFatalError(_("Bad position file"), 0, 1);
1197 } else if (*appData.cmailGameName != NULLCHAR) {
1198 /* Set up cmail mode */
1199 ReloadCmailMsgEvent(TRUE);
1201 /* Set up other modes */
1202 if (initialMode == AnalyzeFile) {
1203 if (*appData.loadGameFile == NULLCHAR) {
1204 DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1208 if (*appData.loadGameFile != NULLCHAR) {
1209 (void) LoadGameFromFile(appData.loadGameFile,
1210 appData.loadGameIndex,
1211 appData.loadGameFile, TRUE);
1212 } else if (*appData.loadPositionFile != NULLCHAR) {
1213 (void) LoadPositionFromFile(appData.loadPositionFile,
1214 appData.loadPositionIndex,
1215 appData.loadPositionFile);
1216 /* [HGM] try to make self-starting even after FEN load */
1217 /* to allow automatic setup of fairy variants with wtm */
1218 if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1219 gameMode = BeginningOfGame;
1220 setboardSpoiledMachineBlack = 1;
1222 /* [HGM] loadPos: make that every new game uses the setup */
1223 /* from file as long as we do not switch variant */
1224 if(!blackPlaysFirst) {
1225 startedFromPositionFile = TRUE;
1226 CopyBoard(filePosition, boards[0]);
1229 if (initialMode == AnalyzeMode) {
1230 if (appData.noChessProgram) {
1231 DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1234 if (appData.icsActive) {
1235 DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1239 } else if (initialMode == AnalyzeFile) {
1240 appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1241 ShowThinkingEvent();
1243 AnalysisPeriodicEvent(1);
1244 } else if (initialMode == MachinePlaysWhite) {
1245 if (appData.noChessProgram) {
1246 DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1250 if (appData.icsActive) {
1251 DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1255 MachineWhiteEvent();
1256 } else if (initialMode == MachinePlaysBlack) {
1257 if (appData.noChessProgram) {
1258 DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1262 if (appData.icsActive) {
1263 DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1267 MachineBlackEvent();
1268 } else if (initialMode == TwoMachinesPlay) {
1269 if (appData.noChessProgram) {
1270 DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1274 if (appData.icsActive) {
1275 DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1280 } else if (initialMode == EditGame) {
1282 } else if (initialMode == EditPosition) {
1283 EditPositionEvent();
1284 } else if (initialMode == Training) {
1285 if (*appData.loadGameFile == NULLCHAR) {
1286 DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1295 * Establish will establish a contact to a remote host.port.
1296 * Sets icsPR to a ProcRef for a process (or pseudo-process)
1297 * used to talk to the host.
1298 * Returns 0 if okay, error code if not.
1305 if (*appData.icsCommPort != NULLCHAR) {
1306 /* Talk to the host through a serial comm port */
1307 return OpenCommPort(appData.icsCommPort, &icsPR);
1309 } else if (*appData.gateway != NULLCHAR) {
1310 if (*appData.remoteShell == NULLCHAR) {
1311 /* Use the rcmd protocol to run telnet program on a gateway host */
1312 snprintf(buf, sizeof(buf), "%s %s %s",
1313 appData.telnetProgram, appData.icsHost, appData.icsPort);
1314 return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1317 /* Use the rsh program to run telnet program on a gateway host */
1318 if (*appData.remoteUser == NULLCHAR) {
1319 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1320 appData.gateway, appData.telnetProgram,
1321 appData.icsHost, appData.icsPort);
1323 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1324 appData.remoteShell, appData.gateway,
1325 appData.remoteUser, appData.telnetProgram,
1326 appData.icsHost, appData.icsPort);
1328 return StartChildProcess(buf, "", &icsPR);
1331 } else if (appData.useTelnet) {
1332 return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1335 /* TCP socket interface differs somewhat between
1336 Unix and NT; handle details in the front end.
1338 return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1343 show_bytes(fp, buf, count)
1349 if (*buf < 040 || *(unsigned char *) buf > 0177) {
1350 fprintf(fp, "\\%03o", *buf & 0xff);
1359 /* Returns an errno value */
1361 OutputMaybeTelnet(pr, message, count, outError)
1367 char buf[8192], *p, *q, *buflim;
1368 int left, newcount, outcount;
1370 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1371 *appData.gateway != NULLCHAR) {
1372 if (appData.debugMode) {
1373 fprintf(debugFP, ">ICS: ");
1374 show_bytes(debugFP, message, count);
1375 fprintf(debugFP, "\n");
1377 return OutputToProcess(pr, message, count, outError);
1380 buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1387 if (appData.debugMode) {
1388 fprintf(debugFP, ">ICS: ");
1389 show_bytes(debugFP, buf, newcount);
1390 fprintf(debugFP, "\n");
1392 outcount = OutputToProcess(pr, buf, newcount, outError);
1393 if (outcount < newcount) return -1; /* to be sure */
1400 } else if (((unsigned char) *p) == TN_IAC) {
1401 *q++ = (char) TN_IAC;
1408 if (appData.debugMode) {
1409 fprintf(debugFP, ">ICS: ");
1410 show_bytes(debugFP, buf, newcount);
1411 fprintf(debugFP, "\n");
1413 outcount = OutputToProcess(pr, buf, newcount, outError);
1414 if (outcount < newcount) return -1; /* to be sure */
1419 read_from_player(isr, closure, message, count, error)
1426 int outError, outCount;
1427 static int gotEof = 0;
1429 /* Pass data read from player on to ICS */
1432 outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1433 if (outCount < count) {
1434 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1436 } else if (count < 0) {
1437 RemoveInputSource(isr);
1438 DisplayFatalError(_("Error reading from keyboard"), error, 1);
1439 } else if (gotEof++ > 0) {
1440 RemoveInputSource(isr);
1441 DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1447 { // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1448 if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1449 connectionAlive = FALSE; // only sticks if no response to 'date' command.
1450 SendToICS("date\n");
1451 if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1454 /* added routine for printf style output to ics */
1455 void ics_printf(char *format, ...)
1457 char buffer[MSG_SIZ];
1460 va_start(args, format);
1461 vsnprintf(buffer, sizeof(buffer), format, args);
1462 buffer[sizeof(buffer)-1] = '\0';
1471 int count, outCount, outError;
1473 if (icsPR == NULL) return;
1476 outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1477 if (outCount < count) {
1478 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1482 /* This is used for sending logon scripts to the ICS. Sending
1483 without a delay causes problems when using timestamp on ICC
1484 (at least on my machine). */
1486 SendToICSDelayed(s,msdelay)
1490 int count, outCount, outError;
1492 if (icsPR == NULL) return;
1495 if (appData.debugMode) {
1496 fprintf(debugFP, ">ICS: ");
1497 show_bytes(debugFP, s, count);
1498 fprintf(debugFP, "\n");
1500 outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1502 if (outCount < count) {
1503 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1508 /* Remove all highlighting escape sequences in s
1509 Also deletes any suffix starting with '('
1512 StripHighlightAndTitle(s)
1515 static char retbuf[MSG_SIZ];
1518 while (*s != NULLCHAR) {
1519 while (*s == '\033') {
1520 while (*s != NULLCHAR && !isalpha(*s)) s++;
1521 if (*s != NULLCHAR) s++;
1523 while (*s != NULLCHAR && *s != '\033') {
1524 if (*s == '(' || *s == '[') {
1535 /* Remove all highlighting escape sequences in s */
1540 static char retbuf[MSG_SIZ];
1543 while (*s != NULLCHAR) {
1544 while (*s == '\033') {
1545 while (*s != NULLCHAR && !isalpha(*s)) s++;
1546 if (*s != NULLCHAR) s++;
1548 while (*s != NULLCHAR && *s != '\033') {
1556 char *variantNames[] = VARIANT_NAMES;
1561 return variantNames[v];
1565 /* Identify a variant from the strings the chess servers use or the
1566 PGN Variant tag names we use. */
1573 VariantClass v = VariantNormal;
1574 int i, found = FALSE;
1579 /* [HGM] skip over optional board-size prefixes */
1580 if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1581 sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1582 while( *e++ != '_');
1585 if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
1589 for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
1590 if (StrCaseStr(e, variantNames[i])) {
1591 v = (VariantClass) i;
1598 if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
1599 || StrCaseStr(e, "wild/fr")
1600 || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
1601 v = VariantFischeRandom;
1602 } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
1603 (i = 1, p = StrCaseStr(e, "w"))) {
1605 while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
1612 case 0: /* FICS only, actually */
1614 /* Castling legal even if K starts on d-file */
1615 v = VariantWildCastle;
1620 /* Castling illegal even if K & R happen to start in
1621 normal positions. */
1622 v = VariantNoCastle;
1635 /* Castling legal iff K & R start in normal positions */
1641 /* Special wilds for position setup; unclear what to do here */
1642 v = VariantLoadable;
1645 /* Bizarre ICC game */
1646 v = VariantTwoKings;
1649 v = VariantKriegspiel;
1655 v = VariantFischeRandom;
1658 v = VariantCrazyhouse;
1661 v = VariantBughouse;
1667 /* Not quite the same as FICS suicide! */
1668 v = VariantGiveaway;
1674 v = VariantShatranj;
1677 /* Temporary names for future ICC types. The name *will* change in
1678 the next xboard/WinBoard release after ICC defines it. */
1716 v = VariantCapablanca;
1719 v = VariantKnightmate;
1725 v = VariantCylinder;
1731 v = VariantCapaRandom;
1734 v = VariantBerolina;
1746 /* Found "wild" or "w" in the string but no number;
1747 must assume it's normal chess. */
1751 sprintf(buf, _("Unknown wild type %d"), wnum);
1752 DisplayError(buf, 0);
1758 if (appData.debugMode) {
1759 fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
1760 e, wnum, VariantName(v));
1765 static int leftover_start = 0, leftover_len = 0;
1766 char star_match[STAR_MATCH_N][MSG_SIZ];
1768 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
1769 advance *index beyond it, and set leftover_start to the new value of
1770 *index; else return FALSE. If pattern contains the character '*', it
1771 matches any sequence of characters not containing '\r', '\n', or the
1772 character following the '*' (if any), and the matched sequence(s) are
1773 copied into star_match.
1776 looking_at(buf, index, pattern)
1781 char *bufp = &buf[*index], *patternp = pattern;
1783 char *matchp = star_match[0];
1786 if (*patternp == NULLCHAR) {
1787 *index = leftover_start = bufp - buf;
1791 if (*bufp == NULLCHAR) return FALSE;
1792 if (*patternp == '*') {
1793 if (*bufp == *(patternp + 1)) {
1795 matchp = star_match[++star_count];
1799 } else if (*bufp == '\n' || *bufp == '\r') {
1801 if (*patternp == NULLCHAR)
1806 *matchp++ = *bufp++;
1810 if (*patternp != *bufp) return FALSE;
1817 SendToPlayer(data, length)
1821 int error, outCount;
1822 outCount = OutputToProcess(NoProc, data, length, &error);
1823 if (outCount < length) {
1824 DisplayFatalError(_("Error writing to display"), error, 1);
1829 PackHolding(packed, holding)
1841 switch (runlength) {
1852 sprintf(q, "%d", runlength);
1864 /* Telnet protocol requests from the front end */
1866 TelnetRequest(ddww, option)
1867 unsigned char ddww, option;
1869 unsigned char msg[3];
1870 int outCount, outError;
1872 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
1874 if (appData.debugMode) {
1875 char buf1[8], buf2[8], *ddwwStr, *optionStr;
1891 sprintf(buf1, "%d", ddww);
1900 sprintf(buf2, "%d", option);
1903 fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
1908 outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
1910 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1917 if (!appData.icsActive) return;
1918 TelnetRequest(TN_DO, TN_ECHO);
1924 if (!appData.icsActive) return;
1925 TelnetRequest(TN_DONT, TN_ECHO);
1929 CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece)
1931 /* put the holdings sent to us by the server on the board holdings area */
1932 int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
1936 if(gameInfo.holdingsWidth < 2) return;
1937 if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
1938 return; // prevent overwriting by pre-board holdings
1940 if( (int)lowestPiece >= BlackPawn ) {
1943 holdingsStartRow = BOARD_HEIGHT-1;
1946 holdingsColumn = BOARD_WIDTH-1;
1947 countsColumn = BOARD_WIDTH-2;
1948 holdingsStartRow = 0;
1952 for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
1953 board[i][holdingsColumn] = EmptySquare;
1954 board[i][countsColumn] = (ChessSquare) 0;
1956 while( (p=*holdings++) != NULLCHAR ) {
1957 piece = CharToPiece( ToUpper(p) );
1958 if(piece == EmptySquare) continue;
1959 /*j = (int) piece - (int) WhitePawn;*/
1960 j = PieceToNumber(piece);
1961 if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
1962 if(j < 0) continue; /* should not happen */
1963 piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
1964 board[holdingsStartRow+j*direction][holdingsColumn] = piece;
1965 board[holdingsStartRow+j*direction][countsColumn]++;
1971 VariantSwitch(Board board, VariantClass newVariant)
1973 int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
1976 startedFromPositionFile = FALSE;
1977 if(gameInfo.variant == newVariant) return;
1979 /* [HGM] This routine is called each time an assignment is made to
1980 * gameInfo.variant during a game, to make sure the board sizes
1981 * are set to match the new variant. If that means adding or deleting
1982 * holdings, we shift the playing board accordingly
1983 * This kludge is needed because in ICS observe mode, we get boards
1984 * of an ongoing game without knowing the variant, and learn about the
1985 * latter only later. This can be because of the move list we requested,
1986 * in which case the game history is refilled from the beginning anyway,
1987 * but also when receiving holdings of a crazyhouse game. In the latter
1988 * case we want to add those holdings to the already received position.
1992 if (appData.debugMode) {
1993 fprintf(debugFP, "Switch board from %s to %s\n",
1994 VariantName(gameInfo.variant), VariantName(newVariant));
1995 setbuf(debugFP, NULL);
1997 shuffleOpenings = 0; /* [HGM] shuffle */
1998 gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2002 newWidth = 9; newHeight = 9;
2003 gameInfo.holdingsSize = 7;
2004 case VariantBughouse:
2005 case VariantCrazyhouse:
2006 newHoldingsWidth = 2; break;
2010 newHoldingsWidth = 2;
2011 gameInfo.holdingsSize = 8;
2014 case VariantCapablanca:
2015 case VariantCapaRandom:
2018 newHoldingsWidth = gameInfo.holdingsSize = 0;
2021 if(newWidth != gameInfo.boardWidth ||
2022 newHeight != gameInfo.boardHeight ||
2023 newHoldingsWidth != gameInfo.holdingsWidth ) {
2025 /* shift position to new playing area, if needed */
2026 if(newHoldingsWidth > gameInfo.holdingsWidth) {
2027 for(i=0; i<BOARD_HEIGHT; i++)
2028 for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2029 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2031 for(i=0; i<newHeight; i++) {
2032 board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2033 board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2035 } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2036 for(i=0; i<BOARD_HEIGHT; i++)
2037 for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2038 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2041 gameInfo.boardWidth = newWidth;
2042 gameInfo.boardHeight = newHeight;
2043 gameInfo.holdingsWidth = newHoldingsWidth;
2044 gameInfo.variant = newVariant;
2045 InitDrawingSizes(-2, 0);
2046 } else gameInfo.variant = newVariant;
2047 CopyBoard(oldBoard, board); // remember correctly formatted board
2048 InitPosition(FALSE); /* this sets up board[0], but also other stuff */
2049 DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2052 static int loggedOn = FALSE;
2054 /*-- Game start info cache: --*/
2056 char gs_kind[MSG_SIZ];
2057 static char player1Name[128] = "";
2058 static char player2Name[128] = "";
2059 static char cont_seq[] = "\n\\ ";
2060 static int player1Rating = -1;
2061 static int player2Rating = -1;
2062 /*----------------------------*/
2064 ColorClass curColor = ColorNormal;
2065 int suppressKibitz = 0;
2068 read_from_ics(isr, closure, data, count, error)
2075 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2076 #define STARTED_NONE 0
2077 #define STARTED_MOVES 1
2078 #define STARTED_BOARD 2
2079 #define STARTED_OBSERVE 3
2080 #define STARTED_HOLDINGS 4
2081 #define STARTED_CHATTER 5
2082 #define STARTED_COMMENT 6
2083 #define STARTED_MOVES_NOHIDE 7
2085 static int started = STARTED_NONE;
2086 static char parse[20000];
2087 static int parse_pos = 0;
2088 static char buf[BUF_SIZE + 1];
2089 static int firstTime = TRUE, intfSet = FALSE;
2090 static ColorClass prevColor = ColorNormal;
2091 static int savingComment = FALSE;
2092 static int cmatch = 0; // continuation sequence match
2099 int backup; /* [DM] For zippy color lines */
2101 char talker[MSG_SIZ]; // [HGM] chat
2104 connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2106 if (appData.debugMode) {
2108 fprintf(debugFP, "<ICS: ");
2109 show_bytes(debugFP, data, count);
2110 fprintf(debugFP, "\n");
2114 if (appData.debugMode) { int f = forwardMostMove;
2115 fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2116 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2117 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2120 /* If last read ended with a partial line that we couldn't parse,
2121 prepend it to the new read and try again. */
2122 if (leftover_len > 0) {
2123 for (i=0; i<leftover_len; i++)
2124 buf[i] = buf[leftover_start + i];
2127 /* copy new characters into the buffer */
2128 bp = buf + leftover_len;
2129 buf_len=leftover_len;
2130 for (i=0; i<count; i++)
2133 if (data[i] == '\r')
2136 // join lines split by ICS?
2137 if (!appData.noJoin)
2140 Joining just consists of finding matches against the
2141 continuation sequence, and discarding that sequence
2142 if found instead of copying it. So, until a match
2143 fails, there's nothing to do since it might be the
2144 complete sequence, and thus, something we don't want
2147 if (data[i] == cont_seq[cmatch])
2150 if (cmatch == strlen(cont_seq))
2152 cmatch = 0; // complete match. just reset the counter
2155 it's possible for the ICS to not include the space
2156 at the end of the last word, making our [correct]
2157 join operation fuse two separate words. the server
2158 does this when the space occurs at the width setting.
2160 if (!buf_len || buf[buf_len-1] != ' ')
2171 match failed, so we have to copy what matched before
2172 falling through and copying this character. In reality,
2173 this will only ever be just the newline character, but
2174 it doesn't hurt to be precise.
2176 strncpy(bp, cont_seq, cmatch);
2188 buf[buf_len] = NULLCHAR;
2189 // next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2194 while (i < buf_len) {
2195 /* Deal with part of the TELNET option negotiation
2196 protocol. We refuse to do anything beyond the
2197 defaults, except that we allow the WILL ECHO option,
2198 which ICS uses to turn off password echoing when we are
2199 directly connected to it. We reject this option
2200 if localLineEditing mode is on (always on in xboard)
2201 and we are talking to port 23, which might be a real
2202 telnet server that will try to keep WILL ECHO on permanently.
2204 if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2205 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2206 unsigned char option;
2208 switch ((unsigned char) buf[++i]) {
2210 if (appData.debugMode)
2211 fprintf(debugFP, "\n<WILL ");
2212 switch (option = (unsigned char) buf[++i]) {
2214 if (appData.debugMode)
2215 fprintf(debugFP, "ECHO ");
2216 /* Reply only if this is a change, according
2217 to the protocol rules. */
2218 if (remoteEchoOption) break;
2219 if (appData.localLineEditing &&
2220 atoi(appData.icsPort) == TN_PORT) {
2221 TelnetRequest(TN_DONT, TN_ECHO);
2224 TelnetRequest(TN_DO, TN_ECHO);
2225 remoteEchoOption = TRUE;
2229 if (appData.debugMode)
2230 fprintf(debugFP, "%d ", option);
2231 /* Whatever this is, we don't want it. */
2232 TelnetRequest(TN_DONT, option);
2237 if (appData.debugMode)
2238 fprintf(debugFP, "\n<WONT ");
2239 switch (option = (unsigned char) buf[++i]) {
2241 if (appData.debugMode)
2242 fprintf(debugFP, "ECHO ");
2243 /* Reply only if this is a change, according
2244 to the protocol rules. */
2245 if (!remoteEchoOption) break;
2247 TelnetRequest(TN_DONT, TN_ECHO);
2248 remoteEchoOption = FALSE;
2251 if (appData.debugMode)
2252 fprintf(debugFP, "%d ", (unsigned char) option);
2253 /* Whatever this is, it must already be turned
2254 off, because we never agree to turn on
2255 anything non-default, so according to the
2256 protocol rules, we don't reply. */
2261 if (appData.debugMode)
2262 fprintf(debugFP, "\n<DO ");
2263 switch (option = (unsigned char) buf[++i]) {
2265 /* Whatever this is, we refuse to do it. */
2266 if (appData.debugMode)
2267 fprintf(debugFP, "%d ", option);
2268 TelnetRequest(TN_WONT, option);
2273 if (appData.debugMode)
2274 fprintf(debugFP, "\n<DONT ");
2275 switch (option = (unsigned char) buf[++i]) {
2277 if (appData.debugMode)
2278 fprintf(debugFP, "%d ", option);
2279 /* Whatever this is, we are already not doing
2280 it, because we never agree to do anything
2281 non-default, so according to the protocol
2282 rules, we don't reply. */
2287 if (appData.debugMode)
2288 fprintf(debugFP, "\n<IAC ");
2289 /* Doubled IAC; pass it through */
2293 if (appData.debugMode)
2294 fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2295 /* Drop all other telnet commands on the floor */
2298 if (oldi > next_out)
2299 SendToPlayer(&buf[next_out], oldi - next_out);
2305 /* OK, this at least will *usually* work */
2306 if (!loggedOn && looking_at(buf, &i, "ics%")) {
2310 if (loggedOn && !intfSet) {
2311 if (ics_type == ICS_ICC) {
2313 "/set-quietly interface %s\n/set-quietly style 12\n",
2315 } else if (ics_type == ICS_CHESSNET) {
2316 sprintf(str, "/style 12\n");
2318 strcpy(str, "alias $ @\n$set interface ");
2319 strcat(str, programVersion);
2320 strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2322 strcat(str, "$iset nohighlight 1\n");
2324 strcat(str, "$iset lock 1\n$style 12\n");
2327 NotifyFrontendLogin();
2331 if (started == STARTED_COMMENT) {
2332 /* Accumulate characters in comment */
2333 parse[parse_pos++] = buf[i];
2334 if (buf[i] == '\n') {
2335 parse[parse_pos] = NULLCHAR;
2336 if(chattingPartner>=0) {
2338 sprintf(mess, "%s%s", talker, parse);
2339 OutputChatMessage(chattingPartner, mess);
2340 chattingPartner = -1;
2342 if(!suppressKibitz) // [HGM] kibitz
2343 AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
2344 else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2345 int nrDigit = 0, nrAlph = 0, j;
2346 if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2347 { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2348 parse[parse_pos] = NULLCHAR;
2349 // try to be smart: if it does not look like search info, it should go to
2350 // ICS interaction window after all, not to engine-output window.
2351 for(j=0; j<parse_pos; j++) { // count letters and digits
2352 nrDigit += (parse[j] >= '0' && parse[j] <= '9');
2353 nrAlph += (parse[j] >= 'a' && parse[j] <= 'z');
2354 nrAlph += (parse[j] >= 'A' && parse[j] <= 'Z');
2356 if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2357 int depth=0; float score;
2358 if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2359 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2360 pvInfoList[forwardMostMove-1].depth = depth;
2361 pvInfoList[forwardMostMove-1].score = 100*score;
2363 OutputKibitz(suppressKibitz, parse);
2364 next_out = i+1; // [HGM] suppress printing in ICS window
2367 sprintf(tmp, _("your opponent kibitzes: %s"), parse);
2368 SendToPlayer(tmp, strlen(tmp));
2371 started = STARTED_NONE;
2373 /* Don't match patterns against characters in comment */
2378 if (started == STARTED_CHATTER) {
2379 if (buf[i] != '\n') {
2380 /* Don't match patterns against characters in chatter */
2384 started = STARTED_NONE;
2387 /* Kludge to deal with rcmd protocol */
2388 if (firstTime && looking_at(buf, &i, "\001*")) {
2389 DisplayFatalError(&buf[1], 0, 1);
2395 if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2398 if (appData.debugMode)
2399 fprintf(debugFP, "ics_type %d\n", ics_type);
2402 if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
2403 ics_type = ICS_FICS;
2405 if (appData.debugMode)
2406 fprintf(debugFP, "ics_type %d\n", ics_type);
2409 if (!loggedOn && looking_at(buf, &i, "chess.net")) {
2410 ics_type = ICS_CHESSNET;
2412 if (appData.debugMode)
2413 fprintf(debugFP, "ics_type %d\n", ics_type);
2418 (looking_at(buf, &i, "\"*\" is *a registered name") ||
2419 looking_at(buf, &i, "Logging you in as \"*\"") ||
2420 looking_at(buf, &i, "will be \"*\""))) {
2421 strcpy(ics_handle, star_match[0]);
2425 if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
2427 snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
2428 DisplayIcsInteractionTitle(buf);
2429 have_set_title = TRUE;
2432 /* skip finger notes */
2433 if (started == STARTED_NONE &&
2434 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
2435 (buf[i] == '1' && buf[i+1] == '0')) &&
2436 buf[i+2] == ':' && buf[i+3] == ' ') {
2437 started = STARTED_CHATTER;
2442 /* skip formula vars */
2443 if (started == STARTED_NONE &&
2444 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
2445 started = STARTED_CHATTER;
2451 // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
2452 if (appData.autoKibitz && started == STARTED_NONE &&
2453 !appData.icsEngineAnalyze && // [HGM] [DM] ICS analyze
2454 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
2455 if(looking_at(buf, &i, "* kibitzes: ") &&
2456 (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
2457 StrStr(star_match[0], gameInfo.black) == star_match[0] )) { // kibitz of self or opponent
2458 suppressKibitz = TRUE;
2459 if((StrStr(star_match[0], gameInfo.white) == star_match[0]
2460 && (gameMode == IcsPlayingWhite)) ||
2461 (StrStr(star_match[0], gameInfo.black) == star_match[0]
2462 && (gameMode == IcsPlayingBlack)) ) // opponent kibitz
2463 started = STARTED_CHATTER; // own kibitz we simply discard
2465 started = STARTED_COMMENT; // make sure it will be collected in parse[]
2466 parse_pos = 0; parse[0] = NULLCHAR;
2467 savingComment = TRUE;
2468 suppressKibitz = gameMode != IcsObserving ? 2 :
2469 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
2473 if(looking_at(buf, &i, "kibitzed to *\n") && atoi(star_match[0])) {
2474 // suppress the acknowledgements of our own autoKibitz
2475 SendToPlayer(star_match[0], strlen(star_match[0]));
2476 looking_at(buf, &i, "*% "); // eat prompt
2479 } // [HGM] kibitz: end of patch
2481 //if(appData.debugMode) fprintf(debugFP, "hunt for tell, buf = %s\n", buf+i);
2483 // [HGM] chat: intercept tells by users for which we have an open chat window
2485 if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
2486 looking_at(buf, &i, "* whispers:") ||
2487 looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
2488 looking_at(buf, &i, "*(*)(*):") && sscanf(star_match[2], "%d", &channel) == 1 )) {
2490 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
2491 chattingPartner = -1;
2493 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
2494 for(p=0; p<MAX_CHAT; p++) {
2495 if(channel == atoi(chatPartner[p])) {
2496 talker[0] = '['; strcat(talker, "]");
2497 chattingPartner = p; break;
2500 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
2501 for(p=0; p<MAX_CHAT; p++) {
2502 if(!strcmp("WHISPER", chatPartner[p])) {
2503 talker[0] = '['; strcat(talker, "]");
2504 chattingPartner = p; break;
2507 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
2508 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
2510 chattingPartner = p; break;
2512 if(chattingPartner<0) i = oldi; else {
2513 started = STARTED_COMMENT;
2514 parse_pos = 0; parse[0] = NULLCHAR;
2515 savingComment = TRUE;
2516 suppressKibitz = TRUE;
2518 } // [HGM] chat: end of patch
2520 if (appData.zippyTalk || appData.zippyPlay) {
2521 /* [DM] Backup address for color zippy lines */
2525 if (loggedOn == TRUE)
2526 if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
2527 (appData.zippyPlay && ZippyMatch(buf, &backup)));
2529 if (ZippyControl(buf, &i) ||
2530 ZippyConverse(buf, &i) ||
2531 (appData.zippyPlay && ZippyMatch(buf, &i))) {
2533 if (!appData.colorize) continue;
2537 } // [DM] 'else { ' deleted
2539 /* Regular tells and says */
2540 (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
2541 looking_at(buf, &i, "* (your partner) tells you: ") ||
2542 looking_at(buf, &i, "* says: ") ||
2543 /* Don't color "message" or "messages" output */
2544 (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
2545 looking_at(buf, &i, "*. * at *:*: ") ||
2546 looking_at(buf, &i, "--* (*:*): ") ||
2547 /* Message notifications (same color as tells) */
2548 looking_at(buf, &i, "* has left a message ") ||
2549 looking_at(buf, &i, "* just sent you a message:\n") ||
2550 /* Whispers and kibitzes */
2551 (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
2552 looking_at(buf, &i, "* kibitzes: ") ||
2554 (tkind = 3, looking_at(buf, &i, "*(*: "))) {
2556 if (tkind == 1 && strchr(star_match[0], ':')) {
2557 /* Avoid "tells you:" spoofs in channels */
2560 if (star_match[0][0] == NULLCHAR ||
2561 strchr(star_match[0], ' ') ||
2562 (tkind == 3 && strchr(star_match[1], ' '))) {
2563 /* Reject bogus matches */
2566 if (appData.colorize) {
2567 if (oldi > next_out) {
2568 SendToPlayer(&buf[next_out], oldi - next_out);
2573 Colorize(ColorTell, FALSE);
2574 curColor = ColorTell;
2577 Colorize(ColorKibitz, FALSE);
2578 curColor = ColorKibitz;
2581 p = strrchr(star_match[1], '(');
2588 Colorize(ColorChannel1, FALSE);
2589 curColor = ColorChannel1;
2591 Colorize(ColorChannel, FALSE);
2592 curColor = ColorChannel;
2596 curColor = ColorNormal;
2600 if (started == STARTED_NONE && appData.autoComment &&
2601 (gameMode == IcsObserving ||
2602 gameMode == IcsPlayingWhite ||
2603 gameMode == IcsPlayingBlack)) {
2604 parse_pos = i - oldi;
2605 memcpy(parse, &buf[oldi], parse_pos);
2606 parse[parse_pos] = NULLCHAR;
2607 started = STARTED_COMMENT;
2608 savingComment = TRUE;
2610 started = STARTED_CHATTER;
2611 savingComment = FALSE;
2618 if (looking_at(buf, &i, "* s-shouts: ") ||
2619 looking_at(buf, &i, "* c-shouts: ")) {
2620 if (appData.colorize) {
2621 if (oldi > next_out) {
2622 SendToPlayer(&buf[next_out], oldi - next_out);
2625 Colorize(ColorSShout, FALSE);
2626 curColor = ColorSShout;
2629 started = STARTED_CHATTER;
2633 if (looking_at(buf, &i, "--->")) {
2638 if (looking_at(buf, &i, "* shouts: ") ||
2639 looking_at(buf, &i, "--> ")) {
2640 if (appData.colorize) {
2641 if (oldi > next_out) {
2642 SendToPlayer(&buf[next_out], oldi - next_out);
2645 Colorize(ColorShout, FALSE);
2646 curColor = ColorShout;
2649 started = STARTED_CHATTER;
2653 if (looking_at( buf, &i, "Challenge:")) {
2654 if (appData.colorize) {
2655 if (oldi > next_out) {
2656 SendToPlayer(&buf[next_out], oldi - next_out);
2659 Colorize(ColorChallenge, FALSE);
2660 curColor = ColorChallenge;
2666 if (looking_at(buf, &i, "* offers you") ||
2667 looking_at(buf, &i, "* offers to be") ||
2668 looking_at(buf, &i, "* would like to") ||
2669 looking_at(buf, &i, "* requests to") ||
2670 looking_at(buf, &i, "Your opponent offers") ||
2671 looking_at(buf, &i, "Your opponent requests")) {
2673 if (appData.colorize) {
2674 if (oldi > next_out) {
2675 SendToPlayer(&buf[next_out], oldi - next_out);
2678 Colorize(ColorRequest, FALSE);
2679 curColor = ColorRequest;
2684 if (looking_at(buf, &i, "* (*) seeking")) {
2685 if (appData.colorize) {
2686 if (oldi > next_out) {
2687 SendToPlayer(&buf[next_out], oldi - next_out);
2690 Colorize(ColorSeek, FALSE);
2691 curColor = ColorSeek;
2696 if (looking_at(buf, &i, "\\ ")) {
2697 if (prevColor != ColorNormal) {
2698 if (oldi > next_out) {
2699 SendToPlayer(&buf[next_out], oldi - next_out);
2702 Colorize(prevColor, TRUE);
2703 curColor = prevColor;
2705 if (savingComment) {
2706 parse_pos = i - oldi;
2707 memcpy(parse, &buf[oldi], parse_pos);
2708 parse[parse_pos] = NULLCHAR;
2709 started = STARTED_COMMENT;
2711 started = STARTED_CHATTER;
2716 if (looking_at(buf, &i, "Black Strength :") ||
2717 looking_at(buf, &i, "<<< style 10 board >>>") ||
2718 looking_at(buf, &i, "<10>") ||
2719 looking_at(buf, &i, "#@#")) {
2720 /* Wrong board style */
2722 SendToICS(ics_prefix);
2723 SendToICS("set style 12\n");
2724 SendToICS(ics_prefix);
2725 SendToICS("refresh\n");
2729 if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
2731 have_sent_ICS_logon = 1;
2735 if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
2736 (looking_at(buf, &i, "\n<12> ") ||
2737 looking_at(buf, &i, "<12> "))) {
2739 if (oldi > next_out) {
2740 SendToPlayer(&buf[next_out], oldi - next_out);
2743 started = STARTED_BOARD;
2748 if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
2749 looking_at(buf, &i, "<b1> ")) {
2750 if (oldi > next_out) {
2751 SendToPlayer(&buf[next_out], oldi - next_out);
2754 started = STARTED_HOLDINGS;
2759 if (looking_at(buf, &i, "* *vs. * *--- *")) {
2761 /* Header for a move list -- first line */
2763 switch (ics_getting_history) {
2767 case BeginningOfGame:
2768 /* User typed "moves" or "oldmoves" while we
2769 were idle. Pretend we asked for these
2770 moves and soak them up so user can step
2771 through them and/or save them.
2774 gameMode = IcsObserving;
2777 ics_getting_history = H_GOT_UNREQ_HEADER;
2779 case EditGame: /*?*/
2780 case EditPosition: /*?*/
2781 /* Should above feature work in these modes too? */
2782 /* For now it doesn't */
2783 ics_getting_history = H_GOT_UNWANTED_HEADER;
2786 ics_getting_history = H_GOT_UNWANTED_HEADER;
2791 /* Is this the right one? */
2792 if (gameInfo.white && gameInfo.black &&
2793 strcmp(gameInfo.white, star_match[0]) == 0 &&
2794 strcmp(gameInfo.black, star_match[2]) == 0) {
2796 ics_getting_history = H_GOT_REQ_HEADER;
2799 case H_GOT_REQ_HEADER:
2800 case H_GOT_UNREQ_HEADER:
2801 case H_GOT_UNWANTED_HEADER:
2802 case H_GETTING_MOVES:
2803 /* Should not happen */
2804 DisplayError(_("Error gathering move list: two headers"), 0);
2805 ics_getting_history = H_FALSE;
2809 /* Save player ratings into gameInfo if needed */
2810 if ((ics_getting_history == H_GOT_REQ_HEADER ||
2811 ics_getting_history == H_GOT_UNREQ_HEADER) &&
2812 (gameInfo.whiteRating == -1 ||
2813 gameInfo.blackRating == -1)) {
2815 gameInfo.whiteRating = string_to_rating(star_match[1]);
2816 gameInfo.blackRating = string_to_rating(star_match[3]);
2817 if (appData.debugMode)
2818 fprintf(debugFP, _("Ratings from header: W %d, B %d\n"),
2819 gameInfo.whiteRating, gameInfo.blackRating);
2824 if (looking_at(buf, &i,
2825 "* * match, initial time: * minute*, increment: * second")) {
2826 /* Header for a move list -- second line */
2827 /* Initial board will follow if this is a wild game */
2828 if (gameInfo.event != NULL) free(gameInfo.event);
2829 sprintf(str, "ICS %s %s match", star_match[0], star_match[1]);
2830 gameInfo.event = StrSave(str);
2831 /* [HGM] we switched variant. Translate boards if needed. */
2832 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
2836 if (looking_at(buf, &i, "Move ")) {
2837 /* Beginning of a move list */
2838 switch (ics_getting_history) {
2840 /* Normally should not happen */
2841 /* Maybe user hit reset while we were parsing */
2844 /* Happens if we are ignoring a move list that is not
2845 * the one we just requested. Common if the user
2846 * tries to observe two games without turning off
2849 case H_GETTING_MOVES:
2850 /* Should not happen */
2851 DisplayError(_("Error gathering move list: nested"), 0);
2852 ics_getting_history = H_FALSE;
2854 case H_GOT_REQ_HEADER:
2855 ics_getting_history = H_GETTING_MOVES;
2856 started = STARTED_MOVES;
2858 if (oldi > next_out) {
2859 SendToPlayer(&buf[next_out], oldi - next_out);
2862 case H_GOT_UNREQ_HEADER:
2863 ics_getting_history = H_GETTING_MOVES;
2864 started = STARTED_MOVES_NOHIDE;
2867 case H_GOT_UNWANTED_HEADER:
2868 ics_getting_history = H_FALSE;
2874 if (looking_at(buf, &i, "% ") ||
2875 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
2876 && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
2877 if(suppressKibitz) next_out = i;
2878 savingComment = FALSE;
2882 case STARTED_MOVES_NOHIDE:
2883 memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
2884 parse[parse_pos + i - oldi] = NULLCHAR;
2885 ParseGameHistory(parse);
2887 if (appData.zippyPlay && first.initDone) {
2888 FeedMovesToProgram(&first, forwardMostMove);
2889 if (gameMode == IcsPlayingWhite) {
2890 if (WhiteOnMove(forwardMostMove)) {
2891 if (first.sendTime) {
2892 if (first.useColors) {
2893 SendToProgram("black\n", &first);
2895 SendTimeRemaining(&first, TRUE);
2897 if (first.useColors) {
2898 SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
2900 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
2901 first.maybeThinking = TRUE;
2903 if (first.usePlayother) {
2904 if (first.sendTime) {
2905 SendTimeRemaining(&first, TRUE);
2907 SendToProgram("playother\n", &first);
2913 } else if (gameMode == IcsPlayingBlack) {
2914 if (!WhiteOnMove(forwardMostMove)) {
2915 if (first.sendTime) {
2916 if (first.useColors) {
2917 SendToProgram("white\n", &first);
2919 SendTimeRemaining(&first, FALSE);
2921 if (first.useColors) {
2922 SendToProgram("black\n", &first);
2924 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
2925 first.maybeThinking = TRUE;
2927 if (first.usePlayother) {
2928 if (first.sendTime) {
2929 SendTimeRemaining(&first, FALSE);
2931 SendToProgram("playother\n", &first);
2940 if (gameMode == IcsObserving && ics_gamenum == -1) {
2941 /* Moves came from oldmoves or moves command
2942 while we weren't doing anything else.
2944 currentMove = forwardMostMove;
2945 ClearHighlights();/*!!could figure this out*/
2946 flipView = appData.flipView;
2947 DrawPosition(TRUE, boards[currentMove]);
2948 DisplayBothClocks();
2949 sprintf(str, "%s vs. %s",
2950 gameInfo.white, gameInfo.black);
2954 /* Moves were history of an active game */
2955 if (gameInfo.resultDetails != NULL) {
2956 free(gameInfo.resultDetails);
2957 gameInfo.resultDetails = NULL;
2960 HistorySet(parseList, backwardMostMove,
2961 forwardMostMove, currentMove-1);
2962 DisplayMove(currentMove - 1);
2963 if (started == STARTED_MOVES) next_out = i;
2964 started = STARTED_NONE;
2965 ics_getting_history = H_FALSE;
2968 case STARTED_OBSERVE:
2969 started = STARTED_NONE;
2970 SendToICS(ics_prefix);
2971 SendToICS("refresh\n");
2977 if(bookHit) { // [HGM] book: simulate book reply
2978 static char bookMove[MSG_SIZ]; // a bit generous?
2980 programStats.nodes = programStats.depth = programStats.time =
2981 programStats.score = programStats.got_only_move = 0;
2982 sprintf(programStats.movelist, "%s (xbook)", bookHit);
2984 strcpy(bookMove, "move ");
2985 strcat(bookMove, bookHit);
2986 HandleMachineMove(bookMove, &first);
2991 if ((started == STARTED_MOVES || started == STARTED_BOARD ||
2992 started == STARTED_HOLDINGS ||
2993 started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
2994 /* Accumulate characters in move list or board */
2995 parse[parse_pos++] = buf[i];
2998 /* Start of game messages. Mostly we detect start of game
2999 when the first board image arrives. On some versions
3000 of the ICS, though, we need to do a "refresh" after starting
3001 to observe in order to get the current board right away. */
3002 if (looking_at(buf, &i, "Adding game * to observation list")) {
3003 started = STARTED_OBSERVE;
3007 /* Handle auto-observe */
3008 if (appData.autoObserve &&
3009 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3010 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3012 /* Choose the player that was highlighted, if any. */
3013 if (star_match[0][0] == '\033' ||
3014 star_match[1][0] != '\033') {
3015 player = star_match[0];
3017 player = star_match[2];
3019 sprintf(str, "%sobserve %s\n",
3020 ics_prefix, StripHighlightAndTitle(player));
3023 /* Save ratings from notify string */
3024 strcpy(player1Name, star_match[0]);
3025 player1Rating = string_to_rating(star_match[1]);
3026 strcpy(player2Name, star_match[2]);
3027 player2Rating = string_to_rating(star_match[3]);
3029 if (appData.debugMode)
3031 "Ratings from 'Game notification:' %s %d, %s %d\n",
3032 player1Name, player1Rating,
3033 player2Name, player2Rating);
3038 /* Deal with automatic examine mode after a game,
3039 and with IcsObserving -> IcsExamining transition */
3040 if (looking_at(buf, &i, "Entering examine mode for game *") ||
3041 looking_at(buf, &i, "has made you an examiner of game *")) {
3043 int gamenum = atoi(star_match[0]);
3044 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3045 gamenum == ics_gamenum) {
3046 /* We were already playing or observing this game;
3047 no need to refetch history */
3048 gameMode = IcsExamining;
3050 pauseExamForwardMostMove = forwardMostMove;
3051 } else if (currentMove < forwardMostMove) {
3052 ForwardInner(forwardMostMove);
3055 /* I don't think this case really can happen */
3056 SendToICS(ics_prefix);
3057 SendToICS("refresh\n");
3062 /* Error messages */
3063 // if (ics_user_moved) {
3064 if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3065 if (looking_at(buf, &i, "Illegal move") ||
3066 looking_at(buf, &i, "Not a legal move") ||
3067 looking_at(buf, &i, "Your king is in check") ||
3068 looking_at(buf, &i, "It isn't your turn") ||
3069 looking_at(buf, &i, "It is not your move")) {
3071 if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3072 currentMove = --forwardMostMove;
3073 DisplayMove(currentMove - 1); /* before DMError */
3074 DrawPosition(FALSE, boards[currentMove]);
3076 DisplayBothClocks();
3078 DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3084 if (looking_at(buf, &i, "still have time") ||
3085 looking_at(buf, &i, "not out of time") ||
3086 looking_at(buf, &i, "either player is out of time") ||
3087 looking_at(buf, &i, "has timeseal; checking")) {
3088 /* We must have called his flag a little too soon */
3089 whiteFlag = blackFlag = FALSE;
3093 if (looking_at(buf, &i, "added * seconds to") ||
3094 looking_at(buf, &i, "seconds were added to")) {
3095 /* Update the clocks */
3096 SendToICS(ics_prefix);
3097 SendToICS("refresh\n");
3101 if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3102 ics_clock_paused = TRUE;
3107 if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3108 ics_clock_paused = FALSE;
3113 /* Grab player ratings from the Creating: message.
3114 Note we have to check for the special case when
3115 the ICS inserts things like [white] or [black]. */
3116 if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3117 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3119 0 player 1 name (not necessarily white)
3121 2 empty, white, or black (IGNORED)
3122 3 player 2 name (not necessarily black)
3125 The names/ratings are sorted out when the game
3126 actually starts (below).
3128 strcpy(player1Name, StripHighlightAndTitle(star_match[0]));
3129 player1Rating = string_to_rating(star_match[1]);
3130 strcpy(player2Name, StripHighlightAndTitle(star_match[3]));
3131 player2Rating = string_to_rating(star_match[4]);
3133 if (appData.debugMode)
3135 "Ratings from 'Creating:' %s %d, %s %d\n",
3136 player1Name, player1Rating,
3137 player2Name, player2Rating);
3142 /* Improved generic start/end-of-game messages */
3143 if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3144 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3145 /* If tkind == 0: */
3146 /* star_match[0] is the game number */
3147 /* [1] is the white player's name */
3148 /* [2] is the black player's name */
3149 /* For end-of-game: */
3150 /* [3] is the reason for the game end */
3151 /* [4] is a PGN end game-token, preceded by " " */
3152 /* For start-of-game: */
3153 /* [3] begins with "Creating" or "Continuing" */
3154 /* [4] is " *" or empty (don't care). */
3155 int gamenum = atoi(star_match[0]);
3156 char *whitename, *blackname, *why, *endtoken;
3157 ChessMove endtype = (ChessMove) 0;
3160 whitename = star_match[1];
3161 blackname = star_match[2];
3162 why = star_match[3];
3163 endtoken = star_match[4];
3165 whitename = star_match[1];
3166 blackname = star_match[3];
3167 why = star_match[5];
3168 endtoken = star_match[6];
3171 /* Game start messages */
3172 if (strncmp(why, "Creating ", 9) == 0 ||
3173 strncmp(why, "Continuing ", 11) == 0) {
3174 gs_gamenum = gamenum;
3175 strcpy(gs_kind, strchr(why, ' ') + 1);
3176 VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
3178 if (appData.zippyPlay) {
3179 ZippyGameStart(whitename, blackname);
3185 /* Game end messages */
3186 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3187 ics_gamenum != gamenum) {
3190 while (endtoken[0] == ' ') endtoken++;
3191 switch (endtoken[0]) {
3194 endtype = GameUnfinished;
3197 endtype = BlackWins;
3200 if (endtoken[1] == '/')
3201 endtype = GameIsDrawn;
3203 endtype = WhiteWins;
3206 GameEnds(endtype, why, GE_ICS);
3208 if (appData.zippyPlay && first.initDone) {
3209 ZippyGameEnd(endtype, why);
3210 if (first.pr == NULL) {
3211 /* Start the next process early so that we'll
3212 be ready for the next challenge */
3213 StartChessProgram(&first);
3215 /* Send "new" early, in case this command takes
3216 a long time to finish, so that we'll be ready
3217 for the next challenge. */
3218 gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3225 if (looking_at(buf, &i, "Removing game * from observation") ||
3226 looking_at(buf, &i, "no longer observing game *") ||
3227 looking_at(buf, &i, "Game * (*) has no examiners")) {
3228 if (gameMode == IcsObserving &&
3229 atoi(star_match[0]) == ics_gamenum)
3231 /* icsEngineAnalyze */
3232 if (appData.icsEngineAnalyze) {
3239 ics_user_moved = FALSE;
3244 if (looking_at(buf, &i, "no longer examining game *")) {
3245 if (gameMode == IcsExamining &&
3246 atoi(star_match[0]) == ics_gamenum)
3250 ics_user_moved = FALSE;
3255 /* Advance leftover_start past any newlines we find,
3256 so only partial lines can get reparsed */
3257 if (looking_at(buf, &i, "\n")) {
3258 prevColor = curColor;
3259 if (curColor != ColorNormal) {
3260 if (oldi > next_out) {
3261 SendToPlayer(&buf[next_out], oldi - next_out);
3264 Colorize(ColorNormal, FALSE);
3265 curColor = ColorNormal;
3267 if (started == STARTED_BOARD) {
3268 started = STARTED_NONE;
3269 parse[parse_pos] = NULLCHAR;
3270 ParseBoard12(parse);
3273 /* Send premove here */
3274 if (appData.premove) {
3276 if (currentMove == 0 &&
3277 gameMode == IcsPlayingWhite &&
3278 appData.premoveWhite) {
3279 sprintf(str, "%s\n", appData.premoveWhiteText);
3280 if (appData.debugMode)
3281 fprintf(debugFP, "Sending premove:\n");
3283 } else if (currentMove == 1 &&
3284 gameMode == IcsPlayingBlack &&
3285 appData.premoveBlack) {
3286 sprintf(str, "%s\n", appData.premoveBlackText);
3287 if (appData.debugMode)
3288 fprintf(debugFP, "Sending premove:\n");
3290 } else if (gotPremove) {
3292 ClearPremoveHighlights();
3293 if (appData.debugMode)
3294 fprintf(debugFP, "Sending premove:\n");
3295 UserMoveEvent(premoveFromX, premoveFromY,
3296 premoveToX, premoveToY,
3301 /* Usually suppress following prompt */
3302 if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
3303 while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
3304 if (looking_at(buf, &i, "*% ")) {
3305 savingComment = FALSE;
3310 } else if (started == STARTED_HOLDINGS) {
3312 char new_piece[MSG_SIZ];
3313 started = STARTED_NONE;
3314 parse[parse_pos] = NULLCHAR;
3315 if (appData.debugMode)
3316 fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
3317 parse, currentMove);
3318 if (sscanf(parse, " game %d", &gamenum) == 1 &&
3319 gamenum == ics_gamenum) {
3320 if (gameInfo.variant == VariantNormal) {
3321 /* [HGM] We seem to switch variant during a game!
3322 * Presumably no holdings were displayed, so we have
3323 * to move the position two files to the right to
3324 * create room for them!
3326 VariantClass newVariant;
3327 switch(gameInfo.boardWidth) { // base guess on board width
3328 case 9: newVariant = VariantShogi; break;
3329 case 10: newVariant = VariantGreat; break;
3330 default: newVariant = VariantCrazyhouse; break;
3332 VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3333 /* Get a move list just to see the header, which
3334 will tell us whether this is really bug or zh */
3335 if (ics_getting_history == H_FALSE) {
3336 ics_getting_history = H_REQUESTED;
3337 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3341 new_piece[0] = NULLCHAR;
3342 sscanf(parse, "game %d white [%s black [%s <- %s",
3343 &gamenum, white_holding, black_holding,
3345 white_holding[strlen(white_holding)-1] = NULLCHAR;
3346 black_holding[strlen(black_holding)-1] = NULLCHAR;
3347 /* [HGM] copy holdings to board holdings area */
3348 CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
3349 CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
3350 boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
3352 if (appData.zippyPlay && first.initDone) {
3353 ZippyHoldings(white_holding, black_holding,
3357 if (tinyLayout || smallLayout) {
3358 char wh[16], bh[16];
3359 PackHolding(wh, white_holding);
3360 PackHolding(bh, black_holding);
3361 sprintf(str, "[%s-%s] %s-%s", wh, bh,
3362 gameInfo.white, gameInfo.black);
3364 sprintf(str, "%s [%s] vs. %s [%s]",
3365 gameInfo.white, white_holding,
3366 gameInfo.black, black_holding);
3369 DrawPosition(FALSE, boards[currentMove]);
3372 /* Suppress following prompt */
3373 if (looking_at(buf, &i, "*% ")) {
3374 if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
3375 savingComment = FALSE;
3383 i++; /* skip unparsed character and loop back */
3386 if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
3387 // started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
3388 // SendToPlayer(&buf[next_out], i - next_out);
3389 started != STARTED_HOLDINGS && leftover_start > next_out) {
3390 SendToPlayer(&buf[next_out], leftover_start - next_out);
3394 leftover_len = buf_len - leftover_start;
3395 /* if buffer ends with something we couldn't parse,
3396 reparse it after appending the next read */
3398 } else if (count == 0) {
3399 RemoveInputSource(isr);
3400 DisplayFatalError(_("Connection closed by ICS"), 0, 0);
3402 DisplayFatalError(_("Error reading from ICS"), error, 1);
3407 /* Board style 12 looks like this:
3409 <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
3411 * The "<12> " is stripped before it gets to this routine. The two
3412 * trailing 0's (flip state and clock ticking) are later addition, and
3413 * some chess servers may not have them, or may have only the first.
3414 * Additional trailing fields may be added in the future.
3417 #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"
3419 #define RELATION_OBSERVING_PLAYED 0
3420 #define RELATION_OBSERVING_STATIC -2 /* examined, oldmoves, or smoves */
3421 #define RELATION_PLAYING_MYMOVE 1
3422 #define RELATION_PLAYING_NOTMYMOVE -1
3423 #define RELATION_EXAMINING 2
3424 #define RELATION_ISOLATED_BOARD -3
3425 #define RELATION_STARTING_POSITION -4 /* FICS only */
3428 ParseBoard12(string)
3431 GameMode newGameMode;
3432 int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
3433 int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
3434 int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
3435 char to_play, board_chars[200];
3436 char move_str[500], str[500], elapsed_time[500];
3437 char black[32], white[32];
3439 int prevMove = currentMove;
3442 int fromX, fromY, toX, toY;
3444 int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
3445 char *bookHit = NULL; // [HGM] book
3446 Boolean weird = FALSE, reqFlag = FALSE;
3448 fromX = fromY = toX = toY = -1;
3452 if (appData.debugMode)
3453 fprintf(debugFP, _("Parsing board: %s\n"), string);
3455 move_str[0] = NULLCHAR;
3456 elapsed_time[0] = NULLCHAR;
3457 { /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
3459 while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
3460 if(string[i] == ' ') { ranks++; files = 0; }
3462 if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
3465 for(j = 0; j <i; j++) board_chars[j] = string[j];
3466 board_chars[i] = '\0';
3469 n = sscanf(string, PATTERN, &to_play, &double_push,
3470 &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
3471 &gamenum, white, black, &relation, &basetime, &increment,
3472 &white_stren, &black_stren, &white_time, &black_time,
3473 &moveNum, str, elapsed_time, move_str, &ics_flip,
3477 snprintf(str, sizeof(str), _("Failed to parse board string:\n\"%s\""), string);
3478 DisplayError(str, 0);
3482 /* Convert the move number to internal form */
3483 moveNum = (moveNum - 1) * 2;
3484 if (to_play == 'B') moveNum++;
3485 if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
3486 DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
3492 case RELATION_OBSERVING_PLAYED:
3493 case RELATION_OBSERVING_STATIC:
3494 if (gamenum == -1) {
3495 /* Old ICC buglet */
3496 relation = RELATION_OBSERVING_STATIC;
3498 newGameMode = IcsObserving;
3500 case RELATION_PLAYING_MYMOVE:
3501 case RELATION_PLAYING_NOTMYMOVE:
3503 ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
3504 IcsPlayingWhite : IcsPlayingBlack;
3506 case RELATION_EXAMINING:
3507 newGameMode = IcsExamining;
3509 case RELATION_ISOLATED_BOARD:
3511 /* Just display this board. If user was doing something else,
3512 we will forget about it until the next board comes. */
3513 newGameMode = IcsIdle;
3515 case RELATION_STARTING_POSITION:
3516 newGameMode = gameMode;
3520 /* Modify behavior for initial board display on move listing
3523 switch (ics_getting_history) {
3527 case H_GOT_REQ_HEADER:
3528 case H_GOT_UNREQ_HEADER:
3529 /* This is the initial position of the current game */
3530 gamenum = ics_gamenum;
3531 moveNum = 0; /* old ICS bug workaround */
3532 if (to_play == 'B') {
3533 startedFromSetupPosition = TRUE;
3534 blackPlaysFirst = TRUE;
3536 if (forwardMostMove == 0) forwardMostMove = 1;
3537 if (backwardMostMove == 0) backwardMostMove = 1;
3538 if (currentMove == 0) currentMove = 1;
3540 newGameMode = gameMode;
3541 relation = RELATION_STARTING_POSITION; /* ICC needs this */
3543 case H_GOT_UNWANTED_HEADER:
3544 /* This is an initial board that we don't want */
3546 case H_GETTING_MOVES:
3547 /* Should not happen */
3548 DisplayError(_("Error gathering move list: extra board"), 0);
3549 ics_getting_history = H_FALSE;
3553 if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
3554 weird && (int)gameInfo.variant <= (int)VariantShogi) {
3555 /* [HGM] We seem to have switched variant unexpectedly
3556 * Try to guess new variant from board size
3558 VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
3559 if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
3560 if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
3561 if(ranks == 8 && files == 12) newVariant = VariantCourier; else
3562 if(ranks == 9 && files == 9) newVariant = VariantShogi; else
3563 if(!weird) newVariant = VariantNormal;
3564 VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3565 /* Get a move list just to see the header, which
3566 will tell us whether this is really bug or zh */
3567 if (ics_getting_history == H_FALSE) {
3568 ics_getting_history = H_REQUESTED; reqFlag = TRUE;
3569 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3574 /* Take action if this is the first board of a new game, or of a
3575 different game than is currently being displayed. */
3576 if (gamenum != ics_gamenum || newGameMode != gameMode ||
3577 relation == RELATION_ISOLATED_BOARD) {
3579 /* Forget the old game and get the history (if any) of the new one */
3580 if (gameMode != BeginningOfGame) {
3584 if (appData.autoRaiseBoard) BoardToTop();
3586 if (gamenum == -1) {
3587 newGameMode = IcsIdle;
3588 } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
3589 appData.getMoveList && !reqFlag) {
3590 /* Need to get game history */
3591 ics_getting_history = H_REQUESTED;
3592 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3596 /* Initially flip the board to have black on the bottom if playing
3597 black or if the ICS flip flag is set, but let the user change
3598 it with the Flip View button. */
3599 flipView = appData.autoFlipView ?
3600 (newGameMode == IcsPlayingBlack) || ics_flip :
3603 /* Done with values from previous mode; copy in new ones */
3604 gameMode = newGameMode;
3606 ics_gamenum = gamenum;
3607 if (gamenum == gs_gamenum) {
3608 int klen = strlen(gs_kind);
3609 if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
3610 sprintf(str, "ICS %s", gs_kind);
3611 gameInfo.event = StrSave(str);
3613 gameInfo.event = StrSave("ICS game");
3615 gameInfo.site = StrSave(appData.icsHost);
3616 gameInfo.date = PGNDate();
3617 gameInfo.round = StrSave("-");
3618 gameInfo.white = StrSave(white);
3619 gameInfo.black = StrSave(black);
3620 timeControl = basetime * 60 * 1000;
3622 timeIncrement = increment * 1000;
3623 movesPerSession = 0;
3624 gameInfo.timeControl = TimeControlTagValue();
3625 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
3626 if (appData.debugMode) {
3627 fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
3628 fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
3629 setbuf(debugFP, NULL);
3632 gameInfo.outOfBook = NULL;
3634 /* Do we have the ratings? */
3635 if (strcmp(player1Name, white) == 0 &&
3636 strcmp(player2Name, black) == 0) {
3637 if (appData.debugMode)
3638 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3639 player1Rating, player2Rating);
3640 gameInfo.whiteRating = player1Rating;
3641 gameInfo.blackRating = player2Rating;
3642 } else if (strcmp(player2Name, white) == 0 &&
3643 strcmp(player1Name, black) == 0) {
3644 if (appData.debugMode)
3645 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3646 player2Rating, player1Rating);
3647 gameInfo.whiteRating = player2Rating;
3648 gameInfo.blackRating = player1Rating;
3650 player1Name[0] = player2Name[0] = NULLCHAR;
3652 /* Silence shouts if requested */
3653 if (appData.quietPlay &&
3654 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
3655 SendToICS(ics_prefix);
3656 SendToICS("set shout 0\n");
3660 /* Deal with midgame name changes */
3662 if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
3663 if (gameInfo.white) free(gameInfo.white);
3664 gameInfo.white = StrSave(white);
3666 if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
3667 if (gameInfo.black) free(gameInfo.black);
3668 gameInfo.black = StrSave(black);
3672 /* Throw away game result if anything actually changes in examine mode */
3673 if (gameMode == IcsExamining && !newGame) {
3674 gameInfo.result = GameUnfinished;
3675 if (gameInfo.resultDetails != NULL) {
3676 free(gameInfo.resultDetails);
3677 gameInfo.resultDetails = NULL;
3681 /* In pausing && IcsExamining mode, we ignore boards coming
3682 in if they are in a different variation than we are. */
3683 if (pauseExamInvalid) return;
3684 if (pausing && gameMode == IcsExamining) {
3685 if (moveNum <= pauseExamForwardMostMove) {
3686 pauseExamInvalid = TRUE;
3687 forwardMostMove = pauseExamForwardMostMove;
3692 if (appData.debugMode) {
3693 fprintf(debugFP, "load %dx%d board\n", files, ranks);
3695 /* Parse the board */
3696 for (k = 0; k < ranks; k++) {
3697 for (j = 0; j < files; j++)
3698 board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
3699 if(gameInfo.holdingsWidth > 1) {
3700 board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
3701 board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
3704 CopyBoard(boards[moveNum], board);
3705 boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
3707 startedFromSetupPosition =
3708 !CompareBoards(board, initialPosition);
3709 if(startedFromSetupPosition)
3710 initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
3713 /* [HGM] Set castling rights. Take the outermost Rooks,
3714 to make it also work for FRC opening positions. Note that board12
3715 is really defective for later FRC positions, as it has no way to
3716 indicate which Rook can castle if they are on the same side of King.
3717 For the initial position we grant rights to the outermost Rooks,
3718 and remember thos rights, and we then copy them on positions
3719 later in an FRC game. This means WB might not recognize castlings with
3720 Rooks that have moved back to their original position as illegal,
3721 but in ICS mode that is not its job anyway.
3723 if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
3724 { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
3726 for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
3727 if(board[0][i] == WhiteRook) j = i;
3728 initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
3729 for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
3730 if(board[0][i] == WhiteRook) j = i;
3731 initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
3732 for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
3733 if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
3734 initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
3735 for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
3736 if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
3737 initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
3739 if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
3740 for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
3741 if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
3742 for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
3743 if(board[BOARD_HEIGHT-1][k] == bKing)
3744 initialRights[5] = boards[moveNum][CASTLING][5] = k;
3745 if(gameInfo.variant == VariantTwoKings) {
3746 // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
3747 if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
3748 if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
3751 r = boards[moveNum][CASTLING][0] = initialRights[0];
3752 if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
3753 r = boards[moveNum][CASTLING][1] = initialRights[1];
3754 if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
3755 r = boards[moveNum][CASTLING][3] = initialRights[3];
3756 if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
3757 r = boards[moveNum][CASTLING][4] = initialRights[4];
3758 if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
3759 /* wildcastle kludge: always assume King has rights */
3760 r = boards[moveNum][CASTLING][2] = initialRights[2];
3761 r = boards[moveNum][CASTLING][5] = initialRights[5];
3763 /* [HGM] e.p. rights. Assume that ICS sends file number here? */
3764 boards[moveNum][EP_STATUS] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
3767 if (ics_getting_history == H_GOT_REQ_HEADER ||
3768 ics_getting_history == H_GOT_UNREQ_HEADER) {
3769 /* This was an initial position from a move list, not
3770 the current position */
3774 /* Update currentMove and known move number limits */
3775 newMove = newGame || moveNum > forwardMostMove;
3778 forwardMostMove = backwardMostMove = currentMove = moveNum;
3779 if (gameMode == IcsExamining && moveNum == 0) {
3780 /* Workaround for ICS limitation: we are not told the wild
3781 type when starting to examine a game. But if we ask for
3782 the move list, the move list header will tell us */
3783 ics_getting_history = H_REQUESTED;
3784 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3787 } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
3788 || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
3790 /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
3791 /* [HGM] applied this also to an engine that is silently watching */
3792 if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
3793 (gameMode == IcsObserving || gameMode == IcsExamining) &&
3794 gameInfo.variant == currentlyInitializedVariant) {
3795 takeback = forwardMostMove - moveNum;
3796 for (i = 0; i < takeback; i++) {
3797 if (appData.debugMode) fprintf(debugFP, "take back move\n");
3798 SendToProgram("undo\n", &first);
3803 forwardMostMove = moveNum;
3804 if (!pausing || currentMove > forwardMostMove)
3805 currentMove = forwardMostMove;
3807 /* New part of history that is not contiguous with old part */
3808 if (pausing && gameMode == IcsExamining) {
3809 pauseExamInvalid = TRUE;
3810 forwardMostMove = pauseExamForwardMostMove;
3813 if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
3815 if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
3816 // [HGM] when we will receive the move list we now request, it will be
3817 // fed to the engine from the first move on. So if the engine is not
3818 // in the initial position now, bring it there.
3819 InitChessProgram(&first, 0);
3822 ics_getting_history = H_REQUESTED;
3823 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3826 forwardMostMove = backwardMostMove = currentMove = moveNum;
3829 /* Update the clocks */
3830 if (strchr(elapsed_time, '.')) {
3832 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
3833 timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
3835 /* Time is in seconds */
3836 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
3837 timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
3842 if (appData.zippyPlay && newGame &&
3843 gameMode != IcsObserving && gameMode != IcsIdle &&
3844 gameMode != IcsExamining)
3845 ZippyFirstBoard(moveNum, basetime, increment);
3848 /* Put the move on the move list, first converting
3849 to canonical algebraic form. */
3851 if (appData.debugMode) {
3852 if (appData.debugMode) { int f = forwardMostMove;
3853 fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
3854 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
3855 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
3857 fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
3858 fprintf(debugFP, "moveNum = %d\n", moveNum);
3859 fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
3860 setbuf(debugFP, NULL);
3862 if (moveNum <= backwardMostMove) {
3863 /* We don't know what the board looked like before
3865 strcpy(parseList[moveNum - 1], move_str);
3866 strcat(parseList[moveNum - 1], " ");
3867 strcat(parseList[moveNum - 1], elapsed_time);
3868 moveList[moveNum - 1][0] = NULLCHAR;
3869 } else if (strcmp(move_str, "none") == 0) {
3870 // [HGM] long SAN: swapped order; test for 'none' before parsing move
3871 /* Again, we don't know what the board looked like;
3872 this is really the start of the game. */
3873 parseList[moveNum - 1][0] = NULLCHAR;
3874 moveList[moveNum - 1][0] = NULLCHAR;
3875 backwardMostMove = moveNum;
3876 startedFromSetupPosition = TRUE;
3877 fromX = fromY = toX = toY = -1;
3879 // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
3880 // So we parse the long-algebraic move string in stead of the SAN move
3881 int valid; char buf[MSG_SIZ], *prom;
3883 // str looks something like "Q/a1-a2"; kill the slash
3885 sprintf(buf, "%c%s", str[0], str+2);
3886 else strcpy(buf, str); // might be castling
3887 if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
3888 strcat(buf, prom); // long move lacks promo specification!
3889 if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
3890 if(appData.debugMode)
3891 fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
3892 strcpy(move_str, buf);
3894 valid = ParseOneMove(move_str, moveNum - 1, &moveType,
3895 &fromX, &fromY, &toX, &toY, &promoChar)
3896 || ParseOneMove(buf, moveNum - 1, &moveType,
3897 &fromX, &fromY, &toX, &toY, &promoChar);
3898 // end of long SAN patch
3900 (void) CoordsToAlgebraic(boards[moveNum - 1],
3901 PosFlags(moveNum - 1),
3902 fromY, fromX, toY, toX, promoChar,
3903 parseList[moveNum-1]);
3904 switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
3910 if(gameInfo.variant != VariantShogi)
3911 strcat(parseList[moveNum - 1], "+");
3914 case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
3915 strcat(parseList[moveNum - 1], "#");
3918 strcat(parseList[moveNum - 1], " ");
3919 strcat(parseList[moveNum - 1], elapsed_time);
3920 /* currentMoveString is set as a side-effect of ParseOneMove */
3921 strcpy(moveList[moveNum - 1], currentMoveString);
3922 strcat(moveList[moveNum - 1], "\n");
3924 /* Move from ICS was illegal!? Punt. */
3925 if (appData.debugMode) {
3926 fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
3927 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
3929 strcpy(parseList[moveNum - 1], move_str);
3930 strcat(parseList[moveNum - 1], " ");
3931 strcat(parseList[moveNum - 1], elapsed_time);
3932 moveList[moveNum - 1][0] = NULLCHAR;
3933 fromX = fromY = toX = toY = -1;
3936 if (appData.debugMode) {
3937 fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
3938 setbuf(debugFP, NULL);
3942 /* Send move to chess program (BEFORE animating it). */
3943 if (appData.zippyPlay && !newGame && newMove &&
3944 (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
3946 if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
3947 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
3948 if (moveList[moveNum - 1][0] == NULLCHAR) {
3949 sprintf(str, _("Couldn't parse move \"%s\" from ICS"),
3951 DisplayError(str, 0);
3953 if (first.sendTime) {
3954 SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
3956 bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
3957 if (firstMove && !bookHit) {
3959 if (first.useColors) {
3960 SendToProgram(gameMode == IcsPlayingWhite ?
3962 "black\ngo\n", &first);
3964 SendToProgram("go\n", &first);
3966 first.maybeThinking = TRUE;
3969 } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
3970 if (moveList[moveNum - 1][0] == NULLCHAR) {
3971 sprintf(str, _("Couldn't parse move \"%s\" from ICS"), move_str);
3972 DisplayError(str, 0);
3974 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
3975 SendMoveToProgram(moveNum - 1, &first);
3982 if (moveNum > 0 && !gotPremove && !appData.noGUI) {
3983 /* If move comes from a remote source, animate it. If it
3984 isn't remote, it will have already been animated. */
3985 if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
3986 AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
3988 if (!pausing && appData.highlightLastMove) {
3989 SetHighlights(fromX, fromY, toX, toY);
3993 /* Start the clocks */
3994 whiteFlag = blackFlag = FALSE;
3995 appData.clockMode = !(basetime == 0 && increment == 0);
3997 ics_clock_paused = TRUE;
3999 } else if (ticking == 1) {
4000 ics_clock_paused = FALSE;
4002 if (gameMode == IcsIdle ||
4003 relation == RELATION_OBSERVING_STATIC ||
4004 relation == RELATION_EXAMINING ||
4006 DisplayBothClocks();
4010 /* Display opponents and material strengths */
4011 if (gameInfo.variant != VariantBughouse &&
4012 gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4013 if (tinyLayout || smallLayout) {
4014 if(gameInfo.variant == VariantNormal)
4015 sprintf(str, "%s(%d) %s(%d) {%d %d}",
4016 gameInfo.white, white_stren, gameInfo.black, black_stren,
4017 basetime, increment);
4019 sprintf(str, "%s(%d) %s(%d) {%d %d w%d}",
4020 gameInfo.white, white_stren, gameInfo.black, black_stren,
4021 basetime, increment, (int) gameInfo.variant);
4023 if(gameInfo.variant == VariantNormal)
4024 sprintf(str, "%s (%d) vs. %s (%d) {%d %d}",
4025 gameInfo.white, white_stren, gameInfo.black, black_stren,
4026 basetime, increment);
4028 sprintf(str, "%s (%d) vs. %s (%d) {%d %d %s}",
4029 gameInfo.white, white_stren, gameInfo.black, black_stren,
4030 basetime, increment, VariantName(gameInfo.variant));
4033 if (appData.debugMode) {
4034 fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
4039 /* Display the board */
4040 if (!pausing && !appData.noGUI) {
4042 if (appData.premove)
4044 ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
4045 ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
4046 ClearPremoveHighlights();
4048 DrawPosition(FALSE, boards[currentMove]);
4049 DisplayMove(moveNum - 1);
4050 if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4051 !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4052 (gameMode == IcsPlayingBlack) && (WhiteOnMove(moveNum)) ) ) {
4053 if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4057 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4059 if(bookHit) { // [HGM] book: simulate book reply
4060 static char bookMove[MSG_SIZ]; // a bit generous?
4062 programStats.nodes = programStats.depth = programStats.time =
4063 programStats.score = programStats.got_only_move = 0;
4064 sprintf(programStats.movelist, "%s (xbook)", bookHit);
4066 strcpy(bookMove, "move ");
4067 strcat(bookMove, bookHit);
4068 HandleMachineMove(bookMove, &first);
4077 if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4078 ics_getting_history = H_REQUESTED;
4079 sprintf(buf, "%smoves %d\n", ics_prefix, ics_gamenum);
4085 AnalysisPeriodicEvent(force)
4088 if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4089 && !force) || !appData.periodicUpdates)
4092 /* Send . command to Crafty to collect stats */
4093 SendToProgram(".\n", &first);
4095 /* Don't send another until we get a response (this makes
4096 us stop sending to old Crafty's which don't understand
4097 the "." command (sending illegal cmds resets node count & time,
4098 which looks bad)) */
4099 programStats.ok_to_send = 0;
4102 void ics_update_width(new_width)
4105 ics_printf("set width %d\n", new_width);
4109 SendMoveToProgram(moveNum, cps)
4111 ChessProgramState *cps;
4115 if (cps->useUsermove) {
4116 SendToProgram("usermove ", cps);
4120 if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4121 int len = space - parseList[moveNum];
4122 memcpy(buf, parseList[moveNum], len);
4124 buf[len] = NULLCHAR;
4126 sprintf(buf, "%s\n", parseList[moveNum]);
4128 SendToProgram(buf, cps);
4130 if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4131 AlphaRank(moveList[moveNum], 4);
4132 SendToProgram(moveList[moveNum], cps);
4133 AlphaRank(moveList[moveNum], 4); // and back
4135 /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
4136 * the engine. It would be nice to have a better way to identify castle
4138 if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
4139 && cps->useOOCastle) {
4140 int fromX = moveList[moveNum][0] - AAA;
4141 int fromY = moveList[moveNum][1] - ONE;
4142 int toX = moveList[moveNum][2] - AAA;
4143 int toY = moveList[moveNum][3] - ONE;
4144 if((boards[moveNum][fromY][fromX] == WhiteKing
4145 && boards[moveNum][toY][toX] == WhiteRook)
4146 || (boards[moveNum][fromY][fromX] == BlackKing
4147 && boards[moveNum][toY][toX] == BlackRook)) {
4148 if(toX > fromX) SendToProgram("O-O\n", cps);
4149 else SendToProgram("O-O-O\n", cps);
4151 else SendToProgram(moveList[moveNum], cps);
4153 else SendToProgram(moveList[moveNum], cps);
4154 /* End of additions by Tord */
4157 /* [HGM] setting up the opening has brought engine in force mode! */
4158 /* Send 'go' if we are in a mode where machine should play. */
4159 if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4160 (gameMode == TwoMachinesPlay ||
4162 gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite ||
4164 gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4165 SendToProgram("go\n", cps);
4166 if (appData.debugMode) {
4167 fprintf(debugFP, "(extra)\n");
4170 setboardSpoiledMachineBlack = 0;
4174 SendMoveToICS(moveType, fromX, fromY, toX, toY)
4176 int fromX, fromY, toX, toY;
4178 char user_move[MSG_SIZ];
4182 sprintf(user_move, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4183 (int)moveType, fromX, fromY, toX, toY);
4184 DisplayError(user_move + strlen("say "), 0);
4186 case WhiteKingSideCastle:
4187 case BlackKingSideCastle:
4188 case WhiteQueenSideCastleWild:
4189 case BlackQueenSideCastleWild:
4191 case WhiteHSideCastleFR:
4192 case BlackHSideCastleFR:
4194 sprintf(user_move, "o-o\n");
4196 case WhiteQueenSideCastle:
4197 case BlackQueenSideCastle:
4198 case WhiteKingSideCastleWild:
4199 case BlackKingSideCastleWild:
4201 case WhiteASideCastleFR:
4202 case BlackASideCastleFR:
4204 sprintf(user_move, "o-o-o\n");
4206 case WhitePromotionQueen:
4207 case BlackPromotionQueen:
4208 case WhitePromotionRook:
4209 case BlackPromotionRook:
4210 case WhitePromotionBishop:
4211 case BlackPromotionBishop:
4212 case WhitePromotionKnight:
4213 case BlackPromotionKnight:
4214 case WhitePromotionKing:
4215 case BlackPromotionKing:
4216 case WhitePromotionChancellor:
4217 case BlackPromotionChancellor:
4218 case WhitePromotionArchbishop:
4219 case BlackPromotionArchbishop:
4220 if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
4221 sprintf(user_move, "%c%c%c%c=%c\n",
4222 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4223 PieceToChar(WhiteFerz));
4224 else if(gameInfo.variant == VariantGreat)
4225 sprintf(user_move, "%c%c%c%c=%c\n",
4226 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4227 PieceToChar(WhiteMan));
4229 sprintf(user_move, "%c%c%c%c=%c\n",
4230 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4231 PieceToChar(PromoPiece(moveType)));
4235 sprintf(user_move, "%c@%c%c\n",
4236 ToUpper(PieceToChar((ChessSquare) fromX)),
4237 AAA + toX, ONE + toY);
4240 case WhiteCapturesEnPassant:
4241 case BlackCapturesEnPassant:
4242 case IllegalMove: /* could be a variant we don't quite understand */
4243 sprintf(user_move, "%c%c%c%c\n",
4244 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4247 SendToICS(user_move);
4248 if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
4249 ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
4253 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
4258 if (rf == DROP_RANK) {
4259 sprintf(move, "%c@%c%c\n",
4260 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
4262 if (promoChar == 'x' || promoChar == NULLCHAR) {
4263 sprintf(move, "%c%c%c%c\n",
4264 AAA + ff, ONE + rf, AAA + ft, ONE + rt);
4266 sprintf(move, "%c%c%c%c%c\n",
4267 AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
4273 ProcessICSInitScript(f)
4278 while (fgets(buf, MSG_SIZ, f)) {
4279 SendToICSDelayed(buf,(long)appData.msLoginDelay);
4286 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
4288 AlphaRank(char *move, int n)
4290 // char *p = move, c; int x, y;
4292 if (appData.debugMode) {
4293 fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
4297 move[2]>='0' && move[2]<='9' &&
4298 move[3]>='a' && move[3]<='x' ) {
4300 move[2] = BOARD_RGHT -1 - (move[2]-'1') + AAA;
4301 move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4303 if(move[0]>='0' && move[0]<='9' &&
4304 move[1]>='a' && move[1]<='x' &&
4305 move[2]>='0' && move[2]<='9' &&
4306 move[3]>='a' && move[3]<='x' ) {
4307 /* input move, Shogi -> normal */
4308 move[0] = BOARD_RGHT -1 - (move[0]-'1') + AAA;
4309 move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
4310 move[2] = BOARD_RGHT -1 - (move[2]-'1') + AAA;
4311 move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4314 move[3]>='0' && move[3]<='9' &&
4315 move[2]>='a' && move[2]<='x' ) {
4317 move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4318 move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4321 move[0]>='a' && move[0]<='x' &&
4322 move[3]>='0' && move[3]<='9' &&
4323 move[2]>='a' && move[2]<='x' ) {
4324 /* output move, normal -> Shogi */
4325 move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
4326 move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
4327 move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4328 move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4329 if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
4331 if (appData.debugMode) {
4332 fprintf(debugFP, " out = '%s'\n", move);
4336 /* Parser for moves from gnuchess, ICS, or user typein box */
4338 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
4341 ChessMove *moveType;
4342 int *fromX, *fromY, *toX, *toY;
4345 if (appData.debugMode) {
4346 fprintf(debugFP, "move to parse: %s\n", move);
4348 *moveType = yylexstr(moveNum, move);
4350 switch (*moveType) {
4351 case WhitePromotionChancellor:
4352 case BlackPromotionChancellor:
4353 case WhitePromotionArchbishop:
4354 case BlackPromotionArchbishop:
4355 case WhitePromotionQueen:
4356 case BlackPromotionQueen:
4357 case WhitePromotionRook:
4358 case BlackPromotionRook:
4359 case WhitePromotionBishop:
4360 case BlackPromotionBishop:
4361 case WhitePromotionKnight:
4362 case BlackPromotionKnight:
4363 case WhitePromotionKing:
4364 case BlackPromotionKing:
4366 case WhiteCapturesEnPassant:
4367 case BlackCapturesEnPassant:
4368 case WhiteKingSideCastle:
4369 case WhiteQueenSideCastle:
4370 case BlackKingSideCastle:
4371 case BlackQueenSideCastle:
4372 case WhiteKingSideCastleWild:
4373 case WhiteQueenSideCastleWild:
4374 case BlackKingSideCastleWild:
4375 case BlackQueenSideCastleWild:
4376 /* Code added by Tord: */
4377 case WhiteHSideCastleFR:
4378 case WhiteASideCastleFR:
4379 case BlackHSideCastleFR:
4380 case BlackASideCastleFR:
4381 /* End of code added by Tord */
4382 case IllegalMove: /* bug or odd chess variant */
4383 *fromX = currentMoveString[0] - AAA;
4384 *fromY = currentMoveString[1] - ONE;
4385 *toX = currentMoveString[2] - AAA;
4386 *toY = currentMoveString[3] - ONE;
4387 *promoChar = currentMoveString[4];
4388 if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
4389 *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
4390 if (appData.debugMode) {
4391 fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
4393 *fromX = *fromY = *toX = *toY = 0;
4396 if (appData.testLegality) {
4397 return (*moveType != IllegalMove);
4399 return !(*fromX == *toX && *fromY == *toY) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
4400 WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
4405 *fromX = *moveType == WhiteDrop ?
4406 (int) CharToPiece(ToUpper(currentMoveString[0])) :
4407 (int) CharToPiece(ToLower(currentMoveString[0]));
4409 *toX = currentMoveString[2] - AAA;
4410 *toY = currentMoveString[3] - ONE;
4411 *promoChar = NULLCHAR;
4415 case ImpossibleMove:
4416 case (ChessMove) 0: /* end of file */
4425 if (appData.debugMode) {
4426 fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
4429 *fromX = *fromY = *toX = *toY = 0;
4430 *promoChar = NULLCHAR;
4438 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
4439 int fromX, fromY, toX, toY; char promoChar;
4444 endPV = forwardMostMove;
4446 while(*pv == ' ') pv++;
4447 if(*pv == '(') pv++; // first (ponder) move can be in parentheses
4448 valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
4449 if(appData.debugMode){
4450 fprintf(debugFP,"parsePV: %d %c%c%c%c '%s'\n", valid, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, pv);
4452 if(!valid && nr == 0 &&
4453 ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
4454 nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
4456 while(*pv && *pv++ != ' '); // skip what we parsed; assume space separators
4457 if(moveType == Comment) { valid++; continue; } // allow comments in PV
4459 if(endPV+1 > framePtr) break; // no space, truncate
4462 CopyBoard(boards[endPV], boards[endPV-1]);
4463 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
4464 moveList[endPV-1][0] = fromX + AAA;
4465 moveList[endPV-1][1] = fromY + ONE;
4466 moveList[endPV-1][2] = toX + AAA;
4467 moveList[endPV-1][3] = toY + ONE;
4468 parseList[endPV-1][0] = NULLCHAR;
4470 currentMove = endPV;
4471 if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
4472 SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
4473 moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
4474 DrawPosition(TRUE, boards[currentMove]);
4477 static int lastX, lastY;
4480 LoadMultiPV(int x, int y, char *buf, int index, int *start, int *end)
4484 if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
4485 lastX = x; lastY = y;
4486 while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
4488 while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
4490 while(buf[index] && buf[index] != '\n') index++;
4492 ParsePV(buf+startPV);
4493 *start = startPV; *end = index-1;
4498 LoadPV(int x, int y)
4499 { // called on right mouse click to load PV
4500 int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
4501 lastX = x; lastY = y;
4502 ParsePV(lastPV[which]); // load the PV of the thinking engine in the boards array.
4509 if(endPV < 0) return;
4511 currentMove = forwardMostMove;
4512 ClearPremoveHighlights();
4513 DrawPosition(TRUE, boards[currentMove]);
4517 MovePV(int x, int y, int h)
4518 { // step through PV based on mouse coordinates (called on mouse move)
4519 int margin = h>>3, step = 0;
4521 if(endPV < 0) return;
4522 // we must somehow check if right button is still down (might be released off board!)
4523 if(y < margin && (abs(x - lastX) > 6 || abs(y - lastY) > 6)) step = 1; else
4524 if(y > h - margin && (abs(x - lastX) > 6 || abs(y - lastY) > 6)) step = -1; else
4525 if( y > lastY + 6 ) step = -1; else if(y < lastY - 6) step = 1;
4527 lastX = x; lastY = y;
4528 if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
4529 currentMove += step;
4530 if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
4531 SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
4532 moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
4533 DrawPosition(FALSE, boards[currentMove]);
4537 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
4538 // All positions will have equal probability, but the current method will not provide a unique
4539 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
4545 int piecesLeft[(int)BlackPawn];
4546 int seed, nrOfShuffles;
4548 void GetPositionNumber()
4549 { // sets global variable seed
4552 seed = appData.defaultFrcPosition;
4553 if(seed < 0) { // randomize based on time for negative FRC position numbers
4554 for(i=0; i<50; i++) seed += random();
4555 seed = random() ^ random() >> 8 ^ random() << 8;
4556 if(seed<0) seed = -seed;
4560 int put(Board board, int pieceType, int rank, int n, int shade)
4561 // put the piece on the (n-1)-th empty squares of the given shade
4565 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
4566 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
4567 board[rank][i] = (ChessSquare) pieceType;
4568 squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
4570 piecesLeft[pieceType]--;
4578 void AddOnePiece(Board board, int pieceType, int rank, int shade)
4579 // calculate where the next piece goes, (any empty square), and put it there
4583 i = seed % squaresLeft[shade];
4584 nrOfShuffles *= squaresLeft[shade];
4585 seed /= squaresLeft[shade];
4586 put(board, pieceType, rank, i, shade);
4589 void AddTwoPieces(Board board, int pieceType, int rank)
4590 // calculate where the next 2 identical pieces go, (any empty square), and put it there
4592 int i, n=squaresLeft[ANY], j=n-1, k;
4594 k = n*(n-1)/2; // nr of possibilities, not counting permutations
4595 i = seed % k; // pick one
4598 while(i >= j) i -= j--;
4599 j = n - 1 - j; i += j;
4600 put(board, pieceType, rank, j, ANY);
4601 put(board, pieceType, rank, i, ANY);
4604 void SetUpShuffle(Board board, int number)
4608 GetPositionNumber(); nrOfShuffles = 1;
4610 squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
4611 squaresLeft[ANY] = BOARD_RGHT - BOARD_LEFT;
4612 squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
4614 for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
4616 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
4617 p = (int) board[0][i];
4618 if(p < (int) BlackPawn) piecesLeft[p] ++;
4619 board[0][i] = EmptySquare;
4622 if(PosFlags(0) & F_ALL_CASTLE_OK) {
4623 // shuffles restricted to allow normal castling put KRR first
4624 if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
4625 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
4626 else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
4627 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
4628 if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
4629 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
4630 if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
4631 put(board, WhiteRook, 0, 0, ANY);
4632 // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
4635 if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
4636 // only for even boards make effort to put pairs of colorbound pieces on opposite colors
4637 for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
4638 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
4639 while(piecesLeft[p] >= 2) {
4640 AddOnePiece(board, p, 0, LITE);
4641 AddOnePiece(board, p, 0, DARK);
4643 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
4646 for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
4647 // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
4648 // but we leave King and Rooks for last, to possibly obey FRC restriction
4649 if(p == (int)WhiteRook) continue;
4650 while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
4651 if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY); // add the odd piece
4654 // now everything is placed, except perhaps King (Unicorn) and Rooks
4656 if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
4657 // Last King gets castling rights
4658 while(piecesLeft[(int)WhiteUnicorn]) {
4659 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
4660 initialRights[2] = initialRights[5] = board[CASTLING][2] = board[CASTLING][5] = i;
4663 while(piecesLeft[(int)WhiteKing]) {
4664 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
4665 initialRights[2] = initialRights[5] = board[CASTLING][2] = board[CASTLING][5] = i;
4670 while(piecesLeft[(int)WhiteKing]) AddOnePiece(board, WhiteKing, 0, ANY);
4671 while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
4674 // Only Rooks can be left; simply place them all
4675 while(piecesLeft[(int)WhiteRook]) {
4676 i = put(board, WhiteRook, 0, 0, ANY);
4677 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
4680 initialRights[1] = initialRights[4] = board[CASTLING][1] = board[CASTLING][4] = i;
4682 initialRights[0] = initialRights[3] = board[CASTLING][0] = board[CASTLING][3] = i;
4685 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
4686 board[BOARD_HEIGHT-1][i] = (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
4689 if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
4692 int SetCharTable( char *table, const char * map )
4693 /* [HGM] moved here from winboard.c because of its general usefulness */
4694 /* Basically a safe strcpy that uses the last character as King */
4696 int result = FALSE; int NrPieces;
4698 if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
4699 && NrPieces >= 12 && !(NrPieces&1)) {
4700 int i; /* [HGM] Accept even length from 12 to 34 */
4702 for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
4703 for( i=0; i<NrPieces/2-1; i++ ) {
4705 table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
4707 table[(int) WhiteKing] = map[NrPieces/2-1];
4708 table[(int) BlackKing] = map[NrPieces-1];
4716 void Prelude(Board board)
4717 { // [HGM] superchess: random selection of exo-pieces
4718 int i, j, k; ChessSquare p;
4719 static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
4721 GetPositionNumber(); // use FRC position number
4723 if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
4724 SetCharTable(pieceToChar, appData.pieceToCharTable);
4725 for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
4726 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
4729 j = seed%4; seed /= 4;
4730 p = board[0][BOARD_LEFT+j]; board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
4731 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
4732 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
4733 j = seed%3 + (seed%3 >= j); seed /= 3;
4734 p = board[0][BOARD_LEFT+j]; board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
4735 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
4736 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
4737 j = seed%3; seed /= 3;
4738 p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
4739 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
4740 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
4741 j = seed%2 + (seed%2 >= j); seed /= 2;
4742 p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
4743 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
4744 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
4745 j = seed%4; seed /= 4; put(board, exoPieces[3], 0, j, ANY);
4746 j = seed%3; seed /= 3; put(board, exoPieces[2], 0, j, ANY);
4747 j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
4748 put(board, exoPieces[0], 0, 0, ANY);
4749 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
4753 InitPosition(redraw)
4756 ChessSquare (* pieces)[BOARD_FILES];
4757 int i, j, pawnRow, overrule,
4758 oldx = gameInfo.boardWidth,
4759 oldy = gameInfo.boardHeight,
4760 oldh = gameInfo.holdingsWidth,
4761 oldv = gameInfo.variant;
4763 if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
4765 /* [AS] Initialize pv info list [HGM] and game status */
4767 for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
4768 pvInfoList[i].depth = 0;
4769 boards[i][EP_STATUS] = EP_NONE;
4770 for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
4773 initialRulePlies = 0; /* 50-move counter start */
4775 castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
4776 castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
4780 /* [HGM] logic here is completely changed. In stead of full positions */
4781 /* the initialized data only consist of the two backranks. The switch */
4782 /* selects which one we will use, which is than copied to the Board */
4783 /* initialPosition, which for the rest is initialized by Pawns and */
4784 /* empty squares. This initial position is then copied to boards[0], */
4785 /* possibly after shuffling, so that it remains available. */
4787 gameInfo.holdingsWidth = 0; /* default board sizes */
4788 gameInfo.boardWidth = 8;
4789 gameInfo.boardHeight = 8;
4790 gameInfo.holdingsSize = 0;
4791 nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
4792 for(i=0; i<BOARD_FILES-2; i++)
4793 initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
4794 initialPosition[EP_STATUS] = EP_NONE;
4795 SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
4797 switch (gameInfo.variant) {
4798 case VariantFischeRandom:
4799 shuffleOpenings = TRUE;
4803 case VariantShatranj:
4804 pieces = ShatranjArray;
4805 nrCastlingRights = 0;
4806 SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
4809 pieces = makrukArray;
4810 nrCastlingRights = 0;
4811 startedFromSetupPosition = TRUE;
4812 SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
4814 case VariantTwoKings:
4815 pieces = twoKingsArray;
4817 case VariantCapaRandom:
4818 shuffleOpenings = TRUE;
4819 case VariantCapablanca:
4820 pieces = CapablancaArray;
4821 gameInfo.boardWidth = 10;
4822 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
4825 pieces = GothicArray;
4826 gameInfo.boardWidth = 10;
4827 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
4830 pieces = JanusArray;
4831 gameInfo.boardWidth = 10;
4832 SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
4833 nrCastlingRights = 6;
4834 initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
4835 initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
4836 initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
4837 initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
4838 initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
4839 initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
4842 pieces = FalconArray;
4843 gameInfo.boardWidth = 10;
4844 SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
4846 case VariantXiangqi:
4847 pieces = XiangqiArray;
4848 gameInfo.boardWidth = 9;
4849 gameInfo.boardHeight = 10;
4850 nrCastlingRights = 0;
4851 SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
4854 pieces = ShogiArray;
4855 gameInfo.boardWidth = 9;
4856 gameInfo.boardHeight = 9;
4857 gameInfo.holdingsSize = 7;
4858 nrCastlingRights = 0;
4859 SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
4861 case VariantCourier:
4862 pieces = CourierArray;
4863 gameInfo.boardWidth = 12;
4864 nrCastlingRights = 0;
4865 SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
4867 case VariantKnightmate:
4868 pieces = KnightmateArray;
4869 SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
4872 pieces = fairyArray;
4873 SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
4876 pieces = GreatArray;
4877 gameInfo.boardWidth = 10;
4878 SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
4879 gameInfo.holdingsSize = 8;
4883 SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
4884 gameInfo.holdingsSize = 8;
4885 startedFromSetupPosition = TRUE;
4887 case VariantCrazyhouse:
4888 case VariantBughouse:
4890 SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
4891 gameInfo.holdingsSize = 5;
4893 case VariantWildCastle:
4895 /* !!?shuffle with kings guaranteed to be on d or e file */
4896 shuffleOpenings = 1;
4898 case VariantNoCastle:
4900 nrCastlingRights = 0;
4901 /* !!?unconstrained back-rank shuffle */
4902 shuffleOpenings = 1;
4907 if(appData.NrFiles >= 0) {
4908 if(gameInfo.boardWidth != appData.NrFiles) overrule++;
4909 gameInfo.boardWidth = appData.NrFiles;
4911 if(appData.NrRanks >= 0) {
4912 gameInfo.boardHeight = appData.NrRanks;
4914 if(appData.holdingsSize >= 0) {
4915 i = appData.holdingsSize;
4916 if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
4917 gameInfo.holdingsSize = i;
4919 if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
4920 if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
4921 DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
4923 pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
4924 if(pawnRow < 1) pawnRow = 1;
4925 if(gameInfo.variant == VariantMakruk) pawnRow = 2;
4927 /* User pieceToChar list overrules defaults */
4928 if(appData.pieceToCharTable != NULL)
4929 SetCharTable(pieceToChar, appData.pieceToCharTable);
4931 for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
4933 if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
4934 s = (ChessSquare) 0; /* account holding counts in guard band */
4935 for( i=0; i<BOARD_HEIGHT; i++ )
4936 initialPosition[i][j] = s;
4938 if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
4939 initialPosition[0][j] = pieces[0][j-gameInfo.holdingsWidth];
4940 initialPosition[pawnRow][j] = WhitePawn;
4941 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = BlackPawn;
4942 if(gameInfo.variant == VariantXiangqi) {
4944 initialPosition[pawnRow][j] =
4945 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
4946 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
4947 initialPosition[2][j] = WhiteCannon;
4948 initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
4952 initialPosition[BOARD_HEIGHT-1][j] = pieces[1][j-gameInfo.holdingsWidth];
4954 if( (gameInfo.variant == VariantShogi) && !overrule ) {
4957 initialPosition[1][j] = WhiteBishop;
4958 initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
4960 initialPosition[1][j] = WhiteRook;
4961 initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
4964 if( nrCastlingRights == -1) {
4965 /* [HGM] Build normal castling rights (must be done after board sizing!) */
4966 /* This sets default castling rights from none to normal corners */
4967 /* Variants with other castling rights must set them themselves above */
4968 nrCastlingRights = 6;
4970 initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
4971 initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
4972 initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
4973 initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
4974 initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
4975 initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
4978 if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
4979 if(gameInfo.variant == VariantGreat) { // promotion commoners
4980 initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
4981 initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
4982 initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
4983 initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
4985 if (appData.debugMode) {
4986 fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
4988 if(shuffleOpenings) {
4989 SetUpShuffle(initialPosition, appData.defaultFrcPosition);
4990 startedFromSetupPosition = TRUE;
4992 if(startedFromPositionFile) {
4993 /* [HGM] loadPos: use PositionFile for every new game */
4994 CopyBoard(initialPosition, filePosition);
4995 for(i=0; i<nrCastlingRights; i++)
4996 initialRights[i] = filePosition[CASTLING][i];
4997 startedFromSetupPosition = TRUE;
5000 CopyBoard(boards[0], initialPosition);
5002 if(oldx != gameInfo.boardWidth ||
5003 oldy != gameInfo.boardHeight ||
5004 oldh != gameInfo.holdingsWidth
5006 || oldv == VariantGothic || // For licensing popups
5007 gameInfo.variant == VariantGothic
5010 || oldv == VariantFalcon ||
5011 gameInfo.variant == VariantFalcon
5014 InitDrawingSizes(-2 ,0);
5017 DrawPosition(TRUE, boards[currentMove]);
5021 SendBoard(cps, moveNum)
5022 ChessProgramState *cps;
5025 char message[MSG_SIZ];
5027 if (cps->useSetboard) {
5028 char* fen = PositionToFEN(moveNum, cps->fenOverride);
5029 sprintf(message, "setboard %s\n", fen);
5030 SendToProgram(message, cps);
5036 /* Kludge to set black to move, avoiding the troublesome and now
5037 * deprecated "black" command.
5039 if (!WhiteOnMove(moveNum)) SendToProgram("a2a3\n", cps);
5041 SendToProgram("edit\n", cps);
5042 SendToProgram("#\n", cps);
5043 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
5044 bp = &boards[moveNum][i][BOARD_LEFT];
5045 for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
5046 if ((int) *bp < (int) BlackPawn) {
5047 sprintf(message, "%c%c%c\n", PieceToChar(*bp),
5049 if(message[0] == '+' || message[0] == '~') {
5050 sprintf(message, "%c%c%c+\n",
5051 PieceToChar((ChessSquare)(DEMOTED *bp)),
5054 if(cps->alphaRank) { /* [HGM] shogi: translate coords */
5055 message[1] = BOARD_RGHT - 1 - j + '1';
5056 message[2] = BOARD_HEIGHT - 1 - i + 'a';
5058 SendToProgram(message, cps);
5063 SendToProgram("c\n", cps);
5064 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
5065 bp = &boards[moveNum][i][BOARD_LEFT];
5066 for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
5067 if (((int) *bp != (int) EmptySquare)
5068 && ((int) *bp >= (int) BlackPawn)) {
5069 sprintf(message, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
5071 if(message[0] == '+' || message[0] == '~') {
5072 sprintf(message, "%c%c%c+\n",
5073 PieceToChar((ChessSquare)(DEMOTED *bp)),
5076 if(cps->alphaRank) { /* [HGM] shogi: translate coords */
5077 message[1] = BOARD_RGHT - 1 - j + '1';
5078 message[2] = BOARD_HEIGHT - 1 - i + 'a';
5080 SendToProgram(message, cps);
5085 SendToProgram(".\n", cps);
5087 setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
5091 HasPromotionChoice(int fromX, int fromY, int toX, int toY, char *promoChoice)
5093 /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
5094 /* [HGM] add Shogi promotions */
5095 int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
5100 if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
5101 if(toX < BOARD_LEFT || toX >= BOARD_RGHT) return FALSE; // move into holdings
5103 if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
5104 !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
5107 piece = boards[currentMove][fromY][fromX];
5108 if(gameInfo.variant == VariantShogi) {
5109 promotionZoneSize = 3;
5110 highestPromotingPiece = (int)WhiteFerz;
5111 } else if(gameInfo.variant == VariantMakruk) {
5112 promotionZoneSize = 3;
5115 // next weed out all moves that do not touch the promotion zone at all
5116 if((int)piece >= BlackPawn) {
5117 if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
5119 highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
5121 if( toY < BOARD_HEIGHT - promotionZoneSize &&
5122 fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
5125 if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
5127 // weed out mandatory Shogi promotions
5128 if(gameInfo.variant == VariantShogi) {
5129 if(piece >= BlackPawn) {
5130 if(toY == 0 && piece == BlackPawn ||
5131 toY == 0 && piece == BlackQueen ||
5132 toY <= 1 && piece == BlackKnight) {
5137 if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
5138 toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
5139 toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
5146 // weed out obviously illegal Pawn moves
5147 if(appData.testLegality && (piece == WhitePawn || piece == BlackPawn) ) {
5148 if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
5149 if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
5150 if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
5151 if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
5152 // note we are not allowed to test for valid (non-)capture, due to premove
5155 // we either have a choice what to promote to, or (in Shogi) whether to promote
5156 if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk) {
5157 *promoChoice = PieceToChar(BlackFerz); // no choice
5160 if(appData.alwaysPromoteToQueen) { // predetermined
5161 if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantLosers)
5162 *promoChoice = PieceToChar(BlackKing); // in Suicide Q is the last thing we want
5163 else *promoChoice = PieceToChar(BlackQueen);
5167 // suppress promotion popup on illegal moves that are not premoves
5168 premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
5169 gameMode == IcsPlayingBlack && WhiteOnMove(currentMove);
5170 if(appData.testLegality && !premove) {
5171 moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5172 fromY, fromX, toY, toX, NULLCHAR);
5173 if(moveType != WhitePromotionQueen && moveType != BlackPromotionQueen &&
5174 moveType != WhitePromotionKnight && moveType != BlackPromotionKnight)
5182 InPalace(row, column)
5184 { /* [HGM] for Xiangqi */
5185 if( (row < 3 || row > BOARD_HEIGHT-4) &&
5186 column < (BOARD_WIDTH + 4)/2 &&
5187 column > (BOARD_WIDTH - 5)/2 ) return TRUE;
5192 PieceForSquare (x, y)
5196 if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
5199 return boards[currentMove][y][x];
5203 OKToStartUserMove(x, y)
5206 ChessSquare from_piece;
5209 if (matchMode) return FALSE;
5210 if (gameMode == EditPosition) return TRUE;
5212 if (x >= 0 && y >= 0)
5213 from_piece = boards[currentMove][y][x];
5215 from_piece = EmptySquare;
5217 if (from_piece == EmptySquare) return FALSE;
5219 white_piece = (int)from_piece >= (int)WhitePawn &&
5220 (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
5223 case PlayFromGameFile:
5225 case TwoMachinesPlay:
5233 case MachinePlaysWhite:
5234 case IcsPlayingBlack:
5235 if (appData.zippyPlay) return FALSE;
5237 DisplayMoveError(_("You are playing Black"));
5242 case MachinePlaysBlack:
5243 case IcsPlayingWhite:
5244 if (appData.zippyPlay) return FALSE;
5246 DisplayMoveError(_("You are playing White"));
5252 if (!white_piece && WhiteOnMove(currentMove)) {
5253 DisplayMoveError(_("It is White's turn"));
5256 if (white_piece && !WhiteOnMove(currentMove)) {
5257 DisplayMoveError(_("It is Black's turn"));
5260 if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
5261 /* Editing correspondence game history */
5262 /* Could disallow this or prompt for confirmation */
5267 case BeginningOfGame:
5268 if (appData.icsActive) return FALSE;
5269 if (!appData.noChessProgram) {
5271 DisplayMoveError(_("You are playing White"));
5278 if (!white_piece && WhiteOnMove(currentMove)) {
5279 DisplayMoveError(_("It is White's turn"));
5282 if (white_piece && !WhiteOnMove(currentMove)) {
5283 DisplayMoveError(_("It is Black's turn"));
5292 if (currentMove != forwardMostMove && gameMode != AnalyzeMode
5293 && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
5294 && gameMode != AnalyzeFile && gameMode != Training) {
5295 DisplayMoveError(_("Displayed position is not current"));
5301 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
5302 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
5303 int lastLoadGameUseList = FALSE;
5304 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
5305 ChessMove lastLoadGameStart = (ChessMove) 0;
5308 UserMoveTest(fromX, fromY, toX, toY, promoChar, captureOwn)
5309 int fromX, fromY, toX, toY;
5314 ChessSquare pdown, pup;
5316 /* Check if the user is playing in turn. This is complicated because we
5317 let the user "pick up" a piece before it is his turn. So the piece he
5318 tried to pick up may have been captured by the time he puts it down!
5319 Therefore we use the color the user is supposed to be playing in this
5320 test, not the color of the piece that is currently on the starting
5321 square---except in EditGame mode, where the user is playing both
5322 sides; fortunately there the capture race can't happen. (It can
5323 now happen in IcsExamining mode, but that's just too bad. The user
5324 will get a somewhat confusing message in that case.)
5328 case PlayFromGameFile:
5330 case TwoMachinesPlay:
5334 /* We switched into a game mode where moves are not accepted,
5335 perhaps while the mouse button was down. */
5336 return ImpossibleMove;
5338 case MachinePlaysWhite:
5339 /* User is moving for Black */
5340 if (WhiteOnMove(currentMove)) {
5341 DisplayMoveError(_("It is White's turn"));
5342 return ImpossibleMove;
5346 case MachinePlaysBlack:
5347 /* User is moving for White */
5348 if (!WhiteOnMove(currentMove)) {
5349 DisplayMoveError(_("It is Black's turn"));
5350 return ImpossibleMove;
5356 case BeginningOfGame:
5359 if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
5360 (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
5361 /* User is moving for Black */
5362 if (WhiteOnMove(currentMove)) {
5363 DisplayMoveError(_("It is White's turn"));
5364 return ImpossibleMove;
5367 /* User is moving for White */
5368 if (!WhiteOnMove(currentMove)) {
5369 DisplayMoveError(_("It is Black's turn"));
5370 return ImpossibleMove;
5375 case IcsPlayingBlack:
5376 /* User is moving for Black */
5377 if (WhiteOnMove(currentMove)) {
5378 if (!appData.premove) {
5379 DisplayMoveError(_("It is White's turn"));
5380 } else if (toX >= 0 && toY >= 0) {
5383 premoveFromX = fromX;
5384 premoveFromY = fromY;
5385 premovePromoChar = promoChar;
5387 if (appData.debugMode)
5388 fprintf(debugFP, "Got premove: fromX %d,"
5389 "fromY %d, toX %d, toY %d\n",
5390 fromX, fromY, toX, toY);
5392 return ImpossibleMove;
5396 case IcsPlayingWhite:
5397 /* User is moving for White */
5398 if (!WhiteOnMove(currentMove)) {
5399 if (!appData.premove) {
5400 DisplayMoveError(_("It is Black's turn"));
5401 } else if (toX >= 0 && toY >= 0) {
5404 premoveFromX = fromX;
5405 premoveFromY = fromY;
5406 premovePromoChar = promoChar;
5408 if (appData.debugMode)
5409 fprintf(debugFP, "Got premove: fromX %d,"
5410 "fromY %d, toX %d, toY %d\n",
5411 fromX, fromY, toX, toY);
5413 return ImpossibleMove;
5421 /* EditPosition, empty square, or different color piece;
5422 click-click move is possible */
5423 if (toX == -2 || toY == -2) {
5424 boards[0][fromY][fromX] = EmptySquare;
5425 return AmbiguousMove;
5426 } else if (toX >= 0 && toY >= 0) {
5427 boards[0][toY][toX] = boards[0][fromY][fromX];
5428 if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
5429 if(boards[0][fromY][0] != EmptySquare) {
5430 if(boards[0][fromY][1]) boards[0][fromY][1]--;
5431 if(boards[0][fromY][1] == 0) boards[0][fromY][0] = EmptySquare;
5434 if(fromX == BOARD_RGHT+1) {
5435 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
5436 if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
5437 if(boards[0][fromY][BOARD_WIDTH-2] == 0) boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
5440 boards[0][fromY][fromX] = EmptySquare;
5441 return AmbiguousMove;
5443 return ImpossibleMove;
5446 if(toX < 0 || toY < 0) return ImpossibleMove;
5447 pdown = boards[currentMove][fromY][fromX];
5448 pup = boards[currentMove][toY][toX];
5450 /* [HGM] If move started in holdings, it means a drop */
5451 if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) {
5452 if( pup != EmptySquare ) return ImpossibleMove;
5453 if(appData.testLegality) {
5454 /* it would be more logical if LegalityTest() also figured out
5455 * which drops are legal. For now we forbid pawns on back rank.
5456 * Shogi is on its own here...
5458 if( (pdown == WhitePawn || pdown == BlackPawn) &&
5459 (toY == 0 || toY == BOARD_HEIGHT -1 ) )
5460 return(ImpossibleMove); /* no pawn drops on 1st/8th */
5462 return WhiteDrop; /* Not needed to specify white or black yet */
5465 /* [HGM] always test for legality, to get promotion info */
5466 moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5467 fromY, fromX, toY, toX, promoChar);
5468 /* [HGM] but possibly ignore an IllegalMove result */
5469 if (appData.testLegality) {
5470 if (moveType == IllegalMove || moveType == ImpossibleMove) {
5471 DisplayMoveError(_("Illegal move"));
5472 return ImpossibleMove;
5476 userOfferedDraw = FALSE; // [HGM] drawclaim: only if we do a legal move!
5479 /* [HGM] <popupFix> in stead of calling FinishMove directly, this
5480 function is made into one that returns an OK move type if FinishMove
5481 should be called. This to give the calling driver routine the
5482 opportunity to finish the userMove input with a promotion popup,
5483 without bothering the user with this for invalid or illegal moves */
5485 /* FinishMove(moveType, fromX, fromY, toX, toY, promoChar); */
5488 /* Common tail of UserMoveEvent and DropMenuEvent */
5490 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
5492 int fromX, fromY, toX, toY;
5493 /*char*/int promoChar;
5497 if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) && promoChar != NULLCHAR) {
5498 // [HGM] superchess: suppress promotions to non-available piece
5499 int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
5500 if(WhiteOnMove(currentMove)) {
5501 if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
5503 if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
5507 /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
5508 move type in caller when we know the move is a legal promotion */
5509 if(moveType == NormalMove && promoChar)
5510 moveType = PromoCharToMoveType(WhiteOnMove(currentMove), promoChar);
5512 /* [HGM] convert drag-and-drop piece drops to standard form */
5513 if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ){
5514 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
5515 if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n",
5516 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
5517 // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
5518 if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
5519 fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
5520 while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++;
5524 /* [HGM] <popupFix> The following if has been moved here from
5525 UserMoveEvent(). Because it seemed to belong here (why not allow
5526 piece drops in training games?), and because it can only be
5527 performed after it is known to what we promote. */
5528 if (gameMode == Training) {
5529 /* compare the move played on the board to the next move in the
5530 * game. If they match, display the move and the opponent's response.
5531 * If they don't match, display an error message.
5535 CopyBoard(testBoard, boards[currentMove]);
5536 ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
5538 if (CompareBoards(testBoard, boards[currentMove+1])) {
5539 ForwardInner(currentMove+1);
5541 /* Autoplay the opponent's response.
5542 * if appData.animate was TRUE when Training mode was entered,
5543 * the response will be animated.
5545 saveAnimate = appData.animate;
5546 appData.animate = animateTraining;
5547 ForwardInner(currentMove+1);
5548 appData.animate = saveAnimate;
5550 /* check for the end of the game */
5551 if (currentMove >= forwardMostMove) {
5552 gameMode = PlayFromGameFile;
5554 SetTrainingModeOff();
5555 DisplayInformation(_("End of game"));
5558 DisplayError(_("Incorrect move"), 0);
5563 /* Ok, now we know that the move is good, so we can kill
5564 the previous line in Analysis Mode */
5565 if ((gameMode == AnalyzeMode || gameMode == EditGame)
5566 && currentMove < forwardMostMove) {
5567 PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
5570 /* If we need the chess program but it's dead, restart it */
5571 ResurrectChessProgram();
5573 /* A user move restarts a paused game*/
5577 thinkOutput[0] = NULLCHAR;
5579 MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
5581 if(Adjudicate(NULL)) return 1; // [HGM] adjudicate: take care of automtic game end
5583 if (gameMode == BeginningOfGame) {
5584 if (appData.noChessProgram) {
5585 gameMode = EditGame;
5589 gameMode = MachinePlaysBlack;
5592 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
5594 if (first.sendName) {
5595 sprintf(buf, "name %s\n", gameInfo.white);
5596 SendToProgram(buf, &first);
5603 /* Relay move to ICS or chess engine */
5604 if (appData.icsActive) {
5605 if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
5606 gameMode == IcsExamining) {
5607 SendMoveToICS(moveType, fromX, fromY, toX, toY);
5611 if (first.sendTime && (gameMode == BeginningOfGame ||
5612 gameMode == MachinePlaysWhite ||
5613 gameMode == MachinePlaysBlack)) {
5614 SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
5616 if (gameMode != EditGame && gameMode != PlayFromGameFile) {
5617 // [HGM] book: if program might be playing, let it use book
5618 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
5619 first.maybeThinking = TRUE;
5620 } else SendMoveToProgram(forwardMostMove-1, &first);
5621 if (currentMove == cmailOldMove + 1) {
5622 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
5626 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5630 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
5636 if (WhiteOnMove(currentMove)) {
5637 GameEnds(BlackWins, "Black mates", GE_PLAYER);
5639 GameEnds(WhiteWins, "White mates", GE_PLAYER);
5643 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
5648 case MachinePlaysBlack:
5649 case MachinePlaysWhite:
5650 /* disable certain menu options while machine is thinking */
5651 SetMachineThinkingEnables();
5658 if(bookHit) { // [HGM] book: simulate book reply
5659 static char bookMove[MSG_SIZ]; // a bit generous?
5661 programStats.nodes = programStats.depth = programStats.time =
5662 programStats.score = programStats.got_only_move = 0;
5663 sprintf(programStats.movelist, "%s (xbook)", bookHit);
5665 strcpy(bookMove, "move ");
5666 strcat(bookMove, bookHit);
5667 HandleMachineMove(bookMove, &first);
5673 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
5674 int fromX, fromY, toX, toY;
5677 /* [HGM] This routine was added to allow calling of its two logical
5678 parts from other modules in the old way. Before, UserMoveEvent()
5679 automatically called FinishMove() if the move was OK, and returned
5680 otherwise. I separated the two, in order to make it possible to
5681 slip a promotion popup in between. But that it always needs two
5682 calls, to the first part, (now called UserMoveTest() ), and to
5683 FinishMove if the first part succeeded. Calls that do not need
5684 to do anything in between, can call this routine the old way.
5686 ChessMove moveType = UserMoveTest(fromX, fromY, toX, toY, promoChar, FALSE);
5687 if(appData.debugMode) fprintf(debugFP, "moveType 4 = %d, promochar = %x\n", moveType, promoChar);
5688 if(moveType == AmbiguousMove)
5689 DrawPosition(FALSE, boards[currentMove]);
5690 else if(moveType != ImpossibleMove && moveType != Comment)
5691 FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
5695 Mark(board, flags, kind, rf, ff, rt, ft, closure)
5702 typedef char Markers[BOARD_RANKS][BOARD_FILES];
5703 Markers *m = (Markers *) closure;
5704 if(rf == fromY && ff == fromX)
5705 (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
5706 || kind == WhiteCapturesEnPassant
5707 || kind == BlackCapturesEnPassant);
5708 else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
5712 MarkTargetSquares(int clear)
5715 if(!appData.markers || !appData.highlightDragging ||
5716 !appData.testLegality || gameMode == EditPosition) return;
5718 for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) marker[y][x] = 0;
5721 GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker);
5722 if(PosFlags(0) & F_MANDATORY_CAPTURE) {
5723 for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
5725 for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
5728 DrawPosition(TRUE, NULL);
5731 void LeftClick(ClickType clickType, int xPix, int yPix)
5734 Boolean saveAnimate;
5735 static int second = 0, promotionChoice = 0;
5736 char promoChoice = NULLCHAR;
5738 if (clickType == Press) ErrorPopDown();
5739 MarkTargetSquares(1);
5741 x = EventToSquare(xPix, BOARD_WIDTH);
5742 y = EventToSquare(yPix, BOARD_HEIGHT);
5743 if (!flipView && y >= 0) {
5744 y = BOARD_HEIGHT - 1 - y;
5746 if (flipView && x >= 0) {
5747 x = BOARD_WIDTH - 1 - x;
5750 if(promotionChoice) { // we are waiting for a click to indicate promotion piece
5751 if(clickType == Release) return; // ignore upclick of click-click destination
5752 promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
5753 if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
5754 if(gameInfo.holdingsWidth &&
5755 (WhiteOnMove(currentMove)
5756 ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y > 0
5757 : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT-1) ) {
5758 // click in right holdings, for determining promotion piece
5759 ChessSquare p = boards[currentMove][y][x];
5760 if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
5761 if(p != EmptySquare) {
5762 FinishMove(NormalMove, fromX, fromY, toX, toY, ToLower(PieceToChar(p)));
5767 DrawPosition(FALSE, boards[currentMove]);
5771 /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
5772 if(clickType == Press
5773 && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
5774 || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
5775 || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
5779 if (clickType == Press) {
5781 if (OKToStartUserMove(x, y)) {
5785 MarkTargetSquares(0);
5786 DragPieceBegin(xPix, yPix);
5787 if (appData.highlightDragging) {
5788 SetHighlights(x, y, -1, -1);
5796 if (clickType == Press && gameMode != EditPosition) {
5801 // ignore off-board to clicks
5802 if(y < 0 || x < 0) return;
5804 /* Check if clicking again on the same color piece */
5805 fromP = boards[currentMove][fromY][fromX];
5806 toP = boards[currentMove][y][x];
5807 frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom;
5808 if ((WhitePawn <= fromP && fromP <= WhiteKing &&
5809 WhitePawn <= toP && toP <= WhiteKing &&
5810 !(fromP == WhiteKing && toP == WhiteRook && frc) &&
5811 !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
5812 (BlackPawn <= fromP && fromP <= BlackKing &&
5813 BlackPawn <= toP && toP <= BlackKing &&
5814 !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
5815 !(fromP == BlackKing && toP == BlackRook && frc))) {
5816 /* Clicked again on same color piece -- changed his mind */
5817 second = (x == fromX && y == fromY);
5818 if (appData.highlightDragging) {
5819 SetHighlights(x, y, -1, -1);
5823 if (OKToStartUserMove(x, y)) {
5826 MarkTargetSquares(0);
5827 DragPieceBegin(xPix, yPix);
5831 // ignore clicks on holdings
5832 if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
5835 if (clickType == Release && x == fromX && y == fromY) {
5836 DragPieceEnd(xPix, yPix);
5837 if (appData.animateDragging) {
5838 /* Undo animation damage if any */
5839 DrawPosition(FALSE, NULL);
5842 /* Second up/down in same square; just abort move */
5847 ClearPremoveHighlights();
5849 /* First upclick in same square; start click-click mode */
5850 SetHighlights(x, y, -1, -1);
5855 /* we now have a different from- and (possibly off-board) to-square */
5856 /* Completed move */
5859 saveAnimate = appData.animate;
5860 if (clickType == Press) {
5861 /* Finish clickclick move */
5862 if (appData.animate || appData.highlightLastMove) {
5863 SetHighlights(fromX, fromY, toX, toY);
5868 /* Finish drag move */
5869 if (appData.highlightLastMove) {
5870 SetHighlights(fromX, fromY, toX, toY);
5874 DragPieceEnd(xPix, yPix);
5875 /* Don't animate move and drag both */
5876 appData.animate = FALSE;
5879 // moves into holding are invalid for now (except in EditPosition, adapting to-square)
5880 if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
5881 ChessSquare piece = boards[currentMove][fromY][fromX];
5882 if(gameMode == EditPosition && piece != EmptySquare &&
5883 fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
5886 if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
5887 n = PieceToNumber(piece - (int)BlackPawn);
5888 if(n > gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
5889 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
5890 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
5892 if(x == BOARD_RGHT+1 && piece < BlackPawn) {
5893 n = PieceToNumber(piece);
5894 if(n > gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
5895 boards[currentMove][n][BOARD_WIDTH-1] = piece;
5896 boards[currentMove][n][BOARD_WIDTH-2]++;
5898 boards[currentMove][fromY][fromX] = EmptySquare;
5902 DrawPosition(TRUE, boards[currentMove]);
5906 // off-board moves should not be highlighted
5907 if(x < 0 || x < 0) ClearHighlights();
5909 if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice)) {
5910 SetHighlights(fromX, fromY, toX, toY);
5911 if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
5912 // [HGM] super: promotion to captured piece selected from holdings
5913 ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
5914 promotionChoice = TRUE;
5915 // kludge follows to temporarily execute move on display, without promoting yet
5916 boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
5917 boards[currentMove][toY][toX] = p;
5918 DrawPosition(FALSE, boards[currentMove]);
5919 boards[currentMove][fromY][fromX] = p; // take back, but display stays
5920 boards[currentMove][toY][toX] = q;
5921 DisplayMessage("Click in holdings to choose piece", "");
5926 UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
5927 if (!appData.highlightLastMove || gotPremove) ClearHighlights();
5928 if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
5931 appData.animate = saveAnimate;
5932 if (appData.animate || appData.animateDragging) {
5933 /* Undo animation damage if needed */
5934 DrawPosition(FALSE, NULL);
5938 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
5940 // char * hint = lastHint;
5941 FrontEndProgramStats stats;
5943 stats.which = cps == &first ? 0 : 1;
5944 stats.depth = cpstats->depth;
5945 stats.nodes = cpstats->nodes;
5946 stats.score = cpstats->score;
5947 stats.time = cpstats->time;
5948 stats.pv = cpstats->movelist;
5949 stats.hint = lastHint;
5950 stats.an_move_index = 0;
5951 stats.an_move_count = 0;
5953 if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
5954 stats.hint = cpstats->move_name;
5955 stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
5956 stats.an_move_count = cpstats->nr_moves;
5959 if(stats.pv && stats.pv[0]) strcpy(lastPV[stats.which], stats.pv); // [HGM] pv: remember last PV of each
5961 SetProgramStats( &stats );
5965 Adjudicate(ChessProgramState *cps)
5966 { // [HGM] some adjudications useful with buggy engines
5967 // [HGM] adjudicate: made into separate routine, which now can be called after every move
5968 // In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
5969 // Actually ending the game is now based on the additional internal condition canAdjudicate.
5970 // Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
5971 int k, count = 0; static int bare = 1;
5972 ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
5973 Boolean canAdjudicate = !appData.icsActive;
5975 // most tests only when we understand the game, i.e. legality-checking on, and (for the time being) no piece drops
5976 if(gameInfo.holdingsSize == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
5977 if( appData.testLegality )
5978 { /* [HGM] Some more adjudications for obstinate engines */
5979 int NrWN=0, NrBN=0, NrWB=0, NrBB=0, NrWR=0, NrBR=0,
5980 NrWQ=0, NrBQ=0, NrW=0, NrK=0, bishopsColor = 0,
5981 NrPieces=0, NrPawns=0, PawnAdvance=0, i, j;
5982 static int moveCount = 6;
5984 char *reason = NULL;
5986 /* Count what is on board. */
5987 for(i=0; i<BOARD_HEIGHT; i++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
5988 { ChessSquare p = boards[forwardMostMove][i][j];
5992 { /* count B,N,R and other of each side */
5995 NrK++; break; // [HGM] atomic: count Kings
5999 case WhiteFerz: // [HGM] shatranj: kludge to mke it work in shatranj
6000 bishopsColor |= 1 << ((i^j)&1);
6005 case BlackFerz: // [HGM] shatranj: kludge to mke it work in shatranj
6006 bishopsColor |= 1 << ((i^j)&1);
6021 PawnAdvance += m; NrPawns++;
6023 NrPieces += (p != EmptySquare);
6024 NrW += ((int)p < (int)BlackPawn);
6025 if(gameInfo.variant == VariantXiangqi &&
6026 (p == WhiteFerz || p == WhiteAlfil || p == BlackFerz || p == BlackAlfil)) {
6027 NrPieces--; // [HGM] XQ: do not count purely defensive pieces
6028 NrW -= ((int)p < (int)BlackPawn);
6032 /* Some material-based adjudications that have to be made before stalemate test */
6033 if(gameInfo.variant == VariantAtomic && NrK < 2) {
6034 // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
6035 boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
6036 if(canAdjudicate && appData.checkMates) {
6038 SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
6039 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6040 GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
6041 "Xboard adjudication: King destroyed", GE_XBOARD );
6046 /* Bare King in Shatranj (loses) or Losers (wins) */
6047 if( NrW == 1 || NrPieces - NrW == 1) {
6048 if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
6049 boards[forwardMostMove][EP_STATUS] = EP_WINS; // mark as win, so it becomes claimable
6050 if(canAdjudicate && appData.checkMates) {
6052 SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
6053 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6054 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
6055 "Xboard adjudication: Bare king", GE_XBOARD );
6059 if( gameInfo.variant == VariantShatranj && --bare < 0)
6061 boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
6062 if(canAdjudicate && appData.checkMates) {
6063 /* but only adjudicate if adjudication enabled */
6065 SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
6066 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6067 GameEnds( NrW > 1 ? WhiteWins : NrPieces - NrW > 1 ? BlackWins : GameIsDrawn,
6068 "Xboard adjudication: Bare king", GE_XBOARD );
6075 // don't wait for engine to announce game end if we can judge ourselves
6076 switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
6078 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
6079 int i, checkCnt = 0; // (should really be done by making nr of checks part of game state)
6080 for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
6081 if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
6084 reason = "Xboard adjudication: 3rd check";
6085 boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
6095 reason = "Xboard adjudication: Stalemate";
6096 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
6097 boards[forwardMostMove][EP_STATUS] = EP_STALEMATE; // default result for stalemate is draw
6098 if(gameInfo.variant == VariantLosers || gameInfo.variant == VariantGiveaway) // [HGM] losers:
6099 boards[forwardMostMove][EP_STATUS] = EP_WINS; // in these variants stalemated is always a win
6100 else if(gameInfo.variant == VariantSuicide) // in suicide it depends
6101 boards[forwardMostMove][EP_STATUS] = NrW == NrPieces-NrW ? EP_STALEMATE :
6102 ((NrW < NrPieces-NrW) != WhiteOnMove(forwardMostMove) ?
6103 EP_CHECKMATE : EP_WINS);
6104 else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
6105 boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
6109 reason = "Xboard adjudication: Checkmate";
6110 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
6114 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
6116 result = GameIsDrawn; break;
6118 result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
6120 result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
6122 result = (ChessMove) 0;
6124 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
6126 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6127 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6128 GameEnds( result, reason, GE_XBOARD );
6132 /* Next absolutely insufficient mating material. */
6133 if( NrPieces == 2 || gameInfo.variant != VariantXiangqi &&
6134 gameInfo.variant != VariantShatranj && // [HGM] baring will remain possible
6135 (NrPieces == 3 && NrWN+NrBN+NrWB+NrBB == 1 ||
6136 NrPieces == NrBB+NrWB+2 && bishopsColor != 3)) // [HGM] all Bishops (Ferz!) same color
6137 { /* KBK, KNK, KK of KBKB with like Bishops */
6139 /* always flag draws, for judging claims */
6140 boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
6142 if(canAdjudicate && appData.materialDraws) {
6143 /* but only adjudicate them if adjudication enabled */
6144 if(engineOpponent) {
6145 SendToProgram("force\n", engineOpponent); // suppress reply
6146 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
6148 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6149 GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
6154 /* Then some trivial draws (only adjudicate, cannot be claimed) */
6156 ( NrWR == 1 && NrBR == 1 /* KRKR */
6157 || NrWQ==1 && NrBQ==1 /* KQKQ */
6158 || NrWN==2 || NrBN==2 /* KNNK */
6159 || NrWN+NrWB == 1 && NrBN+NrBB == 1 /* KBKN, KBKB, KNKN */
6161 if(canAdjudicate && --moveCount < 0 && appData.trivialDraws)
6162 { /* if the first 3 moves do not show a tactical win, declare draw */
6163 if(engineOpponent) {
6164 SendToProgram("force\n", engineOpponent); // suppress reply
6165 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6167 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6168 GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
6171 } else moveCount = 6;
6175 if (appData.debugMode) { int i;
6176 fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
6177 forwardMostMove, backwardMostMove, boards[backwardMostMove][EP_STATUS],
6178 appData.drawRepeats);
6179 for( i=forwardMostMove; i>=backwardMostMove; i-- )
6180 fprintf(debugFP, "%d ep=%d\n", i, (signed char)boards[i][EP_STATUS]);
6184 // Repetition draws and 50-move rule can be applied independently of legality testing
6186 /* Check for rep-draws */
6188 for(k = forwardMostMove-2;
6189 k>=backwardMostMove && k>=forwardMostMove-100 &&
6190 (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
6191 (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE;
6194 if(CompareBoards(boards[k], boards[forwardMostMove])) {
6195 /* compare castling rights */
6196 if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
6197 (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
6198 rights++; /* King lost rights, while rook still had them */
6199 if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
6200 if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
6201 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
6202 rights++; /* but at least one rook lost them */
6204 if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
6205 (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
6207 if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
6208 if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
6209 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
6212 if( canAdjudicate && rights == 0 && ++count > appData.drawRepeats-2
6213 && appData.drawRepeats > 1) {
6214 /* adjudicate after user-specified nr of repeats */
6215 if(engineOpponent) {
6216 SendToProgram("force\n", engineOpponent); // suppress reply
6217 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6219 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6220 if(gameInfo.variant == VariantXiangqi && appData.testLegality) {
6221 // [HGM] xiangqi: check for forbidden perpetuals
6222 int m, ourPerpetual = 1, hisPerpetual = 1;
6223 for(m=forwardMostMove; m>k; m-=2) {
6224 if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
6225 ourPerpetual = 0; // the current mover did not always check
6226 if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
6227 hisPerpetual = 0; // the opponent did not always check
6229 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
6230 ourPerpetual, hisPerpetual);
6231 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
6232 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
6233 "Xboard adjudication: perpetual checking", GE_XBOARD );
6236 if(hisPerpetual && !ourPerpetual) // he is checking us, but did not repeat yet
6237 break; // (or we would have caught him before). Abort repetition-checking loop.
6238 // Now check for perpetual chases
6239 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
6240 hisPerpetual = PerpetualChase(k, forwardMostMove);
6241 ourPerpetual = PerpetualChase(k+1, forwardMostMove);
6242 if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
6243 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
6244 "Xboard adjudication: perpetual chasing", GE_XBOARD );
6247 if(hisPerpetual && !ourPerpetual) // he is chasing us, but did not repeat yet
6248 break; // Abort repetition-checking loop.
6250 // if neither of us is checking or chasing all the time, or both are, it is draw
6252 GameEnds( GameIsDrawn, "Xboard adjudication: repetition draw", GE_XBOARD );
6255 if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
6256 boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
6260 /* Now we test for 50-move draws. Determine ply count */
6261 count = forwardMostMove;
6262 /* look for last irreversble move */
6263 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
6265 /* if we hit starting position, add initial plies */
6266 if( count == backwardMostMove )
6267 count -= initialRulePlies;
6268 count = forwardMostMove - count;
6270 boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
6271 /* this is used to judge if draw claims are legal */
6272 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
6273 if(engineOpponent) {
6274 SendToProgram("force\n", engineOpponent); // suppress reply
6275 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6277 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6278 GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
6282 /* if draw offer is pending, treat it as a draw claim
6283 * when draw condition present, to allow engines a way to
6284 * claim draws before making their move to avoid a race
6285 * condition occurring after their move
6287 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
6289 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
6290 p = "Draw claim: 50-move rule";
6291 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
6292 p = "Draw claim: 3-fold repetition";
6293 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
6294 p = "Draw claim: insufficient mating material";
6295 if( p != NULL && canAdjudicate) {
6296 if(engineOpponent) {
6297 SendToProgram("force\n", engineOpponent); // suppress reply
6298 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6300 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6301 GameEnds( GameIsDrawn, p, GE_XBOARD );
6306 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
6307 if(engineOpponent) {
6308 SendToProgram("force\n", engineOpponent); // suppress reply
6309 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6311 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6312 GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
6318 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
6319 { // [HGM] book: this routine intercepts moves to simulate book replies
6320 char *bookHit = NULL;
6322 //first determine if the incoming move brings opponent into his book
6323 if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
6324 bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
6325 if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
6326 if(bookHit != NULL && !cps->bookSuspend) {
6327 // make sure opponent is not going to reply after receiving move to book position
6328 SendToProgram("force\n", cps);
6329 cps->bookSuspend = TRUE; // flag indicating it has to be restarted
6331 if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
6332 // now arrange restart after book miss
6334 // after a book hit we never send 'go', and the code after the call to this routine
6335 // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
6337 if (cps->useUsermove) sprintf(buf, "usermove "); // sorry, no SAN yet :(
6338 sprintf(buf, "%s\n", bookHit); // force book move into program supposed to play it
6339 SendToProgram(buf, cps);
6340 if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
6341 } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
6342 SendToProgram("go\n", cps);
6343 cps->bookSuspend = FALSE; // after a 'go' we are never suspended
6344 } else { // 'go' might be sent based on 'firstMove' after this routine returns
6345 if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
6346 SendToProgram("go\n", cps);
6347 cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
6349 return bookHit; // notify caller of hit, so it can take action to send move to opponent
6353 ChessProgramState *savedState;
6354 void DeferredBookMove(void)
6356 if(savedState->lastPing != savedState->lastPong)
6357 ScheduleDelayedEvent(DeferredBookMove, 10);
6359 HandleMachineMove(savedMessage, savedState);
6363 HandleMachineMove(message, cps)
6365 ChessProgramState *cps;
6367 char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
6368 char realname[MSG_SIZ];
6369 int fromX, fromY, toX, toY;
6378 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
6380 * Kludge to ignore BEL characters
6382 while (*message == '\007') message++;
6385 * [HGM] engine debug message: ignore lines starting with '#' character
6387 if(cps->debug && *message == '#') return;
6390 * Look for book output
6392 if (cps == &first && bookRequested) {
6393 if (message[0] == '\t' || message[0] == ' ') {
6394 /* Part of the book output is here; append it */
6395 strcat(bookOutput, message);
6396 strcat(bookOutput, " \n");
6398 } else if (bookOutput[0] != NULLCHAR) {
6399 /* All of book output has arrived; display it */
6400 char *p = bookOutput;
6401 while (*p != NULLCHAR) {
6402 if (*p == '\t') *p = ' ';
6405 DisplayInformation(bookOutput);
6406 bookRequested = FALSE;
6407 /* Fall through to parse the current output */
6412 * Look for machine move.
6414 if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
6415 (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
6417 /* This method is only useful on engines that support ping */
6418 if (cps->lastPing != cps->lastPong) {
6419 if (gameMode == BeginningOfGame) {
6420 /* Extra move from before last new; ignore */
6421 if (appData.debugMode) {
6422 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
6425 if (appData.debugMode) {
6426 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
6427 cps->which, gameMode);
6430 SendToProgram("undo\n", cps);
6436 case BeginningOfGame:
6437 /* Extra move from before last reset; ignore */
6438 if (appData.debugMode) {
6439 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
6446 /* Extra move after we tried to stop. The mode test is
6447 not a reliable way of detecting this problem, but it's
6448 the best we can do on engines that don't support ping.
6450 if (appData.debugMode) {
6451 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
6452 cps->which, gameMode);
6454 SendToProgram("undo\n", cps);
6457 case MachinePlaysWhite:
6458 case IcsPlayingWhite:
6459 machineWhite = TRUE;
6462 case MachinePlaysBlack:
6463 case IcsPlayingBlack:
6464 machineWhite = FALSE;
6467 case TwoMachinesPlay:
6468 machineWhite = (cps->twoMachinesColor[0] == 'w');
6471 if (WhiteOnMove(forwardMostMove) != machineWhite) {
6472 if (appData.debugMode) {
6474 "Ignoring move out of turn by %s, gameMode %d"
6475 ", forwardMost %d\n",
6476 cps->which, gameMode, forwardMostMove);
6481 if (appData.debugMode) { int f = forwardMostMove;
6482 fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
6483 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
6484 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
6486 if(cps->alphaRank) AlphaRank(machineMove, 4);
6487 if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
6488 &fromX, &fromY, &toX, &toY, &promoChar)) {
6489 /* Machine move could not be parsed; ignore it. */
6490 sprintf(buf1, _("Illegal move \"%s\" from %s machine"),
6491 machineMove, cps->which);
6492 DisplayError(buf1, 0);
6493 sprintf(buf1, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
6494 machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
6495 if (gameMode == TwoMachinesPlay) {
6496 GameEnds(machineWhite ? BlackWins : WhiteWins,
6502 /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
6503 /* So we have to redo legality test with true e.p. status here, */
6504 /* to make sure an illegal e.p. capture does not slip through, */
6505 /* to cause a forfeit on a justified illegal-move complaint */
6506 /* of the opponent. */
6507 if( gameMode==TwoMachinesPlay && appData.testLegality
6508 && fromY != DROP_RANK /* [HGM] temporary; should still add legality test for drops */
6511 moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
6512 fromY, fromX, toY, toX, promoChar);
6513 if (appData.debugMode) {
6515 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
6516 boards[forwardMostMove][CASTLING][i], castlingRank[i]);
6517 fprintf(debugFP, "castling rights\n");
6519 if(moveType == IllegalMove) {
6520 sprintf(buf1, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
6521 machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
6522 GameEnds(machineWhite ? BlackWins : WhiteWins,
6525 } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
6526 /* [HGM] Kludge to handle engines that send FRC-style castling
6527 when they shouldn't (like TSCP-Gothic) */
6529 case WhiteASideCastleFR:
6530 case BlackASideCastleFR:
6532 currentMoveString[2]++;
6534 case WhiteHSideCastleFR:
6535 case BlackHSideCastleFR:
6537 currentMoveString[2]--;
6539 default: ; // nothing to do, but suppresses warning of pedantic compilers
6542 hintRequested = FALSE;
6543 lastHint[0] = NULLCHAR;
6544 bookRequested = FALSE;
6545 /* Program may be pondering now */
6546 cps->maybeThinking = TRUE;
6547 if (cps->sendTime == 2) cps->sendTime = 1;
6548 if (cps->offeredDraw) cps->offeredDraw--;
6551 if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
6553 SendMoveToICS(moveType, fromX, fromY, toX, toY);
6555 if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
6556 char buf[3*MSG_SIZ];
6558 sprintf(buf, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
6559 programStats.score / 100.,
6561 programStats.time / 100.,
6562 (unsigned int)programStats.nodes,
6563 (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
6564 programStats.movelist);
6566 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
6570 /* currentMoveString is set as a side-effect of ParseOneMove */
6571 strcpy(machineMove, currentMoveString);
6572 strcat(machineMove, "\n");
6573 strcpy(moveList[forwardMostMove], machineMove);
6575 /* [AS] Save move info and clear stats for next move */
6576 pvInfoList[ forwardMostMove ].score = programStats.score;
6577 pvInfoList[ forwardMostMove ].depth = programStats.depth;
6578 pvInfoList[ forwardMostMove ].time = programStats.time; // [HGM] PGNtime: take time from engine stats
6579 ClearProgramStats();
6580 thinkOutput[0] = NULLCHAR;
6581 hiddenThinkOutputState = 0;
6583 MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
6585 /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
6586 if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
6589 while( count < adjudicateLossPlies ) {
6590 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
6593 score = -score; /* Flip score for winning side */
6596 if( score > adjudicateLossThreshold ) {
6603 if( count >= adjudicateLossPlies ) {
6604 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6606 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
6607 "Xboard adjudication",
6614 if(Adjudicate(cps)) return; // [HGM] adjudicate: for all automatic game ends
6617 if (gameMode == TwoMachinesPlay) {
6618 /* [HGM] relaying draw offers moved to after reception of move */
6619 /* and interpreting offer as claim if it brings draw condition */
6620 if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
6621 SendToProgram("draw\n", cps->other);
6623 if (cps->other->sendTime) {
6624 SendTimeRemaining(cps->other,
6625 cps->other->twoMachinesColor[0] == 'w');
6627 bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
6628 if (firstMove && !bookHit) {
6630 if (cps->other->useColors) {
6631 SendToProgram(cps->other->twoMachinesColor, cps->other);
6633 SendToProgram("go\n", cps->other);
6635 cps->other->maybeThinking = TRUE;
6638 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6640 if (!pausing && appData.ringBellAfterMoves) {
6645 * Reenable menu items that were disabled while
6646 * machine was thinking
6648 if (gameMode != TwoMachinesPlay)
6649 SetUserThinkingEnables();
6651 // [HGM] book: after book hit opponent has received move and is now in force mode
6652 // force the book reply into it, and then fake that it outputted this move by jumping
6653 // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
6655 static char bookMove[MSG_SIZ]; // a bit generous?
6657 strcpy(bookMove, "move ");
6658 strcat(bookMove, bookHit);
6661 programStats.nodes = programStats.depth = programStats.time =
6662 programStats.score = programStats.got_only_move = 0;
6663 sprintf(programStats.movelist, "%s (xbook)", bookHit);
6665 if(cps->lastPing != cps->lastPong) {
6666 savedMessage = message; // args for deferred call
6668 ScheduleDelayedEvent(DeferredBookMove, 10);
6677 /* Set special modes for chess engines. Later something general
6678 * could be added here; for now there is just one kludge feature,
6679 * needed because Crafty 15.10 and earlier don't ignore SIGINT
6680 * when "xboard" is given as an interactive command.
6682 if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
6683 cps->useSigint = FALSE;
6684 cps->useSigterm = FALSE;
6686 if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
6687 ParseFeatures(message+8, cps);
6688 return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
6691 /* [HGM] Allow engine to set up a position. Don't ask me why one would
6692 * want this, I was asked to put it in, and obliged.
6694 if (!strncmp(message, "setboard ", 9)) {
6695 Board initial_position;
6697 GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
6699 if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
6700 DisplayError(_("Bad FEN received from engine"), 0);
6704 CopyBoard(boards[0], initial_position);
6705 initialRulePlies = FENrulePlies;
6706 if(blackPlaysFirst) gameMode = MachinePlaysWhite;
6707 else gameMode = MachinePlaysBlack;
6708 DrawPosition(FALSE, boards[currentMove]);
6714 * Look for communication commands
6716 if (!strncmp(message, "telluser ", 9)) {
6717 DisplayNote(message + 9);
6720 if (!strncmp(message, "tellusererror ", 14)) {
6722 DisplayError(message + 14, 0);
6725 if (!strncmp(message, "tellopponent ", 13)) {
6726 if (appData.icsActive) {
6728 snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
6732 DisplayNote(message + 13);
6736 if (!strncmp(message, "tellothers ", 11)) {
6737 if (appData.icsActive) {
6739 snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
6745 if (!strncmp(message, "tellall ", 8)) {
6746 if (appData.icsActive) {
6748 snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
6752 DisplayNote(message + 8);
6756 if (strncmp(message, "warning", 7) == 0) {
6757 /* Undocumented feature, use tellusererror in new code */
6758 DisplayError(message, 0);
6761 if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
6762 strcpy(realname, cps->tidy);
6763 strcat(realname, " query");
6764 AskQuestion(realname, buf2, buf1, cps->pr);
6767 /* Commands from the engine directly to ICS. We don't allow these to be
6768 * sent until we are logged on. Crafty kibitzes have been known to
6769 * interfere with the login process.
6772 if (!strncmp(message, "tellics ", 8)) {
6773 SendToICS(message + 8);
6777 if (!strncmp(message, "tellicsnoalias ", 15)) {
6778 SendToICS(ics_prefix);
6779 SendToICS(message + 15);
6783 /* The following are for backward compatibility only */
6784 if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
6785 !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
6786 SendToICS(ics_prefix);
6792 if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
6796 * If the move is illegal, cancel it and redraw the board.
6797 * Also deal with other error cases. Matching is rather loose
6798 * here to accommodate engines written before the spec.
6800 if (strncmp(message + 1, "llegal move", 11) == 0 ||
6801 strncmp(message, "Error", 5) == 0) {
6802 if (StrStr(message, "name") ||
6803 StrStr(message, "rating") || StrStr(message, "?") ||
6804 StrStr(message, "result") || StrStr(message, "board") ||
6805 StrStr(message, "bk") || StrStr(message, "computer") ||
6806 StrStr(message, "variant") || StrStr(message, "hint") ||
6807 StrStr(message, "random") || StrStr(message, "depth") ||
6808 StrStr(message, "accepted")) {
6811 if (StrStr(message, "protover")) {
6812 /* Program is responding to input, so it's apparently done
6813 initializing, and this error message indicates it is
6814 protocol version 1. So we don't need to wait any longer
6815 for it to initialize and send feature commands. */
6816 FeatureDone(cps, 1);
6817 cps->protocolVersion = 1;
6820 cps->maybeThinking = FALSE;
6822 if (StrStr(message, "draw")) {
6823 /* Program doesn't have "draw" command */
6824 cps->sendDrawOffers = 0;
6827 if (cps->sendTime != 1 &&
6828 (StrStr(message, "time") || StrStr(message, "otim"))) {
6829 /* Program apparently doesn't have "time" or "otim" command */
6833 if (StrStr(message, "analyze")) {
6834 cps->analysisSupport = FALSE;
6835 cps->analyzing = FALSE;
6837 sprintf(buf2, _("%s does not support analysis"), cps->tidy);
6838 DisplayError(buf2, 0);
6841 if (StrStr(message, "(no matching move)st")) {
6842 /* Special kludge for GNU Chess 4 only */
6843 cps->stKludge = TRUE;
6844 SendTimeControl(cps, movesPerSession, timeControl,
6845 timeIncrement, appData.searchDepth,
6849 if (StrStr(message, "(no matching move)sd")) {
6850 /* Special kludge for GNU Chess 4 only */
6851 cps->sdKludge = TRUE;
6852 SendTimeControl(cps, movesPerSession, timeControl,
6853 timeIncrement, appData.searchDepth,
6857 if (!StrStr(message, "llegal")) {
6860 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
6861 gameMode == IcsIdle) return;
6862 if (forwardMostMove <= backwardMostMove) return;
6863 if (pausing) PauseEvent();
6864 if(appData.forceIllegal) {
6865 // [HGM] illegal: machine refused move; force position after move into it
6866 SendToProgram("force\n", cps);
6867 if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
6868 // we have a real problem now, as SendBoard will use the a2a3 kludge
6869 // when black is to move, while there might be nothing on a2 or black
6870 // might already have the move. So send the board as if white has the move.
6871 // But first we must change the stm of the engine, as it refused the last move
6872 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
6873 if(WhiteOnMove(forwardMostMove)) {
6874 SendToProgram("a7a6\n", cps); // for the engine black still had the move
6875 SendBoard(cps, forwardMostMove); // kludgeless board
6877 SendToProgram("a2a3\n", cps); // for the engine white still had the move
6878 CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
6879 SendBoard(cps, forwardMostMove+1); // kludgeless board
6881 } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
6882 if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
6883 gameMode == TwoMachinesPlay)
6884 SendToProgram("go\n", cps);
6887 if (gameMode == PlayFromGameFile) {
6888 /* Stop reading this game file */
6889 gameMode = EditGame;
6892 currentMove = --forwardMostMove;
6893 DisplayMove(currentMove-1); /* before DisplayMoveError */
6895 DisplayBothClocks();
6896 sprintf(buf1, _("Illegal move \"%s\" (rejected by %s chess program)"),
6897 parseList[currentMove], cps->which);
6898 DisplayMoveError(buf1);
6899 DrawPosition(FALSE, boards[currentMove]);
6901 /* [HGM] illegal-move claim should forfeit game when Xboard */
6902 /* only passes fully legal moves */
6903 if( appData.testLegality && gameMode == TwoMachinesPlay ) {
6904 GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
6905 "False illegal-move claim", GE_XBOARD );
6909 if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
6910 /* Program has a broken "time" command that
6911 outputs a string not ending in newline.
6917 * If chess program startup fails, exit with an error message.
6918 * Attempts to recover here are futile.
6920 if ((StrStr(message, "unknown host") != NULL)
6921 || (StrStr(message, "No remote directory") != NULL)
6922 || (StrStr(message, "not found") != NULL)
6923 || (StrStr(message, "No such file") != NULL)
6924 || (StrStr(message, "can't alloc") != NULL)
6925 || (StrStr(message, "Permission denied") != NULL)) {
6927 cps->maybeThinking = FALSE;
6928 snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
6929 cps->which, cps->program, cps->host, message);
6930 RemoveInputSource(cps->isr);
6931 DisplayFatalError(buf1, 0, 1);
6936 * Look for hint output
6938 if (sscanf(message, "Hint: %s", buf1) == 1) {
6939 if (cps == &first && hintRequested) {
6940 hintRequested = FALSE;
6941 if (ParseOneMove(buf1, forwardMostMove, &moveType,
6942 &fromX, &fromY, &toX, &toY, &promoChar)) {
6943 (void) CoordsToAlgebraic(boards[forwardMostMove],
6944 PosFlags(forwardMostMove),
6945 fromY, fromX, toY, toX, promoChar, buf1);
6946 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
6947 DisplayInformation(buf2);
6949 /* Hint move could not be parsed!? */
6950 snprintf(buf2, sizeof(buf2),
6951 _("Illegal hint move \"%s\"\nfrom %s chess program"),
6953 DisplayError(buf2, 0);
6956 strcpy(lastHint, buf1);
6962 * Ignore other messages if game is not in progress
6964 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
6965 gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
6968 * look for win, lose, draw, or draw offer
6970 if (strncmp(message, "1-0", 3) == 0) {
6971 char *p, *q, *r = "";
6972 p = strchr(message, '{');
6980 GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
6982 } else if (strncmp(message, "0-1", 3) == 0) {
6983 char *p, *q, *r = "";
6984 p = strchr(message, '{');
6992 /* Kludge for Arasan 4.1 bug */
6993 if (strcmp(r, "Black resigns") == 0) {
6994 GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
6997 GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
6999 } else if (strncmp(message, "1/2", 3) == 0) {
7000 char *p, *q, *r = "";
7001 p = strchr(message, '{');
7010 GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
7013 } else if (strncmp(message, "White resign", 12) == 0) {
7014 GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
7016 } else if (strncmp(message, "Black resign", 12) == 0) {
7017 GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
7019 } else if (strncmp(message, "White matches", 13) == 0 ||
7020 strncmp(message, "Black matches", 13) == 0 ) {
7021 /* [HGM] ignore GNUShogi noises */
7023 } else if (strncmp(message, "White", 5) == 0 &&
7024 message[5] != '(' &&
7025 StrStr(message, "Black") == NULL) {
7026 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7028 } else if (strncmp(message, "Black", 5) == 0 &&
7029 message[5] != '(') {
7030 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7032 } else if (strcmp(message, "resign") == 0 ||
7033 strcmp(message, "computer resigns") == 0) {
7035 case MachinePlaysBlack:
7036 case IcsPlayingBlack:
7037 GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
7039 case MachinePlaysWhite:
7040 case IcsPlayingWhite:
7041 GameEnds(BlackWins, "White resigns", GE_ENGINE);
7043 case TwoMachinesPlay:
7044 if (cps->twoMachinesColor[0] == 'w')
7045 GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
7047 GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
7054 } else if (strncmp(message, "opponent mates", 14) == 0) {
7056 case MachinePlaysBlack:
7057 case IcsPlayingBlack:
7058 GameEnds(WhiteWins, "White mates", GE_ENGINE);
7060 case MachinePlaysWhite:
7061 case IcsPlayingWhite:
7062 GameEnds(BlackWins, "Black mates", GE_ENGINE);
7064 case TwoMachinesPlay:
7065 if (cps->twoMachinesColor[0] == 'w')
7066 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7068 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7075 } else if (strncmp(message, "computer mates", 14) == 0) {
7077 case MachinePlaysBlack:
7078 case IcsPlayingBlack:
7079 GameEnds(BlackWins, "Black mates", GE_ENGINE1);
7081 case MachinePlaysWhite:
7082 case IcsPlayingWhite:
7083 GameEnds(WhiteWins, "White mates", GE_ENGINE);
7085 case TwoMachinesPlay:
7086 if (cps->twoMachinesColor[0] == 'w')
7087 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7089 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7096 } else if (strncmp(message, "checkmate", 9) == 0) {
7097 if (WhiteOnMove(forwardMostMove)) {
7098 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7100 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7103 } else if (strstr(message, "Draw") != NULL ||
7104 strstr(message, "game is a draw") != NULL) {
7105 GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
7107 } else if (strstr(message, "offer") != NULL &&
7108 strstr(message, "draw") != NULL) {
7110 if (appData.zippyPlay && first.initDone) {
7111 /* Relay offer to ICS */
7112 SendToICS(ics_prefix);
7113 SendToICS("draw\n");
7116 cps->offeredDraw = 2; /* valid until this engine moves twice */
7117 if (gameMode == TwoMachinesPlay) {
7118 if (cps->other->offeredDraw) {
7119 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
7120 /* [HGM] in two-machine mode we delay relaying draw offer */
7121 /* until after we also have move, to see if it is really claim */
7123 } else if (gameMode == MachinePlaysWhite ||
7124 gameMode == MachinePlaysBlack) {
7125 if (userOfferedDraw) {
7126 DisplayInformation(_("Machine accepts your draw offer"));
7127 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
7129 DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
7136 * Look for thinking output
7138 if ( appData.showThinking // [HGM] thinking: test all options that cause this output
7139 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
7141 int plylev, mvleft, mvtot, curscore, time;
7142 char mvname[MOVE_LEN];
7146 int prefixHint = FALSE;
7147 mvname[0] = NULLCHAR;
7150 case MachinePlaysBlack:
7151 case IcsPlayingBlack:
7152 if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
7154 case MachinePlaysWhite:
7155 case IcsPlayingWhite:
7156 if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
7161 case IcsObserving: /* [DM] icsEngineAnalyze */
7162 if (!appData.icsEngineAnalyze) ignore = TRUE;
7164 case TwoMachinesPlay:
7165 if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
7176 if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
7177 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
7179 if (plyext != ' ' && plyext != '\t') {
7183 /* [AS] Negate score if machine is playing black and reporting absolute scores */
7184 if( cps->scoreIsAbsolute &&
7185 ( gameMode == MachinePlaysBlack ||
7186 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
7187 gameMode == IcsPlayingBlack || // [HGM] also add other situations where engine should report black POV
7188 (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
7189 !WhiteOnMove(currentMove)
7192 curscore = -curscore;
7196 programStats.depth = plylev;
7197 programStats.nodes = nodes;
7198 programStats.time = time;
7199 programStats.score = curscore;
7200 programStats.got_only_move = 0;
7202 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
7205 if(cps->nps == 0) ticklen = 10*time; // use engine reported time
7206 else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
7207 if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
7208 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
7209 whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
7210 if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
7211 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
7212 blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
7215 /* Buffer overflow protection */
7216 if (buf1[0] != NULLCHAR) {
7217 if (strlen(buf1) >= sizeof(programStats.movelist)
7218 && appData.debugMode) {
7220 "PV is too long; using the first %u bytes.\n",
7221 (unsigned) sizeof(programStats.movelist) - 1);
7224 safeStrCpy( programStats.movelist, buf1, sizeof(programStats.movelist) );
7226 sprintf(programStats.movelist, " no PV\n");
7229 if (programStats.seen_stat) {
7230 programStats.ok_to_send = 1;
7233 if (strchr(programStats.movelist, '(') != NULL) {
7234 programStats.line_is_book = 1;
7235 programStats.nr_moves = 0;
7236 programStats.moves_left = 0;
7238 programStats.line_is_book = 0;
7241 SendProgramStatsToFrontend( cps, &programStats );
7244 [AS] Protect the thinkOutput buffer from overflow... this
7245 is only useful if buf1 hasn't overflowed first!
7247 sprintf(thinkOutput, "[%d]%c%+.2f %s%s",
7249 (gameMode == TwoMachinesPlay ?
7250 ToUpper(cps->twoMachinesColor[0]) : ' '),
7251 ((double) curscore) / 100.0,
7252 prefixHint ? lastHint : "",
7253 prefixHint ? " " : "" );
7255 if( buf1[0] != NULLCHAR ) {
7256 unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
7258 if( strlen(buf1) > max_len ) {
7259 if( appData.debugMode) {
7260 fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
7262 buf1[max_len+1] = '\0';
7265 strcat( thinkOutput, buf1 );
7268 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
7269 || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7270 DisplayMove(currentMove - 1);
7274 } else if ((p=StrStr(message, "(only move)")) != NULL) {
7275 /* crafty (9.25+) says "(only move) <move>"
7276 * if there is only 1 legal move
7278 sscanf(p, "(only move) %s", buf1);
7279 sprintf(thinkOutput, "%s (only move)", buf1);
7280 sprintf(programStats.movelist, "%s (only move)", buf1);
7281 programStats.depth = 1;
7282 programStats.nr_moves = 1;
7283 programStats.moves_left = 1;
7284 programStats.nodes = 1;
7285 programStats.time = 1;
7286 programStats.got_only_move = 1;
7288 /* Not really, but we also use this member to
7289 mean "line isn't going to change" (Crafty
7290 isn't searching, so stats won't change) */
7291 programStats.line_is_book = 1;
7293 SendProgramStatsToFrontend( cps, &programStats );
7295 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
7296 gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7297 DisplayMove(currentMove - 1);
7300 } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
7301 &time, &nodes, &plylev, &mvleft,
7302 &mvtot, mvname) >= 5) {
7303 /* The stat01: line is from Crafty (9.29+) in response
7304 to the "." command */
7305 programStats.seen_stat = 1;
7306 cps->maybeThinking = TRUE;
7308 if (programStats.got_only_move || !appData.periodicUpdates)
7311 programStats.depth = plylev;
7312 programStats.time = time;
7313 programStats.nodes = nodes;
7314 programStats.moves_left = mvleft;
7315 programStats.nr_moves = mvtot;
7316 strcpy(programStats.move_name, mvname);
7317 programStats.ok_to_send = 1;
7318 programStats.movelist[0] = '\0';
7320 SendProgramStatsToFrontend( cps, &programStats );
7324 } else if (strncmp(message,"++",2) == 0) {
7325 /* Crafty 9.29+ outputs this */
7326 programStats.got_fail = 2;
7329 } else if (strncmp(message,"--",2) == 0) {
7330 /* Crafty 9.29+ outputs this */
7331 programStats.got_fail = 1;
7334 } else if (thinkOutput[0] != NULLCHAR &&
7335 strncmp(message, " ", 4) == 0) {
7336 unsigned message_len;
7339 while (*p && *p == ' ') p++;
7341 message_len = strlen( p );
7343 /* [AS] Avoid buffer overflow */
7344 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
7345 strcat(thinkOutput, " ");
7346 strcat(thinkOutput, p);
7349 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
7350 strcat(programStats.movelist, " ");
7351 strcat(programStats.movelist, p);
7354 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
7355 gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7356 DisplayMove(currentMove - 1);
7364 if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
7365 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
7367 ChessProgramStats cpstats;
7369 if (plyext != ' ' && plyext != '\t') {
7373 /* [AS] Negate score if machine is playing black and reporting absolute scores */
7374 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
7375 curscore = -curscore;
7378 cpstats.depth = plylev;
7379 cpstats.nodes = nodes;
7380 cpstats.time = time;
7381 cpstats.score = curscore;
7382 cpstats.got_only_move = 0;
7383 cpstats.movelist[0] = '\0';
7385 if (buf1[0] != NULLCHAR) {
7386 safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist) );
7389 cpstats.ok_to_send = 0;
7390 cpstats.line_is_book = 0;
7391 cpstats.nr_moves = 0;
7392 cpstats.moves_left = 0;
7394 SendProgramStatsToFrontend( cps, &cpstats );
7401 /* Parse a game score from the character string "game", and
7402 record it as the history of the current game. The game
7403 score is NOT assumed to start from the standard position.
7404 The display is not updated in any way.
7407 ParseGameHistory(game)
7411 int fromX, fromY, toX, toY, boardIndex;
7416 if (appData.debugMode)
7417 fprintf(debugFP, "Parsing game history: %s\n", game);
7419 if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
7420 gameInfo.site = StrSave(appData.icsHost);
7421 gameInfo.date = PGNDate();
7422 gameInfo.round = StrSave("-");
7424 /* Parse out names of players */
7425 while (*game == ' ') game++;
7427 while (*game != ' ') *p++ = *game++;
7429 gameInfo.white = StrSave(buf);
7430 while (*game == ' ') game++;
7432 while (*game != ' ' && *game != '\n') *p++ = *game++;
7434 gameInfo.black = StrSave(buf);
7437 boardIndex = blackPlaysFirst ? 1 : 0;
7440 yyboardindex = boardIndex;
7441 moveType = (ChessMove) yylex();
7443 case IllegalMove: /* maybe suicide chess, etc. */
7444 if (appData.debugMode) {
7445 fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
7446 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7447 setbuf(debugFP, NULL);
7449 case WhitePromotionChancellor:
7450 case BlackPromotionChancellor:
7451 case WhitePromotionArchbishop:
7452 case BlackPromotionArchbishop:
7453 case WhitePromotionQueen:
7454 case BlackPromotionQueen:
7455 case WhitePromotionRook:
7456 case BlackPromotionRook:
7457 case WhitePromotionBishop:
7458 case BlackPromotionBishop:
7459 case WhitePromotionKnight:
7460 case BlackPromotionKnight:
7461 case WhitePromotionKing:
7462 case BlackPromotionKing:
7464 case WhiteCapturesEnPassant:
7465 case BlackCapturesEnPassant:
7466 case WhiteKingSideCastle:
7467 case WhiteQueenSideCastle:
7468 case BlackKingSideCastle:
7469 case BlackQueenSideCastle:
7470 case WhiteKingSideCastleWild:
7471 case WhiteQueenSideCastleWild:
7472 case BlackKingSideCastleWild:
7473 case BlackQueenSideCastleWild:
7475 case WhiteHSideCastleFR:
7476 case WhiteASideCastleFR:
7477 case BlackHSideCastleFR:
7478 case BlackASideCastleFR:
7480 fromX = currentMoveString[0] - AAA;
7481 fromY = currentMoveString[1] - ONE;
7482 toX = currentMoveString[2] - AAA;
7483 toY = currentMoveString[3] - ONE;
7484 promoChar = currentMoveString[4];
7488 fromX = moveType == WhiteDrop ?
7489 (int) CharToPiece(ToUpper(currentMoveString[0])) :
7490 (int) CharToPiece(ToLower(currentMoveString[0]));
7492 toX = currentMoveString[2] - AAA;
7493 toY = currentMoveString[3] - ONE;
7494 promoChar = NULLCHAR;
7498 sprintf(buf, _("Ambiguous move in ICS output: \"%s\""), yy_text);
7499 if (appData.debugMode) {
7500 fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
7501 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7502 setbuf(debugFP, NULL);
7504 DisplayError(buf, 0);
7506 case ImpossibleMove:
7508 sprintf(buf, _("Illegal move in ICS output: \"%s\""), yy_text);
7509 if (appData.debugMode) {
7510 fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
7511 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7512 setbuf(debugFP, NULL);
7514 DisplayError(buf, 0);
7516 case (ChessMove) 0: /* end of file */
7517 if (boardIndex < backwardMostMove) {
7518 /* Oops, gap. How did that happen? */
7519 DisplayError(_("Gap in move list"), 0);
7522 backwardMostMove = blackPlaysFirst ? 1 : 0;
7523 if (boardIndex > forwardMostMove) {
7524 forwardMostMove = boardIndex;
7528 if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
7529 strcat(parseList[boardIndex-1], " ");
7530 strcat(parseList[boardIndex-1], yy_text);
7542 case GameUnfinished:
7543 if (gameMode == IcsExamining) {
7544 if (boardIndex < backwardMostMove) {
7545 /* Oops, gap. How did that happen? */
7548 backwardMostMove = blackPlaysFirst ? 1 : 0;
7551 gameInfo.result = moveType;
7552 p = strchr(yy_text, '{');
7553 if (p == NULL) p = strchr(yy_text, '(');
7556 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
7558 q = strchr(p, *p == '{' ? '}' : ')');
7559 if (q != NULL) *q = NULLCHAR;
7562 gameInfo.resultDetails = StrSave(p);
7565 if (boardIndex >= forwardMostMove &&
7566 !(gameMode == IcsObserving && ics_gamenum == -1)) {
7567 backwardMostMove = blackPlaysFirst ? 1 : 0;
7570 (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
7571 fromY, fromX, toY, toX, promoChar,
7572 parseList[boardIndex]);
7573 CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
7574 /* currentMoveString is set as a side-effect of yylex */
7575 strcpy(moveList[boardIndex], currentMoveString);
7576 strcat(moveList[boardIndex], "\n");
7578 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
7579 switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
7585 if(gameInfo.variant != VariantShogi)
7586 strcat(parseList[boardIndex - 1], "+");
7590 strcat(parseList[boardIndex - 1], "#");
7597 /* Apply a move to the given board */
7599 ApplyMove(fromX, fromY, toX, toY, promoChar, board)
7600 int fromX, fromY, toX, toY;
7604 ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
7605 int promoRank = gameInfo.variant == VariantMakruk ? 3 : 1;
7607 /* [HGM] compute & store e.p. status and castling rights for new position */
7608 /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
7611 if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
7612 oldEP = (signed char)board[EP_STATUS];
7613 board[EP_STATUS] = EP_NONE;
7615 if( board[toY][toX] != EmptySquare )
7616 board[EP_STATUS] = EP_CAPTURE;
7618 if( board[fromY][fromX] == WhitePawn ) {
7619 if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
7620 board[EP_STATUS] = EP_PAWN_MOVE;
7622 if(toX>BOARD_LEFT && board[toY][toX-1] == BlackPawn &&
7623 gameInfo.variant != VariantBerolina || toX < fromX)
7624 board[EP_STATUS] = toX | berolina;
7625 if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
7626 gameInfo.variant != VariantBerolina || toX > fromX)
7627 board[EP_STATUS] = toX;
7630 if( board[fromY][fromX] == BlackPawn ) {
7631 if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
7632 board[EP_STATUS] = EP_PAWN_MOVE;
7633 if( toY-fromY== -2) {
7634 if(toX>BOARD_LEFT && board[toY][toX-1] == WhitePawn &&
7635 gameInfo.variant != VariantBerolina || toX < fromX)
7636 board[EP_STATUS] = toX | berolina;
7637 if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
7638 gameInfo.variant != VariantBerolina || toX > fromX)
7639 board[EP_STATUS] = toX;
7643 for(i=0; i<nrCastlingRights; i++) {
7644 if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
7645 board[CASTLING][i] == toX && castlingRank[i] == toY
7646 ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
7651 /* [HGM] In Shatranj and Courier all promotions are to Ferz */
7652 if((gameInfo.variant==VariantShatranj || gameInfo.variant==VariantCourier || gameInfo.variant == VariantMakruk)
7653 && promoChar != 0) promoChar = PieceToChar(WhiteFerz);
7655 if (fromX == toX && fromY == toY) return;
7657 if (fromY == DROP_RANK) {
7659 piece = board[toY][toX] = (ChessSquare) fromX;
7661 piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
7662 king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
7663 if(gameInfo.variant == VariantKnightmate)
7664 king += (int) WhiteUnicorn - (int) WhiteKing;
7666 /* Code added by Tord: */
7667 /* FRC castling assumed when king captures friendly rook. */
7668 if (board[fromY][fromX] == WhiteKing &&
7669 board[toY][toX] == WhiteRook) {
7670 board[fromY][fromX] = EmptySquare;
7671 board[toY][toX] = EmptySquare;
7673 board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
7675 board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
7677 } else if (board[fromY][fromX] == BlackKing &&
7678 board[toY][toX] == BlackRook) {
7679 board[fromY][fromX] = EmptySquare;
7680 board[toY][toX] = EmptySquare;
7682 board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
7684 board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
7686 /* End of code added by Tord */
7688 } else if (board[fromY][fromX] == king
7689 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7690 && toY == fromY && toX > fromX+1) {
7691 board[fromY][fromX] = EmptySquare;
7692 board[toY][toX] = king;
7693 board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
7694 board[fromY][BOARD_RGHT-1] = EmptySquare;
7695 } else if (board[fromY][fromX] == king
7696 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7697 && toY == fromY && toX < fromX-1) {
7698 board[fromY][fromX] = EmptySquare;
7699 board[toY][toX] = king;
7700 board[toY][toX+1] = board[fromY][BOARD_LEFT];
7701 board[fromY][BOARD_LEFT] = EmptySquare;
7702 } else if (board[fromY][fromX] == WhitePawn
7703 && toY >= BOARD_HEIGHT-promoRank
7704 && gameInfo.variant != VariantXiangqi
7706 /* white pawn promotion */
7707 board[toY][toX] = CharToPiece(ToUpper(promoChar));
7708 if (board[toY][toX] == EmptySquare) {
7709 board[toY][toX] = WhiteQueen;
7711 if(gameInfo.variant==VariantBughouse ||
7712 gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
7713 board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
7714 board[fromY][fromX] = EmptySquare;
7715 } else if ((fromY == BOARD_HEIGHT-4)
7717 && gameInfo.variant != VariantXiangqi
7718 && gameInfo.variant != VariantBerolina
7719 && (board[fromY][fromX] == WhitePawn)
7720 && (board[toY][toX] == EmptySquare)) {
7721 board[fromY][fromX] = EmptySquare;
7722 board[toY][toX] = WhitePawn;
7723 captured = board[toY - 1][toX];
7724 board[toY - 1][toX] = EmptySquare;
7725 } else if ((fromY == BOARD_HEIGHT-4)
7727 && gameInfo.variant == VariantBerolina
7728 && (board[fromY][fromX] == WhitePawn)
7729 && (board[toY][toX] == EmptySquare)) {
7730 board[fromY][fromX] = EmptySquare;
7731 board[toY][toX] = WhitePawn;
7732 if(oldEP & EP_BEROLIN_A) {
7733 captured = board[fromY][fromX-1];
7734 board[fromY][fromX-1] = EmptySquare;
7735 }else{ captured = board[fromY][fromX+1];
7736 board[fromY][fromX+1] = EmptySquare;
7738 } else if (board[fromY][fromX] == king
7739 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7740 && toY == fromY && toX > fromX+1) {
7741 board[fromY][fromX] = EmptySquare;
7742 board[toY][toX] = king;
7743 board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
7744 board[fromY][BOARD_RGHT-1] = EmptySquare;
7745 } else if (board[fromY][fromX] == king
7746 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7747 && toY == fromY && toX < fromX-1) {
7748 board[fromY][fromX] = EmptySquare;
7749 board[toY][toX] = king;
7750 board[toY][toX+1] = board[fromY][BOARD_LEFT];
7751 board[fromY][BOARD_LEFT] = EmptySquare;
7752 } else if (fromY == 7 && fromX == 3
7753 && board[fromY][fromX] == BlackKing
7754 && toY == 7 && toX == 5) {
7755 board[fromY][fromX] = EmptySquare;
7756 board[toY][toX] = BlackKing;
7757 board[fromY][7] = EmptySquare;
7758 board[toY][4] = BlackRook;
7759 } else if (fromY == 7 && fromX == 3
7760 && board[fromY][fromX] == BlackKing
7761 && toY == 7 && toX == 1) {
7762 board[fromY][fromX] = EmptySquare;
7763 board[toY][toX] = BlackKing;
7764 board[fromY][0] = EmptySquare;
7765 board[toY][2] = BlackRook;
7766 } else if (board[fromY][fromX] == BlackPawn
7768 && gameInfo.variant != VariantXiangqi
7770 /* black pawn promotion */
7771 board[toY][toX] = CharToPiece(ToLower(promoChar));
7772 if (board[toY][toX] == EmptySquare) {
7773 board[toY][toX] = BlackQueen;
7775 if(gameInfo.variant==VariantBughouse ||
7776 gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
7777 board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
7778 board[fromY][fromX] = EmptySquare;
7779 } else if ((fromY == 3)
7781 && gameInfo.variant != VariantXiangqi
7782 && gameInfo.variant != VariantBerolina
7783 && (board[fromY][fromX] == BlackPawn)
7784 && (board[toY][toX] == EmptySquare)) {
7785 board[fromY][fromX] = EmptySquare;
7786 board[toY][toX] = BlackPawn;
7787 captured = board[toY + 1][toX];
7788 board[toY + 1][toX] = EmptySquare;
7789 } else if ((fromY == 3)
7791 && gameInfo.variant == VariantBerolina
7792 && (board[fromY][fromX] == BlackPawn)
7793 && (board[toY][toX] == EmptySquare)) {
7794 board[fromY][fromX] = EmptySquare;
7795 board[toY][toX] = BlackPawn;
7796 if(oldEP & EP_BEROLIN_A) {
7797 captured = board[fromY][fromX-1];
7798 board[fromY][fromX-1] = EmptySquare;
7799 }else{ captured = board[fromY][fromX+1];
7800 board[fromY][fromX+1] = EmptySquare;
7803 board[toY][toX] = board[fromY][fromX];
7804 board[fromY][fromX] = EmptySquare;
7807 /* [HGM] now we promote for Shogi, if needed */
7808 if(gameInfo.variant == VariantShogi && promoChar == 'q')
7809 board[toY][toX] = (ChessSquare) (PROMOTED piece);
7812 if (gameInfo.holdingsWidth != 0) {
7814 /* !!A lot more code needs to be written to support holdings */
7815 /* [HGM] OK, so I have written it. Holdings are stored in the */
7816 /* penultimate board files, so they are automaticlly stored */
7817 /* in the game history. */
7818 if (fromY == DROP_RANK) {
7819 /* Delete from holdings, by decreasing count */
7820 /* and erasing image if necessary */
7822 if(p < (int) BlackPawn) { /* white drop */
7823 p -= (int)WhitePawn;
7824 p = PieceToNumber((ChessSquare)p);
7825 if(p >= gameInfo.holdingsSize) p = 0;
7826 if(--board[p][BOARD_WIDTH-2] <= 0)
7827 board[p][BOARD_WIDTH-1] = EmptySquare;
7828 if((int)board[p][BOARD_WIDTH-2] < 0)
7829 board[p][BOARD_WIDTH-2] = 0;
7830 } else { /* black drop */
7831 p -= (int)BlackPawn;
7832 p = PieceToNumber((ChessSquare)p);
7833 if(p >= gameInfo.holdingsSize) p = 0;
7834 if(--board[BOARD_HEIGHT-1-p][1] <= 0)
7835 board[BOARD_HEIGHT-1-p][0] = EmptySquare;
7836 if((int)board[BOARD_HEIGHT-1-p][1] < 0)
7837 board[BOARD_HEIGHT-1-p][1] = 0;
7840 if (captured != EmptySquare && gameInfo.holdingsSize > 0
7841 && gameInfo.variant != VariantBughouse ) {
7842 /* [HGM] holdings: Add to holdings, if holdings exist */
7843 if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
7844 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
7845 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
7848 if (p >= (int) BlackPawn) {
7849 p -= (int)BlackPawn;
7850 if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
7851 /* in Shogi restore piece to its original first */
7852 captured = (ChessSquare) (DEMOTED captured);
7855 p = PieceToNumber((ChessSquare)p);
7856 if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
7857 board[p][BOARD_WIDTH-2]++;
7858 board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
7860 p -= (int)WhitePawn;
7861 if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
7862 captured = (ChessSquare) (DEMOTED captured);
7865 p = PieceToNumber((ChessSquare)p);
7866 if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
7867 board[BOARD_HEIGHT-1-p][1]++;
7868 board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
7871 } else if (gameInfo.variant == VariantAtomic) {
7872 if (captured != EmptySquare) {
7874 for (y = toY-1; y <= toY+1; y++) {
7875 for (x = toX-1; x <= toX+1; x++) {
7876 if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
7877 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
7878 board[y][x] = EmptySquare;
7882 board[toY][toX] = EmptySquare;
7885 if(gameInfo.variant == VariantShogi && promoChar != NULLCHAR && promoChar != '=') {
7886 /* [HGM] Shogi promotions */
7887 board[toY][toX] = (ChessSquare) (PROMOTED piece);
7890 if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
7891 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
7892 // [HGM] superchess: take promotion piece out of holdings
7893 int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
7894 if((int)piece < (int)BlackPawn) { // determine stm from piece color
7895 if(!--board[k][BOARD_WIDTH-2])
7896 board[k][BOARD_WIDTH-1] = EmptySquare;
7898 if(!--board[BOARD_HEIGHT-1-k][1])
7899 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
7905 /* Updates forwardMostMove */
7907 MakeMove(fromX, fromY, toX, toY, promoChar)
7908 int fromX, fromY, toX, toY;
7911 // forwardMostMove++; // [HGM] bare: moved downstream
7913 if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
7914 int timeLeft; static int lastLoadFlag=0; int king, piece;
7915 piece = boards[forwardMostMove][fromY][fromX];
7916 king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
7917 if(gameInfo.variant == VariantKnightmate)
7918 king += (int) WhiteUnicorn - (int) WhiteKing;
7919 if(forwardMostMove == 0) {
7921 fprintf(serverMoves, "%s;", second.tidy);
7922 fprintf(serverMoves, "%s;", first.tidy);
7923 if(!blackPlaysFirst)
7924 fprintf(serverMoves, "%s;", second.tidy);
7925 } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
7926 lastLoadFlag = loadFlag;
7928 fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
7929 // print castling suffix
7930 if( toY == fromY && piece == king ) {
7932 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
7934 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
7937 if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
7938 boards[forwardMostMove][fromY][fromX] == BlackPawn ) &&
7939 boards[forwardMostMove][toY][toX] == EmptySquare
7941 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
7943 if(promoChar != NULLCHAR)
7944 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);
7946 fprintf(serverMoves, "/%d/%d",
7947 pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
7948 if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
7949 else timeLeft = blackTimeRemaining/1000;
7950 fprintf(serverMoves, "/%d", timeLeft);
7952 fflush(serverMoves);
7955 if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations
7956 DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
7960 UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
7961 if (commentList[forwardMostMove+1] != NULL) {
7962 free(commentList[forwardMostMove+1]);
7963 commentList[forwardMostMove+1] = NULL;
7965 CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
7966 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
7967 forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
7968 SwitchClocks(); // uses forwardMostMove, so must be done after incrementing it !
7969 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
7970 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
7971 gameInfo.result = GameUnfinished;
7972 if (gameInfo.resultDetails != NULL) {
7973 free(gameInfo.resultDetails);
7974 gameInfo.resultDetails = NULL;
7976 CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
7977 moveList[forwardMostMove - 1]);
7978 (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
7979 PosFlags(forwardMostMove - 1),
7980 fromY, fromX, toY, toX, promoChar,
7981 parseList[forwardMostMove - 1]);
7982 switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
7988 if(gameInfo.variant != VariantShogi)
7989 strcat(parseList[forwardMostMove - 1], "+");
7993 strcat(parseList[forwardMostMove - 1], "#");
7996 if (appData.debugMode) {
7997 fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
8002 /* Updates currentMove if not pausing */
8004 ShowMove(fromX, fromY, toX, toY)
8006 int instant = (gameMode == PlayFromGameFile) ?
8007 (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
8008 if(appData.noGUI) return;
8009 if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
8011 if (forwardMostMove == currentMove + 1) {
8012 AnimateMove(boards[forwardMostMove - 1],
8013 fromX, fromY, toX, toY);
8015 if (appData.highlightLastMove) {
8016 SetHighlights(fromX, fromY, toX, toY);
8019 currentMove = forwardMostMove;
8022 if (instant) return;
8024 DisplayMove(currentMove - 1);
8025 DrawPosition(FALSE, boards[currentMove]);
8026 DisplayBothClocks();
8027 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
8030 void SendEgtPath(ChessProgramState *cps)
8031 { /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
8032 char buf[MSG_SIZ], name[MSG_SIZ], *p;
8034 if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
8037 char c, *q = name+1, *r, *s;
8039 name[0] = ','; // extract next format name from feature and copy with prefixed ','
8040 while(*p && *p != ',') *q++ = *p++;
8042 if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
8043 strcmp(name, ",nalimov:") == 0 ) {
8044 // take nalimov path from the menu-changeable option first, if it is defined
8045 sprintf(buf, "egtpath nalimov %s\n", appData.defaultPathEGTB);
8046 SendToProgram(buf,cps); // send egtbpath command for nalimov
8048 if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
8049 (s = StrStr(appData.egtFormats, name)) != NULL) {
8050 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
8051 s = r = StrStr(s, ":") + 1; // beginning of path info
8052 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
8053 c = *r; *r = 0; // temporarily null-terminate path info
8054 *--q = 0; // strip of trailig ':' from name
8055 sprintf(buf, "egtpath %s %s\n", name+1, s);
8057 SendToProgram(buf,cps); // send egtbpath command for this format
8059 if(*p == ',') p++; // read away comma to position for next format name
8064 InitChessProgram(cps, setup)
8065 ChessProgramState *cps;
8066 int setup; /* [HGM] needed to setup FRC opening position */
8068 char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
8069 if (appData.noChessProgram) return;
8070 hintRequested = FALSE;
8071 bookRequested = FALSE;
8073 /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
8074 /* moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
8075 if(cps->memSize) { /* [HGM] memory */
8076 sprintf(buf, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
8077 SendToProgram(buf, cps);
8079 SendEgtPath(cps); /* [HGM] EGT */
8080 if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
8081 sprintf(buf, "cores %d\n", appData.smpCores);
8082 SendToProgram(buf, cps);
8085 SendToProgram(cps->initString, cps);
8086 if (gameInfo.variant != VariantNormal &&
8087 gameInfo.variant != VariantLoadable
8088 /* [HGM] also send variant if board size non-standard */
8089 || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
8091 char *v = VariantName(gameInfo.variant);
8092 if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
8093 /* [HGM] in protocol 1 we have to assume all variants valid */
8094 sprintf(buf, _("Variant %s not supported by %s"), v, cps->tidy);
8095 DisplayFatalError(buf, 0, 1);
8099 /* [HGM] make prefix for non-standard board size. Awkward testing... */
8100 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8101 if( gameInfo.variant == VariantXiangqi )
8102 overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
8103 if( gameInfo.variant == VariantShogi )
8104 overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
8105 if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
8106 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
8107 if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
8108 gameInfo.variant == VariantGothic || gameInfo.variant == VariantFalcon )
8109 overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8110 if( gameInfo.variant == VariantCourier )
8111 overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8112 if( gameInfo.variant == VariantSuper )
8113 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
8114 if( gameInfo.variant == VariantGreat )
8115 overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
8118 sprintf(b, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
8119 gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
8120 /* [HGM] varsize: try first if this defiant size variant is specifically known */
8121 if(StrStr(cps->variants, b) == NULL) {
8122 // specific sized variant not known, check if general sizing allowed
8123 if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
8124 if(StrStr(cps->variants, "boardsize") == NULL) {
8125 sprintf(buf, "Board size %dx%d+%d not supported by %s",
8126 gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
8127 DisplayFatalError(buf, 0, 1);
8130 /* [HGM] here we really should compare with the maximum supported board size */
8133 } else sprintf(b, "%s", VariantName(gameInfo.variant));
8134 sprintf(buf, "variant %s\n", b);
8135 SendToProgram(buf, cps);
8137 currentlyInitializedVariant = gameInfo.variant;
8139 /* [HGM] send opening position in FRC to first engine */
8141 SendToProgram("force\n", cps);
8143 /* engine is now in force mode! Set flag to wake it up after first move. */
8144 setboardSpoiledMachineBlack = 1;
8148 snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
8149 SendToProgram(buf, cps);
8151 cps->maybeThinking = FALSE;
8152 cps->offeredDraw = 0;
8153 if (!appData.icsActive) {
8154 SendTimeControl(cps, movesPerSession, timeControl,
8155 timeIncrement, appData.searchDepth,
8158 if (appData.showThinking
8159 // [HGM] thinking: four options require thinking output to be sent
8160 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
8162 SendToProgram("post\n", cps);
8164 SendToProgram("hard\n", cps);
8165 if (!appData.ponderNextMove) {
8166 /* Warning: "easy" is a toggle in GNU Chess, so don't send
8167 it without being sure what state we are in first. "hard"
8168 is not a toggle, so that one is OK.
8170 SendToProgram("easy\n", cps);
8173 sprintf(buf, "ping %d\n", ++cps->lastPing);
8174 SendToProgram(buf, cps);
8176 cps->initDone = TRUE;
8181 StartChessProgram(cps)
8182 ChessProgramState *cps;
8187 if (appData.noChessProgram) return;
8188 cps->initDone = FALSE;
8190 if (strcmp(cps->host, "localhost") == 0) {
8191 err = StartChildProcess(cps->program, cps->dir, &cps->pr);
8192 } else if (*appData.remoteShell == NULLCHAR) {
8193 err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
8195 if (*appData.remoteUser == NULLCHAR) {
8196 snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
8199 snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
8200 cps->host, appData.remoteUser, cps->program);
8202 err = StartChildProcess(buf, "", &cps->pr);
8206 sprintf(buf, _("Startup failure on '%s'"), cps->program);
8207 DisplayFatalError(buf, err, 1);
8213 cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
8214 if (cps->protocolVersion > 1) {
8215 sprintf(buf, "xboard\nprotover %d\n", cps->protocolVersion);
8216 cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
8217 cps->comboCnt = 0; // and values of combo boxes
8218 SendToProgram(buf, cps);
8220 SendToProgram("xboard\n", cps);
8226 TwoMachinesEventIfReady P((void))
8228 if (first.lastPing != first.lastPong) {
8229 DisplayMessage("", _("Waiting for first chess program"));
8230 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
8233 if (second.lastPing != second.lastPong) {
8234 DisplayMessage("", _("Waiting for second chess program"));
8235 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
8243 NextMatchGame P((void))
8245 int index; /* [HGM] autoinc: step load index during match */
8247 if (*appData.loadGameFile != NULLCHAR) {
8248 index = appData.loadGameIndex;
8249 if(index < 0) { // [HGM] autoinc
8250 lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
8251 if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
8253 LoadGameFromFile(appData.loadGameFile,
8255 appData.loadGameFile, FALSE);
8256 } else if (*appData.loadPositionFile != NULLCHAR) {
8257 index = appData.loadPositionIndex;
8258 if(index < 0) { // [HGM] autoinc
8259 lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
8260 if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
8262 LoadPositionFromFile(appData.loadPositionFile,
8264 appData.loadPositionFile);
8266 TwoMachinesEventIfReady();
8269 void UserAdjudicationEvent( int result )
8271 ChessMove gameResult = GameIsDrawn;
8274 gameResult = WhiteWins;
8276 else if( result < 0 ) {
8277 gameResult = BlackWins;
8280 if( gameMode == TwoMachinesPlay ) {
8281 GameEnds( gameResult, "User adjudication", GE_XBOARD );
8286 // [HGM] save: calculate checksum of game to make games easily identifiable
8287 int StringCheckSum(char *s)
8290 if(s==NULL) return 0;
8291 while(*s) i = i*259 + *s++;
8298 for(i=backwardMostMove; i<forwardMostMove; i++) {
8299 sum += pvInfoList[i].depth;
8300 sum += StringCheckSum(parseList[i]);
8301 sum += StringCheckSum(commentList[i]);
8304 if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
8305 return sum + StringCheckSum(commentList[i]);
8306 } // end of save patch
8309 GameEnds(result, resultDetails, whosays)
8311 char *resultDetails;
8314 GameMode nextGameMode;
8318 if(endingGame) return; /* [HGM] crash: forbid recursion */
8321 if (appData.debugMode) {
8322 fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
8323 result, resultDetails ? resultDetails : "(null)", whosays);
8326 if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
8327 /* If we are playing on ICS, the server decides when the
8328 game is over, but the engine can offer to draw, claim
8332 if (appData.zippyPlay && first.initDone) {
8333 if (result == GameIsDrawn) {
8334 /* In case draw still needs to be claimed */
8335 SendToICS(ics_prefix);
8336 SendToICS("draw\n");
8337 } else if (StrCaseStr(resultDetails, "resign")) {
8338 SendToICS(ics_prefix);
8339 SendToICS("resign\n");
8343 endingGame = 0; /* [HGM] crash */
8347 /* If we're loading the game from a file, stop */
8348 if (whosays == GE_FILE) {
8349 (void) StopLoadGameTimer();
8353 /* Cancel draw offers */
8354 first.offeredDraw = second.offeredDraw = 0;
8356 /* If this is an ICS game, only ICS can really say it's done;
8357 if not, anyone can. */
8358 isIcsGame = (gameMode == IcsPlayingWhite ||
8359 gameMode == IcsPlayingBlack ||
8360 gameMode == IcsObserving ||
8361 gameMode == IcsExamining);
8363 if (!isIcsGame || whosays == GE_ICS) {
8364 /* OK -- not an ICS game, or ICS said it was done */
8366 if (!isIcsGame && !appData.noChessProgram)
8367 SetUserThinkingEnables();
8369 /* [HGM] if a machine claims the game end we verify this claim */
8370 if(gameMode == TwoMachinesPlay && appData.testClaims) {
8371 if(appData.testLegality && whosays >= GE_ENGINE1 ) {
8373 ChessMove trueResult = (ChessMove) -1;
8375 claimer = whosays == GE_ENGINE1 ? /* color of claimer */
8376 first.twoMachinesColor[0] :
8377 second.twoMachinesColor[0] ;
8379 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
8380 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
8381 /* [HGM] verify: engine mate claims accepted if they were flagged */
8382 trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
8384 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
8385 /* [HGM] verify: engine mate claims accepted if they were flagged */
8386 trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8388 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
8389 trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
8392 // now verify win claims, but not in drop games, as we don't understand those yet
8393 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
8394 || gameInfo.variant == VariantGreat) &&
8395 (result == WhiteWins && claimer == 'w' ||
8396 result == BlackWins && claimer == 'b' ) ) { // case to verify: engine claims own win
8397 if (appData.debugMode) {
8398 fprintf(debugFP, "result=%d sp=%d move=%d\n",
8399 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
8401 if(result != trueResult) {
8402 sprintf(buf, "False win claim: '%s'", resultDetails);
8403 result = claimer == 'w' ? BlackWins : WhiteWins;
8404 resultDetails = buf;
8407 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
8408 && (forwardMostMove <= backwardMostMove ||
8409 (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
8410 (claimer=='b')==(forwardMostMove&1))
8412 /* [HGM] verify: draws that were not flagged are false claims */
8413 sprintf(buf, "False draw claim: '%s'", resultDetails);
8414 result = claimer == 'w' ? BlackWins : WhiteWins;
8415 resultDetails = buf;
8417 /* (Claiming a loss is accepted no questions asked!) */
8419 /* [HGM] bare: don't allow bare King to win */
8420 if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
8421 && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
8422 && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
8423 && result != GameIsDrawn)
8424 { int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
8425 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
8426 int p = (signed char)boards[forwardMostMove][i][j] - color;
8427 if(p >= 0 && p <= (int)WhiteKing) k++;
8429 if (appData.debugMode) {
8430 fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
8431 result, resultDetails ? resultDetails : "(null)", whosays, k, color);
8434 result = GameIsDrawn;
8435 sprintf(buf, "%s but bare king", resultDetails);
8436 resultDetails = buf;
8442 if(serverMoves != NULL && !loadFlag) { char c = '=';
8443 if(result==WhiteWins) c = '+';
8444 if(result==BlackWins) c = '-';
8445 if(resultDetails != NULL)
8446 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
8448 if (resultDetails != NULL) {
8449 gameInfo.result = result;
8450 gameInfo.resultDetails = StrSave(resultDetails);
8452 /* display last move only if game was not loaded from file */
8453 if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
8454 DisplayMove(currentMove - 1);
8456 if (forwardMostMove != 0) {
8457 if (gameMode != PlayFromGameFile && gameMode != EditGame
8458 && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
8460 if (*appData.saveGameFile != NULLCHAR) {
8461 SaveGameToFile(appData.saveGameFile, TRUE);
8462 } else if (appData.autoSaveGames) {
8465 if (*appData.savePositionFile != NULLCHAR) {
8466 SavePositionToFile(appData.savePositionFile);
8471 /* Tell program how game ended in case it is learning */
8472 /* [HGM] Moved this to after saving the PGN, just in case */
8473 /* engine died and we got here through time loss. In that */
8474 /* case we will get a fatal error writing the pipe, which */
8475 /* would otherwise lose us the PGN. */
8476 /* [HGM] crash: not needed anymore, but doesn't hurt; */
8477 /* output during GameEnds should never be fatal anymore */
8478 if (gameMode == MachinePlaysWhite ||
8479 gameMode == MachinePlaysBlack ||
8480 gameMode == TwoMachinesPlay ||
8481 gameMode == IcsPlayingWhite ||
8482 gameMode == IcsPlayingBlack ||
8483 gameMode == BeginningOfGame) {
8485 sprintf(buf, "result %s {%s}\n", PGNResult(result),
8487 if (first.pr != NoProc) {
8488 SendToProgram(buf, &first);
8490 if (second.pr != NoProc &&
8491 gameMode == TwoMachinesPlay) {
8492 SendToProgram(buf, &second);
8497 if (appData.icsActive) {
8498 if (appData.quietPlay &&
8499 (gameMode == IcsPlayingWhite ||
8500 gameMode == IcsPlayingBlack)) {
8501 SendToICS(ics_prefix);
8502 SendToICS("set shout 1\n");
8504 nextGameMode = IcsIdle;
8505 ics_user_moved = FALSE;
8506 /* clean up premove. It's ugly when the game has ended and the
8507 * premove highlights are still on the board.
8511 ClearPremoveHighlights();
8512 DrawPosition(FALSE, boards[currentMove]);
8514 if (whosays == GE_ICS) {
8517 if (gameMode == IcsPlayingWhite)
8519 else if(gameMode == IcsPlayingBlack)
8523 if (gameMode == IcsPlayingBlack)
8525 else if(gameMode == IcsPlayingWhite)
8532 PlayIcsUnfinishedSound();
8535 } else if (gameMode == EditGame ||
8536 gameMode == PlayFromGameFile ||
8537 gameMode == AnalyzeMode ||
8538 gameMode == AnalyzeFile) {
8539 nextGameMode = gameMode;
8541 nextGameMode = EndOfGame;
8546 nextGameMode = gameMode;
8549 if (appData.noChessProgram) {
8550 gameMode = nextGameMode;
8552 endingGame = 0; /* [HGM] crash */
8557 /* Put first chess program into idle state */
8558 if (first.pr != NoProc &&
8559 (gameMode == MachinePlaysWhite ||
8560 gameMode == MachinePlaysBlack ||
8561 gameMode == TwoMachinesPlay ||
8562 gameMode == IcsPlayingWhite ||
8563 gameMode == IcsPlayingBlack ||
8564 gameMode == BeginningOfGame)) {
8565 SendToProgram("force\n", &first);
8566 if (first.usePing) {
8568 sprintf(buf, "ping %d\n", ++first.lastPing);
8569 SendToProgram(buf, &first);
8572 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
8573 /* Kill off first chess program */
8574 if (first.isr != NULL)
8575 RemoveInputSource(first.isr);
8578 if (first.pr != NoProc) {
8580 DoSleep( appData.delayBeforeQuit );
8581 SendToProgram("quit\n", &first);
8582 DoSleep( appData.delayAfterQuit );
8583 DestroyChildProcess(first.pr, first.useSigterm);
8588 /* Put second chess program into idle state */
8589 if (second.pr != NoProc &&
8590 gameMode == TwoMachinesPlay) {
8591 SendToProgram("force\n", &second);
8592 if (second.usePing) {
8594 sprintf(buf, "ping %d\n", ++second.lastPing);
8595 SendToProgram(buf, &second);
8598 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
8599 /* Kill off second chess program */
8600 if (second.isr != NULL)
8601 RemoveInputSource(second.isr);
8604 if (second.pr != NoProc) {
8605 DoSleep( appData.delayBeforeQuit );
8606 SendToProgram("quit\n", &second);
8607 DoSleep( appData.delayAfterQuit );
8608 DestroyChildProcess(second.pr, second.useSigterm);
8613 if (matchMode && gameMode == TwoMachinesPlay) {
8616 if (first.twoMachinesColor[0] == 'w') {
8623 if (first.twoMachinesColor[0] == 'b') {
8632 if (matchGame < appData.matchGames) {
8634 if(appData.sameColorGames <= 1) { /* [HGM] alternate: suppress color swap */
8635 tmp = first.twoMachinesColor;
8636 first.twoMachinesColor = second.twoMachinesColor;
8637 second.twoMachinesColor = tmp;
8639 gameMode = nextGameMode;
8641 if(appData.matchPause>10000 || appData.matchPause<10)
8642 appData.matchPause = 10000; /* [HGM] make pause adjustable */
8643 ScheduleDelayedEvent(NextMatchGame, appData.matchPause);
8644 endingGame = 0; /* [HGM] crash */
8648 gameMode = nextGameMode;
8649 sprintf(buf, _("Match %s vs. %s: final score %d-%d-%d"),
8650 first.tidy, second.tidy,
8651 first.matchWins, second.matchWins,
8652 appData.matchGames - (first.matchWins + second.matchWins));
8653 DisplayFatalError(buf, 0, 0);
8656 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
8657 !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
8659 gameMode = nextGameMode;
8661 endingGame = 0; /* [HGM] crash */
8664 /* Assumes program was just initialized (initString sent).
8665 Leaves program in force mode. */
8667 FeedMovesToProgram(cps, upto)
8668 ChessProgramState *cps;
8673 if (appData.debugMode)
8674 fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
8675 startedFromSetupPosition ? "position and " : "",
8676 backwardMostMove, upto, cps->which);
8677 if(currentlyInitializedVariant != gameInfo.variant) { char buf[MSG_SIZ];
8678 // [HGM] variantswitch: make engine aware of new variant
8679 if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
8680 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
8681 sprintf(buf, "variant %s\n", VariantName(gameInfo.variant));
8682 SendToProgram(buf, cps);
8683 currentlyInitializedVariant = gameInfo.variant;
8685 SendToProgram("force\n", cps);
8686 if (startedFromSetupPosition) {
8687 SendBoard(cps, backwardMostMove);
8688 if (appData.debugMode) {
8689 fprintf(debugFP, "feedMoves\n");
8692 for (i = backwardMostMove; i < upto; i++) {
8693 SendMoveToProgram(i, cps);
8699 ResurrectChessProgram()
8701 /* The chess program may have exited.
8702 If so, restart it and feed it all the moves made so far. */
8704 if (appData.noChessProgram || first.pr != NoProc) return;
8706 StartChessProgram(&first);
8707 InitChessProgram(&first, FALSE);
8708 FeedMovesToProgram(&first, currentMove);
8710 if (!first.sendTime) {
8711 /* can't tell gnuchess what its clock should read,
8712 so we bow to its notion. */
8714 timeRemaining[0][currentMove] = whiteTimeRemaining;
8715 timeRemaining[1][currentMove] = blackTimeRemaining;
8718 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
8719 appData.icsEngineAnalyze) && first.analysisSupport) {
8720 SendToProgram("analyze\n", &first);
8721 first.analyzing = TRUE;
8734 if (appData.debugMode) {
8735 fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
8736 redraw, init, gameMode);
8738 CleanupTail(); // [HGM] vari: delete any stored variations
8739 pausing = pauseExamInvalid = FALSE;
8740 startedFromSetupPosition = blackPlaysFirst = FALSE;
8742 whiteFlag = blackFlag = FALSE;
8743 userOfferedDraw = FALSE;
8744 hintRequested = bookRequested = FALSE;
8745 first.maybeThinking = FALSE;
8746 second.maybeThinking = FALSE;
8747 first.bookSuspend = FALSE; // [HGM] book
8748 second.bookSuspend = FALSE;
8749 thinkOutput[0] = NULLCHAR;
8750 lastHint[0] = NULLCHAR;
8751 ClearGameInfo(&gameInfo);
8752 gameInfo.variant = StringToVariant(appData.variant);
8753 ics_user_moved = ics_clock_paused = FALSE;
8754 ics_getting_history = H_FALSE;
8756 white_holding[0] = black_holding[0] = NULLCHAR;
8757 ClearProgramStats();
8758 opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
8762 flipView = appData.flipView;
8763 ClearPremoveHighlights();
8765 alarmSounded = FALSE;
8767 GameEnds((ChessMove) 0, NULL, GE_PLAYER);
8768 if(appData.serverMovesName != NULL) {
8769 /* [HGM] prepare to make moves file for broadcasting */
8770 clock_t t = clock();
8771 if(serverMoves != NULL) fclose(serverMoves);
8772 serverMoves = fopen(appData.serverMovesName, "r");
8773 if(serverMoves != NULL) {
8774 fclose(serverMoves);
8775 /* delay 15 sec before overwriting, so all clients can see end */
8776 while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
8778 serverMoves = fopen(appData.serverMovesName, "w");
8782 gameMode = BeginningOfGame;
8784 if(appData.icsActive) gameInfo.variant = VariantNormal;
8785 currentMove = forwardMostMove = backwardMostMove = 0;
8786 InitPosition(redraw);
8787 for (i = 0; i < MAX_MOVES; i++) {
8788 if (commentList[i] != NULL) {
8789 free(commentList[i]);
8790 commentList[i] = NULL;
8794 timeRemaining[0][0] = whiteTimeRemaining;
8795 timeRemaining[1][0] = blackTimeRemaining;
8796 if (first.pr == NULL) {
8797 StartChessProgram(&first);
8800 InitChessProgram(&first, startedFromSetupPosition);
8803 DisplayMessage("", "");
8804 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
8805 lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
8812 if (!AutoPlayOneMove())
8814 if (matchMode || appData.timeDelay == 0)
8816 if (appData.timeDelay < 0 || gameMode == AnalyzeFile)
8818 StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
8827 int fromX, fromY, toX, toY;
8829 if (appData.debugMode) {
8830 fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
8833 if (gameMode != PlayFromGameFile)
8836 if (currentMove >= forwardMostMove) {
8837 gameMode = EditGame;
8840 /* [AS] Clear current move marker at the end of a game */
8841 /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
8846 toX = moveList[currentMove][2] - AAA;
8847 toY = moveList[currentMove][3] - ONE;
8849 if (moveList[currentMove][1] == '@') {
8850 if (appData.highlightLastMove) {
8851 SetHighlights(-1, -1, toX, toY);
8854 fromX = moveList[currentMove][0] - AAA;
8855 fromY = moveList[currentMove][1] - ONE;
8857 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
8859 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
8861 if (appData.highlightLastMove) {
8862 SetHighlights(fromX, fromY, toX, toY);
8865 DisplayMove(currentMove);
8866 SendMoveToProgram(currentMove++, &first);
8867 DisplayBothClocks();
8868 DrawPosition(FALSE, boards[currentMove]);
8869 // [HGM] PV info: always display, routine tests if empty
8870 DisplayComment(currentMove - 1, commentList[currentMove]);
8876 LoadGameOneMove(readAhead)
8877 ChessMove readAhead;
8879 int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
8880 char promoChar = NULLCHAR;
8885 if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
8886 gameMode != AnalyzeMode && gameMode != Training) {
8891 yyboardindex = forwardMostMove;
8892 if (readAhead != (ChessMove)0) {
8893 moveType = readAhead;
8895 if (gameFileFP == NULL)
8897 moveType = (ChessMove) yylex();
8903 if (appData.debugMode)
8904 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
8907 /* append the comment but don't display it */
8908 AppendComment(currentMove, p, FALSE);
8911 case WhiteCapturesEnPassant:
8912 case BlackCapturesEnPassant:
8913 case WhitePromotionChancellor:
8914 case BlackPromotionChancellor:
8915 case WhitePromotionArchbishop:
8916 case BlackPromotionArchbishop:
8917 case WhitePromotionCentaur:
8918 case BlackPromotionCentaur:
8919 case WhitePromotionQueen:
8920 case BlackPromotionQueen:
8921 case WhitePromotionRook:
8922 case BlackPromotionRook:
8923 case WhitePromotionBishop:
8924 case BlackPromotionBishop:
8925 case WhitePromotionKnight:
8926 case BlackPromotionKnight:
8927 case WhitePromotionKing:
8928 case BlackPromotionKing:
8930 case WhiteKingSideCastle:
8931 case WhiteQueenSideCastle:
8932 case BlackKingSideCastle:
8933 case BlackQueenSideCastle:
8934 case WhiteKingSideCastleWild:
8935 case WhiteQueenSideCastleWild:
8936 case BlackKingSideCastleWild:
8937 case BlackQueenSideCastleWild:
8939 case WhiteHSideCastleFR:
8940 case WhiteASideCastleFR:
8941 case BlackHSideCastleFR:
8942 case BlackASideCastleFR:
8944 if (appData.debugMode)
8945 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
8946 fromX = currentMoveString[0] - AAA;
8947 fromY = currentMoveString[1] - ONE;
8948 toX = currentMoveString[2] - AAA;
8949 toY = currentMoveString[3] - ONE;
8950 promoChar = currentMoveString[4];
8955 if (appData.debugMode)
8956 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
8957 fromX = moveType == WhiteDrop ?
8958 (int) CharToPiece(ToUpper(currentMoveString[0])) :
8959 (int) CharToPiece(ToLower(currentMoveString[0]));
8961 toX = currentMoveString[2] - AAA;
8962 toY = currentMoveString[3] - ONE;
8968 case GameUnfinished:
8969 if (appData.debugMode)
8970 fprintf(debugFP, "Parsed game end: %s\n", yy_text);
8971 p = strchr(yy_text, '{');
8972 if (p == NULL) p = strchr(yy_text, '(');
8975 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
8977 q = strchr(p, *p == '{' ? '}' : ')');
8978 if (q != NULL) *q = NULLCHAR;
8981 GameEnds(moveType, p, GE_FILE);
8983 if (cmailMsgLoaded) {
8985 flipView = WhiteOnMove(currentMove);
8986 if (moveType == GameUnfinished) flipView = !flipView;
8987 if (appData.debugMode)
8988 fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
8992 case (ChessMove) 0: /* end of file */
8993 if (appData.debugMode)
8994 fprintf(debugFP, "Parser hit end of file\n");
8995 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9001 if (WhiteOnMove(currentMove)) {
9002 GameEnds(BlackWins, "Black mates", GE_FILE);
9004 GameEnds(WhiteWins, "White mates", GE_FILE);
9008 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
9015 if (lastLoadGameStart == GNUChessGame) {
9016 /* GNUChessGames have numbers, but they aren't move numbers */
9017 if (appData.debugMode)
9018 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
9019 yy_text, (int) moveType);
9020 return LoadGameOneMove((ChessMove)0); /* tail recursion */
9022 /* else fall thru */
9027 /* Reached start of next game in file */
9028 if (appData.debugMode)
9029 fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
9030 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9036 if (WhiteOnMove(currentMove)) {
9037 GameEnds(BlackWins, "Black mates", GE_FILE);
9039 GameEnds(WhiteWins, "White mates", GE_FILE);
9043 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
9049 case PositionDiagram: /* should not happen; ignore */
9050 case ElapsedTime: /* ignore */
9051 case NAG: /* ignore */
9052 if (appData.debugMode)
9053 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
9054 yy_text, (int) moveType);
9055 return LoadGameOneMove((ChessMove)0); /* tail recursion */
9058 if (appData.testLegality) {
9059 if (appData.debugMode)
9060 fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
9061 sprintf(move, _("Illegal move: %d.%s%s"),
9062 (forwardMostMove / 2) + 1,
9063 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9064 DisplayError(move, 0);
9067 if (appData.debugMode)
9068 fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
9069 yy_text, currentMoveString);
9070 fromX = currentMoveString[0] - AAA;
9071 fromY = currentMoveString[1] - ONE;
9072 toX = currentMoveString[2] - AAA;
9073 toY = currentMoveString[3] - ONE;
9074 promoChar = currentMoveString[4];
9079 if (appData.debugMode)
9080 fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
9081 sprintf(move, _("Ambiguous move: %d.%s%s"),
9082 (forwardMostMove / 2) + 1,
9083 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9084 DisplayError(move, 0);
9089 case ImpossibleMove:
9090 if (appData.debugMode)
9091 fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
9092 sprintf(move, _("Illegal move: %d.%s%s"),
9093 (forwardMostMove / 2) + 1,
9094 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9095 DisplayError(move, 0);
9101 if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
9102 DrawPosition(FALSE, boards[currentMove]);
9103 DisplayBothClocks();
9104 if (!appData.matchMode) // [HGM] PV info: routine tests if empty
9105 DisplayComment(currentMove - 1, commentList[currentMove]);
9107 (void) StopLoadGameTimer();
9109 cmailOldMove = forwardMostMove;
9112 /* currentMoveString is set as a side-effect of yylex */
9113 strcat(currentMoveString, "\n");
9114 strcpy(moveList[forwardMostMove], currentMoveString);
9116 thinkOutput[0] = NULLCHAR;
9117 MakeMove(fromX, fromY, toX, toY, promoChar);
9118 currentMove = forwardMostMove;
9123 /* Load the nth game from the given file */
9125 LoadGameFromFile(filename, n, title, useList)
9129 /*Boolean*/ int useList;
9134 if (strcmp(filename, "-") == 0) {
9138 f = fopen(filename, "rb");
9140 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9141 DisplayError(buf, errno);
9145 if (fseek(f, 0, 0) == -1) {
9146 /* f is not seekable; probably a pipe */
9149 if (useList && n == 0) {
9150 int error = GameListBuild(f);
9152 DisplayError(_("Cannot build game list"), error);
9153 } else if (!ListEmpty(&gameList) &&
9154 ((ListGame *) gameList.tailPred)->number > 1) {
9155 GameListPopUp(f, title);
9162 return LoadGame(f, n, title, FALSE);
9167 MakeRegisteredMove()
9169 int fromX, fromY, toX, toY;
9171 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
9172 switch (cmailMoveType[lastLoadGameNumber - 1]) {
9175 if (appData.debugMode)
9176 fprintf(debugFP, "Restoring %s for game %d\n",
9177 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
9179 thinkOutput[0] = NULLCHAR;
9180 strcpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1]);
9181 fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
9182 fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
9183 toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
9184 toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
9185 promoChar = cmailMove[lastLoadGameNumber - 1][4];
9186 MakeMove(fromX, fromY, toX, toY, promoChar);
9187 ShowMove(fromX, fromY, toX, toY);
9189 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9196 if (WhiteOnMove(currentMove)) {
9197 GameEnds(BlackWins, "Black mates", GE_PLAYER);
9199 GameEnds(WhiteWins, "White mates", GE_PLAYER);
9204 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
9211 if (WhiteOnMove(currentMove)) {
9212 GameEnds(BlackWins, "White resigns", GE_PLAYER);
9214 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
9219 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
9230 /* Wrapper around LoadGame for use when a Cmail message is loaded */
9232 CmailLoadGame(f, gameNumber, title, useList)
9240 if (gameNumber > nCmailGames) {
9241 DisplayError(_("No more games in this message"), 0);
9244 if (f == lastLoadGameFP) {
9245 int offset = gameNumber - lastLoadGameNumber;
9247 cmailMsg[0] = NULLCHAR;
9248 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
9249 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
9250 nCmailMovesRegistered--;
9252 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
9253 if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
9254 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
9257 if (! RegisterMove()) return FALSE;
9261 retVal = LoadGame(f, gameNumber, title, useList);
9263 /* Make move registered during previous look at this game, if any */
9264 MakeRegisteredMove();
9266 if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
9267 commentList[currentMove]
9268 = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
9269 DisplayComment(currentMove - 1, commentList[currentMove]);
9275 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
9280 int gameNumber = lastLoadGameNumber + offset;
9281 if (lastLoadGameFP == NULL) {
9282 DisplayError(_("No game has been loaded yet"), 0);
9285 if (gameNumber <= 0) {
9286 DisplayError(_("Can't back up any further"), 0);
9289 if (cmailMsgLoaded) {
9290 return CmailLoadGame(lastLoadGameFP, gameNumber,
9291 lastLoadGameTitle, lastLoadGameUseList);
9293 return LoadGame(lastLoadGameFP, gameNumber,
9294 lastLoadGameTitle, lastLoadGameUseList);
9300 /* Load the nth game from open file f */
9302 LoadGame(f, gameNumber, title, useList)
9310 int gn = gameNumber;
9311 ListGame *lg = NULL;
9314 GameMode oldGameMode;
9315 VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
9317 if (appData.debugMode)
9318 fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
9320 if (gameMode == Training )
9321 SetTrainingModeOff();
9323 oldGameMode = gameMode;
9324 if (gameMode != BeginningOfGame) {
9329 if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
9330 fclose(lastLoadGameFP);
9334 lg = (ListGame *) ListElem(&gameList, gameNumber-1);
9337 fseek(f, lg->offset, 0);
9338 GameListHighlight(gameNumber);
9342 DisplayError(_("Game number out of range"), 0);
9347 if (fseek(f, 0, 0) == -1) {
9348 if (f == lastLoadGameFP ?
9349 gameNumber == lastLoadGameNumber + 1 :
9353 DisplayError(_("Can't seek on game file"), 0);
9359 lastLoadGameNumber = gameNumber;
9360 strcpy(lastLoadGameTitle, title);
9361 lastLoadGameUseList = useList;
9365 if (lg && lg->gameInfo.white && lg->gameInfo.black) {
9366 snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
9367 lg->gameInfo.black);
9369 } else if (*title != NULLCHAR) {
9370 if (gameNumber > 1) {
9371 sprintf(buf, "%s %d", title, gameNumber);
9374 DisplayTitle(title);
9378 if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
9379 gameMode = PlayFromGameFile;
9383 currentMove = forwardMostMove = backwardMostMove = 0;
9384 CopyBoard(boards[0], initialPosition);
9388 * Skip the first gn-1 games in the file.
9389 * Also skip over anything that precedes an identifiable
9390 * start of game marker, to avoid being confused by
9391 * garbage at the start of the file. Currently
9392 * recognized start of game markers are the move number "1",
9393 * the pattern "gnuchess .* game", the pattern
9394 * "^[#;%] [^ ]* game file", and a PGN tag block.
9395 * A game that starts with one of the latter two patterns
9396 * will also have a move number 1, possibly
9397 * following a position diagram.
9398 * 5-4-02: Let's try being more lenient and allowing a game to
9399 * start with an unnumbered move. Does that break anything?
9401 cm = lastLoadGameStart = (ChessMove) 0;
9403 yyboardindex = forwardMostMove;
9404 cm = (ChessMove) yylex();
9407 if (cmailMsgLoaded) {
9408 nCmailGames = CMAIL_MAX_GAMES - gn;
9411 DisplayError(_("Game not found in file"), 0);
9418 lastLoadGameStart = cm;
9422 switch (lastLoadGameStart) {
9429 gn--; /* count this game */
9430 lastLoadGameStart = cm;
9439 switch (lastLoadGameStart) {
9444 gn--; /* count this game */
9445 lastLoadGameStart = cm;
9448 lastLoadGameStart = cm; /* game counted already */
9456 yyboardindex = forwardMostMove;
9457 cm = (ChessMove) yylex();
9458 } while (cm == PGNTag || cm == Comment);
9465 if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
9466 if ( cmailResult[CMAIL_MAX_GAMES - gn - 1]
9467 != CMAIL_OLD_RESULT) {
9469 cmailResult[ CMAIL_MAX_GAMES
9470 - gn - 1] = CMAIL_OLD_RESULT;
9476 /* Only a NormalMove can be at the start of a game
9477 * without a position diagram. */
9478 if (lastLoadGameStart == (ChessMove) 0) {
9480 lastLoadGameStart = MoveNumberOne;
9489 if (appData.debugMode)
9490 fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
9492 if (cm == XBoardGame) {
9493 /* Skip any header junk before position diagram and/or move 1 */
9495 yyboardindex = forwardMostMove;
9496 cm = (ChessMove) yylex();
9498 if (cm == (ChessMove) 0 ||
9499 cm == GNUChessGame || cm == XBoardGame) {
9500 /* Empty game; pretend end-of-file and handle later */
9505 if (cm == MoveNumberOne || cm == PositionDiagram ||
9506 cm == PGNTag || cm == Comment)
9509 } else if (cm == GNUChessGame) {
9510 if (gameInfo.event != NULL) {
9511 free(gameInfo.event);
9513 gameInfo.event = StrSave(yy_text);
9516 startedFromSetupPosition = FALSE;
9517 while (cm == PGNTag) {
9518 if (appData.debugMode)
9519 fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
9520 err = ParsePGNTag(yy_text, &gameInfo);
9521 if (!err) numPGNTags++;
9523 /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
9524 if(gameInfo.variant != oldVariant) {
9525 startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
9527 oldVariant = gameInfo.variant;
9528 if (appData.debugMode)
9529 fprintf(debugFP, "New variant %d\n", (int) oldVariant);
9533 if (gameInfo.fen != NULL) {
9534 Board initial_position;
9535 startedFromSetupPosition = TRUE;
9536 if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
9538 DisplayError(_("Bad FEN position in file"), 0);
9541 CopyBoard(boards[0], initial_position);
9542 if (blackPlaysFirst) {
9543 currentMove = forwardMostMove = backwardMostMove = 1;
9544 CopyBoard(boards[1], initial_position);
9545 strcpy(moveList[0], "");
9546 strcpy(parseList[0], "");
9547 timeRemaining[0][1] = whiteTimeRemaining;
9548 timeRemaining[1][1] = blackTimeRemaining;
9549 if (commentList[0] != NULL) {
9550 commentList[1] = commentList[0];
9551 commentList[0] = NULL;
9554 currentMove = forwardMostMove = backwardMostMove = 0;
9556 /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
9558 initialRulePlies = FENrulePlies;
9559 for( i=0; i< nrCastlingRights; i++ )
9560 initialRights[i] = initial_position[CASTLING][i];
9562 yyboardindex = forwardMostMove;
9564 gameInfo.fen = NULL;
9567 yyboardindex = forwardMostMove;
9568 cm = (ChessMove) yylex();
9570 /* Handle comments interspersed among the tags */
9571 while (cm == Comment) {
9573 if (appData.debugMode)
9574 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9576 AppendComment(currentMove, p, FALSE);
9577 yyboardindex = forwardMostMove;
9578 cm = (ChessMove) yylex();
9582 /* don't rely on existence of Event tag since if game was
9583 * pasted from clipboard the Event tag may not exist
9585 if (numPGNTags > 0){
9587 if (gameInfo.variant == VariantNormal) {
9588 gameInfo.variant = StringToVariant(gameInfo.event);
9591 if( appData.autoDisplayTags ) {
9592 tags = PGNTags(&gameInfo);
9593 TagsPopUp(tags, CmailMsg());
9598 /* Make something up, but don't display it now */
9603 if (cm == PositionDiagram) {
9606 Board initial_position;
9608 if (appData.debugMode)
9609 fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
9611 if (!startedFromSetupPosition) {
9613 for (i = BOARD_HEIGHT - 1; i >= 0; i--)
9614 for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
9624 initial_position[i][j++] = CharToPiece(*p);
9627 while (*p == ' ' || *p == '\t' ||
9628 *p == '\n' || *p == '\r') p++;
9630 if (strncmp(p, "black", strlen("black"))==0)
9631 blackPlaysFirst = TRUE;
9633 blackPlaysFirst = FALSE;
9634 startedFromSetupPosition = TRUE;
9636 CopyBoard(boards[0], initial_position);
9637 if (blackPlaysFirst) {
9638 currentMove = forwardMostMove = backwardMostMove = 1;
9639 CopyBoard(boards[1], initial_position);
9640 strcpy(moveList[0], "");
9641 strcpy(parseList[0], "");
9642 timeRemaining[0][1] = whiteTimeRemaining;
9643 timeRemaining[1][1] = blackTimeRemaining;
9644 if (commentList[0] != NULL) {
9645 commentList[1] = commentList[0];
9646 commentList[0] = NULL;
9649 currentMove = forwardMostMove = backwardMostMove = 0;
9652 yyboardindex = forwardMostMove;
9653 cm = (ChessMove) yylex();
9656 if (first.pr == NoProc) {
9657 StartChessProgram(&first);
9659 InitChessProgram(&first, FALSE);
9660 SendToProgram("force\n", &first);
9661 if (startedFromSetupPosition) {
9662 SendBoard(&first, forwardMostMove);
9663 if (appData.debugMode) {
9664 fprintf(debugFP, "Load Game\n");
9666 DisplayBothClocks();
9669 /* [HGM] server: flag to write setup moves in broadcast file as one */
9670 loadFlag = appData.suppressLoadMoves;
9672 while (cm == Comment) {
9674 if (appData.debugMode)
9675 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9677 AppendComment(currentMove, p, FALSE);
9678 yyboardindex = forwardMostMove;
9679 cm = (ChessMove) yylex();
9682 if ((cm == (ChessMove) 0 && lastLoadGameStart != (ChessMove) 0) ||
9683 cm == WhiteWins || cm == BlackWins ||
9684 cm == GameIsDrawn || cm == GameUnfinished) {
9685 DisplayMessage("", _("No moves in game"));
9686 if (cmailMsgLoaded) {
9687 if (appData.debugMode)
9688 fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
9692 DrawPosition(FALSE, boards[currentMove]);
9693 DisplayBothClocks();
9694 gameMode = EditGame;
9701 // [HGM] PV info: routine tests if comment empty
9702 if (!matchMode && (pausing || appData.timeDelay != 0)) {
9703 DisplayComment(currentMove - 1, commentList[currentMove]);
9705 if (!matchMode && appData.timeDelay != 0)
9706 DrawPosition(FALSE, boards[currentMove]);
9708 if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
9709 programStats.ok_to_send = 1;
9712 /* if the first token after the PGN tags is a move
9713 * and not move number 1, retrieve it from the parser
9715 if (cm != MoveNumberOne)
9716 LoadGameOneMove(cm);
9718 /* load the remaining moves from the file */
9719 while (LoadGameOneMove((ChessMove)0)) {
9720 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
9721 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
9724 /* rewind to the start of the game */
9725 currentMove = backwardMostMove;
9727 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
9729 if (oldGameMode == AnalyzeFile ||
9730 oldGameMode == AnalyzeMode) {
9734 if (matchMode || appData.timeDelay == 0) {
9736 gameMode = EditGame;
9738 } else if (appData.timeDelay > 0) {
9742 if (appData.debugMode)
9743 fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
9745 loadFlag = 0; /* [HGM] true game starts */
9749 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
9751 ReloadPosition(offset)
9754 int positionNumber = lastLoadPositionNumber + offset;
9755 if (lastLoadPositionFP == NULL) {
9756 DisplayError(_("No position has been loaded yet"), 0);
9759 if (positionNumber <= 0) {
9760 DisplayError(_("Can't back up any further"), 0);
9763 return LoadPosition(lastLoadPositionFP, positionNumber,
9764 lastLoadPositionTitle);
9767 /* Load the nth position from the given file */
9769 LoadPositionFromFile(filename, n, title)
9777 if (strcmp(filename, "-") == 0) {
9778 return LoadPosition(stdin, n, "stdin");
9780 f = fopen(filename, "rb");
9782 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9783 DisplayError(buf, errno);
9786 return LoadPosition(f, n, title);
9791 /* Load the nth position from the given open file, and close it */
9793 LoadPosition(f, positionNumber, title)
9798 char *p, line[MSG_SIZ];
9799 Board initial_position;
9800 int i, j, fenMode, pn;
9802 if (gameMode == Training )
9803 SetTrainingModeOff();
9805 if (gameMode != BeginningOfGame) {
9808 if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
9809 fclose(lastLoadPositionFP);
9811 if (positionNumber == 0) positionNumber = 1;
9812 lastLoadPositionFP = f;
9813 lastLoadPositionNumber = positionNumber;
9814 strcpy(lastLoadPositionTitle, title);
9815 if (first.pr == NoProc) {
9816 StartChessProgram(&first);
9817 InitChessProgram(&first, FALSE);
9819 pn = positionNumber;
9820 if (positionNumber < 0) {
9821 /* Negative position number means to seek to that byte offset */
9822 if (fseek(f, -positionNumber, 0) == -1) {
9823 DisplayError(_("Can't seek on position file"), 0);
9828 if (fseek(f, 0, 0) == -1) {
9829 if (f == lastLoadPositionFP ?
9830 positionNumber == lastLoadPositionNumber + 1 :
9831 positionNumber == 1) {
9834 DisplayError(_("Can't seek on position file"), 0);
9839 /* See if this file is FEN or old-style xboard */
9840 if (fgets(line, MSG_SIZ, f) == NULL) {
9841 DisplayError(_("Position not found in file"), 0);
9844 // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
9845 fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
9848 if (fenMode || line[0] == '#') pn--;
9850 /* skip positions before number pn */
9851 if (fgets(line, MSG_SIZ, f) == NULL) {
9853 DisplayError(_("Position not found in file"), 0);
9856 if (fenMode || line[0] == '#') pn--;
9861 if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
9862 DisplayError(_("Bad FEN position in file"), 0);
9866 (void) fgets(line, MSG_SIZ, f);
9867 (void) fgets(line, MSG_SIZ, f);
9869 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
9870 (void) fgets(line, MSG_SIZ, f);
9871 for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
9874 initial_position[i][j++] = CharToPiece(*p);
9878 blackPlaysFirst = FALSE;
9880 (void) fgets(line, MSG_SIZ, f);
9881 if (strncmp(line, "black", strlen("black"))==0)
9882 blackPlaysFirst = TRUE;
9885 startedFromSetupPosition = TRUE;
9887 SendToProgram("force\n", &first);
9888 CopyBoard(boards[0], initial_position);
9889 if (blackPlaysFirst) {
9890 currentMove = forwardMostMove = backwardMostMove = 1;
9891 strcpy(moveList[0], "");
9892 strcpy(parseList[0], "");
9893 CopyBoard(boards[1], initial_position);
9894 DisplayMessage("", _("Black to play"));
9896 currentMove = forwardMostMove = backwardMostMove = 0;
9897 DisplayMessage("", _("White to play"));
9899 initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
9900 SendBoard(&first, forwardMostMove);
9901 if (appData.debugMode) {
9903 for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
9904 for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
9905 fprintf(debugFP, "Load Position\n");
9908 if (positionNumber > 1) {
9909 sprintf(line, "%s %d", title, positionNumber);
9912 DisplayTitle(title);
9914 gameMode = EditGame;
9917 timeRemaining[0][1] = whiteTimeRemaining;
9918 timeRemaining[1][1] = blackTimeRemaining;
9919 DrawPosition(FALSE, boards[currentMove]);
9926 CopyPlayerNameIntoFileName(dest, src)
9929 while (*src != NULLCHAR && *src != ',') {
9934 *(*dest)++ = *src++;
9939 char *DefaultFileName(ext)
9942 static char def[MSG_SIZ];
9945 if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
9947 CopyPlayerNameIntoFileName(&p, gameInfo.white);
9949 CopyPlayerNameIntoFileName(&p, gameInfo.black);
9958 /* Save the current game to the given file */
9960 SaveGameToFile(filename, append)
9967 if (strcmp(filename, "-") == 0) {
9968 return SaveGame(stdout, 0, NULL);
9970 f = fopen(filename, append ? "a" : "w");
9972 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9973 DisplayError(buf, errno);
9976 return SaveGame(f, 0, NULL);
9985 static char buf[MSG_SIZ];
9988 p = strchr(str, ' ');
9989 if (p == NULL) return str;
9990 strncpy(buf, str, p - str);
9991 buf[p - str] = NULLCHAR;
9995 #define PGN_MAX_LINE 75
9997 #define PGN_SIDE_WHITE 0
9998 #define PGN_SIDE_BLACK 1
10001 static int FindFirstMoveOutOfBook( int side )
10005 if( backwardMostMove == 0 && ! startedFromSetupPosition) {
10006 int index = backwardMostMove;
10007 int has_book_hit = 0;
10009 if( (index % 2) != side ) {
10013 while( index < forwardMostMove ) {
10014 /* Check to see if engine is in book */
10015 int depth = pvInfoList[index].depth;
10016 int score = pvInfoList[index].score;
10022 else if( score == 0 && depth == 63 ) {
10023 in_book = 1; /* Zappa */
10025 else if( score == 2 && depth == 99 ) {
10026 in_book = 1; /* Abrok */
10029 has_book_hit += in_book;
10045 void GetOutOfBookInfo( char * buf )
10049 int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10051 oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
10052 oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
10056 if( oob[0] >= 0 || oob[1] >= 0 ) {
10057 for( i=0; i<2; i++ ) {
10061 if( i > 0 && oob[0] >= 0 ) {
10062 strcat( buf, " " );
10065 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
10066 sprintf( buf+strlen(buf), "%s%.2f",
10067 pvInfoList[idx].score >= 0 ? "+" : "",
10068 pvInfoList[idx].score / 100.0 );
10074 /* Save game in PGN style and close the file */
10079 int i, offset, linelen, newblock;
10083 int movelen, numlen, blank;
10084 char move_buffer[100]; /* [AS] Buffer for move+PV info */
10086 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10088 tm = time((time_t *) NULL);
10090 PrintPGNTags(f, &gameInfo);
10092 if (backwardMostMove > 0 || startedFromSetupPosition) {
10093 char *fen = PositionToFEN(backwardMostMove, NULL);
10094 fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
10095 fprintf(f, "\n{--------------\n");
10096 PrintPosition(f, backwardMostMove);
10097 fprintf(f, "--------------}\n");
10101 /* [AS] Out of book annotation */
10102 if( appData.saveOutOfBookInfo ) {
10105 GetOutOfBookInfo( buf );
10107 if( buf[0] != '\0' ) {
10108 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
10115 i = backwardMostMove;
10119 while (i < forwardMostMove) {
10120 /* Print comments preceding this move */
10121 if (commentList[i] != NULL) {
10122 if (linelen > 0) fprintf(f, "\n");
10123 fprintf(f, "%s", commentList[i]);
10128 /* Format move number */
10129 if ((i % 2) == 0) {
10130 sprintf(numtext, "%d.", (i - offset)/2 + 1);
10133 sprintf(numtext, "%d...", (i - offset)/2 + 1);
10135 numtext[0] = NULLCHAR;
10138 numlen = strlen(numtext);
10141 /* Print move number */
10142 blank = linelen > 0 && numlen > 0;
10143 if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
10152 fprintf(f, "%s", numtext);
10156 strcpy(move_buffer, SavePart(parseList[i])); // [HGM] pgn: print move via buffer, so it can be edited
10157 movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
10160 blank = linelen > 0 && movelen > 0;
10161 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
10170 fprintf(f, "%s", move_buffer);
10171 linelen += movelen;
10173 /* [AS] Add PV info if present */
10174 if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
10175 /* [HGM] add time */
10176 char buf[MSG_SIZ]; int seconds;
10178 seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
10180 if( seconds <= 0) buf[0] = 0; else
10181 if( seconds < 30 ) sprintf(buf, " %3.1f%c", seconds/10., 0); else {
10182 seconds = (seconds + 4)/10; // round to full seconds
10183 if( seconds < 60 ) sprintf(buf, " %d%c", seconds, 0); else
10184 sprintf(buf, " %d:%02d%c", seconds/60, seconds%60, 0);
10187 sprintf( move_buffer, "{%s%.2f/%d%s}",
10188 pvInfoList[i].score >= 0 ? "+" : "",
10189 pvInfoList[i].score / 100.0,
10190 pvInfoList[i].depth,
10193 movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
10195 /* Print score/depth */
10196 blank = linelen > 0 && movelen > 0;
10197 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
10206 fprintf(f, "%s", move_buffer);
10207 linelen += movelen;
10213 /* Start a new line */
10214 if (linelen > 0) fprintf(f, "\n");
10216 /* Print comments after last move */
10217 if (commentList[i] != NULL) {
10218 fprintf(f, "%s\n", commentList[i]);
10222 if (gameInfo.resultDetails != NULL &&
10223 gameInfo.resultDetails[0] != NULLCHAR) {
10224 fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
10225 PGNResult(gameInfo.result));
10227 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
10231 lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
10235 /* Save game in old style and close the file */
10237 SaveGameOldStyle(f)
10243 tm = time((time_t *) NULL);
10245 fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
10248 if (backwardMostMove > 0 || startedFromSetupPosition) {
10249 fprintf(f, "\n[--------------\n");
10250 PrintPosition(f, backwardMostMove);
10251 fprintf(f, "--------------]\n");
10256 i = backwardMostMove;
10257 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10259 while (i < forwardMostMove) {
10260 if (commentList[i] != NULL) {
10261 fprintf(f, "[%s]\n", commentList[i]);
10264 if ((i % 2) == 1) {
10265 fprintf(f, "%d. ... %s\n", (i - offset)/2 + 1, parseList[i]);
10268 fprintf(f, "%d. %s ", (i - offset)/2 + 1, parseList[i]);
10270 if (commentList[i] != NULL) {
10274 if (i >= forwardMostMove) {
10278 fprintf(f, "%s\n", parseList[i]);
10283 if (commentList[i] != NULL) {
10284 fprintf(f, "[%s]\n", commentList[i]);
10287 /* This isn't really the old style, but it's close enough */
10288 if (gameInfo.resultDetails != NULL &&
10289 gameInfo.resultDetails[0] != NULLCHAR) {
10290 fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
10291 gameInfo.resultDetails);
10293 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
10300 /* Save the current game to open file f and close the file */
10302 SaveGame(f, dummy, dummy2)
10307 if (gameMode == EditPosition) EditPositionDone(TRUE);
10308 lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
10309 if (appData.oldSaveStyle)
10310 return SaveGameOldStyle(f);
10312 return SaveGamePGN(f);
10315 /* Save the current position to the given file */
10317 SavePositionToFile(filename)
10323 if (strcmp(filename, "-") == 0) {
10324 return SavePosition(stdout, 0, NULL);
10326 f = fopen(filename, "a");
10328 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10329 DisplayError(buf, errno);
10332 SavePosition(f, 0, NULL);
10338 /* Save the current position to the given open file and close the file */
10340 SavePosition(f, dummy, dummy2)
10348 if (gameMode == EditPosition) EditPositionDone(TRUE);
10349 if (appData.oldSaveStyle) {
10350 tm = time((time_t *) NULL);
10352 fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
10354 fprintf(f, "[--------------\n");
10355 PrintPosition(f, currentMove);
10356 fprintf(f, "--------------]\n");
10358 fen = PositionToFEN(currentMove, NULL);
10359 fprintf(f, "%s\n", fen);
10367 ReloadCmailMsgEvent(unregister)
10371 static char *inFilename = NULL;
10372 static char *outFilename;
10374 struct stat inbuf, outbuf;
10377 /* Any registered moves are unregistered if unregister is set, */
10378 /* i.e. invoked by the signal handler */
10380 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
10381 cmailMoveRegistered[i] = FALSE;
10382 if (cmailCommentList[i] != NULL) {
10383 free(cmailCommentList[i]);
10384 cmailCommentList[i] = NULL;
10387 nCmailMovesRegistered = 0;
10390 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
10391 cmailResult[i] = CMAIL_NOT_RESULT;
10395 if (inFilename == NULL) {
10396 /* Because the filenames are static they only get malloced once */
10397 /* and they never get freed */
10398 inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
10399 sprintf(inFilename, "%s.game.in", appData.cmailGameName);
10401 outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
10402 sprintf(outFilename, "%s.out", appData.cmailGameName);
10405 status = stat(outFilename, &outbuf);
10407 cmailMailedMove = FALSE;
10409 status = stat(inFilename, &inbuf);
10410 cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
10413 /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
10414 counts the games, notes how each one terminated, etc.
10416 It would be nice to remove this kludge and instead gather all
10417 the information while building the game list. (And to keep it
10418 in the game list nodes instead of having a bunch of fixed-size
10419 parallel arrays.) Note this will require getting each game's
10420 termination from the PGN tags, as the game list builder does
10421 not process the game moves. --mann
10423 cmailMsgLoaded = TRUE;
10424 LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
10426 /* Load first game in the file or popup game menu */
10427 LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
10429 #endif /* !WIN32 */
10437 char string[MSG_SIZ];
10439 if ( cmailMailedMove
10440 || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
10441 return TRUE; /* Allow free viewing */
10444 /* Unregister move to ensure that we don't leave RegisterMove */
10445 /* with the move registered when the conditions for registering no */
10447 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
10448 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
10449 nCmailMovesRegistered --;
10451 if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
10453 free(cmailCommentList[lastLoadGameNumber - 1]);
10454 cmailCommentList[lastLoadGameNumber - 1] = NULL;
10458 if (cmailOldMove == -1) {
10459 DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
10463 if (currentMove > cmailOldMove + 1) {
10464 DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
10468 if (currentMove < cmailOldMove) {
10469 DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
10473 if (forwardMostMove > currentMove) {
10474 /* Silently truncate extra moves */
10478 if ( (currentMove == cmailOldMove + 1)
10479 || ( (currentMove == cmailOldMove)
10480 && ( (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
10481 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
10482 if (gameInfo.result != GameUnfinished) {
10483 cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
10486 if (commentList[currentMove] != NULL) {
10487 cmailCommentList[lastLoadGameNumber - 1]
10488 = StrSave(commentList[currentMove]);
10490 strcpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1]);
10492 if (appData.debugMode)
10493 fprintf(debugFP, "Saving %s for game %d\n",
10494 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
10497 "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
10499 f = fopen(string, "w");
10500 if (appData.oldSaveStyle) {
10501 SaveGameOldStyle(f); /* also closes the file */
10503 sprintf(string, "%s.pos.out", appData.cmailGameName);
10504 f = fopen(string, "w");
10505 SavePosition(f, 0, NULL); /* also closes the file */
10507 fprintf(f, "{--------------\n");
10508 PrintPosition(f, currentMove);
10509 fprintf(f, "--------------}\n\n");
10511 SaveGame(f, 0, NULL); /* also closes the file*/
10514 cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
10515 nCmailMovesRegistered ++;
10516 } else if (nCmailGames == 1) {
10517 DisplayError(_("You have not made a move yet"), 0);
10528 static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
10529 FILE *commandOutput;
10530 char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
10531 int nBytes = 0; /* Suppress warnings on uninitialized variables */
10537 if (! cmailMsgLoaded) {
10538 DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
10542 if (nCmailGames == nCmailResults) {
10543 DisplayError(_("No unfinished games"), 0);
10547 #if CMAIL_PROHIBIT_REMAIL
10548 if (cmailMailedMove) {
10549 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);
10550 DisplayError(msg, 0);
10555 if (! (cmailMailedMove || RegisterMove())) return;
10557 if ( cmailMailedMove
10558 || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
10559 sprintf(string, partCommandString,
10560 appData.debugMode ? " -v" : "", appData.cmailGameName);
10561 commandOutput = popen(string, "r");
10563 if (commandOutput == NULL) {
10564 DisplayError(_("Failed to invoke cmail"), 0);
10566 for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
10567 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
10569 if (nBuffers > 1) {
10570 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
10571 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
10572 nBytes = MSG_SIZ - 1;
10574 (void) memcpy(msg, buffer, nBytes);
10576 *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
10578 if(StrStr(msg, "Mailed cmail message to ") != NULL) {
10579 cmailMailedMove = TRUE; /* Prevent >1 moves */
10582 for (i = 0; i < nCmailGames; i ++) {
10583 if (cmailResult[i] == CMAIL_NOT_RESULT) {
10588 && ( (arcDir = (char *) getenv("CMAIL_ARCDIR"))
10590 sprintf(buffer, "%s/%s.%s.archive",
10592 appData.cmailGameName,
10594 LoadGameFromFile(buffer, 1, buffer, FALSE);
10595 cmailMsgLoaded = FALSE;
10599 DisplayInformation(msg);
10600 pclose(commandOutput);
10603 if ((*cmailMsg) != '\0') {
10604 DisplayInformation(cmailMsg);
10609 #endif /* !WIN32 */
10618 int prependComma = 0;
10620 char string[MSG_SIZ]; /* Space for game-list */
10623 if (!cmailMsgLoaded) return "";
10625 if (cmailMailedMove) {
10626 sprintf(cmailMsg, _("Waiting for reply from opponent\n"));
10628 /* Create a list of games left */
10629 sprintf(string, "[");
10630 for (i = 0; i < nCmailGames; i ++) {
10631 if (! ( cmailMoveRegistered[i]
10632 || (cmailResult[i] == CMAIL_OLD_RESULT))) {
10633 if (prependComma) {
10634 sprintf(number, ",%d", i + 1);
10636 sprintf(number, "%d", i + 1);
10640 strcat(string, number);
10643 strcat(string, "]");
10645 if (nCmailMovesRegistered + nCmailResults == 0) {
10646 switch (nCmailGames) {
10649 _("Still need to make move for game\n"));
10654 _("Still need to make moves for both games\n"));
10659 _("Still need to make moves for all %d games\n"),
10664 switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
10667 _("Still need to make a move for game %s\n"),
10672 if (nCmailResults == nCmailGames) {
10673 sprintf(cmailMsg, _("No unfinished games\n"));
10675 sprintf(cmailMsg, _("Ready to send mail\n"));
10681 _("Still need to make moves for games %s\n"),
10693 if (gameMode == Training)
10694 SetTrainingModeOff();
10697 cmailMsgLoaded = FALSE;
10698 if (appData.icsActive) {
10699 SendToICS(ics_prefix);
10700 SendToICS("refresh\n");
10710 /* Give up on clean exit */
10714 /* Keep trying for clean exit */
10718 if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
10720 if (telnetISR != NULL) {
10721 RemoveInputSource(telnetISR);
10723 if (icsPR != NoProc) {
10724 DestroyChildProcess(icsPR, TRUE);
10727 /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
10728 GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
10730 /* [HGM] crash: the above GameEnds() is a dud if another one was running */
10731 /* make sure this other one finishes before killing it! */
10732 if(endingGame) { int count = 0;
10733 if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
10734 while(endingGame && count++ < 10) DoSleep(1);
10735 if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
10738 /* Kill off chess programs */
10739 if (first.pr != NoProc) {
10742 DoSleep( appData.delayBeforeQuit );
10743 SendToProgram("quit\n", &first);
10744 DoSleep( appData.delayAfterQuit );
10745 DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
10747 if (second.pr != NoProc) {
10748 DoSleep( appData.delayBeforeQuit );
10749 SendToProgram("quit\n", &second);
10750 DoSleep( appData.delayAfterQuit );
10751 DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
10753 if (first.isr != NULL) {
10754 RemoveInputSource(first.isr);
10756 if (second.isr != NULL) {
10757 RemoveInputSource(second.isr);
10760 ShutDownFrontEnd();
10767 if (appData.debugMode)
10768 fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
10772 if (gameMode == MachinePlaysWhite ||
10773 gameMode == MachinePlaysBlack) {
10776 DisplayBothClocks();
10778 if (gameMode == PlayFromGameFile) {
10779 if (appData.timeDelay >= 0)
10780 AutoPlayGameLoop();
10781 } else if (gameMode == IcsExamining && pauseExamInvalid) {
10782 Reset(FALSE, TRUE);
10783 SendToICS(ics_prefix);
10784 SendToICS("refresh\n");
10785 } else if (currentMove < forwardMostMove) {
10786 ForwardInner(forwardMostMove);
10788 pauseExamInvalid = FALSE;
10790 switch (gameMode) {
10794 pauseExamForwardMostMove = forwardMostMove;
10795 pauseExamInvalid = FALSE;
10798 case IcsPlayingWhite:
10799 case IcsPlayingBlack:
10803 case PlayFromGameFile:
10804 (void) StopLoadGameTimer();
10808 case BeginningOfGame:
10809 if (appData.icsActive) return;
10810 /* else fall through */
10811 case MachinePlaysWhite:
10812 case MachinePlaysBlack:
10813 case TwoMachinesPlay:
10814 if (forwardMostMove == 0)
10815 return; /* don't pause if no one has moved */
10816 if ((gameMode == MachinePlaysWhite &&
10817 !WhiteOnMove(forwardMostMove)) ||
10818 (gameMode == MachinePlaysBlack &&
10819 WhiteOnMove(forwardMostMove))) {
10832 char title[MSG_SIZ];
10834 if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
10835 strcpy(title, _("Edit comment"));
10837 sprintf(title, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
10838 WhiteOnMove(currentMove - 1) ? " " : ".. ",
10839 parseList[currentMove - 1]);
10842 EditCommentPopUp(currentMove, title, commentList[currentMove]);
10849 char *tags = PGNTags(&gameInfo);
10850 EditTagsPopUp(tags);
10857 if (appData.noChessProgram || gameMode == AnalyzeMode)
10860 if (gameMode != AnalyzeFile) {
10861 if (!appData.icsEngineAnalyze) {
10863 if (gameMode != EditGame) return;
10865 ResurrectChessProgram();
10866 SendToProgram("analyze\n", &first);
10867 first.analyzing = TRUE;
10868 /*first.maybeThinking = TRUE;*/
10869 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
10870 EngineOutputPopUp();
10872 if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
10877 StartAnalysisClock();
10878 GetTimeMark(&lastNodeCountTime);
10885 if (appData.noChessProgram || gameMode == AnalyzeFile)
10888 if (gameMode != AnalyzeMode) {
10890 if (gameMode != EditGame) return;
10891 ResurrectChessProgram();
10892 SendToProgram("analyze\n", &first);
10893 first.analyzing = TRUE;
10894 /*first.maybeThinking = TRUE;*/
10895 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
10896 EngineOutputPopUp();
10898 gameMode = AnalyzeFile;
10903 StartAnalysisClock();
10904 GetTimeMark(&lastNodeCountTime);
10909 MachineWhiteEvent()
10912 char *bookHit = NULL;
10914 if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
10918 if (gameMode == PlayFromGameFile ||
10919 gameMode == TwoMachinesPlay ||
10920 gameMode == Training ||
10921 gameMode == AnalyzeMode ||
10922 gameMode == EndOfGame)
10925 if (gameMode == EditPosition)
10926 EditPositionDone(TRUE);
10928 if (!WhiteOnMove(currentMove)) {
10929 DisplayError(_("It is not White's turn"), 0);
10933 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
10936 if (gameMode == EditGame || gameMode == AnalyzeMode ||
10937 gameMode == AnalyzeFile)
10940 ResurrectChessProgram(); /* in case it isn't running */
10941 if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
10942 gameMode = MachinePlaysWhite;
10945 gameMode = MachinePlaysWhite;
10949 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10951 if (first.sendName) {
10952 sprintf(buf, "name %s\n", gameInfo.black);
10953 SendToProgram(buf, &first);
10955 if (first.sendTime) {
10956 if (first.useColors) {
10957 SendToProgram("black\n", &first); /*gnu kludge*/
10959 SendTimeRemaining(&first, TRUE);
10961 if (first.useColors) {
10962 SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
10964 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
10965 SetMachineThinkingEnables();
10966 first.maybeThinking = TRUE;
10970 if (appData.autoFlipView && !flipView) {
10971 flipView = !flipView;
10972 DrawPosition(FALSE, NULL);
10973 DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;
10976 if(bookHit) { // [HGM] book: simulate book reply
10977 static char bookMove[MSG_SIZ]; // a bit generous?
10979 programStats.nodes = programStats.depth = programStats.time =
10980 programStats.score = programStats.got_only_move = 0;
10981 sprintf(programStats.movelist, "%s (xbook)", bookHit);
10983 strcpy(bookMove, "move ");
10984 strcat(bookMove, bookHit);
10985 HandleMachineMove(bookMove, &first);
10990 MachineBlackEvent()
10993 char *bookHit = NULL;
10995 if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
10999 if (gameMode == PlayFromGameFile ||
11000 gameMode == TwoMachinesPlay ||
11001 gameMode == Training ||
11002 gameMode == AnalyzeMode ||
11003 gameMode == EndOfGame)
11006 if (gameMode == EditPosition)
11007 EditPositionDone(TRUE);
11009 if (WhiteOnMove(currentMove)) {
11010 DisplayError(_("It is not Black's turn"), 0);
11014 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
11017 if (gameMode == EditGame || gameMode == AnalyzeMode ||
11018 gameMode == AnalyzeFile)
11021 ResurrectChessProgram(); /* in case it isn't running */
11022 gameMode = MachinePlaysBlack;
11026 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
11028 if (first.sendName) {
11029 sprintf(buf, "name %s\n", gameInfo.white);
11030 SendToProgram(buf, &first);
11032 if (first.sendTime) {
11033 if (first.useColors) {
11034 SendToProgram("white\n", &first); /*gnu kludge*/
11036 SendTimeRemaining(&first, FALSE);
11038 if (first.useColors) {
11039 SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
11041 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
11042 SetMachineThinkingEnables();
11043 first.maybeThinking = TRUE;
11046 if (appData.autoFlipView && flipView) {
11047 flipView = !flipView;
11048 DrawPosition(FALSE, NULL);
11049 DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;
11051 if(bookHit) { // [HGM] book: simulate book reply
11052 static char bookMove[MSG_SIZ]; // a bit generous?
11054 programStats.nodes = programStats.depth = programStats.time =
11055 programStats.score = programStats.got_only_move = 0;
11056 sprintf(programStats.movelist, "%s (xbook)", bookHit);
11058 strcpy(bookMove, "move ");
11059 strcat(bookMove, bookHit);
11060 HandleMachineMove(bookMove, &first);
11066 DisplayTwoMachinesTitle()
11069 if (appData.matchGames > 0) {
11070 if (first.twoMachinesColor[0] == 'w') {
11071 sprintf(buf, "%s vs. %s (%d-%d-%d)",
11072 gameInfo.white, gameInfo.black,
11073 first.matchWins, second.matchWins,
11074 matchGame - 1 - (first.matchWins + second.matchWins));
11076 sprintf(buf, "%s vs. %s (%d-%d-%d)",
11077 gameInfo.white, gameInfo.black,
11078 second.matchWins, first.matchWins,
11079 matchGame - 1 - (first.matchWins + second.matchWins));
11082 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
11088 TwoMachinesEvent P((void))
11092 ChessProgramState *onmove;
11093 char *bookHit = NULL;
11095 if (appData.noChessProgram) return;
11097 switch (gameMode) {
11098 case TwoMachinesPlay:
11100 case MachinePlaysWhite:
11101 case MachinePlaysBlack:
11102 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
11103 DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
11107 case BeginningOfGame:
11108 case PlayFromGameFile:
11111 if (gameMode != EditGame) return;
11114 EditPositionDone(TRUE);
11125 // forwardMostMove = currentMove;
11126 TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
11127 ResurrectChessProgram(); /* in case first program isn't running */
11129 if (second.pr == NULL) {
11130 StartChessProgram(&second);
11131 if (second.protocolVersion == 1) {
11132 TwoMachinesEventIfReady();
11134 /* kludge: allow timeout for initial "feature" command */
11136 DisplayMessage("", _("Starting second chess program"));
11137 ScheduleDelayedEvent(TwoMachinesEventIfReady, FEATURE_TIMEOUT);
11141 DisplayMessage("", "");
11142 InitChessProgram(&second, FALSE);
11143 SendToProgram("force\n", &second);
11144 if (startedFromSetupPosition) {
11145 SendBoard(&second, backwardMostMove);
11146 if (appData.debugMode) {
11147 fprintf(debugFP, "Two Machines\n");
11150 for (i = backwardMostMove; i < forwardMostMove; i++) {
11151 SendMoveToProgram(i, &second);
11154 gameMode = TwoMachinesPlay;
11158 DisplayTwoMachinesTitle();
11160 if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
11166 SendToProgram(first.computerString, &first);
11167 if (first.sendName) {
11168 sprintf(buf, "name %s\n", second.tidy);
11169 SendToProgram(buf, &first);
11171 SendToProgram(second.computerString, &second);
11172 if (second.sendName) {
11173 sprintf(buf, "name %s\n", first.tidy);
11174 SendToProgram(buf, &second);
11178 if (!first.sendTime || !second.sendTime) {
11179 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11180 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11182 if (onmove->sendTime) {
11183 if (onmove->useColors) {
11184 SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
11186 SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
11188 if (onmove->useColors) {
11189 SendToProgram(onmove->twoMachinesColor, onmove);
11191 bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
11192 // SendToProgram("go\n", onmove);
11193 onmove->maybeThinking = TRUE;
11194 SetMachineThinkingEnables();
11198 if(bookHit) { // [HGM] book: simulate book reply
11199 static char bookMove[MSG_SIZ]; // a bit generous?
11201 programStats.nodes = programStats.depth = programStats.time =
11202 programStats.score = programStats.got_only_move = 0;
11203 sprintf(programStats.movelist, "%s (xbook)", bookHit);
11205 strcpy(bookMove, "move ");
11206 strcat(bookMove, bookHit);
11207 savedMessage = bookMove; // args for deferred call
11208 savedState = onmove;
11209 ScheduleDelayedEvent(DeferredBookMove, 1);
11216 if (gameMode == Training) {
11217 SetTrainingModeOff();
11218 gameMode = PlayFromGameFile;
11219 DisplayMessage("", _("Training mode off"));
11221 gameMode = Training;
11222 animateTraining = appData.animate;
11224 /* make sure we are not already at the end of the game */
11225 if (currentMove < forwardMostMove) {
11226 SetTrainingModeOn();
11227 DisplayMessage("", _("Training mode on"));
11229 gameMode = PlayFromGameFile;
11230 DisplayError(_("Already at end of game"), 0);
11239 if (!appData.icsActive) return;
11240 switch (gameMode) {
11241 case IcsPlayingWhite:
11242 case IcsPlayingBlack:
11245 case BeginningOfGame:
11253 EditPositionDone(TRUE);
11266 gameMode = IcsIdle;
11277 switch (gameMode) {
11279 SetTrainingModeOff();
11281 case MachinePlaysWhite:
11282 case MachinePlaysBlack:
11283 case BeginningOfGame:
11284 SendToProgram("force\n", &first);
11285 SetUserThinkingEnables();
11287 case PlayFromGameFile:
11288 (void) StopLoadGameTimer();
11289 if (gameFileFP != NULL) {
11294 EditPositionDone(TRUE);
11299 SendToProgram("force\n", &first);
11301 case TwoMachinesPlay:
11302 GameEnds((ChessMove) 0, NULL, GE_PLAYER);
11303 ResurrectChessProgram();
11304 SetUserThinkingEnables();
11307 ResurrectChessProgram();
11309 case IcsPlayingBlack:
11310 case IcsPlayingWhite:
11311 DisplayError(_("Warning: You are still playing a game"), 0);
11314 DisplayError(_("Warning: You are still observing a game"), 0);
11317 DisplayError(_("Warning: You are still examining a game"), 0);
11328 first.offeredDraw = second.offeredDraw = 0;
11330 if (gameMode == PlayFromGameFile) {
11331 whiteTimeRemaining = timeRemaining[0][currentMove];
11332 blackTimeRemaining = timeRemaining[1][currentMove];
11336 if (gameMode == MachinePlaysWhite ||
11337 gameMode == MachinePlaysBlack ||
11338 gameMode == TwoMachinesPlay ||
11339 gameMode == EndOfGame) {
11340 i = forwardMostMove;
11341 while (i > currentMove) {
11342 SendToProgram("undo\n", &first);
11345 whiteTimeRemaining = timeRemaining[0][currentMove];
11346 blackTimeRemaining = timeRemaining[1][currentMove];
11347 DisplayBothClocks();
11348 if (whiteFlag || blackFlag) {
11349 whiteFlag = blackFlag = 0;
11354 gameMode = EditGame;
11361 EditPositionEvent()
11363 if (gameMode == EditPosition) {
11369 if (gameMode != EditGame) return;
11371 gameMode = EditPosition;
11374 if (currentMove > 0)
11375 CopyBoard(boards[0], boards[currentMove]);
11377 blackPlaysFirst = !WhiteOnMove(currentMove);
11379 currentMove = forwardMostMove = backwardMostMove = 0;
11380 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11387 /* [DM] icsEngineAnalyze - possible call from other functions */
11388 if (appData.icsEngineAnalyze) {
11389 appData.icsEngineAnalyze = FALSE;
11391 DisplayMessage("",_("Close ICS engine analyze..."));
11393 if (first.analysisSupport && first.analyzing) {
11394 SendToProgram("exit\n", &first);
11395 first.analyzing = FALSE;
11397 thinkOutput[0] = NULLCHAR;
11401 EditPositionDone(Boolean fakeRights)
11403 int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
11405 startedFromSetupPosition = TRUE;
11406 InitChessProgram(&first, FALSE);
11407 if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
11408 boards[0][EP_STATUS] = EP_NONE;
11409 boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
11410 if(boards[0][0][BOARD_WIDTH>>1] == king) {
11411 boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : NoRights;
11412 boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
11413 } else boards[0][CASTLING][2] = NoRights;
11414 if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
11415 boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : NoRights;
11416 boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
11417 } else boards[0][CASTLING][5] = NoRights;
11419 SendToProgram("force\n", &first);
11420 if (blackPlaysFirst) {
11421 strcpy(moveList[0], "");
11422 strcpy(parseList[0], "");
11423 currentMove = forwardMostMove = backwardMostMove = 1;
11424 CopyBoard(boards[1], boards[0]);
11426 currentMove = forwardMostMove = backwardMostMove = 0;
11428 SendBoard(&first, forwardMostMove);
11429 if (appData.debugMode) {
11430 fprintf(debugFP, "EditPosDone\n");
11433 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11434 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11435 gameMode = EditGame;
11437 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11438 ClearHighlights(); /* [AS] */
11441 /* Pause for `ms' milliseconds */
11442 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
11452 } while (SubtractTimeMarks(&m2, &m1) < ms);
11455 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
11457 SendMultiLineToICS(buf)
11460 char temp[MSG_SIZ+1], *p;
11467 strncpy(temp, buf, len);
11472 if (*p == '\n' || *p == '\r')
11477 strcat(temp, "\n");
11479 SendToPlayer(temp, strlen(temp));
11483 SetWhiteToPlayEvent()
11485 if (gameMode == EditPosition) {
11486 blackPlaysFirst = FALSE;
11487 DisplayBothClocks(); /* works because currentMove is 0 */
11488 } else if (gameMode == IcsExamining) {
11489 SendToICS(ics_prefix);
11490 SendToICS("tomove white\n");
11495 SetBlackToPlayEvent()
11497 if (gameMode == EditPosition) {
11498 blackPlaysFirst = TRUE;
11499 currentMove = 1; /* kludge */
11500 DisplayBothClocks();
11502 } else if (gameMode == IcsExamining) {
11503 SendToICS(ics_prefix);
11504 SendToICS("tomove black\n");
11509 EditPositionMenuEvent(selection, x, y)
11510 ChessSquare selection;
11514 ChessSquare piece = boards[0][y][x];
11516 if (gameMode != EditPosition && gameMode != IcsExamining) return;
11518 switch (selection) {
11520 if (gameMode == IcsExamining && ics_type == ICS_FICS) {
11521 SendToICS(ics_prefix);
11522 SendToICS("bsetup clear\n");
11523 } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
11524 SendToICS(ics_prefix);
11525 SendToICS("clearboard\n");
11527 for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
11528 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
11529 for (y = 0; y < BOARD_HEIGHT; y++) {
11530 if (gameMode == IcsExamining) {
11531 if (boards[currentMove][y][x] != EmptySquare) {
11532 sprintf(buf, "%sx@%c%c\n", ics_prefix,
11537 boards[0][y][x] = p;
11542 if (gameMode == EditPosition) {
11543 DrawPosition(FALSE, boards[0]);
11548 SetWhiteToPlayEvent();
11552 SetBlackToPlayEvent();
11556 if (gameMode == IcsExamining) {
11557 if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
11558 sprintf(buf, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
11561 if(x < BOARD_LEFT || x >= BOARD_RGHT) {
11562 if(x == BOARD_LEFT-2) {
11563 if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
11564 boards[0][y][1] = 0;
11566 if(x == BOARD_RGHT+1) {
11567 if(y >= gameInfo.holdingsSize) break;
11568 boards[0][y][BOARD_WIDTH-2] = 0;
11571 boards[0][y][x] = EmptySquare;
11572 DrawPosition(FALSE, boards[0]);
11577 if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
11578 piece >= (int)BlackPawn && piece < (int)BlackMan ) {
11579 selection = (ChessSquare) (PROMOTED piece);
11580 } else if(piece == EmptySquare) selection = WhiteSilver;
11581 else selection = (ChessSquare)((int)piece - 1);
11585 if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
11586 piece > (int)BlackMan && piece <= (int)BlackKing ) {
11587 selection = (ChessSquare) (DEMOTED piece);
11588 } else if(piece == EmptySquare) selection = BlackSilver;
11589 else selection = (ChessSquare)((int)piece + 1);
11594 if(gameInfo.variant == VariantShatranj ||
11595 gameInfo.variant == VariantXiangqi ||
11596 gameInfo.variant == VariantCourier ||
11597 gameInfo.variant == VariantMakruk )
11598 selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
11603 if(gameInfo.variant == VariantXiangqi)
11604 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
11605 if(gameInfo.variant == VariantKnightmate)
11606 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
11609 if (gameMode == IcsExamining) {
11610 if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
11611 sprintf(buf, "%s%c@%c%c\n", ics_prefix,
11612 PieceToChar(selection), AAA + x, ONE + y);
11615 if(x < BOARD_LEFT || x >= BOARD_RGHT) {
11617 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
11618 n = PieceToNumber(selection - BlackPawn);
11619 if(n > gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
11620 boards[0][BOARD_HEIGHT-1-n][0] = selection;
11621 boards[0][BOARD_HEIGHT-1-n][1]++;
11623 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
11624 n = PieceToNumber(selection);
11625 if(n > gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
11626 boards[0][n][BOARD_WIDTH-1] = selection;
11627 boards[0][n][BOARD_WIDTH-2]++;
11630 boards[0][y][x] = selection;
11631 DrawPosition(TRUE, boards[0]);
11639 DropMenuEvent(selection, x, y)
11640 ChessSquare selection;
11643 ChessMove moveType;
11645 switch (gameMode) {
11646 case IcsPlayingWhite:
11647 case MachinePlaysBlack:
11648 if (!WhiteOnMove(currentMove)) {
11649 DisplayMoveError(_("It is Black's turn"));
11652 moveType = WhiteDrop;
11654 case IcsPlayingBlack:
11655 case MachinePlaysWhite:
11656 if (WhiteOnMove(currentMove)) {
11657 DisplayMoveError(_("It is White's turn"));
11660 moveType = BlackDrop;
11663 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
11669 if (moveType == BlackDrop && selection < BlackPawn) {
11670 selection = (ChessSquare) ((int) selection
11671 + (int) BlackPawn - (int) WhitePawn);
11673 if (boards[currentMove][y][x] != EmptySquare) {
11674 DisplayMoveError(_("That square is occupied"));
11678 FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
11684 /* Accept a pending offer of any kind from opponent */
11686 if (appData.icsActive) {
11687 SendToICS(ics_prefix);
11688 SendToICS("accept\n");
11689 } else if (cmailMsgLoaded) {
11690 if (currentMove == cmailOldMove &&
11691 commentList[cmailOldMove] != NULL &&
11692 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11693 "Black offers a draw" : "White offers a draw")) {
11695 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11696 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
11698 DisplayError(_("There is no pending offer on this move"), 0);
11699 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11702 /* Not used for offers from chess program */
11709 /* Decline a pending offer of any kind from opponent */
11711 if (appData.icsActive) {
11712 SendToICS(ics_prefix);
11713 SendToICS("decline\n");
11714 } else if (cmailMsgLoaded) {
11715 if (currentMove == cmailOldMove &&
11716 commentList[cmailOldMove] != NULL &&
11717 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11718 "Black offers a draw" : "White offers a draw")) {
11720 AppendComment(cmailOldMove, "Draw declined", TRUE);
11721 DisplayComment(cmailOldMove - 1, "Draw declined");
11724 DisplayError(_("There is no pending offer on this move"), 0);
11727 /* Not used for offers from chess program */
11734 /* Issue ICS rematch command */
11735 if (appData.icsActive) {
11736 SendToICS(ics_prefix);
11737 SendToICS("rematch\n");
11744 /* Call your opponent's flag (claim a win on time) */
11745 if (appData.icsActive) {
11746 SendToICS(ics_prefix);
11747 SendToICS("flag\n");
11749 switch (gameMode) {
11752 case MachinePlaysWhite:
11755 GameEnds(GameIsDrawn, "Both players ran out of time",
11758 GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
11760 DisplayError(_("Your opponent is not out of time"), 0);
11763 case MachinePlaysBlack:
11766 GameEnds(GameIsDrawn, "Both players ran out of time",
11769 GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
11771 DisplayError(_("Your opponent is not out of time"), 0);
11781 /* Offer draw or accept pending draw offer from opponent */
11783 if (appData.icsActive) {
11784 /* Note: tournament rules require draw offers to be
11785 made after you make your move but before you punch
11786 your clock. Currently ICS doesn't let you do that;
11787 instead, you immediately punch your clock after making
11788 a move, but you can offer a draw at any time. */
11790 SendToICS(ics_prefix);
11791 SendToICS("draw\n");
11792 userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
11793 } else if (cmailMsgLoaded) {
11794 if (currentMove == cmailOldMove &&
11795 commentList[cmailOldMove] != NULL &&
11796 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11797 "Black offers a draw" : "White offers a draw")) {
11798 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11799 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
11800 } else if (currentMove == cmailOldMove + 1) {
11801 char *offer = WhiteOnMove(cmailOldMove) ?
11802 "White offers a draw" : "Black offers a draw";
11803 AppendComment(currentMove, offer, TRUE);
11804 DisplayComment(currentMove - 1, offer);
11805 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
11807 DisplayError(_("You must make your move before offering a draw"), 0);
11808 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11810 } else if (first.offeredDraw) {
11811 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
11813 if (first.sendDrawOffers) {
11814 SendToProgram("draw\n", &first);
11815 userOfferedDraw = TRUE;
11823 /* Offer Adjourn or accept pending Adjourn offer from opponent */
11825 if (appData.icsActive) {
11826 SendToICS(ics_prefix);
11827 SendToICS("adjourn\n");
11829 /* Currently GNU Chess doesn't offer or accept Adjourns */
11837 /* Offer Abort or accept pending Abort offer from opponent */
11839 if (appData.icsActive) {
11840 SendToICS(ics_prefix);
11841 SendToICS("abort\n");
11843 GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
11850 /* Resign. You can do this even if it's not your turn. */
11852 if (appData.icsActive) {
11853 SendToICS(ics_prefix);
11854 SendToICS("resign\n");
11856 switch (gameMode) {
11857 case MachinePlaysWhite:
11858 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11860 case MachinePlaysBlack:
11861 GameEnds(BlackWins, "White resigns", GE_PLAYER);
11864 if (cmailMsgLoaded) {
11866 if (WhiteOnMove(cmailOldMove)) {
11867 GameEnds(BlackWins, "White resigns", GE_PLAYER);
11869 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11871 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
11882 StopObservingEvent()
11884 /* Stop observing current games */
11885 SendToICS(ics_prefix);
11886 SendToICS("unobserve\n");
11890 StopExaminingEvent()
11892 /* Stop observing current game */
11893 SendToICS(ics_prefix);
11894 SendToICS("unexamine\n");
11898 ForwardInner(target)
11903 if (appData.debugMode)
11904 fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
11905 target, currentMove, forwardMostMove);
11907 if (gameMode == EditPosition)
11910 if (gameMode == PlayFromGameFile && !pausing)
11913 if (gameMode == IcsExamining && pausing)
11914 limit = pauseExamForwardMostMove;
11916 limit = forwardMostMove;
11918 if (target > limit) target = limit;
11920 if (target > 0 && moveList[target - 1][0]) {
11921 int fromX, fromY, toX, toY;
11922 toX = moveList[target - 1][2] - AAA;
11923 toY = moveList[target - 1][3] - ONE;
11924 if (moveList[target - 1][1] == '@') {
11925 if (appData.highlightLastMove) {
11926 SetHighlights(-1, -1, toX, toY);
11929 fromX = moveList[target - 1][0] - AAA;
11930 fromY = moveList[target - 1][1] - ONE;
11931 if (target == currentMove + 1) {
11932 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
11934 if (appData.highlightLastMove) {
11935 SetHighlights(fromX, fromY, toX, toY);
11939 if (gameMode == EditGame || gameMode == AnalyzeMode ||
11940 gameMode == Training || gameMode == PlayFromGameFile ||
11941 gameMode == AnalyzeFile) {
11942 while (currentMove < target) {
11943 SendMoveToProgram(currentMove++, &first);
11946 currentMove = target;
11949 if (gameMode == EditGame || gameMode == EndOfGame) {
11950 whiteTimeRemaining = timeRemaining[0][currentMove];
11951 blackTimeRemaining = timeRemaining[1][currentMove];
11953 DisplayBothClocks();
11954 DisplayMove(currentMove - 1);
11955 DrawPosition(FALSE, boards[currentMove]);
11956 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
11957 if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
11958 DisplayComment(currentMove - 1, commentList[currentMove]);
11966 if (gameMode == IcsExamining && !pausing) {
11967 SendToICS(ics_prefix);
11968 SendToICS("forward\n");
11970 ForwardInner(currentMove + 1);
11977 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11978 /* to optimze, we temporarily turn off analysis mode while we feed
11979 * the remaining moves to the engine. Otherwise we get analysis output
11982 if (first.analysisSupport) {
11983 SendToProgram("exit\nforce\n", &first);
11984 first.analyzing = FALSE;
11988 if (gameMode == IcsExamining && !pausing) {
11989 SendToICS(ics_prefix);
11990 SendToICS("forward 999999\n");
11992 ForwardInner(forwardMostMove);
11995 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11996 /* we have fed all the moves, so reactivate analysis mode */
11997 SendToProgram("analyze\n", &first);
11998 first.analyzing = TRUE;
11999 /*first.maybeThinking = TRUE;*/
12000 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12005 BackwardInner(target)
12008 int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
12010 if (appData.debugMode)
12011 fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
12012 target, currentMove, forwardMostMove);
12014 if (gameMode == EditPosition) return;
12015 if (currentMove <= backwardMostMove) {
12017 DrawPosition(full_redraw, boards[currentMove]);
12020 if (gameMode == PlayFromGameFile && !pausing)
12023 if (moveList[target][0]) {
12024 int fromX, fromY, toX, toY;
12025 toX = moveList[target][2] - AAA;
12026 toY = moveList[target][3] - ONE;
12027 if (moveList[target][1] == '@') {
12028 if (appData.highlightLastMove) {
12029 SetHighlights(-1, -1, toX, toY);
12032 fromX = moveList[target][0] - AAA;
12033 fromY = moveList[target][1] - ONE;
12034 if (target == currentMove - 1) {
12035 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
12037 if (appData.highlightLastMove) {
12038 SetHighlights(fromX, fromY, toX, toY);
12042 if (gameMode == EditGame || gameMode==AnalyzeMode ||
12043 gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
12044 while (currentMove > target) {
12045 SendToProgram("undo\n", &first);
12049 currentMove = target;
12052 if (gameMode == EditGame || gameMode == EndOfGame) {
12053 whiteTimeRemaining = timeRemaining[0][currentMove];
12054 blackTimeRemaining = timeRemaining[1][currentMove];
12056 DisplayBothClocks();
12057 DisplayMove(currentMove - 1);
12058 DrawPosition(full_redraw, boards[currentMove]);
12059 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
12060 // [HGM] PV info: routine tests if comment empty
12061 DisplayComment(currentMove - 1, commentList[currentMove]);
12067 if (gameMode == IcsExamining && !pausing) {
12068 SendToICS(ics_prefix);
12069 SendToICS("backward\n");
12071 BackwardInner(currentMove - 1);
12078 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12079 /* to optimize, we temporarily turn off analysis mode while we undo
12080 * all the moves. Otherwise we get analysis output after each undo.
12082 if (first.analysisSupport) {
12083 SendToProgram("exit\nforce\n", &first);
12084 first.analyzing = FALSE;
12088 if (gameMode == IcsExamining && !pausing) {
12089 SendToICS(ics_prefix);
12090 SendToICS("backward 999999\n");
12092 BackwardInner(backwardMostMove);
12095 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12096 /* we have fed all the moves, so reactivate analysis mode */
12097 SendToProgram("analyze\n", &first);
12098 first.analyzing = TRUE;
12099 /*first.maybeThinking = TRUE;*/
12100 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12107 if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
12108 if (to >= forwardMostMove) to = forwardMostMove;
12109 if (to <= backwardMostMove) to = backwardMostMove;
12110 if (to < currentMove) {
12120 if(PopTail(TRUE)) { // [HGM] vari: restore old game tail
12123 if (gameMode != IcsExamining) {
12124 DisplayError(_("You are not examining a game"), 0);
12128 DisplayError(_("You can't revert while pausing"), 0);
12131 SendToICS(ics_prefix);
12132 SendToICS("revert\n");
12138 switch (gameMode) {
12139 case MachinePlaysWhite:
12140 case MachinePlaysBlack:
12141 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
12142 DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
12145 if (forwardMostMove < 2) return;
12146 currentMove = forwardMostMove = forwardMostMove - 2;
12147 whiteTimeRemaining = timeRemaining[0][currentMove];
12148 blackTimeRemaining = timeRemaining[1][currentMove];
12149 DisplayBothClocks();
12150 DisplayMove(currentMove - 1);
12151 ClearHighlights();/*!! could figure this out*/
12152 DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
12153 SendToProgram("remove\n", &first);
12154 /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
12157 case BeginningOfGame:
12161 case IcsPlayingWhite:
12162 case IcsPlayingBlack:
12163 if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
12164 SendToICS(ics_prefix);
12165 SendToICS("takeback 2\n");
12167 SendToICS(ics_prefix);
12168 SendToICS("takeback 1\n");
12177 ChessProgramState *cps;
12179 switch (gameMode) {
12180 case MachinePlaysWhite:
12181 if (!WhiteOnMove(forwardMostMove)) {
12182 DisplayError(_("It is your turn"), 0);
12187 case MachinePlaysBlack:
12188 if (WhiteOnMove(forwardMostMove)) {
12189 DisplayError(_("It is your turn"), 0);
12194 case TwoMachinesPlay:
12195 if (WhiteOnMove(forwardMostMove) ==
12196 (first.twoMachinesColor[0] == 'w')) {
12202 case BeginningOfGame:
12206 SendToProgram("?\n", cps);
12210 TruncateGameEvent()
12213 if (gameMode != EditGame) return;
12220 CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
12221 if (forwardMostMove > currentMove) {
12222 if (gameInfo.resultDetails != NULL) {
12223 free(gameInfo.resultDetails);
12224 gameInfo.resultDetails = NULL;
12225 gameInfo.result = GameUnfinished;
12227 forwardMostMove = currentMove;
12228 HistorySet(parseList, backwardMostMove, forwardMostMove,
12236 if (appData.noChessProgram) return;
12237 switch (gameMode) {
12238 case MachinePlaysWhite:
12239 if (WhiteOnMove(forwardMostMove)) {
12240 DisplayError(_("Wait until your turn"), 0);
12244 case BeginningOfGame:
12245 case MachinePlaysBlack:
12246 if (!WhiteOnMove(forwardMostMove)) {
12247 DisplayError(_("Wait until your turn"), 0);
12252 DisplayError(_("No hint available"), 0);
12255 SendToProgram("hint\n", &first);
12256 hintRequested = TRUE;
12262 if (appData.noChessProgram) return;
12263 switch (gameMode) {
12264 case MachinePlaysWhite:
12265 if (WhiteOnMove(forwardMostMove)) {
12266 DisplayError(_("Wait until your turn"), 0);
12270 case BeginningOfGame:
12271 case MachinePlaysBlack:
12272 if (!WhiteOnMove(forwardMostMove)) {
12273 DisplayError(_("Wait until your turn"), 0);
12278 EditPositionDone(TRUE);
12280 case TwoMachinesPlay:
12285 SendToProgram("bk\n", &first);
12286 bookOutput[0] = NULLCHAR;
12287 bookRequested = TRUE;
12293 char *tags = PGNTags(&gameInfo);
12294 TagsPopUp(tags, CmailMsg());
12298 /* end button procedures */
12301 PrintPosition(fp, move)
12307 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
12308 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
12309 char c = PieceToChar(boards[move][i][j]);
12310 fputc(c == 'x' ? '.' : c, fp);
12311 fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
12314 if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
12315 fprintf(fp, "white to play\n");
12317 fprintf(fp, "black to play\n");
12324 if (gameInfo.white != NULL) {
12325 fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
12331 /* Find last component of program's own name, using some heuristics */
12333 TidyProgramName(prog, host, buf)
12334 char *prog, *host, buf[MSG_SIZ];
12337 int local = (strcmp(host, "localhost") == 0);
12338 while (!local && (p = strchr(prog, ';')) != NULL) {
12340 while (*p == ' ') p++;
12343 if (*prog == '"' || *prog == '\'') {
12344 q = strchr(prog + 1, *prog);
12346 q = strchr(prog, ' ');
12348 if (q == NULL) q = prog + strlen(prog);
12350 while (p >= prog && *p != '/' && *p != '\\') p--;
12352 if(p == prog && *p == '"') p++;
12353 if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
12354 memcpy(buf, p, q - p);
12355 buf[q - p] = NULLCHAR;
12363 TimeControlTagValue()
12366 if (!appData.clockMode) {
12368 } else if (movesPerSession > 0) {
12369 sprintf(buf, "%d/%ld", movesPerSession, timeControl/1000);
12370 } else if (timeIncrement == 0) {
12371 sprintf(buf, "%ld", timeControl/1000);
12373 sprintf(buf, "%ld+%ld", timeControl/1000, timeIncrement/1000);
12375 return StrSave(buf);
12381 /* This routine is used only for certain modes */
12382 VariantClass v = gameInfo.variant;
12383 ChessMove r = GameUnfinished;
12386 if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
12387 r = gameInfo.result;
12388 p = gameInfo.resultDetails;
12389 gameInfo.resultDetails = NULL;
12391 ClearGameInfo(&gameInfo);
12392 gameInfo.variant = v;
12394 switch (gameMode) {
12395 case MachinePlaysWhite:
12396 gameInfo.event = StrSave( appData.pgnEventHeader );
12397 gameInfo.site = StrSave(HostName());
12398 gameInfo.date = PGNDate();
12399 gameInfo.round = StrSave("-");
12400 gameInfo.white = StrSave(first.tidy);
12401 gameInfo.black = StrSave(UserName());
12402 gameInfo.timeControl = TimeControlTagValue();
12405 case MachinePlaysBlack:
12406 gameInfo.event = StrSave( appData.pgnEventHeader );
12407 gameInfo.site = StrSave(HostName());
12408 gameInfo.date = PGNDate();
12409 gameInfo.round = StrSave("-");
12410 gameInfo.white = StrSave(UserName());
12411 gameInfo.black = StrSave(first.tidy);
12412 gameInfo.timeControl = TimeControlTagValue();
12415 case TwoMachinesPlay:
12416 gameInfo.event = StrSave( appData.pgnEventHeader );
12417 gameInfo.site = StrSave(HostName());
12418 gameInfo.date = PGNDate();
12419 if (matchGame > 0) {
12421 sprintf(buf, "%d", matchGame);
12422 gameInfo.round = StrSave(buf);
12424 gameInfo.round = StrSave("-");
12426 if (first.twoMachinesColor[0] == 'w') {
12427 gameInfo.white = StrSave(first.tidy);
12428 gameInfo.black = StrSave(second.tidy);
12430 gameInfo.white = StrSave(second.tidy);
12431 gameInfo.black = StrSave(first.tidy);
12433 gameInfo.timeControl = TimeControlTagValue();
12437 gameInfo.event = StrSave("Edited game");
12438 gameInfo.site = StrSave(HostName());
12439 gameInfo.date = PGNDate();
12440 gameInfo.round = StrSave("-");
12441 gameInfo.white = StrSave("-");
12442 gameInfo.black = StrSave("-");
12443 gameInfo.result = r;
12444 gameInfo.resultDetails = p;
12448 gameInfo.event = StrSave("Edited position");
12449 gameInfo.site = StrSave(HostName());
12450 gameInfo.date = PGNDate();
12451 gameInfo.round = StrSave("-");
12452 gameInfo.white = StrSave("-");
12453 gameInfo.black = StrSave("-");
12456 case IcsPlayingWhite:
12457 case IcsPlayingBlack:
12462 case PlayFromGameFile:
12463 gameInfo.event = StrSave("Game from non-PGN file");
12464 gameInfo.site = StrSave(HostName());
12465 gameInfo.date = PGNDate();
12466 gameInfo.round = StrSave("-");
12467 gameInfo.white = StrSave("?");
12468 gameInfo.black = StrSave("?");
12477 ReplaceComment(index, text)
12483 while (*text == '\n') text++;
12484 len = strlen(text);
12485 while (len > 0 && text[len - 1] == '\n') len--;
12487 if (commentList[index] != NULL)
12488 free(commentList[index]);
12491 commentList[index] = NULL;
12494 if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
12495 *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
12496 *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
12497 commentList[index] = (char *) malloc(len + 2);
12498 strncpy(commentList[index], text, len);
12499 commentList[index][len] = '\n';
12500 commentList[index][len + 1] = NULLCHAR;
12502 // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
12504 commentList[index] = (char *) malloc(len + 6);
12505 strcpy(commentList[index], "{\n");
12506 strncpy(commentList[index]+2, text, len);
12507 commentList[index][len+2] = NULLCHAR;
12508 while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
12509 strcat(commentList[index], "\n}\n");
12523 if (ch == '\r') continue;
12525 } while (ch != '\0');
12529 AppendComment(index, text, addBraces)
12532 Boolean addBraces; // [HGM] braces: tells if we should add {}
12537 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
12538 text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
12541 while (*text == '\n') text++;
12542 len = strlen(text);
12543 while (len > 0 && text[len - 1] == '\n') len--;
12545 if (len == 0) return;
12547 if (commentList[index] != NULL) {
12548 old = commentList[index];
12549 oldlen = strlen(old);
12550 while(commentList[index][oldlen-1] == '\n')
12551 commentList[index][--oldlen] = NULLCHAR;
12552 commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
12553 strcpy(commentList[index], old);
12555 // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
12556 if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces)) {
12557 if(addBraces) addBraces = FALSE; else { text++; len--; }
12558 while (*text == '\n') { text++; len--; }
12559 commentList[index][--oldlen] = NULLCHAR;
12561 if(addBraces) strcat(commentList[index], "\n{\n");
12562 else strcat(commentList[index], "\n");
12563 strcat(commentList[index], text);
12564 if(addBraces) strcat(commentList[index], "\n}\n");
12565 else strcat(commentList[index], "\n");
12567 commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
12569 strcpy(commentList[index], "{\n");
12570 else commentList[index][0] = NULLCHAR;
12571 strcat(commentList[index], text);
12572 strcat(commentList[index], "\n");
12573 if(addBraces) strcat(commentList[index], "}\n");
12577 static char * FindStr( char * text, char * sub_text )
12579 char * result = strstr( text, sub_text );
12581 if( result != NULL ) {
12582 result += strlen( sub_text );
12588 /* [AS] Try to extract PV info from PGN comment */
12589 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
12590 char *GetInfoFromComment( int index, char * text )
12594 if( text != NULL && index > 0 ) {
12597 int time = -1, sec = 0, deci;
12598 char * s_eval = FindStr( text, "[%eval " );
12599 char * s_emt = FindStr( text, "[%emt " );
12601 if( s_eval != NULL || s_emt != NULL ) {
12605 if( s_eval != NULL ) {
12606 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
12610 if( delim != ']' ) {
12615 if( s_emt != NULL ) {
12620 /* We expect something like: [+|-]nnn.nn/dd */
12623 if(*text != '{') return text; // [HGM] braces: must be normal comment
12625 sep = strchr( text, '/' );
12626 if( sep == NULL || sep < (text+4) ) {
12630 time = -1; sec = -1; deci = -1;
12631 if( sscanf( text+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
12632 sscanf( text+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
12633 sscanf( text+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
12634 sscanf( text+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3 ) {
12638 if( score_lo < 0 || score_lo >= 100 ) {
12642 if(sec >= 0) time = 600*time + 10*sec; else
12643 if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
12645 score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
12647 /* [HGM] PV time: now locate end of PV info */
12648 while( *++sep >= '0' && *sep <= '9'); // strip depth
12650 while( *++sep >= '0' && *sep <= '9'); // strip time
12652 while( *++sep >= '0' && *sep <= '9'); // strip seconds
12654 while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
12655 while(*sep == ' ') sep++;
12666 pvInfoList[index-1].depth = depth;
12667 pvInfoList[index-1].score = score;
12668 pvInfoList[index-1].time = 10*time; // centi-sec
12669 if(*sep == '}') *sep = 0; else *--sep = '{';
12675 SendToProgram(message, cps)
12677 ChessProgramState *cps;
12679 int count, outCount, error;
12682 if (cps->pr == NULL) return;
12685 if (appData.debugMode) {
12688 fprintf(debugFP, "%ld >%-6s: %s",
12689 SubtractTimeMarks(&now, &programStartTime),
12690 cps->which, message);
12693 count = strlen(message);
12694 outCount = OutputToProcess(cps->pr, message, count, &error);
12695 if (outCount < count && !exiting
12696 && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
12697 sprintf(buf, _("Error writing to %s chess program"), cps->which);
12698 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
12699 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
12700 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
12701 sprintf(buf, "%s program exits in draw position (%s)", cps->which, cps->program);
12703 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
12705 gameInfo.resultDetails = StrSave(buf);
12707 if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
12712 ReceiveFromProgram(isr, closure, message, count, error)
12713 InputSourceRef isr;
12721 ChessProgramState *cps = (ChessProgramState *)closure;
12723 if (isr != cps->isr) return; /* Killed intentionally */
12727 _("Error: %s chess program (%s) exited unexpectedly"),
12728 cps->which, cps->program);
12729 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
12730 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
12731 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
12732 sprintf(buf, _("%s program exits in draw position (%s)"), cps->which, cps->program);
12734 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
12736 gameInfo.resultDetails = StrSave(buf);
12738 RemoveInputSource(cps->isr);
12739 if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
12742 _("Error reading from %s chess program (%s)"),
12743 cps->which, cps->program);
12744 RemoveInputSource(cps->isr);
12746 /* [AS] Program is misbehaving badly... kill it */
12747 if( count == -2 ) {
12748 DestroyChildProcess( cps->pr, 9 );
12752 if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
12757 if ((end_str = strchr(message, '\r')) != NULL)
12758 *end_str = NULLCHAR;
12759 if ((end_str = strchr(message, '\n')) != NULL)
12760 *end_str = NULLCHAR;
12762 if (appData.debugMode) {
12763 TimeMark now; int print = 1;
12764 char *quote = ""; char c; int i;
12766 if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
12767 char start = message[0];
12768 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
12769 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
12770 sscanf(message, "move %c", &c)!=1 && sscanf(message, "offer%c", &c)!=1 &&
12771 sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
12772 sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
12773 sscanf(message, "tell%c", &c)!=1 && sscanf(message, "0-1 %c", &c)!=1 &&
12774 sscanf(message, "1-0 %c", &c)!=1 && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
12775 sscanf(message, "pong %c", &c)!=1 && start != '#')
12776 { quote = "# "; print = (appData.engineComments == 2); }
12777 message[0] = start; // restore original message
12781 fprintf(debugFP, "%ld <%-6s: %s%s\n",
12782 SubtractTimeMarks(&now, &programStartTime), cps->which,
12788 /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
12789 if (appData.icsEngineAnalyze) {
12790 if (strstr(message, "whisper") != NULL ||
12791 strstr(message, "kibitz") != NULL ||
12792 strstr(message, "tellics") != NULL) return;
12795 HandleMachineMove(message, cps);
12800 SendTimeControl(cps, mps, tc, inc, sd, st)
12801 ChessProgramState *cps;
12802 int mps, inc, sd, st;
12808 if( timeControl_2 > 0 ) {
12809 if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
12810 tc = timeControl_2;
12813 tc /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
12814 inc /= cps->timeOdds;
12815 st /= cps->timeOdds;
12817 seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
12820 /* Set exact time per move, normally using st command */
12821 if (cps->stKludge) {
12822 /* GNU Chess 4 has no st command; uses level in a nonstandard way */
12824 if (seconds == 0) {
12825 sprintf(buf, "level 1 %d\n", st/60);
12827 sprintf(buf, "level 1 %d:%02d\n", st/60, seconds);
12830 sprintf(buf, "st %d\n", st);
12833 /* Set conventional or incremental time control, using level command */
12834 if (seconds == 0) {
12835 /* Note old gnuchess bug -- minutes:seconds used to not work.
12836 Fixed in later versions, but still avoid :seconds
12837 when seconds is 0. */
12838 sprintf(buf, "level %d %ld %d\n", mps, tc/60000, inc/1000);
12840 sprintf(buf, "level %d %ld:%02d %d\n", mps, tc/60000,
12841 seconds, inc/1000);
12844 SendToProgram(buf, cps);
12846 /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
12847 /* Orthogonally, limit search to given depth */
12849 if (cps->sdKludge) {
12850 sprintf(buf, "depth\n%d\n", sd);
12852 sprintf(buf, "sd %d\n", sd);
12854 SendToProgram(buf, cps);
12857 if(cps->nps > 0) { /* [HGM] nps */
12858 if(cps->supportsNPS == FALSE) cps->nps = -1; // don't use if engine explicitly says not supported!
12860 sprintf(buf, "nps %d\n", cps->nps);
12861 SendToProgram(buf, cps);
12866 ChessProgramState *WhitePlayer()
12867 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
12869 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
12870 gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
12876 SendTimeRemaining(cps, machineWhite)
12877 ChessProgramState *cps;
12878 int /*boolean*/ machineWhite;
12880 char message[MSG_SIZ];
12883 /* Note: this routine must be called when the clocks are stopped
12884 or when they have *just* been set or switched; otherwise
12885 it will be off by the time since the current tick started.
12887 if (machineWhite) {
12888 time = whiteTimeRemaining / 10;
12889 otime = blackTimeRemaining / 10;
12891 time = blackTimeRemaining / 10;
12892 otime = whiteTimeRemaining / 10;
12894 /* [HGM] translate opponent's time by time-odds factor */
12895 otime = (otime * cps->other->timeOdds) / cps->timeOdds;
12896 if (appData.debugMode) {
12897 fprintf(debugFP, "time odds: %f %f \n", cps->timeOdds, cps->other->timeOdds);
12900 if (time <= 0) time = 1;
12901 if (otime <= 0) otime = 1;
12903 sprintf(message, "time %ld\n", time);
12904 SendToProgram(message, cps);
12906 sprintf(message, "otim %ld\n", otime);
12907 SendToProgram(message, cps);
12911 BoolFeature(p, name, loc, cps)
12915 ChessProgramState *cps;
12918 int len = strlen(name);
12920 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
12922 sscanf(*p, "%d", &val);
12924 while (**p && **p != ' ') (*p)++;
12925 sprintf(buf, "accepted %s\n", name);
12926 SendToProgram(buf, cps);
12933 IntFeature(p, name, loc, cps)
12937 ChessProgramState *cps;
12940 int len = strlen(name);
12941 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
12943 sscanf(*p, "%d", loc);
12944 while (**p && **p != ' ') (*p)++;
12945 sprintf(buf, "accepted %s\n", name);
12946 SendToProgram(buf, cps);
12953 StringFeature(p, name, loc, cps)
12957 ChessProgramState *cps;
12960 int len = strlen(name);
12961 if (strncmp((*p), name, len) == 0
12962 && (*p)[len] == '=' && (*p)[len+1] == '\"') {
12964 sscanf(*p, "%[^\"]", loc);
12965 while (**p && **p != '\"') (*p)++;
12966 if (**p == '\"') (*p)++;
12967 sprintf(buf, "accepted %s\n", name);
12968 SendToProgram(buf, cps);
12975 ParseOption(Option *opt, ChessProgramState *cps)
12976 // [HGM] options: process the string that defines an engine option, and determine
12977 // name, type, default value, and allowed value range
12979 char *p, *q, buf[MSG_SIZ];
12980 int n, min = (-1)<<31, max = 1<<31, def;
12982 if(p = strstr(opt->name, " -spin ")) {
12983 if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
12984 if(max < min) max = min; // enforce consistency
12985 if(def < min) def = min;
12986 if(def > max) def = max;
12991 } else if((p = strstr(opt->name, " -slider "))) {
12992 // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
12993 if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
12994 if(max < min) max = min; // enforce consistency
12995 if(def < min) def = min;
12996 if(def > max) def = max;
13000 opt->type = Spin; // Slider;
13001 } else if((p = strstr(opt->name, " -string "))) {
13002 opt->textValue = p+9;
13003 opt->type = TextBox;
13004 } else if((p = strstr(opt->name, " -file "))) {
13005 // for now -file is a synonym for -string, to already provide compatibility with future polyglots
13006 opt->textValue = p+7;
13007 opt->type = TextBox; // FileName;
13008 } else if((p = strstr(opt->name, " -path "))) {
13009 // for now -file is a synonym for -string, to already provide compatibility with future polyglots
13010 opt->textValue = p+7;
13011 opt->type = TextBox; // PathName;
13012 } else if(p = strstr(opt->name, " -check ")) {
13013 if(sscanf(p, " -check %d", &def) < 1) return FALSE;
13014 opt->value = (def != 0);
13015 opt->type = CheckBox;
13016 } else if(p = strstr(opt->name, " -combo ")) {
13017 opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
13018 cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
13019 if(*q == '*') cps->comboList[cps->comboCnt-1]++;
13020 opt->value = n = 0;
13021 while(q = StrStr(q, " /// ")) {
13022 n++; *q = 0; // count choices, and null-terminate each of them
13024 if(*q == '*') { // remember default, which is marked with * prefix
13028 cps->comboList[cps->comboCnt++] = q;
13030 cps->comboList[cps->comboCnt++] = NULL;
13032 opt->type = ComboBox;
13033 } else if(p = strstr(opt->name, " -button")) {
13034 opt->type = Button;
13035 } else if(p = strstr(opt->name, " -save")) {
13036 opt->type = SaveButton;
13037 } else return FALSE;
13038 *p = 0; // terminate option name
13039 // now look if the command-line options define a setting for this engine option.
13040 if(cps->optionSettings && cps->optionSettings[0])
13041 p = strstr(cps->optionSettings, opt->name); else p = NULL;
13042 if(p && (p == cps->optionSettings || p[-1] == ',')) {
13043 sprintf(buf, "option %s", p);
13044 if(p = strstr(buf, ",")) *p = 0;
13046 SendToProgram(buf, cps);
13052 FeatureDone(cps, val)
13053 ChessProgramState* cps;
13056 DelayedEventCallback cb = GetDelayedEvent();
13057 if ((cb == InitBackEnd3 && cps == &first) ||
13058 (cb == TwoMachinesEventIfReady && cps == &second)) {
13059 CancelDelayedEvent();
13060 ScheduleDelayedEvent(cb, val ? 1 : 3600000);
13062 cps->initDone = val;
13065 /* Parse feature command from engine */
13067 ParseFeatures(args, cps)
13069 ChessProgramState *cps;
13077 while (*p == ' ') p++;
13078 if (*p == NULLCHAR) return;
13080 if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
13081 if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
13082 if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
13083 if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
13084 if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
13085 if (BoolFeature(&p, "reuse", &val, cps)) {
13086 /* Engine can disable reuse, but can't enable it if user said no */
13087 if (!val) cps->reuse = FALSE;
13090 if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
13091 if (StringFeature(&p, "myname", &cps->tidy, cps)) {
13092 if (gameMode == TwoMachinesPlay) {
13093 DisplayTwoMachinesTitle();
13099 if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
13100 if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
13101 if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
13102 if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
13103 if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
13104 if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
13105 if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
13106 if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
13107 if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
13108 if (IntFeature(&p, "done", &val, cps)) {
13109 FeatureDone(cps, val);
13112 /* Added by Tord: */
13113 if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
13114 if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
13115 /* End of additions by Tord */
13117 /* [HGM] added features: */
13118 if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
13119 if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
13120 if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
13121 if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
13122 if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
13123 if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
13124 if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
13125 if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
13126 sprintf(buf, "rejected option %s\n", cps->option[--cps->nrOptions].name);
13127 SendToProgram(buf, cps);
13130 if(cps->nrOptions >= MAX_OPTIONS) {
13132 sprintf(buf, "%s engine has too many options\n", cps->which);
13133 DisplayError(buf, 0);
13137 if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
13138 /* End of additions by HGM */
13140 /* unknown feature: complain and skip */
13142 while (*q && *q != '=') q++;
13143 sprintf(buf, "rejected %.*s\n", (int)(q-p), p);
13144 SendToProgram(buf, cps);
13150 while (*p && *p != '\"') p++;
13151 if (*p == '\"') p++;
13153 while (*p && *p != ' ') p++;
13161 PeriodicUpdatesEvent(newState)
13164 if (newState == appData.periodicUpdates)
13167 appData.periodicUpdates=newState;
13169 /* Display type changes, so update it now */
13170 // DisplayAnalysis();
13172 /* Get the ball rolling again... */
13174 AnalysisPeriodicEvent(1);
13175 StartAnalysisClock();
13180 PonderNextMoveEvent(newState)
13183 if (newState == appData.ponderNextMove) return;
13184 if (gameMode == EditPosition) EditPositionDone(TRUE);
13186 SendToProgram("hard\n", &first);
13187 if (gameMode == TwoMachinesPlay) {
13188 SendToProgram("hard\n", &second);
13191 SendToProgram("easy\n", &first);
13192 thinkOutput[0] = NULLCHAR;
13193 if (gameMode == TwoMachinesPlay) {
13194 SendToProgram("easy\n", &second);
13197 appData.ponderNextMove = newState;
13201 NewSettingEvent(option, command, value)
13207 if (gameMode == EditPosition) EditPositionDone(TRUE);
13208 sprintf(buf, "%s%s %d\n", (option ? "option ": ""), command, value);
13209 SendToProgram(buf, &first);
13210 if (gameMode == TwoMachinesPlay) {
13211 SendToProgram(buf, &second);
13216 ShowThinkingEvent()
13217 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
13219 static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
13220 int newState = appData.showThinking
13221 // [HGM] thinking: other features now need thinking output as well
13222 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
13224 if (oldState == newState) return;
13225 oldState = newState;
13226 if (gameMode == EditPosition) EditPositionDone(TRUE);
13228 SendToProgram("post\n", &first);
13229 if (gameMode == TwoMachinesPlay) {
13230 SendToProgram("post\n", &second);
13233 SendToProgram("nopost\n", &first);
13234 thinkOutput[0] = NULLCHAR;
13235 if (gameMode == TwoMachinesPlay) {
13236 SendToProgram("nopost\n", &second);
13239 // appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
13243 AskQuestionEvent(title, question, replyPrefix, which)
13244 char *title; char *question; char *replyPrefix; char *which;
13246 ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
13247 if (pr == NoProc) return;
13248 AskQuestion(title, question, replyPrefix, pr);
13252 DisplayMove(moveNumber)
13255 char message[MSG_SIZ];
13257 char cpThinkOutput[MSG_SIZ];
13259 if(appData.noGUI) return; // [HGM] fast: suppress display of moves
13261 if (moveNumber == forwardMostMove - 1 ||
13262 gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13264 safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput));
13266 if (strchr(cpThinkOutput, '\n')) {
13267 *strchr(cpThinkOutput, '\n') = NULLCHAR;
13270 *cpThinkOutput = NULLCHAR;
13273 /* [AS] Hide thinking from human user */
13274 if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
13275 *cpThinkOutput = NULLCHAR;
13276 if( thinkOutput[0] != NULLCHAR ) {
13279 for( i=0; i<=hiddenThinkOutputState; i++ ) {
13280 cpThinkOutput[i] = '.';
13282 cpThinkOutput[i] = NULLCHAR;
13283 hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
13287 if (moveNumber == forwardMostMove - 1 &&
13288 gameInfo.resultDetails != NULL) {
13289 if (gameInfo.resultDetails[0] == NULLCHAR) {
13290 sprintf(res, " %s", PGNResult(gameInfo.result));
13292 sprintf(res, " {%s} %s",
13293 gameInfo.resultDetails, PGNResult(gameInfo.result));
13299 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
13300 DisplayMessage(res, cpThinkOutput);
13302 sprintf(message, "%d.%s%s%s", moveNumber / 2 + 1,
13303 WhiteOnMove(moveNumber) ? " " : ".. ",
13304 parseList[moveNumber], res);
13305 DisplayMessage(message, cpThinkOutput);
13310 DisplayComment(moveNumber, text)
13314 char title[MSG_SIZ];
13315 char buf[8000]; // comment can be long!
13318 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
13319 strcpy(title, "Comment");
13321 sprintf(title, "Comment on %d.%s%s", moveNumber / 2 + 1,
13322 WhiteOnMove(moveNumber) ? " " : ".. ",
13323 parseList[moveNumber]);
13325 // [HGM] PV info: display PV info together with (or as) comment
13326 if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {
13327 if(text == NULL) text = "";
13328 score = pvInfoList[moveNumber].score;
13329 sprintf(buf, "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,
13330 depth, (pvInfoList[moveNumber].time+50)/100, text);
13333 if (text != NULL && (appData.autoDisplayComment || commentUp))
13334 CommentPopUp(title, text);
13337 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
13338 * might be busy thinking or pondering. It can be omitted if your
13339 * gnuchess is configured to stop thinking immediately on any user
13340 * input. However, that gnuchess feature depends on the FIONREAD
13341 * ioctl, which does not work properly on some flavors of Unix.
13345 ChessProgramState *cps;
13348 if (!cps->useSigint) return;
13349 if (appData.noChessProgram || (cps->pr == NoProc)) return;
13350 switch (gameMode) {
13351 case MachinePlaysWhite:
13352 case MachinePlaysBlack:
13353 case TwoMachinesPlay:
13354 case IcsPlayingWhite:
13355 case IcsPlayingBlack:
13358 /* Skip if we know it isn't thinking */
13359 if (!cps->maybeThinking) return;
13360 if (appData.debugMode)
13361 fprintf(debugFP, "Interrupting %s\n", cps->which);
13362 InterruptChildProcess(cps->pr);
13363 cps->maybeThinking = FALSE;
13368 #endif /*ATTENTION*/
13374 if (whiteTimeRemaining <= 0) {
13377 if (appData.icsActive) {
13378 if (appData.autoCallFlag &&
13379 gameMode == IcsPlayingBlack && !blackFlag) {
13380 SendToICS(ics_prefix);
13381 SendToICS("flag\n");
13385 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
13387 if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
13388 if (appData.autoCallFlag) {
13389 GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
13396 if (blackTimeRemaining <= 0) {
13399 if (appData.icsActive) {
13400 if (appData.autoCallFlag &&
13401 gameMode == IcsPlayingWhite && !whiteFlag) {
13402 SendToICS(ics_prefix);
13403 SendToICS("flag\n");
13407 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
13409 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
13410 if (appData.autoCallFlag) {
13411 GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
13424 if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
13425 gameMode == PlayFromGameFile || forwardMostMove == 0) return;
13428 * add time to clocks when time control is achieved ([HGM] now also used for increment)
13430 if ( !WhiteOnMove(forwardMostMove) )
13431 /* White made time control */
13432 whiteTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
13433 /* [HGM] time odds: correct new time quota for time odds! */
13434 / WhitePlayer()->timeOdds;
13436 /* Black made time control */
13437 blackTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
13438 / WhitePlayer()->other->timeOdds;
13442 DisplayBothClocks()
13444 int wom = gameMode == EditPosition ?
13445 !blackPlaysFirst : WhiteOnMove(currentMove);
13446 DisplayWhiteClock(whiteTimeRemaining, wom);
13447 DisplayBlackClock(blackTimeRemaining, !wom);
13451 /* Timekeeping seems to be a portability nightmare. I think everyone
13452 has ftime(), but I'm really not sure, so I'm including some ifdefs
13453 to use other calls if you don't. Clocks will be less accurate if
13454 you have neither ftime nor gettimeofday.
13457 /* VS 2008 requires the #include outside of the function */
13458 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
13459 #include <sys/timeb.h>
13462 /* Get the current time as a TimeMark */
13467 #if HAVE_GETTIMEOFDAY
13469 struct timeval timeVal;
13470 struct timezone timeZone;
13472 gettimeofday(&timeVal, &timeZone);
13473 tm->sec = (long) timeVal.tv_sec;
13474 tm->ms = (int) (timeVal.tv_usec / 1000L);
13476 #else /*!HAVE_GETTIMEOFDAY*/
13479 // include <sys/timeb.h> / moved to just above start of function
13480 struct timeb timeB;
13483 tm->sec = (long) timeB.time;
13484 tm->ms = (int) timeB.millitm;
13486 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
13487 tm->sec = (long) time(NULL);
13493 /* Return the difference in milliseconds between two
13494 time marks. We assume the difference will fit in a long!
13497 SubtractTimeMarks(tm2, tm1)
13498 TimeMark *tm2, *tm1;
13500 return 1000L*(tm2->sec - tm1->sec) +
13501 (long) (tm2->ms - tm1->ms);
13506 * Code to manage the game clocks.
13508 * In tournament play, black starts the clock and then white makes a move.
13509 * We give the human user a slight advantage if he is playing white---the
13510 * clocks don't run until he makes his first move, so it takes zero time.
13511 * Also, we don't account for network lag, so we could get out of sync
13512 * with GNU Chess's clock -- but then, referees are always right.
13515 static TimeMark tickStartTM;
13516 static long intendedTickLength;
13519 NextTickLength(timeRemaining)
13520 long timeRemaining;
13522 long nominalTickLength, nextTickLength;
13524 if (timeRemaining > 0L && timeRemaining <= 10000L)
13525 nominalTickLength = 100L;
13527 nominalTickLength = 1000L;
13528 nextTickLength = timeRemaining % nominalTickLength;
13529 if (nextTickLength <= 0) nextTickLength += nominalTickLength;
13531 return nextTickLength;
13534 /* Adjust clock one minute up or down */
13536 AdjustClock(Boolean which, int dir)
13538 if(which) blackTimeRemaining += 60000*dir;
13539 else whiteTimeRemaining += 60000*dir;
13540 DisplayBothClocks();
13543 /* Stop clocks and reset to a fresh time control */
13547 (void) StopClockTimer();
13548 if (appData.icsActive) {
13549 whiteTimeRemaining = blackTimeRemaining = 0;
13550 } else if (searchTime) {
13551 whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
13552 blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
13553 } else { /* [HGM] correct new time quote for time odds */
13554 whiteTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->timeOdds;
13555 blackTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->other->timeOdds;
13557 if (whiteFlag || blackFlag) {
13559 whiteFlag = blackFlag = FALSE;
13561 DisplayBothClocks();
13564 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
13566 /* Decrement running clock by amount of time that has passed */
13570 long timeRemaining;
13571 long lastTickLength, fudge;
13574 if (!appData.clockMode) return;
13575 if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
13579 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13581 /* Fudge if we woke up a little too soon */
13582 fudge = intendedTickLength - lastTickLength;
13583 if (fudge < 0 || fudge > FUDGE) fudge = 0;
13585 if (WhiteOnMove(forwardMostMove)) {
13586 if(whiteNPS >= 0) lastTickLength = 0;
13587 timeRemaining = whiteTimeRemaining -= lastTickLength;
13588 DisplayWhiteClock(whiteTimeRemaining - fudge,
13589 WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
13591 if(blackNPS >= 0) lastTickLength = 0;
13592 timeRemaining = blackTimeRemaining -= lastTickLength;
13593 DisplayBlackClock(blackTimeRemaining - fudge,
13594 !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
13597 if (CheckFlags()) return;
13600 intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
13601 StartClockTimer(intendedTickLength);
13603 /* if the time remaining has fallen below the alarm threshold, sound the
13604 * alarm. if the alarm has sounded and (due to a takeback or time control
13605 * with increment) the time remaining has increased to a level above the
13606 * threshold, reset the alarm so it can sound again.
13609 if (appData.icsActive && appData.icsAlarm) {
13611 /* make sure we are dealing with the user's clock */
13612 if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
13613 ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
13616 if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
13617 alarmSounded = FALSE;
13618 } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
13620 alarmSounded = TRUE;
13626 /* A player has just moved, so stop the previously running
13627 clock and (if in clock mode) start the other one.
13628 We redisplay both clocks in case we're in ICS mode, because
13629 ICS gives us an update to both clocks after every move.
13630 Note that this routine is called *after* forwardMostMove
13631 is updated, so the last fractional tick must be subtracted
13632 from the color that is *not* on move now.
13637 long lastTickLength;
13639 int flagged = FALSE;
13643 if (StopClockTimer() && appData.clockMode) {
13644 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13645 if (WhiteOnMove(forwardMostMove)) {
13646 if(blackNPS >= 0) lastTickLength = 0;
13647 blackTimeRemaining -= lastTickLength;
13648 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
13649 // if(pvInfoList[forwardMostMove-1].time == -1)
13650 pvInfoList[forwardMostMove-1].time = // use GUI time
13651 (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
13653 if(whiteNPS >= 0) lastTickLength = 0;
13654 whiteTimeRemaining -= lastTickLength;
13655 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
13656 // if(pvInfoList[forwardMostMove-1].time == -1)
13657 pvInfoList[forwardMostMove-1].time =
13658 (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
13660 flagged = CheckFlags();
13662 CheckTimeControl();
13664 if (flagged || !appData.clockMode) return;
13666 switch (gameMode) {
13667 case MachinePlaysBlack:
13668 case MachinePlaysWhite:
13669 case BeginningOfGame:
13670 if (pausing) return;
13674 case PlayFromGameFile:
13682 if (searchTime) { // [HGM] st: set clock of player that has to move to max time
13683 if(WhiteOnMove(forwardMostMove))
13684 whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
13685 else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
13689 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
13690 whiteTimeRemaining : blackTimeRemaining);
13691 StartClockTimer(intendedTickLength);
13695 /* Stop both clocks */
13699 long lastTickLength;
13702 if (!StopClockTimer()) return;
13703 if (!appData.clockMode) return;
13707 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13708 if (WhiteOnMove(forwardMostMove)) {
13709 if(whiteNPS >= 0) lastTickLength = 0;
13710 whiteTimeRemaining -= lastTickLength;
13711 DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
13713 if(blackNPS >= 0) lastTickLength = 0;
13714 blackTimeRemaining -= lastTickLength;
13715 DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
13720 /* Start clock of player on move. Time may have been reset, so
13721 if clock is already running, stop and restart it. */
13725 (void) StopClockTimer(); /* in case it was running already */
13726 DisplayBothClocks();
13727 if (CheckFlags()) return;
13729 if (!appData.clockMode) return;
13730 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
13732 GetTimeMark(&tickStartTM);
13733 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
13734 whiteTimeRemaining : blackTimeRemaining);
13736 /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
13737 whiteNPS = blackNPS = -1;
13738 if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
13739 || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
13740 whiteNPS = first.nps;
13741 if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
13742 || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
13743 blackNPS = first.nps;
13744 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
13745 whiteNPS = second.nps;
13746 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
13747 blackNPS = second.nps;
13748 if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
13750 StartClockTimer(intendedTickLength);
13757 long second, minute, hour, day;
13759 static char buf[32];
13761 if (ms > 0 && ms <= 9900) {
13762 /* convert milliseconds to tenths, rounding up */
13763 double tenths = floor( ((double)(ms + 99L)) / 100.00 );
13765 sprintf(buf, " %03.1f ", tenths/10.0);
13769 /* convert milliseconds to seconds, rounding up */
13770 /* use floating point to avoid strangeness of integer division
13771 with negative dividends on many machines */
13772 second = (long) floor(((double) (ms + 999L)) / 1000.0);
13779 day = second / (60 * 60 * 24);
13780 second = second % (60 * 60 * 24);
13781 hour = second / (60 * 60);
13782 second = second % (60 * 60);
13783 minute = second / 60;
13784 second = second % 60;
13787 sprintf(buf, " %s%ld:%02ld:%02ld:%02ld ",
13788 sign, day, hour, minute, second);
13790 sprintf(buf, " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
13792 sprintf(buf, " %s%2ld:%02ld ", sign, minute, second);
13799 * This is necessary because some C libraries aren't ANSI C compliant yet.
13802 StrStr(string, match)
13803 char *string, *match;
13807 length = strlen(match);
13809 for (i = strlen(string) - length; i >= 0; i--, string++)
13810 if (!strncmp(match, string, length))
13817 StrCaseStr(string, match)
13818 char *string, *match;
13822 length = strlen(match);
13824 for (i = strlen(string) - length; i >= 0; i--, string++) {
13825 for (j = 0; j < length; j++) {
13826 if (ToLower(match[j]) != ToLower(string[j]))
13829 if (j == length) return string;
13843 c1 = ToLower(*s1++);
13844 c2 = ToLower(*s2++);
13845 if (c1 > c2) return 1;
13846 if (c1 < c2) return -1;
13847 if (c1 == NULLCHAR) return 0;
13856 return isupper(c) ? tolower(c) : c;
13864 return islower(c) ? toupper(c) : c;
13866 #endif /* !_amigados */
13874 if ((ret = (char *) malloc(strlen(s) + 1))) {
13881 StrSavePtr(s, savePtr)
13882 char *s, **savePtr;
13887 if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
13888 strcpy(*savePtr, s);
13900 clock = time((time_t *)NULL);
13901 tm = localtime(&clock);
13902 sprintf(buf, "%04d.%02d.%02d",
13903 tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
13904 return StrSave(buf);
13909 PositionToFEN(move, overrideCastling)
13911 char *overrideCastling;
13913 int i, j, fromX, fromY, toX, toY;
13920 whiteToPlay = (gameMode == EditPosition) ?
13921 !blackPlaysFirst : (move % 2 == 0);
13924 /* Piece placement data */
13925 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13927 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
13928 if (boards[move][i][j] == EmptySquare) {
13930 } else { ChessSquare piece = boards[move][i][j];
13931 if (emptycount > 0) {
13932 if(emptycount<10) /* [HGM] can be >= 10 */
13933 *p++ = '0' + emptycount;
13934 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
13937 if(PieceToChar(piece) == '+') {
13938 /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
13940 piece = (ChessSquare)(DEMOTED piece);
13942 *p++ = PieceToChar(piece);
13944 /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
13945 p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
13950 if (emptycount > 0) {
13951 if(emptycount<10) /* [HGM] can be >= 10 */
13952 *p++ = '0' + emptycount;
13953 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
13960 /* [HGM] print Crazyhouse or Shogi holdings */
13961 if( gameInfo.holdingsWidth ) {
13962 *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
13964 for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
13965 piece = boards[move][i][BOARD_WIDTH-1];
13966 if( piece != EmptySquare )
13967 for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
13968 *p++ = PieceToChar(piece);
13970 for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
13971 piece = boards[move][BOARD_HEIGHT-i-1][0];
13972 if( piece != EmptySquare )
13973 for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
13974 *p++ = PieceToChar(piece);
13977 if( q == p ) *p++ = '-';
13983 *p++ = whiteToPlay ? 'w' : 'b';
13986 if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
13987 while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
13989 if(nrCastlingRights) {
13991 if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
13992 /* [HGM] write directly from rights */
13993 if(boards[move][CASTLING][2] != NoRights &&
13994 boards[move][CASTLING][0] != NoRights )
13995 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
13996 if(boards[move][CASTLING][2] != NoRights &&
13997 boards[move][CASTLING][1] != NoRights )
13998 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
13999 if(boards[move][CASTLING][5] != NoRights &&
14000 boards[move][CASTLING][3] != NoRights )
14001 *p++ = boards[move][CASTLING][3] + AAA;
14002 if(boards[move][CASTLING][5] != NoRights &&
14003 boards[move][CASTLING][4] != NoRights )
14004 *p++ = boards[move][CASTLING][4] + AAA;
14007 /* [HGM] write true castling rights */
14008 if( nrCastlingRights == 6 ) {
14009 if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
14010 boards[move][CASTLING][2] != NoRights ) *p++ = 'K';
14011 if(boards[move][CASTLING][1] == BOARD_LEFT &&
14012 boards[move][CASTLING][2] != NoRights ) *p++ = 'Q';
14013 if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
14014 boards[move][CASTLING][5] != NoRights ) *p++ = 'k';
14015 if(boards[move][CASTLING][4] == BOARD_LEFT &&
14016 boards[move][CASTLING][5] != NoRights ) *p++ = 'q';
14019 if (q == p) *p++ = '-'; /* No castling rights */
14023 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
14024 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
14025 /* En passant target square */
14026 if (move > backwardMostMove) {
14027 fromX = moveList[move - 1][0] - AAA;
14028 fromY = moveList[move - 1][1] - ONE;
14029 toX = moveList[move - 1][2] - AAA;
14030 toY = moveList[move - 1][3] - ONE;
14031 if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
14032 toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
14033 boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
14035 /* 2-square pawn move just happened */
14037 *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
14041 } else if(move == backwardMostMove) {
14042 // [HGM] perhaps we should always do it like this, and forget the above?
14043 if((signed char)boards[move][EP_STATUS] >= 0) {
14044 *p++ = boards[move][EP_STATUS] + AAA;
14045 *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
14056 /* [HGM] find reversible plies */
14057 { int i = 0, j=move;
14059 if (appData.debugMode) { int k;
14060 fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
14061 for(k=backwardMostMove; k<=forwardMostMove; k++)
14062 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
14066 while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
14067 if( j == backwardMostMove ) i += initialRulePlies;
14068 sprintf(p, "%d ", i);
14069 p += i>=100 ? 4 : i >= 10 ? 3 : 2;
14071 /* Fullmove number */
14072 sprintf(p, "%d", (move / 2) + 1);
14074 return StrSave(buf);
14078 ParseFEN(board, blackPlaysFirst, fen)
14080 int *blackPlaysFirst;
14090 /* [HGM] by default clear Crazyhouse holdings, if present */
14091 if(gameInfo.holdingsWidth) {
14092 for(i=0; i<BOARD_HEIGHT; i++) {
14093 board[i][0] = EmptySquare; /* black holdings */
14094 board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
14095 board[i][1] = (ChessSquare) 0; /* black counts */
14096 board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
14100 /* Piece placement data */
14101 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
14104 if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
14105 if (*p == '/') p++;
14106 emptycount = gameInfo.boardWidth - j;
14107 while (emptycount--)
14108 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
14110 #if(BOARD_FILES >= 10)
14111 } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
14112 p++; emptycount=10;
14113 if (j + emptycount > gameInfo.boardWidth) return FALSE;
14114 while (emptycount--)
14115 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
14117 } else if (isdigit(*p)) {
14118 emptycount = *p++ - '0';
14119 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
14120 if (j + emptycount > gameInfo.boardWidth) return FALSE;
14121 while (emptycount--)
14122 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
14123 } else if (*p == '+' || isalpha(*p)) {
14124 if (j >= gameInfo.boardWidth) return FALSE;
14126 piece = CharToPiece(*++p);
14127 if(piece == EmptySquare) return FALSE; /* unknown piece */
14128 piece = (ChessSquare) (PROMOTED piece ); p++;
14129 if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
14130 } else piece = CharToPiece(*p++);
14132 if(piece==EmptySquare) return FALSE; /* unknown piece */
14133 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
14134 piece = (ChessSquare) (PROMOTED piece);
14135 if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
14138 board[i][(j++)+gameInfo.holdingsWidth] = piece;
14144 while (*p == '/' || *p == ' ') p++;
14146 /* [HGM] look for Crazyhouse holdings here */
14147 while(*p==' ') p++;
14148 if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
14150 if(*p == '-' ) *p++; /* empty holdings */ else {
14151 if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
14152 /* if we would allow FEN reading to set board size, we would */
14153 /* have to add holdings and shift the board read so far here */
14154 while( (piece = CharToPiece(*p) ) != EmptySquare ) {
14156 if((int) piece >= (int) BlackPawn ) {
14157 i = (int)piece - (int)BlackPawn;
14158 i = PieceToNumber((ChessSquare)i);
14159 if( i >= gameInfo.holdingsSize ) return FALSE;
14160 board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
14161 board[BOARD_HEIGHT-1-i][1]++; /* black counts */
14163 i = (int)piece - (int)WhitePawn;
14164 i = PieceToNumber((ChessSquare)i);
14165 if( i >= gameInfo.holdingsSize ) return FALSE;
14166 board[i][BOARD_WIDTH-1] = piece; /* white holdings */
14167 board[i][BOARD_WIDTH-2]++; /* black holdings */
14171 if(*p == ']') *p++;
14174 while(*p == ' ') p++;
14179 *blackPlaysFirst = FALSE;
14182 *blackPlaysFirst = TRUE;
14188 /* [HGM] We NO LONGER ignore the rest of the FEN notation */
14189 /* return the extra info in global variiables */
14191 /* set defaults in case FEN is incomplete */
14192 board[EP_STATUS] = EP_UNKNOWN;
14193 for(i=0; i<nrCastlingRights; i++ ) {
14194 board[CASTLING][i] =
14195 gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
14196 } /* assume possible unless obviously impossible */
14197 if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
14198 if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
14199 if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
14200 && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
14201 if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
14202 if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
14203 if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
14204 && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
14207 while(*p==' ') p++;
14208 if(nrCastlingRights) {
14209 if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
14210 /* castling indicator present, so default becomes no castlings */
14211 for(i=0; i<nrCastlingRights; i++ ) {
14212 board[CASTLING][i] = NoRights;
14215 while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
14216 (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
14217 ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
14218 ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth) ) {
14219 char c = *p++; int whiteKingFile=NoRights, blackKingFile=NoRights;
14221 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
14222 if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
14223 if(board[0 ][i] == WhiteKing) whiteKingFile = i;
14225 if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
14226 whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
14227 if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
14228 && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
14229 if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
14230 && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
14233 for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
14234 board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
14235 board[CASTLING][2] = whiteKingFile;
14238 for(i=BOARD_LEFT; i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
14239 board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
14240 board[CASTLING][2] = whiteKingFile;
14243 for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
14244 board[CASTLING][3] = i != blackKingFile ? i : NoRights;
14245 board[CASTLING][5] = blackKingFile;
14248 for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
14249 board[CASTLING][4] = i != blackKingFile ? i : NoRights;
14250 board[CASTLING][5] = blackKingFile;
14253 default: /* FRC castlings */
14254 if(c >= 'a') { /* black rights */
14255 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
14256 if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
14257 if(i == BOARD_RGHT) break;
14258 board[CASTLING][5] = i;
14260 if(board[BOARD_HEIGHT-1][c] < BlackPawn ||
14261 board[BOARD_HEIGHT-1][c] >= BlackKing ) break;
14263 board[CASTLING][3] = c;
14265 board[CASTLING][4] = c;
14266 } else { /* white rights */
14267 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
14268 if(board[0][i] == WhiteKing) break;
14269 if(i == BOARD_RGHT) break;
14270 board[CASTLING][2] = i;
14271 c -= AAA - 'a' + 'A';
14272 if(board[0][c] >= WhiteKing) break;
14274 board[CASTLING][0] = c;
14276 board[CASTLING][1] = c;
14280 for(i=0; i<nrCastlingRights; i++)
14281 if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
14282 if (appData.debugMode) {
14283 fprintf(debugFP, "FEN castling rights:");
14284 for(i=0; i<nrCastlingRights; i++)
14285 fprintf(debugFP, " %d", board[CASTLING][i]);
14286 fprintf(debugFP, "\n");
14289 while(*p==' ') p++;
14292 /* read e.p. field in games that know e.p. capture */
14293 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
14294 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
14296 p++; board[EP_STATUS] = EP_NONE;
14298 char c = *p++ - AAA;
14300 if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
14301 if(*p >= '0' && *p <='9') *p++;
14302 board[EP_STATUS] = c;
14307 if(sscanf(p, "%d", &i) == 1) {
14308 FENrulePlies = i; /* 50-move ply counter */
14309 /* (The move number is still ignored) */
14316 EditPositionPasteFEN(char *fen)
14319 Board initial_position;
14321 if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
14322 DisplayError(_("Bad FEN position in clipboard"), 0);
14325 int savedBlackPlaysFirst = blackPlaysFirst;
14326 EditPositionEvent();
14327 blackPlaysFirst = savedBlackPlaysFirst;
14328 CopyBoard(boards[0], initial_position);
14329 initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
14330 EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
14331 DisplayBothClocks();
14332 DrawPosition(FALSE, boards[currentMove]);
14337 static char cseq[12] = "\\ ";
14339 Boolean set_cont_sequence(char *new_seq)
14344 // handle bad attempts to set the sequence
14346 return 0; // acceptable error - no debug
14348 len = strlen(new_seq);
14349 ret = (len > 0) && (len < sizeof(cseq));
14351 strcpy(cseq, new_seq);
14352 else if (appData.debugMode)
14353 fprintf(debugFP, "Invalid continuation sequence \"%s\" (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
14358 reformat a source message so words don't cross the width boundary. internal
14359 newlines are not removed. returns the wrapped size (no null character unless
14360 included in source message). If dest is NULL, only calculate the size required
14361 for the dest buffer. lp argument indicats line position upon entry, and it's
14362 passed back upon exit.
14364 int wrap(char *dest, char *src, int count, int width, int *lp)
14366 int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
14368 cseq_len = strlen(cseq);
14369 old_line = line = *lp;
14370 ansi = len = clen = 0;
14372 for (i=0; i < count; i++)
14374 if (src[i] == '\033')
14377 // if we hit the width, back up
14378 if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
14380 // store i & len in case the word is too long
14381 old_i = i, old_len = len;
14383 // find the end of the last word
14384 while (i && src[i] != ' ' && src[i] != '\n')
14390 // word too long? restore i & len before splitting it
14391 if ((old_i-i+clen) >= width)
14398 if (i && src[i-1] == ' ')
14401 if (src[i] != ' ' && src[i] != '\n')
14408 // now append the newline and continuation sequence
14413 strncpy(dest+len, cseq, cseq_len);
14421 dest[len] = src[i];
14425 if (src[i] == '\n')
14430 if (dest && appData.debugMode)
14432 fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
14433 count, width, line, len, *lp);
14434 show_bytes(debugFP, src, count);
14435 fprintf(debugFP, "\ndest: ");
14436 show_bytes(debugFP, dest, len);
14437 fprintf(debugFP, "\n");
14439 *lp = dest ? line : old_line;
14444 // [HGM] vari: routines for shelving variations
14447 PushTail(int firstMove, int lastMove)
14449 int i, j, nrMoves = lastMove - firstMove;
14451 if(appData.icsActive) { // only in local mode
14452 forwardMostMove = currentMove; // mimic old ICS behavior
14455 if(storedGames >= MAX_VARIATIONS-1) return;
14457 // push current tail of game on stack
14458 savedResult[storedGames] = gameInfo.result;
14459 savedDetails[storedGames] = gameInfo.resultDetails;
14460 gameInfo.resultDetails = NULL;
14461 savedFirst[storedGames] = firstMove;
14462 savedLast [storedGames] = lastMove;
14463 savedFramePtr[storedGames] = framePtr;
14464 framePtr -= nrMoves; // reserve space for the boards
14465 for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
14466 CopyBoard(boards[framePtr+i], boards[firstMove+i]);
14467 for(j=0; j<MOVE_LEN; j++)
14468 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
14469 for(j=0; j<2*MOVE_LEN; j++)
14470 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
14471 timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
14472 timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
14473 pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
14474 pvInfoList[firstMove+i-1].depth = 0;
14475 commentList[framePtr+i] = commentList[firstMove+i];
14476 commentList[firstMove+i] = NULL;
14480 forwardMostMove = currentMove; // truncte game so we can start variation
14481 if(storedGames == 1) GreyRevert(FALSE);
14485 PopTail(Boolean annotate)
14488 char buf[8000], moveBuf[20];
14490 if(appData.icsActive) return FALSE; // only in local mode
14491 if(!storedGames) return FALSE; // sanity
14494 ToNrEvent(savedFirst[storedGames]); // sets currentMove
14495 nrMoves = savedLast[storedGames] - currentMove;
14498 if(!WhiteOnMove(currentMove)) sprintf(buf, "(%d...", currentMove+2>>1);
14499 else strcpy(buf, "(");
14500 for(i=currentMove; i<forwardMostMove; i++) {
14502 sprintf(moveBuf, " %d. %s", i+2>>1, SavePart(parseList[i]));
14503 else sprintf(moveBuf, " %s", SavePart(parseList[i]));
14504 strcat(buf, moveBuf);
14505 if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
14509 for(i=1; i<nrMoves; i++) { // copy last variation back
14510 CopyBoard(boards[currentMove+i], boards[framePtr+i]);
14511 for(j=0; j<MOVE_LEN; j++)
14512 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
14513 for(j=0; j<2*MOVE_LEN; j++)
14514 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
14515 timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
14516 timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
14517 pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
14518 if(commentList[currentMove+i]) free(commentList[currentMove+i]);
14519 commentList[currentMove+i] = commentList[framePtr+i];
14520 commentList[framePtr+i] = NULL;
14522 if(annotate) AppendComment(currentMove+1, buf, FALSE);
14523 framePtr = savedFramePtr[storedGames];
14524 gameInfo.result = savedResult[storedGames];
14525 if(gameInfo.resultDetails != NULL) {
14526 free(gameInfo.resultDetails);
14528 gameInfo.resultDetails = savedDetails[storedGames];
14529 forwardMostMove = currentMove + nrMoves;
14530 if(storedGames == 0) GreyRevert(TRUE);
14536 { // remove all shelved variations
14538 for(i=0; i<storedGames; i++) {
14539 if(savedDetails[i])
14540 free(savedDetails[i]);
14541 savedDetails[i] = NULL;
14543 for(i=framePtr; i<MAX_MOVES; i++) {
14544 if(commentList[i]) free(commentList[i]);
14545 commentList[i] = NULL;
14547 framePtr = MAX_MOVES-1;