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;
5477 /* [HGM] <popupFix> in stead of calling FinishMove directly, this
5478 function is made into one that returns an OK move type if FinishMove
5479 should be called. This to give the calling driver routine the
5480 opportunity to finish the userMove input with a promotion popup,
5481 without bothering the user with this for invalid or illegal moves */
5483 /* FinishMove(moveType, fromX, fromY, toX, toY, promoChar); */
5486 /* Common tail of UserMoveEvent and DropMenuEvent */
5488 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
5490 int fromX, fromY, toX, toY;
5491 /*char*/int promoChar;
5495 if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) && promoChar != NULLCHAR) {
5496 // [HGM] superchess: suppress promotions to non-available piece
5497 int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
5498 if(WhiteOnMove(currentMove)) {
5499 if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
5501 if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
5505 /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
5506 move type in caller when we know the move is a legal promotion */
5507 if(moveType == NormalMove && promoChar)
5508 moveType = PromoCharToMoveType(WhiteOnMove(currentMove), promoChar);
5510 /* [HGM] convert drag-and-drop piece drops to standard form */
5511 if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ){
5512 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
5513 if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n",
5514 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
5515 // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
5516 if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
5517 fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
5518 while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++;
5522 /* [HGM] <popupFix> The following if has been moved here from
5523 UserMoveEvent(). Because it seemed to belong here (why not allow
5524 piece drops in training games?), and because it can only be
5525 performed after it is known to what we promote. */
5526 if (gameMode == Training) {
5527 /* compare the move played on the board to the next move in the
5528 * game. If they match, display the move and the opponent's response.
5529 * If they don't match, display an error message.
5533 CopyBoard(testBoard, boards[currentMove]);
5534 ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
5536 if (CompareBoards(testBoard, boards[currentMove+1])) {
5537 ForwardInner(currentMove+1);
5539 /* Autoplay the opponent's response.
5540 * if appData.animate was TRUE when Training mode was entered,
5541 * the response will be animated.
5543 saveAnimate = appData.animate;
5544 appData.animate = animateTraining;
5545 ForwardInner(currentMove+1);
5546 appData.animate = saveAnimate;
5548 /* check for the end of the game */
5549 if (currentMove >= forwardMostMove) {
5550 gameMode = PlayFromGameFile;
5552 SetTrainingModeOff();
5553 DisplayInformation(_("End of game"));
5556 DisplayError(_("Incorrect move"), 0);
5561 /* Ok, now we know that the move is good, so we can kill
5562 the previous line in Analysis Mode */
5563 if ((gameMode == AnalyzeMode || gameMode == EditGame)
5564 && currentMove < forwardMostMove) {
5565 PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
5568 /* If we need the chess program but it's dead, restart it */
5569 ResurrectChessProgram();
5571 /* A user move restarts a paused game*/
5575 thinkOutput[0] = NULLCHAR;
5577 MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
5579 if(Adjudicate(NULL)) return 1; // [HGM] adjudicate: take care of automtic game end
5581 if (gameMode == BeginningOfGame) {
5582 if (appData.noChessProgram) {
5583 gameMode = EditGame;
5587 gameMode = MachinePlaysBlack;
5590 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
5592 if (first.sendName) {
5593 sprintf(buf, "name %s\n", gameInfo.white);
5594 SendToProgram(buf, &first);
5601 /* Relay move to ICS or chess engine */
5602 if (appData.icsActive) {
5603 if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
5604 gameMode == IcsExamining) {
5605 if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
5606 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
5608 SendMoveToICS(moveType, fromX, fromY, toX, toY);
5610 // also send plain move, in case ICS does not understand atomic claims
5611 SendMoveToICS(moveType, fromX, fromY, toX, toY);
5615 if (first.sendTime && (gameMode == BeginningOfGame ||
5616 gameMode == MachinePlaysWhite ||
5617 gameMode == MachinePlaysBlack)) {
5618 SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
5620 if (gameMode != EditGame && gameMode != PlayFromGameFile) {
5621 // [HGM] book: if program might be playing, let it use book
5622 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
5623 first.maybeThinking = TRUE;
5624 } else SendMoveToProgram(forwardMostMove-1, &first);
5625 if (currentMove == cmailOldMove + 1) {
5626 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
5630 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5634 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
5640 if (WhiteOnMove(currentMove)) {
5641 GameEnds(BlackWins, "Black mates", GE_PLAYER);
5643 GameEnds(WhiteWins, "White mates", GE_PLAYER);
5647 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
5652 case MachinePlaysBlack:
5653 case MachinePlaysWhite:
5654 /* disable certain menu options while machine is thinking */
5655 SetMachineThinkingEnables();
5662 userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
5664 if(bookHit) { // [HGM] book: simulate book reply
5665 static char bookMove[MSG_SIZ]; // a bit generous?
5667 programStats.nodes = programStats.depth = programStats.time =
5668 programStats.score = programStats.got_only_move = 0;
5669 sprintf(programStats.movelist, "%s (xbook)", bookHit);
5671 strcpy(bookMove, "move ");
5672 strcat(bookMove, bookHit);
5673 HandleMachineMove(bookMove, &first);
5679 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
5680 int fromX, fromY, toX, toY;
5683 /* [HGM] This routine was added to allow calling of its two logical
5684 parts from other modules in the old way. Before, UserMoveEvent()
5685 automatically called FinishMove() if the move was OK, and returned
5686 otherwise. I separated the two, in order to make it possible to
5687 slip a promotion popup in between. But that it always needs two
5688 calls, to the first part, (now called UserMoveTest() ), and to
5689 FinishMove if the first part succeeded. Calls that do not need
5690 to do anything in between, can call this routine the old way.
5692 ChessMove moveType = UserMoveTest(fromX, fromY, toX, toY, promoChar, FALSE);
5693 if(appData.debugMode) fprintf(debugFP, "moveType 4 = %d, promochar = %x\n", moveType, promoChar);
5694 if(moveType == AmbiguousMove)
5695 DrawPosition(FALSE, boards[currentMove]);
5696 else if(moveType != ImpossibleMove && moveType != Comment)
5697 FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
5701 Mark(board, flags, kind, rf, ff, rt, ft, closure)
5708 typedef char Markers[BOARD_RANKS][BOARD_FILES];
5709 Markers *m = (Markers *) closure;
5710 if(rf == fromY && ff == fromX)
5711 (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
5712 || kind == WhiteCapturesEnPassant
5713 || kind == BlackCapturesEnPassant);
5714 else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
5718 MarkTargetSquares(int clear)
5721 if(!appData.markers || !appData.highlightDragging ||
5722 !appData.testLegality || gameMode == EditPosition) return;
5724 for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) marker[y][x] = 0;
5727 GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker);
5728 if(PosFlags(0) & F_MANDATORY_CAPTURE) {
5729 for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
5731 for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
5734 DrawPosition(TRUE, NULL);
5737 void LeftClick(ClickType clickType, int xPix, int yPix)
5740 Boolean saveAnimate;
5741 static int second = 0, promotionChoice = 0;
5742 char promoChoice = NULLCHAR;
5744 if (clickType == Press) ErrorPopDown();
5745 MarkTargetSquares(1);
5747 x = EventToSquare(xPix, BOARD_WIDTH);
5748 y = EventToSquare(yPix, BOARD_HEIGHT);
5749 if (!flipView && y >= 0) {
5750 y = BOARD_HEIGHT - 1 - y;
5752 if (flipView && x >= 0) {
5753 x = BOARD_WIDTH - 1 - x;
5756 if(promotionChoice) { // we are waiting for a click to indicate promotion piece
5757 if(clickType == Release) return; // ignore upclick of click-click destination
5758 promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
5759 if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
5760 if(gameInfo.holdingsWidth &&
5761 (WhiteOnMove(currentMove)
5762 ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y > 0
5763 : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT-1) ) {
5764 // click in right holdings, for determining promotion piece
5765 ChessSquare p = boards[currentMove][y][x];
5766 if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
5767 if(p != EmptySquare) {
5768 FinishMove(NormalMove, fromX, fromY, toX, toY, ToLower(PieceToChar(p)));
5773 DrawPosition(FALSE, boards[currentMove]);
5777 /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
5778 if(clickType == Press
5779 && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
5780 || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
5781 || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
5785 if (clickType == Press) {
5787 if (OKToStartUserMove(x, y)) {
5791 MarkTargetSquares(0);
5792 DragPieceBegin(xPix, yPix);
5793 if (appData.highlightDragging) {
5794 SetHighlights(x, y, -1, -1);
5802 if (clickType == Press && gameMode != EditPosition) {
5807 // ignore off-board to clicks
5808 if(y < 0 || x < 0) return;
5810 /* Check if clicking again on the same color piece */
5811 fromP = boards[currentMove][fromY][fromX];
5812 toP = boards[currentMove][y][x];
5813 frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom;
5814 if ((WhitePawn <= fromP && fromP <= WhiteKing &&
5815 WhitePawn <= toP && toP <= WhiteKing &&
5816 !(fromP == WhiteKing && toP == WhiteRook && frc) &&
5817 !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
5818 (BlackPawn <= fromP && fromP <= BlackKing &&
5819 BlackPawn <= toP && toP <= BlackKing &&
5820 !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
5821 !(fromP == BlackKing && toP == BlackRook && frc))) {
5822 /* Clicked again on same color piece -- changed his mind */
5823 second = (x == fromX && y == fromY);
5824 if (appData.highlightDragging) {
5825 SetHighlights(x, y, -1, -1);
5829 if (OKToStartUserMove(x, y)) {
5832 MarkTargetSquares(0);
5833 DragPieceBegin(xPix, yPix);
5837 // ignore clicks on holdings
5838 if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
5841 if (clickType == Release && x == fromX && y == fromY) {
5842 DragPieceEnd(xPix, yPix);
5843 if (appData.animateDragging) {
5844 /* Undo animation damage if any */
5845 DrawPosition(FALSE, NULL);
5848 /* Second up/down in same square; just abort move */
5853 ClearPremoveHighlights();
5855 /* First upclick in same square; start click-click mode */
5856 SetHighlights(x, y, -1, -1);
5861 /* we now have a different from- and (possibly off-board) to-square */
5862 /* Completed move */
5865 saveAnimate = appData.animate;
5866 if (clickType == Press) {
5867 /* Finish clickclick move */
5868 if (appData.animate || appData.highlightLastMove) {
5869 SetHighlights(fromX, fromY, toX, toY);
5874 /* Finish drag move */
5875 if (appData.highlightLastMove) {
5876 SetHighlights(fromX, fromY, toX, toY);
5880 DragPieceEnd(xPix, yPix);
5881 /* Don't animate move and drag both */
5882 appData.animate = FALSE;
5885 // moves into holding are invalid for now (except in EditPosition, adapting to-square)
5886 if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
5887 ChessSquare piece = boards[currentMove][fromY][fromX];
5888 if(gameMode == EditPosition && piece != EmptySquare &&
5889 fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
5892 if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
5893 n = PieceToNumber(piece - (int)BlackPawn);
5894 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
5895 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
5896 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
5898 if(x == BOARD_RGHT+1 && piece < BlackPawn) {
5899 n = PieceToNumber(piece);
5900 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
5901 boards[currentMove][n][BOARD_WIDTH-1] = piece;
5902 boards[currentMove][n][BOARD_WIDTH-2]++;
5904 boards[currentMove][fromY][fromX] = EmptySquare;
5908 DrawPosition(TRUE, boards[currentMove]);
5912 // off-board moves should not be highlighted
5913 if(x < 0 || x < 0) ClearHighlights();
5915 if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice)) {
5916 SetHighlights(fromX, fromY, toX, toY);
5917 if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
5918 // [HGM] super: promotion to captured piece selected from holdings
5919 ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
5920 promotionChoice = TRUE;
5921 // kludge follows to temporarily execute move on display, without promoting yet
5922 boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
5923 boards[currentMove][toY][toX] = p;
5924 DrawPosition(FALSE, boards[currentMove]);
5925 boards[currentMove][fromY][fromX] = p; // take back, but display stays
5926 boards[currentMove][toY][toX] = q;
5927 DisplayMessage("Click in holdings to choose piece", "");
5932 UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
5933 if (!appData.highlightLastMove || gotPremove) ClearHighlights();
5934 if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
5937 appData.animate = saveAnimate;
5938 if (appData.animate || appData.animateDragging) {
5939 /* Undo animation damage if needed */
5940 DrawPosition(FALSE, NULL);
5944 int RightClick(ClickType action, int x, int y, int *fromX, int *fromY)
5945 { // front-end-free part taken out of PieceMenuPopup
5946 int whichMenu; int xSqr, ySqr;
5948 xSqr = EventToSquare(x, BOARD_WIDTH);
5949 ySqr = EventToSquare(y, BOARD_HEIGHT);
5950 if (action == Release) UnLoadPV(); // [HGM] pv
5951 if (action != Press) return -2;
5954 if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;
\r
5956 if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;
\r
5957 if (xSqr < 0 || ySqr < 0) return -1;
\r
5958 whichMenu = 0; // edit-position menu
5961 if(!appData.icsEngineAnalyze) return -1;
5962 case IcsPlayingWhite:
5963 case IcsPlayingBlack:
5964 if(!appData.zippyPlay) goto noZip;
5967 case MachinePlaysWhite:
5968 case MachinePlaysBlack:
5969 case TwoMachinesPlay: // [HGM] pv: use for showing PV
5970 if (!appData.dropMenu) {
5972 return 2; // flag front-end to grab mouse events
5974 if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
5975 gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
5978 if (xSqr < 0 || ySqr < 0) return -1;
5979 if (!appData.dropMenu || appData.testLegality &&
5980 gameInfo.variant != VariantBughouse &&
5981 gameInfo.variant != VariantCrazyhouse) return -1;
5982 whichMenu = 1; // drop menu
5988 if (((*fromX = xSqr) < 0) ||
5989 ((*fromY = ySqr) < 0)) {
5990 *fromX = *fromY = -1;
5994 *fromX = BOARD_WIDTH - 1 - *fromX;
5996 *fromY = BOARD_HEIGHT - 1 - *fromY;
6001 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
6003 // char * hint = lastHint;
6004 FrontEndProgramStats stats;
6006 stats.which = cps == &first ? 0 : 1;
6007 stats.depth = cpstats->depth;
6008 stats.nodes = cpstats->nodes;
6009 stats.score = cpstats->score;
6010 stats.time = cpstats->time;
6011 stats.pv = cpstats->movelist;
6012 stats.hint = lastHint;
6013 stats.an_move_index = 0;
6014 stats.an_move_count = 0;
6016 if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
6017 stats.hint = cpstats->move_name;
6018 stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
6019 stats.an_move_count = cpstats->nr_moves;
6022 if(stats.pv && stats.pv[0]) strcpy(lastPV[stats.which], stats.pv); // [HGM] pv: remember last PV of each
6024 SetProgramStats( &stats );
6028 Adjudicate(ChessProgramState *cps)
6029 { // [HGM] some adjudications useful with buggy engines
6030 // [HGM] adjudicate: made into separate routine, which now can be called after every move
6031 // In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
6032 // Actually ending the game is now based on the additional internal condition canAdjudicate.
6033 // Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
6034 int k, count = 0; static int bare = 1;
6035 ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
6036 Boolean canAdjudicate = !appData.icsActive;
6038 // most tests only when we understand the game, i.e. legality-checking on, and (for the time being) no piece drops
6039 if(gameInfo.holdingsSize == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
6040 if( appData.testLegality )
6041 { /* [HGM] Some more adjudications for obstinate engines */
6042 int NrWN=0, NrBN=0, NrWB=0, NrBB=0, NrWR=0, NrBR=0,
6043 NrWQ=0, NrBQ=0, NrW=0, NrK=0, bishopsColor = 0,
6044 NrPieces=0, NrPawns=0, PawnAdvance=0, i, j;
6045 static int moveCount = 6;
6047 char *reason = NULL;
6049 /* Count what is on board. */
6050 for(i=0; i<BOARD_HEIGHT; i++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
6051 { ChessSquare p = boards[forwardMostMove][i][j];
6055 { /* count B,N,R and other of each side */
6058 NrK++; break; // [HGM] atomic: count Kings
6062 case WhiteFerz: // [HGM] shatranj: kludge to mke it work in shatranj
6063 bishopsColor |= 1 << ((i^j)&1);
6068 case BlackFerz: // [HGM] shatranj: kludge to mke it work in shatranj
6069 bishopsColor |= 1 << ((i^j)&1);
6084 PawnAdvance += m; NrPawns++;
6086 NrPieces += (p != EmptySquare);
6087 NrW += ((int)p < (int)BlackPawn);
6088 if(gameInfo.variant == VariantXiangqi &&
6089 (p == WhiteFerz || p == WhiteAlfil || p == BlackFerz || p == BlackAlfil)) {
6090 NrPieces--; // [HGM] XQ: do not count purely defensive pieces
6091 NrW -= ((int)p < (int)BlackPawn);
6095 /* Some material-based adjudications that have to be made before stalemate test */
6096 if(gameInfo.variant == VariantAtomic && NrK < 2) {
6097 // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
6098 boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
6099 if(canAdjudicate && appData.checkMates) {
6101 SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
6102 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6103 GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
6104 "Xboard adjudication: King destroyed", GE_XBOARD );
6109 /* Bare King in Shatranj (loses) or Losers (wins) */
6110 if( NrW == 1 || NrPieces - NrW == 1) {
6111 if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
6112 boards[forwardMostMove][EP_STATUS] = EP_WINS; // mark as win, so it becomes claimable
6113 if(canAdjudicate && appData.checkMates) {
6115 SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
6116 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6117 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
6118 "Xboard adjudication: Bare king", GE_XBOARD );
6122 if( gameInfo.variant == VariantShatranj && --bare < 0)
6124 boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
6125 if(canAdjudicate && appData.checkMates) {
6126 /* but only adjudicate if adjudication enabled */
6128 SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
6129 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6130 GameEnds( NrW > 1 ? WhiteWins : NrPieces - NrW > 1 ? BlackWins : GameIsDrawn,
6131 "Xboard adjudication: Bare king", GE_XBOARD );
6138 // don't wait for engine to announce game end if we can judge ourselves
6139 switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
6141 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
6142 int i, checkCnt = 0; // (should really be done by making nr of checks part of game state)
6143 for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
6144 if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
6147 reason = "Xboard adjudication: 3rd check";
6148 boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
6158 reason = "Xboard adjudication: Stalemate";
6159 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
6160 boards[forwardMostMove][EP_STATUS] = EP_STALEMATE; // default result for stalemate is draw
6161 if(gameInfo.variant == VariantLosers || gameInfo.variant == VariantGiveaway) // [HGM] losers:
6162 boards[forwardMostMove][EP_STATUS] = EP_WINS; // in these variants stalemated is always a win
6163 else if(gameInfo.variant == VariantSuicide) // in suicide it depends
6164 boards[forwardMostMove][EP_STATUS] = NrW == NrPieces-NrW ? EP_STALEMATE :
6165 ((NrW < NrPieces-NrW) != WhiteOnMove(forwardMostMove) ?
6166 EP_CHECKMATE : EP_WINS);
6167 else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
6168 boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
6172 reason = "Xboard adjudication: Checkmate";
6173 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
6177 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
6179 result = GameIsDrawn; break;
6181 result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
6183 result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
6185 result = (ChessMove) 0;
6187 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
6189 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6190 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6191 GameEnds( result, reason, GE_XBOARD );
6195 /* Next absolutely insufficient mating material. */
6196 if( NrPieces == 2 || gameInfo.variant != VariantXiangqi &&
6197 gameInfo.variant != VariantShatranj && // [HGM] baring will remain possible
6198 (NrPieces == 3 && NrWN+NrBN+NrWB+NrBB == 1 ||
6199 NrPieces == NrBB+NrWB+2 && bishopsColor != 3)) // [HGM] all Bishops (Ferz!) same color
6200 { /* KBK, KNK, KK of KBKB with like Bishops */
6202 /* always flag draws, for judging claims */
6203 boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
6205 if(canAdjudicate && appData.materialDraws) {
6206 /* but only adjudicate them if adjudication enabled */
6207 if(engineOpponent) {
6208 SendToProgram("force\n", engineOpponent); // suppress reply
6209 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
6211 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6212 GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
6217 /* Then some trivial draws (only adjudicate, cannot be claimed) */
6219 ( NrWR == 1 && NrBR == 1 /* KRKR */
6220 || NrWQ==1 && NrBQ==1 /* KQKQ */
6221 || NrWN==2 || NrBN==2 /* KNNK */
6222 || NrWN+NrWB == 1 && NrBN+NrBB == 1 /* KBKN, KBKB, KNKN */
6224 if(canAdjudicate && --moveCount < 0 && appData.trivialDraws)
6225 { /* if the first 3 moves do not show a tactical win, declare draw */
6226 if(engineOpponent) {
6227 SendToProgram("force\n", engineOpponent); // suppress reply
6228 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6230 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6231 GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
6234 } else moveCount = 6;
6238 if (appData.debugMode) { int i;
6239 fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
6240 forwardMostMove, backwardMostMove, boards[backwardMostMove][EP_STATUS],
6241 appData.drawRepeats);
6242 for( i=forwardMostMove; i>=backwardMostMove; i-- )
6243 fprintf(debugFP, "%d ep=%d\n", i, (signed char)boards[i][EP_STATUS]);
6247 // Repetition draws and 50-move rule can be applied independently of legality testing
6249 /* Check for rep-draws */
6251 for(k = forwardMostMove-2;
6252 k>=backwardMostMove && k>=forwardMostMove-100 &&
6253 (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
6254 (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE;
6257 if(CompareBoards(boards[k], boards[forwardMostMove])) {
6258 /* compare castling rights */
6259 if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
6260 (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
6261 rights++; /* King lost rights, while rook still had them */
6262 if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
6263 if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
6264 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
6265 rights++; /* but at least one rook lost them */
6267 if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
6268 (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
6270 if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
6271 if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
6272 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
6275 if( canAdjudicate && rights == 0 && ++count > appData.drawRepeats-2
6276 && appData.drawRepeats > 1) {
6277 /* adjudicate after user-specified nr of repeats */
6278 if(engineOpponent) {
6279 SendToProgram("force\n", engineOpponent); // suppress reply
6280 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6282 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6283 if(gameInfo.variant == VariantXiangqi && appData.testLegality) {
6284 // [HGM] xiangqi: check for forbidden perpetuals
6285 int m, ourPerpetual = 1, hisPerpetual = 1;
6286 for(m=forwardMostMove; m>k; m-=2) {
6287 if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
6288 ourPerpetual = 0; // the current mover did not always check
6289 if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
6290 hisPerpetual = 0; // the opponent did not always check
6292 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
6293 ourPerpetual, hisPerpetual);
6294 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
6295 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
6296 "Xboard adjudication: perpetual checking", GE_XBOARD );
6299 if(hisPerpetual && !ourPerpetual) // he is checking us, but did not repeat yet
6300 break; // (or we would have caught him before). Abort repetition-checking loop.
6301 // Now check for perpetual chases
6302 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
6303 hisPerpetual = PerpetualChase(k, forwardMostMove);
6304 ourPerpetual = PerpetualChase(k+1, forwardMostMove);
6305 if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
6306 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
6307 "Xboard adjudication: perpetual chasing", GE_XBOARD );
6310 if(hisPerpetual && !ourPerpetual) // he is chasing us, but did not repeat yet
6311 break; // Abort repetition-checking loop.
6313 // if neither of us is checking or chasing all the time, or both are, it is draw
6315 GameEnds( GameIsDrawn, "Xboard adjudication: repetition draw", GE_XBOARD );
6318 if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
6319 boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
6323 /* Now we test for 50-move draws. Determine ply count */
6324 count = forwardMostMove;
6325 /* look for last irreversble move */
6326 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
6328 /* if we hit starting position, add initial plies */
6329 if( count == backwardMostMove )
6330 count -= initialRulePlies;
6331 count = forwardMostMove - count;
6333 boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
6334 /* this is used to judge if draw claims are legal */
6335 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
6336 if(engineOpponent) {
6337 SendToProgram("force\n", engineOpponent); // suppress reply
6338 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6340 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6341 GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
6345 /* if draw offer is pending, treat it as a draw claim
6346 * when draw condition present, to allow engines a way to
6347 * claim draws before making their move to avoid a race
6348 * condition occurring after their move
6350 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
6352 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
6353 p = "Draw claim: 50-move rule";
6354 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
6355 p = "Draw claim: 3-fold repetition";
6356 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
6357 p = "Draw claim: insufficient mating material";
6358 if( p != NULL && canAdjudicate) {
6359 if(engineOpponent) {
6360 SendToProgram("force\n", engineOpponent); // suppress reply
6361 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6363 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6364 GameEnds( GameIsDrawn, p, GE_XBOARD );
6369 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
6370 if(engineOpponent) {
6371 SendToProgram("force\n", engineOpponent); // suppress reply
6372 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6374 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6375 GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
6381 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
6382 { // [HGM] book: this routine intercepts moves to simulate book replies
6383 char *bookHit = NULL;
6385 //first determine if the incoming move brings opponent into his book
6386 if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
6387 bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
6388 if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
6389 if(bookHit != NULL && !cps->bookSuspend) {
6390 // make sure opponent is not going to reply after receiving move to book position
6391 SendToProgram("force\n", cps);
6392 cps->bookSuspend = TRUE; // flag indicating it has to be restarted
6394 if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
6395 // now arrange restart after book miss
6397 // after a book hit we never send 'go', and the code after the call to this routine
6398 // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
6400 if (cps->useUsermove) sprintf(buf, "usermove "); // sorry, no SAN yet :(
6401 sprintf(buf, "%s\n", bookHit); // force book move into program supposed to play it
6402 SendToProgram(buf, cps);
6403 if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
6404 } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
6405 SendToProgram("go\n", cps);
6406 cps->bookSuspend = FALSE; // after a 'go' we are never suspended
6407 } else { // 'go' might be sent based on 'firstMove' after this routine returns
6408 if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
6409 SendToProgram("go\n", cps);
6410 cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
6412 return bookHit; // notify caller of hit, so it can take action to send move to opponent
6416 ChessProgramState *savedState;
6417 void DeferredBookMove(void)
6419 if(savedState->lastPing != savedState->lastPong)
6420 ScheduleDelayedEvent(DeferredBookMove, 10);
6422 HandleMachineMove(savedMessage, savedState);
6426 HandleMachineMove(message, cps)
6428 ChessProgramState *cps;
6430 char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
6431 char realname[MSG_SIZ];
6432 int fromX, fromY, toX, toY;
6441 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
6443 * Kludge to ignore BEL characters
6445 while (*message == '\007') message++;
6448 * [HGM] engine debug message: ignore lines starting with '#' character
6450 if(cps->debug && *message == '#') return;
6453 * Look for book output
6455 if (cps == &first && bookRequested) {
6456 if (message[0] == '\t' || message[0] == ' ') {
6457 /* Part of the book output is here; append it */
6458 strcat(bookOutput, message);
6459 strcat(bookOutput, " \n");
6461 } else if (bookOutput[0] != NULLCHAR) {
6462 /* All of book output has arrived; display it */
6463 char *p = bookOutput;
6464 while (*p != NULLCHAR) {
6465 if (*p == '\t') *p = ' ';
6468 DisplayInformation(bookOutput);
6469 bookRequested = FALSE;
6470 /* Fall through to parse the current output */
6475 * Look for machine move.
6477 if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
6478 (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
6480 /* This method is only useful on engines that support ping */
6481 if (cps->lastPing != cps->lastPong) {
6482 if (gameMode == BeginningOfGame) {
6483 /* Extra move from before last new; ignore */
6484 if (appData.debugMode) {
6485 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
6488 if (appData.debugMode) {
6489 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
6490 cps->which, gameMode);
6493 SendToProgram("undo\n", cps);
6499 case BeginningOfGame:
6500 /* Extra move from before last reset; ignore */
6501 if (appData.debugMode) {
6502 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
6509 /* Extra move after we tried to stop. The mode test is
6510 not a reliable way of detecting this problem, but it's
6511 the best we can do on engines that don't support ping.
6513 if (appData.debugMode) {
6514 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
6515 cps->which, gameMode);
6517 SendToProgram("undo\n", cps);
6520 case MachinePlaysWhite:
6521 case IcsPlayingWhite:
6522 machineWhite = TRUE;
6525 case MachinePlaysBlack:
6526 case IcsPlayingBlack:
6527 machineWhite = FALSE;
6530 case TwoMachinesPlay:
6531 machineWhite = (cps->twoMachinesColor[0] == 'w');
6534 if (WhiteOnMove(forwardMostMove) != machineWhite) {
6535 if (appData.debugMode) {
6537 "Ignoring move out of turn by %s, gameMode %d"
6538 ", forwardMost %d\n",
6539 cps->which, gameMode, forwardMostMove);
6544 if (appData.debugMode) { int f = forwardMostMove;
6545 fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
6546 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
6547 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
6549 if(cps->alphaRank) AlphaRank(machineMove, 4);
6550 if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
6551 &fromX, &fromY, &toX, &toY, &promoChar)) {
6552 /* Machine move could not be parsed; ignore it. */
6553 sprintf(buf1, _("Illegal move \"%s\" from %s machine"),
6554 machineMove, cps->which);
6555 DisplayError(buf1, 0);
6556 sprintf(buf1, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
6557 machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
6558 if (gameMode == TwoMachinesPlay) {
6559 GameEnds(machineWhite ? BlackWins : WhiteWins,
6565 /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
6566 /* So we have to redo legality test with true e.p. status here, */
6567 /* to make sure an illegal e.p. capture does not slip through, */
6568 /* to cause a forfeit on a justified illegal-move complaint */
6569 /* of the opponent. */
6570 if( gameMode==TwoMachinesPlay && appData.testLegality
6571 && fromY != DROP_RANK /* [HGM] temporary; should still add legality test for drops */
6574 moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
6575 fromY, fromX, toY, toX, promoChar);
6576 if (appData.debugMode) {
6578 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
6579 boards[forwardMostMove][CASTLING][i], castlingRank[i]);
6580 fprintf(debugFP, "castling rights\n");
6582 if(moveType == IllegalMove) {
6583 sprintf(buf1, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
6584 machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
6585 GameEnds(machineWhite ? BlackWins : WhiteWins,
6588 } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
6589 /* [HGM] Kludge to handle engines that send FRC-style castling
6590 when they shouldn't (like TSCP-Gothic) */
6592 case WhiteASideCastleFR:
6593 case BlackASideCastleFR:
6595 currentMoveString[2]++;
6597 case WhiteHSideCastleFR:
6598 case BlackHSideCastleFR:
6600 currentMoveString[2]--;
6602 default: ; // nothing to do, but suppresses warning of pedantic compilers
6605 hintRequested = FALSE;
6606 lastHint[0] = NULLCHAR;
6607 bookRequested = FALSE;
6608 /* Program may be pondering now */
6609 cps->maybeThinking = TRUE;
6610 if (cps->sendTime == 2) cps->sendTime = 1;
6611 if (cps->offeredDraw) cps->offeredDraw--;
6613 /* currentMoveString is set as a side-effect of ParseOneMove */
6614 strcpy(machineMove, currentMoveString);
6615 strcat(machineMove, "\n");
6616 strcpy(moveList[forwardMostMove], machineMove);
6618 /* [AS] Save move info and clear stats for next move */
6619 pvInfoList[ forwardMostMove ].score = programStats.score;
6620 pvInfoList[ forwardMostMove ].depth = programStats.depth;
6621 pvInfoList[ forwardMostMove ].time = programStats.time; // [HGM] PGNtime: take time from engine stats
6622 ClearProgramStats();
6623 thinkOutput[0] = NULLCHAR;
6624 hiddenThinkOutputState = 0;
6626 MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
6628 /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
6629 if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
6632 while( count < adjudicateLossPlies ) {
6633 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
6636 score = -score; /* Flip score for winning side */
6639 if( score > adjudicateLossThreshold ) {
6646 if( count >= adjudicateLossPlies ) {
6647 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6649 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
6650 "Xboard adjudication",
6657 if(Adjudicate(cps)) return; // [HGM] adjudicate: for all automatic game ends
6660 if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
6662 if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
6663 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
6665 SendMoveToICS(moveType, fromX, fromY, toX, toY);
6667 SendMoveToICS(moveType, fromX, fromY, toX, toY);
6669 if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
6670 char buf[3*MSG_SIZ];
6672 sprintf(buf, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
6673 programStats.score / 100.,
6675 programStats.time / 100.,
6676 (unsigned int)programStats.nodes,
6677 (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
6678 programStats.movelist);
6680 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
6686 if (gameMode == TwoMachinesPlay) {
6687 /* [HGM] relaying draw offers moved to after reception of move */
6688 /* and interpreting offer as claim if it brings draw condition */
6689 if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
6690 SendToProgram("draw\n", cps->other);
6692 if (cps->other->sendTime) {
6693 SendTimeRemaining(cps->other,
6694 cps->other->twoMachinesColor[0] == 'w');
6696 bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
6697 if (firstMove && !bookHit) {
6699 if (cps->other->useColors) {
6700 SendToProgram(cps->other->twoMachinesColor, cps->other);
6702 SendToProgram("go\n", cps->other);
6704 cps->other->maybeThinking = TRUE;
6707 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6709 if (!pausing && appData.ringBellAfterMoves) {
6714 * Reenable menu items that were disabled while
6715 * machine was thinking
6717 if (gameMode != TwoMachinesPlay)
6718 SetUserThinkingEnables();
6720 // [HGM] book: after book hit opponent has received move and is now in force mode
6721 // force the book reply into it, and then fake that it outputted this move by jumping
6722 // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
6724 static char bookMove[MSG_SIZ]; // a bit generous?
6726 strcpy(bookMove, "move ");
6727 strcat(bookMove, bookHit);
6730 programStats.nodes = programStats.depth = programStats.time =
6731 programStats.score = programStats.got_only_move = 0;
6732 sprintf(programStats.movelist, "%s (xbook)", bookHit);
6734 if(cps->lastPing != cps->lastPong) {
6735 savedMessage = message; // args for deferred call
6737 ScheduleDelayedEvent(DeferredBookMove, 10);
6746 /* Set special modes for chess engines. Later something general
6747 * could be added here; for now there is just one kludge feature,
6748 * needed because Crafty 15.10 and earlier don't ignore SIGINT
6749 * when "xboard" is given as an interactive command.
6751 if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
6752 cps->useSigint = FALSE;
6753 cps->useSigterm = FALSE;
6755 if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
6756 ParseFeatures(message+8, cps);
6757 return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
6760 /* [HGM] Allow engine to set up a position. Don't ask me why one would
6761 * want this, I was asked to put it in, and obliged.
6763 if (!strncmp(message, "setboard ", 9)) {
6764 Board initial_position;
6766 GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
6768 if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
6769 DisplayError(_("Bad FEN received from engine"), 0);
6773 CopyBoard(boards[0], initial_position);
6774 initialRulePlies = FENrulePlies;
6775 if(blackPlaysFirst) gameMode = MachinePlaysWhite;
6776 else gameMode = MachinePlaysBlack;
6777 DrawPosition(FALSE, boards[currentMove]);
6783 * Look for communication commands
6785 if (!strncmp(message, "telluser ", 9)) {
6786 DisplayNote(message + 9);
6789 if (!strncmp(message, "tellusererror ", 14)) {
6791 DisplayError(message + 14, 0);
6794 if (!strncmp(message, "tellopponent ", 13)) {
6795 if (appData.icsActive) {
6797 snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
6801 DisplayNote(message + 13);
6805 if (!strncmp(message, "tellothers ", 11)) {
6806 if (appData.icsActive) {
6808 snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
6814 if (!strncmp(message, "tellall ", 8)) {
6815 if (appData.icsActive) {
6817 snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
6821 DisplayNote(message + 8);
6825 if (strncmp(message, "warning", 7) == 0) {
6826 /* Undocumented feature, use tellusererror in new code */
6827 DisplayError(message, 0);
6830 if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
6831 strcpy(realname, cps->tidy);
6832 strcat(realname, " query");
6833 AskQuestion(realname, buf2, buf1, cps->pr);
6836 /* Commands from the engine directly to ICS. We don't allow these to be
6837 * sent until we are logged on. Crafty kibitzes have been known to
6838 * interfere with the login process.
6841 if (!strncmp(message, "tellics ", 8)) {
6842 SendToICS(message + 8);
6846 if (!strncmp(message, "tellicsnoalias ", 15)) {
6847 SendToICS(ics_prefix);
6848 SendToICS(message + 15);
6852 /* The following are for backward compatibility only */
6853 if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
6854 !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
6855 SendToICS(ics_prefix);
6861 if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
6865 * If the move is illegal, cancel it and redraw the board.
6866 * Also deal with other error cases. Matching is rather loose
6867 * here to accommodate engines written before the spec.
6869 if (strncmp(message + 1, "llegal move", 11) == 0 ||
6870 strncmp(message, "Error", 5) == 0) {
6871 if (StrStr(message, "name") ||
6872 StrStr(message, "rating") || StrStr(message, "?") ||
6873 StrStr(message, "result") || StrStr(message, "board") ||
6874 StrStr(message, "bk") || StrStr(message, "computer") ||
6875 StrStr(message, "variant") || StrStr(message, "hint") ||
6876 StrStr(message, "random") || StrStr(message, "depth") ||
6877 StrStr(message, "accepted")) {
6880 if (StrStr(message, "protover")) {
6881 /* Program is responding to input, so it's apparently done
6882 initializing, and this error message indicates it is
6883 protocol version 1. So we don't need to wait any longer
6884 for it to initialize and send feature commands. */
6885 FeatureDone(cps, 1);
6886 cps->protocolVersion = 1;
6889 cps->maybeThinking = FALSE;
6891 if (StrStr(message, "draw")) {
6892 /* Program doesn't have "draw" command */
6893 cps->sendDrawOffers = 0;
6896 if (cps->sendTime != 1 &&
6897 (StrStr(message, "time") || StrStr(message, "otim"))) {
6898 /* Program apparently doesn't have "time" or "otim" command */
6902 if (StrStr(message, "analyze")) {
6903 cps->analysisSupport = FALSE;
6904 cps->analyzing = FALSE;
6906 sprintf(buf2, _("%s does not support analysis"), cps->tidy);
6907 DisplayError(buf2, 0);
6910 if (StrStr(message, "(no matching move)st")) {
6911 /* Special kludge for GNU Chess 4 only */
6912 cps->stKludge = TRUE;
6913 SendTimeControl(cps, movesPerSession, timeControl,
6914 timeIncrement, appData.searchDepth,
6918 if (StrStr(message, "(no matching move)sd")) {
6919 /* Special kludge for GNU Chess 4 only */
6920 cps->sdKludge = TRUE;
6921 SendTimeControl(cps, movesPerSession, timeControl,
6922 timeIncrement, appData.searchDepth,
6926 if (!StrStr(message, "llegal")) {
6929 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
6930 gameMode == IcsIdle) return;
6931 if (forwardMostMove <= backwardMostMove) return;
6932 if (pausing) PauseEvent();
6933 if(appData.forceIllegal) {
6934 // [HGM] illegal: machine refused move; force position after move into it
6935 SendToProgram("force\n", cps);
6936 if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
6937 // we have a real problem now, as SendBoard will use the a2a3 kludge
6938 // when black is to move, while there might be nothing on a2 or black
6939 // might already have the move. So send the board as if white has the move.
6940 // But first we must change the stm of the engine, as it refused the last move
6941 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
6942 if(WhiteOnMove(forwardMostMove)) {
6943 SendToProgram("a7a6\n", cps); // for the engine black still had the move
6944 SendBoard(cps, forwardMostMove); // kludgeless board
6946 SendToProgram("a2a3\n", cps); // for the engine white still had the move
6947 CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
6948 SendBoard(cps, forwardMostMove+1); // kludgeless board
6950 } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
6951 if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
6952 gameMode == TwoMachinesPlay)
6953 SendToProgram("go\n", cps);
6956 if (gameMode == PlayFromGameFile) {
6957 /* Stop reading this game file */
6958 gameMode = EditGame;
6961 currentMove = --forwardMostMove;
6962 DisplayMove(currentMove-1); /* before DisplayMoveError */
6964 DisplayBothClocks();
6965 sprintf(buf1, _("Illegal move \"%s\" (rejected by %s chess program)"),
6966 parseList[currentMove], cps->which);
6967 DisplayMoveError(buf1);
6968 DrawPosition(FALSE, boards[currentMove]);
6970 /* [HGM] illegal-move claim should forfeit game when Xboard */
6971 /* only passes fully legal moves */
6972 if( appData.testLegality && gameMode == TwoMachinesPlay ) {
6973 GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
6974 "False illegal-move claim", GE_XBOARD );
6978 if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
6979 /* Program has a broken "time" command that
6980 outputs a string not ending in newline.
6986 * If chess program startup fails, exit with an error message.
6987 * Attempts to recover here are futile.
6989 if ((StrStr(message, "unknown host") != NULL)
6990 || (StrStr(message, "No remote directory") != NULL)
6991 || (StrStr(message, "not found") != NULL)
6992 || (StrStr(message, "No such file") != NULL)
6993 || (StrStr(message, "can't alloc") != NULL)
6994 || (StrStr(message, "Permission denied") != NULL)) {
6996 cps->maybeThinking = FALSE;
6997 snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
6998 cps->which, cps->program, cps->host, message);
6999 RemoveInputSource(cps->isr);
7000 DisplayFatalError(buf1, 0, 1);
7005 * Look for hint output
7007 if (sscanf(message, "Hint: %s", buf1) == 1) {
7008 if (cps == &first && hintRequested) {
7009 hintRequested = FALSE;
7010 if (ParseOneMove(buf1, forwardMostMove, &moveType,
7011 &fromX, &fromY, &toX, &toY, &promoChar)) {
7012 (void) CoordsToAlgebraic(boards[forwardMostMove],
7013 PosFlags(forwardMostMove),
7014 fromY, fromX, toY, toX, promoChar, buf1);
7015 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
7016 DisplayInformation(buf2);
7018 /* Hint move could not be parsed!? */
7019 snprintf(buf2, sizeof(buf2),
7020 _("Illegal hint move \"%s\"\nfrom %s chess program"),
7022 DisplayError(buf2, 0);
7025 strcpy(lastHint, buf1);
7031 * Ignore other messages if game is not in progress
7033 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
7034 gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
7037 * look for win, lose, draw, or draw offer
7039 if (strncmp(message, "1-0", 3) == 0) {
7040 char *p, *q, *r = "";
7041 p = strchr(message, '{');
7049 GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
7051 } else if (strncmp(message, "0-1", 3) == 0) {
7052 char *p, *q, *r = "";
7053 p = strchr(message, '{');
7061 /* Kludge for Arasan 4.1 bug */
7062 if (strcmp(r, "Black resigns") == 0) {
7063 GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
7066 GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
7068 } else if (strncmp(message, "1/2", 3) == 0) {
7069 char *p, *q, *r = "";
7070 p = strchr(message, '{');
7079 GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
7082 } else if (strncmp(message, "White resign", 12) == 0) {
7083 GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
7085 } else if (strncmp(message, "Black resign", 12) == 0) {
7086 GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
7088 } else if (strncmp(message, "White matches", 13) == 0 ||
7089 strncmp(message, "Black matches", 13) == 0 ) {
7090 /* [HGM] ignore GNUShogi noises */
7092 } else if (strncmp(message, "White", 5) == 0 &&
7093 message[5] != '(' &&
7094 StrStr(message, "Black") == NULL) {
7095 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7097 } else if (strncmp(message, "Black", 5) == 0 &&
7098 message[5] != '(') {
7099 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7101 } else if (strcmp(message, "resign") == 0 ||
7102 strcmp(message, "computer resigns") == 0) {
7104 case MachinePlaysBlack:
7105 case IcsPlayingBlack:
7106 GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
7108 case MachinePlaysWhite:
7109 case IcsPlayingWhite:
7110 GameEnds(BlackWins, "White resigns", GE_ENGINE);
7112 case TwoMachinesPlay:
7113 if (cps->twoMachinesColor[0] == 'w')
7114 GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
7116 GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
7123 } else if (strncmp(message, "opponent mates", 14) == 0) {
7125 case MachinePlaysBlack:
7126 case IcsPlayingBlack:
7127 GameEnds(WhiteWins, "White mates", GE_ENGINE);
7129 case MachinePlaysWhite:
7130 case IcsPlayingWhite:
7131 GameEnds(BlackWins, "Black mates", GE_ENGINE);
7133 case TwoMachinesPlay:
7134 if (cps->twoMachinesColor[0] == 'w')
7135 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7137 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7144 } else if (strncmp(message, "computer mates", 14) == 0) {
7146 case MachinePlaysBlack:
7147 case IcsPlayingBlack:
7148 GameEnds(BlackWins, "Black mates", GE_ENGINE1);
7150 case MachinePlaysWhite:
7151 case IcsPlayingWhite:
7152 GameEnds(WhiteWins, "White mates", GE_ENGINE);
7154 case TwoMachinesPlay:
7155 if (cps->twoMachinesColor[0] == 'w')
7156 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7158 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7165 } else if (strncmp(message, "checkmate", 9) == 0) {
7166 if (WhiteOnMove(forwardMostMove)) {
7167 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7169 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7172 } else if (strstr(message, "Draw") != NULL ||
7173 strstr(message, "game is a draw") != NULL) {
7174 GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
7176 } else if (strstr(message, "offer") != NULL &&
7177 strstr(message, "draw") != NULL) {
7179 if (appData.zippyPlay && first.initDone) {
7180 /* Relay offer to ICS */
7181 SendToICS(ics_prefix);
7182 SendToICS("draw\n");
7185 cps->offeredDraw = 2; /* valid until this engine moves twice */
7186 if (gameMode == TwoMachinesPlay) {
7187 if (cps->other->offeredDraw) {
7188 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
7189 /* [HGM] in two-machine mode we delay relaying draw offer */
7190 /* until after we also have move, to see if it is really claim */
7192 } else if (gameMode == MachinePlaysWhite ||
7193 gameMode == MachinePlaysBlack) {
7194 if (userOfferedDraw) {
7195 DisplayInformation(_("Machine accepts your draw offer"));
7196 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
7198 DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
7205 * Look for thinking output
7207 if ( appData.showThinking // [HGM] thinking: test all options that cause this output
7208 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
7210 int plylev, mvleft, mvtot, curscore, time;
7211 char mvname[MOVE_LEN];
7215 int prefixHint = FALSE;
7216 mvname[0] = NULLCHAR;
7219 case MachinePlaysBlack:
7220 case IcsPlayingBlack:
7221 if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
7223 case MachinePlaysWhite:
7224 case IcsPlayingWhite:
7225 if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
7230 case IcsObserving: /* [DM] icsEngineAnalyze */
7231 if (!appData.icsEngineAnalyze) ignore = TRUE;
7233 case TwoMachinesPlay:
7234 if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
7245 if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
7246 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
7248 if (plyext != ' ' && plyext != '\t') {
7252 /* [AS] Negate score if machine is playing black and reporting absolute scores */
7253 if( cps->scoreIsAbsolute &&
7254 ( gameMode == MachinePlaysBlack ||
7255 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
7256 gameMode == IcsPlayingBlack || // [HGM] also add other situations where engine should report black POV
7257 (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
7258 !WhiteOnMove(currentMove)
7261 curscore = -curscore;
7265 programStats.depth = plylev;
7266 programStats.nodes = nodes;
7267 programStats.time = time;
7268 programStats.score = curscore;
7269 programStats.got_only_move = 0;
7271 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
7274 if(cps->nps == 0) ticklen = 10*time; // use engine reported time
7275 else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
7276 if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
7277 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
7278 whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
7279 if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
7280 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
7281 blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
7284 /* Buffer overflow protection */
7285 if (buf1[0] != NULLCHAR) {
7286 if (strlen(buf1) >= sizeof(programStats.movelist)
7287 && appData.debugMode) {
7289 "PV is too long; using the first %u bytes.\n",
7290 (unsigned) sizeof(programStats.movelist) - 1);
7293 safeStrCpy( programStats.movelist, buf1, sizeof(programStats.movelist) );
7295 sprintf(programStats.movelist, " no PV\n");
7298 if (programStats.seen_stat) {
7299 programStats.ok_to_send = 1;
7302 if (strchr(programStats.movelist, '(') != NULL) {
7303 programStats.line_is_book = 1;
7304 programStats.nr_moves = 0;
7305 programStats.moves_left = 0;
7307 programStats.line_is_book = 0;
7310 SendProgramStatsToFrontend( cps, &programStats );
7313 [AS] Protect the thinkOutput buffer from overflow... this
7314 is only useful if buf1 hasn't overflowed first!
7316 sprintf(thinkOutput, "[%d]%c%+.2f %s%s",
7318 (gameMode == TwoMachinesPlay ?
7319 ToUpper(cps->twoMachinesColor[0]) : ' '),
7320 ((double) curscore) / 100.0,
7321 prefixHint ? lastHint : "",
7322 prefixHint ? " " : "" );
7324 if( buf1[0] != NULLCHAR ) {
7325 unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
7327 if( strlen(buf1) > max_len ) {
7328 if( appData.debugMode) {
7329 fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
7331 buf1[max_len+1] = '\0';
7334 strcat( thinkOutput, buf1 );
7337 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
7338 || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7339 DisplayMove(currentMove - 1);
7343 } else if ((p=StrStr(message, "(only move)")) != NULL) {
7344 /* crafty (9.25+) says "(only move) <move>"
7345 * if there is only 1 legal move
7347 sscanf(p, "(only move) %s", buf1);
7348 sprintf(thinkOutput, "%s (only move)", buf1);
7349 sprintf(programStats.movelist, "%s (only move)", buf1);
7350 programStats.depth = 1;
7351 programStats.nr_moves = 1;
7352 programStats.moves_left = 1;
7353 programStats.nodes = 1;
7354 programStats.time = 1;
7355 programStats.got_only_move = 1;
7357 /* Not really, but we also use this member to
7358 mean "line isn't going to change" (Crafty
7359 isn't searching, so stats won't change) */
7360 programStats.line_is_book = 1;
7362 SendProgramStatsToFrontend( cps, &programStats );
7364 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
7365 gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7366 DisplayMove(currentMove - 1);
7369 } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
7370 &time, &nodes, &plylev, &mvleft,
7371 &mvtot, mvname) >= 5) {
7372 /* The stat01: line is from Crafty (9.29+) in response
7373 to the "." command */
7374 programStats.seen_stat = 1;
7375 cps->maybeThinking = TRUE;
7377 if (programStats.got_only_move || !appData.periodicUpdates)
7380 programStats.depth = plylev;
7381 programStats.time = time;
7382 programStats.nodes = nodes;
7383 programStats.moves_left = mvleft;
7384 programStats.nr_moves = mvtot;
7385 strcpy(programStats.move_name, mvname);
7386 programStats.ok_to_send = 1;
7387 programStats.movelist[0] = '\0';
7389 SendProgramStatsToFrontend( cps, &programStats );
7393 } else if (strncmp(message,"++",2) == 0) {
7394 /* Crafty 9.29+ outputs this */
7395 programStats.got_fail = 2;
7398 } else if (strncmp(message,"--",2) == 0) {
7399 /* Crafty 9.29+ outputs this */
7400 programStats.got_fail = 1;
7403 } else if (thinkOutput[0] != NULLCHAR &&
7404 strncmp(message, " ", 4) == 0) {
7405 unsigned message_len;
7408 while (*p && *p == ' ') p++;
7410 message_len = strlen( p );
7412 /* [AS] Avoid buffer overflow */
7413 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
7414 strcat(thinkOutput, " ");
7415 strcat(thinkOutput, p);
7418 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
7419 strcat(programStats.movelist, " ");
7420 strcat(programStats.movelist, p);
7423 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
7424 gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7425 DisplayMove(currentMove - 1);
7433 if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
7434 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
7436 ChessProgramStats cpstats;
7438 if (plyext != ' ' && plyext != '\t') {
7442 /* [AS] Negate score if machine is playing black and reporting absolute scores */
7443 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
7444 curscore = -curscore;
7447 cpstats.depth = plylev;
7448 cpstats.nodes = nodes;
7449 cpstats.time = time;
7450 cpstats.score = curscore;
7451 cpstats.got_only_move = 0;
7452 cpstats.movelist[0] = '\0';
7454 if (buf1[0] != NULLCHAR) {
7455 safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist) );
7458 cpstats.ok_to_send = 0;
7459 cpstats.line_is_book = 0;
7460 cpstats.nr_moves = 0;
7461 cpstats.moves_left = 0;
7463 SendProgramStatsToFrontend( cps, &cpstats );
7470 /* Parse a game score from the character string "game", and
7471 record it as the history of the current game. The game
7472 score is NOT assumed to start from the standard position.
7473 The display is not updated in any way.
7476 ParseGameHistory(game)
7480 int fromX, fromY, toX, toY, boardIndex;
7485 if (appData.debugMode)
7486 fprintf(debugFP, "Parsing game history: %s\n", game);
7488 if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
7489 gameInfo.site = StrSave(appData.icsHost);
7490 gameInfo.date = PGNDate();
7491 gameInfo.round = StrSave("-");
7493 /* Parse out names of players */
7494 while (*game == ' ') game++;
7496 while (*game != ' ') *p++ = *game++;
7498 gameInfo.white = StrSave(buf);
7499 while (*game == ' ') game++;
7501 while (*game != ' ' && *game != '\n') *p++ = *game++;
7503 gameInfo.black = StrSave(buf);
7506 boardIndex = blackPlaysFirst ? 1 : 0;
7509 yyboardindex = boardIndex;
7510 moveType = (ChessMove) yylex();
7512 case IllegalMove: /* maybe suicide chess, etc. */
7513 if (appData.debugMode) {
7514 fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
7515 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7516 setbuf(debugFP, NULL);
7518 case WhitePromotionChancellor:
7519 case BlackPromotionChancellor:
7520 case WhitePromotionArchbishop:
7521 case BlackPromotionArchbishop:
7522 case WhitePromotionQueen:
7523 case BlackPromotionQueen:
7524 case WhitePromotionRook:
7525 case BlackPromotionRook:
7526 case WhitePromotionBishop:
7527 case BlackPromotionBishop:
7528 case WhitePromotionKnight:
7529 case BlackPromotionKnight:
7530 case WhitePromotionKing:
7531 case BlackPromotionKing:
7533 case WhiteCapturesEnPassant:
7534 case BlackCapturesEnPassant:
7535 case WhiteKingSideCastle:
7536 case WhiteQueenSideCastle:
7537 case BlackKingSideCastle:
7538 case BlackQueenSideCastle:
7539 case WhiteKingSideCastleWild:
7540 case WhiteQueenSideCastleWild:
7541 case BlackKingSideCastleWild:
7542 case BlackQueenSideCastleWild:
7544 case WhiteHSideCastleFR:
7545 case WhiteASideCastleFR:
7546 case BlackHSideCastleFR:
7547 case BlackASideCastleFR:
7549 fromX = currentMoveString[0] - AAA;
7550 fromY = currentMoveString[1] - ONE;
7551 toX = currentMoveString[2] - AAA;
7552 toY = currentMoveString[3] - ONE;
7553 promoChar = currentMoveString[4];
7557 fromX = moveType == WhiteDrop ?
7558 (int) CharToPiece(ToUpper(currentMoveString[0])) :
7559 (int) CharToPiece(ToLower(currentMoveString[0]));
7561 toX = currentMoveString[2] - AAA;
7562 toY = currentMoveString[3] - ONE;
7563 promoChar = NULLCHAR;
7567 sprintf(buf, _("Ambiguous move in ICS output: \"%s\""), yy_text);
7568 if (appData.debugMode) {
7569 fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
7570 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7571 setbuf(debugFP, NULL);
7573 DisplayError(buf, 0);
7575 case ImpossibleMove:
7577 sprintf(buf, _("Illegal move in ICS output: \"%s\""), yy_text);
7578 if (appData.debugMode) {
7579 fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
7580 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7581 setbuf(debugFP, NULL);
7583 DisplayError(buf, 0);
7585 case (ChessMove) 0: /* end of file */
7586 if (boardIndex < backwardMostMove) {
7587 /* Oops, gap. How did that happen? */
7588 DisplayError(_("Gap in move list"), 0);
7591 backwardMostMove = blackPlaysFirst ? 1 : 0;
7592 if (boardIndex > forwardMostMove) {
7593 forwardMostMove = boardIndex;
7597 if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
7598 strcat(parseList[boardIndex-1], " ");
7599 strcat(parseList[boardIndex-1], yy_text);
7611 case GameUnfinished:
7612 if (gameMode == IcsExamining) {
7613 if (boardIndex < backwardMostMove) {
7614 /* Oops, gap. How did that happen? */
7617 backwardMostMove = blackPlaysFirst ? 1 : 0;
7620 gameInfo.result = moveType;
7621 p = strchr(yy_text, '{');
7622 if (p == NULL) p = strchr(yy_text, '(');
7625 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
7627 q = strchr(p, *p == '{' ? '}' : ')');
7628 if (q != NULL) *q = NULLCHAR;
7631 gameInfo.resultDetails = StrSave(p);
7634 if (boardIndex >= forwardMostMove &&
7635 !(gameMode == IcsObserving && ics_gamenum == -1)) {
7636 backwardMostMove = blackPlaysFirst ? 1 : 0;
7639 (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
7640 fromY, fromX, toY, toX, promoChar,
7641 parseList[boardIndex]);
7642 CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
7643 /* currentMoveString is set as a side-effect of yylex */
7644 strcpy(moveList[boardIndex], currentMoveString);
7645 strcat(moveList[boardIndex], "\n");
7647 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
7648 switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
7654 if(gameInfo.variant != VariantShogi)
7655 strcat(parseList[boardIndex - 1], "+");
7659 strcat(parseList[boardIndex - 1], "#");
7666 /* Apply a move to the given board */
7668 ApplyMove(fromX, fromY, toX, toY, promoChar, board)
7669 int fromX, fromY, toX, toY;
7673 ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
7674 int promoRank = gameInfo.variant == VariantMakruk ? 3 : 1;
7676 /* [HGM] compute & store e.p. status and castling rights for new position */
7677 /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
7680 if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
7681 oldEP = (signed char)board[EP_STATUS];
7682 board[EP_STATUS] = EP_NONE;
7684 if( board[toY][toX] != EmptySquare )
7685 board[EP_STATUS] = EP_CAPTURE;
7687 if( board[fromY][fromX] == WhitePawn ) {
7688 if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
7689 board[EP_STATUS] = EP_PAWN_MOVE;
7691 if(toX>BOARD_LEFT && board[toY][toX-1] == BlackPawn &&
7692 gameInfo.variant != VariantBerolina || toX < fromX)
7693 board[EP_STATUS] = toX | berolina;
7694 if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
7695 gameInfo.variant != VariantBerolina || toX > fromX)
7696 board[EP_STATUS] = toX;
7699 if( board[fromY][fromX] == BlackPawn ) {
7700 if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
7701 board[EP_STATUS] = EP_PAWN_MOVE;
7702 if( toY-fromY== -2) {
7703 if(toX>BOARD_LEFT && board[toY][toX-1] == WhitePawn &&
7704 gameInfo.variant != VariantBerolina || toX < fromX)
7705 board[EP_STATUS] = toX | berolina;
7706 if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
7707 gameInfo.variant != VariantBerolina || toX > fromX)
7708 board[EP_STATUS] = toX;
7712 for(i=0; i<nrCastlingRights; i++) {
7713 if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
7714 board[CASTLING][i] == toX && castlingRank[i] == toY
7715 ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
7720 /* [HGM] In Shatranj and Courier all promotions are to Ferz */
7721 if((gameInfo.variant==VariantShatranj || gameInfo.variant==VariantCourier || gameInfo.variant == VariantMakruk)
7722 && promoChar != 0) promoChar = PieceToChar(WhiteFerz);
7724 if (fromX == toX && fromY == toY) return;
7726 if (fromY == DROP_RANK) {
7728 piece = board[toY][toX] = (ChessSquare) fromX;
7730 piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
7731 king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
7732 if(gameInfo.variant == VariantKnightmate)
7733 king += (int) WhiteUnicorn - (int) WhiteKing;
7735 /* Code added by Tord: */
7736 /* FRC castling assumed when king captures friendly rook. */
7737 if (board[fromY][fromX] == WhiteKing &&
7738 board[toY][toX] == WhiteRook) {
7739 board[fromY][fromX] = EmptySquare;
7740 board[toY][toX] = EmptySquare;
7742 board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
7744 board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
7746 } else if (board[fromY][fromX] == BlackKing &&
7747 board[toY][toX] == BlackRook) {
7748 board[fromY][fromX] = EmptySquare;
7749 board[toY][toX] = EmptySquare;
7751 board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
7753 board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
7755 /* End of code added by Tord */
7757 } else if (board[fromY][fromX] == king
7758 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7759 && toY == fromY && toX > fromX+1) {
7760 board[fromY][fromX] = EmptySquare;
7761 board[toY][toX] = king;
7762 board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
7763 board[fromY][BOARD_RGHT-1] = EmptySquare;
7764 } else if (board[fromY][fromX] == king
7765 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7766 && toY == fromY && toX < fromX-1) {
7767 board[fromY][fromX] = EmptySquare;
7768 board[toY][toX] = king;
7769 board[toY][toX+1] = board[fromY][BOARD_LEFT];
7770 board[fromY][BOARD_LEFT] = EmptySquare;
7771 } else if (board[fromY][fromX] == WhitePawn
7772 && toY >= BOARD_HEIGHT-promoRank
7773 && gameInfo.variant != VariantXiangqi
7775 /* white pawn promotion */
7776 board[toY][toX] = CharToPiece(ToUpper(promoChar));
7777 if (board[toY][toX] == EmptySquare) {
7778 board[toY][toX] = WhiteQueen;
7780 if(gameInfo.variant==VariantBughouse ||
7781 gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
7782 board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
7783 board[fromY][fromX] = EmptySquare;
7784 } else if ((fromY == BOARD_HEIGHT-4)
7786 && gameInfo.variant != VariantXiangqi
7787 && gameInfo.variant != VariantBerolina
7788 && (board[fromY][fromX] == WhitePawn)
7789 && (board[toY][toX] == EmptySquare)) {
7790 board[fromY][fromX] = EmptySquare;
7791 board[toY][toX] = WhitePawn;
7792 captured = board[toY - 1][toX];
7793 board[toY - 1][toX] = EmptySquare;
7794 } else if ((fromY == BOARD_HEIGHT-4)
7796 && gameInfo.variant == VariantBerolina
7797 && (board[fromY][fromX] == WhitePawn)
7798 && (board[toY][toX] == EmptySquare)) {
7799 board[fromY][fromX] = EmptySquare;
7800 board[toY][toX] = WhitePawn;
7801 if(oldEP & EP_BEROLIN_A) {
7802 captured = board[fromY][fromX-1];
7803 board[fromY][fromX-1] = EmptySquare;
7804 }else{ captured = board[fromY][fromX+1];
7805 board[fromY][fromX+1] = EmptySquare;
7807 } else if (board[fromY][fromX] == king
7808 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7809 && toY == fromY && toX > fromX+1) {
7810 board[fromY][fromX] = EmptySquare;
7811 board[toY][toX] = king;
7812 board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
7813 board[fromY][BOARD_RGHT-1] = EmptySquare;
7814 } else if (board[fromY][fromX] == king
7815 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7816 && toY == fromY && toX < fromX-1) {
7817 board[fromY][fromX] = EmptySquare;
7818 board[toY][toX] = king;
7819 board[toY][toX+1] = board[fromY][BOARD_LEFT];
7820 board[fromY][BOARD_LEFT] = EmptySquare;
7821 } else if (fromY == 7 && fromX == 3
7822 && board[fromY][fromX] == BlackKing
7823 && toY == 7 && toX == 5) {
7824 board[fromY][fromX] = EmptySquare;
7825 board[toY][toX] = BlackKing;
7826 board[fromY][7] = EmptySquare;
7827 board[toY][4] = BlackRook;
7828 } else if (fromY == 7 && fromX == 3
7829 && board[fromY][fromX] == BlackKing
7830 && toY == 7 && toX == 1) {
7831 board[fromY][fromX] = EmptySquare;
7832 board[toY][toX] = BlackKing;
7833 board[fromY][0] = EmptySquare;
7834 board[toY][2] = BlackRook;
7835 } else if (board[fromY][fromX] == BlackPawn
7837 && gameInfo.variant != VariantXiangqi
7839 /* black pawn promotion */
7840 board[toY][toX] = CharToPiece(ToLower(promoChar));
7841 if (board[toY][toX] == EmptySquare) {
7842 board[toY][toX] = BlackQueen;
7844 if(gameInfo.variant==VariantBughouse ||
7845 gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
7846 board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
7847 board[fromY][fromX] = EmptySquare;
7848 } else if ((fromY == 3)
7850 && gameInfo.variant != VariantXiangqi
7851 && gameInfo.variant != VariantBerolina
7852 && (board[fromY][fromX] == BlackPawn)
7853 && (board[toY][toX] == EmptySquare)) {
7854 board[fromY][fromX] = EmptySquare;
7855 board[toY][toX] = BlackPawn;
7856 captured = board[toY + 1][toX];
7857 board[toY + 1][toX] = EmptySquare;
7858 } else if ((fromY == 3)
7860 && gameInfo.variant == VariantBerolina
7861 && (board[fromY][fromX] == BlackPawn)
7862 && (board[toY][toX] == EmptySquare)) {
7863 board[fromY][fromX] = EmptySquare;
7864 board[toY][toX] = BlackPawn;
7865 if(oldEP & EP_BEROLIN_A) {
7866 captured = board[fromY][fromX-1];
7867 board[fromY][fromX-1] = EmptySquare;
7868 }else{ captured = board[fromY][fromX+1];
7869 board[fromY][fromX+1] = EmptySquare;
7872 board[toY][toX] = board[fromY][fromX];
7873 board[fromY][fromX] = EmptySquare;
7876 /* [HGM] now we promote for Shogi, if needed */
7877 if(gameInfo.variant == VariantShogi && promoChar == 'q')
7878 board[toY][toX] = (ChessSquare) (PROMOTED piece);
7881 if (gameInfo.holdingsWidth != 0) {
7883 /* !!A lot more code needs to be written to support holdings */
7884 /* [HGM] OK, so I have written it. Holdings are stored in the */
7885 /* penultimate board files, so they are automaticlly stored */
7886 /* in the game history. */
7887 if (fromY == DROP_RANK) {
7888 /* Delete from holdings, by decreasing count */
7889 /* and erasing image if necessary */
7891 if(p < (int) BlackPawn) { /* white drop */
7892 p -= (int)WhitePawn;
7893 p = PieceToNumber((ChessSquare)p);
7894 if(p >= gameInfo.holdingsSize) p = 0;
7895 if(--board[p][BOARD_WIDTH-2] <= 0)
7896 board[p][BOARD_WIDTH-1] = EmptySquare;
7897 if((int)board[p][BOARD_WIDTH-2] < 0)
7898 board[p][BOARD_WIDTH-2] = 0;
7899 } else { /* black drop */
7900 p -= (int)BlackPawn;
7901 p = PieceToNumber((ChessSquare)p);
7902 if(p >= gameInfo.holdingsSize) p = 0;
7903 if(--board[BOARD_HEIGHT-1-p][1] <= 0)
7904 board[BOARD_HEIGHT-1-p][0] = EmptySquare;
7905 if((int)board[BOARD_HEIGHT-1-p][1] < 0)
7906 board[BOARD_HEIGHT-1-p][1] = 0;
7909 if (captured != EmptySquare && gameInfo.holdingsSize > 0
7910 && gameInfo.variant != VariantBughouse ) {
7911 /* [HGM] holdings: Add to holdings, if holdings exist */
7912 if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
7913 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
7914 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
7917 if (p >= (int) BlackPawn) {
7918 p -= (int)BlackPawn;
7919 if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
7920 /* in Shogi restore piece to its original first */
7921 captured = (ChessSquare) (DEMOTED captured);
7924 p = PieceToNumber((ChessSquare)p);
7925 if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
7926 board[p][BOARD_WIDTH-2]++;
7927 board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
7929 p -= (int)WhitePawn;
7930 if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
7931 captured = (ChessSquare) (DEMOTED captured);
7934 p = PieceToNumber((ChessSquare)p);
7935 if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
7936 board[BOARD_HEIGHT-1-p][1]++;
7937 board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
7940 } else if (gameInfo.variant == VariantAtomic) {
7941 if (captured != EmptySquare) {
7943 for (y = toY-1; y <= toY+1; y++) {
7944 for (x = toX-1; x <= toX+1; x++) {
7945 if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
7946 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
7947 board[y][x] = EmptySquare;
7951 board[toY][toX] = EmptySquare;
7954 if(gameInfo.variant == VariantShogi && promoChar != NULLCHAR && promoChar != '=') {
7955 /* [HGM] Shogi promotions */
7956 board[toY][toX] = (ChessSquare) (PROMOTED piece);
7959 if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
7960 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
7961 // [HGM] superchess: take promotion piece out of holdings
7962 int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
7963 if((int)piece < (int)BlackPawn) { // determine stm from piece color
7964 if(!--board[k][BOARD_WIDTH-2])
7965 board[k][BOARD_WIDTH-1] = EmptySquare;
7967 if(!--board[BOARD_HEIGHT-1-k][1])
7968 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
7974 /* Updates forwardMostMove */
7976 MakeMove(fromX, fromY, toX, toY, promoChar)
7977 int fromX, fromY, toX, toY;
7980 // forwardMostMove++; // [HGM] bare: moved downstream
7982 if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
7983 int timeLeft; static int lastLoadFlag=0; int king, piece;
7984 piece = boards[forwardMostMove][fromY][fromX];
7985 king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
7986 if(gameInfo.variant == VariantKnightmate)
7987 king += (int) WhiteUnicorn - (int) WhiteKing;
7988 if(forwardMostMove == 0) {
7990 fprintf(serverMoves, "%s;", second.tidy);
7991 fprintf(serverMoves, "%s;", first.tidy);
7992 if(!blackPlaysFirst)
7993 fprintf(serverMoves, "%s;", second.tidy);
7994 } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
7995 lastLoadFlag = loadFlag;
7997 fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
7998 // print castling suffix
7999 if( toY == fromY && piece == king ) {
8001 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
8003 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
8006 if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
8007 boards[forwardMostMove][fromY][fromX] == BlackPawn ) &&
8008 boards[forwardMostMove][toY][toX] == EmptySquare
8010 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
8012 if(promoChar != NULLCHAR)
8013 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);
8015 fprintf(serverMoves, "/%d/%d",
8016 pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
8017 if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
8018 else timeLeft = blackTimeRemaining/1000;
8019 fprintf(serverMoves, "/%d", timeLeft);
8021 fflush(serverMoves);
8024 if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations
8025 DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
8029 UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
8030 if (commentList[forwardMostMove+1] != NULL) {
8031 free(commentList[forwardMostMove+1]);
8032 commentList[forwardMostMove+1] = NULL;
8034 CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
8035 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
8036 forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
8037 SwitchClocks(); // uses forwardMostMove, so must be done after incrementing it !
8038 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
8039 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
8040 gameInfo.result = GameUnfinished;
8041 if (gameInfo.resultDetails != NULL) {
8042 free(gameInfo.resultDetails);
8043 gameInfo.resultDetails = NULL;
8045 CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
8046 moveList[forwardMostMove - 1]);
8047 (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
8048 PosFlags(forwardMostMove - 1),
8049 fromY, fromX, toY, toX, promoChar,
8050 parseList[forwardMostMove - 1]);
8051 switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
8057 if(gameInfo.variant != VariantShogi)
8058 strcat(parseList[forwardMostMove - 1], "+");
8062 strcat(parseList[forwardMostMove - 1], "#");
8065 if (appData.debugMode) {
8066 fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
8071 /* Updates currentMove if not pausing */
8073 ShowMove(fromX, fromY, toX, toY)
8075 int instant = (gameMode == PlayFromGameFile) ?
8076 (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
8077 if(appData.noGUI) return;
8078 if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
8080 if (forwardMostMove == currentMove + 1) {
8081 AnimateMove(boards[forwardMostMove - 1],
8082 fromX, fromY, toX, toY);
8084 if (appData.highlightLastMove) {
8085 SetHighlights(fromX, fromY, toX, toY);
8088 currentMove = forwardMostMove;
8091 if (instant) return;
8093 DisplayMove(currentMove - 1);
8094 DrawPosition(FALSE, boards[currentMove]);
8095 DisplayBothClocks();
8096 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
8099 void SendEgtPath(ChessProgramState *cps)
8100 { /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
8101 char buf[MSG_SIZ], name[MSG_SIZ], *p;
8103 if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
8106 char c, *q = name+1, *r, *s;
8108 name[0] = ','; // extract next format name from feature and copy with prefixed ','
8109 while(*p && *p != ',') *q++ = *p++;
8111 if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
8112 strcmp(name, ",nalimov:") == 0 ) {
8113 // take nalimov path from the menu-changeable option first, if it is defined
8114 sprintf(buf, "egtpath nalimov %s\n", appData.defaultPathEGTB);
8115 SendToProgram(buf,cps); // send egtbpath command for nalimov
8117 if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
8118 (s = StrStr(appData.egtFormats, name)) != NULL) {
8119 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
8120 s = r = StrStr(s, ":") + 1; // beginning of path info
8121 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
8122 c = *r; *r = 0; // temporarily null-terminate path info
8123 *--q = 0; // strip of trailig ':' from name
8124 sprintf(buf, "egtpath %s %s\n", name+1, s);
8126 SendToProgram(buf,cps); // send egtbpath command for this format
8128 if(*p == ',') p++; // read away comma to position for next format name
8133 InitChessProgram(cps, setup)
8134 ChessProgramState *cps;
8135 int setup; /* [HGM] needed to setup FRC opening position */
8137 char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
8138 if (appData.noChessProgram) return;
8139 hintRequested = FALSE;
8140 bookRequested = FALSE;
8142 /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
8143 /* moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
8144 if(cps->memSize) { /* [HGM] memory */
8145 sprintf(buf, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
8146 SendToProgram(buf, cps);
8148 SendEgtPath(cps); /* [HGM] EGT */
8149 if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
8150 sprintf(buf, "cores %d\n", appData.smpCores);
8151 SendToProgram(buf, cps);
8154 SendToProgram(cps->initString, cps);
8155 if (gameInfo.variant != VariantNormal &&
8156 gameInfo.variant != VariantLoadable
8157 /* [HGM] also send variant if board size non-standard */
8158 || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
8160 char *v = VariantName(gameInfo.variant);
8161 if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
8162 /* [HGM] in protocol 1 we have to assume all variants valid */
8163 sprintf(buf, _("Variant %s not supported by %s"), v, cps->tidy);
8164 DisplayFatalError(buf, 0, 1);
8168 /* [HGM] make prefix for non-standard board size. Awkward testing... */
8169 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8170 if( gameInfo.variant == VariantXiangqi )
8171 overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
8172 if( gameInfo.variant == VariantShogi )
8173 overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
8174 if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
8175 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
8176 if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
8177 gameInfo.variant == VariantGothic || gameInfo.variant == VariantFalcon )
8178 overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8179 if( gameInfo.variant == VariantCourier )
8180 overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8181 if( gameInfo.variant == VariantSuper )
8182 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
8183 if( gameInfo.variant == VariantGreat )
8184 overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
8187 sprintf(b, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
8188 gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
8189 /* [HGM] varsize: try first if this defiant size variant is specifically known */
8190 if(StrStr(cps->variants, b) == NULL) {
8191 // specific sized variant not known, check if general sizing allowed
8192 if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
8193 if(StrStr(cps->variants, "boardsize") == NULL) {
8194 sprintf(buf, "Board size %dx%d+%d not supported by %s",
8195 gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
8196 DisplayFatalError(buf, 0, 1);
8199 /* [HGM] here we really should compare with the maximum supported board size */
8202 } else sprintf(b, "%s", VariantName(gameInfo.variant));
8203 sprintf(buf, "variant %s\n", b);
8204 SendToProgram(buf, cps);
8206 currentlyInitializedVariant = gameInfo.variant;
8208 /* [HGM] send opening position in FRC to first engine */
8210 SendToProgram("force\n", cps);
8212 /* engine is now in force mode! Set flag to wake it up after first move. */
8213 setboardSpoiledMachineBlack = 1;
8217 snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
8218 SendToProgram(buf, cps);
8220 cps->maybeThinking = FALSE;
8221 cps->offeredDraw = 0;
8222 if (!appData.icsActive) {
8223 SendTimeControl(cps, movesPerSession, timeControl,
8224 timeIncrement, appData.searchDepth,
8227 if (appData.showThinking
8228 // [HGM] thinking: four options require thinking output to be sent
8229 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
8231 SendToProgram("post\n", cps);
8233 SendToProgram("hard\n", cps);
8234 if (!appData.ponderNextMove) {
8235 /* Warning: "easy" is a toggle in GNU Chess, so don't send
8236 it without being sure what state we are in first. "hard"
8237 is not a toggle, so that one is OK.
8239 SendToProgram("easy\n", cps);
8242 sprintf(buf, "ping %d\n", ++cps->lastPing);
8243 SendToProgram(buf, cps);
8245 cps->initDone = TRUE;
8250 StartChessProgram(cps)
8251 ChessProgramState *cps;
8256 if (appData.noChessProgram) return;
8257 cps->initDone = FALSE;
8259 if (strcmp(cps->host, "localhost") == 0) {
8260 err = StartChildProcess(cps->program, cps->dir, &cps->pr);
8261 } else if (*appData.remoteShell == NULLCHAR) {
8262 err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
8264 if (*appData.remoteUser == NULLCHAR) {
8265 snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
8268 snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
8269 cps->host, appData.remoteUser, cps->program);
8271 err = StartChildProcess(buf, "", &cps->pr);
8275 sprintf(buf, _("Startup failure on '%s'"), cps->program);
8276 DisplayFatalError(buf, err, 1);
8282 cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
8283 if (cps->protocolVersion > 1) {
8284 sprintf(buf, "xboard\nprotover %d\n", cps->protocolVersion);
8285 cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
8286 cps->comboCnt = 0; // and values of combo boxes
8287 SendToProgram(buf, cps);
8289 SendToProgram("xboard\n", cps);
8295 TwoMachinesEventIfReady P((void))
8297 if (first.lastPing != first.lastPong) {
8298 DisplayMessage("", _("Waiting for first chess program"));
8299 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
8302 if (second.lastPing != second.lastPong) {
8303 DisplayMessage("", _("Waiting for second chess program"));
8304 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
8312 NextMatchGame P((void))
8314 int index; /* [HGM] autoinc: step load index during match */
8316 if (*appData.loadGameFile != NULLCHAR) {
8317 index = appData.loadGameIndex;
8318 if(index < 0) { // [HGM] autoinc
8319 lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
8320 if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
8322 LoadGameFromFile(appData.loadGameFile,
8324 appData.loadGameFile, FALSE);
8325 } else if (*appData.loadPositionFile != NULLCHAR) {
8326 index = appData.loadPositionIndex;
8327 if(index < 0) { // [HGM] autoinc
8328 lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
8329 if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
8331 LoadPositionFromFile(appData.loadPositionFile,
8333 appData.loadPositionFile);
8335 TwoMachinesEventIfReady();
8338 void UserAdjudicationEvent( int result )
8340 ChessMove gameResult = GameIsDrawn;
8343 gameResult = WhiteWins;
8345 else if( result < 0 ) {
8346 gameResult = BlackWins;
8349 if( gameMode == TwoMachinesPlay ) {
8350 GameEnds( gameResult, "User adjudication", GE_XBOARD );
8355 // [HGM] save: calculate checksum of game to make games easily identifiable
8356 int StringCheckSum(char *s)
8359 if(s==NULL) return 0;
8360 while(*s) i = i*259 + *s++;
8367 for(i=backwardMostMove; i<forwardMostMove; i++) {
8368 sum += pvInfoList[i].depth;
8369 sum += StringCheckSum(parseList[i]);
8370 sum += StringCheckSum(commentList[i]);
8373 if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
8374 return sum + StringCheckSum(commentList[i]);
8375 } // end of save patch
8378 GameEnds(result, resultDetails, whosays)
8380 char *resultDetails;
8383 GameMode nextGameMode;
8387 if(endingGame) return; /* [HGM] crash: forbid recursion */
8390 if (appData.debugMode) {
8391 fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
8392 result, resultDetails ? resultDetails : "(null)", whosays);
8395 if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
8396 /* If we are playing on ICS, the server decides when the
8397 game is over, but the engine can offer to draw, claim
8401 if (appData.zippyPlay && first.initDone) {
8402 if (result == GameIsDrawn) {
8403 /* In case draw still needs to be claimed */
8404 SendToICS(ics_prefix);
8405 SendToICS("draw\n");
8406 } else if (StrCaseStr(resultDetails, "resign")) {
8407 SendToICS(ics_prefix);
8408 SendToICS("resign\n");
8412 endingGame = 0; /* [HGM] crash */
8416 /* If we're loading the game from a file, stop */
8417 if (whosays == GE_FILE) {
8418 (void) StopLoadGameTimer();
8422 /* Cancel draw offers */
8423 first.offeredDraw = second.offeredDraw = 0;
8425 /* If this is an ICS game, only ICS can really say it's done;
8426 if not, anyone can. */
8427 isIcsGame = (gameMode == IcsPlayingWhite ||
8428 gameMode == IcsPlayingBlack ||
8429 gameMode == IcsObserving ||
8430 gameMode == IcsExamining);
8432 if (!isIcsGame || whosays == GE_ICS) {
8433 /* OK -- not an ICS game, or ICS said it was done */
8435 if (!isIcsGame && !appData.noChessProgram)
8436 SetUserThinkingEnables();
8438 /* [HGM] if a machine claims the game end we verify this claim */
8439 if(gameMode == TwoMachinesPlay && appData.testClaims) {
8440 if(appData.testLegality && whosays >= GE_ENGINE1 ) {
8442 ChessMove trueResult = (ChessMove) -1;
8444 claimer = whosays == GE_ENGINE1 ? /* color of claimer */
8445 first.twoMachinesColor[0] :
8446 second.twoMachinesColor[0] ;
8448 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
8449 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
8450 /* [HGM] verify: engine mate claims accepted if they were flagged */
8451 trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
8453 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
8454 /* [HGM] verify: engine mate claims accepted if they were flagged */
8455 trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8457 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
8458 trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
8461 // now verify win claims, but not in drop games, as we don't understand those yet
8462 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
8463 || gameInfo.variant == VariantGreat) &&
8464 (result == WhiteWins && claimer == 'w' ||
8465 result == BlackWins && claimer == 'b' ) ) { // case to verify: engine claims own win
8466 if (appData.debugMode) {
8467 fprintf(debugFP, "result=%d sp=%d move=%d\n",
8468 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
8470 if(result != trueResult) {
8471 sprintf(buf, "False win claim: '%s'", resultDetails);
8472 result = claimer == 'w' ? BlackWins : WhiteWins;
8473 resultDetails = buf;
8476 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
8477 && (forwardMostMove <= backwardMostMove ||
8478 (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
8479 (claimer=='b')==(forwardMostMove&1))
8481 /* [HGM] verify: draws that were not flagged are false claims */
8482 sprintf(buf, "False draw claim: '%s'", resultDetails);
8483 result = claimer == 'w' ? BlackWins : WhiteWins;
8484 resultDetails = buf;
8486 /* (Claiming a loss is accepted no questions asked!) */
8488 /* [HGM] bare: don't allow bare King to win */
8489 if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
8490 && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
8491 && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
8492 && result != GameIsDrawn)
8493 { int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
8494 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
8495 int p = (signed char)boards[forwardMostMove][i][j] - color;
8496 if(p >= 0 && p <= (int)WhiteKing) k++;
8498 if (appData.debugMode) {
8499 fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
8500 result, resultDetails ? resultDetails : "(null)", whosays, k, color);
8503 result = GameIsDrawn;
8504 sprintf(buf, "%s but bare king", resultDetails);
8505 resultDetails = buf;
8511 if(serverMoves != NULL && !loadFlag) { char c = '=';
8512 if(result==WhiteWins) c = '+';
8513 if(result==BlackWins) c = '-';
8514 if(resultDetails != NULL)
8515 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
8517 if (resultDetails != NULL) {
8518 gameInfo.result = result;
8519 gameInfo.resultDetails = StrSave(resultDetails);
8521 /* display last move only if game was not loaded from file */
8522 if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
8523 DisplayMove(currentMove - 1);
8525 if (forwardMostMove != 0) {
8526 if (gameMode != PlayFromGameFile && gameMode != EditGame
8527 && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
8529 if (*appData.saveGameFile != NULLCHAR) {
8530 SaveGameToFile(appData.saveGameFile, TRUE);
8531 } else if (appData.autoSaveGames) {
8534 if (*appData.savePositionFile != NULLCHAR) {
8535 SavePositionToFile(appData.savePositionFile);
8540 /* Tell program how game ended in case it is learning */
8541 /* [HGM] Moved this to after saving the PGN, just in case */
8542 /* engine died and we got here through time loss. In that */
8543 /* case we will get a fatal error writing the pipe, which */
8544 /* would otherwise lose us the PGN. */
8545 /* [HGM] crash: not needed anymore, but doesn't hurt; */
8546 /* output during GameEnds should never be fatal anymore */
8547 if (gameMode == MachinePlaysWhite ||
8548 gameMode == MachinePlaysBlack ||
8549 gameMode == TwoMachinesPlay ||
8550 gameMode == IcsPlayingWhite ||
8551 gameMode == IcsPlayingBlack ||
8552 gameMode == BeginningOfGame) {
8554 sprintf(buf, "result %s {%s}\n", PGNResult(result),
8556 if (first.pr != NoProc) {
8557 SendToProgram(buf, &first);
8559 if (second.pr != NoProc &&
8560 gameMode == TwoMachinesPlay) {
8561 SendToProgram(buf, &second);
8566 if (appData.icsActive) {
8567 if (appData.quietPlay &&
8568 (gameMode == IcsPlayingWhite ||
8569 gameMode == IcsPlayingBlack)) {
8570 SendToICS(ics_prefix);
8571 SendToICS("set shout 1\n");
8573 nextGameMode = IcsIdle;
8574 ics_user_moved = FALSE;
8575 /* clean up premove. It's ugly when the game has ended and the
8576 * premove highlights are still on the board.
8580 ClearPremoveHighlights();
8581 DrawPosition(FALSE, boards[currentMove]);
8583 if (whosays == GE_ICS) {
8586 if (gameMode == IcsPlayingWhite)
8588 else if(gameMode == IcsPlayingBlack)
8592 if (gameMode == IcsPlayingBlack)
8594 else if(gameMode == IcsPlayingWhite)
8601 PlayIcsUnfinishedSound();
8604 } else if (gameMode == EditGame ||
8605 gameMode == PlayFromGameFile ||
8606 gameMode == AnalyzeMode ||
8607 gameMode == AnalyzeFile) {
8608 nextGameMode = gameMode;
8610 nextGameMode = EndOfGame;
8615 nextGameMode = gameMode;
8618 if (appData.noChessProgram) {
8619 gameMode = nextGameMode;
8621 endingGame = 0; /* [HGM] crash */
8626 /* Put first chess program into idle state */
8627 if (first.pr != NoProc &&
8628 (gameMode == MachinePlaysWhite ||
8629 gameMode == MachinePlaysBlack ||
8630 gameMode == TwoMachinesPlay ||
8631 gameMode == IcsPlayingWhite ||
8632 gameMode == IcsPlayingBlack ||
8633 gameMode == BeginningOfGame)) {
8634 SendToProgram("force\n", &first);
8635 if (first.usePing) {
8637 sprintf(buf, "ping %d\n", ++first.lastPing);
8638 SendToProgram(buf, &first);
8641 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
8642 /* Kill off first chess program */
8643 if (first.isr != NULL)
8644 RemoveInputSource(first.isr);
8647 if (first.pr != NoProc) {
8649 DoSleep( appData.delayBeforeQuit );
8650 SendToProgram("quit\n", &first);
8651 DoSleep( appData.delayAfterQuit );
8652 DestroyChildProcess(first.pr, first.useSigterm);
8657 /* Put second chess program into idle state */
8658 if (second.pr != NoProc &&
8659 gameMode == TwoMachinesPlay) {
8660 SendToProgram("force\n", &second);
8661 if (second.usePing) {
8663 sprintf(buf, "ping %d\n", ++second.lastPing);
8664 SendToProgram(buf, &second);
8667 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
8668 /* Kill off second chess program */
8669 if (second.isr != NULL)
8670 RemoveInputSource(second.isr);
8673 if (second.pr != NoProc) {
8674 DoSleep( appData.delayBeforeQuit );
8675 SendToProgram("quit\n", &second);
8676 DoSleep( appData.delayAfterQuit );
8677 DestroyChildProcess(second.pr, second.useSigterm);
8682 if (matchMode && gameMode == TwoMachinesPlay) {
8685 if (first.twoMachinesColor[0] == 'w') {
8692 if (first.twoMachinesColor[0] == 'b') {
8701 if (matchGame < appData.matchGames) {
8703 if(appData.sameColorGames <= 1) { /* [HGM] alternate: suppress color swap */
8704 tmp = first.twoMachinesColor;
8705 first.twoMachinesColor = second.twoMachinesColor;
8706 second.twoMachinesColor = tmp;
8708 gameMode = nextGameMode;
8710 if(appData.matchPause>10000 || appData.matchPause<10)
8711 appData.matchPause = 10000; /* [HGM] make pause adjustable */
8712 ScheduleDelayedEvent(NextMatchGame, appData.matchPause);
8713 endingGame = 0; /* [HGM] crash */
8717 gameMode = nextGameMode;
8718 sprintf(buf, _("Match %s vs. %s: final score %d-%d-%d"),
8719 first.tidy, second.tidy,
8720 first.matchWins, second.matchWins,
8721 appData.matchGames - (first.matchWins + second.matchWins));
8722 DisplayFatalError(buf, 0, 0);
8725 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
8726 !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
8728 gameMode = nextGameMode;
8730 endingGame = 0; /* [HGM] crash */
8733 /* Assumes program was just initialized (initString sent).
8734 Leaves program in force mode. */
8736 FeedMovesToProgram(cps, upto)
8737 ChessProgramState *cps;
8742 if (appData.debugMode)
8743 fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
8744 startedFromSetupPosition ? "position and " : "",
8745 backwardMostMove, upto, cps->which);
8746 if(currentlyInitializedVariant != gameInfo.variant) { char buf[MSG_SIZ];
8747 // [HGM] variantswitch: make engine aware of new variant
8748 if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
8749 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
8750 sprintf(buf, "variant %s\n", VariantName(gameInfo.variant));
8751 SendToProgram(buf, cps);
8752 currentlyInitializedVariant = gameInfo.variant;
8754 SendToProgram("force\n", cps);
8755 if (startedFromSetupPosition) {
8756 SendBoard(cps, backwardMostMove);
8757 if (appData.debugMode) {
8758 fprintf(debugFP, "feedMoves\n");
8761 for (i = backwardMostMove; i < upto; i++) {
8762 SendMoveToProgram(i, cps);
8768 ResurrectChessProgram()
8770 /* The chess program may have exited.
8771 If so, restart it and feed it all the moves made so far. */
8773 if (appData.noChessProgram || first.pr != NoProc) return;
8775 StartChessProgram(&first);
8776 InitChessProgram(&first, FALSE);
8777 FeedMovesToProgram(&first, currentMove);
8779 if (!first.sendTime) {
8780 /* can't tell gnuchess what its clock should read,
8781 so we bow to its notion. */
8783 timeRemaining[0][currentMove] = whiteTimeRemaining;
8784 timeRemaining[1][currentMove] = blackTimeRemaining;
8787 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
8788 appData.icsEngineAnalyze) && first.analysisSupport) {
8789 SendToProgram("analyze\n", &first);
8790 first.analyzing = TRUE;
8803 if (appData.debugMode) {
8804 fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
8805 redraw, init, gameMode);
8807 CleanupTail(); // [HGM] vari: delete any stored variations
8808 pausing = pauseExamInvalid = FALSE;
8809 startedFromSetupPosition = blackPlaysFirst = FALSE;
8811 whiteFlag = blackFlag = FALSE;
8812 userOfferedDraw = FALSE;
8813 hintRequested = bookRequested = FALSE;
8814 first.maybeThinking = FALSE;
8815 second.maybeThinking = FALSE;
8816 first.bookSuspend = FALSE; // [HGM] book
8817 second.bookSuspend = FALSE;
8818 thinkOutput[0] = NULLCHAR;
8819 lastHint[0] = NULLCHAR;
8820 ClearGameInfo(&gameInfo);
8821 gameInfo.variant = StringToVariant(appData.variant);
8822 ics_user_moved = ics_clock_paused = FALSE;
8823 ics_getting_history = H_FALSE;
8825 white_holding[0] = black_holding[0] = NULLCHAR;
8826 ClearProgramStats();
8827 opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
8831 flipView = appData.flipView;
8832 ClearPremoveHighlights();
8834 alarmSounded = FALSE;
8836 GameEnds((ChessMove) 0, NULL, GE_PLAYER);
8837 if(appData.serverMovesName != NULL) {
8838 /* [HGM] prepare to make moves file for broadcasting */
8839 clock_t t = clock();
8840 if(serverMoves != NULL) fclose(serverMoves);
8841 serverMoves = fopen(appData.serverMovesName, "r");
8842 if(serverMoves != NULL) {
8843 fclose(serverMoves);
8844 /* delay 15 sec before overwriting, so all clients can see end */
8845 while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
8847 serverMoves = fopen(appData.serverMovesName, "w");
8851 gameMode = BeginningOfGame;
8853 if(appData.icsActive) gameInfo.variant = VariantNormal;
8854 currentMove = forwardMostMove = backwardMostMove = 0;
8855 InitPosition(redraw);
8856 for (i = 0; i < MAX_MOVES; i++) {
8857 if (commentList[i] != NULL) {
8858 free(commentList[i]);
8859 commentList[i] = NULL;
8863 timeRemaining[0][0] = whiteTimeRemaining;
8864 timeRemaining[1][0] = blackTimeRemaining;
8865 if (first.pr == NULL) {
8866 StartChessProgram(&first);
8869 InitChessProgram(&first, startedFromSetupPosition);
8872 DisplayMessage("", "");
8873 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
8874 lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
8881 if (!AutoPlayOneMove())
8883 if (matchMode || appData.timeDelay == 0)
8885 if (appData.timeDelay < 0 || gameMode == AnalyzeFile)
8887 StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
8896 int fromX, fromY, toX, toY;
8898 if (appData.debugMode) {
8899 fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
8902 if (gameMode != PlayFromGameFile)
8905 if (currentMove >= forwardMostMove) {
8906 gameMode = EditGame;
8909 /* [AS] Clear current move marker at the end of a game */
8910 /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
8915 toX = moveList[currentMove][2] - AAA;
8916 toY = moveList[currentMove][3] - ONE;
8918 if (moveList[currentMove][1] == '@') {
8919 if (appData.highlightLastMove) {
8920 SetHighlights(-1, -1, toX, toY);
8923 fromX = moveList[currentMove][0] - AAA;
8924 fromY = moveList[currentMove][1] - ONE;
8926 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
8928 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
8930 if (appData.highlightLastMove) {
8931 SetHighlights(fromX, fromY, toX, toY);
8934 DisplayMove(currentMove);
8935 SendMoveToProgram(currentMove++, &first);
8936 DisplayBothClocks();
8937 DrawPosition(FALSE, boards[currentMove]);
8938 // [HGM] PV info: always display, routine tests if empty
8939 DisplayComment(currentMove - 1, commentList[currentMove]);
8945 LoadGameOneMove(readAhead)
8946 ChessMove readAhead;
8948 int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
8949 char promoChar = NULLCHAR;
8954 if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
8955 gameMode != AnalyzeMode && gameMode != Training) {
8960 yyboardindex = forwardMostMove;
8961 if (readAhead != (ChessMove)0) {
8962 moveType = readAhead;
8964 if (gameFileFP == NULL)
8966 moveType = (ChessMove) yylex();
8972 if (appData.debugMode)
8973 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
8976 /* append the comment but don't display it */
8977 AppendComment(currentMove, p, FALSE);
8980 case WhiteCapturesEnPassant:
8981 case BlackCapturesEnPassant:
8982 case WhitePromotionChancellor:
8983 case BlackPromotionChancellor:
8984 case WhitePromotionArchbishop:
8985 case BlackPromotionArchbishop:
8986 case WhitePromotionCentaur:
8987 case BlackPromotionCentaur:
8988 case WhitePromotionQueen:
8989 case BlackPromotionQueen:
8990 case WhitePromotionRook:
8991 case BlackPromotionRook:
8992 case WhitePromotionBishop:
8993 case BlackPromotionBishop:
8994 case WhitePromotionKnight:
8995 case BlackPromotionKnight:
8996 case WhitePromotionKing:
8997 case BlackPromotionKing:
8999 case WhiteKingSideCastle:
9000 case WhiteQueenSideCastle:
9001 case BlackKingSideCastle:
9002 case BlackQueenSideCastle:
9003 case WhiteKingSideCastleWild:
9004 case WhiteQueenSideCastleWild:
9005 case BlackKingSideCastleWild:
9006 case BlackQueenSideCastleWild:
9008 case WhiteHSideCastleFR:
9009 case WhiteASideCastleFR:
9010 case BlackHSideCastleFR:
9011 case BlackASideCastleFR:
9013 if (appData.debugMode)
9014 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
9015 fromX = currentMoveString[0] - AAA;
9016 fromY = currentMoveString[1] - ONE;
9017 toX = currentMoveString[2] - AAA;
9018 toY = currentMoveString[3] - ONE;
9019 promoChar = currentMoveString[4];
9024 if (appData.debugMode)
9025 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
9026 fromX = moveType == WhiteDrop ?
9027 (int) CharToPiece(ToUpper(currentMoveString[0])) :
9028 (int) CharToPiece(ToLower(currentMoveString[0]));
9030 toX = currentMoveString[2] - AAA;
9031 toY = currentMoveString[3] - ONE;
9037 case GameUnfinished:
9038 if (appData.debugMode)
9039 fprintf(debugFP, "Parsed game end: %s\n", yy_text);
9040 p = strchr(yy_text, '{');
9041 if (p == NULL) p = strchr(yy_text, '(');
9044 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
9046 q = strchr(p, *p == '{' ? '}' : ')');
9047 if (q != NULL) *q = NULLCHAR;
9050 GameEnds(moveType, p, GE_FILE);
9052 if (cmailMsgLoaded) {
9054 flipView = WhiteOnMove(currentMove);
9055 if (moveType == GameUnfinished) flipView = !flipView;
9056 if (appData.debugMode)
9057 fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
9061 case (ChessMove) 0: /* end of file */
9062 if (appData.debugMode)
9063 fprintf(debugFP, "Parser hit end of file\n");
9064 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9070 if (WhiteOnMove(currentMove)) {
9071 GameEnds(BlackWins, "Black mates", GE_FILE);
9073 GameEnds(WhiteWins, "White mates", GE_FILE);
9077 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
9084 if (lastLoadGameStart == GNUChessGame) {
9085 /* GNUChessGames have numbers, but they aren't move numbers */
9086 if (appData.debugMode)
9087 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
9088 yy_text, (int) moveType);
9089 return LoadGameOneMove((ChessMove)0); /* tail recursion */
9091 /* else fall thru */
9096 /* Reached start of next game in file */
9097 if (appData.debugMode)
9098 fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
9099 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9105 if (WhiteOnMove(currentMove)) {
9106 GameEnds(BlackWins, "Black mates", GE_FILE);
9108 GameEnds(WhiteWins, "White mates", GE_FILE);
9112 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
9118 case PositionDiagram: /* should not happen; ignore */
9119 case ElapsedTime: /* ignore */
9120 case NAG: /* ignore */
9121 if (appData.debugMode)
9122 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
9123 yy_text, (int) moveType);
9124 return LoadGameOneMove((ChessMove)0); /* tail recursion */
9127 if (appData.testLegality) {
9128 if (appData.debugMode)
9129 fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
9130 sprintf(move, _("Illegal move: %d.%s%s"),
9131 (forwardMostMove / 2) + 1,
9132 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9133 DisplayError(move, 0);
9136 if (appData.debugMode)
9137 fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
9138 yy_text, currentMoveString);
9139 fromX = currentMoveString[0] - AAA;
9140 fromY = currentMoveString[1] - ONE;
9141 toX = currentMoveString[2] - AAA;
9142 toY = currentMoveString[3] - ONE;
9143 promoChar = currentMoveString[4];
9148 if (appData.debugMode)
9149 fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
9150 sprintf(move, _("Ambiguous move: %d.%s%s"),
9151 (forwardMostMove / 2) + 1,
9152 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9153 DisplayError(move, 0);
9158 case ImpossibleMove:
9159 if (appData.debugMode)
9160 fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
9161 sprintf(move, _("Illegal move: %d.%s%s"),
9162 (forwardMostMove / 2) + 1,
9163 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9164 DisplayError(move, 0);
9170 if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
9171 DrawPosition(FALSE, boards[currentMove]);
9172 DisplayBothClocks();
9173 if (!appData.matchMode) // [HGM] PV info: routine tests if empty
9174 DisplayComment(currentMove - 1, commentList[currentMove]);
9176 (void) StopLoadGameTimer();
9178 cmailOldMove = forwardMostMove;
9181 /* currentMoveString is set as a side-effect of yylex */
9182 strcat(currentMoveString, "\n");
9183 strcpy(moveList[forwardMostMove], currentMoveString);
9185 thinkOutput[0] = NULLCHAR;
9186 MakeMove(fromX, fromY, toX, toY, promoChar);
9187 currentMove = forwardMostMove;
9192 /* Load the nth game from the given file */
9194 LoadGameFromFile(filename, n, title, useList)
9198 /*Boolean*/ int useList;
9203 if (strcmp(filename, "-") == 0) {
9207 f = fopen(filename, "rb");
9209 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9210 DisplayError(buf, errno);
9214 if (fseek(f, 0, 0) == -1) {
9215 /* f is not seekable; probably a pipe */
9218 if (useList && n == 0) {
9219 int error = GameListBuild(f);
9221 DisplayError(_("Cannot build game list"), error);
9222 } else if (!ListEmpty(&gameList) &&
9223 ((ListGame *) gameList.tailPred)->number > 1) {
9224 GameListPopUp(f, title);
9231 return LoadGame(f, n, title, FALSE);
9236 MakeRegisteredMove()
9238 int fromX, fromY, toX, toY;
9240 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
9241 switch (cmailMoveType[lastLoadGameNumber - 1]) {
9244 if (appData.debugMode)
9245 fprintf(debugFP, "Restoring %s for game %d\n",
9246 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
9248 thinkOutput[0] = NULLCHAR;
9249 strcpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1]);
9250 fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
9251 fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
9252 toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
9253 toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
9254 promoChar = cmailMove[lastLoadGameNumber - 1][4];
9255 MakeMove(fromX, fromY, toX, toY, promoChar);
9256 ShowMove(fromX, fromY, toX, toY);
9258 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9265 if (WhiteOnMove(currentMove)) {
9266 GameEnds(BlackWins, "Black mates", GE_PLAYER);
9268 GameEnds(WhiteWins, "White mates", GE_PLAYER);
9273 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
9280 if (WhiteOnMove(currentMove)) {
9281 GameEnds(BlackWins, "White resigns", GE_PLAYER);
9283 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
9288 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
9299 /* Wrapper around LoadGame for use when a Cmail message is loaded */
9301 CmailLoadGame(f, gameNumber, title, useList)
9309 if (gameNumber > nCmailGames) {
9310 DisplayError(_("No more games in this message"), 0);
9313 if (f == lastLoadGameFP) {
9314 int offset = gameNumber - lastLoadGameNumber;
9316 cmailMsg[0] = NULLCHAR;
9317 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
9318 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
9319 nCmailMovesRegistered--;
9321 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
9322 if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
9323 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
9326 if (! RegisterMove()) return FALSE;
9330 retVal = LoadGame(f, gameNumber, title, useList);
9332 /* Make move registered during previous look at this game, if any */
9333 MakeRegisteredMove();
9335 if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
9336 commentList[currentMove]
9337 = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
9338 DisplayComment(currentMove - 1, commentList[currentMove]);
9344 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
9349 int gameNumber = lastLoadGameNumber + offset;
9350 if (lastLoadGameFP == NULL) {
9351 DisplayError(_("No game has been loaded yet"), 0);
9354 if (gameNumber <= 0) {
9355 DisplayError(_("Can't back up any further"), 0);
9358 if (cmailMsgLoaded) {
9359 return CmailLoadGame(lastLoadGameFP, gameNumber,
9360 lastLoadGameTitle, lastLoadGameUseList);
9362 return LoadGame(lastLoadGameFP, gameNumber,
9363 lastLoadGameTitle, lastLoadGameUseList);
9369 /* Load the nth game from open file f */
9371 LoadGame(f, gameNumber, title, useList)
9379 int gn = gameNumber;
9380 ListGame *lg = NULL;
9383 GameMode oldGameMode;
9384 VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
9386 if (appData.debugMode)
9387 fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
9389 if (gameMode == Training )
9390 SetTrainingModeOff();
9392 oldGameMode = gameMode;
9393 if (gameMode != BeginningOfGame) {
9398 if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
9399 fclose(lastLoadGameFP);
9403 lg = (ListGame *) ListElem(&gameList, gameNumber-1);
9406 fseek(f, lg->offset, 0);
9407 GameListHighlight(gameNumber);
9411 DisplayError(_("Game number out of range"), 0);
9416 if (fseek(f, 0, 0) == -1) {
9417 if (f == lastLoadGameFP ?
9418 gameNumber == lastLoadGameNumber + 1 :
9422 DisplayError(_("Can't seek on game file"), 0);
9428 lastLoadGameNumber = gameNumber;
9429 strcpy(lastLoadGameTitle, title);
9430 lastLoadGameUseList = useList;
9434 if (lg && lg->gameInfo.white && lg->gameInfo.black) {
9435 snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
9436 lg->gameInfo.black);
9438 } else if (*title != NULLCHAR) {
9439 if (gameNumber > 1) {
9440 sprintf(buf, "%s %d", title, gameNumber);
9443 DisplayTitle(title);
9447 if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
9448 gameMode = PlayFromGameFile;
9452 currentMove = forwardMostMove = backwardMostMove = 0;
9453 CopyBoard(boards[0], initialPosition);
9457 * Skip the first gn-1 games in the file.
9458 * Also skip over anything that precedes an identifiable
9459 * start of game marker, to avoid being confused by
9460 * garbage at the start of the file. Currently
9461 * recognized start of game markers are the move number "1",
9462 * the pattern "gnuchess .* game", the pattern
9463 * "^[#;%] [^ ]* game file", and a PGN tag block.
9464 * A game that starts with one of the latter two patterns
9465 * will also have a move number 1, possibly
9466 * following a position diagram.
9467 * 5-4-02: Let's try being more lenient and allowing a game to
9468 * start with an unnumbered move. Does that break anything?
9470 cm = lastLoadGameStart = (ChessMove) 0;
9472 yyboardindex = forwardMostMove;
9473 cm = (ChessMove) yylex();
9476 if (cmailMsgLoaded) {
9477 nCmailGames = CMAIL_MAX_GAMES - gn;
9480 DisplayError(_("Game not found in file"), 0);
9487 lastLoadGameStart = cm;
9491 switch (lastLoadGameStart) {
9498 gn--; /* count this game */
9499 lastLoadGameStart = cm;
9508 switch (lastLoadGameStart) {
9513 gn--; /* count this game */
9514 lastLoadGameStart = cm;
9517 lastLoadGameStart = cm; /* game counted already */
9525 yyboardindex = forwardMostMove;
9526 cm = (ChessMove) yylex();
9527 } while (cm == PGNTag || cm == Comment);
9534 if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
9535 if ( cmailResult[CMAIL_MAX_GAMES - gn - 1]
9536 != CMAIL_OLD_RESULT) {
9538 cmailResult[ CMAIL_MAX_GAMES
9539 - gn - 1] = CMAIL_OLD_RESULT;
9545 /* Only a NormalMove can be at the start of a game
9546 * without a position diagram. */
9547 if (lastLoadGameStart == (ChessMove) 0) {
9549 lastLoadGameStart = MoveNumberOne;
9558 if (appData.debugMode)
9559 fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
9561 if (cm == XBoardGame) {
9562 /* Skip any header junk before position diagram and/or move 1 */
9564 yyboardindex = forwardMostMove;
9565 cm = (ChessMove) yylex();
9567 if (cm == (ChessMove) 0 ||
9568 cm == GNUChessGame || cm == XBoardGame) {
9569 /* Empty game; pretend end-of-file and handle later */
9574 if (cm == MoveNumberOne || cm == PositionDiagram ||
9575 cm == PGNTag || cm == Comment)
9578 } else if (cm == GNUChessGame) {
9579 if (gameInfo.event != NULL) {
9580 free(gameInfo.event);
9582 gameInfo.event = StrSave(yy_text);
9585 startedFromSetupPosition = FALSE;
9586 while (cm == PGNTag) {
9587 if (appData.debugMode)
9588 fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
9589 err = ParsePGNTag(yy_text, &gameInfo);
9590 if (!err) numPGNTags++;
9592 /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
9593 if(gameInfo.variant != oldVariant) {
9594 startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
9596 oldVariant = gameInfo.variant;
9597 if (appData.debugMode)
9598 fprintf(debugFP, "New variant %d\n", (int) oldVariant);
9602 if (gameInfo.fen != NULL) {
9603 Board initial_position;
9604 startedFromSetupPosition = TRUE;
9605 if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
9607 DisplayError(_("Bad FEN position in file"), 0);
9610 CopyBoard(boards[0], initial_position);
9611 if (blackPlaysFirst) {
9612 currentMove = forwardMostMove = backwardMostMove = 1;
9613 CopyBoard(boards[1], initial_position);
9614 strcpy(moveList[0], "");
9615 strcpy(parseList[0], "");
9616 timeRemaining[0][1] = whiteTimeRemaining;
9617 timeRemaining[1][1] = blackTimeRemaining;
9618 if (commentList[0] != NULL) {
9619 commentList[1] = commentList[0];
9620 commentList[0] = NULL;
9623 currentMove = forwardMostMove = backwardMostMove = 0;
9625 /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
9627 initialRulePlies = FENrulePlies;
9628 for( i=0; i< nrCastlingRights; i++ )
9629 initialRights[i] = initial_position[CASTLING][i];
9631 yyboardindex = forwardMostMove;
9633 gameInfo.fen = NULL;
9636 yyboardindex = forwardMostMove;
9637 cm = (ChessMove) yylex();
9639 /* Handle comments interspersed among the tags */
9640 while (cm == Comment) {
9642 if (appData.debugMode)
9643 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9645 AppendComment(currentMove, p, FALSE);
9646 yyboardindex = forwardMostMove;
9647 cm = (ChessMove) yylex();
9651 /* don't rely on existence of Event tag since if game was
9652 * pasted from clipboard the Event tag may not exist
9654 if (numPGNTags > 0){
9656 if (gameInfo.variant == VariantNormal) {
9657 gameInfo.variant = StringToVariant(gameInfo.event);
9660 if( appData.autoDisplayTags ) {
9661 tags = PGNTags(&gameInfo);
9662 TagsPopUp(tags, CmailMsg());
9667 /* Make something up, but don't display it now */
9672 if (cm == PositionDiagram) {
9675 Board initial_position;
9677 if (appData.debugMode)
9678 fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
9680 if (!startedFromSetupPosition) {
9682 for (i = BOARD_HEIGHT - 1; i >= 0; i--)
9683 for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
9693 initial_position[i][j++] = CharToPiece(*p);
9696 while (*p == ' ' || *p == '\t' ||
9697 *p == '\n' || *p == '\r') p++;
9699 if (strncmp(p, "black", strlen("black"))==0)
9700 blackPlaysFirst = TRUE;
9702 blackPlaysFirst = FALSE;
9703 startedFromSetupPosition = TRUE;
9705 CopyBoard(boards[0], initial_position);
9706 if (blackPlaysFirst) {
9707 currentMove = forwardMostMove = backwardMostMove = 1;
9708 CopyBoard(boards[1], initial_position);
9709 strcpy(moveList[0], "");
9710 strcpy(parseList[0], "");
9711 timeRemaining[0][1] = whiteTimeRemaining;
9712 timeRemaining[1][1] = blackTimeRemaining;
9713 if (commentList[0] != NULL) {
9714 commentList[1] = commentList[0];
9715 commentList[0] = NULL;
9718 currentMove = forwardMostMove = backwardMostMove = 0;
9721 yyboardindex = forwardMostMove;
9722 cm = (ChessMove) yylex();
9725 if (first.pr == NoProc) {
9726 StartChessProgram(&first);
9728 InitChessProgram(&first, FALSE);
9729 SendToProgram("force\n", &first);
9730 if (startedFromSetupPosition) {
9731 SendBoard(&first, forwardMostMove);
9732 if (appData.debugMode) {
9733 fprintf(debugFP, "Load Game\n");
9735 DisplayBothClocks();
9738 /* [HGM] server: flag to write setup moves in broadcast file as one */
9739 loadFlag = appData.suppressLoadMoves;
9741 while (cm == Comment) {
9743 if (appData.debugMode)
9744 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9746 AppendComment(currentMove, p, FALSE);
9747 yyboardindex = forwardMostMove;
9748 cm = (ChessMove) yylex();
9751 if ((cm == (ChessMove) 0 && lastLoadGameStart != (ChessMove) 0) ||
9752 cm == WhiteWins || cm == BlackWins ||
9753 cm == GameIsDrawn || cm == GameUnfinished) {
9754 DisplayMessage("", _("No moves in game"));
9755 if (cmailMsgLoaded) {
9756 if (appData.debugMode)
9757 fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
9761 DrawPosition(FALSE, boards[currentMove]);
9762 DisplayBothClocks();
9763 gameMode = EditGame;
9770 // [HGM] PV info: routine tests if comment empty
9771 if (!matchMode && (pausing || appData.timeDelay != 0)) {
9772 DisplayComment(currentMove - 1, commentList[currentMove]);
9774 if (!matchMode && appData.timeDelay != 0)
9775 DrawPosition(FALSE, boards[currentMove]);
9777 if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
9778 programStats.ok_to_send = 1;
9781 /* if the first token after the PGN tags is a move
9782 * and not move number 1, retrieve it from the parser
9784 if (cm != MoveNumberOne)
9785 LoadGameOneMove(cm);
9787 /* load the remaining moves from the file */
9788 while (LoadGameOneMove((ChessMove)0)) {
9789 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
9790 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
9793 /* rewind to the start of the game */
9794 currentMove = backwardMostMove;
9796 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
9798 if (oldGameMode == AnalyzeFile ||
9799 oldGameMode == AnalyzeMode) {
9803 if (matchMode || appData.timeDelay == 0) {
9805 gameMode = EditGame;
9807 } else if (appData.timeDelay > 0) {
9811 if (appData.debugMode)
9812 fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
9814 loadFlag = 0; /* [HGM] true game starts */
9818 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
9820 ReloadPosition(offset)
9823 int positionNumber = lastLoadPositionNumber + offset;
9824 if (lastLoadPositionFP == NULL) {
9825 DisplayError(_("No position has been loaded yet"), 0);
9828 if (positionNumber <= 0) {
9829 DisplayError(_("Can't back up any further"), 0);
9832 return LoadPosition(lastLoadPositionFP, positionNumber,
9833 lastLoadPositionTitle);
9836 /* Load the nth position from the given file */
9838 LoadPositionFromFile(filename, n, title)
9846 if (strcmp(filename, "-") == 0) {
9847 return LoadPosition(stdin, n, "stdin");
9849 f = fopen(filename, "rb");
9851 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9852 DisplayError(buf, errno);
9855 return LoadPosition(f, n, title);
9860 /* Load the nth position from the given open file, and close it */
9862 LoadPosition(f, positionNumber, title)
9867 char *p, line[MSG_SIZ];
9868 Board initial_position;
9869 int i, j, fenMode, pn;
9871 if (gameMode == Training )
9872 SetTrainingModeOff();
9874 if (gameMode != BeginningOfGame) {
9877 if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
9878 fclose(lastLoadPositionFP);
9880 if (positionNumber == 0) positionNumber = 1;
9881 lastLoadPositionFP = f;
9882 lastLoadPositionNumber = positionNumber;
9883 strcpy(lastLoadPositionTitle, title);
9884 if (first.pr == NoProc) {
9885 StartChessProgram(&first);
9886 InitChessProgram(&first, FALSE);
9888 pn = positionNumber;
9889 if (positionNumber < 0) {
9890 /* Negative position number means to seek to that byte offset */
9891 if (fseek(f, -positionNumber, 0) == -1) {
9892 DisplayError(_("Can't seek on position file"), 0);
9897 if (fseek(f, 0, 0) == -1) {
9898 if (f == lastLoadPositionFP ?
9899 positionNumber == lastLoadPositionNumber + 1 :
9900 positionNumber == 1) {
9903 DisplayError(_("Can't seek on position file"), 0);
9908 /* See if this file is FEN or old-style xboard */
9909 if (fgets(line, MSG_SIZ, f) == NULL) {
9910 DisplayError(_("Position not found in file"), 0);
9913 // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
9914 fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
9917 if (fenMode || line[0] == '#') pn--;
9919 /* skip positions before number pn */
9920 if (fgets(line, MSG_SIZ, f) == NULL) {
9922 DisplayError(_("Position not found in file"), 0);
9925 if (fenMode || line[0] == '#') pn--;
9930 if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
9931 DisplayError(_("Bad FEN position in file"), 0);
9935 (void) fgets(line, MSG_SIZ, f);
9936 (void) fgets(line, MSG_SIZ, f);
9938 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
9939 (void) fgets(line, MSG_SIZ, f);
9940 for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
9943 initial_position[i][j++] = CharToPiece(*p);
9947 blackPlaysFirst = FALSE;
9949 (void) fgets(line, MSG_SIZ, f);
9950 if (strncmp(line, "black", strlen("black"))==0)
9951 blackPlaysFirst = TRUE;
9954 startedFromSetupPosition = TRUE;
9956 SendToProgram("force\n", &first);
9957 CopyBoard(boards[0], initial_position);
9958 if (blackPlaysFirst) {
9959 currentMove = forwardMostMove = backwardMostMove = 1;
9960 strcpy(moveList[0], "");
9961 strcpy(parseList[0], "");
9962 CopyBoard(boards[1], initial_position);
9963 DisplayMessage("", _("Black to play"));
9965 currentMove = forwardMostMove = backwardMostMove = 0;
9966 DisplayMessage("", _("White to play"));
9968 initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
9969 SendBoard(&first, forwardMostMove);
9970 if (appData.debugMode) {
9972 for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
9973 for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
9974 fprintf(debugFP, "Load Position\n");
9977 if (positionNumber > 1) {
9978 sprintf(line, "%s %d", title, positionNumber);
9981 DisplayTitle(title);
9983 gameMode = EditGame;
9986 timeRemaining[0][1] = whiteTimeRemaining;
9987 timeRemaining[1][1] = blackTimeRemaining;
9988 DrawPosition(FALSE, boards[currentMove]);
9995 CopyPlayerNameIntoFileName(dest, src)
9998 while (*src != NULLCHAR && *src != ',') {
10003 *(*dest)++ = *src++;
10008 char *DefaultFileName(ext)
10011 static char def[MSG_SIZ];
10014 if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
10016 CopyPlayerNameIntoFileName(&p, gameInfo.white);
10018 CopyPlayerNameIntoFileName(&p, gameInfo.black);
10027 /* Save the current game to the given file */
10029 SaveGameToFile(filename, append)
10036 if (strcmp(filename, "-") == 0) {
10037 return SaveGame(stdout, 0, NULL);
10039 f = fopen(filename, append ? "a" : "w");
10041 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10042 DisplayError(buf, errno);
10045 return SaveGame(f, 0, NULL);
10054 static char buf[MSG_SIZ];
10057 p = strchr(str, ' ');
10058 if (p == NULL) return str;
10059 strncpy(buf, str, p - str);
10060 buf[p - str] = NULLCHAR;
10064 #define PGN_MAX_LINE 75
10066 #define PGN_SIDE_WHITE 0
10067 #define PGN_SIDE_BLACK 1
10070 static int FindFirstMoveOutOfBook( int side )
10074 if( backwardMostMove == 0 && ! startedFromSetupPosition) {
10075 int index = backwardMostMove;
10076 int has_book_hit = 0;
10078 if( (index % 2) != side ) {
10082 while( index < forwardMostMove ) {
10083 /* Check to see if engine is in book */
10084 int depth = pvInfoList[index].depth;
10085 int score = pvInfoList[index].score;
10091 else if( score == 0 && depth == 63 ) {
10092 in_book = 1; /* Zappa */
10094 else if( score == 2 && depth == 99 ) {
10095 in_book = 1; /* Abrok */
10098 has_book_hit += in_book;
10114 void GetOutOfBookInfo( char * buf )
10118 int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10120 oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
10121 oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
10125 if( oob[0] >= 0 || oob[1] >= 0 ) {
10126 for( i=0; i<2; i++ ) {
10130 if( i > 0 && oob[0] >= 0 ) {
10131 strcat( buf, " " );
10134 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
10135 sprintf( buf+strlen(buf), "%s%.2f",
10136 pvInfoList[idx].score >= 0 ? "+" : "",
10137 pvInfoList[idx].score / 100.0 );
10143 /* Save game in PGN style and close the file */
10148 int i, offset, linelen, newblock;
10152 int movelen, numlen, blank;
10153 char move_buffer[100]; /* [AS] Buffer for move+PV info */
10155 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10157 tm = time((time_t *) NULL);
10159 PrintPGNTags(f, &gameInfo);
10161 if (backwardMostMove > 0 || startedFromSetupPosition) {
10162 char *fen = PositionToFEN(backwardMostMove, NULL);
10163 fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
10164 fprintf(f, "\n{--------------\n");
10165 PrintPosition(f, backwardMostMove);
10166 fprintf(f, "--------------}\n");
10170 /* [AS] Out of book annotation */
10171 if( appData.saveOutOfBookInfo ) {
10174 GetOutOfBookInfo( buf );
10176 if( buf[0] != '\0' ) {
10177 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
10184 i = backwardMostMove;
10188 while (i < forwardMostMove) {
10189 /* Print comments preceding this move */
10190 if (commentList[i] != NULL) {
10191 if (linelen > 0) fprintf(f, "\n");
10192 fprintf(f, "%s", commentList[i]);
10197 /* Format move number */
10198 if ((i % 2) == 0) {
10199 sprintf(numtext, "%d.", (i - offset)/2 + 1);
10202 sprintf(numtext, "%d...", (i - offset)/2 + 1);
10204 numtext[0] = NULLCHAR;
10207 numlen = strlen(numtext);
10210 /* Print move number */
10211 blank = linelen > 0 && numlen > 0;
10212 if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
10221 fprintf(f, "%s", numtext);
10225 strcpy(move_buffer, SavePart(parseList[i])); // [HGM] pgn: print move via buffer, so it can be edited
10226 movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
10229 blank = linelen > 0 && movelen > 0;
10230 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
10239 fprintf(f, "%s", move_buffer);
10240 linelen += movelen;
10242 /* [AS] Add PV info if present */
10243 if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
10244 /* [HGM] add time */
10245 char buf[MSG_SIZ]; int seconds;
10247 seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
10249 if( seconds <= 0) buf[0] = 0; else
10250 if( seconds < 30 ) sprintf(buf, " %3.1f%c", seconds/10., 0); else {
10251 seconds = (seconds + 4)/10; // round to full seconds
10252 if( seconds < 60 ) sprintf(buf, " %d%c", seconds, 0); else
10253 sprintf(buf, " %d:%02d%c", seconds/60, seconds%60, 0);
10256 sprintf( move_buffer, "{%s%.2f/%d%s}",
10257 pvInfoList[i].score >= 0 ? "+" : "",
10258 pvInfoList[i].score / 100.0,
10259 pvInfoList[i].depth,
10262 movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
10264 /* Print score/depth */
10265 blank = linelen > 0 && movelen > 0;
10266 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
10275 fprintf(f, "%s", move_buffer);
10276 linelen += movelen;
10282 /* Start a new line */
10283 if (linelen > 0) fprintf(f, "\n");
10285 /* Print comments after last move */
10286 if (commentList[i] != NULL) {
10287 fprintf(f, "%s\n", commentList[i]);
10291 if (gameInfo.resultDetails != NULL &&
10292 gameInfo.resultDetails[0] != NULLCHAR) {
10293 fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
10294 PGNResult(gameInfo.result));
10296 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
10300 lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
10304 /* Save game in old style and close the file */
10306 SaveGameOldStyle(f)
10312 tm = time((time_t *) NULL);
10314 fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
10317 if (backwardMostMove > 0 || startedFromSetupPosition) {
10318 fprintf(f, "\n[--------------\n");
10319 PrintPosition(f, backwardMostMove);
10320 fprintf(f, "--------------]\n");
10325 i = backwardMostMove;
10326 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10328 while (i < forwardMostMove) {
10329 if (commentList[i] != NULL) {
10330 fprintf(f, "[%s]\n", commentList[i]);
10333 if ((i % 2) == 1) {
10334 fprintf(f, "%d. ... %s\n", (i - offset)/2 + 1, parseList[i]);
10337 fprintf(f, "%d. %s ", (i - offset)/2 + 1, parseList[i]);
10339 if (commentList[i] != NULL) {
10343 if (i >= forwardMostMove) {
10347 fprintf(f, "%s\n", parseList[i]);
10352 if (commentList[i] != NULL) {
10353 fprintf(f, "[%s]\n", commentList[i]);
10356 /* This isn't really the old style, but it's close enough */
10357 if (gameInfo.resultDetails != NULL &&
10358 gameInfo.resultDetails[0] != NULLCHAR) {
10359 fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
10360 gameInfo.resultDetails);
10362 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
10369 /* Save the current game to open file f and close the file */
10371 SaveGame(f, dummy, dummy2)
10376 if (gameMode == EditPosition) EditPositionDone(TRUE);
10377 lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
10378 if (appData.oldSaveStyle)
10379 return SaveGameOldStyle(f);
10381 return SaveGamePGN(f);
10384 /* Save the current position to the given file */
10386 SavePositionToFile(filename)
10392 if (strcmp(filename, "-") == 0) {
10393 return SavePosition(stdout, 0, NULL);
10395 f = fopen(filename, "a");
10397 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10398 DisplayError(buf, errno);
10401 SavePosition(f, 0, NULL);
10407 /* Save the current position to the given open file and close the file */
10409 SavePosition(f, dummy, dummy2)
10417 if (gameMode == EditPosition) EditPositionDone(TRUE);
10418 if (appData.oldSaveStyle) {
10419 tm = time((time_t *) NULL);
10421 fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
10423 fprintf(f, "[--------------\n");
10424 PrintPosition(f, currentMove);
10425 fprintf(f, "--------------]\n");
10427 fen = PositionToFEN(currentMove, NULL);
10428 fprintf(f, "%s\n", fen);
10436 ReloadCmailMsgEvent(unregister)
10440 static char *inFilename = NULL;
10441 static char *outFilename;
10443 struct stat inbuf, outbuf;
10446 /* Any registered moves are unregistered if unregister is set, */
10447 /* i.e. invoked by the signal handler */
10449 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
10450 cmailMoveRegistered[i] = FALSE;
10451 if (cmailCommentList[i] != NULL) {
10452 free(cmailCommentList[i]);
10453 cmailCommentList[i] = NULL;
10456 nCmailMovesRegistered = 0;
10459 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
10460 cmailResult[i] = CMAIL_NOT_RESULT;
10464 if (inFilename == NULL) {
10465 /* Because the filenames are static they only get malloced once */
10466 /* and they never get freed */
10467 inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
10468 sprintf(inFilename, "%s.game.in", appData.cmailGameName);
10470 outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
10471 sprintf(outFilename, "%s.out", appData.cmailGameName);
10474 status = stat(outFilename, &outbuf);
10476 cmailMailedMove = FALSE;
10478 status = stat(inFilename, &inbuf);
10479 cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
10482 /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
10483 counts the games, notes how each one terminated, etc.
10485 It would be nice to remove this kludge and instead gather all
10486 the information while building the game list. (And to keep it
10487 in the game list nodes instead of having a bunch of fixed-size
10488 parallel arrays.) Note this will require getting each game's
10489 termination from the PGN tags, as the game list builder does
10490 not process the game moves. --mann
10492 cmailMsgLoaded = TRUE;
10493 LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
10495 /* Load first game in the file or popup game menu */
10496 LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
10498 #endif /* !WIN32 */
10506 char string[MSG_SIZ];
10508 if ( cmailMailedMove
10509 || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
10510 return TRUE; /* Allow free viewing */
10513 /* Unregister move to ensure that we don't leave RegisterMove */
10514 /* with the move registered when the conditions for registering no */
10516 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
10517 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
10518 nCmailMovesRegistered --;
10520 if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
10522 free(cmailCommentList[lastLoadGameNumber - 1]);
10523 cmailCommentList[lastLoadGameNumber - 1] = NULL;
10527 if (cmailOldMove == -1) {
10528 DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
10532 if (currentMove > cmailOldMove + 1) {
10533 DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
10537 if (currentMove < cmailOldMove) {
10538 DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
10542 if (forwardMostMove > currentMove) {
10543 /* Silently truncate extra moves */
10547 if ( (currentMove == cmailOldMove + 1)
10548 || ( (currentMove == cmailOldMove)
10549 && ( (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
10550 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
10551 if (gameInfo.result != GameUnfinished) {
10552 cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
10555 if (commentList[currentMove] != NULL) {
10556 cmailCommentList[lastLoadGameNumber - 1]
10557 = StrSave(commentList[currentMove]);
10559 strcpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1]);
10561 if (appData.debugMode)
10562 fprintf(debugFP, "Saving %s for game %d\n",
10563 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
10566 "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
10568 f = fopen(string, "w");
10569 if (appData.oldSaveStyle) {
10570 SaveGameOldStyle(f); /* also closes the file */
10572 sprintf(string, "%s.pos.out", appData.cmailGameName);
10573 f = fopen(string, "w");
10574 SavePosition(f, 0, NULL); /* also closes the file */
10576 fprintf(f, "{--------------\n");
10577 PrintPosition(f, currentMove);
10578 fprintf(f, "--------------}\n\n");
10580 SaveGame(f, 0, NULL); /* also closes the file*/
10583 cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
10584 nCmailMovesRegistered ++;
10585 } else if (nCmailGames == 1) {
10586 DisplayError(_("You have not made a move yet"), 0);
10597 static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
10598 FILE *commandOutput;
10599 char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
10600 int nBytes = 0; /* Suppress warnings on uninitialized variables */
10606 if (! cmailMsgLoaded) {
10607 DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
10611 if (nCmailGames == nCmailResults) {
10612 DisplayError(_("No unfinished games"), 0);
10616 #if CMAIL_PROHIBIT_REMAIL
10617 if (cmailMailedMove) {
10618 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);
10619 DisplayError(msg, 0);
10624 if (! (cmailMailedMove || RegisterMove())) return;
10626 if ( cmailMailedMove
10627 || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
10628 sprintf(string, partCommandString,
10629 appData.debugMode ? " -v" : "", appData.cmailGameName);
10630 commandOutput = popen(string, "r");
10632 if (commandOutput == NULL) {
10633 DisplayError(_("Failed to invoke cmail"), 0);
10635 for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
10636 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
10638 if (nBuffers > 1) {
10639 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
10640 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
10641 nBytes = MSG_SIZ - 1;
10643 (void) memcpy(msg, buffer, nBytes);
10645 *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
10647 if(StrStr(msg, "Mailed cmail message to ") != NULL) {
10648 cmailMailedMove = TRUE; /* Prevent >1 moves */
10651 for (i = 0; i < nCmailGames; i ++) {
10652 if (cmailResult[i] == CMAIL_NOT_RESULT) {
10657 && ( (arcDir = (char *) getenv("CMAIL_ARCDIR"))
10659 sprintf(buffer, "%s/%s.%s.archive",
10661 appData.cmailGameName,
10663 LoadGameFromFile(buffer, 1, buffer, FALSE);
10664 cmailMsgLoaded = FALSE;
10668 DisplayInformation(msg);
10669 pclose(commandOutput);
10672 if ((*cmailMsg) != '\0') {
10673 DisplayInformation(cmailMsg);
10678 #endif /* !WIN32 */
10687 int prependComma = 0;
10689 char string[MSG_SIZ]; /* Space for game-list */
10692 if (!cmailMsgLoaded) return "";
10694 if (cmailMailedMove) {
10695 sprintf(cmailMsg, _("Waiting for reply from opponent\n"));
10697 /* Create a list of games left */
10698 sprintf(string, "[");
10699 for (i = 0; i < nCmailGames; i ++) {
10700 if (! ( cmailMoveRegistered[i]
10701 || (cmailResult[i] == CMAIL_OLD_RESULT))) {
10702 if (prependComma) {
10703 sprintf(number, ",%d", i + 1);
10705 sprintf(number, "%d", i + 1);
10709 strcat(string, number);
10712 strcat(string, "]");
10714 if (nCmailMovesRegistered + nCmailResults == 0) {
10715 switch (nCmailGames) {
10718 _("Still need to make move for game\n"));
10723 _("Still need to make moves for both games\n"));
10728 _("Still need to make moves for all %d games\n"),
10733 switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
10736 _("Still need to make a move for game %s\n"),
10741 if (nCmailResults == nCmailGames) {
10742 sprintf(cmailMsg, _("No unfinished games\n"));
10744 sprintf(cmailMsg, _("Ready to send mail\n"));
10750 _("Still need to make moves for games %s\n"),
10762 if (gameMode == Training)
10763 SetTrainingModeOff();
10766 cmailMsgLoaded = FALSE;
10767 if (appData.icsActive) {
10768 SendToICS(ics_prefix);
10769 SendToICS("refresh\n");
10779 /* Give up on clean exit */
10783 /* Keep trying for clean exit */
10787 if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
10789 if (telnetISR != NULL) {
10790 RemoveInputSource(telnetISR);
10792 if (icsPR != NoProc) {
10793 DestroyChildProcess(icsPR, TRUE);
10796 /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
10797 GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
10799 /* [HGM] crash: the above GameEnds() is a dud if another one was running */
10800 /* make sure this other one finishes before killing it! */
10801 if(endingGame) { int count = 0;
10802 if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
10803 while(endingGame && count++ < 10) DoSleep(1);
10804 if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
10807 /* Kill off chess programs */
10808 if (first.pr != NoProc) {
10811 DoSleep( appData.delayBeforeQuit );
10812 SendToProgram("quit\n", &first);
10813 DoSleep( appData.delayAfterQuit );
10814 DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
10816 if (second.pr != NoProc) {
10817 DoSleep( appData.delayBeforeQuit );
10818 SendToProgram("quit\n", &second);
10819 DoSleep( appData.delayAfterQuit );
10820 DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
10822 if (first.isr != NULL) {
10823 RemoveInputSource(first.isr);
10825 if (second.isr != NULL) {
10826 RemoveInputSource(second.isr);
10829 ShutDownFrontEnd();
10836 if (appData.debugMode)
10837 fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
10841 if (gameMode == MachinePlaysWhite ||
10842 gameMode == MachinePlaysBlack) {
10845 DisplayBothClocks();
10847 if (gameMode == PlayFromGameFile) {
10848 if (appData.timeDelay >= 0)
10849 AutoPlayGameLoop();
10850 } else if (gameMode == IcsExamining && pauseExamInvalid) {
10851 Reset(FALSE, TRUE);
10852 SendToICS(ics_prefix);
10853 SendToICS("refresh\n");
10854 } else if (currentMove < forwardMostMove) {
10855 ForwardInner(forwardMostMove);
10857 pauseExamInvalid = FALSE;
10859 switch (gameMode) {
10863 pauseExamForwardMostMove = forwardMostMove;
10864 pauseExamInvalid = FALSE;
10867 case IcsPlayingWhite:
10868 case IcsPlayingBlack:
10872 case PlayFromGameFile:
10873 (void) StopLoadGameTimer();
10877 case BeginningOfGame:
10878 if (appData.icsActive) return;
10879 /* else fall through */
10880 case MachinePlaysWhite:
10881 case MachinePlaysBlack:
10882 case TwoMachinesPlay:
10883 if (forwardMostMove == 0)
10884 return; /* don't pause if no one has moved */
10885 if ((gameMode == MachinePlaysWhite &&
10886 !WhiteOnMove(forwardMostMove)) ||
10887 (gameMode == MachinePlaysBlack &&
10888 WhiteOnMove(forwardMostMove))) {
10901 char title[MSG_SIZ];
10903 if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
10904 strcpy(title, _("Edit comment"));
10906 sprintf(title, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
10907 WhiteOnMove(currentMove - 1) ? " " : ".. ",
10908 parseList[currentMove - 1]);
10911 EditCommentPopUp(currentMove, title, commentList[currentMove]);
10918 char *tags = PGNTags(&gameInfo);
10919 EditTagsPopUp(tags);
10926 if (appData.noChessProgram || gameMode == AnalyzeMode)
10929 if (gameMode != AnalyzeFile) {
10930 if (!appData.icsEngineAnalyze) {
10932 if (gameMode != EditGame) return;
10934 ResurrectChessProgram();
10935 SendToProgram("analyze\n", &first);
10936 first.analyzing = TRUE;
10937 /*first.maybeThinking = TRUE;*/
10938 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
10939 EngineOutputPopUp();
10941 if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
10946 StartAnalysisClock();
10947 GetTimeMark(&lastNodeCountTime);
10954 if (appData.noChessProgram || gameMode == AnalyzeFile)
10957 if (gameMode != AnalyzeMode) {
10959 if (gameMode != EditGame) return;
10960 ResurrectChessProgram();
10961 SendToProgram("analyze\n", &first);
10962 first.analyzing = TRUE;
10963 /*first.maybeThinking = TRUE;*/
10964 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
10965 EngineOutputPopUp();
10967 gameMode = AnalyzeFile;
10972 StartAnalysisClock();
10973 GetTimeMark(&lastNodeCountTime);
10978 MachineWhiteEvent()
10981 char *bookHit = NULL;
10983 if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
10987 if (gameMode == PlayFromGameFile ||
10988 gameMode == TwoMachinesPlay ||
10989 gameMode == Training ||
10990 gameMode == AnalyzeMode ||
10991 gameMode == EndOfGame)
10994 if (gameMode == EditPosition)
10995 EditPositionDone(TRUE);
10997 if (!WhiteOnMove(currentMove)) {
10998 DisplayError(_("It is not White's turn"), 0);
11002 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
11005 if (gameMode == EditGame || gameMode == AnalyzeMode ||
11006 gameMode == AnalyzeFile)
11009 ResurrectChessProgram(); /* in case it isn't running */
11010 if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
11011 gameMode = MachinePlaysWhite;
11014 gameMode = MachinePlaysWhite;
11018 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
11020 if (first.sendName) {
11021 sprintf(buf, "name %s\n", gameInfo.black);
11022 SendToProgram(buf, &first);
11024 if (first.sendTime) {
11025 if (first.useColors) {
11026 SendToProgram("black\n", &first); /*gnu kludge*/
11028 SendTimeRemaining(&first, TRUE);
11030 if (first.useColors) {
11031 SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
11033 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
11034 SetMachineThinkingEnables();
11035 first.maybeThinking = TRUE;
11039 if (appData.autoFlipView && !flipView) {
11040 flipView = !flipView;
11041 DrawPosition(FALSE, NULL);
11042 DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;
11045 if(bookHit) { // [HGM] book: simulate book reply
11046 static char bookMove[MSG_SIZ]; // a bit generous?
11048 programStats.nodes = programStats.depth = programStats.time =
11049 programStats.score = programStats.got_only_move = 0;
11050 sprintf(programStats.movelist, "%s (xbook)", bookHit);
11052 strcpy(bookMove, "move ");
11053 strcat(bookMove, bookHit);
11054 HandleMachineMove(bookMove, &first);
11059 MachineBlackEvent()
11062 char *bookHit = NULL;
11064 if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
11068 if (gameMode == PlayFromGameFile ||
11069 gameMode == TwoMachinesPlay ||
11070 gameMode == Training ||
11071 gameMode == AnalyzeMode ||
11072 gameMode == EndOfGame)
11075 if (gameMode == EditPosition)
11076 EditPositionDone(TRUE);
11078 if (WhiteOnMove(currentMove)) {
11079 DisplayError(_("It is not Black's turn"), 0);
11083 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
11086 if (gameMode == EditGame || gameMode == AnalyzeMode ||
11087 gameMode == AnalyzeFile)
11090 ResurrectChessProgram(); /* in case it isn't running */
11091 gameMode = MachinePlaysBlack;
11095 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
11097 if (first.sendName) {
11098 sprintf(buf, "name %s\n", gameInfo.white);
11099 SendToProgram(buf, &first);
11101 if (first.sendTime) {
11102 if (first.useColors) {
11103 SendToProgram("white\n", &first); /*gnu kludge*/
11105 SendTimeRemaining(&first, FALSE);
11107 if (first.useColors) {
11108 SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
11110 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
11111 SetMachineThinkingEnables();
11112 first.maybeThinking = TRUE;
11115 if (appData.autoFlipView && flipView) {
11116 flipView = !flipView;
11117 DrawPosition(FALSE, NULL);
11118 DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;
11120 if(bookHit) { // [HGM] book: simulate book reply
11121 static char bookMove[MSG_SIZ]; // a bit generous?
11123 programStats.nodes = programStats.depth = programStats.time =
11124 programStats.score = programStats.got_only_move = 0;
11125 sprintf(programStats.movelist, "%s (xbook)", bookHit);
11127 strcpy(bookMove, "move ");
11128 strcat(bookMove, bookHit);
11129 HandleMachineMove(bookMove, &first);
11135 DisplayTwoMachinesTitle()
11138 if (appData.matchGames > 0) {
11139 if (first.twoMachinesColor[0] == 'w') {
11140 sprintf(buf, "%s vs. %s (%d-%d-%d)",
11141 gameInfo.white, gameInfo.black,
11142 first.matchWins, second.matchWins,
11143 matchGame - 1 - (first.matchWins + second.matchWins));
11145 sprintf(buf, "%s vs. %s (%d-%d-%d)",
11146 gameInfo.white, gameInfo.black,
11147 second.matchWins, first.matchWins,
11148 matchGame - 1 - (first.matchWins + second.matchWins));
11151 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
11157 TwoMachinesEvent P((void))
11161 ChessProgramState *onmove;
11162 char *bookHit = NULL;
11164 if (appData.noChessProgram) return;
11166 switch (gameMode) {
11167 case TwoMachinesPlay:
11169 case MachinePlaysWhite:
11170 case MachinePlaysBlack:
11171 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
11172 DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
11176 case BeginningOfGame:
11177 case PlayFromGameFile:
11180 if (gameMode != EditGame) return;
11183 EditPositionDone(TRUE);
11194 // forwardMostMove = currentMove;
11195 TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
11196 ResurrectChessProgram(); /* in case first program isn't running */
11198 if (second.pr == NULL) {
11199 StartChessProgram(&second);
11200 if (second.protocolVersion == 1) {
11201 TwoMachinesEventIfReady();
11203 /* kludge: allow timeout for initial "feature" command */
11205 DisplayMessage("", _("Starting second chess program"));
11206 ScheduleDelayedEvent(TwoMachinesEventIfReady, FEATURE_TIMEOUT);
11210 DisplayMessage("", "");
11211 InitChessProgram(&second, FALSE);
11212 SendToProgram("force\n", &second);
11213 if (startedFromSetupPosition) {
11214 SendBoard(&second, backwardMostMove);
11215 if (appData.debugMode) {
11216 fprintf(debugFP, "Two Machines\n");
11219 for (i = backwardMostMove; i < forwardMostMove; i++) {
11220 SendMoveToProgram(i, &second);
11223 gameMode = TwoMachinesPlay;
11227 DisplayTwoMachinesTitle();
11229 if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
11235 SendToProgram(first.computerString, &first);
11236 if (first.sendName) {
11237 sprintf(buf, "name %s\n", second.tidy);
11238 SendToProgram(buf, &first);
11240 SendToProgram(second.computerString, &second);
11241 if (second.sendName) {
11242 sprintf(buf, "name %s\n", first.tidy);
11243 SendToProgram(buf, &second);
11247 if (!first.sendTime || !second.sendTime) {
11248 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11249 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11251 if (onmove->sendTime) {
11252 if (onmove->useColors) {
11253 SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
11255 SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
11257 if (onmove->useColors) {
11258 SendToProgram(onmove->twoMachinesColor, onmove);
11260 bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
11261 // SendToProgram("go\n", onmove);
11262 onmove->maybeThinking = TRUE;
11263 SetMachineThinkingEnables();
11267 if(bookHit) { // [HGM] book: simulate book reply
11268 static char bookMove[MSG_SIZ]; // a bit generous?
11270 programStats.nodes = programStats.depth = programStats.time =
11271 programStats.score = programStats.got_only_move = 0;
11272 sprintf(programStats.movelist, "%s (xbook)", bookHit);
11274 strcpy(bookMove, "move ");
11275 strcat(bookMove, bookHit);
11276 savedMessage = bookMove; // args for deferred call
11277 savedState = onmove;
11278 ScheduleDelayedEvent(DeferredBookMove, 1);
11285 if (gameMode == Training) {
11286 SetTrainingModeOff();
11287 gameMode = PlayFromGameFile;
11288 DisplayMessage("", _("Training mode off"));
11290 gameMode = Training;
11291 animateTraining = appData.animate;
11293 /* make sure we are not already at the end of the game */
11294 if (currentMove < forwardMostMove) {
11295 SetTrainingModeOn();
11296 DisplayMessage("", _("Training mode on"));
11298 gameMode = PlayFromGameFile;
11299 DisplayError(_("Already at end of game"), 0);
11308 if (!appData.icsActive) return;
11309 switch (gameMode) {
11310 case IcsPlayingWhite:
11311 case IcsPlayingBlack:
11314 case BeginningOfGame:
11322 EditPositionDone(TRUE);
11335 gameMode = IcsIdle;
11346 switch (gameMode) {
11348 SetTrainingModeOff();
11350 case MachinePlaysWhite:
11351 case MachinePlaysBlack:
11352 case BeginningOfGame:
11353 SendToProgram("force\n", &first);
11354 SetUserThinkingEnables();
11356 case PlayFromGameFile:
11357 (void) StopLoadGameTimer();
11358 if (gameFileFP != NULL) {
11363 EditPositionDone(TRUE);
11368 SendToProgram("force\n", &first);
11370 case TwoMachinesPlay:
11371 GameEnds((ChessMove) 0, NULL, GE_PLAYER);
11372 ResurrectChessProgram();
11373 SetUserThinkingEnables();
11376 ResurrectChessProgram();
11378 case IcsPlayingBlack:
11379 case IcsPlayingWhite:
11380 DisplayError(_("Warning: You are still playing a game"), 0);
11383 DisplayError(_("Warning: You are still observing a game"), 0);
11386 DisplayError(_("Warning: You are still examining a game"), 0);
11397 first.offeredDraw = second.offeredDraw = 0;
11399 if (gameMode == PlayFromGameFile) {
11400 whiteTimeRemaining = timeRemaining[0][currentMove];
11401 blackTimeRemaining = timeRemaining[1][currentMove];
11405 if (gameMode == MachinePlaysWhite ||
11406 gameMode == MachinePlaysBlack ||
11407 gameMode == TwoMachinesPlay ||
11408 gameMode == EndOfGame) {
11409 i = forwardMostMove;
11410 while (i > currentMove) {
11411 SendToProgram("undo\n", &first);
11414 whiteTimeRemaining = timeRemaining[0][currentMove];
11415 blackTimeRemaining = timeRemaining[1][currentMove];
11416 DisplayBothClocks();
11417 if (whiteFlag || blackFlag) {
11418 whiteFlag = blackFlag = 0;
11423 gameMode = EditGame;
11430 EditPositionEvent()
11432 if (gameMode == EditPosition) {
11438 if (gameMode != EditGame) return;
11440 gameMode = EditPosition;
11443 if (currentMove > 0)
11444 CopyBoard(boards[0], boards[currentMove]);
11446 blackPlaysFirst = !WhiteOnMove(currentMove);
11448 currentMove = forwardMostMove = backwardMostMove = 0;
11449 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11456 /* [DM] icsEngineAnalyze - possible call from other functions */
11457 if (appData.icsEngineAnalyze) {
11458 appData.icsEngineAnalyze = FALSE;
11460 DisplayMessage("",_("Close ICS engine analyze..."));
11462 if (first.analysisSupport && first.analyzing) {
11463 SendToProgram("exit\n", &first);
11464 first.analyzing = FALSE;
11466 thinkOutput[0] = NULLCHAR;
11470 EditPositionDone(Boolean fakeRights)
11472 int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
11474 startedFromSetupPosition = TRUE;
11475 InitChessProgram(&first, FALSE);
11476 if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
11477 boards[0][EP_STATUS] = EP_NONE;
11478 boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
11479 if(boards[0][0][BOARD_WIDTH>>1] == king) {
11480 boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : NoRights;
11481 boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
11482 } else boards[0][CASTLING][2] = NoRights;
11483 if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
11484 boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : NoRights;
11485 boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
11486 } else boards[0][CASTLING][5] = NoRights;
11488 SendToProgram("force\n", &first);
11489 if (blackPlaysFirst) {
11490 strcpy(moveList[0], "");
11491 strcpy(parseList[0], "");
11492 currentMove = forwardMostMove = backwardMostMove = 1;
11493 CopyBoard(boards[1], boards[0]);
11495 currentMove = forwardMostMove = backwardMostMove = 0;
11497 SendBoard(&first, forwardMostMove);
11498 if (appData.debugMode) {
11499 fprintf(debugFP, "EditPosDone\n");
11502 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11503 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11504 gameMode = EditGame;
11506 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11507 ClearHighlights(); /* [AS] */
11510 /* Pause for `ms' milliseconds */
11511 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
11521 } while (SubtractTimeMarks(&m2, &m1) < ms);
11524 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
11526 SendMultiLineToICS(buf)
11529 char temp[MSG_SIZ+1], *p;
11536 strncpy(temp, buf, len);
11541 if (*p == '\n' || *p == '\r')
11546 strcat(temp, "\n");
11548 SendToPlayer(temp, strlen(temp));
11552 SetWhiteToPlayEvent()
11554 if (gameMode == EditPosition) {
11555 blackPlaysFirst = FALSE;
11556 DisplayBothClocks(); /* works because currentMove is 0 */
11557 } else if (gameMode == IcsExamining) {
11558 SendToICS(ics_prefix);
11559 SendToICS("tomove white\n");
11564 SetBlackToPlayEvent()
11566 if (gameMode == EditPosition) {
11567 blackPlaysFirst = TRUE;
11568 currentMove = 1; /* kludge */
11569 DisplayBothClocks();
11571 } else if (gameMode == IcsExamining) {
11572 SendToICS(ics_prefix);
11573 SendToICS("tomove black\n");
11578 EditPositionMenuEvent(selection, x, y)
11579 ChessSquare selection;
11583 ChessSquare piece = boards[0][y][x];
11585 if (gameMode != EditPosition && gameMode != IcsExamining) return;
11587 switch (selection) {
11589 if (gameMode == IcsExamining && ics_type == ICS_FICS) {
11590 SendToICS(ics_prefix);
11591 SendToICS("bsetup clear\n");
11592 } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
11593 SendToICS(ics_prefix);
11594 SendToICS("clearboard\n");
11596 for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
11597 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
11598 for (y = 0; y < BOARD_HEIGHT; y++) {
11599 if (gameMode == IcsExamining) {
11600 if (boards[currentMove][y][x] != EmptySquare) {
11601 sprintf(buf, "%sx@%c%c\n", ics_prefix,
11606 boards[0][y][x] = p;
11611 if (gameMode == EditPosition) {
11612 DrawPosition(FALSE, boards[0]);
11617 SetWhiteToPlayEvent();
11621 SetBlackToPlayEvent();
11625 if (gameMode == IcsExamining) {
11626 if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
11627 sprintf(buf, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
11630 if(x < BOARD_LEFT || x >= BOARD_RGHT) {
11631 if(x == BOARD_LEFT-2) {
11632 if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
11633 boards[0][y][1] = 0;
11635 if(x == BOARD_RGHT+1) {
11636 if(y >= gameInfo.holdingsSize) break;
11637 boards[0][y][BOARD_WIDTH-2] = 0;
11640 boards[0][y][x] = EmptySquare;
11641 DrawPosition(FALSE, boards[0]);
11646 if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
11647 piece >= (int)BlackPawn && piece < (int)BlackMan ) {
11648 selection = (ChessSquare) (PROMOTED piece);
11649 } else if(piece == EmptySquare) selection = WhiteSilver;
11650 else selection = (ChessSquare)((int)piece - 1);
11654 if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
11655 piece > (int)BlackMan && piece <= (int)BlackKing ) {
11656 selection = (ChessSquare) (DEMOTED piece);
11657 } else if(piece == EmptySquare) selection = BlackSilver;
11658 else selection = (ChessSquare)((int)piece + 1);
11663 if(gameInfo.variant == VariantShatranj ||
11664 gameInfo.variant == VariantXiangqi ||
11665 gameInfo.variant == VariantCourier ||
11666 gameInfo.variant == VariantMakruk )
11667 selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
11672 if(gameInfo.variant == VariantXiangqi)
11673 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
11674 if(gameInfo.variant == VariantKnightmate)
11675 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
11678 if (gameMode == IcsExamining) {
11679 if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
11680 sprintf(buf, "%s%c@%c%c\n", ics_prefix,
11681 PieceToChar(selection), AAA + x, ONE + y);
11684 if(x < BOARD_LEFT || x >= BOARD_RGHT) {
11686 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
11687 n = PieceToNumber(selection - BlackPawn);
11688 if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
11689 boards[0][BOARD_HEIGHT-1-n][0] = selection;
11690 boards[0][BOARD_HEIGHT-1-n][1]++;
11692 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
11693 n = PieceToNumber(selection);
11694 if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
11695 boards[0][n][BOARD_WIDTH-1] = selection;
11696 boards[0][n][BOARD_WIDTH-2]++;
11699 boards[0][y][x] = selection;
11700 DrawPosition(TRUE, boards[0]);
11708 DropMenuEvent(selection, x, y)
11709 ChessSquare selection;
11712 ChessMove moveType;
11714 switch (gameMode) {
11715 case IcsPlayingWhite:
11716 case MachinePlaysBlack:
11717 if (!WhiteOnMove(currentMove)) {
11718 DisplayMoveError(_("It is Black's turn"));
11721 moveType = WhiteDrop;
11723 case IcsPlayingBlack:
11724 case MachinePlaysWhite:
11725 if (WhiteOnMove(currentMove)) {
11726 DisplayMoveError(_("It is White's turn"));
11729 moveType = BlackDrop;
11732 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
11738 if (moveType == BlackDrop && selection < BlackPawn) {
11739 selection = (ChessSquare) ((int) selection
11740 + (int) BlackPawn - (int) WhitePawn);
11742 if (boards[currentMove][y][x] != EmptySquare) {
11743 DisplayMoveError(_("That square is occupied"));
11747 FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
11753 /* Accept a pending offer of any kind from opponent */
11755 if (appData.icsActive) {
11756 SendToICS(ics_prefix);
11757 SendToICS("accept\n");
11758 } else if (cmailMsgLoaded) {
11759 if (currentMove == cmailOldMove &&
11760 commentList[cmailOldMove] != NULL &&
11761 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11762 "Black offers a draw" : "White offers a draw")) {
11764 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11765 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
11767 DisplayError(_("There is no pending offer on this move"), 0);
11768 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11771 /* Not used for offers from chess program */
11778 /* Decline a pending offer of any kind from opponent */
11780 if (appData.icsActive) {
11781 SendToICS(ics_prefix);
11782 SendToICS("decline\n");
11783 } else if (cmailMsgLoaded) {
11784 if (currentMove == cmailOldMove &&
11785 commentList[cmailOldMove] != NULL &&
11786 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11787 "Black offers a draw" : "White offers a draw")) {
11789 AppendComment(cmailOldMove, "Draw declined", TRUE);
11790 DisplayComment(cmailOldMove - 1, "Draw declined");
11793 DisplayError(_("There is no pending offer on this move"), 0);
11796 /* Not used for offers from chess program */
11803 /* Issue ICS rematch command */
11804 if (appData.icsActive) {
11805 SendToICS(ics_prefix);
11806 SendToICS("rematch\n");
11813 /* Call your opponent's flag (claim a win on time) */
11814 if (appData.icsActive) {
11815 SendToICS(ics_prefix);
11816 SendToICS("flag\n");
11818 switch (gameMode) {
11821 case MachinePlaysWhite:
11824 GameEnds(GameIsDrawn, "Both players ran out of time",
11827 GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
11829 DisplayError(_("Your opponent is not out of time"), 0);
11832 case MachinePlaysBlack:
11835 GameEnds(GameIsDrawn, "Both players ran out of time",
11838 GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
11840 DisplayError(_("Your opponent is not out of time"), 0);
11850 /* Offer draw or accept pending draw offer from opponent */
11852 if (appData.icsActive) {
11853 /* Note: tournament rules require draw offers to be
11854 made after you make your move but before you punch
11855 your clock. Currently ICS doesn't let you do that;
11856 instead, you immediately punch your clock after making
11857 a move, but you can offer a draw at any time. */
11859 SendToICS(ics_prefix);
11860 SendToICS("draw\n");
11861 userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
11862 } else if (cmailMsgLoaded) {
11863 if (currentMove == cmailOldMove &&
11864 commentList[cmailOldMove] != NULL &&
11865 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11866 "Black offers a draw" : "White offers a draw")) {
11867 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11868 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
11869 } else if (currentMove == cmailOldMove + 1) {
11870 char *offer = WhiteOnMove(cmailOldMove) ?
11871 "White offers a draw" : "Black offers a draw";
11872 AppendComment(currentMove, offer, TRUE);
11873 DisplayComment(currentMove - 1, offer);
11874 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
11876 DisplayError(_("You must make your move before offering a draw"), 0);
11877 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11879 } else if (first.offeredDraw) {
11880 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
11882 if (first.sendDrawOffers) {
11883 SendToProgram("draw\n", &first);
11884 userOfferedDraw = TRUE;
11892 /* Offer Adjourn or accept pending Adjourn offer from opponent */
11894 if (appData.icsActive) {
11895 SendToICS(ics_prefix);
11896 SendToICS("adjourn\n");
11898 /* Currently GNU Chess doesn't offer or accept Adjourns */
11906 /* Offer Abort or accept pending Abort offer from opponent */
11908 if (appData.icsActive) {
11909 SendToICS(ics_prefix);
11910 SendToICS("abort\n");
11912 GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
11919 /* Resign. You can do this even if it's not your turn. */
11921 if (appData.icsActive) {
11922 SendToICS(ics_prefix);
11923 SendToICS("resign\n");
11925 switch (gameMode) {
11926 case MachinePlaysWhite:
11927 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11929 case MachinePlaysBlack:
11930 GameEnds(BlackWins, "White resigns", GE_PLAYER);
11933 if (cmailMsgLoaded) {
11935 if (WhiteOnMove(cmailOldMove)) {
11936 GameEnds(BlackWins, "White resigns", GE_PLAYER);
11938 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11940 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
11951 StopObservingEvent()
11953 /* Stop observing current games */
11954 SendToICS(ics_prefix);
11955 SendToICS("unobserve\n");
11959 StopExaminingEvent()
11961 /* Stop observing current game */
11962 SendToICS(ics_prefix);
11963 SendToICS("unexamine\n");
11967 ForwardInner(target)
11972 if (appData.debugMode)
11973 fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
11974 target, currentMove, forwardMostMove);
11976 if (gameMode == EditPosition)
11979 if (gameMode == PlayFromGameFile && !pausing)
11982 if (gameMode == IcsExamining && pausing)
11983 limit = pauseExamForwardMostMove;
11985 limit = forwardMostMove;
11987 if (target > limit) target = limit;
11989 if (target > 0 && moveList[target - 1][0]) {
11990 int fromX, fromY, toX, toY;
11991 toX = moveList[target - 1][2] - AAA;
11992 toY = moveList[target - 1][3] - ONE;
11993 if (moveList[target - 1][1] == '@') {
11994 if (appData.highlightLastMove) {
11995 SetHighlights(-1, -1, toX, toY);
11998 fromX = moveList[target - 1][0] - AAA;
11999 fromY = moveList[target - 1][1] - ONE;
12000 if (target == currentMove + 1) {
12001 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
12003 if (appData.highlightLastMove) {
12004 SetHighlights(fromX, fromY, toX, toY);
12008 if (gameMode == EditGame || gameMode == AnalyzeMode ||
12009 gameMode == Training || gameMode == PlayFromGameFile ||
12010 gameMode == AnalyzeFile) {
12011 while (currentMove < target) {
12012 SendMoveToProgram(currentMove++, &first);
12015 currentMove = target;
12018 if (gameMode == EditGame || gameMode == EndOfGame) {
12019 whiteTimeRemaining = timeRemaining[0][currentMove];
12020 blackTimeRemaining = timeRemaining[1][currentMove];
12022 DisplayBothClocks();
12023 DisplayMove(currentMove - 1);
12024 DrawPosition(FALSE, boards[currentMove]);
12025 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
12026 if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
12027 DisplayComment(currentMove - 1, commentList[currentMove]);
12035 if (gameMode == IcsExamining && !pausing) {
12036 SendToICS(ics_prefix);
12037 SendToICS("forward\n");
12039 ForwardInner(currentMove + 1);
12046 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12047 /* to optimze, we temporarily turn off analysis mode while we feed
12048 * the remaining moves to the engine. Otherwise we get analysis output
12051 if (first.analysisSupport) {
12052 SendToProgram("exit\nforce\n", &first);
12053 first.analyzing = FALSE;
12057 if (gameMode == IcsExamining && !pausing) {
12058 SendToICS(ics_prefix);
12059 SendToICS("forward 999999\n");
12061 ForwardInner(forwardMostMove);
12064 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12065 /* we have fed all the moves, so reactivate analysis mode */
12066 SendToProgram("analyze\n", &first);
12067 first.analyzing = TRUE;
12068 /*first.maybeThinking = TRUE;*/
12069 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12074 BackwardInner(target)
12077 int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
12079 if (appData.debugMode)
12080 fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
12081 target, currentMove, forwardMostMove);
12083 if (gameMode == EditPosition) return;
12084 if (currentMove <= backwardMostMove) {
12086 DrawPosition(full_redraw, boards[currentMove]);
12089 if (gameMode == PlayFromGameFile && !pausing)
12092 if (moveList[target][0]) {
12093 int fromX, fromY, toX, toY;
12094 toX = moveList[target][2] - AAA;
12095 toY = moveList[target][3] - ONE;
12096 if (moveList[target][1] == '@') {
12097 if (appData.highlightLastMove) {
12098 SetHighlights(-1, -1, toX, toY);
12101 fromX = moveList[target][0] - AAA;
12102 fromY = moveList[target][1] - ONE;
12103 if (target == currentMove - 1) {
12104 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
12106 if (appData.highlightLastMove) {
12107 SetHighlights(fromX, fromY, toX, toY);
12111 if (gameMode == EditGame || gameMode==AnalyzeMode ||
12112 gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
12113 while (currentMove > target) {
12114 SendToProgram("undo\n", &first);
12118 currentMove = target;
12121 if (gameMode == EditGame || gameMode == EndOfGame) {
12122 whiteTimeRemaining = timeRemaining[0][currentMove];
12123 blackTimeRemaining = timeRemaining[1][currentMove];
12125 DisplayBothClocks();
12126 DisplayMove(currentMove - 1);
12127 DrawPosition(full_redraw, boards[currentMove]);
12128 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
12129 // [HGM] PV info: routine tests if comment empty
12130 DisplayComment(currentMove - 1, commentList[currentMove]);
12136 if (gameMode == IcsExamining && !pausing) {
12137 SendToICS(ics_prefix);
12138 SendToICS("backward\n");
12140 BackwardInner(currentMove - 1);
12147 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12148 /* to optimize, we temporarily turn off analysis mode while we undo
12149 * all the moves. Otherwise we get analysis output after each undo.
12151 if (first.analysisSupport) {
12152 SendToProgram("exit\nforce\n", &first);
12153 first.analyzing = FALSE;
12157 if (gameMode == IcsExamining && !pausing) {
12158 SendToICS(ics_prefix);
12159 SendToICS("backward 999999\n");
12161 BackwardInner(backwardMostMove);
12164 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12165 /* we have fed all the moves, so reactivate analysis mode */
12166 SendToProgram("analyze\n", &first);
12167 first.analyzing = TRUE;
12168 /*first.maybeThinking = TRUE;*/
12169 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12176 if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
12177 if (to >= forwardMostMove) to = forwardMostMove;
12178 if (to <= backwardMostMove) to = backwardMostMove;
12179 if (to < currentMove) {
12189 if(PopTail(TRUE)) { // [HGM] vari: restore old game tail
12192 if (gameMode != IcsExamining) {
12193 DisplayError(_("You are not examining a game"), 0);
12197 DisplayError(_("You can't revert while pausing"), 0);
12200 SendToICS(ics_prefix);
12201 SendToICS("revert\n");
12207 switch (gameMode) {
12208 case MachinePlaysWhite:
12209 case MachinePlaysBlack:
12210 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
12211 DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
12214 if (forwardMostMove < 2) return;
12215 currentMove = forwardMostMove = forwardMostMove - 2;
12216 whiteTimeRemaining = timeRemaining[0][currentMove];
12217 blackTimeRemaining = timeRemaining[1][currentMove];
12218 DisplayBothClocks();
12219 DisplayMove(currentMove - 1);
12220 ClearHighlights();/*!! could figure this out*/
12221 DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
12222 SendToProgram("remove\n", &first);
12223 /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
12226 case BeginningOfGame:
12230 case IcsPlayingWhite:
12231 case IcsPlayingBlack:
12232 if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
12233 SendToICS(ics_prefix);
12234 SendToICS("takeback 2\n");
12236 SendToICS(ics_prefix);
12237 SendToICS("takeback 1\n");
12246 ChessProgramState *cps;
12248 switch (gameMode) {
12249 case MachinePlaysWhite:
12250 if (!WhiteOnMove(forwardMostMove)) {
12251 DisplayError(_("It is your turn"), 0);
12256 case MachinePlaysBlack:
12257 if (WhiteOnMove(forwardMostMove)) {
12258 DisplayError(_("It is your turn"), 0);
12263 case TwoMachinesPlay:
12264 if (WhiteOnMove(forwardMostMove) ==
12265 (first.twoMachinesColor[0] == 'w')) {
12271 case BeginningOfGame:
12275 SendToProgram("?\n", cps);
12279 TruncateGameEvent()
12282 if (gameMode != EditGame) return;
12289 CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
12290 if (forwardMostMove > currentMove) {
12291 if (gameInfo.resultDetails != NULL) {
12292 free(gameInfo.resultDetails);
12293 gameInfo.resultDetails = NULL;
12294 gameInfo.result = GameUnfinished;
12296 forwardMostMove = currentMove;
12297 HistorySet(parseList, backwardMostMove, forwardMostMove,
12305 if (appData.noChessProgram) return;
12306 switch (gameMode) {
12307 case MachinePlaysWhite:
12308 if (WhiteOnMove(forwardMostMove)) {
12309 DisplayError(_("Wait until your turn"), 0);
12313 case BeginningOfGame:
12314 case MachinePlaysBlack:
12315 if (!WhiteOnMove(forwardMostMove)) {
12316 DisplayError(_("Wait until your turn"), 0);
12321 DisplayError(_("No hint available"), 0);
12324 SendToProgram("hint\n", &first);
12325 hintRequested = TRUE;
12331 if (appData.noChessProgram) return;
12332 switch (gameMode) {
12333 case MachinePlaysWhite:
12334 if (WhiteOnMove(forwardMostMove)) {
12335 DisplayError(_("Wait until your turn"), 0);
12339 case BeginningOfGame:
12340 case MachinePlaysBlack:
12341 if (!WhiteOnMove(forwardMostMove)) {
12342 DisplayError(_("Wait until your turn"), 0);
12347 EditPositionDone(TRUE);
12349 case TwoMachinesPlay:
12354 SendToProgram("bk\n", &first);
12355 bookOutput[0] = NULLCHAR;
12356 bookRequested = TRUE;
12362 char *tags = PGNTags(&gameInfo);
12363 TagsPopUp(tags, CmailMsg());
12367 /* end button procedures */
12370 PrintPosition(fp, move)
12376 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
12377 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
12378 char c = PieceToChar(boards[move][i][j]);
12379 fputc(c == 'x' ? '.' : c, fp);
12380 fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
12383 if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
12384 fprintf(fp, "white to play\n");
12386 fprintf(fp, "black to play\n");
12393 if (gameInfo.white != NULL) {
12394 fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
12400 /* Find last component of program's own name, using some heuristics */
12402 TidyProgramName(prog, host, buf)
12403 char *prog, *host, buf[MSG_SIZ];
12406 int local = (strcmp(host, "localhost") == 0);
12407 while (!local && (p = strchr(prog, ';')) != NULL) {
12409 while (*p == ' ') p++;
12412 if (*prog == '"' || *prog == '\'') {
12413 q = strchr(prog + 1, *prog);
12415 q = strchr(prog, ' ');
12417 if (q == NULL) q = prog + strlen(prog);
12419 while (p >= prog && *p != '/' && *p != '\\') p--;
12421 if(p == prog && *p == '"') p++;
12422 if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
12423 memcpy(buf, p, q - p);
12424 buf[q - p] = NULLCHAR;
12432 TimeControlTagValue()
12435 if (!appData.clockMode) {
12437 } else if (movesPerSession > 0) {
12438 sprintf(buf, "%d/%ld", movesPerSession, timeControl/1000);
12439 } else if (timeIncrement == 0) {
12440 sprintf(buf, "%ld", timeControl/1000);
12442 sprintf(buf, "%ld+%ld", timeControl/1000, timeIncrement/1000);
12444 return StrSave(buf);
12450 /* This routine is used only for certain modes */
12451 VariantClass v = gameInfo.variant;
12452 ChessMove r = GameUnfinished;
12455 if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
12456 r = gameInfo.result;
12457 p = gameInfo.resultDetails;
12458 gameInfo.resultDetails = NULL;
12460 ClearGameInfo(&gameInfo);
12461 gameInfo.variant = v;
12463 switch (gameMode) {
12464 case MachinePlaysWhite:
12465 gameInfo.event = StrSave( appData.pgnEventHeader );
12466 gameInfo.site = StrSave(HostName());
12467 gameInfo.date = PGNDate();
12468 gameInfo.round = StrSave("-");
12469 gameInfo.white = StrSave(first.tidy);
12470 gameInfo.black = StrSave(UserName());
12471 gameInfo.timeControl = TimeControlTagValue();
12474 case MachinePlaysBlack:
12475 gameInfo.event = StrSave( appData.pgnEventHeader );
12476 gameInfo.site = StrSave(HostName());
12477 gameInfo.date = PGNDate();
12478 gameInfo.round = StrSave("-");
12479 gameInfo.white = StrSave(UserName());
12480 gameInfo.black = StrSave(first.tidy);
12481 gameInfo.timeControl = TimeControlTagValue();
12484 case TwoMachinesPlay:
12485 gameInfo.event = StrSave( appData.pgnEventHeader );
12486 gameInfo.site = StrSave(HostName());
12487 gameInfo.date = PGNDate();
12488 if (matchGame > 0) {
12490 sprintf(buf, "%d", matchGame);
12491 gameInfo.round = StrSave(buf);
12493 gameInfo.round = StrSave("-");
12495 if (first.twoMachinesColor[0] == 'w') {
12496 gameInfo.white = StrSave(first.tidy);
12497 gameInfo.black = StrSave(second.tidy);
12499 gameInfo.white = StrSave(second.tidy);
12500 gameInfo.black = StrSave(first.tidy);
12502 gameInfo.timeControl = TimeControlTagValue();
12506 gameInfo.event = StrSave("Edited game");
12507 gameInfo.site = StrSave(HostName());
12508 gameInfo.date = PGNDate();
12509 gameInfo.round = StrSave("-");
12510 gameInfo.white = StrSave("-");
12511 gameInfo.black = StrSave("-");
12512 gameInfo.result = r;
12513 gameInfo.resultDetails = p;
12517 gameInfo.event = StrSave("Edited position");
12518 gameInfo.site = StrSave(HostName());
12519 gameInfo.date = PGNDate();
12520 gameInfo.round = StrSave("-");
12521 gameInfo.white = StrSave("-");
12522 gameInfo.black = StrSave("-");
12525 case IcsPlayingWhite:
12526 case IcsPlayingBlack:
12531 case PlayFromGameFile:
12532 gameInfo.event = StrSave("Game from non-PGN file");
12533 gameInfo.site = StrSave(HostName());
12534 gameInfo.date = PGNDate();
12535 gameInfo.round = StrSave("-");
12536 gameInfo.white = StrSave("?");
12537 gameInfo.black = StrSave("?");
12546 ReplaceComment(index, text)
12552 while (*text == '\n') text++;
12553 len = strlen(text);
12554 while (len > 0 && text[len - 1] == '\n') len--;
12556 if (commentList[index] != NULL)
12557 free(commentList[index]);
12560 commentList[index] = NULL;
12563 if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
12564 *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
12565 *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
12566 commentList[index] = (char *) malloc(len + 2);
12567 strncpy(commentList[index], text, len);
12568 commentList[index][len] = '\n';
12569 commentList[index][len + 1] = NULLCHAR;
12571 // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
12573 commentList[index] = (char *) malloc(len + 6);
12574 strcpy(commentList[index], "{\n");
12575 strncpy(commentList[index]+2, text, len);
12576 commentList[index][len+2] = NULLCHAR;
12577 while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
12578 strcat(commentList[index], "\n}\n");
12592 if (ch == '\r') continue;
12594 } while (ch != '\0');
12598 AppendComment(index, text, addBraces)
12601 Boolean addBraces; // [HGM] braces: tells if we should add {}
12606 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
12607 text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
12610 while (*text == '\n') text++;
12611 len = strlen(text);
12612 while (len > 0 && text[len - 1] == '\n') len--;
12614 if (len == 0) return;
12616 if (commentList[index] != NULL) {
12617 old = commentList[index];
12618 oldlen = strlen(old);
12619 while(commentList[index][oldlen-1] == '\n')
12620 commentList[index][--oldlen] = NULLCHAR;
12621 commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
12622 strcpy(commentList[index], old);
12624 // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
12625 if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces)) {
12626 if(addBraces) addBraces = FALSE; else { text++; len--; }
12627 while (*text == '\n') { text++; len--; }
12628 commentList[index][--oldlen] = NULLCHAR;
12630 if(addBraces) strcat(commentList[index], "\n{\n");
12631 else strcat(commentList[index], "\n");
12632 strcat(commentList[index], text);
12633 if(addBraces) strcat(commentList[index], "\n}\n");
12634 else strcat(commentList[index], "\n");
12636 commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
12638 strcpy(commentList[index], "{\n");
12639 else commentList[index][0] = NULLCHAR;
12640 strcat(commentList[index], text);
12641 strcat(commentList[index], "\n");
12642 if(addBraces) strcat(commentList[index], "}\n");
12646 static char * FindStr( char * text, char * sub_text )
12648 char * result = strstr( text, sub_text );
12650 if( result != NULL ) {
12651 result += strlen( sub_text );
12657 /* [AS] Try to extract PV info from PGN comment */
12658 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
12659 char *GetInfoFromComment( int index, char * text )
12663 if( text != NULL && index > 0 ) {
12666 int time = -1, sec = 0, deci;
12667 char * s_eval = FindStr( text, "[%eval " );
12668 char * s_emt = FindStr( text, "[%emt " );
12670 if( s_eval != NULL || s_emt != NULL ) {
12674 if( s_eval != NULL ) {
12675 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
12679 if( delim != ']' ) {
12684 if( s_emt != NULL ) {
12689 /* We expect something like: [+|-]nnn.nn/dd */
12692 if(*text != '{') return text; // [HGM] braces: must be normal comment
12694 sep = strchr( text, '/' );
12695 if( sep == NULL || sep < (text+4) ) {
12699 time = -1; sec = -1; deci = -1;
12700 if( sscanf( text+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
12701 sscanf( text+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
12702 sscanf( text+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
12703 sscanf( text+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3 ) {
12707 if( score_lo < 0 || score_lo >= 100 ) {
12711 if(sec >= 0) time = 600*time + 10*sec; else
12712 if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
12714 score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
12716 /* [HGM] PV time: now locate end of PV info */
12717 while( *++sep >= '0' && *sep <= '9'); // strip depth
12719 while( *++sep >= '0' && *sep <= '9'); // strip time
12721 while( *++sep >= '0' && *sep <= '9'); // strip seconds
12723 while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
12724 while(*sep == ' ') sep++;
12735 pvInfoList[index-1].depth = depth;
12736 pvInfoList[index-1].score = score;
12737 pvInfoList[index-1].time = 10*time; // centi-sec
12738 if(*sep == '}') *sep = 0; else *--sep = '{';
12744 SendToProgram(message, cps)
12746 ChessProgramState *cps;
12748 int count, outCount, error;
12751 if (cps->pr == NULL) return;
12754 if (appData.debugMode) {
12757 fprintf(debugFP, "%ld >%-6s: %s",
12758 SubtractTimeMarks(&now, &programStartTime),
12759 cps->which, message);
12762 count = strlen(message);
12763 outCount = OutputToProcess(cps->pr, message, count, &error);
12764 if (outCount < count && !exiting
12765 && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
12766 sprintf(buf, _("Error writing to %s chess program"), cps->which);
12767 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
12768 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
12769 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
12770 sprintf(buf, "%s program exits in draw position (%s)", cps->which, cps->program);
12772 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
12774 gameInfo.resultDetails = StrSave(buf);
12776 if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
12781 ReceiveFromProgram(isr, closure, message, count, error)
12782 InputSourceRef isr;
12790 ChessProgramState *cps = (ChessProgramState *)closure;
12792 if (isr != cps->isr) return; /* Killed intentionally */
12796 _("Error: %s chess program (%s) exited unexpectedly"),
12797 cps->which, cps->program);
12798 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
12799 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
12800 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
12801 sprintf(buf, _("%s program exits in draw position (%s)"), cps->which, cps->program);
12803 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
12805 gameInfo.resultDetails = StrSave(buf);
12807 RemoveInputSource(cps->isr);
12808 if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
12811 _("Error reading from %s chess program (%s)"),
12812 cps->which, cps->program);
12813 RemoveInputSource(cps->isr);
12815 /* [AS] Program is misbehaving badly... kill it */
12816 if( count == -2 ) {
12817 DestroyChildProcess( cps->pr, 9 );
12821 if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
12826 if ((end_str = strchr(message, '\r')) != NULL)
12827 *end_str = NULLCHAR;
12828 if ((end_str = strchr(message, '\n')) != NULL)
12829 *end_str = NULLCHAR;
12831 if (appData.debugMode) {
12832 TimeMark now; int print = 1;
12833 char *quote = ""; char c; int i;
12835 if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
12836 char start = message[0];
12837 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
12838 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
12839 sscanf(message, "move %c", &c)!=1 && sscanf(message, "offer%c", &c)!=1 &&
12840 sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
12841 sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
12842 sscanf(message, "tell%c", &c)!=1 && sscanf(message, "0-1 %c", &c)!=1 &&
12843 sscanf(message, "1-0 %c", &c)!=1 && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
12844 sscanf(message, "pong %c", &c)!=1 && start != '#')
12845 { quote = "# "; print = (appData.engineComments == 2); }
12846 message[0] = start; // restore original message
12850 fprintf(debugFP, "%ld <%-6s: %s%s\n",
12851 SubtractTimeMarks(&now, &programStartTime), cps->which,
12857 /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
12858 if (appData.icsEngineAnalyze) {
12859 if (strstr(message, "whisper") != NULL ||
12860 strstr(message, "kibitz") != NULL ||
12861 strstr(message, "tellics") != NULL) return;
12864 HandleMachineMove(message, cps);
12869 SendTimeControl(cps, mps, tc, inc, sd, st)
12870 ChessProgramState *cps;
12871 int mps, inc, sd, st;
12877 if( timeControl_2 > 0 ) {
12878 if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
12879 tc = timeControl_2;
12882 tc /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
12883 inc /= cps->timeOdds;
12884 st /= cps->timeOdds;
12886 seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
12889 /* Set exact time per move, normally using st command */
12890 if (cps->stKludge) {
12891 /* GNU Chess 4 has no st command; uses level in a nonstandard way */
12893 if (seconds == 0) {
12894 sprintf(buf, "level 1 %d\n", st/60);
12896 sprintf(buf, "level 1 %d:%02d\n", st/60, seconds);
12899 sprintf(buf, "st %d\n", st);
12902 /* Set conventional or incremental time control, using level command */
12903 if (seconds == 0) {
12904 /* Note old gnuchess bug -- minutes:seconds used to not work.
12905 Fixed in later versions, but still avoid :seconds
12906 when seconds is 0. */
12907 sprintf(buf, "level %d %ld %d\n", mps, tc/60000, inc/1000);
12909 sprintf(buf, "level %d %ld:%02d %d\n", mps, tc/60000,
12910 seconds, inc/1000);
12913 SendToProgram(buf, cps);
12915 /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
12916 /* Orthogonally, limit search to given depth */
12918 if (cps->sdKludge) {
12919 sprintf(buf, "depth\n%d\n", sd);
12921 sprintf(buf, "sd %d\n", sd);
12923 SendToProgram(buf, cps);
12926 if(cps->nps > 0) { /* [HGM] nps */
12927 if(cps->supportsNPS == FALSE) cps->nps = -1; // don't use if engine explicitly says not supported!
12929 sprintf(buf, "nps %d\n", cps->nps);
12930 SendToProgram(buf, cps);
12935 ChessProgramState *WhitePlayer()
12936 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
12938 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
12939 gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
12945 SendTimeRemaining(cps, machineWhite)
12946 ChessProgramState *cps;
12947 int /*boolean*/ machineWhite;
12949 char message[MSG_SIZ];
12952 /* Note: this routine must be called when the clocks are stopped
12953 or when they have *just* been set or switched; otherwise
12954 it will be off by the time since the current tick started.
12956 if (machineWhite) {
12957 time = whiteTimeRemaining / 10;
12958 otime = blackTimeRemaining / 10;
12960 time = blackTimeRemaining / 10;
12961 otime = whiteTimeRemaining / 10;
12963 /* [HGM] translate opponent's time by time-odds factor */
12964 otime = (otime * cps->other->timeOdds) / cps->timeOdds;
12965 if (appData.debugMode) {
12966 fprintf(debugFP, "time odds: %f %f \n", cps->timeOdds, cps->other->timeOdds);
12969 if (time <= 0) time = 1;
12970 if (otime <= 0) otime = 1;
12972 sprintf(message, "time %ld\n", time);
12973 SendToProgram(message, cps);
12975 sprintf(message, "otim %ld\n", otime);
12976 SendToProgram(message, cps);
12980 BoolFeature(p, name, loc, cps)
12984 ChessProgramState *cps;
12987 int len = strlen(name);
12989 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
12991 sscanf(*p, "%d", &val);
12993 while (**p && **p != ' ') (*p)++;
12994 sprintf(buf, "accepted %s\n", name);
12995 SendToProgram(buf, cps);
13002 IntFeature(p, name, loc, cps)
13006 ChessProgramState *cps;
13009 int len = strlen(name);
13010 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
13012 sscanf(*p, "%d", loc);
13013 while (**p && **p != ' ') (*p)++;
13014 sprintf(buf, "accepted %s\n", name);
13015 SendToProgram(buf, cps);
13022 StringFeature(p, name, loc, cps)
13026 ChessProgramState *cps;
13029 int len = strlen(name);
13030 if (strncmp((*p), name, len) == 0
13031 && (*p)[len] == '=' && (*p)[len+1] == '\"') {
13033 sscanf(*p, "%[^\"]", loc);
13034 while (**p && **p != '\"') (*p)++;
13035 if (**p == '\"') (*p)++;
13036 sprintf(buf, "accepted %s\n", name);
13037 SendToProgram(buf, cps);
13044 ParseOption(Option *opt, ChessProgramState *cps)
13045 // [HGM] options: process the string that defines an engine option, and determine
13046 // name, type, default value, and allowed value range
13048 char *p, *q, buf[MSG_SIZ];
13049 int n, min = (-1)<<31, max = 1<<31, def;
13051 if(p = strstr(opt->name, " -spin ")) {
13052 if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
13053 if(max < min) max = min; // enforce consistency
13054 if(def < min) def = min;
13055 if(def > max) def = max;
13060 } else if((p = strstr(opt->name, " -slider "))) {
13061 // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
13062 if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
13063 if(max < min) max = min; // enforce consistency
13064 if(def < min) def = min;
13065 if(def > max) def = max;
13069 opt->type = Spin; // Slider;
13070 } else if((p = strstr(opt->name, " -string "))) {
13071 opt->textValue = p+9;
13072 opt->type = TextBox;
13073 } else if((p = strstr(opt->name, " -file "))) {
13074 // for now -file is a synonym for -string, to already provide compatibility with future polyglots
13075 opt->textValue = p+7;
13076 opt->type = TextBox; // FileName;
13077 } else if((p = strstr(opt->name, " -path "))) {
13078 // for now -file is a synonym for -string, to already provide compatibility with future polyglots
13079 opt->textValue = p+7;
13080 opt->type = TextBox; // PathName;
13081 } else if(p = strstr(opt->name, " -check ")) {
13082 if(sscanf(p, " -check %d", &def) < 1) return FALSE;
13083 opt->value = (def != 0);
13084 opt->type = CheckBox;
13085 } else if(p = strstr(opt->name, " -combo ")) {
13086 opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
13087 cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
13088 if(*q == '*') cps->comboList[cps->comboCnt-1]++;
13089 opt->value = n = 0;
13090 while(q = StrStr(q, " /// ")) {
13091 n++; *q = 0; // count choices, and null-terminate each of them
13093 if(*q == '*') { // remember default, which is marked with * prefix
13097 cps->comboList[cps->comboCnt++] = q;
13099 cps->comboList[cps->comboCnt++] = NULL;
13101 opt->type = ComboBox;
13102 } else if(p = strstr(opt->name, " -button")) {
13103 opt->type = Button;
13104 } else if(p = strstr(opt->name, " -save")) {
13105 opt->type = SaveButton;
13106 } else return FALSE;
13107 *p = 0; // terminate option name
13108 // now look if the command-line options define a setting for this engine option.
13109 if(cps->optionSettings && cps->optionSettings[0])
13110 p = strstr(cps->optionSettings, opt->name); else p = NULL;
13111 if(p && (p == cps->optionSettings || p[-1] == ',')) {
13112 sprintf(buf, "option %s", p);
13113 if(p = strstr(buf, ",")) *p = 0;
13115 SendToProgram(buf, cps);
13121 FeatureDone(cps, val)
13122 ChessProgramState* cps;
13125 DelayedEventCallback cb = GetDelayedEvent();
13126 if ((cb == InitBackEnd3 && cps == &first) ||
13127 (cb == TwoMachinesEventIfReady && cps == &second)) {
13128 CancelDelayedEvent();
13129 ScheduleDelayedEvent(cb, val ? 1 : 3600000);
13131 cps->initDone = val;
13134 /* Parse feature command from engine */
13136 ParseFeatures(args, cps)
13138 ChessProgramState *cps;
13146 while (*p == ' ') p++;
13147 if (*p == NULLCHAR) return;
13149 if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
13150 if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
13151 if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
13152 if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
13153 if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
13154 if (BoolFeature(&p, "reuse", &val, cps)) {
13155 /* Engine can disable reuse, but can't enable it if user said no */
13156 if (!val) cps->reuse = FALSE;
13159 if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
13160 if (StringFeature(&p, "myname", &cps->tidy, cps)) {
13161 if (gameMode == TwoMachinesPlay) {
13162 DisplayTwoMachinesTitle();
13168 if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
13169 if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
13170 if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
13171 if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
13172 if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
13173 if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
13174 if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
13175 if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
13176 if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
13177 if (IntFeature(&p, "done", &val, cps)) {
13178 FeatureDone(cps, val);
13181 /* Added by Tord: */
13182 if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
13183 if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
13184 /* End of additions by Tord */
13186 /* [HGM] added features: */
13187 if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
13188 if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
13189 if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
13190 if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
13191 if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
13192 if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
13193 if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
13194 if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
13195 sprintf(buf, "rejected option %s\n", cps->option[--cps->nrOptions].name);
13196 SendToProgram(buf, cps);
13199 if(cps->nrOptions >= MAX_OPTIONS) {
13201 sprintf(buf, "%s engine has too many options\n", cps->which);
13202 DisplayError(buf, 0);
13206 if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
13207 /* End of additions by HGM */
13209 /* unknown feature: complain and skip */
13211 while (*q && *q != '=') q++;
13212 sprintf(buf, "rejected %.*s\n", (int)(q-p), p);
13213 SendToProgram(buf, cps);
13219 while (*p && *p != '\"') p++;
13220 if (*p == '\"') p++;
13222 while (*p && *p != ' ') p++;
13230 PeriodicUpdatesEvent(newState)
13233 if (newState == appData.periodicUpdates)
13236 appData.periodicUpdates=newState;
13238 /* Display type changes, so update it now */
13239 // DisplayAnalysis();
13241 /* Get the ball rolling again... */
13243 AnalysisPeriodicEvent(1);
13244 StartAnalysisClock();
13249 PonderNextMoveEvent(newState)
13252 if (newState == appData.ponderNextMove) return;
13253 if (gameMode == EditPosition) EditPositionDone(TRUE);
13255 SendToProgram("hard\n", &first);
13256 if (gameMode == TwoMachinesPlay) {
13257 SendToProgram("hard\n", &second);
13260 SendToProgram("easy\n", &first);
13261 thinkOutput[0] = NULLCHAR;
13262 if (gameMode == TwoMachinesPlay) {
13263 SendToProgram("easy\n", &second);
13266 appData.ponderNextMove = newState;
13270 NewSettingEvent(option, command, value)
13276 if (gameMode == EditPosition) EditPositionDone(TRUE);
13277 sprintf(buf, "%s%s %d\n", (option ? "option ": ""), command, value);
13278 SendToProgram(buf, &first);
13279 if (gameMode == TwoMachinesPlay) {
13280 SendToProgram(buf, &second);
13285 ShowThinkingEvent()
13286 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
13288 static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
13289 int newState = appData.showThinking
13290 // [HGM] thinking: other features now need thinking output as well
13291 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
13293 if (oldState == newState) return;
13294 oldState = newState;
13295 if (gameMode == EditPosition) EditPositionDone(TRUE);
13297 SendToProgram("post\n", &first);
13298 if (gameMode == TwoMachinesPlay) {
13299 SendToProgram("post\n", &second);
13302 SendToProgram("nopost\n", &first);
13303 thinkOutput[0] = NULLCHAR;
13304 if (gameMode == TwoMachinesPlay) {
13305 SendToProgram("nopost\n", &second);
13308 // appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
13312 AskQuestionEvent(title, question, replyPrefix, which)
13313 char *title; char *question; char *replyPrefix; char *which;
13315 ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
13316 if (pr == NoProc) return;
13317 AskQuestion(title, question, replyPrefix, pr);
13321 DisplayMove(moveNumber)
13324 char message[MSG_SIZ];
13326 char cpThinkOutput[MSG_SIZ];
13328 if(appData.noGUI) return; // [HGM] fast: suppress display of moves
13330 if (moveNumber == forwardMostMove - 1 ||
13331 gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13333 safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput));
13335 if (strchr(cpThinkOutput, '\n')) {
13336 *strchr(cpThinkOutput, '\n') = NULLCHAR;
13339 *cpThinkOutput = NULLCHAR;
13342 /* [AS] Hide thinking from human user */
13343 if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
13344 *cpThinkOutput = NULLCHAR;
13345 if( thinkOutput[0] != NULLCHAR ) {
13348 for( i=0; i<=hiddenThinkOutputState; i++ ) {
13349 cpThinkOutput[i] = '.';
13351 cpThinkOutput[i] = NULLCHAR;
13352 hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
13356 if (moveNumber == forwardMostMove - 1 &&
13357 gameInfo.resultDetails != NULL) {
13358 if (gameInfo.resultDetails[0] == NULLCHAR) {
13359 sprintf(res, " %s", PGNResult(gameInfo.result));
13361 sprintf(res, " {%s} %s",
13362 gameInfo.resultDetails, PGNResult(gameInfo.result));
13368 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
13369 DisplayMessage(res, cpThinkOutput);
13371 sprintf(message, "%d.%s%s%s", moveNumber / 2 + 1,
13372 WhiteOnMove(moveNumber) ? " " : ".. ",
13373 parseList[moveNumber], res);
13374 DisplayMessage(message, cpThinkOutput);
13379 DisplayComment(moveNumber, text)
13383 char title[MSG_SIZ];
13384 char buf[8000]; // comment can be long!
13387 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
13388 strcpy(title, "Comment");
13390 sprintf(title, "Comment on %d.%s%s", moveNumber / 2 + 1,
13391 WhiteOnMove(moveNumber) ? " " : ".. ",
13392 parseList[moveNumber]);
13394 // [HGM] PV info: display PV info together with (or as) comment
13395 if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {
13396 if(text == NULL) text = "";
13397 score = pvInfoList[moveNumber].score;
13398 sprintf(buf, "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,
13399 depth, (pvInfoList[moveNumber].time+50)/100, text);
13402 if (text != NULL && (appData.autoDisplayComment || commentUp))
13403 CommentPopUp(title, text);
13406 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
13407 * might be busy thinking or pondering. It can be omitted if your
13408 * gnuchess is configured to stop thinking immediately on any user
13409 * input. However, that gnuchess feature depends on the FIONREAD
13410 * ioctl, which does not work properly on some flavors of Unix.
13414 ChessProgramState *cps;
13417 if (!cps->useSigint) return;
13418 if (appData.noChessProgram || (cps->pr == NoProc)) return;
13419 switch (gameMode) {
13420 case MachinePlaysWhite:
13421 case MachinePlaysBlack:
13422 case TwoMachinesPlay:
13423 case IcsPlayingWhite:
13424 case IcsPlayingBlack:
13427 /* Skip if we know it isn't thinking */
13428 if (!cps->maybeThinking) return;
13429 if (appData.debugMode)
13430 fprintf(debugFP, "Interrupting %s\n", cps->which);
13431 InterruptChildProcess(cps->pr);
13432 cps->maybeThinking = FALSE;
13437 #endif /*ATTENTION*/
13443 if (whiteTimeRemaining <= 0) {
13446 if (appData.icsActive) {
13447 if (appData.autoCallFlag &&
13448 gameMode == IcsPlayingBlack && !blackFlag) {
13449 SendToICS(ics_prefix);
13450 SendToICS("flag\n");
13454 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
13456 if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
13457 if (appData.autoCallFlag) {
13458 GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
13465 if (blackTimeRemaining <= 0) {
13468 if (appData.icsActive) {
13469 if (appData.autoCallFlag &&
13470 gameMode == IcsPlayingWhite && !whiteFlag) {
13471 SendToICS(ics_prefix);
13472 SendToICS("flag\n");
13476 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
13478 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
13479 if (appData.autoCallFlag) {
13480 GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
13493 if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
13494 gameMode == PlayFromGameFile || forwardMostMove == 0) return;
13497 * add time to clocks when time control is achieved ([HGM] now also used for increment)
13499 if ( !WhiteOnMove(forwardMostMove) )
13500 /* White made time control */
13501 whiteTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
13502 /* [HGM] time odds: correct new time quota for time odds! */
13503 / WhitePlayer()->timeOdds;
13505 /* Black made time control */
13506 blackTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
13507 / WhitePlayer()->other->timeOdds;
13511 DisplayBothClocks()
13513 int wom = gameMode == EditPosition ?
13514 !blackPlaysFirst : WhiteOnMove(currentMove);
13515 DisplayWhiteClock(whiteTimeRemaining, wom);
13516 DisplayBlackClock(blackTimeRemaining, !wom);
13520 /* Timekeeping seems to be a portability nightmare. I think everyone
13521 has ftime(), but I'm really not sure, so I'm including some ifdefs
13522 to use other calls if you don't. Clocks will be less accurate if
13523 you have neither ftime nor gettimeofday.
13526 /* VS 2008 requires the #include outside of the function */
13527 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
13528 #include <sys/timeb.h>
13531 /* Get the current time as a TimeMark */
13536 #if HAVE_GETTIMEOFDAY
13538 struct timeval timeVal;
13539 struct timezone timeZone;
13541 gettimeofday(&timeVal, &timeZone);
13542 tm->sec = (long) timeVal.tv_sec;
13543 tm->ms = (int) (timeVal.tv_usec / 1000L);
13545 #else /*!HAVE_GETTIMEOFDAY*/
13548 // include <sys/timeb.h> / moved to just above start of function
13549 struct timeb timeB;
13552 tm->sec = (long) timeB.time;
13553 tm->ms = (int) timeB.millitm;
13555 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
13556 tm->sec = (long) time(NULL);
13562 /* Return the difference in milliseconds between two
13563 time marks. We assume the difference will fit in a long!
13566 SubtractTimeMarks(tm2, tm1)
13567 TimeMark *tm2, *tm1;
13569 return 1000L*(tm2->sec - tm1->sec) +
13570 (long) (tm2->ms - tm1->ms);
13575 * Code to manage the game clocks.
13577 * In tournament play, black starts the clock and then white makes a move.
13578 * We give the human user a slight advantage if he is playing white---the
13579 * clocks don't run until he makes his first move, so it takes zero time.
13580 * Also, we don't account for network lag, so we could get out of sync
13581 * with GNU Chess's clock -- but then, referees are always right.
13584 static TimeMark tickStartTM;
13585 static long intendedTickLength;
13588 NextTickLength(timeRemaining)
13589 long timeRemaining;
13591 long nominalTickLength, nextTickLength;
13593 if (timeRemaining > 0L && timeRemaining <= 10000L)
13594 nominalTickLength = 100L;
13596 nominalTickLength = 1000L;
13597 nextTickLength = timeRemaining % nominalTickLength;
13598 if (nextTickLength <= 0) nextTickLength += nominalTickLength;
13600 return nextTickLength;
13603 /* Adjust clock one minute up or down */
13605 AdjustClock(Boolean which, int dir)
13607 if(which) blackTimeRemaining += 60000*dir;
13608 else whiteTimeRemaining += 60000*dir;
13609 DisplayBothClocks();
13612 /* Stop clocks and reset to a fresh time control */
13616 (void) StopClockTimer();
13617 if (appData.icsActive) {
13618 whiteTimeRemaining = blackTimeRemaining = 0;
13619 } else if (searchTime) {
13620 whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
13621 blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
13622 } else { /* [HGM] correct new time quote for time odds */
13623 whiteTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->timeOdds;
13624 blackTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->other->timeOdds;
13626 if (whiteFlag || blackFlag) {
13628 whiteFlag = blackFlag = FALSE;
13630 DisplayBothClocks();
13633 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
13635 /* Decrement running clock by amount of time that has passed */
13639 long timeRemaining;
13640 long lastTickLength, fudge;
13643 if (!appData.clockMode) return;
13644 if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
13648 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13650 /* Fudge if we woke up a little too soon */
13651 fudge = intendedTickLength - lastTickLength;
13652 if (fudge < 0 || fudge > FUDGE) fudge = 0;
13654 if (WhiteOnMove(forwardMostMove)) {
13655 if(whiteNPS >= 0) lastTickLength = 0;
13656 timeRemaining = whiteTimeRemaining -= lastTickLength;
13657 DisplayWhiteClock(whiteTimeRemaining - fudge,
13658 WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
13660 if(blackNPS >= 0) lastTickLength = 0;
13661 timeRemaining = blackTimeRemaining -= lastTickLength;
13662 DisplayBlackClock(blackTimeRemaining - fudge,
13663 !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
13666 if (CheckFlags()) return;
13669 intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
13670 StartClockTimer(intendedTickLength);
13672 /* if the time remaining has fallen below the alarm threshold, sound the
13673 * alarm. if the alarm has sounded and (due to a takeback or time control
13674 * with increment) the time remaining has increased to a level above the
13675 * threshold, reset the alarm so it can sound again.
13678 if (appData.icsActive && appData.icsAlarm) {
13680 /* make sure we are dealing with the user's clock */
13681 if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
13682 ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
13685 if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
13686 alarmSounded = FALSE;
13687 } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
13689 alarmSounded = TRUE;
13695 /* A player has just moved, so stop the previously running
13696 clock and (if in clock mode) start the other one.
13697 We redisplay both clocks in case we're in ICS mode, because
13698 ICS gives us an update to both clocks after every move.
13699 Note that this routine is called *after* forwardMostMove
13700 is updated, so the last fractional tick must be subtracted
13701 from the color that is *not* on move now.
13706 long lastTickLength;
13708 int flagged = FALSE;
13712 if (StopClockTimer() && appData.clockMode) {
13713 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13714 if (WhiteOnMove(forwardMostMove)) {
13715 if(blackNPS >= 0) lastTickLength = 0;
13716 blackTimeRemaining -= lastTickLength;
13717 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
13718 // if(pvInfoList[forwardMostMove-1].time == -1)
13719 pvInfoList[forwardMostMove-1].time = // use GUI time
13720 (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
13722 if(whiteNPS >= 0) lastTickLength = 0;
13723 whiteTimeRemaining -= lastTickLength;
13724 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
13725 // if(pvInfoList[forwardMostMove-1].time == -1)
13726 pvInfoList[forwardMostMove-1].time =
13727 (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
13729 flagged = CheckFlags();
13731 CheckTimeControl();
13733 if (flagged || !appData.clockMode) return;
13735 switch (gameMode) {
13736 case MachinePlaysBlack:
13737 case MachinePlaysWhite:
13738 case BeginningOfGame:
13739 if (pausing) return;
13743 case PlayFromGameFile:
13751 if (searchTime) { // [HGM] st: set clock of player that has to move to max time
13752 if(WhiteOnMove(forwardMostMove))
13753 whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
13754 else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
13758 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
13759 whiteTimeRemaining : blackTimeRemaining);
13760 StartClockTimer(intendedTickLength);
13764 /* Stop both clocks */
13768 long lastTickLength;
13771 if (!StopClockTimer()) return;
13772 if (!appData.clockMode) return;
13776 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13777 if (WhiteOnMove(forwardMostMove)) {
13778 if(whiteNPS >= 0) lastTickLength = 0;
13779 whiteTimeRemaining -= lastTickLength;
13780 DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
13782 if(blackNPS >= 0) lastTickLength = 0;
13783 blackTimeRemaining -= lastTickLength;
13784 DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
13789 /* Start clock of player on move. Time may have been reset, so
13790 if clock is already running, stop and restart it. */
13794 (void) StopClockTimer(); /* in case it was running already */
13795 DisplayBothClocks();
13796 if (CheckFlags()) return;
13798 if (!appData.clockMode) return;
13799 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
13801 GetTimeMark(&tickStartTM);
13802 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
13803 whiteTimeRemaining : blackTimeRemaining);
13805 /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
13806 whiteNPS = blackNPS = -1;
13807 if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
13808 || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
13809 whiteNPS = first.nps;
13810 if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
13811 || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
13812 blackNPS = first.nps;
13813 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
13814 whiteNPS = second.nps;
13815 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
13816 blackNPS = second.nps;
13817 if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
13819 StartClockTimer(intendedTickLength);
13826 long second, minute, hour, day;
13828 static char buf[32];
13830 if (ms > 0 && ms <= 9900) {
13831 /* convert milliseconds to tenths, rounding up */
13832 double tenths = floor( ((double)(ms + 99L)) / 100.00 );
13834 sprintf(buf, " %03.1f ", tenths/10.0);
13838 /* convert milliseconds to seconds, rounding up */
13839 /* use floating point to avoid strangeness of integer division
13840 with negative dividends on many machines */
13841 second = (long) floor(((double) (ms + 999L)) / 1000.0);
13848 day = second / (60 * 60 * 24);
13849 second = second % (60 * 60 * 24);
13850 hour = second / (60 * 60);
13851 second = second % (60 * 60);
13852 minute = second / 60;
13853 second = second % 60;
13856 sprintf(buf, " %s%ld:%02ld:%02ld:%02ld ",
13857 sign, day, hour, minute, second);
13859 sprintf(buf, " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
13861 sprintf(buf, " %s%2ld:%02ld ", sign, minute, second);
13868 * This is necessary because some C libraries aren't ANSI C compliant yet.
13871 StrStr(string, match)
13872 char *string, *match;
13876 length = strlen(match);
13878 for (i = strlen(string) - length; i >= 0; i--, string++)
13879 if (!strncmp(match, string, length))
13886 StrCaseStr(string, match)
13887 char *string, *match;
13891 length = strlen(match);
13893 for (i = strlen(string) - length; i >= 0; i--, string++) {
13894 for (j = 0; j < length; j++) {
13895 if (ToLower(match[j]) != ToLower(string[j]))
13898 if (j == length) return string;
13912 c1 = ToLower(*s1++);
13913 c2 = ToLower(*s2++);
13914 if (c1 > c2) return 1;
13915 if (c1 < c2) return -1;
13916 if (c1 == NULLCHAR) return 0;
13925 return isupper(c) ? tolower(c) : c;
13933 return islower(c) ? toupper(c) : c;
13935 #endif /* !_amigados */
13943 if ((ret = (char *) malloc(strlen(s) + 1))) {
13950 StrSavePtr(s, savePtr)
13951 char *s, **savePtr;
13956 if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
13957 strcpy(*savePtr, s);
13969 clock = time((time_t *)NULL);
13970 tm = localtime(&clock);
13971 sprintf(buf, "%04d.%02d.%02d",
13972 tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
13973 return StrSave(buf);
13978 PositionToFEN(move, overrideCastling)
13980 char *overrideCastling;
13982 int i, j, fromX, fromY, toX, toY;
13989 whiteToPlay = (gameMode == EditPosition) ?
13990 !blackPlaysFirst : (move % 2 == 0);
13993 /* Piece placement data */
13994 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13996 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
13997 if (boards[move][i][j] == EmptySquare) {
13999 } else { ChessSquare piece = boards[move][i][j];
14000 if (emptycount > 0) {
14001 if(emptycount<10) /* [HGM] can be >= 10 */
14002 *p++ = '0' + emptycount;
14003 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
14006 if(PieceToChar(piece) == '+') {
14007 /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
14009 piece = (ChessSquare)(DEMOTED piece);
14011 *p++ = PieceToChar(piece);
14013 /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
14014 p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
14019 if (emptycount > 0) {
14020 if(emptycount<10) /* [HGM] can be >= 10 */
14021 *p++ = '0' + emptycount;
14022 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
14029 /* [HGM] print Crazyhouse or Shogi holdings */
14030 if( gameInfo.holdingsWidth ) {
14031 *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
14033 for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
14034 piece = boards[move][i][BOARD_WIDTH-1];
14035 if( piece != EmptySquare )
14036 for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
14037 *p++ = PieceToChar(piece);
14039 for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
14040 piece = boards[move][BOARD_HEIGHT-i-1][0];
14041 if( piece != EmptySquare )
14042 for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
14043 *p++ = PieceToChar(piece);
14046 if( q == p ) *p++ = '-';
14052 *p++ = whiteToPlay ? 'w' : 'b';
14055 if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
14056 while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
14058 if(nrCastlingRights) {
14060 if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
14061 /* [HGM] write directly from rights */
14062 if(boards[move][CASTLING][2] != NoRights &&
14063 boards[move][CASTLING][0] != NoRights )
14064 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
14065 if(boards[move][CASTLING][2] != NoRights &&
14066 boards[move][CASTLING][1] != NoRights )
14067 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
14068 if(boards[move][CASTLING][5] != NoRights &&
14069 boards[move][CASTLING][3] != NoRights )
14070 *p++ = boards[move][CASTLING][3] + AAA;
14071 if(boards[move][CASTLING][5] != NoRights &&
14072 boards[move][CASTLING][4] != NoRights )
14073 *p++ = boards[move][CASTLING][4] + AAA;
14076 /* [HGM] write true castling rights */
14077 if( nrCastlingRights == 6 ) {
14078 if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
14079 boards[move][CASTLING][2] != NoRights ) *p++ = 'K';
14080 if(boards[move][CASTLING][1] == BOARD_LEFT &&
14081 boards[move][CASTLING][2] != NoRights ) *p++ = 'Q';
14082 if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
14083 boards[move][CASTLING][5] != NoRights ) *p++ = 'k';
14084 if(boards[move][CASTLING][4] == BOARD_LEFT &&
14085 boards[move][CASTLING][5] != NoRights ) *p++ = 'q';
14088 if (q == p) *p++ = '-'; /* No castling rights */
14092 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
14093 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
14094 /* En passant target square */
14095 if (move > backwardMostMove) {
14096 fromX = moveList[move - 1][0] - AAA;
14097 fromY = moveList[move - 1][1] - ONE;
14098 toX = moveList[move - 1][2] - AAA;
14099 toY = moveList[move - 1][3] - ONE;
14100 if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
14101 toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
14102 boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
14104 /* 2-square pawn move just happened */
14106 *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
14110 } else if(move == backwardMostMove) {
14111 // [HGM] perhaps we should always do it like this, and forget the above?
14112 if((signed char)boards[move][EP_STATUS] >= 0) {
14113 *p++ = boards[move][EP_STATUS] + AAA;
14114 *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
14125 /* [HGM] find reversible plies */
14126 { int i = 0, j=move;
14128 if (appData.debugMode) { int k;
14129 fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
14130 for(k=backwardMostMove; k<=forwardMostMove; k++)
14131 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
14135 while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
14136 if( j == backwardMostMove ) i += initialRulePlies;
14137 sprintf(p, "%d ", i);
14138 p += i>=100 ? 4 : i >= 10 ? 3 : 2;
14140 /* Fullmove number */
14141 sprintf(p, "%d", (move / 2) + 1);
14143 return StrSave(buf);
14147 ParseFEN(board, blackPlaysFirst, fen)
14149 int *blackPlaysFirst;
14159 /* [HGM] by default clear Crazyhouse holdings, if present */
14160 if(gameInfo.holdingsWidth) {
14161 for(i=0; i<BOARD_HEIGHT; i++) {
14162 board[i][0] = EmptySquare; /* black holdings */
14163 board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
14164 board[i][1] = (ChessSquare) 0; /* black counts */
14165 board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
14169 /* Piece placement data */
14170 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
14173 if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
14174 if (*p == '/') p++;
14175 emptycount = gameInfo.boardWidth - j;
14176 while (emptycount--)
14177 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
14179 #if(BOARD_FILES >= 10)
14180 } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
14181 p++; emptycount=10;
14182 if (j + emptycount > gameInfo.boardWidth) return FALSE;
14183 while (emptycount--)
14184 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
14186 } else if (isdigit(*p)) {
14187 emptycount = *p++ - '0';
14188 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
14189 if (j + emptycount > gameInfo.boardWidth) return FALSE;
14190 while (emptycount--)
14191 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
14192 } else if (*p == '+' || isalpha(*p)) {
14193 if (j >= gameInfo.boardWidth) return FALSE;
14195 piece = CharToPiece(*++p);
14196 if(piece == EmptySquare) return FALSE; /* unknown piece */
14197 piece = (ChessSquare) (PROMOTED piece ); p++;
14198 if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
14199 } else piece = CharToPiece(*p++);
14201 if(piece==EmptySquare) return FALSE; /* unknown piece */
14202 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
14203 piece = (ChessSquare) (PROMOTED piece);
14204 if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
14207 board[i][(j++)+gameInfo.holdingsWidth] = piece;
14213 while (*p == '/' || *p == ' ') p++;
14215 /* [HGM] look for Crazyhouse holdings here */
14216 while(*p==' ') p++;
14217 if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
14219 if(*p == '-' ) *p++; /* empty holdings */ else {
14220 if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
14221 /* if we would allow FEN reading to set board size, we would */
14222 /* have to add holdings and shift the board read so far here */
14223 while( (piece = CharToPiece(*p) ) != EmptySquare ) {
14225 if((int) piece >= (int) BlackPawn ) {
14226 i = (int)piece - (int)BlackPawn;
14227 i = PieceToNumber((ChessSquare)i);
14228 if( i >= gameInfo.holdingsSize ) return FALSE;
14229 board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
14230 board[BOARD_HEIGHT-1-i][1]++; /* black counts */
14232 i = (int)piece - (int)WhitePawn;
14233 i = PieceToNumber((ChessSquare)i);
14234 if( i >= gameInfo.holdingsSize ) return FALSE;
14235 board[i][BOARD_WIDTH-1] = piece; /* white holdings */
14236 board[i][BOARD_WIDTH-2]++; /* black holdings */
14240 if(*p == ']') *p++;
14243 while(*p == ' ') p++;
14248 *blackPlaysFirst = FALSE;
14251 *blackPlaysFirst = TRUE;
14257 /* [HGM] We NO LONGER ignore the rest of the FEN notation */
14258 /* return the extra info in global variiables */
14260 /* set defaults in case FEN is incomplete */
14261 board[EP_STATUS] = EP_UNKNOWN;
14262 for(i=0; i<nrCastlingRights; i++ ) {
14263 board[CASTLING][i] =
14264 gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
14265 } /* assume possible unless obviously impossible */
14266 if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
14267 if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
14268 if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
14269 && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
14270 if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
14271 if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
14272 if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
14273 && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
14276 while(*p==' ') p++;
14277 if(nrCastlingRights) {
14278 if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
14279 /* castling indicator present, so default becomes no castlings */
14280 for(i=0; i<nrCastlingRights; i++ ) {
14281 board[CASTLING][i] = NoRights;
14284 while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
14285 (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
14286 ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
14287 ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth) ) {
14288 char c = *p++; int whiteKingFile=NoRights, blackKingFile=NoRights;
14290 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
14291 if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
14292 if(board[0 ][i] == WhiteKing) whiteKingFile = i;
14294 if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
14295 whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
14296 if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
14297 && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
14298 if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
14299 && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
14302 for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
14303 board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
14304 board[CASTLING][2] = whiteKingFile;
14307 for(i=BOARD_LEFT; i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
14308 board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
14309 board[CASTLING][2] = whiteKingFile;
14312 for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
14313 board[CASTLING][3] = i != blackKingFile ? i : NoRights;
14314 board[CASTLING][5] = blackKingFile;
14317 for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
14318 board[CASTLING][4] = i != blackKingFile ? i : NoRights;
14319 board[CASTLING][5] = blackKingFile;
14322 default: /* FRC castlings */
14323 if(c >= 'a') { /* black rights */
14324 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
14325 if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
14326 if(i == BOARD_RGHT) break;
14327 board[CASTLING][5] = i;
14329 if(board[BOARD_HEIGHT-1][c] < BlackPawn ||
14330 board[BOARD_HEIGHT-1][c] >= BlackKing ) break;
14332 board[CASTLING][3] = c;
14334 board[CASTLING][4] = c;
14335 } else { /* white rights */
14336 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
14337 if(board[0][i] == WhiteKing) break;
14338 if(i == BOARD_RGHT) break;
14339 board[CASTLING][2] = i;
14340 c -= AAA - 'a' + 'A';
14341 if(board[0][c] >= WhiteKing) break;
14343 board[CASTLING][0] = c;
14345 board[CASTLING][1] = c;
14349 for(i=0; i<nrCastlingRights; i++)
14350 if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
14351 if (appData.debugMode) {
14352 fprintf(debugFP, "FEN castling rights:");
14353 for(i=0; i<nrCastlingRights; i++)
14354 fprintf(debugFP, " %d", board[CASTLING][i]);
14355 fprintf(debugFP, "\n");
14358 while(*p==' ') p++;
14361 /* read e.p. field in games that know e.p. capture */
14362 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
14363 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
14365 p++; board[EP_STATUS] = EP_NONE;
14367 char c = *p++ - AAA;
14369 if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
14370 if(*p >= '0' && *p <='9') *p++;
14371 board[EP_STATUS] = c;
14376 if(sscanf(p, "%d", &i) == 1) {
14377 FENrulePlies = i; /* 50-move ply counter */
14378 /* (The move number is still ignored) */
14385 EditPositionPasteFEN(char *fen)
14388 Board initial_position;
14390 if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
14391 DisplayError(_("Bad FEN position in clipboard"), 0);
14394 int savedBlackPlaysFirst = blackPlaysFirst;
14395 EditPositionEvent();
14396 blackPlaysFirst = savedBlackPlaysFirst;
14397 CopyBoard(boards[0], initial_position);
14398 initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
14399 EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
14400 DisplayBothClocks();
14401 DrawPosition(FALSE, boards[currentMove]);
14406 static char cseq[12] = "\\ ";
14408 Boolean set_cont_sequence(char *new_seq)
14413 // handle bad attempts to set the sequence
14415 return 0; // acceptable error - no debug
14417 len = strlen(new_seq);
14418 ret = (len > 0) && (len < sizeof(cseq));
14420 strcpy(cseq, new_seq);
14421 else if (appData.debugMode)
14422 fprintf(debugFP, "Invalid continuation sequence \"%s\" (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
14427 reformat a source message so words don't cross the width boundary. internal
14428 newlines are not removed. returns the wrapped size (no null character unless
14429 included in source message). If dest is NULL, only calculate the size required
14430 for the dest buffer. lp argument indicats line position upon entry, and it's
14431 passed back upon exit.
14433 int wrap(char *dest, char *src, int count, int width, int *lp)
14435 int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
14437 cseq_len = strlen(cseq);
14438 old_line = line = *lp;
14439 ansi = len = clen = 0;
14441 for (i=0; i < count; i++)
14443 if (src[i] == '\033')
14446 // if we hit the width, back up
14447 if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
14449 // store i & len in case the word is too long
14450 old_i = i, old_len = len;
14452 // find the end of the last word
14453 while (i && src[i] != ' ' && src[i] != '\n')
14459 // word too long? restore i & len before splitting it
14460 if ((old_i-i+clen) >= width)
14467 if (i && src[i-1] == ' ')
14470 if (src[i] != ' ' && src[i] != '\n')
14477 // now append the newline and continuation sequence
14482 strncpy(dest+len, cseq, cseq_len);
14490 dest[len] = src[i];
14494 if (src[i] == '\n')
14499 if (dest && appData.debugMode)
14501 fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
14502 count, width, line, len, *lp);
14503 show_bytes(debugFP, src, count);
14504 fprintf(debugFP, "\ndest: ");
14505 show_bytes(debugFP, dest, len);
14506 fprintf(debugFP, "\n");
14508 *lp = dest ? line : old_line;
14513 // [HGM] vari: routines for shelving variations
14516 PushTail(int firstMove, int lastMove)
14518 int i, j, nrMoves = lastMove - firstMove;
14520 if(appData.icsActive) { // only in local mode
14521 forwardMostMove = currentMove; // mimic old ICS behavior
14524 if(storedGames >= MAX_VARIATIONS-1) return;
14526 // push current tail of game on stack
14527 savedResult[storedGames] = gameInfo.result;
14528 savedDetails[storedGames] = gameInfo.resultDetails;
14529 gameInfo.resultDetails = NULL;
14530 savedFirst[storedGames] = firstMove;
14531 savedLast [storedGames] = lastMove;
14532 savedFramePtr[storedGames] = framePtr;
14533 framePtr -= nrMoves; // reserve space for the boards
14534 for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
14535 CopyBoard(boards[framePtr+i], boards[firstMove+i]);
14536 for(j=0; j<MOVE_LEN; j++)
14537 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
14538 for(j=0; j<2*MOVE_LEN; j++)
14539 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
14540 timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
14541 timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
14542 pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
14543 pvInfoList[firstMove+i-1].depth = 0;
14544 commentList[framePtr+i] = commentList[firstMove+i];
14545 commentList[firstMove+i] = NULL;
14549 forwardMostMove = currentMove; // truncte game so we can start variation
14550 if(storedGames == 1) GreyRevert(FALSE);
14554 PopTail(Boolean annotate)
14557 char buf[8000], moveBuf[20];
14559 if(appData.icsActive) return FALSE; // only in local mode
14560 if(!storedGames) return FALSE; // sanity
14563 ToNrEvent(savedFirst[storedGames]); // sets currentMove
14564 nrMoves = savedLast[storedGames] - currentMove;
14567 if(!WhiteOnMove(currentMove)) sprintf(buf, "(%d...", currentMove+2>>1);
14568 else strcpy(buf, "(");
14569 for(i=currentMove; i<forwardMostMove; i++) {
14571 sprintf(moveBuf, " %d. %s", i+2>>1, SavePart(parseList[i]));
14572 else sprintf(moveBuf, " %s", SavePart(parseList[i]));
14573 strcat(buf, moveBuf);
14574 if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
14578 for(i=1; i<nrMoves; i++) { // copy last variation back
14579 CopyBoard(boards[currentMove+i], boards[framePtr+i]);
14580 for(j=0; j<MOVE_LEN; j++)
14581 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
14582 for(j=0; j<2*MOVE_LEN; j++)
14583 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
14584 timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
14585 timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
14586 pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
14587 if(commentList[currentMove+i]) free(commentList[currentMove+i]);
14588 commentList[currentMove+i] = commentList[framePtr+i];
14589 commentList[framePtr+i] = NULL;
14591 if(annotate) AppendComment(currentMove+1, buf, FALSE);
14592 framePtr = savedFramePtr[storedGames];
14593 gameInfo.result = savedResult[storedGames];
14594 if(gameInfo.resultDetails != NULL) {
14595 free(gameInfo.resultDetails);
14597 gameInfo.resultDetails = savedDetails[storedGames];
14598 forwardMostMove = currentMove + nrMoves;
14599 if(storedGames == 0) GreyRevert(TRUE);
14605 { // remove all shelved variations
14607 for(i=0; i<storedGames; i++) {
14608 if(savedDetails[i])
14609 free(savedDetails[i]);
14610 savedDetails[i] = NULL;
14612 for(i=framePtr; i<MAX_MOVES; i++) {
14613 if(commentList[i]) free(commentList[i]);
14614 commentList[i] = NULL;
14616 framePtr = MAX_MOVES-1;