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,
163 Board board, char *castle, char *ep));
164 void MakeMove P((int fromX, int fromY, int toX, int toY, int promoChar));
165 void ShowMove P((int fromX, int fromY, int toX, int toY));
166 int FinishMove P((ChessMove moveType, int fromX, int fromY, int toX, int toY,
167 /*char*/int promoChar));
168 void BackwardInner P((int target));
169 void ForwardInner P((int target));
170 void GameEnds P((ChessMove result, char *resultDetails, int whosays));
171 void EditPositionDone P((void));
172 void PrintOpponents P((FILE *fp));
173 void PrintPosition P((FILE *fp, int move));
174 void StartChessProgram P((ChessProgramState *cps));
175 void SendToProgram P((char *message, ChessProgramState *cps));
176 void SendMoveToProgram P((int moveNum, ChessProgramState *cps));
177 void ReceiveFromProgram P((InputSourceRef isr, VOIDSTAR closure,
178 char *buf, int count, int error));
179 void SendTimeControl P((ChessProgramState *cps,
180 int mps, long tc, int inc, int sd, int st));
181 char *TimeControlTagValue P((void));
182 void Attention P((ChessProgramState *cps));
183 void FeedMovesToProgram P((ChessProgramState *cps, int upto));
184 void ResurrectChessProgram P((void));
185 void DisplayComment P((int moveNumber, char *text));
186 void DisplayMove P((int moveNumber));
188 void ParseGameHistory P((char *game));
189 void ParseBoard12 P((char *string));
190 void StartClocks P((void));
191 void SwitchClocks P((void));
192 void StopClocks P((void));
193 void ResetClocks P((void));
194 char *PGNDate P((void));
195 void SetGameInfo P((void));
196 Boolean ParseFEN P((Board board, int *blackPlaysFirst, char *fen));
197 int RegisterMove P((void));
198 void MakeRegisteredMove P((void));
199 void TruncateGame P((void));
200 int looking_at P((char *, int *, char *));
201 void CopyPlayerNameIntoFileName P((char **, char *));
202 char *SavePart P((char *));
203 int SaveGameOldStyle P((FILE *));
204 int SaveGamePGN P((FILE *));
205 void GetTimeMark P((TimeMark *));
206 long SubtractTimeMarks P((TimeMark *, TimeMark *));
207 int CheckFlags P((void));
208 long NextTickLength P((long));
209 void CheckTimeControl P((void));
210 void show_bytes P((FILE *, char *, int));
211 int string_to_rating P((char *str));
212 void ParseFeatures P((char* args, ChessProgramState *cps));
213 void InitBackEnd3 P((void));
214 void FeatureDone P((ChessProgramState* cps, int val));
215 void InitChessProgram P((ChessProgramState *cps, int setup));
216 void OutputKibitz(int window, char *text);
217 int PerpetualChase(int first, int last);
218 int EngineOutputIsUp();
219 void InitDrawingSizes(int x, int y);
222 extern void ConsoleCreate();
225 ChessProgramState *WhitePlayer();
226 void InsertIntoMemo P((int which, char *text)); // [HGM] kibitz: in engineo.c
227 int VerifyDisplayMode P(());
229 char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment
230 void InitEngineUCI( const char * iniDir, ChessProgramState * cps ); // [HGM] moved here from winboard.c
231 char *ProbeBook P((int moveNr, char *book)); // [HGM] book: returns a book move
232 char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book
233 void ics_update_width P((int new_width));
234 extern char installDir[MSG_SIZ];
236 extern int tinyLayout, smallLayout;
237 ChessProgramStats programStats;
238 static int exiting = 0; /* [HGM] moved to top */
239 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
240 int startedFromPositionFile = FALSE; Board filePosition; /* [HGM] loadPos */
241 char endingGame = 0; /* [HGM] crash: flag to prevent recursion of GameEnds() */
242 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS */
243 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
244 int lastIndex = 0; /* [HGM] autoinc: last game/position used in match mode */
245 int opponentKibitzes;
246 int lastSavedGame; /* [HGM] save: ID of game */
247 char chatPartner[MAX_CHAT][MSG_SIZ]; /* [HGM] chat: list of chatting partners */
248 extern int chatCount;
251 /* States for ics_getting_history */
253 #define H_REQUESTED 1
254 #define H_GOT_REQ_HEADER 2
255 #define H_GOT_UNREQ_HEADER 3
256 #define H_GETTING_MOVES 4
257 #define H_GOT_UNWANTED_HEADER 5
259 /* whosays values for GameEnds */
268 /* Maximum number of games in a cmail message */
269 #define CMAIL_MAX_GAMES 20
271 /* Different types of move when calling RegisterMove */
273 #define CMAIL_RESIGN 1
275 #define CMAIL_ACCEPT 3
277 /* Different types of result to remember for each game */
278 #define CMAIL_NOT_RESULT 0
279 #define CMAIL_OLD_RESULT 1
280 #define CMAIL_NEW_RESULT 2
282 /* Telnet protocol constants */
293 static char * safeStrCpy( char * dst, const char * src, size_t count )
295 assert( dst != NULL );
296 assert( src != NULL );
299 strncpy( dst, src, count );
300 dst[ count-1 ] = '\0';
304 /* Some compiler can't cast u64 to double
305 * This function do the job for us:
307 * We use the highest bit for cast, this only
308 * works if the highest bit is not
309 * in use (This should not happen)
311 * We used this for all compiler
314 u64ToDouble(u64 value)
317 u64 tmp = value & u64Const(0x7fffffffffffffff);
318 r = (double)(s64)tmp;
319 if (value & u64Const(0x8000000000000000))
320 r += 9.2233720368547758080e18; /* 2^63 */
324 /* Fake up flags for now, as we aren't keeping track of castling
325 availability yet. [HGM] Change of logic: the flag now only
326 indicates the type of castlings allowed by the rule of the game.
327 The actual rights themselves are maintained in the array
328 castlingRights, as part of the game history, and are not probed
334 int flags = F_ALL_CASTLE_OK;
335 if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
336 switch (gameInfo.variant) {
338 flags &= ~F_ALL_CASTLE_OK;
339 case VariantGiveaway: // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
340 flags |= F_IGNORE_CHECK;
342 flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
345 flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
347 case VariantKriegspiel:
348 flags |= F_KRIEGSPIEL_CAPTURE;
350 case VariantCapaRandom:
351 case VariantFischeRandom:
352 flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
353 case VariantNoCastle:
354 case VariantShatranj:
356 flags &= ~F_ALL_CASTLE_OK;
364 FILE *gameFileFP, *debugFP;
367 [AS] Note: sometimes, the sscanf() function is used to parse the input
368 into a fixed-size buffer. Because of this, we must be prepared to
369 receive strings as long as the size of the input buffer, which is currently
370 set to 4K for Windows and 8K for the rest.
371 So, we must either allocate sufficiently large buffers here, or
372 reduce the size of the input buffer in the input reading part.
375 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
376 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
377 char thinkOutput1[MSG_SIZ*10];
379 ChessProgramState first, second;
381 /* premove variables */
384 int premoveFromX = 0;
385 int premoveFromY = 0;
386 int premovePromoChar = 0;
388 Boolean alarmSounded;
389 /* end premove variables */
391 char *ics_prefix = "$";
392 int ics_type = ICS_GENERIC;
394 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
395 int pauseExamForwardMostMove = 0;
396 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
397 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
398 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
399 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
400 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
401 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
402 int whiteFlag = FALSE, blackFlag = FALSE;
403 int userOfferedDraw = FALSE;
404 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
405 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
406 int cmailMoveType[CMAIL_MAX_GAMES];
407 long ics_clock_paused = 0;
408 ProcRef icsPR = NoProc, cmailPR = NoProc;
409 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
410 GameMode gameMode = BeginningOfGame;
411 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
412 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
413 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
414 int hiddenThinkOutputState = 0; /* [AS] */
415 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
416 int adjudicateLossPlies = 6;
417 char white_holding[64], black_holding[64];
418 TimeMark lastNodeCountTime;
419 long lastNodeCount=0;
420 int have_sent_ICS_logon = 0;
422 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement;
423 long timeControl_2; /* [AS] Allow separate time controls */
424 char *fullTimeControlString = NULL; /* [HGM] secondary TC: merge of MPS, TC and inc */
425 long timeRemaining[2][MAX_MOVES];
427 TimeMark programStartTime;
428 char ics_handle[MSG_SIZ];
429 int have_set_title = 0;
431 /* animateTraining preserves the state of appData.animate
432 * when Training mode is activated. This allows the
433 * response to be animated when appData.animate == TRUE and
434 * appData.animateDragging == TRUE.
436 Boolean animateTraining;
442 Board boards[MAX_MOVES];
443 /* [HGM] Following 7 needed for accurate legality tests: */
444 signed char epStatus[MAX_MOVES];
445 signed char castlingRights[MAX_MOVES][BOARD_SIZE]; // stores files for pieces with castling rights or -1
446 signed char castlingRank[BOARD_SIZE]; // and corresponding ranks
447 signed char initialRights[BOARD_SIZE], FENcastlingRights[BOARD_SIZE], fileRights[BOARD_SIZE];
448 int nrCastlingRights; // For TwoKings, or to implement castling-unknown status
449 int initialRulePlies, FENrulePlies;
451 FILE *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
454 int mute; // mute all sounds
456 ChessSquare FIDEArray[2][BOARD_SIZE] = {
457 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
458 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
459 { BlackRook, BlackKnight, BlackBishop, BlackQueen,
460 BlackKing, BlackBishop, BlackKnight, BlackRook }
463 ChessSquare twoKingsArray[2][BOARD_SIZE] = {
464 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
465 WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
466 { BlackRook, BlackKnight, BlackBishop, BlackQueen,
467 BlackKing, BlackKing, BlackKnight, BlackRook }
470 ChessSquare KnightmateArray[2][BOARD_SIZE] = {
471 { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
472 WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
473 { BlackRook, BlackMan, BlackBishop, BlackQueen,
474 BlackUnicorn, BlackBishop, BlackMan, BlackRook }
477 ChessSquare fairyArray[2][BOARD_SIZE] = { /* [HGM] Queen side differs from King side */
478 { WhiteCannon, WhiteNightrider, WhiteAlfil, WhiteQueen,
479 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
480 { BlackCannon, BlackNightrider, BlackAlfil, BlackQueen,
481 BlackKing, BlackBishop, BlackKnight, BlackRook }
484 ChessSquare ShatranjArray[2][BOARD_SIZE] = { /* [HGM] (movGen knows about Shatranj Q and P) */
485 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
486 WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
487 { BlackRook, BlackKnight, BlackAlfil, BlackKing,
488 BlackFerz, BlackAlfil, BlackKnight, BlackRook }
493 ChessSquare ShogiArray[2][BOARD_SIZE] = {
494 { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
495 WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
496 { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
497 BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
500 ChessSquare XiangqiArray[2][BOARD_SIZE] = {
501 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
502 WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
503 { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
504 BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
507 ChessSquare CapablancaArray[2][BOARD_SIZE] = {
508 { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
509 WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
510 { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
511 BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
514 ChessSquare GreatArray[2][BOARD_SIZE] = {
515 { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
516 WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
517 { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
518 BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
521 ChessSquare JanusArray[2][BOARD_SIZE] = {
522 { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
523 WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
524 { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
525 BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
529 ChessSquare GothicArray[2][BOARD_SIZE] = {
530 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
531 WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
532 { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
533 BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
536 #define GothicArray CapablancaArray
540 ChessSquare FalconArray[2][BOARD_SIZE] = {
541 { WhiteRook, WhiteKnight, WhiteBishop, WhiteLance, WhiteQueen,
542 WhiteKing, WhiteLance, WhiteBishop, WhiteKnight, WhiteRook },
543 { BlackRook, BlackKnight, BlackBishop, BlackLance, BlackQueen,
544 BlackKing, BlackLance, BlackBishop, BlackKnight, BlackRook }
547 #define FalconArray CapablancaArray
550 #else // !(BOARD_SIZE>=10)
551 #define XiangqiPosition FIDEArray
552 #define CapablancaArray FIDEArray
553 #define GothicArray FIDEArray
554 #define GreatArray FIDEArray
555 #endif // !(BOARD_SIZE>=10)
558 ChessSquare CourierArray[2][BOARD_SIZE] = {
559 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
560 WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
561 { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
562 BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
564 #else // !(BOARD_SIZE>=12)
565 #define CourierArray CapablancaArray
566 #endif // !(BOARD_SIZE>=12)
569 Board initialPosition;
572 /* Convert str to a rating. Checks for special cases of "----",
574 "++++", etc. Also strips ()'s */
576 string_to_rating(str)
579 while(*str && !isdigit(*str)) ++str;
581 return 0; /* One of the special "no rating" cases */
589 /* Init programStats */
590 programStats.movelist[0] = 0;
591 programStats.depth = 0;
592 programStats.nr_moves = 0;
593 programStats.moves_left = 0;
594 programStats.nodes = 0;
595 programStats.time = -1; // [HGM] PGNtime: make invalid to recognize engine output
596 programStats.score = 0;
597 programStats.got_only_move = 0;
598 programStats.got_fail = 0;
599 programStats.line_is_book = 0;
605 int matched, min, sec;
607 ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
609 GetTimeMark(&programStartTime);
610 srand(programStartTime.ms); // [HGM] book: makes sure random is unpredictabe to msec level
613 programStats.ok_to_send = 1;
614 programStats.seen_stat = 0;
617 * Initialize game list
623 * Internet chess server status
625 if (appData.icsActive) {
626 appData.matchMode = FALSE;
627 appData.matchGames = 0;
629 appData.noChessProgram = !appData.zippyPlay;
631 appData.zippyPlay = FALSE;
632 appData.zippyTalk = FALSE;
633 appData.noChessProgram = TRUE;
635 if (*appData.icsHelper != NULLCHAR) {
636 appData.useTelnet = TRUE;
637 appData.telnetProgram = appData.icsHelper;
640 appData.zippyTalk = appData.zippyPlay = FALSE;
643 /* [AS] Initialize pv info list [HGM] and game state */
647 for( i=0; i<MAX_MOVES; i++ ) {
648 pvInfoList[i].depth = -1;
650 for( j=0; j<BOARD_SIZE; j++ ) castlingRights[i][j] = -1;
655 * Parse timeControl resource
657 if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
658 appData.movesPerSession)) {
660 snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
661 DisplayFatalError(buf, 0, 2);
665 * Parse searchTime resource
667 if (*appData.searchTime != NULLCHAR) {
668 matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
670 searchTime = min * 60;
671 } else if (matched == 2) {
672 searchTime = min * 60 + sec;
675 snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
676 DisplayFatalError(buf, 0, 2);
680 /* [AS] Adjudication threshold */
681 adjudicateLossThreshold = appData.adjudicateLossThreshold;
683 first.which = "first";
684 second.which = "second";
685 first.maybeThinking = second.maybeThinking = FALSE;
686 first.pr = second.pr = NoProc;
687 first.isr = second.isr = NULL;
688 first.sendTime = second.sendTime = 2;
689 first.sendDrawOffers = 1;
690 if (appData.firstPlaysBlack) {
691 first.twoMachinesColor = "black\n";
692 second.twoMachinesColor = "white\n";
694 first.twoMachinesColor = "white\n";
695 second.twoMachinesColor = "black\n";
697 first.program = appData.firstChessProgram;
698 second.program = appData.secondChessProgram;
699 first.host = appData.firstHost;
700 second.host = appData.secondHost;
701 first.dir = appData.firstDirectory;
702 second.dir = appData.secondDirectory;
703 first.other = &second;
704 second.other = &first;
705 first.initString = appData.initString;
706 second.initString = appData.secondInitString;
707 first.computerString = appData.firstComputerString;
708 second.computerString = appData.secondComputerString;
709 first.useSigint = second.useSigint = TRUE;
710 first.useSigterm = second.useSigterm = TRUE;
711 first.reuse = appData.reuseFirst;
712 second.reuse = appData.reuseSecond;
713 first.nps = appData.firstNPS; // [HGM] nps: copy nodes per second
714 second.nps = appData.secondNPS;
715 first.useSetboard = second.useSetboard = FALSE;
716 first.useSAN = second.useSAN = FALSE;
717 first.usePing = second.usePing = FALSE;
718 first.lastPing = second.lastPing = 0;
719 first.lastPong = second.lastPong = 0;
720 first.usePlayother = second.usePlayother = FALSE;
721 first.useColors = second.useColors = TRUE;
722 first.useUsermove = second.useUsermove = FALSE;
723 first.sendICS = second.sendICS = FALSE;
724 first.sendName = second.sendName = appData.icsActive;
725 first.sdKludge = second.sdKludge = FALSE;
726 first.stKludge = second.stKludge = FALSE;
727 TidyProgramName(first.program, first.host, first.tidy);
728 TidyProgramName(second.program, second.host, second.tidy);
729 first.matchWins = second.matchWins = 0;
730 strcpy(first.variants, appData.variant);
731 strcpy(second.variants, appData.variant);
732 first.analysisSupport = second.analysisSupport = 2; /* detect */
733 first.analyzing = second.analyzing = FALSE;
734 first.initDone = second.initDone = FALSE;
736 /* New features added by Tord: */
737 first.useFEN960 = FALSE; second.useFEN960 = FALSE;
738 first.useOOCastle = TRUE; second.useOOCastle = TRUE;
739 /* End of new features added by Tord. */
740 first.fenOverride = appData.fenOverride1;
741 second.fenOverride = appData.fenOverride2;
743 /* [HGM] time odds: set factor for each machine */
744 first.timeOdds = appData.firstTimeOdds;
745 second.timeOdds = appData.secondTimeOdds;
747 if(appData.timeOddsMode) {
748 norm = first.timeOdds;
749 if(norm > second.timeOdds) norm = second.timeOdds;
751 first.timeOdds /= norm;
752 second.timeOdds /= norm;
755 /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
756 first.accumulateTC = appData.firstAccumulateTC;
757 second.accumulateTC = appData.secondAccumulateTC;
758 first.maxNrOfSessions = second.maxNrOfSessions = 1;
761 first.debug = second.debug = FALSE;
762 first.supportsNPS = second.supportsNPS = UNKNOWN;
765 first.optionSettings = appData.firstOptions;
766 second.optionSettings = appData.secondOptions;
768 first.scoreIsAbsolute = appData.firstScoreIsAbsolute; /* [AS] */
769 second.scoreIsAbsolute = appData.secondScoreIsAbsolute; /* [AS] */
770 first.isUCI = appData.firstIsUCI; /* [AS] */
771 second.isUCI = appData.secondIsUCI; /* [AS] */
772 first.hasOwnBookUCI = appData.firstHasOwnBookUCI; /* [AS] */
773 second.hasOwnBookUCI = appData.secondHasOwnBookUCI; /* [AS] */
775 if (appData.firstProtocolVersion > PROTOVER ||
776 appData.firstProtocolVersion < 1) {
778 sprintf(buf, _("protocol version %d not supported"),
779 appData.firstProtocolVersion);
780 DisplayFatalError(buf, 0, 2);
782 first.protocolVersion = appData.firstProtocolVersion;
785 if (appData.secondProtocolVersion > PROTOVER ||
786 appData.secondProtocolVersion < 1) {
788 sprintf(buf, _("protocol version %d not supported"),
789 appData.secondProtocolVersion);
790 DisplayFatalError(buf, 0, 2);
792 second.protocolVersion = appData.secondProtocolVersion;
795 if (appData.icsActive) {
796 appData.clockMode = TRUE; /* changes dynamically in ICS mode */
797 } else if (*appData.searchTime != NULLCHAR || appData.noChessProgram) {
798 appData.clockMode = FALSE;
799 first.sendTime = second.sendTime = 0;
803 /* Override some settings from environment variables, for backward
804 compatibility. Unfortunately it's not feasible to have the env
805 vars just set defaults, at least in xboard. Ugh.
807 if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
812 if (appData.noChessProgram) {
813 programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
814 sprintf(programVersion, "%s", PACKAGE_STRING);
816 /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
817 programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
818 sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
821 if (!appData.icsActive) {
823 /* Check for variants that are supported only in ICS mode,
824 or not at all. Some that are accepted here nevertheless
825 have bugs; see comments below.
827 VariantClass variant = StringToVariant(appData.variant);
829 case VariantBughouse: /* need four players and two boards */
830 case VariantKriegspiel: /* need to hide pieces and move details */
831 /* case VariantFischeRandom: (Fabien: moved below) */
832 sprintf(buf, _("Variant %s supported only in ICS mode"), appData.variant);
833 DisplayFatalError(buf, 0, 2);
837 case VariantLoadable:
847 sprintf(buf, _("Unknown variant name %s"), appData.variant);
848 DisplayFatalError(buf, 0, 2);
851 case VariantXiangqi: /* [HGM] repetition rules not implemented */
852 case VariantFairy: /* [HGM] TestLegality definitely off! */
853 case VariantGothic: /* [HGM] should work */
854 case VariantCapablanca: /* [HGM] should work */
855 case VariantCourier: /* [HGM] initial forced moves not implemented */
856 case VariantShogi: /* [HGM] drops not tested for legality */
857 case VariantKnightmate: /* [HGM] should work */
858 case VariantCylinder: /* [HGM] untested */
859 case VariantFalcon: /* [HGM] untested */
860 case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
861 offboard interposition not understood */
862 case VariantNormal: /* definitely works! */
863 case VariantWildCastle: /* pieces not automatically shuffled */
864 case VariantNoCastle: /* pieces not automatically shuffled */
865 case VariantFischeRandom: /* [HGM] works and shuffles pieces */
866 case VariantLosers: /* should work except for win condition,
867 and doesn't know captures are mandatory */
868 case VariantSuicide: /* should work except for win condition,
869 and doesn't know captures are mandatory */
870 case VariantGiveaway: /* should work except for win condition,
871 and doesn't know captures are mandatory */
872 case VariantTwoKings: /* should work */
873 case VariantAtomic: /* should work except for win condition */
874 case Variant3Check: /* should work except for win condition */
875 case VariantShatranj: /* should work except for all win conditions */
876 case VariantBerolina: /* might work if TestLegality is off */
877 case VariantCapaRandom: /* should work */
878 case VariantJanus: /* should work */
879 case VariantSuper: /* experimental */
880 case VariantGreat: /* experimental, requires legality testing to be off */
885 InitEngineUCI( installDir, &first ); // [HGM] moved here from winboard.c, to make available in xboard
886 InitEngineUCI( installDir, &second );
889 int NextIntegerFromString( char ** str, long * value )
894 while( *s == ' ' || *s == '\t' ) {
900 if( *s >= '0' && *s <= '9' ) {
901 while( *s >= '0' && *s <= '9' ) {
902 *value = *value * 10 + (*s - '0');
914 int NextTimeControlFromString( char ** str, long * value )
917 int result = NextIntegerFromString( str, &temp );
920 *value = temp * 60; /* Minutes */
923 result = NextIntegerFromString( str, &temp );
924 *value += temp; /* Seconds */
931 int NextSessionFromString( char ** str, int *moves, long * tc, long *inc)
932 { /* [HGM] routine added to read '+moves/time' for secondary time control */
933 int result = -1; long temp, temp2;
935 if(**str != '+') return -1; // old params remain in force!
937 if( NextTimeControlFromString( str, &temp ) ) return -1;
940 /* time only: incremental or sudden-death time control */
941 if(**str == '+') { /* increment follows; read it */
943 if(result = NextIntegerFromString( str, &temp2)) return -1;
946 *moves = 0; *tc = temp * 1000;
948 } else if(temp % 60 != 0) return -1; /* moves was given as min:sec */
950 (*str)++; /* classical time control */
951 result = NextTimeControlFromString( str, &temp2);
960 int GetTimeQuota(int movenr)
961 { /* [HGM] get time to add from the multi-session time-control string */
962 int moves=1; /* kludge to force reading of first session */
963 long time, increment;
964 char *s = fullTimeControlString;
966 if(appData.debugMode) fprintf(debugFP, "TC string = '%s'\n", fullTimeControlString);
968 if(moves) NextSessionFromString(&s, &moves, &time, &increment);
969 if(appData.debugMode) fprintf(debugFP, "mps=%d tc=%d inc=%d\n", moves, (int) time, (int) increment);
970 if(movenr == -1) return time; /* last move before new session */
971 if(!moves) return increment; /* current session is incremental */
972 if(movenr >= 0) movenr -= moves; /* we already finished this session */
973 } while(movenr >= -1); /* try again for next session */
975 return 0; // no new time quota on this move
979 ParseTimeControl(tc, ti, mps)
988 if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
991 sprintf(buf, "+%d/%s+%d", mps, tc, ti);
992 else sprintf(buf, "+%s+%d", tc, ti);
995 sprintf(buf, "+%d/%s", mps, tc);
996 else sprintf(buf, "+%s", tc);
998 fullTimeControlString = StrSave(buf);
1000 if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1005 /* Parse second time control */
1008 if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1016 timeControl_2 = tc2 * 1000;
1026 timeControl = tc1 * 1000;
1029 timeIncrement = ti * 1000; /* convert to ms */
1030 movesPerSession = 0;
1033 movesPerSession = mps;
1041 if (appData.debugMode) {
1042 fprintf(debugFP, "%s\n", programVersion);
1045 if (appData.matchGames > 0) {
1046 appData.matchMode = TRUE;
1047 } else if (appData.matchMode) {
1048 appData.matchGames = 1;
1050 if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1051 appData.matchGames = appData.sameColorGames;
1052 if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1053 if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1054 if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1057 if (appData.noChessProgram || first.protocolVersion == 1) {
1060 /* kludge: allow timeout for initial "feature" commands */
1062 DisplayMessage("", _("Starting chess program"));
1063 ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1068 InitBackEnd3 P((void))
1070 GameMode initialMode;
1074 InitChessProgram(&first, startedFromSetupPosition);
1077 if (appData.icsActive) {
1079 /* [DM] Make a console window if needed [HGM] merged ifs */
1084 if (*appData.icsCommPort != NULLCHAR) {
1085 sprintf(buf, _("Could not open comm port %s"),
1086 appData.icsCommPort);
1088 snprintf(buf, sizeof(buf), _("Could not connect to host %s, port %s"),
1089 appData.icsHost, appData.icsPort);
1091 DisplayFatalError(buf, err, 1);
1096 AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1098 AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1099 } else if (appData.noChessProgram) {
1105 if (*appData.cmailGameName != NULLCHAR) {
1107 OpenLoopback(&cmailPR);
1109 AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1113 DisplayMessage("", "");
1114 if (StrCaseCmp(appData.initialMode, "") == 0) {
1115 initialMode = BeginningOfGame;
1116 } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1117 initialMode = TwoMachinesPlay;
1118 } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1119 initialMode = AnalyzeFile;
1120 } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1121 initialMode = AnalyzeMode;
1122 } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1123 initialMode = MachinePlaysWhite;
1124 } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1125 initialMode = MachinePlaysBlack;
1126 } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1127 initialMode = EditGame;
1128 } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1129 initialMode = EditPosition;
1130 } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1131 initialMode = Training;
1133 sprintf(buf, _("Unknown initialMode %s"), appData.initialMode);
1134 DisplayFatalError(buf, 0, 2);
1138 if (appData.matchMode) {
1139 /* Set up machine vs. machine match */
1140 if (appData.noChessProgram) {
1141 DisplayFatalError(_("Can't have a match with no chess programs"),
1147 if (*appData.loadGameFile != NULLCHAR) {
1148 int index = appData.loadGameIndex; // [HGM] autoinc
1149 if(index<0) lastIndex = index = 1;
1150 if (!LoadGameFromFile(appData.loadGameFile,
1152 appData.loadGameFile, FALSE)) {
1153 DisplayFatalError(_("Bad game file"), 0, 1);
1156 } else if (*appData.loadPositionFile != NULLCHAR) {
1157 int index = appData.loadPositionIndex; // [HGM] autoinc
1158 if(index<0) lastIndex = index = 1;
1159 if (!LoadPositionFromFile(appData.loadPositionFile,
1161 appData.loadPositionFile)) {
1162 DisplayFatalError(_("Bad position file"), 0, 1);
1167 } else if (*appData.cmailGameName != NULLCHAR) {
1168 /* Set up cmail mode */
1169 ReloadCmailMsgEvent(TRUE);
1171 /* Set up other modes */
1172 if (initialMode == AnalyzeFile) {
1173 if (*appData.loadGameFile == NULLCHAR) {
1174 DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1178 if (*appData.loadGameFile != NULLCHAR) {
1179 (void) LoadGameFromFile(appData.loadGameFile,
1180 appData.loadGameIndex,
1181 appData.loadGameFile, TRUE);
1182 } else if (*appData.loadPositionFile != NULLCHAR) {
1183 (void) LoadPositionFromFile(appData.loadPositionFile,
1184 appData.loadPositionIndex,
1185 appData.loadPositionFile);
1186 /* [HGM] try to make self-starting even after FEN load */
1187 /* to allow automatic setup of fairy variants with wtm */
1188 if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1189 gameMode = BeginningOfGame;
1190 setboardSpoiledMachineBlack = 1;
1192 /* [HGM] loadPos: make that every new game uses the setup */
1193 /* from file as long as we do not switch variant */
1194 if(!blackPlaysFirst) { int i;
1195 startedFromPositionFile = TRUE;
1196 CopyBoard(filePosition, boards[0]);
1197 for(i=0; i<BOARD_SIZE; i++) fileRights[i] = castlingRights[0][i];
1200 if (initialMode == AnalyzeMode) {
1201 if (appData.noChessProgram) {
1202 DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1205 if (appData.icsActive) {
1206 DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1210 } else if (initialMode == AnalyzeFile) {
1211 appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1212 ShowThinkingEvent();
1214 AnalysisPeriodicEvent(1);
1215 } else if (initialMode == MachinePlaysWhite) {
1216 if (appData.noChessProgram) {
1217 DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1221 if (appData.icsActive) {
1222 DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1226 MachineWhiteEvent();
1227 } else if (initialMode == MachinePlaysBlack) {
1228 if (appData.noChessProgram) {
1229 DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1233 if (appData.icsActive) {
1234 DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1238 MachineBlackEvent();
1239 } else if (initialMode == TwoMachinesPlay) {
1240 if (appData.noChessProgram) {
1241 DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1245 if (appData.icsActive) {
1246 DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1251 } else if (initialMode == EditGame) {
1253 } else if (initialMode == EditPosition) {
1254 EditPositionEvent();
1255 } else if (initialMode == Training) {
1256 if (*appData.loadGameFile == NULLCHAR) {
1257 DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1266 * Establish will establish a contact to a remote host.port.
1267 * Sets icsPR to a ProcRef for a process (or pseudo-process)
1268 * used to talk to the host.
1269 * Returns 0 if okay, error code if not.
1276 if (*appData.icsCommPort != NULLCHAR) {
1277 /* Talk to the host through a serial comm port */
1278 return OpenCommPort(appData.icsCommPort, &icsPR);
1280 } else if (*appData.gateway != NULLCHAR) {
1281 if (*appData.remoteShell == NULLCHAR) {
1282 /* Use the rcmd protocol to run telnet program on a gateway host */
1283 snprintf(buf, sizeof(buf), "%s %s %s",
1284 appData.telnetProgram, appData.icsHost, appData.icsPort);
1285 return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1288 /* Use the rsh program to run telnet program on a gateway host */
1289 if (*appData.remoteUser == NULLCHAR) {
1290 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1291 appData.gateway, appData.telnetProgram,
1292 appData.icsHost, appData.icsPort);
1294 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1295 appData.remoteShell, appData.gateway,
1296 appData.remoteUser, appData.telnetProgram,
1297 appData.icsHost, appData.icsPort);
1299 return StartChildProcess(buf, "", &icsPR);
1302 } else if (appData.useTelnet) {
1303 return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1306 /* TCP socket interface differs somewhat between
1307 Unix and NT; handle details in the front end.
1309 return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1314 show_bytes(fp, buf, count)
1320 if (*buf < 040 || *(unsigned char *) buf > 0177) {
1321 fprintf(fp, "\\%03o", *buf & 0xff);
1330 /* Returns an errno value */
1332 OutputMaybeTelnet(pr, message, count, outError)
1338 char buf[8192], *p, *q, *buflim;
1339 int left, newcount, outcount;
1341 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1342 *appData.gateway != NULLCHAR) {
1343 if (appData.debugMode) {
1344 fprintf(debugFP, ">ICS: ");
1345 show_bytes(debugFP, message, count);
1346 fprintf(debugFP, "\n");
1348 return OutputToProcess(pr, message, count, outError);
1351 buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1358 if (appData.debugMode) {
1359 fprintf(debugFP, ">ICS: ");
1360 show_bytes(debugFP, buf, newcount);
1361 fprintf(debugFP, "\n");
1363 outcount = OutputToProcess(pr, buf, newcount, outError);
1364 if (outcount < newcount) return -1; /* to be sure */
1371 } else if (((unsigned char) *p) == TN_IAC) {
1372 *q++ = (char) TN_IAC;
1379 if (appData.debugMode) {
1380 fprintf(debugFP, ">ICS: ");
1381 show_bytes(debugFP, buf, newcount);
1382 fprintf(debugFP, "\n");
1384 outcount = OutputToProcess(pr, buf, newcount, outError);
1385 if (outcount < newcount) return -1; /* to be sure */
1390 read_from_player(isr, closure, message, count, error)
1397 int outError, outCount;
1398 static int gotEof = 0;
1400 /* Pass data read from player on to ICS */
1403 outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1404 if (outCount < count) {
1405 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1407 } else if (count < 0) {
1408 RemoveInputSource(isr);
1409 DisplayFatalError(_("Error reading from keyboard"), error, 1);
1410 } else if (gotEof++ > 0) {
1411 RemoveInputSource(isr);
1412 DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1418 { // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1419 SendToICS("date\n");
1420 if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1423 /* added routine for printf style output to ics */
1424 void ics_printf(char *format, ...)
1426 char buffer[MSG_SIZ];
1429 va_start(args, format);
1430 vsnprintf(buffer, sizeof(buffer), format, args);
1431 buffer[sizeof(buffer)-1] = '\0';
1440 int count, outCount, outError;
1442 if (icsPR == NULL) return;
1445 outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1446 if (outCount < count) {
1447 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1451 /* This is used for sending logon scripts to the ICS. Sending
1452 without a delay causes problems when using timestamp on ICC
1453 (at least on my machine). */
1455 SendToICSDelayed(s,msdelay)
1459 int count, outCount, outError;
1461 if (icsPR == NULL) return;
1464 if (appData.debugMode) {
1465 fprintf(debugFP, ">ICS: ");
1466 show_bytes(debugFP, s, count);
1467 fprintf(debugFP, "\n");
1469 outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1471 if (outCount < count) {
1472 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1477 /* Remove all highlighting escape sequences in s
1478 Also deletes any suffix starting with '('
1481 StripHighlightAndTitle(s)
1484 static char retbuf[MSG_SIZ];
1487 while (*s != NULLCHAR) {
1488 while (*s == '\033') {
1489 while (*s != NULLCHAR && !isalpha(*s)) s++;
1490 if (*s != NULLCHAR) s++;
1492 while (*s != NULLCHAR && *s != '\033') {
1493 if (*s == '(' || *s == '[') {
1504 /* Remove all highlighting escape sequences in s */
1509 static char retbuf[MSG_SIZ];
1512 while (*s != NULLCHAR) {
1513 while (*s == '\033') {
1514 while (*s != NULLCHAR && !isalpha(*s)) s++;
1515 if (*s != NULLCHAR) s++;
1517 while (*s != NULLCHAR && *s != '\033') {
1525 char *variantNames[] = VARIANT_NAMES;
1530 return variantNames[v];
1534 /* Identify a variant from the strings the chess servers use or the
1535 PGN Variant tag names we use. */
1542 VariantClass v = VariantNormal;
1543 int i, found = FALSE;
1548 /* [HGM] skip over optional board-size prefixes */
1549 if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1550 sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1551 while( *e++ != '_');
1554 if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
1558 for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
1559 if (StrCaseStr(e, variantNames[i])) {
1560 v = (VariantClass) i;
1567 if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
1568 || StrCaseStr(e, "wild/fr")
1569 || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
1570 v = VariantFischeRandom;
1571 } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
1572 (i = 1, p = StrCaseStr(e, "w"))) {
1574 while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
1581 case 0: /* FICS only, actually */
1583 /* Castling legal even if K starts on d-file */
1584 v = VariantWildCastle;
1589 /* Castling illegal even if K & R happen to start in
1590 normal positions. */
1591 v = VariantNoCastle;
1604 /* Castling legal iff K & R start in normal positions */
1610 /* Special wilds for position setup; unclear what to do here */
1611 v = VariantLoadable;
1614 /* Bizarre ICC game */
1615 v = VariantTwoKings;
1618 v = VariantKriegspiel;
1624 v = VariantFischeRandom;
1627 v = VariantCrazyhouse;
1630 v = VariantBughouse;
1636 /* Not quite the same as FICS suicide! */
1637 v = VariantGiveaway;
1643 v = VariantShatranj;
1646 /* Temporary names for future ICC types. The name *will* change in
1647 the next xboard/WinBoard release after ICC defines it. */
1685 v = VariantCapablanca;
1688 v = VariantKnightmate;
1694 v = VariantCylinder;
1700 v = VariantCapaRandom;
1703 v = VariantBerolina;
1715 /* Found "wild" or "w" in the string but no number;
1716 must assume it's normal chess. */
1720 sprintf(buf, _("Unknown wild type %d"), wnum);
1721 DisplayError(buf, 0);
1727 if (appData.debugMode) {
1728 fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
1729 e, wnum, VariantName(v));
1734 static int leftover_start = 0, leftover_len = 0;
1735 char star_match[STAR_MATCH_N][MSG_SIZ];
1737 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
1738 advance *index beyond it, and set leftover_start to the new value of
1739 *index; else return FALSE. If pattern contains the character '*', it
1740 matches any sequence of characters not containing '\r', '\n', or the
1741 character following the '*' (if any), and the matched sequence(s) are
1742 copied into star_match.
1745 looking_at(buf, index, pattern)
1750 char *bufp = &buf[*index], *patternp = pattern;
1752 char *matchp = star_match[0];
1755 if (*patternp == NULLCHAR) {
1756 *index = leftover_start = bufp - buf;
1760 if (*bufp == NULLCHAR) return FALSE;
1761 if (*patternp == '*') {
1762 if (*bufp == *(patternp + 1)) {
1764 matchp = star_match[++star_count];
1768 } else if (*bufp == '\n' || *bufp == '\r') {
1770 if (*patternp == NULLCHAR)
1775 *matchp++ = *bufp++;
1779 if (*patternp != *bufp) return FALSE;
1786 SendToPlayer(data, length)
1790 int error, outCount;
1791 outCount = OutputToProcess(NoProc, data, length, &error);
1792 if (outCount < length) {
1793 DisplayFatalError(_("Error writing to display"), error, 1);
1798 PackHolding(packed, holding)
1810 switch (runlength) {
1821 sprintf(q, "%d", runlength);
1833 /* Telnet protocol requests from the front end */
1835 TelnetRequest(ddww, option)
1836 unsigned char ddww, option;
1838 unsigned char msg[3];
1839 int outCount, outError;
1841 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
1843 if (appData.debugMode) {
1844 char buf1[8], buf2[8], *ddwwStr, *optionStr;
1860 sprintf(buf1, "%d", ddww);
1869 sprintf(buf2, "%d", option);
1872 fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
1877 outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
1879 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1886 if (!appData.icsActive) return;
1887 TelnetRequest(TN_DO, TN_ECHO);
1893 if (!appData.icsActive) return;
1894 TelnetRequest(TN_DONT, TN_ECHO);
1898 CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece)
1900 /* put the holdings sent to us by the server on the board holdings area */
1901 int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
1905 if(gameInfo.holdingsWidth < 2) return;
1907 if( (int)lowestPiece >= BlackPawn ) {
1910 holdingsStartRow = BOARD_HEIGHT-1;
1913 holdingsColumn = BOARD_WIDTH-1;
1914 countsColumn = BOARD_WIDTH-2;
1915 holdingsStartRow = 0;
1919 for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
1920 board[i][holdingsColumn] = EmptySquare;
1921 board[i][countsColumn] = (ChessSquare) 0;
1923 while( (p=*holdings++) != NULLCHAR ) {
1924 piece = CharToPiece( ToUpper(p) );
1925 if(piece == EmptySquare) continue;
1926 /*j = (int) piece - (int) WhitePawn;*/
1927 j = PieceToNumber(piece);
1928 if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
1929 if(j < 0) continue; /* should not happen */
1930 piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
1931 board[holdingsStartRow+j*direction][holdingsColumn] = piece;
1932 board[holdingsStartRow+j*direction][countsColumn]++;
1939 VariantSwitch(Board board, VariantClass newVariant)
1941 int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
1943 startedFromPositionFile = FALSE;
1944 if(gameInfo.variant == newVariant) return;
1946 /* [HGM] This routine is called each time an assignment is made to
1947 * gameInfo.variant during a game, to make sure the board sizes
1948 * are set to match the new variant. If that means adding or deleting
1949 * holdings, we shift the playing board accordingly
1950 * This kludge is needed because in ICS observe mode, we get boards
1951 * of an ongoing game without knowing the variant, and learn about the
1952 * latter only later. This can be because of the move list we requested,
1953 * in which case the game history is refilled from the beginning anyway,
1954 * but also when receiving holdings of a crazyhouse game. In the latter
1955 * case we want to add those holdings to the already received position.
1959 if (appData.debugMode) {
1960 fprintf(debugFP, "Switch board from %s to %s\n",
1961 VariantName(gameInfo.variant), VariantName(newVariant));
1962 setbuf(debugFP, NULL);
1964 shuffleOpenings = 0; /* [HGM] shuffle */
1965 gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
1969 newWidth = 9; newHeight = 9;
1970 gameInfo.holdingsSize = 7;
1971 case VariantBughouse:
1972 case VariantCrazyhouse:
1973 newHoldingsWidth = 2; break;
1977 newHoldingsWidth = 2;
1978 gameInfo.holdingsSize = 8;
1981 case VariantCapablanca:
1982 case VariantCapaRandom:
1985 newHoldingsWidth = gameInfo.holdingsSize = 0;
1988 if(newWidth != gameInfo.boardWidth ||
1989 newHeight != gameInfo.boardHeight ||
1990 newHoldingsWidth != gameInfo.holdingsWidth ) {
1992 /* shift position to new playing area, if needed */
1993 if(newHoldingsWidth > gameInfo.holdingsWidth) {
1994 for(i=0; i<BOARD_HEIGHT; i++)
1995 for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
1996 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
1998 for(i=0; i<newHeight; i++) {
1999 board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2000 board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2002 } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2003 for(i=0; i<BOARD_HEIGHT; i++)
2004 for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2005 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2008 gameInfo.boardWidth = newWidth;
2009 gameInfo.boardHeight = newHeight;
2010 gameInfo.holdingsWidth = newHoldingsWidth;
2011 gameInfo.variant = newVariant;
2012 InitDrawingSizes(-2, 0);
2013 InitPosition(FALSE); /* this sets up board[0], but also other stuff */
2014 } else { gameInfo.variant = newVariant; InitPosition(FALSE); }
2016 DrawPosition(TRUE, boards[currentMove]);
2019 static int loggedOn = FALSE;
2021 /*-- Game start info cache: --*/
2023 char gs_kind[MSG_SIZ];
2024 static char player1Name[128] = "";
2025 static char player2Name[128] = "";
2026 static char cont_seq[] = "\n\\ ";
2027 static int player1Rating = -1;
2028 static int player2Rating = -1;
2029 /*----------------------------*/
2031 ColorClass curColor = ColorNormal;
2032 int suppressKibitz = 0;
2035 read_from_ics(isr, closure, data, count, error)
2042 #define BUF_SIZE 8192
2043 #define STARTED_NONE 0
2044 #define STARTED_MOVES 1
2045 #define STARTED_BOARD 2
2046 #define STARTED_OBSERVE 3
2047 #define STARTED_HOLDINGS 4
2048 #define STARTED_CHATTER 5
2049 #define STARTED_COMMENT 6
2050 #define STARTED_MOVES_NOHIDE 7
2052 static int started = STARTED_NONE;
2053 static char parse[20000];
2054 static int parse_pos = 0;
2055 static char buf[BUF_SIZE + 1];
2056 static int firstTime = TRUE, intfSet = FALSE;
2057 static ColorClass prevColor = ColorNormal;
2058 static int savingComment = FALSE;
2059 static int cmatch = 0; // continuation sequence match
2066 int backup; /* [DM] For zippy color lines */
2068 char talker[MSG_SIZ]; // [HGM] chat
2071 if (appData.debugMode) {
2073 fprintf(debugFP, "<ICS: ");
2074 show_bytes(debugFP, data, count);
2075 fprintf(debugFP, "\n");
2079 if (appData.debugMode) { int f = forwardMostMove;
2080 fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2081 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
2084 /* If last read ended with a partial line that we couldn't parse,
2085 prepend it to the new read and try again. */
2086 if (leftover_len > 0) {
2087 for (i=0; i<leftover_len; i++)
2088 buf[i] = buf[leftover_start + i];
2091 /* copy new characters into the buffer */
2092 bp = buf + leftover_len;
2093 buf_len=leftover_len;
2094 for (i=0; i<count; i++)
2097 if (data[i] == '\r')
2100 // join lines split by ICS?
2101 if (!appData.noJoin)
2104 Joining just consists of finding matches against the
2105 continuation sequence, and discarding that sequence
2106 if found instead of copying it. So, until a match
2107 fails, there's nothing to do since it might be the
2108 complete sequence, and thus, something we don't want
2111 if (data[i] == cont_seq[cmatch])
2114 if (cmatch == strlen(cont_seq))
2116 cmatch = 0; // complete match. just reset the counter
2119 it's possible for the ICS to not include the space
2120 at the end of the last word, making our [correct]
2121 join operation fuse two separate words. the server
2122 does this when the space occurs at the width setting.
2124 if (!buf_len || buf[buf_len-1] != ' ')
2135 match failed, so we have to copy what matched before
2136 falling through and copying this character. In reality,
2137 this will only ever be just the newline character, but
2138 it doesn't hurt to be precise.
2140 strncpy(bp, cont_seq, cmatch);
2152 buf[buf_len] = NULLCHAR;
2153 next_out = leftover_len;
2157 while (i < buf_len) {
2158 /* Deal with part of the TELNET option negotiation
2159 protocol. We refuse to do anything beyond the
2160 defaults, except that we allow the WILL ECHO option,
2161 which ICS uses to turn off password echoing when we are
2162 directly connected to it. We reject this option
2163 if localLineEditing mode is on (always on in xboard)
2164 and we are talking to port 23, which might be a real
2165 telnet server that will try to keep WILL ECHO on permanently.
2167 if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2168 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2169 unsigned char option;
2171 switch ((unsigned char) buf[++i]) {
2173 if (appData.debugMode)
2174 fprintf(debugFP, "\n<WILL ");
2175 switch (option = (unsigned char) buf[++i]) {
2177 if (appData.debugMode)
2178 fprintf(debugFP, "ECHO ");
2179 /* Reply only if this is a change, according
2180 to the protocol rules. */
2181 if (remoteEchoOption) break;
2182 if (appData.localLineEditing &&
2183 atoi(appData.icsPort) == TN_PORT) {
2184 TelnetRequest(TN_DONT, TN_ECHO);
2187 TelnetRequest(TN_DO, TN_ECHO);
2188 remoteEchoOption = TRUE;
2192 if (appData.debugMode)
2193 fprintf(debugFP, "%d ", option);
2194 /* Whatever this is, we don't want it. */
2195 TelnetRequest(TN_DONT, option);
2200 if (appData.debugMode)
2201 fprintf(debugFP, "\n<WONT ");
2202 switch (option = (unsigned char) buf[++i]) {
2204 if (appData.debugMode)
2205 fprintf(debugFP, "ECHO ");
2206 /* Reply only if this is a change, according
2207 to the protocol rules. */
2208 if (!remoteEchoOption) break;
2210 TelnetRequest(TN_DONT, TN_ECHO);
2211 remoteEchoOption = FALSE;
2214 if (appData.debugMode)
2215 fprintf(debugFP, "%d ", (unsigned char) option);
2216 /* Whatever this is, it must already be turned
2217 off, because we never agree to turn on
2218 anything non-default, so according to the
2219 protocol rules, we don't reply. */
2224 if (appData.debugMode)
2225 fprintf(debugFP, "\n<DO ");
2226 switch (option = (unsigned char) buf[++i]) {
2228 /* Whatever this is, we refuse to do it. */
2229 if (appData.debugMode)
2230 fprintf(debugFP, "%d ", option);
2231 TelnetRequest(TN_WONT, option);
2236 if (appData.debugMode)
2237 fprintf(debugFP, "\n<DONT ");
2238 switch (option = (unsigned char) buf[++i]) {
2240 if (appData.debugMode)
2241 fprintf(debugFP, "%d ", option);
2242 /* Whatever this is, we are already not doing
2243 it, because we never agree to do anything
2244 non-default, so according to the protocol
2245 rules, we don't reply. */
2250 if (appData.debugMode)
2251 fprintf(debugFP, "\n<IAC ");
2252 /* Doubled IAC; pass it through */
2256 if (appData.debugMode)
2257 fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2258 /* Drop all other telnet commands on the floor */
2261 if (oldi > next_out)
2262 SendToPlayer(&buf[next_out], oldi - next_out);
2268 /* OK, this at least will *usually* work */
2269 if (!loggedOn && looking_at(buf, &i, "ics%")) {
2273 if (loggedOn && !intfSet) {
2274 if (ics_type == ICS_ICC) {
2276 "/set-quietly interface %s\n/set-quietly style 12\n",
2278 } else if (ics_type == ICS_CHESSNET) {
2279 sprintf(str, "/style 12\n");
2281 strcpy(str, "alias $ @\n$set interface ");
2282 strcat(str, programVersion);
2283 strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2285 strcat(str, "$iset nohighlight 1\n");
2287 strcat(str, "$iset lock 1\n$style 12\n");
2290 NotifyFrontendLogin();
2294 if (started == STARTED_COMMENT) {
2295 /* Accumulate characters in comment */
2296 parse[parse_pos++] = buf[i];
2297 if (buf[i] == '\n') {
2298 parse[parse_pos] = NULLCHAR;
2299 if(chattingPartner>=0) {
2301 sprintf(mess, "%s%s", talker, parse);
2302 OutputChatMessage(chattingPartner, mess);
2303 chattingPartner = -1;
2305 if(!suppressKibitz) // [HGM] kibitz
2306 AppendComment(forwardMostMove, StripHighlight(parse));
2307 else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2308 int nrDigit = 0, nrAlph = 0, i;
2309 if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2310 { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2311 parse[parse_pos] = NULLCHAR;
2312 // try to be smart: if it does not look like search info, it should go to
2313 // ICS interaction window after all, not to engine-output window.
2314 for(i=0; i<parse_pos; i++) { // count letters and digits
2315 nrDigit += (parse[i] >= '0' && parse[i] <= '9');
2316 nrAlph += (parse[i] >= 'a' && parse[i] <= 'z');
2317 nrAlph += (parse[i] >= 'A' && parse[i] <= 'Z');
2319 if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2320 int depth=0; float score;
2321 if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2322 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2323 pvInfoList[forwardMostMove-1].depth = depth;
2324 pvInfoList[forwardMostMove-1].score = 100*score;
2326 OutputKibitz(suppressKibitz, parse);
2329 sprintf(tmp, _("your opponent kibitzes: %s"), parse);
2330 SendToPlayer(tmp, strlen(tmp));
2333 started = STARTED_NONE;
2335 /* Don't match patterns against characters in chatter */
2340 if (started == STARTED_CHATTER) {
2341 if (buf[i] != '\n') {
2342 /* Don't match patterns against characters in chatter */
2346 started = STARTED_NONE;
2349 /* Kludge to deal with rcmd protocol */
2350 if (firstTime && looking_at(buf, &i, "\001*")) {
2351 DisplayFatalError(&buf[1], 0, 1);
2357 if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2360 if (appData.debugMode)
2361 fprintf(debugFP, "ics_type %d\n", ics_type);
2364 if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
2365 ics_type = ICS_FICS;
2367 if (appData.debugMode)
2368 fprintf(debugFP, "ics_type %d\n", ics_type);
2371 if (!loggedOn && looking_at(buf, &i, "chess.net")) {
2372 ics_type = ICS_CHESSNET;
2374 if (appData.debugMode)
2375 fprintf(debugFP, "ics_type %d\n", ics_type);
2380 (looking_at(buf, &i, "\"*\" is *a registered name") ||
2381 looking_at(buf, &i, "Logging you in as \"*\"") ||
2382 looking_at(buf, &i, "will be \"*\""))) {
2383 strcpy(ics_handle, star_match[0]);
2387 if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
2389 snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
2390 DisplayIcsInteractionTitle(buf);
2391 have_set_title = TRUE;
2394 /* skip finger notes */
2395 if (started == STARTED_NONE &&
2396 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
2397 (buf[i] == '1' && buf[i+1] == '0')) &&
2398 buf[i+2] == ':' && buf[i+3] == ' ') {
2399 started = STARTED_CHATTER;
2404 /* skip formula vars */
2405 if (started == STARTED_NONE &&
2406 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
2407 started = STARTED_CHATTER;
2413 // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
2414 if (appData.autoKibitz && started == STARTED_NONE &&
2415 !appData.icsEngineAnalyze && // [HGM] [DM] ICS analyze
2416 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
2417 if(looking_at(buf, &i, "* kibitzes: ") &&
2418 (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
2419 StrStr(star_match[0], gameInfo.black) == star_match[0] )) { // kibitz of self or opponent
2420 suppressKibitz = TRUE;
2421 if((StrStr(star_match[0], gameInfo.white) == star_match[0]
2422 && (gameMode == IcsPlayingWhite)) ||
2423 (StrStr(star_match[0], gameInfo.black) == star_match[0]
2424 && (gameMode == IcsPlayingBlack)) ) // opponent kibitz
2425 started = STARTED_CHATTER; // own kibitz we simply discard
2427 started = STARTED_COMMENT; // make sure it will be collected in parse[]
2428 parse_pos = 0; parse[0] = NULLCHAR;
2429 savingComment = TRUE;
2430 suppressKibitz = gameMode != IcsObserving ? 2 :
2431 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
2435 if(looking_at(buf, &i, "kibitzed to")) { // suppress the acknowledgements of our own autoKibitz
2436 started = STARTED_CHATTER;
2437 suppressKibitz = TRUE;
2439 } // [HGM] kibitz: end of patch
2441 //if(appData.debugMode) fprintf(debugFP, "hunt for tell, buf = %s\n", buf+i);
2443 // [HGM] chat: intercept tells by users for which we have an open chat window
2445 if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
2446 looking_at(buf, &i, "* whispers:") ||
2447 looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
2448 looking_at(buf, &i, "*(*)(*):") && sscanf(star_match[2], "%d", &channel) == 1 )) {
2450 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
2451 chattingPartner = -1;
2453 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
2454 for(p=0; p<MAX_CHAT; p++) {
2455 if(channel == atoi(chatPartner[p])) {
2456 talker[0] = '['; strcat(talker, "]");
2457 chattingPartner = p; break;
2460 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
2461 for(p=0; p<MAX_CHAT; p++) {
2462 if(!strcmp("WHISPER", chatPartner[p])) {
2463 talker[0] = '['; strcat(talker, "]");
2464 chattingPartner = p; break;
2467 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
2468 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
2470 chattingPartner = p; break;
2472 if(chattingPartner<0) i = oldi; else {
2473 started = STARTED_COMMENT;
2474 parse_pos = 0; parse[0] = NULLCHAR;
2475 savingComment = TRUE;
2476 suppressKibitz = TRUE;
2478 } // [HGM] chat: end of patch
2480 if (appData.zippyTalk || appData.zippyPlay) {
2481 /* [DM] Backup address for color zippy lines */
2485 if (loggedOn == TRUE)
2486 if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
2487 (appData.zippyPlay && ZippyMatch(buf, &backup)));
2489 if (ZippyControl(buf, &i) ||
2490 ZippyConverse(buf, &i) ||
2491 (appData.zippyPlay && ZippyMatch(buf, &i))) {
2493 if (!appData.colorize) continue;
2497 } // [DM] 'else { ' deleted
2499 /* Regular tells and says */
2500 (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
2501 looking_at(buf, &i, "* (your partner) tells you: ") ||
2502 looking_at(buf, &i, "* says: ") ||
2503 /* Don't color "message" or "messages" output */
2504 (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
2505 looking_at(buf, &i, "*. * at *:*: ") ||
2506 looking_at(buf, &i, "--* (*:*): ") ||
2507 /* Message notifications (same color as tells) */
2508 looking_at(buf, &i, "* has left a message ") ||
2509 looking_at(buf, &i, "* just sent you a message:\n") ||
2510 /* Whispers and kibitzes */
2511 (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
2512 looking_at(buf, &i, "* kibitzes: ") ||
2514 (tkind = 3, looking_at(buf, &i, "*(*: "))) {
2516 if (tkind == 1 && strchr(star_match[0], ':')) {
2517 /* Avoid "tells you:" spoofs in channels */
2520 if (star_match[0][0] == NULLCHAR ||
2521 strchr(star_match[0], ' ') ||
2522 (tkind == 3 && strchr(star_match[1], ' '))) {
2523 /* Reject bogus matches */
2526 if (appData.colorize) {
2527 if (oldi > next_out) {
2528 SendToPlayer(&buf[next_out], oldi - next_out);
2533 Colorize(ColorTell, FALSE);
2534 curColor = ColorTell;
2537 Colorize(ColorKibitz, FALSE);
2538 curColor = ColorKibitz;
2541 p = strrchr(star_match[1], '(');
2548 Colorize(ColorChannel1, FALSE);
2549 curColor = ColorChannel1;
2551 Colorize(ColorChannel, FALSE);
2552 curColor = ColorChannel;
2556 curColor = ColorNormal;
2560 if (started == STARTED_NONE && appData.autoComment &&
2561 (gameMode == IcsObserving ||
2562 gameMode == IcsPlayingWhite ||
2563 gameMode == IcsPlayingBlack)) {
2564 parse_pos = i - oldi;
2565 memcpy(parse, &buf[oldi], parse_pos);
2566 parse[parse_pos] = NULLCHAR;
2567 started = STARTED_COMMENT;
2568 savingComment = TRUE;
2570 started = STARTED_CHATTER;
2571 savingComment = FALSE;
2578 if (looking_at(buf, &i, "* s-shouts: ") ||
2579 looking_at(buf, &i, "* c-shouts: ")) {
2580 if (appData.colorize) {
2581 if (oldi > next_out) {
2582 SendToPlayer(&buf[next_out], oldi - next_out);
2585 Colorize(ColorSShout, FALSE);
2586 curColor = ColorSShout;
2589 started = STARTED_CHATTER;
2593 if (looking_at(buf, &i, "--->")) {
2598 if (looking_at(buf, &i, "* shouts: ") ||
2599 looking_at(buf, &i, "--> ")) {
2600 if (appData.colorize) {
2601 if (oldi > next_out) {
2602 SendToPlayer(&buf[next_out], oldi - next_out);
2605 Colorize(ColorShout, FALSE);
2606 curColor = ColorShout;
2609 started = STARTED_CHATTER;
2613 if (looking_at( buf, &i, "Challenge:")) {
2614 if (appData.colorize) {
2615 if (oldi > next_out) {
2616 SendToPlayer(&buf[next_out], oldi - next_out);
2619 Colorize(ColorChallenge, FALSE);
2620 curColor = ColorChallenge;
2626 if (looking_at(buf, &i, "* offers you") ||
2627 looking_at(buf, &i, "* offers to be") ||
2628 looking_at(buf, &i, "* would like to") ||
2629 looking_at(buf, &i, "* requests to") ||
2630 looking_at(buf, &i, "Your opponent offers") ||
2631 looking_at(buf, &i, "Your opponent requests")) {
2633 if (appData.colorize) {
2634 if (oldi > next_out) {
2635 SendToPlayer(&buf[next_out], oldi - next_out);
2638 Colorize(ColorRequest, FALSE);
2639 curColor = ColorRequest;
2644 if (looking_at(buf, &i, "* (*) seeking")) {
2645 if (appData.colorize) {
2646 if (oldi > next_out) {
2647 SendToPlayer(&buf[next_out], oldi - next_out);
2650 Colorize(ColorSeek, FALSE);
2651 curColor = ColorSeek;
2656 if (looking_at(buf, &i, "\\ ")) {
2657 if (prevColor != ColorNormal) {
2658 if (oldi > next_out) {
2659 SendToPlayer(&buf[next_out], oldi - next_out);
2662 Colorize(prevColor, TRUE);
2663 curColor = prevColor;
2665 if (savingComment) {
2666 parse_pos = i - oldi;
2667 memcpy(parse, &buf[oldi], parse_pos);
2668 parse[parse_pos] = NULLCHAR;
2669 started = STARTED_COMMENT;
2671 started = STARTED_CHATTER;
2676 if (looking_at(buf, &i, "Black Strength :") ||
2677 looking_at(buf, &i, "<<< style 10 board >>>") ||
2678 looking_at(buf, &i, "<10>") ||
2679 looking_at(buf, &i, "#@#")) {
2680 /* Wrong board style */
2682 SendToICS(ics_prefix);
2683 SendToICS("set style 12\n");
2684 SendToICS(ics_prefix);
2685 SendToICS("refresh\n");
2689 if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
2691 have_sent_ICS_logon = 1;
2695 if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
2696 (looking_at(buf, &i, "\n<12> ") ||
2697 looking_at(buf, &i, "<12> "))) {
2699 if (oldi > next_out) {
2700 SendToPlayer(&buf[next_out], oldi - next_out);
2703 started = STARTED_BOARD;
2708 if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
2709 looking_at(buf, &i, "<b1> ")) {
2710 if (oldi > next_out) {
2711 SendToPlayer(&buf[next_out], oldi - next_out);
2714 started = STARTED_HOLDINGS;
2719 if (looking_at(buf, &i, "* *vs. * *--- *")) {
2721 /* Header for a move list -- first line */
2723 switch (ics_getting_history) {
2727 case BeginningOfGame:
2728 /* User typed "moves" or "oldmoves" while we
2729 were idle. Pretend we asked for these
2730 moves and soak them up so user can step
2731 through them and/or save them.
2734 gameMode = IcsObserving;
2737 ics_getting_history = H_GOT_UNREQ_HEADER;
2739 case EditGame: /*?*/
2740 case EditPosition: /*?*/
2741 /* Should above feature work in these modes too? */
2742 /* For now it doesn't */
2743 ics_getting_history = H_GOT_UNWANTED_HEADER;
2746 ics_getting_history = H_GOT_UNWANTED_HEADER;
2751 /* Is this the right one? */
2752 if (gameInfo.white && gameInfo.black &&
2753 strcmp(gameInfo.white, star_match[0]) == 0 &&
2754 strcmp(gameInfo.black, star_match[2]) == 0) {
2756 ics_getting_history = H_GOT_REQ_HEADER;
2759 case H_GOT_REQ_HEADER:
2760 case H_GOT_UNREQ_HEADER:
2761 case H_GOT_UNWANTED_HEADER:
2762 case H_GETTING_MOVES:
2763 /* Should not happen */
2764 DisplayError(_("Error gathering move list: two headers"), 0);
2765 ics_getting_history = H_FALSE;
2769 /* Save player ratings into gameInfo if needed */
2770 if ((ics_getting_history == H_GOT_REQ_HEADER ||
2771 ics_getting_history == H_GOT_UNREQ_HEADER) &&
2772 (gameInfo.whiteRating == -1 ||
2773 gameInfo.blackRating == -1)) {
2775 gameInfo.whiteRating = string_to_rating(star_match[1]);
2776 gameInfo.blackRating = string_to_rating(star_match[3]);
2777 if (appData.debugMode)
2778 fprintf(debugFP, _("Ratings from header: W %d, B %d\n"),
2779 gameInfo.whiteRating, gameInfo.blackRating);
2784 if (looking_at(buf, &i,
2785 "* * match, initial time: * minute*, increment: * second")) {
2786 /* Header for a move list -- second line */
2787 /* Initial board will follow if this is a wild game */
2788 if (gameInfo.event != NULL) free(gameInfo.event);
2789 sprintf(str, "ICS %s %s match", star_match[0], star_match[1]);
2790 gameInfo.event = StrSave(str);
2791 /* [HGM] we switched variant. Translate boards if needed. */
2792 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
2796 if (looking_at(buf, &i, "Move ")) {
2797 /* Beginning of a move list */
2798 switch (ics_getting_history) {
2800 /* Normally should not happen */
2801 /* Maybe user hit reset while we were parsing */
2804 /* Happens if we are ignoring a move list that is not
2805 * the one we just requested. Common if the user
2806 * tries to observe two games without turning off
2809 case H_GETTING_MOVES:
2810 /* Should not happen */
2811 DisplayError(_("Error gathering move list: nested"), 0);
2812 ics_getting_history = H_FALSE;
2814 case H_GOT_REQ_HEADER:
2815 ics_getting_history = H_GETTING_MOVES;
2816 started = STARTED_MOVES;
2818 if (oldi > next_out) {
2819 SendToPlayer(&buf[next_out], oldi - next_out);
2822 case H_GOT_UNREQ_HEADER:
2823 ics_getting_history = H_GETTING_MOVES;
2824 started = STARTED_MOVES_NOHIDE;
2827 case H_GOT_UNWANTED_HEADER:
2828 ics_getting_history = H_FALSE;
2834 if (looking_at(buf, &i, "% ") ||
2835 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
2836 && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
2837 savingComment = FALSE;
2840 case STARTED_MOVES_NOHIDE:
2841 memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
2842 parse[parse_pos + i - oldi] = NULLCHAR;
2843 ParseGameHistory(parse);
2845 if (appData.zippyPlay && first.initDone) {
2846 FeedMovesToProgram(&first, forwardMostMove);
2847 if (gameMode == IcsPlayingWhite) {
2848 if (WhiteOnMove(forwardMostMove)) {
2849 if (first.sendTime) {
2850 if (first.useColors) {
2851 SendToProgram("black\n", &first);
2853 SendTimeRemaining(&first, TRUE);
2855 if (first.useColors) {
2856 SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
2858 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
2859 first.maybeThinking = TRUE;
2861 if (first.usePlayother) {
2862 if (first.sendTime) {
2863 SendTimeRemaining(&first, TRUE);
2865 SendToProgram("playother\n", &first);
2871 } else if (gameMode == IcsPlayingBlack) {
2872 if (!WhiteOnMove(forwardMostMove)) {
2873 if (first.sendTime) {
2874 if (first.useColors) {
2875 SendToProgram("white\n", &first);
2877 SendTimeRemaining(&first, FALSE);
2879 if (first.useColors) {
2880 SendToProgram("black\n", &first);
2882 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
2883 first.maybeThinking = TRUE;
2885 if (first.usePlayother) {
2886 if (first.sendTime) {
2887 SendTimeRemaining(&first, FALSE);
2889 SendToProgram("playother\n", &first);
2898 if (gameMode == IcsObserving && ics_gamenum == -1) {
2899 /* Moves came from oldmoves or moves command
2900 while we weren't doing anything else.
2902 currentMove = forwardMostMove;
2903 ClearHighlights();/*!!could figure this out*/
2904 flipView = appData.flipView;
2905 DrawPosition(FALSE, boards[currentMove]);
2906 DisplayBothClocks();
2907 sprintf(str, "%s vs. %s",
2908 gameInfo.white, gameInfo.black);
2912 /* Moves were history of an active game */
2913 if (gameInfo.resultDetails != NULL) {
2914 free(gameInfo.resultDetails);
2915 gameInfo.resultDetails = NULL;
2918 HistorySet(parseList, backwardMostMove,
2919 forwardMostMove, currentMove-1);
2920 DisplayMove(currentMove - 1);
2921 if (started == STARTED_MOVES) next_out = i;
2922 started = STARTED_NONE;
2923 ics_getting_history = H_FALSE;
2926 case STARTED_OBSERVE:
2927 started = STARTED_NONE;
2928 SendToICS(ics_prefix);
2929 SendToICS("refresh\n");
2935 if(bookHit) { // [HGM] book: simulate book reply
2936 static char bookMove[MSG_SIZ]; // a bit generous?
2938 programStats.nodes = programStats.depth = programStats.time =
2939 programStats.score = programStats.got_only_move = 0;
2940 sprintf(programStats.movelist, "%s (xbook)", bookHit);
2942 strcpy(bookMove, "move ");
2943 strcat(bookMove, bookHit);
2944 HandleMachineMove(bookMove, &first);
2949 if ((started == STARTED_MOVES || started == STARTED_BOARD ||
2950 started == STARTED_HOLDINGS ||
2951 started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
2952 /* Accumulate characters in move list or board */
2953 parse[parse_pos++] = buf[i];
2956 /* Start of game messages. Mostly we detect start of game
2957 when the first board image arrives. On some versions
2958 of the ICS, though, we need to do a "refresh" after starting
2959 to observe in order to get the current board right away. */
2960 if (looking_at(buf, &i, "Adding game * to observation list")) {
2961 started = STARTED_OBSERVE;
2965 /* Handle auto-observe */
2966 if (appData.autoObserve &&
2967 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
2968 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
2970 /* Choose the player that was highlighted, if any. */
2971 if (star_match[0][0] == '\033' ||
2972 star_match[1][0] != '\033') {
2973 player = star_match[0];
2975 player = star_match[2];
2977 sprintf(str, "%sobserve %s\n",
2978 ics_prefix, StripHighlightAndTitle(player));
2981 /* Save ratings from notify string */
2982 strcpy(player1Name, star_match[0]);
2983 player1Rating = string_to_rating(star_match[1]);
2984 strcpy(player2Name, star_match[2]);
2985 player2Rating = string_to_rating(star_match[3]);
2987 if (appData.debugMode)
2989 "Ratings from 'Game notification:' %s %d, %s %d\n",
2990 player1Name, player1Rating,
2991 player2Name, player2Rating);
2996 /* Deal with automatic examine mode after a game,
2997 and with IcsObserving -> IcsExamining transition */
2998 if (looking_at(buf, &i, "Entering examine mode for game *") ||
2999 looking_at(buf, &i, "has made you an examiner of game *")) {
3001 int gamenum = atoi(star_match[0]);
3002 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3003 gamenum == ics_gamenum) {
3004 /* We were already playing or observing this game;
3005 no need to refetch history */
3006 gameMode = IcsExamining;
3008 pauseExamForwardMostMove = forwardMostMove;
3009 } else if (currentMove < forwardMostMove) {
3010 ForwardInner(forwardMostMove);
3013 /* I don't think this case really can happen */
3014 SendToICS(ics_prefix);
3015 SendToICS("refresh\n");
3020 /* Error messages */
3021 // if (ics_user_moved) {
3022 if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3023 if (looking_at(buf, &i, "Illegal move") ||
3024 looking_at(buf, &i, "Not a legal move") ||
3025 looking_at(buf, &i, "Your king is in check") ||
3026 looking_at(buf, &i, "It isn't your turn") ||
3027 looking_at(buf, &i, "It is not your move")) {
3029 if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3030 currentMove = --forwardMostMove;
3031 DisplayMove(currentMove - 1); /* before DMError */
3032 DrawPosition(FALSE, boards[currentMove]);
3034 DisplayBothClocks();
3036 DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3042 if (looking_at(buf, &i, "still have time") ||
3043 looking_at(buf, &i, "not out of time") ||
3044 looking_at(buf, &i, "either player is out of time") ||
3045 looking_at(buf, &i, "has timeseal; checking")) {
3046 /* We must have called his flag a little too soon */
3047 whiteFlag = blackFlag = FALSE;
3051 if (looking_at(buf, &i, "added * seconds to") ||
3052 looking_at(buf, &i, "seconds were added to")) {
3053 /* Update the clocks */
3054 SendToICS(ics_prefix);
3055 SendToICS("refresh\n");
3059 if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3060 ics_clock_paused = TRUE;
3065 if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3066 ics_clock_paused = FALSE;
3071 /* Grab player ratings from the Creating: message.
3072 Note we have to check for the special case when
3073 the ICS inserts things like [white] or [black]. */
3074 if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3075 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3077 0 player 1 name (not necessarily white)
3079 2 empty, white, or black (IGNORED)
3080 3 player 2 name (not necessarily black)
3083 The names/ratings are sorted out when the game
3084 actually starts (below).
3086 strcpy(player1Name, StripHighlightAndTitle(star_match[0]));
3087 player1Rating = string_to_rating(star_match[1]);
3088 strcpy(player2Name, StripHighlightAndTitle(star_match[3]));
3089 player2Rating = string_to_rating(star_match[4]);
3091 if (appData.debugMode)
3093 "Ratings from 'Creating:' %s %d, %s %d\n",
3094 player1Name, player1Rating,
3095 player2Name, player2Rating);
3100 /* Improved generic start/end-of-game messages */
3101 if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3102 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3103 /* If tkind == 0: */
3104 /* star_match[0] is the game number */
3105 /* [1] is the white player's name */
3106 /* [2] is the black player's name */
3107 /* For end-of-game: */
3108 /* [3] is the reason for the game end */
3109 /* [4] is a PGN end game-token, preceded by " " */
3110 /* For start-of-game: */
3111 /* [3] begins with "Creating" or "Continuing" */
3112 /* [4] is " *" or empty (don't care). */
3113 int gamenum = atoi(star_match[0]);
3114 char *whitename, *blackname, *why, *endtoken;
3115 ChessMove endtype = (ChessMove) 0;
3118 whitename = star_match[1];
3119 blackname = star_match[2];
3120 why = star_match[3];
3121 endtoken = star_match[4];
3123 whitename = star_match[1];
3124 blackname = star_match[3];
3125 why = star_match[5];
3126 endtoken = star_match[6];
3129 /* Game start messages */
3130 if (strncmp(why, "Creating ", 9) == 0 ||
3131 strncmp(why, "Continuing ", 11) == 0) {
3132 gs_gamenum = gamenum;
3133 strcpy(gs_kind, strchr(why, ' ') + 1);
3135 if (appData.zippyPlay) {
3136 ZippyGameStart(whitename, blackname);
3142 /* Game end messages */
3143 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3144 ics_gamenum != gamenum) {
3147 while (endtoken[0] == ' ') endtoken++;
3148 switch (endtoken[0]) {
3151 endtype = GameUnfinished;
3154 endtype = BlackWins;
3157 if (endtoken[1] == '/')
3158 endtype = GameIsDrawn;
3160 endtype = WhiteWins;
3163 GameEnds(endtype, why, GE_ICS);
3165 if (appData.zippyPlay && first.initDone) {
3166 ZippyGameEnd(endtype, why);
3167 if (first.pr == NULL) {
3168 /* Start the next process early so that we'll
3169 be ready for the next challenge */
3170 StartChessProgram(&first);
3172 /* Send "new" early, in case this command takes
3173 a long time to finish, so that we'll be ready
3174 for the next challenge. */
3175 gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3182 if (looking_at(buf, &i, "Removing game * from observation") ||
3183 looking_at(buf, &i, "no longer observing game *") ||
3184 looking_at(buf, &i, "Game * (*) has no examiners")) {
3185 if (gameMode == IcsObserving &&
3186 atoi(star_match[0]) == ics_gamenum)
3188 /* icsEngineAnalyze */
3189 if (appData.icsEngineAnalyze) {
3196 ics_user_moved = FALSE;
3201 if (looking_at(buf, &i, "no longer examining game *")) {
3202 if (gameMode == IcsExamining &&
3203 atoi(star_match[0]) == ics_gamenum)
3207 ics_user_moved = FALSE;
3212 /* Advance leftover_start past any newlines we find,
3213 so only partial lines can get reparsed */
3214 if (looking_at(buf, &i, "\n")) {
3215 prevColor = curColor;
3216 if (curColor != ColorNormal) {
3217 if (oldi > next_out) {
3218 SendToPlayer(&buf[next_out], oldi - next_out);
3221 Colorize(ColorNormal, FALSE);
3222 curColor = ColorNormal;
3224 if (started == STARTED_BOARD) {
3225 started = STARTED_NONE;
3226 parse[parse_pos] = NULLCHAR;
3227 ParseBoard12(parse);
3230 /* Send premove here */
3231 if (appData.premove) {
3233 if (currentMove == 0 &&
3234 gameMode == IcsPlayingWhite &&
3235 appData.premoveWhite) {
3236 sprintf(str, "%s%s\n", ics_prefix,
3237 appData.premoveWhiteText);
3238 if (appData.debugMode)
3239 fprintf(debugFP, "Sending premove:\n");
3241 } else if (currentMove == 1 &&
3242 gameMode == IcsPlayingBlack &&
3243 appData.premoveBlack) {
3244 sprintf(str, "%s%s\n", ics_prefix,
3245 appData.premoveBlackText);
3246 if (appData.debugMode)
3247 fprintf(debugFP, "Sending premove:\n");
3249 } else if (gotPremove) {
3251 ClearPremoveHighlights();
3252 if (appData.debugMode)
3253 fprintf(debugFP, "Sending premove:\n");
3254 UserMoveEvent(premoveFromX, premoveFromY,
3255 premoveToX, premoveToY,
3260 /* Usually suppress following prompt */
3261 if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
3262 if (looking_at(buf, &i, "*% ")) {
3263 savingComment = FALSE;
3267 } else if (started == STARTED_HOLDINGS) {
3269 char new_piece[MSG_SIZ];
3270 started = STARTED_NONE;
3271 parse[parse_pos] = NULLCHAR;
3272 if (appData.debugMode)
3273 fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
3274 parse, currentMove);
3275 if (sscanf(parse, " game %d", &gamenum) == 1 &&
3276 gamenum == ics_gamenum) {
3277 if (gameInfo.variant == VariantNormal) {
3278 /* [HGM] We seem to switch variant during a game!
3279 * Presumably no holdings were displayed, so we have
3280 * to move the position two files to the right to
3281 * create room for them!
3283 VariantSwitch(boards[currentMove], VariantCrazyhouse); /* temp guess */
3284 /* Get a move list just to see the header, which
3285 will tell us whether this is really bug or zh */
3286 if (ics_getting_history == H_FALSE) {
3287 ics_getting_history = H_REQUESTED;
3288 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3292 new_piece[0] = NULLCHAR;
3293 sscanf(parse, "game %d white [%s black [%s <- %s",
3294 &gamenum, white_holding, black_holding,
3296 white_holding[strlen(white_holding)-1] = NULLCHAR;
3297 black_holding[strlen(black_holding)-1] = NULLCHAR;
3298 /* [HGM] copy holdings to board holdings area */
3299 CopyHoldings(boards[currentMove], white_holding, WhitePawn);
3300 CopyHoldings(boards[currentMove], black_holding, BlackPawn);
3302 if (appData.zippyPlay && first.initDone) {
3303 ZippyHoldings(white_holding, black_holding,
3307 if (tinyLayout || smallLayout) {
3308 char wh[16], bh[16];
3309 PackHolding(wh, white_holding);
3310 PackHolding(bh, black_holding);
3311 sprintf(str, "[%s-%s] %s-%s", wh, bh,
3312 gameInfo.white, gameInfo.black);
3314 sprintf(str, "%s [%s] vs. %s [%s]",
3315 gameInfo.white, white_holding,
3316 gameInfo.black, black_holding);
3319 DrawPosition(FALSE, boards[currentMove]);
3322 /* Suppress following prompt */
3323 if (looking_at(buf, &i, "*% ")) {
3324 savingComment = FALSE;
3331 i++; /* skip unparsed character and loop back */
3334 if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz suppress printing in ICS interaction window
3335 started != STARTED_HOLDINGS && i > next_out) {
3336 SendToPlayer(&buf[next_out], i - next_out);
3339 suppressKibitz = FALSE; // [HGM] kibitz: has done its duty in if-statement above
3341 leftover_len = buf_len - leftover_start;
3342 /* if buffer ends with something we couldn't parse,
3343 reparse it after appending the next read */
3345 } else if (count == 0) {
3346 RemoveInputSource(isr);
3347 DisplayFatalError(_("Connection closed by ICS"), 0, 0);
3349 DisplayFatalError(_("Error reading from ICS"), error, 1);
3354 /* Board style 12 looks like this:
3356 <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
3358 * The "<12> " is stripped before it gets to this routine. The two
3359 * trailing 0's (flip state and clock ticking) are later addition, and
3360 * some chess servers may not have them, or may have only the first.
3361 * Additional trailing fields may be added in the future.
3364 #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"
3366 #define RELATION_OBSERVING_PLAYED 0
3367 #define RELATION_OBSERVING_STATIC -2 /* examined, oldmoves, or smoves */
3368 #define RELATION_PLAYING_MYMOVE 1
3369 #define RELATION_PLAYING_NOTMYMOVE -1
3370 #define RELATION_EXAMINING 2
3371 #define RELATION_ISOLATED_BOARD -3
3372 #define RELATION_STARTING_POSITION -4 /* FICS only */
3375 ParseBoard12(string)
3378 GameMode newGameMode;
3379 int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
3380 int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
3381 int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
3382 char to_play, board_chars[200];
3383 char move_str[500], str[500], elapsed_time[500];
3384 char black[32], white[32];
3386 int prevMove = currentMove;
3389 int fromX, fromY, toX, toY;
3391 int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
3392 char *bookHit = NULL; // [HGM] book
3394 fromX = fromY = toX = toY = -1;
3398 if (appData.debugMode)
3399 fprintf(debugFP, _("Parsing board: %s\n"), string);
3401 move_str[0] = NULLCHAR;
3402 elapsed_time[0] = NULLCHAR;
3403 { /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
3405 while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
3406 if(string[i] == ' ') { ranks++; files = 0; }
3410 for(j = 0; j <i; j++) board_chars[j] = string[j];
3411 board_chars[i] = '\0';
3414 n = sscanf(string, PATTERN, &to_play, &double_push,
3415 &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
3416 &gamenum, white, black, &relation, &basetime, &increment,
3417 &white_stren, &black_stren, &white_time, &black_time,
3418 &moveNum, str, elapsed_time, move_str, &ics_flip,
3422 snprintf(str, sizeof(str), _("Failed to parse board string:\n\"%s\""), string);
3423 DisplayError(str, 0);
3427 /* Convert the move number to internal form */
3428 moveNum = (moveNum - 1) * 2;
3429 if (to_play == 'B') moveNum++;
3430 if (moveNum >= MAX_MOVES) {
3431 DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
3437 case RELATION_OBSERVING_PLAYED:
3438 case RELATION_OBSERVING_STATIC:
3439 if (gamenum == -1) {
3440 /* Old ICC buglet */
3441 relation = RELATION_OBSERVING_STATIC;
3443 newGameMode = IcsObserving;
3445 case RELATION_PLAYING_MYMOVE:
3446 case RELATION_PLAYING_NOTMYMOVE:
3448 ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
3449 IcsPlayingWhite : IcsPlayingBlack;
3451 case RELATION_EXAMINING:
3452 newGameMode = IcsExamining;
3454 case RELATION_ISOLATED_BOARD:
3456 /* Just display this board. If user was doing something else,
3457 we will forget about it until the next board comes. */
3458 newGameMode = IcsIdle;
3460 case RELATION_STARTING_POSITION:
3461 newGameMode = gameMode;
3465 /* Modify behavior for initial board display on move listing
3468 switch (ics_getting_history) {
3472 case H_GOT_REQ_HEADER:
3473 case H_GOT_UNREQ_HEADER:
3474 /* This is the initial position of the current game */
3475 gamenum = ics_gamenum;
3476 moveNum = 0; /* old ICS bug workaround */
3477 if (to_play == 'B') {
3478 startedFromSetupPosition = TRUE;
3479 blackPlaysFirst = TRUE;
3481 if (forwardMostMove == 0) forwardMostMove = 1;
3482 if (backwardMostMove == 0) backwardMostMove = 1;
3483 if (currentMove == 0) currentMove = 1;
3485 newGameMode = gameMode;
3486 relation = RELATION_STARTING_POSITION; /* ICC needs this */
3488 case H_GOT_UNWANTED_HEADER:
3489 /* This is an initial board that we don't want */
3491 case H_GETTING_MOVES:
3492 /* Should not happen */
3493 DisplayError(_("Error gathering move list: extra board"), 0);
3494 ics_getting_history = H_FALSE;
3498 /* Take action if this is the first board of a new game, or of a
3499 different game than is currently being displayed. */
3500 if (gamenum != ics_gamenum || newGameMode != gameMode ||
3501 relation == RELATION_ISOLATED_BOARD) {
3503 /* Forget the old game and get the history (if any) of the new one */
3504 if (gameMode != BeginningOfGame) {
3508 if (appData.autoRaiseBoard) BoardToTop();
3510 if (gamenum == -1) {
3511 newGameMode = IcsIdle;
3512 } else if (moveNum > 0 && newGameMode != IcsIdle &&
3513 appData.getMoveList) {
3514 /* Need to get game history */
3515 ics_getting_history = H_REQUESTED;
3516 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3520 /* Initially flip the board to have black on the bottom if playing
3521 black or if the ICS flip flag is set, but let the user change
3522 it with the Flip View button. */
3523 flipView = appData.autoFlipView ?
3524 (newGameMode == IcsPlayingBlack) || ics_flip :
3527 /* Done with values from previous mode; copy in new ones */
3528 gameMode = newGameMode;
3530 ics_gamenum = gamenum;
3531 if (gamenum == gs_gamenum) {
3532 int klen = strlen(gs_kind);
3533 if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
3534 sprintf(str, "ICS %s", gs_kind);
3535 gameInfo.event = StrSave(str);
3537 gameInfo.event = StrSave("ICS game");
3539 gameInfo.site = StrSave(appData.icsHost);
3540 gameInfo.date = PGNDate();
3541 gameInfo.round = StrSave("-");
3542 gameInfo.white = StrSave(white);
3543 gameInfo.black = StrSave(black);
3544 timeControl = basetime * 60 * 1000;
3546 timeIncrement = increment * 1000;
3547 movesPerSession = 0;
3548 gameInfo.timeControl = TimeControlTagValue();
3549 VariantSwitch(board, StringToVariant(gameInfo.event) );
3550 if (appData.debugMode) {
3551 fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
3552 fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
3553 setbuf(debugFP, NULL);
3556 gameInfo.outOfBook = NULL;
3558 /* Do we have the ratings? */
3559 if (strcmp(player1Name, white) == 0 &&
3560 strcmp(player2Name, black) == 0) {
3561 if (appData.debugMode)
3562 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3563 player1Rating, player2Rating);
3564 gameInfo.whiteRating = player1Rating;
3565 gameInfo.blackRating = player2Rating;
3566 } else if (strcmp(player2Name, white) == 0 &&
3567 strcmp(player1Name, black) == 0) {
3568 if (appData.debugMode)
3569 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3570 player2Rating, player1Rating);
3571 gameInfo.whiteRating = player2Rating;
3572 gameInfo.blackRating = player1Rating;
3574 player1Name[0] = player2Name[0] = NULLCHAR;
3576 /* Silence shouts if requested */
3577 if (appData.quietPlay &&
3578 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
3579 SendToICS(ics_prefix);
3580 SendToICS("set shout 0\n");
3584 /* Deal with midgame name changes */
3586 if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
3587 if (gameInfo.white) free(gameInfo.white);
3588 gameInfo.white = StrSave(white);
3590 if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
3591 if (gameInfo.black) free(gameInfo.black);
3592 gameInfo.black = StrSave(black);
3596 /* Throw away game result if anything actually changes in examine mode */
3597 if (gameMode == IcsExamining && !newGame) {
3598 gameInfo.result = GameUnfinished;
3599 if (gameInfo.resultDetails != NULL) {
3600 free(gameInfo.resultDetails);
3601 gameInfo.resultDetails = NULL;
3605 /* In pausing && IcsExamining mode, we ignore boards coming
3606 in if they are in a different variation than we are. */
3607 if (pauseExamInvalid) return;
3608 if (pausing && gameMode == IcsExamining) {
3609 if (moveNum <= pauseExamForwardMostMove) {
3610 pauseExamInvalid = TRUE;
3611 forwardMostMove = pauseExamForwardMostMove;
3616 if (appData.debugMode) {
3617 fprintf(debugFP, "load %dx%d board\n", files, ranks);
3619 /* Parse the board */
3620 for (k = 0; k < ranks; k++) {
3621 for (j = 0; j < files; j++)
3622 board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
3623 if(gameInfo.holdingsWidth > 1) {
3624 board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
3625 board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
3628 CopyBoard(boards[moveNum], board);
3630 startedFromSetupPosition =
3631 !CompareBoards(board, initialPosition);
3632 if(startedFromSetupPosition)
3633 initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
3636 /* [HGM] Set castling rights. Take the outermost Rooks,
3637 to make it also work for FRC opening positions. Note that board12
3638 is really defective for later FRC positions, as it has no way to
3639 indicate which Rook can castle if they are on the same side of King.
3640 For the initial position we grant rights to the outermost Rooks,
3641 and remember thos rights, and we then copy them on positions
3642 later in an FRC game. This means WB might not recognize castlings with
3643 Rooks that have moved back to their original position as illegal,
3644 but in ICS mode that is not its job anyway.
3646 if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
3647 { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
3649 for(i=BOARD_LEFT, j= -1; i<BOARD_RGHT; i++)
3650 if(board[0][i] == WhiteRook) j = i;
3651 initialRights[0] = castlingRights[moveNum][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3652 for(i=BOARD_RGHT-1, j= -1; i>=BOARD_LEFT; i--)
3653 if(board[0][i] == WhiteRook) j = i;
3654 initialRights[1] = castlingRights[moveNum][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3655 for(i=BOARD_LEFT, j= -1; i<BOARD_RGHT; i++)
3656 if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
3657 initialRights[3] = castlingRights[moveNum][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3658 for(i=BOARD_RGHT-1, j= -1; i>=BOARD_LEFT; i--)
3659 if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
3660 initialRights[4] = castlingRights[moveNum][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3662 if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
3663 for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
3664 if(board[0][k] == wKing) initialRights[2] = castlingRights[moveNum][2] = k;
3665 for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
3666 if(board[BOARD_HEIGHT-1][k] == bKing)
3667 initialRights[5] = castlingRights[moveNum][5] = k;
3669 r = castlingRights[moveNum][0] = initialRights[0];
3670 if(board[0][r] != WhiteRook) castlingRights[moveNum][0] = -1;
3671 r = castlingRights[moveNum][1] = initialRights[1];
3672 if(board[0][r] != WhiteRook) castlingRights[moveNum][1] = -1;
3673 r = castlingRights[moveNum][3] = initialRights[3];
3674 if(board[BOARD_HEIGHT-1][r] != BlackRook) castlingRights[moveNum][3] = -1;
3675 r = castlingRights[moveNum][4] = initialRights[4];
3676 if(board[BOARD_HEIGHT-1][r] != BlackRook) castlingRights[moveNum][4] = -1;
3677 /* wildcastle kludge: always assume King has rights */
3678 r = castlingRights[moveNum][2] = initialRights[2];
3679 r = castlingRights[moveNum][5] = initialRights[5];
3681 /* [HGM] e.p. rights. Assume that ICS sends file number here? */
3682 epStatus[moveNum] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
3685 if (ics_getting_history == H_GOT_REQ_HEADER ||
3686 ics_getting_history == H_GOT_UNREQ_HEADER) {
3687 /* This was an initial position from a move list, not
3688 the current position */
3692 /* Update currentMove and known move number limits */
3693 newMove = newGame || moveNum > forwardMostMove;
3695 /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
3696 if (!newGame && appData.icsEngineAnalyze && moveNum < forwardMostMove) {
3697 takeback = forwardMostMove - moveNum;
3698 for (i = 0; i < takeback; i++) {
3699 if (appData.debugMode) fprintf(debugFP, "take back move\n");
3700 SendToProgram("undo\n", &first);
3705 forwardMostMove = backwardMostMove = currentMove = moveNum;
3706 if (gameMode == IcsExamining && moveNum == 0) {
3707 /* Workaround for ICS limitation: we are not told the wild
3708 type when starting to examine a game. But if we ask for
3709 the move list, the move list header will tell us */
3710 ics_getting_history = H_REQUESTED;
3711 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3714 } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
3715 || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
3716 forwardMostMove = moveNum;
3717 if (!pausing || currentMove > forwardMostMove)
3718 currentMove = forwardMostMove;
3720 /* New part of history that is not contiguous with old part */
3721 if (pausing && gameMode == IcsExamining) {
3722 pauseExamInvalid = TRUE;
3723 forwardMostMove = pauseExamForwardMostMove;
3726 forwardMostMove = backwardMostMove = currentMove = moveNum;
3727 if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
3728 ics_getting_history = H_REQUESTED;
3729 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3734 /* Update the clocks */
3735 if (strchr(elapsed_time, '.')) {
3737 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
3738 timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
3740 /* Time is in seconds */
3741 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
3742 timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
3747 if (appData.zippyPlay && newGame &&
3748 gameMode != IcsObserving && gameMode != IcsIdle &&
3749 gameMode != IcsExamining)
3750 ZippyFirstBoard(moveNum, basetime, increment);
3753 /* Put the move on the move list, first converting
3754 to canonical algebraic form. */
3756 if (appData.debugMode) {
3757 if (appData.debugMode) { int f = forwardMostMove;
3758 fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
3759 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
3761 fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
3762 fprintf(debugFP, "moveNum = %d\n", moveNum);
3763 fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
3764 setbuf(debugFP, NULL);
3766 if (moveNum <= backwardMostMove) {
3767 /* We don't know what the board looked like before
3769 strcpy(parseList[moveNum - 1], move_str);
3770 strcat(parseList[moveNum - 1], " ");
3771 strcat(parseList[moveNum - 1], elapsed_time);
3772 moveList[moveNum - 1][0] = NULLCHAR;
3773 } else if (strcmp(move_str, "none") == 0) {
3774 // [HGM] long SAN: swapped order; test for 'none' before parsing move
3775 /* Again, we don't know what the board looked like;
3776 this is really the start of the game. */
3777 parseList[moveNum - 1][0] = NULLCHAR;
3778 moveList[moveNum - 1][0] = NULLCHAR;
3779 backwardMostMove = moveNum;
3780 startedFromSetupPosition = TRUE;
3781 fromX = fromY = toX = toY = -1;
3783 // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
3784 // So we parse the long-algebraic move string in stead of the SAN move
3785 int valid; char buf[MSG_SIZ], *prom;
3787 // str looks something like "Q/a1-a2"; kill the slash
3789 sprintf(buf, "%c%s", str[0], str+2);
3790 else strcpy(buf, str); // might be castling
3791 if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
3792 strcat(buf, prom); // long move lacks promo specification!
3793 if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
3794 if(appData.debugMode)
3795 fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
3796 strcpy(move_str, buf);
3798 valid = ParseOneMove(move_str, moveNum - 1, &moveType,
3799 &fromX, &fromY, &toX, &toY, &promoChar)
3800 || ParseOneMove(buf, moveNum - 1, &moveType,
3801 &fromX, &fromY, &toX, &toY, &promoChar);
3802 // end of long SAN patch
3804 (void) CoordsToAlgebraic(boards[moveNum - 1],
3805 PosFlags(moveNum - 1), EP_UNKNOWN,
3806 fromY, fromX, toY, toX, promoChar,
3807 parseList[moveNum-1]);
3808 switch (MateTest(boards[moveNum], PosFlags(moveNum), EP_UNKNOWN,
3809 castlingRights[moveNum]) ) {
3815 if(gameInfo.variant != VariantShogi)
3816 strcat(parseList[moveNum - 1], "+");
3819 case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
3820 strcat(parseList[moveNum - 1], "#");
3823 strcat(parseList[moveNum - 1], " ");
3824 strcat(parseList[moveNum - 1], elapsed_time);
3825 /* currentMoveString is set as a side-effect of ParseOneMove */
3826 strcpy(moveList[moveNum - 1], currentMoveString);
3827 strcat(moveList[moveNum - 1], "\n");
3829 /* Move from ICS was illegal!? Punt. */
3830 if (appData.debugMode) {
3831 fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
3832 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
3834 strcpy(parseList[moveNum - 1], move_str);
3835 strcat(parseList[moveNum - 1], " ");
3836 strcat(parseList[moveNum - 1], elapsed_time);
3837 moveList[moveNum - 1][0] = NULLCHAR;
3838 fromX = fromY = toX = toY = -1;
3841 if (appData.debugMode) {
3842 fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
3843 setbuf(debugFP, NULL);
3847 /* Send move to chess program (BEFORE animating it). */
3848 if (appData.zippyPlay && !newGame && newMove &&
3849 (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
3851 if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
3852 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
3853 if (moveList[moveNum - 1][0] == NULLCHAR) {
3854 sprintf(str, _("Couldn't parse move \"%s\" from ICS"),
3856 DisplayError(str, 0);
3858 if (first.sendTime) {
3859 SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
3861 bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
3862 if (firstMove && !bookHit) {
3864 if (first.useColors) {
3865 SendToProgram(gameMode == IcsPlayingWhite ?
3867 "black\ngo\n", &first);
3869 SendToProgram("go\n", &first);
3871 first.maybeThinking = TRUE;
3874 } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
3875 if (moveList[moveNum - 1][0] == NULLCHAR) {
3876 sprintf(str, _("Couldn't parse move \"%s\" from ICS"), move_str);
3877 DisplayError(str, 0);
3879 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
3880 SendMoveToProgram(moveNum - 1, &first);
3887 if (moveNum > 0 && !gotPremove && !appData.noGUI) {
3888 /* If move comes from a remote source, animate it. If it
3889 isn't remote, it will have already been animated. */
3890 if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
3891 AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
3893 if (!pausing && appData.highlightLastMove) {
3894 SetHighlights(fromX, fromY, toX, toY);
3898 /* Start the clocks */
3899 whiteFlag = blackFlag = FALSE;
3900 appData.clockMode = !(basetime == 0 && increment == 0);
3902 ics_clock_paused = TRUE;
3904 } else if (ticking == 1) {
3905 ics_clock_paused = FALSE;
3907 if (gameMode == IcsIdle ||
3908 relation == RELATION_OBSERVING_STATIC ||
3909 relation == RELATION_EXAMINING ||
3911 DisplayBothClocks();
3915 /* Display opponents and material strengths */
3916 if (gameInfo.variant != VariantBughouse &&
3917 gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
3918 if (tinyLayout || smallLayout) {
3919 if(gameInfo.variant == VariantNormal)
3920 sprintf(str, "%s(%d) %s(%d) {%d %d}",
3921 gameInfo.white, white_stren, gameInfo.black, black_stren,
3922 basetime, increment);
3924 sprintf(str, "%s(%d) %s(%d) {%d %d w%d}",
3925 gameInfo.white, white_stren, gameInfo.black, black_stren,
3926 basetime, increment, (int) gameInfo.variant);
3928 if(gameInfo.variant == VariantNormal)
3929 sprintf(str, "%s (%d) vs. %s (%d) {%d %d}",
3930 gameInfo.white, white_stren, gameInfo.black, black_stren,
3931 basetime, increment);
3933 sprintf(str, "%s (%d) vs. %s (%d) {%d %d %s}",
3934 gameInfo.white, white_stren, gameInfo.black, black_stren,
3935 basetime, increment, VariantName(gameInfo.variant));
3938 if (appData.debugMode) {
3939 fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
3944 /* Display the board */
3945 if (!pausing && !appData.noGUI) {
3947 if (appData.premove)
3949 ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
3950 ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
3951 ClearPremoveHighlights();
3953 DrawPosition(FALSE, boards[currentMove]);
3954 DisplayMove(moveNum - 1);
3955 if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
3956 !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
3957 (gameMode == IcsPlayingBlack) && (WhiteOnMove(moveNum)) ) ) {
3958 if(newMove) RingBell(); else PlayIcsUnfinishedSound();
3962 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
3964 if(bookHit) { // [HGM] book: simulate book reply
3965 static char bookMove[MSG_SIZ]; // a bit generous?
3967 programStats.nodes = programStats.depth = programStats.time =
3968 programStats.score = programStats.got_only_move = 0;
3969 sprintf(programStats.movelist, "%s (xbook)", bookHit);
3971 strcpy(bookMove, "move ");
3972 strcat(bookMove, bookHit);
3973 HandleMachineMove(bookMove, &first);
3982 if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
3983 ics_getting_history = H_REQUESTED;
3984 sprintf(buf, "%smoves %d\n", ics_prefix, ics_gamenum);
3990 AnalysisPeriodicEvent(force)
3993 if (((programStats.ok_to_send == 0 || programStats.line_is_book)
3994 && !force) || !appData.periodicUpdates)
3997 /* Send . command to Crafty to collect stats */
3998 SendToProgram(".\n", &first);
4000 /* Don't send another until we get a response (this makes
4001 us stop sending to old Crafty's which don't understand
4002 the "." command (sending illegal cmds resets node count & time,
4003 which looks bad)) */
4004 programStats.ok_to_send = 0;
4007 void ics_update_width(new_width)
4010 ics_printf("set width %d\n", new_width);
4014 SendMoveToProgram(moveNum, cps)
4016 ChessProgramState *cps;
4020 if (cps->useUsermove) {
4021 SendToProgram("usermove ", cps);
4025 if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4026 int len = space - parseList[moveNum];
4027 memcpy(buf, parseList[moveNum], len);
4029 buf[len] = NULLCHAR;
4031 sprintf(buf, "%s\n", parseList[moveNum]);
4033 SendToProgram(buf, cps);
4035 if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4036 AlphaRank(moveList[moveNum], 4);
4037 SendToProgram(moveList[moveNum], cps);
4038 AlphaRank(moveList[moveNum], 4); // and back
4040 /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
4041 * the engine. It would be nice to have a better way to identify castle
4043 if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
4044 && cps->useOOCastle) {
4045 int fromX = moveList[moveNum][0] - AAA;
4046 int fromY = moveList[moveNum][1] - ONE;
4047 int toX = moveList[moveNum][2] - AAA;
4048 int toY = moveList[moveNum][3] - ONE;
4049 if((boards[moveNum][fromY][fromX] == WhiteKing
4050 && boards[moveNum][toY][toX] == WhiteRook)
4051 || (boards[moveNum][fromY][fromX] == BlackKing
4052 && boards[moveNum][toY][toX] == BlackRook)) {
4053 if(toX > fromX) SendToProgram("O-O\n", cps);
4054 else SendToProgram("O-O-O\n", cps);
4056 else SendToProgram(moveList[moveNum], cps);
4058 else SendToProgram(moveList[moveNum], cps);
4059 /* End of additions by Tord */
4062 /* [HGM] setting up the opening has brought engine in force mode! */
4063 /* Send 'go' if we are in a mode where machine should play. */
4064 if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4065 (gameMode == TwoMachinesPlay ||
4067 gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite ||
4069 gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4070 SendToProgram("go\n", cps);
4071 if (appData.debugMode) {
4072 fprintf(debugFP, "(extra)\n");
4075 setboardSpoiledMachineBlack = 0;
4079 SendMoveToICS(moveType, fromX, fromY, toX, toY)
4081 int fromX, fromY, toX, toY;
4083 char user_move[MSG_SIZ];
4087 sprintf(user_move, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4088 (int)moveType, fromX, fromY, toX, toY);
4089 DisplayError(user_move + strlen("say "), 0);
4091 case WhiteKingSideCastle:
4092 case BlackKingSideCastle:
4093 case WhiteQueenSideCastleWild:
4094 case BlackQueenSideCastleWild:
4096 case WhiteHSideCastleFR:
4097 case BlackHSideCastleFR:
4099 sprintf(user_move, "o-o\n");
4101 case WhiteQueenSideCastle:
4102 case BlackQueenSideCastle:
4103 case WhiteKingSideCastleWild:
4104 case BlackKingSideCastleWild:
4106 case WhiteASideCastleFR:
4107 case BlackASideCastleFR:
4109 sprintf(user_move, "o-o-o\n");
4111 case WhitePromotionQueen:
4112 case BlackPromotionQueen:
4113 case WhitePromotionRook:
4114 case BlackPromotionRook:
4115 case WhitePromotionBishop:
4116 case BlackPromotionBishop:
4117 case WhitePromotionKnight:
4118 case BlackPromotionKnight:
4119 case WhitePromotionKing:
4120 case BlackPromotionKing:
4121 case WhitePromotionChancellor:
4122 case BlackPromotionChancellor:
4123 case WhitePromotionArchbishop:
4124 case BlackPromotionArchbishop:
4125 if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier)
4126 sprintf(user_move, "%c%c%c%c=%c\n",
4127 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4128 PieceToChar(WhiteFerz));
4129 else if(gameInfo.variant == VariantGreat)
4130 sprintf(user_move, "%c%c%c%c=%c\n",
4131 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4132 PieceToChar(WhiteMan));
4134 sprintf(user_move, "%c%c%c%c=%c\n",
4135 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4136 PieceToChar(PromoPiece(moveType)));
4140 sprintf(user_move, "%c@%c%c\n",
4141 ToUpper(PieceToChar((ChessSquare) fromX)),
4142 AAA + toX, ONE + toY);
4145 case WhiteCapturesEnPassant:
4146 case BlackCapturesEnPassant:
4147 case IllegalMove: /* could be a variant we don't quite understand */
4148 sprintf(user_move, "%c%c%c%c\n",
4149 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4152 SendToICS(user_move);
4153 if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
4154 ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
4158 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
4163 if (rf == DROP_RANK) {
4164 sprintf(move, "%c@%c%c\n",
4165 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
4167 if (promoChar == 'x' || promoChar == NULLCHAR) {
4168 sprintf(move, "%c%c%c%c\n",
4169 AAA + ff, ONE + rf, AAA + ft, ONE + rt);
4171 sprintf(move, "%c%c%c%c%c\n",
4172 AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
4178 ProcessICSInitScript(f)
4183 while (fgets(buf, MSG_SIZ, f)) {
4184 SendToICSDelayed(buf,(long)appData.msLoginDelay);
4191 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
4193 AlphaRank(char *move, int n)
4195 // char *p = move, c; int x, y;
4197 if (appData.debugMode) {
4198 fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
4202 move[2]>='0' && move[2]<='9' &&
4203 move[3]>='a' && move[3]<='x' ) {
4205 move[2] = BOARD_RGHT -1 - (move[2]-'1') + AAA;
4206 move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4208 if(move[0]>='0' && move[0]<='9' &&
4209 move[1]>='a' && move[1]<='x' &&
4210 move[2]>='0' && move[2]<='9' &&
4211 move[3]>='a' && move[3]<='x' ) {
4212 /* input move, Shogi -> normal */
4213 move[0] = BOARD_RGHT -1 - (move[0]-'1') + AAA;
4214 move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
4215 move[2] = BOARD_RGHT -1 - (move[2]-'1') + AAA;
4216 move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4219 move[3]>='0' && move[3]<='9' &&
4220 move[2]>='a' && move[2]<='x' ) {
4222 move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4223 move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4226 move[0]>='a' && move[0]<='x' &&
4227 move[3]>='0' && move[3]<='9' &&
4228 move[2]>='a' && move[2]<='x' ) {
4229 /* output move, normal -> Shogi */
4230 move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
4231 move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
4232 move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4233 move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4234 if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
4236 if (appData.debugMode) {
4237 fprintf(debugFP, " out = '%s'\n", move);
4241 /* Parser for moves from gnuchess, ICS, or user typein box */
4243 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
4246 ChessMove *moveType;
4247 int *fromX, *fromY, *toX, *toY;
4250 if (appData.debugMode) {
4251 fprintf(debugFP, "move to parse: %s\n", move);
4253 *moveType = yylexstr(moveNum, move);
4255 switch (*moveType) {
4256 case WhitePromotionChancellor:
4257 case BlackPromotionChancellor:
4258 case WhitePromotionArchbishop:
4259 case BlackPromotionArchbishop:
4260 case WhitePromotionQueen:
4261 case BlackPromotionQueen:
4262 case WhitePromotionRook:
4263 case BlackPromotionRook:
4264 case WhitePromotionBishop:
4265 case BlackPromotionBishop:
4266 case WhitePromotionKnight:
4267 case BlackPromotionKnight:
4268 case WhitePromotionKing:
4269 case BlackPromotionKing:
4271 case WhiteCapturesEnPassant:
4272 case BlackCapturesEnPassant:
4273 case WhiteKingSideCastle:
4274 case WhiteQueenSideCastle:
4275 case BlackKingSideCastle:
4276 case BlackQueenSideCastle:
4277 case WhiteKingSideCastleWild:
4278 case WhiteQueenSideCastleWild:
4279 case BlackKingSideCastleWild:
4280 case BlackQueenSideCastleWild:
4281 /* Code added by Tord: */
4282 case WhiteHSideCastleFR:
4283 case WhiteASideCastleFR:
4284 case BlackHSideCastleFR:
4285 case BlackASideCastleFR:
4286 /* End of code added by Tord */
4287 case IllegalMove: /* bug or odd chess variant */
4288 *fromX = currentMoveString[0] - AAA;
4289 *fromY = currentMoveString[1] - ONE;
4290 *toX = currentMoveString[2] - AAA;
4291 *toY = currentMoveString[3] - ONE;
4292 *promoChar = currentMoveString[4];
4293 if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
4294 *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
4295 if (appData.debugMode) {
4296 fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
4298 *fromX = *fromY = *toX = *toY = 0;
4301 if (appData.testLegality) {
4302 return (*moveType != IllegalMove);
4304 return !(fromX == fromY && toX == toY);
4309 *fromX = *moveType == WhiteDrop ?
4310 (int) CharToPiece(ToUpper(currentMoveString[0])) :
4311 (int) CharToPiece(ToLower(currentMoveString[0]));
4313 *toX = currentMoveString[2] - AAA;
4314 *toY = currentMoveString[3] - ONE;
4315 *promoChar = NULLCHAR;
4319 case ImpossibleMove:
4320 case (ChessMove) 0: /* end of file */
4329 if (appData.debugMode) {
4330 fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
4333 *fromX = *fromY = *toX = *toY = 0;
4334 *promoChar = NULLCHAR;
4339 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
4340 // All positions will have equal probability, but the current method will not provide a unique
4341 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
4347 int piecesLeft[(int)BlackPawn];
4348 int seed, nrOfShuffles;
4350 void GetPositionNumber()
4351 { // sets global variable seed
4354 seed = appData.defaultFrcPosition;
4355 if(seed < 0) { // randomize based on time for negative FRC position numbers
4356 for(i=0; i<50; i++) seed += random();
4357 seed = random() ^ random() >> 8 ^ random() << 8;
4358 if(seed<0) seed = -seed;
4362 int put(Board board, int pieceType, int rank, int n, int shade)
4363 // put the piece on the (n-1)-th empty squares of the given shade
4367 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
4368 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
4369 board[rank][i] = (ChessSquare) pieceType;
4370 squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
4372 piecesLeft[pieceType]--;
4380 void AddOnePiece(Board board, int pieceType, int rank, int shade)
4381 // calculate where the next piece goes, (any empty square), and put it there
4385 i = seed % squaresLeft[shade];
4386 nrOfShuffles *= squaresLeft[shade];
4387 seed /= squaresLeft[shade];
4388 put(board, pieceType, rank, i, shade);
4391 void AddTwoPieces(Board board, int pieceType, int rank)
4392 // calculate where the next 2 identical pieces go, (any empty square), and put it there
4394 int i, n=squaresLeft[ANY], j=n-1, k;
4396 k = n*(n-1)/2; // nr of possibilities, not counting permutations
4397 i = seed % k; // pick one
4400 while(i >= j) i -= j--;
4401 j = n - 1 - j; i += j;
4402 put(board, pieceType, rank, j, ANY);
4403 put(board, pieceType, rank, i, ANY);
4406 void SetUpShuffle(Board board, int number)
4410 GetPositionNumber(); nrOfShuffles = 1;
4412 squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
4413 squaresLeft[ANY] = BOARD_RGHT - BOARD_LEFT;
4414 squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
4416 for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
4418 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
4419 p = (int) board[0][i];
4420 if(p < (int) BlackPawn) piecesLeft[p] ++;
4421 board[0][i] = EmptySquare;
4424 if(PosFlags(0) & F_ALL_CASTLE_OK) {
4425 // shuffles restricted to allow normal castling put KRR first
4426 if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
4427 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
4428 else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
4429 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
4430 if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
4431 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
4432 if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
4433 put(board, WhiteRook, 0, 0, ANY);
4434 // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
4437 if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
4438 // only for even boards make effort to put pairs of colorbound pieces on opposite colors
4439 for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
4440 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
4441 while(piecesLeft[p] >= 2) {
4442 AddOnePiece(board, p, 0, LITE);
4443 AddOnePiece(board, p, 0, DARK);
4445 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
4448 for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
4449 // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
4450 // but we leave King and Rooks for last, to possibly obey FRC restriction
4451 if(p == (int)WhiteRook) continue;
4452 while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
4453 if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY); // add the odd piece
4456 // now everything is placed, except perhaps King (Unicorn) and Rooks
4458 if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
4459 // Last King gets castling rights
4460 while(piecesLeft[(int)WhiteUnicorn]) {
4461 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
4462 initialRights[2] = initialRights[5] = castlingRights[0][2] = castlingRights[0][5] = i;
4465 while(piecesLeft[(int)WhiteKing]) {
4466 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
4467 initialRights[2] = initialRights[5] = castlingRights[0][2] = castlingRights[0][5] = i;
4472 while(piecesLeft[(int)WhiteKing]) AddOnePiece(board, WhiteKing, 0, ANY);
4473 while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
4476 // Only Rooks can be left; simply place them all
4477 while(piecesLeft[(int)WhiteRook]) {
4478 i = put(board, WhiteRook, 0, 0, ANY);
4479 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
4482 initialRights[1] = initialRights[4] = castlingRights[0][1] = castlingRights[0][4] = i;
4484 initialRights[0] = initialRights[3] = castlingRights[0][0] = castlingRights[0][3] = i;
4487 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
4488 board[BOARD_HEIGHT-1][i] = (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
4491 if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
4494 int SetCharTable( char *table, const char * map )
4495 /* [HGM] moved here from winboard.c because of its general usefulness */
4496 /* Basically a safe strcpy that uses the last character as King */
4498 int result = FALSE; int NrPieces;
4500 if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
4501 && NrPieces >= 12 && !(NrPieces&1)) {
4502 int i; /* [HGM] Accept even length from 12 to 34 */
4504 for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
4505 for( i=0; i<NrPieces/2-1; i++ ) {
4507 table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
4509 table[(int) WhiteKing] = map[NrPieces/2-1];
4510 table[(int) BlackKing] = map[NrPieces-1];
4518 void Prelude(Board board)
4519 { // [HGM] superchess: random selection of exo-pieces
4520 int i, j, k; ChessSquare p;
4521 static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
4523 GetPositionNumber(); // use FRC position number
4525 if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
4526 SetCharTable(pieceToChar, appData.pieceToCharTable);
4527 for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
4528 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
4531 j = seed%4; seed /= 4;
4532 p = board[0][BOARD_LEFT+j]; board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
4533 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
4534 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
4535 j = seed%3 + (seed%3 >= j); seed /= 3;
4536 p = board[0][BOARD_LEFT+j]; board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
4537 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
4538 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
4539 j = seed%3; seed /= 3;
4540 p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
4541 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
4542 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
4543 j = seed%2 + (seed%2 >= j); seed /= 2;
4544 p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
4545 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
4546 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
4547 j = seed%4; seed /= 4; put(board, exoPieces[3], 0, j, ANY);
4548 j = seed%3; seed /= 3; put(board, exoPieces[2], 0, j, ANY);
4549 j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
4550 put(board, exoPieces[0], 0, 0, ANY);
4551 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
4555 InitPosition(redraw)
4558 ChessSquare (* pieces)[BOARD_SIZE];
4559 int i, j, pawnRow, overrule,
4560 oldx = gameInfo.boardWidth,
4561 oldy = gameInfo.boardHeight,
4562 oldh = gameInfo.holdingsWidth,
4563 oldv = gameInfo.variant;
4565 if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
4567 /* [AS] Initialize pv info list [HGM] and game status */
4569 for( i=0; i<MAX_MOVES; i++ ) {
4570 pvInfoList[i].depth = 0;
4571 epStatus[i]=EP_NONE;
4572 for( j=0; j<BOARD_SIZE; j++ ) castlingRights[i][j] = -1;
4575 initialRulePlies = 0; /* 50-move counter start */
4577 castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
4578 castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
4582 /* [HGM] logic here is completely changed. In stead of full positions */
4583 /* the initialized data only consist of the two backranks. The switch */
4584 /* selects which one we will use, which is than copied to the Board */
4585 /* initialPosition, which for the rest is initialized by Pawns and */
4586 /* empty squares. This initial position is then copied to boards[0], */
4587 /* possibly after shuffling, so that it remains available. */
4589 gameInfo.holdingsWidth = 0; /* default board sizes */
4590 gameInfo.boardWidth = 8;
4591 gameInfo.boardHeight = 8;
4592 gameInfo.holdingsSize = 0;
4593 nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
4594 for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1; /* but no rights yet */
4595 SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
4597 switch (gameInfo.variant) {
4598 case VariantFischeRandom:
4599 shuffleOpenings = TRUE;
4603 case VariantShatranj:
4604 pieces = ShatranjArray;
4605 nrCastlingRights = 0;
4606 SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
4608 case VariantTwoKings:
4609 pieces = twoKingsArray;
4611 case VariantCapaRandom:
4612 shuffleOpenings = TRUE;
4613 case VariantCapablanca:
4614 pieces = CapablancaArray;
4615 gameInfo.boardWidth = 10;
4616 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
4619 pieces = GothicArray;
4620 gameInfo.boardWidth = 10;
4621 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
4624 pieces = JanusArray;
4625 gameInfo.boardWidth = 10;
4626 SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
4627 nrCastlingRights = 6;
4628 castlingRights[0][0] = initialRights[0] = BOARD_RGHT-1;
4629 castlingRights[0][1] = initialRights[1] = BOARD_LEFT;
4630 castlingRights[0][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
4631 castlingRights[0][3] = initialRights[3] = BOARD_RGHT-1;
4632 castlingRights[0][4] = initialRights[4] = BOARD_LEFT;
4633 castlingRights[0][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
4636 pieces = FalconArray;
4637 gameInfo.boardWidth = 10;
4638 SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
4640 case VariantXiangqi:
4641 pieces = XiangqiArray;
4642 gameInfo.boardWidth = 9;
4643 gameInfo.boardHeight = 10;
4644 nrCastlingRights = 0;
4645 SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
4648 pieces = ShogiArray;
4649 gameInfo.boardWidth = 9;
4650 gameInfo.boardHeight = 9;
4651 gameInfo.holdingsSize = 7;
4652 nrCastlingRights = 0;
4653 SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
4655 case VariantCourier:
4656 pieces = CourierArray;
4657 gameInfo.boardWidth = 12;
4658 nrCastlingRights = 0;
4659 SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
4660 for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1;
4662 case VariantKnightmate:
4663 pieces = KnightmateArray;
4664 SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
4667 pieces = fairyArray;
4668 SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVSLUKpnbrqfeacwmohijgdvsluk");
4671 pieces = GreatArray;
4672 gameInfo.boardWidth = 10;
4673 SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
4674 gameInfo.holdingsSize = 8;
4678 SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
4679 gameInfo.holdingsSize = 8;
4680 startedFromSetupPosition = TRUE;
4682 case VariantCrazyhouse:
4683 case VariantBughouse:
4685 SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
4686 gameInfo.holdingsSize = 5;
4688 case VariantWildCastle:
4690 /* !!?shuffle with kings guaranteed to be on d or e file */
4691 shuffleOpenings = 1;
4693 case VariantNoCastle:
4695 nrCastlingRights = 0;
4696 for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1;
4697 /* !!?unconstrained back-rank shuffle */
4698 shuffleOpenings = 1;
4703 if(appData.NrFiles >= 0) {
4704 if(gameInfo.boardWidth != appData.NrFiles) overrule++;
4705 gameInfo.boardWidth = appData.NrFiles;
4707 if(appData.NrRanks >= 0) {
4708 gameInfo.boardHeight = appData.NrRanks;
4710 if(appData.holdingsSize >= 0) {
4711 i = appData.holdingsSize;
4712 if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
4713 gameInfo.holdingsSize = i;
4715 if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
4716 if(BOARD_HEIGHT > BOARD_SIZE || BOARD_WIDTH > BOARD_SIZE)
4717 DisplayFatalError(_("Recompile to support this BOARD_SIZE!"), 0, 2);
4719 pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
4720 if(pawnRow < 1) pawnRow = 1;
4722 /* User pieceToChar list overrules defaults */
4723 if(appData.pieceToCharTable != NULL)
4724 SetCharTable(pieceToChar, appData.pieceToCharTable);
4726 for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
4728 if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
4729 s = (ChessSquare) 0; /* account holding counts in guard band */
4730 for( i=0; i<BOARD_HEIGHT; i++ )
4731 initialPosition[i][j] = s;
4733 if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
4734 initialPosition[0][j] = pieces[0][j-gameInfo.holdingsWidth];
4735 initialPosition[pawnRow][j] = WhitePawn;
4736 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = BlackPawn;
4737 if(gameInfo.variant == VariantXiangqi) {
4739 initialPosition[pawnRow][j] =
4740 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
4741 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
4742 initialPosition[2][j] = WhiteCannon;
4743 initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
4747 initialPosition[BOARD_HEIGHT-1][j] = pieces[1][j-gameInfo.holdingsWidth];
4749 if( (gameInfo.variant == VariantShogi) && !overrule ) {
4752 initialPosition[1][j] = WhiteBishop;
4753 initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
4755 initialPosition[1][j] = WhiteRook;
4756 initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
4759 if( nrCastlingRights == -1) {
4760 /* [HGM] Build normal castling rights (must be done after board sizing!) */
4761 /* This sets default castling rights from none to normal corners */
4762 /* Variants with other castling rights must set them themselves above */
4763 nrCastlingRights = 6;
4765 castlingRights[0][0] = initialRights[0] = BOARD_RGHT-1;
4766 castlingRights[0][1] = initialRights[1] = BOARD_LEFT;
4767 castlingRights[0][2] = initialRights[2] = BOARD_WIDTH>>1;
4768 castlingRights[0][3] = initialRights[3] = BOARD_RGHT-1;
4769 castlingRights[0][4] = initialRights[4] = BOARD_LEFT;
4770 castlingRights[0][5] = initialRights[5] = BOARD_WIDTH>>1;
4773 if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
4774 if(gameInfo.variant == VariantGreat) { // promotion commoners
4775 initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
4776 initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
4777 initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
4778 initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
4780 if (appData.debugMode) {
4781 fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
4783 if(shuffleOpenings) {
4784 SetUpShuffle(initialPosition, appData.defaultFrcPosition);
4785 startedFromSetupPosition = TRUE;
4787 if(startedFromPositionFile) {
4788 /* [HGM] loadPos: use PositionFile for every new game */
4789 CopyBoard(initialPosition, filePosition);
4790 for(i=0; i<nrCastlingRights; i++)
4791 castlingRights[0][i] = initialRights[i] = fileRights[i];
4792 startedFromSetupPosition = TRUE;
4795 CopyBoard(boards[0], initialPosition);
4797 if(oldx != gameInfo.boardWidth ||
4798 oldy != gameInfo.boardHeight ||
4799 oldh != gameInfo.holdingsWidth
4801 || oldv == VariantGothic || // For licensing popups
4802 gameInfo.variant == VariantGothic
4805 || oldv == VariantFalcon ||
4806 gameInfo.variant == VariantFalcon
4809 InitDrawingSizes(-2 ,0);
4812 DrawPosition(TRUE, boards[currentMove]);
4816 SendBoard(cps, moveNum)
4817 ChessProgramState *cps;
4820 char message[MSG_SIZ];
4822 if (cps->useSetboard) {
4823 char* fen = PositionToFEN(moveNum, cps->fenOverride);
4824 sprintf(message, "setboard %s\n", fen);
4825 SendToProgram(message, cps);
4831 /* Kludge to set black to move, avoiding the troublesome and now
4832 * deprecated "black" command.
4834 if (!WhiteOnMove(moveNum)) SendToProgram("a2a3\n", cps);
4836 SendToProgram("edit\n", cps);
4837 SendToProgram("#\n", cps);
4838 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
4839 bp = &boards[moveNum][i][BOARD_LEFT];
4840 for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
4841 if ((int) *bp < (int) BlackPawn) {
4842 sprintf(message, "%c%c%c\n", PieceToChar(*bp),
4844 if(message[0] == '+' || message[0] == '~') {
4845 sprintf(message, "%c%c%c+\n",
4846 PieceToChar((ChessSquare)(DEMOTED *bp)),
4849 if(cps->alphaRank) { /* [HGM] shogi: translate coords */
4850 message[1] = BOARD_RGHT - 1 - j + '1';
4851 message[2] = BOARD_HEIGHT - 1 - i + 'a';
4853 SendToProgram(message, cps);
4858 SendToProgram("c\n", cps);
4859 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
4860 bp = &boards[moveNum][i][BOARD_LEFT];
4861 for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
4862 if (((int) *bp != (int) EmptySquare)
4863 && ((int) *bp >= (int) BlackPawn)) {
4864 sprintf(message, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
4866 if(message[0] == '+' || message[0] == '~') {
4867 sprintf(message, "%c%c%c+\n",
4868 PieceToChar((ChessSquare)(DEMOTED *bp)),
4871 if(cps->alphaRank) { /* [HGM] shogi: translate coords */
4872 message[1] = BOARD_RGHT - 1 - j + '1';
4873 message[2] = BOARD_HEIGHT - 1 - i + 'a';
4875 SendToProgram(message, cps);
4880 SendToProgram(".\n", cps);
4882 setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
4886 HasPromotionChoice(int fromX, int fromY, int toX, int toY, char *promoChoice)
4888 /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
4889 /* [HGM] add Shogi promotions */
4890 int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
4895 if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
4896 if(toX < BOARD_LEFT || toX >= BOARD_RGHT) return FALSE; // move into holdings
4898 if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
4899 !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
4902 piece = boards[currentMove][fromY][fromX];
4903 if(gameInfo.variant == VariantShogi) {
4904 promotionZoneSize = 3;
4905 highestPromotingPiece = (int)WhiteFerz;
4908 // next weed out all moves that do not touch the promotion zone at all
4909 if((int)piece >= BlackPawn) {
4910 if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
4912 highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
4914 if( toY < BOARD_HEIGHT - promotionZoneSize &&
4915 fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
4918 if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
4920 // weed out mandatory Shogi promotions
4921 if(gameInfo.variant == VariantShogi) {
4922 if(piece >= BlackPawn) {
4923 if(toY == 0 && piece == BlackPawn ||
4924 toY == 0 && piece == BlackQueen ||
4925 toY <= 1 && piece == BlackKnight) {
4930 if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
4931 toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
4932 toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
4939 // weed out obviously illegal Pawn moves
4940 if(appData.testLegality && (piece == WhitePawn || piece == BlackPawn) ) {
4941 if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
4942 if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
4943 if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
4944 if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
4945 // note we are not allowed to test for valid (non-)capture, due to premove
4948 // we either have a choice what to promote to, or (in Shogi) whether to promote
4949 if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier) {
4950 *promoChoice = PieceToChar(BlackFerz); // no choice
4953 if(appData.alwaysPromoteToQueen) { // predetermined
4954 if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantLosers)
4955 *promoChoice = PieceToChar(BlackKing); // in Suicide Q is the last thing we want
4956 else *promoChoice = PieceToChar(BlackQueen);
4960 // suppress promotion popup on illegal moves that are not premoves
4961 premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
4962 gameMode == IcsPlayingBlack && WhiteOnMove(currentMove);
4963 if(appData.testLegality && !premove) {
4964 moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
4965 epStatus[currentMove], castlingRights[currentMove],
4966 fromY, fromX, toY, toX, NULLCHAR);
4967 if(moveType != WhitePromotionQueen && moveType != BlackPromotionQueen &&
4968 moveType != WhitePromotionKnight && moveType != BlackPromotionKnight)
4976 InPalace(row, column)
4978 { /* [HGM] for Xiangqi */
4979 if( (row < 3 || row > BOARD_HEIGHT-4) &&
4980 column < (BOARD_WIDTH + 4)/2 &&
4981 column > (BOARD_WIDTH - 5)/2 ) return TRUE;
4986 PieceForSquare (x, y)
4990 if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
4993 return boards[currentMove][y][x];
4997 OKToStartUserMove(x, y)
5000 ChessSquare from_piece;
5003 if (matchMode) return FALSE;
5004 if (gameMode == EditPosition) return TRUE;
5006 if (x >= 0 && y >= 0)
5007 from_piece = boards[currentMove][y][x];
5009 from_piece = EmptySquare;
5011 if (from_piece == EmptySquare) return FALSE;
5013 white_piece = (int)from_piece >= (int)WhitePawn &&
5014 (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
5017 case PlayFromGameFile:
5019 case TwoMachinesPlay:
5027 case MachinePlaysWhite:
5028 case IcsPlayingBlack:
5029 if (appData.zippyPlay) return FALSE;
5031 DisplayMoveError(_("You are playing Black"));
5036 case MachinePlaysBlack:
5037 case IcsPlayingWhite:
5038 if (appData.zippyPlay) return FALSE;
5040 DisplayMoveError(_("You are playing White"));
5046 if (!white_piece && WhiteOnMove(currentMove)) {
5047 DisplayMoveError(_("It is White's turn"));
5050 if (white_piece && !WhiteOnMove(currentMove)) {
5051 DisplayMoveError(_("It is Black's turn"));
5054 if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
5055 /* Editing correspondence game history */
5056 /* Could disallow this or prompt for confirmation */
5059 if (currentMove < forwardMostMove) {
5060 /* Discarding moves */
5061 /* Could prompt for confirmation here,
5062 but I don't think that's such a good idea */
5063 forwardMostMove = currentMove;
5067 case BeginningOfGame:
5068 if (appData.icsActive) return FALSE;
5069 if (!appData.noChessProgram) {
5071 DisplayMoveError(_("You are playing White"));
5078 if (!white_piece && WhiteOnMove(currentMove)) {
5079 DisplayMoveError(_("It is White's turn"));
5082 if (white_piece && !WhiteOnMove(currentMove)) {
5083 DisplayMoveError(_("It is Black's turn"));
5092 if (currentMove != forwardMostMove && gameMode != AnalyzeMode
5093 && gameMode != AnalyzeFile && gameMode != Training) {
5094 DisplayMoveError(_("Displayed position is not current"));
5100 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
5101 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
5102 int lastLoadGameUseList = FALSE;
5103 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
5104 ChessMove lastLoadGameStart = (ChessMove) 0;
5107 UserMoveTest(fromX, fromY, toX, toY, promoChar, captureOwn)
5108 int fromX, fromY, toX, toY;
5113 ChessSquare pdown, pup;
5115 /* Check if the user is playing in turn. This is complicated because we
5116 let the user "pick up" a piece before it is his turn. So the piece he
5117 tried to pick up may have been captured by the time he puts it down!
5118 Therefore we use the color the user is supposed to be playing in this
5119 test, not the color of the piece that is currently on the starting
5120 square---except in EditGame mode, where the user is playing both
5121 sides; fortunately there the capture race can't happen. (It can
5122 now happen in IcsExamining mode, but that's just too bad. The user
5123 will get a somewhat confusing message in that case.)
5127 case PlayFromGameFile:
5129 case TwoMachinesPlay:
5133 /* We switched into a game mode where moves are not accepted,
5134 perhaps while the mouse button was down. */
5135 return ImpossibleMove;
5137 case MachinePlaysWhite:
5138 /* User is moving for Black */
5139 if (WhiteOnMove(currentMove)) {
5140 DisplayMoveError(_("It is White's turn"));
5141 return ImpossibleMove;
5145 case MachinePlaysBlack:
5146 /* User is moving for White */
5147 if (!WhiteOnMove(currentMove)) {
5148 DisplayMoveError(_("It is Black's turn"));
5149 return ImpossibleMove;
5155 case BeginningOfGame:
5158 if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
5159 (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
5160 /* User is moving for Black */
5161 if (WhiteOnMove(currentMove)) {
5162 DisplayMoveError(_("It is White's turn"));
5163 return ImpossibleMove;
5166 /* User is moving for White */
5167 if (!WhiteOnMove(currentMove)) {
5168 DisplayMoveError(_("It is Black's turn"));
5169 return ImpossibleMove;
5174 case IcsPlayingBlack:
5175 /* User is moving for Black */
5176 if (WhiteOnMove(currentMove)) {
5177 if (!appData.premove) {
5178 DisplayMoveError(_("It is White's turn"));
5179 } else if (toX >= 0 && toY >= 0) {
5182 premoveFromX = fromX;
5183 premoveFromY = fromY;
5184 premovePromoChar = promoChar;
5186 if (appData.debugMode)
5187 fprintf(debugFP, "Got premove: fromX %d,"
5188 "fromY %d, toX %d, toY %d\n",
5189 fromX, fromY, toX, toY);
5191 return ImpossibleMove;
5195 case IcsPlayingWhite:
5196 /* User is moving for White */
5197 if (!WhiteOnMove(currentMove)) {
5198 if (!appData.premove) {
5199 DisplayMoveError(_("It is Black's turn"));
5200 } else if (toX >= 0 && toY >= 0) {
5203 premoveFromX = fromX;
5204 premoveFromY = fromY;
5205 premovePromoChar = promoChar;
5207 if (appData.debugMode)
5208 fprintf(debugFP, "Got premove: fromX %d,"
5209 "fromY %d, toX %d, toY %d\n",
5210 fromX, fromY, toX, toY);
5212 return ImpossibleMove;
5220 /* EditPosition, empty square, or different color piece;
5221 click-click move is possible */
5222 if (toX == -2 || toY == -2) {
5223 boards[0][fromY][fromX] = EmptySquare;
5224 return AmbiguousMove;
5225 } else if (toX >= 0 && toY >= 0) {
5226 boards[0][toY][toX] = boards[0][fromY][fromX];
5227 boards[0][fromY][fromX] = EmptySquare;
5228 return AmbiguousMove;
5230 return ImpossibleMove;
5233 pdown = boards[currentMove][fromY][fromX];
5234 pup = boards[currentMove][toY][toX];
5236 /* [HGM] If move started in holdings, it means a drop */
5237 if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) {
5238 if( pup != EmptySquare ) return ImpossibleMove;
5239 if(appData.testLegality) {
5240 /* it would be more logical if LegalityTest() also figured out
5241 * which drops are legal. For now we forbid pawns on back rank.
5242 * Shogi is on its own here...
5244 if( (pdown == WhitePawn || pdown == BlackPawn) &&
5245 (toY == 0 || toY == BOARD_HEIGHT -1 ) )
5246 return(ImpossibleMove); /* no pawn drops on 1st/8th */
5248 return WhiteDrop; /* Not needed to specify white or black yet */
5251 userOfferedDraw = FALSE;
5253 /* [HGM] always test for legality, to get promotion info */
5254 moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5255 epStatus[currentMove], castlingRights[currentMove],
5256 fromY, fromX, toY, toX, promoChar);
5257 /* [HGM] but possibly ignore an IllegalMove result */
5258 if (appData.testLegality) {
5259 if (moveType == IllegalMove || moveType == ImpossibleMove) {
5260 DisplayMoveError(_("Illegal move"));
5261 return ImpossibleMove;
5264 if(appData.debugMode) fprintf(debugFP, "moveType 3 = %d, promochar = %x\n", moveType, promoChar);
5266 /* [HGM] <popupFix> in stead of calling FinishMove directly, this
5267 function is made into one that returns an OK move type if FinishMove
5268 should be called. This to give the calling driver routine the
5269 opportunity to finish the userMove input with a promotion popup,
5270 without bothering the user with this for invalid or illegal moves */
5272 /* FinishMove(moveType, fromX, fromY, toX, toY, promoChar); */
5275 /* Common tail of UserMoveEvent and DropMenuEvent */
5277 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
5279 int fromX, fromY, toX, toY;
5280 /*char*/int promoChar;
5283 if(appData.debugMode) fprintf(debugFP, "moveType 5 = %d, promochar = %x\n", moveType, promoChar);
5284 if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) && promoChar != NULLCHAR) {
5285 // [HGM] superchess: suppress promotions to non-available piece
5286 int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
5287 if(WhiteOnMove(currentMove)) {
5288 if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
5290 if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
5294 /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
5295 move type in caller when we know the move is a legal promotion */
5296 if(moveType == NormalMove && promoChar)
5297 moveType = PromoCharToMoveType(WhiteOnMove(currentMove), promoChar);
5298 if(appData.debugMode) fprintf(debugFP, "moveType 1 = %d, promochar = %x\n", moveType, promoChar);
5299 /* [HGM] convert drag-and-drop piece drops to standard form */
5300 if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) {
5301 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
5302 if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n",
5303 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
5304 // fromX = boards[currentMove][fromY][fromX];
5305 // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
5306 if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
5307 fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
5308 while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++;
5312 /* [HGM] <popupFix> The following if has been moved here from
5313 UserMoveEvent(). Because it seemed to belon here (why not allow
5314 piece drops in training games?), and because it can only be
5315 performed after it is known to what we promote. */
5316 if (gameMode == Training) {
5317 /* compare the move played on the board to the next move in the
5318 * game. If they match, display the move and the opponent's response.
5319 * If they don't match, display an error message.
5322 Board testBoard; char testRights[BOARD_SIZE]; char testStatus;
5323 CopyBoard(testBoard, boards[currentMove]);
5324 ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard, testRights, &testStatus);
5326 if (CompareBoards(testBoard, boards[currentMove+1])) {
5327 ForwardInner(currentMove+1);
5329 /* Autoplay the opponent's response.
5330 * if appData.animate was TRUE when Training mode was entered,
5331 * the response will be animated.
5333 saveAnimate = appData.animate;
5334 appData.animate = animateTraining;
5335 ForwardInner(currentMove+1);
5336 appData.animate = saveAnimate;
5338 /* check for the end of the game */
5339 if (currentMove >= forwardMostMove) {
5340 gameMode = PlayFromGameFile;
5342 SetTrainingModeOff();
5343 DisplayInformation(_("End of game"));
5346 DisplayError(_("Incorrect move"), 0);
5351 /* Ok, now we know that the move is good, so we can kill
5352 the previous line in Analysis Mode */
5353 if (gameMode == AnalyzeMode && currentMove < forwardMostMove) {
5354 forwardMostMove = currentMove;
5357 /* If we need the chess program but it's dead, restart it */
5358 ResurrectChessProgram();
5360 /* A user move restarts a paused game*/
5364 thinkOutput[0] = NULLCHAR;
5366 MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
5368 if (gameMode == BeginningOfGame) {
5369 if (appData.noChessProgram) {
5370 gameMode = EditGame;
5374 gameMode = MachinePlaysBlack;
5377 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
5379 if (first.sendName) {
5380 sprintf(buf, "name %s\n", gameInfo.white);
5381 SendToProgram(buf, &first);
5387 if(appData.debugMode) fprintf(debugFP, "moveType 2 = %d, promochar = %x\n", moveType, promoChar);
5388 /* Relay move to ICS or chess engine */
5389 if (appData.icsActive) {
5390 if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
5391 gameMode == IcsExamining) {
5392 SendMoveToICS(moveType, fromX, fromY, toX, toY);
5396 if (first.sendTime && (gameMode == BeginningOfGame ||
5397 gameMode == MachinePlaysWhite ||
5398 gameMode == MachinePlaysBlack)) {
5399 SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
5401 if (gameMode != EditGame && gameMode != PlayFromGameFile) {
5402 // [HGM] book: if program might be playing, let it use book
5403 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
5404 first.maybeThinking = TRUE;
5405 } else SendMoveToProgram(forwardMostMove-1, &first);
5406 if (currentMove == cmailOldMove + 1) {
5407 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
5411 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5415 switch (MateTest(boards[currentMove], PosFlags(currentMove),
5416 EP_UNKNOWN, castlingRights[currentMove]) ) {
5422 if (WhiteOnMove(currentMove)) {
5423 GameEnds(BlackWins, "Black mates", GE_PLAYER);
5425 GameEnds(WhiteWins, "White mates", GE_PLAYER);
5429 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
5434 case MachinePlaysBlack:
5435 case MachinePlaysWhite:
5436 /* disable certain menu options while machine is thinking */
5437 SetMachineThinkingEnables();
5444 if(bookHit) { // [HGM] book: simulate book reply
5445 static char bookMove[MSG_SIZ]; // a bit generous?
5447 programStats.nodes = programStats.depth = programStats.time =
5448 programStats.score = programStats.got_only_move = 0;
5449 sprintf(programStats.movelist, "%s (xbook)", bookHit);
5451 strcpy(bookMove, "move ");
5452 strcat(bookMove, bookHit);
5453 HandleMachineMove(bookMove, &first);
5459 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
5460 int fromX, fromY, toX, toY;
5463 /* [HGM] This routine was added to allow calling of its two logical
5464 parts from other modules in the old way. Before, UserMoveEvent()
5465 automatically called FinishMove() if the move was OK, and returned
5466 otherwise. I separated the two, in order to make it possible to
5467 slip a promotion popup in between. But that it always needs two
5468 calls, to the first part, (now called UserMoveTest() ), and to
5469 FinishMove if the first part succeeded. Calls that do not need
5470 to do anything in between, can call this routine the old way.
5472 ChessMove moveType = UserMoveTest(fromX, fromY, toX, toY, promoChar, FALSE);
5473 if(appData.debugMode) fprintf(debugFP, "moveType 4 = %d, promochar = %x\n", moveType, promoChar);
5474 if(moveType == AmbiguousMove)
5475 DrawPosition(FALSE, boards[currentMove]);
5476 else if(moveType != ImpossibleMove && moveType != Comment)
5477 FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
5480 void LeftClick(ClickType clickType, int xPix, int yPix)
5483 Boolean saveAnimate;
5484 static int second = 0, promotionChoice = 0;
5485 char promoChoice = NULLCHAR;
5487 if (clickType == Press) ErrorPopDown();
5489 x = EventToSquare(xPix, BOARD_WIDTH);
5490 y = EventToSquare(yPix, BOARD_HEIGHT);
5491 if (!flipView && y >= 0) {
5492 y = BOARD_HEIGHT - 1 - y;
5494 if (flipView && x >= 0) {
5495 x = BOARD_WIDTH - 1 - x;
5498 if(promotionChoice) { // we are waiting for a click to indicate promotion piece
5499 if(clickType == Release) return; // ignore upclick of click-click destination
5500 promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
5501 if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
5502 if(gameInfo.holdingsWidth &&
5503 (WhiteOnMove(currentMove)
5504 ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y > 0
5505 : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT-1) ) {
5506 // click in right holdings, for determining promotion piece
5507 ChessSquare p = boards[currentMove][y][x];
5508 if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
5509 if(p != EmptySquare) {
5510 FinishMove(NormalMove, fromX, fromY, toX, toY, ToLower(PieceToChar(p)));
5515 DrawPosition(FALSE, boards[currentMove]);
5519 /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
5520 if(clickType == Press
5521 && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
5522 || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
5523 || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
5527 if (clickType == Press) {
5529 if (OKToStartUserMove(x, y)) {
5533 DragPieceBegin(xPix, yPix);
5534 if (appData.highlightDragging) {
5535 SetHighlights(x, y, -1, -1);
5543 if (clickType == Press && gameMode != EditPosition) {
5548 // ignore off-board to clicks
5549 if(y < 0 || x < 0) return;
5551 /* Check if clicking again on the same color piece */
5552 fromP = boards[currentMove][fromY][fromX];
5553 toP = boards[currentMove][y][x];
5554 frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom;
5555 if ((WhitePawn <= fromP && fromP <= WhiteKing &&
5556 WhitePawn <= toP && toP <= WhiteKing &&
5557 !(fromP == WhiteKing && toP == WhiteRook && frc) &&
5558 !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
5559 (BlackPawn <= fromP && fromP <= BlackKing &&
5560 BlackPawn <= toP && toP <= BlackKing &&
5561 !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
5562 !(fromP == BlackKing && toP == BlackRook && frc))) {
5563 /* Clicked again on same color piece -- changed his mind */
5564 second = (x == fromX && y == fromY);
5565 if (appData.highlightDragging) {
5566 SetHighlights(x, y, -1, -1);
5570 if (OKToStartUserMove(x, y)) {
5573 DragPieceBegin(xPix, yPix);
5577 // ignore to-clicks in holdings
5578 if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
5581 if (clickType == Release && (x == fromX && y == fromY ||
5582 x < BOARD_LEFT || x >= BOARD_RGHT)) {
5584 // treat drags into holding as click on start square
5585 x = fromX; y = fromY;
5587 DragPieceEnd(xPix, yPix);
5588 if (appData.animateDragging) {
5589 /* Undo animation damage if any */
5590 DrawPosition(FALSE, NULL);
5593 /* Second up/down in same square; just abort move */
5598 ClearPremoveHighlights();
5600 /* First upclick in same square; start click-click mode */
5601 SetHighlights(x, y, -1, -1);
5606 /* we now have a different from- and to-square */
5607 /* Completed move */
5610 saveAnimate = appData.animate;
5611 if (clickType == Press) {
5612 /* Finish clickclick move */
5613 if (appData.animate || appData.highlightLastMove) {
5614 SetHighlights(fromX, fromY, toX, toY);
5619 /* Finish drag move */
5620 if (appData.highlightLastMove) {
5621 SetHighlights(fromX, fromY, toX, toY);
5625 DragPieceEnd(xPix, yPix);
5626 /* Don't animate move and drag both */
5627 appData.animate = FALSE;
5629 if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice)) {
5630 SetHighlights(fromX, fromY, toX, toY);
5631 if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
5632 // [HGM] super: promotion to captured piece selected from holdings
5633 ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
5634 promotionChoice = TRUE;
5635 // kludge follows to temporarily execute move on display, without promoting yet
5636 boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
5637 boards[currentMove][toY][toX] = p;
5638 DrawPosition(FALSE, boards[currentMove]);
5639 boards[currentMove][fromY][fromX] = p; // take back, but display stays
5640 boards[currentMove][toY][toX] = q;
5641 DisplayMessage("Click in holdings to choose piece", "");
5646 UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
5647 if (!appData.highlightLastMove || gotPremove) ClearHighlights();
5648 if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
5651 appData.animate = saveAnimate;
5652 if (appData.animate || appData.animateDragging) {
5653 /* Undo animation damage if needed */
5654 DrawPosition(FALSE, NULL);
5658 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
5660 // char * hint = lastHint;
5661 FrontEndProgramStats stats;
5663 stats.which = cps == &first ? 0 : 1;
5664 stats.depth = cpstats->depth;
5665 stats.nodes = cpstats->nodes;
5666 stats.score = cpstats->score;
5667 stats.time = cpstats->time;
5668 stats.pv = cpstats->movelist;
5669 stats.hint = lastHint;
5670 stats.an_move_index = 0;
5671 stats.an_move_count = 0;
5673 if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
5674 stats.hint = cpstats->move_name;
5675 stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
5676 stats.an_move_count = cpstats->nr_moves;
5679 SetProgramStats( &stats );
5682 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
5683 { // [HGM] book: this routine intercepts moves to simulate book replies
5684 char *bookHit = NULL;
5686 //first determine if the incoming move brings opponent into his book
5687 if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
5688 bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
5689 if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
5690 if(bookHit != NULL && !cps->bookSuspend) {
5691 // make sure opponent is not going to reply after receiving move to book position
5692 SendToProgram("force\n", cps);
5693 cps->bookSuspend = TRUE; // flag indicating it has to be restarted
5695 if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
5696 // now arrange restart after book miss
5698 // after a book hit we never send 'go', and the code after the call to this routine
5699 // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
5701 if (cps->useUsermove) sprintf(buf, "usermove "); // sorry, no SAN yet :(
5702 sprintf(buf, "%s\n", bookHit); // force book move into program supposed to play it
5703 SendToProgram(buf, cps);
5704 if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
5705 } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
5706 SendToProgram("go\n", cps);
5707 cps->bookSuspend = FALSE; // after a 'go' we are never suspended
5708 } else { // 'go' might be sent based on 'firstMove' after this routine returns
5709 if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
5710 SendToProgram("go\n", cps);
5711 cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
5713 return bookHit; // notify caller of hit, so it can take action to send move to opponent
5717 ChessProgramState *savedState;
5718 void DeferredBookMove(void)
5720 if(savedState->lastPing != savedState->lastPong)
5721 ScheduleDelayedEvent(DeferredBookMove, 10);
5723 HandleMachineMove(savedMessage, savedState);
5727 HandleMachineMove(message, cps)
5729 ChessProgramState *cps;
5731 char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
5732 char realname[MSG_SIZ];
5733 int fromX, fromY, toX, toY;
5740 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
5742 * Kludge to ignore BEL characters
5744 while (*message == '\007') message++;
5747 * [HGM] engine debug message: ignore lines starting with '#' character
5749 if(cps->debug && *message == '#') return;
5752 * Look for book output
5754 if (cps == &first && bookRequested) {
5755 if (message[0] == '\t' || message[0] == ' ') {
5756 /* Part of the book output is here; append it */
5757 strcat(bookOutput, message);
5758 strcat(bookOutput, " \n");
5760 } else if (bookOutput[0] != NULLCHAR) {
5761 /* All of book output has arrived; display it */
5762 char *p = bookOutput;
5763 while (*p != NULLCHAR) {
5764 if (*p == '\t') *p = ' ';
5767 DisplayInformation(bookOutput);
5768 bookRequested = FALSE;
5769 /* Fall through to parse the current output */
5774 * Look for machine move.
5776 if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
5777 (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
5779 /* This method is only useful on engines that support ping */
5780 if (cps->lastPing != cps->lastPong) {
5781 if (gameMode == BeginningOfGame) {
5782 /* Extra move from before last new; ignore */
5783 if (appData.debugMode) {
5784 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
5787 if (appData.debugMode) {
5788 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
5789 cps->which, gameMode);
5792 SendToProgram("undo\n", cps);
5798 case BeginningOfGame:
5799 /* Extra move from before last reset; ignore */
5800 if (appData.debugMode) {
5801 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
5808 /* Extra move after we tried to stop. The mode test is
5809 not a reliable way of detecting this problem, but it's
5810 the best we can do on engines that don't support ping.
5812 if (appData.debugMode) {
5813 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
5814 cps->which, gameMode);
5816 SendToProgram("undo\n", cps);
5819 case MachinePlaysWhite:
5820 case IcsPlayingWhite:
5821 machineWhite = TRUE;
5824 case MachinePlaysBlack:
5825 case IcsPlayingBlack:
5826 machineWhite = FALSE;
5829 case TwoMachinesPlay:
5830 machineWhite = (cps->twoMachinesColor[0] == 'w');
5833 if (WhiteOnMove(forwardMostMove) != machineWhite) {
5834 if (appData.debugMode) {
5836 "Ignoring move out of turn by %s, gameMode %d"
5837 ", forwardMost %d\n",
5838 cps->which, gameMode, forwardMostMove);
5843 if (appData.debugMode) { int f = forwardMostMove;
5844 fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
5845 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
5847 if(cps->alphaRank) AlphaRank(machineMove, 4);
5848 if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
5849 &fromX, &fromY, &toX, &toY, &promoChar)) {
5850 /* Machine move could not be parsed; ignore it. */
5851 sprintf(buf1, _("Illegal move \"%s\" from %s machine"),
5852 machineMove, cps->which);
5853 DisplayError(buf1, 0);
5854 sprintf(buf1, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
5855 machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
5856 if (gameMode == TwoMachinesPlay) {
5857 GameEnds(machineWhite ? BlackWins : WhiteWins,
5863 /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
5864 /* So we have to redo legality test with true e.p. status here, */
5865 /* to make sure an illegal e.p. capture does not slip through, */
5866 /* to cause a forfeit on a justified illegal-move complaint */
5867 /* of the opponent. */
5868 if( gameMode==TwoMachinesPlay && appData.testLegality
5869 && fromY != DROP_RANK /* [HGM] temporary; should still add legality test for drops */
5872 moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
5873 epStatus[forwardMostMove], castlingRights[forwardMostMove],
5874 fromY, fromX, toY, toX, promoChar);
5875 if (appData.debugMode) {
5877 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
5878 castlingRights[forwardMostMove][i], castlingRank[i]);
5879 fprintf(debugFP, "castling rights\n");
5881 if(moveType == IllegalMove) {
5882 sprintf(buf1, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
5883 machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
5884 GameEnds(machineWhite ? BlackWins : WhiteWins,
5887 } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
5888 /* [HGM] Kludge to handle engines that send FRC-style castling
5889 when they shouldn't (like TSCP-Gothic) */
5891 case WhiteASideCastleFR:
5892 case BlackASideCastleFR:
5894 currentMoveString[2]++;
5896 case WhiteHSideCastleFR:
5897 case BlackHSideCastleFR:
5899 currentMoveString[2]--;
5901 default: ; // nothing to do, but suppresses warning of pedantic compilers
5904 hintRequested = FALSE;
5905 lastHint[0] = NULLCHAR;
5906 bookRequested = FALSE;
5907 /* Program may be pondering now */
5908 cps->maybeThinking = TRUE;
5909 if (cps->sendTime == 2) cps->sendTime = 1;
5910 if (cps->offeredDraw) cps->offeredDraw--;
5913 if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
5915 SendMoveToICS(moveType, fromX, fromY, toX, toY);
5917 if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
5918 char buf[3*MSG_SIZ];
5920 sprintf(buf, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
5921 programStats.score / 100.,
5923 programStats.time / 100.,
5924 (unsigned int)programStats.nodes,
5925 (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
5926 programStats.movelist);
5928 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
5932 /* currentMoveString is set as a side-effect of ParseOneMove */
5933 strcpy(machineMove, currentMoveString);
5934 strcat(machineMove, "\n");
5935 strcpy(moveList[forwardMostMove], machineMove);
5937 /* [AS] Save move info and clear stats for next move */
5938 pvInfoList[ forwardMostMove ].score = programStats.score;
5939 pvInfoList[ forwardMostMove ].depth = programStats.depth;
5940 pvInfoList[ forwardMostMove ].time = programStats.time; // [HGM] PGNtime: take time from engine stats
5941 ClearProgramStats();
5942 thinkOutput[0] = NULLCHAR;
5943 hiddenThinkOutputState = 0;
5945 MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
5947 /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
5948 if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
5951 while( count < adjudicateLossPlies ) {
5952 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
5955 score = -score; /* Flip score for winning side */
5958 if( score > adjudicateLossThreshold ) {
5965 if( count >= adjudicateLossPlies ) {
5966 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5968 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
5969 "Xboard adjudication",
5976 if( gameMode == TwoMachinesPlay ) {
5977 // [HGM] some adjudications useful with buggy engines
5978 int k, count = 0, epFile = epStatus[forwardMostMove]; static int bare = 1;
5979 if(gameInfo.holdingsSize == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
5982 if( appData.testLegality )
5983 { /* [HGM] Some more adjudications for obstinate engines */
5984 int NrWN=0, NrBN=0, NrWB=0, NrBB=0, NrWR=0, NrBR=0,
5985 NrWQ=0, NrBQ=0, NrW=0, NrK=0, bishopsColor = 0,
5986 NrPieces=0, NrPawns=0, PawnAdvance=0, i, j;
5987 static int moveCount = 6;
5989 char *reason = NULL;
5991 /* Count what is on board. */
5992 for(i=0; i<BOARD_HEIGHT; i++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
5993 { ChessSquare p = boards[forwardMostMove][i][j];
5997 { /* count B,N,R and other of each side */
6000 NrK++; break; // [HGM] atomic: count Kings
6004 case WhiteFerz: // [HGM] shatranj: kludge to mke it work in shatranj
6005 bishopsColor |= 1 << ((i^j)&1);
6010 case BlackFerz: // [HGM] shatranj: kludge to mke it work in shatranj
6011 bishopsColor |= 1 << ((i^j)&1);
6026 PawnAdvance += m; NrPawns++;
6028 NrPieces += (p != EmptySquare);
6029 NrW += ((int)p < (int)BlackPawn);
6030 if(gameInfo.variant == VariantXiangqi &&
6031 (p == WhiteFerz || p == WhiteAlfil || p == BlackFerz || p == BlackAlfil)) {
6032 NrPieces--; // [HGM] XQ: do not count purely defensive pieces
6033 NrW -= ((int)p < (int)BlackPawn);
6037 /* Some material-based adjudications that have to be made before stalemate test */
6038 if(gameInfo.variant == VariantAtomic && NrK < 2) {
6039 // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
6040 epStatus[forwardMostMove] = EP_CHECKMATE; // make claimable as if stm is checkmated
6041 if(appData.checkMates) {
6042 SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets move
6043 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6044 GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
6045 "Xboard adjudication: King destroyed", GE_XBOARD );
6050 /* Bare King in Shatranj (loses) or Losers (wins) */
6051 if( NrW == 1 || NrPieces - NrW == 1) {
6052 if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
6053 epStatus[forwardMostMove] = EP_WINS; // mark as win, so it becomes claimable
6054 if(appData.checkMates) {
6055 SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets to see move
6056 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6057 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
6058 "Xboard adjudication: Bare king", GE_XBOARD );
6062 if( gameInfo.variant == VariantShatranj && --bare < 0)
6064 epStatus[forwardMostMove] = EP_WINS; // make claimable as win for stm
6065 if(appData.checkMates) {
6066 /* but only adjudicate if adjudication enabled */
6067 SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets move
6068 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6069 GameEnds( NrW > 1 ? WhiteWins : NrPieces - NrW > 1 ? BlackWins : GameIsDrawn,
6070 "Xboard adjudication: Bare king", GE_XBOARD );
6077 // don't wait for engine to announce game end if we can judge ourselves
6078 switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove), epFile,
6079 castlingRights[forwardMostMove]) ) {
6081 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
6082 int i, checkCnt = 0; // (should really be done by making nr of checks part of game state)
6083 for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
6084 if(MateTest(boards[i], PosFlags(i), epStatus[i], castlingRights[i]) == MT_CHECK)
6087 reason = "Xboard adjudication: 3rd check";
6088 epStatus[forwardMostMove] = EP_CHECKMATE;
6098 reason = "Xboard adjudication: Stalemate";
6099 if(epStatus[forwardMostMove] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
6100 epStatus[forwardMostMove] = EP_STALEMATE; // default result for stalemate is draw
6101 if(gameInfo.variant == VariantLosers || gameInfo.variant == VariantGiveaway) // [HGM] losers:
6102 epStatus[forwardMostMove] = EP_WINS; // in these variants stalemated is always a win
6103 else if(gameInfo.variant == VariantSuicide) // in suicide it depends
6104 epStatus[forwardMostMove] = NrW == NrPieces-NrW ? EP_STALEMATE :
6105 ((NrW < NrPieces-NrW) != WhiteOnMove(forwardMostMove) ?
6106 EP_CHECKMATE : EP_WINS);
6107 else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
6108 epStatus[forwardMostMove] = EP_CHECKMATE; // and in these variants being stalemated loses
6112 reason = "Xboard adjudication: Checkmate";
6113 epStatus[forwardMostMove] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
6117 switch(i = epStatus[forwardMostMove]) {
6119 result = GameIsDrawn; break;
6121 result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
6123 result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
6125 result = (ChessMove) 0;
6127 if(appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
6128 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6129 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6130 GameEnds( result, reason, GE_XBOARD );
6134 /* Next absolutely insufficient mating material. */
6135 if( NrPieces == 2 || gameInfo.variant != VariantXiangqi &&
6136 gameInfo.variant != VariantShatranj && // [HGM] baring will remain possible
6137 (NrPieces == 3 && NrWN+NrBN+NrWB+NrBB == 1 ||
6138 NrPieces == NrBB+NrWB+2 && bishopsColor != 3)) // [HGM] all Bishops (Ferz!) same color
6139 { /* KBK, KNK, KK of KBKB with like Bishops */
6141 /* always flag draws, for judging claims */
6142 epStatus[forwardMostMove] = EP_INSUF_DRAW;
6144 if(appData.materialDraws) {
6145 /* but only adjudicate them if adjudication enabled */
6146 SendToProgram("force\n", cps->other); // suppress reply
6147 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see last move */
6148 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6149 GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
6154 /* Then some trivial draws (only adjudicate, cannot be claimed) */
6156 ( NrWR == 1 && NrBR == 1 /* KRKR */
6157 || NrWQ==1 && NrBQ==1 /* KQKQ */
6158 || NrWN==2 || NrBN==2 /* KNNK */
6159 || NrWN+NrWB == 1 && NrBN+NrBB == 1 /* KBKN, KBKB, KNKN */
6161 if(--moveCount < 0 && appData.trivialDraws)
6162 { /* if the first 3 moves do not show a tactical win, declare draw */
6163 SendToProgram("force\n", cps->other); // suppress reply
6164 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6165 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6166 GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
6169 } else moveCount = 6;
6173 if (appData.debugMode) { int i;
6174 fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
6175 forwardMostMove, backwardMostMove, epStatus[backwardMostMove],
6176 appData.drawRepeats);
6177 for( i=forwardMostMove; i>=backwardMostMove; i-- )
6178 fprintf(debugFP, "%d ep=%d\n", i, epStatus[i]);
6182 /* Check for rep-draws */
6184 for(k = forwardMostMove-2;
6185 k>=backwardMostMove && k>=forwardMostMove-100 &&
6186 epStatus[k] < EP_UNKNOWN &&
6187 epStatus[k+2] <= EP_NONE && epStatus[k+1] <= EP_NONE;
6190 if(CompareBoards(boards[k], boards[forwardMostMove])) {
6191 /* compare castling rights */
6192 if( castlingRights[forwardMostMove][2] != castlingRights[k][2] &&
6193 (castlingRights[k][0] >= 0 || castlingRights[k][1] >= 0) )
6194 rights++; /* King lost rights, while rook still had them */
6195 if( castlingRights[forwardMostMove][2] >= 0 ) { /* king has rights */
6196 if( castlingRights[forwardMostMove][0] != castlingRights[k][0] ||
6197 castlingRights[forwardMostMove][1] != castlingRights[k][1] )
6198 rights++; /* but at least one rook lost them */
6200 if( castlingRights[forwardMostMove][5] != castlingRights[k][5] &&
6201 (castlingRights[k][3] >= 0 || castlingRights[k][4] >= 0) )
6203 if( castlingRights[forwardMostMove][5] >= 0 ) {
6204 if( castlingRights[forwardMostMove][3] != castlingRights[k][3] ||
6205 castlingRights[forwardMostMove][4] != castlingRights[k][4] )
6208 if( rights == 0 && ++count > appData.drawRepeats-2
6209 && appData.drawRepeats > 1) {
6210 /* adjudicate after user-specified nr of repeats */
6211 SendToProgram("force\n", cps->other); // suppress reply
6212 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6213 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6214 if(gameInfo.variant == VariantXiangqi && appData.testLegality) {
6215 // [HGM] xiangqi: check for forbidden perpetuals
6216 int m, ourPerpetual = 1, hisPerpetual = 1;
6217 for(m=forwardMostMove; m>k; m-=2) {
6218 if(MateTest(boards[m], PosFlags(m),
6219 EP_NONE, castlingRights[m]) != MT_CHECK)
6220 ourPerpetual = 0; // the current mover did not always check
6221 if(MateTest(boards[m-1], PosFlags(m-1),
6222 EP_NONE, castlingRights[m-1]) != MT_CHECK)
6223 hisPerpetual = 0; // the opponent did not always check
6225 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
6226 ourPerpetual, hisPerpetual);
6227 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
6228 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
6229 "Xboard adjudication: perpetual checking", GE_XBOARD );
6232 if(hisPerpetual && !ourPerpetual) // he is checking us, but did not repeat yet
6233 break; // (or we would have caught him before). Abort repetition-checking loop.
6234 // Now check for perpetual chases
6235 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
6236 hisPerpetual = PerpetualChase(k, forwardMostMove);
6237 ourPerpetual = PerpetualChase(k+1, forwardMostMove);
6238 if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
6239 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
6240 "Xboard adjudication: perpetual chasing", GE_XBOARD );
6243 if(hisPerpetual && !ourPerpetual) // he is chasing us, but did not repeat yet
6244 break; // Abort repetition-checking loop.
6246 // if neither of us is checking or chasing all the time, or both are, it is draw
6248 GameEnds( GameIsDrawn, "Xboard adjudication: repetition draw", GE_XBOARD );
6251 if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
6252 epStatus[forwardMostMove] = EP_REP_DRAW;
6256 /* Now we test for 50-move draws. Determine ply count */
6257 count = forwardMostMove;
6258 /* look for last irreversble move */
6259 while( epStatus[count] <= EP_NONE && count > backwardMostMove )
6261 /* if we hit starting position, add initial plies */
6262 if( count == backwardMostMove )
6263 count -= initialRulePlies;
6264 count = forwardMostMove - count;
6266 epStatus[forwardMostMove] = EP_RULE_DRAW;
6267 /* this is used to judge if draw claims are legal */
6268 if(appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
6269 SendToProgram("force\n", cps->other); // suppress reply
6270 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6271 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6272 GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
6276 /* if draw offer is pending, treat it as a draw claim
6277 * when draw condition present, to allow engines a way to
6278 * claim draws before making their move to avoid a race
6279 * condition occurring after their move
6281 if( cps->other->offeredDraw || cps->offeredDraw ) {
6283 if(epStatus[forwardMostMove] == EP_RULE_DRAW)
6284 p = "Draw claim: 50-move rule";
6285 if(epStatus[forwardMostMove] == EP_REP_DRAW)
6286 p = "Draw claim: 3-fold repetition";
6287 if(epStatus[forwardMostMove] == EP_INSUF_DRAW)
6288 p = "Draw claim: insufficient mating material";
6290 SendToProgram("force\n", cps->other); // suppress reply
6291 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6292 GameEnds( GameIsDrawn, p, GE_XBOARD );
6293 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6299 if( appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
6300 SendToProgram("force\n", cps->other); // suppress reply
6301 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6302 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6304 GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
6311 if (gameMode == TwoMachinesPlay) {
6312 /* [HGM] relaying draw offers moved to after reception of move */
6313 /* and interpreting offer as claim if it brings draw condition */
6314 if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
6315 SendToProgram("draw\n", cps->other);
6317 if (cps->other->sendTime) {
6318 SendTimeRemaining(cps->other,
6319 cps->other->twoMachinesColor[0] == 'w');
6321 bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
6322 if (firstMove && !bookHit) {
6324 if (cps->other->useColors) {
6325 SendToProgram(cps->other->twoMachinesColor, cps->other);
6327 SendToProgram("go\n", cps->other);
6329 cps->other->maybeThinking = TRUE;
6332 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6334 if (!pausing && appData.ringBellAfterMoves) {
6339 * Reenable menu items that were disabled while
6340 * machine was thinking
6342 if (gameMode != TwoMachinesPlay)
6343 SetUserThinkingEnables();
6345 // [HGM] book: after book hit opponent has received move and is now in force mode
6346 // force the book reply into it, and then fake that it outputted this move by jumping
6347 // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
6349 static char bookMove[MSG_SIZ]; // a bit generous?
6351 strcpy(bookMove, "move ");
6352 strcat(bookMove, bookHit);
6355 programStats.nodes = programStats.depth = programStats.time =
6356 programStats.score = programStats.got_only_move = 0;
6357 sprintf(programStats.movelist, "%s (xbook)", bookHit);
6359 if(cps->lastPing != cps->lastPong) {
6360 savedMessage = message; // args for deferred call
6362 ScheduleDelayedEvent(DeferredBookMove, 10);
6371 /* Set special modes for chess engines. Later something general
6372 * could be added here; for now there is just one kludge feature,
6373 * needed because Crafty 15.10 and earlier don't ignore SIGINT
6374 * when "xboard" is given as an interactive command.
6376 if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
6377 cps->useSigint = FALSE;
6378 cps->useSigterm = FALSE;
6380 if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
6381 ParseFeatures(message+8, cps);
6382 return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
6385 /* [HGM] Allow engine to set up a position. Don't ask me why one would
6386 * want this, I was asked to put it in, and obliged.
6388 if (!strncmp(message, "setboard ", 9)) {
6389 Board initial_position; int i;
6391 GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
6393 if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
6394 DisplayError(_("Bad FEN received from engine"), 0);
6397 Reset(FALSE, FALSE);
6398 CopyBoard(boards[0], initial_position);
6399 initialRulePlies = FENrulePlies;
6400 epStatus[0] = FENepStatus;
6401 for( i=0; i<nrCastlingRights; i++ )
6402 castlingRights[0][i] = FENcastlingRights[i];
6403 if(blackPlaysFirst) gameMode = MachinePlaysWhite;
6404 else gameMode = MachinePlaysBlack;
6405 DrawPosition(FALSE, boards[currentMove]);
6411 * Look for communication commands
6413 if (!strncmp(message, "telluser ", 9)) {
6414 DisplayNote(message + 9);
6417 if (!strncmp(message, "tellusererror ", 14)) {
6418 DisplayError(message + 14, 0);
6421 if (!strncmp(message, "tellopponent ", 13)) {
6422 if (appData.icsActive) {
6424 snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
6428 DisplayNote(message + 13);
6432 if (!strncmp(message, "tellothers ", 11)) {
6433 if (appData.icsActive) {
6435 snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
6441 if (!strncmp(message, "tellall ", 8)) {
6442 if (appData.icsActive) {
6444 snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
6448 DisplayNote(message + 8);
6452 if (strncmp(message, "warning", 7) == 0) {
6453 /* Undocumented feature, use tellusererror in new code */
6454 DisplayError(message, 0);
6457 if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
6458 strcpy(realname, cps->tidy);
6459 strcat(realname, " query");
6460 AskQuestion(realname, buf2, buf1, cps->pr);
6463 /* Commands from the engine directly to ICS. We don't allow these to be
6464 * sent until we are logged on. Crafty kibitzes have been known to
6465 * interfere with the login process.
6468 if (!strncmp(message, "tellics ", 8)) {
6469 SendToICS(message + 8);
6473 if (!strncmp(message, "tellicsnoalias ", 15)) {
6474 SendToICS(ics_prefix);
6475 SendToICS(message + 15);
6479 /* The following are for backward compatibility only */
6480 if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
6481 !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
6482 SendToICS(ics_prefix);
6488 if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
6492 * If the move is illegal, cancel it and redraw the board.
6493 * Also deal with other error cases. Matching is rather loose
6494 * here to accommodate engines written before the spec.
6496 if (strncmp(message + 1, "llegal move", 11) == 0 ||
6497 strncmp(message, "Error", 5) == 0) {
6498 if (StrStr(message, "name") ||
6499 StrStr(message, "rating") || StrStr(message, "?") ||
6500 StrStr(message, "result") || StrStr(message, "board") ||
6501 StrStr(message, "bk") || StrStr(message, "computer") ||
6502 StrStr(message, "variant") || StrStr(message, "hint") ||
6503 StrStr(message, "random") || StrStr(message, "depth") ||
6504 StrStr(message, "accepted")) {
6507 if (StrStr(message, "protover")) {
6508 /* Program is responding to input, so it's apparently done
6509 initializing, and this error message indicates it is
6510 protocol version 1. So we don't need to wait any longer
6511 for it to initialize and send feature commands. */
6512 FeatureDone(cps, 1);
6513 cps->protocolVersion = 1;
6516 cps->maybeThinking = FALSE;
6518 if (StrStr(message, "draw")) {
6519 /* Program doesn't have "draw" command */
6520 cps->sendDrawOffers = 0;
6523 if (cps->sendTime != 1 &&
6524 (StrStr(message, "time") || StrStr(message, "otim"))) {
6525 /* Program apparently doesn't have "time" or "otim" command */
6529 if (StrStr(message, "analyze")) {
6530 cps->analysisSupport = FALSE;
6531 cps->analyzing = FALSE;
6533 sprintf(buf2, _("%s does not support analysis"), cps->tidy);
6534 DisplayError(buf2, 0);
6537 if (StrStr(message, "(no matching move)st")) {
6538 /* Special kludge for GNU Chess 4 only */
6539 cps->stKludge = TRUE;
6540 SendTimeControl(cps, movesPerSession, timeControl,
6541 timeIncrement, appData.searchDepth,
6545 if (StrStr(message, "(no matching move)sd")) {
6546 /* Special kludge for GNU Chess 4 only */
6547 cps->sdKludge = TRUE;
6548 SendTimeControl(cps, movesPerSession, timeControl,
6549 timeIncrement, appData.searchDepth,
6553 if (!StrStr(message, "llegal")) {
6556 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
6557 gameMode == IcsIdle) return;
6558 if (forwardMostMove <= backwardMostMove) return;
6559 if (pausing) PauseEvent();
6560 if(appData.forceIllegal) {
6561 // [HGM] illegal: machine refused move; force position after move into it
6562 SendToProgram("force\n", cps);
6563 if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
6564 // we have a real problem now, as SendBoard will use the a2a3 kludge
6565 // when black is to move, while there might be nothing on a2 or black
6566 // might already have the move. So send the board as if white has the move.
6567 // But first we must change the stm of the engine, as it refused the last move
6568 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
6569 if(WhiteOnMove(forwardMostMove)) {
6570 SendToProgram("a7a6\n", cps); // for the engine black still had the move
6571 SendBoard(cps, forwardMostMove); // kludgeless board
6573 SendToProgram("a2a3\n", cps); // for the engine white still had the move
6574 CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
6575 SendBoard(cps, forwardMostMove+1); // kludgeless board
6577 } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
6578 if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
6579 gameMode == TwoMachinesPlay)
6580 SendToProgram("go\n", cps);
6583 if (gameMode == PlayFromGameFile) {
6584 /* Stop reading this game file */
6585 gameMode = EditGame;
6588 currentMove = --forwardMostMove;
6589 DisplayMove(currentMove-1); /* before DisplayMoveError */
6591 DisplayBothClocks();
6592 sprintf(buf1, _("Illegal move \"%s\" (rejected by %s chess program)"),
6593 parseList[currentMove], cps->which);
6594 DisplayMoveError(buf1);
6595 DrawPosition(FALSE, boards[currentMove]);
6597 /* [HGM] illegal-move claim should forfeit game when Xboard */
6598 /* only passes fully legal moves */
6599 if( appData.testLegality && gameMode == TwoMachinesPlay ) {
6600 GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
6601 "False illegal-move claim", GE_XBOARD );
6605 if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
6606 /* Program has a broken "time" command that
6607 outputs a string not ending in newline.
6613 * If chess program startup fails, exit with an error message.
6614 * Attempts to recover here are futile.
6616 if ((StrStr(message, "unknown host") != NULL)
6617 || (StrStr(message, "No remote directory") != NULL)
6618 || (StrStr(message, "not found") != NULL)
6619 || (StrStr(message, "No such file") != NULL)
6620 || (StrStr(message, "can't alloc") != NULL)
6621 || (StrStr(message, "Permission denied") != NULL)) {
6623 cps->maybeThinking = FALSE;
6624 snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
6625 cps->which, cps->program, cps->host, message);
6626 RemoveInputSource(cps->isr);
6627 DisplayFatalError(buf1, 0, 1);
6632 * Look for hint output
6634 if (sscanf(message, "Hint: %s", buf1) == 1) {
6635 if (cps == &first && hintRequested) {
6636 hintRequested = FALSE;
6637 if (ParseOneMove(buf1, forwardMostMove, &moveType,
6638 &fromX, &fromY, &toX, &toY, &promoChar)) {
6639 (void) CoordsToAlgebraic(boards[forwardMostMove],
6640 PosFlags(forwardMostMove), EP_UNKNOWN,
6641 fromY, fromX, toY, toX, promoChar, buf1);
6642 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
6643 DisplayInformation(buf2);
6645 /* Hint move could not be parsed!? */
6646 snprintf(buf2, sizeof(buf2),
6647 _("Illegal hint move \"%s\"\nfrom %s chess program"),
6649 DisplayError(buf2, 0);
6652 strcpy(lastHint, buf1);
6658 * Ignore other messages if game is not in progress
6660 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
6661 gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
6664 * look for win, lose, draw, or draw offer
6666 if (strncmp(message, "1-0", 3) == 0) {
6667 char *p, *q, *r = "";
6668 p = strchr(message, '{');
6676 GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
6678 } else if (strncmp(message, "0-1", 3) == 0) {
6679 char *p, *q, *r = "";
6680 p = strchr(message, '{');
6688 /* Kludge for Arasan 4.1 bug */
6689 if (strcmp(r, "Black resigns") == 0) {
6690 GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
6693 GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
6695 } else if (strncmp(message, "1/2", 3) == 0) {
6696 char *p, *q, *r = "";
6697 p = strchr(message, '{');
6706 GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
6709 } else if (strncmp(message, "White resign", 12) == 0) {
6710 GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
6712 } else if (strncmp(message, "Black resign", 12) == 0) {
6713 GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
6715 } else if (strncmp(message, "White matches", 13) == 0 ||
6716 strncmp(message, "Black matches", 13) == 0 ) {
6717 /* [HGM] ignore GNUShogi noises */
6719 } else if (strncmp(message, "White", 5) == 0 &&
6720 message[5] != '(' &&
6721 StrStr(message, "Black") == NULL) {
6722 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6724 } else if (strncmp(message, "Black", 5) == 0 &&
6725 message[5] != '(') {
6726 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6728 } else if (strcmp(message, "resign") == 0 ||
6729 strcmp(message, "computer resigns") == 0) {
6731 case MachinePlaysBlack:
6732 case IcsPlayingBlack:
6733 GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
6735 case MachinePlaysWhite:
6736 case IcsPlayingWhite:
6737 GameEnds(BlackWins, "White resigns", GE_ENGINE);
6739 case TwoMachinesPlay:
6740 if (cps->twoMachinesColor[0] == 'w')
6741 GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
6743 GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
6750 } else if (strncmp(message, "opponent mates", 14) == 0) {
6752 case MachinePlaysBlack:
6753 case IcsPlayingBlack:
6754 GameEnds(WhiteWins, "White mates", GE_ENGINE);
6756 case MachinePlaysWhite:
6757 case IcsPlayingWhite:
6758 GameEnds(BlackWins, "Black mates", GE_ENGINE);
6760 case TwoMachinesPlay:
6761 if (cps->twoMachinesColor[0] == 'w')
6762 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6764 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6771 } else if (strncmp(message, "computer mates", 14) == 0) {
6773 case MachinePlaysBlack:
6774 case IcsPlayingBlack:
6775 GameEnds(BlackWins, "Black mates", GE_ENGINE1);
6777 case MachinePlaysWhite:
6778 case IcsPlayingWhite:
6779 GameEnds(WhiteWins, "White mates", GE_ENGINE);
6781 case TwoMachinesPlay:
6782 if (cps->twoMachinesColor[0] == 'w')
6783 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6785 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6792 } else if (strncmp(message, "checkmate", 9) == 0) {
6793 if (WhiteOnMove(forwardMostMove)) {
6794 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6796 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6799 } else if (strstr(message, "Draw") != NULL ||
6800 strstr(message, "game is a draw") != NULL) {
6801 GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
6803 } else if (strstr(message, "offer") != NULL &&
6804 strstr(message, "draw") != NULL) {
6806 if (appData.zippyPlay && first.initDone) {
6807 /* Relay offer to ICS */
6808 SendToICS(ics_prefix);
6809 SendToICS("draw\n");
6812 cps->offeredDraw = 2; /* valid until this engine moves twice */
6813 if (gameMode == TwoMachinesPlay) {
6814 if (cps->other->offeredDraw) {
6815 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
6816 /* [HGM] in two-machine mode we delay relaying draw offer */
6817 /* until after we also have move, to see if it is really claim */
6819 } else if (gameMode == MachinePlaysWhite ||
6820 gameMode == MachinePlaysBlack) {
6821 if (userOfferedDraw) {
6822 DisplayInformation(_("Machine accepts your draw offer"));
6823 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
6825 DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
6832 * Look for thinking output
6834 if ( appData.showThinking // [HGM] thinking: test all options that cause this output
6835 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
6837 int plylev, mvleft, mvtot, curscore, time;
6838 char mvname[MOVE_LEN];
6842 int prefixHint = FALSE;
6843 mvname[0] = NULLCHAR;
6846 case MachinePlaysBlack:
6847 case IcsPlayingBlack:
6848 if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
6850 case MachinePlaysWhite:
6851 case IcsPlayingWhite:
6852 if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
6857 case IcsObserving: /* [DM] icsEngineAnalyze */
6858 if (!appData.icsEngineAnalyze) ignore = TRUE;
6860 case TwoMachinesPlay:
6861 if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
6872 if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
6873 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
6875 if (plyext != ' ' && plyext != '\t') {
6879 /* [AS] Negate score if machine is playing black and reporting absolute scores */
6880 if( cps->scoreIsAbsolute &&
6881 ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) )
6883 curscore = -curscore;
6887 programStats.depth = plylev;
6888 programStats.nodes = nodes;
6889 programStats.time = time;
6890 programStats.score = curscore;
6891 programStats.got_only_move = 0;
6893 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
6896 if(cps->nps == 0) ticklen = 10*time; // use engine reported time
6897 else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
6898 if(WhiteOnMove(forwardMostMove))
6899 whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
6900 else blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
6903 /* Buffer overflow protection */
6904 if (buf1[0] != NULLCHAR) {
6905 if (strlen(buf1) >= sizeof(programStats.movelist)
6906 && appData.debugMode) {
6908 "PV is too long; using the first %d bytes.\n",
6909 sizeof(programStats.movelist) - 1);
6912 safeStrCpy( programStats.movelist, buf1, sizeof(programStats.movelist) );
6914 sprintf(programStats.movelist, " no PV\n");
6917 if (programStats.seen_stat) {
6918 programStats.ok_to_send = 1;
6921 if (strchr(programStats.movelist, '(') != NULL) {
6922 programStats.line_is_book = 1;
6923 programStats.nr_moves = 0;
6924 programStats.moves_left = 0;
6926 programStats.line_is_book = 0;
6929 SendProgramStatsToFrontend( cps, &programStats );
6932 [AS] Protect the thinkOutput buffer from overflow... this
6933 is only useful if buf1 hasn't overflowed first!
6935 sprintf(thinkOutput, "[%d]%c%+.2f %s%s",
6937 (gameMode == TwoMachinesPlay ?
6938 ToUpper(cps->twoMachinesColor[0]) : ' '),
6939 ((double) curscore) / 100.0,
6940 prefixHint ? lastHint : "",
6941 prefixHint ? " " : "" );
6943 if( buf1[0] != NULLCHAR ) {
6944 unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
6946 if( strlen(buf1) > max_len ) {
6947 if( appData.debugMode) {
6948 fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
6950 buf1[max_len+1] = '\0';
6953 strcat( thinkOutput, buf1 );
6956 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
6957 || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
6958 DisplayMove(currentMove - 1);
6962 } else if ((p=StrStr(message, "(only move)")) != NULL) {
6963 /* crafty (9.25+) says "(only move) <move>"
6964 * if there is only 1 legal move
6966 sscanf(p, "(only move) %s", buf1);
6967 sprintf(thinkOutput, "%s (only move)", buf1);
6968 sprintf(programStats.movelist, "%s (only move)", buf1);
6969 programStats.depth = 1;
6970 programStats.nr_moves = 1;
6971 programStats.moves_left = 1;
6972 programStats.nodes = 1;
6973 programStats.time = 1;
6974 programStats.got_only_move = 1;
6976 /* Not really, but we also use this member to
6977 mean "line isn't going to change" (Crafty
6978 isn't searching, so stats won't change) */
6979 programStats.line_is_book = 1;
6981 SendProgramStatsToFrontend( cps, &programStats );
6983 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
6984 gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
6985 DisplayMove(currentMove - 1);
6988 } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
6989 &time, &nodes, &plylev, &mvleft,
6990 &mvtot, mvname) >= 5) {
6991 /* The stat01: line is from Crafty (9.29+) in response
6992 to the "." command */
6993 programStats.seen_stat = 1;
6994 cps->maybeThinking = TRUE;
6996 if (programStats.got_only_move || !appData.periodicUpdates)
6999 programStats.depth = plylev;
7000 programStats.time = time;
7001 programStats.nodes = nodes;
7002 programStats.moves_left = mvleft;
7003 programStats.nr_moves = mvtot;
7004 strcpy(programStats.move_name, mvname);
7005 programStats.ok_to_send = 1;
7006 programStats.movelist[0] = '\0';
7008 SendProgramStatsToFrontend( cps, &programStats );
7012 } else if (strncmp(message,"++",2) == 0) {
7013 /* Crafty 9.29+ outputs this */
7014 programStats.got_fail = 2;
7017 } else if (strncmp(message,"--",2) == 0) {
7018 /* Crafty 9.29+ outputs this */
7019 programStats.got_fail = 1;
7022 } else if (thinkOutput[0] != NULLCHAR &&
7023 strncmp(message, " ", 4) == 0) {
7024 unsigned message_len;
7027 while (*p && *p == ' ') p++;
7029 message_len = strlen( p );
7031 /* [AS] Avoid buffer overflow */
7032 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
7033 strcat(thinkOutput, " ");
7034 strcat(thinkOutput, p);
7037 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
7038 strcat(programStats.movelist, " ");
7039 strcat(programStats.movelist, p);
7042 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
7043 gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7044 DisplayMove(currentMove - 1);
7052 if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
7053 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
7055 ChessProgramStats cpstats;
7057 if (plyext != ' ' && plyext != '\t') {
7061 /* [AS] Negate score if machine is playing black and reporting absolute scores */
7062 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
7063 curscore = -curscore;
7066 cpstats.depth = plylev;
7067 cpstats.nodes = nodes;
7068 cpstats.time = time;
7069 cpstats.score = curscore;
7070 cpstats.got_only_move = 0;
7071 cpstats.movelist[0] = '\0';
7073 if (buf1[0] != NULLCHAR) {
7074 safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist) );
7077 cpstats.ok_to_send = 0;
7078 cpstats.line_is_book = 0;
7079 cpstats.nr_moves = 0;
7080 cpstats.moves_left = 0;
7082 SendProgramStatsToFrontend( cps, &cpstats );
7089 /* Parse a game score from the character string "game", and
7090 record it as the history of the current game. The game
7091 score is NOT assumed to start from the standard position.
7092 The display is not updated in any way.
7095 ParseGameHistory(game)
7099 int fromX, fromY, toX, toY, boardIndex;
7104 if (appData.debugMode)
7105 fprintf(debugFP, "Parsing game history: %s\n", game);
7107 if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
7108 gameInfo.site = StrSave(appData.icsHost);
7109 gameInfo.date = PGNDate();
7110 gameInfo.round = StrSave("-");
7112 /* Parse out names of players */
7113 while (*game == ' ') game++;
7115 while (*game != ' ') *p++ = *game++;
7117 gameInfo.white = StrSave(buf);
7118 while (*game == ' ') game++;
7120 while (*game != ' ' && *game != '\n') *p++ = *game++;
7122 gameInfo.black = StrSave(buf);
7125 boardIndex = blackPlaysFirst ? 1 : 0;
7128 yyboardindex = boardIndex;
7129 moveType = (ChessMove) yylex();
7131 case IllegalMove: /* maybe suicide chess, etc. */
7132 if (appData.debugMode) {
7133 fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
7134 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7135 setbuf(debugFP, NULL);
7137 case WhitePromotionChancellor:
7138 case BlackPromotionChancellor:
7139 case WhitePromotionArchbishop:
7140 case BlackPromotionArchbishop:
7141 case WhitePromotionQueen:
7142 case BlackPromotionQueen:
7143 case WhitePromotionRook:
7144 case BlackPromotionRook:
7145 case WhitePromotionBishop:
7146 case BlackPromotionBishop:
7147 case WhitePromotionKnight:
7148 case BlackPromotionKnight:
7149 case WhitePromotionKing:
7150 case BlackPromotionKing:
7152 case WhiteCapturesEnPassant:
7153 case BlackCapturesEnPassant:
7154 case WhiteKingSideCastle:
7155 case WhiteQueenSideCastle:
7156 case BlackKingSideCastle:
7157 case BlackQueenSideCastle:
7158 case WhiteKingSideCastleWild:
7159 case WhiteQueenSideCastleWild:
7160 case BlackKingSideCastleWild:
7161 case BlackQueenSideCastleWild:
7163 case WhiteHSideCastleFR:
7164 case WhiteASideCastleFR:
7165 case BlackHSideCastleFR:
7166 case BlackASideCastleFR:
7168 fromX = currentMoveString[0] - AAA;
7169 fromY = currentMoveString[1] - ONE;
7170 toX = currentMoveString[2] - AAA;
7171 toY = currentMoveString[3] - ONE;
7172 promoChar = currentMoveString[4];
7176 fromX = moveType == WhiteDrop ?
7177 (int) CharToPiece(ToUpper(currentMoveString[0])) :
7178 (int) CharToPiece(ToLower(currentMoveString[0]));
7180 toX = currentMoveString[2] - AAA;
7181 toY = currentMoveString[3] - ONE;
7182 promoChar = NULLCHAR;
7186 sprintf(buf, _("Ambiguous move in ICS output: \"%s\""), yy_text);
7187 if (appData.debugMode) {
7188 fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
7189 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7190 setbuf(debugFP, NULL);
7192 DisplayError(buf, 0);
7194 case ImpossibleMove:
7196 sprintf(buf, _("Illegal move in ICS output: \"%s\""), yy_text);
7197 if (appData.debugMode) {
7198 fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
7199 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7200 setbuf(debugFP, NULL);
7202 DisplayError(buf, 0);
7204 case (ChessMove) 0: /* end of file */
7205 if (boardIndex < backwardMostMove) {
7206 /* Oops, gap. How did that happen? */
7207 DisplayError(_("Gap in move list"), 0);
7210 backwardMostMove = blackPlaysFirst ? 1 : 0;
7211 if (boardIndex > forwardMostMove) {
7212 forwardMostMove = boardIndex;
7216 if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
7217 strcat(parseList[boardIndex-1], " ");
7218 strcat(parseList[boardIndex-1], yy_text);
7230 case GameUnfinished:
7231 if (gameMode == IcsExamining) {
7232 if (boardIndex < backwardMostMove) {
7233 /* Oops, gap. How did that happen? */
7236 backwardMostMove = blackPlaysFirst ? 1 : 0;
7239 gameInfo.result = moveType;
7240 p = strchr(yy_text, '{');
7241 if (p == NULL) p = strchr(yy_text, '(');
7244 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
7246 q = strchr(p, *p == '{' ? '}' : ')');
7247 if (q != NULL) *q = NULLCHAR;
7250 gameInfo.resultDetails = StrSave(p);
7253 if (boardIndex >= forwardMostMove &&
7254 !(gameMode == IcsObserving && ics_gamenum == -1)) {
7255 backwardMostMove = blackPlaysFirst ? 1 : 0;
7258 (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
7259 EP_UNKNOWN, fromY, fromX, toY, toX, promoChar,
7260 parseList[boardIndex]);
7261 CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
7262 {int i; for(i=0; i<BOARD_SIZE; i++) castlingRights[boardIndex+1][i] = castlingRights[boardIndex][i];}
7263 /* currentMoveString is set as a side-effect of yylex */
7264 strcpy(moveList[boardIndex], currentMoveString);
7265 strcat(moveList[boardIndex], "\n");
7267 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex],
7268 castlingRights[boardIndex], &epStatus[boardIndex]);
7269 switch (MateTest(boards[boardIndex], PosFlags(boardIndex),
7270 EP_UNKNOWN, castlingRights[boardIndex]) ) {
7276 if(gameInfo.variant != VariantShogi)
7277 strcat(parseList[boardIndex - 1], "+");
7281 strcat(parseList[boardIndex - 1], "#");
7288 /* Apply a move to the given board */
7290 ApplyMove(fromX, fromY, toX, toY, promoChar, board, castling, ep)
7291 int fromX, fromY, toX, toY;
7297 ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
7299 /* [HGM] compute & store e.p. status and castling rights for new position */
7300 /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
7303 if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
7307 if( board[toY][toX] != EmptySquare )
7310 if( board[fromY][fromX] == WhitePawn ) {
7311 if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
7314 if(toX>BOARD_LEFT && board[toY][toX-1] == BlackPawn &&
7315 gameInfo.variant != VariantBerolina || toX < fromX)
7316 *ep = toX | berolina;
7317 if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
7318 gameInfo.variant != VariantBerolina || toX > fromX)
7322 if( board[fromY][fromX] == BlackPawn ) {
7323 if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
7325 if( toY-fromY== -2) {
7326 if(toX>BOARD_LEFT && board[toY][toX-1] == WhitePawn &&
7327 gameInfo.variant != VariantBerolina || toX < fromX)
7328 *ep = toX | berolina;
7329 if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
7330 gameInfo.variant != VariantBerolina || toX > fromX)
7335 for(i=0; i<nrCastlingRights; i++) {
7336 if(castling[i] == fromX && castlingRank[i] == fromY ||
7337 castling[i] == toX && castlingRank[i] == toY
7338 ) castling[i] = -1; // revoke for moved or captured piece
7343 /* [HGM] In Shatranj and Courier all promotions are to Ferz */
7344 if((gameInfo.variant==VariantShatranj || gameInfo.variant==VariantCourier)
7345 && promoChar != 0) promoChar = PieceToChar(WhiteFerz);
7347 if (fromX == toX && fromY == toY) return;
7349 if (fromY == DROP_RANK) {
7351 piece = board[toY][toX] = (ChessSquare) fromX;
7353 piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
7354 king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
7355 if(gameInfo.variant == VariantKnightmate)
7356 king += (int) WhiteUnicorn - (int) WhiteKing;
7358 /* Code added by Tord: */
7359 /* FRC castling assumed when king captures friendly rook. */
7360 if (board[fromY][fromX] == WhiteKing &&
7361 board[toY][toX] == WhiteRook) {
7362 board[fromY][fromX] = EmptySquare;
7363 board[toY][toX] = EmptySquare;
7365 board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
7367 board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
7369 } else if (board[fromY][fromX] == BlackKing &&
7370 board[toY][toX] == BlackRook) {
7371 board[fromY][fromX] = EmptySquare;
7372 board[toY][toX] = EmptySquare;
7374 board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
7376 board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
7378 /* End of code added by Tord */
7380 } else if (board[fromY][fromX] == king
7381 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7382 && toY == fromY && toX > fromX+1) {
7383 board[fromY][fromX] = EmptySquare;
7384 board[toY][toX] = king;
7385 board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
7386 board[fromY][BOARD_RGHT-1] = EmptySquare;
7387 } else if (board[fromY][fromX] == king
7388 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7389 && toY == fromY && toX < fromX-1) {
7390 board[fromY][fromX] = EmptySquare;
7391 board[toY][toX] = king;
7392 board[toY][toX+1] = board[fromY][BOARD_LEFT];
7393 board[fromY][BOARD_LEFT] = EmptySquare;
7394 } else if (board[fromY][fromX] == WhitePawn
7395 && toY == BOARD_HEIGHT-1
7396 && gameInfo.variant != VariantXiangqi
7398 /* white pawn promotion */
7399 board[toY][toX] = CharToPiece(ToUpper(promoChar));
7400 if (board[toY][toX] == EmptySquare) {
7401 board[toY][toX] = WhiteQueen;
7403 if(gameInfo.variant==VariantBughouse ||
7404 gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
7405 board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
7406 board[fromY][fromX] = EmptySquare;
7407 } else if ((fromY == BOARD_HEIGHT-4)
7409 && gameInfo.variant != VariantXiangqi
7410 && gameInfo.variant != VariantBerolina
7411 && (board[fromY][fromX] == WhitePawn)
7412 && (board[toY][toX] == EmptySquare)) {
7413 board[fromY][fromX] = EmptySquare;
7414 board[toY][toX] = WhitePawn;
7415 captured = board[toY - 1][toX];
7416 board[toY - 1][toX] = EmptySquare;
7417 } else if ((fromY == BOARD_HEIGHT-4)
7419 && gameInfo.variant == VariantBerolina
7420 && (board[fromY][fromX] == WhitePawn)
7421 && (board[toY][toX] == EmptySquare)) {
7422 board[fromY][fromX] = EmptySquare;
7423 board[toY][toX] = WhitePawn;
7424 if(oldEP & EP_BEROLIN_A) {
7425 captured = board[fromY][fromX-1];
7426 board[fromY][fromX-1] = EmptySquare;
7427 }else{ captured = board[fromY][fromX+1];
7428 board[fromY][fromX+1] = EmptySquare;
7430 } else if (board[fromY][fromX] == king
7431 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7432 && toY == fromY && toX > fromX+1) {
7433 board[fromY][fromX] = EmptySquare;
7434 board[toY][toX] = king;
7435 board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
7436 board[fromY][BOARD_RGHT-1] = EmptySquare;
7437 } else if (board[fromY][fromX] == king
7438 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7439 && toY == fromY && toX < fromX-1) {
7440 board[fromY][fromX] = EmptySquare;
7441 board[toY][toX] = king;
7442 board[toY][toX+1] = board[fromY][BOARD_LEFT];
7443 board[fromY][BOARD_LEFT] = EmptySquare;
7444 } else if (fromY == 7 && fromX == 3
7445 && board[fromY][fromX] == BlackKing
7446 && toY == 7 && toX == 5) {
7447 board[fromY][fromX] = EmptySquare;
7448 board[toY][toX] = BlackKing;
7449 board[fromY][7] = EmptySquare;
7450 board[toY][4] = BlackRook;
7451 } else if (fromY == 7 && fromX == 3
7452 && board[fromY][fromX] == BlackKing
7453 && toY == 7 && toX == 1) {
7454 board[fromY][fromX] = EmptySquare;
7455 board[toY][toX] = BlackKing;
7456 board[fromY][0] = EmptySquare;
7457 board[toY][2] = BlackRook;
7458 } else if (board[fromY][fromX] == BlackPawn
7460 && gameInfo.variant != VariantXiangqi
7462 /* black pawn promotion */
7463 board[0][toX] = CharToPiece(ToLower(promoChar));
7464 if (board[0][toX] == EmptySquare) {
7465 board[0][toX] = BlackQueen;
7467 if(gameInfo.variant==VariantBughouse ||
7468 gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
7469 board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
7470 board[fromY][fromX] = EmptySquare;
7471 } else if ((fromY == 3)
7473 && gameInfo.variant != VariantXiangqi
7474 && gameInfo.variant != VariantBerolina
7475 && (board[fromY][fromX] == BlackPawn)
7476 && (board[toY][toX] == EmptySquare)) {
7477 board[fromY][fromX] = EmptySquare;
7478 board[toY][toX] = BlackPawn;
7479 captured = board[toY + 1][toX];
7480 board[toY + 1][toX] = EmptySquare;
7481 } else if ((fromY == 3)
7483 && gameInfo.variant == VariantBerolina
7484 && (board[fromY][fromX] == BlackPawn)
7485 && (board[toY][toX] == EmptySquare)) {
7486 board[fromY][fromX] = EmptySquare;
7487 board[toY][toX] = BlackPawn;
7488 if(oldEP & EP_BEROLIN_A) {
7489 captured = board[fromY][fromX-1];
7490 board[fromY][fromX-1] = EmptySquare;
7491 }else{ captured = board[fromY][fromX+1];
7492 board[fromY][fromX+1] = EmptySquare;
7495 board[toY][toX] = board[fromY][fromX];
7496 board[fromY][fromX] = EmptySquare;
7499 /* [HGM] now we promote for Shogi, if needed */
7500 if(gameInfo.variant == VariantShogi && promoChar == 'q')
7501 board[toY][toX] = (ChessSquare) (PROMOTED piece);
7504 if (gameInfo.holdingsWidth != 0) {
7506 /* !!A lot more code needs to be written to support holdings */
7507 /* [HGM] OK, so I have written it. Holdings are stored in the */
7508 /* penultimate board files, so they are automaticlly stored */
7509 /* in the game history. */
7510 if (fromY == DROP_RANK) {
7511 /* Delete from holdings, by decreasing count */
7512 /* and erasing image if necessary */
7514 if(p < (int) BlackPawn) { /* white drop */
7515 p -= (int)WhitePawn;
7516 if(p >= gameInfo.holdingsSize) p = 0;
7517 if(--board[p][BOARD_WIDTH-2] == 0)
7518 board[p][BOARD_WIDTH-1] = EmptySquare;
7519 } else { /* black drop */
7520 p -= (int)BlackPawn;
7521 if(p >= gameInfo.holdingsSize) p = 0;
7522 if(--board[BOARD_HEIGHT-1-p][1] == 0)
7523 board[BOARD_HEIGHT-1-p][0] = EmptySquare;
7526 if (captured != EmptySquare && gameInfo.holdingsSize > 0
7527 && gameInfo.variant != VariantBughouse ) {
7528 /* [HGM] holdings: Add to holdings, if holdings exist */
7529 if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
7530 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
7531 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
7534 if (p >= (int) BlackPawn) {
7535 p -= (int)BlackPawn;
7536 if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
7537 /* in Shogi restore piece to its original first */
7538 captured = (ChessSquare) (DEMOTED captured);
7541 p = PieceToNumber((ChessSquare)p);
7542 if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
7543 board[p][BOARD_WIDTH-2]++;
7544 board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
7546 p -= (int)WhitePawn;
7547 if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
7548 captured = (ChessSquare) (DEMOTED captured);
7551 p = PieceToNumber((ChessSquare)p);
7552 if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
7553 board[BOARD_HEIGHT-1-p][1]++;
7554 board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
7558 } else if (gameInfo.variant == VariantAtomic) {
7559 if (captured != EmptySquare) {
7561 for (y = toY-1; y <= toY+1; y++) {
7562 for (x = toX-1; x <= toX+1; x++) {
7563 if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
7564 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
7565 board[y][x] = EmptySquare;
7569 board[toY][toX] = EmptySquare;
7572 if(gameInfo.variant == VariantShogi && promoChar != NULLCHAR && promoChar != '=') {
7573 /* [HGM] Shogi promotions */
7574 board[toY][toX] = (ChessSquare) (PROMOTED piece);
7577 if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
7578 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
7579 // [HGM] superchess: take promotion piece out of holdings
7580 int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
7581 if((int)piece < (int)BlackPawn) { // determine stm from piece color
7582 if(!--board[k][BOARD_WIDTH-2])
7583 board[k][BOARD_WIDTH-1] = EmptySquare;
7585 if(!--board[BOARD_HEIGHT-1-k][1])
7586 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
7592 /* Updates forwardMostMove */
7594 MakeMove(fromX, fromY, toX, toY, promoChar)
7595 int fromX, fromY, toX, toY;
7598 // forwardMostMove++; // [HGM] bare: moved downstream
7600 if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
7601 int timeLeft; static int lastLoadFlag=0; int king, piece;
7602 piece = boards[forwardMostMove][fromY][fromX];
7603 king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
7604 if(gameInfo.variant == VariantKnightmate)
7605 king += (int) WhiteUnicorn - (int) WhiteKing;
7606 if(forwardMostMove == 0) {
7608 fprintf(serverMoves, "%s;", second.tidy);
7609 fprintf(serverMoves, "%s;", first.tidy);
7610 if(!blackPlaysFirst)
7611 fprintf(serverMoves, "%s;", second.tidy);
7612 } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
7613 lastLoadFlag = loadFlag;
7615 fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
7616 // print castling suffix
7617 if( toY == fromY && piece == king ) {
7619 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
7621 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
7624 if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
7625 boards[forwardMostMove][fromY][fromX] == BlackPawn ) &&
7626 boards[forwardMostMove][toY][toX] == EmptySquare
7628 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
7630 if(promoChar != NULLCHAR)
7631 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);
7633 fprintf(serverMoves, "/%d/%d",
7634 pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
7635 if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
7636 else timeLeft = blackTimeRemaining/1000;
7637 fprintf(serverMoves, "/%d", timeLeft);
7639 fflush(serverMoves);
7642 if (forwardMostMove+1 >= MAX_MOVES) {
7643 DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
7647 if (commentList[forwardMostMove+1] != NULL) {
7648 free(commentList[forwardMostMove+1]);
7649 commentList[forwardMostMove+1] = NULL;
7651 CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
7652 {int i; for(i=0; i<BOARD_SIZE; i++) castlingRights[forwardMostMove+1][i] = castlingRights[forwardMostMove][i];}
7653 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1],
7654 castlingRights[forwardMostMove+1], &epStatus[forwardMostMove+1]);
7655 forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
7656 SwitchClocks(); // uses forwardMostMove, so must be done after incrementing it !
7657 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
7658 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
7659 gameInfo.result = GameUnfinished;
7660 if (gameInfo.resultDetails != NULL) {
7661 free(gameInfo.resultDetails);
7662 gameInfo.resultDetails = NULL;
7664 CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
7665 moveList[forwardMostMove - 1]);
7666 (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
7667 PosFlags(forwardMostMove - 1), EP_UNKNOWN,
7668 fromY, fromX, toY, toX, promoChar,
7669 parseList[forwardMostMove - 1]);
7670 switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove),
7671 epStatus[forwardMostMove], /* [HGM] use true e.p. */
7672 castlingRights[forwardMostMove]) ) {
7678 if(gameInfo.variant != VariantShogi)
7679 strcat(parseList[forwardMostMove - 1], "+");
7683 strcat(parseList[forwardMostMove - 1], "#");
7686 if (appData.debugMode) {
7687 fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
7692 /* Updates currentMove if not pausing */
7694 ShowMove(fromX, fromY, toX, toY)
7696 int instant = (gameMode == PlayFromGameFile) ?
7697 (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
7698 if(appData.noGUI) return;
7699 if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
7701 if (forwardMostMove == currentMove + 1) {
7702 AnimateMove(boards[forwardMostMove - 1],
7703 fromX, fromY, toX, toY);
7705 if (appData.highlightLastMove) {
7706 SetHighlights(fromX, fromY, toX, toY);
7709 currentMove = forwardMostMove;
7712 if (instant) return;
7714 DisplayMove(currentMove - 1);
7715 DrawPosition(FALSE, boards[currentMove]);
7716 DisplayBothClocks();
7717 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
7720 void SendEgtPath(ChessProgramState *cps)
7721 { /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
7722 char buf[MSG_SIZ], name[MSG_SIZ], *p;
7724 if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
7727 char c, *q = name+1, *r, *s;
7729 name[0] = ','; // extract next format name from feature and copy with prefixed ','
7730 while(*p && *p != ',') *q++ = *p++;
7732 if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
7733 strcmp(name, ",nalimov:") == 0 ) {
7734 // take nalimov path from the menu-changeable option first, if it is defined
7735 sprintf(buf, "egtpath nalimov %s\n", appData.defaultPathEGTB);
7736 SendToProgram(buf,cps); // send egtbpath command for nalimov
7738 if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
7739 (s = StrStr(appData.egtFormats, name)) != NULL) {
7740 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
7741 s = r = StrStr(s, ":") + 1; // beginning of path info
7742 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
7743 c = *r; *r = 0; // temporarily null-terminate path info
7744 *--q = 0; // strip of trailig ':' from name
7745 sprintf(buf, "egtpath %s %s\n", name+1, s);
7747 SendToProgram(buf,cps); // send egtbpath command for this format
7749 if(*p == ',') p++; // read away comma to position for next format name
7754 InitChessProgram(cps, setup)
7755 ChessProgramState *cps;
7756 int setup; /* [HGM] needed to setup FRC opening position */
7758 char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
7759 if (appData.noChessProgram) return;
7760 hintRequested = FALSE;
7761 bookRequested = FALSE;
7763 /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
7764 /* moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
7765 if(cps->memSize) { /* [HGM] memory */
7766 sprintf(buf, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
7767 SendToProgram(buf, cps);
7769 SendEgtPath(cps); /* [HGM] EGT */
7770 if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
7771 sprintf(buf, "cores %d\n", appData.smpCores);
7772 SendToProgram(buf, cps);
7775 SendToProgram(cps->initString, cps);
7776 if (gameInfo.variant != VariantNormal &&
7777 gameInfo.variant != VariantLoadable
7778 /* [HGM] also send variant if board size non-standard */
7779 || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
7781 char *v = VariantName(gameInfo.variant);
7782 if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
7783 /* [HGM] in protocol 1 we have to assume all variants valid */
7784 sprintf(buf, _("Variant %s not supported by %s"), v, cps->tidy);
7785 DisplayFatalError(buf, 0, 1);
7789 /* [HGM] make prefix for non-standard board size. Awkward testing... */
7790 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7791 if( gameInfo.variant == VariantXiangqi )
7792 overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
7793 if( gameInfo.variant == VariantShogi )
7794 overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
7795 if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
7796 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
7797 if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
7798 gameInfo.variant == VariantGothic || gameInfo.variant == VariantFalcon )
7799 overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7800 if( gameInfo.variant == VariantCourier )
7801 overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7802 if( gameInfo.variant == VariantSuper )
7803 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
7804 if( gameInfo.variant == VariantGreat )
7805 overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
7808 sprintf(b, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
7809 gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
7810 /* [HGM] varsize: try first if this defiant size variant is specifically known */
7811 if(StrStr(cps->variants, b) == NULL) {
7812 // specific sized variant not known, check if general sizing allowed
7813 if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
7814 if(StrStr(cps->variants, "boardsize") == NULL) {
7815 sprintf(buf, "Board size %dx%d+%d not supported by %s",
7816 gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
7817 DisplayFatalError(buf, 0, 1);
7820 /* [HGM] here we really should compare with the maximum supported board size */
7823 } else sprintf(b, "%s", VariantName(gameInfo.variant));
7824 sprintf(buf, "variant %s\n", b);
7825 SendToProgram(buf, cps);
7827 currentlyInitializedVariant = gameInfo.variant;
7829 /* [HGM] send opening position in FRC to first engine */
7831 SendToProgram("force\n", cps);
7833 /* engine is now in force mode! Set flag to wake it up after first move. */
7834 setboardSpoiledMachineBlack = 1;
7838 snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
7839 SendToProgram(buf, cps);
7841 cps->maybeThinking = FALSE;
7842 cps->offeredDraw = 0;
7843 if (!appData.icsActive) {
7844 SendTimeControl(cps, movesPerSession, timeControl,
7845 timeIncrement, appData.searchDepth,
7848 if (appData.showThinking
7849 // [HGM] thinking: four options require thinking output to be sent
7850 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
7852 SendToProgram("post\n", cps);
7854 SendToProgram("hard\n", cps);
7855 if (!appData.ponderNextMove) {
7856 /* Warning: "easy" is a toggle in GNU Chess, so don't send
7857 it without being sure what state we are in first. "hard"
7858 is not a toggle, so that one is OK.
7860 SendToProgram("easy\n", cps);
7863 sprintf(buf, "ping %d\n", ++cps->lastPing);
7864 SendToProgram(buf, cps);
7866 cps->initDone = TRUE;
7871 StartChessProgram(cps)
7872 ChessProgramState *cps;
7877 if (appData.noChessProgram) return;
7878 cps->initDone = FALSE;
7880 if (strcmp(cps->host, "localhost") == 0) {
7881 err = StartChildProcess(cps->program, cps->dir, &cps->pr);
7882 } else if (*appData.remoteShell == NULLCHAR) {
7883 err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
7885 if (*appData.remoteUser == NULLCHAR) {
7886 snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
7889 snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
7890 cps->host, appData.remoteUser, cps->program);
7892 err = StartChildProcess(buf, "", &cps->pr);
7896 sprintf(buf, _("Startup failure on '%s'"), cps->program);
7897 DisplayFatalError(buf, err, 1);
7903 cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
7904 if (cps->protocolVersion > 1) {
7905 sprintf(buf, "xboard\nprotover %d\n", cps->protocolVersion);
7906 cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
7907 cps->comboCnt = 0; // and values of combo boxes
7908 SendToProgram(buf, cps);
7910 SendToProgram("xboard\n", cps);
7916 TwoMachinesEventIfReady P((void))
7918 if (first.lastPing != first.lastPong) {
7919 DisplayMessage("", _("Waiting for first chess program"));
7920 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
7923 if (second.lastPing != second.lastPong) {
7924 DisplayMessage("", _("Waiting for second chess program"));
7925 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
7933 NextMatchGame P((void))
7935 int index; /* [HGM] autoinc: step lod index during match */
7937 if (*appData.loadGameFile != NULLCHAR) {
7938 index = appData.loadGameIndex;
7939 if(index < 0) { // [HGM] autoinc
7940 lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
7941 if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
7943 LoadGameFromFile(appData.loadGameFile,
7945 appData.loadGameFile, FALSE);
7946 } else if (*appData.loadPositionFile != NULLCHAR) {
7947 index = appData.loadPositionIndex;
7948 if(index < 0) { // [HGM] autoinc
7949 lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
7950 if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
7952 LoadPositionFromFile(appData.loadPositionFile,
7954 appData.loadPositionFile);
7956 TwoMachinesEventIfReady();
7959 void UserAdjudicationEvent( int result )
7961 ChessMove gameResult = GameIsDrawn;
7964 gameResult = WhiteWins;
7966 else if( result < 0 ) {
7967 gameResult = BlackWins;
7970 if( gameMode == TwoMachinesPlay ) {
7971 GameEnds( gameResult, "User adjudication", GE_XBOARD );
7976 // [HGM] save: calculate checksum of game to make games easily identifiable
7977 int StringCheckSum(char *s)
7980 if(s==NULL) return 0;
7981 while(*s) i = i*259 + *s++;
7988 for(i=backwardMostMove; i<forwardMostMove; i++) {
7989 sum += pvInfoList[i].depth;
7990 sum += StringCheckSum(parseList[i]);
7991 sum += StringCheckSum(commentList[i]);
7994 if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
7995 return sum + StringCheckSum(commentList[i]);
7996 } // end of save patch
7999 GameEnds(result, resultDetails, whosays)
8001 char *resultDetails;
8004 GameMode nextGameMode;
8008 if(endingGame) return; /* [HGM] crash: forbid recursion */
8011 if (appData.debugMode) {
8012 fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
8013 result, resultDetails ? resultDetails : "(null)", whosays);
8016 if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
8017 /* If we are playing on ICS, the server decides when the
8018 game is over, but the engine can offer to draw, claim
8022 if (appData.zippyPlay && first.initDone) {
8023 if (result == GameIsDrawn) {
8024 /* In case draw still needs to be claimed */
8025 SendToICS(ics_prefix);
8026 SendToICS("draw\n");
8027 } else if (StrCaseStr(resultDetails, "resign")) {
8028 SendToICS(ics_prefix);
8029 SendToICS("resign\n");
8033 endingGame = 0; /* [HGM] crash */
8037 /* If we're loading the game from a file, stop */
8038 if (whosays == GE_FILE) {
8039 (void) StopLoadGameTimer();
8043 /* Cancel draw offers */
8044 first.offeredDraw = second.offeredDraw = 0;
8046 /* If this is an ICS game, only ICS can really say it's done;
8047 if not, anyone can. */
8048 isIcsGame = (gameMode == IcsPlayingWhite ||
8049 gameMode == IcsPlayingBlack ||
8050 gameMode == IcsObserving ||
8051 gameMode == IcsExamining);
8053 if (!isIcsGame || whosays == GE_ICS) {
8054 /* OK -- not an ICS game, or ICS said it was done */
8056 if (!isIcsGame && !appData.noChessProgram)
8057 SetUserThinkingEnables();
8059 /* [HGM] if a machine claims the game end we verify this claim */
8060 if(gameMode == TwoMachinesPlay && appData.testClaims) {
8061 if(appData.testLegality && whosays >= GE_ENGINE1 ) {
8063 ChessMove trueResult = (ChessMove) -1;
8065 claimer = whosays == GE_ENGINE1 ? /* color of claimer */
8066 first.twoMachinesColor[0] :
8067 second.twoMachinesColor[0] ;
8069 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
8070 if(epStatus[forwardMostMove] == EP_CHECKMATE) {
8071 /* [HGM] verify: engine mate claims accepted if they were flagged */
8072 trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
8074 if(epStatus[forwardMostMove] == EP_WINS) { // added code for games where being mated is a win
8075 /* [HGM] verify: engine mate claims accepted if they were flagged */
8076 trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8078 if(epStatus[forwardMostMove] == EP_STALEMATE) { // only used to indicate draws now
8079 trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
8082 // now verify win claims, but not in drop games, as we don't understand those yet
8083 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
8084 || gameInfo.variant == VariantGreat) &&
8085 (result == WhiteWins && claimer == 'w' ||
8086 result == BlackWins && claimer == 'b' ) ) { // case to verify: engine claims own win
8087 if (appData.debugMode) {
8088 fprintf(debugFP, "result=%d sp=%d move=%d\n",
8089 result, epStatus[forwardMostMove], forwardMostMove);
8091 if(result != trueResult) {
8092 sprintf(buf, "False win claim: '%s'", resultDetails);
8093 result = claimer == 'w' ? BlackWins : WhiteWins;
8094 resultDetails = buf;
8097 if( result == GameIsDrawn && epStatus[forwardMostMove] > EP_DRAWS
8098 && (forwardMostMove <= backwardMostMove ||
8099 epStatus[forwardMostMove-1] > EP_DRAWS ||
8100 (claimer=='b')==(forwardMostMove&1))
8102 /* [HGM] verify: draws that were not flagged are false claims */
8103 sprintf(buf, "False draw claim: '%s'", resultDetails);
8104 result = claimer == 'w' ? BlackWins : WhiteWins;
8105 resultDetails = buf;
8107 /* (Claiming a loss is accepted no questions asked!) */
8109 /* [HGM] bare: don't allow bare King to win */
8110 if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
8111 && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
8112 && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
8113 && result != GameIsDrawn)
8114 { int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
8115 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
8116 int p = (int)boards[forwardMostMove][i][j] - color;
8117 if(p >= 0 && p <= (int)WhiteKing) k++;
8119 if (appData.debugMode) {
8120 fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
8121 result, resultDetails ? resultDetails : "(null)", whosays, k, color);
8124 result = GameIsDrawn;
8125 sprintf(buf, "%s but bare king", resultDetails);
8126 resultDetails = buf;
8132 if(serverMoves != NULL && !loadFlag) { char c = '=';
8133 if(result==WhiteWins) c = '+';
8134 if(result==BlackWins) c = '-';
8135 if(resultDetails != NULL)
8136 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
8138 if (resultDetails != NULL) {
8139 gameInfo.result = result;
8140 gameInfo.resultDetails = StrSave(resultDetails);
8142 /* display last move only if game was not loaded from file */
8143 if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
8144 DisplayMove(currentMove - 1);
8146 if (forwardMostMove != 0) {
8147 if (gameMode != PlayFromGameFile && gameMode != EditGame
8148 && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
8150 if (*appData.saveGameFile != NULLCHAR) {
8151 SaveGameToFile(appData.saveGameFile, TRUE);
8152 } else if (appData.autoSaveGames) {
8155 if (*appData.savePositionFile != NULLCHAR) {
8156 SavePositionToFile(appData.savePositionFile);
8161 /* Tell program how game ended in case it is learning */
8162 /* [HGM] Moved this to after saving the PGN, just in case */
8163 /* engine died and we got here through time loss. In that */
8164 /* case we will get a fatal error writing the pipe, which */
8165 /* would otherwise lose us the PGN. */
8166 /* [HGM] crash: not needed anymore, but doesn't hurt; */
8167 /* output during GameEnds should never be fatal anymore */
8168 if (gameMode == MachinePlaysWhite ||
8169 gameMode == MachinePlaysBlack ||
8170 gameMode == TwoMachinesPlay ||
8171 gameMode == IcsPlayingWhite ||
8172 gameMode == IcsPlayingBlack ||
8173 gameMode == BeginningOfGame) {
8175 sprintf(buf, "result %s {%s}\n", PGNResult(result),
8177 if (first.pr != NoProc) {
8178 SendToProgram(buf, &first);
8180 if (second.pr != NoProc &&
8181 gameMode == TwoMachinesPlay) {
8182 SendToProgram(buf, &second);
8187 if (appData.icsActive) {
8188 if (appData.quietPlay &&
8189 (gameMode == IcsPlayingWhite ||
8190 gameMode == IcsPlayingBlack)) {
8191 SendToICS(ics_prefix);
8192 SendToICS("set shout 1\n");
8194 nextGameMode = IcsIdle;
8195 ics_user_moved = FALSE;
8196 /* clean up premove. It's ugly when the game has ended and the
8197 * premove highlights are still on the board.
8201 ClearPremoveHighlights();
8202 DrawPosition(FALSE, boards[currentMove]);
8204 if (whosays == GE_ICS) {
8207 if (gameMode == IcsPlayingWhite)
8209 else if(gameMode == IcsPlayingBlack)
8213 if (gameMode == IcsPlayingBlack)
8215 else if(gameMode == IcsPlayingWhite)
8222 PlayIcsUnfinishedSound();
8225 } else if (gameMode == EditGame ||
8226 gameMode == PlayFromGameFile ||
8227 gameMode == AnalyzeMode ||
8228 gameMode == AnalyzeFile) {
8229 nextGameMode = gameMode;
8231 nextGameMode = EndOfGame;
8236 nextGameMode = gameMode;
8239 if (appData.noChessProgram) {
8240 gameMode = nextGameMode;
8242 endingGame = 0; /* [HGM] crash */
8247 /* Put first chess program into idle state */
8248 if (first.pr != NoProc &&
8249 (gameMode == MachinePlaysWhite ||
8250 gameMode == MachinePlaysBlack ||
8251 gameMode == TwoMachinesPlay ||
8252 gameMode == IcsPlayingWhite ||
8253 gameMode == IcsPlayingBlack ||
8254 gameMode == BeginningOfGame)) {
8255 SendToProgram("force\n", &first);
8256 if (first.usePing) {
8258 sprintf(buf, "ping %d\n", ++first.lastPing);
8259 SendToProgram(buf, &first);
8262 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
8263 /* Kill off first chess program */
8264 if (first.isr != NULL)
8265 RemoveInputSource(first.isr);
8268 if (first.pr != NoProc) {
8270 DoSleep( appData.delayBeforeQuit );
8271 SendToProgram("quit\n", &first);
8272 DoSleep( appData.delayAfterQuit );
8273 DestroyChildProcess(first.pr, first.useSigterm);
8278 /* Put second chess program into idle state */
8279 if (second.pr != NoProc &&
8280 gameMode == TwoMachinesPlay) {
8281 SendToProgram("force\n", &second);
8282 if (second.usePing) {
8284 sprintf(buf, "ping %d\n", ++second.lastPing);
8285 SendToProgram(buf, &second);
8288 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
8289 /* Kill off second chess program */
8290 if (second.isr != NULL)
8291 RemoveInputSource(second.isr);
8294 if (second.pr != NoProc) {
8295 DoSleep( appData.delayBeforeQuit );
8296 SendToProgram("quit\n", &second);
8297 DoSleep( appData.delayAfterQuit );
8298 DestroyChildProcess(second.pr, second.useSigterm);
8303 if (matchMode && gameMode == TwoMachinesPlay) {
8306 if (first.twoMachinesColor[0] == 'w') {
8313 if (first.twoMachinesColor[0] == 'b') {
8322 if (matchGame < appData.matchGames) {
8324 if(appData.sameColorGames <= 1) { /* [HGM] alternate: suppress color swap */
8325 tmp = first.twoMachinesColor;
8326 first.twoMachinesColor = second.twoMachinesColor;
8327 second.twoMachinesColor = tmp;
8329 gameMode = nextGameMode;
8331 if(appData.matchPause>10000 || appData.matchPause<10)
8332 appData.matchPause = 10000; /* [HGM] make pause adjustable */
8333 ScheduleDelayedEvent(NextMatchGame, appData.matchPause);
8334 endingGame = 0; /* [HGM] crash */
8338 gameMode = nextGameMode;
8339 sprintf(buf, _("Match %s vs. %s: final score %d-%d-%d"),
8340 first.tidy, second.tidy,
8341 first.matchWins, second.matchWins,
8342 appData.matchGames - (first.matchWins + second.matchWins));
8343 DisplayFatalError(buf, 0, 0);
8346 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
8347 !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
8349 gameMode = nextGameMode;
8351 endingGame = 0; /* [HGM] crash */
8354 /* Assumes program was just initialized (initString sent).
8355 Leaves program in force mode. */
8357 FeedMovesToProgram(cps, upto)
8358 ChessProgramState *cps;
8363 if (appData.debugMode)
8364 fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
8365 startedFromSetupPosition ? "position and " : "",
8366 backwardMostMove, upto, cps->which);
8367 if(currentlyInitializedVariant != gameInfo.variant) { char buf[MSG_SIZ];
8368 // [HGM] variantswitch: make engine aware of new variant
8369 if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
8370 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
8371 sprintf(buf, "variant %s\n", VariantName(gameInfo.variant));
8372 SendToProgram(buf, cps);
8373 currentlyInitializedVariant = gameInfo.variant;
8375 SendToProgram("force\n", cps);
8376 if (startedFromSetupPosition) {
8377 SendBoard(cps, backwardMostMove);
8378 if (appData.debugMode) {
8379 fprintf(debugFP, "feedMoves\n");
8382 for (i = backwardMostMove; i < upto; i++) {
8383 SendMoveToProgram(i, cps);
8389 ResurrectChessProgram()
8391 /* The chess program may have exited.
8392 If so, restart it and feed it all the moves made so far. */
8394 if (appData.noChessProgram || first.pr != NoProc) return;
8396 StartChessProgram(&first);
8397 InitChessProgram(&first, FALSE);
8398 FeedMovesToProgram(&first, currentMove);
8400 if (!first.sendTime) {
8401 /* can't tell gnuchess what its clock should read,
8402 so we bow to its notion. */
8404 timeRemaining[0][currentMove] = whiteTimeRemaining;
8405 timeRemaining[1][currentMove] = blackTimeRemaining;
8408 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
8409 appData.icsEngineAnalyze) && first.analysisSupport) {
8410 SendToProgram("analyze\n", &first);
8411 first.analyzing = TRUE;
8424 if (appData.debugMode) {
8425 fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
8426 redraw, init, gameMode);
8428 pausing = pauseExamInvalid = FALSE;
8429 startedFromSetupPosition = blackPlaysFirst = FALSE;
8431 whiteFlag = blackFlag = FALSE;
8432 userOfferedDraw = FALSE;
8433 hintRequested = bookRequested = FALSE;
8434 first.maybeThinking = FALSE;
8435 second.maybeThinking = FALSE;
8436 first.bookSuspend = FALSE; // [HGM] book
8437 second.bookSuspend = FALSE;
8438 thinkOutput[0] = NULLCHAR;
8439 lastHint[0] = NULLCHAR;
8440 ClearGameInfo(&gameInfo);
8441 gameInfo.variant = StringToVariant(appData.variant);
8442 ics_user_moved = ics_clock_paused = FALSE;
8443 ics_getting_history = H_FALSE;
8445 white_holding[0] = black_holding[0] = NULLCHAR;
8446 ClearProgramStats();
8447 opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
8451 flipView = appData.flipView;
8452 ClearPremoveHighlights();
8454 alarmSounded = FALSE;
8456 GameEnds((ChessMove) 0, NULL, GE_PLAYER);
8457 if(appData.serverMovesName != NULL) {
8458 /* [HGM] prepare to make moves file for broadcasting */
8459 clock_t t = clock();
8460 if(serverMoves != NULL) fclose(serverMoves);
8461 serverMoves = fopen(appData.serverMovesName, "r");
8462 if(serverMoves != NULL) {
8463 fclose(serverMoves);
8464 /* delay 15 sec before overwriting, so all clients can see end */
8465 while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
8467 serverMoves = fopen(appData.serverMovesName, "w");
8471 gameMode = BeginningOfGame;
8473 if(appData.icsActive) gameInfo.variant = VariantNormal;
8474 currentMove = forwardMostMove = backwardMostMove = 0;
8475 InitPosition(redraw);
8476 for (i = 0; i < MAX_MOVES; i++) {
8477 if (commentList[i] != NULL) {
8478 free(commentList[i]);
8479 commentList[i] = NULL;
8483 timeRemaining[0][0] = whiteTimeRemaining;
8484 timeRemaining[1][0] = blackTimeRemaining;
8485 if (first.pr == NULL) {
8486 StartChessProgram(&first);
8489 InitChessProgram(&first, startedFromSetupPosition);
8492 DisplayMessage("", "");
8493 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
8494 lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
8501 if (!AutoPlayOneMove())
8503 if (matchMode || appData.timeDelay == 0)
8505 if (appData.timeDelay < 0 || gameMode == AnalyzeFile)
8507 StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
8516 int fromX, fromY, toX, toY;
8518 if (appData.debugMode) {
8519 fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
8522 if (gameMode != PlayFromGameFile)
8525 if (currentMove >= forwardMostMove) {
8526 gameMode = EditGame;
8529 /* [AS] Clear current move marker at the end of a game */
8530 /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
8535 toX = moveList[currentMove][2] - AAA;
8536 toY = moveList[currentMove][3] - ONE;
8538 if (moveList[currentMove][1] == '@') {
8539 if (appData.highlightLastMove) {
8540 SetHighlights(-1, -1, toX, toY);
8543 fromX = moveList[currentMove][0] - AAA;
8544 fromY = moveList[currentMove][1] - ONE;
8546 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
8548 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
8550 if (appData.highlightLastMove) {
8551 SetHighlights(fromX, fromY, toX, toY);
8554 DisplayMove(currentMove);
8555 SendMoveToProgram(currentMove++, &first);
8556 DisplayBothClocks();
8557 DrawPosition(FALSE, boards[currentMove]);
8558 // [HGM] PV info: always display, routine tests if empty
8559 DisplayComment(currentMove - 1, commentList[currentMove]);
8565 LoadGameOneMove(readAhead)
8566 ChessMove readAhead;
8568 int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
8569 char promoChar = NULLCHAR;
8574 if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
8575 gameMode != AnalyzeMode && gameMode != Training) {
8580 yyboardindex = forwardMostMove;
8581 if (readAhead != (ChessMove)0) {
8582 moveType = readAhead;
8584 if (gameFileFP == NULL)
8586 moveType = (ChessMove) yylex();
8592 if (appData.debugMode)
8593 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
8595 if (*p == '{' || *p == '[' || *p == '(') {
8596 p[strlen(p) - 1] = NULLCHAR;
8600 /* append the comment but don't display it */
8601 while (*p == '\n') p++;
8602 AppendComment(currentMove, p);
8605 case WhiteCapturesEnPassant:
8606 case BlackCapturesEnPassant:
8607 case WhitePromotionChancellor:
8608 case BlackPromotionChancellor:
8609 case WhitePromotionArchbishop:
8610 case BlackPromotionArchbishop:
8611 case WhitePromotionCentaur:
8612 case BlackPromotionCentaur:
8613 case WhitePromotionQueen:
8614 case BlackPromotionQueen:
8615 case WhitePromotionRook:
8616 case BlackPromotionRook:
8617 case WhitePromotionBishop:
8618 case BlackPromotionBishop:
8619 case WhitePromotionKnight:
8620 case BlackPromotionKnight:
8621 case WhitePromotionKing:
8622 case BlackPromotionKing:
8624 case WhiteKingSideCastle:
8625 case WhiteQueenSideCastle:
8626 case BlackKingSideCastle:
8627 case BlackQueenSideCastle:
8628 case WhiteKingSideCastleWild:
8629 case WhiteQueenSideCastleWild:
8630 case BlackKingSideCastleWild:
8631 case BlackQueenSideCastleWild:
8633 case WhiteHSideCastleFR:
8634 case WhiteASideCastleFR:
8635 case BlackHSideCastleFR:
8636 case BlackASideCastleFR:
8638 if (appData.debugMode)
8639 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
8640 fromX = currentMoveString[0] - AAA;
8641 fromY = currentMoveString[1] - ONE;
8642 toX = currentMoveString[2] - AAA;
8643 toY = currentMoveString[3] - ONE;
8644 promoChar = currentMoveString[4];
8649 if (appData.debugMode)
8650 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
8651 fromX = moveType == WhiteDrop ?
8652 (int) CharToPiece(ToUpper(currentMoveString[0])) :
8653 (int) CharToPiece(ToLower(currentMoveString[0]));
8655 toX = currentMoveString[2] - AAA;
8656 toY = currentMoveString[3] - ONE;
8662 case GameUnfinished:
8663 if (appData.debugMode)
8664 fprintf(debugFP, "Parsed game end: %s\n", yy_text);
8665 p = strchr(yy_text, '{');
8666 if (p == NULL) p = strchr(yy_text, '(');
8669 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
8671 q = strchr(p, *p == '{' ? '}' : ')');
8672 if (q != NULL) *q = NULLCHAR;
8675 GameEnds(moveType, p, GE_FILE);
8677 if (cmailMsgLoaded) {
8679 flipView = WhiteOnMove(currentMove);
8680 if (moveType == GameUnfinished) flipView = !flipView;
8681 if (appData.debugMode)
8682 fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
8686 case (ChessMove) 0: /* end of file */
8687 if (appData.debugMode)
8688 fprintf(debugFP, "Parser hit end of file\n");
8689 switch (MateTest(boards[currentMove], PosFlags(currentMove),
8690 EP_UNKNOWN, castlingRights[currentMove]) ) {
8696 if (WhiteOnMove(currentMove)) {
8697 GameEnds(BlackWins, "Black mates", GE_FILE);
8699 GameEnds(WhiteWins, "White mates", GE_FILE);
8703 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
8710 if (lastLoadGameStart == GNUChessGame) {
8711 /* GNUChessGames have numbers, but they aren't move numbers */
8712 if (appData.debugMode)
8713 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
8714 yy_text, (int) moveType);
8715 return LoadGameOneMove((ChessMove)0); /* tail recursion */
8717 /* else fall thru */
8722 /* Reached start of next game in file */
8723 if (appData.debugMode)
8724 fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
8725 switch (MateTest(boards[currentMove], PosFlags(currentMove),
8726 EP_UNKNOWN, castlingRights[currentMove]) ) {
8732 if (WhiteOnMove(currentMove)) {
8733 GameEnds(BlackWins, "Black mates", GE_FILE);
8735 GameEnds(WhiteWins, "White mates", GE_FILE);
8739 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
8745 case PositionDiagram: /* should not happen; ignore */
8746 case ElapsedTime: /* ignore */
8747 case NAG: /* ignore */
8748 if (appData.debugMode)
8749 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
8750 yy_text, (int) moveType);
8751 return LoadGameOneMove((ChessMove)0); /* tail recursion */
8754 if (appData.testLegality) {
8755 if (appData.debugMode)
8756 fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
8757 sprintf(move, _("Illegal move: %d.%s%s"),
8758 (forwardMostMove / 2) + 1,
8759 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8760 DisplayError(move, 0);
8763 if (appData.debugMode)
8764 fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
8765 yy_text, currentMoveString);
8766 fromX = currentMoveString[0] - AAA;
8767 fromY = currentMoveString[1] - ONE;
8768 toX = currentMoveString[2] - AAA;
8769 toY = currentMoveString[3] - ONE;
8770 promoChar = currentMoveString[4];
8775 if (appData.debugMode)
8776 fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
8777 sprintf(move, _("Ambiguous move: %d.%s%s"),
8778 (forwardMostMove / 2) + 1,
8779 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8780 DisplayError(move, 0);
8785 case ImpossibleMove:
8786 if (appData.debugMode)
8787 fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
8788 sprintf(move, _("Illegal move: %d.%s%s"),
8789 (forwardMostMove / 2) + 1,
8790 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8791 DisplayError(move, 0);
8797 if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
8798 DrawPosition(FALSE, boards[currentMove]);
8799 DisplayBothClocks();
8800 if (!appData.matchMode) // [HGM] PV info: routine tests if empty
8801 DisplayComment(currentMove - 1, commentList[currentMove]);
8803 (void) StopLoadGameTimer();
8805 cmailOldMove = forwardMostMove;
8808 /* currentMoveString is set as a side-effect of yylex */
8809 strcat(currentMoveString, "\n");
8810 strcpy(moveList[forwardMostMove], currentMoveString);
8812 thinkOutput[0] = NULLCHAR;
8813 MakeMove(fromX, fromY, toX, toY, promoChar);
8814 currentMove = forwardMostMove;
8819 /* Load the nth game from the given file */
8821 LoadGameFromFile(filename, n, title, useList)
8825 /*Boolean*/ int useList;
8830 if (strcmp(filename, "-") == 0) {
8834 f = fopen(filename, "rb");
8836 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
8837 DisplayError(buf, errno);
8841 if (fseek(f, 0, 0) == -1) {
8842 /* f is not seekable; probably a pipe */
8845 if (useList && n == 0) {
8846 int error = GameListBuild(f);
8848 DisplayError(_("Cannot build game list"), error);
8849 } else if (!ListEmpty(&gameList) &&
8850 ((ListGame *) gameList.tailPred)->number > 1) {
8851 GameListPopUp(f, title);
8858 return LoadGame(f, n, title, FALSE);
8863 MakeRegisteredMove()
8865 int fromX, fromY, toX, toY;
8867 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
8868 switch (cmailMoveType[lastLoadGameNumber - 1]) {
8871 if (appData.debugMode)
8872 fprintf(debugFP, "Restoring %s for game %d\n",
8873 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
8875 thinkOutput[0] = NULLCHAR;
8876 strcpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1]);
8877 fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
8878 fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
8879 toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
8880 toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
8881 promoChar = cmailMove[lastLoadGameNumber - 1][4];
8882 MakeMove(fromX, fromY, toX, toY, promoChar);
8883 ShowMove(fromX, fromY, toX, toY);
8885 switch (MateTest(boards[currentMove], PosFlags(currentMove),
8886 EP_UNKNOWN, castlingRights[currentMove]) ) {
8893 if (WhiteOnMove(currentMove)) {
8894 GameEnds(BlackWins, "Black mates", GE_PLAYER);
8896 GameEnds(WhiteWins, "White mates", GE_PLAYER);
8901 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
8908 if (WhiteOnMove(currentMove)) {
8909 GameEnds(BlackWins, "White resigns", GE_PLAYER);
8911 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
8916 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
8927 /* Wrapper around LoadGame for use when a Cmail message is loaded */
8929 CmailLoadGame(f, gameNumber, title, useList)
8937 if (gameNumber > nCmailGames) {
8938 DisplayError(_("No more games in this message"), 0);
8941 if (f == lastLoadGameFP) {
8942 int offset = gameNumber - lastLoadGameNumber;
8944 cmailMsg[0] = NULLCHAR;
8945 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
8946 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
8947 nCmailMovesRegistered--;
8949 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
8950 if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
8951 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
8954 if (! RegisterMove()) return FALSE;
8958 retVal = LoadGame(f, gameNumber, title, useList);
8960 /* Make move registered during previous look at this game, if any */
8961 MakeRegisteredMove();
8963 if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
8964 commentList[currentMove]
8965 = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
8966 DisplayComment(currentMove - 1, commentList[currentMove]);
8972 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
8977 int gameNumber = lastLoadGameNumber + offset;
8978 if (lastLoadGameFP == NULL) {
8979 DisplayError(_("No game has been loaded yet"), 0);
8982 if (gameNumber <= 0) {
8983 DisplayError(_("Can't back up any further"), 0);
8986 if (cmailMsgLoaded) {
8987 return CmailLoadGame(lastLoadGameFP, gameNumber,
8988 lastLoadGameTitle, lastLoadGameUseList);
8990 return LoadGame(lastLoadGameFP, gameNumber,
8991 lastLoadGameTitle, lastLoadGameUseList);
8997 /* Load the nth game from open file f */
8999 LoadGame(f, gameNumber, title, useList)
9007 int gn = gameNumber;
9008 ListGame *lg = NULL;
9011 GameMode oldGameMode;
9012 VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
9014 if (appData.debugMode)
9015 fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
9017 if (gameMode == Training )
9018 SetTrainingModeOff();
9020 oldGameMode = gameMode;
9021 if (gameMode != BeginningOfGame) {
9026 if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
9027 fclose(lastLoadGameFP);
9031 lg = (ListGame *) ListElem(&gameList, gameNumber-1);
9034 fseek(f, lg->offset, 0);
9035 GameListHighlight(gameNumber);
9039 DisplayError(_("Game number out of range"), 0);
9044 if (fseek(f, 0, 0) == -1) {
9045 if (f == lastLoadGameFP ?
9046 gameNumber == lastLoadGameNumber + 1 :
9050 DisplayError(_("Can't seek on game file"), 0);
9056 lastLoadGameNumber = gameNumber;
9057 strcpy(lastLoadGameTitle, title);
9058 lastLoadGameUseList = useList;
9062 if (lg && lg->gameInfo.white && lg->gameInfo.black) {
9063 snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
9064 lg->gameInfo.black);
9066 } else if (*title != NULLCHAR) {
9067 if (gameNumber > 1) {
9068 sprintf(buf, "%s %d", title, gameNumber);
9071 DisplayTitle(title);
9075 if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
9076 gameMode = PlayFromGameFile;
9080 currentMove = forwardMostMove = backwardMostMove = 0;
9081 CopyBoard(boards[0], initialPosition);
9085 * Skip the first gn-1 games in the file.
9086 * Also skip over anything that precedes an identifiable
9087 * start of game marker, to avoid being confused by
9088 * garbage at the start of the file. Currently
9089 * recognized start of game markers are the move number "1",
9090 * the pattern "gnuchess .* game", the pattern
9091 * "^[#;%] [^ ]* game file", and a PGN tag block.
9092 * A game that starts with one of the latter two patterns
9093 * will also have a move number 1, possibly
9094 * following a position diagram.
9095 * 5-4-02: Let's try being more lenient and allowing a game to
9096 * start with an unnumbered move. Does that break anything?
9098 cm = lastLoadGameStart = (ChessMove) 0;
9100 yyboardindex = forwardMostMove;
9101 cm = (ChessMove) yylex();
9104 if (cmailMsgLoaded) {
9105 nCmailGames = CMAIL_MAX_GAMES - gn;
9108 DisplayError(_("Game not found in file"), 0);
9115 lastLoadGameStart = cm;
9119 switch (lastLoadGameStart) {
9126 gn--; /* count this game */
9127 lastLoadGameStart = cm;
9136 switch (lastLoadGameStart) {
9141 gn--; /* count this game */
9142 lastLoadGameStart = cm;
9145 lastLoadGameStart = cm; /* game counted already */
9153 yyboardindex = forwardMostMove;
9154 cm = (ChessMove) yylex();
9155 } while (cm == PGNTag || cm == Comment);
9162 if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
9163 if ( cmailResult[CMAIL_MAX_GAMES - gn - 1]
9164 != CMAIL_OLD_RESULT) {
9166 cmailResult[ CMAIL_MAX_GAMES
9167 - gn - 1] = CMAIL_OLD_RESULT;
9173 /* Only a NormalMove can be at the start of a game
9174 * without a position diagram. */
9175 if (lastLoadGameStart == (ChessMove) 0) {
9177 lastLoadGameStart = MoveNumberOne;
9186 if (appData.debugMode)
9187 fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
9189 if (cm == XBoardGame) {
9190 /* Skip any header junk before position diagram and/or move 1 */
9192 yyboardindex = forwardMostMove;
9193 cm = (ChessMove) yylex();
9195 if (cm == (ChessMove) 0 ||
9196 cm == GNUChessGame || cm == XBoardGame) {
9197 /* Empty game; pretend end-of-file and handle later */
9202 if (cm == MoveNumberOne || cm == PositionDiagram ||
9203 cm == PGNTag || cm == Comment)
9206 } else if (cm == GNUChessGame) {
9207 if (gameInfo.event != NULL) {
9208 free(gameInfo.event);
9210 gameInfo.event = StrSave(yy_text);
9213 startedFromSetupPosition = FALSE;
9214 while (cm == PGNTag) {
9215 if (appData.debugMode)
9216 fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
9217 err = ParsePGNTag(yy_text, &gameInfo);
9218 if (!err) numPGNTags++;
9220 /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
9221 if(gameInfo.variant != oldVariant) {
9222 startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
9224 oldVariant = gameInfo.variant;
9225 if (appData.debugMode)
9226 fprintf(debugFP, "New variant %d\n", (int) oldVariant);
9230 if (gameInfo.fen != NULL) {
9231 Board initial_position;
9232 startedFromSetupPosition = TRUE;
9233 if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
9235 DisplayError(_("Bad FEN position in file"), 0);
9238 CopyBoard(boards[0], initial_position);
9239 if (blackPlaysFirst) {
9240 currentMove = forwardMostMove = backwardMostMove = 1;
9241 CopyBoard(boards[1], initial_position);
9242 strcpy(moveList[0], "");
9243 strcpy(parseList[0], "");
9244 timeRemaining[0][1] = whiteTimeRemaining;
9245 timeRemaining[1][1] = blackTimeRemaining;
9246 if (commentList[0] != NULL) {
9247 commentList[1] = commentList[0];
9248 commentList[0] = NULL;
9251 currentMove = forwardMostMove = backwardMostMove = 0;
9253 /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
9255 initialRulePlies = FENrulePlies;
9256 epStatus[forwardMostMove] = FENepStatus;
9257 for( i=0; i< nrCastlingRights; i++ )
9258 initialRights[i] = castlingRights[forwardMostMove][i] = FENcastlingRights[i];
9260 yyboardindex = forwardMostMove;
9262 gameInfo.fen = NULL;
9265 yyboardindex = forwardMostMove;
9266 cm = (ChessMove) yylex();
9268 /* Handle comments interspersed among the tags */
9269 while (cm == Comment) {
9271 if (appData.debugMode)
9272 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9274 if (*p == '{' || *p == '[' || *p == '(') {
9275 p[strlen(p) - 1] = NULLCHAR;
9278 while (*p == '\n') p++;
9279 AppendComment(currentMove, p);
9280 yyboardindex = forwardMostMove;
9281 cm = (ChessMove) yylex();
9285 /* don't rely on existence of Event tag since if game was
9286 * pasted from clipboard the Event tag may not exist
9288 if (numPGNTags > 0){
9290 if (gameInfo.variant == VariantNormal) {
9291 gameInfo.variant = StringToVariant(gameInfo.event);
9294 if( appData.autoDisplayTags ) {
9295 tags = PGNTags(&gameInfo);
9296 TagsPopUp(tags, CmailMsg());
9301 /* Make something up, but don't display it now */
9306 if (cm == PositionDiagram) {
9309 Board initial_position;
9311 if (appData.debugMode)
9312 fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
9314 if (!startedFromSetupPosition) {
9316 for (i = BOARD_HEIGHT - 1; i >= 0; i--)
9317 for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
9327 initial_position[i][j++] = CharToPiece(*p);
9330 while (*p == ' ' || *p == '\t' ||
9331 *p == '\n' || *p == '\r') p++;
9333 if (strncmp(p, "black", strlen("black"))==0)
9334 blackPlaysFirst = TRUE;
9336 blackPlaysFirst = FALSE;
9337 startedFromSetupPosition = TRUE;
9339 CopyBoard(boards[0], initial_position);
9340 if (blackPlaysFirst) {
9341 currentMove = forwardMostMove = backwardMostMove = 1;
9342 CopyBoard(boards[1], initial_position);
9343 strcpy(moveList[0], "");
9344 strcpy(parseList[0], "");
9345 timeRemaining[0][1] = whiteTimeRemaining;
9346 timeRemaining[1][1] = blackTimeRemaining;
9347 if (commentList[0] != NULL) {
9348 commentList[1] = commentList[0];
9349 commentList[0] = NULL;
9352 currentMove = forwardMostMove = backwardMostMove = 0;
9355 yyboardindex = forwardMostMove;
9356 cm = (ChessMove) yylex();
9359 if (first.pr == NoProc) {
9360 StartChessProgram(&first);
9362 InitChessProgram(&first, FALSE);
9363 SendToProgram("force\n", &first);
9364 if (startedFromSetupPosition) {
9365 SendBoard(&first, forwardMostMove);
9366 if (appData.debugMode) {
9367 fprintf(debugFP, "Load Game\n");
9369 DisplayBothClocks();
9372 /* [HGM] server: flag to write setup moves in broadcast file as one */
9373 loadFlag = appData.suppressLoadMoves;
9375 while (cm == Comment) {
9377 if (appData.debugMode)
9378 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9380 if (*p == '{' || *p == '[' || *p == '(') {
9381 p[strlen(p) - 1] = NULLCHAR;
9384 while (*p == '\n') p++;
9385 AppendComment(currentMove, p);
9386 yyboardindex = forwardMostMove;
9387 cm = (ChessMove) yylex();
9390 if ((cm == (ChessMove) 0 && lastLoadGameStart != (ChessMove) 0) ||
9391 cm == WhiteWins || cm == BlackWins ||
9392 cm == GameIsDrawn || cm == GameUnfinished) {
9393 DisplayMessage("", _("No moves in game"));
9394 if (cmailMsgLoaded) {
9395 if (appData.debugMode)
9396 fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
9400 DrawPosition(FALSE, boards[currentMove]);
9401 DisplayBothClocks();
9402 gameMode = EditGame;
9409 // [HGM] PV info: routine tests if comment empty
9410 if (!matchMode && (pausing || appData.timeDelay != 0)) {
9411 DisplayComment(currentMove - 1, commentList[currentMove]);
9413 if (!matchMode && appData.timeDelay != 0)
9414 DrawPosition(FALSE, boards[currentMove]);
9416 if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
9417 programStats.ok_to_send = 1;
9420 /* if the first token after the PGN tags is a move
9421 * and not move number 1, retrieve it from the parser
9423 if (cm != MoveNumberOne)
9424 LoadGameOneMove(cm);
9426 /* load the remaining moves from the file */
9427 while (LoadGameOneMove((ChessMove)0)) {
9428 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
9429 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
9432 /* rewind to the start of the game */
9433 currentMove = backwardMostMove;
9435 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
9437 if (oldGameMode == AnalyzeFile ||
9438 oldGameMode == AnalyzeMode) {
9442 if (matchMode || appData.timeDelay == 0) {
9444 gameMode = EditGame;
9446 } else if (appData.timeDelay > 0) {
9450 if (appData.debugMode)
9451 fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
9453 loadFlag = 0; /* [HGM] true game starts */
9457 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
9459 ReloadPosition(offset)
9462 int positionNumber = lastLoadPositionNumber + offset;
9463 if (lastLoadPositionFP == NULL) {
9464 DisplayError(_("No position has been loaded yet"), 0);
9467 if (positionNumber <= 0) {
9468 DisplayError(_("Can't back up any further"), 0);
9471 return LoadPosition(lastLoadPositionFP, positionNumber,
9472 lastLoadPositionTitle);
9475 /* Load the nth position from the given file */
9477 LoadPositionFromFile(filename, n, title)
9485 if (strcmp(filename, "-") == 0) {
9486 return LoadPosition(stdin, n, "stdin");
9488 f = fopen(filename, "rb");
9490 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9491 DisplayError(buf, errno);
9494 return LoadPosition(f, n, title);
9499 /* Load the nth position from the given open file, and close it */
9501 LoadPosition(f, positionNumber, title)
9506 char *p, line[MSG_SIZ];
9507 Board initial_position;
9508 int i, j, fenMode, pn;
9510 if (gameMode == Training )
9511 SetTrainingModeOff();
9513 if (gameMode != BeginningOfGame) {
9516 if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
9517 fclose(lastLoadPositionFP);
9519 if (positionNumber == 0) positionNumber = 1;
9520 lastLoadPositionFP = f;
9521 lastLoadPositionNumber = positionNumber;
9522 strcpy(lastLoadPositionTitle, title);
9523 if (first.pr == NoProc) {
9524 StartChessProgram(&first);
9525 InitChessProgram(&first, FALSE);
9527 pn = positionNumber;
9528 if (positionNumber < 0) {
9529 /* Negative position number means to seek to that byte offset */
9530 if (fseek(f, -positionNumber, 0) == -1) {
9531 DisplayError(_("Can't seek on position file"), 0);
9536 if (fseek(f, 0, 0) == -1) {
9537 if (f == lastLoadPositionFP ?
9538 positionNumber == lastLoadPositionNumber + 1 :
9539 positionNumber == 1) {
9542 DisplayError(_("Can't seek on position file"), 0);
9547 /* See if this file is FEN or old-style xboard */
9548 if (fgets(line, MSG_SIZ, f) == NULL) {
9549 DisplayError(_("Position not found in file"), 0);
9552 // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
9553 fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
9556 if (fenMode || line[0] == '#') pn--;
9558 /* skip positions before number pn */
9559 if (fgets(line, MSG_SIZ, f) == NULL) {
9561 DisplayError(_("Position not found in file"), 0);
9564 if (fenMode || line[0] == '#') pn--;
9569 if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
9570 DisplayError(_("Bad FEN position in file"), 0);
9574 (void) fgets(line, MSG_SIZ, f);
9575 (void) fgets(line, MSG_SIZ, f);
9577 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
9578 (void) fgets(line, MSG_SIZ, f);
9579 for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
9582 initial_position[i][j++] = CharToPiece(*p);
9586 blackPlaysFirst = FALSE;
9588 (void) fgets(line, MSG_SIZ, f);
9589 if (strncmp(line, "black", strlen("black"))==0)
9590 blackPlaysFirst = TRUE;
9593 startedFromSetupPosition = TRUE;
9595 SendToProgram("force\n", &first);
9596 CopyBoard(boards[0], initial_position);
9597 if (blackPlaysFirst) {
9598 currentMove = forwardMostMove = backwardMostMove = 1;
9599 strcpy(moveList[0], "");
9600 strcpy(parseList[0], "");
9601 CopyBoard(boards[1], initial_position);
9602 DisplayMessage("", _("Black to play"));
9604 currentMove = forwardMostMove = backwardMostMove = 0;
9605 DisplayMessage("", _("White to play"));
9607 /* [HGM] copy FEN attributes as well */
9609 initialRulePlies = FENrulePlies;
9610 epStatus[forwardMostMove] = FENepStatus;
9611 for( i=0; i< nrCastlingRights; i++ )
9612 castlingRights[forwardMostMove][i] = FENcastlingRights[i];
9614 SendBoard(&first, forwardMostMove);
9615 if (appData.debugMode) {
9617 for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", castlingRights[i][j]);fprintf(debugFP,"\n");}
9618 for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
9619 fprintf(debugFP, "Load Position\n");
9622 if (positionNumber > 1) {
9623 sprintf(line, "%s %d", title, positionNumber);
9626 DisplayTitle(title);
9628 gameMode = EditGame;
9631 timeRemaining[0][1] = whiteTimeRemaining;
9632 timeRemaining[1][1] = blackTimeRemaining;
9633 DrawPosition(FALSE, boards[currentMove]);
9640 CopyPlayerNameIntoFileName(dest, src)
9643 while (*src != NULLCHAR && *src != ',') {
9648 *(*dest)++ = *src++;
9653 char *DefaultFileName(ext)
9656 static char def[MSG_SIZ];
9659 if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
9661 CopyPlayerNameIntoFileName(&p, gameInfo.white);
9663 CopyPlayerNameIntoFileName(&p, gameInfo.black);
9672 /* Save the current game to the given file */
9674 SaveGameToFile(filename, append)
9681 if (strcmp(filename, "-") == 0) {
9682 return SaveGame(stdout, 0, NULL);
9684 f = fopen(filename, append ? "a" : "w");
9686 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9687 DisplayError(buf, errno);
9690 return SaveGame(f, 0, NULL);
9699 static char buf[MSG_SIZ];
9702 p = strchr(str, ' ');
9703 if (p == NULL) return str;
9704 strncpy(buf, str, p - str);
9705 buf[p - str] = NULLCHAR;
9709 #define PGN_MAX_LINE 75
9711 #define PGN_SIDE_WHITE 0
9712 #define PGN_SIDE_BLACK 1
9715 static int FindFirstMoveOutOfBook( int side )
9719 if( backwardMostMove == 0 && ! startedFromSetupPosition) {
9720 int index = backwardMostMove;
9721 int has_book_hit = 0;
9723 if( (index % 2) != side ) {
9727 while( index < forwardMostMove ) {
9728 /* Check to see if engine is in book */
9729 int depth = pvInfoList[index].depth;
9730 int score = pvInfoList[index].score;
9736 else if( score == 0 && depth == 63 ) {
9737 in_book = 1; /* Zappa */
9739 else if( score == 2 && depth == 99 ) {
9740 in_book = 1; /* Abrok */
9743 has_book_hit += in_book;
9759 void GetOutOfBookInfo( char * buf )
9763 int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
9765 oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
9766 oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
9770 if( oob[0] >= 0 || oob[1] >= 0 ) {
9771 for( i=0; i<2; i++ ) {
9775 if( i > 0 && oob[0] >= 0 ) {
9779 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
9780 sprintf( buf+strlen(buf), "%s%.2f",
9781 pvInfoList[idx].score >= 0 ? "+" : "",
9782 pvInfoList[idx].score / 100.0 );
9788 /* Save game in PGN style and close the file */
9793 int i, offset, linelen, newblock;
9797 int movelen, numlen, blank;
9798 char move_buffer[100]; /* [AS] Buffer for move+PV info */
9800 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
9802 tm = time((time_t *) NULL);
9804 PrintPGNTags(f, &gameInfo);
9806 if (backwardMostMove > 0 || startedFromSetupPosition) {
9807 char *fen = PositionToFEN(backwardMostMove, NULL);
9808 fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
9809 fprintf(f, "\n{--------------\n");
9810 PrintPosition(f, backwardMostMove);
9811 fprintf(f, "--------------}\n");
9815 /* [AS] Out of book annotation */
9816 if( appData.saveOutOfBookInfo ) {
9819 GetOutOfBookInfo( buf );
9821 if( buf[0] != '\0' ) {
9822 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
9829 i = backwardMostMove;
9833 while (i < forwardMostMove) {
9834 /* Print comments preceding this move */
9835 if (commentList[i] != NULL) {
9836 if (linelen > 0) fprintf(f, "\n");
9837 fprintf(f, "{\n%s}\n", commentList[i]);
9842 /* Format move number */
9844 sprintf(numtext, "%d.", (i - offset)/2 + 1);
9847 sprintf(numtext, "%d...", (i - offset)/2 + 1);
9849 numtext[0] = NULLCHAR;
9852 numlen = strlen(numtext);
9855 /* Print move number */
9856 blank = linelen > 0 && numlen > 0;
9857 if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
9866 fprintf(f, "%s", numtext);
9870 strcpy(move_buffer, SavePart(parseList[i])); // [HGM] pgn: print move via buffer, so it can be edited
9871 movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
9874 blank = linelen > 0 && movelen > 0;
9875 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
9884 fprintf(f, "%s", move_buffer);
9887 /* [AS] Add PV info if present */
9888 if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
9889 /* [HGM] add time */
9890 char buf[MSG_SIZ]; int seconds = 0;
9892 if(i >= backwardMostMove) {
9894 seconds = timeRemaining[0][i] - timeRemaining[0][i+1]
9895 + GetTimeQuota(i/2) / (1000*WhitePlayer()->timeOdds);
9897 seconds = timeRemaining[1][i] - timeRemaining[1][i+1]
9898 + GetTimeQuota(i/2) / (1000*WhitePlayer()->other->timeOdds);
9900 seconds = (seconds+50)/100; // deci-seconds, rounded to nearest
9902 if( seconds <= 0) buf[0] = 0; else
9903 if( seconds < 30 ) sprintf(buf, " %3.1f%c", seconds/10., 0); else {
9904 seconds = (seconds + 4)/10; // round to full seconds
9905 if( seconds < 60 ) sprintf(buf, " %d%c", seconds, 0); else
9906 sprintf(buf, " %d:%02d%c", seconds/60, seconds%60, 0);
9909 sprintf( move_buffer, "{%s%.2f/%d%s}",
9910 pvInfoList[i].score >= 0 ? "+" : "",
9911 pvInfoList[i].score / 100.0,
9912 pvInfoList[i].depth,
9915 movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
9917 /* Print score/depth */
9918 blank = linelen > 0 && movelen > 0;
9919 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
9928 fprintf(f, "%s", move_buffer);
9935 /* Start a new line */
9936 if (linelen > 0) fprintf(f, "\n");
9938 /* Print comments after last move */
9939 if (commentList[i] != NULL) {
9940 fprintf(f, "{\n%s}\n", commentList[i]);
9944 if (gameInfo.resultDetails != NULL &&
9945 gameInfo.resultDetails[0] != NULLCHAR) {
9946 fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
9947 PGNResult(gameInfo.result));
9949 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
9953 lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
9957 /* Save game in old style and close the file */
9965 tm = time((time_t *) NULL);
9967 fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
9970 if (backwardMostMove > 0 || startedFromSetupPosition) {
9971 fprintf(f, "\n[--------------\n");
9972 PrintPosition(f, backwardMostMove);
9973 fprintf(f, "--------------]\n");
9978 i = backwardMostMove;
9979 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
9981 while (i < forwardMostMove) {
9982 if (commentList[i] != NULL) {
9983 fprintf(f, "[%s]\n", commentList[i]);
9987 fprintf(f, "%d. ... %s\n", (i - offset)/2 + 1, parseList[i]);
9990 fprintf(f, "%d. %s ", (i - offset)/2 + 1, parseList[i]);
9992 if (commentList[i] != NULL) {
9996 if (i >= forwardMostMove) {
10000 fprintf(f, "%s\n", parseList[i]);
10005 if (commentList[i] != NULL) {
10006 fprintf(f, "[%s]\n", commentList[i]);
10009 /* This isn't really the old style, but it's close enough */
10010 if (gameInfo.resultDetails != NULL &&
10011 gameInfo.resultDetails[0] != NULLCHAR) {
10012 fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
10013 gameInfo.resultDetails);
10015 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
10022 /* Save the current game to open file f and close the file */
10024 SaveGame(f, dummy, dummy2)
10029 if (gameMode == EditPosition) EditPositionDone();
10030 lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
10031 if (appData.oldSaveStyle)
10032 return SaveGameOldStyle(f);
10034 return SaveGamePGN(f);
10037 /* Save the current position to the given file */
10039 SavePositionToFile(filename)
10045 if (strcmp(filename, "-") == 0) {
10046 return SavePosition(stdout, 0, NULL);
10048 f = fopen(filename, "a");
10050 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10051 DisplayError(buf, errno);
10054 SavePosition(f, 0, NULL);
10060 /* Save the current position to the given open file and close the file */
10062 SavePosition(f, dummy, dummy2)
10070 if (appData.oldSaveStyle) {
10071 tm = time((time_t *) NULL);
10073 fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
10075 fprintf(f, "[--------------\n");
10076 PrintPosition(f, currentMove);
10077 fprintf(f, "--------------]\n");
10079 fen = PositionToFEN(currentMove, NULL);
10080 fprintf(f, "%s\n", fen);
10088 ReloadCmailMsgEvent(unregister)
10092 static char *inFilename = NULL;
10093 static char *outFilename;
10095 struct stat inbuf, outbuf;
10098 /* Any registered moves are unregistered if unregister is set, */
10099 /* i.e. invoked by the signal handler */
10101 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
10102 cmailMoveRegistered[i] = FALSE;
10103 if (cmailCommentList[i] != NULL) {
10104 free(cmailCommentList[i]);
10105 cmailCommentList[i] = NULL;
10108 nCmailMovesRegistered = 0;
10111 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
10112 cmailResult[i] = CMAIL_NOT_RESULT;
10116 if (inFilename == NULL) {
10117 /* Because the filenames are static they only get malloced once */
10118 /* and they never get freed */
10119 inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
10120 sprintf(inFilename, "%s.game.in", appData.cmailGameName);
10122 outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
10123 sprintf(outFilename, "%s.out", appData.cmailGameName);
10126 status = stat(outFilename, &outbuf);
10128 cmailMailedMove = FALSE;
10130 status = stat(inFilename, &inbuf);
10131 cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
10134 /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
10135 counts the games, notes how each one terminated, etc.
10137 It would be nice to remove this kludge and instead gather all
10138 the information while building the game list. (And to keep it
10139 in the game list nodes instead of having a bunch of fixed-size
10140 parallel arrays.) Note this will require getting each game's
10141 termination from the PGN tags, as the game list builder does
10142 not process the game moves. --mann
10144 cmailMsgLoaded = TRUE;
10145 LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
10147 /* Load first game in the file or popup game menu */
10148 LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
10150 #endif /* !WIN32 */
10158 char string[MSG_SIZ];
10160 if ( cmailMailedMove
10161 || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
10162 return TRUE; /* Allow free viewing */
10165 /* Unregister move to ensure that we don't leave RegisterMove */
10166 /* with the move registered when the conditions for registering no */
10168 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
10169 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
10170 nCmailMovesRegistered --;
10172 if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
10174 free(cmailCommentList[lastLoadGameNumber - 1]);
10175 cmailCommentList[lastLoadGameNumber - 1] = NULL;
10179 if (cmailOldMove == -1) {
10180 DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
10184 if (currentMove > cmailOldMove + 1) {
10185 DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
10189 if (currentMove < cmailOldMove) {
10190 DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
10194 if (forwardMostMove > currentMove) {
10195 /* Silently truncate extra moves */
10199 if ( (currentMove == cmailOldMove + 1)
10200 || ( (currentMove == cmailOldMove)
10201 && ( (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
10202 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
10203 if (gameInfo.result != GameUnfinished) {
10204 cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
10207 if (commentList[currentMove] != NULL) {
10208 cmailCommentList[lastLoadGameNumber - 1]
10209 = StrSave(commentList[currentMove]);
10211 strcpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1]);
10213 if (appData.debugMode)
10214 fprintf(debugFP, "Saving %s for game %d\n",
10215 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
10218 "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
10220 f = fopen(string, "w");
10221 if (appData.oldSaveStyle) {
10222 SaveGameOldStyle(f); /* also closes the file */
10224 sprintf(string, "%s.pos.out", appData.cmailGameName);
10225 f = fopen(string, "w");
10226 SavePosition(f, 0, NULL); /* also closes the file */
10228 fprintf(f, "{--------------\n");
10229 PrintPosition(f, currentMove);
10230 fprintf(f, "--------------}\n\n");
10232 SaveGame(f, 0, NULL); /* also closes the file*/
10235 cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
10236 nCmailMovesRegistered ++;
10237 } else if (nCmailGames == 1) {
10238 DisplayError(_("You have not made a move yet"), 0);
10249 static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
10250 FILE *commandOutput;
10251 char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
10252 int nBytes = 0; /* Suppress warnings on uninitialized variables */
10258 if (! cmailMsgLoaded) {
10259 DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
10263 if (nCmailGames == nCmailResults) {
10264 DisplayError(_("No unfinished games"), 0);
10268 #if CMAIL_PROHIBIT_REMAIL
10269 if (cmailMailedMove) {
10270 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);
10271 DisplayError(msg, 0);
10276 if (! (cmailMailedMove || RegisterMove())) return;
10278 if ( cmailMailedMove
10279 || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
10280 sprintf(string, partCommandString,
10281 appData.debugMode ? " -v" : "", appData.cmailGameName);
10282 commandOutput = popen(string, "r");
10284 if (commandOutput == NULL) {
10285 DisplayError(_("Failed to invoke cmail"), 0);
10287 for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
10288 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
10290 if (nBuffers > 1) {
10291 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
10292 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
10293 nBytes = MSG_SIZ - 1;
10295 (void) memcpy(msg, buffer, nBytes);
10297 *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
10299 if(StrStr(msg, "Mailed cmail message to ") != NULL) {
10300 cmailMailedMove = TRUE; /* Prevent >1 moves */
10303 for (i = 0; i < nCmailGames; i ++) {
10304 if (cmailResult[i] == CMAIL_NOT_RESULT) {
10309 && ( (arcDir = (char *) getenv("CMAIL_ARCDIR"))
10311 sprintf(buffer, "%s/%s.%s.archive",
10313 appData.cmailGameName,
10315 LoadGameFromFile(buffer, 1, buffer, FALSE);
10316 cmailMsgLoaded = FALSE;
10320 DisplayInformation(msg);
10321 pclose(commandOutput);
10324 if ((*cmailMsg) != '\0') {
10325 DisplayInformation(cmailMsg);
10330 #endif /* !WIN32 */
10339 int prependComma = 0;
10341 char string[MSG_SIZ]; /* Space for game-list */
10344 if (!cmailMsgLoaded) return "";
10346 if (cmailMailedMove) {
10347 sprintf(cmailMsg, _("Waiting for reply from opponent\n"));
10349 /* Create a list of games left */
10350 sprintf(string, "[");
10351 for (i = 0; i < nCmailGames; i ++) {
10352 if (! ( cmailMoveRegistered[i]
10353 || (cmailResult[i] == CMAIL_OLD_RESULT))) {
10354 if (prependComma) {
10355 sprintf(number, ",%d", i + 1);
10357 sprintf(number, "%d", i + 1);
10361 strcat(string, number);
10364 strcat(string, "]");
10366 if (nCmailMovesRegistered + nCmailResults == 0) {
10367 switch (nCmailGames) {
10370 _("Still need to make move for game\n"));
10375 _("Still need to make moves for both games\n"));
10380 _("Still need to make moves for all %d games\n"),
10385 switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
10388 _("Still need to make a move for game %s\n"),
10393 if (nCmailResults == nCmailGames) {
10394 sprintf(cmailMsg, _("No unfinished games\n"));
10396 sprintf(cmailMsg, _("Ready to send mail\n"));
10402 _("Still need to make moves for games %s\n"),
10414 if (gameMode == Training)
10415 SetTrainingModeOff();
10418 cmailMsgLoaded = FALSE;
10419 if (appData.icsActive) {
10420 SendToICS(ics_prefix);
10421 SendToICS("refresh\n");
10431 /* Give up on clean exit */
10435 /* Keep trying for clean exit */
10439 if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
10441 if (telnetISR != NULL) {
10442 RemoveInputSource(telnetISR);
10444 if (icsPR != NoProc) {
10445 DestroyChildProcess(icsPR, TRUE);
10448 /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
10449 GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
10451 /* [HGM] crash: the above GameEnds() is a dud if another one was running */
10452 /* make sure this other one finishes before killing it! */
10453 if(endingGame) { int count = 0;
10454 if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
10455 while(endingGame && count++ < 10) DoSleep(1);
10456 if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
10459 /* Kill off chess programs */
10460 if (first.pr != NoProc) {
10463 DoSleep( appData.delayBeforeQuit );
10464 SendToProgram("quit\n", &first);
10465 DoSleep( appData.delayAfterQuit );
10466 DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
10468 if (second.pr != NoProc) {
10469 DoSleep( appData.delayBeforeQuit );
10470 SendToProgram("quit\n", &second);
10471 DoSleep( appData.delayAfterQuit );
10472 DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
10474 if (first.isr != NULL) {
10475 RemoveInputSource(first.isr);
10477 if (second.isr != NULL) {
10478 RemoveInputSource(second.isr);
10481 ShutDownFrontEnd();
10488 if (appData.debugMode)
10489 fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
10493 if (gameMode == MachinePlaysWhite ||
10494 gameMode == MachinePlaysBlack) {
10497 DisplayBothClocks();
10499 if (gameMode == PlayFromGameFile) {
10500 if (appData.timeDelay >= 0)
10501 AutoPlayGameLoop();
10502 } else if (gameMode == IcsExamining && pauseExamInvalid) {
10503 Reset(FALSE, TRUE);
10504 SendToICS(ics_prefix);
10505 SendToICS("refresh\n");
10506 } else if (currentMove < forwardMostMove) {
10507 ForwardInner(forwardMostMove);
10509 pauseExamInvalid = FALSE;
10511 switch (gameMode) {
10515 pauseExamForwardMostMove = forwardMostMove;
10516 pauseExamInvalid = FALSE;
10519 case IcsPlayingWhite:
10520 case IcsPlayingBlack:
10524 case PlayFromGameFile:
10525 (void) StopLoadGameTimer();
10529 case BeginningOfGame:
10530 if (appData.icsActive) return;
10531 /* else fall through */
10532 case MachinePlaysWhite:
10533 case MachinePlaysBlack:
10534 case TwoMachinesPlay:
10535 if (forwardMostMove == 0)
10536 return; /* don't pause if no one has moved */
10537 if ((gameMode == MachinePlaysWhite &&
10538 !WhiteOnMove(forwardMostMove)) ||
10539 (gameMode == MachinePlaysBlack &&
10540 WhiteOnMove(forwardMostMove))) {
10553 char title[MSG_SIZ];
10555 if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
10556 strcpy(title, _("Edit comment"));
10558 sprintf(title, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
10559 WhiteOnMove(currentMove - 1) ? " " : ".. ",
10560 parseList[currentMove - 1]);
10563 EditCommentPopUp(currentMove, title, commentList[currentMove]);
10570 char *tags = PGNTags(&gameInfo);
10571 EditTagsPopUp(tags);
10578 if (appData.noChessProgram || gameMode == AnalyzeMode)
10581 if (gameMode != AnalyzeFile) {
10582 if (!appData.icsEngineAnalyze) {
10584 if (gameMode != EditGame) return;
10586 ResurrectChessProgram();
10587 SendToProgram("analyze\n", &first);
10588 first.analyzing = TRUE;
10589 /*first.maybeThinking = TRUE;*/
10590 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
10591 EngineOutputPopUp();
10593 if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
10598 StartAnalysisClock();
10599 GetTimeMark(&lastNodeCountTime);
10606 if (appData.noChessProgram || gameMode == AnalyzeFile)
10609 if (gameMode != AnalyzeMode) {
10611 if (gameMode != EditGame) return;
10612 ResurrectChessProgram();
10613 SendToProgram("analyze\n", &first);
10614 first.analyzing = TRUE;
10615 /*first.maybeThinking = TRUE;*/
10616 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
10617 EngineOutputPopUp();
10619 gameMode = AnalyzeFile;
10624 StartAnalysisClock();
10625 GetTimeMark(&lastNodeCountTime);
10630 MachineWhiteEvent()
10633 char *bookHit = NULL;
10635 if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
10639 if (gameMode == PlayFromGameFile ||
10640 gameMode == TwoMachinesPlay ||
10641 gameMode == Training ||
10642 gameMode == AnalyzeMode ||
10643 gameMode == EndOfGame)
10646 if (gameMode == EditPosition)
10647 EditPositionDone();
10649 if (!WhiteOnMove(currentMove)) {
10650 DisplayError(_("It is not White's turn"), 0);
10654 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
10657 if (gameMode == EditGame || gameMode == AnalyzeMode ||
10658 gameMode == AnalyzeFile)
10661 ResurrectChessProgram(); /* in case it isn't running */
10662 if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
10663 gameMode = MachinePlaysWhite;
10666 gameMode = MachinePlaysWhite;
10670 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10672 if (first.sendName) {
10673 sprintf(buf, "name %s\n", gameInfo.black);
10674 SendToProgram(buf, &first);
10676 if (first.sendTime) {
10677 if (first.useColors) {
10678 SendToProgram("black\n", &first); /*gnu kludge*/
10680 SendTimeRemaining(&first, TRUE);
10682 if (first.useColors) {
10683 SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
10685 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
10686 SetMachineThinkingEnables();
10687 first.maybeThinking = TRUE;
10691 if (appData.autoFlipView && !flipView) {
10692 flipView = !flipView;
10693 DrawPosition(FALSE, NULL);
10694 DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;
10697 if(bookHit) { // [HGM] book: simulate book reply
10698 static char bookMove[MSG_SIZ]; // a bit generous?
10700 programStats.nodes = programStats.depth = programStats.time =
10701 programStats.score = programStats.got_only_move = 0;
10702 sprintf(programStats.movelist, "%s (xbook)", bookHit);
10704 strcpy(bookMove, "move ");
10705 strcat(bookMove, bookHit);
10706 HandleMachineMove(bookMove, &first);
10711 MachineBlackEvent()
10714 char *bookHit = NULL;
10716 if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
10720 if (gameMode == PlayFromGameFile ||
10721 gameMode == TwoMachinesPlay ||
10722 gameMode == Training ||
10723 gameMode == AnalyzeMode ||
10724 gameMode == EndOfGame)
10727 if (gameMode == EditPosition)
10728 EditPositionDone();
10730 if (WhiteOnMove(currentMove)) {
10731 DisplayError(_("It is not Black's turn"), 0);
10735 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
10738 if (gameMode == EditGame || gameMode == AnalyzeMode ||
10739 gameMode == AnalyzeFile)
10742 ResurrectChessProgram(); /* in case it isn't running */
10743 gameMode = MachinePlaysBlack;
10747 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10749 if (first.sendName) {
10750 sprintf(buf, "name %s\n", gameInfo.white);
10751 SendToProgram(buf, &first);
10753 if (first.sendTime) {
10754 if (first.useColors) {
10755 SendToProgram("white\n", &first); /*gnu kludge*/
10757 SendTimeRemaining(&first, FALSE);
10759 if (first.useColors) {
10760 SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
10762 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
10763 SetMachineThinkingEnables();
10764 first.maybeThinking = TRUE;
10767 if (appData.autoFlipView && flipView) {
10768 flipView = !flipView;
10769 DrawPosition(FALSE, NULL);
10770 DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;
10772 if(bookHit) { // [HGM] book: simulate book reply
10773 static char bookMove[MSG_SIZ]; // a bit generous?
10775 programStats.nodes = programStats.depth = programStats.time =
10776 programStats.score = programStats.got_only_move = 0;
10777 sprintf(programStats.movelist, "%s (xbook)", bookHit);
10779 strcpy(bookMove, "move ");
10780 strcat(bookMove, bookHit);
10781 HandleMachineMove(bookMove, &first);
10787 DisplayTwoMachinesTitle()
10790 if (appData.matchGames > 0) {
10791 if (first.twoMachinesColor[0] == 'w') {
10792 sprintf(buf, "%s vs. %s (%d-%d-%d)",
10793 gameInfo.white, gameInfo.black,
10794 first.matchWins, second.matchWins,
10795 matchGame - 1 - (first.matchWins + second.matchWins));
10797 sprintf(buf, "%s vs. %s (%d-%d-%d)",
10798 gameInfo.white, gameInfo.black,
10799 second.matchWins, first.matchWins,
10800 matchGame - 1 - (first.matchWins + second.matchWins));
10803 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10809 TwoMachinesEvent P((void))
10813 ChessProgramState *onmove;
10814 char *bookHit = NULL;
10816 if (appData.noChessProgram) return;
10818 switch (gameMode) {
10819 case TwoMachinesPlay:
10821 case MachinePlaysWhite:
10822 case MachinePlaysBlack:
10823 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
10824 DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
10828 case BeginningOfGame:
10829 case PlayFromGameFile:
10832 if (gameMode != EditGame) return;
10835 EditPositionDone();
10846 forwardMostMove = currentMove;
10847 ResurrectChessProgram(); /* in case first program isn't running */
10849 if (second.pr == NULL) {
10850 StartChessProgram(&second);
10851 if (second.protocolVersion == 1) {
10852 TwoMachinesEventIfReady();
10854 /* kludge: allow timeout for initial "feature" command */
10856 DisplayMessage("", _("Starting second chess program"));
10857 ScheduleDelayedEvent(TwoMachinesEventIfReady, FEATURE_TIMEOUT);
10861 DisplayMessage("", "");
10862 InitChessProgram(&second, FALSE);
10863 SendToProgram("force\n", &second);
10864 if (startedFromSetupPosition) {
10865 SendBoard(&second, backwardMostMove);
10866 if (appData.debugMode) {
10867 fprintf(debugFP, "Two Machines\n");
10870 for (i = backwardMostMove; i < forwardMostMove; i++) {
10871 SendMoveToProgram(i, &second);
10874 gameMode = TwoMachinesPlay;
10878 DisplayTwoMachinesTitle();
10880 if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
10886 SendToProgram(first.computerString, &first);
10887 if (first.sendName) {
10888 sprintf(buf, "name %s\n", second.tidy);
10889 SendToProgram(buf, &first);
10891 SendToProgram(second.computerString, &second);
10892 if (second.sendName) {
10893 sprintf(buf, "name %s\n", first.tidy);
10894 SendToProgram(buf, &second);
10898 if (!first.sendTime || !second.sendTime) {
10899 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10900 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10902 if (onmove->sendTime) {
10903 if (onmove->useColors) {
10904 SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
10906 SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
10908 if (onmove->useColors) {
10909 SendToProgram(onmove->twoMachinesColor, onmove);
10911 bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
10912 // SendToProgram("go\n", onmove);
10913 onmove->maybeThinking = TRUE;
10914 SetMachineThinkingEnables();
10918 if(bookHit) { // [HGM] book: simulate book reply
10919 static char bookMove[MSG_SIZ]; // a bit generous?
10921 programStats.nodes = programStats.depth = programStats.time =
10922 programStats.score = programStats.got_only_move = 0;
10923 sprintf(programStats.movelist, "%s (xbook)", bookHit);
10925 strcpy(bookMove, "move ");
10926 strcat(bookMove, bookHit);
10927 HandleMachineMove(bookMove, &first);
10934 if (gameMode == Training) {
10935 SetTrainingModeOff();
10936 gameMode = PlayFromGameFile;
10937 DisplayMessage("", _("Training mode off"));
10939 gameMode = Training;
10940 animateTraining = appData.animate;
10942 /* make sure we are not already at the end of the game */
10943 if (currentMove < forwardMostMove) {
10944 SetTrainingModeOn();
10945 DisplayMessage("", _("Training mode on"));
10947 gameMode = PlayFromGameFile;
10948 DisplayError(_("Already at end of game"), 0);
10957 if (!appData.icsActive) return;
10958 switch (gameMode) {
10959 case IcsPlayingWhite:
10960 case IcsPlayingBlack:
10963 case BeginningOfGame:
10971 EditPositionDone();
10984 gameMode = IcsIdle;
10995 switch (gameMode) {
10997 SetTrainingModeOff();
10999 case MachinePlaysWhite:
11000 case MachinePlaysBlack:
11001 case BeginningOfGame:
11002 SendToProgram("force\n", &first);
11003 SetUserThinkingEnables();
11005 case PlayFromGameFile:
11006 (void) StopLoadGameTimer();
11007 if (gameFileFP != NULL) {
11012 EditPositionDone();
11017 SendToProgram("force\n", &first);
11019 case TwoMachinesPlay:
11020 GameEnds((ChessMove) 0, NULL, GE_PLAYER);
11021 ResurrectChessProgram();
11022 SetUserThinkingEnables();
11025 ResurrectChessProgram();
11027 case IcsPlayingBlack:
11028 case IcsPlayingWhite:
11029 DisplayError(_("Warning: You are still playing a game"), 0);
11032 DisplayError(_("Warning: You are still observing a game"), 0);
11035 DisplayError(_("Warning: You are still examining a game"), 0);
11046 first.offeredDraw = second.offeredDraw = 0;
11048 if (gameMode == PlayFromGameFile) {
11049 whiteTimeRemaining = timeRemaining[0][currentMove];
11050 blackTimeRemaining = timeRemaining[1][currentMove];
11054 if (gameMode == MachinePlaysWhite ||
11055 gameMode == MachinePlaysBlack ||
11056 gameMode == TwoMachinesPlay ||
11057 gameMode == EndOfGame) {
11058 i = forwardMostMove;
11059 while (i > currentMove) {
11060 SendToProgram("undo\n", &first);
11063 whiteTimeRemaining = timeRemaining[0][currentMove];
11064 blackTimeRemaining = timeRemaining[1][currentMove];
11065 DisplayBothClocks();
11066 if (whiteFlag || blackFlag) {
11067 whiteFlag = blackFlag = 0;
11072 gameMode = EditGame;
11079 EditPositionEvent()
11081 if (gameMode == EditPosition) {
11087 if (gameMode != EditGame) return;
11089 gameMode = EditPosition;
11092 if (currentMove > 0)
11093 CopyBoard(boards[0], boards[currentMove]);
11095 blackPlaysFirst = !WhiteOnMove(currentMove);
11097 currentMove = forwardMostMove = backwardMostMove = 0;
11098 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11105 /* [DM] icsEngineAnalyze - possible call from other functions */
11106 if (appData.icsEngineAnalyze) {
11107 appData.icsEngineAnalyze = FALSE;
11109 DisplayMessage("",_("Close ICS engine analyze..."));
11111 if (first.analysisSupport && first.analyzing) {
11112 SendToProgram("exit\n", &first);
11113 first.analyzing = FALSE;
11115 thinkOutput[0] = NULLCHAR;
11121 int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
11123 startedFromSetupPosition = TRUE;
11124 InitChessProgram(&first, FALSE);
11125 castlingRights[0][2] = castlingRights[0][5] = BOARD_WIDTH>>1;
11126 if(boards[0][0][BOARD_WIDTH>>1] == king) {
11127 castlingRights[0][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : -1;
11128 castlingRights[0][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : -1;
11129 } else castlingRights[0][2] = -1;
11130 if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
11131 castlingRights[0][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : -1;
11132 castlingRights[0][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : -1;
11133 } else castlingRights[0][5] = -1;
11134 SendToProgram("force\n", &first);
11135 if (blackPlaysFirst) {
11136 strcpy(moveList[0], "");
11137 strcpy(parseList[0], "");
11138 currentMove = forwardMostMove = backwardMostMove = 1;
11139 CopyBoard(boards[1], boards[0]);
11140 /* [HGM] copy rights as well, as this code is also used after pasting a FEN */
11142 epStatus[1] = epStatus[0];
11143 for(i=0; i<nrCastlingRights; i++) castlingRights[1][i] = castlingRights[0][i];
11146 currentMove = forwardMostMove = backwardMostMove = 0;
11148 SendBoard(&first, forwardMostMove);
11149 if (appData.debugMode) {
11150 fprintf(debugFP, "EditPosDone\n");
11153 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11154 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11155 gameMode = EditGame;
11157 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11158 ClearHighlights(); /* [AS] */
11161 /* Pause for `ms' milliseconds */
11162 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
11172 } while (SubtractTimeMarks(&m2, &m1) < ms);
11175 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
11177 SendMultiLineToICS(buf)
11180 char temp[MSG_SIZ+1], *p;
11187 strncpy(temp, buf, len);
11192 if (*p == '\n' || *p == '\r')
11197 strcat(temp, "\n");
11199 SendToPlayer(temp, strlen(temp));
11203 SetWhiteToPlayEvent()
11205 if (gameMode == EditPosition) {
11206 blackPlaysFirst = FALSE;
11207 DisplayBothClocks(); /* works because currentMove is 0 */
11208 } else if (gameMode == IcsExamining) {
11209 SendToICS(ics_prefix);
11210 SendToICS("tomove white\n");
11215 SetBlackToPlayEvent()
11217 if (gameMode == EditPosition) {
11218 blackPlaysFirst = TRUE;
11219 currentMove = 1; /* kludge */
11220 DisplayBothClocks();
11222 } else if (gameMode == IcsExamining) {
11223 SendToICS(ics_prefix);
11224 SendToICS("tomove black\n");
11229 EditPositionMenuEvent(selection, x, y)
11230 ChessSquare selection;
11234 ChessSquare piece = boards[0][y][x];
11236 if (gameMode != EditPosition && gameMode != IcsExamining) return;
11238 switch (selection) {
11240 if (gameMode == IcsExamining && ics_type == ICS_FICS) {
11241 SendToICS(ics_prefix);
11242 SendToICS("bsetup clear\n");
11243 } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
11244 SendToICS(ics_prefix);
11245 SendToICS("clearboard\n");
11247 for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
11248 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
11249 for (y = 0; y < BOARD_HEIGHT; y++) {
11250 if (gameMode == IcsExamining) {
11251 if (boards[currentMove][y][x] != EmptySquare) {
11252 sprintf(buf, "%sx@%c%c\n", ics_prefix,
11257 boards[0][y][x] = p;
11262 if (gameMode == EditPosition) {
11263 DrawPosition(FALSE, boards[0]);
11268 SetWhiteToPlayEvent();
11272 SetBlackToPlayEvent();
11276 if (gameMode == IcsExamining) {
11277 sprintf(buf, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
11280 boards[0][y][x] = EmptySquare;
11281 DrawPosition(FALSE, boards[0]);
11286 if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
11287 piece >= (int)BlackPawn && piece < (int)BlackMan ) {
11288 selection = (ChessSquare) (PROMOTED piece);
11289 } else if(piece == EmptySquare) selection = WhiteSilver;
11290 else selection = (ChessSquare)((int)piece - 1);
11294 if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
11295 piece > (int)BlackMan && piece <= (int)BlackKing ) {
11296 selection = (ChessSquare) (DEMOTED piece);
11297 } else if(piece == EmptySquare) selection = BlackSilver;
11298 else selection = (ChessSquare)((int)piece + 1);
11303 if(gameInfo.variant == VariantShatranj ||
11304 gameInfo.variant == VariantXiangqi ||
11305 gameInfo.variant == VariantCourier )
11306 selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
11311 if(gameInfo.variant == VariantXiangqi)
11312 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
11313 if(gameInfo.variant == VariantKnightmate)
11314 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
11317 if (gameMode == IcsExamining) {
11318 sprintf(buf, "%s%c@%c%c\n", ics_prefix,
11319 PieceToChar(selection), AAA + x, ONE + y);
11322 boards[0][y][x] = selection;
11323 DrawPosition(FALSE, boards[0]);
11331 DropMenuEvent(selection, x, y)
11332 ChessSquare selection;
11335 ChessMove moveType;
11337 switch (gameMode) {
11338 case IcsPlayingWhite:
11339 case MachinePlaysBlack:
11340 if (!WhiteOnMove(currentMove)) {
11341 DisplayMoveError(_("It is Black's turn"));
11344 moveType = WhiteDrop;
11346 case IcsPlayingBlack:
11347 case MachinePlaysWhite:
11348 if (WhiteOnMove(currentMove)) {
11349 DisplayMoveError(_("It is White's turn"));
11352 moveType = BlackDrop;
11355 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
11361 if (moveType == BlackDrop && selection < BlackPawn) {
11362 selection = (ChessSquare) ((int) selection
11363 + (int) BlackPawn - (int) WhitePawn);
11365 if (boards[currentMove][y][x] != EmptySquare) {
11366 DisplayMoveError(_("That square is occupied"));
11370 FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
11376 /* Accept a pending offer of any kind from opponent */
11378 if (appData.icsActive) {
11379 SendToICS(ics_prefix);
11380 SendToICS("accept\n");
11381 } else if (cmailMsgLoaded) {
11382 if (currentMove == cmailOldMove &&
11383 commentList[cmailOldMove] != NULL &&
11384 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11385 "Black offers a draw" : "White offers a draw")) {
11387 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11388 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
11390 DisplayError(_("There is no pending offer on this move"), 0);
11391 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11394 /* Not used for offers from chess program */
11401 /* Decline a pending offer of any kind from opponent */
11403 if (appData.icsActive) {
11404 SendToICS(ics_prefix);
11405 SendToICS("decline\n");
11406 } else if (cmailMsgLoaded) {
11407 if (currentMove == cmailOldMove &&
11408 commentList[cmailOldMove] != NULL &&
11409 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11410 "Black offers a draw" : "White offers a draw")) {
11412 AppendComment(cmailOldMove, "Draw declined");
11413 DisplayComment(cmailOldMove - 1, "Draw declined");
11416 DisplayError(_("There is no pending offer on this move"), 0);
11419 /* Not used for offers from chess program */
11426 /* Issue ICS rematch command */
11427 if (appData.icsActive) {
11428 SendToICS(ics_prefix);
11429 SendToICS("rematch\n");
11436 /* Call your opponent's flag (claim a win on time) */
11437 if (appData.icsActive) {
11438 SendToICS(ics_prefix);
11439 SendToICS("flag\n");
11441 switch (gameMode) {
11444 case MachinePlaysWhite:
11447 GameEnds(GameIsDrawn, "Both players ran out of time",
11450 GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
11452 DisplayError(_("Your opponent is not out of time"), 0);
11455 case MachinePlaysBlack:
11458 GameEnds(GameIsDrawn, "Both players ran out of time",
11461 GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
11463 DisplayError(_("Your opponent is not out of time"), 0);
11473 /* Offer draw or accept pending draw offer from opponent */
11475 if (appData.icsActive) {
11476 /* Note: tournament rules require draw offers to be
11477 made after you make your move but before you punch
11478 your clock. Currently ICS doesn't let you do that;
11479 instead, you immediately punch your clock after making
11480 a move, but you can offer a draw at any time. */
11482 SendToICS(ics_prefix);
11483 SendToICS("draw\n");
11484 } else if (cmailMsgLoaded) {
11485 if (currentMove == cmailOldMove &&
11486 commentList[cmailOldMove] != NULL &&
11487 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11488 "Black offers a draw" : "White offers a draw")) {
11489 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11490 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
11491 } else if (currentMove == cmailOldMove + 1) {
11492 char *offer = WhiteOnMove(cmailOldMove) ?
11493 "White offers a draw" : "Black offers a draw";
11494 AppendComment(currentMove, offer);
11495 DisplayComment(currentMove - 1, offer);
11496 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
11498 DisplayError(_("You must make your move before offering a draw"), 0);
11499 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11501 } else if (first.offeredDraw) {
11502 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
11504 if (first.sendDrawOffers) {
11505 SendToProgram("draw\n", &first);
11506 userOfferedDraw = TRUE;
11514 /* Offer Adjourn or accept pending Adjourn offer from opponent */
11516 if (appData.icsActive) {
11517 SendToICS(ics_prefix);
11518 SendToICS("adjourn\n");
11520 /* Currently GNU Chess doesn't offer or accept Adjourns */
11528 /* Offer Abort or accept pending Abort offer from opponent */
11530 if (appData.icsActive) {
11531 SendToICS(ics_prefix);
11532 SendToICS("abort\n");
11534 GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
11541 /* Resign. You can do this even if it's not your turn. */
11543 if (appData.icsActive) {
11544 SendToICS(ics_prefix);
11545 SendToICS("resign\n");
11547 switch (gameMode) {
11548 case MachinePlaysWhite:
11549 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11551 case MachinePlaysBlack:
11552 GameEnds(BlackWins, "White resigns", GE_PLAYER);
11555 if (cmailMsgLoaded) {
11557 if (WhiteOnMove(cmailOldMove)) {
11558 GameEnds(BlackWins, "White resigns", GE_PLAYER);
11560 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11562 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
11573 StopObservingEvent()
11575 /* Stop observing current games */
11576 SendToICS(ics_prefix);
11577 SendToICS("unobserve\n");
11581 StopExaminingEvent()
11583 /* Stop observing current game */
11584 SendToICS(ics_prefix);
11585 SendToICS("unexamine\n");
11589 ForwardInner(target)
11594 if (appData.debugMode)
11595 fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
11596 target, currentMove, forwardMostMove);
11598 if (gameMode == EditPosition)
11601 if (gameMode == PlayFromGameFile && !pausing)
11604 if (gameMode == IcsExamining && pausing)
11605 limit = pauseExamForwardMostMove;
11607 limit = forwardMostMove;
11609 if (target > limit) target = limit;
11611 if (target > 0 && moveList[target - 1][0]) {
11612 int fromX, fromY, toX, toY;
11613 toX = moveList[target - 1][2] - AAA;
11614 toY = moveList[target - 1][3] - ONE;
11615 if (moveList[target - 1][1] == '@') {
11616 if (appData.highlightLastMove) {
11617 SetHighlights(-1, -1, toX, toY);
11620 fromX = moveList[target - 1][0] - AAA;
11621 fromY = moveList[target - 1][1] - ONE;
11622 if (target == currentMove + 1) {
11623 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
11625 if (appData.highlightLastMove) {
11626 SetHighlights(fromX, fromY, toX, toY);
11630 if (gameMode == EditGame || gameMode == AnalyzeMode ||
11631 gameMode == Training || gameMode == PlayFromGameFile ||
11632 gameMode == AnalyzeFile) {
11633 while (currentMove < target) {
11634 SendMoveToProgram(currentMove++, &first);
11637 currentMove = target;
11640 if (gameMode == EditGame || gameMode == EndOfGame) {
11641 whiteTimeRemaining = timeRemaining[0][currentMove];
11642 blackTimeRemaining = timeRemaining[1][currentMove];
11644 DisplayBothClocks();
11645 DisplayMove(currentMove - 1);
11646 DrawPosition(FALSE, boards[currentMove]);
11647 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
11648 if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
11649 DisplayComment(currentMove - 1, commentList[currentMove]);
11657 if (gameMode == IcsExamining && !pausing) {
11658 SendToICS(ics_prefix);
11659 SendToICS("forward\n");
11661 ForwardInner(currentMove + 1);
11668 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11669 /* to optimze, we temporarily turn off analysis mode while we feed
11670 * the remaining moves to the engine. Otherwise we get analysis output
11673 if (first.analysisSupport) {
11674 SendToProgram("exit\nforce\n", &first);
11675 first.analyzing = FALSE;
11679 if (gameMode == IcsExamining && !pausing) {
11680 SendToICS(ics_prefix);
11681 SendToICS("forward 999999\n");
11683 ForwardInner(forwardMostMove);
11686 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11687 /* we have fed all the moves, so reactivate analysis mode */
11688 SendToProgram("analyze\n", &first);
11689 first.analyzing = TRUE;
11690 /*first.maybeThinking = TRUE;*/
11691 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11696 BackwardInner(target)
11699 int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
11701 if (appData.debugMode)
11702 fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
11703 target, currentMove, forwardMostMove);
11705 if (gameMode == EditPosition) return;
11706 if (currentMove <= backwardMostMove) {
11708 DrawPosition(full_redraw, boards[currentMove]);
11711 if (gameMode == PlayFromGameFile && !pausing)
11714 if (moveList[target][0]) {
11715 int fromX, fromY, toX, toY;
11716 toX = moveList[target][2] - AAA;
11717 toY = moveList[target][3] - ONE;
11718 if (moveList[target][1] == '@') {
11719 if (appData.highlightLastMove) {
11720 SetHighlights(-1, -1, toX, toY);
11723 fromX = moveList[target][0] - AAA;
11724 fromY = moveList[target][1] - ONE;
11725 if (target == currentMove - 1) {
11726 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
11728 if (appData.highlightLastMove) {
11729 SetHighlights(fromX, fromY, toX, toY);
11733 if (gameMode == EditGame || gameMode==AnalyzeMode ||
11734 gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
11735 while (currentMove > target) {
11736 SendToProgram("undo\n", &first);
11740 currentMove = target;
11743 if (gameMode == EditGame || gameMode == EndOfGame) {
11744 whiteTimeRemaining = timeRemaining[0][currentMove];
11745 blackTimeRemaining = timeRemaining[1][currentMove];
11747 DisplayBothClocks();
11748 DisplayMove(currentMove - 1);
11749 DrawPosition(full_redraw, boards[currentMove]);
11750 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
11751 // [HGM] PV info: routine tests if comment empty
11752 DisplayComment(currentMove - 1, commentList[currentMove]);
11758 if (gameMode == IcsExamining && !pausing) {
11759 SendToICS(ics_prefix);
11760 SendToICS("backward\n");
11762 BackwardInner(currentMove - 1);
11769 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11770 /* to optimze, we temporarily turn off analysis mode while we undo
11771 * all the moves. Otherwise we get analysis output after each undo.
11773 if (first.analysisSupport) {
11774 SendToProgram("exit\nforce\n", &first);
11775 first.analyzing = FALSE;
11779 if (gameMode == IcsExamining && !pausing) {
11780 SendToICS(ics_prefix);
11781 SendToICS("backward 999999\n");
11783 BackwardInner(backwardMostMove);
11786 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11787 /* we have fed all the moves, so reactivate analysis mode */
11788 SendToProgram("analyze\n", &first);
11789 first.analyzing = TRUE;
11790 /*first.maybeThinking = TRUE;*/
11791 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11798 if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
11799 if (to >= forwardMostMove) to = forwardMostMove;
11800 if (to <= backwardMostMove) to = backwardMostMove;
11801 if (to < currentMove) {
11811 if (gameMode != IcsExamining) {
11812 DisplayError(_("You are not examining a game"), 0);
11816 DisplayError(_("You can't revert while pausing"), 0);
11819 SendToICS(ics_prefix);
11820 SendToICS("revert\n");
11826 switch (gameMode) {
11827 case MachinePlaysWhite:
11828 case MachinePlaysBlack:
11829 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
11830 DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
11833 if (forwardMostMove < 2) return;
11834 currentMove = forwardMostMove = forwardMostMove - 2;
11835 whiteTimeRemaining = timeRemaining[0][currentMove];
11836 blackTimeRemaining = timeRemaining[1][currentMove];
11837 DisplayBothClocks();
11838 DisplayMove(currentMove - 1);
11839 ClearHighlights();/*!! could figure this out*/
11840 DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
11841 SendToProgram("remove\n", &first);
11842 /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
11845 case BeginningOfGame:
11849 case IcsPlayingWhite:
11850 case IcsPlayingBlack:
11851 if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
11852 SendToICS(ics_prefix);
11853 SendToICS("takeback 2\n");
11855 SendToICS(ics_prefix);
11856 SendToICS("takeback 1\n");
11865 ChessProgramState *cps;
11867 switch (gameMode) {
11868 case MachinePlaysWhite:
11869 if (!WhiteOnMove(forwardMostMove)) {
11870 DisplayError(_("It is your turn"), 0);
11875 case MachinePlaysBlack:
11876 if (WhiteOnMove(forwardMostMove)) {
11877 DisplayError(_("It is your turn"), 0);
11882 case TwoMachinesPlay:
11883 if (WhiteOnMove(forwardMostMove) ==
11884 (first.twoMachinesColor[0] == 'w')) {
11890 case BeginningOfGame:
11894 SendToProgram("?\n", cps);
11898 TruncateGameEvent()
11901 if (gameMode != EditGame) return;
11908 if (forwardMostMove > currentMove) {
11909 if (gameInfo.resultDetails != NULL) {
11910 free(gameInfo.resultDetails);
11911 gameInfo.resultDetails = NULL;
11912 gameInfo.result = GameUnfinished;
11914 forwardMostMove = currentMove;
11915 HistorySet(parseList, backwardMostMove, forwardMostMove,
11923 if (appData.noChessProgram) return;
11924 switch (gameMode) {
11925 case MachinePlaysWhite:
11926 if (WhiteOnMove(forwardMostMove)) {
11927 DisplayError(_("Wait until your turn"), 0);
11931 case BeginningOfGame:
11932 case MachinePlaysBlack:
11933 if (!WhiteOnMove(forwardMostMove)) {
11934 DisplayError(_("Wait until your turn"), 0);
11939 DisplayError(_("No hint available"), 0);
11942 SendToProgram("hint\n", &first);
11943 hintRequested = TRUE;
11949 if (appData.noChessProgram) return;
11950 switch (gameMode) {
11951 case MachinePlaysWhite:
11952 if (WhiteOnMove(forwardMostMove)) {
11953 DisplayError(_("Wait until your turn"), 0);
11957 case BeginningOfGame:
11958 case MachinePlaysBlack:
11959 if (!WhiteOnMove(forwardMostMove)) {
11960 DisplayError(_("Wait until your turn"), 0);
11965 EditPositionDone();
11967 case TwoMachinesPlay:
11972 SendToProgram("bk\n", &first);
11973 bookOutput[0] = NULLCHAR;
11974 bookRequested = TRUE;
11980 char *tags = PGNTags(&gameInfo);
11981 TagsPopUp(tags, CmailMsg());
11985 /* end button procedures */
11988 PrintPosition(fp, move)
11994 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
11995 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
11996 char c = PieceToChar(boards[move][i][j]);
11997 fputc(c == 'x' ? '.' : c, fp);
11998 fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
12001 if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
12002 fprintf(fp, "white to play\n");
12004 fprintf(fp, "black to play\n");
12011 if (gameInfo.white != NULL) {
12012 fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
12018 /* Find last component of program's own name, using some heuristics */
12020 TidyProgramName(prog, host, buf)
12021 char *prog, *host, buf[MSG_SIZ];
12024 int local = (strcmp(host, "localhost") == 0);
12025 while (!local && (p = strchr(prog, ';')) != NULL) {
12027 while (*p == ' ') p++;
12030 if (*prog == '"' || *prog == '\'') {
12031 q = strchr(prog + 1, *prog);
12033 q = strchr(prog, ' ');
12035 if (q == NULL) q = prog + strlen(prog);
12037 while (p >= prog && *p != '/' && *p != '\\') p--;
12039 if(p == prog && *p == '"') p++;
12040 if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
12041 memcpy(buf, p, q - p);
12042 buf[q - p] = NULLCHAR;
12050 TimeControlTagValue()
12053 if (!appData.clockMode) {
12055 } else if (movesPerSession > 0) {
12056 sprintf(buf, "%d/%ld", movesPerSession, timeControl/1000);
12057 } else if (timeIncrement == 0) {
12058 sprintf(buf, "%ld", timeControl/1000);
12060 sprintf(buf, "%ld+%ld", timeControl/1000, timeIncrement/1000);
12062 return StrSave(buf);
12068 /* This routine is used only for certain modes */
12069 VariantClass v = gameInfo.variant;
12070 ClearGameInfo(&gameInfo);
12071 gameInfo.variant = v;
12073 switch (gameMode) {
12074 case MachinePlaysWhite:
12075 gameInfo.event = StrSave( appData.pgnEventHeader );
12076 gameInfo.site = StrSave(HostName());
12077 gameInfo.date = PGNDate();
12078 gameInfo.round = StrSave("-");
12079 gameInfo.white = StrSave(first.tidy);
12080 gameInfo.black = StrSave(UserName());
12081 gameInfo.timeControl = TimeControlTagValue();
12084 case MachinePlaysBlack:
12085 gameInfo.event = StrSave( appData.pgnEventHeader );
12086 gameInfo.site = StrSave(HostName());
12087 gameInfo.date = PGNDate();
12088 gameInfo.round = StrSave("-");
12089 gameInfo.white = StrSave(UserName());
12090 gameInfo.black = StrSave(first.tidy);
12091 gameInfo.timeControl = TimeControlTagValue();
12094 case TwoMachinesPlay:
12095 gameInfo.event = StrSave( appData.pgnEventHeader );
12096 gameInfo.site = StrSave(HostName());
12097 gameInfo.date = PGNDate();
12098 if (matchGame > 0) {
12100 sprintf(buf, "%d", matchGame);
12101 gameInfo.round = StrSave(buf);
12103 gameInfo.round = StrSave("-");
12105 if (first.twoMachinesColor[0] == 'w') {
12106 gameInfo.white = StrSave(first.tidy);
12107 gameInfo.black = StrSave(second.tidy);
12109 gameInfo.white = StrSave(second.tidy);
12110 gameInfo.black = StrSave(first.tidy);
12112 gameInfo.timeControl = TimeControlTagValue();
12116 gameInfo.event = StrSave("Edited game");
12117 gameInfo.site = StrSave(HostName());
12118 gameInfo.date = PGNDate();
12119 gameInfo.round = StrSave("-");
12120 gameInfo.white = StrSave("-");
12121 gameInfo.black = StrSave("-");
12125 gameInfo.event = StrSave("Edited position");
12126 gameInfo.site = StrSave(HostName());
12127 gameInfo.date = PGNDate();
12128 gameInfo.round = StrSave("-");
12129 gameInfo.white = StrSave("-");
12130 gameInfo.black = StrSave("-");
12133 case IcsPlayingWhite:
12134 case IcsPlayingBlack:
12139 case PlayFromGameFile:
12140 gameInfo.event = StrSave("Game from non-PGN file");
12141 gameInfo.site = StrSave(HostName());
12142 gameInfo.date = PGNDate();
12143 gameInfo.round = StrSave("-");
12144 gameInfo.white = StrSave("?");
12145 gameInfo.black = StrSave("?");
12154 ReplaceComment(index, text)
12160 while (*text == '\n') text++;
12161 len = strlen(text);
12162 while (len > 0 && text[len - 1] == '\n') len--;
12164 if (commentList[index] != NULL)
12165 free(commentList[index]);
12168 commentList[index] = NULL;
12171 commentList[index] = (char *) malloc(len + 2);
12172 strncpy(commentList[index], text, len);
12173 commentList[index][len] = '\n';
12174 commentList[index][len + 1] = NULLCHAR;
12187 if (ch == '\r') continue;
12189 } while (ch != '\0');
12193 AppendComment(index, text)
12200 text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
12203 while (*text == '\n') text++;
12204 len = strlen(text);
12205 while (len > 0 && text[len - 1] == '\n') len--;
12207 if (len == 0) return;
12209 if (commentList[index] != NULL) {
12210 old = commentList[index];
12211 oldlen = strlen(old);
12212 commentList[index] = (char *) malloc(oldlen + len + 2);
12213 strcpy(commentList[index], old);
12215 strncpy(&commentList[index][oldlen], text, len);
12216 commentList[index][oldlen + len] = '\n';
12217 commentList[index][oldlen + len + 1] = NULLCHAR;
12219 commentList[index] = (char *) malloc(len + 2);
12220 strncpy(commentList[index], text, len);
12221 commentList[index][len] = '\n';
12222 commentList[index][len + 1] = NULLCHAR;
12226 static char * FindStr( char * text, char * sub_text )
12228 char * result = strstr( text, sub_text );
12230 if( result != NULL ) {
12231 result += strlen( sub_text );
12237 /* [AS] Try to extract PV info from PGN comment */
12238 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
12239 char *GetInfoFromComment( int index, char * text )
12243 if( text != NULL && index > 0 ) {
12246 int time = -1, sec = 0, deci;
12247 char * s_eval = FindStr( text, "[%eval " );
12248 char * s_emt = FindStr( text, "[%emt " );
12250 if( s_eval != NULL || s_emt != NULL ) {
12254 if( s_eval != NULL ) {
12255 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
12259 if( delim != ']' ) {
12264 if( s_emt != NULL ) {
12268 /* We expect something like: [+|-]nnn.nn/dd */
12271 sep = strchr( text, '/' );
12272 if( sep == NULL || sep < (text+4) ) {
12276 time = -1; sec = -1; deci = -1;
12277 if( sscanf( text, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
12278 sscanf( text, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
12279 sscanf( text, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
12280 sscanf( text, "%d.%d/%d", &score, &score_lo, &depth ) != 3 ) {
12284 if( score_lo < 0 || score_lo >= 100 ) {
12288 if(sec >= 0) time = 600*time + 10*sec; else
12289 if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
12291 score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
12293 /* [HGM] PV time: now locate end of PV info */
12294 while( *++sep >= '0' && *sep <= '9'); // strip depth
12296 while( *++sep >= '0' && *sep <= '9'); // strip time
12298 while( *++sep >= '0' && *sep <= '9'); // strip seconds
12300 while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
12301 while(*sep == ' ') sep++;
12312 pvInfoList[index-1].depth = depth;
12313 pvInfoList[index-1].score = score;
12314 pvInfoList[index-1].time = 10*time; // centi-sec
12320 SendToProgram(message, cps)
12322 ChessProgramState *cps;
12324 int count, outCount, error;
12327 if (cps->pr == NULL) return;
12330 if (appData.debugMode) {
12333 fprintf(debugFP, "%ld >%-6s: %s",
12334 SubtractTimeMarks(&now, &programStartTime),
12335 cps->which, message);
12338 count = strlen(message);
12339 outCount = OutputToProcess(cps->pr, message, count, &error);
12340 if (outCount < count && !exiting
12341 && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
12342 sprintf(buf, _("Error writing to %s chess program"), cps->which);
12343 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
12344 if(epStatus[forwardMostMove] <= EP_DRAWS) {
12345 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
12346 sprintf(buf, "%s program exits in draw position (%s)", cps->which, cps->program);
12348 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
12350 gameInfo.resultDetails = buf;
12352 DisplayFatalError(buf, error, 1);
12357 ReceiveFromProgram(isr, closure, message, count, error)
12358 InputSourceRef isr;
12366 ChessProgramState *cps = (ChessProgramState *)closure;
12368 if (isr != cps->isr) return; /* Killed intentionally */
12372 _("Error: %s chess program (%s) exited unexpectedly"),
12373 cps->which, cps->program);
12374 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
12375 if(epStatus[forwardMostMove] <= EP_DRAWS) {
12376 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
12377 sprintf(buf, _("%s program exits in draw position (%s)"), cps->which, cps->program);
12379 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
12381 gameInfo.resultDetails = buf;
12383 RemoveInputSource(cps->isr);
12384 DisplayFatalError(buf, 0, 1);
12387 _("Error reading from %s chess program (%s)"),
12388 cps->which, cps->program);
12389 RemoveInputSource(cps->isr);
12391 /* [AS] Program is misbehaving badly... kill it */
12392 if( count == -2 ) {
12393 DestroyChildProcess( cps->pr, 9 );
12397 DisplayFatalError(buf, error, 1);
12402 if ((end_str = strchr(message, '\r')) != NULL)
12403 *end_str = NULLCHAR;
12404 if ((end_str = strchr(message, '\n')) != NULL)
12405 *end_str = NULLCHAR;
12407 if (appData.debugMode) {
12408 TimeMark now; int print = 1;
12409 char *quote = ""; char c; int i;
12411 if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
12412 char start = message[0];
12413 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
12414 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
12415 sscanf(message, "move %c", &c)!=1 && sscanf(message, "offer%c", &c)!=1 &&
12416 sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
12417 sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
12418 sscanf(message, "tell%c", &c)!=1 && sscanf(message, "0-1 %c", &c)!=1 &&
12419 sscanf(message, "1-0 %c", &c)!=1 && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
12420 sscanf(message, "pong %c", &c)!=1 && start != '#')
12421 { quote = "# "; print = (appData.engineComments == 2); }
12422 message[0] = start; // restore original message
12426 fprintf(debugFP, "%ld <%-6s: %s%s\n",
12427 SubtractTimeMarks(&now, &programStartTime), cps->which,
12433 /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
12434 if (appData.icsEngineAnalyze) {
12435 if (strstr(message, "whisper") != NULL ||
12436 strstr(message, "kibitz") != NULL ||
12437 strstr(message, "tellics") != NULL) return;
12440 HandleMachineMove(message, cps);
12445 SendTimeControl(cps, mps, tc, inc, sd, st)
12446 ChessProgramState *cps;
12447 int mps, inc, sd, st;
12453 if( timeControl_2 > 0 ) {
12454 if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
12455 tc = timeControl_2;
12458 tc /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
12459 inc /= cps->timeOdds;
12460 st /= cps->timeOdds;
12462 seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
12465 /* Set exact time per move, normally using st command */
12466 if (cps->stKludge) {
12467 /* GNU Chess 4 has no st command; uses level in a nonstandard way */
12469 if (seconds == 0) {
12470 sprintf(buf, "level 1 %d\n", st/60);
12472 sprintf(buf, "level 1 %d:%02d\n", st/60, seconds);
12475 sprintf(buf, "st %d\n", st);
12478 /* Set conventional or incremental time control, using level command */
12479 if (seconds == 0) {
12480 /* Note old gnuchess bug -- minutes:seconds used to not work.
12481 Fixed in later versions, but still avoid :seconds
12482 when seconds is 0. */
12483 sprintf(buf, "level %d %ld %d\n", mps, tc/60000, inc/1000);
12485 sprintf(buf, "level %d %ld:%02d %d\n", mps, tc/60000,
12486 seconds, inc/1000);
12489 SendToProgram(buf, cps);
12491 /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
12492 /* Orthogonally, limit search to given depth */
12494 if (cps->sdKludge) {
12495 sprintf(buf, "depth\n%d\n", sd);
12497 sprintf(buf, "sd %d\n", sd);
12499 SendToProgram(buf, cps);
12502 if(cps->nps > 0) { /* [HGM] nps */
12503 if(cps->supportsNPS == FALSE) cps->nps = -1; // don't use if engine explicitly says not supported!
12505 sprintf(buf, "nps %d\n", cps->nps);
12506 SendToProgram(buf, cps);
12511 ChessProgramState *WhitePlayer()
12512 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
12514 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
12515 gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
12521 SendTimeRemaining(cps, machineWhite)
12522 ChessProgramState *cps;
12523 int /*boolean*/ machineWhite;
12525 char message[MSG_SIZ];
12528 /* Note: this routine must be called when the clocks are stopped
12529 or when they have *just* been set or switched; otherwise
12530 it will be off by the time since the current tick started.
12532 if (machineWhite) {
12533 time = whiteTimeRemaining / 10;
12534 otime = blackTimeRemaining / 10;
12536 time = blackTimeRemaining / 10;
12537 otime = whiteTimeRemaining / 10;
12539 /* [HGM] translate opponent's time by time-odds factor */
12540 otime = (otime * cps->other->timeOdds) / cps->timeOdds;
12541 if (appData.debugMode) {
12542 fprintf(debugFP, "time odds: %d %d \n", cps->timeOdds, cps->other->timeOdds);
12545 if (time <= 0) time = 1;
12546 if (otime <= 0) otime = 1;
12548 sprintf(message, "time %ld\n", time);
12549 SendToProgram(message, cps);
12551 sprintf(message, "otim %ld\n", otime);
12552 SendToProgram(message, cps);
12556 BoolFeature(p, name, loc, cps)
12560 ChessProgramState *cps;
12563 int len = strlen(name);
12565 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
12567 sscanf(*p, "%d", &val);
12569 while (**p && **p != ' ') (*p)++;
12570 sprintf(buf, "accepted %s\n", name);
12571 SendToProgram(buf, cps);
12578 IntFeature(p, name, loc, cps)
12582 ChessProgramState *cps;
12585 int len = strlen(name);
12586 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
12588 sscanf(*p, "%d", loc);
12589 while (**p && **p != ' ') (*p)++;
12590 sprintf(buf, "accepted %s\n", name);
12591 SendToProgram(buf, cps);
12598 StringFeature(p, name, loc, cps)
12602 ChessProgramState *cps;
12605 int len = strlen(name);
12606 if (strncmp((*p), name, len) == 0
12607 && (*p)[len] == '=' && (*p)[len+1] == '\"') {
12609 sscanf(*p, "%[^\"]", loc);
12610 while (**p && **p != '\"') (*p)++;
12611 if (**p == '\"') (*p)++;
12612 sprintf(buf, "accepted %s\n", name);
12613 SendToProgram(buf, cps);
12620 ParseOption(Option *opt, ChessProgramState *cps)
12621 // [HGM] options: process the string that defines an engine option, and determine
12622 // name, type, default value, and allowed value range
12624 char *p, *q, buf[MSG_SIZ];
12625 int n, min = (-1)<<31, max = 1<<31, def;
12627 if(p = strstr(opt->name, " -spin ")) {
12628 if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
12629 if(max < min) max = min; // enforce consistency
12630 if(def < min) def = min;
12631 if(def > max) def = max;
12636 } else if((p = strstr(opt->name, " -slider "))) {
12637 // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
12638 if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
12639 if(max < min) max = min; // enforce consistency
12640 if(def < min) def = min;
12641 if(def > max) def = max;
12645 opt->type = Spin; // Slider;
12646 } else if((p = strstr(opt->name, " -string "))) {
12647 opt->textValue = p+9;
12648 opt->type = TextBox;
12649 } else if((p = strstr(opt->name, " -file "))) {
12650 // for now -file is a synonym for -string, to already provide compatibility with future polyglots
12651 opt->textValue = p+7;
12652 opt->type = TextBox; // FileName;
12653 } else if((p = strstr(opt->name, " -path "))) {
12654 // for now -file is a synonym for -string, to already provide compatibility with future polyglots
12655 opt->textValue = p+7;
12656 opt->type = TextBox; // PathName;
12657 } else if(p = strstr(opt->name, " -check ")) {
12658 if(sscanf(p, " -check %d", &def) < 1) return FALSE;
12659 opt->value = (def != 0);
12660 opt->type = CheckBox;
12661 } else if(p = strstr(opt->name, " -combo ")) {
12662 opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
12663 cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
12664 if(*q == '*') cps->comboList[cps->comboCnt-1]++;
12665 opt->value = n = 0;
12666 while(q = StrStr(q, " /// ")) {
12667 n++; *q = 0; // count choices, and null-terminate each of them
12669 if(*q == '*') { // remember default, which is marked with * prefix
12673 cps->comboList[cps->comboCnt++] = q;
12675 cps->comboList[cps->comboCnt++] = NULL;
12677 opt->type = ComboBox;
12678 } else if(p = strstr(opt->name, " -button")) {
12679 opt->type = Button;
12680 } else if(p = strstr(opt->name, " -save")) {
12681 opt->type = SaveButton;
12682 } else return FALSE;
12683 *p = 0; // terminate option name
12684 // now look if the command-line options define a setting for this engine option.
12685 if(cps->optionSettings && cps->optionSettings[0])
12686 p = strstr(cps->optionSettings, opt->name); else p = NULL;
12687 if(p && (p == cps->optionSettings || p[-1] == ',')) {
12688 sprintf(buf, "option %s", p);
12689 if(p = strstr(buf, ",")) *p = 0;
12691 SendToProgram(buf, cps);
12697 FeatureDone(cps, val)
12698 ChessProgramState* cps;
12701 DelayedEventCallback cb = GetDelayedEvent();
12702 if ((cb == InitBackEnd3 && cps == &first) ||
12703 (cb == TwoMachinesEventIfReady && cps == &second)) {
12704 CancelDelayedEvent();
12705 ScheduleDelayedEvent(cb, val ? 1 : 3600000);
12707 cps->initDone = val;
12710 /* Parse feature command from engine */
12712 ParseFeatures(args, cps)
12714 ChessProgramState *cps;
12722 while (*p == ' ') p++;
12723 if (*p == NULLCHAR) return;
12725 if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
12726 if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
12727 if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
12728 if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
12729 if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
12730 if (BoolFeature(&p, "reuse", &val, cps)) {
12731 /* Engine can disable reuse, but can't enable it if user said no */
12732 if (!val) cps->reuse = FALSE;
12735 if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
12736 if (StringFeature(&p, "myname", &cps->tidy, cps)) {
12737 if (gameMode == TwoMachinesPlay) {
12738 DisplayTwoMachinesTitle();
12744 if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
12745 if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
12746 if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
12747 if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
12748 if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
12749 if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
12750 if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
12751 if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
12752 if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
12753 if (IntFeature(&p, "done", &val, cps)) {
12754 FeatureDone(cps, val);
12757 /* Added by Tord: */
12758 if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
12759 if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
12760 /* End of additions by Tord */
12762 /* [HGM] added features: */
12763 if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
12764 if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
12765 if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
12766 if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
12767 if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
12768 if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
12769 if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
12770 if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
12771 sprintf(buf, "rejected option %s\n", cps->option[--cps->nrOptions].name);
12772 SendToProgram(buf, cps);
12775 if(cps->nrOptions >= MAX_OPTIONS) {
12777 sprintf(buf, "%s engine has too many options\n", cps->which);
12778 DisplayError(buf, 0);
12782 if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
12783 /* End of additions by HGM */
12785 /* unknown feature: complain and skip */
12787 while (*q && *q != '=') q++;
12788 sprintf(buf, "rejected %.*s\n", q-p, p);
12789 SendToProgram(buf, cps);
12795 while (*p && *p != '\"') p++;
12796 if (*p == '\"') p++;
12798 while (*p && *p != ' ') p++;
12806 PeriodicUpdatesEvent(newState)
12809 if (newState == appData.periodicUpdates)
12812 appData.periodicUpdates=newState;
12814 /* Display type changes, so update it now */
12815 // DisplayAnalysis();
12817 /* Get the ball rolling again... */
12819 AnalysisPeriodicEvent(1);
12820 StartAnalysisClock();
12825 PonderNextMoveEvent(newState)
12828 if (newState == appData.ponderNextMove) return;
12829 if (gameMode == EditPosition) EditPositionDone();
12831 SendToProgram("hard\n", &first);
12832 if (gameMode == TwoMachinesPlay) {
12833 SendToProgram("hard\n", &second);
12836 SendToProgram("easy\n", &first);
12837 thinkOutput[0] = NULLCHAR;
12838 if (gameMode == TwoMachinesPlay) {
12839 SendToProgram("easy\n", &second);
12842 appData.ponderNextMove = newState;
12846 NewSettingEvent(option, command, value)
12852 if (gameMode == EditPosition) EditPositionDone();
12853 sprintf(buf, "%s%s %d\n", (option ? "option ": ""), command, value);
12854 SendToProgram(buf, &first);
12855 if (gameMode == TwoMachinesPlay) {
12856 SendToProgram(buf, &second);
12861 ShowThinkingEvent()
12862 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
12864 static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
12865 int newState = appData.showThinking
12866 // [HGM] thinking: other features now need thinking output as well
12867 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
12869 if (oldState == newState) return;
12870 oldState = newState;
12871 if (gameMode == EditPosition) EditPositionDone();
12873 SendToProgram("post\n", &first);
12874 if (gameMode == TwoMachinesPlay) {
12875 SendToProgram("post\n", &second);
12878 SendToProgram("nopost\n", &first);
12879 thinkOutput[0] = NULLCHAR;
12880 if (gameMode == TwoMachinesPlay) {
12881 SendToProgram("nopost\n", &second);
12884 // appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
12888 AskQuestionEvent(title, question, replyPrefix, which)
12889 char *title; char *question; char *replyPrefix; char *which;
12891 ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
12892 if (pr == NoProc) return;
12893 AskQuestion(title, question, replyPrefix, pr);
12897 DisplayMove(moveNumber)
12900 char message[MSG_SIZ];
12902 char cpThinkOutput[MSG_SIZ];
12904 if(appData.noGUI) return; // [HGM] fast: suppress display of moves
12906 if (moveNumber == forwardMostMove - 1 ||
12907 gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12909 safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput));
12911 if (strchr(cpThinkOutput, '\n')) {
12912 *strchr(cpThinkOutput, '\n') = NULLCHAR;
12915 *cpThinkOutput = NULLCHAR;
12918 /* [AS] Hide thinking from human user */
12919 if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
12920 *cpThinkOutput = NULLCHAR;
12921 if( thinkOutput[0] != NULLCHAR ) {
12924 for( i=0; i<=hiddenThinkOutputState; i++ ) {
12925 cpThinkOutput[i] = '.';
12927 cpThinkOutput[i] = NULLCHAR;
12928 hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
12932 if (moveNumber == forwardMostMove - 1 &&
12933 gameInfo.resultDetails != NULL) {
12934 if (gameInfo.resultDetails[0] == NULLCHAR) {
12935 sprintf(res, " %s", PGNResult(gameInfo.result));
12937 sprintf(res, " {%s} %s",
12938 gameInfo.resultDetails, PGNResult(gameInfo.result));
12944 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
12945 DisplayMessage(res, cpThinkOutput);
12947 sprintf(message, "%d.%s%s%s", moveNumber / 2 + 1,
12948 WhiteOnMove(moveNumber) ? " " : ".. ",
12949 parseList[moveNumber], res);
12950 DisplayMessage(message, cpThinkOutput);
12955 DisplayComment(moveNumber, text)
12959 char title[MSG_SIZ];
12960 char buf[8000]; // comment can be long!
12963 if( appData.autoDisplayComment ) {
12964 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
12965 strcpy(title, "Comment");
12967 sprintf(title, "Comment on %d.%s%s", moveNumber / 2 + 1,
12968 WhiteOnMove(moveNumber) ? " " : ".. ",
12969 parseList[moveNumber]);
12971 // [HGM] PV info: display PV info together with (or as) comment
12972 if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {
12973 if(text == NULL) text = "";
12974 score = pvInfoList[moveNumber].score;
12975 sprintf(buf, "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,
12976 depth, (pvInfoList[moveNumber].time+50)/100, text);
12979 } else title[0] = 0;
12982 CommentPopUp(title, text);
12985 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
12986 * might be busy thinking or pondering. It can be omitted if your
12987 * gnuchess is configured to stop thinking immediately on any user
12988 * input. However, that gnuchess feature depends on the FIONREAD
12989 * ioctl, which does not work properly on some flavors of Unix.
12993 ChessProgramState *cps;
12996 if (!cps->useSigint) return;
12997 if (appData.noChessProgram || (cps->pr == NoProc)) return;
12998 switch (gameMode) {
12999 case MachinePlaysWhite:
13000 case MachinePlaysBlack:
13001 case TwoMachinesPlay:
13002 case IcsPlayingWhite:
13003 case IcsPlayingBlack:
13006 /* Skip if we know it isn't thinking */
13007 if (!cps->maybeThinking) return;
13008 if (appData.debugMode)
13009 fprintf(debugFP, "Interrupting %s\n", cps->which);
13010 InterruptChildProcess(cps->pr);
13011 cps->maybeThinking = FALSE;
13016 #endif /*ATTENTION*/
13022 if (whiteTimeRemaining <= 0) {
13025 if (appData.icsActive) {
13026 if (appData.autoCallFlag &&
13027 gameMode == IcsPlayingBlack && !blackFlag) {
13028 SendToICS(ics_prefix);
13029 SendToICS("flag\n");
13033 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
13035 if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
13036 if (appData.autoCallFlag) {
13037 GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
13044 if (blackTimeRemaining <= 0) {
13047 if (appData.icsActive) {
13048 if (appData.autoCallFlag &&
13049 gameMode == IcsPlayingWhite && !whiteFlag) {
13050 SendToICS(ics_prefix);
13051 SendToICS("flag\n");
13055 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
13057 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
13058 if (appData.autoCallFlag) {
13059 GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
13072 if (!appData.clockMode || appData.icsActive ||
13073 gameMode == PlayFromGameFile || forwardMostMove == 0) return;
13076 * add time to clocks when time control is achieved ([HGM] now also used for increment)
13078 if ( !WhiteOnMove(forwardMostMove) )
13079 /* White made time control */
13080 whiteTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
13081 /* [HGM] time odds: correct new time quota for time odds! */
13082 / WhitePlayer()->timeOdds;
13084 /* Black made time control */
13085 blackTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
13086 / WhitePlayer()->other->timeOdds;
13090 DisplayBothClocks()
13092 int wom = gameMode == EditPosition ?
13093 !blackPlaysFirst : WhiteOnMove(currentMove);
13094 DisplayWhiteClock(whiteTimeRemaining, wom);
13095 DisplayBlackClock(blackTimeRemaining, !wom);
13099 /* Timekeeping seems to be a portability nightmare. I think everyone
13100 has ftime(), but I'm really not sure, so I'm including some ifdefs
13101 to use other calls if you don't. Clocks will be less accurate if
13102 you have neither ftime nor gettimeofday.
13105 /* VS 2008 requires the #include outside of the function */
13106 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
13107 #include <sys/timeb.h>
13110 /* Get the current time as a TimeMark */
13115 #if HAVE_GETTIMEOFDAY
13117 struct timeval timeVal;
13118 struct timezone timeZone;
13120 gettimeofday(&timeVal, &timeZone);
13121 tm->sec = (long) timeVal.tv_sec;
13122 tm->ms = (int) (timeVal.tv_usec / 1000L);
13124 #else /*!HAVE_GETTIMEOFDAY*/
13127 // include <sys/timeb.h> / moved to just above start of function
13128 struct timeb timeB;
13131 tm->sec = (long) timeB.time;
13132 tm->ms = (int) timeB.millitm;
13134 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
13135 tm->sec = (long) time(NULL);
13141 /* Return the difference in milliseconds between two
13142 time marks. We assume the difference will fit in a long!
13145 SubtractTimeMarks(tm2, tm1)
13146 TimeMark *tm2, *tm1;
13148 return 1000L*(tm2->sec - tm1->sec) +
13149 (long) (tm2->ms - tm1->ms);
13154 * Code to manage the game clocks.
13156 * In tournament play, black starts the clock and then white makes a move.
13157 * We give the human user a slight advantage if he is playing white---the
13158 * clocks don't run until he makes his first move, so it takes zero time.
13159 * Also, we don't account for network lag, so we could get out of sync
13160 * with GNU Chess's clock -- but then, referees are always right.
13163 static TimeMark tickStartTM;
13164 static long intendedTickLength;
13167 NextTickLength(timeRemaining)
13168 long timeRemaining;
13170 long nominalTickLength, nextTickLength;
13172 if (timeRemaining > 0L && timeRemaining <= 10000L)
13173 nominalTickLength = 100L;
13175 nominalTickLength = 1000L;
13176 nextTickLength = timeRemaining % nominalTickLength;
13177 if (nextTickLength <= 0) nextTickLength += nominalTickLength;
13179 return nextTickLength;
13182 /* Adjust clock one minute up or down */
13184 AdjustClock(Boolean which, int dir)
13186 if(which) blackTimeRemaining += 60000*dir;
13187 else whiteTimeRemaining += 60000*dir;
13188 DisplayBothClocks();
13191 /* Stop clocks and reset to a fresh time control */
13195 (void) StopClockTimer();
13196 if (appData.icsActive) {
13197 whiteTimeRemaining = blackTimeRemaining = 0;
13198 } else { /* [HGM] correct new time quote for time odds */
13199 whiteTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->timeOdds;
13200 blackTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->other->timeOdds;
13202 if (whiteFlag || blackFlag) {
13204 whiteFlag = blackFlag = FALSE;
13206 DisplayBothClocks();
13209 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
13211 /* Decrement running clock by amount of time that has passed */
13215 long timeRemaining;
13216 long lastTickLength, fudge;
13219 if (!appData.clockMode) return;
13220 if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
13224 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13226 /* Fudge if we woke up a little too soon */
13227 fudge = intendedTickLength - lastTickLength;
13228 if (fudge < 0 || fudge > FUDGE) fudge = 0;
13230 if (WhiteOnMove(forwardMostMove)) {
13231 if(whiteNPS >= 0) lastTickLength = 0;
13232 timeRemaining = whiteTimeRemaining -= lastTickLength;
13233 DisplayWhiteClock(whiteTimeRemaining - fudge,
13234 WhiteOnMove(currentMove));
13236 if(blackNPS >= 0) lastTickLength = 0;
13237 timeRemaining = blackTimeRemaining -= lastTickLength;
13238 DisplayBlackClock(blackTimeRemaining - fudge,
13239 !WhiteOnMove(currentMove));
13242 if (CheckFlags()) return;
13245 intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
13246 StartClockTimer(intendedTickLength);
13248 /* if the time remaining has fallen below the alarm threshold, sound the
13249 * alarm. if the alarm has sounded and (due to a takeback or time control
13250 * with increment) the time remaining has increased to a level above the
13251 * threshold, reset the alarm so it can sound again.
13254 if (appData.icsActive && appData.icsAlarm) {
13256 /* make sure we are dealing with the user's clock */
13257 if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
13258 ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
13261 if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
13262 alarmSounded = FALSE;
13263 } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
13265 alarmSounded = TRUE;
13271 /* A player has just moved, so stop the previously running
13272 clock and (if in clock mode) start the other one.
13273 We redisplay both clocks in case we're in ICS mode, because
13274 ICS gives us an update to both clocks after every move.
13275 Note that this routine is called *after* forwardMostMove
13276 is updated, so the last fractional tick must be subtracted
13277 from the color that is *not* on move now.
13282 long lastTickLength;
13284 int flagged = FALSE;
13288 if (StopClockTimer() && appData.clockMode) {
13289 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13290 if (WhiteOnMove(forwardMostMove)) {
13291 if(blackNPS >= 0) lastTickLength = 0;
13292 blackTimeRemaining -= lastTickLength;
13293 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
13294 // if(pvInfoList[forwardMostMove-1].time == -1)
13295 pvInfoList[forwardMostMove-1].time = // use GUI time
13296 (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
13298 if(whiteNPS >= 0) lastTickLength = 0;
13299 whiteTimeRemaining -= lastTickLength;
13300 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
13301 // if(pvInfoList[forwardMostMove-1].time == -1)
13302 pvInfoList[forwardMostMove-1].time =
13303 (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
13305 flagged = CheckFlags();
13307 CheckTimeControl();
13309 if (flagged || !appData.clockMode) return;
13311 switch (gameMode) {
13312 case MachinePlaysBlack:
13313 case MachinePlaysWhite:
13314 case BeginningOfGame:
13315 if (pausing) return;
13319 case PlayFromGameFile:
13328 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
13329 whiteTimeRemaining : blackTimeRemaining);
13330 StartClockTimer(intendedTickLength);
13334 /* Stop both clocks */
13338 long lastTickLength;
13341 if (!StopClockTimer()) return;
13342 if (!appData.clockMode) return;
13346 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13347 if (WhiteOnMove(forwardMostMove)) {
13348 if(whiteNPS >= 0) lastTickLength = 0;
13349 whiteTimeRemaining -= lastTickLength;
13350 DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
13352 if(blackNPS >= 0) lastTickLength = 0;
13353 blackTimeRemaining -= lastTickLength;
13354 DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
13359 /* Start clock of player on move. Time may have been reset, so
13360 if clock is already running, stop and restart it. */
13364 (void) StopClockTimer(); /* in case it was running already */
13365 DisplayBothClocks();
13366 if (CheckFlags()) return;
13368 if (!appData.clockMode) return;
13369 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
13371 GetTimeMark(&tickStartTM);
13372 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
13373 whiteTimeRemaining : blackTimeRemaining);
13375 /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
13376 whiteNPS = blackNPS = -1;
13377 if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
13378 || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
13379 whiteNPS = first.nps;
13380 if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
13381 || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
13382 blackNPS = first.nps;
13383 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
13384 whiteNPS = second.nps;
13385 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
13386 blackNPS = second.nps;
13387 if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
13389 StartClockTimer(intendedTickLength);
13396 long second, minute, hour, day;
13398 static char buf[32];
13400 if (ms > 0 && ms <= 9900) {
13401 /* convert milliseconds to tenths, rounding up */
13402 double tenths = floor( ((double)(ms + 99L)) / 100.00 );
13404 sprintf(buf, " %03.1f ", tenths/10.0);
13408 /* convert milliseconds to seconds, rounding up */
13409 /* use floating point to avoid strangeness of integer division
13410 with negative dividends on many machines */
13411 second = (long) floor(((double) (ms + 999L)) / 1000.0);
13418 day = second / (60 * 60 * 24);
13419 second = second % (60 * 60 * 24);
13420 hour = second / (60 * 60);
13421 second = second % (60 * 60);
13422 minute = second / 60;
13423 second = second % 60;
13426 sprintf(buf, " %s%ld:%02ld:%02ld:%02ld ",
13427 sign, day, hour, minute, second);
13429 sprintf(buf, " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
13431 sprintf(buf, " %s%2ld:%02ld ", sign, minute, second);
13438 * This is necessary because some C libraries aren't ANSI C compliant yet.
13441 StrStr(string, match)
13442 char *string, *match;
13446 length = strlen(match);
13448 for (i = strlen(string) - length; i >= 0; i--, string++)
13449 if (!strncmp(match, string, length))
13456 StrCaseStr(string, match)
13457 char *string, *match;
13461 length = strlen(match);
13463 for (i = strlen(string) - length; i >= 0; i--, string++) {
13464 for (j = 0; j < length; j++) {
13465 if (ToLower(match[j]) != ToLower(string[j]))
13468 if (j == length) return string;
13482 c1 = ToLower(*s1++);
13483 c2 = ToLower(*s2++);
13484 if (c1 > c2) return 1;
13485 if (c1 < c2) return -1;
13486 if (c1 == NULLCHAR) return 0;
13495 return isupper(c) ? tolower(c) : c;
13503 return islower(c) ? toupper(c) : c;
13505 #endif /* !_amigados */
13513 if ((ret = (char *) malloc(strlen(s) + 1))) {
13520 StrSavePtr(s, savePtr)
13521 char *s, **savePtr;
13526 if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
13527 strcpy(*savePtr, s);
13539 clock = time((time_t *)NULL);
13540 tm = localtime(&clock);
13541 sprintf(buf, "%04d.%02d.%02d",
13542 tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
13543 return StrSave(buf);
13548 PositionToFEN(move, overrideCastling)
13550 char *overrideCastling;
13552 int i, j, fromX, fromY, toX, toY;
13559 whiteToPlay = (gameMode == EditPosition) ?
13560 !blackPlaysFirst : (move % 2 == 0);
13563 /* Piece placement data */
13564 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13566 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
13567 if (boards[move][i][j] == EmptySquare) {
13569 } else { ChessSquare piece = boards[move][i][j];
13570 if (emptycount > 0) {
13571 if(emptycount<10) /* [HGM] can be >= 10 */
13572 *p++ = '0' + emptycount;
13573 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
13576 if(PieceToChar(piece) == '+') {
13577 /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
13579 piece = (ChessSquare)(DEMOTED piece);
13581 *p++ = PieceToChar(piece);
13583 /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
13584 p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
13589 if (emptycount > 0) {
13590 if(emptycount<10) /* [HGM] can be >= 10 */
13591 *p++ = '0' + emptycount;
13592 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
13599 /* [HGM] print Crazyhouse or Shogi holdings */
13600 if( gameInfo.holdingsWidth ) {
13601 *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
13603 for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
13604 piece = boards[move][i][BOARD_WIDTH-1];
13605 if( piece != EmptySquare )
13606 for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
13607 *p++ = PieceToChar(piece);
13609 for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
13610 piece = boards[move][BOARD_HEIGHT-i-1][0];
13611 if( piece != EmptySquare )
13612 for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
13613 *p++ = PieceToChar(piece);
13616 if( q == p ) *p++ = '-';
13622 *p++ = whiteToPlay ? 'w' : 'b';
13625 if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
13626 while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' ';
13628 if(nrCastlingRights) {
13630 if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
13631 /* [HGM] write directly from rights */
13632 if(castlingRights[move][2] >= 0 &&
13633 castlingRights[move][0] >= 0 )
13634 *p++ = castlingRights[move][0] + AAA + 'A' - 'a';
13635 if(castlingRights[move][2] >= 0 &&
13636 castlingRights[move][1] >= 0 )
13637 *p++ = castlingRights[move][1] + AAA + 'A' - 'a';
13638 if(castlingRights[move][5] >= 0 &&
13639 castlingRights[move][3] >= 0 )
13640 *p++ = castlingRights[move][3] + AAA;
13641 if(castlingRights[move][5] >= 0 &&
13642 castlingRights[move][4] >= 0 )
13643 *p++ = castlingRights[move][4] + AAA;
13646 /* [HGM] write true castling rights */
13647 if( nrCastlingRights == 6 ) {
13648 if(castlingRights[move][0] == BOARD_RGHT-1 &&
13649 castlingRights[move][2] >= 0 ) *p++ = 'K';
13650 if(castlingRights[move][1] == BOARD_LEFT &&
13651 castlingRights[move][2] >= 0 ) *p++ = 'Q';
13652 if(castlingRights[move][3] == BOARD_RGHT-1 &&
13653 castlingRights[move][5] >= 0 ) *p++ = 'k';
13654 if(castlingRights[move][4] == BOARD_LEFT &&
13655 castlingRights[move][5] >= 0 ) *p++ = 'q';
13658 if (q == p) *p++ = '-'; /* No castling rights */
13662 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
13663 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier ) {
13664 /* En passant target square */
13665 if (move > backwardMostMove) {
13666 fromX = moveList[move - 1][0] - AAA;
13667 fromY = moveList[move - 1][1] - ONE;
13668 toX = moveList[move - 1][2] - AAA;
13669 toY = moveList[move - 1][3] - ONE;
13670 if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
13671 toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
13672 boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
13674 /* 2-square pawn move just happened */
13676 *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
13680 } else if(move == backwardMostMove) {
13681 // [HGM] perhaps we should always do it like this, and forget the above?
13682 if(epStatus[move] >= 0) {
13683 *p++ = epStatus[move] + AAA;
13684 *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
13695 /* [HGM] find reversible plies */
13696 { int i = 0, j=move;
13698 if (appData.debugMode) { int k;
13699 fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
13700 for(k=backwardMostMove; k<=forwardMostMove; k++)
13701 fprintf(debugFP, "e%d. p=%d\n", k, epStatus[k]);
13705 while(j > backwardMostMove && epStatus[j] <= EP_NONE) j--,i++;
13706 if( j == backwardMostMove ) i += initialRulePlies;
13707 sprintf(p, "%d ", i);
13708 p += i>=100 ? 4 : i >= 10 ? 3 : 2;
13710 /* Fullmove number */
13711 sprintf(p, "%d", (move / 2) + 1);
13713 return StrSave(buf);
13717 ParseFEN(board, blackPlaysFirst, fen)
13719 int *blackPlaysFirst;
13729 /* [HGM] by default clear Crazyhouse holdings, if present */
13730 if(gameInfo.holdingsWidth) {
13731 for(i=0; i<BOARD_HEIGHT; i++) {
13732 board[i][0] = EmptySquare; /* black holdings */
13733 board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
13734 board[i][1] = (ChessSquare) 0; /* black counts */
13735 board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
13739 /* Piece placement data */
13740 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13743 if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
13744 if (*p == '/') p++;
13745 emptycount = gameInfo.boardWidth - j;
13746 while (emptycount--)
13747 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13749 #if(BOARD_SIZE >= 10)
13750 } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
13751 p++; emptycount=10;
13752 if (j + emptycount > gameInfo.boardWidth) return FALSE;
13753 while (emptycount--)
13754 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13756 } else if (isdigit(*p)) {
13757 emptycount = *p++ - '0';
13758 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
13759 if (j + emptycount > gameInfo.boardWidth) return FALSE;
13760 while (emptycount--)
13761 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13762 } else if (*p == '+' || isalpha(*p)) {
13763 if (j >= gameInfo.boardWidth) return FALSE;
13765 piece = CharToPiece(*++p);
13766 if(piece == EmptySquare) return FALSE; /* unknown piece */
13767 piece = (ChessSquare) (PROMOTED piece ); p++;
13768 if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
13769 } else piece = CharToPiece(*p++);
13771 if(piece==EmptySquare) return FALSE; /* unknown piece */
13772 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
13773 piece = (ChessSquare) (PROMOTED piece);
13774 if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
13777 board[i][(j++)+gameInfo.holdingsWidth] = piece;
13783 while (*p == '/' || *p == ' ') p++;
13785 /* [HGM] look for Crazyhouse holdings here */
13786 while(*p==' ') p++;
13787 if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
13789 if(*p == '-' ) *p++; /* empty holdings */ else {
13790 if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
13791 /* if we would allow FEN reading to set board size, we would */
13792 /* have to add holdings and shift the board read so far here */
13793 while( (piece = CharToPiece(*p) ) != EmptySquare ) {
13795 if((int) piece >= (int) BlackPawn ) {
13796 i = (int)piece - (int)BlackPawn;
13797 i = PieceToNumber((ChessSquare)i);
13798 if( i >= gameInfo.holdingsSize ) return FALSE;
13799 board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
13800 board[BOARD_HEIGHT-1-i][1]++; /* black counts */
13802 i = (int)piece - (int)WhitePawn;
13803 i = PieceToNumber((ChessSquare)i);
13804 if( i >= gameInfo.holdingsSize ) return FALSE;
13805 board[i][BOARD_WIDTH-1] = piece; /* white holdings */
13806 board[i][BOARD_WIDTH-2]++; /* black holdings */
13810 if(*p == ']') *p++;
13813 while(*p == ' ') p++;
13818 *blackPlaysFirst = FALSE;
13821 *blackPlaysFirst = TRUE;
13827 /* [HGM] We NO LONGER ignore the rest of the FEN notation */
13828 /* return the extra info in global variiables */
13830 /* set defaults in case FEN is incomplete */
13831 FENepStatus = EP_UNKNOWN;
13832 for(i=0; i<nrCastlingRights; i++ ) {
13833 FENcastlingRights[i] =
13834 gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? -1 : initialRights[i];
13835 } /* assume possible unless obviously impossible */
13836 if(initialRights[0]>=0 && board[castlingRank[0]][initialRights[0]] != WhiteRook) FENcastlingRights[0] = -1;
13837 if(initialRights[1]>=0 && board[castlingRank[1]][initialRights[1]] != WhiteRook) FENcastlingRights[1] = -1;
13838 if(initialRights[2]>=0 && board[castlingRank[2]][initialRights[2]] != WhiteKing) FENcastlingRights[2] = -1;
13839 if(initialRights[3]>=0 && board[castlingRank[3]][initialRights[3]] != BlackRook) FENcastlingRights[3] = -1;
13840 if(initialRights[4]>=0 && board[castlingRank[4]][initialRights[4]] != BlackRook) FENcastlingRights[4] = -1;
13841 if(initialRights[5]>=0 && board[castlingRank[5]][initialRights[5]] != BlackKing) FENcastlingRights[5] = -1;
13844 while(*p==' ') p++;
13845 if(nrCastlingRights) {
13846 if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
13847 /* castling indicator present, so default becomes no castlings */
13848 for(i=0; i<nrCastlingRights; i++ ) {
13849 FENcastlingRights[i] = -1;
13852 while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
13853 (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
13854 ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
13855 ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth) ) {
13856 char c = *p++; int whiteKingFile=-1, blackKingFile=-1;
13858 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
13859 if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
13860 if(board[0 ][i] == WhiteKing) whiteKingFile = i;
13864 for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
13865 FENcastlingRights[0] = i != whiteKingFile ? i : -1;
13866 FENcastlingRights[2] = whiteKingFile;
13869 for(i=BOARD_LEFT; board[0][i]!=WhiteRook && i<whiteKingFile; i++);
13870 FENcastlingRights[1] = i != whiteKingFile ? i : -1;
13871 FENcastlingRights[2] = whiteKingFile;
13874 for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
13875 FENcastlingRights[3] = i != blackKingFile ? i : -1;
13876 FENcastlingRights[5] = blackKingFile;
13879 for(i=BOARD_LEFT; board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
13880 FENcastlingRights[4] = i != blackKingFile ? i : -1;
13881 FENcastlingRights[5] = blackKingFile;
13884 default: /* FRC castlings */
13885 if(c >= 'a') { /* black rights */
13886 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
13887 if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
13888 if(i == BOARD_RGHT) break;
13889 FENcastlingRights[5] = i;
13891 if(board[BOARD_HEIGHT-1][c] < BlackPawn ||
13892 board[BOARD_HEIGHT-1][c] >= BlackKing ) break;
13894 FENcastlingRights[3] = c;
13896 FENcastlingRights[4] = c;
13897 } else { /* white rights */
13898 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
13899 if(board[0][i] == WhiteKing) break;
13900 if(i == BOARD_RGHT) break;
13901 FENcastlingRights[2] = i;
13902 c -= AAA - 'a' + 'A';
13903 if(board[0][c] >= WhiteKing) break;
13905 FENcastlingRights[0] = c;
13907 FENcastlingRights[1] = c;
13911 if (appData.debugMode) {
13912 fprintf(debugFP, "FEN castling rights:");
13913 for(i=0; i<nrCastlingRights; i++)
13914 fprintf(debugFP, " %d", FENcastlingRights[i]);
13915 fprintf(debugFP, "\n");
13918 while(*p==' ') p++;
13921 /* read e.p. field in games that know e.p. capture */
13922 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
13923 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier ) {
13925 p++; FENepStatus = EP_NONE;
13927 char c = *p++ - AAA;
13929 if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
13930 if(*p >= '0' && *p <='9') *p++;
13936 if(sscanf(p, "%d", &i) == 1) {
13937 FENrulePlies = i; /* 50-move ply counter */
13938 /* (The move number is still ignored) */
13945 EditPositionPasteFEN(char *fen)
13948 Board initial_position;
13950 if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
13951 DisplayError(_("Bad FEN position in clipboard"), 0);
13954 int savedBlackPlaysFirst = blackPlaysFirst;
13955 EditPositionEvent();
13956 blackPlaysFirst = savedBlackPlaysFirst;
13957 CopyBoard(boards[0], initial_position);
13958 /* [HGM] copy FEN attributes as well */
13960 initialRulePlies = FENrulePlies;
13961 epStatus[0] = FENepStatus;
13962 for( i=0; i<nrCastlingRights; i++ )
13963 castlingRights[0][i] = FENcastlingRights[i];
13965 EditPositionDone();
13966 DisplayBothClocks();
13967 DrawPosition(FALSE, boards[currentMove]);