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 = 3 + chattingPartner; // counts as 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;
2710 if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
2711 chattingPartner = savingComment - 3; // kludge to remember the box
2713 started = STARTED_CHATTER;
2718 if (looking_at(buf, &i, "Black Strength :") ||
2719 looking_at(buf, &i, "<<< style 10 board >>>") ||
2720 looking_at(buf, &i, "<10>") ||
2721 looking_at(buf, &i, "#@#")) {
2722 /* Wrong board style */
2724 SendToICS(ics_prefix);
2725 SendToICS("set style 12\n");
2726 SendToICS(ics_prefix);
2727 SendToICS("refresh\n");
2731 if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
2733 have_sent_ICS_logon = 1;
2737 if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
2738 (looking_at(buf, &i, "\n<12> ") ||
2739 looking_at(buf, &i, "<12> "))) {
2741 if (oldi > next_out) {
2742 SendToPlayer(&buf[next_out], oldi - next_out);
2745 started = STARTED_BOARD;
2750 if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
2751 looking_at(buf, &i, "<b1> ")) {
2752 if (oldi > next_out) {
2753 SendToPlayer(&buf[next_out], oldi - next_out);
2756 started = STARTED_HOLDINGS;
2761 if (looking_at(buf, &i, "* *vs. * *--- *")) {
2763 /* Header for a move list -- first line */
2765 switch (ics_getting_history) {
2769 case BeginningOfGame:
2770 /* User typed "moves" or "oldmoves" while we
2771 were idle. Pretend we asked for these
2772 moves and soak them up so user can step
2773 through them and/or save them.
2776 gameMode = IcsObserving;
2779 ics_getting_history = H_GOT_UNREQ_HEADER;
2781 case EditGame: /*?*/
2782 case EditPosition: /*?*/
2783 /* Should above feature work in these modes too? */
2784 /* For now it doesn't */
2785 ics_getting_history = H_GOT_UNWANTED_HEADER;
2788 ics_getting_history = H_GOT_UNWANTED_HEADER;
2793 /* Is this the right one? */
2794 if (gameInfo.white && gameInfo.black &&
2795 strcmp(gameInfo.white, star_match[0]) == 0 &&
2796 strcmp(gameInfo.black, star_match[2]) == 0) {
2798 ics_getting_history = H_GOT_REQ_HEADER;
2801 case H_GOT_REQ_HEADER:
2802 case H_GOT_UNREQ_HEADER:
2803 case H_GOT_UNWANTED_HEADER:
2804 case H_GETTING_MOVES:
2805 /* Should not happen */
2806 DisplayError(_("Error gathering move list: two headers"), 0);
2807 ics_getting_history = H_FALSE;
2811 /* Save player ratings into gameInfo if needed */
2812 if ((ics_getting_history == H_GOT_REQ_HEADER ||
2813 ics_getting_history == H_GOT_UNREQ_HEADER) &&
2814 (gameInfo.whiteRating == -1 ||
2815 gameInfo.blackRating == -1)) {
2817 gameInfo.whiteRating = string_to_rating(star_match[1]);
2818 gameInfo.blackRating = string_to_rating(star_match[3]);
2819 if (appData.debugMode)
2820 fprintf(debugFP, _("Ratings from header: W %d, B %d\n"),
2821 gameInfo.whiteRating, gameInfo.blackRating);
2826 if (looking_at(buf, &i,
2827 "* * match, initial time: * minute*, increment: * second")) {
2828 /* Header for a move list -- second line */
2829 /* Initial board will follow if this is a wild game */
2830 if (gameInfo.event != NULL) free(gameInfo.event);
2831 sprintf(str, "ICS %s %s match", star_match[0], star_match[1]);
2832 gameInfo.event = StrSave(str);
2833 /* [HGM] we switched variant. Translate boards if needed. */
2834 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
2838 if (looking_at(buf, &i, "Move ")) {
2839 /* Beginning of a move list */
2840 switch (ics_getting_history) {
2842 /* Normally should not happen */
2843 /* Maybe user hit reset while we were parsing */
2846 /* Happens if we are ignoring a move list that is not
2847 * the one we just requested. Common if the user
2848 * tries to observe two games without turning off
2851 case H_GETTING_MOVES:
2852 /* Should not happen */
2853 DisplayError(_("Error gathering move list: nested"), 0);
2854 ics_getting_history = H_FALSE;
2856 case H_GOT_REQ_HEADER:
2857 ics_getting_history = H_GETTING_MOVES;
2858 started = STARTED_MOVES;
2860 if (oldi > next_out) {
2861 SendToPlayer(&buf[next_out], oldi - next_out);
2864 case H_GOT_UNREQ_HEADER:
2865 ics_getting_history = H_GETTING_MOVES;
2866 started = STARTED_MOVES_NOHIDE;
2869 case H_GOT_UNWANTED_HEADER:
2870 ics_getting_history = H_FALSE;
2876 if (looking_at(buf, &i, "% ") ||
2877 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
2878 && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
2879 if(suppressKibitz) next_out = i;
2880 savingComment = FALSE;
2884 case STARTED_MOVES_NOHIDE:
2885 memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
2886 parse[parse_pos + i - oldi] = NULLCHAR;
2887 ParseGameHistory(parse);
2889 if (appData.zippyPlay && first.initDone) {
2890 FeedMovesToProgram(&first, forwardMostMove);
2891 if (gameMode == IcsPlayingWhite) {
2892 if (WhiteOnMove(forwardMostMove)) {
2893 if (first.sendTime) {
2894 if (first.useColors) {
2895 SendToProgram("black\n", &first);
2897 SendTimeRemaining(&first, TRUE);
2899 if (first.useColors) {
2900 SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
2902 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
2903 first.maybeThinking = TRUE;
2905 if (first.usePlayother) {
2906 if (first.sendTime) {
2907 SendTimeRemaining(&first, TRUE);
2909 SendToProgram("playother\n", &first);
2915 } else if (gameMode == IcsPlayingBlack) {
2916 if (!WhiteOnMove(forwardMostMove)) {
2917 if (first.sendTime) {
2918 if (first.useColors) {
2919 SendToProgram("white\n", &first);
2921 SendTimeRemaining(&first, FALSE);
2923 if (first.useColors) {
2924 SendToProgram("black\n", &first);
2926 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
2927 first.maybeThinking = TRUE;
2929 if (first.usePlayother) {
2930 if (first.sendTime) {
2931 SendTimeRemaining(&first, FALSE);
2933 SendToProgram("playother\n", &first);
2942 if (gameMode == IcsObserving && ics_gamenum == -1) {
2943 /* Moves came from oldmoves or moves command
2944 while we weren't doing anything else.
2946 currentMove = forwardMostMove;
2947 ClearHighlights();/*!!could figure this out*/
2948 flipView = appData.flipView;
2949 DrawPosition(TRUE, boards[currentMove]);
2950 DisplayBothClocks();
2951 sprintf(str, "%s vs. %s",
2952 gameInfo.white, gameInfo.black);
2956 /* Moves were history of an active game */
2957 if (gameInfo.resultDetails != NULL) {
2958 free(gameInfo.resultDetails);
2959 gameInfo.resultDetails = NULL;
2962 HistorySet(parseList, backwardMostMove,
2963 forwardMostMove, currentMove-1);
2964 DisplayMove(currentMove - 1);
2965 if (started == STARTED_MOVES) next_out = i;
2966 started = STARTED_NONE;
2967 ics_getting_history = H_FALSE;
2970 case STARTED_OBSERVE:
2971 started = STARTED_NONE;
2972 SendToICS(ics_prefix);
2973 SendToICS("refresh\n");
2979 if(bookHit) { // [HGM] book: simulate book reply
2980 static char bookMove[MSG_SIZ]; // a bit generous?
2982 programStats.nodes = programStats.depth = programStats.time =
2983 programStats.score = programStats.got_only_move = 0;
2984 sprintf(programStats.movelist, "%s (xbook)", bookHit);
2986 strcpy(bookMove, "move ");
2987 strcat(bookMove, bookHit);
2988 HandleMachineMove(bookMove, &first);
2993 if ((started == STARTED_MOVES || started == STARTED_BOARD ||
2994 started == STARTED_HOLDINGS ||
2995 started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
2996 /* Accumulate characters in move list or board */
2997 parse[parse_pos++] = buf[i];
3000 /* Start of game messages. Mostly we detect start of game
3001 when the first board image arrives. On some versions
3002 of the ICS, though, we need to do a "refresh" after starting
3003 to observe in order to get the current board right away. */
3004 if (looking_at(buf, &i, "Adding game * to observation list")) {
3005 started = STARTED_OBSERVE;
3009 /* Handle auto-observe */
3010 if (appData.autoObserve &&
3011 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3012 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3014 /* Choose the player that was highlighted, if any. */
3015 if (star_match[0][0] == '\033' ||
3016 star_match[1][0] != '\033') {
3017 player = star_match[0];
3019 player = star_match[2];
3021 sprintf(str, "%sobserve %s\n",
3022 ics_prefix, StripHighlightAndTitle(player));
3025 /* Save ratings from notify string */
3026 strcpy(player1Name, star_match[0]);
3027 player1Rating = string_to_rating(star_match[1]);
3028 strcpy(player2Name, star_match[2]);
3029 player2Rating = string_to_rating(star_match[3]);
3031 if (appData.debugMode)
3033 "Ratings from 'Game notification:' %s %d, %s %d\n",
3034 player1Name, player1Rating,
3035 player2Name, player2Rating);
3040 /* Deal with automatic examine mode after a game,
3041 and with IcsObserving -> IcsExamining transition */
3042 if (looking_at(buf, &i, "Entering examine mode for game *") ||
3043 looking_at(buf, &i, "has made you an examiner of game *")) {
3045 int gamenum = atoi(star_match[0]);
3046 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3047 gamenum == ics_gamenum) {
3048 /* We were already playing or observing this game;
3049 no need to refetch history */
3050 gameMode = IcsExamining;
3052 pauseExamForwardMostMove = forwardMostMove;
3053 } else if (currentMove < forwardMostMove) {
3054 ForwardInner(forwardMostMove);
3057 /* I don't think this case really can happen */
3058 SendToICS(ics_prefix);
3059 SendToICS("refresh\n");
3064 /* Error messages */
3065 // if (ics_user_moved) {
3066 if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3067 if (looking_at(buf, &i, "Illegal move") ||
3068 looking_at(buf, &i, "Not a legal move") ||
3069 looking_at(buf, &i, "Your king is in check") ||
3070 looking_at(buf, &i, "It isn't your turn") ||
3071 looking_at(buf, &i, "It is not your move")) {
3073 if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3074 currentMove = --forwardMostMove;
3075 DisplayMove(currentMove - 1); /* before DMError */
3076 DrawPosition(FALSE, boards[currentMove]);
3078 DisplayBothClocks();
3080 DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3086 if (looking_at(buf, &i, "still have time") ||
3087 looking_at(buf, &i, "not out of time") ||
3088 looking_at(buf, &i, "either player is out of time") ||
3089 looking_at(buf, &i, "has timeseal; checking")) {
3090 /* We must have called his flag a little too soon */
3091 whiteFlag = blackFlag = FALSE;
3095 if (looking_at(buf, &i, "added * seconds to") ||
3096 looking_at(buf, &i, "seconds were added to")) {
3097 /* Update the clocks */
3098 SendToICS(ics_prefix);
3099 SendToICS("refresh\n");
3103 if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3104 ics_clock_paused = TRUE;
3109 if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3110 ics_clock_paused = FALSE;
3115 /* Grab player ratings from the Creating: message.
3116 Note we have to check for the special case when
3117 the ICS inserts things like [white] or [black]. */
3118 if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3119 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3121 0 player 1 name (not necessarily white)
3123 2 empty, white, or black (IGNORED)
3124 3 player 2 name (not necessarily black)
3127 The names/ratings are sorted out when the game
3128 actually starts (below).
3130 strcpy(player1Name, StripHighlightAndTitle(star_match[0]));
3131 player1Rating = string_to_rating(star_match[1]);
3132 strcpy(player2Name, StripHighlightAndTitle(star_match[3]));
3133 player2Rating = string_to_rating(star_match[4]);
3135 if (appData.debugMode)
3137 "Ratings from 'Creating:' %s %d, %s %d\n",
3138 player1Name, player1Rating,
3139 player2Name, player2Rating);
3144 /* Improved generic start/end-of-game messages */
3145 if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3146 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3147 /* If tkind == 0: */
3148 /* star_match[0] is the game number */
3149 /* [1] is the white player's name */
3150 /* [2] is the black player's name */
3151 /* For end-of-game: */
3152 /* [3] is the reason for the game end */
3153 /* [4] is a PGN end game-token, preceded by " " */
3154 /* For start-of-game: */
3155 /* [3] begins with "Creating" or "Continuing" */
3156 /* [4] is " *" or empty (don't care). */
3157 int gamenum = atoi(star_match[0]);
3158 char *whitename, *blackname, *why, *endtoken;
3159 ChessMove endtype = (ChessMove) 0;
3162 whitename = star_match[1];
3163 blackname = star_match[2];
3164 why = star_match[3];
3165 endtoken = star_match[4];
3167 whitename = star_match[1];
3168 blackname = star_match[3];
3169 why = star_match[5];
3170 endtoken = star_match[6];
3173 /* Game start messages */
3174 if (strncmp(why, "Creating ", 9) == 0 ||
3175 strncmp(why, "Continuing ", 11) == 0) {
3176 gs_gamenum = gamenum;
3177 strcpy(gs_kind, strchr(why, ' ') + 1);
3178 VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
3180 if (appData.zippyPlay) {
3181 ZippyGameStart(whitename, blackname);
3187 /* Game end messages */
3188 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3189 ics_gamenum != gamenum) {
3192 while (endtoken[0] == ' ') endtoken++;
3193 switch (endtoken[0]) {
3196 endtype = GameUnfinished;
3199 endtype = BlackWins;
3202 if (endtoken[1] == '/')
3203 endtype = GameIsDrawn;
3205 endtype = WhiteWins;
3208 GameEnds(endtype, why, GE_ICS);
3210 if (appData.zippyPlay && first.initDone) {
3211 ZippyGameEnd(endtype, why);
3212 if (first.pr == NULL) {
3213 /* Start the next process early so that we'll
3214 be ready for the next challenge */
3215 StartChessProgram(&first);
3217 /* Send "new" early, in case this command takes
3218 a long time to finish, so that we'll be ready
3219 for the next challenge. */
3220 gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3227 if (looking_at(buf, &i, "Removing game * from observation") ||
3228 looking_at(buf, &i, "no longer observing game *") ||
3229 looking_at(buf, &i, "Game * (*) has no examiners")) {
3230 if (gameMode == IcsObserving &&
3231 atoi(star_match[0]) == ics_gamenum)
3233 /* icsEngineAnalyze */
3234 if (appData.icsEngineAnalyze) {
3241 ics_user_moved = FALSE;
3246 if (looking_at(buf, &i, "no longer examining game *")) {
3247 if (gameMode == IcsExamining &&
3248 atoi(star_match[0]) == ics_gamenum)
3252 ics_user_moved = FALSE;
3257 /* Advance leftover_start past any newlines we find,
3258 so only partial lines can get reparsed */
3259 if (looking_at(buf, &i, "\n")) {
3260 prevColor = curColor;
3261 if (curColor != ColorNormal) {
3262 if (oldi > next_out) {
3263 SendToPlayer(&buf[next_out], oldi - next_out);
3266 Colorize(ColorNormal, FALSE);
3267 curColor = ColorNormal;
3269 if (started == STARTED_BOARD) {
3270 started = STARTED_NONE;
3271 parse[parse_pos] = NULLCHAR;
3272 ParseBoard12(parse);
3275 /* Send premove here */
3276 if (appData.premove) {
3278 if (currentMove == 0 &&
3279 gameMode == IcsPlayingWhite &&
3280 appData.premoveWhite) {
3281 sprintf(str, "%s\n", appData.premoveWhiteText);
3282 if (appData.debugMode)
3283 fprintf(debugFP, "Sending premove:\n");
3285 } else if (currentMove == 1 &&
3286 gameMode == IcsPlayingBlack &&
3287 appData.premoveBlack) {
3288 sprintf(str, "%s\n", appData.premoveBlackText);
3289 if (appData.debugMode)
3290 fprintf(debugFP, "Sending premove:\n");
3292 } else if (gotPremove) {
3294 ClearPremoveHighlights();
3295 if (appData.debugMode)
3296 fprintf(debugFP, "Sending premove:\n");
3297 UserMoveEvent(premoveFromX, premoveFromY,
3298 premoveToX, premoveToY,
3303 /* Usually suppress following prompt */
3304 if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
3305 while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
3306 if (looking_at(buf, &i, "*% ")) {
3307 savingComment = FALSE;
3312 } else if (started == STARTED_HOLDINGS) {
3314 char new_piece[MSG_SIZ];
3315 started = STARTED_NONE;
3316 parse[parse_pos] = NULLCHAR;
3317 if (appData.debugMode)
3318 fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
3319 parse, currentMove);
3320 if (sscanf(parse, " game %d", &gamenum) == 1 &&
3321 gamenum == ics_gamenum) {
3322 if (gameInfo.variant == VariantNormal) {
3323 /* [HGM] We seem to switch variant during a game!
3324 * Presumably no holdings were displayed, so we have
3325 * to move the position two files to the right to
3326 * create room for them!
3328 VariantClass newVariant;
3329 switch(gameInfo.boardWidth) { // base guess on board width
3330 case 9: newVariant = VariantShogi; break;
3331 case 10: newVariant = VariantGreat; break;
3332 default: newVariant = VariantCrazyhouse; break;
3334 VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3335 /* Get a move list just to see the header, which
3336 will tell us whether this is really bug or zh */
3337 if (ics_getting_history == H_FALSE) {
3338 ics_getting_history = H_REQUESTED;
3339 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3343 new_piece[0] = NULLCHAR;
3344 sscanf(parse, "game %d white [%s black [%s <- %s",
3345 &gamenum, white_holding, black_holding,
3347 white_holding[strlen(white_holding)-1] = NULLCHAR;
3348 black_holding[strlen(black_holding)-1] = NULLCHAR;
3349 /* [HGM] copy holdings to board holdings area */
3350 CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
3351 CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
3352 boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
3354 if (appData.zippyPlay && first.initDone) {
3355 ZippyHoldings(white_holding, black_holding,
3359 if (tinyLayout || smallLayout) {
3360 char wh[16], bh[16];
3361 PackHolding(wh, white_holding);
3362 PackHolding(bh, black_holding);
3363 sprintf(str, "[%s-%s] %s-%s", wh, bh,
3364 gameInfo.white, gameInfo.black);
3366 sprintf(str, "%s [%s] vs. %s [%s]",
3367 gameInfo.white, white_holding,
3368 gameInfo.black, black_holding);
3371 DrawPosition(FALSE, boards[currentMove]);
3374 /* Suppress following prompt */
3375 if (looking_at(buf, &i, "*% ")) {
3376 if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
3377 savingComment = FALSE;
3385 i++; /* skip unparsed character and loop back */
3388 if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
3389 // started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
3390 // SendToPlayer(&buf[next_out], i - next_out);
3391 started != STARTED_HOLDINGS && leftover_start > next_out) {
3392 SendToPlayer(&buf[next_out], leftover_start - next_out);
3396 leftover_len = buf_len - leftover_start;
3397 /* if buffer ends with something we couldn't parse,
3398 reparse it after appending the next read */
3400 } else if (count == 0) {
3401 RemoveInputSource(isr);
3402 DisplayFatalError(_("Connection closed by ICS"), 0, 0);
3404 DisplayFatalError(_("Error reading from ICS"), error, 1);
3409 /* Board style 12 looks like this:
3411 <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
3413 * The "<12> " is stripped before it gets to this routine. The two
3414 * trailing 0's (flip state and clock ticking) are later addition, and
3415 * some chess servers may not have them, or may have only the first.
3416 * Additional trailing fields may be added in the future.
3419 #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"
3421 #define RELATION_OBSERVING_PLAYED 0
3422 #define RELATION_OBSERVING_STATIC -2 /* examined, oldmoves, or smoves */
3423 #define RELATION_PLAYING_MYMOVE 1
3424 #define RELATION_PLAYING_NOTMYMOVE -1
3425 #define RELATION_EXAMINING 2
3426 #define RELATION_ISOLATED_BOARD -3
3427 #define RELATION_STARTING_POSITION -4 /* FICS only */
3430 ParseBoard12(string)
3433 GameMode newGameMode;
3434 int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
3435 int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
3436 int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
3437 char to_play, board_chars[200];
3438 char move_str[500], str[500], elapsed_time[500];
3439 char black[32], white[32];
3441 int prevMove = currentMove;
3444 int fromX, fromY, toX, toY;
3446 int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
3447 char *bookHit = NULL; // [HGM] book
3448 Boolean weird = FALSE, reqFlag = FALSE;
3450 fromX = fromY = toX = toY = -1;
3454 if (appData.debugMode)
3455 fprintf(debugFP, _("Parsing board: %s\n"), string);
3457 move_str[0] = NULLCHAR;
3458 elapsed_time[0] = NULLCHAR;
3459 { /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
3461 while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
3462 if(string[i] == ' ') { ranks++; files = 0; }
3464 if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
3467 for(j = 0; j <i; j++) board_chars[j] = string[j];
3468 board_chars[i] = '\0';
3471 n = sscanf(string, PATTERN, &to_play, &double_push,
3472 &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
3473 &gamenum, white, black, &relation, &basetime, &increment,
3474 &white_stren, &black_stren, &white_time, &black_time,
3475 &moveNum, str, elapsed_time, move_str, &ics_flip,
3479 snprintf(str, sizeof(str), _("Failed to parse board string:\n\"%s\""), string);
3480 DisplayError(str, 0);
3484 /* Convert the move number to internal form */
3485 moveNum = (moveNum - 1) * 2;
3486 if (to_play == 'B') moveNum++;
3487 if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
3488 DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
3494 case RELATION_OBSERVING_PLAYED:
3495 case RELATION_OBSERVING_STATIC:
3496 if (gamenum == -1) {
3497 /* Old ICC buglet */
3498 relation = RELATION_OBSERVING_STATIC;
3500 newGameMode = IcsObserving;
3502 case RELATION_PLAYING_MYMOVE:
3503 case RELATION_PLAYING_NOTMYMOVE:
3505 ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
3506 IcsPlayingWhite : IcsPlayingBlack;
3508 case RELATION_EXAMINING:
3509 newGameMode = IcsExamining;
3511 case RELATION_ISOLATED_BOARD:
3513 /* Just display this board. If user was doing something else,
3514 we will forget about it until the next board comes. */
3515 newGameMode = IcsIdle;
3517 case RELATION_STARTING_POSITION:
3518 newGameMode = gameMode;
3522 /* Modify behavior for initial board display on move listing
3525 switch (ics_getting_history) {
3529 case H_GOT_REQ_HEADER:
3530 case H_GOT_UNREQ_HEADER:
3531 /* This is the initial position of the current game */
3532 gamenum = ics_gamenum;
3533 moveNum = 0; /* old ICS bug workaround */
3534 if (to_play == 'B') {
3535 startedFromSetupPosition = TRUE;
3536 blackPlaysFirst = TRUE;
3538 if (forwardMostMove == 0) forwardMostMove = 1;
3539 if (backwardMostMove == 0) backwardMostMove = 1;
3540 if (currentMove == 0) currentMove = 1;
3542 newGameMode = gameMode;
3543 relation = RELATION_STARTING_POSITION; /* ICC needs this */
3545 case H_GOT_UNWANTED_HEADER:
3546 /* This is an initial board that we don't want */
3548 case H_GETTING_MOVES:
3549 /* Should not happen */
3550 DisplayError(_("Error gathering move list: extra board"), 0);
3551 ics_getting_history = H_FALSE;
3555 if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
3556 weird && (int)gameInfo.variant <= (int)VariantShogi) {
3557 /* [HGM] We seem to have switched variant unexpectedly
3558 * Try to guess new variant from board size
3560 VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
3561 if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
3562 if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
3563 if(ranks == 8 && files == 12) newVariant = VariantCourier; else
3564 if(ranks == 9 && files == 9) newVariant = VariantShogi; else
3565 if(!weird) newVariant = VariantNormal;
3566 VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3567 /* Get a move list just to see the header, which
3568 will tell us whether this is really bug or zh */
3569 if (ics_getting_history == H_FALSE) {
3570 ics_getting_history = H_REQUESTED; reqFlag = TRUE;
3571 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3576 /* Take action if this is the first board of a new game, or of a
3577 different game than is currently being displayed. */
3578 if (gamenum != ics_gamenum || newGameMode != gameMode ||
3579 relation == RELATION_ISOLATED_BOARD) {
3581 /* Forget the old game and get the history (if any) of the new one */
3582 if (gameMode != BeginningOfGame) {
3586 if (appData.autoRaiseBoard) BoardToTop();
3588 if (gamenum == -1) {
3589 newGameMode = IcsIdle;
3590 } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
3591 appData.getMoveList && !reqFlag) {
3592 /* Need to get game history */
3593 ics_getting_history = H_REQUESTED;
3594 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3598 /* Initially flip the board to have black on the bottom if playing
3599 black or if the ICS flip flag is set, but let the user change
3600 it with the Flip View button. */
3601 flipView = appData.autoFlipView ?
3602 (newGameMode == IcsPlayingBlack) || ics_flip :
3605 /* Done with values from previous mode; copy in new ones */
3606 gameMode = newGameMode;
3608 ics_gamenum = gamenum;
3609 if (gamenum == gs_gamenum) {
3610 int klen = strlen(gs_kind);
3611 if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
3612 sprintf(str, "ICS %s", gs_kind);
3613 gameInfo.event = StrSave(str);
3615 gameInfo.event = StrSave("ICS game");
3617 gameInfo.site = StrSave(appData.icsHost);
3618 gameInfo.date = PGNDate();
3619 gameInfo.round = StrSave("-");
3620 gameInfo.white = StrSave(white);
3621 gameInfo.black = StrSave(black);
3622 timeControl = basetime * 60 * 1000;
3624 timeIncrement = increment * 1000;
3625 movesPerSession = 0;
3626 gameInfo.timeControl = TimeControlTagValue();
3627 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
3628 if (appData.debugMode) {
3629 fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
3630 fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
3631 setbuf(debugFP, NULL);
3634 gameInfo.outOfBook = NULL;
3636 /* Do we have the ratings? */
3637 if (strcmp(player1Name, white) == 0 &&
3638 strcmp(player2Name, black) == 0) {
3639 if (appData.debugMode)
3640 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3641 player1Rating, player2Rating);
3642 gameInfo.whiteRating = player1Rating;
3643 gameInfo.blackRating = player2Rating;
3644 } else if (strcmp(player2Name, white) == 0 &&
3645 strcmp(player1Name, black) == 0) {
3646 if (appData.debugMode)
3647 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3648 player2Rating, player1Rating);
3649 gameInfo.whiteRating = player2Rating;
3650 gameInfo.blackRating = player1Rating;
3652 player1Name[0] = player2Name[0] = NULLCHAR;
3654 /* Silence shouts if requested */
3655 if (appData.quietPlay &&
3656 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
3657 SendToICS(ics_prefix);
3658 SendToICS("set shout 0\n");
3662 /* Deal with midgame name changes */
3664 if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
3665 if (gameInfo.white) free(gameInfo.white);
3666 gameInfo.white = StrSave(white);
3668 if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
3669 if (gameInfo.black) free(gameInfo.black);
3670 gameInfo.black = StrSave(black);
3674 /* Throw away game result if anything actually changes in examine mode */
3675 if (gameMode == IcsExamining && !newGame) {
3676 gameInfo.result = GameUnfinished;
3677 if (gameInfo.resultDetails != NULL) {
3678 free(gameInfo.resultDetails);
3679 gameInfo.resultDetails = NULL;
3683 /* In pausing && IcsExamining mode, we ignore boards coming
3684 in if they are in a different variation than we are. */
3685 if (pauseExamInvalid) return;
3686 if (pausing && gameMode == IcsExamining) {
3687 if (moveNum <= pauseExamForwardMostMove) {
3688 pauseExamInvalid = TRUE;
3689 forwardMostMove = pauseExamForwardMostMove;
3694 if (appData.debugMode) {
3695 fprintf(debugFP, "load %dx%d board\n", files, ranks);
3697 /* Parse the board */
3698 for (k = 0; k < ranks; k++) {
3699 for (j = 0; j < files; j++)
3700 board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
3701 if(gameInfo.holdingsWidth > 1) {
3702 board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
3703 board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
3706 CopyBoard(boards[moveNum], board);
3707 boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
3709 startedFromSetupPosition =
3710 !CompareBoards(board, initialPosition);
3711 if(startedFromSetupPosition)
3712 initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
3715 /* [HGM] Set castling rights. Take the outermost Rooks,
3716 to make it also work for FRC opening positions. Note that board12
3717 is really defective for later FRC positions, as it has no way to
3718 indicate which Rook can castle if they are on the same side of King.
3719 For the initial position we grant rights to the outermost Rooks,
3720 and remember thos rights, and we then copy them on positions
3721 later in an FRC game. This means WB might not recognize castlings with
3722 Rooks that have moved back to their original position as illegal,
3723 but in ICS mode that is not its job anyway.
3725 if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
3726 { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
3728 for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
3729 if(board[0][i] == WhiteRook) j = i;
3730 initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
3731 for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
3732 if(board[0][i] == WhiteRook) j = i;
3733 initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
3734 for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
3735 if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
3736 initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
3737 for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
3738 if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
3739 initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
3741 if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
3742 for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
3743 if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
3744 for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
3745 if(board[BOARD_HEIGHT-1][k] == bKing)
3746 initialRights[5] = boards[moveNum][CASTLING][5] = k;
3747 if(gameInfo.variant == VariantTwoKings) {
3748 // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
3749 if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
3750 if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
3753 r = boards[moveNum][CASTLING][0] = initialRights[0];
3754 if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
3755 r = boards[moveNum][CASTLING][1] = initialRights[1];
3756 if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
3757 r = boards[moveNum][CASTLING][3] = initialRights[3];
3758 if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
3759 r = boards[moveNum][CASTLING][4] = initialRights[4];
3760 if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
3761 /* wildcastle kludge: always assume King has rights */
3762 r = boards[moveNum][CASTLING][2] = initialRights[2];
3763 r = boards[moveNum][CASTLING][5] = initialRights[5];
3765 /* [HGM] e.p. rights. Assume that ICS sends file number here? */
3766 boards[moveNum][EP_STATUS] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
3769 if (ics_getting_history == H_GOT_REQ_HEADER ||
3770 ics_getting_history == H_GOT_UNREQ_HEADER) {
3771 /* This was an initial position from a move list, not
3772 the current position */
3776 /* Update currentMove and known move number limits */
3777 newMove = newGame || moveNum > forwardMostMove;
3780 forwardMostMove = backwardMostMove = currentMove = moveNum;
3781 if (gameMode == IcsExamining && moveNum == 0) {
3782 /* Workaround for ICS limitation: we are not told the wild
3783 type when starting to examine a game. But if we ask for
3784 the move list, the move list header will tell us */
3785 ics_getting_history = H_REQUESTED;
3786 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3789 } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
3790 || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
3792 /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
3793 /* [HGM] applied this also to an engine that is silently watching */
3794 if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
3795 (gameMode == IcsObserving || gameMode == IcsExamining) &&
3796 gameInfo.variant == currentlyInitializedVariant) {
3797 takeback = forwardMostMove - moveNum;
3798 for (i = 0; i < takeback; i++) {
3799 if (appData.debugMode) fprintf(debugFP, "take back move\n");
3800 SendToProgram("undo\n", &first);
3805 forwardMostMove = moveNum;
3806 if (!pausing || currentMove > forwardMostMove)
3807 currentMove = forwardMostMove;
3809 /* New part of history that is not contiguous with old part */
3810 if (pausing && gameMode == IcsExamining) {
3811 pauseExamInvalid = TRUE;
3812 forwardMostMove = pauseExamForwardMostMove;
3815 if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
3817 if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
3818 // [HGM] when we will receive the move list we now request, it will be
3819 // fed to the engine from the first move on. So if the engine is not
3820 // in the initial position now, bring it there.
3821 InitChessProgram(&first, 0);
3824 ics_getting_history = H_REQUESTED;
3825 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3828 forwardMostMove = backwardMostMove = currentMove = moveNum;
3831 /* Update the clocks */
3832 if (strchr(elapsed_time, '.')) {
3834 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
3835 timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
3837 /* Time is in seconds */
3838 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
3839 timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
3844 if (appData.zippyPlay && newGame &&
3845 gameMode != IcsObserving && gameMode != IcsIdle &&
3846 gameMode != IcsExamining)
3847 ZippyFirstBoard(moveNum, basetime, increment);
3850 /* Put the move on the move list, first converting
3851 to canonical algebraic form. */
3853 if (appData.debugMode) {
3854 if (appData.debugMode) { int f = forwardMostMove;
3855 fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
3856 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
3857 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
3859 fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
3860 fprintf(debugFP, "moveNum = %d\n", moveNum);
3861 fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
3862 setbuf(debugFP, NULL);
3864 if (moveNum <= backwardMostMove) {
3865 /* We don't know what the board looked like before
3867 strcpy(parseList[moveNum - 1], move_str);
3868 strcat(parseList[moveNum - 1], " ");
3869 strcat(parseList[moveNum - 1], elapsed_time);
3870 moveList[moveNum - 1][0] = NULLCHAR;
3871 } else if (strcmp(move_str, "none") == 0) {
3872 // [HGM] long SAN: swapped order; test for 'none' before parsing move
3873 /* Again, we don't know what the board looked like;
3874 this is really the start of the game. */
3875 parseList[moveNum - 1][0] = NULLCHAR;
3876 moveList[moveNum - 1][0] = NULLCHAR;
3877 backwardMostMove = moveNum;
3878 startedFromSetupPosition = TRUE;
3879 fromX = fromY = toX = toY = -1;
3881 // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
3882 // So we parse the long-algebraic move string in stead of the SAN move
3883 int valid; char buf[MSG_SIZ], *prom;
3885 // str looks something like "Q/a1-a2"; kill the slash
3887 sprintf(buf, "%c%s", str[0], str+2);
3888 else strcpy(buf, str); // might be castling
3889 if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
3890 strcat(buf, prom); // long move lacks promo specification!
3891 if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
3892 if(appData.debugMode)
3893 fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
3894 strcpy(move_str, buf);
3896 valid = ParseOneMove(move_str, moveNum - 1, &moveType,
3897 &fromX, &fromY, &toX, &toY, &promoChar)
3898 || ParseOneMove(buf, moveNum - 1, &moveType,
3899 &fromX, &fromY, &toX, &toY, &promoChar);
3900 // end of long SAN patch
3902 (void) CoordsToAlgebraic(boards[moveNum - 1],
3903 PosFlags(moveNum - 1),
3904 fromY, fromX, toY, toX, promoChar,
3905 parseList[moveNum-1]);
3906 switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
3912 if(gameInfo.variant != VariantShogi)
3913 strcat(parseList[moveNum - 1], "+");
3916 case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
3917 strcat(parseList[moveNum - 1], "#");
3920 strcat(parseList[moveNum - 1], " ");
3921 strcat(parseList[moveNum - 1], elapsed_time);
3922 /* currentMoveString is set as a side-effect of ParseOneMove */
3923 strcpy(moveList[moveNum - 1], currentMoveString);
3924 strcat(moveList[moveNum - 1], "\n");
3926 /* Move from ICS was illegal!? Punt. */
3927 if (appData.debugMode) {
3928 fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
3929 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
3931 strcpy(parseList[moveNum - 1], move_str);
3932 strcat(parseList[moveNum - 1], " ");
3933 strcat(parseList[moveNum - 1], elapsed_time);
3934 moveList[moveNum - 1][0] = NULLCHAR;
3935 fromX = fromY = toX = toY = -1;
3938 if (appData.debugMode) {
3939 fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
3940 setbuf(debugFP, NULL);
3944 /* Send move to chess program (BEFORE animating it). */
3945 if (appData.zippyPlay && !newGame && newMove &&
3946 (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
3948 if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
3949 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
3950 if (moveList[moveNum - 1][0] == NULLCHAR) {
3951 sprintf(str, _("Couldn't parse move \"%s\" from ICS"),
3953 DisplayError(str, 0);
3955 if (first.sendTime) {
3956 SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
3958 bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
3959 if (firstMove && !bookHit) {
3961 if (first.useColors) {
3962 SendToProgram(gameMode == IcsPlayingWhite ?
3964 "black\ngo\n", &first);
3966 SendToProgram("go\n", &first);
3968 first.maybeThinking = TRUE;
3971 } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
3972 if (moveList[moveNum - 1][0] == NULLCHAR) {
3973 sprintf(str, _("Couldn't parse move \"%s\" from ICS"), move_str);
3974 DisplayError(str, 0);
3976 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
3977 SendMoveToProgram(moveNum - 1, &first);
3984 if (moveNum > 0 && !gotPremove && !appData.noGUI) {
3985 /* If move comes from a remote source, animate it. If it
3986 isn't remote, it will have already been animated. */
3987 if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
3988 AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
3990 if (!pausing && appData.highlightLastMove) {
3991 SetHighlights(fromX, fromY, toX, toY);
3995 /* Start the clocks */
3996 whiteFlag = blackFlag = FALSE;
3997 appData.clockMode = !(basetime == 0 && increment == 0);
3999 ics_clock_paused = TRUE;
4001 } else if (ticking == 1) {
4002 ics_clock_paused = FALSE;
4004 if (gameMode == IcsIdle ||
4005 relation == RELATION_OBSERVING_STATIC ||
4006 relation == RELATION_EXAMINING ||
4008 DisplayBothClocks();
4012 /* Display opponents and material strengths */
4013 if (gameInfo.variant != VariantBughouse &&
4014 gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4015 if (tinyLayout || smallLayout) {
4016 if(gameInfo.variant == VariantNormal)
4017 sprintf(str, "%s(%d) %s(%d) {%d %d}",
4018 gameInfo.white, white_stren, gameInfo.black, black_stren,
4019 basetime, increment);
4021 sprintf(str, "%s(%d) %s(%d) {%d %d w%d}",
4022 gameInfo.white, white_stren, gameInfo.black, black_stren,
4023 basetime, increment, (int) gameInfo.variant);
4025 if(gameInfo.variant == VariantNormal)
4026 sprintf(str, "%s (%d) vs. %s (%d) {%d %d}",
4027 gameInfo.white, white_stren, gameInfo.black, black_stren,
4028 basetime, increment);
4030 sprintf(str, "%s (%d) vs. %s (%d) {%d %d %s}",
4031 gameInfo.white, white_stren, gameInfo.black, black_stren,
4032 basetime, increment, VariantName(gameInfo.variant));
4035 if (appData.debugMode) {
4036 fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
4041 /* Display the board */
4042 if (!pausing && !appData.noGUI) {
4044 if (appData.premove)
4046 ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
4047 ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
4048 ClearPremoveHighlights();
4050 DrawPosition(FALSE, boards[currentMove]);
4051 DisplayMove(moveNum - 1);
4052 if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4053 !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4054 (gameMode == IcsPlayingBlack) && (WhiteOnMove(moveNum)) ) ) {
4055 if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4059 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4061 if(bookHit) { // [HGM] book: simulate book reply
4062 static char bookMove[MSG_SIZ]; // a bit generous?
4064 programStats.nodes = programStats.depth = programStats.time =
4065 programStats.score = programStats.got_only_move = 0;
4066 sprintf(programStats.movelist, "%s (xbook)", bookHit);
4068 strcpy(bookMove, "move ");
4069 strcat(bookMove, bookHit);
4070 HandleMachineMove(bookMove, &first);
4079 if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4080 ics_getting_history = H_REQUESTED;
4081 sprintf(buf, "%smoves %d\n", ics_prefix, ics_gamenum);
4087 AnalysisPeriodicEvent(force)
4090 if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4091 && !force) || !appData.periodicUpdates)
4094 /* Send . command to Crafty to collect stats */
4095 SendToProgram(".\n", &first);
4097 /* Don't send another until we get a response (this makes
4098 us stop sending to old Crafty's which don't understand
4099 the "." command (sending illegal cmds resets node count & time,
4100 which looks bad)) */
4101 programStats.ok_to_send = 0;
4104 void ics_update_width(new_width)
4107 ics_printf("set width %d\n", new_width);
4111 SendMoveToProgram(moveNum, cps)
4113 ChessProgramState *cps;
4117 if (cps->useUsermove) {
4118 SendToProgram("usermove ", cps);
4122 if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4123 int len = space - parseList[moveNum];
4124 memcpy(buf, parseList[moveNum], len);
4126 buf[len] = NULLCHAR;
4128 sprintf(buf, "%s\n", parseList[moveNum]);
4130 SendToProgram(buf, cps);
4132 if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4133 AlphaRank(moveList[moveNum], 4);
4134 SendToProgram(moveList[moveNum], cps);
4135 AlphaRank(moveList[moveNum], 4); // and back
4137 /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
4138 * the engine. It would be nice to have a better way to identify castle
4140 if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
4141 && cps->useOOCastle) {
4142 int fromX = moveList[moveNum][0] - AAA;
4143 int fromY = moveList[moveNum][1] - ONE;
4144 int toX = moveList[moveNum][2] - AAA;
4145 int toY = moveList[moveNum][3] - ONE;
4146 if((boards[moveNum][fromY][fromX] == WhiteKing
4147 && boards[moveNum][toY][toX] == WhiteRook)
4148 || (boards[moveNum][fromY][fromX] == BlackKing
4149 && boards[moveNum][toY][toX] == BlackRook)) {
4150 if(toX > fromX) SendToProgram("O-O\n", cps);
4151 else SendToProgram("O-O-O\n", cps);
4153 else SendToProgram(moveList[moveNum], cps);
4155 else SendToProgram(moveList[moveNum], cps);
4156 /* End of additions by Tord */
4159 /* [HGM] setting up the opening has brought engine in force mode! */
4160 /* Send 'go' if we are in a mode where machine should play. */
4161 if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4162 (gameMode == TwoMachinesPlay ||
4164 gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite ||
4166 gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4167 SendToProgram("go\n", cps);
4168 if (appData.debugMode) {
4169 fprintf(debugFP, "(extra)\n");
4172 setboardSpoiledMachineBlack = 0;
4176 SendMoveToICS(moveType, fromX, fromY, toX, toY)
4178 int fromX, fromY, toX, toY;
4180 char user_move[MSG_SIZ];
4184 sprintf(user_move, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4185 (int)moveType, fromX, fromY, toX, toY);
4186 DisplayError(user_move + strlen("say "), 0);
4188 case WhiteKingSideCastle:
4189 case BlackKingSideCastle:
4190 case WhiteQueenSideCastleWild:
4191 case BlackQueenSideCastleWild:
4193 case WhiteHSideCastleFR:
4194 case BlackHSideCastleFR:
4196 sprintf(user_move, "o-o\n");
4198 case WhiteQueenSideCastle:
4199 case BlackQueenSideCastle:
4200 case WhiteKingSideCastleWild:
4201 case BlackKingSideCastleWild:
4203 case WhiteASideCastleFR:
4204 case BlackASideCastleFR:
4206 sprintf(user_move, "o-o-o\n");
4208 case WhitePromotionQueen:
4209 case BlackPromotionQueen:
4210 case WhitePromotionRook:
4211 case BlackPromotionRook:
4212 case WhitePromotionBishop:
4213 case BlackPromotionBishop:
4214 case WhitePromotionKnight:
4215 case BlackPromotionKnight:
4216 case WhitePromotionKing:
4217 case BlackPromotionKing:
4218 case WhitePromotionChancellor:
4219 case BlackPromotionChancellor:
4220 case WhitePromotionArchbishop:
4221 case BlackPromotionArchbishop:
4222 if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
4223 sprintf(user_move, "%c%c%c%c=%c\n",
4224 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4225 PieceToChar(WhiteFerz));
4226 else if(gameInfo.variant == VariantGreat)
4227 sprintf(user_move, "%c%c%c%c=%c\n",
4228 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4229 PieceToChar(WhiteMan));
4231 sprintf(user_move, "%c%c%c%c=%c\n",
4232 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4233 PieceToChar(PromoPiece(moveType)));
4237 sprintf(user_move, "%c@%c%c\n",
4238 ToUpper(PieceToChar((ChessSquare) fromX)),
4239 AAA + toX, ONE + toY);
4242 case WhiteCapturesEnPassant:
4243 case BlackCapturesEnPassant:
4244 case IllegalMove: /* could be a variant we don't quite understand */
4245 sprintf(user_move, "%c%c%c%c\n",
4246 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4249 SendToICS(user_move);
4250 if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
4251 ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
4255 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
4260 if (rf == DROP_RANK) {
4261 sprintf(move, "%c@%c%c\n",
4262 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
4264 if (promoChar == 'x' || promoChar == NULLCHAR) {
4265 sprintf(move, "%c%c%c%c\n",
4266 AAA + ff, ONE + rf, AAA + ft, ONE + rt);
4268 sprintf(move, "%c%c%c%c%c\n",
4269 AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
4275 ProcessICSInitScript(f)
4280 while (fgets(buf, MSG_SIZ, f)) {
4281 SendToICSDelayed(buf,(long)appData.msLoginDelay);
4288 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
4290 AlphaRank(char *move, int n)
4292 // char *p = move, c; int x, y;
4294 if (appData.debugMode) {
4295 fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
4299 move[2]>='0' && move[2]<='9' &&
4300 move[3]>='a' && move[3]<='x' ) {
4302 move[2] = BOARD_RGHT -1 - (move[2]-'1') + AAA;
4303 move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4305 if(move[0]>='0' && move[0]<='9' &&
4306 move[1]>='a' && move[1]<='x' &&
4307 move[2]>='0' && move[2]<='9' &&
4308 move[3]>='a' && move[3]<='x' ) {
4309 /* input move, Shogi -> normal */
4310 move[0] = BOARD_RGHT -1 - (move[0]-'1') + AAA;
4311 move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
4312 move[2] = BOARD_RGHT -1 - (move[2]-'1') + AAA;
4313 move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4316 move[3]>='0' && move[3]<='9' &&
4317 move[2]>='a' && move[2]<='x' ) {
4319 move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4320 move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4323 move[0]>='a' && move[0]<='x' &&
4324 move[3]>='0' && move[3]<='9' &&
4325 move[2]>='a' && move[2]<='x' ) {
4326 /* output move, normal -> Shogi */
4327 move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
4328 move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
4329 move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4330 move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4331 if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
4333 if (appData.debugMode) {
4334 fprintf(debugFP, " out = '%s'\n", move);
4338 /* Parser for moves from gnuchess, ICS, or user typein box */
4340 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
4343 ChessMove *moveType;
4344 int *fromX, *fromY, *toX, *toY;
4347 if (appData.debugMode) {
4348 fprintf(debugFP, "move to parse: %s\n", move);
4350 *moveType = yylexstr(moveNum, move);
4352 switch (*moveType) {
4353 case WhitePromotionChancellor:
4354 case BlackPromotionChancellor:
4355 case WhitePromotionArchbishop:
4356 case BlackPromotionArchbishop:
4357 case WhitePromotionQueen:
4358 case BlackPromotionQueen:
4359 case WhitePromotionRook:
4360 case BlackPromotionRook:
4361 case WhitePromotionBishop:
4362 case BlackPromotionBishop:
4363 case WhitePromotionKnight:
4364 case BlackPromotionKnight:
4365 case WhitePromotionKing:
4366 case BlackPromotionKing:
4368 case WhiteCapturesEnPassant:
4369 case BlackCapturesEnPassant:
4370 case WhiteKingSideCastle:
4371 case WhiteQueenSideCastle:
4372 case BlackKingSideCastle:
4373 case BlackQueenSideCastle:
4374 case WhiteKingSideCastleWild:
4375 case WhiteQueenSideCastleWild:
4376 case BlackKingSideCastleWild:
4377 case BlackQueenSideCastleWild:
4378 /* Code added by Tord: */
4379 case WhiteHSideCastleFR:
4380 case WhiteASideCastleFR:
4381 case BlackHSideCastleFR:
4382 case BlackASideCastleFR:
4383 /* End of code added by Tord */
4384 case IllegalMove: /* bug or odd chess variant */
4385 *fromX = currentMoveString[0] - AAA;
4386 *fromY = currentMoveString[1] - ONE;
4387 *toX = currentMoveString[2] - AAA;
4388 *toY = currentMoveString[3] - ONE;
4389 *promoChar = currentMoveString[4];
4390 if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
4391 *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
4392 if (appData.debugMode) {
4393 fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
4395 *fromX = *fromY = *toX = *toY = 0;
4398 if (appData.testLegality) {
4399 return (*moveType != IllegalMove);
4401 return !(*fromX == *toX && *fromY == *toY) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
4402 WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
4407 *fromX = *moveType == WhiteDrop ?
4408 (int) CharToPiece(ToUpper(currentMoveString[0])) :
4409 (int) CharToPiece(ToLower(currentMoveString[0]));
4411 *toX = currentMoveString[2] - AAA;
4412 *toY = currentMoveString[3] - ONE;
4413 *promoChar = NULLCHAR;
4417 case ImpossibleMove:
4418 case (ChessMove) 0: /* end of file */
4427 if (appData.debugMode) {
4428 fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
4431 *fromX = *fromY = *toX = *toY = 0;
4432 *promoChar = NULLCHAR;
4440 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
4441 int fromX, fromY, toX, toY; char promoChar;
4446 endPV = forwardMostMove;
4448 while(*pv == ' ') pv++;
4449 if(*pv == '(') pv++; // first (ponder) move can be in parentheses
4450 valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
4451 if(appData.debugMode){
4452 fprintf(debugFP,"parsePV: %d %c%c%c%c '%s'\n", valid, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, pv);
4454 if(!valid && nr == 0 &&
4455 ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
4456 nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
4458 while(*pv && *pv++ != ' '); // skip what we parsed; assume space separators
4459 if(moveType == Comment) { valid++; continue; } // allow comments in PV
4461 if(endPV+1 > framePtr) break; // no space, truncate
4464 CopyBoard(boards[endPV], boards[endPV-1]);
4465 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
4466 moveList[endPV-1][0] = fromX + AAA;
4467 moveList[endPV-1][1] = fromY + ONE;
4468 moveList[endPV-1][2] = toX + AAA;
4469 moveList[endPV-1][3] = toY + ONE;
4470 parseList[endPV-1][0] = NULLCHAR;
4472 currentMove = endPV;
4473 if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
4474 SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
4475 moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
4476 DrawPosition(TRUE, boards[currentMove]);
4479 static int lastX, lastY;
4482 LoadMultiPV(int x, int y, char *buf, int index, int *start, int *end)
4486 if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
4487 lastX = x; lastY = y;
4488 while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
4490 while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
4492 while(buf[index] && buf[index] != '\n') index++;
4494 ParsePV(buf+startPV);
4495 *start = startPV; *end = index-1;
4500 LoadPV(int x, int y)
4501 { // called on right mouse click to load PV
4502 int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
4503 lastX = x; lastY = y;
4504 ParsePV(lastPV[which]); // load the PV of the thinking engine in the boards array.
4511 if(endPV < 0) return;
4513 currentMove = forwardMostMove;
4514 ClearPremoveHighlights();
4515 DrawPosition(TRUE, boards[currentMove]);
4519 MovePV(int x, int y, int h)
4520 { // step through PV based on mouse coordinates (called on mouse move)
4521 int margin = h>>3, step = 0;
4523 if(endPV < 0) return;
4524 // we must somehow check if right button is still down (might be released off board!)
4525 if(y < margin && (abs(x - lastX) > 6 || abs(y - lastY) > 6)) step = 1; else
4526 if(y > h - margin && (abs(x - lastX) > 6 || abs(y - lastY) > 6)) step = -1; else
4527 if( y > lastY + 6 ) step = -1; else if(y < lastY - 6) step = 1;
4529 lastX = x; lastY = y;
4530 if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
4531 currentMove += step;
4532 if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
4533 SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
4534 moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
4535 DrawPosition(FALSE, boards[currentMove]);
4539 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
4540 // All positions will have equal probability, but the current method will not provide a unique
4541 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
4547 int piecesLeft[(int)BlackPawn];
4548 int seed, nrOfShuffles;
4550 void GetPositionNumber()
4551 { // sets global variable seed
4554 seed = appData.defaultFrcPosition;
4555 if(seed < 0) { // randomize based on time for negative FRC position numbers
4556 for(i=0; i<50; i++) seed += random();
4557 seed = random() ^ random() >> 8 ^ random() << 8;
4558 if(seed<0) seed = -seed;
4562 int put(Board board, int pieceType, int rank, int n, int shade)
4563 // put the piece on the (n-1)-th empty squares of the given shade
4567 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
4568 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
4569 board[rank][i] = (ChessSquare) pieceType;
4570 squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
4572 piecesLeft[pieceType]--;
4580 void AddOnePiece(Board board, int pieceType, int rank, int shade)
4581 // calculate where the next piece goes, (any empty square), and put it there
4585 i = seed % squaresLeft[shade];
4586 nrOfShuffles *= squaresLeft[shade];
4587 seed /= squaresLeft[shade];
4588 put(board, pieceType, rank, i, shade);
4591 void AddTwoPieces(Board board, int pieceType, int rank)
4592 // calculate where the next 2 identical pieces go, (any empty square), and put it there
4594 int i, n=squaresLeft[ANY], j=n-1, k;
4596 k = n*(n-1)/2; // nr of possibilities, not counting permutations
4597 i = seed % k; // pick one
4600 while(i >= j) i -= j--;
4601 j = n - 1 - j; i += j;
4602 put(board, pieceType, rank, j, ANY);
4603 put(board, pieceType, rank, i, ANY);
4606 void SetUpShuffle(Board board, int number)
4610 GetPositionNumber(); nrOfShuffles = 1;
4612 squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
4613 squaresLeft[ANY] = BOARD_RGHT - BOARD_LEFT;
4614 squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
4616 for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
4618 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
4619 p = (int) board[0][i];
4620 if(p < (int) BlackPawn) piecesLeft[p] ++;
4621 board[0][i] = EmptySquare;
4624 if(PosFlags(0) & F_ALL_CASTLE_OK) {
4625 // shuffles restricted to allow normal castling put KRR first
4626 if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
4627 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
4628 else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
4629 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
4630 if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
4631 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
4632 if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
4633 put(board, WhiteRook, 0, 0, ANY);
4634 // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
4637 if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
4638 // only for even boards make effort to put pairs of colorbound pieces on opposite colors
4639 for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
4640 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
4641 while(piecesLeft[p] >= 2) {
4642 AddOnePiece(board, p, 0, LITE);
4643 AddOnePiece(board, p, 0, DARK);
4645 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
4648 for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
4649 // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
4650 // but we leave King and Rooks for last, to possibly obey FRC restriction
4651 if(p == (int)WhiteRook) continue;
4652 while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
4653 if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY); // add the odd piece
4656 // now everything is placed, except perhaps King (Unicorn) and Rooks
4658 if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
4659 // Last King gets castling rights
4660 while(piecesLeft[(int)WhiteUnicorn]) {
4661 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
4662 initialRights[2] = initialRights[5] = board[CASTLING][2] = board[CASTLING][5] = i;
4665 while(piecesLeft[(int)WhiteKing]) {
4666 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
4667 initialRights[2] = initialRights[5] = board[CASTLING][2] = board[CASTLING][5] = i;
4672 while(piecesLeft[(int)WhiteKing]) AddOnePiece(board, WhiteKing, 0, ANY);
4673 while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
4676 // Only Rooks can be left; simply place them all
4677 while(piecesLeft[(int)WhiteRook]) {
4678 i = put(board, WhiteRook, 0, 0, ANY);
4679 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
4682 initialRights[1] = initialRights[4] = board[CASTLING][1] = board[CASTLING][4] = i;
4684 initialRights[0] = initialRights[3] = board[CASTLING][0] = board[CASTLING][3] = i;
4687 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
4688 board[BOARD_HEIGHT-1][i] = (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
4691 if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
4694 int SetCharTable( char *table, const char * map )
4695 /* [HGM] moved here from winboard.c because of its general usefulness */
4696 /* Basically a safe strcpy that uses the last character as King */
4698 int result = FALSE; int NrPieces;
4700 if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
4701 && NrPieces >= 12 && !(NrPieces&1)) {
4702 int i; /* [HGM] Accept even length from 12 to 34 */
4704 for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
4705 for( i=0; i<NrPieces/2-1; i++ ) {
4707 table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
4709 table[(int) WhiteKing] = map[NrPieces/2-1];
4710 table[(int) BlackKing] = map[NrPieces-1];
4718 void Prelude(Board board)
4719 { // [HGM] superchess: random selection of exo-pieces
4720 int i, j, k; ChessSquare p;
4721 static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
4723 GetPositionNumber(); // use FRC position number
4725 if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
4726 SetCharTable(pieceToChar, appData.pieceToCharTable);
4727 for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
4728 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
4731 j = seed%4; seed /= 4;
4732 p = board[0][BOARD_LEFT+j]; board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
4733 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
4734 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
4735 j = seed%3 + (seed%3 >= j); seed /= 3;
4736 p = board[0][BOARD_LEFT+j]; board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
4737 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
4738 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
4739 j = seed%3; seed /= 3;
4740 p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
4741 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
4742 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
4743 j = seed%2 + (seed%2 >= j); seed /= 2;
4744 p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
4745 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
4746 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
4747 j = seed%4; seed /= 4; put(board, exoPieces[3], 0, j, ANY);
4748 j = seed%3; seed /= 3; put(board, exoPieces[2], 0, j, ANY);
4749 j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
4750 put(board, exoPieces[0], 0, 0, ANY);
4751 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
4755 InitPosition(redraw)
4758 ChessSquare (* pieces)[BOARD_FILES];
4759 int i, j, pawnRow, overrule,
4760 oldx = gameInfo.boardWidth,
4761 oldy = gameInfo.boardHeight,
4762 oldh = gameInfo.holdingsWidth,
4763 oldv = gameInfo.variant;
4765 if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
4767 /* [AS] Initialize pv info list [HGM] and game status */
4769 for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
4770 pvInfoList[i].depth = 0;
4771 boards[i][EP_STATUS] = EP_NONE;
4772 for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
4775 initialRulePlies = 0; /* 50-move counter start */
4777 castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
4778 castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
4782 /* [HGM] logic here is completely changed. In stead of full positions */
4783 /* the initialized data only consist of the two backranks. The switch */
4784 /* selects which one we will use, which is than copied to the Board */
4785 /* initialPosition, which for the rest is initialized by Pawns and */
4786 /* empty squares. This initial position is then copied to boards[0], */
4787 /* possibly after shuffling, so that it remains available. */
4789 gameInfo.holdingsWidth = 0; /* default board sizes */
4790 gameInfo.boardWidth = 8;
4791 gameInfo.boardHeight = 8;
4792 gameInfo.holdingsSize = 0;
4793 nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
4794 for(i=0; i<BOARD_FILES-2; i++)
4795 initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
4796 initialPosition[EP_STATUS] = EP_NONE;
4797 SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
4799 switch (gameInfo.variant) {
4800 case VariantFischeRandom:
4801 shuffleOpenings = TRUE;
4805 case VariantShatranj:
4806 pieces = ShatranjArray;
4807 nrCastlingRights = 0;
4808 SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
4811 pieces = makrukArray;
4812 nrCastlingRights = 0;
4813 startedFromSetupPosition = TRUE;
4814 SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
4816 case VariantTwoKings:
4817 pieces = twoKingsArray;
4819 case VariantCapaRandom:
4820 shuffleOpenings = TRUE;
4821 case VariantCapablanca:
4822 pieces = CapablancaArray;
4823 gameInfo.boardWidth = 10;
4824 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
4827 pieces = GothicArray;
4828 gameInfo.boardWidth = 10;
4829 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
4832 pieces = JanusArray;
4833 gameInfo.boardWidth = 10;
4834 SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
4835 nrCastlingRights = 6;
4836 initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
4837 initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
4838 initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
4839 initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
4840 initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
4841 initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
4844 pieces = FalconArray;
4845 gameInfo.boardWidth = 10;
4846 SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
4848 case VariantXiangqi:
4849 pieces = XiangqiArray;
4850 gameInfo.boardWidth = 9;
4851 gameInfo.boardHeight = 10;
4852 nrCastlingRights = 0;
4853 SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
4856 pieces = ShogiArray;
4857 gameInfo.boardWidth = 9;
4858 gameInfo.boardHeight = 9;
4859 gameInfo.holdingsSize = 7;
4860 nrCastlingRights = 0;
4861 SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
4863 case VariantCourier:
4864 pieces = CourierArray;
4865 gameInfo.boardWidth = 12;
4866 nrCastlingRights = 0;
4867 SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
4869 case VariantKnightmate:
4870 pieces = KnightmateArray;
4871 SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
4874 pieces = fairyArray;
4875 SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
4878 pieces = GreatArray;
4879 gameInfo.boardWidth = 10;
4880 SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
4881 gameInfo.holdingsSize = 8;
4885 SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
4886 gameInfo.holdingsSize = 8;
4887 startedFromSetupPosition = TRUE;
4889 case VariantCrazyhouse:
4890 case VariantBughouse:
4892 SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
4893 gameInfo.holdingsSize = 5;
4895 case VariantWildCastle:
4897 /* !!?shuffle with kings guaranteed to be on d or e file */
4898 shuffleOpenings = 1;
4900 case VariantNoCastle:
4902 nrCastlingRights = 0;
4903 /* !!?unconstrained back-rank shuffle */
4904 shuffleOpenings = 1;
4909 if(appData.NrFiles >= 0) {
4910 if(gameInfo.boardWidth != appData.NrFiles) overrule++;
4911 gameInfo.boardWidth = appData.NrFiles;
4913 if(appData.NrRanks >= 0) {
4914 gameInfo.boardHeight = appData.NrRanks;
4916 if(appData.holdingsSize >= 0) {
4917 i = appData.holdingsSize;
4918 if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
4919 gameInfo.holdingsSize = i;
4921 if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
4922 if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
4923 DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
4925 pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
4926 if(pawnRow < 1) pawnRow = 1;
4927 if(gameInfo.variant == VariantMakruk) pawnRow = 2;
4929 /* User pieceToChar list overrules defaults */
4930 if(appData.pieceToCharTable != NULL)
4931 SetCharTable(pieceToChar, appData.pieceToCharTable);
4933 for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
4935 if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
4936 s = (ChessSquare) 0; /* account holding counts in guard band */
4937 for( i=0; i<BOARD_HEIGHT; i++ )
4938 initialPosition[i][j] = s;
4940 if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
4941 initialPosition[0][j] = pieces[0][j-gameInfo.holdingsWidth];
4942 initialPosition[pawnRow][j] = WhitePawn;
4943 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = BlackPawn;
4944 if(gameInfo.variant == VariantXiangqi) {
4946 initialPosition[pawnRow][j] =
4947 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
4948 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
4949 initialPosition[2][j] = WhiteCannon;
4950 initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
4954 initialPosition[BOARD_HEIGHT-1][j] = pieces[1][j-gameInfo.holdingsWidth];
4956 if( (gameInfo.variant == VariantShogi) && !overrule ) {
4959 initialPosition[1][j] = WhiteBishop;
4960 initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
4962 initialPosition[1][j] = WhiteRook;
4963 initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
4966 if( nrCastlingRights == -1) {
4967 /* [HGM] Build normal castling rights (must be done after board sizing!) */
4968 /* This sets default castling rights from none to normal corners */
4969 /* Variants with other castling rights must set them themselves above */
4970 nrCastlingRights = 6;
4972 initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
4973 initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
4974 initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
4975 initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
4976 initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
4977 initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
4980 if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
4981 if(gameInfo.variant == VariantGreat) { // promotion commoners
4982 initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
4983 initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
4984 initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
4985 initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
4987 if (appData.debugMode) {
4988 fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
4990 if(shuffleOpenings) {
4991 SetUpShuffle(initialPosition, appData.defaultFrcPosition);
4992 startedFromSetupPosition = TRUE;
4994 if(startedFromPositionFile) {
4995 /* [HGM] loadPos: use PositionFile for every new game */
4996 CopyBoard(initialPosition, filePosition);
4997 for(i=0; i<nrCastlingRights; i++)
4998 initialRights[i] = filePosition[CASTLING][i];
4999 startedFromSetupPosition = TRUE;
5002 CopyBoard(boards[0], initialPosition);
5004 if(oldx != gameInfo.boardWidth ||
5005 oldy != gameInfo.boardHeight ||
5006 oldh != gameInfo.holdingsWidth
5008 || oldv == VariantGothic || // For licensing popups
5009 gameInfo.variant == VariantGothic
5012 || oldv == VariantFalcon ||
5013 gameInfo.variant == VariantFalcon
5016 InitDrawingSizes(-2 ,0);
5019 DrawPosition(TRUE, boards[currentMove]);
5023 SendBoard(cps, moveNum)
5024 ChessProgramState *cps;
5027 char message[MSG_SIZ];
5029 if (cps->useSetboard) {
5030 char* fen = PositionToFEN(moveNum, cps->fenOverride);
5031 sprintf(message, "setboard %s\n", fen);
5032 SendToProgram(message, cps);
5038 /* Kludge to set black to move, avoiding the troublesome and now
5039 * deprecated "black" command.
5041 if (!WhiteOnMove(moveNum)) SendToProgram("a2a3\n", cps);
5043 SendToProgram("edit\n", cps);
5044 SendToProgram("#\n", cps);
5045 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
5046 bp = &boards[moveNum][i][BOARD_LEFT];
5047 for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
5048 if ((int) *bp < (int) BlackPawn) {
5049 sprintf(message, "%c%c%c\n", PieceToChar(*bp),
5051 if(message[0] == '+' || message[0] == '~') {
5052 sprintf(message, "%c%c%c+\n",
5053 PieceToChar((ChessSquare)(DEMOTED *bp)),
5056 if(cps->alphaRank) { /* [HGM] shogi: translate coords */
5057 message[1] = BOARD_RGHT - 1 - j + '1';
5058 message[2] = BOARD_HEIGHT - 1 - i + 'a';
5060 SendToProgram(message, cps);
5065 SendToProgram("c\n", cps);
5066 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
5067 bp = &boards[moveNum][i][BOARD_LEFT];
5068 for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
5069 if (((int) *bp != (int) EmptySquare)
5070 && ((int) *bp >= (int) BlackPawn)) {
5071 sprintf(message, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
5073 if(message[0] == '+' || message[0] == '~') {
5074 sprintf(message, "%c%c%c+\n",
5075 PieceToChar((ChessSquare)(DEMOTED *bp)),
5078 if(cps->alphaRank) { /* [HGM] shogi: translate coords */
5079 message[1] = BOARD_RGHT - 1 - j + '1';
5080 message[2] = BOARD_HEIGHT - 1 - i + 'a';
5082 SendToProgram(message, cps);
5087 SendToProgram(".\n", cps);
5089 setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
5093 HasPromotionChoice(int fromX, int fromY, int toX, int toY, char *promoChoice)
5095 /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
5096 /* [HGM] add Shogi promotions */
5097 int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
5102 if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
5103 if(toX < BOARD_LEFT || toX >= BOARD_RGHT) return FALSE; // move into holdings
5105 if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
5106 !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
5109 piece = boards[currentMove][fromY][fromX];
5110 if(gameInfo.variant == VariantShogi) {
5111 promotionZoneSize = 3;
5112 highestPromotingPiece = (int)WhiteFerz;
5113 } else if(gameInfo.variant == VariantMakruk) {
5114 promotionZoneSize = 3;
5117 // next weed out all moves that do not touch the promotion zone at all
5118 if((int)piece >= BlackPawn) {
5119 if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
5121 highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
5123 if( toY < BOARD_HEIGHT - promotionZoneSize &&
5124 fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
5127 if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
5129 // weed out mandatory Shogi promotions
5130 if(gameInfo.variant == VariantShogi) {
5131 if(piece >= BlackPawn) {
5132 if(toY == 0 && piece == BlackPawn ||
5133 toY == 0 && piece == BlackQueen ||
5134 toY <= 1 && piece == BlackKnight) {
5139 if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
5140 toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
5141 toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
5148 // weed out obviously illegal Pawn moves
5149 if(appData.testLegality && (piece == WhitePawn || piece == BlackPawn) ) {
5150 if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
5151 if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
5152 if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
5153 if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
5154 // note we are not allowed to test for valid (non-)capture, due to premove
5157 // we either have a choice what to promote to, or (in Shogi) whether to promote
5158 if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk) {
5159 *promoChoice = PieceToChar(BlackFerz); // no choice
5162 if(appData.alwaysPromoteToQueen) { // predetermined
5163 if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantLosers)
5164 *promoChoice = PieceToChar(BlackKing); // in Suicide Q is the last thing we want
5165 else *promoChoice = PieceToChar(BlackQueen);
5169 // suppress promotion popup on illegal moves that are not premoves
5170 premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
5171 gameMode == IcsPlayingBlack && WhiteOnMove(currentMove);
5172 if(appData.testLegality && !premove) {
5173 moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5174 fromY, fromX, toY, toX, NULLCHAR);
5175 if(moveType != WhitePromotionQueen && moveType != BlackPromotionQueen &&
5176 moveType != WhitePromotionKnight && moveType != BlackPromotionKnight)
5184 InPalace(row, column)
5186 { /* [HGM] for Xiangqi */
5187 if( (row < 3 || row > BOARD_HEIGHT-4) &&
5188 column < (BOARD_WIDTH + 4)/2 &&
5189 column > (BOARD_WIDTH - 5)/2 ) return TRUE;
5194 PieceForSquare (x, y)
5198 if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
5201 return boards[currentMove][y][x];
5205 OKToStartUserMove(x, y)
5208 ChessSquare from_piece;
5211 if (matchMode) return FALSE;
5212 if (gameMode == EditPosition) return TRUE;
5214 if (x >= 0 && y >= 0)
5215 from_piece = boards[currentMove][y][x];
5217 from_piece = EmptySquare;
5219 if (from_piece == EmptySquare) return FALSE;
5221 white_piece = (int)from_piece >= (int)WhitePawn &&
5222 (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
5225 case PlayFromGameFile:
5227 case TwoMachinesPlay:
5235 case MachinePlaysWhite:
5236 case IcsPlayingBlack:
5237 if (appData.zippyPlay) return FALSE;
5239 DisplayMoveError(_("You are playing Black"));
5244 case MachinePlaysBlack:
5245 case IcsPlayingWhite:
5246 if (appData.zippyPlay) return FALSE;
5248 DisplayMoveError(_("You are playing White"));
5254 if (!white_piece && WhiteOnMove(currentMove)) {
5255 DisplayMoveError(_("It is White's turn"));
5258 if (white_piece && !WhiteOnMove(currentMove)) {
5259 DisplayMoveError(_("It is Black's turn"));
5262 if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
5263 /* Editing correspondence game history */
5264 /* Could disallow this or prompt for confirmation */
5269 case BeginningOfGame:
5270 if (appData.icsActive) return FALSE;
5271 if (!appData.noChessProgram) {
5273 DisplayMoveError(_("You are playing White"));
5280 if (!white_piece && WhiteOnMove(currentMove)) {
5281 DisplayMoveError(_("It is White's turn"));
5284 if (white_piece && !WhiteOnMove(currentMove)) {
5285 DisplayMoveError(_("It is Black's turn"));
5294 if (currentMove != forwardMostMove && gameMode != AnalyzeMode
5295 && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
5296 && gameMode != AnalyzeFile && gameMode != Training) {
5297 DisplayMoveError(_("Displayed position is not current"));
5303 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
5304 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
5305 int lastLoadGameUseList = FALSE;
5306 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
5307 ChessMove lastLoadGameStart = (ChessMove) 0;
5310 UserMoveTest(fromX, fromY, toX, toY, promoChar, captureOwn)
5311 int fromX, fromY, toX, toY;
5316 ChessSquare pdown, pup;
5318 /* Check if the user is playing in turn. This is complicated because we
5319 let the user "pick up" a piece before it is his turn. So the piece he
5320 tried to pick up may have been captured by the time he puts it down!
5321 Therefore we use the color the user is supposed to be playing in this
5322 test, not the color of the piece that is currently on the starting
5323 square---except in EditGame mode, where the user is playing both
5324 sides; fortunately there the capture race can't happen. (It can
5325 now happen in IcsExamining mode, but that's just too bad. The user
5326 will get a somewhat confusing message in that case.)
5330 case PlayFromGameFile:
5332 case TwoMachinesPlay:
5336 /* We switched into a game mode where moves are not accepted,
5337 perhaps while the mouse button was down. */
5338 return ImpossibleMove;
5340 case MachinePlaysWhite:
5341 /* User is moving for Black */
5342 if (WhiteOnMove(currentMove)) {
5343 DisplayMoveError(_("It is White's turn"));
5344 return ImpossibleMove;
5348 case MachinePlaysBlack:
5349 /* User is moving for White */
5350 if (!WhiteOnMove(currentMove)) {
5351 DisplayMoveError(_("It is Black's turn"));
5352 return ImpossibleMove;
5358 case BeginningOfGame:
5361 if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
5362 (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
5363 /* User is moving for Black */
5364 if (WhiteOnMove(currentMove)) {
5365 DisplayMoveError(_("It is White's turn"));
5366 return ImpossibleMove;
5369 /* User is moving for White */
5370 if (!WhiteOnMove(currentMove)) {
5371 DisplayMoveError(_("It is Black's turn"));
5372 return ImpossibleMove;
5377 case IcsPlayingBlack:
5378 /* User is moving for Black */
5379 if (WhiteOnMove(currentMove)) {
5380 if (!appData.premove) {
5381 DisplayMoveError(_("It is White's turn"));
5382 } else if (toX >= 0 && toY >= 0) {
5385 premoveFromX = fromX;
5386 premoveFromY = fromY;
5387 premovePromoChar = promoChar;
5389 if (appData.debugMode)
5390 fprintf(debugFP, "Got premove: fromX %d,"
5391 "fromY %d, toX %d, toY %d\n",
5392 fromX, fromY, toX, toY);
5394 return ImpossibleMove;
5398 case IcsPlayingWhite:
5399 /* User is moving for White */
5400 if (!WhiteOnMove(currentMove)) {
5401 if (!appData.premove) {
5402 DisplayMoveError(_("It is Black's turn"));
5403 } else if (toX >= 0 && toY >= 0) {
5406 premoveFromX = fromX;
5407 premoveFromY = fromY;
5408 premovePromoChar = promoChar;
5410 if (appData.debugMode)
5411 fprintf(debugFP, "Got premove: fromX %d,"
5412 "fromY %d, toX %d, toY %d\n",
5413 fromX, fromY, toX, toY);
5415 return ImpossibleMove;
5423 /* EditPosition, empty square, or different color piece;
5424 click-click move is possible */
5425 if (toX == -2 || toY == -2) {
5426 boards[0][fromY][fromX] = EmptySquare;
5427 return AmbiguousMove;
5428 } else if (toX >= 0 && toY >= 0) {
5429 boards[0][toY][toX] = boards[0][fromY][fromX];
5430 if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
5431 if(boards[0][fromY][0] != EmptySquare) {
5432 if(boards[0][fromY][1]) boards[0][fromY][1]--;
5433 if(boards[0][fromY][1] == 0) boards[0][fromY][0] = EmptySquare;
5436 if(fromX == BOARD_RGHT+1) {
5437 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
5438 if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
5439 if(boards[0][fromY][BOARD_WIDTH-2] == 0) boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
5442 boards[0][fromY][fromX] = EmptySquare;
5443 return AmbiguousMove;
5445 return ImpossibleMove;
5448 if(toX < 0 || toY < 0) return ImpossibleMove;
5449 pdown = boards[currentMove][fromY][fromX];
5450 pup = boards[currentMove][toY][toX];
5452 /* [HGM] If move started in holdings, it means a drop */
5453 if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) {
5454 if( pup != EmptySquare ) return ImpossibleMove;
5455 if(appData.testLegality) {
5456 /* it would be more logical if LegalityTest() also figured out
5457 * which drops are legal. For now we forbid pawns on back rank.
5458 * Shogi is on its own here...
5460 if( (pdown == WhitePawn || pdown == BlackPawn) &&
5461 (toY == 0 || toY == BOARD_HEIGHT -1 ) )
5462 return(ImpossibleMove); /* no pawn drops on 1st/8th */
5464 return WhiteDrop; /* Not needed to specify white or black yet */
5467 /* [HGM] always test for legality, to get promotion info */
5468 moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5469 fromY, fromX, toY, toX, promoChar);
5470 /* [HGM] but possibly ignore an IllegalMove result */
5471 if (appData.testLegality) {
5472 if (moveType == IllegalMove || moveType == ImpossibleMove) {
5473 DisplayMoveError(_("Illegal move"));
5474 return ImpossibleMove;
5479 /* [HGM] <popupFix> in stead of calling FinishMove directly, this
5480 function is made into one that returns an OK move type if FinishMove
5481 should be called. This to give the calling driver routine the
5482 opportunity to finish the userMove input with a promotion popup,
5483 without bothering the user with this for invalid or illegal moves */
5485 /* FinishMove(moveType, fromX, fromY, toX, toY, promoChar); */
5488 /* Common tail of UserMoveEvent and DropMenuEvent */
5490 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
5492 int fromX, fromY, toX, toY;
5493 /*char*/int promoChar;
5497 if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) && promoChar != NULLCHAR) {
5498 // [HGM] superchess: suppress promotions to non-available piece
5499 int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
5500 if(WhiteOnMove(currentMove)) {
5501 if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
5503 if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
5507 /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
5508 move type in caller when we know the move is a legal promotion */
5509 if(moveType == NormalMove && promoChar)
5510 moveType = PromoCharToMoveType(WhiteOnMove(currentMove), promoChar);
5512 /* [HGM] convert drag-and-drop piece drops to standard form */
5513 if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ){
5514 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
5515 if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n",
5516 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
5517 // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
5518 if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
5519 fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
5520 while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++;
5524 /* [HGM] <popupFix> The following if has been moved here from
5525 UserMoveEvent(). Because it seemed to belong here (why not allow
5526 piece drops in training games?), and because it can only be
5527 performed after it is known to what we promote. */
5528 if (gameMode == Training) {
5529 /* compare the move played on the board to the next move in the
5530 * game. If they match, display the move and the opponent's response.
5531 * If they don't match, display an error message.
5535 CopyBoard(testBoard, boards[currentMove]);
5536 ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
5538 if (CompareBoards(testBoard, boards[currentMove+1])) {
5539 ForwardInner(currentMove+1);
5541 /* Autoplay the opponent's response.
5542 * if appData.animate was TRUE when Training mode was entered,
5543 * the response will be animated.
5545 saveAnimate = appData.animate;
5546 appData.animate = animateTraining;
5547 ForwardInner(currentMove+1);
5548 appData.animate = saveAnimate;
5550 /* check for the end of the game */
5551 if (currentMove >= forwardMostMove) {
5552 gameMode = PlayFromGameFile;
5554 SetTrainingModeOff();
5555 DisplayInformation(_("End of game"));
5558 DisplayError(_("Incorrect move"), 0);
5563 /* Ok, now we know that the move is good, so we can kill
5564 the previous line in Analysis Mode */
5565 if ((gameMode == AnalyzeMode || gameMode == EditGame)
5566 && currentMove < forwardMostMove) {
5567 PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
5570 /* If we need the chess program but it's dead, restart it */
5571 ResurrectChessProgram();
5573 /* A user move restarts a paused game*/
5577 thinkOutput[0] = NULLCHAR;
5579 MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
5581 if(Adjudicate(NULL)) return 1; // [HGM] adjudicate: take care of automtic game end
5583 if (gameMode == BeginningOfGame) {
5584 if (appData.noChessProgram) {
5585 gameMode = EditGame;
5589 gameMode = MachinePlaysBlack;
5592 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
5594 if (first.sendName) {
5595 sprintf(buf, "name %s\n", gameInfo.white);
5596 SendToProgram(buf, &first);
5603 /* Relay move to ICS or chess engine */
5604 if (appData.icsActive) {
5605 if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
5606 gameMode == IcsExamining) {
5607 if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
5608 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
5610 SendMoveToICS(moveType, fromX, fromY, toX, toY);
5612 // also send plain move, in case ICS does not understand atomic claims
5613 SendMoveToICS(moveType, fromX, fromY, toX, toY);
5617 if (first.sendTime && (gameMode == BeginningOfGame ||
5618 gameMode == MachinePlaysWhite ||
5619 gameMode == MachinePlaysBlack)) {
5620 SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
5622 if (gameMode != EditGame && gameMode != PlayFromGameFile) {
5623 // [HGM] book: if program might be playing, let it use book
5624 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
5625 first.maybeThinking = TRUE;
5626 } else SendMoveToProgram(forwardMostMove-1, &first);
5627 if (currentMove == cmailOldMove + 1) {
5628 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
5632 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5636 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
5642 if (WhiteOnMove(currentMove)) {
5643 GameEnds(BlackWins, "Black mates", GE_PLAYER);
5645 GameEnds(WhiteWins, "White mates", GE_PLAYER);
5649 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
5654 case MachinePlaysBlack:
5655 case MachinePlaysWhite:
5656 /* disable certain menu options while machine is thinking */
5657 SetMachineThinkingEnables();
5664 userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
5666 if(bookHit) { // [HGM] book: simulate book reply
5667 static char bookMove[MSG_SIZ]; // a bit generous?
5669 programStats.nodes = programStats.depth = programStats.time =
5670 programStats.score = programStats.got_only_move = 0;
5671 sprintf(programStats.movelist, "%s (xbook)", bookHit);
5673 strcpy(bookMove, "move ");
5674 strcat(bookMove, bookHit);
5675 HandleMachineMove(bookMove, &first);
5681 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
5682 int fromX, fromY, toX, toY;
5685 /* [HGM] This routine was added to allow calling of its two logical
5686 parts from other modules in the old way. Before, UserMoveEvent()
5687 automatically called FinishMove() if the move was OK, and returned
5688 otherwise. I separated the two, in order to make it possible to
5689 slip a promotion popup in between. But that it always needs two
5690 calls, to the first part, (now called UserMoveTest() ), and to
5691 FinishMove if the first part succeeded. Calls that do not need
5692 to do anything in between, can call this routine the old way.
5694 ChessMove moveType = UserMoveTest(fromX, fromY, toX, toY, promoChar, FALSE);
5695 if(appData.debugMode) fprintf(debugFP, "moveType 4 = %d, promochar = %x\n", moveType, promoChar);
5696 if(moveType == AmbiguousMove)
5697 DrawPosition(FALSE, boards[currentMove]);
5698 else if(moveType != ImpossibleMove && moveType != Comment)
5699 FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
5703 Mark(board, flags, kind, rf, ff, rt, ft, closure)
5710 typedef char Markers[BOARD_RANKS][BOARD_FILES];
5711 Markers *m = (Markers *) closure;
5712 if(rf == fromY && ff == fromX)
5713 (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
5714 || kind == WhiteCapturesEnPassant
5715 || kind == BlackCapturesEnPassant);
5716 else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
5720 MarkTargetSquares(int clear)
5723 if(!appData.markers || !appData.highlightDragging ||
5724 !appData.testLegality || gameMode == EditPosition) return;
5726 for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) marker[y][x] = 0;
5729 GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker);
5730 if(PosFlags(0) & F_MANDATORY_CAPTURE) {
5731 for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
5733 for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
5736 DrawPosition(TRUE, NULL);
5739 void LeftClick(ClickType clickType, int xPix, int yPix)
5742 Boolean saveAnimate;
5743 static int second = 0, promotionChoice = 0;
5744 char promoChoice = NULLCHAR;
5746 if (clickType == Press) ErrorPopDown();
5747 MarkTargetSquares(1);
5749 x = EventToSquare(xPix, BOARD_WIDTH);
5750 y = EventToSquare(yPix, BOARD_HEIGHT);
5751 if (!flipView && y >= 0) {
5752 y = BOARD_HEIGHT - 1 - y;
5754 if (flipView && x >= 0) {
5755 x = BOARD_WIDTH - 1 - x;
5758 if(promotionChoice) { // we are waiting for a click to indicate promotion piece
5759 if(clickType == Release) return; // ignore upclick of click-click destination
5760 promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
5761 if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
5762 if(gameInfo.holdingsWidth &&
5763 (WhiteOnMove(currentMove)
5764 ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y > 0
5765 : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT-1) ) {
5766 // click in right holdings, for determining promotion piece
5767 ChessSquare p = boards[currentMove][y][x];
5768 if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
5769 if(p != EmptySquare) {
5770 FinishMove(NormalMove, fromX, fromY, toX, toY, ToLower(PieceToChar(p)));
5775 DrawPosition(FALSE, boards[currentMove]);
5779 /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
5780 if(clickType == Press
5781 && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
5782 || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
5783 || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
5787 if (clickType == Press) {
5789 if (OKToStartUserMove(x, y)) {
5793 MarkTargetSquares(0);
5794 DragPieceBegin(xPix, yPix);
5795 if (appData.highlightDragging) {
5796 SetHighlights(x, y, -1, -1);
5804 if (clickType == Press && gameMode != EditPosition) {
5809 // ignore off-board to clicks
5810 if(y < 0 || x < 0) return;
5812 /* Check if clicking again on the same color piece */
5813 fromP = boards[currentMove][fromY][fromX];
5814 toP = boards[currentMove][y][x];
5815 frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom;
5816 if ((WhitePawn <= fromP && fromP <= WhiteKing &&
5817 WhitePawn <= toP && toP <= WhiteKing &&
5818 !(fromP == WhiteKing && toP == WhiteRook && frc) &&
5819 !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
5820 (BlackPawn <= fromP && fromP <= BlackKing &&
5821 BlackPawn <= toP && toP <= BlackKing &&
5822 !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
5823 !(fromP == BlackKing && toP == BlackRook && frc))) {
5824 /* Clicked again on same color piece -- changed his mind */
5825 second = (x == fromX && y == fromY);
5826 if (appData.highlightDragging) {
5827 SetHighlights(x, y, -1, -1);
5831 if (OKToStartUserMove(x, y)) {
5834 MarkTargetSquares(0);
5835 DragPieceBegin(xPix, yPix);
5839 // ignore clicks on holdings
5840 if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
5843 if (clickType == Release && x == fromX && y == fromY) {
5844 DragPieceEnd(xPix, yPix);
5845 if (appData.animateDragging) {
5846 /* Undo animation damage if any */
5847 DrawPosition(FALSE, NULL);
5850 /* Second up/down in same square; just abort move */
5855 ClearPremoveHighlights();
5857 /* First upclick in same square; start click-click mode */
5858 SetHighlights(x, y, -1, -1);
5863 /* we now have a different from- and (possibly off-board) to-square */
5864 /* Completed move */
5867 saveAnimate = appData.animate;
5868 if (clickType == Press) {
5869 /* Finish clickclick move */
5870 if (appData.animate || appData.highlightLastMove) {
5871 SetHighlights(fromX, fromY, toX, toY);
5876 /* Finish drag move */
5877 if (appData.highlightLastMove) {
5878 SetHighlights(fromX, fromY, toX, toY);
5882 DragPieceEnd(xPix, yPix);
5883 /* Don't animate move and drag both */
5884 appData.animate = FALSE;
5887 // moves into holding are invalid for now (except in EditPosition, adapting to-square)
5888 if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
5889 ChessSquare piece = boards[currentMove][fromY][fromX];
5890 if(gameMode == EditPosition && piece != EmptySquare &&
5891 fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
5894 if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
5895 n = PieceToNumber(piece - (int)BlackPawn);
5896 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
5897 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
5898 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
5900 if(x == BOARD_RGHT+1 && piece < BlackPawn) {
5901 n = PieceToNumber(piece);
5902 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
5903 boards[currentMove][n][BOARD_WIDTH-1] = piece;
5904 boards[currentMove][n][BOARD_WIDTH-2]++;
5906 boards[currentMove][fromY][fromX] = EmptySquare;
5910 DrawPosition(TRUE, boards[currentMove]);
5914 // off-board moves should not be highlighted
5915 if(x < 0 || x < 0) ClearHighlights();
5917 if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice)) {
5918 SetHighlights(fromX, fromY, toX, toY);
5919 if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
5920 // [HGM] super: promotion to captured piece selected from holdings
5921 ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
5922 promotionChoice = TRUE;
5923 // kludge follows to temporarily execute move on display, without promoting yet
5924 boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
5925 boards[currentMove][toY][toX] = p;
5926 DrawPosition(FALSE, boards[currentMove]);
5927 boards[currentMove][fromY][fromX] = p; // take back, but display stays
5928 boards[currentMove][toY][toX] = q;
5929 DisplayMessage("Click in holdings to choose piece", "");
5934 UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
5935 if (!appData.highlightLastMove || gotPremove) ClearHighlights();
5936 if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
5939 appData.animate = saveAnimate;
5940 if (appData.animate || appData.animateDragging) {
5941 /* Undo animation damage if needed */
5942 DrawPosition(FALSE, NULL);
5946 int RightClick(ClickType action, int x, int y, int *fromX, int *fromY)
5947 { // front-end-free part taken out of PieceMenuPopup
5948 int whichMenu; int xSqr, ySqr;
5950 xSqr = EventToSquare(x, BOARD_WIDTH);
5951 ySqr = EventToSquare(y, BOARD_HEIGHT);
5952 if (action == Release) UnLoadPV(); // [HGM] pv
5953 if (action != Press) return -2;
5956 if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;
\r
5958 if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;
\r
5959 if (xSqr < 0 || ySqr < 0) return -1;
\r
5960 whichMenu = 0; // edit-position menu
5963 if(!appData.icsEngineAnalyze) return -1;
5964 case IcsPlayingWhite:
5965 case IcsPlayingBlack:
5966 if(!appData.zippyPlay) goto noZip;
5969 case MachinePlaysWhite:
5970 case MachinePlaysBlack:
5971 case TwoMachinesPlay: // [HGM] pv: use for showing PV
5972 if (!appData.dropMenu) {
5974 return 2; // flag front-end to grab mouse events
5976 if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
5977 gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
5980 if (xSqr < 0 || ySqr < 0) return -1;
5981 if (!appData.dropMenu || appData.testLegality &&
5982 gameInfo.variant != VariantBughouse &&
5983 gameInfo.variant != VariantCrazyhouse) return -1;
5984 whichMenu = 1; // drop menu
5990 if (((*fromX = xSqr) < 0) ||
5991 ((*fromY = ySqr) < 0)) {
5992 *fromX = *fromY = -1;
5996 *fromX = BOARD_WIDTH - 1 - *fromX;
5998 *fromY = BOARD_HEIGHT - 1 - *fromY;
6003 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
6005 // char * hint = lastHint;
6006 FrontEndProgramStats stats;
6008 stats.which = cps == &first ? 0 : 1;
6009 stats.depth = cpstats->depth;
6010 stats.nodes = cpstats->nodes;
6011 stats.score = cpstats->score;
6012 stats.time = cpstats->time;
6013 stats.pv = cpstats->movelist;
6014 stats.hint = lastHint;
6015 stats.an_move_index = 0;
6016 stats.an_move_count = 0;
6018 if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
6019 stats.hint = cpstats->move_name;
6020 stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
6021 stats.an_move_count = cpstats->nr_moves;
6024 if(stats.pv && stats.pv[0]) strcpy(lastPV[stats.which], stats.pv); // [HGM] pv: remember last PV of each
6026 SetProgramStats( &stats );
6030 Adjudicate(ChessProgramState *cps)
6031 { // [HGM] some adjudications useful with buggy engines
6032 // [HGM] adjudicate: made into separate routine, which now can be called after every move
6033 // In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
6034 // Actually ending the game is now based on the additional internal condition canAdjudicate.
6035 // Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
6036 int k, count = 0; static int bare = 1;
6037 ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
6038 Boolean canAdjudicate = !appData.icsActive;
6040 // most tests only when we understand the game, i.e. legality-checking on, and (for the time being) no piece drops
6041 if(gameInfo.holdingsSize == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
6042 if( appData.testLegality )
6043 { /* [HGM] Some more adjudications for obstinate engines */
6044 int NrWN=0, NrBN=0, NrWB=0, NrBB=0, NrWR=0, NrBR=0,
6045 NrWQ=0, NrBQ=0, NrW=0, NrK=0, bishopsColor = 0,
6046 NrPieces=0, NrPawns=0, PawnAdvance=0, i, j;
6047 static int moveCount = 6;
6049 char *reason = NULL;
6051 /* Count what is on board. */
6052 for(i=0; i<BOARD_HEIGHT; i++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
6053 { ChessSquare p = boards[forwardMostMove][i][j];
6057 { /* count B,N,R and other of each side */
6060 NrK++; break; // [HGM] atomic: count Kings
6064 case WhiteFerz: // [HGM] shatranj: kludge to mke it work in shatranj
6065 bishopsColor |= 1 << ((i^j)&1);
6070 case BlackFerz: // [HGM] shatranj: kludge to mke it work in shatranj
6071 bishopsColor |= 1 << ((i^j)&1);
6086 PawnAdvance += m; NrPawns++;
6088 NrPieces += (p != EmptySquare);
6089 NrW += ((int)p < (int)BlackPawn);
6090 if(gameInfo.variant == VariantXiangqi &&
6091 (p == WhiteFerz || p == WhiteAlfil || p == BlackFerz || p == BlackAlfil)) {
6092 NrPieces--; // [HGM] XQ: do not count purely defensive pieces
6093 NrW -= ((int)p < (int)BlackPawn);
6097 /* Some material-based adjudications that have to be made before stalemate test */
6098 if(gameInfo.variant == VariantAtomic && NrK < 2) {
6099 // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
6100 boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
6101 if(canAdjudicate && appData.checkMates) {
6103 SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
6104 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6105 GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
6106 "Xboard adjudication: King destroyed", GE_XBOARD );
6111 /* Bare King in Shatranj (loses) or Losers (wins) */
6112 if( NrW == 1 || NrPieces - NrW == 1) {
6113 if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
6114 boards[forwardMostMove][EP_STATUS] = EP_WINS; // mark as win, so it becomes claimable
6115 if(canAdjudicate && appData.checkMates) {
6117 SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
6118 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6119 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
6120 "Xboard adjudication: Bare king", GE_XBOARD );
6124 if( gameInfo.variant == VariantShatranj && --bare < 0)
6126 boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
6127 if(canAdjudicate && appData.checkMates) {
6128 /* but only adjudicate if adjudication enabled */
6130 SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
6131 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6132 GameEnds( NrW > 1 ? WhiteWins : NrPieces - NrW > 1 ? BlackWins : GameIsDrawn,
6133 "Xboard adjudication: Bare king", GE_XBOARD );
6140 // don't wait for engine to announce game end if we can judge ourselves
6141 switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
6143 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
6144 int i, checkCnt = 0; // (should really be done by making nr of checks part of game state)
6145 for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
6146 if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
6149 reason = "Xboard adjudication: 3rd check";
6150 boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
6160 reason = "Xboard adjudication: Stalemate";
6161 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
6162 boards[forwardMostMove][EP_STATUS] = EP_STALEMATE; // default result for stalemate is draw
6163 if(gameInfo.variant == VariantLosers || gameInfo.variant == VariantGiveaway) // [HGM] losers:
6164 boards[forwardMostMove][EP_STATUS] = EP_WINS; // in these variants stalemated is always a win
6165 else if(gameInfo.variant == VariantSuicide) // in suicide it depends
6166 boards[forwardMostMove][EP_STATUS] = NrW == NrPieces-NrW ? EP_STALEMATE :
6167 ((NrW < NrPieces-NrW) != WhiteOnMove(forwardMostMove) ?
6168 EP_CHECKMATE : EP_WINS);
6169 else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
6170 boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
6174 reason = "Xboard adjudication: Checkmate";
6175 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
6179 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
6181 result = GameIsDrawn; break;
6183 result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
6185 result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
6187 result = (ChessMove) 0;
6189 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
6191 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6192 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6193 GameEnds( result, reason, GE_XBOARD );
6197 /* Next absolutely insufficient mating material. */
6198 if( NrPieces == 2 || gameInfo.variant != VariantXiangqi &&
6199 gameInfo.variant != VariantShatranj && // [HGM] baring will remain possible
6200 (NrPieces == 3 && NrWN+NrBN+NrWB+NrBB == 1 ||
6201 NrPieces == NrBB+NrWB+2 && bishopsColor != 3)) // [HGM] all Bishops (Ferz!) same color
6202 { /* KBK, KNK, KK of KBKB with like Bishops */
6204 /* always flag draws, for judging claims */
6205 boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
6207 if(canAdjudicate && appData.materialDraws) {
6208 /* but only adjudicate them if adjudication enabled */
6209 if(engineOpponent) {
6210 SendToProgram("force\n", engineOpponent); // suppress reply
6211 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
6213 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6214 GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
6219 /* Then some trivial draws (only adjudicate, cannot be claimed) */
6221 ( NrWR == 1 && NrBR == 1 /* KRKR */
6222 || NrWQ==1 && NrBQ==1 /* KQKQ */
6223 || NrWN==2 || NrBN==2 /* KNNK */
6224 || NrWN+NrWB == 1 && NrBN+NrBB == 1 /* KBKN, KBKB, KNKN */
6226 if(canAdjudicate && --moveCount < 0 && appData.trivialDraws)
6227 { /* if the first 3 moves do not show a tactical win, declare draw */
6228 if(engineOpponent) {
6229 SendToProgram("force\n", engineOpponent); // suppress reply
6230 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6232 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6233 GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
6236 } else moveCount = 6;
6240 if (appData.debugMode) { int i;
6241 fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
6242 forwardMostMove, backwardMostMove, boards[backwardMostMove][EP_STATUS],
6243 appData.drawRepeats);
6244 for( i=forwardMostMove; i>=backwardMostMove; i-- )
6245 fprintf(debugFP, "%d ep=%d\n", i, (signed char)boards[i][EP_STATUS]);
6249 // Repetition draws and 50-move rule can be applied independently of legality testing
6251 /* Check for rep-draws */
6253 for(k = forwardMostMove-2;
6254 k>=backwardMostMove && k>=forwardMostMove-100 &&
6255 (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
6256 (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE;
6259 if(CompareBoards(boards[k], boards[forwardMostMove])) {
6260 /* compare castling rights */
6261 if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
6262 (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
6263 rights++; /* King lost rights, while rook still had them */
6264 if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
6265 if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
6266 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
6267 rights++; /* but at least one rook lost them */
6269 if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
6270 (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
6272 if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
6273 if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
6274 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
6277 if( canAdjudicate && rights == 0 && ++count > appData.drawRepeats-2
6278 && appData.drawRepeats > 1) {
6279 /* adjudicate after user-specified nr of repeats */
6280 if(engineOpponent) {
6281 SendToProgram("force\n", engineOpponent); // suppress reply
6282 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6284 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6285 if(gameInfo.variant == VariantXiangqi && appData.testLegality) {
6286 // [HGM] xiangqi: check for forbidden perpetuals
6287 int m, ourPerpetual = 1, hisPerpetual = 1;
6288 for(m=forwardMostMove; m>k; m-=2) {
6289 if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
6290 ourPerpetual = 0; // the current mover did not always check
6291 if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
6292 hisPerpetual = 0; // the opponent did not always check
6294 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
6295 ourPerpetual, hisPerpetual);
6296 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
6297 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
6298 "Xboard adjudication: perpetual checking", GE_XBOARD );
6301 if(hisPerpetual && !ourPerpetual) // he is checking us, but did not repeat yet
6302 break; // (or we would have caught him before). Abort repetition-checking loop.
6303 // Now check for perpetual chases
6304 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
6305 hisPerpetual = PerpetualChase(k, forwardMostMove);
6306 ourPerpetual = PerpetualChase(k+1, forwardMostMove);
6307 if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
6308 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
6309 "Xboard adjudication: perpetual chasing", GE_XBOARD );
6312 if(hisPerpetual && !ourPerpetual) // he is chasing us, but did not repeat yet
6313 break; // Abort repetition-checking loop.
6315 // if neither of us is checking or chasing all the time, or both are, it is draw
6317 GameEnds( GameIsDrawn, "Xboard adjudication: repetition draw", GE_XBOARD );
6320 if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
6321 boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
6325 /* Now we test for 50-move draws. Determine ply count */
6326 count = forwardMostMove;
6327 /* look for last irreversble move */
6328 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
6330 /* if we hit starting position, add initial plies */
6331 if( count == backwardMostMove )
6332 count -= initialRulePlies;
6333 count = forwardMostMove - count;
6335 boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
6336 /* this is used to judge if draw claims are legal */
6337 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
6338 if(engineOpponent) {
6339 SendToProgram("force\n", engineOpponent); // suppress reply
6340 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6342 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6343 GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
6347 /* if draw offer is pending, treat it as a draw claim
6348 * when draw condition present, to allow engines a way to
6349 * claim draws before making their move to avoid a race
6350 * condition occurring after their move
6352 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
6354 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
6355 p = "Draw claim: 50-move rule";
6356 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
6357 p = "Draw claim: 3-fold repetition";
6358 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
6359 p = "Draw claim: insufficient mating material";
6360 if( p != NULL && canAdjudicate) {
6361 if(engineOpponent) {
6362 SendToProgram("force\n", engineOpponent); // suppress reply
6363 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6365 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6366 GameEnds( GameIsDrawn, p, GE_XBOARD );
6371 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
6372 if(engineOpponent) {
6373 SendToProgram("force\n", engineOpponent); // suppress reply
6374 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6376 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6377 GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
6383 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
6384 { // [HGM] book: this routine intercepts moves to simulate book replies
6385 char *bookHit = NULL;
6387 //first determine if the incoming move brings opponent into his book
6388 if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
6389 bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
6390 if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
6391 if(bookHit != NULL && !cps->bookSuspend) {
6392 // make sure opponent is not going to reply after receiving move to book position
6393 SendToProgram("force\n", cps);
6394 cps->bookSuspend = TRUE; // flag indicating it has to be restarted
6396 if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
6397 // now arrange restart after book miss
6399 // after a book hit we never send 'go', and the code after the call to this routine
6400 // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
6402 if (cps->useUsermove) sprintf(buf, "usermove "); // sorry, no SAN yet :(
6403 sprintf(buf, "%s\n", bookHit); // force book move into program supposed to play it
6404 SendToProgram(buf, cps);
6405 if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
6406 } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
6407 SendToProgram("go\n", cps);
6408 cps->bookSuspend = FALSE; // after a 'go' we are never suspended
6409 } else { // 'go' might be sent based on 'firstMove' after this routine returns
6410 if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
6411 SendToProgram("go\n", cps);
6412 cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
6414 return bookHit; // notify caller of hit, so it can take action to send move to opponent
6418 ChessProgramState *savedState;
6419 void DeferredBookMove(void)
6421 if(savedState->lastPing != savedState->lastPong)
6422 ScheduleDelayedEvent(DeferredBookMove, 10);
6424 HandleMachineMove(savedMessage, savedState);
6428 HandleMachineMove(message, cps)
6430 ChessProgramState *cps;
6432 char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
6433 char realname[MSG_SIZ];
6434 int fromX, fromY, toX, toY;
6443 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
6445 * Kludge to ignore BEL characters
6447 while (*message == '\007') message++;
6450 * [HGM] engine debug message: ignore lines starting with '#' character
6452 if(cps->debug && *message == '#') return;
6455 * Look for book output
6457 if (cps == &first && bookRequested) {
6458 if (message[0] == '\t' || message[0] == ' ') {
6459 /* Part of the book output is here; append it */
6460 strcat(bookOutput, message);
6461 strcat(bookOutput, " \n");
6463 } else if (bookOutput[0] != NULLCHAR) {
6464 /* All of book output has arrived; display it */
6465 char *p = bookOutput;
6466 while (*p != NULLCHAR) {
6467 if (*p == '\t') *p = ' ';
6470 DisplayInformation(bookOutput);
6471 bookRequested = FALSE;
6472 /* Fall through to parse the current output */
6477 * Look for machine move.
6479 if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
6480 (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
6482 /* This method is only useful on engines that support ping */
6483 if (cps->lastPing != cps->lastPong) {
6484 if (gameMode == BeginningOfGame) {
6485 /* Extra move from before last new; ignore */
6486 if (appData.debugMode) {
6487 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
6490 if (appData.debugMode) {
6491 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
6492 cps->which, gameMode);
6495 SendToProgram("undo\n", cps);
6501 case BeginningOfGame:
6502 /* Extra move from before last reset; ignore */
6503 if (appData.debugMode) {
6504 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
6511 /* Extra move after we tried to stop. The mode test is
6512 not a reliable way of detecting this problem, but it's
6513 the best we can do on engines that don't support ping.
6515 if (appData.debugMode) {
6516 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
6517 cps->which, gameMode);
6519 SendToProgram("undo\n", cps);
6522 case MachinePlaysWhite:
6523 case IcsPlayingWhite:
6524 machineWhite = TRUE;
6527 case MachinePlaysBlack:
6528 case IcsPlayingBlack:
6529 machineWhite = FALSE;
6532 case TwoMachinesPlay:
6533 machineWhite = (cps->twoMachinesColor[0] == 'w');
6536 if (WhiteOnMove(forwardMostMove) != machineWhite) {
6537 if (appData.debugMode) {
6539 "Ignoring move out of turn by %s, gameMode %d"
6540 ", forwardMost %d\n",
6541 cps->which, gameMode, forwardMostMove);
6546 if (appData.debugMode) { int f = forwardMostMove;
6547 fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
6548 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
6549 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
6551 if(cps->alphaRank) AlphaRank(machineMove, 4);
6552 if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
6553 &fromX, &fromY, &toX, &toY, &promoChar)) {
6554 /* Machine move could not be parsed; ignore it. */
6555 sprintf(buf1, _("Illegal move \"%s\" from %s machine"),
6556 machineMove, cps->which);
6557 DisplayError(buf1, 0);
6558 sprintf(buf1, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
6559 machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
6560 if (gameMode == TwoMachinesPlay) {
6561 GameEnds(machineWhite ? BlackWins : WhiteWins,
6567 /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
6568 /* So we have to redo legality test with true e.p. status here, */
6569 /* to make sure an illegal e.p. capture does not slip through, */
6570 /* to cause a forfeit on a justified illegal-move complaint */
6571 /* of the opponent. */
6572 if( gameMode==TwoMachinesPlay && appData.testLegality
6573 && fromY != DROP_RANK /* [HGM] temporary; should still add legality test for drops */
6576 moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
6577 fromY, fromX, toY, toX, promoChar);
6578 if (appData.debugMode) {
6580 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
6581 boards[forwardMostMove][CASTLING][i], castlingRank[i]);
6582 fprintf(debugFP, "castling rights\n");
6584 if(moveType == IllegalMove) {
6585 sprintf(buf1, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
6586 machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
6587 GameEnds(machineWhite ? BlackWins : WhiteWins,
6590 } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
6591 /* [HGM] Kludge to handle engines that send FRC-style castling
6592 when they shouldn't (like TSCP-Gothic) */
6594 case WhiteASideCastleFR:
6595 case BlackASideCastleFR:
6597 currentMoveString[2]++;
6599 case WhiteHSideCastleFR:
6600 case BlackHSideCastleFR:
6602 currentMoveString[2]--;
6604 default: ; // nothing to do, but suppresses warning of pedantic compilers
6607 hintRequested = FALSE;
6608 lastHint[0] = NULLCHAR;
6609 bookRequested = FALSE;
6610 /* Program may be pondering now */
6611 cps->maybeThinking = TRUE;
6612 if (cps->sendTime == 2) cps->sendTime = 1;
6613 if (cps->offeredDraw) cps->offeredDraw--;
6615 /* currentMoveString is set as a side-effect of ParseOneMove */
6616 strcpy(machineMove, currentMoveString);
6617 strcat(machineMove, "\n");
6618 strcpy(moveList[forwardMostMove], machineMove);
6620 MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
6622 /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
6623 if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
6626 while( count < adjudicateLossPlies ) {
6627 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
6630 score = -score; /* Flip score for winning side */
6633 if( score > adjudicateLossThreshold ) {
6640 if( count >= adjudicateLossPlies ) {
6641 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6643 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
6644 "Xboard adjudication",
6651 if(Adjudicate(cps)) return; // [HGM] adjudicate: for all automatic game ends
6654 if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
6656 if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
6657 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
6659 SendMoveToICS(moveType, fromX, fromY, toX, toY);
6661 SendMoveToICS(moveType, fromX, fromY, toX, toY);
6663 if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
6664 char buf[3*MSG_SIZ];
6666 sprintf(buf, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
6667 programStats.score / 100.,
6669 programStats.time / 100.,
6670 (unsigned int)programStats.nodes,
6671 (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
6672 programStats.movelist);
6674 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
6679 /* [AS] Save move info and clear stats for next move */
6680 pvInfoList[ forwardMostMove-1 ].score = programStats.score;
6681 pvInfoList[ forwardMostMove-1 ].depth = programStats.depth;
6682 pvInfoList[ forwardMostMove-1 ].time = programStats.time; // [HGM] PGNtime: take time from engine stats
6683 ClearProgramStats();
6684 thinkOutput[0] = NULLCHAR;
6685 hiddenThinkOutputState = 0;
6688 if (gameMode == TwoMachinesPlay) {
6689 /* [HGM] relaying draw offers moved to after reception of move */
6690 /* and interpreting offer as claim if it brings draw condition */
6691 if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
6692 SendToProgram("draw\n", cps->other);
6694 if (cps->other->sendTime) {
6695 SendTimeRemaining(cps->other,
6696 cps->other->twoMachinesColor[0] == 'w');
6698 bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
6699 if (firstMove && !bookHit) {
6701 if (cps->other->useColors) {
6702 SendToProgram(cps->other->twoMachinesColor, cps->other);
6704 SendToProgram("go\n", cps->other);
6706 cps->other->maybeThinking = TRUE;
6709 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6711 if (!pausing && appData.ringBellAfterMoves) {
6716 * Reenable menu items that were disabled while
6717 * machine was thinking
6719 if (gameMode != TwoMachinesPlay)
6720 SetUserThinkingEnables();
6722 // [HGM] book: after book hit opponent has received move and is now in force mode
6723 // force the book reply into it, and then fake that it outputted this move by jumping
6724 // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
6726 static char bookMove[MSG_SIZ]; // a bit generous?
6728 strcpy(bookMove, "move ");
6729 strcat(bookMove, bookHit);
6732 programStats.nodes = programStats.depth = programStats.time =
6733 programStats.score = programStats.got_only_move = 0;
6734 sprintf(programStats.movelist, "%s (xbook)", bookHit);
6736 if(cps->lastPing != cps->lastPong) {
6737 savedMessage = message; // args for deferred call
6739 ScheduleDelayedEvent(DeferredBookMove, 10);
6748 /* Set special modes for chess engines. Later something general
6749 * could be added here; for now there is just one kludge feature,
6750 * needed because Crafty 15.10 and earlier don't ignore SIGINT
6751 * when "xboard" is given as an interactive command.
6753 if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
6754 cps->useSigint = FALSE;
6755 cps->useSigterm = FALSE;
6757 if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
6758 ParseFeatures(message+8, cps);
6759 return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
6762 /* [HGM] Allow engine to set up a position. Don't ask me why one would
6763 * want this, I was asked to put it in, and obliged.
6765 if (!strncmp(message, "setboard ", 9)) {
6766 Board initial_position;
6768 GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
6770 if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
6771 DisplayError(_("Bad FEN received from engine"), 0);
6775 CopyBoard(boards[0], initial_position);
6776 initialRulePlies = FENrulePlies;
6777 if(blackPlaysFirst) gameMode = MachinePlaysWhite;
6778 else gameMode = MachinePlaysBlack;
6779 DrawPosition(FALSE, boards[currentMove]);
6785 * Look for communication commands
6787 if (!strncmp(message, "telluser ", 9)) {
6788 DisplayNote(message + 9);
6791 if (!strncmp(message, "tellusererror ", 14)) {
6793 DisplayError(message + 14, 0);
6796 if (!strncmp(message, "tellopponent ", 13)) {
6797 if (appData.icsActive) {
6799 snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
6803 DisplayNote(message + 13);
6807 if (!strncmp(message, "tellothers ", 11)) {
6808 if (appData.icsActive) {
6810 snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
6816 if (!strncmp(message, "tellall ", 8)) {
6817 if (appData.icsActive) {
6819 snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
6823 DisplayNote(message + 8);
6827 if (strncmp(message, "warning", 7) == 0) {
6828 /* Undocumented feature, use tellusererror in new code */
6829 DisplayError(message, 0);
6832 if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
6833 strcpy(realname, cps->tidy);
6834 strcat(realname, " query");
6835 AskQuestion(realname, buf2, buf1, cps->pr);
6838 /* Commands from the engine directly to ICS. We don't allow these to be
6839 * sent until we are logged on. Crafty kibitzes have been known to
6840 * interfere with the login process.
6843 if (!strncmp(message, "tellics ", 8)) {
6844 SendToICS(message + 8);
6848 if (!strncmp(message, "tellicsnoalias ", 15)) {
6849 SendToICS(ics_prefix);
6850 SendToICS(message + 15);
6854 /* The following are for backward compatibility only */
6855 if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
6856 !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
6857 SendToICS(ics_prefix);
6863 if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
6867 * If the move is illegal, cancel it and redraw the board.
6868 * Also deal with other error cases. Matching is rather loose
6869 * here to accommodate engines written before the spec.
6871 if (strncmp(message + 1, "llegal move", 11) == 0 ||
6872 strncmp(message, "Error", 5) == 0) {
6873 if (StrStr(message, "name") ||
6874 StrStr(message, "rating") || StrStr(message, "?") ||
6875 StrStr(message, "result") || StrStr(message, "board") ||
6876 StrStr(message, "bk") || StrStr(message, "computer") ||
6877 StrStr(message, "variant") || StrStr(message, "hint") ||
6878 StrStr(message, "random") || StrStr(message, "depth") ||
6879 StrStr(message, "accepted")) {
6882 if (StrStr(message, "protover")) {
6883 /* Program is responding to input, so it's apparently done
6884 initializing, and this error message indicates it is
6885 protocol version 1. So we don't need to wait any longer
6886 for it to initialize and send feature commands. */
6887 FeatureDone(cps, 1);
6888 cps->protocolVersion = 1;
6891 cps->maybeThinking = FALSE;
6893 if (StrStr(message, "draw")) {
6894 /* Program doesn't have "draw" command */
6895 cps->sendDrawOffers = 0;
6898 if (cps->sendTime != 1 &&
6899 (StrStr(message, "time") || StrStr(message, "otim"))) {
6900 /* Program apparently doesn't have "time" or "otim" command */
6904 if (StrStr(message, "analyze")) {
6905 cps->analysisSupport = FALSE;
6906 cps->analyzing = FALSE;
6908 sprintf(buf2, _("%s does not support analysis"), cps->tidy);
6909 DisplayError(buf2, 0);
6912 if (StrStr(message, "(no matching move)st")) {
6913 /* Special kludge for GNU Chess 4 only */
6914 cps->stKludge = TRUE;
6915 SendTimeControl(cps, movesPerSession, timeControl,
6916 timeIncrement, appData.searchDepth,
6920 if (StrStr(message, "(no matching move)sd")) {
6921 /* Special kludge for GNU Chess 4 only */
6922 cps->sdKludge = TRUE;
6923 SendTimeControl(cps, movesPerSession, timeControl,
6924 timeIncrement, appData.searchDepth,
6928 if (!StrStr(message, "llegal")) {
6931 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
6932 gameMode == IcsIdle) return;
6933 if (forwardMostMove <= backwardMostMove) return;
6934 if (pausing) PauseEvent();
6935 if(appData.forceIllegal) {
6936 // [HGM] illegal: machine refused move; force position after move into it
6937 SendToProgram("force\n", cps);
6938 if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
6939 // we have a real problem now, as SendBoard will use the a2a3 kludge
6940 // when black is to move, while there might be nothing on a2 or black
6941 // might already have the move. So send the board as if white has the move.
6942 // But first we must change the stm of the engine, as it refused the last move
6943 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
6944 if(WhiteOnMove(forwardMostMove)) {
6945 SendToProgram("a7a6\n", cps); // for the engine black still had the move
6946 SendBoard(cps, forwardMostMove); // kludgeless board
6948 SendToProgram("a2a3\n", cps); // for the engine white still had the move
6949 CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
6950 SendBoard(cps, forwardMostMove+1); // kludgeless board
6952 } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
6953 if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
6954 gameMode == TwoMachinesPlay)
6955 SendToProgram("go\n", cps);
6958 if (gameMode == PlayFromGameFile) {
6959 /* Stop reading this game file */
6960 gameMode = EditGame;
6963 currentMove = --forwardMostMove;
6964 DisplayMove(currentMove-1); /* before DisplayMoveError */
6966 DisplayBothClocks();
6967 sprintf(buf1, _("Illegal move \"%s\" (rejected by %s chess program)"),
6968 parseList[currentMove], cps->which);
6969 DisplayMoveError(buf1);
6970 DrawPosition(FALSE, boards[currentMove]);
6972 /* [HGM] illegal-move claim should forfeit game when Xboard */
6973 /* only passes fully legal moves */
6974 if( appData.testLegality && gameMode == TwoMachinesPlay ) {
6975 GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
6976 "False illegal-move claim", GE_XBOARD );
6980 if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
6981 /* Program has a broken "time" command that
6982 outputs a string not ending in newline.
6988 * If chess program startup fails, exit with an error message.
6989 * Attempts to recover here are futile.
6991 if ((StrStr(message, "unknown host") != NULL)
6992 || (StrStr(message, "No remote directory") != NULL)
6993 || (StrStr(message, "not found") != NULL)
6994 || (StrStr(message, "No such file") != NULL)
6995 || (StrStr(message, "can't alloc") != NULL)
6996 || (StrStr(message, "Permission denied") != NULL)) {
6998 cps->maybeThinking = FALSE;
6999 snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
7000 cps->which, cps->program, cps->host, message);
7001 RemoveInputSource(cps->isr);
7002 DisplayFatalError(buf1, 0, 1);
7007 * Look for hint output
7009 if (sscanf(message, "Hint: %s", buf1) == 1) {
7010 if (cps == &first && hintRequested) {
7011 hintRequested = FALSE;
7012 if (ParseOneMove(buf1, forwardMostMove, &moveType,
7013 &fromX, &fromY, &toX, &toY, &promoChar)) {
7014 (void) CoordsToAlgebraic(boards[forwardMostMove],
7015 PosFlags(forwardMostMove),
7016 fromY, fromX, toY, toX, promoChar, buf1);
7017 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
7018 DisplayInformation(buf2);
7020 /* Hint move could not be parsed!? */
7021 snprintf(buf2, sizeof(buf2),
7022 _("Illegal hint move \"%s\"\nfrom %s chess program"),
7024 DisplayError(buf2, 0);
7027 strcpy(lastHint, buf1);
7033 * Ignore other messages if game is not in progress
7035 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
7036 gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
7039 * look for win, lose, draw, or draw offer
7041 if (strncmp(message, "1-0", 3) == 0) {
7042 char *p, *q, *r = "";
7043 p = strchr(message, '{');
7051 GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
7053 } else if (strncmp(message, "0-1", 3) == 0) {
7054 char *p, *q, *r = "";
7055 p = strchr(message, '{');
7063 /* Kludge for Arasan 4.1 bug */
7064 if (strcmp(r, "Black resigns") == 0) {
7065 GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
7068 GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
7070 } else if (strncmp(message, "1/2", 3) == 0) {
7071 char *p, *q, *r = "";
7072 p = strchr(message, '{');
7081 GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
7084 } else if (strncmp(message, "White resign", 12) == 0) {
7085 GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
7087 } else if (strncmp(message, "Black resign", 12) == 0) {
7088 GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
7090 } else if (strncmp(message, "White matches", 13) == 0 ||
7091 strncmp(message, "Black matches", 13) == 0 ) {
7092 /* [HGM] ignore GNUShogi noises */
7094 } else if (strncmp(message, "White", 5) == 0 &&
7095 message[5] != '(' &&
7096 StrStr(message, "Black") == NULL) {
7097 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7099 } else if (strncmp(message, "Black", 5) == 0 &&
7100 message[5] != '(') {
7101 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7103 } else if (strcmp(message, "resign") == 0 ||
7104 strcmp(message, "computer resigns") == 0) {
7106 case MachinePlaysBlack:
7107 case IcsPlayingBlack:
7108 GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
7110 case MachinePlaysWhite:
7111 case IcsPlayingWhite:
7112 GameEnds(BlackWins, "White resigns", GE_ENGINE);
7114 case TwoMachinesPlay:
7115 if (cps->twoMachinesColor[0] == 'w')
7116 GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
7118 GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
7125 } else if (strncmp(message, "opponent mates", 14) == 0) {
7127 case MachinePlaysBlack:
7128 case IcsPlayingBlack:
7129 GameEnds(WhiteWins, "White mates", GE_ENGINE);
7131 case MachinePlaysWhite:
7132 case IcsPlayingWhite:
7133 GameEnds(BlackWins, "Black mates", GE_ENGINE);
7135 case TwoMachinesPlay:
7136 if (cps->twoMachinesColor[0] == 'w')
7137 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7139 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7146 } else if (strncmp(message, "computer mates", 14) == 0) {
7148 case MachinePlaysBlack:
7149 case IcsPlayingBlack:
7150 GameEnds(BlackWins, "Black mates", GE_ENGINE1);
7152 case MachinePlaysWhite:
7153 case IcsPlayingWhite:
7154 GameEnds(WhiteWins, "White mates", GE_ENGINE);
7156 case TwoMachinesPlay:
7157 if (cps->twoMachinesColor[0] == 'w')
7158 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7160 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7167 } else if (strncmp(message, "checkmate", 9) == 0) {
7168 if (WhiteOnMove(forwardMostMove)) {
7169 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7171 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7174 } else if (strstr(message, "Draw") != NULL ||
7175 strstr(message, "game is a draw") != NULL) {
7176 GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
7178 } else if (strstr(message, "offer") != NULL &&
7179 strstr(message, "draw") != NULL) {
7181 if (appData.zippyPlay && first.initDone) {
7182 /* Relay offer to ICS */
7183 SendToICS(ics_prefix);
7184 SendToICS("draw\n");
7187 cps->offeredDraw = 2; /* valid until this engine moves twice */
7188 if (gameMode == TwoMachinesPlay) {
7189 if (cps->other->offeredDraw) {
7190 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
7191 /* [HGM] in two-machine mode we delay relaying draw offer */
7192 /* until after we also have move, to see if it is really claim */
7194 } else if (gameMode == MachinePlaysWhite ||
7195 gameMode == MachinePlaysBlack) {
7196 if (userOfferedDraw) {
7197 DisplayInformation(_("Machine accepts your draw offer"));
7198 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
7200 DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
7207 * Look for thinking output
7209 if ( appData.showThinking // [HGM] thinking: test all options that cause this output
7210 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
7212 int plylev, mvleft, mvtot, curscore, time;
7213 char mvname[MOVE_LEN];
7217 int prefixHint = FALSE;
7218 mvname[0] = NULLCHAR;
7221 case MachinePlaysBlack:
7222 case IcsPlayingBlack:
7223 if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
7225 case MachinePlaysWhite:
7226 case IcsPlayingWhite:
7227 if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
7232 case IcsObserving: /* [DM] icsEngineAnalyze */
7233 if (!appData.icsEngineAnalyze) ignore = TRUE;
7235 case TwoMachinesPlay:
7236 if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
7247 if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
7248 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
7250 if (plyext != ' ' && plyext != '\t') {
7254 /* [AS] Negate score if machine is playing black and reporting absolute scores */
7255 if( cps->scoreIsAbsolute &&
7256 ( gameMode == MachinePlaysBlack ||
7257 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
7258 gameMode == IcsPlayingBlack || // [HGM] also add other situations where engine should report black POV
7259 (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
7260 !WhiteOnMove(currentMove)
7263 curscore = -curscore;
7267 programStats.depth = plylev;
7268 programStats.nodes = nodes;
7269 programStats.time = time;
7270 programStats.score = curscore;
7271 programStats.got_only_move = 0;
7273 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
7276 if(cps->nps == 0) ticklen = 10*time; // use engine reported time
7277 else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
7278 if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
7279 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
7280 whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
7281 if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
7282 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
7283 blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
7286 /* Buffer overflow protection */
7287 if (buf1[0] != NULLCHAR) {
7288 if (strlen(buf1) >= sizeof(programStats.movelist)
7289 && appData.debugMode) {
7291 "PV is too long; using the first %u bytes.\n",
7292 (unsigned) sizeof(programStats.movelist) - 1);
7295 safeStrCpy( programStats.movelist, buf1, sizeof(programStats.movelist) );
7297 sprintf(programStats.movelist, " no PV\n");
7300 if (programStats.seen_stat) {
7301 programStats.ok_to_send = 1;
7304 if (strchr(programStats.movelist, '(') != NULL) {
7305 programStats.line_is_book = 1;
7306 programStats.nr_moves = 0;
7307 programStats.moves_left = 0;
7309 programStats.line_is_book = 0;
7312 SendProgramStatsToFrontend( cps, &programStats );
7315 [AS] Protect the thinkOutput buffer from overflow... this
7316 is only useful if buf1 hasn't overflowed first!
7318 sprintf(thinkOutput, "[%d]%c%+.2f %s%s",
7320 (gameMode == TwoMachinesPlay ?
7321 ToUpper(cps->twoMachinesColor[0]) : ' '),
7322 ((double) curscore) / 100.0,
7323 prefixHint ? lastHint : "",
7324 prefixHint ? " " : "" );
7326 if( buf1[0] != NULLCHAR ) {
7327 unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
7329 if( strlen(buf1) > max_len ) {
7330 if( appData.debugMode) {
7331 fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
7333 buf1[max_len+1] = '\0';
7336 strcat( thinkOutput, buf1 );
7339 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
7340 || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7341 DisplayMove(currentMove - 1);
7345 } else if ((p=StrStr(message, "(only move)")) != NULL) {
7346 /* crafty (9.25+) says "(only move) <move>"
7347 * if there is only 1 legal move
7349 sscanf(p, "(only move) %s", buf1);
7350 sprintf(thinkOutput, "%s (only move)", buf1);
7351 sprintf(programStats.movelist, "%s (only move)", buf1);
7352 programStats.depth = 1;
7353 programStats.nr_moves = 1;
7354 programStats.moves_left = 1;
7355 programStats.nodes = 1;
7356 programStats.time = 1;
7357 programStats.got_only_move = 1;
7359 /* Not really, but we also use this member to
7360 mean "line isn't going to change" (Crafty
7361 isn't searching, so stats won't change) */
7362 programStats.line_is_book = 1;
7364 SendProgramStatsToFrontend( cps, &programStats );
7366 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
7367 gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7368 DisplayMove(currentMove - 1);
7371 } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
7372 &time, &nodes, &plylev, &mvleft,
7373 &mvtot, mvname) >= 5) {
7374 /* The stat01: line is from Crafty (9.29+) in response
7375 to the "." command */
7376 programStats.seen_stat = 1;
7377 cps->maybeThinking = TRUE;
7379 if (programStats.got_only_move || !appData.periodicUpdates)
7382 programStats.depth = plylev;
7383 programStats.time = time;
7384 programStats.nodes = nodes;
7385 programStats.moves_left = mvleft;
7386 programStats.nr_moves = mvtot;
7387 strcpy(programStats.move_name, mvname);
7388 programStats.ok_to_send = 1;
7389 programStats.movelist[0] = '\0';
7391 SendProgramStatsToFrontend( cps, &programStats );
7395 } else if (strncmp(message,"++",2) == 0) {
7396 /* Crafty 9.29+ outputs this */
7397 programStats.got_fail = 2;
7400 } else if (strncmp(message,"--",2) == 0) {
7401 /* Crafty 9.29+ outputs this */
7402 programStats.got_fail = 1;
7405 } else if (thinkOutput[0] != NULLCHAR &&
7406 strncmp(message, " ", 4) == 0) {
7407 unsigned message_len;
7410 while (*p && *p == ' ') p++;
7412 message_len = strlen( p );
7414 /* [AS] Avoid buffer overflow */
7415 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
7416 strcat(thinkOutput, " ");
7417 strcat(thinkOutput, p);
7420 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
7421 strcat(programStats.movelist, " ");
7422 strcat(programStats.movelist, p);
7425 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
7426 gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7427 DisplayMove(currentMove - 1);
7435 if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
7436 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
7438 ChessProgramStats cpstats;
7440 if (plyext != ' ' && plyext != '\t') {
7444 /* [AS] Negate score if machine is playing black and reporting absolute scores */
7445 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
7446 curscore = -curscore;
7449 cpstats.depth = plylev;
7450 cpstats.nodes = nodes;
7451 cpstats.time = time;
7452 cpstats.score = curscore;
7453 cpstats.got_only_move = 0;
7454 cpstats.movelist[0] = '\0';
7456 if (buf1[0] != NULLCHAR) {
7457 safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist) );
7460 cpstats.ok_to_send = 0;
7461 cpstats.line_is_book = 0;
7462 cpstats.nr_moves = 0;
7463 cpstats.moves_left = 0;
7465 SendProgramStatsToFrontend( cps, &cpstats );
7472 /* Parse a game score from the character string "game", and
7473 record it as the history of the current game. The game
7474 score is NOT assumed to start from the standard position.
7475 The display is not updated in any way.
7478 ParseGameHistory(game)
7482 int fromX, fromY, toX, toY, boardIndex;
7487 if (appData.debugMode)
7488 fprintf(debugFP, "Parsing game history: %s\n", game);
7490 if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
7491 gameInfo.site = StrSave(appData.icsHost);
7492 gameInfo.date = PGNDate();
7493 gameInfo.round = StrSave("-");
7495 /* Parse out names of players */
7496 while (*game == ' ') game++;
7498 while (*game != ' ') *p++ = *game++;
7500 gameInfo.white = StrSave(buf);
7501 while (*game == ' ') game++;
7503 while (*game != ' ' && *game != '\n') *p++ = *game++;
7505 gameInfo.black = StrSave(buf);
7508 boardIndex = blackPlaysFirst ? 1 : 0;
7511 yyboardindex = boardIndex;
7512 moveType = (ChessMove) yylex();
7514 case IllegalMove: /* maybe suicide chess, etc. */
7515 if (appData.debugMode) {
7516 fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
7517 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7518 setbuf(debugFP, NULL);
7520 case WhitePromotionChancellor:
7521 case BlackPromotionChancellor:
7522 case WhitePromotionArchbishop:
7523 case BlackPromotionArchbishop:
7524 case WhitePromotionQueen:
7525 case BlackPromotionQueen:
7526 case WhitePromotionRook:
7527 case BlackPromotionRook:
7528 case WhitePromotionBishop:
7529 case BlackPromotionBishop:
7530 case WhitePromotionKnight:
7531 case BlackPromotionKnight:
7532 case WhitePromotionKing:
7533 case BlackPromotionKing:
7535 case WhiteCapturesEnPassant:
7536 case BlackCapturesEnPassant:
7537 case WhiteKingSideCastle:
7538 case WhiteQueenSideCastle:
7539 case BlackKingSideCastle:
7540 case BlackQueenSideCastle:
7541 case WhiteKingSideCastleWild:
7542 case WhiteQueenSideCastleWild:
7543 case BlackKingSideCastleWild:
7544 case BlackQueenSideCastleWild:
7546 case WhiteHSideCastleFR:
7547 case WhiteASideCastleFR:
7548 case BlackHSideCastleFR:
7549 case BlackASideCastleFR:
7551 fromX = currentMoveString[0] - AAA;
7552 fromY = currentMoveString[1] - ONE;
7553 toX = currentMoveString[2] - AAA;
7554 toY = currentMoveString[3] - ONE;
7555 promoChar = currentMoveString[4];
7559 fromX = moveType == WhiteDrop ?
7560 (int) CharToPiece(ToUpper(currentMoveString[0])) :
7561 (int) CharToPiece(ToLower(currentMoveString[0]));
7563 toX = currentMoveString[2] - AAA;
7564 toY = currentMoveString[3] - ONE;
7565 promoChar = NULLCHAR;
7569 sprintf(buf, _("Ambiguous move in ICS output: \"%s\""), yy_text);
7570 if (appData.debugMode) {
7571 fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
7572 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7573 setbuf(debugFP, NULL);
7575 DisplayError(buf, 0);
7577 case ImpossibleMove:
7579 sprintf(buf, _("Illegal move in ICS output: \"%s\""), yy_text);
7580 if (appData.debugMode) {
7581 fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
7582 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7583 setbuf(debugFP, NULL);
7585 DisplayError(buf, 0);
7587 case (ChessMove) 0: /* end of file */
7588 if (boardIndex < backwardMostMove) {
7589 /* Oops, gap. How did that happen? */
7590 DisplayError(_("Gap in move list"), 0);
7593 backwardMostMove = blackPlaysFirst ? 1 : 0;
7594 if (boardIndex > forwardMostMove) {
7595 forwardMostMove = boardIndex;
7599 if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
7600 strcat(parseList[boardIndex-1], " ");
7601 strcat(parseList[boardIndex-1], yy_text);
7613 case GameUnfinished:
7614 if (gameMode == IcsExamining) {
7615 if (boardIndex < backwardMostMove) {
7616 /* Oops, gap. How did that happen? */
7619 backwardMostMove = blackPlaysFirst ? 1 : 0;
7622 gameInfo.result = moveType;
7623 p = strchr(yy_text, '{');
7624 if (p == NULL) p = strchr(yy_text, '(');
7627 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
7629 q = strchr(p, *p == '{' ? '}' : ')');
7630 if (q != NULL) *q = NULLCHAR;
7633 gameInfo.resultDetails = StrSave(p);
7636 if (boardIndex >= forwardMostMove &&
7637 !(gameMode == IcsObserving && ics_gamenum == -1)) {
7638 backwardMostMove = blackPlaysFirst ? 1 : 0;
7641 (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
7642 fromY, fromX, toY, toX, promoChar,
7643 parseList[boardIndex]);
7644 CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
7645 /* currentMoveString is set as a side-effect of yylex */
7646 strcpy(moveList[boardIndex], currentMoveString);
7647 strcat(moveList[boardIndex], "\n");
7649 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
7650 switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
7656 if(gameInfo.variant != VariantShogi)
7657 strcat(parseList[boardIndex - 1], "+");
7661 strcat(parseList[boardIndex - 1], "#");
7668 /* Apply a move to the given board */
7670 ApplyMove(fromX, fromY, toX, toY, promoChar, board)
7671 int fromX, fromY, toX, toY;
7675 ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
7676 int promoRank = gameInfo.variant == VariantMakruk ? 3 : 1;
7678 /* [HGM] compute & store e.p. status and castling rights for new position */
7679 /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
7682 if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
7683 oldEP = (signed char)board[EP_STATUS];
7684 board[EP_STATUS] = EP_NONE;
7686 if( board[toY][toX] != EmptySquare )
7687 board[EP_STATUS] = EP_CAPTURE;
7689 if( board[fromY][fromX] == WhitePawn ) {
7690 if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
7691 board[EP_STATUS] = EP_PAWN_MOVE;
7693 if(toX>BOARD_LEFT && board[toY][toX-1] == BlackPawn &&
7694 gameInfo.variant != VariantBerolina || toX < fromX)
7695 board[EP_STATUS] = toX | berolina;
7696 if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
7697 gameInfo.variant != VariantBerolina || toX > fromX)
7698 board[EP_STATUS] = toX;
7701 if( board[fromY][fromX] == BlackPawn ) {
7702 if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
7703 board[EP_STATUS] = EP_PAWN_MOVE;
7704 if( toY-fromY== -2) {
7705 if(toX>BOARD_LEFT && board[toY][toX-1] == WhitePawn &&
7706 gameInfo.variant != VariantBerolina || toX < fromX)
7707 board[EP_STATUS] = toX | berolina;
7708 if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
7709 gameInfo.variant != VariantBerolina || toX > fromX)
7710 board[EP_STATUS] = toX;
7714 for(i=0; i<nrCastlingRights; i++) {
7715 if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
7716 board[CASTLING][i] == toX && castlingRank[i] == toY
7717 ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
7722 /* [HGM] In Shatranj and Courier all promotions are to Ferz */
7723 if((gameInfo.variant==VariantShatranj || gameInfo.variant==VariantCourier || gameInfo.variant == VariantMakruk)
7724 && promoChar != 0) promoChar = PieceToChar(WhiteFerz);
7726 if (fromX == toX && fromY == toY) return;
7728 if (fromY == DROP_RANK) {
7730 piece = board[toY][toX] = (ChessSquare) fromX;
7732 piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
7733 king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
7734 if(gameInfo.variant == VariantKnightmate)
7735 king += (int) WhiteUnicorn - (int) WhiteKing;
7737 /* Code added by Tord: */
7738 /* FRC castling assumed when king captures friendly rook. */
7739 if (board[fromY][fromX] == WhiteKing &&
7740 board[toY][toX] == WhiteRook) {
7741 board[fromY][fromX] = EmptySquare;
7742 board[toY][toX] = EmptySquare;
7744 board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
7746 board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
7748 } else if (board[fromY][fromX] == BlackKing &&
7749 board[toY][toX] == BlackRook) {
7750 board[fromY][fromX] = EmptySquare;
7751 board[toY][toX] = EmptySquare;
7753 board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
7755 board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
7757 /* End of code added by Tord */
7759 } else if (board[fromY][fromX] == king
7760 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7761 && toY == fromY && toX > fromX+1) {
7762 board[fromY][fromX] = EmptySquare;
7763 board[toY][toX] = king;
7764 board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
7765 board[fromY][BOARD_RGHT-1] = EmptySquare;
7766 } else if (board[fromY][fromX] == king
7767 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7768 && toY == fromY && toX < fromX-1) {
7769 board[fromY][fromX] = EmptySquare;
7770 board[toY][toX] = king;
7771 board[toY][toX+1] = board[fromY][BOARD_LEFT];
7772 board[fromY][BOARD_LEFT] = EmptySquare;
7773 } else if (board[fromY][fromX] == WhitePawn
7774 && toY >= BOARD_HEIGHT-promoRank
7775 && gameInfo.variant != VariantXiangqi
7777 /* white pawn promotion */
7778 board[toY][toX] = CharToPiece(ToUpper(promoChar));
7779 if (board[toY][toX] == EmptySquare) {
7780 board[toY][toX] = WhiteQueen;
7782 if(gameInfo.variant==VariantBughouse ||
7783 gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
7784 board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
7785 board[fromY][fromX] = EmptySquare;
7786 } else if ((fromY == BOARD_HEIGHT-4)
7788 && gameInfo.variant != VariantXiangqi
7789 && gameInfo.variant != VariantBerolina
7790 && (board[fromY][fromX] == WhitePawn)
7791 && (board[toY][toX] == EmptySquare)) {
7792 board[fromY][fromX] = EmptySquare;
7793 board[toY][toX] = WhitePawn;
7794 captured = board[toY - 1][toX];
7795 board[toY - 1][toX] = EmptySquare;
7796 } else if ((fromY == BOARD_HEIGHT-4)
7798 && gameInfo.variant == VariantBerolina
7799 && (board[fromY][fromX] == WhitePawn)
7800 && (board[toY][toX] == EmptySquare)) {
7801 board[fromY][fromX] = EmptySquare;
7802 board[toY][toX] = WhitePawn;
7803 if(oldEP & EP_BEROLIN_A) {
7804 captured = board[fromY][fromX-1];
7805 board[fromY][fromX-1] = EmptySquare;
7806 }else{ captured = board[fromY][fromX+1];
7807 board[fromY][fromX+1] = EmptySquare;
7809 } else if (board[fromY][fromX] == king
7810 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7811 && toY == fromY && toX > fromX+1) {
7812 board[fromY][fromX] = EmptySquare;
7813 board[toY][toX] = king;
7814 board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
7815 board[fromY][BOARD_RGHT-1] = EmptySquare;
7816 } else if (board[fromY][fromX] == king
7817 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7818 && toY == fromY && toX < fromX-1) {
7819 board[fromY][fromX] = EmptySquare;
7820 board[toY][toX] = king;
7821 board[toY][toX+1] = board[fromY][BOARD_LEFT];
7822 board[fromY][BOARD_LEFT] = EmptySquare;
7823 } else if (fromY == 7 && fromX == 3
7824 && board[fromY][fromX] == BlackKing
7825 && toY == 7 && toX == 5) {
7826 board[fromY][fromX] = EmptySquare;
7827 board[toY][toX] = BlackKing;
7828 board[fromY][7] = EmptySquare;
7829 board[toY][4] = BlackRook;
7830 } else if (fromY == 7 && fromX == 3
7831 && board[fromY][fromX] == BlackKing
7832 && toY == 7 && toX == 1) {
7833 board[fromY][fromX] = EmptySquare;
7834 board[toY][toX] = BlackKing;
7835 board[fromY][0] = EmptySquare;
7836 board[toY][2] = BlackRook;
7837 } else if (board[fromY][fromX] == BlackPawn
7839 && gameInfo.variant != VariantXiangqi
7841 /* black pawn promotion */
7842 board[toY][toX] = CharToPiece(ToLower(promoChar));
7843 if (board[toY][toX] == EmptySquare) {
7844 board[toY][toX] = BlackQueen;
7846 if(gameInfo.variant==VariantBughouse ||
7847 gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
7848 board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
7849 board[fromY][fromX] = EmptySquare;
7850 } else if ((fromY == 3)
7852 && gameInfo.variant != VariantXiangqi
7853 && gameInfo.variant != VariantBerolina
7854 && (board[fromY][fromX] == BlackPawn)
7855 && (board[toY][toX] == EmptySquare)) {
7856 board[fromY][fromX] = EmptySquare;
7857 board[toY][toX] = BlackPawn;
7858 captured = board[toY + 1][toX];
7859 board[toY + 1][toX] = EmptySquare;
7860 } else if ((fromY == 3)
7862 && gameInfo.variant == VariantBerolina
7863 && (board[fromY][fromX] == BlackPawn)
7864 && (board[toY][toX] == EmptySquare)) {
7865 board[fromY][fromX] = EmptySquare;
7866 board[toY][toX] = BlackPawn;
7867 if(oldEP & EP_BEROLIN_A) {
7868 captured = board[fromY][fromX-1];
7869 board[fromY][fromX-1] = EmptySquare;
7870 }else{ captured = board[fromY][fromX+1];
7871 board[fromY][fromX+1] = EmptySquare;
7874 board[toY][toX] = board[fromY][fromX];
7875 board[fromY][fromX] = EmptySquare;
7878 /* [HGM] now we promote for Shogi, if needed */
7879 if(gameInfo.variant == VariantShogi && promoChar == 'q')
7880 board[toY][toX] = (ChessSquare) (PROMOTED piece);
7883 if (gameInfo.holdingsWidth != 0) {
7885 /* !!A lot more code needs to be written to support holdings */
7886 /* [HGM] OK, so I have written it. Holdings are stored in the */
7887 /* penultimate board files, so they are automaticlly stored */
7888 /* in the game history. */
7889 if (fromY == DROP_RANK) {
7890 /* Delete from holdings, by decreasing count */
7891 /* and erasing image if necessary */
7893 if(p < (int) BlackPawn) { /* white drop */
7894 p -= (int)WhitePawn;
7895 p = PieceToNumber((ChessSquare)p);
7896 if(p >= gameInfo.holdingsSize) p = 0;
7897 if(--board[p][BOARD_WIDTH-2] <= 0)
7898 board[p][BOARD_WIDTH-1] = EmptySquare;
7899 if((int)board[p][BOARD_WIDTH-2] < 0)
7900 board[p][BOARD_WIDTH-2] = 0;
7901 } else { /* black drop */
7902 p -= (int)BlackPawn;
7903 p = PieceToNumber((ChessSquare)p);
7904 if(p >= gameInfo.holdingsSize) p = 0;
7905 if(--board[BOARD_HEIGHT-1-p][1] <= 0)
7906 board[BOARD_HEIGHT-1-p][0] = EmptySquare;
7907 if((int)board[BOARD_HEIGHT-1-p][1] < 0)
7908 board[BOARD_HEIGHT-1-p][1] = 0;
7911 if (captured != EmptySquare && gameInfo.holdingsSize > 0
7912 && gameInfo.variant != VariantBughouse ) {
7913 /* [HGM] holdings: Add to holdings, if holdings exist */
7914 if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
7915 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
7916 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
7919 if (p >= (int) BlackPawn) {
7920 p -= (int)BlackPawn;
7921 if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
7922 /* in Shogi restore piece to its original first */
7923 captured = (ChessSquare) (DEMOTED captured);
7926 p = PieceToNumber((ChessSquare)p);
7927 if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
7928 board[p][BOARD_WIDTH-2]++;
7929 board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
7931 p -= (int)WhitePawn;
7932 if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
7933 captured = (ChessSquare) (DEMOTED captured);
7936 p = PieceToNumber((ChessSquare)p);
7937 if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
7938 board[BOARD_HEIGHT-1-p][1]++;
7939 board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
7942 } else if (gameInfo.variant == VariantAtomic) {
7943 if (captured != EmptySquare) {
7945 for (y = toY-1; y <= toY+1; y++) {
7946 for (x = toX-1; x <= toX+1; x++) {
7947 if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
7948 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
7949 board[y][x] = EmptySquare;
7953 board[toY][toX] = EmptySquare;
7956 if(gameInfo.variant == VariantShogi && promoChar != NULLCHAR && promoChar != '=') {
7957 /* [HGM] Shogi promotions */
7958 board[toY][toX] = (ChessSquare) (PROMOTED piece);
7961 if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
7962 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
7963 // [HGM] superchess: take promotion piece out of holdings
7964 int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
7965 if((int)piece < (int)BlackPawn) { // determine stm from piece color
7966 if(!--board[k][BOARD_WIDTH-2])
7967 board[k][BOARD_WIDTH-1] = EmptySquare;
7969 if(!--board[BOARD_HEIGHT-1-k][1])
7970 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
7976 /* Updates forwardMostMove */
7978 MakeMove(fromX, fromY, toX, toY, promoChar)
7979 int fromX, fromY, toX, toY;
7982 // forwardMostMove++; // [HGM] bare: moved downstream
7984 if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
7985 int timeLeft; static int lastLoadFlag=0; int king, piece;
7986 piece = boards[forwardMostMove][fromY][fromX];
7987 king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
7988 if(gameInfo.variant == VariantKnightmate)
7989 king += (int) WhiteUnicorn - (int) WhiteKing;
7990 if(forwardMostMove == 0) {
7992 fprintf(serverMoves, "%s;", second.tidy);
7993 fprintf(serverMoves, "%s;", first.tidy);
7994 if(!blackPlaysFirst)
7995 fprintf(serverMoves, "%s;", second.tidy);
7996 } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
7997 lastLoadFlag = loadFlag;
7999 fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
8000 // print castling suffix
8001 if( toY == fromY && piece == king ) {
8003 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
8005 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
8008 if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
8009 boards[forwardMostMove][fromY][fromX] == BlackPawn ) &&
8010 boards[forwardMostMove][toY][toX] == EmptySquare
8012 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
8014 if(promoChar != NULLCHAR)
8015 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);
8017 fprintf(serverMoves, "/%d/%d",
8018 pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
8019 if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
8020 else timeLeft = blackTimeRemaining/1000;
8021 fprintf(serverMoves, "/%d", timeLeft);
8023 fflush(serverMoves);
8026 if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations
8027 DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
8031 UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
8032 if (commentList[forwardMostMove+1] != NULL) {
8033 free(commentList[forwardMostMove+1]);
8034 commentList[forwardMostMove+1] = NULL;
8036 CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
8037 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
8038 forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
8039 SwitchClocks(); // uses forwardMostMove, so must be done after incrementing it !
8040 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
8041 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
8042 gameInfo.result = GameUnfinished;
8043 if (gameInfo.resultDetails != NULL) {
8044 free(gameInfo.resultDetails);
8045 gameInfo.resultDetails = NULL;
8047 CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
8048 moveList[forwardMostMove - 1]);
8049 (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
8050 PosFlags(forwardMostMove - 1),
8051 fromY, fromX, toY, toX, promoChar,
8052 parseList[forwardMostMove - 1]);
8053 switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
8059 if(gameInfo.variant != VariantShogi)
8060 strcat(parseList[forwardMostMove - 1], "+");
8064 strcat(parseList[forwardMostMove - 1], "#");
8067 if (appData.debugMode) {
8068 fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
8073 /* Updates currentMove if not pausing */
8075 ShowMove(fromX, fromY, toX, toY)
8077 int instant = (gameMode == PlayFromGameFile) ?
8078 (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
8079 if(appData.noGUI) return;
8080 if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
8082 if (forwardMostMove == currentMove + 1) {
8083 AnimateMove(boards[forwardMostMove - 1],
8084 fromX, fromY, toX, toY);
8086 if (appData.highlightLastMove) {
8087 SetHighlights(fromX, fromY, toX, toY);
8090 currentMove = forwardMostMove;
8093 if (instant) return;
8095 DisplayMove(currentMove - 1);
8096 DrawPosition(FALSE, boards[currentMove]);
8097 DisplayBothClocks();
8098 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
8101 void SendEgtPath(ChessProgramState *cps)
8102 { /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
8103 char buf[MSG_SIZ], name[MSG_SIZ], *p;
8105 if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
8108 char c, *q = name+1, *r, *s;
8110 name[0] = ','; // extract next format name from feature and copy with prefixed ','
8111 while(*p && *p != ',') *q++ = *p++;
8113 if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
8114 strcmp(name, ",nalimov:") == 0 ) {
8115 // take nalimov path from the menu-changeable option first, if it is defined
8116 sprintf(buf, "egtpath nalimov %s\n", appData.defaultPathEGTB);
8117 SendToProgram(buf,cps); // send egtbpath command for nalimov
8119 if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
8120 (s = StrStr(appData.egtFormats, name)) != NULL) {
8121 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
8122 s = r = StrStr(s, ":") + 1; // beginning of path info
8123 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
8124 c = *r; *r = 0; // temporarily null-terminate path info
8125 *--q = 0; // strip of trailig ':' from name
8126 sprintf(buf, "egtpath %s %s\n", name+1, s);
8128 SendToProgram(buf,cps); // send egtbpath command for this format
8130 if(*p == ',') p++; // read away comma to position for next format name
8135 InitChessProgram(cps, setup)
8136 ChessProgramState *cps;
8137 int setup; /* [HGM] needed to setup FRC opening position */
8139 char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
8140 if (appData.noChessProgram) return;
8141 hintRequested = FALSE;
8142 bookRequested = FALSE;
8144 /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
8145 /* moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
8146 if(cps->memSize) { /* [HGM] memory */
8147 sprintf(buf, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
8148 SendToProgram(buf, cps);
8150 SendEgtPath(cps); /* [HGM] EGT */
8151 if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
8152 sprintf(buf, "cores %d\n", appData.smpCores);
8153 SendToProgram(buf, cps);
8156 SendToProgram(cps->initString, cps);
8157 if (gameInfo.variant != VariantNormal &&
8158 gameInfo.variant != VariantLoadable
8159 /* [HGM] also send variant if board size non-standard */
8160 || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
8162 char *v = VariantName(gameInfo.variant);
8163 if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
8164 /* [HGM] in protocol 1 we have to assume all variants valid */
8165 sprintf(buf, _("Variant %s not supported by %s"), v, cps->tidy);
8166 DisplayFatalError(buf, 0, 1);
8170 /* [HGM] make prefix for non-standard board size. Awkward testing... */
8171 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8172 if( gameInfo.variant == VariantXiangqi )
8173 overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
8174 if( gameInfo.variant == VariantShogi )
8175 overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
8176 if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
8177 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
8178 if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
8179 gameInfo.variant == VariantGothic || gameInfo.variant == VariantFalcon )
8180 overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8181 if( gameInfo.variant == VariantCourier )
8182 overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8183 if( gameInfo.variant == VariantSuper )
8184 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
8185 if( gameInfo.variant == VariantGreat )
8186 overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
8189 sprintf(b, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
8190 gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
8191 /* [HGM] varsize: try first if this defiant size variant is specifically known */
8192 if(StrStr(cps->variants, b) == NULL) {
8193 // specific sized variant not known, check if general sizing allowed
8194 if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
8195 if(StrStr(cps->variants, "boardsize") == NULL) {
8196 sprintf(buf, "Board size %dx%d+%d not supported by %s",
8197 gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
8198 DisplayFatalError(buf, 0, 1);
8201 /* [HGM] here we really should compare with the maximum supported board size */
8204 } else sprintf(b, "%s", VariantName(gameInfo.variant));
8205 sprintf(buf, "variant %s\n", b);
8206 SendToProgram(buf, cps);
8208 currentlyInitializedVariant = gameInfo.variant;
8210 /* [HGM] send opening position in FRC to first engine */
8212 SendToProgram("force\n", cps);
8214 /* engine is now in force mode! Set flag to wake it up after first move. */
8215 setboardSpoiledMachineBlack = 1;
8219 snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
8220 SendToProgram(buf, cps);
8222 cps->maybeThinking = FALSE;
8223 cps->offeredDraw = 0;
8224 if (!appData.icsActive) {
8225 SendTimeControl(cps, movesPerSession, timeControl,
8226 timeIncrement, appData.searchDepth,
8229 if (appData.showThinking
8230 // [HGM] thinking: four options require thinking output to be sent
8231 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
8233 SendToProgram("post\n", cps);
8235 SendToProgram("hard\n", cps);
8236 if (!appData.ponderNextMove) {
8237 /* Warning: "easy" is a toggle in GNU Chess, so don't send
8238 it without being sure what state we are in first. "hard"
8239 is not a toggle, so that one is OK.
8241 SendToProgram("easy\n", cps);
8244 sprintf(buf, "ping %d\n", ++cps->lastPing);
8245 SendToProgram(buf, cps);
8247 cps->initDone = TRUE;
8252 StartChessProgram(cps)
8253 ChessProgramState *cps;
8258 if (appData.noChessProgram) return;
8259 cps->initDone = FALSE;
8261 if (strcmp(cps->host, "localhost") == 0) {
8262 err = StartChildProcess(cps->program, cps->dir, &cps->pr);
8263 } else if (*appData.remoteShell == NULLCHAR) {
8264 err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
8266 if (*appData.remoteUser == NULLCHAR) {
8267 snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
8270 snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
8271 cps->host, appData.remoteUser, cps->program);
8273 err = StartChildProcess(buf, "", &cps->pr);
8277 sprintf(buf, _("Startup failure on '%s'"), cps->program);
8278 DisplayFatalError(buf, err, 1);
8284 cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
8285 if (cps->protocolVersion > 1) {
8286 sprintf(buf, "xboard\nprotover %d\n", cps->protocolVersion);
8287 cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
8288 cps->comboCnt = 0; // and values of combo boxes
8289 SendToProgram(buf, cps);
8291 SendToProgram("xboard\n", cps);
8297 TwoMachinesEventIfReady P((void))
8299 if (first.lastPing != first.lastPong) {
8300 DisplayMessage("", _("Waiting for first chess program"));
8301 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
8304 if (second.lastPing != second.lastPong) {
8305 DisplayMessage("", _("Waiting for second chess program"));
8306 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
8314 NextMatchGame P((void))
8316 int index; /* [HGM] autoinc: step load index during match */
8318 if (*appData.loadGameFile != NULLCHAR) {
8319 index = appData.loadGameIndex;
8320 if(index < 0) { // [HGM] autoinc
8321 lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
8322 if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
8324 LoadGameFromFile(appData.loadGameFile,
8326 appData.loadGameFile, FALSE);
8327 } else if (*appData.loadPositionFile != NULLCHAR) {
8328 index = appData.loadPositionIndex;
8329 if(index < 0) { // [HGM] autoinc
8330 lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
8331 if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
8333 LoadPositionFromFile(appData.loadPositionFile,
8335 appData.loadPositionFile);
8337 TwoMachinesEventIfReady();
8340 void UserAdjudicationEvent( int result )
8342 ChessMove gameResult = GameIsDrawn;
8345 gameResult = WhiteWins;
8347 else if( result < 0 ) {
8348 gameResult = BlackWins;
8351 if( gameMode == TwoMachinesPlay ) {
8352 GameEnds( gameResult, "User adjudication", GE_XBOARD );
8357 // [HGM] save: calculate checksum of game to make games easily identifiable
8358 int StringCheckSum(char *s)
8361 if(s==NULL) return 0;
8362 while(*s) i = i*259 + *s++;
8369 for(i=backwardMostMove; i<forwardMostMove; i++) {
8370 sum += pvInfoList[i].depth;
8371 sum += StringCheckSum(parseList[i]);
8372 sum += StringCheckSum(commentList[i]);
8375 if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
8376 return sum + StringCheckSum(commentList[i]);
8377 } // end of save patch
8380 GameEnds(result, resultDetails, whosays)
8382 char *resultDetails;
8385 GameMode nextGameMode;
8389 if(endingGame) return; /* [HGM] crash: forbid recursion */
8392 if (appData.debugMode) {
8393 fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
8394 result, resultDetails ? resultDetails : "(null)", whosays);
8397 if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
8398 /* If we are playing on ICS, the server decides when the
8399 game is over, but the engine can offer to draw, claim
8403 if (appData.zippyPlay && first.initDone) {
8404 if (result == GameIsDrawn) {
8405 /* In case draw still needs to be claimed */
8406 SendToICS(ics_prefix);
8407 SendToICS("draw\n");
8408 } else if (StrCaseStr(resultDetails, "resign")) {
8409 SendToICS(ics_prefix);
8410 SendToICS("resign\n");
8414 endingGame = 0; /* [HGM] crash */
8418 /* If we're loading the game from a file, stop */
8419 if (whosays == GE_FILE) {
8420 (void) StopLoadGameTimer();
8424 /* Cancel draw offers */
8425 first.offeredDraw = second.offeredDraw = 0;
8427 /* If this is an ICS game, only ICS can really say it's done;
8428 if not, anyone can. */
8429 isIcsGame = (gameMode == IcsPlayingWhite ||
8430 gameMode == IcsPlayingBlack ||
8431 gameMode == IcsObserving ||
8432 gameMode == IcsExamining);
8434 if (!isIcsGame || whosays == GE_ICS) {
8435 /* OK -- not an ICS game, or ICS said it was done */
8437 if (!isIcsGame && !appData.noChessProgram)
8438 SetUserThinkingEnables();
8440 /* [HGM] if a machine claims the game end we verify this claim */
8441 if(gameMode == TwoMachinesPlay && appData.testClaims) {
8442 if(appData.testLegality && whosays >= GE_ENGINE1 ) {
8444 ChessMove trueResult = (ChessMove) -1;
8446 claimer = whosays == GE_ENGINE1 ? /* color of claimer */
8447 first.twoMachinesColor[0] :
8448 second.twoMachinesColor[0] ;
8450 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
8451 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
8452 /* [HGM] verify: engine mate claims accepted if they were flagged */
8453 trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
8455 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
8456 /* [HGM] verify: engine mate claims accepted if they were flagged */
8457 trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8459 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
8460 trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
8463 // now verify win claims, but not in drop games, as we don't understand those yet
8464 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
8465 || gameInfo.variant == VariantGreat) &&
8466 (result == WhiteWins && claimer == 'w' ||
8467 result == BlackWins && claimer == 'b' ) ) { // case to verify: engine claims own win
8468 if (appData.debugMode) {
8469 fprintf(debugFP, "result=%d sp=%d move=%d\n",
8470 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
8472 if(result != trueResult) {
8473 sprintf(buf, "False win claim: '%s'", resultDetails);
8474 result = claimer == 'w' ? BlackWins : WhiteWins;
8475 resultDetails = buf;
8478 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
8479 && (forwardMostMove <= backwardMostMove ||
8480 (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
8481 (claimer=='b')==(forwardMostMove&1))
8483 /* [HGM] verify: draws that were not flagged are false claims */
8484 sprintf(buf, "False draw claim: '%s'", resultDetails);
8485 result = claimer == 'w' ? BlackWins : WhiteWins;
8486 resultDetails = buf;
8488 /* (Claiming a loss is accepted no questions asked!) */
8490 /* [HGM] bare: don't allow bare King to win */
8491 if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
8492 && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
8493 && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
8494 && result != GameIsDrawn)
8495 { int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
8496 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
8497 int p = (signed char)boards[forwardMostMove][i][j] - color;
8498 if(p >= 0 && p <= (int)WhiteKing) k++;
8500 if (appData.debugMode) {
8501 fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
8502 result, resultDetails ? resultDetails : "(null)", whosays, k, color);
8505 result = GameIsDrawn;
8506 sprintf(buf, "%s but bare king", resultDetails);
8507 resultDetails = buf;
8513 if(serverMoves != NULL && !loadFlag) { char c = '=';
8514 if(result==WhiteWins) c = '+';
8515 if(result==BlackWins) c = '-';
8516 if(resultDetails != NULL)
8517 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
8519 if (resultDetails != NULL) {
8520 gameInfo.result = result;
8521 gameInfo.resultDetails = StrSave(resultDetails);
8523 /* display last move only if game was not loaded from file */
8524 if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
8525 DisplayMove(currentMove - 1);
8527 if (forwardMostMove != 0) {
8528 if (gameMode != PlayFromGameFile && gameMode != EditGame
8529 && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
8531 if (*appData.saveGameFile != NULLCHAR) {
8532 SaveGameToFile(appData.saveGameFile, TRUE);
8533 } else if (appData.autoSaveGames) {
8536 if (*appData.savePositionFile != NULLCHAR) {
8537 SavePositionToFile(appData.savePositionFile);
8542 /* Tell program how game ended in case it is learning */
8543 /* [HGM] Moved this to after saving the PGN, just in case */
8544 /* engine died and we got here through time loss. In that */
8545 /* case we will get a fatal error writing the pipe, which */
8546 /* would otherwise lose us the PGN. */
8547 /* [HGM] crash: not needed anymore, but doesn't hurt; */
8548 /* output during GameEnds should never be fatal anymore */
8549 if (gameMode == MachinePlaysWhite ||
8550 gameMode == MachinePlaysBlack ||
8551 gameMode == TwoMachinesPlay ||
8552 gameMode == IcsPlayingWhite ||
8553 gameMode == IcsPlayingBlack ||
8554 gameMode == BeginningOfGame) {
8556 sprintf(buf, "result %s {%s}\n", PGNResult(result),
8558 if (first.pr != NoProc) {
8559 SendToProgram(buf, &first);
8561 if (second.pr != NoProc &&
8562 gameMode == TwoMachinesPlay) {
8563 SendToProgram(buf, &second);
8568 if (appData.icsActive) {
8569 if (appData.quietPlay &&
8570 (gameMode == IcsPlayingWhite ||
8571 gameMode == IcsPlayingBlack)) {
8572 SendToICS(ics_prefix);
8573 SendToICS("set shout 1\n");
8575 nextGameMode = IcsIdle;
8576 ics_user_moved = FALSE;
8577 /* clean up premove. It's ugly when the game has ended and the
8578 * premove highlights are still on the board.
8582 ClearPremoveHighlights();
8583 DrawPosition(FALSE, boards[currentMove]);
8585 if (whosays == GE_ICS) {
8588 if (gameMode == IcsPlayingWhite)
8590 else if(gameMode == IcsPlayingBlack)
8594 if (gameMode == IcsPlayingBlack)
8596 else if(gameMode == IcsPlayingWhite)
8603 PlayIcsUnfinishedSound();
8606 } else if (gameMode == EditGame ||
8607 gameMode == PlayFromGameFile ||
8608 gameMode == AnalyzeMode ||
8609 gameMode == AnalyzeFile) {
8610 nextGameMode = gameMode;
8612 nextGameMode = EndOfGame;
8617 nextGameMode = gameMode;
8620 if (appData.noChessProgram) {
8621 gameMode = nextGameMode;
8623 endingGame = 0; /* [HGM] crash */
8628 /* Put first chess program into idle state */
8629 if (first.pr != NoProc &&
8630 (gameMode == MachinePlaysWhite ||
8631 gameMode == MachinePlaysBlack ||
8632 gameMode == TwoMachinesPlay ||
8633 gameMode == IcsPlayingWhite ||
8634 gameMode == IcsPlayingBlack ||
8635 gameMode == BeginningOfGame)) {
8636 SendToProgram("force\n", &first);
8637 if (first.usePing) {
8639 sprintf(buf, "ping %d\n", ++first.lastPing);
8640 SendToProgram(buf, &first);
8643 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
8644 /* Kill off first chess program */
8645 if (first.isr != NULL)
8646 RemoveInputSource(first.isr);
8649 if (first.pr != NoProc) {
8651 DoSleep( appData.delayBeforeQuit );
8652 SendToProgram("quit\n", &first);
8653 DoSleep( appData.delayAfterQuit );
8654 DestroyChildProcess(first.pr, first.useSigterm);
8659 /* Put second chess program into idle state */
8660 if (second.pr != NoProc &&
8661 gameMode == TwoMachinesPlay) {
8662 SendToProgram("force\n", &second);
8663 if (second.usePing) {
8665 sprintf(buf, "ping %d\n", ++second.lastPing);
8666 SendToProgram(buf, &second);
8669 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
8670 /* Kill off second chess program */
8671 if (second.isr != NULL)
8672 RemoveInputSource(second.isr);
8675 if (second.pr != NoProc) {
8676 DoSleep( appData.delayBeforeQuit );
8677 SendToProgram("quit\n", &second);
8678 DoSleep( appData.delayAfterQuit );
8679 DestroyChildProcess(second.pr, second.useSigterm);
8684 if (matchMode && gameMode == TwoMachinesPlay) {
8687 if (first.twoMachinesColor[0] == 'w') {
8694 if (first.twoMachinesColor[0] == 'b') {
8703 if (matchGame < appData.matchGames) {
8705 if(appData.sameColorGames <= 1) { /* [HGM] alternate: suppress color swap */
8706 tmp = first.twoMachinesColor;
8707 first.twoMachinesColor = second.twoMachinesColor;
8708 second.twoMachinesColor = tmp;
8710 gameMode = nextGameMode;
8712 if(appData.matchPause>10000 || appData.matchPause<10)
8713 appData.matchPause = 10000; /* [HGM] make pause adjustable */
8714 ScheduleDelayedEvent(NextMatchGame, appData.matchPause);
8715 endingGame = 0; /* [HGM] crash */
8719 gameMode = nextGameMode;
8720 sprintf(buf, _("Match %s vs. %s: final score %d-%d-%d"),
8721 first.tidy, second.tidy,
8722 first.matchWins, second.matchWins,
8723 appData.matchGames - (first.matchWins + second.matchWins));
8724 DisplayFatalError(buf, 0, 0);
8727 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
8728 !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
8730 gameMode = nextGameMode;
8732 endingGame = 0; /* [HGM] crash */
8735 /* Assumes program was just initialized (initString sent).
8736 Leaves program in force mode. */
8738 FeedMovesToProgram(cps, upto)
8739 ChessProgramState *cps;
8744 if (appData.debugMode)
8745 fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
8746 startedFromSetupPosition ? "position and " : "",
8747 backwardMostMove, upto, cps->which);
8748 if(currentlyInitializedVariant != gameInfo.variant) { char buf[MSG_SIZ];
8749 // [HGM] variantswitch: make engine aware of new variant
8750 if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
8751 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
8752 sprintf(buf, "variant %s\n", VariantName(gameInfo.variant));
8753 SendToProgram(buf, cps);
8754 currentlyInitializedVariant = gameInfo.variant;
8756 SendToProgram("force\n", cps);
8757 if (startedFromSetupPosition) {
8758 SendBoard(cps, backwardMostMove);
8759 if (appData.debugMode) {
8760 fprintf(debugFP, "feedMoves\n");
8763 for (i = backwardMostMove; i < upto; i++) {
8764 SendMoveToProgram(i, cps);
8770 ResurrectChessProgram()
8772 /* The chess program may have exited.
8773 If so, restart it and feed it all the moves made so far. */
8775 if (appData.noChessProgram || first.pr != NoProc) return;
8777 StartChessProgram(&first);
8778 InitChessProgram(&first, FALSE);
8779 FeedMovesToProgram(&first, currentMove);
8781 if (!first.sendTime) {
8782 /* can't tell gnuchess what its clock should read,
8783 so we bow to its notion. */
8785 timeRemaining[0][currentMove] = whiteTimeRemaining;
8786 timeRemaining[1][currentMove] = blackTimeRemaining;
8789 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
8790 appData.icsEngineAnalyze) && first.analysisSupport) {
8791 SendToProgram("analyze\n", &first);
8792 first.analyzing = TRUE;
8805 if (appData.debugMode) {
8806 fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
8807 redraw, init, gameMode);
8809 CleanupTail(); // [HGM] vari: delete any stored variations
8810 pausing = pauseExamInvalid = FALSE;
8811 startedFromSetupPosition = blackPlaysFirst = FALSE;
8813 whiteFlag = blackFlag = FALSE;
8814 userOfferedDraw = FALSE;
8815 hintRequested = bookRequested = FALSE;
8816 first.maybeThinking = FALSE;
8817 second.maybeThinking = FALSE;
8818 first.bookSuspend = FALSE; // [HGM] book
8819 second.bookSuspend = FALSE;
8820 thinkOutput[0] = NULLCHAR;
8821 lastHint[0] = NULLCHAR;
8822 ClearGameInfo(&gameInfo);
8823 gameInfo.variant = StringToVariant(appData.variant);
8824 ics_user_moved = ics_clock_paused = FALSE;
8825 ics_getting_history = H_FALSE;
8827 white_holding[0] = black_holding[0] = NULLCHAR;
8828 ClearProgramStats();
8829 opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
8833 flipView = appData.flipView;
8834 ClearPremoveHighlights();
8836 alarmSounded = FALSE;
8838 GameEnds((ChessMove) 0, NULL, GE_PLAYER);
8839 if(appData.serverMovesName != NULL) {
8840 /* [HGM] prepare to make moves file for broadcasting */
8841 clock_t t = clock();
8842 if(serverMoves != NULL) fclose(serverMoves);
8843 serverMoves = fopen(appData.serverMovesName, "r");
8844 if(serverMoves != NULL) {
8845 fclose(serverMoves);
8846 /* delay 15 sec before overwriting, so all clients can see end */
8847 while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
8849 serverMoves = fopen(appData.serverMovesName, "w");
8853 gameMode = BeginningOfGame;
8855 if(appData.icsActive) gameInfo.variant = VariantNormal;
8856 currentMove = forwardMostMove = backwardMostMove = 0;
8857 InitPosition(redraw);
8858 for (i = 0; i < MAX_MOVES; i++) {
8859 if (commentList[i] != NULL) {
8860 free(commentList[i]);
8861 commentList[i] = NULL;
8865 timeRemaining[0][0] = whiteTimeRemaining;
8866 timeRemaining[1][0] = blackTimeRemaining;
8867 if (first.pr == NULL) {
8868 StartChessProgram(&first);
8871 InitChessProgram(&first, startedFromSetupPosition);
8874 DisplayMessage("", "");
8875 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
8876 lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
8883 if (!AutoPlayOneMove())
8885 if (matchMode || appData.timeDelay == 0)
8887 if (appData.timeDelay < 0 || gameMode == AnalyzeFile)
8889 StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
8898 int fromX, fromY, toX, toY;
8900 if (appData.debugMode) {
8901 fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
8904 if (gameMode != PlayFromGameFile)
8907 if (currentMove >= forwardMostMove) {
8908 gameMode = EditGame;
8911 /* [AS] Clear current move marker at the end of a game */
8912 /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
8917 toX = moveList[currentMove][2] - AAA;
8918 toY = moveList[currentMove][3] - ONE;
8920 if (moveList[currentMove][1] == '@') {
8921 if (appData.highlightLastMove) {
8922 SetHighlights(-1, -1, toX, toY);
8925 fromX = moveList[currentMove][0] - AAA;
8926 fromY = moveList[currentMove][1] - ONE;
8928 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
8930 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
8932 if (appData.highlightLastMove) {
8933 SetHighlights(fromX, fromY, toX, toY);
8936 DisplayMove(currentMove);
8937 SendMoveToProgram(currentMove++, &first);
8938 DisplayBothClocks();
8939 DrawPosition(FALSE, boards[currentMove]);
8940 // [HGM] PV info: always display, routine tests if empty
8941 DisplayComment(currentMove - 1, commentList[currentMove]);
8947 LoadGameOneMove(readAhead)
8948 ChessMove readAhead;
8950 int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
8951 char promoChar = NULLCHAR;
8956 if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
8957 gameMode != AnalyzeMode && gameMode != Training) {
8962 yyboardindex = forwardMostMove;
8963 if (readAhead != (ChessMove)0) {
8964 moveType = readAhead;
8966 if (gameFileFP == NULL)
8968 moveType = (ChessMove) yylex();
8974 if (appData.debugMode)
8975 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
8978 /* append the comment but don't display it */
8979 AppendComment(currentMove, p, FALSE);
8982 case WhiteCapturesEnPassant:
8983 case BlackCapturesEnPassant:
8984 case WhitePromotionChancellor:
8985 case BlackPromotionChancellor:
8986 case WhitePromotionArchbishop:
8987 case BlackPromotionArchbishop:
8988 case WhitePromotionCentaur:
8989 case BlackPromotionCentaur:
8990 case WhitePromotionQueen:
8991 case BlackPromotionQueen:
8992 case WhitePromotionRook:
8993 case BlackPromotionRook:
8994 case WhitePromotionBishop:
8995 case BlackPromotionBishop:
8996 case WhitePromotionKnight:
8997 case BlackPromotionKnight:
8998 case WhitePromotionKing:
8999 case BlackPromotionKing:
9001 case WhiteKingSideCastle:
9002 case WhiteQueenSideCastle:
9003 case BlackKingSideCastle:
9004 case BlackQueenSideCastle:
9005 case WhiteKingSideCastleWild:
9006 case WhiteQueenSideCastleWild:
9007 case BlackKingSideCastleWild:
9008 case BlackQueenSideCastleWild:
9010 case WhiteHSideCastleFR:
9011 case WhiteASideCastleFR:
9012 case BlackHSideCastleFR:
9013 case BlackASideCastleFR:
9015 if (appData.debugMode)
9016 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
9017 fromX = currentMoveString[0] - AAA;
9018 fromY = currentMoveString[1] - ONE;
9019 toX = currentMoveString[2] - AAA;
9020 toY = currentMoveString[3] - ONE;
9021 promoChar = currentMoveString[4];
9026 if (appData.debugMode)
9027 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
9028 fromX = moveType == WhiteDrop ?
9029 (int) CharToPiece(ToUpper(currentMoveString[0])) :
9030 (int) CharToPiece(ToLower(currentMoveString[0]));
9032 toX = currentMoveString[2] - AAA;
9033 toY = currentMoveString[3] - ONE;
9039 case GameUnfinished:
9040 if (appData.debugMode)
9041 fprintf(debugFP, "Parsed game end: %s\n", yy_text);
9042 p = strchr(yy_text, '{');
9043 if (p == NULL) p = strchr(yy_text, '(');
9046 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
9048 q = strchr(p, *p == '{' ? '}' : ')');
9049 if (q != NULL) *q = NULLCHAR;
9052 GameEnds(moveType, p, GE_FILE);
9054 if (cmailMsgLoaded) {
9056 flipView = WhiteOnMove(currentMove);
9057 if (moveType == GameUnfinished) flipView = !flipView;
9058 if (appData.debugMode)
9059 fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
9063 case (ChessMove) 0: /* end of file */
9064 if (appData.debugMode)
9065 fprintf(debugFP, "Parser hit end of file\n");
9066 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9072 if (WhiteOnMove(currentMove)) {
9073 GameEnds(BlackWins, "Black mates", GE_FILE);
9075 GameEnds(WhiteWins, "White mates", GE_FILE);
9079 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
9086 if (lastLoadGameStart == GNUChessGame) {
9087 /* GNUChessGames have numbers, but they aren't move numbers */
9088 if (appData.debugMode)
9089 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
9090 yy_text, (int) moveType);
9091 return LoadGameOneMove((ChessMove)0); /* tail recursion */
9093 /* else fall thru */
9098 /* Reached start of next game in file */
9099 if (appData.debugMode)
9100 fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
9101 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9107 if (WhiteOnMove(currentMove)) {
9108 GameEnds(BlackWins, "Black mates", GE_FILE);
9110 GameEnds(WhiteWins, "White mates", GE_FILE);
9114 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
9120 case PositionDiagram: /* should not happen; ignore */
9121 case ElapsedTime: /* ignore */
9122 case NAG: /* ignore */
9123 if (appData.debugMode)
9124 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
9125 yy_text, (int) moveType);
9126 return LoadGameOneMove((ChessMove)0); /* tail recursion */
9129 if (appData.testLegality) {
9130 if (appData.debugMode)
9131 fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
9132 sprintf(move, _("Illegal move: %d.%s%s"),
9133 (forwardMostMove / 2) + 1,
9134 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9135 DisplayError(move, 0);
9138 if (appData.debugMode)
9139 fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
9140 yy_text, currentMoveString);
9141 fromX = currentMoveString[0] - AAA;
9142 fromY = currentMoveString[1] - ONE;
9143 toX = currentMoveString[2] - AAA;
9144 toY = currentMoveString[3] - ONE;
9145 promoChar = currentMoveString[4];
9150 if (appData.debugMode)
9151 fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
9152 sprintf(move, _("Ambiguous move: %d.%s%s"),
9153 (forwardMostMove / 2) + 1,
9154 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9155 DisplayError(move, 0);
9160 case ImpossibleMove:
9161 if (appData.debugMode)
9162 fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
9163 sprintf(move, _("Illegal move: %d.%s%s"),
9164 (forwardMostMove / 2) + 1,
9165 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9166 DisplayError(move, 0);
9172 if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
9173 DrawPosition(FALSE, boards[currentMove]);
9174 DisplayBothClocks();
9175 if (!appData.matchMode) // [HGM] PV info: routine tests if empty
9176 DisplayComment(currentMove - 1, commentList[currentMove]);
9178 (void) StopLoadGameTimer();
9180 cmailOldMove = forwardMostMove;
9183 /* currentMoveString is set as a side-effect of yylex */
9184 strcat(currentMoveString, "\n");
9185 strcpy(moveList[forwardMostMove], currentMoveString);
9187 thinkOutput[0] = NULLCHAR;
9188 MakeMove(fromX, fromY, toX, toY, promoChar);
9189 currentMove = forwardMostMove;
9194 /* Load the nth game from the given file */
9196 LoadGameFromFile(filename, n, title, useList)
9200 /*Boolean*/ int useList;
9205 if (strcmp(filename, "-") == 0) {
9209 f = fopen(filename, "rb");
9211 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9212 DisplayError(buf, errno);
9216 if (fseek(f, 0, 0) == -1) {
9217 /* f is not seekable; probably a pipe */
9220 if (useList && n == 0) {
9221 int error = GameListBuild(f);
9223 DisplayError(_("Cannot build game list"), error);
9224 } else if (!ListEmpty(&gameList) &&
9225 ((ListGame *) gameList.tailPred)->number > 1) {
9226 GameListPopUp(f, title);
9233 return LoadGame(f, n, title, FALSE);
9238 MakeRegisteredMove()
9240 int fromX, fromY, toX, toY;
9242 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
9243 switch (cmailMoveType[lastLoadGameNumber - 1]) {
9246 if (appData.debugMode)
9247 fprintf(debugFP, "Restoring %s for game %d\n",
9248 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
9250 thinkOutput[0] = NULLCHAR;
9251 strcpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1]);
9252 fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
9253 fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
9254 toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
9255 toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
9256 promoChar = cmailMove[lastLoadGameNumber - 1][4];
9257 MakeMove(fromX, fromY, toX, toY, promoChar);
9258 ShowMove(fromX, fromY, toX, toY);
9260 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9267 if (WhiteOnMove(currentMove)) {
9268 GameEnds(BlackWins, "Black mates", GE_PLAYER);
9270 GameEnds(WhiteWins, "White mates", GE_PLAYER);
9275 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
9282 if (WhiteOnMove(currentMove)) {
9283 GameEnds(BlackWins, "White resigns", GE_PLAYER);
9285 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
9290 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
9301 /* Wrapper around LoadGame for use when a Cmail message is loaded */
9303 CmailLoadGame(f, gameNumber, title, useList)
9311 if (gameNumber > nCmailGames) {
9312 DisplayError(_("No more games in this message"), 0);
9315 if (f == lastLoadGameFP) {
9316 int offset = gameNumber - lastLoadGameNumber;
9318 cmailMsg[0] = NULLCHAR;
9319 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
9320 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
9321 nCmailMovesRegistered--;
9323 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
9324 if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
9325 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
9328 if (! RegisterMove()) return FALSE;
9332 retVal = LoadGame(f, gameNumber, title, useList);
9334 /* Make move registered during previous look at this game, if any */
9335 MakeRegisteredMove();
9337 if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
9338 commentList[currentMove]
9339 = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
9340 DisplayComment(currentMove - 1, commentList[currentMove]);
9346 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
9351 int gameNumber = lastLoadGameNumber + offset;
9352 if (lastLoadGameFP == NULL) {
9353 DisplayError(_("No game has been loaded yet"), 0);
9356 if (gameNumber <= 0) {
9357 DisplayError(_("Can't back up any further"), 0);
9360 if (cmailMsgLoaded) {
9361 return CmailLoadGame(lastLoadGameFP, gameNumber,
9362 lastLoadGameTitle, lastLoadGameUseList);
9364 return LoadGame(lastLoadGameFP, gameNumber,
9365 lastLoadGameTitle, lastLoadGameUseList);
9371 /* Load the nth game from open file f */
9373 LoadGame(f, gameNumber, title, useList)
9381 int gn = gameNumber;
9382 ListGame *lg = NULL;
9385 GameMode oldGameMode;
9386 VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
9388 if (appData.debugMode)
9389 fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
9391 if (gameMode == Training )
9392 SetTrainingModeOff();
9394 oldGameMode = gameMode;
9395 if (gameMode != BeginningOfGame) {
9400 if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
9401 fclose(lastLoadGameFP);
9405 lg = (ListGame *) ListElem(&gameList, gameNumber-1);
9408 fseek(f, lg->offset, 0);
9409 GameListHighlight(gameNumber);
9413 DisplayError(_("Game number out of range"), 0);
9418 if (fseek(f, 0, 0) == -1) {
9419 if (f == lastLoadGameFP ?
9420 gameNumber == lastLoadGameNumber + 1 :
9424 DisplayError(_("Can't seek on game file"), 0);
9430 lastLoadGameNumber = gameNumber;
9431 strcpy(lastLoadGameTitle, title);
9432 lastLoadGameUseList = useList;
9436 if (lg && lg->gameInfo.white && lg->gameInfo.black) {
9437 snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
9438 lg->gameInfo.black);
9440 } else if (*title != NULLCHAR) {
9441 if (gameNumber > 1) {
9442 sprintf(buf, "%s %d", title, gameNumber);
9445 DisplayTitle(title);
9449 if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
9450 gameMode = PlayFromGameFile;
9454 currentMove = forwardMostMove = backwardMostMove = 0;
9455 CopyBoard(boards[0], initialPosition);
9459 * Skip the first gn-1 games in the file.
9460 * Also skip over anything that precedes an identifiable
9461 * start of game marker, to avoid being confused by
9462 * garbage at the start of the file. Currently
9463 * recognized start of game markers are the move number "1",
9464 * the pattern "gnuchess .* game", the pattern
9465 * "^[#;%] [^ ]* game file", and a PGN tag block.
9466 * A game that starts with one of the latter two patterns
9467 * will also have a move number 1, possibly
9468 * following a position diagram.
9469 * 5-4-02: Let's try being more lenient and allowing a game to
9470 * start with an unnumbered move. Does that break anything?
9472 cm = lastLoadGameStart = (ChessMove) 0;
9474 yyboardindex = forwardMostMove;
9475 cm = (ChessMove) yylex();
9478 if (cmailMsgLoaded) {
9479 nCmailGames = CMAIL_MAX_GAMES - gn;
9482 DisplayError(_("Game not found in file"), 0);
9489 lastLoadGameStart = cm;
9493 switch (lastLoadGameStart) {
9500 gn--; /* count this game */
9501 lastLoadGameStart = cm;
9510 switch (lastLoadGameStart) {
9515 gn--; /* count this game */
9516 lastLoadGameStart = cm;
9519 lastLoadGameStart = cm; /* game counted already */
9527 yyboardindex = forwardMostMove;
9528 cm = (ChessMove) yylex();
9529 } while (cm == PGNTag || cm == Comment);
9536 if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
9537 if ( cmailResult[CMAIL_MAX_GAMES - gn - 1]
9538 != CMAIL_OLD_RESULT) {
9540 cmailResult[ CMAIL_MAX_GAMES
9541 - gn - 1] = CMAIL_OLD_RESULT;
9547 /* Only a NormalMove can be at the start of a game
9548 * without a position diagram. */
9549 if (lastLoadGameStart == (ChessMove) 0) {
9551 lastLoadGameStart = MoveNumberOne;
9560 if (appData.debugMode)
9561 fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
9563 if (cm == XBoardGame) {
9564 /* Skip any header junk before position diagram and/or move 1 */
9566 yyboardindex = forwardMostMove;
9567 cm = (ChessMove) yylex();
9569 if (cm == (ChessMove) 0 ||
9570 cm == GNUChessGame || cm == XBoardGame) {
9571 /* Empty game; pretend end-of-file and handle later */
9576 if (cm == MoveNumberOne || cm == PositionDiagram ||
9577 cm == PGNTag || cm == Comment)
9580 } else if (cm == GNUChessGame) {
9581 if (gameInfo.event != NULL) {
9582 free(gameInfo.event);
9584 gameInfo.event = StrSave(yy_text);
9587 startedFromSetupPosition = FALSE;
9588 while (cm == PGNTag) {
9589 if (appData.debugMode)
9590 fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
9591 err = ParsePGNTag(yy_text, &gameInfo);
9592 if (!err) numPGNTags++;
9594 /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
9595 if(gameInfo.variant != oldVariant) {
9596 startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
9598 oldVariant = gameInfo.variant;
9599 if (appData.debugMode)
9600 fprintf(debugFP, "New variant %d\n", (int) oldVariant);
9604 if (gameInfo.fen != NULL) {
9605 Board initial_position;
9606 startedFromSetupPosition = TRUE;
9607 if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
9609 DisplayError(_("Bad FEN position in file"), 0);
9612 CopyBoard(boards[0], initial_position);
9613 if (blackPlaysFirst) {
9614 currentMove = forwardMostMove = backwardMostMove = 1;
9615 CopyBoard(boards[1], initial_position);
9616 strcpy(moveList[0], "");
9617 strcpy(parseList[0], "");
9618 timeRemaining[0][1] = whiteTimeRemaining;
9619 timeRemaining[1][1] = blackTimeRemaining;
9620 if (commentList[0] != NULL) {
9621 commentList[1] = commentList[0];
9622 commentList[0] = NULL;
9625 currentMove = forwardMostMove = backwardMostMove = 0;
9627 /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
9629 initialRulePlies = FENrulePlies;
9630 for( i=0; i< nrCastlingRights; i++ )
9631 initialRights[i] = initial_position[CASTLING][i];
9633 yyboardindex = forwardMostMove;
9635 gameInfo.fen = NULL;
9638 yyboardindex = forwardMostMove;
9639 cm = (ChessMove) yylex();
9641 /* Handle comments interspersed among the tags */
9642 while (cm == Comment) {
9644 if (appData.debugMode)
9645 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9647 AppendComment(currentMove, p, FALSE);
9648 yyboardindex = forwardMostMove;
9649 cm = (ChessMove) yylex();
9653 /* don't rely on existence of Event tag since if game was
9654 * pasted from clipboard the Event tag may not exist
9656 if (numPGNTags > 0){
9658 if (gameInfo.variant == VariantNormal) {
9659 gameInfo.variant = StringToVariant(gameInfo.event);
9662 if( appData.autoDisplayTags ) {
9663 tags = PGNTags(&gameInfo);
9664 TagsPopUp(tags, CmailMsg());
9669 /* Make something up, but don't display it now */
9674 if (cm == PositionDiagram) {
9677 Board initial_position;
9679 if (appData.debugMode)
9680 fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
9682 if (!startedFromSetupPosition) {
9684 for (i = BOARD_HEIGHT - 1; i >= 0; i--)
9685 for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
9695 initial_position[i][j++] = CharToPiece(*p);
9698 while (*p == ' ' || *p == '\t' ||
9699 *p == '\n' || *p == '\r') p++;
9701 if (strncmp(p, "black", strlen("black"))==0)
9702 blackPlaysFirst = TRUE;
9704 blackPlaysFirst = FALSE;
9705 startedFromSetupPosition = TRUE;
9707 CopyBoard(boards[0], initial_position);
9708 if (blackPlaysFirst) {
9709 currentMove = forwardMostMove = backwardMostMove = 1;
9710 CopyBoard(boards[1], initial_position);
9711 strcpy(moveList[0], "");
9712 strcpy(parseList[0], "");
9713 timeRemaining[0][1] = whiteTimeRemaining;
9714 timeRemaining[1][1] = blackTimeRemaining;
9715 if (commentList[0] != NULL) {
9716 commentList[1] = commentList[0];
9717 commentList[0] = NULL;
9720 currentMove = forwardMostMove = backwardMostMove = 0;
9723 yyboardindex = forwardMostMove;
9724 cm = (ChessMove) yylex();
9727 if (first.pr == NoProc) {
9728 StartChessProgram(&first);
9730 InitChessProgram(&first, FALSE);
9731 SendToProgram("force\n", &first);
9732 if (startedFromSetupPosition) {
9733 SendBoard(&first, forwardMostMove);
9734 if (appData.debugMode) {
9735 fprintf(debugFP, "Load Game\n");
9737 DisplayBothClocks();
9740 /* [HGM] server: flag to write setup moves in broadcast file as one */
9741 loadFlag = appData.suppressLoadMoves;
9743 while (cm == Comment) {
9745 if (appData.debugMode)
9746 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9748 AppendComment(currentMove, p, FALSE);
9749 yyboardindex = forwardMostMove;
9750 cm = (ChessMove) yylex();
9753 if ((cm == (ChessMove) 0 && lastLoadGameStart != (ChessMove) 0) ||
9754 cm == WhiteWins || cm == BlackWins ||
9755 cm == GameIsDrawn || cm == GameUnfinished) {
9756 DisplayMessage("", _("No moves in game"));
9757 if (cmailMsgLoaded) {
9758 if (appData.debugMode)
9759 fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
9763 DrawPosition(FALSE, boards[currentMove]);
9764 DisplayBothClocks();
9765 gameMode = EditGame;
9772 // [HGM] PV info: routine tests if comment empty
9773 if (!matchMode && (pausing || appData.timeDelay != 0)) {
9774 DisplayComment(currentMove - 1, commentList[currentMove]);
9776 if (!matchMode && appData.timeDelay != 0)
9777 DrawPosition(FALSE, boards[currentMove]);
9779 if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
9780 programStats.ok_to_send = 1;
9783 /* if the first token after the PGN tags is a move
9784 * and not move number 1, retrieve it from the parser
9786 if (cm != MoveNumberOne)
9787 LoadGameOneMove(cm);
9789 /* load the remaining moves from the file */
9790 while (LoadGameOneMove((ChessMove)0)) {
9791 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
9792 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
9795 /* rewind to the start of the game */
9796 currentMove = backwardMostMove;
9798 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
9800 if (oldGameMode == AnalyzeFile ||
9801 oldGameMode == AnalyzeMode) {
9805 if (matchMode || appData.timeDelay == 0) {
9807 gameMode = EditGame;
9809 } else if (appData.timeDelay > 0) {
9813 if (appData.debugMode)
9814 fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
9816 loadFlag = 0; /* [HGM] true game starts */
9820 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
9822 ReloadPosition(offset)
9825 int positionNumber = lastLoadPositionNumber + offset;
9826 if (lastLoadPositionFP == NULL) {
9827 DisplayError(_("No position has been loaded yet"), 0);
9830 if (positionNumber <= 0) {
9831 DisplayError(_("Can't back up any further"), 0);
9834 return LoadPosition(lastLoadPositionFP, positionNumber,
9835 lastLoadPositionTitle);
9838 /* Load the nth position from the given file */
9840 LoadPositionFromFile(filename, n, title)
9848 if (strcmp(filename, "-") == 0) {
9849 return LoadPosition(stdin, n, "stdin");
9851 f = fopen(filename, "rb");
9853 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9854 DisplayError(buf, errno);
9857 return LoadPosition(f, n, title);
9862 /* Load the nth position from the given open file, and close it */
9864 LoadPosition(f, positionNumber, title)
9869 char *p, line[MSG_SIZ];
9870 Board initial_position;
9871 int i, j, fenMode, pn;
9873 if (gameMode == Training )
9874 SetTrainingModeOff();
9876 if (gameMode != BeginningOfGame) {
9879 if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
9880 fclose(lastLoadPositionFP);
9882 if (positionNumber == 0) positionNumber = 1;
9883 lastLoadPositionFP = f;
9884 lastLoadPositionNumber = positionNumber;
9885 strcpy(lastLoadPositionTitle, title);
9886 if (first.pr == NoProc) {
9887 StartChessProgram(&first);
9888 InitChessProgram(&first, FALSE);
9890 pn = positionNumber;
9891 if (positionNumber < 0) {
9892 /* Negative position number means to seek to that byte offset */
9893 if (fseek(f, -positionNumber, 0) == -1) {
9894 DisplayError(_("Can't seek on position file"), 0);
9899 if (fseek(f, 0, 0) == -1) {
9900 if (f == lastLoadPositionFP ?
9901 positionNumber == lastLoadPositionNumber + 1 :
9902 positionNumber == 1) {
9905 DisplayError(_("Can't seek on position file"), 0);
9910 /* See if this file is FEN or old-style xboard */
9911 if (fgets(line, MSG_SIZ, f) == NULL) {
9912 DisplayError(_("Position not found in file"), 0);
9915 // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
9916 fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
9919 if (fenMode || line[0] == '#') pn--;
9921 /* skip positions before number pn */
9922 if (fgets(line, MSG_SIZ, f) == NULL) {
9924 DisplayError(_("Position not found in file"), 0);
9927 if (fenMode || line[0] == '#') pn--;
9932 if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
9933 DisplayError(_("Bad FEN position in file"), 0);
9937 (void) fgets(line, MSG_SIZ, f);
9938 (void) fgets(line, MSG_SIZ, f);
9940 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
9941 (void) fgets(line, MSG_SIZ, f);
9942 for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
9945 initial_position[i][j++] = CharToPiece(*p);
9949 blackPlaysFirst = FALSE;
9951 (void) fgets(line, MSG_SIZ, f);
9952 if (strncmp(line, "black", strlen("black"))==0)
9953 blackPlaysFirst = TRUE;
9956 startedFromSetupPosition = TRUE;
9958 SendToProgram("force\n", &first);
9959 CopyBoard(boards[0], initial_position);
9960 if (blackPlaysFirst) {
9961 currentMove = forwardMostMove = backwardMostMove = 1;
9962 strcpy(moveList[0], "");
9963 strcpy(parseList[0], "");
9964 CopyBoard(boards[1], initial_position);
9965 DisplayMessage("", _("Black to play"));
9967 currentMove = forwardMostMove = backwardMostMove = 0;
9968 DisplayMessage("", _("White to play"));
9970 initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
9971 SendBoard(&first, forwardMostMove);
9972 if (appData.debugMode) {
9974 for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
9975 for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
9976 fprintf(debugFP, "Load Position\n");
9979 if (positionNumber > 1) {
9980 sprintf(line, "%s %d", title, positionNumber);
9983 DisplayTitle(title);
9985 gameMode = EditGame;
9988 timeRemaining[0][1] = whiteTimeRemaining;
9989 timeRemaining[1][1] = blackTimeRemaining;
9990 DrawPosition(FALSE, boards[currentMove]);
9997 CopyPlayerNameIntoFileName(dest, src)
10000 while (*src != NULLCHAR && *src != ',') {
10005 *(*dest)++ = *src++;
10010 char *DefaultFileName(ext)
10013 static char def[MSG_SIZ];
10016 if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
10018 CopyPlayerNameIntoFileName(&p, gameInfo.white);
10020 CopyPlayerNameIntoFileName(&p, gameInfo.black);
10029 /* Save the current game to the given file */
10031 SaveGameToFile(filename, append)
10038 if (strcmp(filename, "-") == 0) {
10039 return SaveGame(stdout, 0, NULL);
10041 f = fopen(filename, append ? "a" : "w");
10043 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10044 DisplayError(buf, errno);
10047 return SaveGame(f, 0, NULL);
10056 static char buf[MSG_SIZ];
10059 p = strchr(str, ' ');
10060 if (p == NULL) return str;
10061 strncpy(buf, str, p - str);
10062 buf[p - str] = NULLCHAR;
10066 #define PGN_MAX_LINE 75
10068 #define PGN_SIDE_WHITE 0
10069 #define PGN_SIDE_BLACK 1
10072 static int FindFirstMoveOutOfBook( int side )
10076 if( backwardMostMove == 0 && ! startedFromSetupPosition) {
10077 int index = backwardMostMove;
10078 int has_book_hit = 0;
10080 if( (index % 2) != side ) {
10084 while( index < forwardMostMove ) {
10085 /* Check to see if engine is in book */
10086 int depth = pvInfoList[index].depth;
10087 int score = pvInfoList[index].score;
10093 else if( score == 0 && depth == 63 ) {
10094 in_book = 1; /* Zappa */
10096 else if( score == 2 && depth == 99 ) {
10097 in_book = 1; /* Abrok */
10100 has_book_hit += in_book;
10116 void GetOutOfBookInfo( char * buf )
10120 int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10122 oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
10123 oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
10127 if( oob[0] >= 0 || oob[1] >= 0 ) {
10128 for( i=0; i<2; i++ ) {
10132 if( i > 0 && oob[0] >= 0 ) {
10133 strcat( buf, " " );
10136 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
10137 sprintf( buf+strlen(buf), "%s%.2f",
10138 pvInfoList[idx].score >= 0 ? "+" : "",
10139 pvInfoList[idx].score / 100.0 );
10145 /* Save game in PGN style and close the file */
10150 int i, offset, linelen, newblock;
10154 int movelen, numlen, blank;
10155 char move_buffer[100]; /* [AS] Buffer for move+PV info */
10157 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10159 tm = time((time_t *) NULL);
10161 PrintPGNTags(f, &gameInfo);
10163 if (backwardMostMove > 0 || startedFromSetupPosition) {
10164 char *fen = PositionToFEN(backwardMostMove, NULL);
10165 fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
10166 fprintf(f, "\n{--------------\n");
10167 PrintPosition(f, backwardMostMove);
10168 fprintf(f, "--------------}\n");
10172 /* [AS] Out of book annotation */
10173 if( appData.saveOutOfBookInfo ) {
10176 GetOutOfBookInfo( buf );
10178 if( buf[0] != '\0' ) {
10179 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
10186 i = backwardMostMove;
10190 while (i < forwardMostMove) {
10191 /* Print comments preceding this move */
10192 if (commentList[i] != NULL) {
10193 if (linelen > 0) fprintf(f, "\n");
10194 fprintf(f, "%s", commentList[i]);
10199 /* Format move number */
10200 if ((i % 2) == 0) {
10201 sprintf(numtext, "%d.", (i - offset)/2 + 1);
10204 sprintf(numtext, "%d...", (i - offset)/2 + 1);
10206 numtext[0] = NULLCHAR;
10209 numlen = strlen(numtext);
10212 /* Print move number */
10213 blank = linelen > 0 && numlen > 0;
10214 if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
10223 fprintf(f, "%s", numtext);
10227 strcpy(move_buffer, SavePart(parseList[i])); // [HGM] pgn: print move via buffer, so it can be edited
10228 movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
10231 blank = linelen > 0 && movelen > 0;
10232 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
10241 fprintf(f, "%s", move_buffer);
10242 linelen += movelen;
10244 /* [AS] Add PV info if present */
10245 if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
10246 /* [HGM] add time */
10247 char buf[MSG_SIZ]; int seconds;
10249 seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
10251 if( seconds <= 0) buf[0] = 0; else
10252 if( seconds < 30 ) sprintf(buf, " %3.1f%c", seconds/10., 0); else {
10253 seconds = (seconds + 4)/10; // round to full seconds
10254 if( seconds < 60 ) sprintf(buf, " %d%c", seconds, 0); else
10255 sprintf(buf, " %d:%02d%c", seconds/60, seconds%60, 0);
10258 sprintf( move_buffer, "{%s%.2f/%d%s}",
10259 pvInfoList[i].score >= 0 ? "+" : "",
10260 pvInfoList[i].score / 100.0,
10261 pvInfoList[i].depth,
10264 movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
10266 /* Print score/depth */
10267 blank = linelen > 0 && movelen > 0;
10268 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
10277 fprintf(f, "%s", move_buffer);
10278 linelen += movelen;
10284 /* Start a new line */
10285 if (linelen > 0) fprintf(f, "\n");
10287 /* Print comments after last move */
10288 if (commentList[i] != NULL) {
10289 fprintf(f, "%s\n", commentList[i]);
10293 if (gameInfo.resultDetails != NULL &&
10294 gameInfo.resultDetails[0] != NULLCHAR) {
10295 fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
10296 PGNResult(gameInfo.result));
10298 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
10302 lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
10306 /* Save game in old style and close the file */
10308 SaveGameOldStyle(f)
10314 tm = time((time_t *) NULL);
10316 fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
10319 if (backwardMostMove > 0 || startedFromSetupPosition) {
10320 fprintf(f, "\n[--------------\n");
10321 PrintPosition(f, backwardMostMove);
10322 fprintf(f, "--------------]\n");
10327 i = backwardMostMove;
10328 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10330 while (i < forwardMostMove) {
10331 if (commentList[i] != NULL) {
10332 fprintf(f, "[%s]\n", commentList[i]);
10335 if ((i % 2) == 1) {
10336 fprintf(f, "%d. ... %s\n", (i - offset)/2 + 1, parseList[i]);
10339 fprintf(f, "%d. %s ", (i - offset)/2 + 1, parseList[i]);
10341 if (commentList[i] != NULL) {
10345 if (i >= forwardMostMove) {
10349 fprintf(f, "%s\n", parseList[i]);
10354 if (commentList[i] != NULL) {
10355 fprintf(f, "[%s]\n", commentList[i]);
10358 /* This isn't really the old style, but it's close enough */
10359 if (gameInfo.resultDetails != NULL &&
10360 gameInfo.resultDetails[0] != NULLCHAR) {
10361 fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
10362 gameInfo.resultDetails);
10364 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
10371 /* Save the current game to open file f and close the file */
10373 SaveGame(f, dummy, dummy2)
10378 if (gameMode == EditPosition) EditPositionDone(TRUE);
10379 lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
10380 if (appData.oldSaveStyle)
10381 return SaveGameOldStyle(f);
10383 return SaveGamePGN(f);
10386 /* Save the current position to the given file */
10388 SavePositionToFile(filename)
10394 if (strcmp(filename, "-") == 0) {
10395 return SavePosition(stdout, 0, NULL);
10397 f = fopen(filename, "a");
10399 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10400 DisplayError(buf, errno);
10403 SavePosition(f, 0, NULL);
10409 /* Save the current position to the given open file and close the file */
10411 SavePosition(f, dummy, dummy2)
10419 if (gameMode == EditPosition) EditPositionDone(TRUE);
10420 if (appData.oldSaveStyle) {
10421 tm = time((time_t *) NULL);
10423 fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
10425 fprintf(f, "[--------------\n");
10426 PrintPosition(f, currentMove);
10427 fprintf(f, "--------------]\n");
10429 fen = PositionToFEN(currentMove, NULL);
10430 fprintf(f, "%s\n", fen);
10438 ReloadCmailMsgEvent(unregister)
10442 static char *inFilename = NULL;
10443 static char *outFilename;
10445 struct stat inbuf, outbuf;
10448 /* Any registered moves are unregistered if unregister is set, */
10449 /* i.e. invoked by the signal handler */
10451 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
10452 cmailMoveRegistered[i] = FALSE;
10453 if (cmailCommentList[i] != NULL) {
10454 free(cmailCommentList[i]);
10455 cmailCommentList[i] = NULL;
10458 nCmailMovesRegistered = 0;
10461 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
10462 cmailResult[i] = CMAIL_NOT_RESULT;
10466 if (inFilename == NULL) {
10467 /* Because the filenames are static they only get malloced once */
10468 /* and they never get freed */
10469 inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
10470 sprintf(inFilename, "%s.game.in", appData.cmailGameName);
10472 outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
10473 sprintf(outFilename, "%s.out", appData.cmailGameName);
10476 status = stat(outFilename, &outbuf);
10478 cmailMailedMove = FALSE;
10480 status = stat(inFilename, &inbuf);
10481 cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
10484 /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
10485 counts the games, notes how each one terminated, etc.
10487 It would be nice to remove this kludge and instead gather all
10488 the information while building the game list. (And to keep it
10489 in the game list nodes instead of having a bunch of fixed-size
10490 parallel arrays.) Note this will require getting each game's
10491 termination from the PGN tags, as the game list builder does
10492 not process the game moves. --mann
10494 cmailMsgLoaded = TRUE;
10495 LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
10497 /* Load first game in the file or popup game menu */
10498 LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
10500 #endif /* !WIN32 */
10508 char string[MSG_SIZ];
10510 if ( cmailMailedMove
10511 || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
10512 return TRUE; /* Allow free viewing */
10515 /* Unregister move to ensure that we don't leave RegisterMove */
10516 /* with the move registered when the conditions for registering no */
10518 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
10519 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
10520 nCmailMovesRegistered --;
10522 if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
10524 free(cmailCommentList[lastLoadGameNumber - 1]);
10525 cmailCommentList[lastLoadGameNumber - 1] = NULL;
10529 if (cmailOldMove == -1) {
10530 DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
10534 if (currentMove > cmailOldMove + 1) {
10535 DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
10539 if (currentMove < cmailOldMove) {
10540 DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
10544 if (forwardMostMove > currentMove) {
10545 /* Silently truncate extra moves */
10549 if ( (currentMove == cmailOldMove + 1)
10550 || ( (currentMove == cmailOldMove)
10551 && ( (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
10552 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
10553 if (gameInfo.result != GameUnfinished) {
10554 cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
10557 if (commentList[currentMove] != NULL) {
10558 cmailCommentList[lastLoadGameNumber - 1]
10559 = StrSave(commentList[currentMove]);
10561 strcpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1]);
10563 if (appData.debugMode)
10564 fprintf(debugFP, "Saving %s for game %d\n",
10565 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
10568 "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
10570 f = fopen(string, "w");
10571 if (appData.oldSaveStyle) {
10572 SaveGameOldStyle(f); /* also closes the file */
10574 sprintf(string, "%s.pos.out", appData.cmailGameName);
10575 f = fopen(string, "w");
10576 SavePosition(f, 0, NULL); /* also closes the file */
10578 fprintf(f, "{--------------\n");
10579 PrintPosition(f, currentMove);
10580 fprintf(f, "--------------}\n\n");
10582 SaveGame(f, 0, NULL); /* also closes the file*/
10585 cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
10586 nCmailMovesRegistered ++;
10587 } else if (nCmailGames == 1) {
10588 DisplayError(_("You have not made a move yet"), 0);
10599 static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
10600 FILE *commandOutput;
10601 char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
10602 int nBytes = 0; /* Suppress warnings on uninitialized variables */
10608 if (! cmailMsgLoaded) {
10609 DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
10613 if (nCmailGames == nCmailResults) {
10614 DisplayError(_("No unfinished games"), 0);
10618 #if CMAIL_PROHIBIT_REMAIL
10619 if (cmailMailedMove) {
10620 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);
10621 DisplayError(msg, 0);
10626 if (! (cmailMailedMove || RegisterMove())) return;
10628 if ( cmailMailedMove
10629 || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
10630 sprintf(string, partCommandString,
10631 appData.debugMode ? " -v" : "", appData.cmailGameName);
10632 commandOutput = popen(string, "r");
10634 if (commandOutput == NULL) {
10635 DisplayError(_("Failed to invoke cmail"), 0);
10637 for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
10638 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
10640 if (nBuffers > 1) {
10641 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
10642 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
10643 nBytes = MSG_SIZ - 1;
10645 (void) memcpy(msg, buffer, nBytes);
10647 *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
10649 if(StrStr(msg, "Mailed cmail message to ") != NULL) {
10650 cmailMailedMove = TRUE; /* Prevent >1 moves */
10653 for (i = 0; i < nCmailGames; i ++) {
10654 if (cmailResult[i] == CMAIL_NOT_RESULT) {
10659 && ( (arcDir = (char *) getenv("CMAIL_ARCDIR"))
10661 sprintf(buffer, "%s/%s.%s.archive",
10663 appData.cmailGameName,
10665 LoadGameFromFile(buffer, 1, buffer, FALSE);
10666 cmailMsgLoaded = FALSE;
10670 DisplayInformation(msg);
10671 pclose(commandOutput);
10674 if ((*cmailMsg) != '\0') {
10675 DisplayInformation(cmailMsg);
10680 #endif /* !WIN32 */
10689 int prependComma = 0;
10691 char string[MSG_SIZ]; /* Space for game-list */
10694 if (!cmailMsgLoaded) return "";
10696 if (cmailMailedMove) {
10697 sprintf(cmailMsg, _("Waiting for reply from opponent\n"));
10699 /* Create a list of games left */
10700 sprintf(string, "[");
10701 for (i = 0; i < nCmailGames; i ++) {
10702 if (! ( cmailMoveRegistered[i]
10703 || (cmailResult[i] == CMAIL_OLD_RESULT))) {
10704 if (prependComma) {
10705 sprintf(number, ",%d", i + 1);
10707 sprintf(number, "%d", i + 1);
10711 strcat(string, number);
10714 strcat(string, "]");
10716 if (nCmailMovesRegistered + nCmailResults == 0) {
10717 switch (nCmailGames) {
10720 _("Still need to make move for game\n"));
10725 _("Still need to make moves for both games\n"));
10730 _("Still need to make moves for all %d games\n"),
10735 switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
10738 _("Still need to make a move for game %s\n"),
10743 if (nCmailResults == nCmailGames) {
10744 sprintf(cmailMsg, _("No unfinished games\n"));
10746 sprintf(cmailMsg, _("Ready to send mail\n"));
10752 _("Still need to make moves for games %s\n"),
10764 if (gameMode == Training)
10765 SetTrainingModeOff();
10768 cmailMsgLoaded = FALSE;
10769 if (appData.icsActive) {
10770 SendToICS(ics_prefix);
10771 SendToICS("refresh\n");
10781 /* Give up on clean exit */
10785 /* Keep trying for clean exit */
10789 if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
10791 if (telnetISR != NULL) {
10792 RemoveInputSource(telnetISR);
10794 if (icsPR != NoProc) {
10795 DestroyChildProcess(icsPR, TRUE);
10798 /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
10799 GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
10801 /* [HGM] crash: the above GameEnds() is a dud if another one was running */
10802 /* make sure this other one finishes before killing it! */
10803 if(endingGame) { int count = 0;
10804 if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
10805 while(endingGame && count++ < 10) DoSleep(1);
10806 if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
10809 /* Kill off chess programs */
10810 if (first.pr != NoProc) {
10813 DoSleep( appData.delayBeforeQuit );
10814 SendToProgram("quit\n", &first);
10815 DoSleep( appData.delayAfterQuit );
10816 DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
10818 if (second.pr != NoProc) {
10819 DoSleep( appData.delayBeforeQuit );
10820 SendToProgram("quit\n", &second);
10821 DoSleep( appData.delayAfterQuit );
10822 DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
10824 if (first.isr != NULL) {
10825 RemoveInputSource(first.isr);
10827 if (second.isr != NULL) {
10828 RemoveInputSource(second.isr);
10831 ShutDownFrontEnd();
10838 if (appData.debugMode)
10839 fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
10843 if (gameMode == MachinePlaysWhite ||
10844 gameMode == MachinePlaysBlack) {
10847 DisplayBothClocks();
10849 if (gameMode == PlayFromGameFile) {
10850 if (appData.timeDelay >= 0)
10851 AutoPlayGameLoop();
10852 } else if (gameMode == IcsExamining && pauseExamInvalid) {
10853 Reset(FALSE, TRUE);
10854 SendToICS(ics_prefix);
10855 SendToICS("refresh\n");
10856 } else if (currentMove < forwardMostMove) {
10857 ForwardInner(forwardMostMove);
10859 pauseExamInvalid = FALSE;
10861 switch (gameMode) {
10865 pauseExamForwardMostMove = forwardMostMove;
10866 pauseExamInvalid = FALSE;
10869 case IcsPlayingWhite:
10870 case IcsPlayingBlack:
10874 case PlayFromGameFile:
10875 (void) StopLoadGameTimer();
10879 case BeginningOfGame:
10880 if (appData.icsActive) return;
10881 /* else fall through */
10882 case MachinePlaysWhite:
10883 case MachinePlaysBlack:
10884 case TwoMachinesPlay:
10885 if (forwardMostMove == 0)
10886 return; /* don't pause if no one has moved */
10887 if ((gameMode == MachinePlaysWhite &&
10888 !WhiteOnMove(forwardMostMove)) ||
10889 (gameMode == MachinePlaysBlack &&
10890 WhiteOnMove(forwardMostMove))) {
10903 char title[MSG_SIZ];
10905 if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
10906 strcpy(title, _("Edit comment"));
10908 sprintf(title, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
10909 WhiteOnMove(currentMove - 1) ? " " : ".. ",
10910 parseList[currentMove - 1]);
10913 EditCommentPopUp(currentMove, title, commentList[currentMove]);
10920 char *tags = PGNTags(&gameInfo);
10921 EditTagsPopUp(tags);
10928 if (appData.noChessProgram || gameMode == AnalyzeMode)
10931 if (gameMode != AnalyzeFile) {
10932 if (!appData.icsEngineAnalyze) {
10934 if (gameMode != EditGame) return;
10936 ResurrectChessProgram();
10937 SendToProgram("analyze\n", &first);
10938 first.analyzing = TRUE;
10939 /*first.maybeThinking = TRUE;*/
10940 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
10941 EngineOutputPopUp();
10943 if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
10948 StartAnalysisClock();
10949 GetTimeMark(&lastNodeCountTime);
10956 if (appData.noChessProgram || gameMode == AnalyzeFile)
10959 if (gameMode != AnalyzeMode) {
10961 if (gameMode != EditGame) return;
10962 ResurrectChessProgram();
10963 SendToProgram("analyze\n", &first);
10964 first.analyzing = TRUE;
10965 /*first.maybeThinking = TRUE;*/
10966 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
10967 EngineOutputPopUp();
10969 gameMode = AnalyzeFile;
10974 StartAnalysisClock();
10975 GetTimeMark(&lastNodeCountTime);
10980 MachineWhiteEvent()
10983 char *bookHit = NULL;
10985 if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
10989 if (gameMode == PlayFromGameFile ||
10990 gameMode == TwoMachinesPlay ||
10991 gameMode == Training ||
10992 gameMode == AnalyzeMode ||
10993 gameMode == EndOfGame)
10996 if (gameMode == EditPosition)
10997 EditPositionDone(TRUE);
10999 if (!WhiteOnMove(currentMove)) {
11000 DisplayError(_("It is not White's turn"), 0);
11004 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
11007 if (gameMode == EditGame || gameMode == AnalyzeMode ||
11008 gameMode == AnalyzeFile)
11011 ResurrectChessProgram(); /* in case it isn't running */
11012 if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
11013 gameMode = MachinePlaysWhite;
11016 gameMode = MachinePlaysWhite;
11020 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
11022 if (first.sendName) {
11023 sprintf(buf, "name %s\n", gameInfo.black);
11024 SendToProgram(buf, &first);
11026 if (first.sendTime) {
11027 if (first.useColors) {
11028 SendToProgram("black\n", &first); /*gnu kludge*/
11030 SendTimeRemaining(&first, TRUE);
11032 if (first.useColors) {
11033 SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
11035 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
11036 SetMachineThinkingEnables();
11037 first.maybeThinking = TRUE;
11041 if (appData.autoFlipView && !flipView) {
11042 flipView = !flipView;
11043 DrawPosition(FALSE, NULL);
11044 DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;
11047 if(bookHit) { // [HGM] book: simulate book reply
11048 static char bookMove[MSG_SIZ]; // a bit generous?
11050 programStats.nodes = programStats.depth = programStats.time =
11051 programStats.score = programStats.got_only_move = 0;
11052 sprintf(programStats.movelist, "%s (xbook)", bookHit);
11054 strcpy(bookMove, "move ");
11055 strcat(bookMove, bookHit);
11056 HandleMachineMove(bookMove, &first);
11061 MachineBlackEvent()
11064 char *bookHit = NULL;
11066 if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
11070 if (gameMode == PlayFromGameFile ||
11071 gameMode == TwoMachinesPlay ||
11072 gameMode == Training ||
11073 gameMode == AnalyzeMode ||
11074 gameMode == EndOfGame)
11077 if (gameMode == EditPosition)
11078 EditPositionDone(TRUE);
11080 if (WhiteOnMove(currentMove)) {
11081 DisplayError(_("It is not Black's turn"), 0);
11085 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
11088 if (gameMode == EditGame || gameMode == AnalyzeMode ||
11089 gameMode == AnalyzeFile)
11092 ResurrectChessProgram(); /* in case it isn't running */
11093 gameMode = MachinePlaysBlack;
11097 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
11099 if (first.sendName) {
11100 sprintf(buf, "name %s\n", gameInfo.white);
11101 SendToProgram(buf, &first);
11103 if (first.sendTime) {
11104 if (first.useColors) {
11105 SendToProgram("white\n", &first); /*gnu kludge*/
11107 SendTimeRemaining(&first, FALSE);
11109 if (first.useColors) {
11110 SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
11112 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
11113 SetMachineThinkingEnables();
11114 first.maybeThinking = TRUE;
11117 if (appData.autoFlipView && flipView) {
11118 flipView = !flipView;
11119 DrawPosition(FALSE, NULL);
11120 DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;
11122 if(bookHit) { // [HGM] book: simulate book reply
11123 static char bookMove[MSG_SIZ]; // a bit generous?
11125 programStats.nodes = programStats.depth = programStats.time =
11126 programStats.score = programStats.got_only_move = 0;
11127 sprintf(programStats.movelist, "%s (xbook)", bookHit);
11129 strcpy(bookMove, "move ");
11130 strcat(bookMove, bookHit);
11131 HandleMachineMove(bookMove, &first);
11137 DisplayTwoMachinesTitle()
11140 if (appData.matchGames > 0) {
11141 if (first.twoMachinesColor[0] == 'w') {
11142 sprintf(buf, "%s vs. %s (%d-%d-%d)",
11143 gameInfo.white, gameInfo.black,
11144 first.matchWins, second.matchWins,
11145 matchGame - 1 - (first.matchWins + second.matchWins));
11147 sprintf(buf, "%s vs. %s (%d-%d-%d)",
11148 gameInfo.white, gameInfo.black,
11149 second.matchWins, first.matchWins,
11150 matchGame - 1 - (first.matchWins + second.matchWins));
11153 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
11159 TwoMachinesEvent P((void))
11163 ChessProgramState *onmove;
11164 char *bookHit = NULL;
11166 if (appData.noChessProgram) return;
11168 switch (gameMode) {
11169 case TwoMachinesPlay:
11171 case MachinePlaysWhite:
11172 case MachinePlaysBlack:
11173 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
11174 DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
11178 case BeginningOfGame:
11179 case PlayFromGameFile:
11182 if (gameMode != EditGame) return;
11185 EditPositionDone(TRUE);
11196 // forwardMostMove = currentMove;
11197 TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
11198 ResurrectChessProgram(); /* in case first program isn't running */
11200 if (second.pr == NULL) {
11201 StartChessProgram(&second);
11202 if (second.protocolVersion == 1) {
11203 TwoMachinesEventIfReady();
11205 /* kludge: allow timeout for initial "feature" command */
11207 DisplayMessage("", _("Starting second chess program"));
11208 ScheduleDelayedEvent(TwoMachinesEventIfReady, FEATURE_TIMEOUT);
11212 DisplayMessage("", "");
11213 InitChessProgram(&second, FALSE);
11214 SendToProgram("force\n", &second);
11215 if (startedFromSetupPosition) {
11216 SendBoard(&second, backwardMostMove);
11217 if (appData.debugMode) {
11218 fprintf(debugFP, "Two Machines\n");
11221 for (i = backwardMostMove; i < forwardMostMove; i++) {
11222 SendMoveToProgram(i, &second);
11225 gameMode = TwoMachinesPlay;
11229 DisplayTwoMachinesTitle();
11231 if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
11237 SendToProgram(first.computerString, &first);
11238 if (first.sendName) {
11239 sprintf(buf, "name %s\n", second.tidy);
11240 SendToProgram(buf, &first);
11242 SendToProgram(second.computerString, &second);
11243 if (second.sendName) {
11244 sprintf(buf, "name %s\n", first.tidy);
11245 SendToProgram(buf, &second);
11249 if (!first.sendTime || !second.sendTime) {
11250 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11251 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11253 if (onmove->sendTime) {
11254 if (onmove->useColors) {
11255 SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
11257 SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
11259 if (onmove->useColors) {
11260 SendToProgram(onmove->twoMachinesColor, onmove);
11262 bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
11263 // SendToProgram("go\n", onmove);
11264 onmove->maybeThinking = TRUE;
11265 SetMachineThinkingEnables();
11269 if(bookHit) { // [HGM] book: simulate book reply
11270 static char bookMove[MSG_SIZ]; // a bit generous?
11272 programStats.nodes = programStats.depth = programStats.time =
11273 programStats.score = programStats.got_only_move = 0;
11274 sprintf(programStats.movelist, "%s (xbook)", bookHit);
11276 strcpy(bookMove, "move ");
11277 strcat(bookMove, bookHit);
11278 savedMessage = bookMove; // args for deferred call
11279 savedState = onmove;
11280 ScheduleDelayedEvent(DeferredBookMove, 1);
11287 if (gameMode == Training) {
11288 SetTrainingModeOff();
11289 gameMode = PlayFromGameFile;
11290 DisplayMessage("", _("Training mode off"));
11292 gameMode = Training;
11293 animateTraining = appData.animate;
11295 /* make sure we are not already at the end of the game */
11296 if (currentMove < forwardMostMove) {
11297 SetTrainingModeOn();
11298 DisplayMessage("", _("Training mode on"));
11300 gameMode = PlayFromGameFile;
11301 DisplayError(_("Already at end of game"), 0);
11310 if (!appData.icsActive) return;
11311 switch (gameMode) {
11312 case IcsPlayingWhite:
11313 case IcsPlayingBlack:
11316 case BeginningOfGame:
11324 EditPositionDone(TRUE);
11337 gameMode = IcsIdle;
11348 switch (gameMode) {
11350 SetTrainingModeOff();
11352 case MachinePlaysWhite:
11353 case MachinePlaysBlack:
11354 case BeginningOfGame:
11355 SendToProgram("force\n", &first);
11356 SetUserThinkingEnables();
11358 case PlayFromGameFile:
11359 (void) StopLoadGameTimer();
11360 if (gameFileFP != NULL) {
11365 EditPositionDone(TRUE);
11370 SendToProgram("force\n", &first);
11372 case TwoMachinesPlay:
11373 GameEnds((ChessMove) 0, NULL, GE_PLAYER);
11374 ResurrectChessProgram();
11375 SetUserThinkingEnables();
11378 ResurrectChessProgram();
11380 case IcsPlayingBlack:
11381 case IcsPlayingWhite:
11382 DisplayError(_("Warning: You are still playing a game"), 0);
11385 DisplayError(_("Warning: You are still observing a game"), 0);
11388 DisplayError(_("Warning: You are still examining a game"), 0);
11399 first.offeredDraw = second.offeredDraw = 0;
11401 if (gameMode == PlayFromGameFile) {
11402 whiteTimeRemaining = timeRemaining[0][currentMove];
11403 blackTimeRemaining = timeRemaining[1][currentMove];
11407 if (gameMode == MachinePlaysWhite ||
11408 gameMode == MachinePlaysBlack ||
11409 gameMode == TwoMachinesPlay ||
11410 gameMode == EndOfGame) {
11411 i = forwardMostMove;
11412 while (i > currentMove) {
11413 SendToProgram("undo\n", &first);
11416 whiteTimeRemaining = timeRemaining[0][currentMove];
11417 blackTimeRemaining = timeRemaining[1][currentMove];
11418 DisplayBothClocks();
11419 if (whiteFlag || blackFlag) {
11420 whiteFlag = blackFlag = 0;
11425 gameMode = EditGame;
11432 EditPositionEvent()
11434 if (gameMode == EditPosition) {
11440 if (gameMode != EditGame) return;
11442 gameMode = EditPosition;
11445 if (currentMove > 0)
11446 CopyBoard(boards[0], boards[currentMove]);
11448 blackPlaysFirst = !WhiteOnMove(currentMove);
11450 currentMove = forwardMostMove = backwardMostMove = 0;
11451 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11458 /* [DM] icsEngineAnalyze - possible call from other functions */
11459 if (appData.icsEngineAnalyze) {
11460 appData.icsEngineAnalyze = FALSE;
11462 DisplayMessage("",_("Close ICS engine analyze..."));
11464 if (first.analysisSupport && first.analyzing) {
11465 SendToProgram("exit\n", &first);
11466 first.analyzing = FALSE;
11468 thinkOutput[0] = NULLCHAR;
11472 EditPositionDone(Boolean fakeRights)
11474 int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
11476 startedFromSetupPosition = TRUE;
11477 InitChessProgram(&first, FALSE);
11478 if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
11479 boards[0][EP_STATUS] = EP_NONE;
11480 boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
11481 if(boards[0][0][BOARD_WIDTH>>1] == king) {
11482 boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : NoRights;
11483 boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
11484 } else boards[0][CASTLING][2] = NoRights;
11485 if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
11486 boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : NoRights;
11487 boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
11488 } else boards[0][CASTLING][5] = NoRights;
11490 SendToProgram("force\n", &first);
11491 if (blackPlaysFirst) {
11492 strcpy(moveList[0], "");
11493 strcpy(parseList[0], "");
11494 currentMove = forwardMostMove = backwardMostMove = 1;
11495 CopyBoard(boards[1], boards[0]);
11497 currentMove = forwardMostMove = backwardMostMove = 0;
11499 SendBoard(&first, forwardMostMove);
11500 if (appData.debugMode) {
11501 fprintf(debugFP, "EditPosDone\n");
11504 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11505 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11506 gameMode = EditGame;
11508 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11509 ClearHighlights(); /* [AS] */
11512 /* Pause for `ms' milliseconds */
11513 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
11523 } while (SubtractTimeMarks(&m2, &m1) < ms);
11526 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
11528 SendMultiLineToICS(buf)
11531 char temp[MSG_SIZ+1], *p;
11538 strncpy(temp, buf, len);
11543 if (*p == '\n' || *p == '\r')
11548 strcat(temp, "\n");
11550 SendToPlayer(temp, strlen(temp));
11554 SetWhiteToPlayEvent()
11556 if (gameMode == EditPosition) {
11557 blackPlaysFirst = FALSE;
11558 DisplayBothClocks(); /* works because currentMove is 0 */
11559 } else if (gameMode == IcsExamining) {
11560 SendToICS(ics_prefix);
11561 SendToICS("tomove white\n");
11566 SetBlackToPlayEvent()
11568 if (gameMode == EditPosition) {
11569 blackPlaysFirst = TRUE;
11570 currentMove = 1; /* kludge */
11571 DisplayBothClocks();
11573 } else if (gameMode == IcsExamining) {
11574 SendToICS(ics_prefix);
11575 SendToICS("tomove black\n");
11580 EditPositionMenuEvent(selection, x, y)
11581 ChessSquare selection;
11585 ChessSquare piece = boards[0][y][x];
11587 if (gameMode != EditPosition && gameMode != IcsExamining) return;
11589 switch (selection) {
11591 if (gameMode == IcsExamining && ics_type == ICS_FICS) {
11592 SendToICS(ics_prefix);
11593 SendToICS("bsetup clear\n");
11594 } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
11595 SendToICS(ics_prefix);
11596 SendToICS("clearboard\n");
11598 for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
11599 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
11600 for (y = 0; y < BOARD_HEIGHT; y++) {
11601 if (gameMode == IcsExamining) {
11602 if (boards[currentMove][y][x] != EmptySquare) {
11603 sprintf(buf, "%sx@%c%c\n", ics_prefix,
11608 boards[0][y][x] = p;
11613 if (gameMode == EditPosition) {
11614 DrawPosition(FALSE, boards[0]);
11619 SetWhiteToPlayEvent();
11623 SetBlackToPlayEvent();
11627 if (gameMode == IcsExamining) {
11628 if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
11629 sprintf(buf, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
11632 if(x < BOARD_LEFT || x >= BOARD_RGHT) {
11633 if(x == BOARD_LEFT-2) {
11634 if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
11635 boards[0][y][1] = 0;
11637 if(x == BOARD_RGHT+1) {
11638 if(y >= gameInfo.holdingsSize) break;
11639 boards[0][y][BOARD_WIDTH-2] = 0;
11642 boards[0][y][x] = EmptySquare;
11643 DrawPosition(FALSE, boards[0]);
11648 if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
11649 piece >= (int)BlackPawn && piece < (int)BlackMan ) {
11650 selection = (ChessSquare) (PROMOTED piece);
11651 } else if(piece == EmptySquare) selection = WhiteSilver;
11652 else selection = (ChessSquare)((int)piece - 1);
11656 if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
11657 piece > (int)BlackMan && piece <= (int)BlackKing ) {
11658 selection = (ChessSquare) (DEMOTED piece);
11659 } else if(piece == EmptySquare) selection = BlackSilver;
11660 else selection = (ChessSquare)((int)piece + 1);
11665 if(gameInfo.variant == VariantShatranj ||
11666 gameInfo.variant == VariantXiangqi ||
11667 gameInfo.variant == VariantCourier ||
11668 gameInfo.variant == VariantMakruk )
11669 selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
11674 if(gameInfo.variant == VariantXiangqi)
11675 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
11676 if(gameInfo.variant == VariantKnightmate)
11677 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
11680 if (gameMode == IcsExamining) {
11681 if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
11682 sprintf(buf, "%s%c@%c%c\n", ics_prefix,
11683 PieceToChar(selection), AAA + x, ONE + y);
11686 if(x < BOARD_LEFT || x >= BOARD_RGHT) {
11688 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
11689 n = PieceToNumber(selection - BlackPawn);
11690 if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
11691 boards[0][BOARD_HEIGHT-1-n][0] = selection;
11692 boards[0][BOARD_HEIGHT-1-n][1]++;
11694 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
11695 n = PieceToNumber(selection);
11696 if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
11697 boards[0][n][BOARD_WIDTH-1] = selection;
11698 boards[0][n][BOARD_WIDTH-2]++;
11701 boards[0][y][x] = selection;
11702 DrawPosition(TRUE, boards[0]);
11710 DropMenuEvent(selection, x, y)
11711 ChessSquare selection;
11714 ChessMove moveType;
11716 switch (gameMode) {
11717 case IcsPlayingWhite:
11718 case MachinePlaysBlack:
11719 if (!WhiteOnMove(currentMove)) {
11720 DisplayMoveError(_("It is Black's turn"));
11723 moveType = WhiteDrop;
11725 case IcsPlayingBlack:
11726 case MachinePlaysWhite:
11727 if (WhiteOnMove(currentMove)) {
11728 DisplayMoveError(_("It is White's turn"));
11731 moveType = BlackDrop;
11734 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
11740 if (moveType == BlackDrop && selection < BlackPawn) {
11741 selection = (ChessSquare) ((int) selection
11742 + (int) BlackPawn - (int) WhitePawn);
11744 if (boards[currentMove][y][x] != EmptySquare) {
11745 DisplayMoveError(_("That square is occupied"));
11749 FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
11755 /* Accept a pending offer of any kind from opponent */
11757 if (appData.icsActive) {
11758 SendToICS(ics_prefix);
11759 SendToICS("accept\n");
11760 } else if (cmailMsgLoaded) {
11761 if (currentMove == cmailOldMove &&
11762 commentList[cmailOldMove] != NULL &&
11763 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11764 "Black offers a draw" : "White offers a draw")) {
11766 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11767 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
11769 DisplayError(_("There is no pending offer on this move"), 0);
11770 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11773 /* Not used for offers from chess program */
11780 /* Decline a pending offer of any kind from opponent */
11782 if (appData.icsActive) {
11783 SendToICS(ics_prefix);
11784 SendToICS("decline\n");
11785 } else if (cmailMsgLoaded) {
11786 if (currentMove == cmailOldMove &&
11787 commentList[cmailOldMove] != NULL &&
11788 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11789 "Black offers a draw" : "White offers a draw")) {
11791 AppendComment(cmailOldMove, "Draw declined", TRUE);
11792 DisplayComment(cmailOldMove - 1, "Draw declined");
11795 DisplayError(_("There is no pending offer on this move"), 0);
11798 /* Not used for offers from chess program */
11805 /* Issue ICS rematch command */
11806 if (appData.icsActive) {
11807 SendToICS(ics_prefix);
11808 SendToICS("rematch\n");
11815 /* Call your opponent's flag (claim a win on time) */
11816 if (appData.icsActive) {
11817 SendToICS(ics_prefix);
11818 SendToICS("flag\n");
11820 switch (gameMode) {
11823 case MachinePlaysWhite:
11826 GameEnds(GameIsDrawn, "Both players ran out of time",
11829 GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
11831 DisplayError(_("Your opponent is not out of time"), 0);
11834 case MachinePlaysBlack:
11837 GameEnds(GameIsDrawn, "Both players ran out of time",
11840 GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
11842 DisplayError(_("Your opponent is not out of time"), 0);
11852 /* Offer draw or accept pending draw offer from opponent */
11854 if (appData.icsActive) {
11855 /* Note: tournament rules require draw offers to be
11856 made after you make your move but before you punch
11857 your clock. Currently ICS doesn't let you do that;
11858 instead, you immediately punch your clock after making
11859 a move, but you can offer a draw at any time. */
11861 SendToICS(ics_prefix);
11862 SendToICS("draw\n");
11863 userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
11864 } else if (cmailMsgLoaded) {
11865 if (currentMove == cmailOldMove &&
11866 commentList[cmailOldMove] != NULL &&
11867 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11868 "Black offers a draw" : "White offers a draw")) {
11869 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11870 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
11871 } else if (currentMove == cmailOldMove + 1) {
11872 char *offer = WhiteOnMove(cmailOldMove) ?
11873 "White offers a draw" : "Black offers a draw";
11874 AppendComment(currentMove, offer, TRUE);
11875 DisplayComment(currentMove - 1, offer);
11876 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
11878 DisplayError(_("You must make your move before offering a draw"), 0);
11879 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11881 } else if (first.offeredDraw) {
11882 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
11884 if (first.sendDrawOffers) {
11885 SendToProgram("draw\n", &first);
11886 userOfferedDraw = TRUE;
11894 /* Offer Adjourn or accept pending Adjourn offer from opponent */
11896 if (appData.icsActive) {
11897 SendToICS(ics_prefix);
11898 SendToICS("adjourn\n");
11900 /* Currently GNU Chess doesn't offer or accept Adjourns */
11908 /* Offer Abort or accept pending Abort offer from opponent */
11910 if (appData.icsActive) {
11911 SendToICS(ics_prefix);
11912 SendToICS("abort\n");
11914 GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
11921 /* Resign. You can do this even if it's not your turn. */
11923 if (appData.icsActive) {
11924 SendToICS(ics_prefix);
11925 SendToICS("resign\n");
11927 switch (gameMode) {
11928 case MachinePlaysWhite:
11929 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11931 case MachinePlaysBlack:
11932 GameEnds(BlackWins, "White resigns", GE_PLAYER);
11935 if (cmailMsgLoaded) {
11937 if (WhiteOnMove(cmailOldMove)) {
11938 GameEnds(BlackWins, "White resigns", GE_PLAYER);
11940 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11942 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
11953 StopObservingEvent()
11955 /* Stop observing current games */
11956 SendToICS(ics_prefix);
11957 SendToICS("unobserve\n");
11961 StopExaminingEvent()
11963 /* Stop observing current game */
11964 SendToICS(ics_prefix);
11965 SendToICS("unexamine\n");
11969 ForwardInner(target)
11974 if (appData.debugMode)
11975 fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
11976 target, currentMove, forwardMostMove);
11978 if (gameMode == EditPosition)
11981 if (gameMode == PlayFromGameFile && !pausing)
11984 if (gameMode == IcsExamining && pausing)
11985 limit = pauseExamForwardMostMove;
11987 limit = forwardMostMove;
11989 if (target > limit) target = limit;
11991 if (target > 0 && moveList[target - 1][0]) {
11992 int fromX, fromY, toX, toY;
11993 toX = moveList[target - 1][2] - AAA;
11994 toY = moveList[target - 1][3] - ONE;
11995 if (moveList[target - 1][1] == '@') {
11996 if (appData.highlightLastMove) {
11997 SetHighlights(-1, -1, toX, toY);
12000 fromX = moveList[target - 1][0] - AAA;
12001 fromY = moveList[target - 1][1] - ONE;
12002 if (target == currentMove + 1) {
12003 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
12005 if (appData.highlightLastMove) {
12006 SetHighlights(fromX, fromY, toX, toY);
12010 if (gameMode == EditGame || gameMode == AnalyzeMode ||
12011 gameMode == Training || gameMode == PlayFromGameFile ||
12012 gameMode == AnalyzeFile) {
12013 while (currentMove < target) {
12014 SendMoveToProgram(currentMove++, &first);
12017 currentMove = target;
12020 if (gameMode == EditGame || gameMode == EndOfGame) {
12021 whiteTimeRemaining = timeRemaining[0][currentMove];
12022 blackTimeRemaining = timeRemaining[1][currentMove];
12024 DisplayBothClocks();
12025 DisplayMove(currentMove - 1);
12026 DrawPosition(FALSE, boards[currentMove]);
12027 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
12028 if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
12029 DisplayComment(currentMove - 1, commentList[currentMove]);
12037 if (gameMode == IcsExamining && !pausing) {
12038 SendToICS(ics_prefix);
12039 SendToICS("forward\n");
12041 ForwardInner(currentMove + 1);
12048 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12049 /* to optimze, we temporarily turn off analysis mode while we feed
12050 * the remaining moves to the engine. Otherwise we get analysis output
12053 if (first.analysisSupport) {
12054 SendToProgram("exit\nforce\n", &first);
12055 first.analyzing = FALSE;
12059 if (gameMode == IcsExamining && !pausing) {
12060 SendToICS(ics_prefix);
12061 SendToICS("forward 999999\n");
12063 ForwardInner(forwardMostMove);
12066 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12067 /* we have fed all the moves, so reactivate analysis mode */
12068 SendToProgram("analyze\n", &first);
12069 first.analyzing = TRUE;
12070 /*first.maybeThinking = TRUE;*/
12071 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12076 BackwardInner(target)
12079 int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
12081 if (appData.debugMode)
12082 fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
12083 target, currentMove, forwardMostMove);
12085 if (gameMode == EditPosition) return;
12086 if (currentMove <= backwardMostMove) {
12088 DrawPosition(full_redraw, boards[currentMove]);
12091 if (gameMode == PlayFromGameFile && !pausing)
12094 if (moveList[target][0]) {
12095 int fromX, fromY, toX, toY;
12096 toX = moveList[target][2] - AAA;
12097 toY = moveList[target][3] - ONE;
12098 if (moveList[target][1] == '@') {
12099 if (appData.highlightLastMove) {
12100 SetHighlights(-1, -1, toX, toY);
12103 fromX = moveList[target][0] - AAA;
12104 fromY = moveList[target][1] - ONE;
12105 if (target == currentMove - 1) {
12106 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
12108 if (appData.highlightLastMove) {
12109 SetHighlights(fromX, fromY, toX, toY);
12113 if (gameMode == EditGame || gameMode==AnalyzeMode ||
12114 gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
12115 while (currentMove > target) {
12116 SendToProgram("undo\n", &first);
12120 currentMove = target;
12123 if (gameMode == EditGame || gameMode == EndOfGame) {
12124 whiteTimeRemaining = timeRemaining[0][currentMove];
12125 blackTimeRemaining = timeRemaining[1][currentMove];
12127 DisplayBothClocks();
12128 DisplayMove(currentMove - 1);
12129 DrawPosition(full_redraw, boards[currentMove]);
12130 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
12131 // [HGM] PV info: routine tests if comment empty
12132 DisplayComment(currentMove - 1, commentList[currentMove]);
12138 if (gameMode == IcsExamining && !pausing) {
12139 SendToICS(ics_prefix);
12140 SendToICS("backward\n");
12142 BackwardInner(currentMove - 1);
12149 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12150 /* to optimize, we temporarily turn off analysis mode while we undo
12151 * all the moves. Otherwise we get analysis output after each undo.
12153 if (first.analysisSupport) {
12154 SendToProgram("exit\nforce\n", &first);
12155 first.analyzing = FALSE;
12159 if (gameMode == IcsExamining && !pausing) {
12160 SendToICS(ics_prefix);
12161 SendToICS("backward 999999\n");
12163 BackwardInner(backwardMostMove);
12166 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12167 /* we have fed all the moves, so reactivate analysis mode */
12168 SendToProgram("analyze\n", &first);
12169 first.analyzing = TRUE;
12170 /*first.maybeThinking = TRUE;*/
12171 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12178 if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
12179 if (to >= forwardMostMove) to = forwardMostMove;
12180 if (to <= backwardMostMove) to = backwardMostMove;
12181 if (to < currentMove) {
12191 if(PopTail(TRUE)) { // [HGM] vari: restore old game tail
12194 if (gameMode != IcsExamining) {
12195 DisplayError(_("You are not examining a game"), 0);
12199 DisplayError(_("You can't revert while pausing"), 0);
12202 SendToICS(ics_prefix);
12203 SendToICS("revert\n");
12209 switch (gameMode) {
12210 case MachinePlaysWhite:
12211 case MachinePlaysBlack:
12212 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
12213 DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
12216 if (forwardMostMove < 2) return;
12217 currentMove = forwardMostMove = forwardMostMove - 2;
12218 whiteTimeRemaining = timeRemaining[0][currentMove];
12219 blackTimeRemaining = timeRemaining[1][currentMove];
12220 DisplayBothClocks();
12221 DisplayMove(currentMove - 1);
12222 ClearHighlights();/*!! could figure this out*/
12223 DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
12224 SendToProgram("remove\n", &first);
12225 /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
12228 case BeginningOfGame:
12232 case IcsPlayingWhite:
12233 case IcsPlayingBlack:
12234 if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
12235 SendToICS(ics_prefix);
12236 SendToICS("takeback 2\n");
12238 SendToICS(ics_prefix);
12239 SendToICS("takeback 1\n");
12248 ChessProgramState *cps;
12250 switch (gameMode) {
12251 case MachinePlaysWhite:
12252 if (!WhiteOnMove(forwardMostMove)) {
12253 DisplayError(_("It is your turn"), 0);
12258 case MachinePlaysBlack:
12259 if (WhiteOnMove(forwardMostMove)) {
12260 DisplayError(_("It is your turn"), 0);
12265 case TwoMachinesPlay:
12266 if (WhiteOnMove(forwardMostMove) ==
12267 (first.twoMachinesColor[0] == 'w')) {
12273 case BeginningOfGame:
12277 SendToProgram("?\n", cps);
12281 TruncateGameEvent()
12284 if (gameMode != EditGame) return;
12291 CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
12292 if (forwardMostMove > currentMove) {
12293 if (gameInfo.resultDetails != NULL) {
12294 free(gameInfo.resultDetails);
12295 gameInfo.resultDetails = NULL;
12296 gameInfo.result = GameUnfinished;
12298 forwardMostMove = currentMove;
12299 HistorySet(parseList, backwardMostMove, forwardMostMove,
12307 if (appData.noChessProgram) return;
12308 switch (gameMode) {
12309 case MachinePlaysWhite:
12310 if (WhiteOnMove(forwardMostMove)) {
12311 DisplayError(_("Wait until your turn"), 0);
12315 case BeginningOfGame:
12316 case MachinePlaysBlack:
12317 if (!WhiteOnMove(forwardMostMove)) {
12318 DisplayError(_("Wait until your turn"), 0);
12323 DisplayError(_("No hint available"), 0);
12326 SendToProgram("hint\n", &first);
12327 hintRequested = TRUE;
12333 if (appData.noChessProgram) return;
12334 switch (gameMode) {
12335 case MachinePlaysWhite:
12336 if (WhiteOnMove(forwardMostMove)) {
12337 DisplayError(_("Wait until your turn"), 0);
12341 case BeginningOfGame:
12342 case MachinePlaysBlack:
12343 if (!WhiteOnMove(forwardMostMove)) {
12344 DisplayError(_("Wait until your turn"), 0);
12349 EditPositionDone(TRUE);
12351 case TwoMachinesPlay:
12356 SendToProgram("bk\n", &first);
12357 bookOutput[0] = NULLCHAR;
12358 bookRequested = TRUE;
12364 char *tags = PGNTags(&gameInfo);
12365 TagsPopUp(tags, CmailMsg());
12369 /* end button procedures */
12372 PrintPosition(fp, move)
12378 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
12379 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
12380 char c = PieceToChar(boards[move][i][j]);
12381 fputc(c == 'x' ? '.' : c, fp);
12382 fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
12385 if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
12386 fprintf(fp, "white to play\n");
12388 fprintf(fp, "black to play\n");
12395 if (gameInfo.white != NULL) {
12396 fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
12402 /* Find last component of program's own name, using some heuristics */
12404 TidyProgramName(prog, host, buf)
12405 char *prog, *host, buf[MSG_SIZ];
12408 int local = (strcmp(host, "localhost") == 0);
12409 while (!local && (p = strchr(prog, ';')) != NULL) {
12411 while (*p == ' ') p++;
12414 if (*prog == '"' || *prog == '\'') {
12415 q = strchr(prog + 1, *prog);
12417 q = strchr(prog, ' ');
12419 if (q == NULL) q = prog + strlen(prog);
12421 while (p >= prog && *p != '/' && *p != '\\') p--;
12423 if(p == prog && *p == '"') p++;
12424 if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
12425 memcpy(buf, p, q - p);
12426 buf[q - p] = NULLCHAR;
12434 TimeControlTagValue()
12437 if (!appData.clockMode) {
12439 } else if (movesPerSession > 0) {
12440 sprintf(buf, "%d/%ld", movesPerSession, timeControl/1000);
12441 } else if (timeIncrement == 0) {
12442 sprintf(buf, "%ld", timeControl/1000);
12444 sprintf(buf, "%ld+%ld", timeControl/1000, timeIncrement/1000);
12446 return StrSave(buf);
12452 /* This routine is used only for certain modes */
12453 VariantClass v = gameInfo.variant;
12454 ChessMove r = GameUnfinished;
12457 if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
12458 r = gameInfo.result;
12459 p = gameInfo.resultDetails;
12460 gameInfo.resultDetails = NULL;
12462 ClearGameInfo(&gameInfo);
12463 gameInfo.variant = v;
12465 switch (gameMode) {
12466 case MachinePlaysWhite:
12467 gameInfo.event = StrSave( appData.pgnEventHeader );
12468 gameInfo.site = StrSave(HostName());
12469 gameInfo.date = PGNDate();
12470 gameInfo.round = StrSave("-");
12471 gameInfo.white = StrSave(first.tidy);
12472 gameInfo.black = StrSave(UserName());
12473 gameInfo.timeControl = TimeControlTagValue();
12476 case MachinePlaysBlack:
12477 gameInfo.event = StrSave( appData.pgnEventHeader );
12478 gameInfo.site = StrSave(HostName());
12479 gameInfo.date = PGNDate();
12480 gameInfo.round = StrSave("-");
12481 gameInfo.white = StrSave(UserName());
12482 gameInfo.black = StrSave(first.tidy);
12483 gameInfo.timeControl = TimeControlTagValue();
12486 case TwoMachinesPlay:
12487 gameInfo.event = StrSave( appData.pgnEventHeader );
12488 gameInfo.site = StrSave(HostName());
12489 gameInfo.date = PGNDate();
12490 if (matchGame > 0) {
12492 sprintf(buf, "%d", matchGame);
12493 gameInfo.round = StrSave(buf);
12495 gameInfo.round = StrSave("-");
12497 if (first.twoMachinesColor[0] == 'w') {
12498 gameInfo.white = StrSave(first.tidy);
12499 gameInfo.black = StrSave(second.tidy);
12501 gameInfo.white = StrSave(second.tidy);
12502 gameInfo.black = StrSave(first.tidy);
12504 gameInfo.timeControl = TimeControlTagValue();
12508 gameInfo.event = StrSave("Edited game");
12509 gameInfo.site = StrSave(HostName());
12510 gameInfo.date = PGNDate();
12511 gameInfo.round = StrSave("-");
12512 gameInfo.white = StrSave("-");
12513 gameInfo.black = StrSave("-");
12514 gameInfo.result = r;
12515 gameInfo.resultDetails = p;
12519 gameInfo.event = StrSave("Edited position");
12520 gameInfo.site = StrSave(HostName());
12521 gameInfo.date = PGNDate();
12522 gameInfo.round = StrSave("-");
12523 gameInfo.white = StrSave("-");
12524 gameInfo.black = StrSave("-");
12527 case IcsPlayingWhite:
12528 case IcsPlayingBlack:
12533 case PlayFromGameFile:
12534 gameInfo.event = StrSave("Game from non-PGN file");
12535 gameInfo.site = StrSave(HostName());
12536 gameInfo.date = PGNDate();
12537 gameInfo.round = StrSave("-");
12538 gameInfo.white = StrSave("?");
12539 gameInfo.black = StrSave("?");
12548 ReplaceComment(index, text)
12554 while (*text == '\n') text++;
12555 len = strlen(text);
12556 while (len > 0 && text[len - 1] == '\n') len--;
12558 if (commentList[index] != NULL)
12559 free(commentList[index]);
12562 commentList[index] = NULL;
12565 if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
12566 *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
12567 *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
12568 commentList[index] = (char *) malloc(len + 2);
12569 strncpy(commentList[index], text, len);
12570 commentList[index][len] = '\n';
12571 commentList[index][len + 1] = NULLCHAR;
12573 // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
12575 commentList[index] = (char *) malloc(len + 6);
12576 strcpy(commentList[index], "{\n");
12577 strncpy(commentList[index]+2, text, len);
12578 commentList[index][len+2] = NULLCHAR;
12579 while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
12580 strcat(commentList[index], "\n}\n");
12594 if (ch == '\r') continue;
12596 } while (ch != '\0');
12600 AppendComment(index, text, addBraces)
12603 Boolean addBraces; // [HGM] braces: tells if we should add {}
12608 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
12609 text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
12612 while (*text == '\n') text++;
12613 len = strlen(text);
12614 while (len > 0 && text[len - 1] == '\n') len--;
12616 if (len == 0) return;
12618 if (commentList[index] != NULL) {
12619 old = commentList[index];
12620 oldlen = strlen(old);
12621 while(commentList[index][oldlen-1] == '\n')
12622 commentList[index][--oldlen] = NULLCHAR;
12623 commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
12624 strcpy(commentList[index], old);
12626 // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
12627 if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces)) {
12628 if(addBraces) addBraces = FALSE; else { text++; len--; }
12629 while (*text == '\n') { text++; len--; }
12630 commentList[index][--oldlen] = NULLCHAR;
12632 if(addBraces) strcat(commentList[index], "\n{\n");
12633 else strcat(commentList[index], "\n");
12634 strcat(commentList[index], text);
12635 if(addBraces) strcat(commentList[index], "\n}\n");
12636 else strcat(commentList[index], "\n");
12638 commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
12640 strcpy(commentList[index], "{\n");
12641 else commentList[index][0] = NULLCHAR;
12642 strcat(commentList[index], text);
12643 strcat(commentList[index], "\n");
12644 if(addBraces) strcat(commentList[index], "}\n");
12648 static char * FindStr( char * text, char * sub_text )
12650 char * result = strstr( text, sub_text );
12652 if( result != NULL ) {
12653 result += strlen( sub_text );
12659 /* [AS] Try to extract PV info from PGN comment */
12660 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
12661 char *GetInfoFromComment( int index, char * text )
12665 if( text != NULL && index > 0 ) {
12668 int time = -1, sec = 0, deci;
12669 char * s_eval = FindStr( text, "[%eval " );
12670 char * s_emt = FindStr( text, "[%emt " );
12672 if( s_eval != NULL || s_emt != NULL ) {
12676 if( s_eval != NULL ) {
12677 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
12681 if( delim != ']' ) {
12686 if( s_emt != NULL ) {
12691 /* We expect something like: [+|-]nnn.nn/dd */
12694 if(*text != '{') return text; // [HGM] braces: must be normal comment
12696 sep = strchr( text, '/' );
12697 if( sep == NULL || sep < (text+4) ) {
12701 time = -1; sec = -1; deci = -1;
12702 if( sscanf( text+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
12703 sscanf( text+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
12704 sscanf( text+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
12705 sscanf( text+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3 ) {
12709 if( score_lo < 0 || score_lo >= 100 ) {
12713 if(sec >= 0) time = 600*time + 10*sec; else
12714 if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
12716 score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
12718 /* [HGM] PV time: now locate end of PV info */
12719 while( *++sep >= '0' && *sep <= '9'); // strip depth
12721 while( *++sep >= '0' && *sep <= '9'); // strip time
12723 while( *++sep >= '0' && *sep <= '9'); // strip seconds
12725 while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
12726 while(*sep == ' ') sep++;
12737 pvInfoList[index-1].depth = depth;
12738 pvInfoList[index-1].score = score;
12739 pvInfoList[index-1].time = 10*time; // centi-sec
12740 if(*sep == '}') *sep = 0; else *--sep = '{';
12746 SendToProgram(message, cps)
12748 ChessProgramState *cps;
12750 int count, outCount, error;
12753 if (cps->pr == NULL) return;
12756 if (appData.debugMode) {
12759 fprintf(debugFP, "%ld >%-6s: %s",
12760 SubtractTimeMarks(&now, &programStartTime),
12761 cps->which, message);
12764 count = strlen(message);
12765 outCount = OutputToProcess(cps->pr, message, count, &error);
12766 if (outCount < count && !exiting
12767 && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
12768 sprintf(buf, _("Error writing to %s chess program"), cps->which);
12769 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
12770 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
12771 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
12772 sprintf(buf, "%s program exits in draw position (%s)", cps->which, cps->program);
12774 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
12776 gameInfo.resultDetails = StrSave(buf);
12778 if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
12783 ReceiveFromProgram(isr, closure, message, count, error)
12784 InputSourceRef isr;
12792 ChessProgramState *cps = (ChessProgramState *)closure;
12794 if (isr != cps->isr) return; /* Killed intentionally */
12798 _("Error: %s chess program (%s) exited unexpectedly"),
12799 cps->which, cps->program);
12800 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
12801 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
12802 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
12803 sprintf(buf, _("%s program exits in draw position (%s)"), cps->which, cps->program);
12805 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
12807 gameInfo.resultDetails = StrSave(buf);
12809 RemoveInputSource(cps->isr);
12810 if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
12813 _("Error reading from %s chess program (%s)"),
12814 cps->which, cps->program);
12815 RemoveInputSource(cps->isr);
12817 /* [AS] Program is misbehaving badly... kill it */
12818 if( count == -2 ) {
12819 DestroyChildProcess( cps->pr, 9 );
12823 if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
12828 if ((end_str = strchr(message, '\r')) != NULL)
12829 *end_str = NULLCHAR;
12830 if ((end_str = strchr(message, '\n')) != NULL)
12831 *end_str = NULLCHAR;
12833 if (appData.debugMode) {
12834 TimeMark now; int print = 1;
12835 char *quote = ""; char c; int i;
12837 if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
12838 char start = message[0];
12839 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
12840 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
12841 sscanf(message, "move %c", &c)!=1 && sscanf(message, "offer%c", &c)!=1 &&
12842 sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
12843 sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
12844 sscanf(message, "tell%c", &c)!=1 && sscanf(message, "0-1 %c", &c)!=1 &&
12845 sscanf(message, "1-0 %c", &c)!=1 && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
12846 sscanf(message, "pong %c", &c)!=1 && start != '#')
12847 { quote = "# "; print = (appData.engineComments == 2); }
12848 message[0] = start; // restore original message
12852 fprintf(debugFP, "%ld <%-6s: %s%s\n",
12853 SubtractTimeMarks(&now, &programStartTime), cps->which,
12859 /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
12860 if (appData.icsEngineAnalyze) {
12861 if (strstr(message, "whisper") != NULL ||
12862 strstr(message, "kibitz") != NULL ||
12863 strstr(message, "tellics") != NULL) return;
12866 HandleMachineMove(message, cps);
12871 SendTimeControl(cps, mps, tc, inc, sd, st)
12872 ChessProgramState *cps;
12873 int mps, inc, sd, st;
12879 if( timeControl_2 > 0 ) {
12880 if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
12881 tc = timeControl_2;
12884 tc /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
12885 inc /= cps->timeOdds;
12886 st /= cps->timeOdds;
12888 seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
12891 /* Set exact time per move, normally using st command */
12892 if (cps->stKludge) {
12893 /* GNU Chess 4 has no st command; uses level in a nonstandard way */
12895 if (seconds == 0) {
12896 sprintf(buf, "level 1 %d\n", st/60);
12898 sprintf(buf, "level 1 %d:%02d\n", st/60, seconds);
12901 sprintf(buf, "st %d\n", st);
12904 /* Set conventional or incremental time control, using level command */
12905 if (seconds == 0) {
12906 /* Note old gnuchess bug -- minutes:seconds used to not work.
12907 Fixed in later versions, but still avoid :seconds
12908 when seconds is 0. */
12909 sprintf(buf, "level %d %ld %d\n", mps, tc/60000, inc/1000);
12911 sprintf(buf, "level %d %ld:%02d %d\n", mps, tc/60000,
12912 seconds, inc/1000);
12915 SendToProgram(buf, cps);
12917 /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
12918 /* Orthogonally, limit search to given depth */
12920 if (cps->sdKludge) {
12921 sprintf(buf, "depth\n%d\n", sd);
12923 sprintf(buf, "sd %d\n", sd);
12925 SendToProgram(buf, cps);
12928 if(cps->nps > 0) { /* [HGM] nps */
12929 if(cps->supportsNPS == FALSE) cps->nps = -1; // don't use if engine explicitly says not supported!
12931 sprintf(buf, "nps %d\n", cps->nps);
12932 SendToProgram(buf, cps);
12937 ChessProgramState *WhitePlayer()
12938 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
12940 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
12941 gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
12947 SendTimeRemaining(cps, machineWhite)
12948 ChessProgramState *cps;
12949 int /*boolean*/ machineWhite;
12951 char message[MSG_SIZ];
12954 /* Note: this routine must be called when the clocks are stopped
12955 or when they have *just* been set or switched; otherwise
12956 it will be off by the time since the current tick started.
12958 if (machineWhite) {
12959 time = whiteTimeRemaining / 10;
12960 otime = blackTimeRemaining / 10;
12962 time = blackTimeRemaining / 10;
12963 otime = whiteTimeRemaining / 10;
12965 /* [HGM] translate opponent's time by time-odds factor */
12966 otime = (otime * cps->other->timeOdds) / cps->timeOdds;
12967 if (appData.debugMode) {
12968 fprintf(debugFP, "time odds: %f %f \n", cps->timeOdds, cps->other->timeOdds);
12971 if (time <= 0) time = 1;
12972 if (otime <= 0) otime = 1;
12974 sprintf(message, "time %ld\n", time);
12975 SendToProgram(message, cps);
12977 sprintf(message, "otim %ld\n", otime);
12978 SendToProgram(message, cps);
12982 BoolFeature(p, name, loc, cps)
12986 ChessProgramState *cps;
12989 int len = strlen(name);
12991 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
12993 sscanf(*p, "%d", &val);
12995 while (**p && **p != ' ') (*p)++;
12996 sprintf(buf, "accepted %s\n", name);
12997 SendToProgram(buf, cps);
13004 IntFeature(p, name, loc, cps)
13008 ChessProgramState *cps;
13011 int len = strlen(name);
13012 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
13014 sscanf(*p, "%d", loc);
13015 while (**p && **p != ' ') (*p)++;
13016 sprintf(buf, "accepted %s\n", name);
13017 SendToProgram(buf, cps);
13024 StringFeature(p, name, loc, cps)
13028 ChessProgramState *cps;
13031 int len = strlen(name);
13032 if (strncmp((*p), name, len) == 0
13033 && (*p)[len] == '=' && (*p)[len+1] == '\"') {
13035 sscanf(*p, "%[^\"]", loc);
13036 while (**p && **p != '\"') (*p)++;
13037 if (**p == '\"') (*p)++;
13038 sprintf(buf, "accepted %s\n", name);
13039 SendToProgram(buf, cps);
13046 ParseOption(Option *opt, ChessProgramState *cps)
13047 // [HGM] options: process the string that defines an engine option, and determine
13048 // name, type, default value, and allowed value range
13050 char *p, *q, buf[MSG_SIZ];
13051 int n, min = (-1)<<31, max = 1<<31, def;
13053 if(p = strstr(opt->name, " -spin ")) {
13054 if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
13055 if(max < min) max = min; // enforce consistency
13056 if(def < min) def = min;
13057 if(def > max) def = max;
13062 } else if((p = strstr(opt->name, " -slider "))) {
13063 // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
13064 if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
13065 if(max < min) max = min; // enforce consistency
13066 if(def < min) def = min;
13067 if(def > max) def = max;
13071 opt->type = Spin; // Slider;
13072 } else if((p = strstr(opt->name, " -string "))) {
13073 opt->textValue = p+9;
13074 opt->type = TextBox;
13075 } else if((p = strstr(opt->name, " -file "))) {
13076 // for now -file is a synonym for -string, to already provide compatibility with future polyglots
13077 opt->textValue = p+7;
13078 opt->type = TextBox; // FileName;
13079 } else if((p = strstr(opt->name, " -path "))) {
13080 // for now -file is a synonym for -string, to already provide compatibility with future polyglots
13081 opt->textValue = p+7;
13082 opt->type = TextBox; // PathName;
13083 } else if(p = strstr(opt->name, " -check ")) {
13084 if(sscanf(p, " -check %d", &def) < 1) return FALSE;
13085 opt->value = (def != 0);
13086 opt->type = CheckBox;
13087 } else if(p = strstr(opt->name, " -combo ")) {
13088 opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
13089 cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
13090 if(*q == '*') cps->comboList[cps->comboCnt-1]++;
13091 opt->value = n = 0;
13092 while(q = StrStr(q, " /// ")) {
13093 n++; *q = 0; // count choices, and null-terminate each of them
13095 if(*q == '*') { // remember default, which is marked with * prefix
13099 cps->comboList[cps->comboCnt++] = q;
13101 cps->comboList[cps->comboCnt++] = NULL;
13103 opt->type = ComboBox;
13104 } else if(p = strstr(opt->name, " -button")) {
13105 opt->type = Button;
13106 } else if(p = strstr(opt->name, " -save")) {
13107 opt->type = SaveButton;
13108 } else return FALSE;
13109 *p = 0; // terminate option name
13110 // now look if the command-line options define a setting for this engine option.
13111 if(cps->optionSettings && cps->optionSettings[0])
13112 p = strstr(cps->optionSettings, opt->name); else p = NULL;
13113 if(p && (p == cps->optionSettings || p[-1] == ',')) {
13114 sprintf(buf, "option %s", p);
13115 if(p = strstr(buf, ",")) *p = 0;
13117 SendToProgram(buf, cps);
13123 FeatureDone(cps, val)
13124 ChessProgramState* cps;
13127 DelayedEventCallback cb = GetDelayedEvent();
13128 if ((cb == InitBackEnd3 && cps == &first) ||
13129 (cb == TwoMachinesEventIfReady && cps == &second)) {
13130 CancelDelayedEvent();
13131 ScheduleDelayedEvent(cb, val ? 1 : 3600000);
13133 cps->initDone = val;
13136 /* Parse feature command from engine */
13138 ParseFeatures(args, cps)
13140 ChessProgramState *cps;
13148 while (*p == ' ') p++;
13149 if (*p == NULLCHAR) return;
13151 if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
13152 if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
13153 if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
13154 if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
13155 if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
13156 if (BoolFeature(&p, "reuse", &val, cps)) {
13157 /* Engine can disable reuse, but can't enable it if user said no */
13158 if (!val) cps->reuse = FALSE;
13161 if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
13162 if (StringFeature(&p, "myname", &cps->tidy, cps)) {
13163 if (gameMode == TwoMachinesPlay) {
13164 DisplayTwoMachinesTitle();
13170 if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
13171 if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
13172 if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
13173 if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
13174 if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
13175 if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
13176 if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
13177 if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
13178 if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
13179 if (IntFeature(&p, "done", &val, cps)) {
13180 FeatureDone(cps, val);
13183 /* Added by Tord: */
13184 if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
13185 if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
13186 /* End of additions by Tord */
13188 /* [HGM] added features: */
13189 if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
13190 if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
13191 if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
13192 if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
13193 if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
13194 if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
13195 if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
13196 if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
13197 sprintf(buf, "rejected option %s\n", cps->option[--cps->nrOptions].name);
13198 SendToProgram(buf, cps);
13201 if(cps->nrOptions >= MAX_OPTIONS) {
13203 sprintf(buf, "%s engine has too many options\n", cps->which);
13204 DisplayError(buf, 0);
13208 if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
13209 /* End of additions by HGM */
13211 /* unknown feature: complain and skip */
13213 while (*q && *q != '=') q++;
13214 sprintf(buf, "rejected %.*s\n", (int)(q-p), p);
13215 SendToProgram(buf, cps);
13221 while (*p && *p != '\"') p++;
13222 if (*p == '\"') p++;
13224 while (*p && *p != ' ') p++;
13232 PeriodicUpdatesEvent(newState)
13235 if (newState == appData.periodicUpdates)
13238 appData.periodicUpdates=newState;
13240 /* Display type changes, so update it now */
13241 // DisplayAnalysis();
13243 /* Get the ball rolling again... */
13245 AnalysisPeriodicEvent(1);
13246 StartAnalysisClock();
13251 PonderNextMoveEvent(newState)
13254 if (newState == appData.ponderNextMove) return;
13255 if (gameMode == EditPosition) EditPositionDone(TRUE);
13257 SendToProgram("hard\n", &first);
13258 if (gameMode == TwoMachinesPlay) {
13259 SendToProgram("hard\n", &second);
13262 SendToProgram("easy\n", &first);
13263 thinkOutput[0] = NULLCHAR;
13264 if (gameMode == TwoMachinesPlay) {
13265 SendToProgram("easy\n", &second);
13268 appData.ponderNextMove = newState;
13272 NewSettingEvent(option, command, value)
13278 if (gameMode == EditPosition) EditPositionDone(TRUE);
13279 sprintf(buf, "%s%s %d\n", (option ? "option ": ""), command, value);
13280 SendToProgram(buf, &first);
13281 if (gameMode == TwoMachinesPlay) {
13282 SendToProgram(buf, &second);
13287 ShowThinkingEvent()
13288 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
13290 static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
13291 int newState = appData.showThinking
13292 // [HGM] thinking: other features now need thinking output as well
13293 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
13295 if (oldState == newState) return;
13296 oldState = newState;
13297 if (gameMode == EditPosition) EditPositionDone(TRUE);
13299 SendToProgram("post\n", &first);
13300 if (gameMode == TwoMachinesPlay) {
13301 SendToProgram("post\n", &second);
13304 SendToProgram("nopost\n", &first);
13305 thinkOutput[0] = NULLCHAR;
13306 if (gameMode == TwoMachinesPlay) {
13307 SendToProgram("nopost\n", &second);
13310 // appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
13314 AskQuestionEvent(title, question, replyPrefix, which)
13315 char *title; char *question; char *replyPrefix; char *which;
13317 ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
13318 if (pr == NoProc) return;
13319 AskQuestion(title, question, replyPrefix, pr);
13323 DisplayMove(moveNumber)
13326 char message[MSG_SIZ];
13328 char cpThinkOutput[MSG_SIZ];
13330 if(appData.noGUI) return; // [HGM] fast: suppress display of moves
13332 if (moveNumber == forwardMostMove - 1 ||
13333 gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13335 safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput));
13337 if (strchr(cpThinkOutput, '\n')) {
13338 *strchr(cpThinkOutput, '\n') = NULLCHAR;
13341 *cpThinkOutput = NULLCHAR;
13344 /* [AS] Hide thinking from human user */
13345 if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
13346 *cpThinkOutput = NULLCHAR;
13347 if( thinkOutput[0] != NULLCHAR ) {
13350 for( i=0; i<=hiddenThinkOutputState; i++ ) {
13351 cpThinkOutput[i] = '.';
13353 cpThinkOutput[i] = NULLCHAR;
13354 hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
13358 if (moveNumber == forwardMostMove - 1 &&
13359 gameInfo.resultDetails != NULL) {
13360 if (gameInfo.resultDetails[0] == NULLCHAR) {
13361 sprintf(res, " %s", PGNResult(gameInfo.result));
13363 sprintf(res, " {%s} %s",
13364 gameInfo.resultDetails, PGNResult(gameInfo.result));
13370 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
13371 DisplayMessage(res, cpThinkOutput);
13373 sprintf(message, "%d.%s%s%s", moveNumber / 2 + 1,
13374 WhiteOnMove(moveNumber) ? " " : ".. ",
13375 parseList[moveNumber], res);
13376 DisplayMessage(message, cpThinkOutput);
13381 DisplayComment(moveNumber, text)
13385 char title[MSG_SIZ];
13386 char buf[8000]; // comment can be long!
13389 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
13390 strcpy(title, "Comment");
13392 sprintf(title, "Comment on %d.%s%s", moveNumber / 2 + 1,
13393 WhiteOnMove(moveNumber) ? " " : ".. ",
13394 parseList[moveNumber]);
13396 // [HGM] PV info: display PV info together with (or as) comment
13397 if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {
13398 if(text == NULL) text = "";
13399 score = pvInfoList[moveNumber].score;
13400 sprintf(buf, "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,
13401 depth, (pvInfoList[moveNumber].time+50)/100, text);
13404 if (text != NULL && (appData.autoDisplayComment || commentUp))
13405 CommentPopUp(title, text);
13408 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
13409 * might be busy thinking or pondering. It can be omitted if your
13410 * gnuchess is configured to stop thinking immediately on any user
13411 * input. However, that gnuchess feature depends on the FIONREAD
13412 * ioctl, which does not work properly on some flavors of Unix.
13416 ChessProgramState *cps;
13419 if (!cps->useSigint) return;
13420 if (appData.noChessProgram || (cps->pr == NoProc)) return;
13421 switch (gameMode) {
13422 case MachinePlaysWhite:
13423 case MachinePlaysBlack:
13424 case TwoMachinesPlay:
13425 case IcsPlayingWhite:
13426 case IcsPlayingBlack:
13429 /* Skip if we know it isn't thinking */
13430 if (!cps->maybeThinking) return;
13431 if (appData.debugMode)
13432 fprintf(debugFP, "Interrupting %s\n", cps->which);
13433 InterruptChildProcess(cps->pr);
13434 cps->maybeThinking = FALSE;
13439 #endif /*ATTENTION*/
13445 if (whiteTimeRemaining <= 0) {
13448 if (appData.icsActive) {
13449 if (appData.autoCallFlag &&
13450 gameMode == IcsPlayingBlack && !blackFlag) {
13451 SendToICS(ics_prefix);
13452 SendToICS("flag\n");
13456 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
13458 if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
13459 if (appData.autoCallFlag) {
13460 GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
13467 if (blackTimeRemaining <= 0) {
13470 if (appData.icsActive) {
13471 if (appData.autoCallFlag &&
13472 gameMode == IcsPlayingWhite && !whiteFlag) {
13473 SendToICS(ics_prefix);
13474 SendToICS("flag\n");
13478 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
13480 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
13481 if (appData.autoCallFlag) {
13482 GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
13495 if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
13496 gameMode == PlayFromGameFile || forwardMostMove == 0) return;
13499 * add time to clocks when time control is achieved ([HGM] now also used for increment)
13501 if ( !WhiteOnMove(forwardMostMove) )
13502 /* White made time control */
13503 whiteTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
13504 /* [HGM] time odds: correct new time quota for time odds! */
13505 / WhitePlayer()->timeOdds;
13507 /* Black made time control */
13508 blackTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
13509 / WhitePlayer()->other->timeOdds;
13513 DisplayBothClocks()
13515 int wom = gameMode == EditPosition ?
13516 !blackPlaysFirst : WhiteOnMove(currentMove);
13517 DisplayWhiteClock(whiteTimeRemaining, wom);
13518 DisplayBlackClock(blackTimeRemaining, !wom);
13522 /* Timekeeping seems to be a portability nightmare. I think everyone
13523 has ftime(), but I'm really not sure, so I'm including some ifdefs
13524 to use other calls if you don't. Clocks will be less accurate if
13525 you have neither ftime nor gettimeofday.
13528 /* VS 2008 requires the #include outside of the function */
13529 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
13530 #include <sys/timeb.h>
13533 /* Get the current time as a TimeMark */
13538 #if HAVE_GETTIMEOFDAY
13540 struct timeval timeVal;
13541 struct timezone timeZone;
13543 gettimeofday(&timeVal, &timeZone);
13544 tm->sec = (long) timeVal.tv_sec;
13545 tm->ms = (int) (timeVal.tv_usec / 1000L);
13547 #else /*!HAVE_GETTIMEOFDAY*/
13550 // include <sys/timeb.h> / moved to just above start of function
13551 struct timeb timeB;
13554 tm->sec = (long) timeB.time;
13555 tm->ms = (int) timeB.millitm;
13557 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
13558 tm->sec = (long) time(NULL);
13564 /* Return the difference in milliseconds between two
13565 time marks. We assume the difference will fit in a long!
13568 SubtractTimeMarks(tm2, tm1)
13569 TimeMark *tm2, *tm1;
13571 return 1000L*(tm2->sec - tm1->sec) +
13572 (long) (tm2->ms - tm1->ms);
13577 * Code to manage the game clocks.
13579 * In tournament play, black starts the clock and then white makes a move.
13580 * We give the human user a slight advantage if he is playing white---the
13581 * clocks don't run until he makes his first move, so it takes zero time.
13582 * Also, we don't account for network lag, so we could get out of sync
13583 * with GNU Chess's clock -- but then, referees are always right.
13586 static TimeMark tickStartTM;
13587 static long intendedTickLength;
13590 NextTickLength(timeRemaining)
13591 long timeRemaining;
13593 long nominalTickLength, nextTickLength;
13595 if (timeRemaining > 0L && timeRemaining <= 10000L)
13596 nominalTickLength = 100L;
13598 nominalTickLength = 1000L;
13599 nextTickLength = timeRemaining % nominalTickLength;
13600 if (nextTickLength <= 0) nextTickLength += nominalTickLength;
13602 return nextTickLength;
13605 /* Adjust clock one minute up or down */
13607 AdjustClock(Boolean which, int dir)
13609 if(which) blackTimeRemaining += 60000*dir;
13610 else whiteTimeRemaining += 60000*dir;
13611 DisplayBothClocks();
13614 /* Stop clocks and reset to a fresh time control */
13618 (void) StopClockTimer();
13619 if (appData.icsActive) {
13620 whiteTimeRemaining = blackTimeRemaining = 0;
13621 } else if (searchTime) {
13622 whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
13623 blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
13624 } else { /* [HGM] correct new time quote for time odds */
13625 whiteTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->timeOdds;
13626 blackTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->other->timeOdds;
13628 if (whiteFlag || blackFlag) {
13630 whiteFlag = blackFlag = FALSE;
13632 DisplayBothClocks();
13635 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
13637 /* Decrement running clock by amount of time that has passed */
13641 long timeRemaining;
13642 long lastTickLength, fudge;
13645 if (!appData.clockMode) return;
13646 if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
13650 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13652 /* Fudge if we woke up a little too soon */
13653 fudge = intendedTickLength - lastTickLength;
13654 if (fudge < 0 || fudge > FUDGE) fudge = 0;
13656 if (WhiteOnMove(forwardMostMove)) {
13657 if(whiteNPS >= 0) lastTickLength = 0;
13658 timeRemaining = whiteTimeRemaining -= lastTickLength;
13659 DisplayWhiteClock(whiteTimeRemaining - fudge,
13660 WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
13662 if(blackNPS >= 0) lastTickLength = 0;
13663 timeRemaining = blackTimeRemaining -= lastTickLength;
13664 DisplayBlackClock(blackTimeRemaining - fudge,
13665 !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
13668 if (CheckFlags()) return;
13671 intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
13672 StartClockTimer(intendedTickLength);
13674 /* if the time remaining has fallen below the alarm threshold, sound the
13675 * alarm. if the alarm has sounded and (due to a takeback or time control
13676 * with increment) the time remaining has increased to a level above the
13677 * threshold, reset the alarm so it can sound again.
13680 if (appData.icsActive && appData.icsAlarm) {
13682 /* make sure we are dealing with the user's clock */
13683 if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
13684 ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
13687 if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
13688 alarmSounded = FALSE;
13689 } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
13691 alarmSounded = TRUE;
13697 /* A player has just moved, so stop the previously running
13698 clock and (if in clock mode) start the other one.
13699 We redisplay both clocks in case we're in ICS mode, because
13700 ICS gives us an update to both clocks after every move.
13701 Note that this routine is called *after* forwardMostMove
13702 is updated, so the last fractional tick must be subtracted
13703 from the color that is *not* on move now.
13708 long lastTickLength;
13710 int flagged = FALSE;
13714 if (StopClockTimer() && appData.clockMode) {
13715 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13716 if (WhiteOnMove(forwardMostMove)) {
13717 if(blackNPS >= 0) lastTickLength = 0;
13718 blackTimeRemaining -= lastTickLength;
13719 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
13720 // if(pvInfoList[forwardMostMove-1].time == -1)
13721 pvInfoList[forwardMostMove-1].time = // use GUI time
13722 (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
13724 if(whiteNPS >= 0) lastTickLength = 0;
13725 whiteTimeRemaining -= lastTickLength;
13726 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
13727 // if(pvInfoList[forwardMostMove-1].time == -1)
13728 pvInfoList[forwardMostMove-1].time =
13729 (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
13731 flagged = CheckFlags();
13733 CheckTimeControl();
13735 if (flagged || !appData.clockMode) return;
13737 switch (gameMode) {
13738 case MachinePlaysBlack:
13739 case MachinePlaysWhite:
13740 case BeginningOfGame:
13741 if (pausing) return;
13745 case PlayFromGameFile:
13753 if (searchTime) { // [HGM] st: set clock of player that has to move to max time
13754 if(WhiteOnMove(forwardMostMove))
13755 whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
13756 else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
13760 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
13761 whiteTimeRemaining : blackTimeRemaining);
13762 StartClockTimer(intendedTickLength);
13766 /* Stop both clocks */
13770 long lastTickLength;
13773 if (!StopClockTimer()) return;
13774 if (!appData.clockMode) return;
13778 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13779 if (WhiteOnMove(forwardMostMove)) {
13780 if(whiteNPS >= 0) lastTickLength = 0;
13781 whiteTimeRemaining -= lastTickLength;
13782 DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
13784 if(blackNPS >= 0) lastTickLength = 0;
13785 blackTimeRemaining -= lastTickLength;
13786 DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
13791 /* Start clock of player on move. Time may have been reset, so
13792 if clock is already running, stop and restart it. */
13796 (void) StopClockTimer(); /* in case it was running already */
13797 DisplayBothClocks();
13798 if (CheckFlags()) return;
13800 if (!appData.clockMode) return;
13801 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
13803 GetTimeMark(&tickStartTM);
13804 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
13805 whiteTimeRemaining : blackTimeRemaining);
13807 /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
13808 whiteNPS = blackNPS = -1;
13809 if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
13810 || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
13811 whiteNPS = first.nps;
13812 if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
13813 || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
13814 blackNPS = first.nps;
13815 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
13816 whiteNPS = second.nps;
13817 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
13818 blackNPS = second.nps;
13819 if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
13821 StartClockTimer(intendedTickLength);
13828 long second, minute, hour, day;
13830 static char buf[32];
13832 if (ms > 0 && ms <= 9900) {
13833 /* convert milliseconds to tenths, rounding up */
13834 double tenths = floor( ((double)(ms + 99L)) / 100.00 );
13836 sprintf(buf, " %03.1f ", tenths/10.0);
13840 /* convert milliseconds to seconds, rounding up */
13841 /* use floating point to avoid strangeness of integer division
13842 with negative dividends on many machines */
13843 second = (long) floor(((double) (ms + 999L)) / 1000.0);
13850 day = second / (60 * 60 * 24);
13851 second = second % (60 * 60 * 24);
13852 hour = second / (60 * 60);
13853 second = second % (60 * 60);
13854 minute = second / 60;
13855 second = second % 60;
13858 sprintf(buf, " %s%ld:%02ld:%02ld:%02ld ",
13859 sign, day, hour, minute, second);
13861 sprintf(buf, " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
13863 sprintf(buf, " %s%2ld:%02ld ", sign, minute, second);
13870 * This is necessary because some C libraries aren't ANSI C compliant yet.
13873 StrStr(string, match)
13874 char *string, *match;
13878 length = strlen(match);
13880 for (i = strlen(string) - length; i >= 0; i--, string++)
13881 if (!strncmp(match, string, length))
13888 StrCaseStr(string, match)
13889 char *string, *match;
13893 length = strlen(match);
13895 for (i = strlen(string) - length; i >= 0; i--, string++) {
13896 for (j = 0; j < length; j++) {
13897 if (ToLower(match[j]) != ToLower(string[j]))
13900 if (j == length) return string;
13914 c1 = ToLower(*s1++);
13915 c2 = ToLower(*s2++);
13916 if (c1 > c2) return 1;
13917 if (c1 < c2) return -1;
13918 if (c1 == NULLCHAR) return 0;
13927 return isupper(c) ? tolower(c) : c;
13935 return islower(c) ? toupper(c) : c;
13937 #endif /* !_amigados */
13945 if ((ret = (char *) malloc(strlen(s) + 1))) {
13952 StrSavePtr(s, savePtr)
13953 char *s, **savePtr;
13958 if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
13959 strcpy(*savePtr, s);
13971 clock = time((time_t *)NULL);
13972 tm = localtime(&clock);
13973 sprintf(buf, "%04d.%02d.%02d",
13974 tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
13975 return StrSave(buf);
13980 PositionToFEN(move, overrideCastling)
13982 char *overrideCastling;
13984 int i, j, fromX, fromY, toX, toY;
13991 whiteToPlay = (gameMode == EditPosition) ?
13992 !blackPlaysFirst : (move % 2 == 0);
13995 /* Piece placement data */
13996 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13998 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
13999 if (boards[move][i][j] == EmptySquare) {
14001 } else { ChessSquare piece = boards[move][i][j];
14002 if (emptycount > 0) {
14003 if(emptycount<10) /* [HGM] can be >= 10 */
14004 *p++ = '0' + emptycount;
14005 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
14008 if(PieceToChar(piece) == '+') {
14009 /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
14011 piece = (ChessSquare)(DEMOTED piece);
14013 *p++ = PieceToChar(piece);
14015 /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
14016 p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
14021 if (emptycount > 0) {
14022 if(emptycount<10) /* [HGM] can be >= 10 */
14023 *p++ = '0' + emptycount;
14024 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
14031 /* [HGM] print Crazyhouse or Shogi holdings */
14032 if( gameInfo.holdingsWidth ) {
14033 *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
14035 for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
14036 piece = boards[move][i][BOARD_WIDTH-1];
14037 if( piece != EmptySquare )
14038 for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
14039 *p++ = PieceToChar(piece);
14041 for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
14042 piece = boards[move][BOARD_HEIGHT-i-1][0];
14043 if( piece != EmptySquare )
14044 for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
14045 *p++ = PieceToChar(piece);
14048 if( q == p ) *p++ = '-';
14054 *p++ = whiteToPlay ? 'w' : 'b';
14057 if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
14058 while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
14060 if(nrCastlingRights) {
14062 if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
14063 /* [HGM] write directly from rights */
14064 if(boards[move][CASTLING][2] != NoRights &&
14065 boards[move][CASTLING][0] != NoRights )
14066 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
14067 if(boards[move][CASTLING][2] != NoRights &&
14068 boards[move][CASTLING][1] != NoRights )
14069 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
14070 if(boards[move][CASTLING][5] != NoRights &&
14071 boards[move][CASTLING][3] != NoRights )
14072 *p++ = boards[move][CASTLING][3] + AAA;
14073 if(boards[move][CASTLING][5] != NoRights &&
14074 boards[move][CASTLING][4] != NoRights )
14075 *p++ = boards[move][CASTLING][4] + AAA;
14078 /* [HGM] write true castling rights */
14079 if( nrCastlingRights == 6 ) {
14080 if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
14081 boards[move][CASTLING][2] != NoRights ) *p++ = 'K';
14082 if(boards[move][CASTLING][1] == BOARD_LEFT &&
14083 boards[move][CASTLING][2] != NoRights ) *p++ = 'Q';
14084 if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
14085 boards[move][CASTLING][5] != NoRights ) *p++ = 'k';
14086 if(boards[move][CASTLING][4] == BOARD_LEFT &&
14087 boards[move][CASTLING][5] != NoRights ) *p++ = 'q';
14090 if (q == p) *p++ = '-'; /* No castling rights */
14094 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
14095 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
14096 /* En passant target square */
14097 if (move > backwardMostMove) {
14098 fromX = moveList[move - 1][0] - AAA;
14099 fromY = moveList[move - 1][1] - ONE;
14100 toX = moveList[move - 1][2] - AAA;
14101 toY = moveList[move - 1][3] - ONE;
14102 if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
14103 toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
14104 boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
14106 /* 2-square pawn move just happened */
14108 *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
14112 } else if(move == backwardMostMove) {
14113 // [HGM] perhaps we should always do it like this, and forget the above?
14114 if((signed char)boards[move][EP_STATUS] >= 0) {
14115 *p++ = boards[move][EP_STATUS] + AAA;
14116 *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
14127 /* [HGM] find reversible plies */
14128 { int i = 0, j=move;
14130 if (appData.debugMode) { int k;
14131 fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
14132 for(k=backwardMostMove; k<=forwardMostMove; k++)
14133 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
14137 while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
14138 if( j == backwardMostMove ) i += initialRulePlies;
14139 sprintf(p, "%d ", i);
14140 p += i>=100 ? 4 : i >= 10 ? 3 : 2;
14142 /* Fullmove number */
14143 sprintf(p, "%d", (move / 2) + 1);
14145 return StrSave(buf);
14149 ParseFEN(board, blackPlaysFirst, fen)
14151 int *blackPlaysFirst;
14161 /* [HGM] by default clear Crazyhouse holdings, if present */
14162 if(gameInfo.holdingsWidth) {
14163 for(i=0; i<BOARD_HEIGHT; i++) {
14164 board[i][0] = EmptySquare; /* black holdings */
14165 board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
14166 board[i][1] = (ChessSquare) 0; /* black counts */
14167 board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
14171 /* Piece placement data */
14172 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
14175 if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
14176 if (*p == '/') p++;
14177 emptycount = gameInfo.boardWidth - j;
14178 while (emptycount--)
14179 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
14181 #if(BOARD_FILES >= 10)
14182 } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
14183 p++; emptycount=10;
14184 if (j + emptycount > gameInfo.boardWidth) return FALSE;
14185 while (emptycount--)
14186 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
14188 } else if (isdigit(*p)) {
14189 emptycount = *p++ - '0';
14190 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
14191 if (j + emptycount > gameInfo.boardWidth) return FALSE;
14192 while (emptycount--)
14193 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
14194 } else if (*p == '+' || isalpha(*p)) {
14195 if (j >= gameInfo.boardWidth) return FALSE;
14197 piece = CharToPiece(*++p);
14198 if(piece == EmptySquare) return FALSE; /* unknown piece */
14199 piece = (ChessSquare) (PROMOTED piece ); p++;
14200 if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
14201 } else piece = CharToPiece(*p++);
14203 if(piece==EmptySquare) return FALSE; /* unknown piece */
14204 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
14205 piece = (ChessSquare) (PROMOTED piece);
14206 if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
14209 board[i][(j++)+gameInfo.holdingsWidth] = piece;
14215 while (*p == '/' || *p == ' ') p++;
14217 /* [HGM] look for Crazyhouse holdings here */
14218 while(*p==' ') p++;
14219 if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
14221 if(*p == '-' ) *p++; /* empty holdings */ else {
14222 if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
14223 /* if we would allow FEN reading to set board size, we would */
14224 /* have to add holdings and shift the board read so far here */
14225 while( (piece = CharToPiece(*p) ) != EmptySquare ) {
14227 if((int) piece >= (int) BlackPawn ) {
14228 i = (int)piece - (int)BlackPawn;
14229 i = PieceToNumber((ChessSquare)i);
14230 if( i >= gameInfo.holdingsSize ) return FALSE;
14231 board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
14232 board[BOARD_HEIGHT-1-i][1]++; /* black counts */
14234 i = (int)piece - (int)WhitePawn;
14235 i = PieceToNumber((ChessSquare)i);
14236 if( i >= gameInfo.holdingsSize ) return FALSE;
14237 board[i][BOARD_WIDTH-1] = piece; /* white holdings */
14238 board[i][BOARD_WIDTH-2]++; /* black holdings */
14242 if(*p == ']') *p++;
14245 while(*p == ' ') p++;
14250 *blackPlaysFirst = FALSE;
14253 *blackPlaysFirst = TRUE;
14259 /* [HGM] We NO LONGER ignore the rest of the FEN notation */
14260 /* return the extra info in global variiables */
14262 /* set defaults in case FEN is incomplete */
14263 board[EP_STATUS] = EP_UNKNOWN;
14264 for(i=0; i<nrCastlingRights; i++ ) {
14265 board[CASTLING][i] =
14266 gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
14267 } /* assume possible unless obviously impossible */
14268 if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
14269 if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
14270 if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
14271 && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
14272 if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
14273 if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
14274 if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
14275 && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
14278 while(*p==' ') p++;
14279 if(nrCastlingRights) {
14280 if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
14281 /* castling indicator present, so default becomes no castlings */
14282 for(i=0; i<nrCastlingRights; i++ ) {
14283 board[CASTLING][i] = NoRights;
14286 while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
14287 (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
14288 ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
14289 ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth) ) {
14290 char c = *p++; int whiteKingFile=NoRights, blackKingFile=NoRights;
14292 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
14293 if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
14294 if(board[0 ][i] == WhiteKing) whiteKingFile = i;
14296 if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
14297 whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
14298 if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
14299 && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
14300 if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
14301 && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
14304 for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
14305 board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
14306 board[CASTLING][2] = whiteKingFile;
14309 for(i=BOARD_LEFT; i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
14310 board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
14311 board[CASTLING][2] = whiteKingFile;
14314 for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
14315 board[CASTLING][3] = i != blackKingFile ? i : NoRights;
14316 board[CASTLING][5] = blackKingFile;
14319 for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
14320 board[CASTLING][4] = i != blackKingFile ? i : NoRights;
14321 board[CASTLING][5] = blackKingFile;
14324 default: /* FRC castlings */
14325 if(c >= 'a') { /* black rights */
14326 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
14327 if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
14328 if(i == BOARD_RGHT) break;
14329 board[CASTLING][5] = i;
14331 if(board[BOARD_HEIGHT-1][c] < BlackPawn ||
14332 board[BOARD_HEIGHT-1][c] >= BlackKing ) break;
14334 board[CASTLING][3] = c;
14336 board[CASTLING][4] = c;
14337 } else { /* white rights */
14338 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
14339 if(board[0][i] == WhiteKing) break;
14340 if(i == BOARD_RGHT) break;
14341 board[CASTLING][2] = i;
14342 c -= AAA - 'a' + 'A';
14343 if(board[0][c] >= WhiteKing) break;
14345 board[CASTLING][0] = c;
14347 board[CASTLING][1] = c;
14351 for(i=0; i<nrCastlingRights; i++)
14352 if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
14353 if (appData.debugMode) {
14354 fprintf(debugFP, "FEN castling rights:");
14355 for(i=0; i<nrCastlingRights; i++)
14356 fprintf(debugFP, " %d", board[CASTLING][i]);
14357 fprintf(debugFP, "\n");
14360 while(*p==' ') p++;
14363 /* read e.p. field in games that know e.p. capture */
14364 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
14365 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
14367 p++; board[EP_STATUS] = EP_NONE;
14369 char c = *p++ - AAA;
14371 if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
14372 if(*p >= '0' && *p <='9') *p++;
14373 board[EP_STATUS] = c;
14378 if(sscanf(p, "%d", &i) == 1) {
14379 FENrulePlies = i; /* 50-move ply counter */
14380 /* (The move number is still ignored) */
14387 EditPositionPasteFEN(char *fen)
14390 Board initial_position;
14392 if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
14393 DisplayError(_("Bad FEN position in clipboard"), 0);
14396 int savedBlackPlaysFirst = blackPlaysFirst;
14397 EditPositionEvent();
14398 blackPlaysFirst = savedBlackPlaysFirst;
14399 CopyBoard(boards[0], initial_position);
14400 initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
14401 EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
14402 DisplayBothClocks();
14403 DrawPosition(FALSE, boards[currentMove]);
14408 static char cseq[12] = "\\ ";
14410 Boolean set_cont_sequence(char *new_seq)
14415 // handle bad attempts to set the sequence
14417 return 0; // acceptable error - no debug
14419 len = strlen(new_seq);
14420 ret = (len > 0) && (len < sizeof(cseq));
14422 strcpy(cseq, new_seq);
14423 else if (appData.debugMode)
14424 fprintf(debugFP, "Invalid continuation sequence \"%s\" (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
14429 reformat a source message so words don't cross the width boundary. internal
14430 newlines are not removed. returns the wrapped size (no null character unless
14431 included in source message). If dest is NULL, only calculate the size required
14432 for the dest buffer. lp argument indicats line position upon entry, and it's
14433 passed back upon exit.
14435 int wrap(char *dest, char *src, int count, int width, int *lp)
14437 int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
14439 cseq_len = strlen(cseq);
14440 old_line = line = *lp;
14441 ansi = len = clen = 0;
14443 for (i=0; i < count; i++)
14445 if (src[i] == '\033')
14448 // if we hit the width, back up
14449 if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
14451 // store i & len in case the word is too long
14452 old_i = i, old_len = len;
14454 // find the end of the last word
14455 while (i && src[i] != ' ' && src[i] != '\n')
14461 // word too long? restore i & len before splitting it
14462 if ((old_i-i+clen) >= width)
14469 if (i && src[i-1] == ' ')
14472 if (src[i] != ' ' && src[i] != '\n')
14479 // now append the newline and continuation sequence
14484 strncpy(dest+len, cseq, cseq_len);
14492 dest[len] = src[i];
14496 if (src[i] == '\n')
14501 if (dest && appData.debugMode)
14503 fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
14504 count, width, line, len, *lp);
14505 show_bytes(debugFP, src, count);
14506 fprintf(debugFP, "\ndest: ");
14507 show_bytes(debugFP, dest, len);
14508 fprintf(debugFP, "\n");
14510 *lp = dest ? line : old_line;
14515 // [HGM] vari: routines for shelving variations
14518 PushTail(int firstMove, int lastMove)
14520 int i, j, nrMoves = lastMove - firstMove;
14522 if(appData.icsActive) { // only in local mode
14523 forwardMostMove = currentMove; // mimic old ICS behavior
14526 if(storedGames >= MAX_VARIATIONS-1) return;
14528 // push current tail of game on stack
14529 savedResult[storedGames] = gameInfo.result;
14530 savedDetails[storedGames] = gameInfo.resultDetails;
14531 gameInfo.resultDetails = NULL;
14532 savedFirst[storedGames] = firstMove;
14533 savedLast [storedGames] = lastMove;
14534 savedFramePtr[storedGames] = framePtr;
14535 framePtr -= nrMoves; // reserve space for the boards
14536 for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
14537 CopyBoard(boards[framePtr+i], boards[firstMove+i]);
14538 for(j=0; j<MOVE_LEN; j++)
14539 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
14540 for(j=0; j<2*MOVE_LEN; j++)
14541 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
14542 timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
14543 timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
14544 pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
14545 pvInfoList[firstMove+i-1].depth = 0;
14546 commentList[framePtr+i] = commentList[firstMove+i];
14547 commentList[firstMove+i] = NULL;
14551 forwardMostMove = currentMove; // truncte game so we can start variation
14552 if(storedGames == 1) GreyRevert(FALSE);
14556 PopTail(Boolean annotate)
14559 char buf[8000], moveBuf[20];
14561 if(appData.icsActive) return FALSE; // only in local mode
14562 if(!storedGames) return FALSE; // sanity
14565 ToNrEvent(savedFirst[storedGames]); // sets currentMove
14566 nrMoves = savedLast[storedGames] - currentMove;
14569 if(!WhiteOnMove(currentMove)) sprintf(buf, "(%d...", currentMove+2>>1);
14570 else strcpy(buf, "(");
14571 for(i=currentMove; i<forwardMostMove; i++) {
14573 sprintf(moveBuf, " %d. %s", i+2>>1, SavePart(parseList[i]));
14574 else sprintf(moveBuf, " %s", SavePart(parseList[i]));
14575 strcat(buf, moveBuf);
14576 if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
14580 for(i=1; i<nrMoves; i++) { // copy last variation back
14581 CopyBoard(boards[currentMove+i], boards[framePtr+i]);
14582 for(j=0; j<MOVE_LEN; j++)
14583 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
14584 for(j=0; j<2*MOVE_LEN; j++)
14585 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
14586 timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
14587 timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
14588 pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
14589 if(commentList[currentMove+i]) free(commentList[currentMove+i]);
14590 commentList[currentMove+i] = commentList[framePtr+i];
14591 commentList[framePtr+i] = NULL;
14593 if(annotate) AppendComment(currentMove+1, buf, FALSE);
14594 framePtr = savedFramePtr[storedGames];
14595 gameInfo.result = savedResult[storedGames];
14596 if(gameInfo.resultDetails != NULL) {
14597 free(gameInfo.resultDetails);
14599 gameInfo.resultDetails = savedDetails[storedGames];
14600 forwardMostMove = currentMove + nrMoves;
14601 if(storedGames == 0) GreyRevert(TRUE);
14607 { // remove all shelved variations
14609 for(i=0; i<storedGames; i++) {
14610 if(savedDetails[i])
14611 free(savedDetails[i]);
14612 savedDetails[i] = NULL;
14614 for(i=framePtr; i<MAX_MOVES; i++) {
14615 if(commentList[i]) free(commentList[i]);
14616 commentList[i] = NULL;
14618 framePtr = MAX_MOVES-1;