2 * backend.c -- Common back end for X and Windows NT versions of
4 * Copyright 1991 by Digital Equipment Corporation, Maynard,
7 * Enhancements Copyright 1992-2001, 2002, 2003, 2004, 2005, 2006,
8 * 2007, 2008, 2009 Free Software Foundation, Inc.
10 * Enhancements Copyright 2005 Alessandro Scotti
12 * The following terms apply to Digital Equipment Corporation's copyright
14 * ------------------------------------------------------------------------
17 * Permission to use, copy, modify, and distribute this software and its
18 * documentation for any purpose and without fee is hereby granted,
19 * provided that the above copyright notice appear in all copies and that
20 * both that copyright notice and this permission notice appear in
21 * supporting documentation, and that the name of Digital not be
22 * used in advertising or publicity pertaining to distribution of the
23 * software without specific, written prior permission.
25 * DIGITAL DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
26 * ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL
27 * DIGITAL BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR
28 * ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
29 * WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
30 * ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
32 * ------------------------------------------------------------------------
34 * The following terms apply to the enhanced version of XBoard
35 * distributed by the Free Software Foundation:
36 * ------------------------------------------------------------------------
38 * GNU XBoard is free software: you can redistribute it and/or modify
39 * it under the terms of the GNU General Public License as published by
40 * the Free Software Foundation, either version 3 of the License, or (at
41 * your option) any later version.
43 * GNU XBoard is distributed in the hope that it will be useful, but
44 * WITHOUT ANY WARRANTY; without even the implied warranty of
45 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
46 * General Public License for more details.
48 * You should have received a copy of the GNU General Public License
49 * along with this program. If not, see http://www.gnu.org/licenses/. *
51 *------------------------------------------------------------------------
52 ** See the file ChangeLog for a revision history. */
54 /* [AS] Also useful here for debugging */
58 #define DoSleep( n ) if( (n) != 0 ) Sleep( (n) );
62 #define DoSleep( n ) if( (n) >= 0) sleep(n)
72 #include <sys/types.h>
81 #else /* not STDC_HEADERS */
84 # else /* not HAVE_STRING_H */
86 # endif /* not HAVE_STRING_H */
87 #endif /* not STDC_HEADERS */
90 # include <sys/fcntl.h>
91 #else /* not HAVE_SYS_FCNTL_H */
94 # endif /* HAVE_FCNTL_H */
95 #endif /* not HAVE_SYS_FCNTL_H */
97 #if TIME_WITH_SYS_TIME
98 # include <sys/time.h>
102 # include <sys/time.h>
108 #if defined(_amigados) && !defined(__GNUC__)
113 extern int gettimeofday(struct timeval *, struct timezone *);
121 #include "frontend.h"
128 #include "backendz.h"
132 # define _(s) gettext (s)
133 # define N_(s) gettext_noop (s)
140 /* A point in time */
142 long sec; /* Assuming this is >= 32 bits */
143 int ms; /* Assuming this is >= 16 bits */
146 int establish P((void));
147 void read_from_player P((InputSourceRef isr, VOIDSTAR closure,
148 char *buf, int count, int error));
149 void read_from_ics P((InputSourceRef isr, VOIDSTAR closure,
150 char *buf, int count, int error));
151 void ics_printf P((char *format, ...));
152 void SendToICS P((char *s));
153 void SendToICSDelayed P((char *s, long msdelay));
154 void SendMoveToICS P((ChessMove moveType, int fromX, int fromY,
156 void HandleMachineMove P((char *message, ChessProgramState *cps));
157 int AutoPlayOneMove P((void));
158 int LoadGameOneMove P((ChessMove readAhead));
159 int LoadGameFromFile P((char *filename, int n, char *title, int useList));
160 int LoadPositionFromFile P((char *filename, int n, char *title));
161 int SavePositionToFile P((char *filename));
162 void ApplyMove P((int fromX, int fromY, int toX, int toY, int promoChar,
163 Board board, char *castle, char *ep));
164 void MakeMove P((int fromX, int fromY, int toX, int toY, int promoChar));
165 void ShowMove P((int fromX, int fromY, int toX, int toY));
166 int FinishMove P((ChessMove moveType, int fromX, int fromY, int toX, int toY,
167 /*char*/int promoChar));
168 void BackwardInner P((int target));
169 void ForwardInner P((int target));
170 void GameEnds P((ChessMove result, char *resultDetails, int whosays));
171 void EditPositionDone P((void));
172 void PrintOpponents P((FILE *fp));
173 void PrintPosition P((FILE *fp, int move));
174 void StartChessProgram P((ChessProgramState *cps));
175 void SendToProgram P((char *message, ChessProgramState *cps));
176 void SendMoveToProgram P((int moveNum, ChessProgramState *cps));
177 void ReceiveFromProgram P((InputSourceRef isr, VOIDSTAR closure,
178 char *buf, int count, int error));
179 void SendTimeControl P((ChessProgramState *cps,
180 int mps, long tc, int inc, int sd, int st));
181 char *TimeControlTagValue P((void));
182 void Attention P((ChessProgramState *cps));
183 void FeedMovesToProgram P((ChessProgramState *cps, int upto));
184 void ResurrectChessProgram P((void));
185 void DisplayComment P((int moveNumber, char *text));
186 void DisplayMove P((int moveNumber));
188 void ParseGameHistory P((char *game));
189 void ParseBoard12 P((char *string));
190 void StartClocks P((void));
191 void SwitchClocks P((void));
192 void StopClocks P((void));
193 void ResetClocks P((void));
194 char *PGNDate P((void));
195 void SetGameInfo P((void));
196 Boolean ParseFEN P((Board board, int *blackPlaysFirst, char *fen));
197 int RegisterMove P((void));
198 void MakeRegisteredMove P((void));
199 void TruncateGame P((void));
200 int looking_at P((char *, int *, char *));
201 void CopyPlayerNameIntoFileName P((char **, char *));
202 char *SavePart P((char *));
203 int SaveGameOldStyle P((FILE *));
204 int SaveGamePGN P((FILE *));
205 void GetTimeMark P((TimeMark *));
206 long SubtractTimeMarks P((TimeMark *, TimeMark *));
207 int CheckFlags P((void));
208 long NextTickLength P((long));
209 void CheckTimeControl P((void));
210 void show_bytes P((FILE *, char *, int));
211 int string_to_rating P((char *str));
212 void ParseFeatures P((char* args, ChessProgramState *cps));
213 void InitBackEnd3 P((void));
214 void FeatureDone P((ChessProgramState* cps, int val));
215 void InitChessProgram P((ChessProgramState *cps, int setup));
216 void OutputKibitz(int window, char *text);
217 int PerpetualChase(int first, int last);
218 int EngineOutputIsUp();
219 void InitDrawingSizes(int x, int y);
222 extern void ConsoleCreate();
225 ChessProgramState *WhitePlayer();
226 void InsertIntoMemo P((int which, char *text)); // [HGM] kibitz: in engineo.c
227 int VerifyDisplayMode P(());
229 char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment
230 void InitEngineUCI( const char * iniDir, ChessProgramState * cps ); // [HGM] moved here from winboard.c
231 char *ProbeBook P((int moveNr, char *book)); // [HGM] book: returns a book move
232 char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book
233 void ics_update_width P((int new_width));
234 extern char installDir[MSG_SIZ];
236 extern int tinyLayout, smallLayout;
237 ChessProgramStats programStats;
238 static int exiting = 0; /* [HGM] moved to top */
239 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
240 int startedFromPositionFile = FALSE; Board filePosition; /* [HGM] loadPos */
241 char endingGame = 0; /* [HGM] crash: flag to prevent recursion of GameEnds() */
242 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS */
243 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
244 int lastIndex = 0; /* [HGM] autoinc: last game/position used in match mode */
245 int opponentKibitzes;
246 int lastSavedGame; /* [HGM] save: ID of game */
247 char chatPartner[MAX_CHAT][MSG_SIZ]; /* [HGM] chat: list of chatting partners */
248 extern int chatCount;
251 /* States for ics_getting_history */
253 #define H_REQUESTED 1
254 #define H_GOT_REQ_HEADER 2
255 #define H_GOT_UNREQ_HEADER 3
256 #define H_GETTING_MOVES 4
257 #define H_GOT_UNWANTED_HEADER 5
259 /* whosays values for GameEnds */
268 /* Maximum number of games in a cmail message */
269 #define CMAIL_MAX_GAMES 20
271 /* Different types of move when calling RegisterMove */
273 #define CMAIL_RESIGN 1
275 #define CMAIL_ACCEPT 3
277 /* Different types of result to remember for each game */
278 #define CMAIL_NOT_RESULT 0
279 #define CMAIL_OLD_RESULT 1
280 #define CMAIL_NEW_RESULT 2
282 /* Telnet protocol constants */
293 static char * safeStrCpy( char * dst, const char * src, size_t count )
295 assert( dst != NULL );
296 assert( src != NULL );
299 strncpy( dst, src, count );
300 dst[ count-1 ] = '\0';
304 /* Some compiler can't cast u64 to double
305 * This function do the job for us:
307 * We use the highest bit for cast, this only
308 * works if the highest bit is not
309 * in use (This should not happen)
311 * We used this for all compiler
314 u64ToDouble(u64 value)
317 u64 tmp = value & u64Const(0x7fffffffffffffff);
318 r = (double)(s64)tmp;
319 if (value & u64Const(0x8000000000000000))
320 r += 9.2233720368547758080e18; /* 2^63 */
324 /* Fake up flags for now, as we aren't keeping track of castling
325 availability yet. [HGM] Change of logic: the flag now only
326 indicates the type of castlings allowed by the rule of the game.
327 The actual rights themselves are maintained in the array
328 castlingRights, as part of the game history, and are not probed
334 int flags = F_ALL_CASTLE_OK;
335 if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
336 switch (gameInfo.variant) {
338 flags &= ~F_ALL_CASTLE_OK;
339 case VariantGiveaway: // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
340 flags |= F_IGNORE_CHECK;
342 flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
345 flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
347 case VariantKriegspiel:
348 flags |= F_KRIEGSPIEL_CAPTURE;
350 case VariantCapaRandom:
351 case VariantFischeRandom:
352 flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
353 case VariantNoCastle:
354 case VariantShatranj:
356 flags &= ~F_ALL_CASTLE_OK;
364 FILE *gameFileFP, *debugFP;
367 [AS] Note: sometimes, the sscanf() function is used to parse the input
368 into a fixed-size buffer. Because of this, we must be prepared to
369 receive strings as long as the size of the input buffer, which is currently
370 set to 4K for Windows and 8K for the rest.
371 So, we must either allocate sufficiently large buffers here, or
372 reduce the size of the input buffer in the input reading part.
375 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
376 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
377 char thinkOutput1[MSG_SIZ*10];
379 ChessProgramState first, second;
381 /* premove variables */
384 int premoveFromX = 0;
385 int premoveFromY = 0;
386 int premovePromoChar = 0;
388 Boolean alarmSounded;
389 /* end premove variables */
391 char *ics_prefix = "$";
392 int ics_type = ICS_GENERIC;
394 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
395 int pauseExamForwardMostMove = 0;
396 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
397 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
398 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
399 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
400 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
401 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
402 int whiteFlag = FALSE, blackFlag = FALSE;
403 int userOfferedDraw = FALSE;
404 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
405 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
406 int cmailMoveType[CMAIL_MAX_GAMES];
407 long ics_clock_paused = 0;
408 ProcRef icsPR = NoProc, cmailPR = NoProc;
409 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
410 GameMode gameMode = BeginningOfGame;
411 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
412 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
413 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
414 int hiddenThinkOutputState = 0; /* [AS] */
415 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
416 int adjudicateLossPlies = 6;
417 char white_holding[64], black_holding[64];
418 TimeMark lastNodeCountTime;
419 long lastNodeCount=0;
420 int have_sent_ICS_logon = 0;
422 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement;
423 long timeControl_2; /* [AS] Allow separate time controls */
424 char *fullTimeControlString = NULL; /* [HGM] secondary TC: merge of MPS, TC and inc */
425 long timeRemaining[2][MAX_MOVES];
427 TimeMark programStartTime;
428 char ics_handle[MSG_SIZ];
429 int have_set_title = 0;
431 /* animateTraining preserves the state of appData.animate
432 * when Training mode is activated. This allows the
433 * response to be animated when appData.animate == TRUE and
434 * appData.animateDragging == TRUE.
436 Boolean animateTraining;
442 Board boards[MAX_MOVES];
443 /* [HGM] Following 7 needed for accurate legality tests: */
444 signed char epStatus[MAX_MOVES];
445 signed char castlingRights[MAX_MOVES][BOARD_SIZE]; // stores files for pieces with castling rights or -1
446 signed char castlingRank[BOARD_SIZE]; // and corresponding ranks
447 signed char initialRights[BOARD_SIZE], FENcastlingRights[BOARD_SIZE], fileRights[BOARD_SIZE];
448 int nrCastlingRights; // For TwoKings, or to implement castling-unknown status
449 int initialRulePlies, FENrulePlies;
451 FILE *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
454 int mute; // mute all sounds
456 ChessSquare FIDEArray[2][BOARD_SIZE] = {
457 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
458 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
459 { BlackRook, BlackKnight, BlackBishop, BlackQueen,
460 BlackKing, BlackBishop, BlackKnight, BlackRook }
463 ChessSquare twoKingsArray[2][BOARD_SIZE] = {
464 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
465 WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
466 { BlackRook, BlackKnight, BlackBishop, BlackQueen,
467 BlackKing, BlackKing, BlackKnight, BlackRook }
470 ChessSquare KnightmateArray[2][BOARD_SIZE] = {
471 { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
472 WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
473 { BlackRook, BlackMan, BlackBishop, BlackQueen,
474 BlackUnicorn, BlackBishop, BlackMan, BlackRook }
477 ChessSquare fairyArray[2][BOARD_SIZE] = { /* [HGM] Queen side differs from King side */
478 { WhiteCannon, WhiteNightrider, WhiteAlfil, WhiteQueen,
479 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
480 { BlackCannon, BlackNightrider, BlackAlfil, BlackQueen,
481 BlackKing, BlackBishop, BlackKnight, BlackRook }
484 ChessSquare ShatranjArray[2][BOARD_SIZE] = { /* [HGM] (movGen knows about Shatranj Q and P) */
485 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
486 WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
487 { BlackRook, BlackKnight, BlackAlfil, BlackKing,
488 BlackFerz, BlackAlfil, BlackKnight, BlackRook }
493 ChessSquare ShogiArray[2][BOARD_SIZE] = {
494 { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
495 WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
496 { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
497 BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
500 ChessSquare XiangqiArray[2][BOARD_SIZE] = {
501 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
502 WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
503 { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
504 BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
507 ChessSquare CapablancaArray[2][BOARD_SIZE] = {
508 { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
509 WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
510 { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
511 BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
514 ChessSquare GreatArray[2][BOARD_SIZE] = {
515 { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
516 WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
517 { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
518 BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
521 ChessSquare JanusArray[2][BOARD_SIZE] = {
522 { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
523 WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
524 { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
525 BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
529 ChessSquare GothicArray[2][BOARD_SIZE] = {
530 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
531 WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
532 { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
533 BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
536 #define GothicArray CapablancaArray
540 ChessSquare FalconArray[2][BOARD_SIZE] = {
541 { WhiteRook, WhiteKnight, WhiteBishop, WhiteLance, WhiteQueen,
542 WhiteKing, WhiteLance, WhiteBishop, WhiteKnight, WhiteRook },
543 { BlackRook, BlackKnight, BlackBishop, BlackLance, BlackQueen,
544 BlackKing, BlackLance, BlackBishop, BlackKnight, BlackRook }
547 #define FalconArray CapablancaArray
550 #else // !(BOARD_SIZE>=10)
551 #define XiangqiPosition FIDEArray
552 #define CapablancaArray FIDEArray
553 #define GothicArray FIDEArray
554 #define GreatArray FIDEArray
555 #endif // !(BOARD_SIZE>=10)
558 ChessSquare CourierArray[2][BOARD_SIZE] = {
559 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
560 WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
561 { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
562 BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
564 #else // !(BOARD_SIZE>=12)
565 #define CourierArray CapablancaArray
566 #endif // !(BOARD_SIZE>=12)
569 Board initialPosition;
572 /* Convert str to a rating. Checks for special cases of "----",
574 "++++", etc. Also strips ()'s */
576 string_to_rating(str)
579 while(*str && !isdigit(*str)) ++str;
581 return 0; /* One of the special "no rating" cases */
589 /* Init programStats */
590 programStats.movelist[0] = 0;
591 programStats.depth = 0;
592 programStats.nr_moves = 0;
593 programStats.moves_left = 0;
594 programStats.nodes = 0;
595 programStats.time = -1; // [HGM] PGNtime: make invalid to recognize engine output
596 programStats.score = 0;
597 programStats.got_only_move = 0;
598 programStats.got_fail = 0;
599 programStats.line_is_book = 0;
605 int matched, min, sec;
607 ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
609 GetTimeMark(&programStartTime);
610 srand(programStartTime.ms); // [HGM] book: makes sure random is unpredictabe to msec level
613 programStats.ok_to_send = 1;
614 programStats.seen_stat = 0;
617 * Initialize game list
623 * Internet chess server status
625 if (appData.icsActive) {
626 appData.matchMode = FALSE;
627 appData.matchGames = 0;
629 appData.noChessProgram = !appData.zippyPlay;
631 appData.zippyPlay = FALSE;
632 appData.zippyTalk = FALSE;
633 appData.noChessProgram = TRUE;
635 if (*appData.icsHelper != NULLCHAR) {
636 appData.useTelnet = TRUE;
637 appData.telnetProgram = appData.icsHelper;
640 appData.zippyTalk = appData.zippyPlay = FALSE;
643 /* [AS] Initialize pv info list [HGM] and game state */
647 for( i=0; i<MAX_MOVES; i++ ) {
648 pvInfoList[i].depth = -1;
650 for( j=0; j<BOARD_SIZE; j++ ) castlingRights[i][j] = -1;
655 * Parse timeControl resource
657 if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
658 appData.movesPerSession)) {
660 snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
661 DisplayFatalError(buf, 0, 2);
665 * Parse searchTime resource
667 if (*appData.searchTime != NULLCHAR) {
668 matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
670 searchTime = min * 60;
671 } else if (matched == 2) {
672 searchTime = min * 60 + sec;
675 snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
676 DisplayFatalError(buf, 0, 2);
680 /* [AS] Adjudication threshold */
681 adjudicateLossThreshold = appData.adjudicateLossThreshold;
683 first.which = "first";
684 second.which = "second";
685 first.maybeThinking = second.maybeThinking = FALSE;
686 first.pr = second.pr = NoProc;
687 first.isr = second.isr = NULL;
688 first.sendTime = second.sendTime = 2;
689 first.sendDrawOffers = 1;
690 if (appData.firstPlaysBlack) {
691 first.twoMachinesColor = "black\n";
692 second.twoMachinesColor = "white\n";
694 first.twoMachinesColor = "white\n";
695 second.twoMachinesColor = "black\n";
697 first.program = appData.firstChessProgram;
698 second.program = appData.secondChessProgram;
699 first.host = appData.firstHost;
700 second.host = appData.secondHost;
701 first.dir = appData.firstDirectory;
702 second.dir = appData.secondDirectory;
703 first.other = &second;
704 second.other = &first;
705 first.initString = appData.initString;
706 second.initString = appData.secondInitString;
707 first.computerString = appData.firstComputerString;
708 second.computerString = appData.secondComputerString;
709 first.useSigint = second.useSigint = TRUE;
710 first.useSigterm = second.useSigterm = TRUE;
711 first.reuse = appData.reuseFirst;
712 second.reuse = appData.reuseSecond;
713 first.nps = appData.firstNPS; // [HGM] nps: copy nodes per second
714 second.nps = appData.secondNPS;
715 first.useSetboard = second.useSetboard = FALSE;
716 first.useSAN = second.useSAN = FALSE;
717 first.usePing = second.usePing = FALSE;
718 first.lastPing = second.lastPing = 0;
719 first.lastPong = second.lastPong = 0;
720 first.usePlayother = second.usePlayother = FALSE;
721 first.useColors = second.useColors = TRUE;
722 first.useUsermove = second.useUsermove = FALSE;
723 first.sendICS = second.sendICS = FALSE;
724 first.sendName = second.sendName = appData.icsActive;
725 first.sdKludge = second.sdKludge = FALSE;
726 first.stKludge = second.stKludge = FALSE;
727 TidyProgramName(first.program, first.host, first.tidy);
728 TidyProgramName(second.program, second.host, second.tidy);
729 first.matchWins = second.matchWins = 0;
730 strcpy(first.variants, appData.variant);
731 strcpy(second.variants, appData.variant);
732 first.analysisSupport = second.analysisSupport = 2; /* detect */
733 first.analyzing = second.analyzing = FALSE;
734 first.initDone = second.initDone = FALSE;
736 /* New features added by Tord: */
737 first.useFEN960 = FALSE; second.useFEN960 = FALSE;
738 first.useOOCastle = TRUE; second.useOOCastle = TRUE;
739 /* End of new features added by Tord. */
740 first.fenOverride = appData.fenOverride1;
741 second.fenOverride = appData.fenOverride2;
743 /* [HGM] time odds: set factor for each machine */
744 first.timeOdds = appData.firstTimeOdds;
745 second.timeOdds = appData.secondTimeOdds;
747 if(appData.timeOddsMode) {
748 norm = first.timeOdds;
749 if(norm > second.timeOdds) norm = second.timeOdds;
751 first.timeOdds /= norm;
752 second.timeOdds /= norm;
755 /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
756 first.accumulateTC = appData.firstAccumulateTC;
757 second.accumulateTC = appData.secondAccumulateTC;
758 first.maxNrOfSessions = second.maxNrOfSessions = 1;
761 first.debug = second.debug = FALSE;
762 first.supportsNPS = second.supportsNPS = UNKNOWN;
765 first.optionSettings = appData.firstOptions;
766 second.optionSettings = appData.secondOptions;
768 first.scoreIsAbsolute = appData.firstScoreIsAbsolute; /* [AS] */
769 second.scoreIsAbsolute = appData.secondScoreIsAbsolute; /* [AS] */
770 first.isUCI = appData.firstIsUCI; /* [AS] */
771 second.isUCI = appData.secondIsUCI; /* [AS] */
772 first.hasOwnBookUCI = appData.firstHasOwnBookUCI; /* [AS] */
773 second.hasOwnBookUCI = appData.secondHasOwnBookUCI; /* [AS] */
775 if (appData.firstProtocolVersion > PROTOVER ||
776 appData.firstProtocolVersion < 1) {
778 sprintf(buf, _("protocol version %d not supported"),
779 appData.firstProtocolVersion);
780 DisplayFatalError(buf, 0, 2);
782 first.protocolVersion = appData.firstProtocolVersion;
785 if (appData.secondProtocolVersion > PROTOVER ||
786 appData.secondProtocolVersion < 1) {
788 sprintf(buf, _("protocol version %d not supported"),
789 appData.secondProtocolVersion);
790 DisplayFatalError(buf, 0, 2);
792 second.protocolVersion = appData.secondProtocolVersion;
795 if (appData.icsActive) {
796 appData.clockMode = TRUE; /* changes dynamically in ICS mode */
797 } else if (*appData.searchTime != NULLCHAR || appData.noChessProgram) {
798 appData.clockMode = FALSE;
799 first.sendTime = second.sendTime = 0;
803 /* Override some settings from environment variables, for backward
804 compatibility. Unfortunately it's not feasible to have the env
805 vars just set defaults, at least in xboard. Ugh.
807 if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
812 if (appData.noChessProgram) {
813 programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
814 sprintf(programVersion, "%s", PACKAGE_STRING);
816 /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
817 programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
818 sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
821 if (!appData.icsActive) {
823 /* Check for variants that are supported only in ICS mode,
824 or not at all. Some that are accepted here nevertheless
825 have bugs; see comments below.
827 VariantClass variant = StringToVariant(appData.variant);
829 case VariantBughouse: /* need four players and two boards */
830 case VariantKriegspiel: /* need to hide pieces and move details */
831 /* case VariantFischeRandom: (Fabien: moved below) */
832 sprintf(buf, _("Variant %s supported only in ICS mode"), appData.variant);
833 DisplayFatalError(buf, 0, 2);
837 case VariantLoadable:
847 sprintf(buf, _("Unknown variant name %s"), appData.variant);
848 DisplayFatalError(buf, 0, 2);
851 case VariantXiangqi: /* [HGM] repetition rules not implemented */
852 case VariantFairy: /* [HGM] TestLegality definitely off! */
853 case VariantGothic: /* [HGM] should work */
854 case VariantCapablanca: /* [HGM] should work */
855 case VariantCourier: /* [HGM] initial forced moves not implemented */
856 case VariantShogi: /* [HGM] drops not tested for legality */
857 case VariantKnightmate: /* [HGM] should work */
858 case VariantCylinder: /* [HGM] untested */
859 case VariantFalcon: /* [HGM] untested */
860 case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
861 offboard interposition not understood */
862 case VariantNormal: /* definitely works! */
863 case VariantWildCastle: /* pieces not automatically shuffled */
864 case VariantNoCastle: /* pieces not automatically shuffled */
865 case VariantFischeRandom: /* [HGM] works and shuffles pieces */
866 case VariantLosers: /* should work except for win condition,
867 and doesn't know captures are mandatory */
868 case VariantSuicide: /* should work except for win condition,
869 and doesn't know captures are mandatory */
870 case VariantGiveaway: /* should work except for win condition,
871 and doesn't know captures are mandatory */
872 case VariantTwoKings: /* should work */
873 case VariantAtomic: /* should work except for win condition */
874 case Variant3Check: /* should work except for win condition */
875 case VariantShatranj: /* should work except for all win conditions */
876 case VariantBerolina: /* might work if TestLegality is off */
877 case VariantCapaRandom: /* should work */
878 case VariantJanus: /* should work */
879 case VariantSuper: /* experimental */
880 case VariantGreat: /* experimental, requires legality testing to be off */
885 InitEngineUCI( installDir, &first ); // [HGM] moved here from winboard.c, to make available in xboard
886 InitEngineUCI( installDir, &second );
889 int NextIntegerFromString( char ** str, long * value )
894 while( *s == ' ' || *s == '\t' ) {
900 if( *s >= '0' && *s <= '9' ) {
901 while( *s >= '0' && *s <= '9' ) {
902 *value = *value * 10 + (*s - '0');
914 int NextTimeControlFromString( char ** str, long * value )
917 int result = NextIntegerFromString( str, &temp );
920 *value = temp * 60; /* Minutes */
923 result = NextIntegerFromString( str, &temp );
924 *value += temp; /* Seconds */
931 int NextSessionFromString( char ** str, int *moves, long * tc, long *inc)
932 { /* [HGM] routine added to read '+moves/time' for secondary time control */
933 int result = -1; long temp, temp2;
935 if(**str != '+') return -1; // old params remain in force!
937 if( NextTimeControlFromString( str, &temp ) ) return -1;
940 /* time only: incremental or sudden-death time control */
941 if(**str == '+') { /* increment follows; read it */
943 if(result = NextIntegerFromString( str, &temp2)) return -1;
946 *moves = 0; *tc = temp * 1000;
948 } else if(temp % 60 != 0) return -1; /* moves was given as min:sec */
950 (*str)++; /* classical time control */
951 result = NextTimeControlFromString( str, &temp2);
960 int GetTimeQuota(int movenr)
961 { /* [HGM] get time to add from the multi-session time-control string */
962 int moves=1; /* kludge to force reading of first session */
963 long time, increment;
964 char *s = fullTimeControlString;
966 if(appData.debugMode) fprintf(debugFP, "TC string = '%s'\n", fullTimeControlString);
968 if(moves) NextSessionFromString(&s, &moves, &time, &increment);
969 if(appData.debugMode) fprintf(debugFP, "mps=%d tc=%d inc=%d\n", moves, (int) time, (int) increment);
970 if(movenr == -1) return time; /* last move before new session */
971 if(!moves) return increment; /* current session is incremental */
972 if(movenr >= 0) movenr -= moves; /* we already finished this session */
973 } while(movenr >= -1); /* try again for next session */
975 return 0; // no new time quota on this move
979 ParseTimeControl(tc, ti, mps)
988 if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
991 sprintf(buf, "+%d/%s+%d", mps, tc, ti);
992 else sprintf(buf, "+%s+%d", tc, ti);
995 sprintf(buf, "+%d/%s", mps, tc);
996 else sprintf(buf, "+%s", tc);
998 fullTimeControlString = StrSave(buf);
1000 if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1005 /* Parse second time control */
1008 if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1016 timeControl_2 = tc2 * 1000;
1026 timeControl = tc1 * 1000;
1029 timeIncrement = ti * 1000; /* convert to ms */
1030 movesPerSession = 0;
1033 movesPerSession = mps;
1041 if (appData.debugMode) {
1042 fprintf(debugFP, "%s\n", programVersion);
1045 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;
1908 if( (int)lowestPiece >= BlackPawn ) {
1911 holdingsStartRow = BOARD_HEIGHT-1;
1914 holdingsColumn = BOARD_WIDTH-1;
1915 countsColumn = BOARD_WIDTH-2;
1916 holdingsStartRow = 0;
1920 for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
1921 board[i][holdingsColumn] = EmptySquare;
1922 board[i][countsColumn] = (ChessSquare) 0;
1924 while( (p=*holdings++) != NULLCHAR ) {
1925 piece = CharToPiece( ToUpper(p) );
1926 if(piece == EmptySquare) continue;
1927 /*j = (int) piece - (int) WhitePawn;*/
1928 j = PieceToNumber(piece);
1929 if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
1930 if(j < 0) continue; /* should not happen */
1931 piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
1932 board[holdingsStartRow+j*direction][holdingsColumn] = piece;
1933 board[holdingsStartRow+j*direction][countsColumn]++;
1940 VariantSwitch(Board board, VariantClass newVariant)
1942 int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
1944 startedFromPositionFile = FALSE;
1945 if(gameInfo.variant == newVariant) return;
1947 /* [HGM] This routine is called each time an assignment is made to
1948 * gameInfo.variant during a game, to make sure the board sizes
1949 * are set to match the new variant. If that means adding or deleting
1950 * holdings, we shift the playing board accordingly
1951 * This kludge is needed because in ICS observe mode, we get boards
1952 * of an ongoing game without knowing the variant, and learn about the
1953 * latter only later. This can be because of the move list we requested,
1954 * in which case the game history is refilled from the beginning anyway,
1955 * but also when receiving holdings of a crazyhouse game. In the latter
1956 * 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); }
2017 DrawPosition(TRUE, boards[currentMove]);
2020 static int loggedOn = FALSE;
2022 /*-- Game start info cache: --*/
2024 char gs_kind[MSG_SIZ];
2025 static char player1Name[128] = "";
2026 static char player2Name[128] = "";
2027 static char cont_seq[] = "\n\\ ";
2028 static int player1Rating = -1;
2029 static int player2Rating = -1;
2030 /*----------------------------*/
2032 ColorClass curColor = ColorNormal;
2033 int suppressKibitz = 0;
2036 read_from_ics(isr, closure, data, count, error)
2043 #define BUF_SIZE 8192
2044 #define STARTED_NONE 0
2045 #define STARTED_MOVES 1
2046 #define STARTED_BOARD 2
2047 #define STARTED_OBSERVE 3
2048 #define STARTED_HOLDINGS 4
2049 #define STARTED_CHATTER 5
2050 #define STARTED_COMMENT 6
2051 #define STARTED_MOVES_NOHIDE 7
2053 static int started = STARTED_NONE;
2054 static char parse[20000];
2055 static int parse_pos = 0;
2056 static char buf[BUF_SIZE + 1];
2057 static int firstTime = TRUE, intfSet = FALSE;
2058 static ColorClass prevColor = ColorNormal;
2059 static int savingComment = FALSE;
2060 static int cmatch = 0; // continuation sequence match
2067 int backup; /* [DM] For zippy color lines */
2069 char talker[MSG_SIZ]; // [HGM] chat
2072 if (appData.debugMode) {
2074 fprintf(debugFP, "<ICS: ");
2075 show_bytes(debugFP, data, count);
2076 fprintf(debugFP, "\n");
2080 if (appData.debugMode) { int f = forwardMostMove;
2081 fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2082 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
2085 /* If last read ended with a partial line that we couldn't parse,
2086 prepend it to the new read and try again. */
2087 if (leftover_len > 0) {
2088 for (i=0; i<leftover_len; i++)
2089 buf[i] = buf[leftover_start + i];
2092 /* copy new characters into the buffer */
2093 bp = buf + leftover_len;
2094 buf_len=leftover_len;
2095 for (i=0; i<count; i++)
2098 if (data[i] == '\r')
2101 // join lines split by ICS?
2102 if (!appData.noJoin)
2105 Joining just consists of finding matches against the
2106 continuation sequence, and discarding that sequence
2107 if found instead of copying it. So, until a match
2108 fails, there's nothing to do since it might be the
2109 complete sequence, and thus, something we don't want
2112 if (data[i] == cont_seq[cmatch])
2115 if (cmatch == strlen(cont_seq))
2117 cmatch = 0; // complete match. just reset the counter
2120 it's possible for the ICS to not include the space
2121 at the end of the last word, making our [correct]
2122 join operation fuse two separate words. the server
2123 does this when the space occurs at the width setting.
2125 if (!buf_len || buf[buf_len-1] != ' ')
2136 match failed, so we have to copy what matched before
2137 falling through and copying this character. In reality,
2138 this will only ever be just the newline character, but
2139 it doesn't hurt to be precise.
2141 strncpy(bp, cont_seq, cmatch);
2153 buf[buf_len] = NULLCHAR;
2154 next_out = leftover_len;
2158 while (i < buf_len) {
2159 /* Deal with part of the TELNET option negotiation
2160 protocol. We refuse to do anything beyond the
2161 defaults, except that we allow the WILL ECHO option,
2162 which ICS uses to turn off password echoing when we are
2163 directly connected to it. We reject this option
2164 if localLineEditing mode is on (always on in xboard)
2165 and we are talking to port 23, which might be a real
2166 telnet server that will try to keep WILL ECHO on permanently.
2168 if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2169 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2170 unsigned char option;
2172 switch ((unsigned char) buf[++i]) {
2174 if (appData.debugMode)
2175 fprintf(debugFP, "\n<WILL ");
2176 switch (option = (unsigned char) buf[++i]) {
2178 if (appData.debugMode)
2179 fprintf(debugFP, "ECHO ");
2180 /* Reply only if this is a change, according
2181 to the protocol rules. */
2182 if (remoteEchoOption) break;
2183 if (appData.localLineEditing &&
2184 atoi(appData.icsPort) == TN_PORT) {
2185 TelnetRequest(TN_DONT, TN_ECHO);
2188 TelnetRequest(TN_DO, TN_ECHO);
2189 remoteEchoOption = TRUE;
2193 if (appData.debugMode)
2194 fprintf(debugFP, "%d ", option);
2195 /* Whatever this is, we don't want it. */
2196 TelnetRequest(TN_DONT, option);
2201 if (appData.debugMode)
2202 fprintf(debugFP, "\n<WONT ");
2203 switch (option = (unsigned char) buf[++i]) {
2205 if (appData.debugMode)
2206 fprintf(debugFP, "ECHO ");
2207 /* Reply only if this is a change, according
2208 to the protocol rules. */
2209 if (!remoteEchoOption) break;
2211 TelnetRequest(TN_DONT, TN_ECHO);
2212 remoteEchoOption = FALSE;
2215 if (appData.debugMode)
2216 fprintf(debugFP, "%d ", (unsigned char) option);
2217 /* Whatever this is, it must already be turned
2218 off, because we never agree to turn on
2219 anything non-default, so according to the
2220 protocol rules, we don't reply. */
2225 if (appData.debugMode)
2226 fprintf(debugFP, "\n<DO ");
2227 switch (option = (unsigned char) buf[++i]) {
2229 /* Whatever this is, we refuse to do it. */
2230 if (appData.debugMode)
2231 fprintf(debugFP, "%d ", option);
2232 TelnetRequest(TN_WONT, option);
2237 if (appData.debugMode)
2238 fprintf(debugFP, "\n<DONT ");
2239 switch (option = (unsigned char) buf[++i]) {
2241 if (appData.debugMode)
2242 fprintf(debugFP, "%d ", option);
2243 /* Whatever this is, we are already not doing
2244 it, because we never agree to do anything
2245 non-default, so according to the protocol
2246 rules, we don't reply. */
2251 if (appData.debugMode)
2252 fprintf(debugFP, "\n<IAC ");
2253 /* Doubled IAC; pass it through */
2257 if (appData.debugMode)
2258 fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2259 /* Drop all other telnet commands on the floor */
2262 if (oldi > next_out)
2263 SendToPlayer(&buf[next_out], oldi - next_out);
2269 /* OK, this at least will *usually* work */
2270 if (!loggedOn && looking_at(buf, &i, "ics%")) {
2274 if (loggedOn && !intfSet) {
2275 if (ics_type == ICS_ICC) {
2277 "/set-quietly interface %s\n/set-quietly style 12\n",
2279 } else if (ics_type == ICS_CHESSNET) {
2280 sprintf(str, "/style 12\n");
2282 strcpy(str, "alias $ @\n$set interface ");
2283 strcat(str, programVersion);
2284 strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2286 strcat(str, "$iset nohighlight 1\n");
2288 strcat(str, "$iset lock 1\n$style 12\n");
2291 NotifyFrontendLogin();
2295 if (started == STARTED_COMMENT) {
2296 /* Accumulate characters in comment */
2297 parse[parse_pos++] = buf[i];
2298 if (buf[i] == '\n') {
2299 parse[parse_pos] = NULLCHAR;
2300 if(chattingPartner>=0) {
2302 sprintf(mess, "%s%s", talker, parse);
2303 OutputChatMessage(chattingPartner, mess);
2304 chattingPartner = -1;
2306 if(!suppressKibitz) // [HGM] kibitz
2307 AppendComment(forwardMostMove, StripHighlight(parse));
2308 else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2309 int nrDigit = 0, nrAlph = 0, i;
2310 if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2311 { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2312 parse[parse_pos] = NULLCHAR;
2313 // try to be smart: if it does not look like search info, it should go to
2314 // ICS interaction window after all, not to engine-output window.
2315 for(i=0; i<parse_pos; i++) { // count letters and digits
2316 nrDigit += (parse[i] >= '0' && parse[i] <= '9');
2317 nrAlph += (parse[i] >= 'a' && parse[i] <= 'z');
2318 nrAlph += (parse[i] >= 'A' && parse[i] <= 'Z');
2320 if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2321 int depth=0; float score;
2322 if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2323 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2324 pvInfoList[forwardMostMove-1].depth = depth;
2325 pvInfoList[forwardMostMove-1].score = 100*score;
2327 OutputKibitz(suppressKibitz, parse);
2330 sprintf(tmp, _("your opponent kibitzes: %s"), parse);
2331 SendToPlayer(tmp, strlen(tmp));
2334 started = STARTED_NONE;
2336 /* Don't match patterns against characters in chatter */
2341 if (started == STARTED_CHATTER) {
2342 if (buf[i] != '\n') {
2343 /* Don't match patterns against characters in chatter */
2347 started = STARTED_NONE;
2350 /* Kludge to deal with rcmd protocol */
2351 if (firstTime && looking_at(buf, &i, "\001*")) {
2352 DisplayFatalError(&buf[1], 0, 1);
2358 if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2361 if (appData.debugMode)
2362 fprintf(debugFP, "ics_type %d\n", ics_type);
2365 if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
2366 ics_type = ICS_FICS;
2368 if (appData.debugMode)
2369 fprintf(debugFP, "ics_type %d\n", ics_type);
2372 if (!loggedOn && looking_at(buf, &i, "chess.net")) {
2373 ics_type = ICS_CHESSNET;
2375 if (appData.debugMode)
2376 fprintf(debugFP, "ics_type %d\n", ics_type);
2381 (looking_at(buf, &i, "\"*\" is *a registered name") ||
2382 looking_at(buf, &i, "Logging you in as \"*\"") ||
2383 looking_at(buf, &i, "will be \"*\""))) {
2384 strcpy(ics_handle, star_match[0]);
2388 if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
2390 snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
2391 DisplayIcsInteractionTitle(buf);
2392 have_set_title = TRUE;
2395 /* skip finger notes */
2396 if (started == STARTED_NONE &&
2397 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
2398 (buf[i] == '1' && buf[i+1] == '0')) &&
2399 buf[i+2] == ':' && buf[i+3] == ' ') {
2400 started = STARTED_CHATTER;
2405 /* skip formula vars */
2406 if (started == STARTED_NONE &&
2407 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
2408 started = STARTED_CHATTER;
2414 // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
2415 if (appData.autoKibitz && started == STARTED_NONE &&
2416 !appData.icsEngineAnalyze && // [HGM] [DM] ICS analyze
2417 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
2418 if(looking_at(buf, &i, "* kibitzes: ") &&
2419 (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
2420 StrStr(star_match[0], gameInfo.black) == star_match[0] )) { // kibitz of self or opponent
2421 suppressKibitz = TRUE;
2422 if((StrStr(star_match[0], gameInfo.white) == star_match[0]
2423 && (gameMode == IcsPlayingWhite)) ||
2424 (StrStr(star_match[0], gameInfo.black) == star_match[0]
2425 && (gameMode == IcsPlayingBlack)) ) // opponent kibitz
2426 started = STARTED_CHATTER; // own kibitz we simply discard
2428 started = STARTED_COMMENT; // make sure it will be collected in parse[]
2429 parse_pos = 0; parse[0] = NULLCHAR;
2430 savingComment = TRUE;
2431 suppressKibitz = gameMode != IcsObserving ? 2 :
2432 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
2436 if(looking_at(buf, &i, "kibitzed to")) { // suppress the acknowledgements of our own autoKibitz
2437 started = STARTED_CHATTER;
2438 suppressKibitz = TRUE;
2440 } // [HGM] kibitz: end of patch
2442 //if(appData.debugMode) fprintf(debugFP, "hunt for tell, buf = %s\n", buf+i);
2444 // [HGM] chat: intercept tells by users for which we have an open chat window
2446 if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
2447 looking_at(buf, &i, "* whispers:") ||
2448 looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
2449 looking_at(buf, &i, "*(*)(*):") && sscanf(star_match[2], "%d", &channel) == 1 )) {
2451 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
2452 chattingPartner = -1;
2454 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
2455 for(p=0; p<MAX_CHAT; p++) {
2456 if(channel == atoi(chatPartner[p])) {
2457 talker[0] = '['; strcat(talker, "]");
2458 chattingPartner = p; break;
2461 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
2462 for(p=0; p<MAX_CHAT; p++) {
2463 if(!strcmp("WHISPER", chatPartner[p])) {
2464 talker[0] = '['; strcat(talker, "]");
2465 chattingPartner = p; break;
2468 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
2469 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
2471 chattingPartner = p; break;
2473 if(chattingPartner<0) i = oldi; else {
2474 started = STARTED_COMMENT;
2475 parse_pos = 0; parse[0] = NULLCHAR;
2476 savingComment = TRUE;
2477 suppressKibitz = TRUE;
2479 } // [HGM] chat: end of patch
2481 if (appData.zippyTalk || appData.zippyPlay) {
2482 /* [DM] Backup address for color zippy lines */
2486 if (loggedOn == TRUE)
2487 if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
2488 (appData.zippyPlay && ZippyMatch(buf, &backup)));
2490 if (ZippyControl(buf, &i) ||
2491 ZippyConverse(buf, &i) ||
2492 (appData.zippyPlay && ZippyMatch(buf, &i))) {
2494 if (!appData.colorize) continue;
2498 } // [DM] 'else { ' deleted
2500 /* Regular tells and says */
2501 (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
2502 looking_at(buf, &i, "* (your partner) tells you: ") ||
2503 looking_at(buf, &i, "* says: ") ||
2504 /* Don't color "message" or "messages" output */
2505 (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
2506 looking_at(buf, &i, "*. * at *:*: ") ||
2507 looking_at(buf, &i, "--* (*:*): ") ||
2508 /* Message notifications (same color as tells) */
2509 looking_at(buf, &i, "* has left a message ") ||
2510 looking_at(buf, &i, "* just sent you a message:\n") ||
2511 /* Whispers and kibitzes */
2512 (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
2513 looking_at(buf, &i, "* kibitzes: ") ||
2515 (tkind = 3, looking_at(buf, &i, "*(*: "))) {
2517 if (tkind == 1 && strchr(star_match[0], ':')) {
2518 /* Avoid "tells you:" spoofs in channels */
2521 if (star_match[0][0] == NULLCHAR ||
2522 strchr(star_match[0], ' ') ||
2523 (tkind == 3 && strchr(star_match[1], ' '))) {
2524 /* Reject bogus matches */
2527 if (appData.colorize) {
2528 if (oldi > next_out) {
2529 SendToPlayer(&buf[next_out], oldi - next_out);
2534 Colorize(ColorTell, FALSE);
2535 curColor = ColorTell;
2538 Colorize(ColorKibitz, FALSE);
2539 curColor = ColorKibitz;
2542 p = strrchr(star_match[1], '(');
2549 Colorize(ColorChannel1, FALSE);
2550 curColor = ColorChannel1;
2552 Colorize(ColorChannel, FALSE);
2553 curColor = ColorChannel;
2557 curColor = ColorNormal;
2561 if (started == STARTED_NONE && appData.autoComment &&
2562 (gameMode == IcsObserving ||
2563 gameMode == IcsPlayingWhite ||
2564 gameMode == IcsPlayingBlack)) {
2565 parse_pos = i - oldi;
2566 memcpy(parse, &buf[oldi], parse_pos);
2567 parse[parse_pos] = NULLCHAR;
2568 started = STARTED_COMMENT;
2569 savingComment = TRUE;
2571 started = STARTED_CHATTER;
2572 savingComment = FALSE;
2579 if (looking_at(buf, &i, "* s-shouts: ") ||
2580 looking_at(buf, &i, "* c-shouts: ")) {
2581 if (appData.colorize) {
2582 if (oldi > next_out) {
2583 SendToPlayer(&buf[next_out], oldi - next_out);
2586 Colorize(ColorSShout, FALSE);
2587 curColor = ColorSShout;
2590 started = STARTED_CHATTER;
2594 if (looking_at(buf, &i, "--->")) {
2599 if (looking_at(buf, &i, "* shouts: ") ||
2600 looking_at(buf, &i, "--> ")) {
2601 if (appData.colorize) {
2602 if (oldi > next_out) {
2603 SendToPlayer(&buf[next_out], oldi - next_out);
2606 Colorize(ColorShout, FALSE);
2607 curColor = ColorShout;
2610 started = STARTED_CHATTER;
2614 if (looking_at( buf, &i, "Challenge:")) {
2615 if (appData.colorize) {
2616 if (oldi > next_out) {
2617 SendToPlayer(&buf[next_out], oldi - next_out);
2620 Colorize(ColorChallenge, FALSE);
2621 curColor = ColorChallenge;
2627 if (looking_at(buf, &i, "* offers you") ||
2628 looking_at(buf, &i, "* offers to be") ||
2629 looking_at(buf, &i, "* would like to") ||
2630 looking_at(buf, &i, "* requests to") ||
2631 looking_at(buf, &i, "Your opponent offers") ||
2632 looking_at(buf, &i, "Your opponent requests")) {
2634 if (appData.colorize) {
2635 if (oldi > next_out) {
2636 SendToPlayer(&buf[next_out], oldi - next_out);
2639 Colorize(ColorRequest, FALSE);
2640 curColor = ColorRequest;
2645 if (looking_at(buf, &i, "* (*) seeking")) {
2646 if (appData.colorize) {
2647 if (oldi > next_out) {
2648 SendToPlayer(&buf[next_out], oldi - next_out);
2651 Colorize(ColorSeek, FALSE);
2652 curColor = ColorSeek;
2657 if (looking_at(buf, &i, "\\ ")) {
2658 if (prevColor != ColorNormal) {
2659 if (oldi > next_out) {
2660 SendToPlayer(&buf[next_out], oldi - next_out);
2663 Colorize(prevColor, TRUE);
2664 curColor = prevColor;
2666 if (savingComment) {
2667 parse_pos = i - oldi;
2668 memcpy(parse, &buf[oldi], parse_pos);
2669 parse[parse_pos] = NULLCHAR;
2670 started = STARTED_COMMENT;
2672 started = STARTED_CHATTER;
2677 if (looking_at(buf, &i, "Black Strength :") ||
2678 looking_at(buf, &i, "<<< style 10 board >>>") ||
2679 looking_at(buf, &i, "<10>") ||
2680 looking_at(buf, &i, "#@#")) {
2681 /* Wrong board style */
2683 SendToICS(ics_prefix);
2684 SendToICS("set style 12\n");
2685 SendToICS(ics_prefix);
2686 SendToICS("refresh\n");
2690 if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
2692 have_sent_ICS_logon = 1;
2696 if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
2697 (looking_at(buf, &i, "\n<12> ") ||
2698 looking_at(buf, &i, "<12> "))) {
2700 if (oldi > next_out) {
2701 SendToPlayer(&buf[next_out], oldi - next_out);
2704 started = STARTED_BOARD;
2709 if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
2710 looking_at(buf, &i, "<b1> ")) {
2711 if (oldi > next_out) {
2712 SendToPlayer(&buf[next_out], oldi - next_out);
2715 started = STARTED_HOLDINGS;
2720 if (looking_at(buf, &i, "* *vs. * *--- *")) {
2722 /* Header for a move list -- first line */
2724 switch (ics_getting_history) {
2728 case BeginningOfGame:
2729 /* User typed "moves" or "oldmoves" while we
2730 were idle. Pretend we asked for these
2731 moves and soak them up so user can step
2732 through them and/or save them.
2735 gameMode = IcsObserving;
2738 ics_getting_history = H_GOT_UNREQ_HEADER;
2740 case EditGame: /*?*/
2741 case EditPosition: /*?*/
2742 /* Should above feature work in these modes too? */
2743 /* For now it doesn't */
2744 ics_getting_history = H_GOT_UNWANTED_HEADER;
2747 ics_getting_history = H_GOT_UNWANTED_HEADER;
2752 /* Is this the right one? */
2753 if (gameInfo.white && gameInfo.black &&
2754 strcmp(gameInfo.white, star_match[0]) == 0 &&
2755 strcmp(gameInfo.black, star_match[2]) == 0) {
2757 ics_getting_history = H_GOT_REQ_HEADER;
2760 case H_GOT_REQ_HEADER:
2761 case H_GOT_UNREQ_HEADER:
2762 case H_GOT_UNWANTED_HEADER:
2763 case H_GETTING_MOVES:
2764 /* Should not happen */
2765 DisplayError(_("Error gathering move list: two headers"), 0);
2766 ics_getting_history = H_FALSE;
2770 /* Save player ratings into gameInfo if needed */
2771 if ((ics_getting_history == H_GOT_REQ_HEADER ||
2772 ics_getting_history == H_GOT_UNREQ_HEADER) &&
2773 (gameInfo.whiteRating == -1 ||
2774 gameInfo.blackRating == -1)) {
2776 gameInfo.whiteRating = string_to_rating(star_match[1]);
2777 gameInfo.blackRating = string_to_rating(star_match[3]);
2778 if (appData.debugMode)
2779 fprintf(debugFP, _("Ratings from header: W %d, B %d\n"),
2780 gameInfo.whiteRating, gameInfo.blackRating);
2785 if (looking_at(buf, &i,
2786 "* * match, initial time: * minute*, increment: * second")) {
2787 /* Header for a move list -- second line */
2788 /* Initial board will follow if this is a wild game */
2789 if (gameInfo.event != NULL) free(gameInfo.event);
2790 sprintf(str, "ICS %s %s match", star_match[0], star_match[1]);
2791 gameInfo.event = StrSave(str);
2792 /* [HGM] we switched variant. Translate boards if needed. */
2793 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
2797 if (looking_at(buf, &i, "Move ")) {
2798 /* Beginning of a move list */
2799 switch (ics_getting_history) {
2801 /* Normally should not happen */
2802 /* Maybe user hit reset while we were parsing */
2805 /* Happens if we are ignoring a move list that is not
2806 * the one we just requested. Common if the user
2807 * tries to observe two games without turning off
2810 case H_GETTING_MOVES:
2811 /* Should not happen */
2812 DisplayError(_("Error gathering move list: nested"), 0);
2813 ics_getting_history = H_FALSE;
2815 case H_GOT_REQ_HEADER:
2816 ics_getting_history = H_GETTING_MOVES;
2817 started = STARTED_MOVES;
2819 if (oldi > next_out) {
2820 SendToPlayer(&buf[next_out], oldi - next_out);
2823 case H_GOT_UNREQ_HEADER:
2824 ics_getting_history = H_GETTING_MOVES;
2825 started = STARTED_MOVES_NOHIDE;
2828 case H_GOT_UNWANTED_HEADER:
2829 ics_getting_history = H_FALSE;
2835 if (looking_at(buf, &i, "% ") ||
2836 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
2837 && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
2838 savingComment = FALSE;
2841 case STARTED_MOVES_NOHIDE:
2842 memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
2843 parse[parse_pos + i - oldi] = NULLCHAR;
2844 ParseGameHistory(parse);
2846 if (appData.zippyPlay && first.initDone) {
2847 FeedMovesToProgram(&first, forwardMostMove);
2848 if (gameMode == IcsPlayingWhite) {
2849 if (WhiteOnMove(forwardMostMove)) {
2850 if (first.sendTime) {
2851 if (first.useColors) {
2852 SendToProgram("black\n", &first);
2854 SendTimeRemaining(&first, TRUE);
2856 if (first.useColors) {
2857 SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
2859 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
2860 first.maybeThinking = TRUE;
2862 if (first.usePlayother) {
2863 if (first.sendTime) {
2864 SendTimeRemaining(&first, TRUE);
2866 SendToProgram("playother\n", &first);
2872 } else if (gameMode == IcsPlayingBlack) {
2873 if (!WhiteOnMove(forwardMostMove)) {
2874 if (first.sendTime) {
2875 if (first.useColors) {
2876 SendToProgram("white\n", &first);
2878 SendTimeRemaining(&first, FALSE);
2880 if (first.useColors) {
2881 SendToProgram("black\n", &first);
2883 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
2884 first.maybeThinking = TRUE;
2886 if (first.usePlayother) {
2887 if (first.sendTime) {
2888 SendTimeRemaining(&first, FALSE);
2890 SendToProgram("playother\n", &first);
2899 if (gameMode == IcsObserving && ics_gamenum == -1) {
2900 /* Moves came from oldmoves or moves command
2901 while we weren't doing anything else.
2903 currentMove = forwardMostMove;
2904 ClearHighlights();/*!!could figure this out*/
2905 flipView = appData.flipView;
2906 DrawPosition(FALSE, boards[currentMove]);
2907 DisplayBothClocks();
2908 sprintf(str, "%s vs. %s",
2909 gameInfo.white, gameInfo.black);
2913 /* Moves were history of an active game */
2914 if (gameInfo.resultDetails != NULL) {
2915 free(gameInfo.resultDetails);
2916 gameInfo.resultDetails = NULL;
2919 HistorySet(parseList, backwardMostMove,
2920 forwardMostMove, currentMove-1);
2921 DisplayMove(currentMove - 1);
2922 if (started == STARTED_MOVES) next_out = i;
2923 started = STARTED_NONE;
2924 ics_getting_history = H_FALSE;
2927 case STARTED_OBSERVE:
2928 started = STARTED_NONE;
2929 SendToICS(ics_prefix);
2930 SendToICS("refresh\n");
2936 if(bookHit) { // [HGM] book: simulate book reply
2937 static char bookMove[MSG_SIZ]; // a bit generous?
2939 programStats.nodes = programStats.depth = programStats.time =
2940 programStats.score = programStats.got_only_move = 0;
2941 sprintf(programStats.movelist, "%s (xbook)", bookHit);
2943 strcpy(bookMove, "move ");
2944 strcat(bookMove, bookHit);
2945 HandleMachineMove(bookMove, &first);
2950 if ((started == STARTED_MOVES || started == STARTED_BOARD ||
2951 started == STARTED_HOLDINGS ||
2952 started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
2953 /* Accumulate characters in move list or board */
2954 parse[parse_pos++] = buf[i];
2957 /* Start of game messages. Mostly we detect start of game
2958 when the first board image arrives. On some versions
2959 of the ICS, though, we need to do a "refresh" after starting
2960 to observe in order to get the current board right away. */
2961 if (looking_at(buf, &i, "Adding game * to observation list")) {
2962 started = STARTED_OBSERVE;
2966 /* Handle auto-observe */
2967 if (appData.autoObserve &&
2968 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
2969 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
2971 /* Choose the player that was highlighted, if any. */
2972 if (star_match[0][0] == '\033' ||
2973 star_match[1][0] != '\033') {
2974 player = star_match[0];
2976 player = star_match[2];
2978 sprintf(str, "%sobserve %s\n",
2979 ics_prefix, StripHighlightAndTitle(player));
2982 /* Save ratings from notify string */
2983 strcpy(player1Name, star_match[0]);
2984 player1Rating = string_to_rating(star_match[1]);
2985 strcpy(player2Name, star_match[2]);
2986 player2Rating = string_to_rating(star_match[3]);
2988 if (appData.debugMode)
2990 "Ratings from 'Game notification:' %s %d, %s %d\n",
2991 player1Name, player1Rating,
2992 player2Name, player2Rating);
2997 /* Deal with automatic examine mode after a game,
2998 and with IcsObserving -> IcsExamining transition */
2999 if (looking_at(buf, &i, "Entering examine mode for game *") ||
3000 looking_at(buf, &i, "has made you an examiner of game *")) {
3002 int gamenum = atoi(star_match[0]);
3003 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3004 gamenum == ics_gamenum) {
3005 /* We were already playing or observing this game;
3006 no need to refetch history */
3007 gameMode = IcsExamining;
3009 pauseExamForwardMostMove = forwardMostMove;
3010 } else if (currentMove < forwardMostMove) {
3011 ForwardInner(forwardMostMove);
3014 /* I don't think this case really can happen */
3015 SendToICS(ics_prefix);
3016 SendToICS("refresh\n");
3021 /* Error messages */
3022 // if (ics_user_moved) {
3023 if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3024 if (looking_at(buf, &i, "Illegal move") ||
3025 looking_at(buf, &i, "Not a legal move") ||
3026 looking_at(buf, &i, "Your king is in check") ||
3027 looking_at(buf, &i, "It isn't your turn") ||
3028 looking_at(buf, &i, "It is not your move")) {
3030 if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3031 currentMove = --forwardMostMove;
3032 DisplayMove(currentMove - 1); /* before DMError */
3033 DrawPosition(FALSE, boards[currentMove]);
3035 DisplayBothClocks();
3037 DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3043 if (looking_at(buf, &i, "still have time") ||
3044 looking_at(buf, &i, "not out of time") ||
3045 looking_at(buf, &i, "either player is out of time") ||
3046 looking_at(buf, &i, "has timeseal; checking")) {
3047 /* We must have called his flag a little too soon */
3048 whiteFlag = blackFlag = FALSE;
3052 if (looking_at(buf, &i, "added * seconds to") ||
3053 looking_at(buf, &i, "seconds were added to")) {
3054 /* Update the clocks */
3055 SendToICS(ics_prefix);
3056 SendToICS("refresh\n");
3060 if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3061 ics_clock_paused = TRUE;
3066 if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3067 ics_clock_paused = FALSE;
3072 /* Grab player ratings from the Creating: message.
3073 Note we have to check for the special case when
3074 the ICS inserts things like [white] or [black]. */
3075 if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3076 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3078 0 player 1 name (not necessarily white)
3080 2 empty, white, or black (IGNORED)
3081 3 player 2 name (not necessarily black)
3084 The names/ratings are sorted out when the game
3085 actually starts (below).
3087 strcpy(player1Name, StripHighlightAndTitle(star_match[0]));
3088 player1Rating = string_to_rating(star_match[1]);
3089 strcpy(player2Name, StripHighlightAndTitle(star_match[3]));
3090 player2Rating = string_to_rating(star_match[4]);
3092 if (appData.debugMode)
3094 "Ratings from 'Creating:' %s %d, %s %d\n",
3095 player1Name, player1Rating,
3096 player2Name, player2Rating);
3101 /* Improved generic start/end-of-game messages */
3102 if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3103 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3104 /* If tkind == 0: */
3105 /* star_match[0] is the game number */
3106 /* [1] is the white player's name */
3107 /* [2] is the black player's name */
3108 /* For end-of-game: */
3109 /* [3] is the reason for the game end */
3110 /* [4] is a PGN end game-token, preceded by " " */
3111 /* For start-of-game: */
3112 /* [3] begins with "Creating" or "Continuing" */
3113 /* [4] is " *" or empty (don't care). */
3114 int gamenum = atoi(star_match[0]);
3115 char *whitename, *blackname, *why, *endtoken;
3116 ChessMove endtype = (ChessMove) 0;
3119 whitename = star_match[1];
3120 blackname = star_match[2];
3121 why = star_match[3];
3122 endtoken = star_match[4];
3124 whitename = star_match[1];
3125 blackname = star_match[3];
3126 why = star_match[5];
3127 endtoken = star_match[6];
3130 /* Game start messages */
3131 if (strncmp(why, "Creating ", 9) == 0 ||
3132 strncmp(why, "Continuing ", 11) == 0) {
3133 gs_gamenum = gamenum;
3134 strcpy(gs_kind, strchr(why, ' ') + 1);
3136 if (appData.zippyPlay) {
3137 ZippyGameStart(whitename, blackname);
3143 /* Game end messages */
3144 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3145 ics_gamenum != gamenum) {
3148 while (endtoken[0] == ' ') endtoken++;
3149 switch (endtoken[0]) {
3152 endtype = GameUnfinished;
3155 endtype = BlackWins;
3158 if (endtoken[1] == '/')
3159 endtype = GameIsDrawn;
3161 endtype = WhiteWins;
3164 GameEnds(endtype, why, GE_ICS);
3166 if (appData.zippyPlay && first.initDone) {
3167 ZippyGameEnd(endtype, why);
3168 if (first.pr == NULL) {
3169 /* Start the next process early so that we'll
3170 be ready for the next challenge */
3171 StartChessProgram(&first);
3173 /* Send "new" early, in case this command takes
3174 a long time to finish, so that we'll be ready
3175 for the next challenge. */
3176 gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3183 if (looking_at(buf, &i, "Removing game * from observation") ||
3184 looking_at(buf, &i, "no longer observing game *") ||
3185 looking_at(buf, &i, "Game * (*) has no examiners")) {
3186 if (gameMode == IcsObserving &&
3187 atoi(star_match[0]) == ics_gamenum)
3189 /* icsEngineAnalyze */
3190 if (appData.icsEngineAnalyze) {
3197 ics_user_moved = FALSE;
3202 if (looking_at(buf, &i, "no longer examining game *")) {
3203 if (gameMode == IcsExamining &&
3204 atoi(star_match[0]) == ics_gamenum)
3208 ics_user_moved = FALSE;
3213 /* Advance leftover_start past any newlines we find,
3214 so only partial lines can get reparsed */
3215 if (looking_at(buf, &i, "\n")) {
3216 prevColor = curColor;
3217 if (curColor != ColorNormal) {
3218 if (oldi > next_out) {
3219 SendToPlayer(&buf[next_out], oldi - next_out);
3222 Colorize(ColorNormal, FALSE);
3223 curColor = ColorNormal;
3225 if (started == STARTED_BOARD) {
3226 started = STARTED_NONE;
3227 parse[parse_pos] = NULLCHAR;
3228 ParseBoard12(parse);
3231 /* Send premove here */
3232 if (appData.premove) {
3234 if (currentMove == 0 &&
3235 gameMode == IcsPlayingWhite &&
3236 appData.premoveWhite) {
3237 sprintf(str, "%s%s\n", ics_prefix,
3238 appData.premoveWhiteText);
3239 if (appData.debugMode)
3240 fprintf(debugFP, "Sending premove:\n");
3242 } else if (currentMove == 1 &&
3243 gameMode == IcsPlayingBlack &&
3244 appData.premoveBlack) {
3245 sprintf(str, "%s%s\n", ics_prefix,
3246 appData.premoveBlackText);
3247 if (appData.debugMode)
3248 fprintf(debugFP, "Sending premove:\n");
3250 } else if (gotPremove) {
3252 ClearPremoveHighlights();
3253 if (appData.debugMode)
3254 fprintf(debugFP, "Sending premove:\n");
3255 UserMoveEvent(premoveFromX, premoveFromY,
3256 premoveToX, premoveToY,
3261 /* Usually suppress following prompt */
3262 if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
3263 if (looking_at(buf, &i, "*% ")) {
3264 savingComment = FALSE;
3268 } else if (started == STARTED_HOLDINGS) {
3270 char new_piece[MSG_SIZ];
3271 started = STARTED_NONE;
3272 parse[parse_pos] = NULLCHAR;
3273 if (appData.debugMode)
3274 fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
3275 parse, currentMove);
3276 if (sscanf(parse, " game %d", &gamenum) == 1 &&
3277 gamenum == ics_gamenum) {
3278 if (gameInfo.variant == VariantNormal) {
3279 /* [HGM] We seem to switch variant during a game!
3280 * Presumably no holdings were displayed, so we have
3281 * to move the position two files to the right to
3282 * create room for them!
3284 VariantSwitch(boards[currentMove], VariantCrazyhouse); /* temp guess */
3285 /* Get a move list just to see the header, which
3286 will tell us whether this is really bug or zh */
3287 if (ics_getting_history == H_FALSE) {
3288 ics_getting_history = H_REQUESTED;
3289 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3293 new_piece[0] = NULLCHAR;
3294 sscanf(parse, "game %d white [%s black [%s <- %s",
3295 &gamenum, white_holding, black_holding,
3297 white_holding[strlen(white_holding)-1] = NULLCHAR;
3298 black_holding[strlen(black_holding)-1] = NULLCHAR;
3299 /* [HGM] copy holdings to board holdings area */
3300 CopyHoldings(boards[currentMove], white_holding, WhitePawn);
3301 CopyHoldings(boards[currentMove], black_holding, BlackPawn);
3303 if (appData.zippyPlay && first.initDone) {
3304 ZippyHoldings(white_holding, black_holding,
3308 if (tinyLayout || smallLayout) {
3309 char wh[16], bh[16];
3310 PackHolding(wh, white_holding);
3311 PackHolding(bh, black_holding);
3312 sprintf(str, "[%s-%s] %s-%s", wh, bh,
3313 gameInfo.white, gameInfo.black);
3315 sprintf(str, "%s [%s] vs. %s [%s]",
3316 gameInfo.white, white_holding,
3317 gameInfo.black, black_holding);
3320 DrawPosition(FALSE, boards[currentMove]);
3323 /* Suppress following prompt */
3324 if (looking_at(buf, &i, "*% ")) {
3325 savingComment = FALSE;
3332 i++; /* skip unparsed character and loop back */
3335 if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz suppress printing in ICS interaction window
3336 started != STARTED_HOLDINGS && i > next_out) {
3337 SendToPlayer(&buf[next_out], i - next_out);
3340 suppressKibitz = FALSE; // [HGM] kibitz: has done its duty in if-statement above
3342 leftover_len = buf_len - leftover_start;
3343 /* if buffer ends with something we couldn't parse,
3344 reparse it after appending the next read */
3346 } else if (count == 0) {
3347 RemoveInputSource(isr);
3348 DisplayFatalError(_("Connection closed by ICS"), 0, 0);
3350 DisplayFatalError(_("Error reading from ICS"), error, 1);
3355 /* Board style 12 looks like this:
3357 <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
3359 * The "<12> " is stripped before it gets to this routine. The two
3360 * trailing 0's (flip state and clock ticking) are later addition, and
3361 * some chess servers may not have them, or may have only the first.
3362 * Additional trailing fields may be added in the future.
3365 #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"
3367 #define RELATION_OBSERVING_PLAYED 0
3368 #define RELATION_OBSERVING_STATIC -2 /* examined, oldmoves, or smoves */
3369 #define RELATION_PLAYING_MYMOVE 1
3370 #define RELATION_PLAYING_NOTMYMOVE -1
3371 #define RELATION_EXAMINING 2
3372 #define RELATION_ISOLATED_BOARD -3
3373 #define RELATION_STARTING_POSITION -4 /* FICS only */
3376 ParseBoard12(string)
3379 GameMode newGameMode;
3380 int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
3381 int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
3382 int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
3383 char to_play, board_chars[200];
3384 char move_str[500], str[500], elapsed_time[500];
3385 char black[32], white[32];
3387 int prevMove = currentMove;
3390 int fromX, fromY, toX, toY;
3392 int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
3393 char *bookHit = NULL; // [HGM] book
3395 fromX = fromY = toX = toY = -1;
3399 if (appData.debugMode)
3400 fprintf(debugFP, _("Parsing board: %s\n"), string);
3402 move_str[0] = NULLCHAR;
3403 elapsed_time[0] = NULLCHAR;
3404 { /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
3406 while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
3407 if(string[i] == ' ') { ranks++; files = 0; }
3411 for(j = 0; j <i; j++) board_chars[j] = string[j];
3412 board_chars[i] = '\0';
3415 n = sscanf(string, PATTERN, &to_play, &double_push,
3416 &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
3417 &gamenum, white, black, &relation, &basetime, &increment,
3418 &white_stren, &black_stren, &white_time, &black_time,
3419 &moveNum, str, elapsed_time, move_str, &ics_flip,
3423 snprintf(str, sizeof(str), _("Failed to parse board string:\n\"%s\""), string);
3424 DisplayError(str, 0);
3428 /* Convert the move number to internal form */
3429 moveNum = (moveNum - 1) * 2;
3430 if (to_play == 'B') moveNum++;
3431 if (moveNum >= MAX_MOVES) {
3432 DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
3438 case RELATION_OBSERVING_PLAYED:
3439 case RELATION_OBSERVING_STATIC:
3440 if (gamenum == -1) {
3441 /* Old ICC buglet */
3442 relation = RELATION_OBSERVING_STATIC;
3444 newGameMode = IcsObserving;
3446 case RELATION_PLAYING_MYMOVE:
3447 case RELATION_PLAYING_NOTMYMOVE:
3449 ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
3450 IcsPlayingWhite : IcsPlayingBlack;
3452 case RELATION_EXAMINING:
3453 newGameMode = IcsExamining;
3455 case RELATION_ISOLATED_BOARD:
3457 /* Just display this board. If user was doing something else,
3458 we will forget about it until the next board comes. */
3459 newGameMode = IcsIdle;
3461 case RELATION_STARTING_POSITION:
3462 newGameMode = gameMode;
3466 /* Modify behavior for initial board display on move listing
3469 switch (ics_getting_history) {
3473 case H_GOT_REQ_HEADER:
3474 case H_GOT_UNREQ_HEADER:
3475 /* This is the initial position of the current game */
3476 gamenum = ics_gamenum;
3477 moveNum = 0; /* old ICS bug workaround */
3478 if (to_play == 'B') {
3479 startedFromSetupPosition = TRUE;
3480 blackPlaysFirst = TRUE;
3482 if (forwardMostMove == 0) forwardMostMove = 1;
3483 if (backwardMostMove == 0) backwardMostMove = 1;
3484 if (currentMove == 0) currentMove = 1;
3486 newGameMode = gameMode;
3487 relation = RELATION_STARTING_POSITION; /* ICC needs this */
3489 case H_GOT_UNWANTED_HEADER:
3490 /* This is an initial board that we don't want */
3492 case H_GETTING_MOVES:
3493 /* Should not happen */
3494 DisplayError(_("Error gathering move list: extra board"), 0);
3495 ics_getting_history = H_FALSE;
3499 /* Take action if this is the first board of a new game, or of a
3500 different game than is currently being displayed. */
3501 if (gamenum != ics_gamenum || newGameMode != gameMode ||
3502 relation == RELATION_ISOLATED_BOARD) {
3504 /* Forget the old game and get the history (if any) of the new one */
3505 if (gameMode != BeginningOfGame) {
3509 if (appData.autoRaiseBoard) BoardToTop();
3511 if (gamenum == -1) {
3512 newGameMode = IcsIdle;
3513 } else if (moveNum > 0 && newGameMode != IcsIdle &&
3514 appData.getMoveList) {
3515 /* Need to get game history */
3516 ics_getting_history = H_REQUESTED;
3517 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3521 /* Initially flip the board to have black on the bottom if playing
3522 black or if the ICS flip flag is set, but let the user change
3523 it with the Flip View button. */
3524 flipView = appData.autoFlipView ?
3525 (newGameMode == IcsPlayingBlack) || ics_flip :
3528 /* Done with values from previous mode; copy in new ones */
3529 gameMode = newGameMode;
3531 ics_gamenum = gamenum;
3532 if (gamenum == gs_gamenum) {
3533 int klen = strlen(gs_kind);
3534 if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
3535 sprintf(str, "ICS %s", gs_kind);
3536 gameInfo.event = StrSave(str);
3538 gameInfo.event = StrSave("ICS game");
3540 gameInfo.site = StrSave(appData.icsHost);
3541 gameInfo.date = PGNDate();
3542 gameInfo.round = StrSave("-");
3543 gameInfo.white = StrSave(white);
3544 gameInfo.black = StrSave(black);
3545 timeControl = basetime * 60 * 1000;
3547 timeIncrement = increment * 1000;
3548 movesPerSession = 0;
3549 gameInfo.timeControl = TimeControlTagValue();
3550 VariantSwitch(board, StringToVariant(gameInfo.event) );
3551 if (appData.debugMode) {
3552 fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
3553 fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
3554 setbuf(debugFP, NULL);
3557 gameInfo.outOfBook = NULL;
3559 /* Do we have the ratings? */
3560 if (strcmp(player1Name, white) == 0 &&
3561 strcmp(player2Name, black) == 0) {
3562 if (appData.debugMode)
3563 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3564 player1Rating, player2Rating);
3565 gameInfo.whiteRating = player1Rating;
3566 gameInfo.blackRating = player2Rating;
3567 } else if (strcmp(player2Name, white) == 0 &&
3568 strcmp(player1Name, black) == 0) {
3569 if (appData.debugMode)
3570 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3571 player2Rating, player1Rating);
3572 gameInfo.whiteRating = player2Rating;
3573 gameInfo.blackRating = player1Rating;
3575 player1Name[0] = player2Name[0] = NULLCHAR;
3577 /* Silence shouts if requested */
3578 if (appData.quietPlay &&
3579 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
3580 SendToICS(ics_prefix);
3581 SendToICS("set shout 0\n");
3585 /* Deal with midgame name changes */
3587 if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
3588 if (gameInfo.white) free(gameInfo.white);
3589 gameInfo.white = StrSave(white);
3591 if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
3592 if (gameInfo.black) free(gameInfo.black);
3593 gameInfo.black = StrSave(black);
3597 /* Throw away game result if anything actually changes in examine mode */
3598 if (gameMode == IcsExamining && !newGame) {
3599 gameInfo.result = GameUnfinished;
3600 if (gameInfo.resultDetails != NULL) {
3601 free(gameInfo.resultDetails);
3602 gameInfo.resultDetails = NULL;
3606 /* In pausing && IcsExamining mode, we ignore boards coming
3607 in if they are in a different variation than we are. */
3608 if (pauseExamInvalid) return;
3609 if (pausing && gameMode == IcsExamining) {
3610 if (moveNum <= pauseExamForwardMostMove) {
3611 pauseExamInvalid = TRUE;
3612 forwardMostMove = pauseExamForwardMostMove;
3617 if (appData.debugMode) {
3618 fprintf(debugFP, "load %dx%d board\n", files, ranks);
3620 /* Parse the board */
3621 for (k = 0; k < ranks; k++) {
3622 for (j = 0; j < files; j++)
3623 board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
3624 if(gameInfo.holdingsWidth > 1) {
3625 board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
3626 board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
3629 CopyBoard(boards[moveNum], board);
3631 startedFromSetupPosition =
3632 !CompareBoards(board, initialPosition);
3633 if(startedFromSetupPosition)
3634 initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
3637 /* [HGM] Set castling rights. Take the outermost Rooks,
3638 to make it also work for FRC opening positions. Note that board12
3639 is really defective for later FRC positions, as it has no way to
3640 indicate which Rook can castle if they are on the same side of King.
3641 For the initial position we grant rights to the outermost Rooks,
3642 and remember thos rights, and we then copy them on positions
3643 later in an FRC game. This means WB might not recognize castlings with
3644 Rooks that have moved back to their original position as illegal,
3645 but in ICS mode that is not its job anyway.
3647 if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
3648 { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
3650 for(i=BOARD_LEFT, j= -1; i<BOARD_RGHT; i++)
3651 if(board[0][i] == WhiteRook) j = i;
3652 initialRights[0] = castlingRights[moveNum][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3653 for(i=BOARD_RGHT-1, j= -1; i>=BOARD_LEFT; i--)
3654 if(board[0][i] == WhiteRook) j = i;
3655 initialRights[1] = castlingRights[moveNum][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3656 for(i=BOARD_LEFT, j= -1; i<BOARD_RGHT; i++)
3657 if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
3658 initialRights[3] = castlingRights[moveNum][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3659 for(i=BOARD_RGHT-1, j= -1; i>=BOARD_LEFT; i--)
3660 if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
3661 initialRights[4] = castlingRights[moveNum][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3663 if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
3664 for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
3665 if(board[0][k] == wKing) initialRights[2] = castlingRights[moveNum][2] = k;
3666 for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
3667 if(board[BOARD_HEIGHT-1][k] == bKing)
3668 initialRights[5] = castlingRights[moveNum][5] = k;
3670 r = castlingRights[moveNum][0] = initialRights[0];
3671 if(board[0][r] != WhiteRook) castlingRights[moveNum][0] = -1;
3672 r = castlingRights[moveNum][1] = initialRights[1];
3673 if(board[0][r] != WhiteRook) castlingRights[moveNum][1] = -1;
3674 r = castlingRights[moveNum][3] = initialRights[3];
3675 if(board[BOARD_HEIGHT-1][r] != BlackRook) castlingRights[moveNum][3] = -1;
3676 r = castlingRights[moveNum][4] = initialRights[4];
3677 if(board[BOARD_HEIGHT-1][r] != BlackRook) castlingRights[moveNum][4] = -1;
3678 /* wildcastle kludge: always assume King has rights */
3679 r = castlingRights[moveNum][2] = initialRights[2];
3680 r = castlingRights[moveNum][5] = initialRights[5];
3682 /* [HGM] e.p. rights. Assume that ICS sends file number here? */
3683 epStatus[moveNum] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
3686 if (ics_getting_history == H_GOT_REQ_HEADER ||
3687 ics_getting_history == H_GOT_UNREQ_HEADER) {
3688 /* This was an initial position from a move list, not
3689 the current position */
3693 /* Update currentMove and known move number limits */
3694 newMove = newGame || moveNum > forwardMostMove;
3696 /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
3697 if (!newGame && appData.icsEngineAnalyze && moveNum < forwardMostMove) {
3698 takeback = forwardMostMove - moveNum;
3699 for (i = 0; i < takeback; i++) {
3700 if (appData.debugMode) fprintf(debugFP, "take back move\n");
3701 SendToProgram("undo\n", &first);
3706 forwardMostMove = backwardMostMove = currentMove = moveNum;
3707 if (gameMode == IcsExamining && moveNum == 0) {
3708 /* Workaround for ICS limitation: we are not told the wild
3709 type when starting to examine a game. But if we ask for
3710 the move list, the move list header will tell us */
3711 ics_getting_history = H_REQUESTED;
3712 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3715 } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
3716 || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
3717 forwardMostMove = moveNum;
3718 if (!pausing || currentMove > forwardMostMove)
3719 currentMove = forwardMostMove;
3721 /* New part of history that is not contiguous with old part */
3722 if (pausing && gameMode == IcsExamining) {
3723 pauseExamInvalid = TRUE;
3724 forwardMostMove = pauseExamForwardMostMove;
3727 forwardMostMove = backwardMostMove = currentMove = moveNum;
3728 if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
3729 ics_getting_history = H_REQUESTED;
3730 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3735 /* Update the clocks */
3736 if (strchr(elapsed_time, '.')) {
3738 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
3739 timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
3741 /* Time is in seconds */
3742 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
3743 timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
3748 if (appData.zippyPlay && newGame &&
3749 gameMode != IcsObserving && gameMode != IcsIdle &&
3750 gameMode != IcsExamining)
3751 ZippyFirstBoard(moveNum, basetime, increment);
3754 /* Put the move on the move list, first converting
3755 to canonical algebraic form. */
3757 if (appData.debugMode) {
3758 if (appData.debugMode) { int f = forwardMostMove;
3759 fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
3760 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
3762 fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
3763 fprintf(debugFP, "moveNum = %d\n", moveNum);
3764 fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
3765 setbuf(debugFP, NULL);
3767 if (moveNum <= backwardMostMove) {
3768 /* We don't know what the board looked like before
3770 strcpy(parseList[moveNum - 1], move_str);
3771 strcat(parseList[moveNum - 1], " ");
3772 strcat(parseList[moveNum - 1], elapsed_time);
3773 moveList[moveNum - 1][0] = NULLCHAR;
3774 } else if (strcmp(move_str, "none") == 0) {
3775 // [HGM] long SAN: swapped order; test for 'none' before parsing move
3776 /* Again, we don't know what the board looked like;
3777 this is really the start of the game. */
3778 parseList[moveNum - 1][0] = NULLCHAR;
3779 moveList[moveNum - 1][0] = NULLCHAR;
3780 backwardMostMove = moveNum;
3781 startedFromSetupPosition = TRUE;
3782 fromX = fromY = toX = toY = -1;
3784 // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
3785 // So we parse the long-algebraic move string in stead of the SAN move
3786 int valid; char buf[MSG_SIZ], *prom;
3788 // str looks something like "Q/a1-a2"; kill the slash
3790 sprintf(buf, "%c%s", str[0], str+2);
3791 else strcpy(buf, str); // might be castling
3792 if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
3793 strcat(buf, prom); // long move lacks promo specification!
3794 if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
3795 if(appData.debugMode)
3796 fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
3797 strcpy(move_str, buf);
3799 valid = ParseOneMove(move_str, moveNum - 1, &moveType,
3800 &fromX, &fromY, &toX, &toY, &promoChar)
3801 || ParseOneMove(buf, moveNum - 1, &moveType,
3802 &fromX, &fromY, &toX, &toY, &promoChar);
3803 // end of long SAN patch
3805 (void) CoordsToAlgebraic(boards[moveNum - 1],
3806 PosFlags(moveNum - 1), EP_UNKNOWN,
3807 fromY, fromX, toY, toX, promoChar,
3808 parseList[moveNum-1]);
3809 switch (MateTest(boards[moveNum], PosFlags(moveNum), EP_UNKNOWN,
3810 castlingRights[moveNum]) ) {
3816 if(gameInfo.variant != VariantShogi)
3817 strcat(parseList[moveNum - 1], "+");
3820 case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
3821 strcat(parseList[moveNum - 1], "#");
3824 strcat(parseList[moveNum - 1], " ");
3825 strcat(parseList[moveNum - 1], elapsed_time);
3826 /* currentMoveString is set as a side-effect of ParseOneMove */
3827 strcpy(moveList[moveNum - 1], currentMoveString);
3828 strcat(moveList[moveNum - 1], "\n");
3830 /* Move from ICS was illegal!? Punt. */
3831 if (appData.debugMode) {
3832 fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
3833 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
3835 strcpy(parseList[moveNum - 1], move_str);
3836 strcat(parseList[moveNum - 1], " ");
3837 strcat(parseList[moveNum - 1], elapsed_time);
3838 moveList[moveNum - 1][0] = NULLCHAR;
3839 fromX = fromY = toX = toY = -1;
3842 if (appData.debugMode) {
3843 fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
3844 setbuf(debugFP, NULL);
3848 /* Send move to chess program (BEFORE animating it). */
3849 if (appData.zippyPlay && !newGame && newMove &&
3850 (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
3852 if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
3853 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
3854 if (moveList[moveNum - 1][0] == NULLCHAR) {
3855 sprintf(str, _("Couldn't parse move \"%s\" from ICS"),
3857 DisplayError(str, 0);
3859 if (first.sendTime) {
3860 SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
3862 bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
3863 if (firstMove && !bookHit) {
3865 if (first.useColors) {
3866 SendToProgram(gameMode == IcsPlayingWhite ?
3868 "black\ngo\n", &first);
3870 SendToProgram("go\n", &first);
3872 first.maybeThinking = TRUE;
3875 } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
3876 if (moveList[moveNum - 1][0] == NULLCHAR) {
3877 sprintf(str, _("Couldn't parse move \"%s\" from ICS"), move_str);
3878 DisplayError(str, 0);
3880 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
3881 SendMoveToProgram(moveNum - 1, &first);
3888 if (moveNum > 0 && !gotPremove && !appData.noGUI) {
3889 /* If move comes from a remote source, animate it. If it
3890 isn't remote, it will have already been animated. */
3891 if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
3892 AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
3894 if (!pausing && appData.highlightLastMove) {
3895 SetHighlights(fromX, fromY, toX, toY);
3899 /* Start the clocks */
3900 whiteFlag = blackFlag = FALSE;
3901 appData.clockMode = !(basetime == 0 && increment == 0);
3903 ics_clock_paused = TRUE;
3905 } else if (ticking == 1) {
3906 ics_clock_paused = FALSE;
3908 if (gameMode == IcsIdle ||
3909 relation == RELATION_OBSERVING_STATIC ||
3910 relation == RELATION_EXAMINING ||
3912 DisplayBothClocks();
3916 /* Display opponents and material strengths */
3917 if (gameInfo.variant != VariantBughouse &&
3918 gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
3919 if (tinyLayout || smallLayout) {
3920 if(gameInfo.variant == VariantNormal)
3921 sprintf(str, "%s(%d) %s(%d) {%d %d}",
3922 gameInfo.white, white_stren, gameInfo.black, black_stren,
3923 basetime, increment);
3925 sprintf(str, "%s(%d) %s(%d) {%d %d w%d}",
3926 gameInfo.white, white_stren, gameInfo.black, black_stren,
3927 basetime, increment, (int) gameInfo.variant);
3929 if(gameInfo.variant == VariantNormal)
3930 sprintf(str, "%s (%d) vs. %s (%d) {%d %d}",
3931 gameInfo.white, white_stren, gameInfo.black, black_stren,
3932 basetime, increment);
3934 sprintf(str, "%s (%d) vs. %s (%d) {%d %d %s}",
3935 gameInfo.white, white_stren, gameInfo.black, black_stren,
3936 basetime, increment, VariantName(gameInfo.variant));
3939 if (appData.debugMode) {
3940 fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
3945 /* Display the board */
3946 if (!pausing && !appData.noGUI) {
3948 if (appData.premove)
3950 ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
3951 ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
3952 ClearPremoveHighlights();
3954 DrawPosition(FALSE, boards[currentMove]);
3955 DisplayMove(moveNum - 1);
3956 if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
3957 !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
3958 (gameMode == IcsPlayingBlack) && (WhiteOnMove(moveNum)) ) ) {
3959 if(newMove) RingBell(); else PlayIcsUnfinishedSound();
3963 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
3965 if(bookHit) { // [HGM] book: simulate book reply
3966 static char bookMove[MSG_SIZ]; // a bit generous?
3968 programStats.nodes = programStats.depth = programStats.time =
3969 programStats.score = programStats.got_only_move = 0;
3970 sprintf(programStats.movelist, "%s (xbook)", bookHit);
3972 strcpy(bookMove, "move ");
3973 strcat(bookMove, bookHit);
3974 HandleMachineMove(bookMove, &first);
3983 if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
3984 ics_getting_history = H_REQUESTED;
3985 sprintf(buf, "%smoves %d\n", ics_prefix, ics_gamenum);
3991 AnalysisPeriodicEvent(force)
3994 if (((programStats.ok_to_send == 0 || programStats.line_is_book)
3995 && !force) || !appData.periodicUpdates)
3998 /* Send . command to Crafty to collect stats */
3999 SendToProgram(".\n", &first);
4001 /* Don't send another until we get a response (this makes
4002 us stop sending to old Crafty's which don't understand
4003 the "." command (sending illegal cmds resets node count & time,
4004 which looks bad)) */
4005 programStats.ok_to_send = 0;
4008 void ics_update_width(new_width)
4011 ics_printf("set width %d\n", new_width);
4015 SendMoveToProgram(moveNum, cps)
4017 ChessProgramState *cps;
4021 if (cps->useUsermove) {
4022 SendToProgram("usermove ", cps);
4026 if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4027 int len = space - parseList[moveNum];
4028 memcpy(buf, parseList[moveNum], len);
4030 buf[len] = NULLCHAR;
4032 sprintf(buf, "%s\n", parseList[moveNum]);
4034 SendToProgram(buf, cps);
4036 if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4037 AlphaRank(moveList[moveNum], 4);
4038 SendToProgram(moveList[moveNum], cps);
4039 AlphaRank(moveList[moveNum], 4); // and back
4041 /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
4042 * the engine. It would be nice to have a better way to identify castle
4044 if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
4045 && cps->useOOCastle) {
4046 int fromX = moveList[moveNum][0] - AAA;
4047 int fromY = moveList[moveNum][1] - ONE;
4048 int toX = moveList[moveNum][2] - AAA;
4049 int toY = moveList[moveNum][3] - ONE;
4050 if((boards[moveNum][fromY][fromX] == WhiteKing
4051 && boards[moveNum][toY][toX] == WhiteRook)
4052 || (boards[moveNum][fromY][fromX] == BlackKing
4053 && boards[moveNum][toY][toX] == BlackRook)) {
4054 if(toX > fromX) SendToProgram("O-O\n", cps);
4055 else SendToProgram("O-O-O\n", cps);
4057 else SendToProgram(moveList[moveNum], cps);
4059 else SendToProgram(moveList[moveNum], cps);
4060 /* End of additions by Tord */
4063 /* [HGM] setting up the opening has brought engine in force mode! */
4064 /* Send 'go' if we are in a mode where machine should play. */
4065 if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4066 (gameMode == TwoMachinesPlay ||
4068 gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite ||
4070 gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4071 SendToProgram("go\n", cps);
4072 if (appData.debugMode) {
4073 fprintf(debugFP, "(extra)\n");
4076 setboardSpoiledMachineBlack = 0;
4080 SendMoveToICS(moveType, fromX, fromY, toX, toY)
4082 int fromX, fromY, toX, toY;
4084 char user_move[MSG_SIZ];
4088 sprintf(user_move, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4089 (int)moveType, fromX, fromY, toX, toY);
4090 DisplayError(user_move + strlen("say "), 0);
4092 case WhiteKingSideCastle:
4093 case BlackKingSideCastle:
4094 case WhiteQueenSideCastleWild:
4095 case BlackQueenSideCastleWild:
4097 case WhiteHSideCastleFR:
4098 case BlackHSideCastleFR:
4100 sprintf(user_move, "o-o\n");
4102 case WhiteQueenSideCastle:
4103 case BlackQueenSideCastle:
4104 case WhiteKingSideCastleWild:
4105 case BlackKingSideCastleWild:
4107 case WhiteASideCastleFR:
4108 case BlackASideCastleFR:
4110 sprintf(user_move, "o-o-o\n");
4112 case WhitePromotionQueen:
4113 case BlackPromotionQueen:
4114 case WhitePromotionRook:
4115 case BlackPromotionRook:
4116 case WhitePromotionBishop:
4117 case BlackPromotionBishop:
4118 case WhitePromotionKnight:
4119 case BlackPromotionKnight:
4120 case WhitePromotionKing:
4121 case BlackPromotionKing:
4122 case WhitePromotionChancellor:
4123 case BlackPromotionChancellor:
4124 case WhitePromotionArchbishop:
4125 case BlackPromotionArchbishop:
4126 if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier)
4127 sprintf(user_move, "%c%c%c%c=%c\n",
4128 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4129 PieceToChar(WhiteFerz));
4130 else if(gameInfo.variant == VariantGreat)
4131 sprintf(user_move, "%c%c%c%c=%c\n",
4132 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4133 PieceToChar(WhiteMan));
4135 sprintf(user_move, "%c%c%c%c=%c\n",
4136 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4137 PieceToChar(PromoPiece(moveType)));
4141 sprintf(user_move, "%c@%c%c\n",
4142 ToUpper(PieceToChar((ChessSquare) fromX)),
4143 AAA + toX, ONE + toY);
4146 case WhiteCapturesEnPassant:
4147 case BlackCapturesEnPassant:
4148 case IllegalMove: /* could be a variant we don't quite understand */
4149 sprintf(user_move, "%c%c%c%c\n",
4150 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4153 SendToICS(user_move);
4154 if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
4155 ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
4159 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
4164 if (rf == DROP_RANK) {
4165 sprintf(move, "%c@%c%c\n",
4166 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
4168 if (promoChar == 'x' || promoChar == NULLCHAR) {
4169 sprintf(move, "%c%c%c%c\n",
4170 AAA + ff, ONE + rf, AAA + ft, ONE + rt);
4172 sprintf(move, "%c%c%c%c%c\n",
4173 AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
4179 ProcessICSInitScript(f)
4184 while (fgets(buf, MSG_SIZ, f)) {
4185 SendToICSDelayed(buf,(long)appData.msLoginDelay);
4192 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
4194 AlphaRank(char *move, int n)
4196 // char *p = move, c; int x, y;
4198 if (appData.debugMode) {
4199 fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
4203 move[2]>='0' && move[2]<='9' &&
4204 move[3]>='a' && move[3]<='x' ) {
4206 move[2] = BOARD_RGHT -1 - (move[2]-'1') + AAA;
4207 move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4209 if(move[0]>='0' && move[0]<='9' &&
4210 move[1]>='a' && move[1]<='x' &&
4211 move[2]>='0' && move[2]<='9' &&
4212 move[3]>='a' && move[3]<='x' ) {
4213 /* input move, Shogi -> normal */
4214 move[0] = BOARD_RGHT -1 - (move[0]-'1') + AAA;
4215 move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
4216 move[2] = BOARD_RGHT -1 - (move[2]-'1') + AAA;
4217 move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4220 move[3]>='0' && move[3]<='9' &&
4221 move[2]>='a' && move[2]<='x' ) {
4223 move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4224 move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4227 move[0]>='a' && move[0]<='x' &&
4228 move[3]>='0' && move[3]<='9' &&
4229 move[2]>='a' && move[2]<='x' ) {
4230 /* output move, normal -> Shogi */
4231 move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
4232 move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
4233 move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4234 move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4235 if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
4237 if (appData.debugMode) {
4238 fprintf(debugFP, " out = '%s'\n", move);
4242 /* Parser for moves from gnuchess, ICS, or user typein box */
4244 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
4247 ChessMove *moveType;
4248 int *fromX, *fromY, *toX, *toY;
4251 if (appData.debugMode) {
4252 fprintf(debugFP, "move to parse: %s\n", move);
4254 *moveType = yylexstr(moveNum, move);
4256 switch (*moveType) {
4257 case WhitePromotionChancellor:
4258 case BlackPromotionChancellor:
4259 case WhitePromotionArchbishop:
4260 case BlackPromotionArchbishop:
4261 case WhitePromotionQueen:
4262 case BlackPromotionQueen:
4263 case WhitePromotionRook:
4264 case BlackPromotionRook:
4265 case WhitePromotionBishop:
4266 case BlackPromotionBishop:
4267 case WhitePromotionKnight:
4268 case BlackPromotionKnight:
4269 case WhitePromotionKing:
4270 case BlackPromotionKing:
4272 case WhiteCapturesEnPassant:
4273 case BlackCapturesEnPassant:
4274 case WhiteKingSideCastle:
4275 case WhiteQueenSideCastle:
4276 case BlackKingSideCastle:
4277 case BlackQueenSideCastle:
4278 case WhiteKingSideCastleWild:
4279 case WhiteQueenSideCastleWild:
4280 case BlackKingSideCastleWild:
4281 case BlackQueenSideCastleWild:
4282 /* Code added by Tord: */
4283 case WhiteHSideCastleFR:
4284 case WhiteASideCastleFR:
4285 case BlackHSideCastleFR:
4286 case BlackASideCastleFR:
4287 /* End of code added by Tord */
4288 case IllegalMove: /* bug or odd chess variant */
4289 *fromX = currentMoveString[0] - AAA;
4290 *fromY = currentMoveString[1] - ONE;
4291 *toX = currentMoveString[2] - AAA;
4292 *toY = currentMoveString[3] - ONE;
4293 *promoChar = currentMoveString[4];
4294 if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
4295 *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
4296 if (appData.debugMode) {
4297 fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
4299 *fromX = *fromY = *toX = *toY = 0;
4302 if (appData.testLegality) {
4303 return (*moveType != IllegalMove);
4305 return !(fromX == fromY && toX == toY);
4310 *fromX = *moveType == WhiteDrop ?
4311 (int) CharToPiece(ToUpper(currentMoveString[0])) :
4312 (int) CharToPiece(ToLower(currentMoveString[0]));
4314 *toX = currentMoveString[2] - AAA;
4315 *toY = currentMoveString[3] - ONE;
4316 *promoChar = NULLCHAR;
4320 case ImpossibleMove:
4321 case (ChessMove) 0: /* end of file */
4330 if (appData.debugMode) {
4331 fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
4334 *fromX = *fromY = *toX = *toY = 0;
4335 *promoChar = NULLCHAR;
4340 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
4341 // All positions will have equal probability, but the current method will not provide a unique
4342 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
4348 int piecesLeft[(int)BlackPawn];
4349 int seed, nrOfShuffles;
4351 void GetPositionNumber()
4352 { // sets global variable seed
4355 seed = appData.defaultFrcPosition;
4356 if(seed < 0) { // randomize based on time for negative FRC position numbers
4357 for(i=0; i<50; i++) seed += random();
4358 seed = random() ^ random() >> 8 ^ random() << 8;
4359 if(seed<0) seed = -seed;
4363 int put(Board board, int pieceType, int rank, int n, int shade)
4364 // put the piece on the (n-1)-th empty squares of the given shade
4368 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
4369 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
4370 board[rank][i] = (ChessSquare) pieceType;
4371 squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
4373 piecesLeft[pieceType]--;
4381 void AddOnePiece(Board board, int pieceType, int rank, int shade)
4382 // calculate where the next piece goes, (any empty square), and put it there
4386 i = seed % squaresLeft[shade];
4387 nrOfShuffles *= squaresLeft[shade];
4388 seed /= squaresLeft[shade];
4389 put(board, pieceType, rank, i, shade);
4392 void AddTwoPieces(Board board, int pieceType, int rank)
4393 // calculate where the next 2 identical pieces go, (any empty square), and put it there
4395 int i, n=squaresLeft[ANY], j=n-1, k;
4397 k = n*(n-1)/2; // nr of possibilities, not counting permutations
4398 i = seed % k; // pick one
4401 while(i >= j) i -= j--;
4402 j = n - 1 - j; i += j;
4403 put(board, pieceType, rank, j, ANY);
4404 put(board, pieceType, rank, i, ANY);
4407 void SetUpShuffle(Board board, int number)
4411 GetPositionNumber(); nrOfShuffles = 1;
4413 squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
4414 squaresLeft[ANY] = BOARD_RGHT - BOARD_LEFT;
4415 squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
4417 for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
4419 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
4420 p = (int) board[0][i];
4421 if(p < (int) BlackPawn) piecesLeft[p] ++;
4422 board[0][i] = EmptySquare;
4425 if(PosFlags(0) & F_ALL_CASTLE_OK) {
4426 // shuffles restricted to allow normal castling put KRR first
4427 if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
4428 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
4429 else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
4430 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
4431 if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
4432 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
4433 if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
4434 put(board, WhiteRook, 0, 0, ANY);
4435 // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
4438 if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
4439 // only for even boards make effort to put pairs of colorbound pieces on opposite colors
4440 for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
4441 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
4442 while(piecesLeft[p] >= 2) {
4443 AddOnePiece(board, p, 0, LITE);
4444 AddOnePiece(board, p, 0, DARK);
4446 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
4449 for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
4450 // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
4451 // but we leave King and Rooks for last, to possibly obey FRC restriction
4452 if(p == (int)WhiteRook) continue;
4453 while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
4454 if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY); // add the odd piece
4457 // now everything is placed, except perhaps King (Unicorn) and Rooks
4459 if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
4460 // Last King gets castling rights
4461 while(piecesLeft[(int)WhiteUnicorn]) {
4462 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
4463 initialRights[2] = initialRights[5] = castlingRights[0][2] = castlingRights[0][5] = i;
4466 while(piecesLeft[(int)WhiteKing]) {
4467 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
4468 initialRights[2] = initialRights[5] = castlingRights[0][2] = castlingRights[0][5] = i;
4473 while(piecesLeft[(int)WhiteKing]) AddOnePiece(board, WhiteKing, 0, ANY);
4474 while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
4477 // Only Rooks can be left; simply place them all
4478 while(piecesLeft[(int)WhiteRook]) {
4479 i = put(board, WhiteRook, 0, 0, ANY);
4480 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
4483 initialRights[1] = initialRights[4] = castlingRights[0][1] = castlingRights[0][4] = i;
4485 initialRights[0] = initialRights[3] = castlingRights[0][0] = castlingRights[0][3] = i;
4488 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
4489 board[BOARD_HEIGHT-1][i] = (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
4492 if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
4495 int SetCharTable( char *table, const char * map )
4496 /* [HGM] moved here from winboard.c because of its general usefulness */
4497 /* Basically a safe strcpy that uses the last character as King */
4499 int result = FALSE; int NrPieces;
4501 if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
4502 && NrPieces >= 12 && !(NrPieces&1)) {
4503 int i; /* [HGM] Accept even length from 12 to 34 */
4505 for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
4506 for( i=0; i<NrPieces/2-1; i++ ) {
4508 table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
4510 table[(int) WhiteKing] = map[NrPieces/2-1];
4511 table[(int) BlackKing] = map[NrPieces-1];
4519 void Prelude(Board board)
4520 { // [HGM] superchess: random selection of exo-pieces
4521 int i, j, k; ChessSquare p;
4522 static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
4524 GetPositionNumber(); // use FRC position number
4526 if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
4527 SetCharTable(pieceToChar, appData.pieceToCharTable);
4528 for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
4529 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
4532 j = seed%4; seed /= 4;
4533 p = board[0][BOARD_LEFT+j]; board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
4534 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
4535 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
4536 j = seed%3 + (seed%3 >= j); seed /= 3;
4537 p = board[0][BOARD_LEFT+j]; board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
4538 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
4539 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
4540 j = seed%3; seed /= 3;
4541 p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
4542 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
4543 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
4544 j = seed%2 + (seed%2 >= j); seed /= 2;
4545 p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
4546 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
4547 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
4548 j = seed%4; seed /= 4; put(board, exoPieces[3], 0, j, ANY);
4549 j = seed%3; seed /= 3; put(board, exoPieces[2], 0, j, ANY);
4550 j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
4551 put(board, exoPieces[0], 0, 0, ANY);
4552 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
4556 InitPosition(redraw)
4559 ChessSquare (* pieces)[BOARD_SIZE];
4560 int i, j, pawnRow, overrule,
4561 oldx = gameInfo.boardWidth,
4562 oldy = gameInfo.boardHeight,
4563 oldh = gameInfo.holdingsWidth,
4564 oldv = gameInfo.variant;
4566 if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
4568 /* [AS] Initialize pv info list [HGM] and game status */
4570 for( i=0; i<MAX_MOVES; i++ ) {
4571 pvInfoList[i].depth = 0;
4572 epStatus[i]=EP_NONE;
4573 for( j=0; j<BOARD_SIZE; j++ ) castlingRights[i][j] = -1;
4576 initialRulePlies = 0; /* 50-move counter start */
4578 castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
4579 castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
4583 /* [HGM] logic here is completely changed. In stead of full positions */
4584 /* the initialized data only consist of the two backranks. The switch */
4585 /* selects which one we will use, which is than copied to the Board */
4586 /* initialPosition, which for the rest is initialized by Pawns and */
4587 /* empty squares. This initial position is then copied to boards[0], */
4588 /* possibly after shuffling, so that it remains available. */
4590 gameInfo.holdingsWidth = 0; /* default board sizes */
4591 gameInfo.boardWidth = 8;
4592 gameInfo.boardHeight = 8;
4593 gameInfo.holdingsSize = 0;
4594 nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
4595 for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1; /* but no rights yet */
4596 SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
4598 switch (gameInfo.variant) {
4599 case VariantFischeRandom:
4600 shuffleOpenings = TRUE;
4604 case VariantShatranj:
4605 pieces = ShatranjArray;
4606 nrCastlingRights = 0;
4607 SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
4609 case VariantTwoKings:
4610 pieces = twoKingsArray;
4612 case VariantCapaRandom:
4613 shuffleOpenings = TRUE;
4614 case VariantCapablanca:
4615 pieces = CapablancaArray;
4616 gameInfo.boardWidth = 10;
4617 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
4620 pieces = GothicArray;
4621 gameInfo.boardWidth = 10;
4622 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
4625 pieces = JanusArray;
4626 gameInfo.boardWidth = 10;
4627 SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
4628 nrCastlingRights = 6;
4629 castlingRights[0][0] = initialRights[0] = BOARD_RGHT-1;
4630 castlingRights[0][1] = initialRights[1] = BOARD_LEFT;
4631 castlingRights[0][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
4632 castlingRights[0][3] = initialRights[3] = BOARD_RGHT-1;
4633 castlingRights[0][4] = initialRights[4] = BOARD_LEFT;
4634 castlingRights[0][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
4637 pieces = FalconArray;
4638 gameInfo.boardWidth = 10;
4639 SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
4641 case VariantXiangqi:
4642 pieces = XiangqiArray;
4643 gameInfo.boardWidth = 9;
4644 gameInfo.boardHeight = 10;
4645 nrCastlingRights = 0;
4646 SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
4649 pieces = ShogiArray;
4650 gameInfo.boardWidth = 9;
4651 gameInfo.boardHeight = 9;
4652 gameInfo.holdingsSize = 7;
4653 nrCastlingRights = 0;
4654 SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
4656 case VariantCourier:
4657 pieces = CourierArray;
4658 gameInfo.boardWidth = 12;
4659 nrCastlingRights = 0;
4660 SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
4661 for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1;
4663 case VariantKnightmate:
4664 pieces = KnightmateArray;
4665 SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
4668 pieces = fairyArray;
4669 SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVSLUKpnbrqfeacwmohijgdvsluk");
4672 pieces = GreatArray;
4673 gameInfo.boardWidth = 10;
4674 SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
4675 gameInfo.holdingsSize = 8;
4679 SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
4680 gameInfo.holdingsSize = 8;
4681 startedFromSetupPosition = TRUE;
4683 case VariantCrazyhouse:
4684 case VariantBughouse:
4686 SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
4687 gameInfo.holdingsSize = 5;
4689 case VariantWildCastle:
4691 /* !!?shuffle with kings guaranteed to be on d or e file */
4692 shuffleOpenings = 1;
4694 case VariantNoCastle:
4696 nrCastlingRights = 0;
4697 for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1;
4698 /* !!?unconstrained back-rank shuffle */
4699 shuffleOpenings = 1;
4704 if(appData.NrFiles >= 0) {
4705 if(gameInfo.boardWidth != appData.NrFiles) overrule++;
4706 gameInfo.boardWidth = appData.NrFiles;
4708 if(appData.NrRanks >= 0) {
4709 gameInfo.boardHeight = appData.NrRanks;
4711 if(appData.holdingsSize >= 0) {
4712 i = appData.holdingsSize;
4713 if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
4714 gameInfo.holdingsSize = i;
4716 if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
4717 if(BOARD_HEIGHT > BOARD_SIZE || BOARD_WIDTH > BOARD_SIZE)
4718 DisplayFatalError(_("Recompile to support this BOARD_SIZE!"), 0, 2);
4720 pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
4721 if(pawnRow < 1) pawnRow = 1;
4723 /* User pieceToChar list overrules defaults */
4724 if(appData.pieceToCharTable != NULL)
4725 SetCharTable(pieceToChar, appData.pieceToCharTable);
4727 for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
4729 if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
4730 s = (ChessSquare) 0; /* account holding counts in guard band */
4731 for( i=0; i<BOARD_HEIGHT; i++ )
4732 initialPosition[i][j] = s;
4734 if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
4735 initialPosition[0][j] = pieces[0][j-gameInfo.holdingsWidth];
4736 initialPosition[pawnRow][j] = WhitePawn;
4737 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = BlackPawn;
4738 if(gameInfo.variant == VariantXiangqi) {
4740 initialPosition[pawnRow][j] =
4741 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
4742 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
4743 initialPosition[2][j] = WhiteCannon;
4744 initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
4748 initialPosition[BOARD_HEIGHT-1][j] = pieces[1][j-gameInfo.holdingsWidth];
4750 if( (gameInfo.variant == VariantShogi) && !overrule ) {
4753 initialPosition[1][j] = WhiteBishop;
4754 initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
4756 initialPosition[1][j] = WhiteRook;
4757 initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
4760 if( nrCastlingRights == -1) {
4761 /* [HGM] Build normal castling rights (must be done after board sizing!) */
4762 /* This sets default castling rights from none to normal corners */
4763 /* Variants with other castling rights must set them themselves above */
4764 nrCastlingRights = 6;
4766 castlingRights[0][0] = initialRights[0] = BOARD_RGHT-1;
4767 castlingRights[0][1] = initialRights[1] = BOARD_LEFT;
4768 castlingRights[0][2] = initialRights[2] = BOARD_WIDTH>>1;
4769 castlingRights[0][3] = initialRights[3] = BOARD_RGHT-1;
4770 castlingRights[0][4] = initialRights[4] = BOARD_LEFT;
4771 castlingRights[0][5] = initialRights[5] = BOARD_WIDTH>>1;
4774 if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
4775 if(gameInfo.variant == VariantGreat) { // promotion commoners
4776 initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
4777 initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
4778 initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
4779 initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
4781 if (appData.debugMode) {
4782 fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
4784 if(shuffleOpenings) {
4785 SetUpShuffle(initialPosition, appData.defaultFrcPosition);
4786 startedFromSetupPosition = TRUE;
4788 if(startedFromPositionFile) {
4789 /* [HGM] loadPos: use PositionFile for every new game */
4790 CopyBoard(initialPosition, filePosition);
4791 for(i=0; i<nrCastlingRights; i++)
4792 castlingRights[0][i] = initialRights[i] = fileRights[i];
4793 startedFromSetupPosition = TRUE;
4796 CopyBoard(boards[0], initialPosition);
4798 if(oldx != gameInfo.boardWidth ||
4799 oldy != gameInfo.boardHeight ||
4800 oldh != gameInfo.holdingsWidth
4802 || oldv == VariantGothic || // For licensing popups
4803 gameInfo.variant == VariantGothic
4806 || oldv == VariantFalcon ||
4807 gameInfo.variant == VariantFalcon
4810 InitDrawingSizes(-2 ,0);
4813 DrawPosition(TRUE, boards[currentMove]);
4817 SendBoard(cps, moveNum)
4818 ChessProgramState *cps;
4821 char message[MSG_SIZ];
4823 if (cps->useSetboard) {
4824 char* fen = PositionToFEN(moveNum, cps->fenOverride);
4825 sprintf(message, "setboard %s\n", fen);
4826 SendToProgram(message, cps);
4832 /* Kludge to set black to move, avoiding the troublesome and now
4833 * deprecated "black" command.
4835 if (!WhiteOnMove(moveNum)) SendToProgram("a2a3\n", cps);
4837 SendToProgram("edit\n", cps);
4838 SendToProgram("#\n", cps);
4839 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
4840 bp = &boards[moveNum][i][BOARD_LEFT];
4841 for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
4842 if ((int) *bp < (int) BlackPawn) {
4843 sprintf(message, "%c%c%c\n", PieceToChar(*bp),
4845 if(message[0] == '+' || message[0] == '~') {
4846 sprintf(message, "%c%c%c+\n",
4847 PieceToChar((ChessSquare)(DEMOTED *bp)),
4850 if(cps->alphaRank) { /* [HGM] shogi: translate coords */
4851 message[1] = BOARD_RGHT - 1 - j + '1';
4852 message[2] = BOARD_HEIGHT - 1 - i + 'a';
4854 SendToProgram(message, cps);
4859 SendToProgram("c\n", cps);
4860 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
4861 bp = &boards[moveNum][i][BOARD_LEFT];
4862 for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
4863 if (((int) *bp != (int) EmptySquare)
4864 && ((int) *bp >= (int) BlackPawn)) {
4865 sprintf(message, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
4867 if(message[0] == '+' || message[0] == '~') {
4868 sprintf(message, "%c%c%c+\n",
4869 PieceToChar((ChessSquare)(DEMOTED *bp)),
4872 if(cps->alphaRank) { /* [HGM] shogi: translate coords */
4873 message[1] = BOARD_RGHT - 1 - j + '1';
4874 message[2] = BOARD_HEIGHT - 1 - i + 'a';
4876 SendToProgram(message, cps);
4881 SendToProgram(".\n", cps);
4883 setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
4887 HasPromotionChoice(int fromX, int fromY, int toX, int toY, char *promoChoice)
4889 /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
4890 /* [HGM] add Shogi promotions */
4891 int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
4896 if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
4897 if(toX < BOARD_LEFT || toX >= BOARD_RGHT) return FALSE; // move into holdings
4899 if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
4900 !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
4903 piece = boards[currentMove][fromY][fromX];
4904 if(gameInfo.variant == VariantShogi) {
4905 promotionZoneSize = 3;
4906 highestPromotingPiece = (int)WhiteFerz;
4909 // next weed out all moves that do not touch the promotion zone at all
4910 if((int)piece >= BlackPawn) {
4911 if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
4913 highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
4915 if( toY < BOARD_HEIGHT - promotionZoneSize &&
4916 fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
4919 if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
4921 // weed out mandatory Shogi promotions
4922 if(gameInfo.variant == VariantShogi) {
4923 if(piece >= BlackPawn) {
4924 if(toY == 0 && piece == BlackPawn ||
4925 toY == 0 && piece == BlackQueen ||
4926 toY <= 1 && piece == BlackKnight) {
4931 if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
4932 toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
4933 toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
4940 // weed out obviously illegal Pawn moves
4941 if(appData.testLegality && (piece == WhitePawn || piece == BlackPawn) ) {
4942 if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
4943 if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
4944 if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
4945 if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
4946 // note we are not allowed to test for valid (non-)capture, due to premove
4949 // we either have a choice what to promote to, or (in Shogi) whether to promote
4950 if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier) {
4951 *promoChoice = PieceToChar(BlackFerz); // no choice
4954 if(appData.alwaysPromoteToQueen) { // predetermined
4955 if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantLosers)
4956 *promoChoice = PieceToChar(BlackKing); // in Suicide Q is the last thing we want
4957 else *promoChoice = PieceToChar(BlackQueen);
4961 // suppress promotion popup on illegal moves that are not premoves
4962 premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
4963 gameMode == IcsPlayingBlack && WhiteOnMove(currentMove);
4964 if(appData.testLegality && !premove) {
4965 moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
4966 epStatus[currentMove], castlingRights[currentMove],
4967 fromY, fromX, toY, toX, NULLCHAR);
4968 if(moveType != WhitePromotionQueen && moveType != BlackPromotionQueen &&
4969 moveType != WhitePromotionKnight && moveType != BlackPromotionKnight)
4977 InPalace(row, column)
4979 { /* [HGM] for Xiangqi */
4980 if( (row < 3 || row > BOARD_HEIGHT-4) &&
4981 column < (BOARD_WIDTH + 4)/2 &&
4982 column > (BOARD_WIDTH - 5)/2 ) return TRUE;
4987 PieceForSquare (x, y)
4991 if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
4994 return boards[currentMove][y][x];
4998 OKToStartUserMove(x, y)
5001 ChessSquare from_piece;
5004 if (matchMode) return FALSE;
5005 if (gameMode == EditPosition) return TRUE;
5007 if (x >= 0 && y >= 0)
5008 from_piece = boards[currentMove][y][x];
5010 from_piece = EmptySquare;
5012 if (from_piece == EmptySquare) return FALSE;
5014 white_piece = (int)from_piece >= (int)WhitePawn &&
5015 (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
5018 case PlayFromGameFile:
5020 case TwoMachinesPlay:
5028 case MachinePlaysWhite:
5029 case IcsPlayingBlack:
5030 if (appData.zippyPlay) return FALSE;
5032 DisplayMoveError(_("You are playing Black"));
5037 case MachinePlaysBlack:
5038 case IcsPlayingWhite:
5039 if (appData.zippyPlay) return FALSE;
5041 DisplayMoveError(_("You are playing White"));
5047 if (!white_piece && WhiteOnMove(currentMove)) {
5048 DisplayMoveError(_("It is White's turn"));
5051 if (white_piece && !WhiteOnMove(currentMove)) {
5052 DisplayMoveError(_("It is Black's turn"));
5055 if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
5056 /* Editing correspondence game history */
5057 /* Could disallow this or prompt for confirmation */
5060 if (currentMove < forwardMostMove) {
5061 /* Discarding moves */
5062 /* Could prompt for confirmation here,
5063 but I don't think that's such a good idea */
5064 forwardMostMove = currentMove;
5068 case BeginningOfGame:
5069 if (appData.icsActive) return FALSE;
5070 if (!appData.noChessProgram) {
5072 DisplayMoveError(_("You are playing White"));
5079 if (!white_piece && WhiteOnMove(currentMove)) {
5080 DisplayMoveError(_("It is White's turn"));
5083 if (white_piece && !WhiteOnMove(currentMove)) {
5084 DisplayMoveError(_("It is Black's turn"));
5093 if (currentMove != forwardMostMove && gameMode != AnalyzeMode
5094 && gameMode != AnalyzeFile && gameMode != Training) {
5095 DisplayMoveError(_("Displayed position is not current"));
5101 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
5102 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
5103 int lastLoadGameUseList = FALSE;
5104 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
5105 ChessMove lastLoadGameStart = (ChessMove) 0;
5108 UserMoveTest(fromX, fromY, toX, toY, promoChar, captureOwn)
5109 int fromX, fromY, toX, toY;
5114 ChessSquare pdown, pup;
5116 /* Check if the user is playing in turn. This is complicated because we
5117 let the user "pick up" a piece before it is his turn. So the piece he
5118 tried to pick up may have been captured by the time he puts it down!
5119 Therefore we use the color the user is supposed to be playing in this
5120 test, not the color of the piece that is currently on the starting
5121 square---except in EditGame mode, where the user is playing both
5122 sides; fortunately there the capture race can't happen. (It can
5123 now happen in IcsExamining mode, but that's just too bad. The user
5124 will get a somewhat confusing message in that case.)
5128 case PlayFromGameFile:
5130 case TwoMachinesPlay:
5134 /* We switched into a game mode where moves are not accepted,
5135 perhaps while the mouse button was down. */
5136 return ImpossibleMove;
5138 case MachinePlaysWhite:
5139 /* User is moving for Black */
5140 if (WhiteOnMove(currentMove)) {
5141 DisplayMoveError(_("It is White's turn"));
5142 return ImpossibleMove;
5146 case MachinePlaysBlack:
5147 /* User is moving for White */
5148 if (!WhiteOnMove(currentMove)) {
5149 DisplayMoveError(_("It is Black's turn"));
5150 return ImpossibleMove;
5156 case BeginningOfGame:
5159 if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
5160 (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
5161 /* User is moving for Black */
5162 if (WhiteOnMove(currentMove)) {
5163 DisplayMoveError(_("It is White's turn"));
5164 return ImpossibleMove;
5167 /* User is moving for White */
5168 if (!WhiteOnMove(currentMove)) {
5169 DisplayMoveError(_("It is Black's turn"));
5170 return ImpossibleMove;
5175 case IcsPlayingBlack:
5176 /* User is moving for Black */
5177 if (WhiteOnMove(currentMove)) {
5178 if (!appData.premove) {
5179 DisplayMoveError(_("It is White's turn"));
5180 } else if (toX >= 0 && toY >= 0) {
5183 premoveFromX = fromX;
5184 premoveFromY = fromY;
5185 premovePromoChar = promoChar;
5187 if (appData.debugMode)
5188 fprintf(debugFP, "Got premove: fromX %d,"
5189 "fromY %d, toX %d, toY %d\n",
5190 fromX, fromY, toX, toY);
5192 return ImpossibleMove;
5196 case IcsPlayingWhite:
5197 /* User is moving for White */
5198 if (!WhiteOnMove(currentMove)) {
5199 if (!appData.premove) {
5200 DisplayMoveError(_("It is Black's turn"));
5201 } else if (toX >= 0 && toY >= 0) {
5204 premoveFromX = fromX;
5205 premoveFromY = fromY;
5206 premovePromoChar = promoChar;
5208 if (appData.debugMode)
5209 fprintf(debugFP, "Got premove: fromX %d,"
5210 "fromY %d, toX %d, toY %d\n",
5211 fromX, fromY, toX, toY);
5213 return ImpossibleMove;
5221 /* EditPosition, empty square, or different color piece;
5222 click-click move is possible */
5223 if (toX == -2 || toY == -2) {
5224 boards[0][fromY][fromX] = EmptySquare;
5225 return AmbiguousMove;
5226 } else if (toX >= 0 && toY >= 0) {
5227 boards[0][toY][toX] = boards[0][fromY][fromX];
5228 boards[0][fromY][fromX] = EmptySquare;
5229 return AmbiguousMove;
5231 return ImpossibleMove;
5234 pdown = boards[currentMove][fromY][fromX];
5235 pup = boards[currentMove][toY][toX];
5237 /* [HGM] If move started in holdings, it means a drop */
5238 if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) {
5239 if( pup != EmptySquare ) return ImpossibleMove;
5240 if(appData.testLegality) {
5241 /* it would be more logical if LegalityTest() also figured out
5242 * which drops are legal. For now we forbid pawns on back rank.
5243 * Shogi is on its own here...
5245 if( (pdown == WhitePawn || pdown == BlackPawn) &&
5246 (toY == 0 || toY == BOARD_HEIGHT -1 ) )
5247 return(ImpossibleMove); /* no pawn drops on 1st/8th */
5249 return WhiteDrop; /* Not needed to specify white or black yet */
5252 userOfferedDraw = FALSE;
5254 /* [HGM] always test for legality, to get promotion info */
5255 moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5256 epStatus[currentMove], castlingRights[currentMove],
5257 fromY, fromX, toY, toX, promoChar);
5258 /* [HGM] but possibly ignore an IllegalMove result */
5259 if (appData.testLegality) {
5260 if (moveType == IllegalMove || moveType == ImpossibleMove) {
5261 DisplayMoveError(_("Illegal move"));
5262 return ImpossibleMove;
5265 if(appData.debugMode) fprintf(debugFP, "moveType 3 = %d, promochar = %x\n", moveType, promoChar);
5267 /* [HGM] <popupFix> in stead of calling FinishMove directly, this
5268 function is made into one that returns an OK move type if FinishMove
5269 should be called. This to give the calling driver routine the
5270 opportunity to finish the userMove input with a promotion popup,
5271 without bothering the user with this for invalid or illegal moves */
5273 /* FinishMove(moveType, fromX, fromY, toX, toY, promoChar); */
5276 /* Common tail of UserMoveEvent and DropMenuEvent */
5278 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
5280 int fromX, fromY, toX, toY;
5281 /*char*/int promoChar;
5284 if(appData.debugMode) fprintf(debugFP, "moveType 5 = %d, promochar = %x\n", moveType, promoChar);
5285 if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) && promoChar != NULLCHAR) {
5286 // [HGM] superchess: suppress promotions to non-available piece
5287 int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
5288 if(WhiteOnMove(currentMove)) {
5289 if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
5291 if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
5295 /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
5296 move type in caller when we know the move is a legal promotion */
5297 if(moveType == NormalMove && promoChar)
5298 moveType = PromoCharToMoveType(WhiteOnMove(currentMove), promoChar);
5299 if(appData.debugMode) fprintf(debugFP, "moveType 1 = %d, promochar = %x\n", moveType, promoChar);
5300 /* [HGM] convert drag-and-drop piece drops to standard form */
5301 if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) {
5302 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
5303 if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n",
5304 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
5305 // fromX = boards[currentMove][fromY][fromX];
5306 // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
5307 if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
5308 fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
5309 while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++;
5313 /* [HGM] <popupFix> The following if has been moved here from
5314 UserMoveEvent(). Because it seemed to belon here (why not allow
5315 piece drops in training games?), and because it can only be
5316 performed after it is known to what we promote. */
5317 if (gameMode == Training) {
5318 /* compare the move played on the board to the next move in the
5319 * game. If they match, display the move and the opponent's response.
5320 * If they don't match, display an error message.
5323 Board testBoard; char testRights[BOARD_SIZE]; char testStatus;
5324 CopyBoard(testBoard, boards[currentMove]);
5325 ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard, testRights, &testStatus);
5327 if (CompareBoards(testBoard, boards[currentMove+1])) {
5328 ForwardInner(currentMove+1);
5330 /* Autoplay the opponent's response.
5331 * if appData.animate was TRUE when Training mode was entered,
5332 * the response will be animated.
5334 saveAnimate = appData.animate;
5335 appData.animate = animateTraining;
5336 ForwardInner(currentMove+1);
5337 appData.animate = saveAnimate;
5339 /* check for the end of the game */
5340 if (currentMove >= forwardMostMove) {
5341 gameMode = PlayFromGameFile;
5343 SetTrainingModeOff();
5344 DisplayInformation(_("End of game"));
5347 DisplayError(_("Incorrect move"), 0);
5352 /* Ok, now we know that the move is good, so we can kill
5353 the previous line in Analysis Mode */
5354 if (gameMode == AnalyzeMode && currentMove < forwardMostMove) {
5355 forwardMostMove = currentMove;
5358 /* If we need the chess program but it's dead, restart it */
5359 ResurrectChessProgram();
5361 /* A user move restarts a paused game*/
5365 thinkOutput[0] = NULLCHAR;
5367 MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
5369 if (gameMode == BeginningOfGame) {
5370 if (appData.noChessProgram) {
5371 gameMode = EditGame;
5375 gameMode = MachinePlaysBlack;
5378 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
5380 if (first.sendName) {
5381 sprintf(buf, "name %s\n", gameInfo.white);
5382 SendToProgram(buf, &first);
5388 if(appData.debugMode) fprintf(debugFP, "moveType 2 = %d, promochar = %x\n", moveType, promoChar);
5389 /* Relay move to ICS or chess engine */
5390 if (appData.icsActive) {
5391 if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
5392 gameMode == IcsExamining) {
5393 SendMoveToICS(moveType, fromX, fromY, toX, toY);
5397 if (first.sendTime && (gameMode == BeginningOfGame ||
5398 gameMode == MachinePlaysWhite ||
5399 gameMode == MachinePlaysBlack)) {
5400 SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
5402 if (gameMode != EditGame && gameMode != PlayFromGameFile) {
5403 // [HGM] book: if program might be playing, let it use book
5404 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
5405 first.maybeThinking = TRUE;
5406 } else SendMoveToProgram(forwardMostMove-1, &first);
5407 if (currentMove == cmailOldMove + 1) {
5408 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
5412 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5416 switch (MateTest(boards[currentMove], PosFlags(currentMove),
5417 EP_UNKNOWN, castlingRights[currentMove]) ) {
5423 if (WhiteOnMove(currentMove)) {
5424 GameEnds(BlackWins, "Black mates", GE_PLAYER);
5426 GameEnds(WhiteWins, "White mates", GE_PLAYER);
5430 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
5435 case MachinePlaysBlack:
5436 case MachinePlaysWhite:
5437 /* disable certain menu options while machine is thinking */
5438 SetMachineThinkingEnables();
5445 if(bookHit) { // [HGM] book: simulate book reply
5446 static char bookMove[MSG_SIZ]; // a bit generous?
5448 programStats.nodes = programStats.depth = programStats.time =
5449 programStats.score = programStats.got_only_move = 0;
5450 sprintf(programStats.movelist, "%s (xbook)", bookHit);
5452 strcpy(bookMove, "move ");
5453 strcat(bookMove, bookHit);
5454 HandleMachineMove(bookMove, &first);
5460 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
5461 int fromX, fromY, toX, toY;
5464 /* [HGM] This routine was added to allow calling of its two logical
5465 parts from other modules in the old way. Before, UserMoveEvent()
5466 automatically called FinishMove() if the move was OK, and returned
5467 otherwise. I separated the two, in order to make it possible to
5468 slip a promotion popup in between. But that it always needs two
5469 calls, to the first part, (now called UserMoveTest() ), and to
5470 FinishMove if the first part succeeded. Calls that do not need
5471 to do anything in between, can call this routine the old way.
5473 ChessMove moveType = UserMoveTest(fromX, fromY, toX, toY, promoChar, FALSE);
5474 if(appData.debugMode) fprintf(debugFP, "moveType 4 = %d, promochar = %x\n", moveType, promoChar);
5475 if(moveType == AmbiguousMove)
5476 DrawPosition(FALSE, boards[currentMove]);
5477 else if(moveType != ImpossibleMove && moveType != Comment)
5478 FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
5481 void LeftClick(ClickType clickType, int xPix, int yPix)
5484 Boolean saveAnimate;
5485 static int second = 0, promotionChoice = 0;
5486 char promoChoice = NULLCHAR;
5488 if (clickType == Press) ErrorPopDown();
5490 x = EventToSquare(xPix, BOARD_WIDTH);
5491 y = EventToSquare(yPix, BOARD_HEIGHT);
5492 if (!flipView && y >= 0) {
5493 y = BOARD_HEIGHT - 1 - y;
5495 if (flipView && x >= 0) {
5496 x = BOARD_WIDTH - 1 - x;
5499 if(promotionChoice) { // we are waiting for a click to indicate promotion piece
5500 if(clickType == Release) return; // ignore upclick of click-click destination
5501 promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
5502 if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
5503 if(gameInfo.holdingsWidth &&
5504 (WhiteOnMove(currentMove)
5505 ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y > 0
5506 : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT-1) ) {
5507 // click in right holdings, for determining promotion piece
5508 ChessSquare p = boards[currentMove][y][x];
5509 if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
5510 if(p != EmptySquare) {
5511 FinishMove(NormalMove, fromX, fromY, toX, toY, ToLower(PieceToChar(p)));
5516 DrawPosition(FALSE, boards[currentMove]);
5520 /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
5521 if(clickType == Press
5522 && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
5523 || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
5524 || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
5528 if (clickType == Press) {
5530 if (OKToStartUserMove(x, y)) {
5534 DragPieceBegin(xPix, yPix);
5535 if (appData.highlightDragging) {
5536 SetHighlights(x, y, -1, -1);
5544 if (clickType == Press && gameMode != EditPosition) {
5549 // ignore off-board to clicks
5550 if(y < 0 || x < 0) return;
5552 /* Check if clicking again on the same color piece */
5553 fromP = boards[currentMove][fromY][fromX];
5554 toP = boards[currentMove][y][x];
5555 frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom;
5556 if ((WhitePawn <= fromP && fromP <= WhiteKing &&
5557 WhitePawn <= toP && toP <= WhiteKing &&
5558 !(fromP == WhiteKing && toP == WhiteRook && frc) &&
5559 !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
5560 (BlackPawn <= fromP && fromP <= BlackKing &&
5561 BlackPawn <= toP && toP <= BlackKing &&
5562 !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
5563 !(fromP == BlackKing && toP == BlackRook && frc))) {
5564 /* Clicked again on same color piece -- changed his mind */
5565 second = (x == fromX && y == fromY);
5566 if (appData.highlightDragging) {
5567 SetHighlights(x, y, -1, -1);
5571 if (OKToStartUserMove(x, y)) {
5574 DragPieceBegin(xPix, yPix);
5578 // ignore to-clicks in holdings
5579 if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
5582 if (clickType == Release && (x == fromX && y == fromY ||
5583 x < BOARD_LEFT || x >= BOARD_RGHT)) {
5585 // treat drags into holding as click on start square
5586 x = fromX; y = fromY;
5588 DragPieceEnd(xPix, yPix);
5589 if (appData.animateDragging) {
5590 /* Undo animation damage if any */
5591 DrawPosition(FALSE, NULL);
5594 /* Second up/down in same square; just abort move */
5599 ClearPremoveHighlights();
5601 /* First upclick in same square; start click-click mode */
5602 SetHighlights(x, y, -1, -1);
5607 /* we now have a different from- and to-square */
5608 /* Completed move */
5611 saveAnimate = appData.animate;
5612 if (clickType == Press) {
5613 /* Finish clickclick move */
5614 if (appData.animate || appData.highlightLastMove) {
5615 SetHighlights(fromX, fromY, toX, toY);
5620 /* Finish drag move */
5621 if (appData.highlightLastMove) {
5622 SetHighlights(fromX, fromY, toX, toY);
5626 DragPieceEnd(xPix, yPix);
5627 /* Don't animate move and drag both */
5628 appData.animate = FALSE;
5630 if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice)) {
5631 SetHighlights(fromX, fromY, toX, toY);
5632 if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
5633 // [HGM] super: promotion to captured piece selected from holdings
5634 ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
5635 promotionChoice = TRUE;
5636 // kludge follows to temporarily execute move on display, without promoting yet
5637 boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
5638 boards[currentMove][toY][toX] = p;
5639 DrawPosition(FALSE, boards[currentMove]);
5640 boards[currentMove][fromY][fromX] = p; // take back, but display stays
5641 boards[currentMove][toY][toX] = q;
5642 DisplayMessage("Click in holdings to choose piece", "");
5647 UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
5648 if (!appData.highlightLastMove || gotPremove) ClearHighlights();
5649 if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
5652 appData.animate = saveAnimate;
5653 if (appData.animate || appData.animateDragging) {
5654 /* Undo animation damage if needed */
5655 DrawPosition(FALSE, NULL);
5659 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
5661 // char * hint = lastHint;
5662 FrontEndProgramStats stats;
5664 stats.which = cps == &first ? 0 : 1;
5665 stats.depth = cpstats->depth;
5666 stats.nodes = cpstats->nodes;
5667 stats.score = cpstats->score;
5668 stats.time = cpstats->time;
5669 stats.pv = cpstats->movelist;
5670 stats.hint = lastHint;
5671 stats.an_move_index = 0;
5672 stats.an_move_count = 0;
5674 if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
5675 stats.hint = cpstats->move_name;
5676 stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
5677 stats.an_move_count = cpstats->nr_moves;
5680 SetProgramStats( &stats );
5683 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
5684 { // [HGM] book: this routine intercepts moves to simulate book replies
5685 char *bookHit = NULL;
5687 //first determine if the incoming move brings opponent into his book
5688 if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
5689 bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
5690 if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
5691 if(bookHit != NULL && !cps->bookSuspend) {
5692 // make sure opponent is not going to reply after receiving move to book position
5693 SendToProgram("force\n", cps);
5694 cps->bookSuspend = TRUE; // flag indicating it has to be restarted
5696 if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
5697 // now arrange restart after book miss
5699 // after a book hit we never send 'go', and the code after the call to this routine
5700 // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
5702 if (cps->useUsermove) sprintf(buf, "usermove "); // sorry, no SAN yet :(
5703 sprintf(buf, "%s\n", bookHit); // force book move into program supposed to play it
5704 SendToProgram(buf, cps);
5705 if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
5706 } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
5707 SendToProgram("go\n", cps);
5708 cps->bookSuspend = FALSE; // after a 'go' we are never suspended
5709 } else { // 'go' might be sent based on 'firstMove' after this routine returns
5710 if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
5711 SendToProgram("go\n", cps);
5712 cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
5714 return bookHit; // notify caller of hit, so it can take action to send move to opponent
5718 ChessProgramState *savedState;
5719 void DeferredBookMove(void)
5721 if(savedState->lastPing != savedState->lastPong)
5722 ScheduleDelayedEvent(DeferredBookMove, 10);
5724 HandleMachineMove(savedMessage, savedState);
5728 HandleMachineMove(message, cps)
5730 ChessProgramState *cps;
5732 char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
5733 char realname[MSG_SIZ];
5734 int fromX, fromY, toX, toY;
5741 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
5743 * Kludge to ignore BEL characters
5745 while (*message == '\007') message++;
5748 * [HGM] engine debug message: ignore lines starting with '#' character
5750 if(cps->debug && *message == '#') return;
5753 * Look for book output
5755 if (cps == &first && bookRequested) {
5756 if (message[0] == '\t' || message[0] == ' ') {
5757 /* Part of the book output is here; append it */
5758 strcat(bookOutput, message);
5759 strcat(bookOutput, " \n");
5761 } else if (bookOutput[0] != NULLCHAR) {
5762 /* All of book output has arrived; display it */
5763 char *p = bookOutput;
5764 while (*p != NULLCHAR) {
5765 if (*p == '\t') *p = ' ';
5768 DisplayInformation(bookOutput);
5769 bookRequested = FALSE;
5770 /* Fall through to parse the current output */
5775 * Look for machine move.
5777 if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
5778 (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
5780 /* This method is only useful on engines that support ping */
5781 if (cps->lastPing != cps->lastPong) {
5782 if (gameMode == BeginningOfGame) {
5783 /* Extra move from before last new; ignore */
5784 if (appData.debugMode) {
5785 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
5788 if (appData.debugMode) {
5789 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
5790 cps->which, gameMode);
5793 SendToProgram("undo\n", cps);
5799 case BeginningOfGame:
5800 /* Extra move from before last reset; ignore */
5801 if (appData.debugMode) {
5802 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
5809 /* Extra move after we tried to stop. The mode test is
5810 not a reliable way of detecting this problem, but it's
5811 the best we can do on engines that don't support ping.
5813 if (appData.debugMode) {
5814 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
5815 cps->which, gameMode);
5817 SendToProgram("undo\n", cps);
5820 case MachinePlaysWhite:
5821 case IcsPlayingWhite:
5822 machineWhite = TRUE;
5825 case MachinePlaysBlack:
5826 case IcsPlayingBlack:
5827 machineWhite = FALSE;
5830 case TwoMachinesPlay:
5831 machineWhite = (cps->twoMachinesColor[0] == 'w');
5834 if (WhiteOnMove(forwardMostMove) != machineWhite) {
5835 if (appData.debugMode) {
5837 "Ignoring move out of turn by %s, gameMode %d"
5838 ", forwardMost %d\n",
5839 cps->which, gameMode, forwardMostMove);
5844 if (appData.debugMode) { int f = forwardMostMove;
5845 fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
5846 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
5848 if(cps->alphaRank) AlphaRank(machineMove, 4);
5849 if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
5850 &fromX, &fromY, &toX, &toY, &promoChar)) {
5851 /* Machine move could not be parsed; ignore it. */
5852 sprintf(buf1, _("Illegal move \"%s\" from %s machine"),
5853 machineMove, cps->which);
5854 DisplayError(buf1, 0);
5855 sprintf(buf1, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
5856 machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
5857 if (gameMode == TwoMachinesPlay) {
5858 GameEnds(machineWhite ? BlackWins : WhiteWins,
5864 /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
5865 /* So we have to redo legality test with true e.p. status here, */
5866 /* to make sure an illegal e.p. capture does not slip through, */
5867 /* to cause a forfeit on a justified illegal-move complaint */
5868 /* of the opponent. */
5869 if( gameMode==TwoMachinesPlay && appData.testLegality
5870 && fromY != DROP_RANK /* [HGM] temporary; should still add legality test for drops */
5873 moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
5874 epStatus[forwardMostMove], castlingRights[forwardMostMove],
5875 fromY, fromX, toY, toX, promoChar);
5876 if (appData.debugMode) {
5878 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
5879 castlingRights[forwardMostMove][i], castlingRank[i]);
5880 fprintf(debugFP, "castling rights\n");
5882 if(moveType == IllegalMove) {
5883 sprintf(buf1, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
5884 machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
5885 GameEnds(machineWhite ? BlackWins : WhiteWins,
5888 } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
5889 /* [HGM] Kludge to handle engines that send FRC-style castling
5890 when they shouldn't (like TSCP-Gothic) */
5892 case WhiteASideCastleFR:
5893 case BlackASideCastleFR:
5895 currentMoveString[2]++;
5897 case WhiteHSideCastleFR:
5898 case BlackHSideCastleFR:
5900 currentMoveString[2]--;
5902 default: ; // nothing to do, but suppresses warning of pedantic compilers
5905 hintRequested = FALSE;
5906 lastHint[0] = NULLCHAR;
5907 bookRequested = FALSE;
5908 /* Program may be pondering now */
5909 cps->maybeThinking = TRUE;
5910 if (cps->sendTime == 2) cps->sendTime = 1;
5911 if (cps->offeredDraw) cps->offeredDraw--;
5914 if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
5916 SendMoveToICS(moveType, fromX, fromY, toX, toY);
5918 if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
5919 char buf[3*MSG_SIZ];
5921 sprintf(buf, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
5922 programStats.score / 100.,
5924 programStats.time / 100.,
5925 (unsigned int)programStats.nodes,
5926 (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
5927 programStats.movelist);
5929 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
5933 /* currentMoveString is set as a side-effect of ParseOneMove */
5934 strcpy(machineMove, currentMoveString);
5935 strcat(machineMove, "\n");
5936 strcpy(moveList[forwardMostMove], machineMove);
5938 /* [AS] Save move info and clear stats for next move */
5939 pvInfoList[ forwardMostMove ].score = programStats.score;
5940 pvInfoList[ forwardMostMove ].depth = programStats.depth;
5941 pvInfoList[ forwardMostMove ].time = programStats.time; // [HGM] PGNtime: take time from engine stats
5942 ClearProgramStats();
5943 thinkOutput[0] = NULLCHAR;
5944 hiddenThinkOutputState = 0;
5946 MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
5948 /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
5949 if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
5952 while( count < adjudicateLossPlies ) {
5953 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
5956 score = -score; /* Flip score for winning side */
5959 if( score > adjudicateLossThreshold ) {
5966 if( count >= adjudicateLossPlies ) {
5967 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5969 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
5970 "Xboard adjudication",
5977 if( gameMode == TwoMachinesPlay ) {
5978 // [HGM] some adjudications useful with buggy engines
5979 int k, count = 0, epFile = epStatus[forwardMostMove]; static int bare = 1;
5980 if(gameInfo.holdingsSize == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
5983 if( appData.testLegality )
5984 { /* [HGM] Some more adjudications for obstinate engines */
5985 int NrWN=0, NrBN=0, NrWB=0, NrBB=0, NrWR=0, NrBR=0,
5986 NrWQ=0, NrBQ=0, NrW=0, NrK=0, bishopsColor = 0,
5987 NrPieces=0, NrPawns=0, PawnAdvance=0, i, j;
5988 static int moveCount = 6;
5990 char *reason = NULL;
5992 /* Count what is on board. */
5993 for(i=0; i<BOARD_HEIGHT; i++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
5994 { ChessSquare p = boards[forwardMostMove][i][j];
5998 { /* count B,N,R and other of each side */
6001 NrK++; break; // [HGM] atomic: count Kings
6005 case WhiteFerz: // [HGM] shatranj: kludge to mke it work in shatranj
6006 bishopsColor |= 1 << ((i^j)&1);
6011 case BlackFerz: // [HGM] shatranj: kludge to mke it work in shatranj
6012 bishopsColor |= 1 << ((i^j)&1);
6027 PawnAdvance += m; NrPawns++;
6029 NrPieces += (p != EmptySquare);
6030 NrW += ((int)p < (int)BlackPawn);
6031 if(gameInfo.variant == VariantXiangqi &&
6032 (p == WhiteFerz || p == WhiteAlfil || p == BlackFerz || p == BlackAlfil)) {
6033 NrPieces--; // [HGM] XQ: do not count purely defensive pieces
6034 NrW -= ((int)p < (int)BlackPawn);
6038 /* Some material-based adjudications that have to be made before stalemate test */
6039 if(gameInfo.variant == VariantAtomic && NrK < 2) {
6040 // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
6041 epStatus[forwardMostMove] = EP_CHECKMATE; // make claimable as if stm is checkmated
6042 if(appData.checkMates) {
6043 SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets move
6044 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6045 GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
6046 "Xboard adjudication: King destroyed", GE_XBOARD );
6051 /* Bare King in Shatranj (loses) or Losers (wins) */
6052 if( NrW == 1 || NrPieces - NrW == 1) {
6053 if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
6054 epStatus[forwardMostMove] = EP_WINS; // mark as win, so it becomes claimable
6055 if(appData.checkMates) {
6056 SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets to see move
6057 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6058 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
6059 "Xboard adjudication: Bare king", GE_XBOARD );
6063 if( gameInfo.variant == VariantShatranj && --bare < 0)
6065 epStatus[forwardMostMove] = EP_WINS; // make claimable as win for stm
6066 if(appData.checkMates) {
6067 /* but only adjudicate if adjudication enabled */
6068 SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets move
6069 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6070 GameEnds( NrW > 1 ? WhiteWins : NrPieces - NrW > 1 ? BlackWins : GameIsDrawn,
6071 "Xboard adjudication: Bare king", GE_XBOARD );
6078 // don't wait for engine to announce game end if we can judge ourselves
6079 switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove), epFile,
6080 castlingRights[forwardMostMove]) ) {
6082 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
6083 int i, checkCnt = 0; // (should really be done by making nr of checks part of game state)
6084 for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
6085 if(MateTest(boards[i], PosFlags(i), epStatus[i], castlingRights[i]) == MT_CHECK)
6088 reason = "Xboard adjudication: 3rd check";
6089 epStatus[forwardMostMove] = EP_CHECKMATE;
6099 reason = "Xboard adjudication: Stalemate";
6100 if(epStatus[forwardMostMove] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
6101 epStatus[forwardMostMove] = EP_STALEMATE; // default result for stalemate is draw
6102 if(gameInfo.variant == VariantLosers || gameInfo.variant == VariantGiveaway) // [HGM] losers:
6103 epStatus[forwardMostMove] = EP_WINS; // in these variants stalemated is always a win
6104 else if(gameInfo.variant == VariantSuicide) // in suicide it depends
6105 epStatus[forwardMostMove] = NrW == NrPieces-NrW ? EP_STALEMATE :
6106 ((NrW < NrPieces-NrW) != WhiteOnMove(forwardMostMove) ?
6107 EP_CHECKMATE : EP_WINS);
6108 else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
6109 epStatus[forwardMostMove] = EP_CHECKMATE; // and in these variants being stalemated loses
6113 reason = "Xboard adjudication: Checkmate";
6114 epStatus[forwardMostMove] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
6118 switch(i = epStatus[forwardMostMove]) {
6120 result = GameIsDrawn; break;
6122 result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
6124 result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
6126 result = (ChessMove) 0;
6128 if(appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
6129 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6130 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6131 GameEnds( result, reason, GE_XBOARD );
6135 /* Next absolutely insufficient mating material. */
6136 if( NrPieces == 2 || gameInfo.variant != VariantXiangqi &&
6137 gameInfo.variant != VariantShatranj && // [HGM] baring will remain possible
6138 (NrPieces == 3 && NrWN+NrBN+NrWB+NrBB == 1 ||
6139 NrPieces == NrBB+NrWB+2 && bishopsColor != 3)) // [HGM] all Bishops (Ferz!) same color
6140 { /* KBK, KNK, KK of KBKB with like Bishops */
6142 /* always flag draws, for judging claims */
6143 epStatus[forwardMostMove] = EP_INSUF_DRAW;
6145 if(appData.materialDraws) {
6146 /* but only adjudicate them if adjudication enabled */
6147 SendToProgram("force\n", cps->other); // suppress reply
6148 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see last move */
6149 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6150 GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
6155 /* Then some trivial draws (only adjudicate, cannot be claimed) */
6157 ( NrWR == 1 && NrBR == 1 /* KRKR */
6158 || NrWQ==1 && NrBQ==1 /* KQKQ */
6159 || NrWN==2 || NrBN==2 /* KNNK */
6160 || NrWN+NrWB == 1 && NrBN+NrBB == 1 /* KBKN, KBKB, KNKN */
6162 if(--moveCount < 0 && appData.trivialDraws)
6163 { /* if the first 3 moves do not show a tactical win, declare draw */
6164 SendToProgram("force\n", cps->other); // suppress reply
6165 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6166 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6167 GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
6170 } else moveCount = 6;
6174 if (appData.debugMode) { int i;
6175 fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
6176 forwardMostMove, backwardMostMove, epStatus[backwardMostMove],
6177 appData.drawRepeats);
6178 for( i=forwardMostMove; i>=backwardMostMove; i-- )
6179 fprintf(debugFP, "%d ep=%d\n", i, epStatus[i]);
6183 /* Check for rep-draws */
6185 for(k = forwardMostMove-2;
6186 k>=backwardMostMove && k>=forwardMostMove-100 &&
6187 epStatus[k] < EP_UNKNOWN &&
6188 epStatus[k+2] <= EP_NONE && epStatus[k+1] <= EP_NONE;
6191 if(CompareBoards(boards[k], boards[forwardMostMove])) {
6192 /* compare castling rights */
6193 if( castlingRights[forwardMostMove][2] != castlingRights[k][2] &&
6194 (castlingRights[k][0] >= 0 || castlingRights[k][1] >= 0) )
6195 rights++; /* King lost rights, while rook still had them */
6196 if( castlingRights[forwardMostMove][2] >= 0 ) { /* king has rights */
6197 if( castlingRights[forwardMostMove][0] != castlingRights[k][0] ||
6198 castlingRights[forwardMostMove][1] != castlingRights[k][1] )
6199 rights++; /* but at least one rook lost them */
6201 if( castlingRights[forwardMostMove][5] != castlingRights[k][5] &&
6202 (castlingRights[k][3] >= 0 || castlingRights[k][4] >= 0) )
6204 if( castlingRights[forwardMostMove][5] >= 0 ) {
6205 if( castlingRights[forwardMostMove][3] != castlingRights[k][3] ||
6206 castlingRights[forwardMostMove][4] != castlingRights[k][4] )
6209 if( rights == 0 && ++count > appData.drawRepeats-2
6210 && appData.drawRepeats > 1) {
6211 /* adjudicate after user-specified nr of repeats */
6212 SendToProgram("force\n", cps->other); // suppress reply
6213 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6214 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6215 if(gameInfo.variant == VariantXiangqi && appData.testLegality) {
6216 // [HGM] xiangqi: check for forbidden perpetuals
6217 int m, ourPerpetual = 1, hisPerpetual = 1;
6218 for(m=forwardMostMove; m>k; m-=2) {
6219 if(MateTest(boards[m], PosFlags(m),
6220 EP_NONE, castlingRights[m]) != MT_CHECK)
6221 ourPerpetual = 0; // the current mover did not always check
6222 if(MateTest(boards[m-1], PosFlags(m-1),
6223 EP_NONE, castlingRights[m-1]) != MT_CHECK)
6224 hisPerpetual = 0; // the opponent did not always check
6226 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
6227 ourPerpetual, hisPerpetual);
6228 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
6229 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
6230 "Xboard adjudication: perpetual checking", GE_XBOARD );
6233 if(hisPerpetual && !ourPerpetual) // he is checking us, but did not repeat yet
6234 break; // (or we would have caught him before). Abort repetition-checking loop.
6235 // Now check for perpetual chases
6236 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
6237 hisPerpetual = PerpetualChase(k, forwardMostMove);
6238 ourPerpetual = PerpetualChase(k+1, forwardMostMove);
6239 if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
6240 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
6241 "Xboard adjudication: perpetual chasing", GE_XBOARD );
6244 if(hisPerpetual && !ourPerpetual) // he is chasing us, but did not repeat yet
6245 break; // Abort repetition-checking loop.
6247 // if neither of us is checking or chasing all the time, or both are, it is draw
6249 GameEnds( GameIsDrawn, "Xboard adjudication: repetition draw", GE_XBOARD );
6252 if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
6253 epStatus[forwardMostMove] = EP_REP_DRAW;
6257 /* Now we test for 50-move draws. Determine ply count */
6258 count = forwardMostMove;
6259 /* look for last irreversble move */
6260 while( epStatus[count] <= EP_NONE && count > backwardMostMove )
6262 /* if we hit starting position, add initial plies */
6263 if( count == backwardMostMove )
6264 count -= initialRulePlies;
6265 count = forwardMostMove - count;
6267 epStatus[forwardMostMove] = EP_RULE_DRAW;
6268 /* this is used to judge if draw claims are legal */
6269 if(appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
6270 SendToProgram("force\n", cps->other); // suppress reply
6271 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6272 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6273 GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
6277 /* if draw offer is pending, treat it as a draw claim
6278 * when draw condition present, to allow engines a way to
6279 * claim draws before making their move to avoid a race
6280 * condition occurring after their move
6282 if( cps->other->offeredDraw || cps->offeredDraw ) {
6284 if(epStatus[forwardMostMove] == EP_RULE_DRAW)
6285 p = "Draw claim: 50-move rule";
6286 if(epStatus[forwardMostMove] == EP_REP_DRAW)
6287 p = "Draw claim: 3-fold repetition";
6288 if(epStatus[forwardMostMove] == EP_INSUF_DRAW)
6289 p = "Draw claim: insufficient mating material";
6291 SendToProgram("force\n", cps->other); // suppress reply
6292 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6293 GameEnds( GameIsDrawn, p, GE_XBOARD );
6294 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6300 if( appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
6301 SendToProgram("force\n", cps->other); // suppress reply
6302 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6303 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6305 GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
6312 if (gameMode == TwoMachinesPlay) {
6313 /* [HGM] relaying draw offers moved to after reception of move */
6314 /* and interpreting offer as claim if it brings draw condition */
6315 if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
6316 SendToProgram("draw\n", cps->other);
6318 if (cps->other->sendTime) {
6319 SendTimeRemaining(cps->other,
6320 cps->other->twoMachinesColor[0] == 'w');
6322 bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
6323 if (firstMove && !bookHit) {
6325 if (cps->other->useColors) {
6326 SendToProgram(cps->other->twoMachinesColor, cps->other);
6328 SendToProgram("go\n", cps->other);
6330 cps->other->maybeThinking = TRUE;
6333 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6335 if (!pausing && appData.ringBellAfterMoves) {
6340 * Reenable menu items that were disabled while
6341 * machine was thinking
6343 if (gameMode != TwoMachinesPlay)
6344 SetUserThinkingEnables();
6346 // [HGM] book: after book hit opponent has received move and is now in force mode
6347 // force the book reply into it, and then fake that it outputted this move by jumping
6348 // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
6350 static char bookMove[MSG_SIZ]; // a bit generous?
6352 strcpy(bookMove, "move ");
6353 strcat(bookMove, bookHit);
6356 programStats.nodes = programStats.depth = programStats.time =
6357 programStats.score = programStats.got_only_move = 0;
6358 sprintf(programStats.movelist, "%s (xbook)", bookHit);
6360 if(cps->lastPing != cps->lastPong) {
6361 savedMessage = message; // args for deferred call
6363 ScheduleDelayedEvent(DeferredBookMove, 10);
6372 /* Set special modes for chess engines. Later something general
6373 * could be added here; for now there is just one kludge feature,
6374 * needed because Crafty 15.10 and earlier don't ignore SIGINT
6375 * when "xboard" is given as an interactive command.
6377 if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
6378 cps->useSigint = FALSE;
6379 cps->useSigterm = FALSE;
6381 if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
6382 ParseFeatures(message+8, cps);
6383 return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
6386 /* [HGM] Allow engine to set up a position. Don't ask me why one would
6387 * want this, I was asked to put it in, and obliged.
6389 if (!strncmp(message, "setboard ", 9)) {
6390 Board initial_position; int i;
6392 GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
6394 if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
6395 DisplayError(_("Bad FEN received from engine"), 0);
6398 Reset(FALSE, FALSE);
6399 CopyBoard(boards[0], initial_position);
6400 initialRulePlies = FENrulePlies;
6401 epStatus[0] = FENepStatus;
6402 for( i=0; i<nrCastlingRights; i++ )
6403 castlingRights[0][i] = FENcastlingRights[i];
6404 if(blackPlaysFirst) gameMode = MachinePlaysWhite;
6405 else gameMode = MachinePlaysBlack;
6406 DrawPosition(FALSE, boards[currentMove]);
6412 * Look for communication commands
6414 if (!strncmp(message, "telluser ", 9)) {
6415 DisplayNote(message + 9);
6418 if (!strncmp(message, "tellusererror ", 14)) {
6419 DisplayError(message + 14, 0);
6422 if (!strncmp(message, "tellopponent ", 13)) {
6423 if (appData.icsActive) {
6425 snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
6429 DisplayNote(message + 13);
6433 if (!strncmp(message, "tellothers ", 11)) {
6434 if (appData.icsActive) {
6436 snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
6442 if (!strncmp(message, "tellall ", 8)) {
6443 if (appData.icsActive) {
6445 snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
6449 DisplayNote(message + 8);
6453 if (strncmp(message, "warning", 7) == 0) {
6454 /* Undocumented feature, use tellusererror in new code */
6455 DisplayError(message, 0);
6458 if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
6459 strcpy(realname, cps->tidy);
6460 strcat(realname, " query");
6461 AskQuestion(realname, buf2, buf1, cps->pr);
6464 /* Commands from the engine directly to ICS. We don't allow these to be
6465 * sent until we are logged on. Crafty kibitzes have been known to
6466 * interfere with the login process.
6469 if (!strncmp(message, "tellics ", 8)) {
6470 SendToICS(message + 8);
6474 if (!strncmp(message, "tellicsnoalias ", 15)) {
6475 SendToICS(ics_prefix);
6476 SendToICS(message + 15);
6480 /* The following are for backward compatibility only */
6481 if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
6482 !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
6483 SendToICS(ics_prefix);
6489 if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
6493 * If the move is illegal, cancel it and redraw the board.
6494 * Also deal with other error cases. Matching is rather loose
6495 * here to accommodate engines written before the spec.
6497 if (strncmp(message + 1, "llegal move", 11) == 0 ||
6498 strncmp(message, "Error", 5) == 0) {
6499 if (StrStr(message, "name") ||
6500 StrStr(message, "rating") || StrStr(message, "?") ||
6501 StrStr(message, "result") || StrStr(message, "board") ||
6502 StrStr(message, "bk") || StrStr(message, "computer") ||
6503 StrStr(message, "variant") || StrStr(message, "hint") ||
6504 StrStr(message, "random") || StrStr(message, "depth") ||
6505 StrStr(message, "accepted")) {
6508 if (StrStr(message, "protover")) {
6509 /* Program is responding to input, so it's apparently done
6510 initializing, and this error message indicates it is
6511 protocol version 1. So we don't need to wait any longer
6512 for it to initialize and send feature commands. */
6513 FeatureDone(cps, 1);
6514 cps->protocolVersion = 1;
6517 cps->maybeThinking = FALSE;
6519 if (StrStr(message, "draw")) {
6520 /* Program doesn't have "draw" command */
6521 cps->sendDrawOffers = 0;
6524 if (cps->sendTime != 1 &&
6525 (StrStr(message, "time") || StrStr(message, "otim"))) {
6526 /* Program apparently doesn't have "time" or "otim" command */
6530 if (StrStr(message, "analyze")) {
6531 cps->analysisSupport = FALSE;
6532 cps->analyzing = FALSE;
6534 sprintf(buf2, _("%s does not support analysis"), cps->tidy);
6535 DisplayError(buf2, 0);
6538 if (StrStr(message, "(no matching move)st")) {
6539 /* Special kludge for GNU Chess 4 only */
6540 cps->stKludge = TRUE;
6541 SendTimeControl(cps, movesPerSession, timeControl,
6542 timeIncrement, appData.searchDepth,
6546 if (StrStr(message, "(no matching move)sd")) {
6547 /* Special kludge for GNU Chess 4 only */
6548 cps->sdKludge = TRUE;
6549 SendTimeControl(cps, movesPerSession, timeControl,
6550 timeIncrement, appData.searchDepth,
6554 if (!StrStr(message, "llegal")) {
6557 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
6558 gameMode == IcsIdle) return;
6559 if (forwardMostMove <= backwardMostMove) return;
6560 if (pausing) PauseEvent();
6561 if(appData.forceIllegal) {
6562 // [HGM] illegal: machine refused move; force position after move into it
6563 SendToProgram("force\n", cps);
6564 if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
6565 // we have a real problem now, as SendBoard will use the a2a3 kludge
6566 // when black is to move, while there might be nothing on a2 or black
6567 // might already have the move. So send the board as if white has the move.
6568 // But first we must change the stm of the engine, as it refused the last move
6569 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
6570 if(WhiteOnMove(forwardMostMove)) {
6571 SendToProgram("a7a6\n", cps); // for the engine black still had the move
6572 SendBoard(cps, forwardMostMove); // kludgeless board
6574 SendToProgram("a2a3\n", cps); // for the engine white still had the move
6575 CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
6576 SendBoard(cps, forwardMostMove+1); // kludgeless board
6578 } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
6579 if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
6580 gameMode == TwoMachinesPlay)
6581 SendToProgram("go\n", cps);
6584 if (gameMode == PlayFromGameFile) {
6585 /* Stop reading this game file */
6586 gameMode = EditGame;
6589 currentMove = --forwardMostMove;
6590 DisplayMove(currentMove-1); /* before DisplayMoveError */
6592 DisplayBothClocks();
6593 sprintf(buf1, _("Illegal move \"%s\" (rejected by %s chess program)"),
6594 parseList[currentMove], cps->which);
6595 DisplayMoveError(buf1);
6596 DrawPosition(FALSE, boards[currentMove]);
6598 /* [HGM] illegal-move claim should forfeit game when Xboard */
6599 /* only passes fully legal moves */
6600 if( appData.testLegality && gameMode == TwoMachinesPlay ) {
6601 GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
6602 "False illegal-move claim", GE_XBOARD );
6606 if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
6607 /* Program has a broken "time" command that
6608 outputs a string not ending in newline.
6614 * If chess program startup fails, exit with an error message.
6615 * Attempts to recover here are futile.
6617 if ((StrStr(message, "unknown host") != NULL)
6618 || (StrStr(message, "No remote directory") != NULL)
6619 || (StrStr(message, "not found") != NULL)
6620 || (StrStr(message, "No such file") != NULL)
6621 || (StrStr(message, "can't alloc") != NULL)
6622 || (StrStr(message, "Permission denied") != NULL)) {
6624 cps->maybeThinking = FALSE;
6625 snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
6626 cps->which, cps->program, cps->host, message);
6627 RemoveInputSource(cps->isr);
6628 DisplayFatalError(buf1, 0, 1);
6633 * Look for hint output
6635 if (sscanf(message, "Hint: %s", buf1) == 1) {
6636 if (cps == &first && hintRequested) {
6637 hintRequested = FALSE;
6638 if (ParseOneMove(buf1, forwardMostMove, &moveType,
6639 &fromX, &fromY, &toX, &toY, &promoChar)) {
6640 (void) CoordsToAlgebraic(boards[forwardMostMove],
6641 PosFlags(forwardMostMove), EP_UNKNOWN,
6642 fromY, fromX, toY, toX, promoChar, buf1);
6643 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
6644 DisplayInformation(buf2);
6646 /* Hint move could not be parsed!? */
6647 snprintf(buf2, sizeof(buf2),
6648 _("Illegal hint move \"%s\"\nfrom %s chess program"),
6650 DisplayError(buf2, 0);
6653 strcpy(lastHint, buf1);
6659 * Ignore other messages if game is not in progress
6661 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
6662 gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
6665 * look for win, lose, draw, or draw offer
6667 if (strncmp(message, "1-0", 3) == 0) {
6668 char *p, *q, *r = "";
6669 p = strchr(message, '{');
6677 GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
6679 } else if (strncmp(message, "0-1", 3) == 0) {
6680 char *p, *q, *r = "";
6681 p = strchr(message, '{');
6689 /* Kludge for Arasan 4.1 bug */
6690 if (strcmp(r, "Black resigns") == 0) {
6691 GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
6694 GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
6696 } else if (strncmp(message, "1/2", 3) == 0) {
6697 char *p, *q, *r = "";
6698 p = strchr(message, '{');
6707 GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
6710 } else if (strncmp(message, "White resign", 12) == 0) {
6711 GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
6713 } else if (strncmp(message, "Black resign", 12) == 0) {
6714 GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
6716 } else if (strncmp(message, "White matches", 13) == 0 ||
6717 strncmp(message, "Black matches", 13) == 0 ) {
6718 /* [HGM] ignore GNUShogi noises */
6720 } else if (strncmp(message, "White", 5) == 0 &&
6721 message[5] != '(' &&
6722 StrStr(message, "Black") == NULL) {
6723 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6725 } else if (strncmp(message, "Black", 5) == 0 &&
6726 message[5] != '(') {
6727 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6729 } else if (strcmp(message, "resign") == 0 ||
6730 strcmp(message, "computer resigns") == 0) {
6732 case MachinePlaysBlack:
6733 case IcsPlayingBlack:
6734 GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
6736 case MachinePlaysWhite:
6737 case IcsPlayingWhite:
6738 GameEnds(BlackWins, "White resigns", GE_ENGINE);
6740 case TwoMachinesPlay:
6741 if (cps->twoMachinesColor[0] == 'w')
6742 GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
6744 GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
6751 } else if (strncmp(message, "opponent mates", 14) == 0) {
6753 case MachinePlaysBlack:
6754 case IcsPlayingBlack:
6755 GameEnds(WhiteWins, "White mates", GE_ENGINE);
6757 case MachinePlaysWhite:
6758 case IcsPlayingWhite:
6759 GameEnds(BlackWins, "Black mates", GE_ENGINE);
6761 case TwoMachinesPlay:
6762 if (cps->twoMachinesColor[0] == 'w')
6763 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6765 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6772 } else if (strncmp(message, "computer mates", 14) == 0) {
6774 case MachinePlaysBlack:
6775 case IcsPlayingBlack:
6776 GameEnds(BlackWins, "Black mates", GE_ENGINE1);
6778 case MachinePlaysWhite:
6779 case IcsPlayingWhite:
6780 GameEnds(WhiteWins, "White mates", GE_ENGINE);
6782 case TwoMachinesPlay:
6783 if (cps->twoMachinesColor[0] == 'w')
6784 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6786 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6793 } else if (strncmp(message, "checkmate", 9) == 0) {
6794 if (WhiteOnMove(forwardMostMove)) {
6795 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6797 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6800 } else if (strstr(message, "Draw") != NULL ||
6801 strstr(message, "game is a draw") != NULL) {
6802 GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
6804 } else if (strstr(message, "offer") != NULL &&
6805 strstr(message, "draw") != NULL) {
6807 if (appData.zippyPlay && first.initDone) {
6808 /* Relay offer to ICS */
6809 SendToICS(ics_prefix);
6810 SendToICS("draw\n");
6813 cps->offeredDraw = 2; /* valid until this engine moves twice */
6814 if (gameMode == TwoMachinesPlay) {
6815 if (cps->other->offeredDraw) {
6816 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
6817 /* [HGM] in two-machine mode we delay relaying draw offer */
6818 /* until after we also have move, to see if it is really claim */
6820 } else if (gameMode == MachinePlaysWhite ||
6821 gameMode == MachinePlaysBlack) {
6822 if (userOfferedDraw) {
6823 DisplayInformation(_("Machine accepts your draw offer"));
6824 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
6826 DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
6833 * Look for thinking output
6835 if ( appData.showThinking // [HGM] thinking: test all options that cause this output
6836 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
6838 int plylev, mvleft, mvtot, curscore, time;
6839 char mvname[MOVE_LEN];
6843 int prefixHint = FALSE;
6844 mvname[0] = NULLCHAR;
6847 case MachinePlaysBlack:
6848 case IcsPlayingBlack:
6849 if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
6851 case MachinePlaysWhite:
6852 case IcsPlayingWhite:
6853 if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
6858 case IcsObserving: /* [DM] icsEngineAnalyze */
6859 if (!appData.icsEngineAnalyze) ignore = TRUE;
6861 case TwoMachinesPlay:
6862 if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
6873 if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
6874 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
6876 if (plyext != ' ' && plyext != '\t') {
6880 /* [AS] Negate score if machine is playing black and reporting absolute scores */
6881 if( cps->scoreIsAbsolute &&
6882 ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) )
6884 curscore = -curscore;
6888 programStats.depth = plylev;
6889 programStats.nodes = nodes;
6890 programStats.time = time;
6891 programStats.score = curscore;
6892 programStats.got_only_move = 0;
6894 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
6897 if(cps->nps == 0) ticklen = 10*time; // use engine reported time
6898 else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
6899 if(WhiteOnMove(forwardMostMove))
6900 whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
6901 else blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
6904 /* Buffer overflow protection */
6905 if (buf1[0] != NULLCHAR) {
6906 if (strlen(buf1) >= sizeof(programStats.movelist)
6907 && appData.debugMode) {
6909 "PV is too long; using the first %d bytes.\n",
6910 sizeof(programStats.movelist) - 1);
6913 safeStrCpy( programStats.movelist, buf1, sizeof(programStats.movelist) );
6915 sprintf(programStats.movelist, " no PV\n");
6918 if (programStats.seen_stat) {
6919 programStats.ok_to_send = 1;
6922 if (strchr(programStats.movelist, '(') != NULL) {
6923 programStats.line_is_book = 1;
6924 programStats.nr_moves = 0;
6925 programStats.moves_left = 0;
6927 programStats.line_is_book = 0;
6930 SendProgramStatsToFrontend( cps, &programStats );
6933 [AS] Protect the thinkOutput buffer from overflow... this
6934 is only useful if buf1 hasn't overflowed first!
6936 sprintf(thinkOutput, "[%d]%c%+.2f %s%s",
6938 (gameMode == TwoMachinesPlay ?
6939 ToUpper(cps->twoMachinesColor[0]) : ' '),
6940 ((double) curscore) / 100.0,
6941 prefixHint ? lastHint : "",
6942 prefixHint ? " " : "" );
6944 if( buf1[0] != NULLCHAR ) {
6945 unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
6947 if( strlen(buf1) > max_len ) {
6948 if( appData.debugMode) {
6949 fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
6951 buf1[max_len+1] = '\0';
6954 strcat( thinkOutput, buf1 );
6957 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
6958 || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
6959 DisplayMove(currentMove - 1);
6963 } else if ((p=StrStr(message, "(only move)")) != NULL) {
6964 /* crafty (9.25+) says "(only move) <move>"
6965 * if there is only 1 legal move
6967 sscanf(p, "(only move) %s", buf1);
6968 sprintf(thinkOutput, "%s (only move)", buf1);
6969 sprintf(programStats.movelist, "%s (only move)", buf1);
6970 programStats.depth = 1;
6971 programStats.nr_moves = 1;
6972 programStats.moves_left = 1;
6973 programStats.nodes = 1;
6974 programStats.time = 1;
6975 programStats.got_only_move = 1;
6977 /* Not really, but we also use this member to
6978 mean "line isn't going to change" (Crafty
6979 isn't searching, so stats won't change) */
6980 programStats.line_is_book = 1;
6982 SendProgramStatsToFrontend( cps, &programStats );
6984 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
6985 gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
6986 DisplayMove(currentMove - 1);
6989 } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
6990 &time, &nodes, &plylev, &mvleft,
6991 &mvtot, mvname) >= 5) {
6992 /* The stat01: line is from Crafty (9.29+) in response
6993 to the "." command */
6994 programStats.seen_stat = 1;
6995 cps->maybeThinking = TRUE;
6997 if (programStats.got_only_move || !appData.periodicUpdates)
7000 programStats.depth = plylev;
7001 programStats.time = time;
7002 programStats.nodes = nodes;
7003 programStats.moves_left = mvleft;
7004 programStats.nr_moves = mvtot;
7005 strcpy(programStats.move_name, mvname);
7006 programStats.ok_to_send = 1;
7007 programStats.movelist[0] = '\0';
7009 SendProgramStatsToFrontend( cps, &programStats );
7013 } else if (strncmp(message,"++",2) == 0) {
7014 /* Crafty 9.29+ outputs this */
7015 programStats.got_fail = 2;
7018 } else if (strncmp(message,"--",2) == 0) {
7019 /* Crafty 9.29+ outputs this */
7020 programStats.got_fail = 1;
7023 } else if (thinkOutput[0] != NULLCHAR &&
7024 strncmp(message, " ", 4) == 0) {
7025 unsigned message_len;
7028 while (*p && *p == ' ') p++;
7030 message_len = strlen( p );
7032 /* [AS] Avoid buffer overflow */
7033 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
7034 strcat(thinkOutput, " ");
7035 strcat(thinkOutput, p);
7038 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
7039 strcat(programStats.movelist, " ");
7040 strcat(programStats.movelist, p);
7043 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
7044 gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7045 DisplayMove(currentMove - 1);
7053 if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
7054 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
7056 ChessProgramStats cpstats;
7058 if (plyext != ' ' && plyext != '\t') {
7062 /* [AS] Negate score if machine is playing black and reporting absolute scores */
7063 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
7064 curscore = -curscore;
7067 cpstats.depth = plylev;
7068 cpstats.nodes = nodes;
7069 cpstats.time = time;
7070 cpstats.score = curscore;
7071 cpstats.got_only_move = 0;
7072 cpstats.movelist[0] = '\0';
7074 if (buf1[0] != NULLCHAR) {
7075 safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist) );
7078 cpstats.ok_to_send = 0;
7079 cpstats.line_is_book = 0;
7080 cpstats.nr_moves = 0;
7081 cpstats.moves_left = 0;
7083 SendProgramStatsToFrontend( cps, &cpstats );
7090 /* Parse a game score from the character string "game", and
7091 record it as the history of the current game. The game
7092 score is NOT assumed to start from the standard position.
7093 The display is not updated in any way.
7096 ParseGameHistory(game)
7100 int fromX, fromY, toX, toY, boardIndex;
7105 if (appData.debugMode)
7106 fprintf(debugFP, "Parsing game history: %s\n", game);
7108 if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
7109 gameInfo.site = StrSave(appData.icsHost);
7110 gameInfo.date = PGNDate();
7111 gameInfo.round = StrSave("-");
7113 /* Parse out names of players */
7114 while (*game == ' ') game++;
7116 while (*game != ' ') *p++ = *game++;
7118 gameInfo.white = StrSave(buf);
7119 while (*game == ' ') game++;
7121 while (*game != ' ' && *game != '\n') *p++ = *game++;
7123 gameInfo.black = StrSave(buf);
7126 boardIndex = blackPlaysFirst ? 1 : 0;
7129 yyboardindex = boardIndex;
7130 moveType = (ChessMove) yylex();
7132 case IllegalMove: /* maybe suicide chess, etc. */
7133 if (appData.debugMode) {
7134 fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
7135 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7136 setbuf(debugFP, NULL);
7138 case WhitePromotionChancellor:
7139 case BlackPromotionChancellor:
7140 case WhitePromotionArchbishop:
7141 case BlackPromotionArchbishop:
7142 case WhitePromotionQueen:
7143 case BlackPromotionQueen:
7144 case WhitePromotionRook:
7145 case BlackPromotionRook:
7146 case WhitePromotionBishop:
7147 case BlackPromotionBishop:
7148 case WhitePromotionKnight:
7149 case BlackPromotionKnight:
7150 case WhitePromotionKing:
7151 case BlackPromotionKing:
7153 case WhiteCapturesEnPassant:
7154 case BlackCapturesEnPassant:
7155 case WhiteKingSideCastle:
7156 case WhiteQueenSideCastle:
7157 case BlackKingSideCastle:
7158 case BlackQueenSideCastle:
7159 case WhiteKingSideCastleWild:
7160 case WhiteQueenSideCastleWild:
7161 case BlackKingSideCastleWild:
7162 case BlackQueenSideCastleWild:
7164 case WhiteHSideCastleFR:
7165 case WhiteASideCastleFR:
7166 case BlackHSideCastleFR:
7167 case BlackASideCastleFR:
7169 fromX = currentMoveString[0] - AAA;
7170 fromY = currentMoveString[1] - ONE;
7171 toX = currentMoveString[2] - AAA;
7172 toY = currentMoveString[3] - ONE;
7173 promoChar = currentMoveString[4];
7177 fromX = moveType == WhiteDrop ?
7178 (int) CharToPiece(ToUpper(currentMoveString[0])) :
7179 (int) CharToPiece(ToLower(currentMoveString[0]));
7181 toX = currentMoveString[2] - AAA;
7182 toY = currentMoveString[3] - ONE;
7183 promoChar = NULLCHAR;
7187 sprintf(buf, _("Ambiguous move in ICS output: \"%s\""), yy_text);
7188 if (appData.debugMode) {
7189 fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
7190 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7191 setbuf(debugFP, NULL);
7193 DisplayError(buf, 0);
7195 case ImpossibleMove:
7197 sprintf(buf, _("Illegal move in ICS output: \"%s\""), yy_text);
7198 if (appData.debugMode) {
7199 fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
7200 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7201 setbuf(debugFP, NULL);
7203 DisplayError(buf, 0);
7205 case (ChessMove) 0: /* end of file */
7206 if (boardIndex < backwardMostMove) {
7207 /* Oops, gap. How did that happen? */
7208 DisplayError(_("Gap in move list"), 0);
7211 backwardMostMove = blackPlaysFirst ? 1 : 0;
7212 if (boardIndex > forwardMostMove) {
7213 forwardMostMove = boardIndex;
7217 if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
7218 strcat(parseList[boardIndex-1], " ");
7219 strcat(parseList[boardIndex-1], yy_text);
7231 case GameUnfinished:
7232 if (gameMode == IcsExamining) {
7233 if (boardIndex < backwardMostMove) {
7234 /* Oops, gap. How did that happen? */
7237 backwardMostMove = blackPlaysFirst ? 1 : 0;
7240 gameInfo.result = moveType;
7241 p = strchr(yy_text, '{');
7242 if (p == NULL) p = strchr(yy_text, '(');
7245 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
7247 q = strchr(p, *p == '{' ? '}' : ')');
7248 if (q != NULL) *q = NULLCHAR;
7251 gameInfo.resultDetails = StrSave(p);
7254 if (boardIndex >= forwardMostMove &&
7255 !(gameMode == IcsObserving && ics_gamenum == -1)) {
7256 backwardMostMove = blackPlaysFirst ? 1 : 0;
7259 (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
7260 EP_UNKNOWN, fromY, fromX, toY, toX, promoChar,
7261 parseList[boardIndex]);
7262 CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
7263 {int i; for(i=0; i<BOARD_SIZE; i++) castlingRights[boardIndex+1][i] = castlingRights[boardIndex][i];}
7264 /* currentMoveString is set as a side-effect of yylex */
7265 strcpy(moveList[boardIndex], currentMoveString);
7266 strcat(moveList[boardIndex], "\n");
7268 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex],
7269 castlingRights[boardIndex], &epStatus[boardIndex]);
7270 switch (MateTest(boards[boardIndex], PosFlags(boardIndex),
7271 EP_UNKNOWN, castlingRights[boardIndex]) ) {
7277 if(gameInfo.variant != VariantShogi)
7278 strcat(parseList[boardIndex - 1], "+");
7282 strcat(parseList[boardIndex - 1], "#");
7289 /* Apply a move to the given board */
7291 ApplyMove(fromX, fromY, toX, toY, promoChar, board, castling, ep)
7292 int fromX, fromY, toX, toY;
7298 ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
7300 /* [HGM] compute & store e.p. status and castling rights for new position */
7301 /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
7304 if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
7308 if( board[toY][toX] != EmptySquare )
7311 if( board[fromY][fromX] == WhitePawn ) {
7312 if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
7315 if(toX>BOARD_LEFT && board[toY][toX-1] == BlackPawn &&
7316 gameInfo.variant != VariantBerolina || toX < fromX)
7317 *ep = toX | berolina;
7318 if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
7319 gameInfo.variant != VariantBerolina || toX > fromX)
7323 if( board[fromY][fromX] == BlackPawn ) {
7324 if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
7326 if( toY-fromY== -2) {
7327 if(toX>BOARD_LEFT && board[toY][toX-1] == WhitePawn &&
7328 gameInfo.variant != VariantBerolina || toX < fromX)
7329 *ep = toX | berolina;
7330 if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
7331 gameInfo.variant != VariantBerolina || toX > fromX)
7336 for(i=0; i<nrCastlingRights; i++) {
7337 if(castling[i] == fromX && castlingRank[i] == fromY ||
7338 castling[i] == toX && castlingRank[i] == toY
7339 ) castling[i] = -1; // revoke for moved or captured piece
7344 /* [HGM] In Shatranj and Courier all promotions are to Ferz */
7345 if((gameInfo.variant==VariantShatranj || gameInfo.variant==VariantCourier)
7346 && promoChar != 0) promoChar = PieceToChar(WhiteFerz);
7348 if (fromX == toX && fromY == toY) return;
7350 if (fromY == DROP_RANK) {
7352 piece = board[toY][toX] = (ChessSquare) fromX;
7354 piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
7355 king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
7356 if(gameInfo.variant == VariantKnightmate)
7357 king += (int) WhiteUnicorn - (int) WhiteKing;
7359 /* Code added by Tord: */
7360 /* FRC castling assumed when king captures friendly rook. */
7361 if (board[fromY][fromX] == WhiteKing &&
7362 board[toY][toX] == WhiteRook) {
7363 board[fromY][fromX] = EmptySquare;
7364 board[toY][toX] = EmptySquare;
7366 board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
7368 board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
7370 } else if (board[fromY][fromX] == BlackKing &&
7371 board[toY][toX] == BlackRook) {
7372 board[fromY][fromX] = EmptySquare;
7373 board[toY][toX] = EmptySquare;
7375 board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
7377 board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
7379 /* End of code added by Tord */
7381 } else if (board[fromY][fromX] == king
7382 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7383 && toY == fromY && toX > fromX+1) {
7384 board[fromY][fromX] = EmptySquare;
7385 board[toY][toX] = king;
7386 board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
7387 board[fromY][BOARD_RGHT-1] = EmptySquare;
7388 } else if (board[fromY][fromX] == king
7389 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7390 && toY == fromY && toX < fromX-1) {
7391 board[fromY][fromX] = EmptySquare;
7392 board[toY][toX] = king;
7393 board[toY][toX+1] = board[fromY][BOARD_LEFT];
7394 board[fromY][BOARD_LEFT] = EmptySquare;
7395 } else if (board[fromY][fromX] == WhitePawn
7396 && toY == BOARD_HEIGHT-1
7397 && gameInfo.variant != VariantXiangqi
7399 /* white pawn promotion */
7400 board[toY][toX] = CharToPiece(ToUpper(promoChar));
7401 if (board[toY][toX] == EmptySquare) {
7402 board[toY][toX] = WhiteQueen;
7404 if(gameInfo.variant==VariantBughouse ||
7405 gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
7406 board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
7407 board[fromY][fromX] = EmptySquare;
7408 } else if ((fromY == BOARD_HEIGHT-4)
7410 && gameInfo.variant != VariantXiangqi
7411 && gameInfo.variant != VariantBerolina
7412 && (board[fromY][fromX] == WhitePawn)
7413 && (board[toY][toX] == EmptySquare)) {
7414 board[fromY][fromX] = EmptySquare;
7415 board[toY][toX] = WhitePawn;
7416 captured = board[toY - 1][toX];
7417 board[toY - 1][toX] = EmptySquare;
7418 } else if ((fromY == BOARD_HEIGHT-4)
7420 && gameInfo.variant == VariantBerolina
7421 && (board[fromY][fromX] == WhitePawn)
7422 && (board[toY][toX] == EmptySquare)) {
7423 board[fromY][fromX] = EmptySquare;
7424 board[toY][toX] = WhitePawn;
7425 if(oldEP & EP_BEROLIN_A) {
7426 captured = board[fromY][fromX-1];
7427 board[fromY][fromX-1] = EmptySquare;
7428 }else{ captured = board[fromY][fromX+1];
7429 board[fromY][fromX+1] = EmptySquare;
7431 } else if (board[fromY][fromX] == king
7432 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7433 && toY == fromY && toX > fromX+1) {
7434 board[fromY][fromX] = EmptySquare;
7435 board[toY][toX] = king;
7436 board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
7437 board[fromY][BOARD_RGHT-1] = EmptySquare;
7438 } else if (board[fromY][fromX] == king
7439 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7440 && toY == fromY && toX < fromX-1) {
7441 board[fromY][fromX] = EmptySquare;
7442 board[toY][toX] = king;
7443 board[toY][toX+1] = board[fromY][BOARD_LEFT];
7444 board[fromY][BOARD_LEFT] = EmptySquare;
7445 } else if (fromY == 7 && fromX == 3
7446 && board[fromY][fromX] == BlackKing
7447 && toY == 7 && toX == 5) {
7448 board[fromY][fromX] = EmptySquare;
7449 board[toY][toX] = BlackKing;
7450 board[fromY][7] = EmptySquare;
7451 board[toY][4] = BlackRook;
7452 } else if (fromY == 7 && fromX == 3
7453 && board[fromY][fromX] == BlackKing
7454 && toY == 7 && toX == 1) {
7455 board[fromY][fromX] = EmptySquare;
7456 board[toY][toX] = BlackKing;
7457 board[fromY][0] = EmptySquare;
7458 board[toY][2] = BlackRook;
7459 } else if (board[fromY][fromX] == BlackPawn
7461 && gameInfo.variant != VariantXiangqi
7463 /* black pawn promotion */
7464 board[0][toX] = CharToPiece(ToLower(promoChar));
7465 if (board[0][toX] == EmptySquare) {
7466 board[0][toX] = BlackQueen;
7468 if(gameInfo.variant==VariantBughouse ||
7469 gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
7470 board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
7471 board[fromY][fromX] = EmptySquare;
7472 } else if ((fromY == 3)
7474 && gameInfo.variant != VariantXiangqi
7475 && gameInfo.variant != VariantBerolina
7476 && (board[fromY][fromX] == BlackPawn)
7477 && (board[toY][toX] == EmptySquare)) {
7478 board[fromY][fromX] = EmptySquare;
7479 board[toY][toX] = BlackPawn;
7480 captured = board[toY + 1][toX];
7481 board[toY + 1][toX] = EmptySquare;
7482 } else if ((fromY == 3)
7484 && gameInfo.variant == VariantBerolina
7485 && (board[fromY][fromX] == BlackPawn)
7486 && (board[toY][toX] == EmptySquare)) {
7487 board[fromY][fromX] = EmptySquare;
7488 board[toY][toX] = BlackPawn;
7489 if(oldEP & EP_BEROLIN_A) {
7490 captured = board[fromY][fromX-1];
7491 board[fromY][fromX-1] = EmptySquare;
7492 }else{ captured = board[fromY][fromX+1];
7493 board[fromY][fromX+1] = EmptySquare;
7496 board[toY][toX] = board[fromY][fromX];
7497 board[fromY][fromX] = EmptySquare;
7500 /* [HGM] now we promote for Shogi, if needed */
7501 if(gameInfo.variant == VariantShogi && promoChar == 'q')
7502 board[toY][toX] = (ChessSquare) (PROMOTED piece);
7505 if (gameInfo.holdingsWidth != 0) {
7507 /* !!A lot more code needs to be written to support holdings */
7508 /* [HGM] OK, so I have written it. Holdings are stored in the */
7509 /* penultimate board files, so they are automaticlly stored */
7510 /* in the game history. */
7511 if (fromY == DROP_RANK) {
7512 /* Delete from holdings, by decreasing count */
7513 /* and erasing image if necessary */
7515 if(p < (int) BlackPawn) { /* white drop */
7516 p -= (int)WhitePawn;
7517 p = PieceToNumber((ChessSquare)p);
7518 if(p >= gameInfo.holdingsSize) p = 0;
7519 if(--board[p][BOARD_WIDTH-2] <= 0)
7520 board[p][BOARD_WIDTH-1] = EmptySquare;
7521 if((int)board[p][BOARD_WIDTH-2] < 0)
7522 board[p][BOARD_WIDTH-2] = 0;
7523 } else { /* black drop */
7524 p -= (int)BlackPawn;
7525 p = PieceToNumber((ChessSquare)p);
7526 if(p >= gameInfo.holdingsSize) p = 0;
7527 if(--board[BOARD_HEIGHT-1-p][1] <= 0)
7528 board[BOARD_HEIGHT-1-p][0] = EmptySquare;
7529 if((int)board[BOARD_HEIGHT-1-p][1] < 0)
7530 board[BOARD_HEIGHT-1-p][1] = 0;
7533 if (captured != EmptySquare && gameInfo.holdingsSize > 0
7534 && gameInfo.variant != VariantBughouse ) {
7535 /* [HGM] holdings: Add to holdings, if holdings exist */
7536 if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
7537 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
7538 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
7541 if (p >= (int) BlackPawn) {
7542 p -= (int)BlackPawn;
7543 if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
7544 /* in Shogi restore piece to its original first */
7545 captured = (ChessSquare) (DEMOTED captured);
7548 p = PieceToNumber((ChessSquare)p);
7549 if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
7550 board[p][BOARD_WIDTH-2]++;
7551 board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
7553 p -= (int)WhitePawn;
7554 if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
7555 captured = (ChessSquare) (DEMOTED captured);
7558 p = PieceToNumber((ChessSquare)p);
7559 if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
7560 board[BOARD_HEIGHT-1-p][1]++;
7561 board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
7564 } else if (gameInfo.variant == VariantAtomic) {
7565 if (captured != EmptySquare) {
7567 for (y = toY-1; y <= toY+1; y++) {
7568 for (x = toX-1; x <= toX+1; x++) {
7569 if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
7570 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
7571 board[y][x] = EmptySquare;
7575 board[toY][toX] = EmptySquare;
7578 if(gameInfo.variant == VariantShogi && promoChar != NULLCHAR && promoChar != '=') {
7579 /* [HGM] Shogi promotions */
7580 board[toY][toX] = (ChessSquare) (PROMOTED piece);
7583 if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
7584 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
7585 // [HGM] superchess: take promotion piece out of holdings
7586 int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
7587 if((int)piece < (int)BlackPawn) { // determine stm from piece color
7588 if(!--board[k][BOARD_WIDTH-2])
7589 board[k][BOARD_WIDTH-1] = EmptySquare;
7591 if(!--board[BOARD_HEIGHT-1-k][1])
7592 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
7598 /* Updates forwardMostMove */
7600 MakeMove(fromX, fromY, toX, toY, promoChar)
7601 int fromX, fromY, toX, toY;
7604 // forwardMostMove++; // [HGM] bare: moved downstream
7606 if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
7607 int timeLeft; static int lastLoadFlag=0; int king, piece;
7608 piece = boards[forwardMostMove][fromY][fromX];
7609 king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
7610 if(gameInfo.variant == VariantKnightmate)
7611 king += (int) WhiteUnicorn - (int) WhiteKing;
7612 if(forwardMostMove == 0) {
7614 fprintf(serverMoves, "%s;", second.tidy);
7615 fprintf(serverMoves, "%s;", first.tidy);
7616 if(!blackPlaysFirst)
7617 fprintf(serverMoves, "%s;", second.tidy);
7618 } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
7619 lastLoadFlag = loadFlag;
7621 fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
7622 // print castling suffix
7623 if( toY == fromY && piece == king ) {
7625 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
7627 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
7630 if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
7631 boards[forwardMostMove][fromY][fromX] == BlackPawn ) &&
7632 boards[forwardMostMove][toY][toX] == EmptySquare
7634 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
7636 if(promoChar != NULLCHAR)
7637 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);
7639 fprintf(serverMoves, "/%d/%d",
7640 pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
7641 if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
7642 else timeLeft = blackTimeRemaining/1000;
7643 fprintf(serverMoves, "/%d", timeLeft);
7645 fflush(serverMoves);
7648 if (forwardMostMove+1 >= MAX_MOVES) {
7649 DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
7653 if (commentList[forwardMostMove+1] != NULL) {
7654 free(commentList[forwardMostMove+1]);
7655 commentList[forwardMostMove+1] = NULL;
7657 CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
7658 {int i; for(i=0; i<BOARD_SIZE; i++) castlingRights[forwardMostMove+1][i] = castlingRights[forwardMostMove][i];}
7659 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1],
7660 castlingRights[forwardMostMove+1], &epStatus[forwardMostMove+1]);
7661 forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
7662 SwitchClocks(); // uses forwardMostMove, so must be done after incrementing it !
7663 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
7664 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
7665 gameInfo.result = GameUnfinished;
7666 if (gameInfo.resultDetails != NULL) {
7667 free(gameInfo.resultDetails);
7668 gameInfo.resultDetails = NULL;
7670 CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
7671 moveList[forwardMostMove - 1]);
7672 (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
7673 PosFlags(forwardMostMove - 1), EP_UNKNOWN,
7674 fromY, fromX, toY, toX, promoChar,
7675 parseList[forwardMostMove - 1]);
7676 switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove),
7677 epStatus[forwardMostMove], /* [HGM] use true e.p. */
7678 castlingRights[forwardMostMove]) ) {
7684 if(gameInfo.variant != VariantShogi)
7685 strcat(parseList[forwardMostMove - 1], "+");
7689 strcat(parseList[forwardMostMove - 1], "#");
7692 if (appData.debugMode) {
7693 fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
7698 /* Updates currentMove if not pausing */
7700 ShowMove(fromX, fromY, toX, toY)
7702 int instant = (gameMode == PlayFromGameFile) ?
7703 (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
7704 if(appData.noGUI) return;
7705 if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
7707 if (forwardMostMove == currentMove + 1) {
7708 AnimateMove(boards[forwardMostMove - 1],
7709 fromX, fromY, toX, toY);
7711 if (appData.highlightLastMove) {
7712 SetHighlights(fromX, fromY, toX, toY);
7715 currentMove = forwardMostMove;
7718 if (instant) return;
7720 DisplayMove(currentMove - 1);
7721 DrawPosition(FALSE, boards[currentMove]);
7722 DisplayBothClocks();
7723 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
7726 void SendEgtPath(ChessProgramState *cps)
7727 { /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
7728 char buf[MSG_SIZ], name[MSG_SIZ], *p;
7730 if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
7733 char c, *q = name+1, *r, *s;
7735 name[0] = ','; // extract next format name from feature and copy with prefixed ','
7736 while(*p && *p != ',') *q++ = *p++;
7738 if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
7739 strcmp(name, ",nalimov:") == 0 ) {
7740 // take nalimov path from the menu-changeable option first, if it is defined
7741 sprintf(buf, "egtpath nalimov %s\n", appData.defaultPathEGTB);
7742 SendToProgram(buf,cps); // send egtbpath command for nalimov
7744 if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
7745 (s = StrStr(appData.egtFormats, name)) != NULL) {
7746 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
7747 s = r = StrStr(s, ":") + 1; // beginning of path info
7748 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
7749 c = *r; *r = 0; // temporarily null-terminate path info
7750 *--q = 0; // strip of trailig ':' from name
7751 sprintf(buf, "egtpath %s %s\n", name+1, s);
7753 SendToProgram(buf,cps); // send egtbpath command for this format
7755 if(*p == ',') p++; // read away comma to position for next format name
7760 InitChessProgram(cps, setup)
7761 ChessProgramState *cps;
7762 int setup; /* [HGM] needed to setup FRC opening position */
7764 char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
7765 if (appData.noChessProgram) return;
7766 hintRequested = FALSE;
7767 bookRequested = FALSE;
7769 /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
7770 /* moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
7771 if(cps->memSize) { /* [HGM] memory */
7772 sprintf(buf, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
7773 SendToProgram(buf, cps);
7775 SendEgtPath(cps); /* [HGM] EGT */
7776 if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
7777 sprintf(buf, "cores %d\n", appData.smpCores);
7778 SendToProgram(buf, cps);
7781 SendToProgram(cps->initString, cps);
7782 if (gameInfo.variant != VariantNormal &&
7783 gameInfo.variant != VariantLoadable
7784 /* [HGM] also send variant if board size non-standard */
7785 || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
7787 char *v = VariantName(gameInfo.variant);
7788 if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
7789 /* [HGM] in protocol 1 we have to assume all variants valid */
7790 sprintf(buf, _("Variant %s not supported by %s"), v, cps->tidy);
7791 DisplayFatalError(buf, 0, 1);
7795 /* [HGM] make prefix for non-standard board size. Awkward testing... */
7796 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7797 if( gameInfo.variant == VariantXiangqi )
7798 overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
7799 if( gameInfo.variant == VariantShogi )
7800 overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
7801 if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
7802 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
7803 if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
7804 gameInfo.variant == VariantGothic || gameInfo.variant == VariantFalcon )
7805 overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7806 if( gameInfo.variant == VariantCourier )
7807 overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7808 if( gameInfo.variant == VariantSuper )
7809 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
7810 if( gameInfo.variant == VariantGreat )
7811 overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
7814 sprintf(b, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
7815 gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
7816 /* [HGM] varsize: try first if this defiant size variant is specifically known */
7817 if(StrStr(cps->variants, b) == NULL) {
7818 // specific sized variant not known, check if general sizing allowed
7819 if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
7820 if(StrStr(cps->variants, "boardsize") == NULL) {
7821 sprintf(buf, "Board size %dx%d+%d not supported by %s",
7822 gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
7823 DisplayFatalError(buf, 0, 1);
7826 /* [HGM] here we really should compare with the maximum supported board size */
7829 } else sprintf(b, "%s", VariantName(gameInfo.variant));
7830 sprintf(buf, "variant %s\n", b);
7831 SendToProgram(buf, cps);
7833 currentlyInitializedVariant = gameInfo.variant;
7835 /* [HGM] send opening position in FRC to first engine */
7837 SendToProgram("force\n", cps);
7839 /* engine is now in force mode! Set flag to wake it up after first move. */
7840 setboardSpoiledMachineBlack = 1;
7844 snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
7845 SendToProgram(buf, cps);
7847 cps->maybeThinking = FALSE;
7848 cps->offeredDraw = 0;
7849 if (!appData.icsActive) {
7850 SendTimeControl(cps, movesPerSession, timeControl,
7851 timeIncrement, appData.searchDepth,
7854 if (appData.showThinking
7855 // [HGM] thinking: four options require thinking output to be sent
7856 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
7858 SendToProgram("post\n", cps);
7860 SendToProgram("hard\n", cps);
7861 if (!appData.ponderNextMove) {
7862 /* Warning: "easy" is a toggle in GNU Chess, so don't send
7863 it without being sure what state we are in first. "hard"
7864 is not a toggle, so that one is OK.
7866 SendToProgram("easy\n", cps);
7869 sprintf(buf, "ping %d\n", ++cps->lastPing);
7870 SendToProgram(buf, cps);
7872 cps->initDone = TRUE;
7877 StartChessProgram(cps)
7878 ChessProgramState *cps;
7883 if (appData.noChessProgram) return;
7884 cps->initDone = FALSE;
7886 if (strcmp(cps->host, "localhost") == 0) {
7887 err = StartChildProcess(cps->program, cps->dir, &cps->pr);
7888 } else if (*appData.remoteShell == NULLCHAR) {
7889 err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
7891 if (*appData.remoteUser == NULLCHAR) {
7892 snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
7895 snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
7896 cps->host, appData.remoteUser, cps->program);
7898 err = StartChildProcess(buf, "", &cps->pr);
7902 sprintf(buf, _("Startup failure on '%s'"), cps->program);
7903 DisplayFatalError(buf, err, 1);
7909 cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
7910 if (cps->protocolVersion > 1) {
7911 sprintf(buf, "xboard\nprotover %d\n", cps->protocolVersion);
7912 cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
7913 cps->comboCnt = 0; // and values of combo boxes
7914 SendToProgram(buf, cps);
7916 SendToProgram("xboard\n", cps);
7922 TwoMachinesEventIfReady P((void))
7924 if (first.lastPing != first.lastPong) {
7925 DisplayMessage("", _("Waiting for first chess program"));
7926 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
7929 if (second.lastPing != second.lastPong) {
7930 DisplayMessage("", _("Waiting for second chess program"));
7931 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
7939 NextMatchGame P((void))
7941 int index; /* [HGM] autoinc: step lod index during match */
7943 if (*appData.loadGameFile != NULLCHAR) {
7944 index = appData.loadGameIndex;
7945 if(index < 0) { // [HGM] autoinc
7946 lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
7947 if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
7949 LoadGameFromFile(appData.loadGameFile,
7951 appData.loadGameFile, FALSE);
7952 } else if (*appData.loadPositionFile != NULLCHAR) {
7953 index = appData.loadPositionIndex;
7954 if(index < 0) { // [HGM] autoinc
7955 lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
7956 if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
7958 LoadPositionFromFile(appData.loadPositionFile,
7960 appData.loadPositionFile);
7962 TwoMachinesEventIfReady();
7965 void UserAdjudicationEvent( int result )
7967 ChessMove gameResult = GameIsDrawn;
7970 gameResult = WhiteWins;
7972 else if( result < 0 ) {
7973 gameResult = BlackWins;
7976 if( gameMode == TwoMachinesPlay ) {
7977 GameEnds( gameResult, "User adjudication", GE_XBOARD );
7982 // [HGM] save: calculate checksum of game to make games easily identifiable
7983 int StringCheckSum(char *s)
7986 if(s==NULL) return 0;
7987 while(*s) i = i*259 + *s++;
7994 for(i=backwardMostMove; i<forwardMostMove; i++) {
7995 sum += pvInfoList[i].depth;
7996 sum += StringCheckSum(parseList[i]);
7997 sum += StringCheckSum(commentList[i]);
8000 if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
8001 return sum + StringCheckSum(commentList[i]);
8002 } // end of save patch
8005 GameEnds(result, resultDetails, whosays)
8007 char *resultDetails;
8010 GameMode nextGameMode;
8014 if(endingGame) return; /* [HGM] crash: forbid recursion */
8017 if (appData.debugMode) {
8018 fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
8019 result, resultDetails ? resultDetails : "(null)", whosays);
8022 if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
8023 /* If we are playing on ICS, the server decides when the
8024 game is over, but the engine can offer to draw, claim
8028 if (appData.zippyPlay && first.initDone) {
8029 if (result == GameIsDrawn) {
8030 /* In case draw still needs to be claimed */
8031 SendToICS(ics_prefix);
8032 SendToICS("draw\n");
8033 } else if (StrCaseStr(resultDetails, "resign")) {
8034 SendToICS(ics_prefix);
8035 SendToICS("resign\n");
8039 endingGame = 0; /* [HGM] crash */
8043 /* If we're loading the game from a file, stop */
8044 if (whosays == GE_FILE) {
8045 (void) StopLoadGameTimer();
8049 /* Cancel draw offers */
8050 first.offeredDraw = second.offeredDraw = 0;
8052 /* If this is an ICS game, only ICS can really say it's done;
8053 if not, anyone can. */
8054 isIcsGame = (gameMode == IcsPlayingWhite ||
8055 gameMode == IcsPlayingBlack ||
8056 gameMode == IcsObserving ||
8057 gameMode == IcsExamining);
8059 if (!isIcsGame || whosays == GE_ICS) {
8060 /* OK -- not an ICS game, or ICS said it was done */
8062 if (!isIcsGame && !appData.noChessProgram)
8063 SetUserThinkingEnables();
8065 /* [HGM] if a machine claims the game end we verify this claim */
8066 if(gameMode == TwoMachinesPlay && appData.testClaims) {
8067 if(appData.testLegality && whosays >= GE_ENGINE1 ) {
8069 ChessMove trueResult = (ChessMove) -1;
8071 claimer = whosays == GE_ENGINE1 ? /* color of claimer */
8072 first.twoMachinesColor[0] :
8073 second.twoMachinesColor[0] ;
8075 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
8076 if(epStatus[forwardMostMove] == EP_CHECKMATE) {
8077 /* [HGM] verify: engine mate claims accepted if they were flagged */
8078 trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
8080 if(epStatus[forwardMostMove] == EP_WINS) { // added code for games where being mated is a win
8081 /* [HGM] verify: engine mate claims accepted if they were flagged */
8082 trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8084 if(epStatus[forwardMostMove] == EP_STALEMATE) { // only used to indicate draws now
8085 trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
8088 // now verify win claims, but not in drop games, as we don't understand those yet
8089 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
8090 || gameInfo.variant == VariantGreat) &&
8091 (result == WhiteWins && claimer == 'w' ||
8092 result == BlackWins && claimer == 'b' ) ) { // case to verify: engine claims own win
8093 if (appData.debugMode) {
8094 fprintf(debugFP, "result=%d sp=%d move=%d\n",
8095 result, epStatus[forwardMostMove], forwardMostMove);
8097 if(result != trueResult) {
8098 sprintf(buf, "False win claim: '%s'", resultDetails);
8099 result = claimer == 'w' ? BlackWins : WhiteWins;
8100 resultDetails = buf;
8103 if( result == GameIsDrawn && epStatus[forwardMostMove] > EP_DRAWS
8104 && (forwardMostMove <= backwardMostMove ||
8105 epStatus[forwardMostMove-1] > EP_DRAWS ||
8106 (claimer=='b')==(forwardMostMove&1))
8108 /* [HGM] verify: draws that were not flagged are false claims */
8109 sprintf(buf, "False draw claim: '%s'", resultDetails);
8110 result = claimer == 'w' ? BlackWins : WhiteWins;
8111 resultDetails = buf;
8113 /* (Claiming a loss is accepted no questions asked!) */
8115 /* [HGM] bare: don't allow bare King to win */
8116 if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
8117 && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
8118 && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
8119 && result != GameIsDrawn)
8120 { int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
8121 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
8122 int p = (int)boards[forwardMostMove][i][j] - color;
8123 if(p >= 0 && p <= (int)WhiteKing) k++;
8125 if (appData.debugMode) {
8126 fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
8127 result, resultDetails ? resultDetails : "(null)", whosays, k, color);
8130 result = GameIsDrawn;
8131 sprintf(buf, "%s but bare king", resultDetails);
8132 resultDetails = buf;
8138 if(serverMoves != NULL && !loadFlag) { char c = '=';
8139 if(result==WhiteWins) c = '+';
8140 if(result==BlackWins) c = '-';
8141 if(resultDetails != NULL)
8142 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
8144 if (resultDetails != NULL) {
8145 gameInfo.result = result;
8146 gameInfo.resultDetails = StrSave(resultDetails);
8148 /* display last move only if game was not loaded from file */
8149 if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
8150 DisplayMove(currentMove - 1);
8152 if (forwardMostMove != 0) {
8153 if (gameMode != PlayFromGameFile && gameMode != EditGame
8154 && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
8156 if (*appData.saveGameFile != NULLCHAR) {
8157 SaveGameToFile(appData.saveGameFile, TRUE);
8158 } else if (appData.autoSaveGames) {
8161 if (*appData.savePositionFile != NULLCHAR) {
8162 SavePositionToFile(appData.savePositionFile);
8167 /* Tell program how game ended in case it is learning */
8168 /* [HGM] Moved this to after saving the PGN, just in case */
8169 /* engine died and we got here through time loss. In that */
8170 /* case we will get a fatal error writing the pipe, which */
8171 /* would otherwise lose us the PGN. */
8172 /* [HGM] crash: not needed anymore, but doesn't hurt; */
8173 /* output during GameEnds should never be fatal anymore */
8174 if (gameMode == MachinePlaysWhite ||
8175 gameMode == MachinePlaysBlack ||
8176 gameMode == TwoMachinesPlay ||
8177 gameMode == IcsPlayingWhite ||
8178 gameMode == IcsPlayingBlack ||
8179 gameMode == BeginningOfGame) {
8181 sprintf(buf, "result %s {%s}\n", PGNResult(result),
8183 if (first.pr != NoProc) {
8184 SendToProgram(buf, &first);
8186 if (second.pr != NoProc &&
8187 gameMode == TwoMachinesPlay) {
8188 SendToProgram(buf, &second);
8193 if (appData.icsActive) {
8194 if (appData.quietPlay &&
8195 (gameMode == IcsPlayingWhite ||
8196 gameMode == IcsPlayingBlack)) {
8197 SendToICS(ics_prefix);
8198 SendToICS("set shout 1\n");
8200 nextGameMode = IcsIdle;
8201 ics_user_moved = FALSE;
8202 /* clean up premove. It's ugly when the game has ended and the
8203 * premove highlights are still on the board.
8207 ClearPremoveHighlights();
8208 DrawPosition(FALSE, boards[currentMove]);
8210 if (whosays == GE_ICS) {
8213 if (gameMode == IcsPlayingWhite)
8215 else if(gameMode == IcsPlayingBlack)
8219 if (gameMode == IcsPlayingBlack)
8221 else if(gameMode == IcsPlayingWhite)
8228 PlayIcsUnfinishedSound();
8231 } else if (gameMode == EditGame ||
8232 gameMode == PlayFromGameFile ||
8233 gameMode == AnalyzeMode ||
8234 gameMode == AnalyzeFile) {
8235 nextGameMode = gameMode;
8237 nextGameMode = EndOfGame;
8242 nextGameMode = gameMode;
8245 if (appData.noChessProgram) {
8246 gameMode = nextGameMode;
8248 endingGame = 0; /* [HGM] crash */
8253 /* Put first chess program into idle state */
8254 if (first.pr != NoProc &&
8255 (gameMode == MachinePlaysWhite ||
8256 gameMode == MachinePlaysBlack ||
8257 gameMode == TwoMachinesPlay ||
8258 gameMode == IcsPlayingWhite ||
8259 gameMode == IcsPlayingBlack ||
8260 gameMode == BeginningOfGame)) {
8261 SendToProgram("force\n", &first);
8262 if (first.usePing) {
8264 sprintf(buf, "ping %d\n", ++first.lastPing);
8265 SendToProgram(buf, &first);
8268 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
8269 /* Kill off first chess program */
8270 if (first.isr != NULL)
8271 RemoveInputSource(first.isr);
8274 if (first.pr != NoProc) {
8276 DoSleep( appData.delayBeforeQuit );
8277 SendToProgram("quit\n", &first);
8278 DoSleep( appData.delayAfterQuit );
8279 DestroyChildProcess(first.pr, first.useSigterm);
8284 /* Put second chess program into idle state */
8285 if (second.pr != NoProc &&
8286 gameMode == TwoMachinesPlay) {
8287 SendToProgram("force\n", &second);
8288 if (second.usePing) {
8290 sprintf(buf, "ping %d\n", ++second.lastPing);
8291 SendToProgram(buf, &second);
8294 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
8295 /* Kill off second chess program */
8296 if (second.isr != NULL)
8297 RemoveInputSource(second.isr);
8300 if (second.pr != NoProc) {
8301 DoSleep( appData.delayBeforeQuit );
8302 SendToProgram("quit\n", &second);
8303 DoSleep( appData.delayAfterQuit );
8304 DestroyChildProcess(second.pr, second.useSigterm);
8309 if (matchMode && gameMode == TwoMachinesPlay) {
8312 if (first.twoMachinesColor[0] == 'w') {
8319 if (first.twoMachinesColor[0] == 'b') {
8328 if (matchGame < appData.matchGames) {
8330 if(appData.sameColorGames <= 1) { /* [HGM] alternate: suppress color swap */
8331 tmp = first.twoMachinesColor;
8332 first.twoMachinesColor = second.twoMachinesColor;
8333 second.twoMachinesColor = tmp;
8335 gameMode = nextGameMode;
8337 if(appData.matchPause>10000 || appData.matchPause<10)
8338 appData.matchPause = 10000; /* [HGM] make pause adjustable */
8339 ScheduleDelayedEvent(NextMatchGame, appData.matchPause);
8340 endingGame = 0; /* [HGM] crash */
8344 gameMode = nextGameMode;
8345 sprintf(buf, _("Match %s vs. %s: final score %d-%d-%d"),
8346 first.tidy, second.tidy,
8347 first.matchWins, second.matchWins,
8348 appData.matchGames - (first.matchWins + second.matchWins));
8349 DisplayFatalError(buf, 0, 0);
8352 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
8353 !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
8355 gameMode = nextGameMode;
8357 endingGame = 0; /* [HGM] crash */
8360 /* Assumes program was just initialized (initString sent).
8361 Leaves program in force mode. */
8363 FeedMovesToProgram(cps, upto)
8364 ChessProgramState *cps;
8369 if (appData.debugMode)
8370 fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
8371 startedFromSetupPosition ? "position and " : "",
8372 backwardMostMove, upto, cps->which);
8373 if(currentlyInitializedVariant != gameInfo.variant) { char buf[MSG_SIZ];
8374 // [HGM] variantswitch: make engine aware of new variant
8375 if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
8376 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
8377 sprintf(buf, "variant %s\n", VariantName(gameInfo.variant));
8378 SendToProgram(buf, cps);
8379 currentlyInitializedVariant = gameInfo.variant;
8381 SendToProgram("force\n", cps);
8382 if (startedFromSetupPosition) {
8383 SendBoard(cps, backwardMostMove);
8384 if (appData.debugMode) {
8385 fprintf(debugFP, "feedMoves\n");
8388 for (i = backwardMostMove; i < upto; i++) {
8389 SendMoveToProgram(i, cps);
8395 ResurrectChessProgram()
8397 /* The chess program may have exited.
8398 If so, restart it and feed it all the moves made so far. */
8400 if (appData.noChessProgram || first.pr != NoProc) return;
8402 StartChessProgram(&first);
8403 InitChessProgram(&first, FALSE);
8404 FeedMovesToProgram(&first, currentMove);
8406 if (!first.sendTime) {
8407 /* can't tell gnuchess what its clock should read,
8408 so we bow to its notion. */
8410 timeRemaining[0][currentMove] = whiteTimeRemaining;
8411 timeRemaining[1][currentMove] = blackTimeRemaining;
8414 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
8415 appData.icsEngineAnalyze) && first.analysisSupport) {
8416 SendToProgram("analyze\n", &first);
8417 first.analyzing = TRUE;
8430 if (appData.debugMode) {
8431 fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
8432 redraw, init, gameMode);
8434 pausing = pauseExamInvalid = FALSE;
8435 startedFromSetupPosition = blackPlaysFirst = FALSE;
8437 whiteFlag = blackFlag = FALSE;
8438 userOfferedDraw = FALSE;
8439 hintRequested = bookRequested = FALSE;
8440 first.maybeThinking = FALSE;
8441 second.maybeThinking = FALSE;
8442 first.bookSuspend = FALSE; // [HGM] book
8443 second.bookSuspend = FALSE;
8444 thinkOutput[0] = NULLCHAR;
8445 lastHint[0] = NULLCHAR;
8446 ClearGameInfo(&gameInfo);
8447 gameInfo.variant = StringToVariant(appData.variant);
8448 ics_user_moved = ics_clock_paused = FALSE;
8449 ics_getting_history = H_FALSE;
8451 white_holding[0] = black_holding[0] = NULLCHAR;
8452 ClearProgramStats();
8453 opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
8457 flipView = appData.flipView;
8458 ClearPremoveHighlights();
8460 alarmSounded = FALSE;
8462 GameEnds((ChessMove) 0, NULL, GE_PLAYER);
8463 if(appData.serverMovesName != NULL) {
8464 /* [HGM] prepare to make moves file for broadcasting */
8465 clock_t t = clock();
8466 if(serverMoves != NULL) fclose(serverMoves);
8467 serverMoves = fopen(appData.serverMovesName, "r");
8468 if(serverMoves != NULL) {
8469 fclose(serverMoves);
8470 /* delay 15 sec before overwriting, so all clients can see end */
8471 while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
8473 serverMoves = fopen(appData.serverMovesName, "w");
8477 gameMode = BeginningOfGame;
8479 if(appData.icsActive) gameInfo.variant = VariantNormal;
8480 currentMove = forwardMostMove = backwardMostMove = 0;
8481 InitPosition(redraw);
8482 for (i = 0; i < MAX_MOVES; i++) {
8483 if (commentList[i] != NULL) {
8484 free(commentList[i]);
8485 commentList[i] = NULL;
8489 timeRemaining[0][0] = whiteTimeRemaining;
8490 timeRemaining[1][0] = blackTimeRemaining;
8491 if (first.pr == NULL) {
8492 StartChessProgram(&first);
8495 InitChessProgram(&first, startedFromSetupPosition);
8498 DisplayMessage("", "");
8499 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
8500 lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
8507 if (!AutoPlayOneMove())
8509 if (matchMode || appData.timeDelay == 0)
8511 if (appData.timeDelay < 0 || gameMode == AnalyzeFile)
8513 StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
8522 int fromX, fromY, toX, toY;
8524 if (appData.debugMode) {
8525 fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
8528 if (gameMode != PlayFromGameFile)
8531 if (currentMove >= forwardMostMove) {
8532 gameMode = EditGame;
8535 /* [AS] Clear current move marker at the end of a game */
8536 /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
8541 toX = moveList[currentMove][2] - AAA;
8542 toY = moveList[currentMove][3] - ONE;
8544 if (moveList[currentMove][1] == '@') {
8545 if (appData.highlightLastMove) {
8546 SetHighlights(-1, -1, toX, toY);
8549 fromX = moveList[currentMove][0] - AAA;
8550 fromY = moveList[currentMove][1] - ONE;
8552 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
8554 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
8556 if (appData.highlightLastMove) {
8557 SetHighlights(fromX, fromY, toX, toY);
8560 DisplayMove(currentMove);
8561 SendMoveToProgram(currentMove++, &first);
8562 DisplayBothClocks();
8563 DrawPosition(FALSE, boards[currentMove]);
8564 // [HGM] PV info: always display, routine tests if empty
8565 DisplayComment(currentMove - 1, commentList[currentMove]);
8571 LoadGameOneMove(readAhead)
8572 ChessMove readAhead;
8574 int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
8575 char promoChar = NULLCHAR;
8580 if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
8581 gameMode != AnalyzeMode && gameMode != Training) {
8586 yyboardindex = forwardMostMove;
8587 if (readAhead != (ChessMove)0) {
8588 moveType = readAhead;
8590 if (gameFileFP == NULL)
8592 moveType = (ChessMove) yylex();
8598 if (appData.debugMode)
8599 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
8601 if (*p == '{' || *p == '[' || *p == '(') {
8602 p[strlen(p) - 1] = NULLCHAR;
8606 /* append the comment but don't display it */
8607 while (*p == '\n') p++;
8608 AppendComment(currentMove, p);
8611 case WhiteCapturesEnPassant:
8612 case BlackCapturesEnPassant:
8613 case WhitePromotionChancellor:
8614 case BlackPromotionChancellor:
8615 case WhitePromotionArchbishop:
8616 case BlackPromotionArchbishop:
8617 case WhitePromotionCentaur:
8618 case BlackPromotionCentaur:
8619 case WhitePromotionQueen:
8620 case BlackPromotionQueen:
8621 case WhitePromotionRook:
8622 case BlackPromotionRook:
8623 case WhitePromotionBishop:
8624 case BlackPromotionBishop:
8625 case WhitePromotionKnight:
8626 case BlackPromotionKnight:
8627 case WhitePromotionKing:
8628 case BlackPromotionKing:
8630 case WhiteKingSideCastle:
8631 case WhiteQueenSideCastle:
8632 case BlackKingSideCastle:
8633 case BlackQueenSideCastle:
8634 case WhiteKingSideCastleWild:
8635 case WhiteQueenSideCastleWild:
8636 case BlackKingSideCastleWild:
8637 case BlackQueenSideCastleWild:
8639 case WhiteHSideCastleFR:
8640 case WhiteASideCastleFR:
8641 case BlackHSideCastleFR:
8642 case BlackASideCastleFR:
8644 if (appData.debugMode)
8645 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
8646 fromX = currentMoveString[0] - AAA;
8647 fromY = currentMoveString[1] - ONE;
8648 toX = currentMoveString[2] - AAA;
8649 toY = currentMoveString[3] - ONE;
8650 promoChar = currentMoveString[4];
8655 if (appData.debugMode)
8656 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
8657 fromX = moveType == WhiteDrop ?
8658 (int) CharToPiece(ToUpper(currentMoveString[0])) :
8659 (int) CharToPiece(ToLower(currentMoveString[0]));
8661 toX = currentMoveString[2] - AAA;
8662 toY = currentMoveString[3] - ONE;
8668 case GameUnfinished:
8669 if (appData.debugMode)
8670 fprintf(debugFP, "Parsed game end: %s\n", yy_text);
8671 p = strchr(yy_text, '{');
8672 if (p == NULL) p = strchr(yy_text, '(');
8675 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
8677 q = strchr(p, *p == '{' ? '}' : ')');
8678 if (q != NULL) *q = NULLCHAR;
8681 GameEnds(moveType, p, GE_FILE);
8683 if (cmailMsgLoaded) {
8685 flipView = WhiteOnMove(currentMove);
8686 if (moveType == GameUnfinished) flipView = !flipView;
8687 if (appData.debugMode)
8688 fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
8692 case (ChessMove) 0: /* end of file */
8693 if (appData.debugMode)
8694 fprintf(debugFP, "Parser hit end of file\n");
8695 switch (MateTest(boards[currentMove], PosFlags(currentMove),
8696 EP_UNKNOWN, castlingRights[currentMove]) ) {
8702 if (WhiteOnMove(currentMove)) {
8703 GameEnds(BlackWins, "Black mates", GE_FILE);
8705 GameEnds(WhiteWins, "White mates", GE_FILE);
8709 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
8716 if (lastLoadGameStart == GNUChessGame) {
8717 /* GNUChessGames have numbers, but they aren't move numbers */
8718 if (appData.debugMode)
8719 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
8720 yy_text, (int) moveType);
8721 return LoadGameOneMove((ChessMove)0); /* tail recursion */
8723 /* else fall thru */
8728 /* Reached start of next game in file */
8729 if (appData.debugMode)
8730 fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
8731 switch (MateTest(boards[currentMove], PosFlags(currentMove),
8732 EP_UNKNOWN, castlingRights[currentMove]) ) {
8738 if (WhiteOnMove(currentMove)) {
8739 GameEnds(BlackWins, "Black mates", GE_FILE);
8741 GameEnds(WhiteWins, "White mates", GE_FILE);
8745 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
8751 case PositionDiagram: /* should not happen; ignore */
8752 case ElapsedTime: /* ignore */
8753 case NAG: /* ignore */
8754 if (appData.debugMode)
8755 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
8756 yy_text, (int) moveType);
8757 return LoadGameOneMove((ChessMove)0); /* tail recursion */
8760 if (appData.testLegality) {
8761 if (appData.debugMode)
8762 fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
8763 sprintf(move, _("Illegal move: %d.%s%s"),
8764 (forwardMostMove / 2) + 1,
8765 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8766 DisplayError(move, 0);
8769 if (appData.debugMode)
8770 fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
8771 yy_text, currentMoveString);
8772 fromX = currentMoveString[0] - AAA;
8773 fromY = currentMoveString[1] - ONE;
8774 toX = currentMoveString[2] - AAA;
8775 toY = currentMoveString[3] - ONE;
8776 promoChar = currentMoveString[4];
8781 if (appData.debugMode)
8782 fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
8783 sprintf(move, _("Ambiguous move: %d.%s%s"),
8784 (forwardMostMove / 2) + 1,
8785 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8786 DisplayError(move, 0);
8791 case ImpossibleMove:
8792 if (appData.debugMode)
8793 fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
8794 sprintf(move, _("Illegal move: %d.%s%s"),
8795 (forwardMostMove / 2) + 1,
8796 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8797 DisplayError(move, 0);
8803 if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
8804 DrawPosition(FALSE, boards[currentMove]);
8805 DisplayBothClocks();
8806 if (!appData.matchMode) // [HGM] PV info: routine tests if empty
8807 DisplayComment(currentMove - 1, commentList[currentMove]);
8809 (void) StopLoadGameTimer();
8811 cmailOldMove = forwardMostMove;
8814 /* currentMoveString is set as a side-effect of yylex */
8815 strcat(currentMoveString, "\n");
8816 strcpy(moveList[forwardMostMove], currentMoveString);
8818 thinkOutput[0] = NULLCHAR;
8819 MakeMove(fromX, fromY, toX, toY, promoChar);
8820 currentMove = forwardMostMove;
8825 /* Load the nth game from the given file */
8827 LoadGameFromFile(filename, n, title, useList)
8831 /*Boolean*/ int useList;
8836 if (strcmp(filename, "-") == 0) {
8840 f = fopen(filename, "rb");
8842 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
8843 DisplayError(buf, errno);
8847 if (fseek(f, 0, 0) == -1) {
8848 /* f is not seekable; probably a pipe */
8851 if (useList && n == 0) {
8852 int error = GameListBuild(f);
8854 DisplayError(_("Cannot build game list"), error);
8855 } else if (!ListEmpty(&gameList) &&
8856 ((ListGame *) gameList.tailPred)->number > 1) {
8857 GameListPopUp(f, title);
8864 return LoadGame(f, n, title, FALSE);
8869 MakeRegisteredMove()
8871 int fromX, fromY, toX, toY;
8873 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
8874 switch (cmailMoveType[lastLoadGameNumber - 1]) {
8877 if (appData.debugMode)
8878 fprintf(debugFP, "Restoring %s for game %d\n",
8879 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
8881 thinkOutput[0] = NULLCHAR;
8882 strcpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1]);
8883 fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
8884 fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
8885 toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
8886 toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
8887 promoChar = cmailMove[lastLoadGameNumber - 1][4];
8888 MakeMove(fromX, fromY, toX, toY, promoChar);
8889 ShowMove(fromX, fromY, toX, toY);
8891 switch (MateTest(boards[currentMove], PosFlags(currentMove),
8892 EP_UNKNOWN, castlingRights[currentMove]) ) {
8899 if (WhiteOnMove(currentMove)) {
8900 GameEnds(BlackWins, "Black mates", GE_PLAYER);
8902 GameEnds(WhiteWins, "White mates", GE_PLAYER);
8907 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
8914 if (WhiteOnMove(currentMove)) {
8915 GameEnds(BlackWins, "White resigns", GE_PLAYER);
8917 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
8922 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
8933 /* Wrapper around LoadGame for use when a Cmail message is loaded */
8935 CmailLoadGame(f, gameNumber, title, useList)
8943 if (gameNumber > nCmailGames) {
8944 DisplayError(_("No more games in this message"), 0);
8947 if (f == lastLoadGameFP) {
8948 int offset = gameNumber - lastLoadGameNumber;
8950 cmailMsg[0] = NULLCHAR;
8951 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
8952 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
8953 nCmailMovesRegistered--;
8955 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
8956 if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
8957 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
8960 if (! RegisterMove()) return FALSE;
8964 retVal = LoadGame(f, gameNumber, title, useList);
8966 /* Make move registered during previous look at this game, if any */
8967 MakeRegisteredMove();
8969 if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
8970 commentList[currentMove]
8971 = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
8972 DisplayComment(currentMove - 1, commentList[currentMove]);
8978 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
8983 int gameNumber = lastLoadGameNumber + offset;
8984 if (lastLoadGameFP == NULL) {
8985 DisplayError(_("No game has been loaded yet"), 0);
8988 if (gameNumber <= 0) {
8989 DisplayError(_("Can't back up any further"), 0);
8992 if (cmailMsgLoaded) {
8993 return CmailLoadGame(lastLoadGameFP, gameNumber,
8994 lastLoadGameTitle, lastLoadGameUseList);
8996 return LoadGame(lastLoadGameFP, gameNumber,
8997 lastLoadGameTitle, lastLoadGameUseList);
9003 /* Load the nth game from open file f */
9005 LoadGame(f, gameNumber, title, useList)
9013 int gn = gameNumber;
9014 ListGame *lg = NULL;
9017 GameMode oldGameMode;
9018 VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
9020 if (appData.debugMode)
9021 fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
9023 if (gameMode == Training )
9024 SetTrainingModeOff();
9026 oldGameMode = gameMode;
9027 if (gameMode != BeginningOfGame) {
9032 if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
9033 fclose(lastLoadGameFP);
9037 lg = (ListGame *) ListElem(&gameList, gameNumber-1);
9040 fseek(f, lg->offset, 0);
9041 GameListHighlight(gameNumber);
9045 DisplayError(_("Game number out of range"), 0);
9050 if (fseek(f, 0, 0) == -1) {
9051 if (f == lastLoadGameFP ?
9052 gameNumber == lastLoadGameNumber + 1 :
9056 DisplayError(_("Can't seek on game file"), 0);
9062 lastLoadGameNumber = gameNumber;
9063 strcpy(lastLoadGameTitle, title);
9064 lastLoadGameUseList = useList;
9068 if (lg && lg->gameInfo.white && lg->gameInfo.black) {
9069 snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
9070 lg->gameInfo.black);
9072 } else if (*title != NULLCHAR) {
9073 if (gameNumber > 1) {
9074 sprintf(buf, "%s %d", title, gameNumber);
9077 DisplayTitle(title);
9081 if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
9082 gameMode = PlayFromGameFile;
9086 currentMove = forwardMostMove = backwardMostMove = 0;
9087 CopyBoard(boards[0], initialPosition);
9091 * Skip the first gn-1 games in the file.
9092 * Also skip over anything that precedes an identifiable
9093 * start of game marker, to avoid being confused by
9094 * garbage at the start of the file. Currently
9095 * recognized start of game markers are the move number "1",
9096 * the pattern "gnuchess .* game", the pattern
9097 * "^[#;%] [^ ]* game file", and a PGN tag block.
9098 * A game that starts with one of the latter two patterns
9099 * will also have a move number 1, possibly
9100 * following a position diagram.
9101 * 5-4-02: Let's try being more lenient and allowing a game to
9102 * start with an unnumbered move. Does that break anything?
9104 cm = lastLoadGameStart = (ChessMove) 0;
9106 yyboardindex = forwardMostMove;
9107 cm = (ChessMove) yylex();
9110 if (cmailMsgLoaded) {
9111 nCmailGames = CMAIL_MAX_GAMES - gn;
9114 DisplayError(_("Game not found in file"), 0);
9121 lastLoadGameStart = cm;
9125 switch (lastLoadGameStart) {
9132 gn--; /* count this game */
9133 lastLoadGameStart = cm;
9142 switch (lastLoadGameStart) {
9147 gn--; /* count this game */
9148 lastLoadGameStart = cm;
9151 lastLoadGameStart = cm; /* game counted already */
9159 yyboardindex = forwardMostMove;
9160 cm = (ChessMove) yylex();
9161 } while (cm == PGNTag || cm == Comment);
9168 if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
9169 if ( cmailResult[CMAIL_MAX_GAMES - gn - 1]
9170 != CMAIL_OLD_RESULT) {
9172 cmailResult[ CMAIL_MAX_GAMES
9173 - gn - 1] = CMAIL_OLD_RESULT;
9179 /* Only a NormalMove can be at the start of a game
9180 * without a position diagram. */
9181 if (lastLoadGameStart == (ChessMove) 0) {
9183 lastLoadGameStart = MoveNumberOne;
9192 if (appData.debugMode)
9193 fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
9195 if (cm == XBoardGame) {
9196 /* Skip any header junk before position diagram and/or move 1 */
9198 yyboardindex = forwardMostMove;
9199 cm = (ChessMove) yylex();
9201 if (cm == (ChessMove) 0 ||
9202 cm == GNUChessGame || cm == XBoardGame) {
9203 /* Empty game; pretend end-of-file and handle later */
9208 if (cm == MoveNumberOne || cm == PositionDiagram ||
9209 cm == PGNTag || cm == Comment)
9212 } else if (cm == GNUChessGame) {
9213 if (gameInfo.event != NULL) {
9214 free(gameInfo.event);
9216 gameInfo.event = StrSave(yy_text);
9219 startedFromSetupPosition = FALSE;
9220 while (cm == PGNTag) {
9221 if (appData.debugMode)
9222 fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
9223 err = ParsePGNTag(yy_text, &gameInfo);
9224 if (!err) numPGNTags++;
9226 /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
9227 if(gameInfo.variant != oldVariant) {
9228 startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
9230 oldVariant = gameInfo.variant;
9231 if (appData.debugMode)
9232 fprintf(debugFP, "New variant %d\n", (int) oldVariant);
9236 if (gameInfo.fen != NULL) {
9237 Board initial_position;
9238 startedFromSetupPosition = TRUE;
9239 if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
9241 DisplayError(_("Bad FEN position in file"), 0);
9244 CopyBoard(boards[0], initial_position);
9245 if (blackPlaysFirst) {
9246 currentMove = forwardMostMove = backwardMostMove = 1;
9247 CopyBoard(boards[1], initial_position);
9248 strcpy(moveList[0], "");
9249 strcpy(parseList[0], "");
9250 timeRemaining[0][1] = whiteTimeRemaining;
9251 timeRemaining[1][1] = blackTimeRemaining;
9252 if (commentList[0] != NULL) {
9253 commentList[1] = commentList[0];
9254 commentList[0] = NULL;
9257 currentMove = forwardMostMove = backwardMostMove = 0;
9259 /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
9261 initialRulePlies = FENrulePlies;
9262 epStatus[forwardMostMove] = FENepStatus;
9263 for( i=0; i< nrCastlingRights; i++ )
9264 initialRights[i] = castlingRights[forwardMostMove][i] = FENcastlingRights[i];
9266 yyboardindex = forwardMostMove;
9268 gameInfo.fen = NULL;
9271 yyboardindex = forwardMostMove;
9272 cm = (ChessMove) yylex();
9274 /* Handle comments interspersed among the tags */
9275 while (cm == Comment) {
9277 if (appData.debugMode)
9278 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9280 if (*p == '{' || *p == '[' || *p == '(') {
9281 p[strlen(p) - 1] = NULLCHAR;
9284 while (*p == '\n') p++;
9285 AppendComment(currentMove, p);
9286 yyboardindex = forwardMostMove;
9287 cm = (ChessMove) yylex();
9291 /* don't rely on existence of Event tag since if game was
9292 * pasted from clipboard the Event tag may not exist
9294 if (numPGNTags > 0){
9296 if (gameInfo.variant == VariantNormal) {
9297 gameInfo.variant = StringToVariant(gameInfo.event);
9300 if( appData.autoDisplayTags ) {
9301 tags = PGNTags(&gameInfo);
9302 TagsPopUp(tags, CmailMsg());
9307 /* Make something up, but don't display it now */
9312 if (cm == PositionDiagram) {
9315 Board initial_position;
9317 if (appData.debugMode)
9318 fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
9320 if (!startedFromSetupPosition) {
9322 for (i = BOARD_HEIGHT - 1; i >= 0; i--)
9323 for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
9333 initial_position[i][j++] = CharToPiece(*p);
9336 while (*p == ' ' || *p == '\t' ||
9337 *p == '\n' || *p == '\r') p++;
9339 if (strncmp(p, "black", strlen("black"))==0)
9340 blackPlaysFirst = TRUE;
9342 blackPlaysFirst = FALSE;
9343 startedFromSetupPosition = TRUE;
9345 CopyBoard(boards[0], initial_position);
9346 if (blackPlaysFirst) {
9347 currentMove = forwardMostMove = backwardMostMove = 1;
9348 CopyBoard(boards[1], initial_position);
9349 strcpy(moveList[0], "");
9350 strcpy(parseList[0], "");
9351 timeRemaining[0][1] = whiteTimeRemaining;
9352 timeRemaining[1][1] = blackTimeRemaining;
9353 if (commentList[0] != NULL) {
9354 commentList[1] = commentList[0];
9355 commentList[0] = NULL;
9358 currentMove = forwardMostMove = backwardMostMove = 0;
9361 yyboardindex = forwardMostMove;
9362 cm = (ChessMove) yylex();
9365 if (first.pr == NoProc) {
9366 StartChessProgram(&first);
9368 InitChessProgram(&first, FALSE);
9369 SendToProgram("force\n", &first);
9370 if (startedFromSetupPosition) {
9371 SendBoard(&first, forwardMostMove);
9372 if (appData.debugMode) {
9373 fprintf(debugFP, "Load Game\n");
9375 DisplayBothClocks();
9378 /* [HGM] server: flag to write setup moves in broadcast file as one */
9379 loadFlag = appData.suppressLoadMoves;
9381 while (cm == Comment) {
9383 if (appData.debugMode)
9384 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9386 if (*p == '{' || *p == '[' || *p == '(') {
9387 p[strlen(p) - 1] = NULLCHAR;
9390 while (*p == '\n') p++;
9391 AppendComment(currentMove, p);
9392 yyboardindex = forwardMostMove;
9393 cm = (ChessMove) yylex();
9396 if ((cm == (ChessMove) 0 && lastLoadGameStart != (ChessMove) 0) ||
9397 cm == WhiteWins || cm == BlackWins ||
9398 cm == GameIsDrawn || cm == GameUnfinished) {
9399 DisplayMessage("", _("No moves in game"));
9400 if (cmailMsgLoaded) {
9401 if (appData.debugMode)
9402 fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
9406 DrawPosition(FALSE, boards[currentMove]);
9407 DisplayBothClocks();
9408 gameMode = EditGame;
9415 // [HGM] PV info: routine tests if comment empty
9416 if (!matchMode && (pausing || appData.timeDelay != 0)) {
9417 DisplayComment(currentMove - 1, commentList[currentMove]);
9419 if (!matchMode && appData.timeDelay != 0)
9420 DrawPosition(FALSE, boards[currentMove]);
9422 if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
9423 programStats.ok_to_send = 1;
9426 /* if the first token after the PGN tags is a move
9427 * and not move number 1, retrieve it from the parser
9429 if (cm != MoveNumberOne)
9430 LoadGameOneMove(cm);
9432 /* load the remaining moves from the file */
9433 while (LoadGameOneMove((ChessMove)0)) {
9434 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
9435 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
9438 /* rewind to the start of the game */
9439 currentMove = backwardMostMove;
9441 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
9443 if (oldGameMode == AnalyzeFile ||
9444 oldGameMode == AnalyzeMode) {
9448 if (matchMode || appData.timeDelay == 0) {
9450 gameMode = EditGame;
9452 } else if (appData.timeDelay > 0) {
9456 if (appData.debugMode)
9457 fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
9459 loadFlag = 0; /* [HGM] true game starts */
9463 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
9465 ReloadPosition(offset)
9468 int positionNumber = lastLoadPositionNumber + offset;
9469 if (lastLoadPositionFP == NULL) {
9470 DisplayError(_("No position has been loaded yet"), 0);
9473 if (positionNumber <= 0) {
9474 DisplayError(_("Can't back up any further"), 0);
9477 return LoadPosition(lastLoadPositionFP, positionNumber,
9478 lastLoadPositionTitle);
9481 /* Load the nth position from the given file */
9483 LoadPositionFromFile(filename, n, title)
9491 if (strcmp(filename, "-") == 0) {
9492 return LoadPosition(stdin, n, "stdin");
9494 f = fopen(filename, "rb");
9496 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9497 DisplayError(buf, errno);
9500 return LoadPosition(f, n, title);
9505 /* Load the nth position from the given open file, and close it */
9507 LoadPosition(f, positionNumber, title)
9512 char *p, line[MSG_SIZ];
9513 Board initial_position;
9514 int i, j, fenMode, pn;
9516 if (gameMode == Training )
9517 SetTrainingModeOff();
9519 if (gameMode != BeginningOfGame) {
9522 if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
9523 fclose(lastLoadPositionFP);
9525 if (positionNumber == 0) positionNumber = 1;
9526 lastLoadPositionFP = f;
9527 lastLoadPositionNumber = positionNumber;
9528 strcpy(lastLoadPositionTitle, title);
9529 if (first.pr == NoProc) {
9530 StartChessProgram(&first);
9531 InitChessProgram(&first, FALSE);
9533 pn = positionNumber;
9534 if (positionNumber < 0) {
9535 /* Negative position number means to seek to that byte offset */
9536 if (fseek(f, -positionNumber, 0) == -1) {
9537 DisplayError(_("Can't seek on position file"), 0);
9542 if (fseek(f, 0, 0) == -1) {
9543 if (f == lastLoadPositionFP ?
9544 positionNumber == lastLoadPositionNumber + 1 :
9545 positionNumber == 1) {
9548 DisplayError(_("Can't seek on position file"), 0);
9553 /* See if this file is FEN or old-style xboard */
9554 if (fgets(line, MSG_SIZ, f) == NULL) {
9555 DisplayError(_("Position not found in file"), 0);
9558 // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
9559 fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
9562 if (fenMode || line[0] == '#') pn--;
9564 /* skip positions before number pn */
9565 if (fgets(line, MSG_SIZ, f) == NULL) {
9567 DisplayError(_("Position not found in file"), 0);
9570 if (fenMode || line[0] == '#') pn--;
9575 if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
9576 DisplayError(_("Bad FEN position in file"), 0);
9580 (void) fgets(line, MSG_SIZ, f);
9581 (void) fgets(line, MSG_SIZ, f);
9583 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
9584 (void) fgets(line, MSG_SIZ, f);
9585 for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
9588 initial_position[i][j++] = CharToPiece(*p);
9592 blackPlaysFirst = FALSE;
9594 (void) fgets(line, MSG_SIZ, f);
9595 if (strncmp(line, "black", strlen("black"))==0)
9596 blackPlaysFirst = TRUE;
9599 startedFromSetupPosition = TRUE;
9601 SendToProgram("force\n", &first);
9602 CopyBoard(boards[0], initial_position);
9603 if (blackPlaysFirst) {
9604 currentMove = forwardMostMove = backwardMostMove = 1;
9605 strcpy(moveList[0], "");
9606 strcpy(parseList[0], "");
9607 CopyBoard(boards[1], initial_position);
9608 DisplayMessage("", _("Black to play"));
9610 currentMove = forwardMostMove = backwardMostMove = 0;
9611 DisplayMessage("", _("White to play"));
9613 /* [HGM] copy FEN attributes as well */
9615 initialRulePlies = FENrulePlies;
9616 epStatus[forwardMostMove] = FENepStatus;
9617 for( i=0; i< nrCastlingRights; i++ )
9618 castlingRights[forwardMostMove][i] = FENcastlingRights[i];
9620 SendBoard(&first, forwardMostMove);
9621 if (appData.debugMode) {
9623 for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", castlingRights[i][j]);fprintf(debugFP,"\n");}
9624 for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
9625 fprintf(debugFP, "Load Position\n");
9628 if (positionNumber > 1) {
9629 sprintf(line, "%s %d", title, positionNumber);
9632 DisplayTitle(title);
9634 gameMode = EditGame;
9637 timeRemaining[0][1] = whiteTimeRemaining;
9638 timeRemaining[1][1] = blackTimeRemaining;
9639 DrawPosition(FALSE, boards[currentMove]);
9646 CopyPlayerNameIntoFileName(dest, src)
9649 while (*src != NULLCHAR && *src != ',') {
9654 *(*dest)++ = *src++;
9659 char *DefaultFileName(ext)
9662 static char def[MSG_SIZ];
9665 if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
9667 CopyPlayerNameIntoFileName(&p, gameInfo.white);
9669 CopyPlayerNameIntoFileName(&p, gameInfo.black);
9678 /* Save the current game to the given file */
9680 SaveGameToFile(filename, append)
9687 if (strcmp(filename, "-") == 0) {
9688 return SaveGame(stdout, 0, NULL);
9690 f = fopen(filename, append ? "a" : "w");
9692 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9693 DisplayError(buf, errno);
9696 return SaveGame(f, 0, NULL);
9705 static char buf[MSG_SIZ];
9708 p = strchr(str, ' ');
9709 if (p == NULL) return str;
9710 strncpy(buf, str, p - str);
9711 buf[p - str] = NULLCHAR;
9715 #define PGN_MAX_LINE 75
9717 #define PGN_SIDE_WHITE 0
9718 #define PGN_SIDE_BLACK 1
9721 static int FindFirstMoveOutOfBook( int side )
9725 if( backwardMostMove == 0 && ! startedFromSetupPosition) {
9726 int index = backwardMostMove;
9727 int has_book_hit = 0;
9729 if( (index % 2) != side ) {
9733 while( index < forwardMostMove ) {
9734 /* Check to see if engine is in book */
9735 int depth = pvInfoList[index].depth;
9736 int score = pvInfoList[index].score;
9742 else if( score == 0 && depth == 63 ) {
9743 in_book = 1; /* Zappa */
9745 else if( score == 2 && depth == 99 ) {
9746 in_book = 1; /* Abrok */
9749 has_book_hit += in_book;
9765 void GetOutOfBookInfo( char * buf )
9769 int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
9771 oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
9772 oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
9776 if( oob[0] >= 0 || oob[1] >= 0 ) {
9777 for( i=0; i<2; i++ ) {
9781 if( i > 0 && oob[0] >= 0 ) {
9785 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
9786 sprintf( buf+strlen(buf), "%s%.2f",
9787 pvInfoList[idx].score >= 0 ? "+" : "",
9788 pvInfoList[idx].score / 100.0 );
9794 /* Save game in PGN style and close the file */
9799 int i, offset, linelen, newblock;
9803 int movelen, numlen, blank;
9804 char move_buffer[100]; /* [AS] Buffer for move+PV info */
9806 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
9808 tm = time((time_t *) NULL);
9810 PrintPGNTags(f, &gameInfo);
9812 if (backwardMostMove > 0 || startedFromSetupPosition) {
9813 char *fen = PositionToFEN(backwardMostMove, NULL);
9814 fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
9815 fprintf(f, "\n{--------------\n");
9816 PrintPosition(f, backwardMostMove);
9817 fprintf(f, "--------------}\n");
9821 /* [AS] Out of book annotation */
9822 if( appData.saveOutOfBookInfo ) {
9825 GetOutOfBookInfo( buf );
9827 if( buf[0] != '\0' ) {
9828 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
9835 i = backwardMostMove;
9839 while (i < forwardMostMove) {
9840 /* Print comments preceding this move */
9841 if (commentList[i] != NULL) {
9842 if (linelen > 0) fprintf(f, "\n");
9843 fprintf(f, "{\n%s}\n", commentList[i]);
9848 /* Format move number */
9850 sprintf(numtext, "%d.", (i - offset)/2 + 1);
9853 sprintf(numtext, "%d...", (i - offset)/2 + 1);
9855 numtext[0] = NULLCHAR;
9858 numlen = strlen(numtext);
9861 /* Print move number */
9862 blank = linelen > 0 && numlen > 0;
9863 if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
9872 fprintf(f, "%s", numtext);
9876 strcpy(move_buffer, SavePart(parseList[i])); // [HGM] pgn: print move via buffer, so it can be edited
9877 movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
9880 blank = linelen > 0 && movelen > 0;
9881 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
9890 fprintf(f, "%s", move_buffer);
9893 /* [AS] Add PV info if present */
9894 if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
9895 /* [HGM] add time */
9896 char buf[MSG_SIZ]; int seconds = 0;
9898 if(i >= backwardMostMove) {
9900 seconds = timeRemaining[0][i] - timeRemaining[0][i+1]
9901 + GetTimeQuota(i/2) / (1000*WhitePlayer()->timeOdds);
9903 seconds = timeRemaining[1][i] - timeRemaining[1][i+1]
9904 + GetTimeQuota(i/2) / (1000*WhitePlayer()->other->timeOdds);
9906 seconds = (seconds+50)/100; // deci-seconds, rounded to nearest
9908 if( seconds <= 0) buf[0] = 0; else
9909 if( seconds < 30 ) sprintf(buf, " %3.1f%c", seconds/10., 0); else {
9910 seconds = (seconds + 4)/10; // round to full seconds
9911 if( seconds < 60 ) sprintf(buf, " %d%c", seconds, 0); else
9912 sprintf(buf, " %d:%02d%c", seconds/60, seconds%60, 0);
9915 sprintf( move_buffer, "{%s%.2f/%d%s}",
9916 pvInfoList[i].score >= 0 ? "+" : "",
9917 pvInfoList[i].score / 100.0,
9918 pvInfoList[i].depth,
9921 movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
9923 /* Print score/depth */
9924 blank = linelen > 0 && movelen > 0;
9925 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
9934 fprintf(f, "%s", move_buffer);
9941 /* Start a new line */
9942 if (linelen > 0) fprintf(f, "\n");
9944 /* Print comments after last move */
9945 if (commentList[i] != NULL) {
9946 fprintf(f, "{\n%s}\n", commentList[i]);
9950 if (gameInfo.resultDetails != NULL &&
9951 gameInfo.resultDetails[0] != NULLCHAR) {
9952 fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
9953 PGNResult(gameInfo.result));
9955 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
9959 lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
9963 /* Save game in old style and close the file */
9971 tm = time((time_t *) NULL);
9973 fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
9976 if (backwardMostMove > 0 || startedFromSetupPosition) {
9977 fprintf(f, "\n[--------------\n");
9978 PrintPosition(f, backwardMostMove);
9979 fprintf(f, "--------------]\n");
9984 i = backwardMostMove;
9985 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
9987 while (i < forwardMostMove) {
9988 if (commentList[i] != NULL) {
9989 fprintf(f, "[%s]\n", commentList[i]);
9993 fprintf(f, "%d. ... %s\n", (i - offset)/2 + 1, parseList[i]);
9996 fprintf(f, "%d. %s ", (i - offset)/2 + 1, parseList[i]);
9998 if (commentList[i] != NULL) {
10002 if (i >= forwardMostMove) {
10006 fprintf(f, "%s\n", parseList[i]);
10011 if (commentList[i] != NULL) {
10012 fprintf(f, "[%s]\n", commentList[i]);
10015 /* This isn't really the old style, but it's close enough */
10016 if (gameInfo.resultDetails != NULL &&
10017 gameInfo.resultDetails[0] != NULLCHAR) {
10018 fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
10019 gameInfo.resultDetails);
10021 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
10028 /* Save the current game to open file f and close the file */
10030 SaveGame(f, dummy, dummy2)
10035 if (gameMode == EditPosition) EditPositionDone();
10036 lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
10037 if (appData.oldSaveStyle)
10038 return SaveGameOldStyle(f);
10040 return SaveGamePGN(f);
10043 /* Save the current position to the given file */
10045 SavePositionToFile(filename)
10051 if (strcmp(filename, "-") == 0) {
10052 return SavePosition(stdout, 0, NULL);
10054 f = fopen(filename, "a");
10056 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10057 DisplayError(buf, errno);
10060 SavePosition(f, 0, NULL);
10066 /* Save the current position to the given open file and close the file */
10068 SavePosition(f, dummy, dummy2)
10076 if (appData.oldSaveStyle) {
10077 tm = time((time_t *) NULL);
10079 fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
10081 fprintf(f, "[--------------\n");
10082 PrintPosition(f, currentMove);
10083 fprintf(f, "--------------]\n");
10085 fen = PositionToFEN(currentMove, NULL);
10086 fprintf(f, "%s\n", fen);
10094 ReloadCmailMsgEvent(unregister)
10098 static char *inFilename = NULL;
10099 static char *outFilename;
10101 struct stat inbuf, outbuf;
10104 /* Any registered moves are unregistered if unregister is set, */
10105 /* i.e. invoked by the signal handler */
10107 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
10108 cmailMoveRegistered[i] = FALSE;
10109 if (cmailCommentList[i] != NULL) {
10110 free(cmailCommentList[i]);
10111 cmailCommentList[i] = NULL;
10114 nCmailMovesRegistered = 0;
10117 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
10118 cmailResult[i] = CMAIL_NOT_RESULT;
10122 if (inFilename == NULL) {
10123 /* Because the filenames are static they only get malloced once */
10124 /* and they never get freed */
10125 inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
10126 sprintf(inFilename, "%s.game.in", appData.cmailGameName);
10128 outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
10129 sprintf(outFilename, "%s.out", appData.cmailGameName);
10132 status = stat(outFilename, &outbuf);
10134 cmailMailedMove = FALSE;
10136 status = stat(inFilename, &inbuf);
10137 cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
10140 /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
10141 counts the games, notes how each one terminated, etc.
10143 It would be nice to remove this kludge and instead gather all
10144 the information while building the game list. (And to keep it
10145 in the game list nodes instead of having a bunch of fixed-size
10146 parallel arrays.) Note this will require getting each game's
10147 termination from the PGN tags, as the game list builder does
10148 not process the game moves. --mann
10150 cmailMsgLoaded = TRUE;
10151 LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
10153 /* Load first game in the file or popup game menu */
10154 LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
10156 #endif /* !WIN32 */
10164 char string[MSG_SIZ];
10166 if ( cmailMailedMove
10167 || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
10168 return TRUE; /* Allow free viewing */
10171 /* Unregister move to ensure that we don't leave RegisterMove */
10172 /* with the move registered when the conditions for registering no */
10174 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
10175 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
10176 nCmailMovesRegistered --;
10178 if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
10180 free(cmailCommentList[lastLoadGameNumber - 1]);
10181 cmailCommentList[lastLoadGameNumber - 1] = NULL;
10185 if (cmailOldMove == -1) {
10186 DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
10190 if (currentMove > cmailOldMove + 1) {
10191 DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
10195 if (currentMove < cmailOldMove) {
10196 DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
10200 if (forwardMostMove > currentMove) {
10201 /* Silently truncate extra moves */
10205 if ( (currentMove == cmailOldMove + 1)
10206 || ( (currentMove == cmailOldMove)
10207 && ( (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
10208 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
10209 if (gameInfo.result != GameUnfinished) {
10210 cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
10213 if (commentList[currentMove] != NULL) {
10214 cmailCommentList[lastLoadGameNumber - 1]
10215 = StrSave(commentList[currentMove]);
10217 strcpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1]);
10219 if (appData.debugMode)
10220 fprintf(debugFP, "Saving %s for game %d\n",
10221 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
10224 "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
10226 f = fopen(string, "w");
10227 if (appData.oldSaveStyle) {
10228 SaveGameOldStyle(f); /* also closes the file */
10230 sprintf(string, "%s.pos.out", appData.cmailGameName);
10231 f = fopen(string, "w");
10232 SavePosition(f, 0, NULL); /* also closes the file */
10234 fprintf(f, "{--------------\n");
10235 PrintPosition(f, currentMove);
10236 fprintf(f, "--------------}\n\n");
10238 SaveGame(f, 0, NULL); /* also closes the file*/
10241 cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
10242 nCmailMovesRegistered ++;
10243 } else if (nCmailGames == 1) {
10244 DisplayError(_("You have not made a move yet"), 0);
10255 static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
10256 FILE *commandOutput;
10257 char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
10258 int nBytes = 0; /* Suppress warnings on uninitialized variables */
10264 if (! cmailMsgLoaded) {
10265 DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
10269 if (nCmailGames == nCmailResults) {
10270 DisplayError(_("No unfinished games"), 0);
10274 #if CMAIL_PROHIBIT_REMAIL
10275 if (cmailMailedMove) {
10276 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);
10277 DisplayError(msg, 0);
10282 if (! (cmailMailedMove || RegisterMove())) return;
10284 if ( cmailMailedMove
10285 || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
10286 sprintf(string, partCommandString,
10287 appData.debugMode ? " -v" : "", appData.cmailGameName);
10288 commandOutput = popen(string, "r");
10290 if (commandOutput == NULL) {
10291 DisplayError(_("Failed to invoke cmail"), 0);
10293 for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
10294 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
10296 if (nBuffers > 1) {
10297 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
10298 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
10299 nBytes = MSG_SIZ - 1;
10301 (void) memcpy(msg, buffer, nBytes);
10303 *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
10305 if(StrStr(msg, "Mailed cmail message to ") != NULL) {
10306 cmailMailedMove = TRUE; /* Prevent >1 moves */
10309 for (i = 0; i < nCmailGames; i ++) {
10310 if (cmailResult[i] == CMAIL_NOT_RESULT) {
10315 && ( (arcDir = (char *) getenv("CMAIL_ARCDIR"))
10317 sprintf(buffer, "%s/%s.%s.archive",
10319 appData.cmailGameName,
10321 LoadGameFromFile(buffer, 1, buffer, FALSE);
10322 cmailMsgLoaded = FALSE;
10326 DisplayInformation(msg);
10327 pclose(commandOutput);
10330 if ((*cmailMsg) != '\0') {
10331 DisplayInformation(cmailMsg);
10336 #endif /* !WIN32 */
10345 int prependComma = 0;
10347 char string[MSG_SIZ]; /* Space for game-list */
10350 if (!cmailMsgLoaded) return "";
10352 if (cmailMailedMove) {
10353 sprintf(cmailMsg, _("Waiting for reply from opponent\n"));
10355 /* Create a list of games left */
10356 sprintf(string, "[");
10357 for (i = 0; i < nCmailGames; i ++) {
10358 if (! ( cmailMoveRegistered[i]
10359 || (cmailResult[i] == CMAIL_OLD_RESULT))) {
10360 if (prependComma) {
10361 sprintf(number, ",%d", i + 1);
10363 sprintf(number, "%d", i + 1);
10367 strcat(string, number);
10370 strcat(string, "]");
10372 if (nCmailMovesRegistered + nCmailResults == 0) {
10373 switch (nCmailGames) {
10376 _("Still need to make move for game\n"));
10381 _("Still need to make moves for both games\n"));
10386 _("Still need to make moves for all %d games\n"),
10391 switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
10394 _("Still need to make a move for game %s\n"),
10399 if (nCmailResults == nCmailGames) {
10400 sprintf(cmailMsg, _("No unfinished games\n"));
10402 sprintf(cmailMsg, _("Ready to send mail\n"));
10408 _("Still need to make moves for games %s\n"),
10420 if (gameMode == Training)
10421 SetTrainingModeOff();
10424 cmailMsgLoaded = FALSE;
10425 if (appData.icsActive) {
10426 SendToICS(ics_prefix);
10427 SendToICS("refresh\n");
10437 /* Give up on clean exit */
10441 /* Keep trying for clean exit */
10445 if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
10447 if (telnetISR != NULL) {
10448 RemoveInputSource(telnetISR);
10450 if (icsPR != NoProc) {
10451 DestroyChildProcess(icsPR, TRUE);
10454 /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
10455 GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
10457 /* [HGM] crash: the above GameEnds() is a dud if another one was running */
10458 /* make sure this other one finishes before killing it! */
10459 if(endingGame) { int count = 0;
10460 if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
10461 while(endingGame && count++ < 10) DoSleep(1);
10462 if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
10465 /* Kill off chess programs */
10466 if (first.pr != NoProc) {
10469 DoSleep( appData.delayBeforeQuit );
10470 SendToProgram("quit\n", &first);
10471 DoSleep( appData.delayAfterQuit );
10472 DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
10474 if (second.pr != NoProc) {
10475 DoSleep( appData.delayBeforeQuit );
10476 SendToProgram("quit\n", &second);
10477 DoSleep( appData.delayAfterQuit );
10478 DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
10480 if (first.isr != NULL) {
10481 RemoveInputSource(first.isr);
10483 if (second.isr != NULL) {
10484 RemoveInputSource(second.isr);
10487 ShutDownFrontEnd();
10494 if (appData.debugMode)
10495 fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
10499 if (gameMode == MachinePlaysWhite ||
10500 gameMode == MachinePlaysBlack) {
10503 DisplayBothClocks();
10505 if (gameMode == PlayFromGameFile) {
10506 if (appData.timeDelay >= 0)
10507 AutoPlayGameLoop();
10508 } else if (gameMode == IcsExamining && pauseExamInvalid) {
10509 Reset(FALSE, TRUE);
10510 SendToICS(ics_prefix);
10511 SendToICS("refresh\n");
10512 } else if (currentMove < forwardMostMove) {
10513 ForwardInner(forwardMostMove);
10515 pauseExamInvalid = FALSE;
10517 switch (gameMode) {
10521 pauseExamForwardMostMove = forwardMostMove;
10522 pauseExamInvalid = FALSE;
10525 case IcsPlayingWhite:
10526 case IcsPlayingBlack:
10530 case PlayFromGameFile:
10531 (void) StopLoadGameTimer();
10535 case BeginningOfGame:
10536 if (appData.icsActive) return;
10537 /* else fall through */
10538 case MachinePlaysWhite:
10539 case MachinePlaysBlack:
10540 case TwoMachinesPlay:
10541 if (forwardMostMove == 0)
10542 return; /* don't pause if no one has moved */
10543 if ((gameMode == MachinePlaysWhite &&
10544 !WhiteOnMove(forwardMostMove)) ||
10545 (gameMode == MachinePlaysBlack &&
10546 WhiteOnMove(forwardMostMove))) {
10559 char title[MSG_SIZ];
10561 if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
10562 strcpy(title, _("Edit comment"));
10564 sprintf(title, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
10565 WhiteOnMove(currentMove - 1) ? " " : ".. ",
10566 parseList[currentMove - 1]);
10569 EditCommentPopUp(currentMove, title, commentList[currentMove]);
10576 char *tags = PGNTags(&gameInfo);
10577 EditTagsPopUp(tags);
10584 if (appData.noChessProgram || gameMode == AnalyzeMode)
10587 if (gameMode != AnalyzeFile) {
10588 if (!appData.icsEngineAnalyze) {
10590 if (gameMode != EditGame) return;
10592 ResurrectChessProgram();
10593 SendToProgram("analyze\n", &first);
10594 first.analyzing = TRUE;
10595 /*first.maybeThinking = TRUE;*/
10596 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
10597 EngineOutputPopUp();
10599 if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
10604 StartAnalysisClock();
10605 GetTimeMark(&lastNodeCountTime);
10612 if (appData.noChessProgram || gameMode == AnalyzeFile)
10615 if (gameMode != AnalyzeMode) {
10617 if (gameMode != EditGame) return;
10618 ResurrectChessProgram();
10619 SendToProgram("analyze\n", &first);
10620 first.analyzing = TRUE;
10621 /*first.maybeThinking = TRUE;*/
10622 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
10623 EngineOutputPopUp();
10625 gameMode = AnalyzeFile;
10630 StartAnalysisClock();
10631 GetTimeMark(&lastNodeCountTime);
10636 MachineWhiteEvent()
10639 char *bookHit = NULL;
10641 if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
10645 if (gameMode == PlayFromGameFile ||
10646 gameMode == TwoMachinesPlay ||
10647 gameMode == Training ||
10648 gameMode == AnalyzeMode ||
10649 gameMode == EndOfGame)
10652 if (gameMode == EditPosition)
10653 EditPositionDone();
10655 if (!WhiteOnMove(currentMove)) {
10656 DisplayError(_("It is not White's turn"), 0);
10660 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
10663 if (gameMode == EditGame || gameMode == AnalyzeMode ||
10664 gameMode == AnalyzeFile)
10667 ResurrectChessProgram(); /* in case it isn't running */
10668 if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
10669 gameMode = MachinePlaysWhite;
10672 gameMode = MachinePlaysWhite;
10676 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10678 if (first.sendName) {
10679 sprintf(buf, "name %s\n", gameInfo.black);
10680 SendToProgram(buf, &first);
10682 if (first.sendTime) {
10683 if (first.useColors) {
10684 SendToProgram("black\n", &first); /*gnu kludge*/
10686 SendTimeRemaining(&first, TRUE);
10688 if (first.useColors) {
10689 SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
10691 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
10692 SetMachineThinkingEnables();
10693 first.maybeThinking = TRUE;
10697 if (appData.autoFlipView && !flipView) {
10698 flipView = !flipView;
10699 DrawPosition(FALSE, NULL);
10700 DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;
10703 if(bookHit) { // [HGM] book: simulate book reply
10704 static char bookMove[MSG_SIZ]; // a bit generous?
10706 programStats.nodes = programStats.depth = programStats.time =
10707 programStats.score = programStats.got_only_move = 0;
10708 sprintf(programStats.movelist, "%s (xbook)", bookHit);
10710 strcpy(bookMove, "move ");
10711 strcat(bookMove, bookHit);
10712 HandleMachineMove(bookMove, &first);
10717 MachineBlackEvent()
10720 char *bookHit = NULL;
10722 if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
10726 if (gameMode == PlayFromGameFile ||
10727 gameMode == TwoMachinesPlay ||
10728 gameMode == Training ||
10729 gameMode == AnalyzeMode ||
10730 gameMode == EndOfGame)
10733 if (gameMode == EditPosition)
10734 EditPositionDone();
10736 if (WhiteOnMove(currentMove)) {
10737 DisplayError(_("It is not Black's turn"), 0);
10741 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
10744 if (gameMode == EditGame || gameMode == AnalyzeMode ||
10745 gameMode == AnalyzeFile)
10748 ResurrectChessProgram(); /* in case it isn't running */
10749 gameMode = MachinePlaysBlack;
10753 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10755 if (first.sendName) {
10756 sprintf(buf, "name %s\n", gameInfo.white);
10757 SendToProgram(buf, &first);
10759 if (first.sendTime) {
10760 if (first.useColors) {
10761 SendToProgram("white\n", &first); /*gnu kludge*/
10763 SendTimeRemaining(&first, FALSE);
10765 if (first.useColors) {
10766 SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
10768 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
10769 SetMachineThinkingEnables();
10770 first.maybeThinking = TRUE;
10773 if (appData.autoFlipView && flipView) {
10774 flipView = !flipView;
10775 DrawPosition(FALSE, NULL);
10776 DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;
10778 if(bookHit) { // [HGM] book: simulate book reply
10779 static char bookMove[MSG_SIZ]; // a bit generous?
10781 programStats.nodes = programStats.depth = programStats.time =
10782 programStats.score = programStats.got_only_move = 0;
10783 sprintf(programStats.movelist, "%s (xbook)", bookHit);
10785 strcpy(bookMove, "move ");
10786 strcat(bookMove, bookHit);
10787 HandleMachineMove(bookMove, &first);
10793 DisplayTwoMachinesTitle()
10796 if (appData.matchGames > 0) {
10797 if (first.twoMachinesColor[0] == 'w') {
10798 sprintf(buf, "%s vs. %s (%d-%d-%d)",
10799 gameInfo.white, gameInfo.black,
10800 first.matchWins, second.matchWins,
10801 matchGame - 1 - (first.matchWins + second.matchWins));
10803 sprintf(buf, "%s vs. %s (%d-%d-%d)",
10804 gameInfo.white, gameInfo.black,
10805 second.matchWins, first.matchWins,
10806 matchGame - 1 - (first.matchWins + second.matchWins));
10809 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10815 TwoMachinesEvent P((void))
10819 ChessProgramState *onmove;
10820 char *bookHit = NULL;
10822 if (appData.noChessProgram) return;
10824 switch (gameMode) {
10825 case TwoMachinesPlay:
10827 case MachinePlaysWhite:
10828 case MachinePlaysBlack:
10829 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
10830 DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
10834 case BeginningOfGame:
10835 case PlayFromGameFile:
10838 if (gameMode != EditGame) return;
10841 EditPositionDone();
10852 forwardMostMove = currentMove;
10853 ResurrectChessProgram(); /* in case first program isn't running */
10855 if (second.pr == NULL) {
10856 StartChessProgram(&second);
10857 if (second.protocolVersion == 1) {
10858 TwoMachinesEventIfReady();
10860 /* kludge: allow timeout for initial "feature" command */
10862 DisplayMessage("", _("Starting second chess program"));
10863 ScheduleDelayedEvent(TwoMachinesEventIfReady, FEATURE_TIMEOUT);
10867 DisplayMessage("", "");
10868 InitChessProgram(&second, FALSE);
10869 SendToProgram("force\n", &second);
10870 if (startedFromSetupPosition) {
10871 SendBoard(&second, backwardMostMove);
10872 if (appData.debugMode) {
10873 fprintf(debugFP, "Two Machines\n");
10876 for (i = backwardMostMove; i < forwardMostMove; i++) {
10877 SendMoveToProgram(i, &second);
10880 gameMode = TwoMachinesPlay;
10884 DisplayTwoMachinesTitle();
10886 if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
10892 SendToProgram(first.computerString, &first);
10893 if (first.sendName) {
10894 sprintf(buf, "name %s\n", second.tidy);
10895 SendToProgram(buf, &first);
10897 SendToProgram(second.computerString, &second);
10898 if (second.sendName) {
10899 sprintf(buf, "name %s\n", first.tidy);
10900 SendToProgram(buf, &second);
10904 if (!first.sendTime || !second.sendTime) {
10905 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10906 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10908 if (onmove->sendTime) {
10909 if (onmove->useColors) {
10910 SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
10912 SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
10914 if (onmove->useColors) {
10915 SendToProgram(onmove->twoMachinesColor, onmove);
10917 bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
10918 // SendToProgram("go\n", onmove);
10919 onmove->maybeThinking = TRUE;
10920 SetMachineThinkingEnables();
10924 if(bookHit) { // [HGM] book: simulate book reply
10925 static char bookMove[MSG_SIZ]; // a bit generous?
10927 programStats.nodes = programStats.depth = programStats.time =
10928 programStats.score = programStats.got_only_move = 0;
10929 sprintf(programStats.movelist, "%s (xbook)", bookHit);
10931 strcpy(bookMove, "move ");
10932 strcat(bookMove, bookHit);
10933 savedMessage = bookMove; // args for deferred call
10934 savedState = onmove;
10935 ScheduleDelayedEvent(DeferredBookMove, 1);
10942 if (gameMode == Training) {
10943 SetTrainingModeOff();
10944 gameMode = PlayFromGameFile;
10945 DisplayMessage("", _("Training mode off"));
10947 gameMode = Training;
10948 animateTraining = appData.animate;
10950 /* make sure we are not already at the end of the game */
10951 if (currentMove < forwardMostMove) {
10952 SetTrainingModeOn();
10953 DisplayMessage("", _("Training mode on"));
10955 gameMode = PlayFromGameFile;
10956 DisplayError(_("Already at end of game"), 0);
10965 if (!appData.icsActive) return;
10966 switch (gameMode) {
10967 case IcsPlayingWhite:
10968 case IcsPlayingBlack:
10971 case BeginningOfGame:
10979 EditPositionDone();
10992 gameMode = IcsIdle;
11003 switch (gameMode) {
11005 SetTrainingModeOff();
11007 case MachinePlaysWhite:
11008 case MachinePlaysBlack:
11009 case BeginningOfGame:
11010 SendToProgram("force\n", &first);
11011 SetUserThinkingEnables();
11013 case PlayFromGameFile:
11014 (void) StopLoadGameTimer();
11015 if (gameFileFP != NULL) {
11020 EditPositionDone();
11025 SendToProgram("force\n", &first);
11027 case TwoMachinesPlay:
11028 GameEnds((ChessMove) 0, NULL, GE_PLAYER);
11029 ResurrectChessProgram();
11030 SetUserThinkingEnables();
11033 ResurrectChessProgram();
11035 case IcsPlayingBlack:
11036 case IcsPlayingWhite:
11037 DisplayError(_("Warning: You are still playing a game"), 0);
11040 DisplayError(_("Warning: You are still observing a game"), 0);
11043 DisplayError(_("Warning: You are still examining a game"), 0);
11054 first.offeredDraw = second.offeredDraw = 0;
11056 if (gameMode == PlayFromGameFile) {
11057 whiteTimeRemaining = timeRemaining[0][currentMove];
11058 blackTimeRemaining = timeRemaining[1][currentMove];
11062 if (gameMode == MachinePlaysWhite ||
11063 gameMode == MachinePlaysBlack ||
11064 gameMode == TwoMachinesPlay ||
11065 gameMode == EndOfGame) {
11066 i = forwardMostMove;
11067 while (i > currentMove) {
11068 SendToProgram("undo\n", &first);
11071 whiteTimeRemaining = timeRemaining[0][currentMove];
11072 blackTimeRemaining = timeRemaining[1][currentMove];
11073 DisplayBothClocks();
11074 if (whiteFlag || blackFlag) {
11075 whiteFlag = blackFlag = 0;
11080 gameMode = EditGame;
11087 EditPositionEvent()
11089 if (gameMode == EditPosition) {
11095 if (gameMode != EditGame) return;
11097 gameMode = EditPosition;
11100 if (currentMove > 0)
11101 CopyBoard(boards[0], boards[currentMove]);
11103 blackPlaysFirst = !WhiteOnMove(currentMove);
11105 currentMove = forwardMostMove = backwardMostMove = 0;
11106 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11113 /* [DM] icsEngineAnalyze - possible call from other functions */
11114 if (appData.icsEngineAnalyze) {
11115 appData.icsEngineAnalyze = FALSE;
11117 DisplayMessage("",_("Close ICS engine analyze..."));
11119 if (first.analysisSupport && first.analyzing) {
11120 SendToProgram("exit\n", &first);
11121 first.analyzing = FALSE;
11123 thinkOutput[0] = NULLCHAR;
11129 int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
11131 startedFromSetupPosition = TRUE;
11132 InitChessProgram(&first, FALSE);
11133 castlingRights[0][2] = castlingRights[0][5] = BOARD_WIDTH>>1;
11134 if(boards[0][0][BOARD_WIDTH>>1] == king) {
11135 castlingRights[0][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : -1;
11136 castlingRights[0][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : -1;
11137 } else castlingRights[0][2] = -1;
11138 if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
11139 castlingRights[0][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : -1;
11140 castlingRights[0][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : -1;
11141 } else castlingRights[0][5] = -1;
11142 SendToProgram("force\n", &first);
11143 if (blackPlaysFirst) {
11144 strcpy(moveList[0], "");
11145 strcpy(parseList[0], "");
11146 currentMove = forwardMostMove = backwardMostMove = 1;
11147 CopyBoard(boards[1], boards[0]);
11148 /* [HGM] copy rights as well, as this code is also used after pasting a FEN */
11150 epStatus[1] = epStatus[0];
11151 for(i=0; i<nrCastlingRights; i++) castlingRights[1][i] = castlingRights[0][i];
11154 currentMove = forwardMostMove = backwardMostMove = 0;
11156 SendBoard(&first, forwardMostMove);
11157 if (appData.debugMode) {
11158 fprintf(debugFP, "EditPosDone\n");
11161 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11162 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11163 gameMode = EditGame;
11165 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11166 ClearHighlights(); /* [AS] */
11169 /* Pause for `ms' milliseconds */
11170 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
11180 } while (SubtractTimeMarks(&m2, &m1) < ms);
11183 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
11185 SendMultiLineToICS(buf)
11188 char temp[MSG_SIZ+1], *p;
11195 strncpy(temp, buf, len);
11200 if (*p == '\n' || *p == '\r')
11205 strcat(temp, "\n");
11207 SendToPlayer(temp, strlen(temp));
11211 SetWhiteToPlayEvent()
11213 if (gameMode == EditPosition) {
11214 blackPlaysFirst = FALSE;
11215 DisplayBothClocks(); /* works because currentMove is 0 */
11216 } else if (gameMode == IcsExamining) {
11217 SendToICS(ics_prefix);
11218 SendToICS("tomove white\n");
11223 SetBlackToPlayEvent()
11225 if (gameMode == EditPosition) {
11226 blackPlaysFirst = TRUE;
11227 currentMove = 1; /* kludge */
11228 DisplayBothClocks();
11230 } else if (gameMode == IcsExamining) {
11231 SendToICS(ics_prefix);
11232 SendToICS("tomove black\n");
11237 EditPositionMenuEvent(selection, x, y)
11238 ChessSquare selection;
11242 ChessSquare piece = boards[0][y][x];
11244 if (gameMode != EditPosition && gameMode != IcsExamining) return;
11246 switch (selection) {
11248 if (gameMode == IcsExamining && ics_type == ICS_FICS) {
11249 SendToICS(ics_prefix);
11250 SendToICS("bsetup clear\n");
11251 } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
11252 SendToICS(ics_prefix);
11253 SendToICS("clearboard\n");
11255 for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
11256 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
11257 for (y = 0; y < BOARD_HEIGHT; y++) {
11258 if (gameMode == IcsExamining) {
11259 if (boards[currentMove][y][x] != EmptySquare) {
11260 sprintf(buf, "%sx@%c%c\n", ics_prefix,
11265 boards[0][y][x] = p;
11270 if (gameMode == EditPosition) {
11271 DrawPosition(FALSE, boards[0]);
11276 SetWhiteToPlayEvent();
11280 SetBlackToPlayEvent();
11284 if (gameMode == IcsExamining) {
11285 sprintf(buf, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
11288 boards[0][y][x] = EmptySquare;
11289 DrawPosition(FALSE, boards[0]);
11294 if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
11295 piece >= (int)BlackPawn && piece < (int)BlackMan ) {
11296 selection = (ChessSquare) (PROMOTED piece);
11297 } else if(piece == EmptySquare) selection = WhiteSilver;
11298 else selection = (ChessSquare)((int)piece - 1);
11302 if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
11303 piece > (int)BlackMan && piece <= (int)BlackKing ) {
11304 selection = (ChessSquare) (DEMOTED piece);
11305 } else if(piece == EmptySquare) selection = BlackSilver;
11306 else selection = (ChessSquare)((int)piece + 1);
11311 if(gameInfo.variant == VariantShatranj ||
11312 gameInfo.variant == VariantXiangqi ||
11313 gameInfo.variant == VariantCourier )
11314 selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
11319 if(gameInfo.variant == VariantXiangqi)
11320 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
11321 if(gameInfo.variant == VariantKnightmate)
11322 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
11325 if (gameMode == IcsExamining) {
11326 sprintf(buf, "%s%c@%c%c\n", ics_prefix,
11327 PieceToChar(selection), AAA + x, ONE + y);
11330 boards[0][y][x] = selection;
11331 DrawPosition(FALSE, boards[0]);
11339 DropMenuEvent(selection, x, y)
11340 ChessSquare selection;
11343 ChessMove moveType;
11345 switch (gameMode) {
11346 case IcsPlayingWhite:
11347 case MachinePlaysBlack:
11348 if (!WhiteOnMove(currentMove)) {
11349 DisplayMoveError(_("It is Black's turn"));
11352 moveType = WhiteDrop;
11354 case IcsPlayingBlack:
11355 case MachinePlaysWhite:
11356 if (WhiteOnMove(currentMove)) {
11357 DisplayMoveError(_("It is White's turn"));
11360 moveType = BlackDrop;
11363 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
11369 if (moveType == BlackDrop && selection < BlackPawn) {
11370 selection = (ChessSquare) ((int) selection
11371 + (int) BlackPawn - (int) WhitePawn);
11373 if (boards[currentMove][y][x] != EmptySquare) {
11374 DisplayMoveError(_("That square is occupied"));
11378 FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
11384 /* Accept a pending offer of any kind from opponent */
11386 if (appData.icsActive) {
11387 SendToICS(ics_prefix);
11388 SendToICS("accept\n");
11389 } else if (cmailMsgLoaded) {
11390 if (currentMove == cmailOldMove &&
11391 commentList[cmailOldMove] != NULL &&
11392 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11393 "Black offers a draw" : "White offers a draw")) {
11395 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11396 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
11398 DisplayError(_("There is no pending offer on this move"), 0);
11399 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11402 /* Not used for offers from chess program */
11409 /* Decline a pending offer of any kind from opponent */
11411 if (appData.icsActive) {
11412 SendToICS(ics_prefix);
11413 SendToICS("decline\n");
11414 } else if (cmailMsgLoaded) {
11415 if (currentMove == cmailOldMove &&
11416 commentList[cmailOldMove] != NULL &&
11417 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11418 "Black offers a draw" : "White offers a draw")) {
11420 AppendComment(cmailOldMove, "Draw declined");
11421 DisplayComment(cmailOldMove - 1, "Draw declined");
11424 DisplayError(_("There is no pending offer on this move"), 0);
11427 /* Not used for offers from chess program */
11434 /* Issue ICS rematch command */
11435 if (appData.icsActive) {
11436 SendToICS(ics_prefix);
11437 SendToICS("rematch\n");
11444 /* Call your opponent's flag (claim a win on time) */
11445 if (appData.icsActive) {
11446 SendToICS(ics_prefix);
11447 SendToICS("flag\n");
11449 switch (gameMode) {
11452 case MachinePlaysWhite:
11455 GameEnds(GameIsDrawn, "Both players ran out of time",
11458 GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
11460 DisplayError(_("Your opponent is not out of time"), 0);
11463 case MachinePlaysBlack:
11466 GameEnds(GameIsDrawn, "Both players ran out of time",
11469 GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
11471 DisplayError(_("Your opponent is not out of time"), 0);
11481 /* Offer draw or accept pending draw offer from opponent */
11483 if (appData.icsActive) {
11484 /* Note: tournament rules require draw offers to be
11485 made after you make your move but before you punch
11486 your clock. Currently ICS doesn't let you do that;
11487 instead, you immediately punch your clock after making
11488 a move, but you can offer a draw at any time. */
11490 SendToICS(ics_prefix);
11491 SendToICS("draw\n");
11492 } else if (cmailMsgLoaded) {
11493 if (currentMove == cmailOldMove &&
11494 commentList[cmailOldMove] != NULL &&
11495 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11496 "Black offers a draw" : "White offers a draw")) {
11497 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11498 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
11499 } else if (currentMove == cmailOldMove + 1) {
11500 char *offer = WhiteOnMove(cmailOldMove) ?
11501 "White offers a draw" : "Black offers a draw";
11502 AppendComment(currentMove, offer);
11503 DisplayComment(currentMove - 1, offer);
11504 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
11506 DisplayError(_("You must make your move before offering a draw"), 0);
11507 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11509 } else if (first.offeredDraw) {
11510 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
11512 if (first.sendDrawOffers) {
11513 SendToProgram("draw\n", &first);
11514 userOfferedDraw = TRUE;
11522 /* Offer Adjourn or accept pending Adjourn offer from opponent */
11524 if (appData.icsActive) {
11525 SendToICS(ics_prefix);
11526 SendToICS("adjourn\n");
11528 /* Currently GNU Chess doesn't offer or accept Adjourns */
11536 /* Offer Abort or accept pending Abort offer from opponent */
11538 if (appData.icsActive) {
11539 SendToICS(ics_prefix);
11540 SendToICS("abort\n");
11542 GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
11549 /* Resign. You can do this even if it's not your turn. */
11551 if (appData.icsActive) {
11552 SendToICS(ics_prefix);
11553 SendToICS("resign\n");
11555 switch (gameMode) {
11556 case MachinePlaysWhite:
11557 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11559 case MachinePlaysBlack:
11560 GameEnds(BlackWins, "White resigns", GE_PLAYER);
11563 if (cmailMsgLoaded) {
11565 if (WhiteOnMove(cmailOldMove)) {
11566 GameEnds(BlackWins, "White resigns", GE_PLAYER);
11568 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11570 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
11581 StopObservingEvent()
11583 /* Stop observing current games */
11584 SendToICS(ics_prefix);
11585 SendToICS("unobserve\n");
11589 StopExaminingEvent()
11591 /* Stop observing current game */
11592 SendToICS(ics_prefix);
11593 SendToICS("unexamine\n");
11597 ForwardInner(target)
11602 if (appData.debugMode)
11603 fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
11604 target, currentMove, forwardMostMove);
11606 if (gameMode == EditPosition)
11609 if (gameMode == PlayFromGameFile && !pausing)
11612 if (gameMode == IcsExamining && pausing)
11613 limit = pauseExamForwardMostMove;
11615 limit = forwardMostMove;
11617 if (target > limit) target = limit;
11619 if (target > 0 && moveList[target - 1][0]) {
11620 int fromX, fromY, toX, toY;
11621 toX = moveList[target - 1][2] - AAA;
11622 toY = moveList[target - 1][3] - ONE;
11623 if (moveList[target - 1][1] == '@') {
11624 if (appData.highlightLastMove) {
11625 SetHighlights(-1, -1, toX, toY);
11628 fromX = moveList[target - 1][0] - AAA;
11629 fromY = moveList[target - 1][1] - ONE;
11630 if (target == currentMove + 1) {
11631 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
11633 if (appData.highlightLastMove) {
11634 SetHighlights(fromX, fromY, toX, toY);
11638 if (gameMode == EditGame || gameMode == AnalyzeMode ||
11639 gameMode == Training || gameMode == PlayFromGameFile ||
11640 gameMode == AnalyzeFile) {
11641 while (currentMove < target) {
11642 SendMoveToProgram(currentMove++, &first);
11645 currentMove = target;
11648 if (gameMode == EditGame || gameMode == EndOfGame) {
11649 whiteTimeRemaining = timeRemaining[0][currentMove];
11650 blackTimeRemaining = timeRemaining[1][currentMove];
11652 DisplayBothClocks();
11653 DisplayMove(currentMove - 1);
11654 DrawPosition(FALSE, boards[currentMove]);
11655 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
11656 if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
11657 DisplayComment(currentMove - 1, commentList[currentMove]);
11665 if (gameMode == IcsExamining && !pausing) {
11666 SendToICS(ics_prefix);
11667 SendToICS("forward\n");
11669 ForwardInner(currentMove + 1);
11676 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11677 /* to optimze, we temporarily turn off analysis mode while we feed
11678 * the remaining moves to the engine. Otherwise we get analysis output
11681 if (first.analysisSupport) {
11682 SendToProgram("exit\nforce\n", &first);
11683 first.analyzing = FALSE;
11687 if (gameMode == IcsExamining && !pausing) {
11688 SendToICS(ics_prefix);
11689 SendToICS("forward 999999\n");
11691 ForwardInner(forwardMostMove);
11694 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11695 /* we have fed all the moves, so reactivate analysis mode */
11696 SendToProgram("analyze\n", &first);
11697 first.analyzing = TRUE;
11698 /*first.maybeThinking = TRUE;*/
11699 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11704 BackwardInner(target)
11707 int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
11709 if (appData.debugMode)
11710 fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
11711 target, currentMove, forwardMostMove);
11713 if (gameMode == EditPosition) return;
11714 if (currentMove <= backwardMostMove) {
11716 DrawPosition(full_redraw, boards[currentMove]);
11719 if (gameMode == PlayFromGameFile && !pausing)
11722 if (moveList[target][0]) {
11723 int fromX, fromY, toX, toY;
11724 toX = moveList[target][2] - AAA;
11725 toY = moveList[target][3] - ONE;
11726 if (moveList[target][1] == '@') {
11727 if (appData.highlightLastMove) {
11728 SetHighlights(-1, -1, toX, toY);
11731 fromX = moveList[target][0] - AAA;
11732 fromY = moveList[target][1] - ONE;
11733 if (target == currentMove - 1) {
11734 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
11736 if (appData.highlightLastMove) {
11737 SetHighlights(fromX, fromY, toX, toY);
11741 if (gameMode == EditGame || gameMode==AnalyzeMode ||
11742 gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
11743 while (currentMove > target) {
11744 SendToProgram("undo\n", &first);
11748 currentMove = target;
11751 if (gameMode == EditGame || gameMode == EndOfGame) {
11752 whiteTimeRemaining = timeRemaining[0][currentMove];
11753 blackTimeRemaining = timeRemaining[1][currentMove];
11755 DisplayBothClocks();
11756 DisplayMove(currentMove - 1);
11757 DrawPosition(full_redraw, boards[currentMove]);
11758 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
11759 // [HGM] PV info: routine tests if comment empty
11760 DisplayComment(currentMove - 1, commentList[currentMove]);
11766 if (gameMode == IcsExamining && !pausing) {
11767 SendToICS(ics_prefix);
11768 SendToICS("backward\n");
11770 BackwardInner(currentMove - 1);
11777 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11778 /* to optimze, we temporarily turn off analysis mode while we undo
11779 * all the moves. Otherwise we get analysis output after each undo.
11781 if (first.analysisSupport) {
11782 SendToProgram("exit\nforce\n", &first);
11783 first.analyzing = FALSE;
11787 if (gameMode == IcsExamining && !pausing) {
11788 SendToICS(ics_prefix);
11789 SendToICS("backward 999999\n");
11791 BackwardInner(backwardMostMove);
11794 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11795 /* we have fed all the moves, so reactivate analysis mode */
11796 SendToProgram("analyze\n", &first);
11797 first.analyzing = TRUE;
11798 /*first.maybeThinking = TRUE;*/
11799 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11806 if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
11807 if (to >= forwardMostMove) to = forwardMostMove;
11808 if (to <= backwardMostMove) to = backwardMostMove;
11809 if (to < currentMove) {
11819 if (gameMode != IcsExamining) {
11820 DisplayError(_("You are not examining a game"), 0);
11824 DisplayError(_("You can't revert while pausing"), 0);
11827 SendToICS(ics_prefix);
11828 SendToICS("revert\n");
11834 switch (gameMode) {
11835 case MachinePlaysWhite:
11836 case MachinePlaysBlack:
11837 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
11838 DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
11841 if (forwardMostMove < 2) return;
11842 currentMove = forwardMostMove = forwardMostMove - 2;
11843 whiteTimeRemaining = timeRemaining[0][currentMove];
11844 blackTimeRemaining = timeRemaining[1][currentMove];
11845 DisplayBothClocks();
11846 DisplayMove(currentMove - 1);
11847 ClearHighlights();/*!! could figure this out*/
11848 DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
11849 SendToProgram("remove\n", &first);
11850 /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
11853 case BeginningOfGame:
11857 case IcsPlayingWhite:
11858 case IcsPlayingBlack:
11859 if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
11860 SendToICS(ics_prefix);
11861 SendToICS("takeback 2\n");
11863 SendToICS(ics_prefix);
11864 SendToICS("takeback 1\n");
11873 ChessProgramState *cps;
11875 switch (gameMode) {
11876 case MachinePlaysWhite:
11877 if (!WhiteOnMove(forwardMostMove)) {
11878 DisplayError(_("It is your turn"), 0);
11883 case MachinePlaysBlack:
11884 if (WhiteOnMove(forwardMostMove)) {
11885 DisplayError(_("It is your turn"), 0);
11890 case TwoMachinesPlay:
11891 if (WhiteOnMove(forwardMostMove) ==
11892 (first.twoMachinesColor[0] == 'w')) {
11898 case BeginningOfGame:
11902 SendToProgram("?\n", cps);
11906 TruncateGameEvent()
11909 if (gameMode != EditGame) return;
11916 if (forwardMostMove > currentMove) {
11917 if (gameInfo.resultDetails != NULL) {
11918 free(gameInfo.resultDetails);
11919 gameInfo.resultDetails = NULL;
11920 gameInfo.result = GameUnfinished;
11922 forwardMostMove = currentMove;
11923 HistorySet(parseList, backwardMostMove, forwardMostMove,
11931 if (appData.noChessProgram) return;
11932 switch (gameMode) {
11933 case MachinePlaysWhite:
11934 if (WhiteOnMove(forwardMostMove)) {
11935 DisplayError(_("Wait until your turn"), 0);
11939 case BeginningOfGame:
11940 case MachinePlaysBlack:
11941 if (!WhiteOnMove(forwardMostMove)) {
11942 DisplayError(_("Wait until your turn"), 0);
11947 DisplayError(_("No hint available"), 0);
11950 SendToProgram("hint\n", &first);
11951 hintRequested = TRUE;
11957 if (appData.noChessProgram) return;
11958 switch (gameMode) {
11959 case MachinePlaysWhite:
11960 if (WhiteOnMove(forwardMostMove)) {
11961 DisplayError(_("Wait until your turn"), 0);
11965 case BeginningOfGame:
11966 case MachinePlaysBlack:
11967 if (!WhiteOnMove(forwardMostMove)) {
11968 DisplayError(_("Wait until your turn"), 0);
11973 EditPositionDone();
11975 case TwoMachinesPlay:
11980 SendToProgram("bk\n", &first);
11981 bookOutput[0] = NULLCHAR;
11982 bookRequested = TRUE;
11988 char *tags = PGNTags(&gameInfo);
11989 TagsPopUp(tags, CmailMsg());
11993 /* end button procedures */
11996 PrintPosition(fp, move)
12002 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
12003 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
12004 char c = PieceToChar(boards[move][i][j]);
12005 fputc(c == 'x' ? '.' : c, fp);
12006 fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
12009 if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
12010 fprintf(fp, "white to play\n");
12012 fprintf(fp, "black to play\n");
12019 if (gameInfo.white != NULL) {
12020 fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
12026 /* Find last component of program's own name, using some heuristics */
12028 TidyProgramName(prog, host, buf)
12029 char *prog, *host, buf[MSG_SIZ];
12032 int local = (strcmp(host, "localhost") == 0);
12033 while (!local && (p = strchr(prog, ';')) != NULL) {
12035 while (*p == ' ') p++;
12038 if (*prog == '"' || *prog == '\'') {
12039 q = strchr(prog + 1, *prog);
12041 q = strchr(prog, ' ');
12043 if (q == NULL) q = prog + strlen(prog);
12045 while (p >= prog && *p != '/' && *p != '\\') p--;
12047 if(p == prog && *p == '"') p++;
12048 if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
12049 memcpy(buf, p, q - p);
12050 buf[q - p] = NULLCHAR;
12058 TimeControlTagValue()
12061 if (!appData.clockMode) {
12063 } else if (movesPerSession > 0) {
12064 sprintf(buf, "%d/%ld", movesPerSession, timeControl/1000);
12065 } else if (timeIncrement == 0) {
12066 sprintf(buf, "%ld", timeControl/1000);
12068 sprintf(buf, "%ld+%ld", timeControl/1000, timeIncrement/1000);
12070 return StrSave(buf);
12076 /* This routine is used only for certain modes */
12077 VariantClass v = gameInfo.variant;
12078 ClearGameInfo(&gameInfo);
12079 gameInfo.variant = v;
12081 switch (gameMode) {
12082 case MachinePlaysWhite:
12083 gameInfo.event = StrSave( appData.pgnEventHeader );
12084 gameInfo.site = StrSave(HostName());
12085 gameInfo.date = PGNDate();
12086 gameInfo.round = StrSave("-");
12087 gameInfo.white = StrSave(first.tidy);
12088 gameInfo.black = StrSave(UserName());
12089 gameInfo.timeControl = TimeControlTagValue();
12092 case MachinePlaysBlack:
12093 gameInfo.event = StrSave( appData.pgnEventHeader );
12094 gameInfo.site = StrSave(HostName());
12095 gameInfo.date = PGNDate();
12096 gameInfo.round = StrSave("-");
12097 gameInfo.white = StrSave(UserName());
12098 gameInfo.black = StrSave(first.tidy);
12099 gameInfo.timeControl = TimeControlTagValue();
12102 case TwoMachinesPlay:
12103 gameInfo.event = StrSave( appData.pgnEventHeader );
12104 gameInfo.site = StrSave(HostName());
12105 gameInfo.date = PGNDate();
12106 if (matchGame > 0) {
12108 sprintf(buf, "%d", matchGame);
12109 gameInfo.round = StrSave(buf);
12111 gameInfo.round = StrSave("-");
12113 if (first.twoMachinesColor[0] == 'w') {
12114 gameInfo.white = StrSave(first.tidy);
12115 gameInfo.black = StrSave(second.tidy);
12117 gameInfo.white = StrSave(second.tidy);
12118 gameInfo.black = StrSave(first.tidy);
12120 gameInfo.timeControl = TimeControlTagValue();
12124 gameInfo.event = StrSave("Edited game");
12125 gameInfo.site = StrSave(HostName());
12126 gameInfo.date = PGNDate();
12127 gameInfo.round = StrSave("-");
12128 gameInfo.white = StrSave("-");
12129 gameInfo.black = StrSave("-");
12133 gameInfo.event = StrSave("Edited position");
12134 gameInfo.site = StrSave(HostName());
12135 gameInfo.date = PGNDate();
12136 gameInfo.round = StrSave("-");
12137 gameInfo.white = StrSave("-");
12138 gameInfo.black = StrSave("-");
12141 case IcsPlayingWhite:
12142 case IcsPlayingBlack:
12147 case PlayFromGameFile:
12148 gameInfo.event = StrSave("Game from non-PGN file");
12149 gameInfo.site = StrSave(HostName());
12150 gameInfo.date = PGNDate();
12151 gameInfo.round = StrSave("-");
12152 gameInfo.white = StrSave("?");
12153 gameInfo.black = StrSave("?");
12162 ReplaceComment(index, text)
12168 while (*text == '\n') text++;
12169 len = strlen(text);
12170 while (len > 0 && text[len - 1] == '\n') len--;
12172 if (commentList[index] != NULL)
12173 free(commentList[index]);
12176 commentList[index] = NULL;
12179 commentList[index] = (char *) malloc(len + 2);
12180 strncpy(commentList[index], text, len);
12181 commentList[index][len] = '\n';
12182 commentList[index][len + 1] = NULLCHAR;
12195 if (ch == '\r') continue;
12197 } while (ch != '\0');
12201 AppendComment(index, text)
12208 text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
12211 while (*text == '\n') text++;
12212 len = strlen(text);
12213 while (len > 0 && text[len - 1] == '\n') len--;
12215 if (len == 0) return;
12217 if (commentList[index] != NULL) {
12218 old = commentList[index];
12219 oldlen = strlen(old);
12220 commentList[index] = (char *) malloc(oldlen + len + 2);
12221 strcpy(commentList[index], old);
12223 strncpy(&commentList[index][oldlen], text, len);
12224 commentList[index][oldlen + len] = '\n';
12225 commentList[index][oldlen + len + 1] = NULLCHAR;
12227 commentList[index] = (char *) malloc(len + 2);
12228 strncpy(commentList[index], text, len);
12229 commentList[index][len] = '\n';
12230 commentList[index][len + 1] = NULLCHAR;
12234 static char * FindStr( char * text, char * sub_text )
12236 char * result = strstr( text, sub_text );
12238 if( result != NULL ) {
12239 result += strlen( sub_text );
12245 /* [AS] Try to extract PV info from PGN comment */
12246 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
12247 char *GetInfoFromComment( int index, char * text )
12251 if( text != NULL && index > 0 ) {
12254 int time = -1, sec = 0, deci;
12255 char * s_eval = FindStr( text, "[%eval " );
12256 char * s_emt = FindStr( text, "[%emt " );
12258 if( s_eval != NULL || s_emt != NULL ) {
12262 if( s_eval != NULL ) {
12263 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
12267 if( delim != ']' ) {
12272 if( s_emt != NULL ) {
12276 /* We expect something like: [+|-]nnn.nn/dd */
12279 sep = strchr( text, '/' );
12280 if( sep == NULL || sep < (text+4) ) {
12284 time = -1; sec = -1; deci = -1;
12285 if( sscanf( text, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
12286 sscanf( text, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
12287 sscanf( text, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
12288 sscanf( text, "%d.%d/%d", &score, &score_lo, &depth ) != 3 ) {
12292 if( score_lo < 0 || score_lo >= 100 ) {
12296 if(sec >= 0) time = 600*time + 10*sec; else
12297 if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
12299 score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
12301 /* [HGM] PV time: now locate end of PV info */
12302 while( *++sep >= '0' && *sep <= '9'); // strip depth
12304 while( *++sep >= '0' && *sep <= '9'); // strip time
12306 while( *++sep >= '0' && *sep <= '9'); // strip seconds
12308 while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
12309 while(*sep == ' ') sep++;
12320 pvInfoList[index-1].depth = depth;
12321 pvInfoList[index-1].score = score;
12322 pvInfoList[index-1].time = 10*time; // centi-sec
12328 SendToProgram(message, cps)
12330 ChessProgramState *cps;
12332 int count, outCount, error;
12335 if (cps->pr == NULL) return;
12338 if (appData.debugMode) {
12341 fprintf(debugFP, "%ld >%-6s: %s",
12342 SubtractTimeMarks(&now, &programStartTime),
12343 cps->which, message);
12346 count = strlen(message);
12347 outCount = OutputToProcess(cps->pr, message, count, &error);
12348 if (outCount < count && !exiting
12349 && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
12350 sprintf(buf, _("Error writing to %s chess program"), cps->which);
12351 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
12352 if(epStatus[forwardMostMove] <= EP_DRAWS) {
12353 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
12354 sprintf(buf, "%s program exits in draw position (%s)", cps->which, cps->program);
12356 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
12358 gameInfo.resultDetails = buf;
12360 DisplayFatalError(buf, error, 1);
12365 ReceiveFromProgram(isr, closure, message, count, error)
12366 InputSourceRef isr;
12374 ChessProgramState *cps = (ChessProgramState *)closure;
12376 if (isr != cps->isr) return; /* Killed intentionally */
12380 _("Error: %s chess program (%s) exited unexpectedly"),
12381 cps->which, cps->program);
12382 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
12383 if(epStatus[forwardMostMove] <= EP_DRAWS) {
12384 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
12385 sprintf(buf, _("%s program exits in draw position (%s)"), cps->which, cps->program);
12387 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
12389 gameInfo.resultDetails = buf;
12391 RemoveInputSource(cps->isr);
12392 DisplayFatalError(buf, 0, 1);
12395 _("Error reading from %s chess program (%s)"),
12396 cps->which, cps->program);
12397 RemoveInputSource(cps->isr);
12399 /* [AS] Program is misbehaving badly... kill it */
12400 if( count == -2 ) {
12401 DestroyChildProcess( cps->pr, 9 );
12405 DisplayFatalError(buf, error, 1);
12410 if ((end_str = strchr(message, '\r')) != NULL)
12411 *end_str = NULLCHAR;
12412 if ((end_str = strchr(message, '\n')) != NULL)
12413 *end_str = NULLCHAR;
12415 if (appData.debugMode) {
12416 TimeMark now; int print = 1;
12417 char *quote = ""; char c; int i;
12419 if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
12420 char start = message[0];
12421 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
12422 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
12423 sscanf(message, "move %c", &c)!=1 && sscanf(message, "offer%c", &c)!=1 &&
12424 sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
12425 sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
12426 sscanf(message, "tell%c", &c)!=1 && sscanf(message, "0-1 %c", &c)!=1 &&
12427 sscanf(message, "1-0 %c", &c)!=1 && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
12428 sscanf(message, "pong %c", &c)!=1 && start != '#')
12429 { quote = "# "; print = (appData.engineComments == 2); }
12430 message[0] = start; // restore original message
12434 fprintf(debugFP, "%ld <%-6s: %s%s\n",
12435 SubtractTimeMarks(&now, &programStartTime), cps->which,
12441 /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
12442 if (appData.icsEngineAnalyze) {
12443 if (strstr(message, "whisper") != NULL ||
12444 strstr(message, "kibitz") != NULL ||
12445 strstr(message, "tellics") != NULL) return;
12448 HandleMachineMove(message, cps);
12453 SendTimeControl(cps, mps, tc, inc, sd, st)
12454 ChessProgramState *cps;
12455 int mps, inc, sd, st;
12461 if( timeControl_2 > 0 ) {
12462 if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
12463 tc = timeControl_2;
12466 tc /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
12467 inc /= cps->timeOdds;
12468 st /= cps->timeOdds;
12470 seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
12473 /* Set exact time per move, normally using st command */
12474 if (cps->stKludge) {
12475 /* GNU Chess 4 has no st command; uses level in a nonstandard way */
12477 if (seconds == 0) {
12478 sprintf(buf, "level 1 %d\n", st/60);
12480 sprintf(buf, "level 1 %d:%02d\n", st/60, seconds);
12483 sprintf(buf, "st %d\n", st);
12486 /* Set conventional or incremental time control, using level command */
12487 if (seconds == 0) {
12488 /* Note old gnuchess bug -- minutes:seconds used to not work.
12489 Fixed in later versions, but still avoid :seconds
12490 when seconds is 0. */
12491 sprintf(buf, "level %d %ld %d\n", mps, tc/60000, inc/1000);
12493 sprintf(buf, "level %d %ld:%02d %d\n", mps, tc/60000,
12494 seconds, inc/1000);
12497 SendToProgram(buf, cps);
12499 /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
12500 /* Orthogonally, limit search to given depth */
12502 if (cps->sdKludge) {
12503 sprintf(buf, "depth\n%d\n", sd);
12505 sprintf(buf, "sd %d\n", sd);
12507 SendToProgram(buf, cps);
12510 if(cps->nps > 0) { /* [HGM] nps */
12511 if(cps->supportsNPS == FALSE) cps->nps = -1; // don't use if engine explicitly says not supported!
12513 sprintf(buf, "nps %d\n", cps->nps);
12514 SendToProgram(buf, cps);
12519 ChessProgramState *WhitePlayer()
12520 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
12522 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
12523 gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
12529 SendTimeRemaining(cps, machineWhite)
12530 ChessProgramState *cps;
12531 int /*boolean*/ machineWhite;
12533 char message[MSG_SIZ];
12536 /* Note: this routine must be called when the clocks are stopped
12537 or when they have *just* been set or switched; otherwise
12538 it will be off by the time since the current tick started.
12540 if (machineWhite) {
12541 time = whiteTimeRemaining / 10;
12542 otime = blackTimeRemaining / 10;
12544 time = blackTimeRemaining / 10;
12545 otime = whiteTimeRemaining / 10;
12547 /* [HGM] translate opponent's time by time-odds factor */
12548 otime = (otime * cps->other->timeOdds) / cps->timeOdds;
12549 if (appData.debugMode) {
12550 fprintf(debugFP, "time odds: %d %d \n", cps->timeOdds, cps->other->timeOdds);
12553 if (time <= 0) time = 1;
12554 if (otime <= 0) otime = 1;
12556 sprintf(message, "time %ld\n", time);
12557 SendToProgram(message, cps);
12559 sprintf(message, "otim %ld\n", otime);
12560 SendToProgram(message, cps);
12564 BoolFeature(p, name, loc, cps)
12568 ChessProgramState *cps;
12571 int len = strlen(name);
12573 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
12575 sscanf(*p, "%d", &val);
12577 while (**p && **p != ' ') (*p)++;
12578 sprintf(buf, "accepted %s\n", name);
12579 SendToProgram(buf, cps);
12586 IntFeature(p, name, loc, cps)
12590 ChessProgramState *cps;
12593 int len = strlen(name);
12594 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
12596 sscanf(*p, "%d", loc);
12597 while (**p && **p != ' ') (*p)++;
12598 sprintf(buf, "accepted %s\n", name);
12599 SendToProgram(buf, cps);
12606 StringFeature(p, name, loc, cps)
12610 ChessProgramState *cps;
12613 int len = strlen(name);
12614 if (strncmp((*p), name, len) == 0
12615 && (*p)[len] == '=' && (*p)[len+1] == '\"') {
12617 sscanf(*p, "%[^\"]", loc);
12618 while (**p && **p != '\"') (*p)++;
12619 if (**p == '\"') (*p)++;
12620 sprintf(buf, "accepted %s\n", name);
12621 SendToProgram(buf, cps);
12628 ParseOption(Option *opt, ChessProgramState *cps)
12629 // [HGM] options: process the string that defines an engine option, and determine
12630 // name, type, default value, and allowed value range
12632 char *p, *q, buf[MSG_SIZ];
12633 int n, min = (-1)<<31, max = 1<<31, def;
12635 if(p = strstr(opt->name, " -spin ")) {
12636 if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
12637 if(max < min) max = min; // enforce consistency
12638 if(def < min) def = min;
12639 if(def > max) def = max;
12644 } else if((p = strstr(opt->name, " -slider "))) {
12645 // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
12646 if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
12647 if(max < min) max = min; // enforce consistency
12648 if(def < min) def = min;
12649 if(def > max) def = max;
12653 opt->type = Spin; // Slider;
12654 } else if((p = strstr(opt->name, " -string "))) {
12655 opt->textValue = p+9;
12656 opt->type = TextBox;
12657 } else if((p = strstr(opt->name, " -file "))) {
12658 // for now -file is a synonym for -string, to already provide compatibility with future polyglots
12659 opt->textValue = p+7;
12660 opt->type = TextBox; // FileName;
12661 } else if((p = strstr(opt->name, " -path "))) {
12662 // for now -file is a synonym for -string, to already provide compatibility with future polyglots
12663 opt->textValue = p+7;
12664 opt->type = TextBox; // PathName;
12665 } else if(p = strstr(opt->name, " -check ")) {
12666 if(sscanf(p, " -check %d", &def) < 1) return FALSE;
12667 opt->value = (def != 0);
12668 opt->type = CheckBox;
12669 } else if(p = strstr(opt->name, " -combo ")) {
12670 opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
12671 cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
12672 if(*q == '*') cps->comboList[cps->comboCnt-1]++;
12673 opt->value = n = 0;
12674 while(q = StrStr(q, " /// ")) {
12675 n++; *q = 0; // count choices, and null-terminate each of them
12677 if(*q == '*') { // remember default, which is marked with * prefix
12681 cps->comboList[cps->comboCnt++] = q;
12683 cps->comboList[cps->comboCnt++] = NULL;
12685 opt->type = ComboBox;
12686 } else if(p = strstr(opt->name, " -button")) {
12687 opt->type = Button;
12688 } else if(p = strstr(opt->name, " -save")) {
12689 opt->type = SaveButton;
12690 } else return FALSE;
12691 *p = 0; // terminate option name
12692 // now look if the command-line options define a setting for this engine option.
12693 if(cps->optionSettings && cps->optionSettings[0])
12694 p = strstr(cps->optionSettings, opt->name); else p = NULL;
12695 if(p && (p == cps->optionSettings || p[-1] == ',')) {
12696 sprintf(buf, "option %s", p);
12697 if(p = strstr(buf, ",")) *p = 0;
12699 SendToProgram(buf, cps);
12705 FeatureDone(cps, val)
12706 ChessProgramState* cps;
12709 DelayedEventCallback cb = GetDelayedEvent();
12710 if ((cb == InitBackEnd3 && cps == &first) ||
12711 (cb == TwoMachinesEventIfReady && cps == &second)) {
12712 CancelDelayedEvent();
12713 ScheduleDelayedEvent(cb, val ? 1 : 3600000);
12715 cps->initDone = val;
12718 /* Parse feature command from engine */
12720 ParseFeatures(args, cps)
12722 ChessProgramState *cps;
12730 while (*p == ' ') p++;
12731 if (*p == NULLCHAR) return;
12733 if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
12734 if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
12735 if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
12736 if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
12737 if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
12738 if (BoolFeature(&p, "reuse", &val, cps)) {
12739 /* Engine can disable reuse, but can't enable it if user said no */
12740 if (!val) cps->reuse = FALSE;
12743 if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
12744 if (StringFeature(&p, "myname", &cps->tidy, cps)) {
12745 if (gameMode == TwoMachinesPlay) {
12746 DisplayTwoMachinesTitle();
12752 if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
12753 if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
12754 if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
12755 if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
12756 if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
12757 if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
12758 if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
12759 if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
12760 if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
12761 if (IntFeature(&p, "done", &val, cps)) {
12762 FeatureDone(cps, val);
12765 /* Added by Tord: */
12766 if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
12767 if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
12768 /* End of additions by Tord */
12770 /* [HGM] added features: */
12771 if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
12772 if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
12773 if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
12774 if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
12775 if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
12776 if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
12777 if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
12778 if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
12779 sprintf(buf, "rejected option %s\n", cps->option[--cps->nrOptions].name);
12780 SendToProgram(buf, cps);
12783 if(cps->nrOptions >= MAX_OPTIONS) {
12785 sprintf(buf, "%s engine has too many options\n", cps->which);
12786 DisplayError(buf, 0);
12790 if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
12791 /* End of additions by HGM */
12793 /* unknown feature: complain and skip */
12795 while (*q && *q != '=') q++;
12796 sprintf(buf, "rejected %.*s\n", q-p, p);
12797 SendToProgram(buf, cps);
12803 while (*p && *p != '\"') p++;
12804 if (*p == '\"') p++;
12806 while (*p && *p != ' ') p++;
12814 PeriodicUpdatesEvent(newState)
12817 if (newState == appData.periodicUpdates)
12820 appData.periodicUpdates=newState;
12822 /* Display type changes, so update it now */
12823 // DisplayAnalysis();
12825 /* Get the ball rolling again... */
12827 AnalysisPeriodicEvent(1);
12828 StartAnalysisClock();
12833 PonderNextMoveEvent(newState)
12836 if (newState == appData.ponderNextMove) return;
12837 if (gameMode == EditPosition) EditPositionDone();
12839 SendToProgram("hard\n", &first);
12840 if (gameMode == TwoMachinesPlay) {
12841 SendToProgram("hard\n", &second);
12844 SendToProgram("easy\n", &first);
12845 thinkOutput[0] = NULLCHAR;
12846 if (gameMode == TwoMachinesPlay) {
12847 SendToProgram("easy\n", &second);
12850 appData.ponderNextMove = newState;
12854 NewSettingEvent(option, command, value)
12860 if (gameMode == EditPosition) EditPositionDone();
12861 sprintf(buf, "%s%s %d\n", (option ? "option ": ""), command, value);
12862 SendToProgram(buf, &first);
12863 if (gameMode == TwoMachinesPlay) {
12864 SendToProgram(buf, &second);
12869 ShowThinkingEvent()
12870 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
12872 static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
12873 int newState = appData.showThinking
12874 // [HGM] thinking: other features now need thinking output as well
12875 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
12877 if (oldState == newState) return;
12878 oldState = newState;
12879 if (gameMode == EditPosition) EditPositionDone();
12881 SendToProgram("post\n", &first);
12882 if (gameMode == TwoMachinesPlay) {
12883 SendToProgram("post\n", &second);
12886 SendToProgram("nopost\n", &first);
12887 thinkOutput[0] = NULLCHAR;
12888 if (gameMode == TwoMachinesPlay) {
12889 SendToProgram("nopost\n", &second);
12892 // appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
12896 AskQuestionEvent(title, question, replyPrefix, which)
12897 char *title; char *question; char *replyPrefix; char *which;
12899 ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
12900 if (pr == NoProc) return;
12901 AskQuestion(title, question, replyPrefix, pr);
12905 DisplayMove(moveNumber)
12908 char message[MSG_SIZ];
12910 char cpThinkOutput[MSG_SIZ];
12912 if(appData.noGUI) return; // [HGM] fast: suppress display of moves
12914 if (moveNumber == forwardMostMove - 1 ||
12915 gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12917 safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput));
12919 if (strchr(cpThinkOutput, '\n')) {
12920 *strchr(cpThinkOutput, '\n') = NULLCHAR;
12923 *cpThinkOutput = NULLCHAR;
12926 /* [AS] Hide thinking from human user */
12927 if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
12928 *cpThinkOutput = NULLCHAR;
12929 if( thinkOutput[0] != NULLCHAR ) {
12932 for( i=0; i<=hiddenThinkOutputState; i++ ) {
12933 cpThinkOutput[i] = '.';
12935 cpThinkOutput[i] = NULLCHAR;
12936 hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
12940 if (moveNumber == forwardMostMove - 1 &&
12941 gameInfo.resultDetails != NULL) {
12942 if (gameInfo.resultDetails[0] == NULLCHAR) {
12943 sprintf(res, " %s", PGNResult(gameInfo.result));
12945 sprintf(res, " {%s} %s",
12946 gameInfo.resultDetails, PGNResult(gameInfo.result));
12952 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
12953 DisplayMessage(res, cpThinkOutput);
12955 sprintf(message, "%d.%s%s%s", moveNumber / 2 + 1,
12956 WhiteOnMove(moveNumber) ? " " : ".. ",
12957 parseList[moveNumber], res);
12958 DisplayMessage(message, cpThinkOutput);
12963 DisplayComment(moveNumber, text)
12967 char title[MSG_SIZ];
12968 char buf[8000]; // comment can be long!
12971 if( appData.autoDisplayComment ) {
12972 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
12973 strcpy(title, "Comment");
12975 sprintf(title, "Comment on %d.%s%s", moveNumber / 2 + 1,
12976 WhiteOnMove(moveNumber) ? " " : ".. ",
12977 parseList[moveNumber]);
12979 // [HGM] PV info: display PV info together with (or as) comment
12980 if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {
12981 if(text == NULL) text = "";
12982 score = pvInfoList[moveNumber].score;
12983 sprintf(buf, "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,
12984 depth, (pvInfoList[moveNumber].time+50)/100, text);
12987 } else title[0] = 0;
12990 CommentPopUp(title, text);
12993 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
12994 * might be busy thinking or pondering. It can be omitted if your
12995 * gnuchess is configured to stop thinking immediately on any user
12996 * input. However, that gnuchess feature depends on the FIONREAD
12997 * ioctl, which does not work properly on some flavors of Unix.
13001 ChessProgramState *cps;
13004 if (!cps->useSigint) return;
13005 if (appData.noChessProgram || (cps->pr == NoProc)) return;
13006 switch (gameMode) {
13007 case MachinePlaysWhite:
13008 case MachinePlaysBlack:
13009 case TwoMachinesPlay:
13010 case IcsPlayingWhite:
13011 case IcsPlayingBlack:
13014 /* Skip if we know it isn't thinking */
13015 if (!cps->maybeThinking) return;
13016 if (appData.debugMode)
13017 fprintf(debugFP, "Interrupting %s\n", cps->which);
13018 InterruptChildProcess(cps->pr);
13019 cps->maybeThinking = FALSE;
13024 #endif /*ATTENTION*/
13030 if (whiteTimeRemaining <= 0) {
13033 if (appData.icsActive) {
13034 if (appData.autoCallFlag &&
13035 gameMode == IcsPlayingBlack && !blackFlag) {
13036 SendToICS(ics_prefix);
13037 SendToICS("flag\n");
13041 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
13043 if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
13044 if (appData.autoCallFlag) {
13045 GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
13052 if (blackTimeRemaining <= 0) {
13055 if (appData.icsActive) {
13056 if (appData.autoCallFlag &&
13057 gameMode == IcsPlayingWhite && !whiteFlag) {
13058 SendToICS(ics_prefix);
13059 SendToICS("flag\n");
13063 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
13065 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
13066 if (appData.autoCallFlag) {
13067 GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
13080 if (!appData.clockMode || appData.icsActive ||
13081 gameMode == PlayFromGameFile || forwardMostMove == 0) return;
13084 * add time to clocks when time control is achieved ([HGM] now also used for increment)
13086 if ( !WhiteOnMove(forwardMostMove) )
13087 /* White made time control */
13088 whiteTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
13089 /* [HGM] time odds: correct new time quota for time odds! */
13090 / WhitePlayer()->timeOdds;
13092 /* Black made time control */
13093 blackTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
13094 / WhitePlayer()->other->timeOdds;
13098 DisplayBothClocks()
13100 int wom = gameMode == EditPosition ?
13101 !blackPlaysFirst : WhiteOnMove(currentMove);
13102 DisplayWhiteClock(whiteTimeRemaining, wom);
13103 DisplayBlackClock(blackTimeRemaining, !wom);
13107 /* Timekeeping seems to be a portability nightmare. I think everyone
13108 has ftime(), but I'm really not sure, so I'm including some ifdefs
13109 to use other calls if you don't. Clocks will be less accurate if
13110 you have neither ftime nor gettimeofday.
13113 /* VS 2008 requires the #include outside of the function */
13114 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
13115 #include <sys/timeb.h>
13118 /* Get the current time as a TimeMark */
13123 #if HAVE_GETTIMEOFDAY
13125 struct timeval timeVal;
13126 struct timezone timeZone;
13128 gettimeofday(&timeVal, &timeZone);
13129 tm->sec = (long) timeVal.tv_sec;
13130 tm->ms = (int) (timeVal.tv_usec / 1000L);
13132 #else /*!HAVE_GETTIMEOFDAY*/
13135 // include <sys/timeb.h> / moved to just above start of function
13136 struct timeb timeB;
13139 tm->sec = (long) timeB.time;
13140 tm->ms = (int) timeB.millitm;
13142 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
13143 tm->sec = (long) time(NULL);
13149 /* Return the difference in milliseconds between two
13150 time marks. We assume the difference will fit in a long!
13153 SubtractTimeMarks(tm2, tm1)
13154 TimeMark *tm2, *tm1;
13156 return 1000L*(tm2->sec - tm1->sec) +
13157 (long) (tm2->ms - tm1->ms);
13162 * Code to manage the game clocks.
13164 * In tournament play, black starts the clock and then white makes a move.
13165 * We give the human user a slight advantage if he is playing white---the
13166 * clocks don't run until he makes his first move, so it takes zero time.
13167 * Also, we don't account for network lag, so we could get out of sync
13168 * with GNU Chess's clock -- but then, referees are always right.
13171 static TimeMark tickStartTM;
13172 static long intendedTickLength;
13175 NextTickLength(timeRemaining)
13176 long timeRemaining;
13178 long nominalTickLength, nextTickLength;
13180 if (timeRemaining > 0L && timeRemaining <= 10000L)
13181 nominalTickLength = 100L;
13183 nominalTickLength = 1000L;
13184 nextTickLength = timeRemaining % nominalTickLength;
13185 if (nextTickLength <= 0) nextTickLength += nominalTickLength;
13187 return nextTickLength;
13190 /* Adjust clock one minute up or down */
13192 AdjustClock(Boolean which, int dir)
13194 if(which) blackTimeRemaining += 60000*dir;
13195 else whiteTimeRemaining += 60000*dir;
13196 DisplayBothClocks();
13199 /* Stop clocks and reset to a fresh time control */
13203 (void) StopClockTimer();
13204 if (appData.icsActive) {
13205 whiteTimeRemaining = blackTimeRemaining = 0;
13206 } else { /* [HGM] correct new time quote for time odds */
13207 whiteTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->timeOdds;
13208 blackTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->other->timeOdds;
13210 if (whiteFlag || blackFlag) {
13212 whiteFlag = blackFlag = FALSE;
13214 DisplayBothClocks();
13217 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
13219 /* Decrement running clock by amount of time that has passed */
13223 long timeRemaining;
13224 long lastTickLength, fudge;
13227 if (!appData.clockMode) return;
13228 if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
13232 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13234 /* Fudge if we woke up a little too soon */
13235 fudge = intendedTickLength - lastTickLength;
13236 if (fudge < 0 || fudge > FUDGE) fudge = 0;
13238 if (WhiteOnMove(forwardMostMove)) {
13239 if(whiteNPS >= 0) lastTickLength = 0;
13240 timeRemaining = whiteTimeRemaining -= lastTickLength;
13241 DisplayWhiteClock(whiteTimeRemaining - fudge,
13242 WhiteOnMove(currentMove));
13244 if(blackNPS >= 0) lastTickLength = 0;
13245 timeRemaining = blackTimeRemaining -= lastTickLength;
13246 DisplayBlackClock(blackTimeRemaining - fudge,
13247 !WhiteOnMove(currentMove));
13250 if (CheckFlags()) return;
13253 intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
13254 StartClockTimer(intendedTickLength);
13256 /* if the time remaining has fallen below the alarm threshold, sound the
13257 * alarm. if the alarm has sounded and (due to a takeback or time control
13258 * with increment) the time remaining has increased to a level above the
13259 * threshold, reset the alarm so it can sound again.
13262 if (appData.icsActive && appData.icsAlarm) {
13264 /* make sure we are dealing with the user's clock */
13265 if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
13266 ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
13269 if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
13270 alarmSounded = FALSE;
13271 } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
13273 alarmSounded = TRUE;
13279 /* A player has just moved, so stop the previously running
13280 clock and (if in clock mode) start the other one.
13281 We redisplay both clocks in case we're in ICS mode, because
13282 ICS gives us an update to both clocks after every move.
13283 Note that this routine is called *after* forwardMostMove
13284 is updated, so the last fractional tick must be subtracted
13285 from the color that is *not* on move now.
13290 long lastTickLength;
13292 int flagged = FALSE;
13296 if (StopClockTimer() && appData.clockMode) {
13297 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13298 if (WhiteOnMove(forwardMostMove)) {
13299 if(blackNPS >= 0) lastTickLength = 0;
13300 blackTimeRemaining -= lastTickLength;
13301 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
13302 // if(pvInfoList[forwardMostMove-1].time == -1)
13303 pvInfoList[forwardMostMove-1].time = // use GUI time
13304 (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
13306 if(whiteNPS >= 0) lastTickLength = 0;
13307 whiteTimeRemaining -= lastTickLength;
13308 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
13309 // if(pvInfoList[forwardMostMove-1].time == -1)
13310 pvInfoList[forwardMostMove-1].time =
13311 (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
13313 flagged = CheckFlags();
13315 CheckTimeControl();
13317 if (flagged || !appData.clockMode) return;
13319 switch (gameMode) {
13320 case MachinePlaysBlack:
13321 case MachinePlaysWhite:
13322 case BeginningOfGame:
13323 if (pausing) return;
13327 case PlayFromGameFile:
13336 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
13337 whiteTimeRemaining : blackTimeRemaining);
13338 StartClockTimer(intendedTickLength);
13342 /* Stop both clocks */
13346 long lastTickLength;
13349 if (!StopClockTimer()) return;
13350 if (!appData.clockMode) return;
13354 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13355 if (WhiteOnMove(forwardMostMove)) {
13356 if(whiteNPS >= 0) lastTickLength = 0;
13357 whiteTimeRemaining -= lastTickLength;
13358 DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
13360 if(blackNPS >= 0) lastTickLength = 0;
13361 blackTimeRemaining -= lastTickLength;
13362 DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
13367 /* Start clock of player on move. Time may have been reset, so
13368 if clock is already running, stop and restart it. */
13372 (void) StopClockTimer(); /* in case it was running already */
13373 DisplayBothClocks();
13374 if (CheckFlags()) return;
13376 if (!appData.clockMode) return;
13377 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
13379 GetTimeMark(&tickStartTM);
13380 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
13381 whiteTimeRemaining : blackTimeRemaining);
13383 /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
13384 whiteNPS = blackNPS = -1;
13385 if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
13386 || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
13387 whiteNPS = first.nps;
13388 if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
13389 || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
13390 blackNPS = first.nps;
13391 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
13392 whiteNPS = second.nps;
13393 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
13394 blackNPS = second.nps;
13395 if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
13397 StartClockTimer(intendedTickLength);
13404 long second, minute, hour, day;
13406 static char buf[32];
13408 if (ms > 0 && ms <= 9900) {
13409 /* convert milliseconds to tenths, rounding up */
13410 double tenths = floor( ((double)(ms + 99L)) / 100.00 );
13412 sprintf(buf, " %03.1f ", tenths/10.0);
13416 /* convert milliseconds to seconds, rounding up */
13417 /* use floating point to avoid strangeness of integer division
13418 with negative dividends on many machines */
13419 second = (long) floor(((double) (ms + 999L)) / 1000.0);
13426 day = second / (60 * 60 * 24);
13427 second = second % (60 * 60 * 24);
13428 hour = second / (60 * 60);
13429 second = second % (60 * 60);
13430 minute = second / 60;
13431 second = second % 60;
13434 sprintf(buf, " %s%ld:%02ld:%02ld:%02ld ",
13435 sign, day, hour, minute, second);
13437 sprintf(buf, " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
13439 sprintf(buf, " %s%2ld:%02ld ", sign, minute, second);
13446 * This is necessary because some C libraries aren't ANSI C compliant yet.
13449 StrStr(string, match)
13450 char *string, *match;
13454 length = strlen(match);
13456 for (i = strlen(string) - length; i >= 0; i--, string++)
13457 if (!strncmp(match, string, length))
13464 StrCaseStr(string, match)
13465 char *string, *match;
13469 length = strlen(match);
13471 for (i = strlen(string) - length; i >= 0; i--, string++) {
13472 for (j = 0; j < length; j++) {
13473 if (ToLower(match[j]) != ToLower(string[j]))
13476 if (j == length) return string;
13490 c1 = ToLower(*s1++);
13491 c2 = ToLower(*s2++);
13492 if (c1 > c2) return 1;
13493 if (c1 < c2) return -1;
13494 if (c1 == NULLCHAR) return 0;
13503 return isupper(c) ? tolower(c) : c;
13511 return islower(c) ? toupper(c) : c;
13513 #endif /* !_amigados */
13521 if ((ret = (char *) malloc(strlen(s) + 1))) {
13528 StrSavePtr(s, savePtr)
13529 char *s, **savePtr;
13534 if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
13535 strcpy(*savePtr, s);
13547 clock = time((time_t *)NULL);
13548 tm = localtime(&clock);
13549 sprintf(buf, "%04d.%02d.%02d",
13550 tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
13551 return StrSave(buf);
13556 PositionToFEN(move, overrideCastling)
13558 char *overrideCastling;
13560 int i, j, fromX, fromY, toX, toY;
13567 whiteToPlay = (gameMode == EditPosition) ?
13568 !blackPlaysFirst : (move % 2 == 0);
13571 /* Piece placement data */
13572 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13574 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
13575 if (boards[move][i][j] == EmptySquare) {
13577 } else { ChessSquare piece = boards[move][i][j];
13578 if (emptycount > 0) {
13579 if(emptycount<10) /* [HGM] can be >= 10 */
13580 *p++ = '0' + emptycount;
13581 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
13584 if(PieceToChar(piece) == '+') {
13585 /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
13587 piece = (ChessSquare)(DEMOTED piece);
13589 *p++ = PieceToChar(piece);
13591 /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
13592 p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
13597 if (emptycount > 0) {
13598 if(emptycount<10) /* [HGM] can be >= 10 */
13599 *p++ = '0' + emptycount;
13600 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
13607 /* [HGM] print Crazyhouse or Shogi holdings */
13608 if( gameInfo.holdingsWidth ) {
13609 *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
13611 for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
13612 piece = boards[move][i][BOARD_WIDTH-1];
13613 if( piece != EmptySquare )
13614 for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
13615 *p++ = PieceToChar(piece);
13617 for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
13618 piece = boards[move][BOARD_HEIGHT-i-1][0];
13619 if( piece != EmptySquare )
13620 for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
13621 *p++ = PieceToChar(piece);
13624 if( q == p ) *p++ = '-';
13630 *p++ = whiteToPlay ? 'w' : 'b';
13633 if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
13634 while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' ';
13636 if(nrCastlingRights) {
13638 if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
13639 /* [HGM] write directly from rights */
13640 if(castlingRights[move][2] >= 0 &&
13641 castlingRights[move][0] >= 0 )
13642 *p++ = castlingRights[move][0] + AAA + 'A' - 'a';
13643 if(castlingRights[move][2] >= 0 &&
13644 castlingRights[move][1] >= 0 )
13645 *p++ = castlingRights[move][1] + AAA + 'A' - 'a';
13646 if(castlingRights[move][5] >= 0 &&
13647 castlingRights[move][3] >= 0 )
13648 *p++ = castlingRights[move][3] + AAA;
13649 if(castlingRights[move][5] >= 0 &&
13650 castlingRights[move][4] >= 0 )
13651 *p++ = castlingRights[move][4] + AAA;
13654 /* [HGM] write true castling rights */
13655 if( nrCastlingRights == 6 ) {
13656 if(castlingRights[move][0] == BOARD_RGHT-1 &&
13657 castlingRights[move][2] >= 0 ) *p++ = 'K';
13658 if(castlingRights[move][1] == BOARD_LEFT &&
13659 castlingRights[move][2] >= 0 ) *p++ = 'Q';
13660 if(castlingRights[move][3] == BOARD_RGHT-1 &&
13661 castlingRights[move][5] >= 0 ) *p++ = 'k';
13662 if(castlingRights[move][4] == BOARD_LEFT &&
13663 castlingRights[move][5] >= 0 ) *p++ = 'q';
13666 if (q == p) *p++ = '-'; /* No castling rights */
13670 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
13671 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier ) {
13672 /* En passant target square */
13673 if (move > backwardMostMove) {
13674 fromX = moveList[move - 1][0] - AAA;
13675 fromY = moveList[move - 1][1] - ONE;
13676 toX = moveList[move - 1][2] - AAA;
13677 toY = moveList[move - 1][3] - ONE;
13678 if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
13679 toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
13680 boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
13682 /* 2-square pawn move just happened */
13684 *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
13688 } else if(move == backwardMostMove) {
13689 // [HGM] perhaps we should always do it like this, and forget the above?
13690 if(epStatus[move] >= 0) {
13691 *p++ = epStatus[move] + AAA;
13692 *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
13703 /* [HGM] find reversible plies */
13704 { int i = 0, j=move;
13706 if (appData.debugMode) { int k;
13707 fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
13708 for(k=backwardMostMove; k<=forwardMostMove; k++)
13709 fprintf(debugFP, "e%d. p=%d\n", k, epStatus[k]);
13713 while(j > backwardMostMove && epStatus[j] <= EP_NONE) j--,i++;
13714 if( j == backwardMostMove ) i += initialRulePlies;
13715 sprintf(p, "%d ", i);
13716 p += i>=100 ? 4 : i >= 10 ? 3 : 2;
13718 /* Fullmove number */
13719 sprintf(p, "%d", (move / 2) + 1);
13721 return StrSave(buf);
13725 ParseFEN(board, blackPlaysFirst, fen)
13727 int *blackPlaysFirst;
13737 /* [HGM] by default clear Crazyhouse holdings, if present */
13738 if(gameInfo.holdingsWidth) {
13739 for(i=0; i<BOARD_HEIGHT; i++) {
13740 board[i][0] = EmptySquare; /* black holdings */
13741 board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
13742 board[i][1] = (ChessSquare) 0; /* black counts */
13743 board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
13747 /* Piece placement data */
13748 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13751 if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
13752 if (*p == '/') p++;
13753 emptycount = gameInfo.boardWidth - j;
13754 while (emptycount--)
13755 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13757 #if(BOARD_SIZE >= 10)
13758 } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
13759 p++; emptycount=10;
13760 if (j + emptycount > gameInfo.boardWidth) return FALSE;
13761 while (emptycount--)
13762 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13764 } else if (isdigit(*p)) {
13765 emptycount = *p++ - '0';
13766 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
13767 if (j + emptycount > gameInfo.boardWidth) return FALSE;
13768 while (emptycount--)
13769 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13770 } else if (*p == '+' || isalpha(*p)) {
13771 if (j >= gameInfo.boardWidth) return FALSE;
13773 piece = CharToPiece(*++p);
13774 if(piece == EmptySquare) return FALSE; /* unknown piece */
13775 piece = (ChessSquare) (PROMOTED piece ); p++;
13776 if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
13777 } else piece = CharToPiece(*p++);
13779 if(piece==EmptySquare) return FALSE; /* unknown piece */
13780 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
13781 piece = (ChessSquare) (PROMOTED piece);
13782 if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
13785 board[i][(j++)+gameInfo.holdingsWidth] = piece;
13791 while (*p == '/' || *p == ' ') p++;
13793 /* [HGM] look for Crazyhouse holdings here */
13794 while(*p==' ') p++;
13795 if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
13797 if(*p == '-' ) *p++; /* empty holdings */ else {
13798 if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
13799 /* if we would allow FEN reading to set board size, we would */
13800 /* have to add holdings and shift the board read so far here */
13801 while( (piece = CharToPiece(*p) ) != EmptySquare ) {
13803 if((int) piece >= (int) BlackPawn ) {
13804 i = (int)piece - (int)BlackPawn;
13805 i = PieceToNumber((ChessSquare)i);
13806 if( i >= gameInfo.holdingsSize ) return FALSE;
13807 board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
13808 board[BOARD_HEIGHT-1-i][1]++; /* black counts */
13810 i = (int)piece - (int)WhitePawn;
13811 i = PieceToNumber((ChessSquare)i);
13812 if( i >= gameInfo.holdingsSize ) return FALSE;
13813 board[i][BOARD_WIDTH-1] = piece; /* white holdings */
13814 board[i][BOARD_WIDTH-2]++; /* black holdings */
13818 if(*p == ']') *p++;
13821 while(*p == ' ') p++;
13826 *blackPlaysFirst = FALSE;
13829 *blackPlaysFirst = TRUE;
13835 /* [HGM] We NO LONGER ignore the rest of the FEN notation */
13836 /* return the extra info in global variiables */
13838 /* set defaults in case FEN is incomplete */
13839 FENepStatus = EP_UNKNOWN;
13840 for(i=0; i<nrCastlingRights; i++ ) {
13841 FENcastlingRights[i] =
13842 gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? -1 : initialRights[i];
13843 } /* assume possible unless obviously impossible */
13844 if(initialRights[0]>=0 && board[castlingRank[0]][initialRights[0]] != WhiteRook) FENcastlingRights[0] = -1;
13845 if(initialRights[1]>=0 && board[castlingRank[1]][initialRights[1]] != WhiteRook) FENcastlingRights[1] = -1;
13846 if(initialRights[2]>=0 && board[castlingRank[2]][initialRights[2]] != WhiteKing) FENcastlingRights[2] = -1;
13847 if(initialRights[3]>=0 && board[castlingRank[3]][initialRights[3]] != BlackRook) FENcastlingRights[3] = -1;
13848 if(initialRights[4]>=0 && board[castlingRank[4]][initialRights[4]] != BlackRook) FENcastlingRights[4] = -1;
13849 if(initialRights[5]>=0 && board[castlingRank[5]][initialRights[5]] != BlackKing) FENcastlingRights[5] = -1;
13852 while(*p==' ') p++;
13853 if(nrCastlingRights) {
13854 if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
13855 /* castling indicator present, so default becomes no castlings */
13856 for(i=0; i<nrCastlingRights; i++ ) {
13857 FENcastlingRights[i] = -1;
13860 while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
13861 (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
13862 ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
13863 ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth) ) {
13864 char c = *p++; int whiteKingFile=-1, blackKingFile=-1;
13866 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
13867 if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
13868 if(board[0 ][i] == WhiteKing) whiteKingFile = i;
13872 for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
13873 FENcastlingRights[0] = i != whiteKingFile ? i : -1;
13874 FENcastlingRights[2] = whiteKingFile;
13877 for(i=BOARD_LEFT; board[0][i]!=WhiteRook && i<whiteKingFile; i++);
13878 FENcastlingRights[1] = i != whiteKingFile ? i : -1;
13879 FENcastlingRights[2] = whiteKingFile;
13882 for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
13883 FENcastlingRights[3] = i != blackKingFile ? i : -1;
13884 FENcastlingRights[5] = blackKingFile;
13887 for(i=BOARD_LEFT; board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
13888 FENcastlingRights[4] = i != blackKingFile ? i : -1;
13889 FENcastlingRights[5] = blackKingFile;
13892 default: /* FRC castlings */
13893 if(c >= 'a') { /* black rights */
13894 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
13895 if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
13896 if(i == BOARD_RGHT) break;
13897 FENcastlingRights[5] = i;
13899 if(board[BOARD_HEIGHT-1][c] < BlackPawn ||
13900 board[BOARD_HEIGHT-1][c] >= BlackKing ) break;
13902 FENcastlingRights[3] = c;
13904 FENcastlingRights[4] = c;
13905 } else { /* white rights */
13906 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
13907 if(board[0][i] == WhiteKing) break;
13908 if(i == BOARD_RGHT) break;
13909 FENcastlingRights[2] = i;
13910 c -= AAA - 'a' + 'A';
13911 if(board[0][c] >= WhiteKing) break;
13913 FENcastlingRights[0] = c;
13915 FENcastlingRights[1] = c;
13919 if (appData.debugMode) {
13920 fprintf(debugFP, "FEN castling rights:");
13921 for(i=0; i<nrCastlingRights; i++)
13922 fprintf(debugFP, " %d", FENcastlingRights[i]);
13923 fprintf(debugFP, "\n");
13926 while(*p==' ') p++;
13929 /* read e.p. field in games that know e.p. capture */
13930 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
13931 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier ) {
13933 p++; FENepStatus = EP_NONE;
13935 char c = *p++ - AAA;
13937 if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
13938 if(*p >= '0' && *p <='9') *p++;
13944 if(sscanf(p, "%d", &i) == 1) {
13945 FENrulePlies = i; /* 50-move ply counter */
13946 /* (The move number is still ignored) */
13953 EditPositionPasteFEN(char *fen)
13956 Board initial_position;
13958 if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
13959 DisplayError(_("Bad FEN position in clipboard"), 0);
13962 int savedBlackPlaysFirst = blackPlaysFirst;
13963 EditPositionEvent();
13964 blackPlaysFirst = savedBlackPlaysFirst;
13965 CopyBoard(boards[0], initial_position);
13966 /* [HGM] copy FEN attributes as well */
13968 initialRulePlies = FENrulePlies;
13969 epStatus[0] = FENepStatus;
13970 for( i=0; i<nrCastlingRights; i++ )
13971 castlingRights[0][i] = FENcastlingRights[i];
13973 EditPositionDone();
13974 DisplayBothClocks();
13975 DrawPosition(FALSE, boards[currentMove]);
13980 static char cseq[12] = "\\ ";
13982 Boolean set_cont_sequence(char *new_seq)
13987 // handle bad attempts to set the sequence
13989 return 0; // acceptable error - no debug
13991 len = strlen(new_seq);
13992 ret = (len > 0) && (len < sizeof(cseq));
13994 strcpy(cseq, new_seq);
13995 else if (appData.debugMode)
13996 fprintf(debugFP, "Invalid continuation sequence \"%s\" (maximum length is: %d)\n", new_seq, sizeof(cseq)-1);
14001 reformat a source message so words don't cross the width boundary. internal
14002 newlines are not removed. returns the wrapped size (no null character unless
14003 included in source message). If dest is NULL, only calculate the size required
14004 for the dest buffer. lp argument indicats line position upon entry, and it's
14005 passed back upon exit.
14007 int wrap(char *dest, char *src, int count, int width, int *lp)
14009 int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
14011 cseq_len = strlen(cseq);
14012 old_line = line = *lp;
14013 ansi = len = clen = 0;
14015 for (i=0; i < count; i++)
14017 if (src[i] == '\033')
14020 // if we hit the width, back up
14021 if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
14023 // store i & len in case the word is too long
14024 old_i = i, old_len = len;
14026 // find the end of the last word
14027 while (i && src[i] != ' ' && src[i] != '\n')
14033 // word too long? restore i & len before splitting it
14034 if ((old_i-i+clen) >= width)
14041 if (i && src[i-1] == ' ')
14044 if (src[i] != ' ' && src[i] != '\n')
14051 // now append the newline and continuation sequence
14056 strncpy(dest+len, cseq, cseq_len);
14064 dest[len] = src[i];
14068 if (src[i] == '\n')
14073 if (dest && appData.debugMode)
14075 fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
14076 count, width, line, len, *lp);
14077 show_bytes(debugFP, src, count);
14078 fprintf(debugFP, "\ndest: ");
14079 show_bytes(debugFP, dest, len);
14080 fprintf(debugFP, "\n");
14082 *lp = dest ? line : old_line;