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 srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [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 set_cont_sequence(appData.wrapContSeq);
1046 if (appData.matchGames > 0) {
1047 appData.matchMode = TRUE;
1048 } else if (appData.matchMode) {
1049 appData.matchGames = 1;
1051 if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1052 appData.matchGames = appData.sameColorGames;
1053 if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1054 if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1055 if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1058 if (appData.noChessProgram || first.protocolVersion == 1) {
1061 /* kludge: allow timeout for initial "feature" commands */
1063 DisplayMessage("", _("Starting chess program"));
1064 ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1069 InitBackEnd3 P((void))
1071 GameMode initialMode;
1075 InitChessProgram(&first, startedFromSetupPosition);
1078 if (appData.icsActive) {
1080 /* [DM] Make a console window if needed [HGM] merged ifs */
1085 if (*appData.icsCommPort != NULLCHAR) {
1086 sprintf(buf, _("Could not open comm port %s"),
1087 appData.icsCommPort);
1089 snprintf(buf, sizeof(buf), _("Could not connect to host %s, port %s"),
1090 appData.icsHost, appData.icsPort);
1092 DisplayFatalError(buf, err, 1);
1097 AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1099 AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1100 } else if (appData.noChessProgram) {
1106 if (*appData.cmailGameName != NULLCHAR) {
1108 OpenLoopback(&cmailPR);
1110 AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1114 DisplayMessage("", "");
1115 if (StrCaseCmp(appData.initialMode, "") == 0) {
1116 initialMode = BeginningOfGame;
1117 } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1118 initialMode = TwoMachinesPlay;
1119 } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1120 initialMode = AnalyzeFile;
1121 } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1122 initialMode = AnalyzeMode;
1123 } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1124 initialMode = MachinePlaysWhite;
1125 } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1126 initialMode = MachinePlaysBlack;
1127 } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1128 initialMode = EditGame;
1129 } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1130 initialMode = EditPosition;
1131 } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1132 initialMode = Training;
1134 sprintf(buf, _("Unknown initialMode %s"), appData.initialMode);
1135 DisplayFatalError(buf, 0, 2);
1139 if (appData.matchMode) {
1140 /* Set up machine vs. machine match */
1141 if (appData.noChessProgram) {
1142 DisplayFatalError(_("Can't have a match with no chess programs"),
1148 if (*appData.loadGameFile != NULLCHAR) {
1149 int index = appData.loadGameIndex; // [HGM] autoinc
1150 if(index<0) lastIndex = index = 1;
1151 if (!LoadGameFromFile(appData.loadGameFile,
1153 appData.loadGameFile, FALSE)) {
1154 DisplayFatalError(_("Bad game file"), 0, 1);
1157 } else if (*appData.loadPositionFile != NULLCHAR) {
1158 int index = appData.loadPositionIndex; // [HGM] autoinc
1159 if(index<0) lastIndex = index = 1;
1160 if (!LoadPositionFromFile(appData.loadPositionFile,
1162 appData.loadPositionFile)) {
1163 DisplayFatalError(_("Bad position file"), 0, 1);
1168 } else if (*appData.cmailGameName != NULLCHAR) {
1169 /* Set up cmail mode */
1170 ReloadCmailMsgEvent(TRUE);
1172 /* Set up other modes */
1173 if (initialMode == AnalyzeFile) {
1174 if (*appData.loadGameFile == NULLCHAR) {
1175 DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1179 if (*appData.loadGameFile != NULLCHAR) {
1180 (void) LoadGameFromFile(appData.loadGameFile,
1181 appData.loadGameIndex,
1182 appData.loadGameFile, TRUE);
1183 } else if (*appData.loadPositionFile != NULLCHAR) {
1184 (void) LoadPositionFromFile(appData.loadPositionFile,
1185 appData.loadPositionIndex,
1186 appData.loadPositionFile);
1187 /* [HGM] try to make self-starting even after FEN load */
1188 /* to allow automatic setup of fairy variants with wtm */
1189 if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1190 gameMode = BeginningOfGame;
1191 setboardSpoiledMachineBlack = 1;
1193 /* [HGM] loadPos: make that every new game uses the setup */
1194 /* from file as long as we do not switch variant */
1195 if(!blackPlaysFirst) { int i;
1196 startedFromPositionFile = TRUE;
1197 CopyBoard(filePosition, boards[0]);
1198 for(i=0; i<BOARD_SIZE; i++) fileRights[i] = castlingRights[0][i];
1201 if (initialMode == AnalyzeMode) {
1202 if (appData.noChessProgram) {
1203 DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1206 if (appData.icsActive) {
1207 DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1211 } else if (initialMode == AnalyzeFile) {
1212 appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1213 ShowThinkingEvent();
1215 AnalysisPeriodicEvent(1);
1216 } else if (initialMode == MachinePlaysWhite) {
1217 if (appData.noChessProgram) {
1218 DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1222 if (appData.icsActive) {
1223 DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1227 MachineWhiteEvent();
1228 } else if (initialMode == MachinePlaysBlack) {
1229 if (appData.noChessProgram) {
1230 DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1234 if (appData.icsActive) {
1235 DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1239 MachineBlackEvent();
1240 } else if (initialMode == TwoMachinesPlay) {
1241 if (appData.noChessProgram) {
1242 DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1246 if (appData.icsActive) {
1247 DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1252 } else if (initialMode == EditGame) {
1254 } else if (initialMode == EditPosition) {
1255 EditPositionEvent();
1256 } else if (initialMode == Training) {
1257 if (*appData.loadGameFile == NULLCHAR) {
1258 DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1267 * Establish will establish a contact to a remote host.port.
1268 * Sets icsPR to a ProcRef for a process (or pseudo-process)
1269 * used to talk to the host.
1270 * Returns 0 if okay, error code if not.
1277 if (*appData.icsCommPort != NULLCHAR) {
1278 /* Talk to the host through a serial comm port */
1279 return OpenCommPort(appData.icsCommPort, &icsPR);
1281 } else if (*appData.gateway != NULLCHAR) {
1282 if (*appData.remoteShell == NULLCHAR) {
1283 /* Use the rcmd protocol to run telnet program on a gateway host */
1284 snprintf(buf, sizeof(buf), "%s %s %s",
1285 appData.telnetProgram, appData.icsHost, appData.icsPort);
1286 return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1289 /* Use the rsh program to run telnet program on a gateway host */
1290 if (*appData.remoteUser == NULLCHAR) {
1291 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1292 appData.gateway, appData.telnetProgram,
1293 appData.icsHost, appData.icsPort);
1295 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1296 appData.remoteShell, appData.gateway,
1297 appData.remoteUser, appData.telnetProgram,
1298 appData.icsHost, appData.icsPort);
1300 return StartChildProcess(buf, "", &icsPR);
1303 } else if (appData.useTelnet) {
1304 return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1307 /* TCP socket interface differs somewhat between
1308 Unix and NT; handle details in the front end.
1310 return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1315 show_bytes(fp, buf, count)
1321 if (*buf < 040 || *(unsigned char *) buf > 0177) {
1322 fprintf(fp, "\\%03o", *buf & 0xff);
1331 /* Returns an errno value */
1333 OutputMaybeTelnet(pr, message, count, outError)
1339 char buf[8192], *p, *q, *buflim;
1340 int left, newcount, outcount;
1342 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1343 *appData.gateway != NULLCHAR) {
1344 if (appData.debugMode) {
1345 fprintf(debugFP, ">ICS: ");
1346 show_bytes(debugFP, message, count);
1347 fprintf(debugFP, "\n");
1349 return OutputToProcess(pr, message, count, outError);
1352 buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1359 if (appData.debugMode) {
1360 fprintf(debugFP, ">ICS: ");
1361 show_bytes(debugFP, buf, newcount);
1362 fprintf(debugFP, "\n");
1364 outcount = OutputToProcess(pr, buf, newcount, outError);
1365 if (outcount < newcount) return -1; /* to be sure */
1372 } else if (((unsigned char) *p) == TN_IAC) {
1373 *q++ = (char) TN_IAC;
1380 if (appData.debugMode) {
1381 fprintf(debugFP, ">ICS: ");
1382 show_bytes(debugFP, buf, newcount);
1383 fprintf(debugFP, "\n");
1385 outcount = OutputToProcess(pr, buf, newcount, outError);
1386 if (outcount < newcount) return -1; /* to be sure */
1391 read_from_player(isr, closure, message, count, error)
1398 int outError, outCount;
1399 static int gotEof = 0;
1401 /* Pass data read from player on to ICS */
1404 outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1405 if (outCount < count) {
1406 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1408 } else if (count < 0) {
1409 RemoveInputSource(isr);
1410 DisplayFatalError(_("Error reading from keyboard"), error, 1);
1411 } else if (gotEof++ > 0) {
1412 RemoveInputSource(isr);
1413 DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1419 { // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1420 SendToICS("date\n");
1421 if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1424 /* added routine for printf style output to ics */
1425 void ics_printf(char *format, ...)
1427 char buffer[MSG_SIZ];
1430 va_start(args, format);
1431 vsnprintf(buffer, sizeof(buffer), format, args);
1432 buffer[sizeof(buffer)-1] = '\0';
1441 int count, outCount, outError;
1443 if (icsPR == NULL) return;
1446 outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1447 if (outCount < count) {
1448 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1452 /* This is used for sending logon scripts to the ICS. Sending
1453 without a delay causes problems when using timestamp on ICC
1454 (at least on my machine). */
1456 SendToICSDelayed(s,msdelay)
1460 int count, outCount, outError;
1462 if (icsPR == NULL) return;
1465 if (appData.debugMode) {
1466 fprintf(debugFP, ">ICS: ");
1467 show_bytes(debugFP, s, count);
1468 fprintf(debugFP, "\n");
1470 outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1472 if (outCount < count) {
1473 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1478 /* Remove all highlighting escape sequences in s
1479 Also deletes any suffix starting with '('
1482 StripHighlightAndTitle(s)
1485 static char retbuf[MSG_SIZ];
1488 while (*s != NULLCHAR) {
1489 while (*s == '\033') {
1490 while (*s != NULLCHAR && !isalpha(*s)) s++;
1491 if (*s != NULLCHAR) s++;
1493 while (*s != NULLCHAR && *s != '\033') {
1494 if (*s == '(' || *s == '[') {
1505 /* Remove all highlighting escape sequences in s */
1510 static char retbuf[MSG_SIZ];
1513 while (*s != NULLCHAR) {
1514 while (*s == '\033') {
1515 while (*s != NULLCHAR && !isalpha(*s)) s++;
1516 if (*s != NULLCHAR) s++;
1518 while (*s != NULLCHAR && *s != '\033') {
1526 char *variantNames[] = VARIANT_NAMES;
1531 return variantNames[v];
1535 /* Identify a variant from the strings the chess servers use or the
1536 PGN Variant tag names we use. */
1543 VariantClass v = VariantNormal;
1544 int i, found = FALSE;
1549 /* [HGM] skip over optional board-size prefixes */
1550 if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1551 sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1552 while( *e++ != '_');
1555 if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
1559 for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
1560 if (StrCaseStr(e, variantNames[i])) {
1561 v = (VariantClass) i;
1568 if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
1569 || StrCaseStr(e, "wild/fr")
1570 || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
1571 v = VariantFischeRandom;
1572 } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
1573 (i = 1, p = StrCaseStr(e, "w"))) {
1575 while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
1582 case 0: /* FICS only, actually */
1584 /* Castling legal even if K starts on d-file */
1585 v = VariantWildCastle;
1590 /* Castling illegal even if K & R happen to start in
1591 normal positions. */
1592 v = VariantNoCastle;
1605 /* Castling legal iff K & R start in normal positions */
1611 /* Special wilds for position setup; unclear what to do here */
1612 v = VariantLoadable;
1615 /* Bizarre ICC game */
1616 v = VariantTwoKings;
1619 v = VariantKriegspiel;
1625 v = VariantFischeRandom;
1628 v = VariantCrazyhouse;
1631 v = VariantBughouse;
1637 /* Not quite the same as FICS suicide! */
1638 v = VariantGiveaway;
1644 v = VariantShatranj;
1647 /* Temporary names for future ICC types. The name *will* change in
1648 the next xboard/WinBoard release after ICC defines it. */
1686 v = VariantCapablanca;
1689 v = VariantKnightmate;
1695 v = VariantCylinder;
1701 v = VariantCapaRandom;
1704 v = VariantBerolina;
1716 /* Found "wild" or "w" in the string but no number;
1717 must assume it's normal chess. */
1721 sprintf(buf, _("Unknown wild type %d"), wnum);
1722 DisplayError(buf, 0);
1728 if (appData.debugMode) {
1729 fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
1730 e, wnum, VariantName(v));
1735 static int leftover_start = 0, leftover_len = 0;
1736 char star_match[STAR_MATCH_N][MSG_SIZ];
1738 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
1739 advance *index beyond it, and set leftover_start to the new value of
1740 *index; else return FALSE. If pattern contains the character '*', it
1741 matches any sequence of characters not containing '\r', '\n', or the
1742 character following the '*' (if any), and the matched sequence(s) are
1743 copied into star_match.
1746 looking_at(buf, index, pattern)
1751 char *bufp = &buf[*index], *patternp = pattern;
1753 char *matchp = star_match[0];
1756 if (*patternp == NULLCHAR) {
1757 *index = leftover_start = bufp - buf;
1761 if (*bufp == NULLCHAR) return FALSE;
1762 if (*patternp == '*') {
1763 if (*bufp == *(patternp + 1)) {
1765 matchp = star_match[++star_count];
1769 } else if (*bufp == '\n' || *bufp == '\r') {
1771 if (*patternp == NULLCHAR)
1776 *matchp++ = *bufp++;
1780 if (*patternp != *bufp) return FALSE;
1787 SendToPlayer(data, length)
1791 int error, outCount;
1792 outCount = OutputToProcess(NoProc, data, length, &error);
1793 if (outCount < length) {
1794 DisplayFatalError(_("Error writing to display"), error, 1);
1799 PackHolding(packed, holding)
1811 switch (runlength) {
1822 sprintf(q, "%d", runlength);
1834 /* Telnet protocol requests from the front end */
1836 TelnetRequest(ddww, option)
1837 unsigned char ddww, option;
1839 unsigned char msg[3];
1840 int outCount, outError;
1842 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
1844 if (appData.debugMode) {
1845 char buf1[8], buf2[8], *ddwwStr, *optionStr;
1861 sprintf(buf1, "%d", ddww);
1870 sprintf(buf2, "%d", option);
1873 fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
1878 outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
1880 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1887 if (!appData.icsActive) return;
1888 TelnetRequest(TN_DO, TN_ECHO);
1894 if (!appData.icsActive) return;
1895 TelnetRequest(TN_DONT, TN_ECHO);
1899 CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece)
1901 /* put the holdings sent to us by the server on the board holdings area */
1902 int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
1906 if(gameInfo.holdingsWidth < 2) return;
1907 if(gameInfo.variant != VariantBughouse && board[BOARD_SIZE-1][BOARD_SIZE-2])
1908 return; // prevent overwriting by pre-board holdings
1910 if( (int)lowestPiece >= BlackPawn ) {
1913 holdingsStartRow = BOARD_HEIGHT-1;
1916 holdingsColumn = BOARD_WIDTH-1;
1917 countsColumn = BOARD_WIDTH-2;
1918 holdingsStartRow = 0;
1922 for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
1923 board[i][holdingsColumn] = EmptySquare;
1924 board[i][countsColumn] = (ChessSquare) 0;
1926 while( (p=*holdings++) != NULLCHAR ) {
1927 piece = CharToPiece( ToUpper(p) );
1928 if(piece == EmptySquare) continue;
1929 /*j = (int) piece - (int) WhitePawn;*/
1930 j = PieceToNumber(piece);
1931 if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
1932 if(j < 0) continue; /* should not happen */
1933 piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
1934 board[holdingsStartRow+j*direction][holdingsColumn] = piece;
1935 board[holdingsStartRow+j*direction][countsColumn]++;
1941 VariantSwitch(Board board, VariantClass newVariant)
1943 int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
1945 startedFromPositionFile = FALSE;
1946 if(gameInfo.variant == newVariant) return;
1948 /* [HGM] This routine is called each time an assignment is made to
1949 * gameInfo.variant during a game, to make sure the board sizes
1950 * are set to match the new variant. If that means adding or deleting
1951 * holdings, we shift the playing board accordingly
1952 * This kludge is needed because in ICS observe mode, we get boards
1953 * of an ongoing game without knowing the variant, and learn about the
1954 * latter only later. This can be because of the move list we requested,
1955 * in which case the game history is refilled from the beginning anyway,
1956 * but also when receiving holdings of a crazyhouse game. In the latter
1957 * case we want to add those holdings to the already received position.
1960 if (appData.debugMode) {
1961 fprintf(debugFP, "Switch board from %s to %s\n",
1962 VariantName(gameInfo.variant), VariantName(newVariant));
1963 setbuf(debugFP, NULL);
1965 shuffleOpenings = 0; /* [HGM] shuffle */
1966 gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
1970 newWidth = 9; newHeight = 9;
1971 gameInfo.holdingsSize = 7;
1972 case VariantBughouse:
1973 case VariantCrazyhouse:
1974 newHoldingsWidth = 2; break;
1978 newHoldingsWidth = 2;
1979 gameInfo.holdingsSize = 8;
1982 case VariantCapablanca:
1983 case VariantCapaRandom:
1986 newHoldingsWidth = gameInfo.holdingsSize = 0;
1989 if(newWidth != gameInfo.boardWidth ||
1990 newHeight != gameInfo.boardHeight ||
1991 newHoldingsWidth != gameInfo.holdingsWidth ) {
1993 /* shift position to new playing area, if needed */
1994 if(newHoldingsWidth > gameInfo.holdingsWidth) {
1995 for(i=0; i<BOARD_HEIGHT; i++)
1996 for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
1997 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
1999 for(i=0; i<newHeight; i++) {
2000 board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2001 board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2003 } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2004 for(i=0; i<BOARD_HEIGHT; i++)
2005 for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2006 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2009 gameInfo.boardWidth = newWidth;
2010 gameInfo.boardHeight = newHeight;
2011 gameInfo.holdingsWidth = newHoldingsWidth;
2012 gameInfo.variant = newVariant;
2013 InitDrawingSizes(-2, 0);
2014 InitPosition(FALSE); /* this sets up board[0], but also other stuff */
2015 } 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(TRUE, 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\n", appData.premoveWhiteText);
3237 if (appData.debugMode)
3238 fprintf(debugFP, "Sending premove:\n");
3240 } else if (currentMove == 1 &&
3241 gameMode == IcsPlayingBlack &&
3242 appData.premoveBlack) {
3243 sprintf(str, "%s\n", appData.premoveBlackText);
3244 if (appData.debugMode)
3245 fprintf(debugFP, "Sending premove:\n");
3247 } else if (gotPremove) {
3249 ClearPremoveHighlights();
3250 if (appData.debugMode)
3251 fprintf(debugFP, "Sending premove:\n");
3252 UserMoveEvent(premoveFromX, premoveFromY,
3253 premoveToX, premoveToY,
3258 /* Usually suppress following prompt */
3259 if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
3260 if (looking_at(buf, &i, "*% ")) {
3261 savingComment = FALSE;
3265 } else if (started == STARTED_HOLDINGS) {
3267 char new_piece[MSG_SIZ];
3268 started = STARTED_NONE;
3269 parse[parse_pos] = NULLCHAR;
3270 if (appData.debugMode)
3271 fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
3272 parse, currentMove);
3273 if (sscanf(parse, " game %d", &gamenum) == 1 &&
3274 gamenum == ics_gamenum) {
3275 if (gameInfo.variant == VariantNormal) {
3276 /* [HGM] We seem to switch variant during a game!
3277 * Presumably no holdings were displayed, so we have
3278 * to move the position two files to the right to
3279 * create room for them!
3281 VariantClass newVariant;
3282 switch(gameInfo.boardWidth) { // base guess on board width
3283 case 9: newVariant = VariantShogi; break;
3284 case 10: newVariant = VariantGreat; break;
3285 default: newVariant = VariantCrazyhouse; break;
3287 VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3288 /* Get a move list just to see the header, which
3289 will tell us whether this is really bug or zh */
3290 if (ics_getting_history == H_FALSE) {
3291 ics_getting_history = H_REQUESTED;
3292 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3296 new_piece[0] = NULLCHAR;
3297 sscanf(parse, "game %d white [%s black [%s <- %s",
3298 &gamenum, white_holding, black_holding,
3300 white_holding[strlen(white_holding)-1] = NULLCHAR;
3301 black_holding[strlen(black_holding)-1] = NULLCHAR;
3302 /* [HGM] copy holdings to board holdings area */
3303 CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
3304 CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
3305 boards[forwardMostMove][BOARD_SIZE-1][BOARD_SIZE-2] = 1; // flag holdings as set
3307 if (appData.zippyPlay && first.initDone) {
3308 ZippyHoldings(white_holding, black_holding,
3312 if (tinyLayout || smallLayout) {
3313 char wh[16], bh[16];
3314 PackHolding(wh, white_holding);
3315 PackHolding(bh, black_holding);
3316 sprintf(str, "[%s-%s] %s-%s", wh, bh,
3317 gameInfo.white, gameInfo.black);
3319 sprintf(str, "%s [%s] vs. %s [%s]",
3320 gameInfo.white, white_holding,
3321 gameInfo.black, black_holding);
3324 DrawPosition(FALSE, boards[currentMove]);
3327 /* Suppress following prompt */
3328 if (looking_at(buf, &i, "*% ")) {
3329 savingComment = FALSE;
3336 i++; /* skip unparsed character and loop back */
3339 if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz suppress printing in ICS interaction window
3340 started != STARTED_HOLDINGS && i > next_out) {
3341 SendToPlayer(&buf[next_out], i - next_out);
3344 suppressKibitz = FALSE; // [HGM] kibitz: has done its duty in if-statement above
3346 leftover_len = buf_len - leftover_start;
3347 /* if buffer ends with something we couldn't parse,
3348 reparse it after appending the next read */
3350 } else if (count == 0) {
3351 RemoveInputSource(isr);
3352 DisplayFatalError(_("Connection closed by ICS"), 0, 0);
3354 DisplayFatalError(_("Error reading from ICS"), error, 1);
3359 /* Board style 12 looks like this:
3361 <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
3363 * The "<12> " is stripped before it gets to this routine. The two
3364 * trailing 0's (flip state and clock ticking) are later addition, and
3365 * some chess servers may not have them, or may have only the first.
3366 * Additional trailing fields may be added in the future.
3369 #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"
3371 #define RELATION_OBSERVING_PLAYED 0
3372 #define RELATION_OBSERVING_STATIC -2 /* examined, oldmoves, or smoves */
3373 #define RELATION_PLAYING_MYMOVE 1
3374 #define RELATION_PLAYING_NOTMYMOVE -1
3375 #define RELATION_EXAMINING 2
3376 #define RELATION_ISOLATED_BOARD -3
3377 #define RELATION_STARTING_POSITION -4 /* FICS only */
3380 ParseBoard12(string)
3383 GameMode newGameMode;
3384 int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
3385 int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
3386 int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
3387 char to_play, board_chars[200];
3388 char move_str[500], str[500], elapsed_time[500];
3389 char black[32], white[32];
3391 int prevMove = currentMove;
3394 int fromX, fromY, toX, toY;
3396 int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
3397 char *bookHit = NULL; // [HGM] book
3398 Boolean weird = FALSE;
3400 fromX = fromY = toX = toY = -1;
3404 if (appData.debugMode)
3405 fprintf(debugFP, _("Parsing board: %s\n"), string);
3407 move_str[0] = NULLCHAR;
3408 elapsed_time[0] = NULLCHAR;
3409 { /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
3411 while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
3412 if(string[i] == ' ') { ranks++; files = 0; }
3414 if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
3417 for(j = 0; j <i; j++) board_chars[j] = string[j];
3418 board_chars[i] = '\0';
3421 n = sscanf(string, PATTERN, &to_play, &double_push,
3422 &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
3423 &gamenum, white, black, &relation, &basetime, &increment,
3424 &white_stren, &black_stren, &white_time, &black_time,
3425 &moveNum, str, elapsed_time, move_str, &ics_flip,
3428 if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
3429 weird && (int)gameInfo.variant <= (int)VariantShogi) {
3430 /* [HGM] We seem to switch variant during a game!
3431 * Try to guess new variant from board size
3433 VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
3434 if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
3435 if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
3436 if(ranks == 8 && files == 12) newVariant = VariantCourier; else
3437 if(ranks == 9 && files == 9) newVariant = VariantShogi; else
3438 if(!weird) newVariant = VariantNormal;
3439 VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3440 /* Get a move list just to see the header, which
3441 will tell us whether this is really bug or zh */
3442 if (ics_getting_history == H_FALSE) {
3443 ics_getting_history = H_REQUESTED;
3444 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3450 snprintf(str, sizeof(str), _("Failed to parse board string:\n\"%s\""), string);
3451 DisplayError(str, 0);
3455 /* Convert the move number to internal form */
3456 moveNum = (moveNum - 1) * 2;
3457 if (to_play == 'B') moveNum++;
3458 if (moveNum >= MAX_MOVES) {
3459 DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
3465 case RELATION_OBSERVING_PLAYED:
3466 case RELATION_OBSERVING_STATIC:
3467 if (gamenum == -1) {
3468 /* Old ICC buglet */
3469 relation = RELATION_OBSERVING_STATIC;
3471 newGameMode = IcsObserving;
3473 case RELATION_PLAYING_MYMOVE:
3474 case RELATION_PLAYING_NOTMYMOVE:
3476 ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
3477 IcsPlayingWhite : IcsPlayingBlack;
3479 case RELATION_EXAMINING:
3480 newGameMode = IcsExamining;
3482 case RELATION_ISOLATED_BOARD:
3484 /* Just display this board. If user was doing something else,
3485 we will forget about it until the next board comes. */
3486 newGameMode = IcsIdle;
3488 case RELATION_STARTING_POSITION:
3489 newGameMode = gameMode;
3493 /* Modify behavior for initial board display on move listing
3496 switch (ics_getting_history) {
3500 case H_GOT_REQ_HEADER:
3501 case H_GOT_UNREQ_HEADER:
3502 /* This is the initial position of the current game */
3503 gamenum = ics_gamenum;
3504 moveNum = 0; /* old ICS bug workaround */
3505 if (to_play == 'B') {
3506 startedFromSetupPosition = TRUE;
3507 blackPlaysFirst = TRUE;
3509 if (forwardMostMove == 0) forwardMostMove = 1;
3510 if (backwardMostMove == 0) backwardMostMove = 1;
3511 if (currentMove == 0) currentMove = 1;
3513 newGameMode = gameMode;
3514 relation = RELATION_STARTING_POSITION; /* ICC needs this */
3516 case H_GOT_UNWANTED_HEADER:
3517 /* This is an initial board that we don't want */
3519 case H_GETTING_MOVES:
3520 /* Should not happen */
3521 DisplayError(_("Error gathering move list: extra board"), 0);
3522 ics_getting_history = H_FALSE;
3526 /* Take action if this is the first board of a new game, or of a
3527 different game than is currently being displayed. */
3528 if (gamenum != ics_gamenum || newGameMode != gameMode ||
3529 relation == RELATION_ISOLATED_BOARD) {
3531 /* Forget the old game and get the history (if any) of the new one */
3532 if (gameMode != BeginningOfGame) {
3536 if (appData.autoRaiseBoard) BoardToTop();
3538 if (gamenum == -1) {
3539 newGameMode = IcsIdle;
3540 } else if (moveNum > 0 && newGameMode != IcsIdle &&
3541 appData.getMoveList) {
3542 /* Need to get game history */
3543 ics_getting_history = H_REQUESTED;
3544 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3548 /* Initially flip the board to have black on the bottom if playing
3549 black or if the ICS flip flag is set, but let the user change
3550 it with the Flip View button. */
3551 flipView = appData.autoFlipView ?
3552 (newGameMode == IcsPlayingBlack) || ics_flip :
3555 /* Done with values from previous mode; copy in new ones */
3556 gameMode = newGameMode;
3558 ics_gamenum = gamenum;
3559 if (gamenum == gs_gamenum) {
3560 int klen = strlen(gs_kind);
3561 if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
3562 sprintf(str, "ICS %s", gs_kind);
3563 gameInfo.event = StrSave(str);
3565 gameInfo.event = StrSave("ICS game");
3567 gameInfo.site = StrSave(appData.icsHost);
3568 gameInfo.date = PGNDate();
3569 gameInfo.round = StrSave("-");
3570 gameInfo.white = StrSave(white);
3571 gameInfo.black = StrSave(black);
3572 timeControl = basetime * 60 * 1000;
3574 timeIncrement = increment * 1000;
3575 movesPerSession = 0;
3576 gameInfo.timeControl = TimeControlTagValue();
3577 VariantSwitch(board, StringToVariant(gameInfo.event) );
3578 if (appData.debugMode) {
3579 fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
3580 fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
3581 setbuf(debugFP, NULL);
3584 gameInfo.outOfBook = NULL;
3586 /* Do we have the ratings? */
3587 if (strcmp(player1Name, white) == 0 &&
3588 strcmp(player2Name, black) == 0) {
3589 if (appData.debugMode)
3590 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3591 player1Rating, player2Rating);
3592 gameInfo.whiteRating = player1Rating;
3593 gameInfo.blackRating = player2Rating;
3594 } else if (strcmp(player2Name, white) == 0 &&
3595 strcmp(player1Name, black) == 0) {
3596 if (appData.debugMode)
3597 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3598 player2Rating, player1Rating);
3599 gameInfo.whiteRating = player2Rating;
3600 gameInfo.blackRating = player1Rating;
3602 player1Name[0] = player2Name[0] = NULLCHAR;
3604 /* Silence shouts if requested */
3605 if (appData.quietPlay &&
3606 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
3607 SendToICS(ics_prefix);
3608 SendToICS("set shout 0\n");
3612 /* Deal with midgame name changes */
3614 if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
3615 if (gameInfo.white) free(gameInfo.white);
3616 gameInfo.white = StrSave(white);
3618 if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
3619 if (gameInfo.black) free(gameInfo.black);
3620 gameInfo.black = StrSave(black);
3624 /* Throw away game result if anything actually changes in examine mode */
3625 if (gameMode == IcsExamining && !newGame) {
3626 gameInfo.result = GameUnfinished;
3627 if (gameInfo.resultDetails != NULL) {
3628 free(gameInfo.resultDetails);
3629 gameInfo.resultDetails = NULL;
3633 /* In pausing && IcsExamining mode, we ignore boards coming
3634 in if they are in a different variation than we are. */
3635 if (pauseExamInvalid) return;
3636 if (pausing && gameMode == IcsExamining) {
3637 if (moveNum <= pauseExamForwardMostMove) {
3638 pauseExamInvalid = TRUE;
3639 forwardMostMove = pauseExamForwardMostMove;
3644 if (appData.debugMode) {
3645 fprintf(debugFP, "load %dx%d board\n", files, ranks);
3647 /* Parse the board */
3648 for (k = 0; k < ranks; k++) {
3649 for (j = 0; j < files; j++)
3650 board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
3651 if(gameInfo.holdingsWidth > 1) {
3652 board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
3653 board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
3656 CopyBoard(boards[moveNum], board);
3657 boards[moveNum][BOARD_SIZE-1][BOARD_SIZE-2] = 0; // [HGM] indicate holdings not set
3659 startedFromSetupPosition =
3660 !CompareBoards(board, initialPosition);
3661 if(startedFromSetupPosition)
3662 initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
3665 /* [HGM] Set castling rights. Take the outermost Rooks,
3666 to make it also work for FRC opening positions. Note that board12
3667 is really defective for later FRC positions, as it has no way to
3668 indicate which Rook can castle if they are on the same side of King.
3669 For the initial position we grant rights to the outermost Rooks,
3670 and remember thos rights, and we then copy them on positions
3671 later in an FRC game. This means WB might not recognize castlings with
3672 Rooks that have moved back to their original position as illegal,
3673 but in ICS mode that is not its job anyway.
3675 if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
3676 { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
3678 for(i=BOARD_LEFT, j= -1; i<BOARD_RGHT; i++)
3679 if(board[0][i] == WhiteRook) j = i;
3680 initialRights[0] = castlingRights[moveNum][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3681 for(i=BOARD_RGHT-1, j= -1; i>=BOARD_LEFT; i--)
3682 if(board[0][i] == WhiteRook) j = i;
3683 initialRights[1] = castlingRights[moveNum][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3684 for(i=BOARD_LEFT, j= -1; i<BOARD_RGHT; i++)
3685 if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
3686 initialRights[3] = castlingRights[moveNum][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3687 for(i=BOARD_RGHT-1, j= -1; i>=BOARD_LEFT; i--)
3688 if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
3689 initialRights[4] = castlingRights[moveNum][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3691 if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
3692 for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
3693 if(board[0][k] == wKing) initialRights[2] = castlingRights[moveNum][2] = k;
3694 for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
3695 if(board[BOARD_HEIGHT-1][k] == bKing)
3696 initialRights[5] = castlingRights[moveNum][5] = k;
3698 r = castlingRights[moveNum][0] = initialRights[0];
3699 if(board[0][r] != WhiteRook) castlingRights[moveNum][0] = -1;
3700 r = castlingRights[moveNum][1] = initialRights[1];
3701 if(board[0][r] != WhiteRook) castlingRights[moveNum][1] = -1;
3702 r = castlingRights[moveNum][3] = initialRights[3];
3703 if(board[BOARD_HEIGHT-1][r] != BlackRook) castlingRights[moveNum][3] = -1;
3704 r = castlingRights[moveNum][4] = initialRights[4];
3705 if(board[BOARD_HEIGHT-1][r] != BlackRook) castlingRights[moveNum][4] = -1;
3706 /* wildcastle kludge: always assume King has rights */
3707 r = castlingRights[moveNum][2] = initialRights[2];
3708 r = castlingRights[moveNum][5] = initialRights[5];
3710 /* [HGM] e.p. rights. Assume that ICS sends file number here? */
3711 epStatus[moveNum] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
3714 if (ics_getting_history == H_GOT_REQ_HEADER ||
3715 ics_getting_history == H_GOT_UNREQ_HEADER) {
3716 /* This was an initial position from a move list, not
3717 the current position */
3721 /* Update currentMove and known move number limits */
3722 newMove = newGame || moveNum > forwardMostMove;
3725 forwardMostMove = backwardMostMove = currentMove = moveNum;
3726 if (gameMode == IcsExamining && moveNum == 0) {
3727 /* Workaround for ICS limitation: we are not told the wild
3728 type when starting to examine a game. But if we ask for
3729 the move list, the move list header will tell us */
3730 ics_getting_history = H_REQUESTED;
3731 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3734 } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
3735 || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
3737 /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
3738 /* [HGM] applied this also to an engine that is silently watching */
3739 if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
3740 (gameMode == IcsObserving || gameMode == IcsExamining) &&
3741 gameInfo.variant == currentlyInitializedVariant) {
3742 takeback = forwardMostMove - moveNum;
3743 for (i = 0; i < takeback; i++) {
3744 if (appData.debugMode) fprintf(debugFP, "take back move\n");
3745 SendToProgram("undo\n", &first);
3750 forwardMostMove = moveNum;
3751 if (!pausing || currentMove > forwardMostMove)
3752 currentMove = forwardMostMove;
3754 /* New part of history that is not contiguous with old part */
3755 if (pausing && gameMode == IcsExamining) {
3756 pauseExamInvalid = TRUE;
3757 forwardMostMove = pauseExamForwardMostMove;
3760 if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
3762 if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
3763 // [HGM] when we will receive the move list we now request, it will be
3764 // fed to the engine from the first move on. So if the engine is not
3765 // in the initial position now, bring it there.
3766 InitChessProgram(&first, 0);
3769 ics_getting_history = H_REQUESTED;
3770 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3773 forwardMostMove = backwardMostMove = currentMove = moveNum;
3776 /* Update the clocks */
3777 if (strchr(elapsed_time, '.')) {
3779 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
3780 timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
3782 /* Time is in seconds */
3783 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
3784 timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
3789 if (appData.zippyPlay && newGame &&
3790 gameMode != IcsObserving && gameMode != IcsIdle &&
3791 gameMode != IcsExamining)
3792 ZippyFirstBoard(moveNum, basetime, increment);
3795 /* Put the move on the move list, first converting
3796 to canonical algebraic form. */
3798 if (appData.debugMode) {
3799 if (appData.debugMode) { int f = forwardMostMove;
3800 fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
3801 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
3803 fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
3804 fprintf(debugFP, "moveNum = %d\n", moveNum);
3805 fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
3806 setbuf(debugFP, NULL);
3808 if (moveNum <= backwardMostMove) {
3809 /* We don't know what the board looked like before
3811 strcpy(parseList[moveNum - 1], move_str);
3812 strcat(parseList[moveNum - 1], " ");
3813 strcat(parseList[moveNum - 1], elapsed_time);
3814 moveList[moveNum - 1][0] = NULLCHAR;
3815 } else if (strcmp(move_str, "none") == 0) {
3816 // [HGM] long SAN: swapped order; test for 'none' before parsing move
3817 /* Again, we don't know what the board looked like;
3818 this is really the start of the game. */
3819 parseList[moveNum - 1][0] = NULLCHAR;
3820 moveList[moveNum - 1][0] = NULLCHAR;
3821 backwardMostMove = moveNum;
3822 startedFromSetupPosition = TRUE;
3823 fromX = fromY = toX = toY = -1;
3825 // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
3826 // So we parse the long-algebraic move string in stead of the SAN move
3827 int valid; char buf[MSG_SIZ], *prom;
3829 // str looks something like "Q/a1-a2"; kill the slash
3831 sprintf(buf, "%c%s", str[0], str+2);
3832 else strcpy(buf, str); // might be castling
3833 if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
3834 strcat(buf, prom); // long move lacks promo specification!
3835 if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
3836 if(appData.debugMode)
3837 fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
3838 strcpy(move_str, buf);
3840 valid = ParseOneMove(move_str, moveNum - 1, &moveType,
3841 &fromX, &fromY, &toX, &toY, &promoChar)
3842 || ParseOneMove(buf, moveNum - 1, &moveType,
3843 &fromX, &fromY, &toX, &toY, &promoChar);
3844 // end of long SAN patch
3846 (void) CoordsToAlgebraic(boards[moveNum - 1],
3847 PosFlags(moveNum - 1), EP_UNKNOWN,
3848 fromY, fromX, toY, toX, promoChar,
3849 parseList[moveNum-1]);
3850 switch (MateTest(boards[moveNum], PosFlags(moveNum), EP_UNKNOWN,
3851 castlingRights[moveNum]) ) {
3857 if(gameInfo.variant != VariantShogi)
3858 strcat(parseList[moveNum - 1], "+");
3861 case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
3862 strcat(parseList[moveNum - 1], "#");
3865 strcat(parseList[moveNum - 1], " ");
3866 strcat(parseList[moveNum - 1], elapsed_time);
3867 /* currentMoveString is set as a side-effect of ParseOneMove */
3868 strcpy(moveList[moveNum - 1], currentMoveString);
3869 strcat(moveList[moveNum - 1], "\n");
3871 /* Move from ICS was illegal!? Punt. */
3872 if (appData.debugMode) {
3873 fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
3874 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
3876 strcpy(parseList[moveNum - 1], move_str);
3877 strcat(parseList[moveNum - 1], " ");
3878 strcat(parseList[moveNum - 1], elapsed_time);
3879 moveList[moveNum - 1][0] = NULLCHAR;
3880 fromX = fromY = toX = toY = -1;
3883 if (appData.debugMode) {
3884 fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
3885 setbuf(debugFP, NULL);
3889 /* Send move to chess program (BEFORE animating it). */
3890 if (appData.zippyPlay && !newGame && newMove &&
3891 (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
3893 if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
3894 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
3895 if (moveList[moveNum - 1][0] == NULLCHAR) {
3896 sprintf(str, _("Couldn't parse move \"%s\" from ICS"),
3898 DisplayError(str, 0);
3900 if (first.sendTime) {
3901 SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
3903 bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
3904 if (firstMove && !bookHit) {
3906 if (first.useColors) {
3907 SendToProgram(gameMode == IcsPlayingWhite ?
3909 "black\ngo\n", &first);
3911 SendToProgram("go\n", &first);
3913 first.maybeThinking = TRUE;
3916 } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
3917 if (moveList[moveNum - 1][0] == NULLCHAR) {
3918 sprintf(str, _("Couldn't parse move \"%s\" from ICS"), move_str);
3919 DisplayError(str, 0);
3921 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
3922 SendMoveToProgram(moveNum - 1, &first);
3929 if (moveNum > 0 && !gotPremove && !appData.noGUI) {
3930 /* If move comes from a remote source, animate it. If it
3931 isn't remote, it will have already been animated. */
3932 if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
3933 AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
3935 if (!pausing && appData.highlightLastMove) {
3936 SetHighlights(fromX, fromY, toX, toY);
3940 /* Start the clocks */
3941 whiteFlag = blackFlag = FALSE;
3942 appData.clockMode = !(basetime == 0 && increment == 0);
3944 ics_clock_paused = TRUE;
3946 } else if (ticking == 1) {
3947 ics_clock_paused = FALSE;
3949 if (gameMode == IcsIdle ||
3950 relation == RELATION_OBSERVING_STATIC ||
3951 relation == RELATION_EXAMINING ||
3953 DisplayBothClocks();
3957 /* Display opponents and material strengths */
3958 if (gameInfo.variant != VariantBughouse &&
3959 gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
3960 if (tinyLayout || smallLayout) {
3961 if(gameInfo.variant == VariantNormal)
3962 sprintf(str, "%s(%d) %s(%d) {%d %d}",
3963 gameInfo.white, white_stren, gameInfo.black, black_stren,
3964 basetime, increment);
3966 sprintf(str, "%s(%d) %s(%d) {%d %d w%d}",
3967 gameInfo.white, white_stren, gameInfo.black, black_stren,
3968 basetime, increment, (int) gameInfo.variant);
3970 if(gameInfo.variant == VariantNormal)
3971 sprintf(str, "%s (%d) vs. %s (%d) {%d %d}",
3972 gameInfo.white, white_stren, gameInfo.black, black_stren,
3973 basetime, increment);
3975 sprintf(str, "%s (%d) vs. %s (%d) {%d %d %s}",
3976 gameInfo.white, white_stren, gameInfo.black, black_stren,
3977 basetime, increment, VariantName(gameInfo.variant));
3980 if (appData.debugMode) {
3981 fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
3986 /* Display the board */
3987 if (!pausing && !appData.noGUI) {
3988 if (appData.premove)
3990 ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
3991 ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
3992 ClearPremoveHighlights();
3994 DrawPosition(FALSE, boards[currentMove]);
3995 DisplayMove(moveNum - 1);
3996 if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
3997 !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
3998 (gameMode == IcsPlayingBlack) && (WhiteOnMove(moveNum)) ) ) {
3999 if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4003 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4005 if(bookHit) { // [HGM] book: simulate book reply
4006 static char bookMove[MSG_SIZ]; // a bit generous?
4008 programStats.nodes = programStats.depth = programStats.time =
4009 programStats.score = programStats.got_only_move = 0;
4010 sprintf(programStats.movelist, "%s (xbook)", bookHit);
4012 strcpy(bookMove, "move ");
4013 strcat(bookMove, bookHit);
4014 HandleMachineMove(bookMove, &first);
4023 if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4024 ics_getting_history = H_REQUESTED;
4025 sprintf(buf, "%smoves %d\n", ics_prefix, ics_gamenum);
4031 AnalysisPeriodicEvent(force)
4034 if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4035 && !force) || !appData.periodicUpdates)
4038 /* Send . command to Crafty to collect stats */
4039 SendToProgram(".\n", &first);
4041 /* Don't send another until we get a response (this makes
4042 us stop sending to old Crafty's which don't understand
4043 the "." command (sending illegal cmds resets node count & time,
4044 which looks bad)) */
4045 programStats.ok_to_send = 0;
4048 void ics_update_width(new_width)
4051 ics_printf("set width %d\n", new_width);
4055 SendMoveToProgram(moveNum, cps)
4057 ChessProgramState *cps;
4061 if (cps->useUsermove) {
4062 SendToProgram("usermove ", cps);
4066 if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4067 int len = space - parseList[moveNum];
4068 memcpy(buf, parseList[moveNum], len);
4070 buf[len] = NULLCHAR;
4072 sprintf(buf, "%s\n", parseList[moveNum]);
4074 SendToProgram(buf, cps);
4076 if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4077 AlphaRank(moveList[moveNum], 4);
4078 SendToProgram(moveList[moveNum], cps);
4079 AlphaRank(moveList[moveNum], 4); // and back
4081 /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
4082 * the engine. It would be nice to have a better way to identify castle
4084 if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
4085 && cps->useOOCastle) {
4086 int fromX = moveList[moveNum][0] - AAA;
4087 int fromY = moveList[moveNum][1] - ONE;
4088 int toX = moveList[moveNum][2] - AAA;
4089 int toY = moveList[moveNum][3] - ONE;
4090 if((boards[moveNum][fromY][fromX] == WhiteKing
4091 && boards[moveNum][toY][toX] == WhiteRook)
4092 || (boards[moveNum][fromY][fromX] == BlackKing
4093 && boards[moveNum][toY][toX] == BlackRook)) {
4094 if(toX > fromX) SendToProgram("O-O\n", cps);
4095 else SendToProgram("O-O-O\n", cps);
4097 else SendToProgram(moveList[moveNum], cps);
4099 else SendToProgram(moveList[moveNum], cps);
4100 /* End of additions by Tord */
4103 /* [HGM] setting up the opening has brought engine in force mode! */
4104 /* Send 'go' if we are in a mode where machine should play. */
4105 if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4106 (gameMode == TwoMachinesPlay ||
4108 gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite ||
4110 gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4111 SendToProgram("go\n", cps);
4112 if (appData.debugMode) {
4113 fprintf(debugFP, "(extra)\n");
4116 setboardSpoiledMachineBlack = 0;
4120 SendMoveToICS(moveType, fromX, fromY, toX, toY)
4122 int fromX, fromY, toX, toY;
4124 char user_move[MSG_SIZ];
4128 sprintf(user_move, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4129 (int)moveType, fromX, fromY, toX, toY);
4130 DisplayError(user_move + strlen("say "), 0);
4132 case WhiteKingSideCastle:
4133 case BlackKingSideCastle:
4134 case WhiteQueenSideCastleWild:
4135 case BlackQueenSideCastleWild:
4137 case WhiteHSideCastleFR:
4138 case BlackHSideCastleFR:
4140 sprintf(user_move, "o-o\n");
4142 case WhiteQueenSideCastle:
4143 case BlackQueenSideCastle:
4144 case WhiteKingSideCastleWild:
4145 case BlackKingSideCastleWild:
4147 case WhiteASideCastleFR:
4148 case BlackASideCastleFR:
4150 sprintf(user_move, "o-o-o\n");
4152 case WhitePromotionQueen:
4153 case BlackPromotionQueen:
4154 case WhitePromotionRook:
4155 case BlackPromotionRook:
4156 case WhitePromotionBishop:
4157 case BlackPromotionBishop:
4158 case WhitePromotionKnight:
4159 case BlackPromotionKnight:
4160 case WhitePromotionKing:
4161 case BlackPromotionKing:
4162 case WhitePromotionChancellor:
4163 case BlackPromotionChancellor:
4164 case WhitePromotionArchbishop:
4165 case BlackPromotionArchbishop:
4166 if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier)
4167 sprintf(user_move, "%c%c%c%c=%c\n",
4168 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4169 PieceToChar(WhiteFerz));
4170 else if(gameInfo.variant == VariantGreat)
4171 sprintf(user_move, "%c%c%c%c=%c\n",
4172 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4173 PieceToChar(WhiteMan));
4175 sprintf(user_move, "%c%c%c%c=%c\n",
4176 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4177 PieceToChar(PromoPiece(moveType)));
4181 sprintf(user_move, "%c@%c%c\n",
4182 ToUpper(PieceToChar((ChessSquare) fromX)),
4183 AAA + toX, ONE + toY);
4186 case WhiteCapturesEnPassant:
4187 case BlackCapturesEnPassant:
4188 case IllegalMove: /* could be a variant we don't quite understand */
4189 sprintf(user_move, "%c%c%c%c\n",
4190 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4193 SendToICS(user_move);
4194 if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
4195 ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
4199 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
4204 if (rf == DROP_RANK) {
4205 sprintf(move, "%c@%c%c\n",
4206 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
4208 if (promoChar == 'x' || promoChar == NULLCHAR) {
4209 sprintf(move, "%c%c%c%c\n",
4210 AAA + ff, ONE + rf, AAA + ft, ONE + rt);
4212 sprintf(move, "%c%c%c%c%c\n",
4213 AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
4219 ProcessICSInitScript(f)
4224 while (fgets(buf, MSG_SIZ, f)) {
4225 SendToICSDelayed(buf,(long)appData.msLoginDelay);
4232 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
4234 AlphaRank(char *move, int n)
4236 // char *p = move, c; int x, y;
4238 if (appData.debugMode) {
4239 fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
4243 move[2]>='0' && move[2]<='9' &&
4244 move[3]>='a' && move[3]<='x' ) {
4246 move[2] = BOARD_RGHT -1 - (move[2]-'1') + AAA;
4247 move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4249 if(move[0]>='0' && move[0]<='9' &&
4250 move[1]>='a' && move[1]<='x' &&
4251 move[2]>='0' && move[2]<='9' &&
4252 move[3]>='a' && move[3]<='x' ) {
4253 /* input move, Shogi -> normal */
4254 move[0] = BOARD_RGHT -1 - (move[0]-'1') + AAA;
4255 move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
4256 move[2] = BOARD_RGHT -1 - (move[2]-'1') + AAA;
4257 move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4260 move[3]>='0' && move[3]<='9' &&
4261 move[2]>='a' && move[2]<='x' ) {
4263 move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4264 move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4267 move[0]>='a' && move[0]<='x' &&
4268 move[3]>='0' && move[3]<='9' &&
4269 move[2]>='a' && move[2]<='x' ) {
4270 /* output move, normal -> Shogi */
4271 move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
4272 move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
4273 move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4274 move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4275 if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
4277 if (appData.debugMode) {
4278 fprintf(debugFP, " out = '%s'\n", move);
4282 /* Parser for moves from gnuchess, ICS, or user typein box */
4284 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
4287 ChessMove *moveType;
4288 int *fromX, *fromY, *toX, *toY;
4291 if (appData.debugMode) {
4292 fprintf(debugFP, "move to parse: %s\n", move);
4294 *moveType = yylexstr(moveNum, move);
4296 switch (*moveType) {
4297 case WhitePromotionChancellor:
4298 case BlackPromotionChancellor:
4299 case WhitePromotionArchbishop:
4300 case BlackPromotionArchbishop:
4301 case WhitePromotionQueen:
4302 case BlackPromotionQueen:
4303 case WhitePromotionRook:
4304 case BlackPromotionRook:
4305 case WhitePromotionBishop:
4306 case BlackPromotionBishop:
4307 case WhitePromotionKnight:
4308 case BlackPromotionKnight:
4309 case WhitePromotionKing:
4310 case BlackPromotionKing:
4312 case WhiteCapturesEnPassant:
4313 case BlackCapturesEnPassant:
4314 case WhiteKingSideCastle:
4315 case WhiteQueenSideCastle:
4316 case BlackKingSideCastle:
4317 case BlackQueenSideCastle:
4318 case WhiteKingSideCastleWild:
4319 case WhiteQueenSideCastleWild:
4320 case BlackKingSideCastleWild:
4321 case BlackQueenSideCastleWild:
4322 /* Code added by Tord: */
4323 case WhiteHSideCastleFR:
4324 case WhiteASideCastleFR:
4325 case BlackHSideCastleFR:
4326 case BlackASideCastleFR:
4327 /* End of code added by Tord */
4328 case IllegalMove: /* bug or odd chess variant */
4329 *fromX = currentMoveString[0] - AAA;
4330 *fromY = currentMoveString[1] - ONE;
4331 *toX = currentMoveString[2] - AAA;
4332 *toY = currentMoveString[3] - ONE;
4333 *promoChar = currentMoveString[4];
4334 if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
4335 *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
4336 if (appData.debugMode) {
4337 fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
4339 *fromX = *fromY = *toX = *toY = 0;
4342 if (appData.testLegality) {
4343 return (*moveType != IllegalMove);
4345 return !(fromX == fromY && toX == toY);
4350 *fromX = *moveType == WhiteDrop ?
4351 (int) CharToPiece(ToUpper(currentMoveString[0])) :
4352 (int) CharToPiece(ToLower(currentMoveString[0]));
4354 *toX = currentMoveString[2] - AAA;
4355 *toY = currentMoveString[3] - ONE;
4356 *promoChar = NULLCHAR;
4360 case ImpossibleMove:
4361 case (ChessMove) 0: /* end of file */
4370 if (appData.debugMode) {
4371 fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
4374 *fromX = *fromY = *toX = *toY = 0;
4375 *promoChar = NULLCHAR;
4380 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
4381 // All positions will have equal probability, but the current method will not provide a unique
4382 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
4388 int piecesLeft[(int)BlackPawn];
4389 int seed, nrOfShuffles;
4391 void GetPositionNumber()
4392 { // sets global variable seed
4395 seed = appData.defaultFrcPosition;
4396 if(seed < 0) { // randomize based on time for negative FRC position numbers
4397 for(i=0; i<50; i++) seed += random();
4398 seed = random() ^ random() >> 8 ^ random() << 8;
4399 if(seed<0) seed = -seed;
4403 int put(Board board, int pieceType, int rank, int n, int shade)
4404 // put the piece on the (n-1)-th empty squares of the given shade
4408 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
4409 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
4410 board[rank][i] = (ChessSquare) pieceType;
4411 squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
4413 piecesLeft[pieceType]--;
4421 void AddOnePiece(Board board, int pieceType, int rank, int shade)
4422 // calculate where the next piece goes, (any empty square), and put it there
4426 i = seed % squaresLeft[shade];
4427 nrOfShuffles *= squaresLeft[shade];
4428 seed /= squaresLeft[shade];
4429 put(board, pieceType, rank, i, shade);
4432 void AddTwoPieces(Board board, int pieceType, int rank)
4433 // calculate where the next 2 identical pieces go, (any empty square), and put it there
4435 int i, n=squaresLeft[ANY], j=n-1, k;
4437 k = n*(n-1)/2; // nr of possibilities, not counting permutations
4438 i = seed % k; // pick one
4441 while(i >= j) i -= j--;
4442 j = n - 1 - j; i += j;
4443 put(board, pieceType, rank, j, ANY);
4444 put(board, pieceType, rank, i, ANY);
4447 void SetUpShuffle(Board board, int number)
4451 GetPositionNumber(); nrOfShuffles = 1;
4453 squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
4454 squaresLeft[ANY] = BOARD_RGHT - BOARD_LEFT;
4455 squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
4457 for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
4459 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
4460 p = (int) board[0][i];
4461 if(p < (int) BlackPawn) piecesLeft[p] ++;
4462 board[0][i] = EmptySquare;
4465 if(PosFlags(0) & F_ALL_CASTLE_OK) {
4466 // shuffles restricted to allow normal castling put KRR first
4467 if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
4468 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
4469 else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
4470 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
4471 if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
4472 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
4473 if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
4474 put(board, WhiteRook, 0, 0, ANY);
4475 // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
4478 if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
4479 // only for even boards make effort to put pairs of colorbound pieces on opposite colors
4480 for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
4481 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
4482 while(piecesLeft[p] >= 2) {
4483 AddOnePiece(board, p, 0, LITE);
4484 AddOnePiece(board, p, 0, DARK);
4486 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
4489 for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
4490 // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
4491 // but we leave King and Rooks for last, to possibly obey FRC restriction
4492 if(p == (int)WhiteRook) continue;
4493 while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
4494 if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY); // add the odd piece
4497 // now everything is placed, except perhaps King (Unicorn) and Rooks
4499 if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
4500 // Last King gets castling rights
4501 while(piecesLeft[(int)WhiteUnicorn]) {
4502 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
4503 initialRights[2] = initialRights[5] = castlingRights[0][2] = castlingRights[0][5] = i;
4506 while(piecesLeft[(int)WhiteKing]) {
4507 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
4508 initialRights[2] = initialRights[5] = castlingRights[0][2] = castlingRights[0][5] = i;
4513 while(piecesLeft[(int)WhiteKing]) AddOnePiece(board, WhiteKing, 0, ANY);
4514 while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
4517 // Only Rooks can be left; simply place them all
4518 while(piecesLeft[(int)WhiteRook]) {
4519 i = put(board, WhiteRook, 0, 0, ANY);
4520 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
4523 initialRights[1] = initialRights[4] = castlingRights[0][1] = castlingRights[0][4] = i;
4525 initialRights[0] = initialRights[3] = castlingRights[0][0] = castlingRights[0][3] = i;
4528 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
4529 board[BOARD_HEIGHT-1][i] = (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
4532 if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
4535 int SetCharTable( char *table, const char * map )
4536 /* [HGM] moved here from winboard.c because of its general usefulness */
4537 /* Basically a safe strcpy that uses the last character as King */
4539 int result = FALSE; int NrPieces;
4541 if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
4542 && NrPieces >= 12 && !(NrPieces&1)) {
4543 int i; /* [HGM] Accept even length from 12 to 34 */
4545 for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
4546 for( i=0; i<NrPieces/2-1; i++ ) {
4548 table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
4550 table[(int) WhiteKing] = map[NrPieces/2-1];
4551 table[(int) BlackKing] = map[NrPieces-1];
4559 void Prelude(Board board)
4560 { // [HGM] superchess: random selection of exo-pieces
4561 int i, j, k; ChessSquare p;
4562 static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
4564 GetPositionNumber(); // use FRC position number
4566 if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
4567 SetCharTable(pieceToChar, appData.pieceToCharTable);
4568 for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
4569 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
4572 j = seed%4; seed /= 4;
4573 p = board[0][BOARD_LEFT+j]; board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
4574 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
4575 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
4576 j = seed%3 + (seed%3 >= j); seed /= 3;
4577 p = board[0][BOARD_LEFT+j]; board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
4578 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
4579 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
4580 j = seed%3; seed /= 3;
4581 p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
4582 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
4583 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
4584 j = seed%2 + (seed%2 >= j); seed /= 2;
4585 p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
4586 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
4587 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
4588 j = seed%4; seed /= 4; put(board, exoPieces[3], 0, j, ANY);
4589 j = seed%3; seed /= 3; put(board, exoPieces[2], 0, j, ANY);
4590 j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
4591 put(board, exoPieces[0], 0, 0, ANY);
4592 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
4596 InitPosition(redraw)
4599 ChessSquare (* pieces)[BOARD_SIZE];
4600 int i, j, pawnRow, overrule,
4601 oldx = gameInfo.boardWidth,
4602 oldy = gameInfo.boardHeight,
4603 oldh = gameInfo.holdingsWidth,
4604 oldv = gameInfo.variant;
4606 if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
4608 /* [AS] Initialize pv info list [HGM] and game status */
4610 for( i=0; i<MAX_MOVES; i++ ) {
4611 pvInfoList[i].depth = 0;
4612 epStatus[i]=EP_NONE;
4613 for( j=0; j<BOARD_SIZE; j++ ) castlingRights[i][j] = -1;
4616 initialRulePlies = 0; /* 50-move counter start */
4618 castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
4619 castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
4623 /* [HGM] logic here is completely changed. In stead of full positions */
4624 /* the initialized data only consist of the two backranks. The switch */
4625 /* selects which one we will use, which is than copied to the Board */
4626 /* initialPosition, which for the rest is initialized by Pawns and */
4627 /* empty squares. This initial position is then copied to boards[0], */
4628 /* possibly after shuffling, so that it remains available. */
4630 gameInfo.holdingsWidth = 0; /* default board sizes */
4631 gameInfo.boardWidth = 8;
4632 gameInfo.boardHeight = 8;
4633 gameInfo.holdingsSize = 0;
4634 nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
4635 for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1; /* but no rights yet */
4636 SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
4638 switch (gameInfo.variant) {
4639 case VariantFischeRandom:
4640 shuffleOpenings = TRUE;
4644 case VariantShatranj:
4645 pieces = ShatranjArray;
4646 nrCastlingRights = 0;
4647 SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
4649 case VariantTwoKings:
4650 pieces = twoKingsArray;
4652 case VariantCapaRandom:
4653 shuffleOpenings = TRUE;
4654 case VariantCapablanca:
4655 pieces = CapablancaArray;
4656 gameInfo.boardWidth = 10;
4657 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
4660 pieces = GothicArray;
4661 gameInfo.boardWidth = 10;
4662 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
4665 pieces = JanusArray;
4666 gameInfo.boardWidth = 10;
4667 SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
4668 nrCastlingRights = 6;
4669 castlingRights[0][0] = initialRights[0] = BOARD_RGHT-1;
4670 castlingRights[0][1] = initialRights[1] = BOARD_LEFT;
4671 castlingRights[0][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
4672 castlingRights[0][3] = initialRights[3] = BOARD_RGHT-1;
4673 castlingRights[0][4] = initialRights[4] = BOARD_LEFT;
4674 castlingRights[0][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
4677 pieces = FalconArray;
4678 gameInfo.boardWidth = 10;
4679 SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
4681 case VariantXiangqi:
4682 pieces = XiangqiArray;
4683 gameInfo.boardWidth = 9;
4684 gameInfo.boardHeight = 10;
4685 nrCastlingRights = 0;
4686 SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
4689 pieces = ShogiArray;
4690 gameInfo.boardWidth = 9;
4691 gameInfo.boardHeight = 9;
4692 gameInfo.holdingsSize = 7;
4693 nrCastlingRights = 0;
4694 SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
4696 case VariantCourier:
4697 pieces = CourierArray;
4698 gameInfo.boardWidth = 12;
4699 nrCastlingRights = 0;
4700 SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
4701 for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1;
4703 case VariantKnightmate:
4704 pieces = KnightmateArray;
4705 SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
4708 pieces = fairyArray;
4709 SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVSLUKpnbrqfeacwmohijgdvsluk");
4712 pieces = GreatArray;
4713 gameInfo.boardWidth = 10;
4714 SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
4715 gameInfo.holdingsSize = 8;
4719 SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
4720 gameInfo.holdingsSize = 8;
4721 startedFromSetupPosition = TRUE;
4723 case VariantCrazyhouse:
4724 case VariantBughouse:
4726 SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
4727 gameInfo.holdingsSize = 5;
4729 case VariantWildCastle:
4731 /* !!?shuffle with kings guaranteed to be on d or e file */
4732 shuffleOpenings = 1;
4734 case VariantNoCastle:
4736 nrCastlingRights = 0;
4737 for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1;
4738 /* !!?unconstrained back-rank shuffle */
4739 shuffleOpenings = 1;
4744 if(appData.NrFiles >= 0) {
4745 if(gameInfo.boardWidth != appData.NrFiles) overrule++;
4746 gameInfo.boardWidth = appData.NrFiles;
4748 if(appData.NrRanks >= 0) {
4749 gameInfo.boardHeight = appData.NrRanks;
4751 if(appData.holdingsSize >= 0) {
4752 i = appData.holdingsSize;
4753 if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
4754 gameInfo.holdingsSize = i;
4756 if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
4757 if(BOARD_HEIGHT > BOARD_SIZE || BOARD_WIDTH > BOARD_SIZE)
4758 DisplayFatalError(_("Recompile to support this BOARD_SIZE!"), 0, 2);
4760 pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
4761 if(pawnRow < 1) pawnRow = 1;
4763 /* User pieceToChar list overrules defaults */
4764 if(appData.pieceToCharTable != NULL)
4765 SetCharTable(pieceToChar, appData.pieceToCharTable);
4767 for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
4769 if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
4770 s = (ChessSquare) 0; /* account holding counts in guard band */
4771 for( i=0; i<BOARD_HEIGHT; i++ )
4772 initialPosition[i][j] = s;
4774 if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
4775 initialPosition[0][j] = pieces[0][j-gameInfo.holdingsWidth];
4776 initialPosition[pawnRow][j] = WhitePawn;
4777 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = BlackPawn;
4778 if(gameInfo.variant == VariantXiangqi) {
4780 initialPosition[pawnRow][j] =
4781 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
4782 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
4783 initialPosition[2][j] = WhiteCannon;
4784 initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
4788 initialPosition[BOARD_HEIGHT-1][j] = pieces[1][j-gameInfo.holdingsWidth];
4790 if( (gameInfo.variant == VariantShogi) && !overrule ) {
4793 initialPosition[1][j] = WhiteBishop;
4794 initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
4796 initialPosition[1][j] = WhiteRook;
4797 initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
4800 if( nrCastlingRights == -1) {
4801 /* [HGM] Build normal castling rights (must be done after board sizing!) */
4802 /* This sets default castling rights from none to normal corners */
4803 /* Variants with other castling rights must set them themselves above */
4804 nrCastlingRights = 6;
4806 castlingRights[0][0] = initialRights[0] = BOARD_RGHT-1;
4807 castlingRights[0][1] = initialRights[1] = BOARD_LEFT;
4808 castlingRights[0][2] = initialRights[2] = BOARD_WIDTH>>1;
4809 castlingRights[0][3] = initialRights[3] = BOARD_RGHT-1;
4810 castlingRights[0][4] = initialRights[4] = BOARD_LEFT;
4811 castlingRights[0][5] = initialRights[5] = BOARD_WIDTH>>1;
4814 if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
4815 if(gameInfo.variant == VariantGreat) { // promotion commoners
4816 initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
4817 initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
4818 initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
4819 initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
4821 if (appData.debugMode) {
4822 fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
4824 if(shuffleOpenings) {
4825 SetUpShuffle(initialPosition, appData.defaultFrcPosition);
4826 startedFromSetupPosition = TRUE;
4828 if(startedFromPositionFile) {
4829 /* [HGM] loadPos: use PositionFile for every new game */
4830 CopyBoard(initialPosition, filePosition);
4831 for(i=0; i<nrCastlingRights; i++)
4832 castlingRights[0][i] = initialRights[i] = fileRights[i];
4833 startedFromSetupPosition = TRUE;
4836 CopyBoard(boards[0], initialPosition);
4837 if(oldx != gameInfo.boardWidth ||
4838 oldy != gameInfo.boardHeight ||
4839 oldh != gameInfo.holdingsWidth
4841 || oldv == VariantGothic || // For licensing popups
4842 gameInfo.variant == VariantGothic
4845 || oldv == VariantFalcon ||
4846 gameInfo.variant == VariantFalcon
4850 InitDrawingSizes(-2 ,0);
4854 DrawPosition(TRUE, boards[currentMove]);
4859 SendBoard(cps, moveNum)
4860 ChessProgramState *cps;
4863 char message[MSG_SIZ];
4865 if (cps->useSetboard) {
4866 char* fen = PositionToFEN(moveNum, cps->fenOverride);
4867 sprintf(message, "setboard %s\n", fen);
4868 SendToProgram(message, cps);
4874 /* Kludge to set black to move, avoiding the troublesome and now
4875 * deprecated "black" command.
4877 if (!WhiteOnMove(moveNum)) SendToProgram("a2a3\n", cps);
4879 SendToProgram("edit\n", cps);
4880 SendToProgram("#\n", cps);
4881 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
4882 bp = &boards[moveNum][i][BOARD_LEFT];
4883 for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
4884 if ((int) *bp < (int) BlackPawn) {
4885 sprintf(message, "%c%c%c\n", PieceToChar(*bp),
4887 if(message[0] == '+' || message[0] == '~') {
4888 sprintf(message, "%c%c%c+\n",
4889 PieceToChar((ChessSquare)(DEMOTED *bp)),
4892 if(cps->alphaRank) { /* [HGM] shogi: translate coords */
4893 message[1] = BOARD_RGHT - 1 - j + '1';
4894 message[2] = BOARD_HEIGHT - 1 - i + 'a';
4896 SendToProgram(message, cps);
4901 SendToProgram("c\n", cps);
4902 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
4903 bp = &boards[moveNum][i][BOARD_LEFT];
4904 for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
4905 if (((int) *bp != (int) EmptySquare)
4906 && ((int) *bp >= (int) BlackPawn)) {
4907 sprintf(message, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
4909 if(message[0] == '+' || message[0] == '~') {
4910 sprintf(message, "%c%c%c+\n",
4911 PieceToChar((ChessSquare)(DEMOTED *bp)),
4914 if(cps->alphaRank) { /* [HGM] shogi: translate coords */
4915 message[1] = BOARD_RGHT - 1 - j + '1';
4916 message[2] = BOARD_HEIGHT - 1 - i + 'a';
4918 SendToProgram(message, cps);
4923 SendToProgram(".\n", cps);
4925 setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
4929 HasPromotionChoice(int fromX, int fromY, int toX, int toY, char *promoChoice)
4931 /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
4932 /* [HGM] add Shogi promotions */
4933 int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
4938 if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
4939 if(toX < BOARD_LEFT || toX >= BOARD_RGHT) return FALSE; // move into holdings
4941 if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
4942 !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
4945 piece = boards[currentMove][fromY][fromX];
4946 if(gameInfo.variant == VariantShogi) {
4947 promotionZoneSize = 3;
4948 highestPromotingPiece = (int)WhiteFerz;
4951 // next weed out all moves that do not touch the promotion zone at all
4952 if((int)piece >= BlackPawn) {
4953 if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
4955 highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
4957 if( toY < BOARD_HEIGHT - promotionZoneSize &&
4958 fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
4961 if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
4963 // weed out mandatory Shogi promotions
4964 if(gameInfo.variant == VariantShogi) {
4965 if(piece >= BlackPawn) {
4966 if(toY == 0 && piece == BlackPawn ||
4967 toY == 0 && piece == BlackQueen ||
4968 toY <= 1 && piece == BlackKnight) {
4973 if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
4974 toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
4975 toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
4982 // weed out obviously illegal Pawn moves
4983 if(appData.testLegality && (piece == WhitePawn || piece == BlackPawn) ) {
4984 if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
4985 if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
4986 if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
4987 if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
4988 // note we are not allowed to test for valid (non-)capture, due to premove
4991 // we either have a choice what to promote to, or (in Shogi) whether to promote
4992 if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier) {
4993 *promoChoice = PieceToChar(BlackFerz); // no choice
4996 if(appData.alwaysPromoteToQueen) { // predetermined
4997 if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantLosers)
4998 *promoChoice = PieceToChar(BlackKing); // in Suicide Q is the last thing we want
4999 else *promoChoice = PieceToChar(BlackQueen);
5003 // suppress promotion popup on illegal moves that are not premoves
5004 premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
5005 gameMode == IcsPlayingBlack && WhiteOnMove(currentMove);
5006 if(appData.testLegality && !premove) {
5007 moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5008 epStatus[currentMove], castlingRights[currentMove],
5009 fromY, fromX, toY, toX, NULLCHAR);
5010 if(moveType != WhitePromotionQueen && moveType != BlackPromotionQueen &&
5011 moveType != WhitePromotionKnight && moveType != BlackPromotionKnight)
5019 InPalace(row, column)
5021 { /* [HGM] for Xiangqi */
5022 if( (row < 3 || row > BOARD_HEIGHT-4) &&
5023 column < (BOARD_WIDTH + 4)/2 &&
5024 column > (BOARD_WIDTH - 5)/2 ) return TRUE;
5029 PieceForSquare (x, y)
5033 if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
5036 return boards[currentMove][y][x];
5040 OKToStartUserMove(x, y)
5043 ChessSquare from_piece;
5046 if (matchMode) return FALSE;
5047 if (gameMode == EditPosition) return TRUE;
5049 if (x >= 0 && y >= 0)
5050 from_piece = boards[currentMove][y][x];
5052 from_piece = EmptySquare;
5054 if (from_piece == EmptySquare) return FALSE;
5056 white_piece = (int)from_piece >= (int)WhitePawn &&
5057 (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
5060 case PlayFromGameFile:
5062 case TwoMachinesPlay:
5070 case MachinePlaysWhite:
5071 case IcsPlayingBlack:
5072 if (appData.zippyPlay) return FALSE;
5074 DisplayMoveError(_("You are playing Black"));
5079 case MachinePlaysBlack:
5080 case IcsPlayingWhite:
5081 if (appData.zippyPlay) return FALSE;
5083 DisplayMoveError(_("You are playing White"));
5089 if (!white_piece && WhiteOnMove(currentMove)) {
5090 DisplayMoveError(_("It is White's turn"));
5093 if (white_piece && !WhiteOnMove(currentMove)) {
5094 DisplayMoveError(_("It is Black's turn"));
5097 if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
5098 /* Editing correspondence game history */
5099 /* Could disallow this or prompt for confirmation */
5102 if (currentMove < forwardMostMove) {
5103 /* Discarding moves */
5104 /* Could prompt for confirmation here,
5105 but I don't think that's such a good idea */
5106 forwardMostMove = currentMove;
5110 case BeginningOfGame:
5111 if (appData.icsActive) return FALSE;
5112 if (!appData.noChessProgram) {
5114 DisplayMoveError(_("You are playing White"));
5121 if (!white_piece && WhiteOnMove(currentMove)) {
5122 DisplayMoveError(_("It is White's turn"));
5125 if (white_piece && !WhiteOnMove(currentMove)) {
5126 DisplayMoveError(_("It is Black's turn"));
5135 if (currentMove != forwardMostMove && gameMode != AnalyzeMode
5136 && gameMode != AnalyzeFile && gameMode != Training) {
5137 DisplayMoveError(_("Displayed position is not current"));
5143 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
5144 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
5145 int lastLoadGameUseList = FALSE;
5146 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
5147 ChessMove lastLoadGameStart = (ChessMove) 0;
5150 UserMoveTest(fromX, fromY, toX, toY, promoChar, captureOwn)
5151 int fromX, fromY, toX, toY;
5156 ChessSquare pdown, pup;
5158 /* Check if the user is playing in turn. This is complicated because we
5159 let the user "pick up" a piece before it is his turn. So the piece he
5160 tried to pick up may have been captured by the time he puts it down!
5161 Therefore we use the color the user is supposed to be playing in this
5162 test, not the color of the piece that is currently on the starting
5163 square---except in EditGame mode, where the user is playing both
5164 sides; fortunately there the capture race can't happen. (It can
5165 now happen in IcsExamining mode, but that's just too bad. The user
5166 will get a somewhat confusing message in that case.)
5170 case PlayFromGameFile:
5172 case TwoMachinesPlay:
5176 /* We switched into a game mode where moves are not accepted,
5177 perhaps while the mouse button was down. */
5178 return ImpossibleMove;
5180 case MachinePlaysWhite:
5181 /* User is moving for Black */
5182 if (WhiteOnMove(currentMove)) {
5183 DisplayMoveError(_("It is White's turn"));
5184 return ImpossibleMove;
5188 case MachinePlaysBlack:
5189 /* User is moving for White */
5190 if (!WhiteOnMove(currentMove)) {
5191 DisplayMoveError(_("It is Black's turn"));
5192 return ImpossibleMove;
5198 case BeginningOfGame:
5201 if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
5202 (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
5203 /* User is moving for Black */
5204 if (WhiteOnMove(currentMove)) {
5205 DisplayMoveError(_("It is White's turn"));
5206 return ImpossibleMove;
5209 /* User is moving for White */
5210 if (!WhiteOnMove(currentMove)) {
5211 DisplayMoveError(_("It is Black's turn"));
5212 return ImpossibleMove;
5217 case IcsPlayingBlack:
5218 /* User is moving for Black */
5219 if (WhiteOnMove(currentMove)) {
5220 if (!appData.premove) {
5221 DisplayMoveError(_("It is White's turn"));
5222 } else if (toX >= 0 && toY >= 0) {
5225 premoveFromX = fromX;
5226 premoveFromY = fromY;
5227 premovePromoChar = promoChar;
5229 if (appData.debugMode)
5230 fprintf(debugFP, "Got premove: fromX %d,"
5231 "fromY %d, toX %d, toY %d\n",
5232 fromX, fromY, toX, toY);
5234 return ImpossibleMove;
5238 case IcsPlayingWhite:
5239 /* User is moving for White */
5240 if (!WhiteOnMove(currentMove)) {
5241 if (!appData.premove) {
5242 DisplayMoveError(_("It is Black's turn"));
5243 } else if (toX >= 0 && toY >= 0) {
5246 premoveFromX = fromX;
5247 premoveFromY = fromY;
5248 premovePromoChar = promoChar;
5250 if (appData.debugMode)
5251 fprintf(debugFP, "Got premove: fromX %d,"
5252 "fromY %d, toX %d, toY %d\n",
5253 fromX, fromY, toX, toY);
5255 return ImpossibleMove;
5263 /* EditPosition, empty square, or different color piece;
5264 click-click move is possible */
5265 if (toX == -2 || toY == -2) {
5266 boards[0][fromY][fromX] = EmptySquare;
5267 return AmbiguousMove;
5268 } else if (toX >= 0 && toY >= 0) {
5269 boards[0][toY][toX] = boards[0][fromY][fromX];
5270 boards[0][fromY][fromX] = EmptySquare;
5271 return AmbiguousMove;
5273 return ImpossibleMove;
5276 if(toX < 0 || toY < 0) return ImpossibleMove;
5277 pdown = boards[currentMove][fromY][fromX];
5278 pup = boards[currentMove][toY][toX];
5280 /* [HGM] If move started in holdings, it means a drop */
5281 if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) {
5282 if( pup != EmptySquare ) return ImpossibleMove;
5283 if(appData.testLegality) {
5284 /* it would be more logical if LegalityTest() also figured out
5285 * which drops are legal. For now we forbid pawns on back rank.
5286 * Shogi is on its own here...
5288 if( (pdown == WhitePawn || pdown == BlackPawn) &&
5289 (toY == 0 || toY == BOARD_HEIGHT -1 ) )
5290 return(ImpossibleMove); /* no pawn drops on 1st/8th */
5292 return WhiteDrop; /* Not needed to specify white or black yet */
5295 userOfferedDraw = FALSE;
5297 /* [HGM] always test for legality, to get promotion info */
5298 moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5299 epStatus[currentMove], castlingRights[currentMove],
5300 fromY, fromX, toY, toX, promoChar);
5301 /* [HGM] but possibly ignore an IllegalMove result */
5302 if (appData.testLegality) {
5303 if (moveType == IllegalMove || moveType == ImpossibleMove) {
5304 DisplayMoveError(_("Illegal move"));
5305 return ImpossibleMove;
5308 if(appData.debugMode) fprintf(debugFP, "moveType 3 = %d, promochar = %x\n", moveType, promoChar);
5310 /* [HGM] <popupFix> in stead of calling FinishMove directly, this
5311 function is made into one that returns an OK move type if FinishMove
5312 should be called. This to give the calling driver routine the
5313 opportunity to finish the userMove input with a promotion popup,
5314 without bothering the user with this for invalid or illegal moves */
5316 /* FinishMove(moveType, fromX, fromY, toX, toY, promoChar); */
5319 /* Common tail of UserMoveEvent and DropMenuEvent */
5321 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
5323 int fromX, fromY, toX, toY;
5324 /*char*/int promoChar;
5328 if(appData.debugMode)
5329 fprintf(debugFP, "moveType 5 = %d, promochar = %x\n", moveType, promoChar);
5331 if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) && promoChar != NULLCHAR)
5333 // [HGM] superchess: suppress promotions to non-available piece
5334 int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
5335 if(WhiteOnMove(currentMove))
5337 if(!boards[currentMove][k][BOARD_WIDTH-2])
5342 if(!boards[currentMove][BOARD_HEIGHT-1-k][1])
5347 /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
5348 move type in caller when we know the move is a legal promotion */
5349 if(moveType == NormalMove && promoChar)
5350 moveType = PromoCharToMoveType(WhiteOnMove(currentMove), promoChar);
5352 if(appData.debugMode)
5353 fprintf(debugFP, "moveType 1 = %d, promochar = %x\n", moveType, promoChar);
5355 /* [HGM] convert drag-and-drop piece drops to standard form */
5356 if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1)
5358 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
5359 if(appData.debugMode)
5360 fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n",
5361 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
5362 // fromX = boards[currentMove][fromY][fromX];
5363 // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
5365 fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
5367 fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
5369 while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare)
5375 /* [HGM] <popupFix> The following if has been moved here from
5376 UserMoveEvent(). Because it seemed to belon here (why not allow
5377 piece drops in training games?), and because it can only be
5378 performed after it is known to what we promote. */
5379 if (gameMode == Training)
5381 /* compare the move played on the board to the next move in the
5382 * game. If they match, display the move and the opponent's response.
5383 * If they don't match, display an error message.
5386 Board testBoard; char testRights[BOARD_SIZE]; char testStatus;
5387 CopyBoard(testBoard, boards[currentMove]);
5388 ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard, testRights, &testStatus);
5390 if (CompareBoards(testBoard, boards[currentMove+1]))
5392 ForwardInner(currentMove+1);
5394 /* Autoplay the opponent's response.
5395 * if appData.animate was TRUE when Training mode was entered,
5396 * the response will be animated.
5398 saveAnimate = appData.animate;
5399 appData.animate = animateTraining;
5400 ForwardInner(currentMove+1);
5401 appData.animate = saveAnimate;
5403 /* check for the end of the game */
5404 if (currentMove >= forwardMostMove)
5406 gameMode = PlayFromGameFile;
5408 SetTrainingModeOff();
5409 DisplayInformation(_("End of game"));
5414 DisplayError(_("Incorrect move"), 0);
5419 /* Ok, now we know that the move is good, so we can kill
5420 the previous line in Analysis Mode */
5421 if (gameMode == AnalyzeMode && currentMove < forwardMostMove)
5423 forwardMostMove = currentMove;
5426 /* If we need the chess program but it's dead, restart it */
5427 ResurrectChessProgram();
5429 /* A user move restarts a paused game*/
5433 thinkOutput[0] = NULLCHAR;
5435 MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
5437 if (gameMode == BeginningOfGame)
5439 if (appData.noChessProgram)
5441 gameMode = EditGame;
5447 gameMode = MachinePlaysBlack;
5450 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
5454 sprintf(buf, "name %s\n", gameInfo.white);
5455 SendToProgram(buf, &first);
5461 if(appData.debugMode) fprintf(debugFP, "moveType 2 = %d, promochar = %x\n", moveType, promoChar);
5463 /* Relay move to ICS or chess engine */
5464 if (appData.icsActive)
5466 if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
5467 gameMode == IcsExamining)
5469 SendMoveToICS(moveType, fromX, fromY, toX, toY);
5475 if (first.sendTime && (gameMode == BeginningOfGame ||
5476 gameMode == MachinePlaysWhite ||
5477 gameMode == MachinePlaysBlack))
5479 SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
5481 if (gameMode != EditGame && gameMode != PlayFromGameFile)
5483 // [HGM] book: if program might be playing, let it use book
5484 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
5485 first.maybeThinking = TRUE;
5488 SendMoveToProgram(forwardMostMove-1, &first);
5489 if (currentMove == cmailOldMove + 1)
5491 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
5495 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5500 switch (MateTest(boards[currentMove], PosFlags(currentMove),
5501 EP_UNKNOWN, castlingRights[currentMove]) )
5508 if (WhiteOnMove(currentMove))
5510 GameEnds(BlackWins, "Black mates", GE_PLAYER);
5514 GameEnds(WhiteWins, "White mates", GE_PLAYER);
5518 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
5523 case MachinePlaysBlack:
5524 case MachinePlaysWhite:
5525 /* disable certain menu options while machine is thinking */
5526 SetMachineThinkingEnables();
5534 { // [HGM] book: simulate book reply
5535 static char bookMove[MSG_SIZ]; // a bit generous?
5537 programStats.nodes = programStats.depth = programStats.time =
5538 programStats.score = programStats.got_only_move = 0;
5539 sprintf(programStats.movelist, "%s (xbook)", bookHit);
5541 strcpy(bookMove, "move ");
5542 strcat(bookMove, bookHit);
5543 HandleMachineMove(bookMove, &first);
5550 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
5551 int fromX, fromY, toX, toY;
5554 /* [HGM] This routine was added to allow calling of its two logical
5555 parts from other modules in the old way. Before, UserMoveEvent()
5556 automatically called FinishMove() if the move was OK, and returned
5557 otherwise. I separated the two, in order to make it possible to
5558 slip a promotion popup in between. But that it always needs two
5559 calls, to the first part, (now called UserMoveTest() ), and to
5560 FinishMove if the first part succeeded. Calls that do not need
5561 to do anything in between, can call this routine the old way.
5563 ChessMove moveType = UserMoveTest(fromX, fromY, toX, toY, promoChar, FALSE);
5564 if(appData.debugMode) fprintf(debugFP, "moveType 4 = %d, promochar = %x\n", moveType, promoChar);
5565 if(moveType == AmbiguousMove)
5566 DrawPosition(FALSE, boards[currentMove]);
5567 else if(moveType != ImpossibleMove && moveType != Comment)
5568 FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
5571 void LeftClick(ClickType clickType, int xPix, int yPix)
5574 Boolean saveAnimate;
5575 static int second = 0, promotionChoice = 0;
5576 char promoChoice = NULLCHAR;
5578 if (clickType == Press) ErrorPopDown();
5580 x = EventToSquare(xPix, BOARD_WIDTH);
5581 y = EventToSquare(yPix, BOARD_HEIGHT);
5582 if (!flipView && y >= 0) {
5583 y = BOARD_HEIGHT - 1 - y;
5585 if (flipView && x >= 0) {
5586 x = BOARD_WIDTH - 1 - x;
5589 if(promotionChoice) { // we are waiting for a click to indicate promotion piece
5590 if(clickType == Release) return; // ignore upclick of click-click destination
5591 promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
5592 if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
5593 if(gameInfo.holdingsWidth &&
5594 (WhiteOnMove(currentMove)
5595 ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y > 0
5596 : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT-1) ) {
5597 // click in right holdings, for determining promotion piece
5598 ChessSquare p = boards[currentMove][y][x];
5599 if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
5600 if(p != EmptySquare) {
5601 FinishMove(NormalMove, fromX, fromY, toX, toY, ToLower(PieceToChar(p)));
5606 DrawPosition(FALSE, boards[currentMove]);
5610 /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
5611 if(clickType == Press
5612 && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
5613 || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
5614 || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
5618 if (clickType == Press) {
5620 if (OKToStartUserMove(x, y)) {
5624 DragPieceBegin(xPix, yPix);
5625 if (appData.highlightDragging) {
5626 SetHighlights(x, y, -1, -1);
5634 if (clickType == Press && gameMode != EditPosition) {
5639 // ignore off-board to clicks
5640 if(y < 0 || x < 0) return;
5642 /* Check if clicking again on the same color piece */
5643 fromP = boards[currentMove][fromY][fromX];
5644 toP = boards[currentMove][y][x];
5645 frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom;
5646 if ((WhitePawn <= fromP && fromP <= WhiteKing &&
5647 WhitePawn <= toP && toP <= WhiteKing &&
5648 !(fromP == WhiteKing && toP == WhiteRook && frc) &&
5649 !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
5650 (BlackPawn <= fromP && fromP <= BlackKing &&
5651 BlackPawn <= toP && toP <= BlackKing &&
5652 !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
5653 !(fromP == BlackKing && toP == BlackRook && frc))) {
5654 /* Clicked again on same color piece -- changed his mind */
5655 second = (x == fromX && y == fromY);
5656 if (appData.highlightDragging) {
5657 SetHighlights(x, y, -1, -1);
5661 if (OKToStartUserMove(x, y)) {
5664 DragPieceBegin(xPix, yPix);
5668 // ignore clicks on holdings
5669 if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
5672 if (clickType == Release && x == fromX && y == fromY) {
5673 DragPieceEnd(xPix, yPix);
5674 if (appData.animateDragging) {
5675 /* Undo animation damage if any */
5676 DrawPosition(FALSE, NULL);
5679 /* Second up/down in same square; just abort move */
5684 ClearPremoveHighlights();
5686 /* First upclick in same square; start click-click mode */
5687 SetHighlights(x, y, -1, -1);
5692 /* we now have a different from- and (possibly off-board) to-square */
5693 /* Completed move */
5696 saveAnimate = appData.animate;
5697 if (clickType == Press) {
5698 /* Finish clickclick move */
5699 if (appData.animate || appData.highlightLastMove) {
5700 SetHighlights(fromX, fromY, toX, toY);
5705 /* Finish drag move */
5706 if (appData.highlightLastMove) {
5707 SetHighlights(fromX, fromY, toX, toY);
5711 DragPieceEnd(xPix, yPix);
5712 /* Don't animate move and drag both */
5713 appData.animate = FALSE;
5716 // moves into holding are invalid for now (later perhaps allow in EditPosition)
5717 if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
5720 DrawPosition(FALSE, NULL);
5724 // off-board moves should not be highlighted
5725 if(x < 0 || x < 0) ClearHighlights();
5727 if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice)) {
5728 SetHighlights(fromX, fromY, toX, toY);
5729 if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
5730 // [HGM] super: promotion to captured piece selected from holdings
5731 ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
5732 promotionChoice = TRUE;
5733 // kludge follows to temporarily execute move on display, without promoting yet
5734 boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
5735 boards[currentMove][toY][toX] = p;
5736 DrawPosition(FALSE, boards[currentMove]);
5737 boards[currentMove][fromY][fromX] = p; // take back, but display stays
5738 boards[currentMove][toY][toX] = q;
5739 DisplayMessage("Click in holdings to choose piece", "");
5744 UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
5745 if (!appData.highlightLastMove || gotPremove) ClearHighlights();
5746 if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
5749 appData.animate = saveAnimate;
5750 if (appData.animate || appData.animateDragging) {
5751 /* Undo animation damage if needed */
5752 DrawPosition(FALSE, NULL);
5756 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
5758 // char * hint = lastHint;
5759 FrontEndProgramStats stats;
5761 stats.which = cps == &first ? 0 : 1;
5762 stats.depth = cpstats->depth;
5763 stats.nodes = cpstats->nodes;
5764 stats.score = cpstats->score;
5765 stats.time = cpstats->time;
5766 stats.pv = cpstats->movelist;
5767 stats.hint = lastHint;
5768 stats.an_move_index = 0;
5769 stats.an_move_count = 0;
5771 if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
5772 stats.hint = cpstats->move_name;
5773 stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
5774 stats.an_move_count = cpstats->nr_moves;
5777 SetProgramStats( &stats );
5780 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
5781 { // [HGM] book: this routine intercepts moves to simulate book replies
5782 char *bookHit = NULL;
5784 //first determine if the incoming move brings opponent into his book
5785 if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
5786 bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
5787 if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
5788 if(bookHit != NULL && !cps->bookSuspend) {
5789 // make sure opponent is not going to reply after receiving move to book position
5790 SendToProgram("force\n", cps);
5791 cps->bookSuspend = TRUE; // flag indicating it has to be restarted
5793 if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
5794 // now arrange restart after book miss
5796 // after a book hit we never send 'go', and the code after the call to this routine
5797 // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
5799 if (cps->useUsermove) sprintf(buf, "usermove "); // sorry, no SAN yet :(
5800 sprintf(buf, "%s\n", bookHit); // force book move into program supposed to play it
5801 SendToProgram(buf, cps);
5802 if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
5803 } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
5804 SendToProgram("go\n", cps);
5805 cps->bookSuspend = FALSE; // after a 'go' we are never suspended
5806 } else { // 'go' might be sent based on 'firstMove' after this routine returns
5807 if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
5808 SendToProgram("go\n", cps);
5809 cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
5811 return bookHit; // notify caller of hit, so it can take action to send move to opponent
5815 ChessProgramState *savedState;
5816 void DeferredBookMove(void)
5818 if(savedState->lastPing != savedState->lastPong)
5819 ScheduleDelayedEvent(DeferredBookMove, 10);
5821 HandleMachineMove(savedMessage, savedState);
5825 HandleMachineMove(message, cps)
5827 ChessProgramState *cps;
5829 char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
5830 char realname[MSG_SIZ];
5831 int fromX, fromY, toX, toY;
5838 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
5840 * Kludge to ignore BEL characters
5842 while (*message == '\007') message++;
5845 * [HGM] engine debug message: ignore lines starting with '#' character
5847 if(cps->debug && *message == '#') return;
5850 * Look for book output
5852 if (cps == &first && bookRequested) {
5853 if (message[0] == '\t' || message[0] == ' ') {
5854 /* Part of the book output is here; append it */
5855 strcat(bookOutput, message);
5856 strcat(bookOutput, " \n");
5858 } else if (bookOutput[0] != NULLCHAR) {
5859 /* All of book output has arrived; display it */
5860 char *p = bookOutput;
5861 while (*p != NULLCHAR) {
5862 if (*p == '\t') *p = ' ';
5865 DisplayInformation(bookOutput);
5866 bookRequested = FALSE;
5867 /* Fall through to parse the current output */
5872 * Look for machine move.
5874 if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
5875 (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
5877 /* This method is only useful on engines that support ping */
5878 if (cps->lastPing != cps->lastPong) {
5879 if (gameMode == BeginningOfGame) {
5880 /* Extra move from before last new; ignore */
5881 if (appData.debugMode) {
5882 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
5885 if (appData.debugMode) {
5886 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
5887 cps->which, gameMode);
5890 SendToProgram("undo\n", cps);
5896 case BeginningOfGame:
5897 /* Extra move from before last reset; ignore */
5898 if (appData.debugMode) {
5899 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
5906 /* Extra move after we tried to stop. The mode test is
5907 not a reliable way of detecting this problem, but it's
5908 the best we can do on engines that don't support ping.
5910 if (appData.debugMode) {
5911 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
5912 cps->which, gameMode);
5914 SendToProgram("undo\n", cps);
5917 case MachinePlaysWhite:
5918 case IcsPlayingWhite:
5919 machineWhite = TRUE;
5922 case MachinePlaysBlack:
5923 case IcsPlayingBlack:
5924 machineWhite = FALSE;
5927 case TwoMachinesPlay:
5928 machineWhite = (cps->twoMachinesColor[0] == 'w');
5931 if (WhiteOnMove(forwardMostMove) != machineWhite) {
5932 if (appData.debugMode) {
5934 "Ignoring move out of turn by %s, gameMode %d"
5935 ", forwardMost %d\n",
5936 cps->which, gameMode, forwardMostMove);
5941 if (appData.debugMode) { int f = forwardMostMove;
5942 fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
5943 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
5945 if(cps->alphaRank) AlphaRank(machineMove, 4);
5946 if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
5947 &fromX, &fromY, &toX, &toY, &promoChar)) {
5948 /* Machine move could not be parsed; ignore it. */
5949 sprintf(buf1, _("Illegal move \"%s\" from %s machine"),
5950 machineMove, cps->which);
5951 DisplayError(buf1, 0);
5952 sprintf(buf1, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
5953 machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
5954 if (gameMode == TwoMachinesPlay) {
5955 GameEnds(machineWhite ? BlackWins : WhiteWins,
5961 /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
5962 /* So we have to redo legality test with true e.p. status here, */
5963 /* to make sure an illegal e.p. capture does not slip through, */
5964 /* to cause a forfeit on a justified illegal-move complaint */
5965 /* of the opponent. */
5966 if( gameMode==TwoMachinesPlay && appData.testLegality
5967 && fromY != DROP_RANK /* [HGM] temporary; should still add legality test for drops */
5970 moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
5971 epStatus[forwardMostMove], castlingRights[forwardMostMove],
5972 fromY, fromX, toY, toX, promoChar);
5973 if (appData.debugMode) {
5975 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
5976 castlingRights[forwardMostMove][i], castlingRank[i]);
5977 fprintf(debugFP, "castling rights\n");
5979 if(moveType == IllegalMove) {
5980 sprintf(buf1, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
5981 machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
5982 GameEnds(machineWhite ? BlackWins : WhiteWins,
5985 } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
5986 /* [HGM] Kludge to handle engines that send FRC-style castling
5987 when they shouldn't (like TSCP-Gothic) */
5989 case WhiteASideCastleFR:
5990 case BlackASideCastleFR:
5992 currentMoveString[2]++;
5994 case WhiteHSideCastleFR:
5995 case BlackHSideCastleFR:
5997 currentMoveString[2]--;
5999 default: ; // nothing to do, but suppresses warning of pedantic compilers
6002 hintRequested = FALSE;
6003 lastHint[0] = NULLCHAR;
6004 bookRequested = FALSE;
6005 /* Program may be pondering now */
6006 cps->maybeThinking = TRUE;
6007 if (cps->sendTime == 2) cps->sendTime = 1;
6008 if (cps->offeredDraw) cps->offeredDraw--;
6011 if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
6013 SendMoveToICS(moveType, fromX, fromY, toX, toY);
6015 if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
6016 char buf[3*MSG_SIZ];
6018 sprintf(buf, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
6019 programStats.score / 100.,
6021 programStats.time / 100.,
6022 (unsigned int)programStats.nodes,
6023 (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
6024 programStats.movelist);
6026 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
6030 /* currentMoveString is set as a side-effect of ParseOneMove */
6031 strcpy(machineMove, currentMoveString);
6032 strcat(machineMove, "\n");
6033 strcpy(moveList[forwardMostMove], machineMove);
6035 /* [AS] Save move info and clear stats for next move */
6036 pvInfoList[ forwardMostMove ].score = programStats.score;
6037 pvInfoList[ forwardMostMove ].depth = programStats.depth;
6038 pvInfoList[ forwardMostMove ].time = programStats.time; // [HGM] PGNtime: take time from engine stats
6039 ClearProgramStats();
6040 thinkOutput[0] = NULLCHAR;
6041 hiddenThinkOutputState = 0;
6043 MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
6045 /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
6046 if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
6049 while( count < adjudicateLossPlies ) {
6050 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
6053 score = -score; /* Flip score for winning side */
6056 if( score > adjudicateLossThreshold ) {
6063 if( count >= adjudicateLossPlies ) {
6064 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6066 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
6067 "Xboard adjudication",
6074 if( gameMode == TwoMachinesPlay ) {
6075 // [HGM] some adjudications useful with buggy engines
6076 int k, count = 0, epFile = epStatus[forwardMostMove]; static int bare = 1;
6077 if(gameInfo.holdingsSize == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
6080 if( appData.testLegality )
6081 { /* [HGM] Some more adjudications for obstinate engines */
6082 int NrWN=0, NrBN=0, NrWB=0, NrBB=0, NrWR=0, NrBR=0,
6083 NrWQ=0, NrBQ=0, NrW=0, NrK=0, bishopsColor = 0,
6084 NrPieces=0, NrPawns=0, PawnAdvance=0, i, j;
6085 static int moveCount = 6;
6087 char *reason = NULL;
6089 /* Count what is on board. */
6090 for(i=0; i<BOARD_HEIGHT; i++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
6091 { ChessSquare p = boards[forwardMostMove][i][j];
6095 { /* count B,N,R and other of each side */
6098 NrK++; break; // [HGM] atomic: count Kings
6102 case WhiteFerz: // [HGM] shatranj: kludge to mke it work in shatranj
6103 bishopsColor |= 1 << ((i^j)&1);
6108 case BlackFerz: // [HGM] shatranj: kludge to mke it work in shatranj
6109 bishopsColor |= 1 << ((i^j)&1);
6124 PawnAdvance += m; NrPawns++;
6126 NrPieces += (p != EmptySquare);
6127 NrW += ((int)p < (int)BlackPawn);
6128 if(gameInfo.variant == VariantXiangqi &&
6129 (p == WhiteFerz || p == WhiteAlfil || p == BlackFerz || p == BlackAlfil)) {
6130 NrPieces--; // [HGM] XQ: do not count purely defensive pieces
6131 NrW -= ((int)p < (int)BlackPawn);
6135 /* Some material-based adjudications that have to be made before stalemate test */
6136 if(gameInfo.variant == VariantAtomic && NrK < 2) {
6137 // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
6138 epStatus[forwardMostMove] = EP_CHECKMATE; // make claimable as if stm is checkmated
6139 if(appData.checkMates) {
6140 SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets move
6141 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6142 GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
6143 "Xboard adjudication: King destroyed", GE_XBOARD );
6148 /* Bare King in Shatranj (loses) or Losers (wins) */
6149 if( NrW == 1 || NrPieces - NrW == 1) {
6150 if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
6151 epStatus[forwardMostMove] = EP_WINS; // mark as win, so it becomes claimable
6152 if(appData.checkMates) {
6153 SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets to see move
6154 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6155 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
6156 "Xboard adjudication: Bare king", GE_XBOARD );
6160 if( gameInfo.variant == VariantShatranj && --bare < 0)
6162 epStatus[forwardMostMove] = EP_WINS; // make claimable as win for stm
6163 if(appData.checkMates) {
6164 /* but only adjudicate if adjudication enabled */
6165 SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets move
6166 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6167 GameEnds( NrW > 1 ? WhiteWins : NrPieces - NrW > 1 ? BlackWins : GameIsDrawn,
6168 "Xboard adjudication: Bare king", GE_XBOARD );
6175 // don't wait for engine to announce game end if we can judge ourselves
6176 switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove), epFile,
6177 castlingRights[forwardMostMove]) ) {
6179 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
6180 int i, checkCnt = 0; // (should really be done by making nr of checks part of game state)
6181 for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
6182 if(MateTest(boards[i], PosFlags(i), epStatus[i], castlingRights[i]) == MT_CHECK)
6185 reason = "Xboard adjudication: 3rd check";
6186 epStatus[forwardMostMove] = EP_CHECKMATE;
6196 reason = "Xboard adjudication: Stalemate";
6197 if(epStatus[forwardMostMove] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
6198 epStatus[forwardMostMove] = EP_STALEMATE; // default result for stalemate is draw
6199 if(gameInfo.variant == VariantLosers || gameInfo.variant == VariantGiveaway) // [HGM] losers:
6200 epStatus[forwardMostMove] = EP_WINS; // in these variants stalemated is always a win
6201 else if(gameInfo.variant == VariantSuicide) // in suicide it depends
6202 epStatus[forwardMostMove] = NrW == NrPieces-NrW ? EP_STALEMATE :
6203 ((NrW < NrPieces-NrW) != WhiteOnMove(forwardMostMove) ?
6204 EP_CHECKMATE : EP_WINS);
6205 else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
6206 epStatus[forwardMostMove] = EP_CHECKMATE; // and in these variants being stalemated loses
6210 reason = "Xboard adjudication: Checkmate";
6211 epStatus[forwardMostMove] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
6215 switch(i = epStatus[forwardMostMove]) {
6217 result = GameIsDrawn; break;
6219 result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
6221 result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
6223 result = (ChessMove) 0;
6225 if(appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
6226 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6227 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6228 GameEnds( result, reason, GE_XBOARD );
6232 /* Next absolutely insufficient mating material. */
6233 if( NrPieces == 2 || gameInfo.variant != VariantXiangqi &&
6234 gameInfo.variant != VariantShatranj && // [HGM] baring will remain possible
6235 (NrPieces == 3 && NrWN+NrBN+NrWB+NrBB == 1 ||
6236 NrPieces == NrBB+NrWB+2 && bishopsColor != 3)) // [HGM] all Bishops (Ferz!) same color
6237 { /* KBK, KNK, KK of KBKB with like Bishops */
6239 /* always flag draws, for judging claims */
6240 epStatus[forwardMostMove] = EP_INSUF_DRAW;
6242 if(appData.materialDraws) {
6243 /* but only adjudicate them if adjudication enabled */
6244 SendToProgram("force\n", cps->other); // suppress reply
6245 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see last move */
6246 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6247 GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
6252 /* Then some trivial draws (only adjudicate, cannot be claimed) */
6254 ( NrWR == 1 && NrBR == 1 /* KRKR */
6255 || NrWQ==1 && NrBQ==1 /* KQKQ */
6256 || NrWN==2 || NrBN==2 /* KNNK */
6257 || NrWN+NrWB == 1 && NrBN+NrBB == 1 /* KBKN, KBKB, KNKN */
6259 if(--moveCount < 0 && appData.trivialDraws)
6260 { /* if the first 3 moves do not show a tactical win, declare draw */
6261 SendToProgram("force\n", cps->other); // suppress reply
6262 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6263 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6264 GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
6267 } else moveCount = 6;
6271 if (appData.debugMode) { int i;
6272 fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
6273 forwardMostMove, backwardMostMove, epStatus[backwardMostMove],
6274 appData.drawRepeats);
6275 for( i=forwardMostMove; i>=backwardMostMove; i-- )
6276 fprintf(debugFP, "%d ep=%d\n", i, epStatus[i]);
6280 /* Check for rep-draws */
6282 for(k = forwardMostMove-2;
6283 k>=backwardMostMove && k>=forwardMostMove-100 &&
6284 epStatus[k] < EP_UNKNOWN &&
6285 epStatus[k+2] <= EP_NONE && epStatus[k+1] <= EP_NONE;
6288 if(CompareBoards(boards[k], boards[forwardMostMove])) {
6289 /* compare castling rights */
6290 if( castlingRights[forwardMostMove][2] != castlingRights[k][2] &&
6291 (castlingRights[k][0] >= 0 || castlingRights[k][1] >= 0) )
6292 rights++; /* King lost rights, while rook still had them */
6293 if( castlingRights[forwardMostMove][2] >= 0 ) { /* king has rights */
6294 if( castlingRights[forwardMostMove][0] != castlingRights[k][0] ||
6295 castlingRights[forwardMostMove][1] != castlingRights[k][1] )
6296 rights++; /* but at least one rook lost them */
6298 if( castlingRights[forwardMostMove][5] != castlingRights[k][5] &&
6299 (castlingRights[k][3] >= 0 || castlingRights[k][4] >= 0) )
6301 if( castlingRights[forwardMostMove][5] >= 0 ) {
6302 if( castlingRights[forwardMostMove][3] != castlingRights[k][3] ||
6303 castlingRights[forwardMostMove][4] != castlingRights[k][4] )
6306 if( rights == 0 && ++count > appData.drawRepeats-2
6307 && appData.drawRepeats > 1) {
6308 /* adjudicate after user-specified nr of repeats */
6309 SendToProgram("force\n", cps->other); // suppress reply
6310 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6311 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6312 if(gameInfo.variant == VariantXiangqi && appData.testLegality) {
6313 // [HGM] xiangqi: check for forbidden perpetuals
6314 int m, ourPerpetual = 1, hisPerpetual = 1;
6315 for(m=forwardMostMove; m>k; m-=2) {
6316 if(MateTest(boards[m], PosFlags(m),
6317 EP_NONE, castlingRights[m]) != MT_CHECK)
6318 ourPerpetual = 0; // the current mover did not always check
6319 if(MateTest(boards[m-1], PosFlags(m-1),
6320 EP_NONE, castlingRights[m-1]) != MT_CHECK)
6321 hisPerpetual = 0; // the opponent did not always check
6323 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
6324 ourPerpetual, hisPerpetual);
6325 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
6326 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
6327 "Xboard adjudication: perpetual checking", GE_XBOARD );
6330 if(hisPerpetual && !ourPerpetual) // he is checking us, but did not repeat yet
6331 break; // (or we would have caught him before). Abort repetition-checking loop.
6332 // Now check for perpetual chases
6333 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
6334 hisPerpetual = PerpetualChase(k, forwardMostMove);
6335 ourPerpetual = PerpetualChase(k+1, forwardMostMove);
6336 if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
6337 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
6338 "Xboard adjudication: perpetual chasing", GE_XBOARD );
6341 if(hisPerpetual && !ourPerpetual) // he is chasing us, but did not repeat yet
6342 break; // Abort repetition-checking loop.
6344 // if neither of us is checking or chasing all the time, or both are, it is draw
6346 GameEnds( GameIsDrawn, "Xboard adjudication: repetition draw", GE_XBOARD );
6349 if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
6350 epStatus[forwardMostMove] = EP_REP_DRAW;
6354 /* Now we test for 50-move draws. Determine ply count */
6355 count = forwardMostMove;
6356 /* look for last irreversble move */
6357 while( epStatus[count] <= EP_NONE && count > backwardMostMove )
6359 /* if we hit starting position, add initial plies */
6360 if( count == backwardMostMove )
6361 count -= initialRulePlies;
6362 count = forwardMostMove - count;
6364 epStatus[forwardMostMove] = EP_RULE_DRAW;
6365 /* this is used to judge if draw claims are legal */
6366 if(appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
6367 SendToProgram("force\n", cps->other); // suppress reply
6368 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6369 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6370 GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
6374 /* if draw offer is pending, treat it as a draw claim
6375 * when draw condition present, to allow engines a way to
6376 * claim draws before making their move to avoid a race
6377 * condition occurring after their move
6379 if( cps->other->offeredDraw || cps->offeredDraw ) {
6381 if(epStatus[forwardMostMove] == EP_RULE_DRAW)
6382 p = "Draw claim: 50-move rule";
6383 if(epStatus[forwardMostMove] == EP_REP_DRAW)
6384 p = "Draw claim: 3-fold repetition";
6385 if(epStatus[forwardMostMove] == EP_INSUF_DRAW)
6386 p = "Draw claim: insufficient mating material";
6388 SendToProgram("force\n", cps->other); // suppress reply
6389 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6390 GameEnds( GameIsDrawn, p, GE_XBOARD );
6391 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6397 if( appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
6398 SendToProgram("force\n", cps->other); // suppress reply
6399 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6400 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6402 GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
6409 if (gameMode == TwoMachinesPlay) {
6410 /* [HGM] relaying draw offers moved to after reception of move */
6411 /* and interpreting offer as claim if it brings draw condition */
6412 if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
6413 SendToProgram("draw\n", cps->other);
6415 if (cps->other->sendTime) {
6416 SendTimeRemaining(cps->other,
6417 cps->other->twoMachinesColor[0] == 'w');
6419 bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
6420 if (firstMove && !bookHit) {
6422 if (cps->other->useColors) {
6423 SendToProgram(cps->other->twoMachinesColor, cps->other);
6425 SendToProgram("go\n", cps->other);
6427 cps->other->maybeThinking = TRUE;
6430 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6432 if (!pausing && appData.ringBellAfterMoves) {
6437 * Reenable menu items that were disabled while
6438 * machine was thinking
6440 if (gameMode != TwoMachinesPlay)
6441 SetUserThinkingEnables();
6443 // [HGM] book: after book hit opponent has received move and is now in force mode
6444 // force the book reply into it, and then fake that it outputted this move by jumping
6445 // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
6447 static char bookMove[MSG_SIZ]; // a bit generous?
6449 strcpy(bookMove, "move ");
6450 strcat(bookMove, bookHit);
6453 programStats.nodes = programStats.depth = programStats.time =
6454 programStats.score = programStats.got_only_move = 0;
6455 sprintf(programStats.movelist, "%s (xbook)", bookHit);
6457 if(cps->lastPing != cps->lastPong) {
6458 savedMessage = message; // args for deferred call
6460 ScheduleDelayedEvent(DeferredBookMove, 10);
6469 /* Set special modes for chess engines. Later something general
6470 * could be added here; for now there is just one kludge feature,
6471 * needed because Crafty 15.10 and earlier don't ignore SIGINT
6472 * when "xboard" is given as an interactive command.
6474 if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
6475 cps->useSigint = FALSE;
6476 cps->useSigterm = FALSE;
6478 if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
6479 ParseFeatures(message+8, cps);
6480 return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
6483 /* [HGM] Allow engine to set up a position. Don't ask me why one would
6484 * want this, I was asked to put it in, and obliged.
6486 if (!strncmp(message, "setboard ", 9)) {
6487 Board initial_position; int i;
6489 GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
6491 if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
6492 DisplayError(_("Bad FEN received from engine"), 0);
6496 CopyBoard(boards[0], initial_position);
6497 initialRulePlies = FENrulePlies;
6498 epStatus[0] = FENepStatus;
6499 for( i=0; i<nrCastlingRights; i++ )
6500 castlingRights[0][i] = FENcastlingRights[i];
6501 if(blackPlaysFirst) gameMode = MachinePlaysWhite;
6502 else gameMode = MachinePlaysBlack;
6503 DrawPosition(FALSE, boards[currentMove]);
6509 * Look for communication commands
6511 if (!strncmp(message, "telluser ", 9)) {
6512 DisplayNote(message + 9);
6515 if (!strncmp(message, "tellusererror ", 14)) {
6516 DisplayError(message + 14, 0);
6519 if (!strncmp(message, "tellopponent ", 13)) {
6520 if (appData.icsActive) {
6522 snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
6526 DisplayNote(message + 13);
6530 if (!strncmp(message, "tellothers ", 11)) {
6531 if (appData.icsActive) {
6533 snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
6539 if (!strncmp(message, "tellall ", 8)) {
6540 if (appData.icsActive) {
6542 snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
6546 DisplayNote(message + 8);
6550 if (strncmp(message, "warning", 7) == 0) {
6551 /* Undocumented feature, use tellusererror in new code */
6552 DisplayError(message, 0);
6555 if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
6556 strcpy(realname, cps->tidy);
6557 strcat(realname, " query");
6558 AskQuestion(realname, buf2, buf1, cps->pr);
6561 /* Commands from the engine directly to ICS. We don't allow these to be
6562 * sent until we are logged on. Crafty kibitzes have been known to
6563 * interfere with the login process.
6566 if (!strncmp(message, "tellics ", 8)) {
6567 SendToICS(message + 8);
6571 if (!strncmp(message, "tellicsnoalias ", 15)) {
6572 SendToICS(ics_prefix);
6573 SendToICS(message + 15);
6577 /* The following are for backward compatibility only */
6578 if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
6579 !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
6580 SendToICS(ics_prefix);
6586 if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
6590 * If the move is illegal, cancel it and redraw the board.
6591 * Also deal with other error cases. Matching is rather loose
6592 * here to accommodate engines written before the spec.
6594 if (strncmp(message + 1, "llegal move", 11) == 0 ||
6595 strncmp(message, "Error", 5) == 0) {
6596 if (StrStr(message, "name") ||
6597 StrStr(message, "rating") || StrStr(message, "?") ||
6598 StrStr(message, "result") || StrStr(message, "board") ||
6599 StrStr(message, "bk") || StrStr(message, "computer") ||
6600 StrStr(message, "variant") || StrStr(message, "hint") ||
6601 StrStr(message, "random") || StrStr(message, "depth") ||
6602 StrStr(message, "accepted")) {
6605 if (StrStr(message, "protover")) {
6606 /* Program is responding to input, so it's apparently done
6607 initializing, and this error message indicates it is
6608 protocol version 1. So we don't need to wait any longer
6609 for it to initialize and send feature commands. */
6610 FeatureDone(cps, 1);
6611 cps->protocolVersion = 1;
6614 cps->maybeThinking = FALSE;
6616 if (StrStr(message, "draw")) {
6617 /* Program doesn't have "draw" command */
6618 cps->sendDrawOffers = 0;
6621 if (cps->sendTime != 1 &&
6622 (StrStr(message, "time") || StrStr(message, "otim"))) {
6623 /* Program apparently doesn't have "time" or "otim" command */
6627 if (StrStr(message, "analyze")) {
6628 cps->analysisSupport = FALSE;
6629 cps->analyzing = FALSE;
6631 sprintf(buf2, _("%s does not support analysis"), cps->tidy);
6632 DisplayError(buf2, 0);
6635 if (StrStr(message, "(no matching move)st")) {
6636 /* Special kludge for GNU Chess 4 only */
6637 cps->stKludge = TRUE;
6638 SendTimeControl(cps, movesPerSession, timeControl,
6639 timeIncrement, appData.searchDepth,
6643 if (StrStr(message, "(no matching move)sd")) {
6644 /* Special kludge for GNU Chess 4 only */
6645 cps->sdKludge = TRUE;
6646 SendTimeControl(cps, movesPerSession, timeControl,
6647 timeIncrement, appData.searchDepth,
6651 if (!StrStr(message, "llegal")) {
6654 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
6655 gameMode == IcsIdle) return;
6656 if (forwardMostMove <= backwardMostMove) return;
6657 if (pausing) PauseEvent();
6658 if(appData.forceIllegal) {
6659 // [HGM] illegal: machine refused move; force position after move into it
6660 SendToProgram("force\n", cps);
6661 if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
6662 // we have a real problem now, as SendBoard will use the a2a3 kludge
6663 // when black is to move, while there might be nothing on a2 or black
6664 // might already have the move. So send the board as if white has the move.
6665 // But first we must change the stm of the engine, as it refused the last move
6666 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
6667 if(WhiteOnMove(forwardMostMove)) {
6668 SendToProgram("a7a6\n", cps); // for the engine black still had the move
6669 SendBoard(cps, forwardMostMove); // kludgeless board
6671 SendToProgram("a2a3\n", cps); // for the engine white still had the move
6672 CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
6673 SendBoard(cps, forwardMostMove+1); // kludgeless board
6675 } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
6676 if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
6677 gameMode == TwoMachinesPlay)
6678 SendToProgram("go\n", cps);
6681 if (gameMode == PlayFromGameFile) {
6682 /* Stop reading this game file */
6683 gameMode = EditGame;
6686 currentMove = --forwardMostMove;
6687 DisplayMove(currentMove-1); /* before DisplayMoveError */
6689 DisplayBothClocks();
6690 sprintf(buf1, _("Illegal move \"%s\" (rejected by %s chess program)"),
6691 parseList[currentMove], cps->which);
6692 DisplayMoveError(buf1);
6693 DrawPosition(FALSE, boards[currentMove]);
6695 /* [HGM] illegal-move claim should forfeit game when Xboard */
6696 /* only passes fully legal moves */
6697 if( appData.testLegality && gameMode == TwoMachinesPlay ) {
6698 GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
6699 "False illegal-move claim", GE_XBOARD );
6703 if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
6704 /* Program has a broken "time" command that
6705 outputs a string not ending in newline.
6711 * If chess program startup fails, exit with an error message.
6712 * Attempts to recover here are futile.
6714 if ((StrStr(message, "unknown host") != NULL)
6715 || (StrStr(message, "No remote directory") != NULL)
6716 || (StrStr(message, "not found") != NULL)
6717 || (StrStr(message, "No such file") != NULL)
6718 || (StrStr(message, "can't alloc") != NULL)
6719 || (StrStr(message, "Permission denied") != NULL)) {
6721 cps->maybeThinking = FALSE;
6722 snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
6723 cps->which, cps->program, cps->host, message);
6724 RemoveInputSource(cps->isr);
6725 DisplayFatalError(buf1, 0, 1);
6730 * Look for hint output
6732 if (sscanf(message, "Hint: %s", buf1) == 1) {
6733 if (cps == &first && hintRequested) {
6734 hintRequested = FALSE;
6735 if (ParseOneMove(buf1, forwardMostMove, &moveType,
6736 &fromX, &fromY, &toX, &toY, &promoChar)) {
6737 (void) CoordsToAlgebraic(boards[forwardMostMove],
6738 PosFlags(forwardMostMove), EP_UNKNOWN,
6739 fromY, fromX, toY, toX, promoChar, buf1);
6740 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
6741 DisplayInformation(buf2);
6743 /* Hint move could not be parsed!? */
6744 snprintf(buf2, sizeof(buf2),
6745 _("Illegal hint move \"%s\"\nfrom %s chess program"),
6747 DisplayError(buf2, 0);
6750 strcpy(lastHint, buf1);
6756 * Ignore other messages if game is not in progress
6758 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
6759 gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
6762 * look for win, lose, draw, or draw offer
6764 if (strncmp(message, "1-0", 3) == 0) {
6765 char *p, *q, *r = "";
6766 p = strchr(message, '{');
6774 GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
6776 } else if (strncmp(message, "0-1", 3) == 0) {
6777 char *p, *q, *r = "";
6778 p = strchr(message, '{');
6786 /* Kludge for Arasan 4.1 bug */
6787 if (strcmp(r, "Black resigns") == 0) {
6788 GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
6791 GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
6793 } else if (strncmp(message, "1/2", 3) == 0) {
6794 char *p, *q, *r = "";
6795 p = strchr(message, '{');
6804 GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
6807 } else if (strncmp(message, "White resign", 12) == 0) {
6808 GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
6810 } else if (strncmp(message, "Black resign", 12) == 0) {
6811 GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
6813 } else if (strncmp(message, "White matches", 13) == 0 ||
6814 strncmp(message, "Black matches", 13) == 0 ) {
6815 /* [HGM] ignore GNUShogi noises */
6817 } else if (strncmp(message, "White", 5) == 0 &&
6818 message[5] != '(' &&
6819 StrStr(message, "Black") == NULL) {
6820 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6822 } else if (strncmp(message, "Black", 5) == 0 &&
6823 message[5] != '(') {
6824 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6826 } else if (strcmp(message, "resign") == 0 ||
6827 strcmp(message, "computer resigns") == 0) {
6829 case MachinePlaysBlack:
6830 case IcsPlayingBlack:
6831 GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
6833 case MachinePlaysWhite:
6834 case IcsPlayingWhite:
6835 GameEnds(BlackWins, "White resigns", GE_ENGINE);
6837 case TwoMachinesPlay:
6838 if (cps->twoMachinesColor[0] == 'w')
6839 GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
6841 GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
6848 } else if (strncmp(message, "opponent mates", 14) == 0) {
6850 case MachinePlaysBlack:
6851 case IcsPlayingBlack:
6852 GameEnds(WhiteWins, "White mates", GE_ENGINE);
6854 case MachinePlaysWhite:
6855 case IcsPlayingWhite:
6856 GameEnds(BlackWins, "Black mates", GE_ENGINE);
6858 case TwoMachinesPlay:
6859 if (cps->twoMachinesColor[0] == 'w')
6860 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6862 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6869 } else if (strncmp(message, "computer mates", 14) == 0) {
6871 case MachinePlaysBlack:
6872 case IcsPlayingBlack:
6873 GameEnds(BlackWins, "Black mates", GE_ENGINE1);
6875 case MachinePlaysWhite:
6876 case IcsPlayingWhite:
6877 GameEnds(WhiteWins, "White mates", GE_ENGINE);
6879 case TwoMachinesPlay:
6880 if (cps->twoMachinesColor[0] == 'w')
6881 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6883 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6890 } else if (strncmp(message, "checkmate", 9) == 0) {
6891 if (WhiteOnMove(forwardMostMove)) {
6892 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6894 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6897 } else if (strstr(message, "Draw") != NULL ||
6898 strstr(message, "game is a draw") != NULL) {
6899 GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
6901 } else if (strstr(message, "offer") != NULL &&
6902 strstr(message, "draw") != NULL) {
6904 if (appData.zippyPlay && first.initDone) {
6905 /* Relay offer to ICS */
6906 SendToICS(ics_prefix);
6907 SendToICS("draw\n");
6910 cps->offeredDraw = 2; /* valid until this engine moves twice */
6911 if (gameMode == TwoMachinesPlay) {
6912 if (cps->other->offeredDraw) {
6913 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
6914 /* [HGM] in two-machine mode we delay relaying draw offer */
6915 /* until after we also have move, to see if it is really claim */
6917 } else if (gameMode == MachinePlaysWhite ||
6918 gameMode == MachinePlaysBlack) {
6919 if (userOfferedDraw) {
6920 DisplayInformation(_("Machine accepts your draw offer"));
6921 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
6923 DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
6930 * Look for thinking output
6932 if ( appData.showThinking // [HGM] thinking: test all options that cause this output
6933 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
6935 int plylev, mvleft, mvtot, curscore, time;
6936 char mvname[MOVE_LEN];
6940 int prefixHint = FALSE;
6941 mvname[0] = NULLCHAR;
6944 case MachinePlaysBlack:
6945 case IcsPlayingBlack:
6946 if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
6948 case MachinePlaysWhite:
6949 case IcsPlayingWhite:
6950 if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
6955 case IcsObserving: /* [DM] icsEngineAnalyze */
6956 if (!appData.icsEngineAnalyze) ignore = TRUE;
6958 case TwoMachinesPlay:
6959 if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
6970 if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
6971 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
6973 if (plyext != ' ' && plyext != '\t') {
6977 /* [AS] Negate score if machine is playing black and reporting absolute scores */
6978 if( cps->scoreIsAbsolute &&
6979 ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) )
6981 curscore = -curscore;
6985 programStats.depth = plylev;
6986 programStats.nodes = nodes;
6987 programStats.time = time;
6988 programStats.score = curscore;
6989 programStats.got_only_move = 0;
6991 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
6994 if(cps->nps == 0) ticklen = 10*time; // use engine reported time
6995 else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
6996 if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
6997 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
6998 whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
6999 if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
7000 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
7001 blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
7004 /* Buffer overflow protection */
7005 if (buf1[0] != NULLCHAR) {
7006 if (strlen(buf1) >= sizeof(programStats.movelist)
7007 && appData.debugMode) {
7009 "PV is too long; using the first %d bytes.\n",
7010 sizeof(programStats.movelist) - 1);
7013 safeStrCpy( programStats.movelist, buf1, sizeof(programStats.movelist) );
7015 sprintf(programStats.movelist, " no PV\n");
7018 if (programStats.seen_stat) {
7019 programStats.ok_to_send = 1;
7022 if (strchr(programStats.movelist, '(') != NULL) {
7023 programStats.line_is_book = 1;
7024 programStats.nr_moves = 0;
7025 programStats.moves_left = 0;
7027 programStats.line_is_book = 0;
7030 SendProgramStatsToFrontend( cps, &programStats );
7033 [AS] Protect the thinkOutput buffer from overflow... this
7034 is only useful if buf1 hasn't overflowed first!
7036 sprintf(thinkOutput, "[%d]%c%+.2f %s%s",
7038 (gameMode == TwoMachinesPlay ?
7039 ToUpper(cps->twoMachinesColor[0]) : ' '),
7040 ((double) curscore) / 100.0,
7041 prefixHint ? lastHint : "",
7042 prefixHint ? " " : "" );
7044 if( buf1[0] != NULLCHAR ) {
7045 unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
7047 if( strlen(buf1) > max_len ) {
7048 if( appData.debugMode) {
7049 fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
7051 buf1[max_len+1] = '\0';
7054 strcat( thinkOutput, buf1 );
7057 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
7058 || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7059 DisplayMove(currentMove - 1);
7063 } else if ((p=StrStr(message, "(only move)")) != NULL) {
7064 /* crafty (9.25+) says "(only move) <move>"
7065 * if there is only 1 legal move
7067 sscanf(p, "(only move) %s", buf1);
7068 sprintf(thinkOutput, "%s (only move)", buf1);
7069 sprintf(programStats.movelist, "%s (only move)", buf1);
7070 programStats.depth = 1;
7071 programStats.nr_moves = 1;
7072 programStats.moves_left = 1;
7073 programStats.nodes = 1;
7074 programStats.time = 1;
7075 programStats.got_only_move = 1;
7077 /* Not really, but we also use this member to
7078 mean "line isn't going to change" (Crafty
7079 isn't searching, so stats won't change) */
7080 programStats.line_is_book = 1;
7082 SendProgramStatsToFrontend( cps, &programStats );
7084 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
7085 gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7086 DisplayMove(currentMove - 1);
7089 } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
7090 &time, &nodes, &plylev, &mvleft,
7091 &mvtot, mvname) >= 5) {
7092 /* The stat01: line is from Crafty (9.29+) in response
7093 to the "." command */
7094 programStats.seen_stat = 1;
7095 cps->maybeThinking = TRUE;
7097 if (programStats.got_only_move || !appData.periodicUpdates)
7100 programStats.depth = plylev;
7101 programStats.time = time;
7102 programStats.nodes = nodes;
7103 programStats.moves_left = mvleft;
7104 programStats.nr_moves = mvtot;
7105 strcpy(programStats.move_name, mvname);
7106 programStats.ok_to_send = 1;
7107 programStats.movelist[0] = '\0';
7109 SendProgramStatsToFrontend( cps, &programStats );
7113 } else if (strncmp(message,"++",2) == 0) {
7114 /* Crafty 9.29+ outputs this */
7115 programStats.got_fail = 2;
7118 } else if (strncmp(message,"--",2) == 0) {
7119 /* Crafty 9.29+ outputs this */
7120 programStats.got_fail = 1;
7123 } else if (thinkOutput[0] != NULLCHAR &&
7124 strncmp(message, " ", 4) == 0) {
7125 unsigned message_len;
7128 while (*p && *p == ' ') p++;
7130 message_len = strlen( p );
7132 /* [AS] Avoid buffer overflow */
7133 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
7134 strcat(thinkOutput, " ");
7135 strcat(thinkOutput, p);
7138 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
7139 strcat(programStats.movelist, " ");
7140 strcat(programStats.movelist, p);
7143 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
7144 gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7145 DisplayMove(currentMove - 1);
7153 if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
7154 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
7156 ChessProgramStats cpstats;
7158 if (plyext != ' ' && plyext != '\t') {
7162 /* [AS] Negate score if machine is playing black and reporting absolute scores */
7163 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
7164 curscore = -curscore;
7167 cpstats.depth = plylev;
7168 cpstats.nodes = nodes;
7169 cpstats.time = time;
7170 cpstats.score = curscore;
7171 cpstats.got_only_move = 0;
7172 cpstats.movelist[0] = '\0';
7174 if (buf1[0] != NULLCHAR) {
7175 safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist) );
7178 cpstats.ok_to_send = 0;
7179 cpstats.line_is_book = 0;
7180 cpstats.nr_moves = 0;
7181 cpstats.moves_left = 0;
7183 SendProgramStatsToFrontend( cps, &cpstats );
7190 /* Parse a game score from the character string "game", and
7191 record it as the history of the current game. The game
7192 score is NOT assumed to start from the standard position.
7193 The display is not updated in any way.
7196 ParseGameHistory(game)
7200 int fromX, fromY, toX, toY, boardIndex;
7205 if (appData.debugMode)
7206 fprintf(debugFP, "Parsing game history: %s\n", game);
7208 if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
7209 gameInfo.site = StrSave(appData.icsHost);
7210 gameInfo.date = PGNDate();
7211 gameInfo.round = StrSave("-");
7213 /* Parse out names of players */
7214 while (*game == ' ') game++;
7216 while (*game != ' ') *p++ = *game++;
7218 gameInfo.white = StrSave(buf);
7219 while (*game == ' ') game++;
7221 while (*game != ' ' && *game != '\n') *p++ = *game++;
7223 gameInfo.black = StrSave(buf);
7226 boardIndex = blackPlaysFirst ? 1 : 0;
7229 yyboardindex = boardIndex;
7230 moveType = (ChessMove) yylex();
7232 case IllegalMove: /* maybe suicide chess, etc. */
7233 if (appData.debugMode) {
7234 fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
7235 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7236 setbuf(debugFP, NULL);
7238 case WhitePromotionChancellor:
7239 case BlackPromotionChancellor:
7240 case WhitePromotionArchbishop:
7241 case BlackPromotionArchbishop:
7242 case WhitePromotionQueen:
7243 case BlackPromotionQueen:
7244 case WhitePromotionRook:
7245 case BlackPromotionRook:
7246 case WhitePromotionBishop:
7247 case BlackPromotionBishop:
7248 case WhitePromotionKnight:
7249 case BlackPromotionKnight:
7250 case WhitePromotionKing:
7251 case BlackPromotionKing:
7253 case WhiteCapturesEnPassant:
7254 case BlackCapturesEnPassant:
7255 case WhiteKingSideCastle:
7256 case WhiteQueenSideCastle:
7257 case BlackKingSideCastle:
7258 case BlackQueenSideCastle:
7259 case WhiteKingSideCastleWild:
7260 case WhiteQueenSideCastleWild:
7261 case BlackKingSideCastleWild:
7262 case BlackQueenSideCastleWild:
7264 case WhiteHSideCastleFR:
7265 case WhiteASideCastleFR:
7266 case BlackHSideCastleFR:
7267 case BlackASideCastleFR:
7269 fromX = currentMoveString[0] - AAA;
7270 fromY = currentMoveString[1] - ONE;
7271 toX = currentMoveString[2] - AAA;
7272 toY = currentMoveString[3] - ONE;
7273 promoChar = currentMoveString[4];
7277 fromX = moveType == WhiteDrop ?
7278 (int) CharToPiece(ToUpper(currentMoveString[0])) :
7279 (int) CharToPiece(ToLower(currentMoveString[0]));
7281 toX = currentMoveString[2] - AAA;
7282 toY = currentMoveString[3] - ONE;
7283 promoChar = NULLCHAR;
7287 sprintf(buf, _("Ambiguous move in ICS output: \"%s\""), yy_text);
7288 if (appData.debugMode) {
7289 fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
7290 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7291 setbuf(debugFP, NULL);
7293 DisplayError(buf, 0);
7295 case ImpossibleMove:
7297 sprintf(buf, _("Illegal move in ICS output: \"%s\""), yy_text);
7298 if (appData.debugMode) {
7299 fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
7300 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7301 setbuf(debugFP, NULL);
7303 DisplayError(buf, 0);
7305 case (ChessMove) 0: /* end of file */
7306 if (boardIndex < backwardMostMove) {
7307 /* Oops, gap. How did that happen? */
7308 DisplayError(_("Gap in move list"), 0);
7311 backwardMostMove = blackPlaysFirst ? 1 : 0;
7312 if (boardIndex > forwardMostMove) {
7313 forwardMostMove = boardIndex;
7317 if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
7318 strcat(parseList[boardIndex-1], " ");
7319 strcat(parseList[boardIndex-1], yy_text);
7331 case GameUnfinished:
7332 if (gameMode == IcsExamining) {
7333 if (boardIndex < backwardMostMove) {
7334 /* Oops, gap. How did that happen? */
7337 backwardMostMove = blackPlaysFirst ? 1 : 0;
7340 gameInfo.result = moveType;
7341 p = strchr(yy_text, '{');
7342 if (p == NULL) p = strchr(yy_text, '(');
7345 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
7347 q = strchr(p, *p == '{' ? '}' : ')');
7348 if (q != NULL) *q = NULLCHAR;
7351 gameInfo.resultDetails = StrSave(p);
7354 if (boardIndex >= forwardMostMove &&
7355 !(gameMode == IcsObserving && ics_gamenum == -1)) {
7356 backwardMostMove = blackPlaysFirst ? 1 : 0;
7359 (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
7360 EP_UNKNOWN, fromY, fromX, toY, toX, promoChar,
7361 parseList[boardIndex]);
7362 CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
7363 {int i; for(i=0; i<BOARD_SIZE; i++) castlingRights[boardIndex+1][i] = castlingRights[boardIndex][i];}
7364 /* currentMoveString is set as a side-effect of yylex */
7365 strcpy(moveList[boardIndex], currentMoveString);
7366 strcat(moveList[boardIndex], "\n");
7368 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex],
7369 castlingRights[boardIndex], &epStatus[boardIndex]);
7370 switch (MateTest(boards[boardIndex], PosFlags(boardIndex),
7371 EP_UNKNOWN, castlingRights[boardIndex]) ) {
7377 if(gameInfo.variant != VariantShogi)
7378 strcat(parseList[boardIndex - 1], "+");
7382 strcat(parseList[boardIndex - 1], "#");
7389 /* Apply a move to the given board */
7391 ApplyMove(fromX, fromY, toX, toY, promoChar, board, castling, ep)
7392 int fromX, fromY, toX, toY;
7398 ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
7400 /* [HGM] compute & store e.p. status and castling rights for new position */
7401 /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
7404 if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
7408 if( board[toY][toX] != EmptySquare )
7411 if( board[fromY][fromX] == WhitePawn ) {
7412 if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
7415 if(toX>BOARD_LEFT && board[toY][toX-1] == BlackPawn &&
7416 gameInfo.variant != VariantBerolina || toX < fromX)
7417 *ep = toX | berolina;
7418 if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
7419 gameInfo.variant != VariantBerolina || toX > fromX)
7423 if( board[fromY][fromX] == BlackPawn ) {
7424 if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
7426 if( toY-fromY== -2) {
7427 if(toX>BOARD_LEFT && board[toY][toX-1] == WhitePawn &&
7428 gameInfo.variant != VariantBerolina || toX < fromX)
7429 *ep = toX | berolina;
7430 if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
7431 gameInfo.variant != VariantBerolina || toX > fromX)
7436 for(i=0; i<nrCastlingRights; i++) {
7437 if(castling[i] == fromX && castlingRank[i] == fromY ||
7438 castling[i] == toX && castlingRank[i] == toY
7439 ) castling[i] = -1; // revoke for moved or captured piece
7444 /* [HGM] In Shatranj and Courier all promotions are to Ferz */
7445 if((gameInfo.variant==VariantShatranj || gameInfo.variant==VariantCourier)
7446 && promoChar != 0) promoChar = PieceToChar(WhiteFerz);
7448 if (fromX == toX && fromY == toY) return;
7450 if (fromY == DROP_RANK) {
7452 piece = board[toY][toX] = (ChessSquare) fromX;
7454 piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
7455 king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
7456 if(gameInfo.variant == VariantKnightmate)
7457 king += (int) WhiteUnicorn - (int) WhiteKing;
7459 /* Code added by Tord: */
7460 /* FRC castling assumed when king captures friendly rook. */
7461 if (board[fromY][fromX] == WhiteKing &&
7462 board[toY][toX] == WhiteRook) {
7463 board[fromY][fromX] = EmptySquare;
7464 board[toY][toX] = EmptySquare;
7466 board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
7468 board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
7470 } else if (board[fromY][fromX] == BlackKing &&
7471 board[toY][toX] == BlackRook) {
7472 board[fromY][fromX] = EmptySquare;
7473 board[toY][toX] = EmptySquare;
7475 board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
7477 board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
7479 /* End of code added by Tord */
7481 } else if (board[fromY][fromX] == king
7482 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7483 && toY == fromY && toX > fromX+1) {
7484 board[fromY][fromX] = EmptySquare;
7485 board[toY][toX] = king;
7486 board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
7487 board[fromY][BOARD_RGHT-1] = EmptySquare;
7488 } else if (board[fromY][fromX] == king
7489 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7490 && toY == fromY && toX < fromX-1) {
7491 board[fromY][fromX] = EmptySquare;
7492 board[toY][toX] = king;
7493 board[toY][toX+1] = board[fromY][BOARD_LEFT];
7494 board[fromY][BOARD_LEFT] = EmptySquare;
7495 } else if (board[fromY][fromX] == WhitePawn
7496 && toY == BOARD_HEIGHT-1
7497 && gameInfo.variant != VariantXiangqi
7499 /* white pawn promotion */
7500 board[toY][toX] = CharToPiece(ToUpper(promoChar));
7501 if (board[toY][toX] == EmptySquare) {
7502 board[toY][toX] = WhiteQueen;
7504 if(gameInfo.variant==VariantBughouse ||
7505 gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
7506 board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
7507 board[fromY][fromX] = EmptySquare;
7508 } else if ((fromY == BOARD_HEIGHT-4)
7510 && gameInfo.variant != VariantXiangqi
7511 && gameInfo.variant != VariantBerolina
7512 && (board[fromY][fromX] == WhitePawn)
7513 && (board[toY][toX] == EmptySquare)) {
7514 board[fromY][fromX] = EmptySquare;
7515 board[toY][toX] = WhitePawn;
7516 captured = board[toY - 1][toX];
7517 board[toY - 1][toX] = EmptySquare;
7518 } else if ((fromY == BOARD_HEIGHT-4)
7520 && gameInfo.variant == VariantBerolina
7521 && (board[fromY][fromX] == WhitePawn)
7522 && (board[toY][toX] == EmptySquare)) {
7523 board[fromY][fromX] = EmptySquare;
7524 board[toY][toX] = WhitePawn;
7525 if(oldEP & EP_BEROLIN_A) {
7526 captured = board[fromY][fromX-1];
7527 board[fromY][fromX-1] = EmptySquare;
7528 }else{ captured = board[fromY][fromX+1];
7529 board[fromY][fromX+1] = EmptySquare;
7531 } else if (board[fromY][fromX] == king
7532 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7533 && toY == fromY && toX > fromX+1) {
7534 board[fromY][fromX] = EmptySquare;
7535 board[toY][toX] = king;
7536 board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
7537 board[fromY][BOARD_RGHT-1] = EmptySquare;
7538 } else if (board[fromY][fromX] == king
7539 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7540 && toY == fromY && toX < fromX-1) {
7541 board[fromY][fromX] = EmptySquare;
7542 board[toY][toX] = king;
7543 board[toY][toX+1] = board[fromY][BOARD_LEFT];
7544 board[fromY][BOARD_LEFT] = EmptySquare;
7545 } else if (fromY == 7 && fromX == 3
7546 && board[fromY][fromX] == BlackKing
7547 && toY == 7 && toX == 5) {
7548 board[fromY][fromX] = EmptySquare;
7549 board[toY][toX] = BlackKing;
7550 board[fromY][7] = EmptySquare;
7551 board[toY][4] = BlackRook;
7552 } else if (fromY == 7 && fromX == 3
7553 && board[fromY][fromX] == BlackKing
7554 && toY == 7 && toX == 1) {
7555 board[fromY][fromX] = EmptySquare;
7556 board[toY][toX] = BlackKing;
7557 board[fromY][0] = EmptySquare;
7558 board[toY][2] = BlackRook;
7559 } else if (board[fromY][fromX] == BlackPawn
7561 && gameInfo.variant != VariantXiangqi
7563 /* black pawn promotion */
7564 board[0][toX] = CharToPiece(ToLower(promoChar));
7565 if (board[0][toX] == EmptySquare) {
7566 board[0][toX] = BlackQueen;
7568 if(gameInfo.variant==VariantBughouse ||
7569 gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
7570 board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
7571 board[fromY][fromX] = EmptySquare;
7572 } else if ((fromY == 3)
7574 && gameInfo.variant != VariantXiangqi
7575 && gameInfo.variant != VariantBerolina
7576 && (board[fromY][fromX] == BlackPawn)
7577 && (board[toY][toX] == EmptySquare)) {
7578 board[fromY][fromX] = EmptySquare;
7579 board[toY][toX] = BlackPawn;
7580 captured = board[toY + 1][toX];
7581 board[toY + 1][toX] = EmptySquare;
7582 } else if ((fromY == 3)
7584 && gameInfo.variant == VariantBerolina
7585 && (board[fromY][fromX] == BlackPawn)
7586 && (board[toY][toX] == EmptySquare)) {
7587 board[fromY][fromX] = EmptySquare;
7588 board[toY][toX] = BlackPawn;
7589 if(oldEP & EP_BEROLIN_A) {
7590 captured = board[fromY][fromX-1];
7591 board[fromY][fromX-1] = EmptySquare;
7592 }else{ captured = board[fromY][fromX+1];
7593 board[fromY][fromX+1] = EmptySquare;
7596 board[toY][toX] = board[fromY][fromX];
7597 board[fromY][fromX] = EmptySquare;
7600 /* [HGM] now we promote for Shogi, if needed */
7601 if(gameInfo.variant == VariantShogi && promoChar == 'q')
7602 board[toY][toX] = (ChessSquare) (PROMOTED piece);
7605 if (gameInfo.holdingsWidth != 0) {
7607 /* !!A lot more code needs to be written to support holdings */
7608 /* [HGM] OK, so I have written it. Holdings are stored in the */
7609 /* penultimate board files, so they are automaticlly stored */
7610 /* in the game history. */
7611 if (fromY == DROP_RANK) {
7612 /* Delete from holdings, by decreasing count */
7613 /* and erasing image if necessary */
7615 if(p < (int) BlackPawn) { /* white drop */
7616 p -= (int)WhitePawn;
7617 p = PieceToNumber((ChessSquare)p);
7618 if(p >= gameInfo.holdingsSize) p = 0;
7619 if(--board[p][BOARD_WIDTH-2] <= 0)
7620 board[p][BOARD_WIDTH-1] = EmptySquare;
7621 if((int)board[p][BOARD_WIDTH-2] < 0)
7622 board[p][BOARD_WIDTH-2] = 0;
7623 } else { /* black drop */
7624 p -= (int)BlackPawn;
7625 p = PieceToNumber((ChessSquare)p);
7626 if(p >= gameInfo.holdingsSize) p = 0;
7627 if(--board[BOARD_HEIGHT-1-p][1] <= 0)
7628 board[BOARD_HEIGHT-1-p][0] = EmptySquare;
7629 if((int)board[BOARD_HEIGHT-1-p][1] < 0)
7630 board[BOARD_HEIGHT-1-p][1] = 0;
7633 if (captured != EmptySquare && gameInfo.holdingsSize > 0
7634 && gameInfo.variant != VariantBughouse ) {
7635 /* [HGM] holdings: Add to holdings, if holdings exist */
7636 if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
7637 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
7638 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
7641 if (p >= (int) BlackPawn) {
7642 p -= (int)BlackPawn;
7643 if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
7644 /* in Shogi restore piece to its original first */
7645 captured = (ChessSquare) (DEMOTED captured);
7648 p = PieceToNumber((ChessSquare)p);
7649 if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
7650 board[p][BOARD_WIDTH-2]++;
7651 board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
7653 p -= (int)WhitePawn;
7654 if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
7655 captured = (ChessSquare) (DEMOTED captured);
7658 p = PieceToNumber((ChessSquare)p);
7659 if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
7660 board[BOARD_HEIGHT-1-p][1]++;
7661 board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
7664 } else if (gameInfo.variant == VariantAtomic) {
7665 if (captured != EmptySquare) {
7667 for (y = toY-1; y <= toY+1; y++) {
7668 for (x = toX-1; x <= toX+1; x++) {
7669 if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
7670 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
7671 board[y][x] = EmptySquare;
7675 board[toY][toX] = EmptySquare;
7678 if(gameInfo.variant == VariantShogi && promoChar != NULLCHAR && promoChar != '=') {
7679 /* [HGM] Shogi promotions */
7680 board[toY][toX] = (ChessSquare) (PROMOTED piece);
7683 if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
7684 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
7685 // [HGM] superchess: take promotion piece out of holdings
7686 int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
7687 if((int)piece < (int)BlackPawn) { // determine stm from piece color
7688 if(!--board[k][BOARD_WIDTH-2])
7689 board[k][BOARD_WIDTH-1] = EmptySquare;
7691 if(!--board[BOARD_HEIGHT-1-k][1])
7692 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
7698 /* Updates forwardMostMove */
7700 MakeMove(fromX, fromY, toX, toY, promoChar)
7701 int fromX, fromY, toX, toY;
7704 // forwardMostMove++; // [HGM] bare: moved downstream
7706 if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
7707 int timeLeft; static int lastLoadFlag=0; int king, piece;
7708 piece = boards[forwardMostMove][fromY][fromX];
7709 king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
7710 if(gameInfo.variant == VariantKnightmate)
7711 king += (int) WhiteUnicorn - (int) WhiteKing;
7712 if(forwardMostMove == 0) {
7714 fprintf(serverMoves, "%s;", second.tidy);
7715 fprintf(serverMoves, "%s;", first.tidy);
7716 if(!blackPlaysFirst)
7717 fprintf(serverMoves, "%s;", second.tidy);
7718 } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
7719 lastLoadFlag = loadFlag;
7721 fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
7722 // print castling suffix
7723 if( toY == fromY && piece == king ) {
7725 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
7727 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
7730 if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
7731 boards[forwardMostMove][fromY][fromX] == BlackPawn ) &&
7732 boards[forwardMostMove][toY][toX] == EmptySquare
7734 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
7736 if(promoChar != NULLCHAR)
7737 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);
7739 fprintf(serverMoves, "/%d/%d",
7740 pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
7741 if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
7742 else timeLeft = blackTimeRemaining/1000;
7743 fprintf(serverMoves, "/%d", timeLeft);
7745 fflush(serverMoves);
7748 if (forwardMostMove+1 >= MAX_MOVES) {
7749 DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
7753 if (commentList[forwardMostMove+1] != NULL) {
7754 free(commentList[forwardMostMove+1]);
7755 commentList[forwardMostMove+1] = NULL;
7757 CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
7758 {int i; for(i=0; i<BOARD_SIZE; i++) castlingRights[forwardMostMove+1][i] = castlingRights[forwardMostMove][i];}
7759 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1],
7760 castlingRights[forwardMostMove+1], &epStatus[forwardMostMove+1]);
7761 forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
7762 SwitchClocks(); // uses forwardMostMove, so must be done after incrementing it !
7763 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
7764 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
7765 gameInfo.result = GameUnfinished;
7766 if (gameInfo.resultDetails != NULL) {
7767 free(gameInfo.resultDetails);
7768 gameInfo.resultDetails = NULL;
7770 CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
7771 moveList[forwardMostMove - 1]);
7772 (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
7773 PosFlags(forwardMostMove - 1), EP_UNKNOWN,
7774 fromY, fromX, toY, toX, promoChar,
7775 parseList[forwardMostMove - 1]);
7776 switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove),
7777 epStatus[forwardMostMove], /* [HGM] use true e.p. */
7778 castlingRights[forwardMostMove]) ) {
7784 if(gameInfo.variant != VariantShogi)
7785 strcat(parseList[forwardMostMove - 1], "+");
7789 strcat(parseList[forwardMostMove - 1], "#");
7792 if (appData.debugMode) {
7793 fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
7798 /* Updates currentMove if not pausing */
7800 ShowMove(fromX, fromY, toX, toY)
7802 int instant = (gameMode == PlayFromGameFile) ?
7803 (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
7805 if(appData.noGUI) return;
7807 if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile)
7811 if (forwardMostMove == currentMove + 1)
7814 // AnimateMove(boards[forwardMostMove - 1],
7815 // fromX, fromY, toX, toY);
7817 if (appData.highlightLastMove)
7819 SetHighlights(fromX, fromY, toX, toY);
7822 currentMove = forwardMostMove;
7825 if (instant) return;
7827 DisplayMove(currentMove - 1);
7828 DrawPosition(FALSE, boards[currentMove]);
7829 DisplayBothClocks();
7830 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
7835 void SendEgtPath(ChessProgramState *cps)
7836 { /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
7837 char buf[MSG_SIZ], name[MSG_SIZ], *p;
7839 if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
7842 char c, *q = name+1, *r, *s;
7844 name[0] = ','; // extract next format name from feature and copy with prefixed ','
7845 while(*p && *p != ',') *q++ = *p++;
7847 if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
7848 strcmp(name, ",nalimov:") == 0 ) {
7849 // take nalimov path from the menu-changeable option first, if it is defined
7850 sprintf(buf, "egtpath nalimov %s\n", appData.defaultPathEGTB);
7851 SendToProgram(buf,cps); // send egtbpath command for nalimov
7853 if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
7854 (s = StrStr(appData.egtFormats, name)) != NULL) {
7855 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
7856 s = r = StrStr(s, ":") + 1; // beginning of path info
7857 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
7858 c = *r; *r = 0; // temporarily null-terminate path info
7859 *--q = 0; // strip of trailig ':' from name
7860 sprintf(buf, "egtpath %s %s\n", name+1, s);
7862 SendToProgram(buf,cps); // send egtbpath command for this format
7864 if(*p == ',') p++; // read away comma to position for next format name
7869 InitChessProgram(cps, setup)
7870 ChessProgramState *cps;
7871 int setup; /* [HGM] needed to setup FRC opening position */
7873 char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
7874 if (appData.noChessProgram) return;
7875 hintRequested = FALSE;
7876 bookRequested = FALSE;
7878 /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
7879 /* moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
7880 if(cps->memSize) { /* [HGM] memory */
7881 sprintf(buf, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
7882 SendToProgram(buf, cps);
7884 SendEgtPath(cps); /* [HGM] EGT */
7885 if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
7886 sprintf(buf, "cores %d\n", appData.smpCores);
7887 SendToProgram(buf, cps);
7890 SendToProgram(cps->initString, cps);
7891 if (gameInfo.variant != VariantNormal &&
7892 gameInfo.variant != VariantLoadable
7893 /* [HGM] also send variant if board size non-standard */
7894 || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
7896 char *v = VariantName(gameInfo.variant);
7897 if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
7898 /* [HGM] in protocol 1 we have to assume all variants valid */
7899 sprintf(buf, _("Variant %s not supported by %s"), v, cps->tidy);
7900 DisplayFatalError(buf, 0, 1);
7904 /* [HGM] make prefix for non-standard board size. Awkward testing... */
7905 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7906 if( gameInfo.variant == VariantXiangqi )
7907 overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
7908 if( gameInfo.variant == VariantShogi )
7909 overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
7910 if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
7911 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
7912 if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
7913 gameInfo.variant == VariantGothic || gameInfo.variant == VariantFalcon )
7914 overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7915 if( gameInfo.variant == VariantCourier )
7916 overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7917 if( gameInfo.variant == VariantSuper )
7918 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
7919 if( gameInfo.variant == VariantGreat )
7920 overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
7923 sprintf(b, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
7924 gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
7925 /* [HGM] varsize: try first if this defiant size variant is specifically known */
7926 if(StrStr(cps->variants, b) == NULL) {
7927 // specific sized variant not known, check if general sizing allowed
7928 if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
7929 if(StrStr(cps->variants, "boardsize") == NULL) {
7930 sprintf(buf, "Board size %dx%d+%d not supported by %s",
7931 gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
7932 DisplayFatalError(buf, 0, 1);
7935 /* [HGM] here we really should compare with the maximum supported board size */
7938 } else sprintf(b, "%s", VariantName(gameInfo.variant));
7939 sprintf(buf, "variant %s\n", b);
7940 SendToProgram(buf, cps);
7942 currentlyInitializedVariant = gameInfo.variant;
7944 /* [HGM] send opening position in FRC to first engine */
7946 SendToProgram("force\n", cps);
7948 /* engine is now in force mode! Set flag to wake it up after first move. */
7949 setboardSpoiledMachineBlack = 1;
7953 snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
7954 SendToProgram(buf, cps);
7956 cps->maybeThinking = FALSE;
7957 cps->offeredDraw = 0;
7958 if (!appData.icsActive) {
7959 SendTimeControl(cps, movesPerSession, timeControl,
7960 timeIncrement, appData.searchDepth,
7963 if (appData.showThinking
7964 // [HGM] thinking: four options require thinking output to be sent
7965 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
7967 SendToProgram("post\n", cps);
7969 SendToProgram("hard\n", cps);
7970 if (!appData.ponderNextMove) {
7971 /* Warning: "easy" is a toggle in GNU Chess, so don't send
7972 it without being sure what state we are in first. "hard"
7973 is not a toggle, so that one is OK.
7975 SendToProgram("easy\n", cps);
7978 sprintf(buf, "ping %d\n", ++cps->lastPing);
7979 SendToProgram(buf, cps);
7981 cps->initDone = TRUE;
7986 StartChessProgram(cps)
7987 ChessProgramState *cps;
7992 if (appData.noChessProgram) return;
7993 cps->initDone = FALSE;
7995 if (strcmp(cps->host, "localhost") == 0) {
7996 err = StartChildProcess(cps->program, cps->dir, &cps->pr);
7997 } else if (*appData.remoteShell == NULLCHAR) {
7998 err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
8000 if (*appData.remoteUser == NULLCHAR) {
8001 snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
8004 snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
8005 cps->host, appData.remoteUser, cps->program);
8007 err = StartChildProcess(buf, "", &cps->pr);
8011 sprintf(buf, _("Startup failure on '%s'"), cps->program);
8012 DisplayFatalError(buf, err, 1);
8018 cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
8019 if (cps->protocolVersion > 1) {
8020 sprintf(buf, "xboard\nprotover %d\n", cps->protocolVersion);
8021 cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
8022 cps->comboCnt = 0; // and values of combo boxes
8023 SendToProgram(buf, cps);
8025 SendToProgram("xboard\n", cps);
8031 TwoMachinesEventIfReady P((void))
8033 if (first.lastPing != first.lastPong) {
8034 DisplayMessage("", _("Waiting for first chess program"));
8035 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
8038 if (second.lastPing != second.lastPong) {
8039 DisplayMessage("", _("Waiting for second chess program"));
8040 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
8048 NextMatchGame P((void))
8050 int index; /* [HGM] autoinc: step load index during match */
8052 if (*appData.loadGameFile != NULLCHAR) {
8053 index = appData.loadGameIndex;
8054 if(index < 0) { // [HGM] autoinc
8055 lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
8056 if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
8058 LoadGameFromFile(appData.loadGameFile,
8060 appData.loadGameFile, FALSE);
8061 } else if (*appData.loadPositionFile != NULLCHAR) {
8062 index = appData.loadPositionIndex;
8063 if(index < 0) { // [HGM] autoinc
8064 lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
8065 if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
8067 LoadPositionFromFile(appData.loadPositionFile,
8069 appData.loadPositionFile);
8071 TwoMachinesEventIfReady();
8074 void UserAdjudicationEvent( int result )
8076 ChessMove gameResult = GameIsDrawn;
8079 gameResult = WhiteWins;
8081 else if( result < 0 ) {
8082 gameResult = BlackWins;
8085 if( gameMode == TwoMachinesPlay ) {
8086 GameEnds( gameResult, "User adjudication", GE_XBOARD );
8091 // [HGM] save: calculate checksum of game to make games easily identifiable
8092 int StringCheckSum(char *s)
8095 if(s==NULL) return 0;
8096 while(*s) i = i*259 + *s++;
8103 for(i=backwardMostMove; i<forwardMostMove; i++) {
8104 sum += pvInfoList[i].depth;
8105 sum += StringCheckSum(parseList[i]);
8106 sum += StringCheckSum(commentList[i]);
8109 if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
8110 return sum + StringCheckSum(commentList[i]);
8111 } // end of save patch
8114 GameEnds(result, resultDetails, whosays)
8116 char *resultDetails;
8119 GameMode nextGameMode;
8123 if(endingGame) return; /* [HGM] crash: forbid recursion */
8126 if (appData.debugMode) {
8127 fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
8128 result, resultDetails ? resultDetails : "(null)", whosays);
8131 if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
8132 /* If we are playing on ICS, the server decides when the
8133 game is over, but the engine can offer to draw, claim
8137 if (appData.zippyPlay && first.initDone) {
8138 if (result == GameIsDrawn) {
8139 /* In case draw still needs to be claimed */
8140 SendToICS(ics_prefix);
8141 SendToICS("draw\n");
8142 } else if (StrCaseStr(resultDetails, "resign")) {
8143 SendToICS(ics_prefix);
8144 SendToICS("resign\n");
8148 endingGame = 0; /* [HGM] crash */
8152 /* If we're loading the game from a file, stop */
8153 if (whosays == GE_FILE) {
8154 (void) StopLoadGameTimer();
8158 /* Cancel draw offers */
8159 first.offeredDraw = second.offeredDraw = 0;
8161 /* If this is an ICS game, only ICS can really say it's done;
8162 if not, anyone can. */
8163 isIcsGame = (gameMode == IcsPlayingWhite ||
8164 gameMode == IcsPlayingBlack ||
8165 gameMode == IcsObserving ||
8166 gameMode == IcsExamining);
8168 if (!isIcsGame || whosays == GE_ICS) {
8169 /* OK -- not an ICS game, or ICS said it was done */
8171 if (!isIcsGame && !appData.noChessProgram)
8172 SetUserThinkingEnables();
8174 /* [HGM] if a machine claims the game end we verify this claim */
8175 if(gameMode == TwoMachinesPlay && appData.testClaims) {
8176 if(appData.testLegality && whosays >= GE_ENGINE1 ) {
8178 ChessMove trueResult = (ChessMove) -1;
8180 claimer = whosays == GE_ENGINE1 ? /* color of claimer */
8181 first.twoMachinesColor[0] :
8182 second.twoMachinesColor[0] ;
8184 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
8185 if(epStatus[forwardMostMove] == EP_CHECKMATE) {
8186 /* [HGM] verify: engine mate claims accepted if they were flagged */
8187 trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
8189 if(epStatus[forwardMostMove] == EP_WINS) { // added code for games where being mated is a win
8190 /* [HGM] verify: engine mate claims accepted if they were flagged */
8191 trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8193 if(epStatus[forwardMostMove] == EP_STALEMATE) { // only used to indicate draws now
8194 trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
8197 // now verify win claims, but not in drop games, as we don't understand those yet
8198 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
8199 || gameInfo.variant == VariantGreat) &&
8200 (result == WhiteWins && claimer == 'w' ||
8201 result == BlackWins && claimer == 'b' ) ) { // case to verify: engine claims own win
8202 if (appData.debugMode) {
8203 fprintf(debugFP, "result=%d sp=%d move=%d\n",
8204 result, epStatus[forwardMostMove], forwardMostMove);
8206 if(result != trueResult) {
8207 sprintf(buf, "False win claim: '%s'", resultDetails);
8208 result = claimer == 'w' ? BlackWins : WhiteWins;
8209 resultDetails = buf;
8212 if( result == GameIsDrawn && epStatus[forwardMostMove] > EP_DRAWS
8213 && (forwardMostMove <= backwardMostMove ||
8214 epStatus[forwardMostMove-1] > EP_DRAWS ||
8215 (claimer=='b')==(forwardMostMove&1))
8217 /* [HGM] verify: draws that were not flagged are false claims */
8218 sprintf(buf, "False draw claim: '%s'", resultDetails);
8219 result = claimer == 'w' ? BlackWins : WhiteWins;
8220 resultDetails = buf;
8222 /* (Claiming a loss is accepted no questions asked!) */
8225 /* [HGM] bare: don't allow bare King to win */
8226 if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
8227 && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
8228 && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
8229 && result != GameIsDrawn)
8230 { int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
8231 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
8232 int p = (int)boards[forwardMostMove][i][j] - color;
8233 if(p >= 0 && p <= (int)WhiteKing) k++;
8235 if (appData.debugMode) {
8236 fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
8237 result, resultDetails ? resultDetails : "(null)", whosays, k, color);
8240 result = GameIsDrawn;
8241 sprintf(buf, "%s but bare king", resultDetails);
8242 resultDetails = buf;
8247 if(serverMoves != NULL && !loadFlag) { char c = '=';
8248 if(result==WhiteWins) c = '+';
8249 if(result==BlackWins) c = '-';
8250 if(resultDetails != NULL)
8251 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
8253 if (resultDetails != NULL) {
8254 gameInfo.result = result;
8255 gameInfo.resultDetails = StrSave(resultDetails);
8257 /* display last move only if game was not loaded from file */
8258 if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
8259 DisplayMove(currentMove - 1);
8261 if (forwardMostMove != 0) {
8262 if (gameMode != PlayFromGameFile && gameMode != EditGame
8263 && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
8265 if (*appData.saveGameFile != NULLCHAR) {
8266 SaveGameToFile(appData.saveGameFile, TRUE);
8267 } else if (appData.autoSaveGames) {
8270 if (*appData.savePositionFile != NULLCHAR) {
8271 SavePositionToFile(appData.savePositionFile);
8276 /* Tell program how game ended in case it is learning */
8277 /* [HGM] Moved this to after saving the PGN, just in case */
8278 /* engine died and we got here through time loss. In that */
8279 /* case we will get a fatal error writing the pipe, which */
8280 /* would otherwise lose us the PGN. */
8281 /* [HGM] crash: not needed anymore, but doesn't hurt; */
8282 /* output during GameEnds should never be fatal anymore */
8283 if (gameMode == MachinePlaysWhite ||
8284 gameMode == MachinePlaysBlack ||
8285 gameMode == TwoMachinesPlay ||
8286 gameMode == IcsPlayingWhite ||
8287 gameMode == IcsPlayingBlack ||
8288 gameMode == BeginningOfGame) {
8290 sprintf(buf, "result %s {%s}\n", PGNResult(result),
8292 if (first.pr != NoProc) {
8293 SendToProgram(buf, &first);
8295 if (second.pr != NoProc &&
8296 gameMode == TwoMachinesPlay) {
8297 SendToProgram(buf, &second);
8302 if (appData.icsActive) {
8303 if (appData.quietPlay &&
8304 (gameMode == IcsPlayingWhite ||
8305 gameMode == IcsPlayingBlack)) {
8306 SendToICS(ics_prefix);
8307 SendToICS("set shout 1\n");
8309 nextGameMode = IcsIdle;
8310 ics_user_moved = FALSE;
8311 /* clean up premove. It's ugly when the game has ended and the
8312 * premove highlights are still on the board.
8316 ClearPremoveHighlights();
8317 DrawPosition(FALSE, boards[currentMove]);
8319 if (whosays == GE_ICS) {
8322 if (gameMode == IcsPlayingWhite)
8324 else if(gameMode == IcsPlayingBlack)
8328 if (gameMode == IcsPlayingBlack)
8330 else if(gameMode == IcsPlayingWhite)
8337 PlayIcsUnfinishedSound();
8340 } else if (gameMode == EditGame ||
8341 gameMode == PlayFromGameFile ||
8342 gameMode == AnalyzeMode ||
8343 gameMode == AnalyzeFile) {
8344 nextGameMode = gameMode;
8346 nextGameMode = EndOfGame;
8351 nextGameMode = gameMode;
8354 if (appData.noChessProgram) {
8355 gameMode = nextGameMode;
8357 endingGame = 0; /* [HGM] crash */
8362 /* Put first chess program into idle state */
8363 if (first.pr != NoProc &&
8364 (gameMode == MachinePlaysWhite ||
8365 gameMode == MachinePlaysBlack ||
8366 gameMode == TwoMachinesPlay ||
8367 gameMode == IcsPlayingWhite ||
8368 gameMode == IcsPlayingBlack ||
8369 gameMode == BeginningOfGame)) {
8370 SendToProgram("force\n", &first);
8371 if (first.usePing) {
8373 sprintf(buf, "ping %d\n", ++first.lastPing);
8374 SendToProgram(buf, &first);
8377 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
8378 /* Kill off first chess program */
8379 if (first.isr != NULL)
8380 RemoveInputSource(first.isr);
8383 if (first.pr != NoProc) {
8385 DoSleep( appData.delayBeforeQuit );
8386 SendToProgram("quit\n", &first);
8387 DoSleep( appData.delayAfterQuit );
8388 DestroyChildProcess(first.pr, first.useSigterm);
8393 /* Put second chess program into idle state */
8394 if (second.pr != NoProc &&
8395 gameMode == TwoMachinesPlay) {
8396 SendToProgram("force\n", &second);
8397 if (second.usePing) {
8399 sprintf(buf, "ping %d\n", ++second.lastPing);
8400 SendToProgram(buf, &second);
8403 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
8404 /* Kill off second chess program */
8405 if (second.isr != NULL)
8406 RemoveInputSource(second.isr);
8409 if (second.pr != NoProc) {
8410 DoSleep( appData.delayBeforeQuit );
8411 SendToProgram("quit\n", &second);
8412 DoSleep( appData.delayAfterQuit );
8413 DestroyChildProcess(second.pr, second.useSigterm);
8418 if (matchMode && gameMode == TwoMachinesPlay) {
8421 if (first.twoMachinesColor[0] == 'w') {
8428 if (first.twoMachinesColor[0] == 'b') {
8437 if (matchGame < appData.matchGames) {
8439 if(appData.sameColorGames <= 1) { /* [HGM] alternate: suppress color swap */
8440 tmp = first.twoMachinesColor;
8441 first.twoMachinesColor = second.twoMachinesColor;
8442 second.twoMachinesColor = tmp;
8444 gameMode = nextGameMode;
8446 if(appData.matchPause>10000 || appData.matchPause<10)
8447 appData.matchPause = 10000; /* [HGM] make pause adjustable */
8448 ScheduleDelayedEvent(NextMatchGame, appData.matchPause);
8449 endingGame = 0; /* [HGM] crash */
8453 gameMode = nextGameMode;
8454 sprintf(buf, _("Match %s vs. %s: final score %d-%d-%d"),
8455 first.tidy, second.tidy,
8456 first.matchWins, second.matchWins,
8457 appData.matchGames - (first.matchWins + second.matchWins));
8458 DisplayFatalError(buf, 0, 0);
8461 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
8462 !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
8464 gameMode = nextGameMode;
8466 endingGame = 0; /* [HGM] crash */
8469 /* Assumes program was just initialized (initString sent).
8470 Leaves program in force mode. */
8472 FeedMovesToProgram(cps, upto)
8473 ChessProgramState *cps;
8478 if (appData.debugMode)
8479 fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
8480 startedFromSetupPosition ? "position and " : "",
8481 backwardMostMove, upto, cps->which);
8482 if(currentlyInitializedVariant != gameInfo.variant) { char buf[MSG_SIZ];
8483 // [HGM] variantswitch: make engine aware of new variant
8484 if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
8485 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
8486 sprintf(buf, "variant %s\n", VariantName(gameInfo.variant));
8487 SendToProgram(buf, cps);
8488 currentlyInitializedVariant = gameInfo.variant;
8490 SendToProgram("force\n", cps);
8491 if (startedFromSetupPosition) {
8492 SendBoard(cps, backwardMostMove);
8493 if (appData.debugMode) {
8494 fprintf(debugFP, "feedMoves\n");
8497 for (i = backwardMostMove; i < upto; i++) {
8498 SendMoveToProgram(i, cps);
8504 ResurrectChessProgram()
8506 /* The chess program may have exited.
8507 If so, restart it and feed it all the moves made so far. */
8509 if (appData.noChessProgram || first.pr != NoProc) return;
8511 StartChessProgram(&first);
8512 InitChessProgram(&first, FALSE);
8513 FeedMovesToProgram(&first, currentMove);
8515 if (!first.sendTime) {
8516 /* can't tell gnuchess what its clock should read,
8517 so we bow to its notion. */
8519 timeRemaining[0][currentMove] = whiteTimeRemaining;
8520 timeRemaining[1][currentMove] = blackTimeRemaining;
8523 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
8524 appData.icsEngineAnalyze) && first.analysisSupport) {
8525 SendToProgram("analyze\n", &first);
8526 first.analyzing = TRUE;
8539 if (appData.debugMode) {
8540 fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
8541 redraw, init, gameMode);
8543 pausing = pauseExamInvalid = FALSE;
8544 startedFromSetupPosition = blackPlaysFirst = FALSE;
8546 whiteFlag = blackFlag = FALSE;
8547 userOfferedDraw = FALSE;
8548 hintRequested = bookRequested = FALSE;
8549 first.maybeThinking = FALSE;
8550 second.maybeThinking = FALSE;
8551 first.bookSuspend = FALSE; // [HGM] book
8552 second.bookSuspend = FALSE;
8553 thinkOutput[0] = NULLCHAR;
8554 lastHint[0] = NULLCHAR;
8555 ClearGameInfo(&gameInfo);
8556 gameInfo.variant = StringToVariant(appData.variant);
8557 ics_user_moved = ics_clock_paused = FALSE;
8558 ics_getting_history = H_FALSE;
8560 white_holding[0] = black_holding[0] = NULLCHAR;
8561 ClearProgramStats();
8562 opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
8566 flipView = appData.flipView;
8567 ClearPremoveHighlights();
8569 alarmSounded = FALSE;
8571 GameEnds((ChessMove) 0, NULL, GE_PLAYER);
8572 if(appData.serverMovesName != NULL) {
8573 /* [HGM] prepare to make moves file for broadcasting */
8574 clock_t t = clock();
8575 if(serverMoves != NULL) fclose(serverMoves);
8576 serverMoves = fopen(appData.serverMovesName, "r");
8577 if(serverMoves != NULL) {
8578 fclose(serverMoves);
8579 /* delay 15 sec before overwriting, so all clients can see end */
8580 while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
8582 serverMoves = fopen(appData.serverMovesName, "w");
8586 gameMode = BeginningOfGame;
8589 if(appData.icsActive) gameInfo.variant = VariantNormal;
8590 currentMove = forwardMostMove = backwardMostMove = 0;
8591 InitPosition(redraw);
8592 for (i = 0; i < MAX_MOVES; i++) {
8593 if (commentList[i] != NULL) {
8594 free(commentList[i]);
8595 commentList[i] = NULL;
8600 timeRemaining[0][0] = whiteTimeRemaining;
8601 timeRemaining[1][0] = blackTimeRemaining;
8602 if (first.pr == NULL) {
8603 StartChessProgram(&first);
8606 InitChessProgram(&first, startedFromSetupPosition);
8610 DisplayMessage("", "");
8611 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
8612 lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
8620 if (!AutoPlayOneMove())
8622 if (matchMode || appData.timeDelay == 0)
8624 if (appData.timeDelay < 0 || gameMode == AnalyzeFile)
8626 StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
8635 int fromX, fromY, toX, toY;
8637 if (appData.debugMode) {
8638 fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
8641 if (gameMode != PlayFromGameFile)
8644 if (currentMove >= forwardMostMove) {
8645 gameMode = EditGame;
8648 /* [AS] Clear current move marker at the end of a game */
8649 /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
8654 toX = moveList[currentMove][2] - AAA;
8655 toY = moveList[currentMove][3] - ONE;
8657 if (moveList[currentMove][1] == '@') {
8658 if (appData.highlightLastMove) {
8659 SetHighlights(-1, -1, toX, toY);
8662 fromX = moveList[currentMove][0] - AAA;
8663 fromY = moveList[currentMove][1] - ONE;
8665 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
8667 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
8669 if (appData.highlightLastMove) {
8670 SetHighlights(fromX, fromY, toX, toY);
8673 DisplayMove(currentMove);
8674 SendMoveToProgram(currentMove++, &first);
8675 DisplayBothClocks();
8676 DrawPosition(FALSE, boards[currentMove]);
8677 // [HGM] PV info: always display, routine tests if empty
8678 DisplayComment(currentMove - 1, commentList[currentMove]);
8684 LoadGameOneMove(readAhead)
8685 ChessMove readAhead;
8687 int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
8688 char promoChar = NULLCHAR;
8693 if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
8694 gameMode != AnalyzeMode && gameMode != Training) {
8699 yyboardindex = forwardMostMove;
8700 if (readAhead != (ChessMove)0) {
8701 moveType = readAhead;
8703 if (gameFileFP == NULL)
8705 moveType = (ChessMove) yylex();
8711 if (appData.debugMode)
8712 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
8714 if (*p == '{' || *p == '[' || *p == '(') {
8715 p[strlen(p) - 1] = NULLCHAR;
8719 /* append the comment but don't display it */
8720 while (*p == '\n') p++;
8721 AppendComment(currentMove, p);
8724 case WhiteCapturesEnPassant:
8725 case BlackCapturesEnPassant:
8726 case WhitePromotionChancellor:
8727 case BlackPromotionChancellor:
8728 case WhitePromotionArchbishop:
8729 case BlackPromotionArchbishop:
8730 case WhitePromotionCentaur:
8731 case BlackPromotionCentaur:
8732 case WhitePromotionQueen:
8733 case BlackPromotionQueen:
8734 case WhitePromotionRook:
8735 case BlackPromotionRook:
8736 case WhitePromotionBishop:
8737 case BlackPromotionBishop:
8738 case WhitePromotionKnight:
8739 case BlackPromotionKnight:
8740 case WhitePromotionKing:
8741 case BlackPromotionKing:
8743 case WhiteKingSideCastle:
8744 case WhiteQueenSideCastle:
8745 case BlackKingSideCastle:
8746 case BlackQueenSideCastle:
8747 case WhiteKingSideCastleWild:
8748 case WhiteQueenSideCastleWild:
8749 case BlackKingSideCastleWild:
8750 case BlackQueenSideCastleWild:
8752 case WhiteHSideCastleFR:
8753 case WhiteASideCastleFR:
8754 case BlackHSideCastleFR:
8755 case BlackASideCastleFR:
8757 if (appData.debugMode)
8758 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
8759 fromX = currentMoveString[0] - AAA;
8760 fromY = currentMoveString[1] - ONE;
8761 toX = currentMoveString[2] - AAA;
8762 toY = currentMoveString[3] - ONE;
8763 promoChar = currentMoveString[4];
8768 if (appData.debugMode)
8769 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
8770 fromX = moveType == WhiteDrop ?
8771 (int) CharToPiece(ToUpper(currentMoveString[0])) :
8772 (int) CharToPiece(ToLower(currentMoveString[0]));
8774 toX = currentMoveString[2] - AAA;
8775 toY = currentMoveString[3] - ONE;
8781 case GameUnfinished:
8782 if (appData.debugMode)
8783 fprintf(debugFP, "Parsed game end: %s\n", yy_text);
8784 p = strchr(yy_text, '{');
8785 if (p == NULL) p = strchr(yy_text, '(');
8788 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
8790 q = strchr(p, *p == '{' ? '}' : ')');
8791 if (q != NULL) *q = NULLCHAR;
8794 GameEnds(moveType, p, GE_FILE);
8796 if (cmailMsgLoaded) {
8798 flipView = WhiteOnMove(currentMove);
8799 if (moveType == GameUnfinished) flipView = !flipView;
8800 if (appData.debugMode)
8801 fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
8805 case (ChessMove) 0: /* end of file */
8806 if (appData.debugMode)
8807 fprintf(debugFP, "Parser hit end of file\n");
8808 switch (MateTest(boards[currentMove], PosFlags(currentMove),
8809 EP_UNKNOWN, castlingRights[currentMove]) ) {
8815 if (WhiteOnMove(currentMove)) {
8816 GameEnds(BlackWins, "Black mates", GE_FILE);
8818 GameEnds(WhiteWins, "White mates", GE_FILE);
8822 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
8829 if (lastLoadGameStart == GNUChessGame) {
8830 /* GNUChessGames have numbers, but they aren't move numbers */
8831 if (appData.debugMode)
8832 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
8833 yy_text, (int) moveType);
8834 return LoadGameOneMove((ChessMove)0); /* tail recursion */
8836 /* else fall thru */
8841 /* Reached start of next game in file */
8842 if (appData.debugMode)
8843 fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
8844 switch (MateTest(boards[currentMove], PosFlags(currentMove),
8845 EP_UNKNOWN, castlingRights[currentMove]) ) {
8851 if (WhiteOnMove(currentMove)) {
8852 GameEnds(BlackWins, "Black mates", GE_FILE);
8854 GameEnds(WhiteWins, "White mates", GE_FILE);
8858 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
8864 case PositionDiagram: /* should not happen; ignore */
8865 case ElapsedTime: /* ignore */
8866 case NAG: /* ignore */
8867 if (appData.debugMode)
8868 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
8869 yy_text, (int) moveType);
8870 return LoadGameOneMove((ChessMove)0); /* tail recursion */
8873 if (appData.testLegality) {
8874 if (appData.debugMode)
8875 fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
8876 sprintf(move, _("Illegal move: %d.%s%s"),
8877 (forwardMostMove / 2) + 1,
8878 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8879 DisplayError(move, 0);
8882 if (appData.debugMode)
8883 fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
8884 yy_text, currentMoveString);
8885 fromX = currentMoveString[0] - AAA;
8886 fromY = currentMoveString[1] - ONE;
8887 toX = currentMoveString[2] - AAA;
8888 toY = currentMoveString[3] - ONE;
8889 promoChar = currentMoveString[4];
8894 if (appData.debugMode)
8895 fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
8896 sprintf(move, _("Ambiguous move: %d.%s%s"),
8897 (forwardMostMove / 2) + 1,
8898 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8899 DisplayError(move, 0);
8904 case ImpossibleMove:
8905 if (appData.debugMode)
8906 fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
8907 sprintf(move, _("Illegal move: %d.%s%s"),
8908 (forwardMostMove / 2) + 1,
8909 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8910 DisplayError(move, 0);
8916 if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
8917 DrawPosition(FALSE, boards[currentMove]);
8918 DisplayBothClocks();
8919 if (!appData.matchMode) // [HGM] PV info: routine tests if empty
8920 DisplayComment(currentMove - 1, commentList[currentMove]);
8922 (void) StopLoadGameTimer();
8924 cmailOldMove = forwardMostMove;
8927 /* currentMoveString is set as a side-effect of yylex */
8928 strcat(currentMoveString, "\n");
8929 strcpy(moveList[forwardMostMove], currentMoveString);
8931 thinkOutput[0] = NULLCHAR;
8932 MakeMove(fromX, fromY, toX, toY, promoChar);
8933 currentMove = forwardMostMove;
8938 /* Load the nth game from the given file */
8940 LoadGameFromFile(filename, n, title, useList)
8944 /*Boolean*/ int useList;
8949 if (strcmp(filename, "-") == 0) {
8953 f = fopen(filename, "rb");
8955 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
8956 DisplayError(buf, errno);
8960 if (fseek(f, 0, 0) == -1) {
8961 /* f is not seekable; probably a pipe */
8964 if (useList && n == 0) {
8965 int error = GameListBuild(f);
8967 DisplayError(_("Cannot build game list"), error);
8968 } else if (!ListEmpty(&gameList) &&
8969 ((ListGame *) gameList.tailPred)->number > 1) {
8970 // TODO convert to GTK
8971 // GameListPopUp(f, title);
8978 return LoadGame(f, n, title, FALSE);
8983 MakeRegisteredMove()
8985 int fromX, fromY, toX, toY;
8987 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
8988 switch (cmailMoveType[lastLoadGameNumber - 1]) {
8991 if (appData.debugMode)
8992 fprintf(debugFP, "Restoring %s for game %d\n",
8993 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
8995 thinkOutput[0] = NULLCHAR;
8996 strcpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1]);
8997 fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
8998 fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
8999 toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
9000 toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
9001 promoChar = cmailMove[lastLoadGameNumber - 1][4];
9002 MakeMove(fromX, fromY, toX, toY, promoChar);
9003 ShowMove(fromX, fromY, toX, toY);
9005 switch (MateTest(boards[currentMove], PosFlags(currentMove),
9006 EP_UNKNOWN, castlingRights[currentMove]) ) {
9013 if (WhiteOnMove(currentMove)) {
9014 GameEnds(BlackWins, "Black mates", GE_PLAYER);
9016 GameEnds(WhiteWins, "White mates", GE_PLAYER);
9021 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
9028 if (WhiteOnMove(currentMove)) {
9029 GameEnds(BlackWins, "White resigns", GE_PLAYER);
9031 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
9036 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
9047 /* Wrapper around LoadGame for use when a Cmail message is loaded */
9049 CmailLoadGame(f, gameNumber, title, useList)
9057 if (gameNumber > nCmailGames) {
9058 DisplayError(_("No more games in this message"), 0);
9061 if (f == lastLoadGameFP) {
9062 int offset = gameNumber - lastLoadGameNumber;
9064 cmailMsg[0] = NULLCHAR;
9065 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
9066 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
9067 nCmailMovesRegistered--;
9069 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
9070 if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
9071 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
9074 if (! RegisterMove()) return FALSE;
9078 retVal = LoadGame(f, gameNumber, title, useList);
9080 /* Make move registered during previous look at this game, if any */
9081 MakeRegisteredMove();
9083 if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
9084 commentList[currentMove]
9085 = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
9086 DisplayComment(currentMove - 1, commentList[currentMove]);
9092 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
9097 int gameNumber = lastLoadGameNumber + offset;
9098 if (lastLoadGameFP == NULL) {
9099 DisplayError(_("No game has been loaded yet"), 0);
9102 if (gameNumber <= 0) {
9103 DisplayError(_("Can't back up any further"), 0);
9106 if (cmailMsgLoaded) {
9107 return CmailLoadGame(lastLoadGameFP, gameNumber,
9108 lastLoadGameTitle, lastLoadGameUseList);
9110 return LoadGame(lastLoadGameFP, gameNumber,
9111 lastLoadGameTitle, lastLoadGameUseList);
9117 /* Load the nth game from open file f */
9119 LoadGame(f, gameNumber, title, useList)
9127 int gn = gameNumber;
9128 ListGame *lg = NULL;
9131 GameMode oldGameMode;
9132 VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
9134 if (appData.debugMode)
9135 fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
9137 if (gameMode == Training )
9138 SetTrainingModeOff();
9140 oldGameMode = gameMode;
9141 if (gameMode != BeginningOfGame)
9147 if (lastLoadGameFP != NULL && lastLoadGameFP != f)
9149 fclose(lastLoadGameFP);
9154 lg = (ListGame *) ListElem(&gameList, gameNumber-1);
9158 fseek(f, lg->offset, 0);
9159 GameListHighlight(gameNumber);
9164 DisplayError(_("Game number out of range"), 0);
9171 if (fseek(f, 0, 0) == -1)
9173 if (f == lastLoadGameFP ?
9174 gameNumber == lastLoadGameNumber + 1 :
9181 DisplayError(_("Can't seek on game file"), 0);
9188 lastLoadGameNumber = gameNumber;
9189 strcpy(lastLoadGameTitle, title);
9190 lastLoadGameUseList = useList;
9194 if (lg && lg->gameInfo.white && lg->gameInfo.black)
9196 snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
9197 lg->gameInfo.black);
9200 else if (*title != NULLCHAR)
9204 sprintf(buf, "%s %d", title, gameNumber);
9209 DisplayTitle(title);
9213 if (gameMode != AnalyzeFile && gameMode != AnalyzeMode)
9215 gameMode = PlayFromGameFile;
9219 currentMove = forwardMostMove = backwardMostMove = 0;
9220 CopyBoard(boards[0], initialPosition);
9224 * Skip the first gn-1 games in the file.
9225 * Also skip over anything that precedes an identifiable
9226 * start of game marker, to avoid being confused by
9227 * garbage at the start of the file. Currently
9228 * recognized start of game markers are the move number "1",
9229 * the pattern "gnuchess .* game", the pattern
9230 * "^[#;%] [^ ]* game file", and a PGN tag block.
9231 * A game that starts with one of the latter two patterns
9232 * will also have a move number 1, possibly
9233 * following a position diagram.
9234 * 5-4-02: Let's try being more lenient and allowing a game to
9235 * start with an unnumbered move. Does that break anything?
9237 cm = lastLoadGameStart = (ChessMove) 0;
9239 yyboardindex = forwardMostMove;
9240 cm = (ChessMove) yylex();
9243 if (cmailMsgLoaded) {
9244 nCmailGames = CMAIL_MAX_GAMES - gn;
9247 DisplayError(_("Game not found in file"), 0);
9254 lastLoadGameStart = cm;
9258 switch (lastLoadGameStart) {
9265 gn--; /* count this game */
9266 lastLoadGameStart = cm;
9275 switch (lastLoadGameStart) {
9280 gn--; /* count this game */
9281 lastLoadGameStart = cm;
9284 lastLoadGameStart = cm; /* game counted already */
9292 yyboardindex = forwardMostMove;
9293 cm = (ChessMove) yylex();
9294 } while (cm == PGNTag || cm == Comment);
9301 if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
9302 if ( cmailResult[CMAIL_MAX_GAMES - gn - 1]
9303 != CMAIL_OLD_RESULT) {
9305 cmailResult[ CMAIL_MAX_GAMES
9306 - gn - 1] = CMAIL_OLD_RESULT;
9312 /* Only a NormalMove can be at the start of a game
9313 * without a position diagram. */
9314 if (lastLoadGameStart == (ChessMove) 0) {
9316 lastLoadGameStart = MoveNumberOne;
9325 if (appData.debugMode)
9326 fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
9328 if (cm == XBoardGame) {
9329 /* Skip any header junk before position diagram and/or move 1 */
9331 yyboardindex = forwardMostMove;
9332 cm = (ChessMove) yylex();
9334 if (cm == (ChessMove) 0 ||
9335 cm == GNUChessGame || cm == XBoardGame) {
9336 /* Empty game; pretend end-of-file and handle later */
9341 if (cm == MoveNumberOne || cm == PositionDiagram ||
9342 cm == PGNTag || cm == Comment)
9345 } else if (cm == GNUChessGame) {
9346 if (gameInfo.event != NULL) {
9347 free(gameInfo.event);
9349 gameInfo.event = StrSave(yy_text);
9352 startedFromSetupPosition = FALSE;
9353 while (cm == PGNTag) {
9354 if (appData.debugMode)
9355 fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
9356 err = ParsePGNTag(yy_text, &gameInfo);
9357 if (!err) numPGNTags++;
9359 /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
9360 if(gameInfo.variant != oldVariant) {
9361 startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
9363 oldVariant = gameInfo.variant;
9364 if (appData.debugMode)
9365 fprintf(debugFP, "New variant %d\n", (int) oldVariant);
9369 if (gameInfo.fen != NULL) {
9370 Board initial_position;
9371 startedFromSetupPosition = TRUE;
9372 if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
9374 DisplayError(_("Bad FEN position in file"), 0);
9377 CopyBoard(boards[0], initial_position);
9378 if (blackPlaysFirst) {
9379 currentMove = forwardMostMove = backwardMostMove = 1;
9380 CopyBoard(boards[1], initial_position);
9381 strcpy(moveList[0], "");
9382 strcpy(parseList[0], "");
9383 timeRemaining[0][1] = whiteTimeRemaining;
9384 timeRemaining[1][1] = blackTimeRemaining;
9385 if (commentList[0] != NULL) {
9386 commentList[1] = commentList[0];
9387 commentList[0] = NULL;
9390 currentMove = forwardMostMove = backwardMostMove = 0;
9392 /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
9394 initialRulePlies = FENrulePlies;
9395 epStatus[forwardMostMove] = FENepStatus;
9396 for( i=0; i< nrCastlingRights; i++ )
9397 initialRights[i] = castlingRights[forwardMostMove][i] = FENcastlingRights[i];
9399 yyboardindex = forwardMostMove;
9401 gameInfo.fen = NULL;
9404 yyboardindex = forwardMostMove;
9405 cm = (ChessMove) yylex();
9407 /* Handle comments interspersed among the tags */
9408 while (cm == Comment) {
9410 if (appData.debugMode)
9411 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9413 if (*p == '{' || *p == '[' || *p == '(') {
9414 p[strlen(p) - 1] = NULLCHAR;
9417 while (*p == '\n') p++;
9418 AppendComment(currentMove, p);
9419 yyboardindex = forwardMostMove;
9420 cm = (ChessMove) yylex();
9424 /* don't rely on existence of Event tag since if game was
9425 * pasted from clipboard the Event tag may not exist
9427 if (numPGNTags > 0){
9429 if (gameInfo.variant == VariantNormal) {
9430 gameInfo.variant = StringToVariant(gameInfo.event);
9433 if( appData.autoDisplayTags ) {
9434 tags = PGNTags(&gameInfo);
9435 TagsPopUp(tags, CmailMsg());
9440 /* Make something up, but don't display it now */
9445 if (cm == PositionDiagram) {
9448 Board initial_position;
9450 if (appData.debugMode)
9451 fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
9453 if (!startedFromSetupPosition) {
9455 for (i = BOARD_HEIGHT - 1; i >= 0; i--)
9456 for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
9466 initial_position[i][j++] = CharToPiece(*p);
9469 while (*p == ' ' || *p == '\t' ||
9470 *p == '\n' || *p == '\r') p++;
9472 if (strncmp(p, "black", strlen("black"))==0)
9473 blackPlaysFirst = TRUE;
9475 blackPlaysFirst = FALSE;
9476 startedFromSetupPosition = TRUE;
9478 CopyBoard(boards[0], initial_position);
9479 if (blackPlaysFirst) {
9480 currentMove = forwardMostMove = backwardMostMove = 1;
9481 CopyBoard(boards[1], initial_position);
9482 strcpy(moveList[0], "");
9483 strcpy(parseList[0], "");
9484 timeRemaining[0][1] = whiteTimeRemaining;
9485 timeRemaining[1][1] = blackTimeRemaining;
9486 if (commentList[0] != NULL) {
9487 commentList[1] = commentList[0];
9488 commentList[0] = NULL;
9491 currentMove = forwardMostMove = backwardMostMove = 0;
9494 yyboardindex = forwardMostMove;
9495 cm = (ChessMove) yylex();
9498 if (first.pr == NoProc) {
9499 StartChessProgram(&first);
9501 InitChessProgram(&first, FALSE);
9502 SendToProgram("force\n", &first);
9503 if (startedFromSetupPosition) {
9504 SendBoard(&first, forwardMostMove);
9505 if (appData.debugMode) {
9506 fprintf(debugFP, "Load Game\n");
9508 DisplayBothClocks();
9511 /* [HGM] server: flag to write setup moves in broadcast file as one */
9512 loadFlag = appData.suppressLoadMoves;
9514 while (cm == Comment) {
9516 if (appData.debugMode)
9517 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9519 if (*p == '{' || *p == '[' || *p == '(') {
9520 p[strlen(p) - 1] = NULLCHAR;
9523 while (*p == '\n') p++;
9524 AppendComment(currentMove, p);
9525 yyboardindex = forwardMostMove;
9526 cm = (ChessMove) yylex();
9529 if ((cm == (ChessMove) 0 && lastLoadGameStart != (ChessMove) 0) ||
9530 cm == WhiteWins || cm == BlackWins ||
9531 cm == GameIsDrawn || cm == GameUnfinished) {
9532 DisplayMessage("", _("No moves in game"));
9533 if (cmailMsgLoaded) {
9534 if (appData.debugMode)
9535 fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
9539 DrawPosition(FALSE, boards[currentMove]);
9540 DisplayBothClocks();
9541 gameMode = EditGame;
9548 // [HGM] PV info: routine tests if comment empty
9549 if (!matchMode && (pausing || appData.timeDelay != 0)) {
9550 DisplayComment(currentMove - 1, commentList[currentMove]);
9552 if (!matchMode && appData.timeDelay != 0)
9553 DrawPosition(FALSE, boards[currentMove]);
9555 if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
9556 programStats.ok_to_send = 1;
9559 /* if the first token after the PGN tags is a move
9560 * and not move number 1, retrieve it from the parser
9562 if (cm != MoveNumberOne)
9563 LoadGameOneMove(cm);
9565 /* load the remaining moves from the file */
9566 while (LoadGameOneMove((ChessMove)0)) {
9567 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
9568 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
9571 /* rewind to the start of the game */
9572 currentMove = backwardMostMove;
9574 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
9576 if (oldGameMode == AnalyzeFile ||
9577 oldGameMode == AnalyzeMode) {
9581 if (matchMode || appData.timeDelay == 0) {
9583 gameMode = EditGame;
9585 } else if (appData.timeDelay > 0) {
9589 if (appData.debugMode)
9590 fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
9592 loadFlag = 0; /* [HGM] true game starts */
9596 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
9598 ReloadPosition(offset)
9601 int positionNumber = lastLoadPositionNumber + offset;
9602 if (lastLoadPositionFP == NULL) {
9603 DisplayError(_("No position has been loaded yet"), 0);
9606 if (positionNumber <= 0) {
9607 DisplayError(_("Can't back up any further"), 0);
9610 return LoadPosition(lastLoadPositionFP, positionNumber,
9611 lastLoadPositionTitle);
9614 /* Load the nth position from the given file */
9616 LoadPositionFromFile(filename, n, title)
9624 if (strcmp(filename, "-") == 0) {
9625 return LoadPosition(stdin, n, "stdin");
9627 f = fopen(filename, "rb");
9629 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9630 DisplayError(buf, errno);
9633 return LoadPosition(f, n, title);
9638 /* Load the nth position from the given open file, and close it */
9640 LoadPosition(f, positionNumber, title)
9645 char *p, line[MSG_SIZ];
9646 Board initial_position;
9647 int i, j, fenMode, pn;
9649 if (gameMode == Training )
9650 SetTrainingModeOff();
9652 if (gameMode != BeginningOfGame) {
9655 if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
9656 fclose(lastLoadPositionFP);
9658 if (positionNumber == 0) positionNumber = 1;
9659 lastLoadPositionFP = f;
9660 lastLoadPositionNumber = positionNumber;
9661 strcpy(lastLoadPositionTitle, title);
9662 if (first.pr == NoProc) {
9663 StartChessProgram(&first);
9664 InitChessProgram(&first, FALSE);
9666 pn = positionNumber;
9667 if (positionNumber < 0) {
9668 /* Negative position number means to seek to that byte offset */
9669 if (fseek(f, -positionNumber, 0) == -1) {
9670 DisplayError(_("Can't seek on position file"), 0);
9675 if (fseek(f, 0, 0) == -1) {
9676 if (f == lastLoadPositionFP ?
9677 positionNumber == lastLoadPositionNumber + 1 :
9678 positionNumber == 1) {
9681 DisplayError(_("Can't seek on position file"), 0);
9686 /* See if this file is FEN or old-style xboard */
9687 if (fgets(line, MSG_SIZ, f) == NULL) {
9688 DisplayError(_("Position not found in file"), 0);
9691 // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
9692 fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
9695 if (fenMode || line[0] == '#') pn--;
9697 /* skip positions before number pn */
9698 if (fgets(line, MSG_SIZ, f) == NULL) {
9700 DisplayError(_("Position not found in file"), 0);
9703 if (fenMode || line[0] == '#') pn--;
9708 if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
9709 DisplayError(_("Bad FEN position in file"), 0);
9713 (void) fgets(line, MSG_SIZ, f);
9714 (void) fgets(line, MSG_SIZ, f);
9716 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
9717 (void) fgets(line, MSG_SIZ, f);
9718 for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
9721 initial_position[i][j++] = CharToPiece(*p);
9725 blackPlaysFirst = FALSE;
9727 (void) fgets(line, MSG_SIZ, f);
9728 if (strncmp(line, "black", strlen("black"))==0)
9729 blackPlaysFirst = TRUE;
9732 startedFromSetupPosition = TRUE;
9734 SendToProgram("force\n", &first);
9735 CopyBoard(boards[0], initial_position);
9736 if (blackPlaysFirst) {
9737 currentMove = forwardMostMove = backwardMostMove = 1;
9738 strcpy(moveList[0], "");
9739 strcpy(parseList[0], "");
9740 CopyBoard(boards[1], initial_position);
9741 DisplayMessage("", _("Black to play"));
9743 currentMove = forwardMostMove = backwardMostMove = 0;
9744 DisplayMessage("", _("White to play"));
9746 /* [HGM] copy FEN attributes as well */
9748 initialRulePlies = FENrulePlies;
9749 epStatus[forwardMostMove] = FENepStatus;
9750 for( i=0; i< nrCastlingRights; i++ )
9751 castlingRights[forwardMostMove][i] = FENcastlingRights[i];
9753 SendBoard(&first, forwardMostMove);
9754 if (appData.debugMode) {
9756 for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", castlingRights[i][j]);fprintf(debugFP,"\n");}
9757 for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
9758 fprintf(debugFP, "Load Position\n");
9761 if (positionNumber > 1) {
9762 sprintf(line, "%s %d", title, positionNumber);
9765 DisplayTitle(title);
9767 gameMode = EditGame;
9770 timeRemaining[0][1] = whiteTimeRemaining;
9771 timeRemaining[1][1] = blackTimeRemaining;
9772 DrawPosition(FALSE, boards[currentMove]);
9779 CopyPlayerNameIntoFileName(dest, src)
9782 while (*src != NULLCHAR && *src != ',') {
9787 *(*dest)++ = *src++;
9792 char *DefaultFileName(ext)
9795 static char def[MSG_SIZ];
9798 if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
9800 CopyPlayerNameIntoFileName(&p, gameInfo.white);
9802 CopyPlayerNameIntoFileName(&p, gameInfo.black);
9811 /* Save the current game to the given file */
9813 SaveGameToFile(filename, append)
9820 if (strcmp(filename, "-") == 0) {
9821 return SaveGame(stdout, 0, NULL);
9823 f = fopen(filename, append ? "a" : "w");
9825 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9826 DisplayError(buf, errno);
9829 return SaveGame(f, 0, NULL);
9838 static char buf[MSG_SIZ];
9841 p = strchr(str, ' ');
9842 if (p == NULL) return str;
9843 strncpy(buf, str, p - str);
9844 buf[p - str] = NULLCHAR;
9848 #define PGN_MAX_LINE 75
9850 #define PGN_SIDE_WHITE 0
9851 #define PGN_SIDE_BLACK 1
9854 static int FindFirstMoveOutOfBook( int side )
9858 if( backwardMostMove == 0 && ! startedFromSetupPosition) {
9859 int index = backwardMostMove;
9860 int has_book_hit = 0;
9862 if( (index % 2) != side ) {
9866 while( index < forwardMostMove ) {
9867 /* Check to see if engine is in book */
9868 int depth = pvInfoList[index].depth;
9869 int score = pvInfoList[index].score;
9875 else if( score == 0 && depth == 63 ) {
9876 in_book = 1; /* Zappa */
9878 else if( score == 2 && depth == 99 ) {
9879 in_book = 1; /* Abrok */
9882 has_book_hit += in_book;
9898 void GetOutOfBookInfo( char * buf )
9902 int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
9904 oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
9905 oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
9909 if( oob[0] >= 0 || oob[1] >= 0 ) {
9910 for( i=0; i<2; i++ ) {
9914 if( i > 0 && oob[0] >= 0 ) {
9918 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
9919 sprintf( buf+strlen(buf), "%s%.2f",
9920 pvInfoList[idx].score >= 0 ? "+" : "",
9921 pvInfoList[idx].score / 100.0 );
9927 /* Save game in PGN style and close the file */
9932 int i, offset, linelen, newblock;
9936 int movelen, numlen, blank;
9937 char move_buffer[100]; /* [AS] Buffer for move+PV info */
9939 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
9941 tm = time((time_t *) NULL);
9943 PrintPGNTags(f, &gameInfo);
9945 if (backwardMostMove > 0 || startedFromSetupPosition) {
9946 char *fen = PositionToFEN(backwardMostMove, NULL);
9947 fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
9948 fprintf(f, "\n{--------------\n");
9949 PrintPosition(f, backwardMostMove);
9950 fprintf(f, "--------------}\n");
9954 /* [AS] Out of book annotation */
9955 if( appData.saveOutOfBookInfo ) {
9958 GetOutOfBookInfo( buf );
9960 if( buf[0] != '\0' ) {
9961 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
9968 i = backwardMostMove;
9972 while (i < forwardMostMove) {
9973 /* Print comments preceding this move */
9974 if (commentList[i] != NULL) {
9975 if (linelen > 0) fprintf(f, "\n");
9976 fprintf(f, "{\n%s}\n", commentList[i]);
9981 /* Format move number */
9983 sprintf(numtext, "%d.", (i - offset)/2 + 1);
9986 sprintf(numtext, "%d...", (i - offset)/2 + 1);
9988 numtext[0] = NULLCHAR;
9991 numlen = strlen(numtext);
9994 /* Print move number */
9995 blank = linelen > 0 && numlen > 0;
9996 if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
10005 fprintf(f, "%s", numtext);
10009 strcpy(move_buffer, SavePart(parseList[i])); // [HGM] pgn: print move via buffer, so it can be edited
10010 movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
10013 blank = linelen > 0 && movelen > 0;
10014 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
10023 fprintf(f, "%s", move_buffer);
10024 linelen += movelen;
10026 /* [AS] Add PV info if present */
10027 if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
10028 /* [HGM] add time */
10029 char buf[MSG_SIZ]; int seconds = 0;
10031 if(i >= backwardMostMove) {
10033 seconds = timeRemaining[0][i] - timeRemaining[0][i+1]
10034 + GetTimeQuota(i/2) / (1000*WhitePlayer()->timeOdds);
10036 seconds = timeRemaining[1][i] - timeRemaining[1][i+1]
10037 + GetTimeQuota(i/2) / (1000*WhitePlayer()->other->timeOdds);
10039 seconds = (seconds+50)/100; // deci-seconds, rounded to nearest
10041 if( seconds <= 0) buf[0] = 0; else
10042 if( seconds < 30 ) sprintf(buf, " %3.1f%c", seconds/10., 0); else {
10043 seconds = (seconds + 4)/10; // round to full seconds
10044 if( seconds < 60 ) sprintf(buf, " %d%c", seconds, 0); else
10045 sprintf(buf, " %d:%02d%c", seconds/60, seconds%60, 0);
10048 sprintf( move_buffer, "{%s%.2f/%d%s}",
10049 pvInfoList[i].score >= 0 ? "+" : "",
10050 pvInfoList[i].score / 100.0,
10051 pvInfoList[i].depth,
10054 movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
10056 /* Print score/depth */
10057 blank = linelen > 0 && movelen > 0;
10058 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
10067 fprintf(f, "%s", move_buffer);
10068 linelen += movelen;
10074 /* Start a new line */
10075 if (linelen > 0) fprintf(f, "\n");
10077 /* Print comments after last move */
10078 if (commentList[i] != NULL) {
10079 fprintf(f, "{\n%s}\n", commentList[i]);
10083 if (gameInfo.resultDetails != NULL &&
10084 gameInfo.resultDetails[0] != NULLCHAR) {
10085 fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
10086 PGNResult(gameInfo.result));
10088 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
10092 lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
10096 /* Save game in old style and close the file */
10098 SaveGameOldStyle(f)
10104 tm = time((time_t *) NULL);
10106 fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
10109 if (backwardMostMove > 0 || startedFromSetupPosition) {
10110 fprintf(f, "\n[--------------\n");
10111 PrintPosition(f, backwardMostMove);
10112 fprintf(f, "--------------]\n");
10117 i = backwardMostMove;
10118 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10120 while (i < forwardMostMove) {
10121 if (commentList[i] != NULL) {
10122 fprintf(f, "[%s]\n", commentList[i]);
10125 if ((i % 2) == 1) {
10126 fprintf(f, "%d. ... %s\n", (i - offset)/2 + 1, parseList[i]);
10129 fprintf(f, "%d. %s ", (i - offset)/2 + 1, parseList[i]);
10131 if (commentList[i] != NULL) {
10135 if (i >= forwardMostMove) {
10139 fprintf(f, "%s\n", parseList[i]);
10144 if (commentList[i] != NULL) {
10145 fprintf(f, "[%s]\n", commentList[i]);
10148 /* This isn't really the old style, but it's close enough */
10149 if (gameInfo.resultDetails != NULL &&
10150 gameInfo.resultDetails[0] != NULLCHAR) {
10151 fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
10152 gameInfo.resultDetails);
10154 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
10161 /* Save the current game to open file f and close the file */
10163 SaveGame(f, dummy, dummy2)
10168 if (gameMode == EditPosition) EditPositionDone();
10169 lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
10170 if (appData.oldSaveStyle)
10171 return SaveGameOldStyle(f);
10173 return SaveGamePGN(f);
10176 /* Save the current position to the given file */
10178 SavePositionToFile(filename)
10184 if (strcmp(filename, "-") == 0) {
10185 return SavePosition(stdout, 0, NULL);
10187 f = fopen(filename, "a");
10189 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10190 DisplayError(buf, errno);
10193 SavePosition(f, 0, NULL);
10199 /* Save the current position to the given open file and close the file */
10201 SavePosition(f, dummy, dummy2)
10209 if (appData.oldSaveStyle) {
10210 tm = time((time_t *) NULL);
10212 fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
10214 fprintf(f, "[--------------\n");
10215 PrintPosition(f, currentMove);
10216 fprintf(f, "--------------]\n");
10218 fen = PositionToFEN(currentMove, NULL);
10219 fprintf(f, "%s\n", fen);
10227 ReloadCmailMsgEvent(unregister)
10231 static char *inFilename = NULL;
10232 static char *outFilename;
10234 struct stat inbuf, outbuf;
10237 /* Any registered moves are unregistered if unregister is set, */
10238 /* i.e. invoked by the signal handler */
10240 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
10241 cmailMoveRegistered[i] = FALSE;
10242 if (cmailCommentList[i] != NULL) {
10243 free(cmailCommentList[i]);
10244 cmailCommentList[i] = NULL;
10247 nCmailMovesRegistered = 0;
10250 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
10251 cmailResult[i] = CMAIL_NOT_RESULT;
10255 if (inFilename == NULL) {
10256 /* Because the filenames are static they only get malloced once */
10257 /* and they never get freed */
10258 inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
10259 sprintf(inFilename, "%s.game.in", appData.cmailGameName);
10261 outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
10262 sprintf(outFilename, "%s.out", appData.cmailGameName);
10265 status = stat(outFilename, &outbuf);
10267 cmailMailedMove = FALSE;
10269 status = stat(inFilename, &inbuf);
10270 cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
10273 /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
10274 counts the games, notes how each one terminated, etc.
10276 It would be nice to remove this kludge and instead gather all
10277 the information while building the game list. (And to keep it
10278 in the game list nodes instead of having a bunch of fixed-size
10279 parallel arrays.) Note this will require getting each game's
10280 termination from the PGN tags, as the game list builder does
10281 not process the game moves. --mann
10283 cmailMsgLoaded = TRUE;
10284 LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
10286 /* Load first game in the file or popup game menu */
10287 LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
10289 #endif /* !WIN32 */
10297 char string[MSG_SIZ];
10299 if ( cmailMailedMove
10300 || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
10301 return TRUE; /* Allow free viewing */
10304 /* Unregister move to ensure that we don't leave RegisterMove */
10305 /* with the move registered when the conditions for registering no */
10307 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
10308 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
10309 nCmailMovesRegistered --;
10311 if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
10313 free(cmailCommentList[lastLoadGameNumber - 1]);
10314 cmailCommentList[lastLoadGameNumber - 1] = NULL;
10318 if (cmailOldMove == -1) {
10319 DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
10323 if (currentMove > cmailOldMove + 1) {
10324 DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
10328 if (currentMove < cmailOldMove) {
10329 DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
10333 if (forwardMostMove > currentMove) {
10334 /* Silently truncate extra moves */
10338 if ( (currentMove == cmailOldMove + 1)
10339 || ( (currentMove == cmailOldMove)
10340 && ( (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
10341 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
10342 if (gameInfo.result != GameUnfinished) {
10343 cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
10346 if (commentList[currentMove] != NULL) {
10347 cmailCommentList[lastLoadGameNumber - 1]
10348 = StrSave(commentList[currentMove]);
10350 strcpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1]);
10352 if (appData.debugMode)
10353 fprintf(debugFP, "Saving %s for game %d\n",
10354 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
10357 "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
10359 f = fopen(string, "w");
10360 if (appData.oldSaveStyle) {
10361 SaveGameOldStyle(f); /* also closes the file */
10363 sprintf(string, "%s.pos.out", appData.cmailGameName);
10364 f = fopen(string, "w");
10365 SavePosition(f, 0, NULL); /* also closes the file */
10367 fprintf(f, "{--------------\n");
10368 PrintPosition(f, currentMove);
10369 fprintf(f, "--------------}\n\n");
10371 SaveGame(f, 0, NULL); /* also closes the file*/
10374 cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
10375 nCmailMovesRegistered ++;
10376 } else if (nCmailGames == 1) {
10377 DisplayError(_("You have not made a move yet"), 0);
10388 static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
10389 FILE *commandOutput;
10390 char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
10391 int nBytes = 0; /* Suppress warnings on uninitialized variables */
10397 if (! cmailMsgLoaded) {
10398 DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
10402 if (nCmailGames == nCmailResults) {
10403 DisplayError(_("No unfinished games"), 0);
10407 #if CMAIL_PROHIBIT_REMAIL
10408 if (cmailMailedMove) {
10409 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);
10410 DisplayError(msg, 0);
10415 if (! (cmailMailedMove || RegisterMove())) return;
10417 if ( cmailMailedMove
10418 || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
10419 sprintf(string, partCommandString,
10420 appData.debugMode ? " -v" : "", appData.cmailGameName);
10421 commandOutput = popen(string, "r");
10423 if (commandOutput == NULL) {
10424 DisplayError(_("Failed to invoke cmail"), 0);
10426 for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
10427 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
10429 if (nBuffers > 1) {
10430 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
10431 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
10432 nBytes = MSG_SIZ - 1;
10434 (void) memcpy(msg, buffer, nBytes);
10436 *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
10438 if(StrStr(msg, "Mailed cmail message to ") != NULL) {
10439 cmailMailedMove = TRUE; /* Prevent >1 moves */
10442 for (i = 0; i < nCmailGames; i ++) {
10443 if (cmailResult[i] == CMAIL_NOT_RESULT) {
10448 && ( (arcDir = (char *) getenv("CMAIL_ARCDIR"))
10450 sprintf(buffer, "%s/%s.%s.archive",
10452 appData.cmailGameName,
10454 LoadGameFromFile(buffer, 1, buffer, FALSE);
10455 cmailMsgLoaded = FALSE;
10459 DisplayInformation(msg);
10460 pclose(commandOutput);
10463 if ((*cmailMsg) != '\0') {
10464 DisplayInformation(cmailMsg);
10469 #endif /* !WIN32 */
10478 int prependComma = 0;
10480 char string[MSG_SIZ]; /* Space for game-list */
10483 if (!cmailMsgLoaded) return "";
10485 if (cmailMailedMove) {
10486 sprintf(cmailMsg, _("Waiting for reply from opponent\n"));
10488 /* Create a list of games left */
10489 sprintf(string, "[");
10490 for (i = 0; i < nCmailGames; i ++) {
10491 if (! ( cmailMoveRegistered[i]
10492 || (cmailResult[i] == CMAIL_OLD_RESULT))) {
10493 if (prependComma) {
10494 sprintf(number, ",%d", i + 1);
10496 sprintf(number, "%d", i + 1);
10500 strcat(string, number);
10503 strcat(string, "]");
10505 if (nCmailMovesRegistered + nCmailResults == 0) {
10506 switch (nCmailGames) {
10509 _("Still need to make move for game\n"));
10514 _("Still need to make moves for both games\n"));
10519 _("Still need to make moves for all %d games\n"),
10524 switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
10527 _("Still need to make a move for game %s\n"),
10532 if (nCmailResults == nCmailGames) {
10533 sprintf(cmailMsg, _("No unfinished games\n"));
10535 sprintf(cmailMsg, _("Ready to send mail\n"));
10541 _("Still need to make moves for games %s\n"),
10553 if (gameMode == Training)
10554 SetTrainingModeOff();
10557 cmailMsgLoaded = FALSE;
10558 if (appData.icsActive) {
10559 SendToICS(ics_prefix);
10560 SendToICS("refresh\n");
10570 /* Give up on clean exit */
10574 /* Keep trying for clean exit */
10578 if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
10580 if (telnetISR != NULL) {
10581 RemoveInputSource(telnetISR);
10583 if (icsPR != NoProc) {
10584 DestroyChildProcess(icsPR, TRUE);
10587 /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
10588 GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
10590 /* [HGM] crash: the above GameEnds() is a dud if another one was running */
10591 /* make sure this other one finishes before killing it! */
10592 if(endingGame) { int count = 0;
10593 if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
10594 while(endingGame && count++ < 10) DoSleep(1);
10595 if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
10598 /* Kill off chess programs */
10599 if (first.pr != NoProc) {
10602 DoSleep( appData.delayBeforeQuit );
10603 SendToProgram("quit\n", &first);
10604 DoSleep( appData.delayAfterQuit );
10605 DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
10607 if (second.pr != NoProc) {
10608 DoSleep( appData.delayBeforeQuit );
10609 SendToProgram("quit\n", &second);
10610 DoSleep( appData.delayAfterQuit );
10611 DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
10613 if (first.isr != NULL) {
10614 RemoveInputSource(first.isr);
10616 if (second.isr != NULL) {
10617 RemoveInputSource(second.isr);
10620 ShutDownFrontEnd();
10627 if (appData.debugMode)
10628 fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
10632 if (gameMode == MachinePlaysWhite ||
10633 gameMode == MachinePlaysBlack) {
10636 DisplayBothClocks();
10638 if (gameMode == PlayFromGameFile) {
10639 if (appData.timeDelay >= 0)
10640 AutoPlayGameLoop();
10641 } else if (gameMode == IcsExamining && pauseExamInvalid) {
10642 Reset(FALSE, TRUE);
10643 SendToICS(ics_prefix);
10644 SendToICS("refresh\n");
10645 } else if (currentMove < forwardMostMove) {
10646 ForwardInner(forwardMostMove);
10648 pauseExamInvalid = FALSE;
10650 switch (gameMode) {
10654 pauseExamForwardMostMove = forwardMostMove;
10655 pauseExamInvalid = FALSE;
10658 case IcsPlayingWhite:
10659 case IcsPlayingBlack:
10663 case PlayFromGameFile:
10664 (void) StopLoadGameTimer();
10668 case BeginningOfGame:
10669 if (appData.icsActive) return;
10670 /* else fall through */
10671 case MachinePlaysWhite:
10672 case MachinePlaysBlack:
10673 case TwoMachinesPlay:
10674 if (forwardMostMove == 0)
10675 return; /* don't pause if no one has moved */
10676 if ((gameMode == MachinePlaysWhite &&
10677 !WhiteOnMove(forwardMostMove)) ||
10678 (gameMode == MachinePlaysBlack &&
10679 WhiteOnMove(forwardMostMove))) {
10692 char title[MSG_SIZ];
10694 if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
10695 strcpy(title, _("Edit comment"));
10697 sprintf(title, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
10698 WhiteOnMove(currentMove - 1) ? " " : ".. ",
10699 parseList[currentMove - 1]);
10702 EditCommentPopUp(currentMove, title, commentList[currentMove]);
10709 char *tags = PGNTags(&gameInfo);
10710 EditTagsPopUp(tags);
10717 if (appData.noChessProgram || gameMode == AnalyzeMode)
10720 if (gameMode != AnalyzeFile) {
10721 if (!appData.icsEngineAnalyze) {
10723 if (gameMode != EditGame) return;
10725 ResurrectChessProgram();
10726 SendToProgram("analyze\n", &first);
10727 first.analyzing = TRUE;
10728 /*first.maybeThinking = TRUE;*/
10729 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
10730 EngineOutputPopUp();
10732 if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
10737 StartAnalysisClock();
10738 GetTimeMark(&lastNodeCountTime);
10745 if (appData.noChessProgram || gameMode == AnalyzeFile)
10748 if (gameMode != AnalyzeMode) {
10750 if (gameMode != EditGame) return;
10751 ResurrectChessProgram();
10752 SendToProgram("analyze\n", &first);
10753 first.analyzing = TRUE;
10754 /*first.maybeThinking = TRUE;*/
10755 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
10756 EngineOutputPopUp();
10758 gameMode = AnalyzeFile;
10763 StartAnalysisClock();
10764 GetTimeMark(&lastNodeCountTime);
10769 MachineWhiteEvent()
10772 char *bookHit = NULL;
10774 if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
10778 if (gameMode == PlayFromGameFile ||
10779 gameMode == TwoMachinesPlay ||
10780 gameMode == Training ||
10781 gameMode == AnalyzeMode ||
10782 gameMode == EndOfGame)
10785 if (gameMode == EditPosition)
10786 EditPositionDone();
10788 if (!WhiteOnMove(currentMove)) {
10789 DisplayError(_("It is not White's turn"), 0);
10793 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
10796 if (gameMode == EditGame || gameMode == AnalyzeMode ||
10797 gameMode == AnalyzeFile)
10800 ResurrectChessProgram(); /* in case it isn't running */
10801 if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
10802 gameMode = MachinePlaysWhite;
10805 gameMode = MachinePlaysWhite;
10809 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10811 if (first.sendName) {
10812 sprintf(buf, "name %s\n", gameInfo.black);
10813 SendToProgram(buf, &first);
10815 if (first.sendTime) {
10816 if (first.useColors) {
10817 SendToProgram("black\n", &first); /*gnu kludge*/
10819 SendTimeRemaining(&first, TRUE);
10821 if (first.useColors) {
10822 SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
10824 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
10825 SetMachineThinkingEnables();
10826 first.maybeThinking = TRUE;
10830 if (appData.autoFlipView && !flipView) {
10831 flipView = !flipView;
10832 DrawPosition(FALSE, NULL);
10833 DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;
10836 if(bookHit) { // [HGM] book: simulate book reply
10837 static char bookMove[MSG_SIZ]; // a bit generous?
10839 programStats.nodes = programStats.depth = programStats.time =
10840 programStats.score = programStats.got_only_move = 0;
10841 sprintf(programStats.movelist, "%s (xbook)", bookHit);
10843 strcpy(bookMove, "move ");
10844 strcat(bookMove, bookHit);
10845 HandleMachineMove(bookMove, &first);
10850 MachineBlackEvent()
10853 char *bookHit = NULL;
10855 if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
10859 if (gameMode == PlayFromGameFile ||
10860 gameMode == TwoMachinesPlay ||
10861 gameMode == Training ||
10862 gameMode == AnalyzeMode ||
10863 gameMode == EndOfGame)
10866 if (gameMode == EditPosition)
10867 EditPositionDone();
10869 if (WhiteOnMove(currentMove)) {
10870 DisplayError(_("It is not Black's turn"), 0);
10874 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
10877 if (gameMode == EditGame || gameMode == AnalyzeMode ||
10878 gameMode == AnalyzeFile)
10881 ResurrectChessProgram(); /* in case it isn't running */
10882 gameMode = MachinePlaysBlack;
10886 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10888 if (first.sendName) {
10889 sprintf(buf, "name %s\n", gameInfo.white);
10890 SendToProgram(buf, &first);
10892 if (first.sendTime) {
10893 if (first.useColors) {
10894 SendToProgram("white\n", &first); /*gnu kludge*/
10896 SendTimeRemaining(&first, FALSE);
10898 if (first.useColors) {
10899 SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
10901 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
10902 SetMachineThinkingEnables();
10903 first.maybeThinking = TRUE;
10906 if (appData.autoFlipView && flipView) {
10907 flipView = !flipView;
10908 DrawPosition(FALSE, NULL);
10909 DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;
10911 if(bookHit) { // [HGM] book: simulate book reply
10912 static char bookMove[MSG_SIZ]; // a bit generous?
10914 programStats.nodes = programStats.depth = programStats.time =
10915 programStats.score = programStats.got_only_move = 0;
10916 sprintf(programStats.movelist, "%s (xbook)", bookHit);
10918 strcpy(bookMove, "move ");
10919 strcat(bookMove, bookHit);
10920 HandleMachineMove(bookMove, &first);
10926 DisplayTwoMachinesTitle()
10929 if (appData.matchGames > 0) {
10930 if (first.twoMachinesColor[0] == 'w') {
10931 sprintf(buf, "%s vs. %s (%d-%d-%d)",
10932 gameInfo.white, gameInfo.black,
10933 first.matchWins, second.matchWins,
10934 matchGame - 1 - (first.matchWins + second.matchWins));
10936 sprintf(buf, "%s vs. %s (%d-%d-%d)",
10937 gameInfo.white, gameInfo.black,
10938 second.matchWins, first.matchWins,
10939 matchGame - 1 - (first.matchWins + second.matchWins));
10942 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10948 TwoMachinesEvent P((void))
10952 ChessProgramState *onmove;
10953 char *bookHit = NULL;
10955 if (appData.noChessProgram) return;
10957 switch (gameMode) {
10958 case TwoMachinesPlay:
10960 case MachinePlaysWhite:
10961 case MachinePlaysBlack:
10962 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
10963 DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
10967 case BeginningOfGame:
10968 case PlayFromGameFile:
10971 if (gameMode != EditGame) return;
10974 EditPositionDone();
10985 forwardMostMove = currentMove;
10986 ResurrectChessProgram(); /* in case first program isn't running */
10988 if (second.pr == NULL) {
10989 StartChessProgram(&second);
10990 if (second.protocolVersion == 1) {
10991 TwoMachinesEventIfReady();
10993 /* kludge: allow timeout for initial "feature" command */
10995 DisplayMessage("", _("Starting second chess program"));
10996 ScheduleDelayedEvent(TwoMachinesEventIfReady, FEATURE_TIMEOUT);
11000 DisplayMessage("", "");
11001 InitChessProgram(&second, FALSE);
11002 SendToProgram("force\n", &second);
11003 if (startedFromSetupPosition) {
11004 SendBoard(&second, backwardMostMove);
11005 if (appData.debugMode) {
11006 fprintf(debugFP, "Two Machines\n");
11009 for (i = backwardMostMove; i < forwardMostMove; i++) {
11010 SendMoveToProgram(i, &second);
11013 gameMode = TwoMachinesPlay;
11017 DisplayTwoMachinesTitle();
11019 if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
11025 SendToProgram(first.computerString, &first);
11026 if (first.sendName) {
11027 sprintf(buf, "name %s\n", second.tidy);
11028 SendToProgram(buf, &first);
11030 SendToProgram(second.computerString, &second);
11031 if (second.sendName) {
11032 sprintf(buf, "name %s\n", first.tidy);
11033 SendToProgram(buf, &second);
11037 if (!first.sendTime || !second.sendTime) {
11038 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11039 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11041 if (onmove->sendTime) {
11042 if (onmove->useColors) {
11043 SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
11045 SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
11047 if (onmove->useColors) {
11048 SendToProgram(onmove->twoMachinesColor, onmove);
11050 bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
11051 // SendToProgram("go\n", onmove);
11052 onmove->maybeThinking = TRUE;
11053 SetMachineThinkingEnables();
11057 if(bookHit) { // [HGM] book: simulate book reply
11058 static char bookMove[MSG_SIZ]; // a bit generous?
11060 programStats.nodes = programStats.depth = programStats.time =
11061 programStats.score = programStats.got_only_move = 0;
11062 sprintf(programStats.movelist, "%s (xbook)", bookHit);
11064 strcpy(bookMove, "move ");
11065 strcat(bookMove, bookHit);
11066 savedMessage = bookMove; // args for deferred call
11067 savedState = onmove;
11068 ScheduleDelayedEvent(DeferredBookMove, 1);
11075 if (gameMode == Training) {
11076 SetTrainingModeOff();
11077 gameMode = PlayFromGameFile;
11078 DisplayMessage("", _("Training mode off"));
11080 gameMode = Training;
11081 animateTraining = appData.animate;
11083 /* make sure we are not already at the end of the game */
11084 if (currentMove < forwardMostMove) {
11085 SetTrainingModeOn();
11086 DisplayMessage("", _("Training mode on"));
11088 gameMode = PlayFromGameFile;
11089 DisplayError(_("Already at end of game"), 0);
11098 if (!appData.icsActive) return;
11099 switch (gameMode) {
11100 case IcsPlayingWhite:
11101 case IcsPlayingBlack:
11104 case BeginningOfGame:
11112 EditPositionDone();
11125 gameMode = IcsIdle;
11136 switch (gameMode) {
11138 SetTrainingModeOff();
11140 case MachinePlaysWhite:
11141 case MachinePlaysBlack:
11142 case BeginningOfGame:
11143 SendToProgram("force\n", &first);
11144 SetUserThinkingEnables();
11146 case PlayFromGameFile:
11147 (void) StopLoadGameTimer();
11148 if (gameFileFP != NULL) {
11153 EditPositionDone();
11158 SendToProgram("force\n", &first);
11160 case TwoMachinesPlay:
11161 GameEnds((ChessMove) 0, NULL, GE_PLAYER);
11162 ResurrectChessProgram();
11163 SetUserThinkingEnables();
11166 ResurrectChessProgram();
11168 case IcsPlayingBlack:
11169 case IcsPlayingWhite:
11170 DisplayError(_("Warning: You are still playing a game"), 0);
11173 DisplayError(_("Warning: You are still observing a game"), 0);
11176 DisplayError(_("Warning: You are still examining a game"), 0);
11187 first.offeredDraw = second.offeredDraw = 0;
11189 if (gameMode == PlayFromGameFile) {
11190 whiteTimeRemaining = timeRemaining[0][currentMove];
11191 blackTimeRemaining = timeRemaining[1][currentMove];
11195 if (gameMode == MachinePlaysWhite ||
11196 gameMode == MachinePlaysBlack ||
11197 gameMode == TwoMachinesPlay ||
11198 gameMode == EndOfGame) {
11199 i = forwardMostMove;
11200 while (i > currentMove) {
11201 SendToProgram("undo\n", &first);
11204 whiteTimeRemaining = timeRemaining[0][currentMove];
11205 blackTimeRemaining = timeRemaining[1][currentMove];
11206 DisplayBothClocks();
11207 if (whiteFlag || blackFlag) {
11208 whiteFlag = blackFlag = 0;
11213 gameMode = EditGame;
11220 EditPositionEvent()
11222 if (gameMode == EditPosition) {
11228 if (gameMode != EditGame) return;
11230 gameMode = EditPosition;
11233 if (currentMove > 0)
11234 CopyBoard(boards[0], boards[currentMove]);
11236 blackPlaysFirst = !WhiteOnMove(currentMove);
11238 currentMove = forwardMostMove = backwardMostMove = 0;
11239 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11246 /* [DM] icsEngineAnalyze - possible call from other functions */
11247 if (appData.icsEngineAnalyze) {
11248 appData.icsEngineAnalyze = FALSE;
11250 DisplayMessage("",_("Close ICS engine analyze..."));
11252 if (first.analysisSupport && first.analyzing) {
11253 SendToProgram("exit\n", &first);
11254 first.analyzing = FALSE;
11256 thinkOutput[0] = NULLCHAR;
11262 int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
11264 startedFromSetupPosition = TRUE;
11265 InitChessProgram(&first, FALSE);
11266 castlingRights[0][2] = castlingRights[0][5] = BOARD_WIDTH>>1;
11267 if(boards[0][0][BOARD_WIDTH>>1] == king) {
11268 castlingRights[0][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : -1;
11269 castlingRights[0][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : -1;
11270 } else castlingRights[0][2] = -1;
11271 if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
11272 castlingRights[0][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : -1;
11273 castlingRights[0][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : -1;
11274 } else castlingRights[0][5] = -1;
11275 SendToProgram("force\n", &first);
11276 if (blackPlaysFirst) {
11277 strcpy(moveList[0], "");
11278 strcpy(parseList[0], "");
11279 currentMove = forwardMostMove = backwardMostMove = 1;
11280 CopyBoard(boards[1], boards[0]);
11281 /* [HGM] copy rights as well, as this code is also used after pasting a FEN */
11283 epStatus[1] = epStatus[0];
11284 for(i=0; i<nrCastlingRights; i++) castlingRights[1][i] = castlingRights[0][i];
11287 currentMove = forwardMostMove = backwardMostMove = 0;
11289 SendBoard(&first, forwardMostMove);
11290 if (appData.debugMode) {
11291 fprintf(debugFP, "EditPosDone\n");
11294 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11295 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11296 gameMode = EditGame;
11298 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11299 ClearHighlights(); /* [AS] */
11302 /* Pause for `ms' milliseconds */
11303 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
11313 } while (SubtractTimeMarks(&m2, &m1) < ms);
11316 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
11318 SendMultiLineToICS(buf)
11321 char temp[MSG_SIZ+1], *p;
11328 strncpy(temp, buf, len);
11333 if (*p == '\n' || *p == '\r')
11338 strcat(temp, "\n");
11340 SendToPlayer(temp, strlen(temp));
11344 SetWhiteToPlayEvent()
11346 if (gameMode == EditPosition) {
11347 blackPlaysFirst = FALSE;
11348 DisplayBothClocks(); /* works because currentMove is 0 */
11349 } else if (gameMode == IcsExamining) {
11350 SendToICS(ics_prefix);
11351 SendToICS("tomove white\n");
11356 SetBlackToPlayEvent()
11358 if (gameMode == EditPosition) {
11359 blackPlaysFirst = TRUE;
11360 currentMove = 1; /* kludge */
11361 DisplayBothClocks();
11363 } else if (gameMode == IcsExamining) {
11364 SendToICS(ics_prefix);
11365 SendToICS("tomove black\n");
11370 EditPositionMenuEvent(selection, x, y)
11371 ChessSquare selection;
11375 ChessSquare piece = boards[0][y][x];
11377 if (gameMode != EditPosition && gameMode != IcsExamining) return;
11379 switch (selection) {
11381 if (gameMode == IcsExamining && ics_type == ICS_FICS) {
11382 SendToICS(ics_prefix);
11383 SendToICS("bsetup clear\n");
11384 } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
11385 SendToICS(ics_prefix);
11386 SendToICS("clearboard\n");
11388 for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
11389 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
11390 for (y = 0; y < BOARD_HEIGHT; y++) {
11391 if (gameMode == IcsExamining) {
11392 if (boards[currentMove][y][x] != EmptySquare) {
11393 sprintf(buf, "%sx@%c%c\n", ics_prefix,
11398 boards[0][y][x] = p;
11403 if (gameMode == EditPosition) {
11404 DrawPosition(FALSE, boards[0]);
11409 SetWhiteToPlayEvent();
11413 SetBlackToPlayEvent();
11417 if (gameMode == IcsExamining) {
11418 sprintf(buf, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
11421 boards[0][y][x] = EmptySquare;
11422 DrawPosition(FALSE, boards[0]);
11427 if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
11428 piece >= (int)BlackPawn && piece < (int)BlackMan ) {
11429 selection = (ChessSquare) (PROMOTED piece);
11430 } else if(piece == EmptySquare) selection = WhiteSilver;
11431 else selection = (ChessSquare)((int)piece - 1);
11435 if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
11436 piece > (int)BlackMan && piece <= (int)BlackKing ) {
11437 selection = (ChessSquare) (DEMOTED piece);
11438 } else if(piece == EmptySquare) selection = BlackSilver;
11439 else selection = (ChessSquare)((int)piece + 1);
11444 if(gameInfo.variant == VariantShatranj ||
11445 gameInfo.variant == VariantXiangqi ||
11446 gameInfo.variant == VariantCourier )
11447 selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
11452 if(gameInfo.variant == VariantXiangqi)
11453 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
11454 if(gameInfo.variant == VariantKnightmate)
11455 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
11458 if (gameMode == IcsExamining) {
11459 sprintf(buf, "%s%c@%c%c\n", ics_prefix,
11460 PieceToChar(selection), AAA + x, ONE + y);
11463 boards[0][y][x] = selection;
11464 DrawPosition(FALSE, boards[0]);
11472 DropMenuEvent(selection, x, y)
11473 ChessSquare selection;
11476 ChessMove moveType;
11478 switch (gameMode) {
11479 case IcsPlayingWhite:
11480 case MachinePlaysBlack:
11481 if (!WhiteOnMove(currentMove)) {
11482 DisplayMoveError(_("It is Black's turn"));
11485 moveType = WhiteDrop;
11487 case IcsPlayingBlack:
11488 case MachinePlaysWhite:
11489 if (WhiteOnMove(currentMove)) {
11490 DisplayMoveError(_("It is White's turn"));
11493 moveType = BlackDrop;
11496 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
11502 if (moveType == BlackDrop && selection < BlackPawn) {
11503 selection = (ChessSquare) ((int) selection
11504 + (int) BlackPawn - (int) WhitePawn);
11506 if (boards[currentMove][y][x] != EmptySquare) {
11507 DisplayMoveError(_("That square is occupied"));
11511 FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
11517 /* Accept a pending offer of any kind from opponent */
11519 if (appData.icsActive) {
11520 SendToICS(ics_prefix);
11521 SendToICS("accept\n");
11522 } else if (cmailMsgLoaded) {
11523 if (currentMove == cmailOldMove &&
11524 commentList[cmailOldMove] != NULL &&
11525 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11526 "Black offers a draw" : "White offers a draw")) {
11528 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11529 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
11531 DisplayError(_("There is no pending offer on this move"), 0);
11532 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11535 /* Not used for offers from chess program */
11542 /* Decline a pending offer of any kind from opponent */
11544 if (appData.icsActive) {
11545 SendToICS(ics_prefix);
11546 SendToICS("decline\n");
11547 } else if (cmailMsgLoaded) {
11548 if (currentMove == cmailOldMove &&
11549 commentList[cmailOldMove] != NULL &&
11550 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11551 "Black offers a draw" : "White offers a draw")) {
11553 AppendComment(cmailOldMove, "Draw declined");
11554 DisplayComment(cmailOldMove - 1, "Draw declined");
11557 DisplayError(_("There is no pending offer on this move"), 0);
11560 /* Not used for offers from chess program */
11567 /* Issue ICS rematch command */
11568 if (appData.icsActive) {
11569 SendToICS(ics_prefix);
11570 SendToICS("rematch\n");
11577 /* Call your opponent's flag (claim a win on time) */
11578 if (appData.icsActive) {
11579 SendToICS(ics_prefix);
11580 SendToICS("flag\n");
11582 switch (gameMode) {
11585 case MachinePlaysWhite:
11588 GameEnds(GameIsDrawn, "Both players ran out of time",
11591 GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
11593 DisplayError(_("Your opponent is not out of time"), 0);
11596 case MachinePlaysBlack:
11599 GameEnds(GameIsDrawn, "Both players ran out of time",
11602 GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
11604 DisplayError(_("Your opponent is not out of time"), 0);
11614 /* Offer draw or accept pending draw offer from opponent */
11616 if (appData.icsActive) {
11617 /* Note: tournament rules require draw offers to be
11618 made after you make your move but before you punch
11619 your clock. Currently ICS doesn't let you do that;
11620 instead, you immediately punch your clock after making
11621 a move, but you can offer a draw at any time. */
11623 SendToICS(ics_prefix);
11624 SendToICS("draw\n");
11625 } else if (cmailMsgLoaded) {
11626 if (currentMove == cmailOldMove &&
11627 commentList[cmailOldMove] != NULL &&
11628 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11629 "Black offers a draw" : "White offers a draw")) {
11630 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11631 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
11632 } else if (currentMove == cmailOldMove + 1) {
11633 char *offer = WhiteOnMove(cmailOldMove) ?
11634 "White offers a draw" : "Black offers a draw";
11635 AppendComment(currentMove, offer);
11636 DisplayComment(currentMove - 1, offer);
11637 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
11639 DisplayError(_("You must make your move before offering a draw"), 0);
11640 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11642 } else if (first.offeredDraw) {
11643 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
11645 if (first.sendDrawOffers) {
11646 SendToProgram("draw\n", &first);
11647 userOfferedDraw = TRUE;
11655 /* Offer Adjourn or accept pending Adjourn offer from opponent */
11657 if (appData.icsActive) {
11658 SendToICS(ics_prefix);
11659 SendToICS("adjourn\n");
11661 /* Currently GNU Chess doesn't offer or accept Adjourns */
11669 /* Offer Abort or accept pending Abort offer from opponent */
11671 if (appData.icsActive) {
11672 SendToICS(ics_prefix);
11673 SendToICS("abort\n");
11675 GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
11682 /* Resign. You can do this even if it's not your turn. */
11684 if (appData.icsActive) {
11685 SendToICS(ics_prefix);
11686 SendToICS("resign\n");
11688 switch (gameMode) {
11689 case MachinePlaysWhite:
11690 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11692 case MachinePlaysBlack:
11693 GameEnds(BlackWins, "White resigns", GE_PLAYER);
11696 if (cmailMsgLoaded) {
11698 if (WhiteOnMove(cmailOldMove)) {
11699 GameEnds(BlackWins, "White resigns", GE_PLAYER);
11701 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11703 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
11714 StopObservingEvent()
11716 /* Stop observing current games */
11717 SendToICS(ics_prefix);
11718 SendToICS("unobserve\n");
11722 StopExaminingEvent()
11724 /* Stop observing current game */
11725 SendToICS(ics_prefix);
11726 SendToICS("unexamine\n");
11730 ForwardInner(target)
11735 if (appData.debugMode)
11736 fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
11737 target, currentMove, forwardMostMove);
11739 if (gameMode == EditPosition)
11742 if (gameMode == PlayFromGameFile && !pausing)
11745 if (gameMode == IcsExamining && pausing)
11746 limit = pauseExamForwardMostMove;
11748 limit = forwardMostMove;
11750 if (target > limit) target = limit;
11752 if (target > 0 && moveList[target - 1][0]) {
11753 int fromX, fromY, toX, toY;
11754 toX = moveList[target - 1][2] - AAA;
11755 toY = moveList[target - 1][3] - ONE;
11756 if (moveList[target - 1][1] == '@') {
11757 if (appData.highlightLastMove) {
11758 SetHighlights(-1, -1, toX, toY);
11761 fromX = moveList[target - 1][0] - AAA;
11762 fromY = moveList[target - 1][1] - ONE;
11763 if (target == currentMove + 1) {
11764 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
11766 if (appData.highlightLastMove) {
11767 SetHighlights(fromX, fromY, toX, toY);
11771 if (gameMode == EditGame || gameMode == AnalyzeMode ||
11772 gameMode == Training || gameMode == PlayFromGameFile ||
11773 gameMode == AnalyzeFile) {
11774 while (currentMove < target) {
11775 SendMoveToProgram(currentMove++, &first);
11778 currentMove = target;
11781 if (gameMode == EditGame || gameMode == EndOfGame) {
11782 whiteTimeRemaining = timeRemaining[0][currentMove];
11783 blackTimeRemaining = timeRemaining[1][currentMove];
11785 DisplayBothClocks();
11786 DisplayMove(currentMove - 1);
11787 DrawPosition(FALSE, boards[currentMove]);
11788 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
11789 if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
11790 DisplayComment(currentMove - 1, commentList[currentMove]);
11798 if (gameMode == IcsExamining && !pausing) {
11799 SendToICS(ics_prefix);
11800 SendToICS("forward\n");
11802 ForwardInner(currentMove + 1);
11809 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11810 /* to optimze, we temporarily turn off analysis mode while we feed
11811 * the remaining moves to the engine. Otherwise we get analysis output
11814 if (first.analysisSupport) {
11815 SendToProgram("exit\nforce\n", &first);
11816 first.analyzing = FALSE;
11820 if (gameMode == IcsExamining && !pausing) {
11821 SendToICS(ics_prefix);
11822 SendToICS("forward 999999\n");
11824 ForwardInner(forwardMostMove);
11827 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11828 /* we have fed all the moves, so reactivate analysis mode */
11829 SendToProgram("analyze\n", &first);
11830 first.analyzing = TRUE;
11831 /*first.maybeThinking = TRUE;*/
11832 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11837 BackwardInner(target)
11840 int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
11842 if (appData.debugMode)
11843 fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
11844 target, currentMove, forwardMostMove);
11846 if (gameMode == EditPosition) return;
11847 if (currentMove <= backwardMostMove) {
11849 DrawPosition(full_redraw, boards[currentMove]);
11852 if (gameMode == PlayFromGameFile && !pausing)
11855 if (moveList[target][0]) {
11856 int fromX, fromY, toX, toY;
11857 toX = moveList[target][2] - AAA;
11858 toY = moveList[target][3] - ONE;
11859 if (moveList[target][1] == '@') {
11860 if (appData.highlightLastMove) {
11861 SetHighlights(-1, -1, toX, toY);
11864 fromX = moveList[target][0] - AAA;
11865 fromY = moveList[target][1] - ONE;
11866 if (target == currentMove - 1) {
11867 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
11869 if (appData.highlightLastMove) {
11870 SetHighlights(fromX, fromY, toX, toY);
11874 if (gameMode == EditGame || gameMode==AnalyzeMode ||
11875 gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
11876 while (currentMove > target) {
11877 SendToProgram("undo\n", &first);
11881 currentMove = target;
11884 if (gameMode == EditGame || gameMode == EndOfGame) {
11885 whiteTimeRemaining = timeRemaining[0][currentMove];
11886 blackTimeRemaining = timeRemaining[1][currentMove];
11888 DisplayBothClocks();
11889 DisplayMove(currentMove - 1);
11890 DrawPosition(full_redraw, boards[currentMove]);
11891 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
11892 // [HGM] PV info: routine tests if comment empty
11893 DisplayComment(currentMove - 1, commentList[currentMove]);
11899 if (gameMode == IcsExamining && !pausing) {
11900 SendToICS(ics_prefix);
11901 SendToICS("backward\n");
11903 BackwardInner(currentMove - 1);
11910 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11911 /* to optimze, we temporarily turn off analysis mode while we undo
11912 * all the moves. Otherwise we get analysis output after each undo.
11914 if (first.analysisSupport) {
11915 SendToProgram("exit\nforce\n", &first);
11916 first.analyzing = FALSE;
11920 if (gameMode == IcsExamining && !pausing) {
11921 SendToICS(ics_prefix);
11922 SendToICS("backward 999999\n");
11924 BackwardInner(backwardMostMove);
11927 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11928 /* we have fed all the moves, so reactivate analysis mode */
11929 SendToProgram("analyze\n", &first);
11930 first.analyzing = TRUE;
11931 /*first.maybeThinking = TRUE;*/
11932 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11939 if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
11940 if (to >= forwardMostMove) to = forwardMostMove;
11941 if (to <= backwardMostMove) to = backwardMostMove;
11942 if (to < currentMove) {
11952 if (gameMode != IcsExamining) {
11953 DisplayError(_("You are not examining a game"), 0);
11957 DisplayError(_("You can't revert while pausing"), 0);
11960 SendToICS(ics_prefix);
11961 SendToICS("revert\n");
11967 switch (gameMode) {
11968 case MachinePlaysWhite:
11969 case MachinePlaysBlack:
11970 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
11971 DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
11974 if (forwardMostMove < 2) return;
11975 currentMove = forwardMostMove = forwardMostMove - 2;
11976 whiteTimeRemaining = timeRemaining[0][currentMove];
11977 blackTimeRemaining = timeRemaining[1][currentMove];
11978 DisplayBothClocks();
11979 DisplayMove(currentMove - 1);
11980 ClearHighlights();/*!! could figure this out*/
11981 DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
11982 SendToProgram("remove\n", &first);
11983 /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
11986 case BeginningOfGame:
11990 case IcsPlayingWhite:
11991 case IcsPlayingBlack:
11992 if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
11993 SendToICS(ics_prefix);
11994 SendToICS("takeback 2\n");
11996 SendToICS(ics_prefix);
11997 SendToICS("takeback 1\n");
12006 ChessProgramState *cps;
12008 switch (gameMode) {
12009 case MachinePlaysWhite:
12010 if (!WhiteOnMove(forwardMostMove)) {
12011 DisplayError(_("It is your turn"), 0);
12016 case MachinePlaysBlack:
12017 if (WhiteOnMove(forwardMostMove)) {
12018 DisplayError(_("It is your turn"), 0);
12023 case TwoMachinesPlay:
12024 if (WhiteOnMove(forwardMostMove) ==
12025 (first.twoMachinesColor[0] == 'w')) {
12031 case BeginningOfGame:
12035 SendToProgram("?\n", cps);
12039 TruncateGameEvent()
12042 if (gameMode != EditGame) return;
12049 if (forwardMostMove > currentMove) {
12050 if (gameInfo.resultDetails != NULL) {
12051 free(gameInfo.resultDetails);
12052 gameInfo.resultDetails = NULL;
12053 gameInfo.result = GameUnfinished;
12055 forwardMostMove = currentMove;
12056 HistorySet(parseList, backwardMostMove, forwardMostMove,
12064 if (appData.noChessProgram) return;
12065 switch (gameMode) {
12066 case MachinePlaysWhite:
12067 if (WhiteOnMove(forwardMostMove)) {
12068 DisplayError(_("Wait until your turn"), 0);
12072 case BeginningOfGame:
12073 case MachinePlaysBlack:
12074 if (!WhiteOnMove(forwardMostMove)) {
12075 DisplayError(_("Wait until your turn"), 0);
12080 DisplayError(_("No hint available"), 0);
12083 SendToProgram("hint\n", &first);
12084 hintRequested = TRUE;
12090 if (appData.noChessProgram) return;
12091 switch (gameMode) {
12092 case MachinePlaysWhite:
12093 if (WhiteOnMove(forwardMostMove)) {
12094 DisplayError(_("Wait until your turn"), 0);
12098 case BeginningOfGame:
12099 case MachinePlaysBlack:
12100 if (!WhiteOnMove(forwardMostMove)) {
12101 DisplayError(_("Wait until your turn"), 0);
12106 EditPositionDone();
12108 case TwoMachinesPlay:
12113 SendToProgram("bk\n", &first);
12114 bookOutput[0] = NULLCHAR;
12115 bookRequested = TRUE;
12121 char *tags = PGNTags(&gameInfo);
12122 TagsPopUp(tags, CmailMsg());
12126 /* end button procedures */
12129 PrintPosition(fp, move)
12135 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
12136 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
12137 char c = PieceToChar(boards[move][i][j]);
12138 fputc(c == 'x' ? '.' : c, fp);
12139 fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
12142 if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
12143 fprintf(fp, "white to play\n");
12145 fprintf(fp, "black to play\n");
12152 if (gameInfo.white != NULL) {
12153 fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
12159 /* Find last component of program's own name, using some heuristics */
12161 TidyProgramName(prog, host, buf)
12162 char *prog, *host, buf[MSG_SIZ];
12165 int local = (strcmp(host, "localhost") == 0);
12166 while (!local && (p = strchr(prog, ';')) != NULL) {
12168 while (*p == ' ') p++;
12171 if (*prog == '"' || *prog == '\'') {
12172 q = strchr(prog + 1, *prog);
12174 q = strchr(prog, ' ');
12176 if (q == NULL) q = prog + strlen(prog);
12178 while (p >= prog && *p != '/' && *p != '\\') p--;
12180 if(p == prog && *p == '"') p++;
12181 if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
12182 memcpy(buf, p, q - p);
12183 buf[q - p] = NULLCHAR;
12191 TimeControlTagValue()
12194 if (!appData.clockMode) {
12196 } else if (movesPerSession > 0) {
12197 sprintf(buf, "%d/%ld", movesPerSession, timeControl/1000);
12198 } else if (timeIncrement == 0) {
12199 sprintf(buf, "%ld", timeControl/1000);
12201 sprintf(buf, "%ld+%ld", timeControl/1000, timeIncrement/1000);
12203 return StrSave(buf);
12209 /* This routine is used only for certain modes */
12210 VariantClass v = gameInfo.variant;
12211 ClearGameInfo(&gameInfo);
12212 gameInfo.variant = v;
12214 switch (gameMode) {
12215 case MachinePlaysWhite:
12216 gameInfo.event = StrSave( appData.pgnEventHeader );
12217 gameInfo.site = StrSave(HostName());
12218 gameInfo.date = PGNDate();
12219 gameInfo.round = StrSave("-");
12220 gameInfo.white = StrSave(first.tidy);
12221 gameInfo.black = StrSave(UserName());
12222 gameInfo.timeControl = TimeControlTagValue();
12225 case MachinePlaysBlack:
12226 gameInfo.event = StrSave( appData.pgnEventHeader );
12227 gameInfo.site = StrSave(HostName());
12228 gameInfo.date = PGNDate();
12229 gameInfo.round = StrSave("-");
12230 gameInfo.white = StrSave(UserName());
12231 gameInfo.black = StrSave(first.tidy);
12232 gameInfo.timeControl = TimeControlTagValue();
12235 case TwoMachinesPlay:
12236 gameInfo.event = StrSave( appData.pgnEventHeader );
12237 gameInfo.site = StrSave(HostName());
12238 gameInfo.date = PGNDate();
12239 if (matchGame > 0) {
12241 sprintf(buf, "%d", matchGame);
12242 gameInfo.round = StrSave(buf);
12244 gameInfo.round = StrSave("-");
12246 if (first.twoMachinesColor[0] == 'w') {
12247 gameInfo.white = StrSave(first.tidy);
12248 gameInfo.black = StrSave(second.tidy);
12250 gameInfo.white = StrSave(second.tidy);
12251 gameInfo.black = StrSave(first.tidy);
12253 gameInfo.timeControl = TimeControlTagValue();
12257 gameInfo.event = StrSave("Edited game");
12258 gameInfo.site = StrSave(HostName());
12259 gameInfo.date = PGNDate();
12260 gameInfo.round = StrSave("-");
12261 gameInfo.white = StrSave("-");
12262 gameInfo.black = StrSave("-");
12266 gameInfo.event = StrSave("Edited position");
12267 gameInfo.site = StrSave(HostName());
12268 gameInfo.date = PGNDate();
12269 gameInfo.round = StrSave("-");
12270 gameInfo.white = StrSave("-");
12271 gameInfo.black = StrSave("-");
12274 case IcsPlayingWhite:
12275 case IcsPlayingBlack:
12280 case PlayFromGameFile:
12281 gameInfo.event = StrSave("Game from non-PGN file");
12282 gameInfo.site = StrSave(HostName());
12283 gameInfo.date = PGNDate();
12284 gameInfo.round = StrSave("-");
12285 gameInfo.white = StrSave("?");
12286 gameInfo.black = StrSave("?");
12295 ReplaceComment(index, text)
12301 while (*text == '\n') text++;
12302 len = strlen(text);
12303 while (len > 0 && text[len - 1] == '\n') len--;
12305 if (commentList[index] != NULL)
12306 free(commentList[index]);
12309 commentList[index] = NULL;
12312 commentList[index] = (char *) malloc(len + 2);
12313 strncpy(commentList[index], text, len);
12314 commentList[index][len] = '\n';
12315 commentList[index][len + 1] = NULLCHAR;
12328 if (ch == '\r') continue;
12330 } while (ch != '\0');
12334 AppendComment(index, text)
12341 text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
12344 while (*text == '\n') text++;
12345 len = strlen(text);
12346 while (len > 0 && text[len - 1] == '\n') len--;
12348 if (len == 0) return;
12350 if (commentList[index] != NULL) {
12351 old = commentList[index];
12352 oldlen = strlen(old);
12353 commentList[index] = (char *) malloc(oldlen + len + 2);
12354 strcpy(commentList[index], old);
12356 strncpy(&commentList[index][oldlen], text, len);
12357 commentList[index][oldlen + len] = '\n';
12358 commentList[index][oldlen + len + 1] = NULLCHAR;
12360 commentList[index] = (char *) malloc(len + 2);
12361 strncpy(commentList[index], text, len);
12362 commentList[index][len] = '\n';
12363 commentList[index][len + 1] = NULLCHAR;
12367 static char * FindStr( char * text, char * sub_text )
12369 char * result = strstr( text, sub_text );
12371 if( result != NULL ) {
12372 result += strlen( sub_text );
12378 /* [AS] Try to extract PV info from PGN comment */
12379 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
12380 char *GetInfoFromComment( int index, char * text )
12384 if( text != NULL && index > 0 ) {
12387 int time = -1, sec = 0, deci;
12388 char * s_eval = FindStr( text, "[%eval " );
12389 char * s_emt = FindStr( text, "[%emt " );
12391 if( s_eval != NULL || s_emt != NULL ) {
12395 if( s_eval != NULL ) {
12396 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
12400 if( delim != ']' ) {
12405 if( s_emt != NULL ) {
12409 /* We expect something like: [+|-]nnn.nn/dd */
12412 sep = strchr( text, '/' );
12413 if( sep == NULL || sep < (text+4) ) {
12417 time = -1; sec = -1; deci = -1;
12418 if( sscanf( text, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
12419 sscanf( text, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
12420 sscanf( text, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
12421 sscanf( text, "%d.%d/%d", &score, &score_lo, &depth ) != 3 ) {
12425 if( score_lo < 0 || score_lo >= 100 ) {
12429 if(sec >= 0) time = 600*time + 10*sec; else
12430 if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
12432 score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
12434 /* [HGM] PV time: now locate end of PV info */
12435 while( *++sep >= '0' && *sep <= '9'); // strip depth
12437 while( *++sep >= '0' && *sep <= '9'); // strip time
12439 while( *++sep >= '0' && *sep <= '9'); // strip seconds
12441 while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
12442 while(*sep == ' ') sep++;
12453 pvInfoList[index-1].depth = depth;
12454 pvInfoList[index-1].score = score;
12455 pvInfoList[index-1].time = 10*time; // centi-sec
12461 SendToProgram(message, cps)
12463 ChessProgramState *cps;
12465 int count, outCount, error;
12468 if (cps->pr == NULL) return;
12471 if (appData.debugMode) {
12474 fprintf(debugFP, "%ld >%-6s: %s",
12475 SubtractTimeMarks(&now, &programStartTime),
12476 cps->which, message);
12479 count = strlen(message);
12480 outCount = OutputToProcess(cps->pr, message, count, &error);
12481 if (outCount < count && !exiting
12482 && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
12483 sprintf(buf, _("Error writing to %s chess program"), cps->which);
12484 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
12485 if(epStatus[forwardMostMove] <= EP_DRAWS) {
12486 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
12487 sprintf(buf, "%s program exits in draw position (%s)", cps->which, cps->program);
12489 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
12491 gameInfo.resultDetails = buf;
12493 DisplayFatalError(buf, error, 1);
12498 ReceiveFromProgram(isr, closure, message, count, error)
12499 InputSourceRef isr;
12507 ChessProgramState *cps = (ChessProgramState *)closure;
12509 if (isr != cps->isr) return; /* Killed intentionally */
12513 _("Error: %s chess program (%s) exited unexpectedly"),
12514 cps->which, cps->program);
12515 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
12516 if(epStatus[forwardMostMove] <= EP_DRAWS) {
12517 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
12518 sprintf(buf, _("%s program exits in draw position (%s)"), cps->which, cps->program);
12520 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
12522 gameInfo.resultDetails = buf;
12524 RemoveInputSource(cps->isr);
12525 DisplayFatalError(buf, 0, 1);
12528 _("Error reading from %s chess program (%s)"),
12529 cps->which, cps->program);
12530 RemoveInputSource(cps->isr);
12532 /* [AS] Program is misbehaving badly... kill it */
12533 if( count == -2 ) {
12534 DestroyChildProcess( cps->pr, 9 );
12538 DisplayFatalError(buf, error, 1);
12543 if ((end_str = strchr(message, '\r')) != NULL)
12544 *end_str = NULLCHAR;
12545 if ((end_str = strchr(message, '\n')) != NULL)
12546 *end_str = NULLCHAR;
12548 if (appData.debugMode) {
12549 TimeMark now; int print = 1;
12550 char *quote = ""; char c; int i;
12552 if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
12553 char start = message[0];
12554 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
12555 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
12556 sscanf(message, "move %c", &c)!=1 && sscanf(message, "offer%c", &c)!=1 &&
12557 sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
12558 sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
12559 sscanf(message, "tell%c", &c)!=1 && sscanf(message, "0-1 %c", &c)!=1 &&
12560 sscanf(message, "1-0 %c", &c)!=1 && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
12561 sscanf(message, "pong %c", &c)!=1 && start != '#')
12562 { quote = "# "; print = (appData.engineComments == 2); }
12563 message[0] = start; // restore original message
12567 fprintf(debugFP, "%ld <%-6s: %s%s\n",
12568 SubtractTimeMarks(&now, &programStartTime), cps->which,
12574 /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
12575 if (appData.icsEngineAnalyze) {
12576 if (strstr(message, "whisper") != NULL ||
12577 strstr(message, "kibitz") != NULL ||
12578 strstr(message, "tellics") != NULL) return;
12581 HandleMachineMove(message, cps);
12586 SendTimeControl(cps, mps, tc, inc, sd, st)
12587 ChessProgramState *cps;
12588 int mps, inc, sd, st;
12594 if( timeControl_2 > 0 ) {
12595 if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
12596 tc = timeControl_2;
12599 tc /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
12600 inc /= cps->timeOdds;
12601 st /= cps->timeOdds;
12603 seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
12606 /* Set exact time per move, normally using st command */
12607 if (cps->stKludge) {
12608 /* GNU Chess 4 has no st command; uses level in a nonstandard way */
12610 if (seconds == 0) {
12611 sprintf(buf, "level 1 %d\n", st/60);
12613 sprintf(buf, "level 1 %d:%02d\n", st/60, seconds);
12616 sprintf(buf, "st %d\n", st);
12619 /* Set conventional or incremental time control, using level command */
12620 if (seconds == 0) {
12621 /* Note old gnuchess bug -- minutes:seconds used to not work.
12622 Fixed in later versions, but still avoid :seconds
12623 when seconds is 0. */
12624 sprintf(buf, "level %d %ld %d\n", mps, tc/60000, inc/1000);
12626 sprintf(buf, "level %d %ld:%02d %d\n", mps, tc/60000,
12627 seconds, inc/1000);
12630 SendToProgram(buf, cps);
12632 /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
12633 /* Orthogonally, limit search to given depth */
12635 if (cps->sdKludge) {
12636 sprintf(buf, "depth\n%d\n", sd);
12638 sprintf(buf, "sd %d\n", sd);
12640 SendToProgram(buf, cps);
12643 if(cps->nps > 0) { /* [HGM] nps */
12644 if(cps->supportsNPS == FALSE) cps->nps = -1; // don't use if engine explicitly says not supported!
12646 sprintf(buf, "nps %d\n", cps->nps);
12647 SendToProgram(buf, cps);
12652 ChessProgramState *WhitePlayer()
12653 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
12655 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
12656 gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
12662 SendTimeRemaining(cps, machineWhite)
12663 ChessProgramState *cps;
12664 int /*boolean*/ machineWhite;
12666 char message[MSG_SIZ];
12669 /* Note: this routine must be called when the clocks are stopped
12670 or when they have *just* been set or switched; otherwise
12671 it will be off by the time since the current tick started.
12673 if (machineWhite) {
12674 time = whiteTimeRemaining / 10;
12675 otime = blackTimeRemaining / 10;
12677 time = blackTimeRemaining / 10;
12678 otime = whiteTimeRemaining / 10;
12680 /* [HGM] translate opponent's time by time-odds factor */
12681 otime = (otime * cps->other->timeOdds) / cps->timeOdds;
12682 if (appData.debugMode) {
12683 fprintf(debugFP, "time odds: %d %d \n", cps->timeOdds, cps->other->timeOdds);
12686 if (time <= 0) time = 1;
12687 if (otime <= 0) otime = 1;
12689 sprintf(message, "time %ld\n", time);
12690 SendToProgram(message, cps);
12692 sprintf(message, "otim %ld\n", otime);
12693 SendToProgram(message, cps);
12697 BoolFeature(p, name, loc, cps)
12701 ChessProgramState *cps;
12704 int len = strlen(name);
12706 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
12708 sscanf(*p, "%d", &val);
12710 while (**p && **p != ' ') (*p)++;
12711 sprintf(buf, "accepted %s\n", name);
12712 SendToProgram(buf, cps);
12719 IntFeature(p, name, loc, cps)
12723 ChessProgramState *cps;
12726 int len = strlen(name);
12727 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
12729 sscanf(*p, "%d", loc);
12730 while (**p && **p != ' ') (*p)++;
12731 sprintf(buf, "accepted %s\n", name);
12732 SendToProgram(buf, cps);
12739 StringFeature(p, name, loc, cps)
12743 ChessProgramState *cps;
12746 int len = strlen(name);
12747 if (strncmp((*p), name, len) == 0
12748 && (*p)[len] == '=' && (*p)[len+1] == '\"') {
12750 sscanf(*p, "%[^\"]", loc);
12751 while (**p && **p != '\"') (*p)++;
12752 if (**p == '\"') (*p)++;
12753 sprintf(buf, "accepted %s\n", name);
12754 SendToProgram(buf, cps);
12761 ParseOption(Option *opt, ChessProgramState *cps)
12762 // [HGM] options: process the string that defines an engine option, and determine
12763 // name, type, default value, and allowed value range
12765 char *p, *q, buf[MSG_SIZ];
12766 int n, min = (-1)<<31, max = 1<<31, def;
12768 if(p = strstr(opt->name, " -spin ")) {
12769 if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
12770 if(max < min) max = min; // enforce consistency
12771 if(def < min) def = min;
12772 if(def > max) def = max;
12777 } else if((p = strstr(opt->name, " -slider "))) {
12778 // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
12779 if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
12780 if(max < min) max = min; // enforce consistency
12781 if(def < min) def = min;
12782 if(def > max) def = max;
12786 opt->type = Spin; // Slider;
12787 } else if((p = strstr(opt->name, " -string "))) {
12788 opt->textValue = p+9;
12789 opt->type = TextBox;
12790 } else if((p = strstr(opt->name, " -file "))) {
12791 // for now -file is a synonym for -string, to already provide compatibility with future polyglots
12792 opt->textValue = p+7;
12793 opt->type = TextBox; // FileName;
12794 } else if((p = strstr(opt->name, " -path "))) {
12795 // for now -file is a synonym for -string, to already provide compatibility with future polyglots
12796 opt->textValue = p+7;
12797 opt->type = TextBox; // PathName;
12798 } else if(p = strstr(opt->name, " -check ")) {
12799 if(sscanf(p, " -check %d", &def) < 1) return FALSE;
12800 opt->value = (def != 0);
12801 opt->type = CheckBox;
12802 } else if(p = strstr(opt->name, " -combo ")) {
12803 opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
12804 cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
12805 if(*q == '*') cps->comboList[cps->comboCnt-1]++;
12806 opt->value = n = 0;
12807 while(q = StrStr(q, " /// ")) {
12808 n++; *q = 0; // count choices, and null-terminate each of them
12810 if(*q == '*') { // remember default, which is marked with * prefix
12814 cps->comboList[cps->comboCnt++] = q;
12816 cps->comboList[cps->comboCnt++] = NULL;
12818 opt->type = ComboBox;
12819 } else if(p = strstr(opt->name, " -button")) {
12820 opt->type = Button;
12821 } else if(p = strstr(opt->name, " -save")) {
12822 opt->type = SaveButton;
12823 } else return FALSE;
12824 *p = 0; // terminate option name
12825 // now look if the command-line options define a setting for this engine option.
12826 if(cps->optionSettings && cps->optionSettings[0])
12827 p = strstr(cps->optionSettings, opt->name); else p = NULL;
12828 if(p && (p == cps->optionSettings || p[-1] == ',')) {
12829 sprintf(buf, "option %s", p);
12830 if(p = strstr(buf, ",")) *p = 0;
12832 SendToProgram(buf, cps);
12838 FeatureDone(cps, val)
12839 ChessProgramState* cps;
12842 DelayedEventCallback cb = GetDelayedEvent();
12843 if ((cb == InitBackEnd3 && cps == &first) ||
12844 (cb == TwoMachinesEventIfReady && cps == &second)) {
12845 CancelDelayedEvent();
12846 ScheduleDelayedEvent(cb, val ? 1 : 3600000);
12848 cps->initDone = val;
12851 /* Parse feature command from engine */
12853 ParseFeatures(args, cps)
12855 ChessProgramState *cps;
12863 while (*p == ' ') p++;
12864 if (*p == NULLCHAR) return;
12866 if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
12867 if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
12868 if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
12869 if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
12870 if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
12871 if (BoolFeature(&p, "reuse", &val, cps)) {
12872 /* Engine can disable reuse, but can't enable it if user said no */
12873 if (!val) cps->reuse = FALSE;
12876 if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
12877 if (StringFeature(&p, "myname", &cps->tidy, cps)) {
12878 if (gameMode == TwoMachinesPlay) {
12879 DisplayTwoMachinesTitle();
12885 if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
12886 if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
12887 if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
12888 if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
12889 if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
12890 if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
12891 if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
12892 if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
12893 if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
12894 if (IntFeature(&p, "done", &val, cps)) {
12895 FeatureDone(cps, val);
12898 /* Added by Tord: */
12899 if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
12900 if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
12901 /* End of additions by Tord */
12903 /* [HGM] added features: */
12904 if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
12905 if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
12906 if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
12907 if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
12908 if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
12909 if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
12910 if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
12911 if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
12912 sprintf(buf, "rejected option %s\n", cps->option[--cps->nrOptions].name);
12913 SendToProgram(buf, cps);
12916 if(cps->nrOptions >= MAX_OPTIONS) {
12918 sprintf(buf, "%s engine has too many options\n", cps->which);
12919 DisplayError(buf, 0);
12923 if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
12924 /* End of additions by HGM */
12926 /* unknown feature: complain and skip */
12928 while (*q && *q != '=') q++;
12929 sprintf(buf, "rejected %.*s\n", q-p, p);
12930 SendToProgram(buf, cps);
12936 while (*p && *p != '\"') p++;
12937 if (*p == '\"') p++;
12939 while (*p && *p != ' ') p++;
12947 PeriodicUpdatesEvent(newState)
12950 if (newState == appData.periodicUpdates)
12953 appData.periodicUpdates=newState;
12955 /* Display type changes, so update it now */
12956 // DisplayAnalysis();
12958 /* Get the ball rolling again... */
12960 AnalysisPeriodicEvent(1);
12961 StartAnalysisClock();
12966 PonderNextMoveEvent(newState)
12969 if (newState == appData.ponderNextMove) return;
12970 if (gameMode == EditPosition) EditPositionDone();
12972 SendToProgram("hard\n", &first);
12973 if (gameMode == TwoMachinesPlay) {
12974 SendToProgram("hard\n", &second);
12977 SendToProgram("easy\n", &first);
12978 thinkOutput[0] = NULLCHAR;
12979 if (gameMode == TwoMachinesPlay) {
12980 SendToProgram("easy\n", &second);
12983 appData.ponderNextMove = newState;
12987 NewSettingEvent(option, command, value)
12993 if (gameMode == EditPosition) EditPositionDone();
12994 sprintf(buf, "%s%s %d\n", (option ? "option ": ""), command, value);
12995 SendToProgram(buf, &first);
12996 if (gameMode == TwoMachinesPlay) {
12997 SendToProgram(buf, &second);
13002 ShowThinkingEvent()
13003 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
13005 static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
13006 int newState = appData.showThinking
13007 // [HGM] thinking: other features now need thinking output as well
13008 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
13010 if (oldState == newState) return;
13011 oldState = newState;
13012 if (gameMode == EditPosition) EditPositionDone();
13014 SendToProgram("post\n", &first);
13015 if (gameMode == TwoMachinesPlay) {
13016 SendToProgram("post\n", &second);
13019 SendToProgram("nopost\n", &first);
13020 thinkOutput[0] = NULLCHAR;
13021 if (gameMode == TwoMachinesPlay) {
13022 SendToProgram("nopost\n", &second);
13025 // appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
13029 AskQuestionEvent(title, question, replyPrefix, which)
13030 char *title; char *question; char *replyPrefix; char *which;
13032 ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
13033 if (pr == NoProc) return;
13034 AskQuestion(title, question, replyPrefix, pr);
13038 DisplayMove(moveNumber)
13041 char message[MSG_SIZ];
13043 char cpThinkOutput[MSG_SIZ];
13045 if(appData.noGUI) return; // [HGM] fast: suppress display of moves
13047 if (moveNumber == forwardMostMove - 1 ||
13048 gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13050 safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput));
13052 if (strchr(cpThinkOutput, '\n')) {
13053 *strchr(cpThinkOutput, '\n') = NULLCHAR;
13056 *cpThinkOutput = NULLCHAR;
13059 /* [AS] Hide thinking from human user */
13060 if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
13061 *cpThinkOutput = NULLCHAR;
13062 if( thinkOutput[0] != NULLCHAR ) {
13065 for( i=0; i<=hiddenThinkOutputState; i++ ) {
13066 cpThinkOutput[i] = '.';
13068 cpThinkOutput[i] = NULLCHAR;
13069 hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
13073 if (moveNumber == forwardMostMove - 1 &&
13074 gameInfo.resultDetails != NULL) {
13075 if (gameInfo.resultDetails[0] == NULLCHAR) {
13076 sprintf(res, " %s", PGNResult(gameInfo.result));
13078 sprintf(res, " {%s} %s",
13079 gameInfo.resultDetails, PGNResult(gameInfo.result));
13085 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
13086 DisplayMessage(res, cpThinkOutput);
13088 sprintf(message, "%d.%s%s%s", moveNumber / 2 + 1,
13089 WhiteOnMove(moveNumber) ? " " : ".. ",
13090 parseList[moveNumber], res);
13091 DisplayMessage(message, cpThinkOutput);
13096 DisplayComment(moveNumber, text)
13100 char title[MSG_SIZ];
13101 char buf[8000]; // comment can be long!
13104 if( appData.autoDisplayComment ) {
13105 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
13106 strcpy(title, "Comment");
13108 sprintf(title, "Comment on %d.%s%s", moveNumber / 2 + 1,
13109 WhiteOnMove(moveNumber) ? " " : ".. ",
13110 parseList[moveNumber]);
13112 // [HGM] PV info: display PV info together with (or as) comment
13113 if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {
13114 if(text == NULL) text = "";
13115 score = pvInfoList[moveNumber].score;
13116 sprintf(buf, "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,
13117 depth, (pvInfoList[moveNumber].time+50)/100, text);
13120 } else title[0] = 0;
13123 CommentPopUp(title, text);
13126 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
13127 * might be busy thinking or pondering. It can be omitted if your
13128 * gnuchess is configured to stop thinking immediately on any user
13129 * input. However, that gnuchess feature depends on the FIONREAD
13130 * ioctl, which does not work properly on some flavors of Unix.
13134 ChessProgramState *cps;
13137 if (!cps->useSigint) return;
13138 if (appData.noChessProgram || (cps->pr == NoProc)) return;
13139 switch (gameMode) {
13140 case MachinePlaysWhite:
13141 case MachinePlaysBlack:
13142 case TwoMachinesPlay:
13143 case IcsPlayingWhite:
13144 case IcsPlayingBlack:
13147 /* Skip if we know it isn't thinking */
13148 if (!cps->maybeThinking) return;
13149 if (appData.debugMode)
13150 fprintf(debugFP, "Interrupting %s\n", cps->which);
13151 InterruptChildProcess(cps->pr);
13152 cps->maybeThinking = FALSE;
13157 #endif /*ATTENTION*/
13163 if (whiteTimeRemaining <= 0) {
13166 if (appData.icsActive) {
13167 if (appData.autoCallFlag &&
13168 gameMode == IcsPlayingBlack && !blackFlag) {
13169 SendToICS(ics_prefix);
13170 SendToICS("flag\n");
13174 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
13176 if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
13177 if (appData.autoCallFlag) {
13178 GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
13185 if (blackTimeRemaining <= 0) {
13188 if (appData.icsActive) {
13189 if (appData.autoCallFlag &&
13190 gameMode == IcsPlayingWhite && !whiteFlag) {
13191 SendToICS(ics_prefix);
13192 SendToICS("flag\n");
13196 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
13198 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
13199 if (appData.autoCallFlag) {
13200 GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
13213 if (!appData.clockMode || appData.icsActive ||
13214 gameMode == PlayFromGameFile || forwardMostMove == 0) return;
13217 * add time to clocks when time control is achieved ([HGM] now also used for increment)
13219 if ( !WhiteOnMove(forwardMostMove) )
13220 /* White made time control */
13221 whiteTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
13222 /* [HGM] time odds: correct new time quota for time odds! */
13223 / WhitePlayer()->timeOdds;
13225 /* Black made time control */
13226 blackTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
13227 / WhitePlayer()->other->timeOdds;
13231 DisplayBothClocks()
13233 int wom = gameMode == EditPosition ?
13234 !blackPlaysFirst : WhiteOnMove(currentMove);
13235 DisplayWhiteClock(whiteTimeRemaining, wom);
13236 DisplayBlackClock(blackTimeRemaining, !wom);
13240 /* Timekeeping seems to be a portability nightmare. I think everyone
13241 has ftime(), but I'm really not sure, so I'm including some ifdefs
13242 to use other calls if you don't. Clocks will be less accurate if
13243 you have neither ftime nor gettimeofday.
13246 /* VS 2008 requires the #include outside of the function */
13247 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
13248 #include <sys/timeb.h>
13251 /* Get the current time as a TimeMark */
13256 #if HAVE_GETTIMEOFDAY
13258 struct timeval timeVal;
13259 struct timezone timeZone;
13261 gettimeofday(&timeVal, &timeZone);
13262 tm->sec = (long) timeVal.tv_sec;
13263 tm->ms = (int) (timeVal.tv_usec / 1000L);
13265 #else /*!HAVE_GETTIMEOFDAY*/
13268 // include <sys/timeb.h> / moved to just above start of function
13269 struct timeb timeB;
13272 tm->sec = (long) timeB.time;
13273 tm->ms = (int) timeB.millitm;
13275 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
13276 tm->sec = (long) time(NULL);
13282 /* Return the difference in milliseconds between two
13283 time marks. We assume the difference will fit in a long!
13286 SubtractTimeMarks(tm2, tm1)
13287 TimeMark *tm2, *tm1;
13289 return 1000L*(tm2->sec - tm1->sec) +
13290 (long) (tm2->ms - tm1->ms);
13295 * Code to manage the game clocks.
13297 * In tournament play, black starts the clock and then white makes a move.
13298 * We give the human user a slight advantage if he is playing white---the
13299 * clocks don't run until he makes his first move, so it takes zero time.
13300 * Also, we don't account for network lag, so we could get out of sync
13301 * with GNU Chess's clock -- but then, referees are always right.
13304 static TimeMark tickStartTM;
13305 static long intendedTickLength;
13308 NextTickLength(timeRemaining)
13309 long timeRemaining;
13311 long nominalTickLength, nextTickLength;
13313 if (timeRemaining > 0L && timeRemaining <= 10000L)
13314 nominalTickLength = 100L;
13316 nominalTickLength = 1000L;
13317 nextTickLength = timeRemaining % nominalTickLength;
13318 if (nextTickLength <= 0) nextTickLength += nominalTickLength;
13320 return nextTickLength;
13323 /* Adjust clock one minute up or down */
13325 AdjustClock(Boolean which, int dir)
13327 if(which) blackTimeRemaining += 60000*dir;
13328 else whiteTimeRemaining += 60000*dir;
13329 DisplayBothClocks();
13332 /* Stop clocks and reset to a fresh time control */
13336 (void) StopClockTimer();
13337 if (appData.icsActive) {
13338 whiteTimeRemaining = blackTimeRemaining = 0;
13339 } else { /* [HGM] correct new time quote for time odds */
13340 whiteTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->timeOdds;
13341 blackTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->other->timeOdds;
13343 if (whiteFlag || blackFlag) {
13345 whiteFlag = blackFlag = FALSE;
13347 DisplayBothClocks();
13350 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
13352 /* Decrement running clock by amount of time that has passed */
13356 long timeRemaining;
13357 long lastTickLength, fudge;
13360 if (!appData.clockMode) return;
13361 if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
13365 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13367 /* Fudge if we woke up a little too soon */
13368 fudge = intendedTickLength - lastTickLength;
13369 if (fudge < 0 || fudge > FUDGE) fudge = 0;
13371 if (WhiteOnMove(forwardMostMove)) {
13372 if(whiteNPS >= 0) lastTickLength = 0;
13373 timeRemaining = whiteTimeRemaining -= lastTickLength;
13374 DisplayWhiteClock(whiteTimeRemaining - fudge,
13375 WhiteOnMove(currentMove));
13377 if(blackNPS >= 0) lastTickLength = 0;
13378 timeRemaining = blackTimeRemaining -= lastTickLength;
13379 DisplayBlackClock(blackTimeRemaining - fudge,
13380 !WhiteOnMove(currentMove));
13383 if (CheckFlags()) return;
13386 intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
13387 StartClockTimer(intendedTickLength);
13389 /* if the time remaining has fallen below the alarm threshold, sound the
13390 * alarm. if the alarm has sounded and (due to a takeback or time control
13391 * with increment) the time remaining has increased to a level above the
13392 * threshold, reset the alarm so it can sound again.
13395 if (appData.icsActive && appData.icsAlarm) {
13397 /* make sure we are dealing with the user's clock */
13398 if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
13399 ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
13402 if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
13403 alarmSounded = FALSE;
13404 } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
13406 alarmSounded = TRUE;
13412 /* A player has just moved, so stop the previously running
13413 clock and (if in clock mode) start the other one.
13414 We redisplay both clocks in case we're in ICS mode, because
13415 ICS gives us an update to both clocks after every move.
13416 Note that this routine is called *after* forwardMostMove
13417 is updated, so the last fractional tick must be subtracted
13418 from the color that is *not* on move now.
13423 long lastTickLength;
13425 int flagged = FALSE;
13429 if (StopClockTimer() && appData.clockMode) {
13430 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13431 if (WhiteOnMove(forwardMostMove)) {
13432 if(blackNPS >= 0) lastTickLength = 0;
13433 blackTimeRemaining -= lastTickLength;
13434 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
13435 // if(pvInfoList[forwardMostMove-1].time == -1)
13436 pvInfoList[forwardMostMove-1].time = // use GUI time
13437 (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
13439 if(whiteNPS >= 0) lastTickLength = 0;
13440 whiteTimeRemaining -= lastTickLength;
13441 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
13442 // if(pvInfoList[forwardMostMove-1].time == -1)
13443 pvInfoList[forwardMostMove-1].time =
13444 (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
13446 flagged = CheckFlags();
13448 CheckTimeControl();
13450 if (flagged || !appData.clockMode) return;
13452 switch (gameMode) {
13453 case MachinePlaysBlack:
13454 case MachinePlaysWhite:
13455 case BeginningOfGame:
13456 if (pausing) return;
13460 case PlayFromGameFile:
13469 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
13470 whiteTimeRemaining : blackTimeRemaining);
13471 StartClockTimer(intendedTickLength);
13475 /* Stop both clocks */
13479 long lastTickLength;
13482 if (!StopClockTimer()) return;
13483 if (!appData.clockMode) return;
13487 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13488 if (WhiteOnMove(forwardMostMove)) {
13489 if(whiteNPS >= 0) lastTickLength = 0;
13490 whiteTimeRemaining -= lastTickLength;
13491 DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
13493 if(blackNPS >= 0) lastTickLength = 0;
13494 blackTimeRemaining -= lastTickLength;
13495 DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
13500 /* Start clock of player on move. Time may have been reset, so
13501 if clock is already running, stop and restart it. */
13505 (void) StopClockTimer(); /* in case it was running already */
13506 DisplayBothClocks();
13507 if (CheckFlags()) return;
13509 if (!appData.clockMode) return;
13510 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
13512 GetTimeMark(&tickStartTM);
13513 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
13514 whiteTimeRemaining : blackTimeRemaining);
13516 /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
13517 whiteNPS = blackNPS = -1;
13518 if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
13519 || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
13520 whiteNPS = first.nps;
13521 if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
13522 || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
13523 blackNPS = first.nps;
13524 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
13525 whiteNPS = second.nps;
13526 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
13527 blackNPS = second.nps;
13528 if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
13530 StartClockTimer(intendedTickLength);
13537 long second, minute, hour, day;
13539 static char buf[32];
13541 if (ms > 0 && ms <= 9900) {
13542 /* convert milliseconds to tenths, rounding up */
13543 double tenths = floor( ((double)(ms + 99L)) / 100.00 );
13545 sprintf(buf, " %03.1f ", tenths/10.0);
13549 /* convert milliseconds to seconds, rounding up */
13550 /* use floating point to avoid strangeness of integer division
13551 with negative dividends on many machines */
13552 second = (long) floor(((double) (ms + 999L)) / 1000.0);
13559 day = second / (60 * 60 * 24);
13560 second = second % (60 * 60 * 24);
13561 hour = second / (60 * 60);
13562 second = second % (60 * 60);
13563 minute = second / 60;
13564 second = second % 60;
13567 sprintf(buf, " %s%ld:%02ld:%02ld:%02ld ",
13568 sign, day, hour, minute, second);
13570 sprintf(buf, " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
13572 sprintf(buf, " %s%2ld:%02ld ", sign, minute, second);
13579 * This is necessary because some C libraries aren't ANSI C compliant yet.
13582 StrStr(string, match)
13583 char *string, *match;
13587 length = strlen(match);
13589 for (i = strlen(string) - length; i >= 0; i--, string++)
13590 if (!strncmp(match, string, length))
13597 StrCaseStr(string, match)
13598 char *string, *match;
13602 length = strlen(match);
13604 for (i = strlen(string) - length; i >= 0; i--, string++) {
13605 for (j = 0; j < length; j++) {
13606 if (ToLower(match[j]) != ToLower(string[j]))
13609 if (j == length) return string;
13623 c1 = ToLower(*s1++);
13624 c2 = ToLower(*s2++);
13625 if (c1 > c2) return 1;
13626 if (c1 < c2) return -1;
13627 if (c1 == NULLCHAR) return 0;
13636 return isupper(c) ? tolower(c) : c;
13644 return islower(c) ? toupper(c) : c;
13646 #endif /* !_amigados */
13654 if ((ret = (char *) malloc(strlen(s) + 1))) {
13661 StrSavePtr(s, savePtr)
13662 char *s, **savePtr;
13667 if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
13668 strcpy(*savePtr, s);
13680 clock = time((time_t *)NULL);
13681 tm = localtime(&clock);
13682 sprintf(buf, "%04d.%02d.%02d",
13683 tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
13684 return StrSave(buf);
13689 PositionToFEN(move, overrideCastling)
13691 char *overrideCastling;
13693 int i, j, fromX, fromY, toX, toY;
13700 whiteToPlay = (gameMode == EditPosition) ?
13701 !blackPlaysFirst : (move % 2 == 0);
13704 /* Piece placement data */
13705 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13707 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
13708 if (boards[move][i][j] == EmptySquare) {
13710 } else { ChessSquare piece = boards[move][i][j];
13711 if (emptycount > 0) {
13712 if(emptycount<10) /* [HGM] can be >= 10 */
13713 *p++ = '0' + emptycount;
13714 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
13717 if(PieceToChar(piece) == '+') {
13718 /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
13720 piece = (ChessSquare)(DEMOTED piece);
13722 *p++ = PieceToChar(piece);
13724 /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
13725 p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
13730 if (emptycount > 0) {
13731 if(emptycount<10) /* [HGM] can be >= 10 */
13732 *p++ = '0' + emptycount;
13733 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
13740 /* [HGM] print Crazyhouse or Shogi holdings */
13741 if( gameInfo.holdingsWidth ) {
13742 *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
13744 for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
13745 piece = boards[move][i][BOARD_WIDTH-1];
13746 if( piece != EmptySquare )
13747 for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
13748 *p++ = PieceToChar(piece);
13750 for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
13751 piece = boards[move][BOARD_HEIGHT-i-1][0];
13752 if( piece != EmptySquare )
13753 for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
13754 *p++ = PieceToChar(piece);
13757 if( q == p ) *p++ = '-';
13763 *p++ = whiteToPlay ? 'w' : 'b';
13766 if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
13767 while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' ';
13769 if(nrCastlingRights) {
13771 if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
13772 /* [HGM] write directly from rights */
13773 if(castlingRights[move][2] >= 0 &&
13774 castlingRights[move][0] >= 0 )
13775 *p++ = castlingRights[move][0] + AAA + 'A' - 'a';
13776 if(castlingRights[move][2] >= 0 &&
13777 castlingRights[move][1] >= 0 )
13778 *p++ = castlingRights[move][1] + AAA + 'A' - 'a';
13779 if(castlingRights[move][5] >= 0 &&
13780 castlingRights[move][3] >= 0 )
13781 *p++ = castlingRights[move][3] + AAA;
13782 if(castlingRights[move][5] >= 0 &&
13783 castlingRights[move][4] >= 0 )
13784 *p++ = castlingRights[move][4] + AAA;
13787 /* [HGM] write true castling rights */
13788 if( nrCastlingRights == 6 ) {
13789 if(castlingRights[move][0] == BOARD_RGHT-1 &&
13790 castlingRights[move][2] >= 0 ) *p++ = 'K';
13791 if(castlingRights[move][1] == BOARD_LEFT &&
13792 castlingRights[move][2] >= 0 ) *p++ = 'Q';
13793 if(castlingRights[move][3] == BOARD_RGHT-1 &&
13794 castlingRights[move][5] >= 0 ) *p++ = 'k';
13795 if(castlingRights[move][4] == BOARD_LEFT &&
13796 castlingRights[move][5] >= 0 ) *p++ = 'q';
13799 if (q == p) *p++ = '-'; /* No castling rights */
13803 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
13804 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier ) {
13805 /* En passant target square */
13806 if (move > backwardMostMove) {
13807 fromX = moveList[move - 1][0] - AAA;
13808 fromY = moveList[move - 1][1] - ONE;
13809 toX = moveList[move - 1][2] - AAA;
13810 toY = moveList[move - 1][3] - ONE;
13811 if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
13812 toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
13813 boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
13815 /* 2-square pawn move just happened */
13817 *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
13821 } else if(move == backwardMostMove) {
13822 // [HGM] perhaps we should always do it like this, and forget the above?
13823 if(epStatus[move] >= 0) {
13824 *p++ = epStatus[move] + AAA;
13825 *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
13836 /* [HGM] find reversible plies */
13837 { int i = 0, j=move;
13839 if (appData.debugMode) { int k;
13840 fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
13841 for(k=backwardMostMove; k<=forwardMostMove; k++)
13842 fprintf(debugFP, "e%d. p=%d\n", k, epStatus[k]);
13846 while(j > backwardMostMove && epStatus[j] <= EP_NONE) j--,i++;
13847 if( j == backwardMostMove ) i += initialRulePlies;
13848 sprintf(p, "%d ", i);
13849 p += i>=100 ? 4 : i >= 10 ? 3 : 2;
13851 /* Fullmove number */
13852 sprintf(p, "%d", (move / 2) + 1);
13854 return StrSave(buf);
13858 ParseFEN(board, blackPlaysFirst, fen)
13860 int *blackPlaysFirst;
13870 /* [HGM] by default clear Crazyhouse holdings, if present */
13871 if(gameInfo.holdingsWidth) {
13872 for(i=0; i<BOARD_HEIGHT; i++) {
13873 board[i][0] = EmptySquare; /* black holdings */
13874 board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
13875 board[i][1] = (ChessSquare) 0; /* black counts */
13876 board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
13880 /* Piece placement data */
13881 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13884 if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
13885 if (*p == '/') p++;
13886 emptycount = gameInfo.boardWidth - j;
13887 while (emptycount--)
13888 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13890 #if(BOARD_SIZE >= 10)
13891 } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
13892 p++; emptycount=10;
13893 if (j + emptycount > gameInfo.boardWidth) return FALSE;
13894 while (emptycount--)
13895 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13897 } else if (isdigit(*p)) {
13898 emptycount = *p++ - '0';
13899 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
13900 if (j + emptycount > gameInfo.boardWidth) return FALSE;
13901 while (emptycount--)
13902 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13903 } else if (*p == '+' || isalpha(*p)) {
13904 if (j >= gameInfo.boardWidth) return FALSE;
13906 piece = CharToPiece(*++p);
13907 if(piece == EmptySquare) return FALSE; /* unknown piece */
13908 piece = (ChessSquare) (PROMOTED piece ); p++;
13909 if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
13910 } else piece = CharToPiece(*p++);
13912 if(piece==EmptySquare) return FALSE; /* unknown piece */
13913 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
13914 piece = (ChessSquare) (PROMOTED piece);
13915 if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
13918 board[i][(j++)+gameInfo.holdingsWidth] = piece;
13924 while (*p == '/' || *p == ' ') p++;
13926 /* [HGM] look for Crazyhouse holdings here */
13927 while(*p==' ') p++;
13928 if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
13930 if(*p == '-' ) *p++; /* empty holdings */ else {
13931 if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
13932 /* if we would allow FEN reading to set board size, we would */
13933 /* have to add holdings and shift the board read so far here */
13934 while( (piece = CharToPiece(*p) ) != EmptySquare ) {
13936 if((int) piece >= (int) BlackPawn ) {
13937 i = (int)piece - (int)BlackPawn;
13938 i = PieceToNumber((ChessSquare)i);
13939 if( i >= gameInfo.holdingsSize ) return FALSE;
13940 board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
13941 board[BOARD_HEIGHT-1-i][1]++; /* black counts */
13943 i = (int)piece - (int)WhitePawn;
13944 i = PieceToNumber((ChessSquare)i);
13945 if( i >= gameInfo.holdingsSize ) return FALSE;
13946 board[i][BOARD_WIDTH-1] = piece; /* white holdings */
13947 board[i][BOARD_WIDTH-2]++; /* black holdings */
13951 if(*p == ']') *p++;
13954 while(*p == ' ') p++;
13959 *blackPlaysFirst = FALSE;
13962 *blackPlaysFirst = TRUE;
13968 /* [HGM] We NO LONGER ignore the rest of the FEN notation */
13969 /* return the extra info in global variiables */
13971 /* set defaults in case FEN is incomplete */
13972 FENepStatus = EP_UNKNOWN;
13973 for(i=0; i<nrCastlingRights; i++ ) {
13974 FENcastlingRights[i] =
13975 gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? -1 : initialRights[i];
13976 } /* assume possible unless obviously impossible */
13977 if(initialRights[0]>=0 && board[castlingRank[0]][initialRights[0]] != WhiteRook) FENcastlingRights[0] = -1;
13978 if(initialRights[1]>=0 && board[castlingRank[1]][initialRights[1]] != WhiteRook) FENcastlingRights[1] = -1;
13979 if(initialRights[2]>=0 && board[castlingRank[2]][initialRights[2]] != WhiteKing) FENcastlingRights[2] = -1;
13980 if(initialRights[3]>=0 && board[castlingRank[3]][initialRights[3]] != BlackRook) FENcastlingRights[3] = -1;
13981 if(initialRights[4]>=0 && board[castlingRank[4]][initialRights[4]] != BlackRook) FENcastlingRights[4] = -1;
13982 if(initialRights[5]>=0 && board[castlingRank[5]][initialRights[5]] != BlackKing) FENcastlingRights[5] = -1;
13985 while(*p==' ') p++;
13986 if(nrCastlingRights) {
13987 if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
13988 /* castling indicator present, so default becomes no castlings */
13989 for(i=0; i<nrCastlingRights; i++ ) {
13990 FENcastlingRights[i] = -1;
13993 while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
13994 (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
13995 ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
13996 ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth) ) {
13997 char c = *p++; int whiteKingFile=-1, blackKingFile=-1;
13999 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
14000 if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
14001 if(board[0 ][i] == WhiteKing) whiteKingFile = i;
14005 for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
14006 FENcastlingRights[0] = i != whiteKingFile ? i : -1;
14007 FENcastlingRights[2] = whiteKingFile;
14010 for(i=BOARD_LEFT; board[0][i]!=WhiteRook && i<whiteKingFile; i++);
14011 FENcastlingRights[1] = i != whiteKingFile ? i : -1;
14012 FENcastlingRights[2] = whiteKingFile;
14015 for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
14016 FENcastlingRights[3] = i != blackKingFile ? i : -1;
14017 FENcastlingRights[5] = blackKingFile;
14020 for(i=BOARD_LEFT; board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
14021 FENcastlingRights[4] = i != blackKingFile ? i : -1;
14022 FENcastlingRights[5] = blackKingFile;
14025 default: /* FRC castlings */
14026 if(c >= 'a') { /* black rights */
14027 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
14028 if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
14029 if(i == BOARD_RGHT) break;
14030 FENcastlingRights[5] = i;
14032 if(board[BOARD_HEIGHT-1][c] < BlackPawn ||
14033 board[BOARD_HEIGHT-1][c] >= BlackKing ) break;
14035 FENcastlingRights[3] = c;
14037 FENcastlingRights[4] = c;
14038 } else { /* white rights */
14039 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
14040 if(board[0][i] == WhiteKing) break;
14041 if(i == BOARD_RGHT) break;
14042 FENcastlingRights[2] = i;
14043 c -= AAA - 'a' + 'A';
14044 if(board[0][c] >= WhiteKing) break;
14046 FENcastlingRights[0] = c;
14048 FENcastlingRights[1] = c;
14052 if (appData.debugMode) {
14053 fprintf(debugFP, "FEN castling rights:");
14054 for(i=0; i<nrCastlingRights; i++)
14055 fprintf(debugFP, " %d", FENcastlingRights[i]);
14056 fprintf(debugFP, "\n");
14059 while(*p==' ') p++;
14062 /* read e.p. field in games that know e.p. capture */
14063 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
14064 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier ) {
14066 p++; FENepStatus = EP_NONE;
14068 char c = *p++ - AAA;
14070 if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
14071 if(*p >= '0' && *p <='9') *p++;
14077 if(sscanf(p, "%d", &i) == 1) {
14078 FENrulePlies = i; /* 50-move ply counter */
14079 /* (The move number is still ignored) */
14086 EditPositionPasteFEN(char *fen)
14089 Board initial_position;
14091 if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
14092 DisplayError(_("Bad FEN position in clipboard"), 0);
14095 int savedBlackPlaysFirst = blackPlaysFirst;
14096 EditPositionEvent();
14097 blackPlaysFirst = savedBlackPlaysFirst;
14098 CopyBoard(boards[0], initial_position);
14099 /* [HGM] copy FEN attributes as well */
14101 initialRulePlies = FENrulePlies;
14102 epStatus[0] = FENepStatus;
14103 for( i=0; i<nrCastlingRights; i++ )
14104 castlingRights[0][i] = FENcastlingRights[i];
14106 EditPositionDone();
14107 DisplayBothClocks();
14108 DrawPosition(FALSE, boards[currentMove]);
14113 static char cseq[12] = "\\ ";
14115 Boolean set_cont_sequence(char *new_seq)
14120 // handle bad attempts to set the sequence
14122 return 0; // acceptable error - no debug
14124 len = strlen(new_seq);
14125 ret = (len > 0) && (len < sizeof(cseq));
14127 strcpy(cseq, new_seq);
14128 else if (appData.debugMode)
14129 fprintf(debugFP, "Invalid continuation sequence \"%s\" (maximum length is: %d)\n", new_seq, sizeof(cseq)-1);
14134 reformat a source message so words don't cross the width boundary. internal
14135 newlines are not removed. returns the wrapped size (no null character unless
14136 included in source message). If dest is NULL, only calculate the size required
14137 for the dest buffer. lp argument indicats line position upon entry, and it's
14138 passed back upon exit.
14140 int wrap(char *dest, char *src, int count, int width, int *lp)
14142 int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
14144 cseq_len = strlen(cseq);
14145 old_line = line = *lp;
14146 ansi = len = clen = 0;
14148 for (i=0; i < count; i++)
14150 if (src[i] == '\033')
14153 // if we hit the width, back up
14154 if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
14156 // store i & len in case the word is too long
14157 old_i = i, old_len = len;
14159 // find the end of the last word
14160 while (i && src[i] != ' ' && src[i] != '\n')
14166 // word too long? restore i & len before splitting it
14167 if ((old_i-i+clen) >= width)
14174 if (i && src[i-1] == ' ')
14177 if (src[i] != ' ' && src[i] != '\n')
14184 // now append the newline and continuation sequence
14189 strncpy(dest+len, cseq, cseq_len);
14197 dest[len] = src[i];
14201 if (src[i] == '\n')
14206 if (dest && appData.debugMode)
14208 fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
14209 count, width, line, len, *lp);
14210 show_bytes(debugFP, src, count);
14211 fprintf(debugFP, "\ndest: ");
14212 show_bytes(debugFP, dest, len);
14213 fprintf(debugFP, "\n");
14215 *lp = dest ? line : old_line;