2 * backend.c -- Common back end for X and Windows NT versions of
4 * Copyright 1991 by Digital Equipment Corporation, Maynard,
7 * Enhancements Copyright 1992-2001, 2002, 2003, 2004, 2005, 2006,
8 * 2007, 2008, 2009 Free Software Foundation, Inc.
10 * Enhancements Copyright 2005 Alessandro Scotti
12 * The following terms apply to Digital Equipment Corporation's copyright
14 * ------------------------------------------------------------------------
17 * Permission to use, copy, modify, and distribute this software and its
18 * documentation for any purpose and without fee is hereby granted,
19 * provided that the above copyright notice appear in all copies and that
20 * both that copyright notice and this permission notice appear in
21 * supporting documentation, and that the name of Digital not be
22 * used in advertising or publicity pertaining to distribution of the
23 * software without specific, written prior permission.
25 * DIGITAL DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
26 * ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL
27 * DIGITAL BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR
28 * ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
29 * WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
30 * ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
32 * ------------------------------------------------------------------------
34 * The following terms apply to the enhanced version of XBoard
35 * distributed by the Free Software Foundation:
36 * ------------------------------------------------------------------------
38 * GNU XBoard is free software: you can redistribute it and/or modify
39 * it under the terms of the GNU General Public License as published by
40 * the Free Software Foundation, either version 3 of the License, or (at
41 * your option) any later version.
43 * GNU XBoard is distributed in the hope that it will be useful, but
44 * WITHOUT ANY WARRANTY; without even the implied warranty of
45 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
46 * General Public License for more details.
48 * You should have received a copy of the GNU General Public License
49 * along with this program. If not, see http://www.gnu.org/licenses/. *
51 *------------------------------------------------------------------------
52 ** See the file ChangeLog for a revision history. */
54 /* [AS] Also useful here for debugging */
58 #define DoSleep( n ) if( (n) != 0 ) Sleep( (n) );
62 #define DoSleep( n ) if( (n) >= 0) sleep(n)
72 #include <sys/types.h>
81 #else /* not STDC_HEADERS */
84 # else /* not HAVE_STRING_H */
86 # endif /* not HAVE_STRING_H */
87 #endif /* not STDC_HEADERS */
90 # include <sys/fcntl.h>
91 #else /* not HAVE_SYS_FCNTL_H */
94 # endif /* HAVE_FCNTL_H */
95 #endif /* not HAVE_SYS_FCNTL_H */
97 #if TIME_WITH_SYS_TIME
98 # include <sys/time.h>
102 # include <sys/time.h>
108 #if defined(_amigados) && !defined(__GNUC__)
113 extern int gettimeofday(struct timeval *, struct timezone *);
121 #include "frontend.h"
128 #include "backendz.h"
132 # define _(s) gettext (s)
133 # define N_(s) gettext_noop (s)
140 /* A point in time */
142 long sec; /* Assuming this is >= 32 bits */
143 int ms; /* Assuming this is >= 16 bits */
146 int establish P((void));
147 void read_from_player P((InputSourceRef isr, VOIDSTAR closure,
148 char *buf, int count, int error));
149 void read_from_ics P((InputSourceRef isr, VOIDSTAR closure,
150 char *buf, int count, int error));
151 void ics_printf P((char *format, ...));
152 void SendToICS P((char *s));
153 void SendToICSDelayed P((char *s, long msdelay));
154 void SendMoveToICS P((ChessMove moveType, int fromX, int fromY,
156 void HandleMachineMove P((char *message, ChessProgramState *cps));
157 int AutoPlayOneMove P((void));
158 int LoadGameOneMove P((ChessMove readAhead));
159 int LoadGameFromFile P((char *filename, int n, char *title, int useList));
160 int LoadPositionFromFile P((char *filename, int n, char *title));
161 int SavePositionToFile P((char *filename));
162 void ApplyMove P((int fromX, int fromY, int toX, int toY, int promoChar,
164 void MakeMove P((int fromX, int fromY, int toX, int toY, int promoChar));
165 void ShowMove P((int fromX, int fromY, int toX, int toY));
166 int FinishMove P((ChessMove moveType, int fromX, int fromY, int toX, int toY,
167 /*char*/int promoChar));
168 void BackwardInner P((int target));
169 void ForwardInner P((int target));
170 void GameEnds P((ChessMove result, char *resultDetails, int whosays));
171 void EditPositionDone P((Boolean fakeRights));
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 char lastPV[2][2*MSG_SIZ]; /* [HGM] pv: last PV in thinking output of each engine */
240 static int exiting = 0; /* [HGM] moved to top */
241 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
242 int startedFromPositionFile = FALSE; Board filePosition; /* [HGM] loadPos */
243 char endingGame = 0; /* [HGM] crash: flag to prevent recursion of GameEnds() */
244 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS */
245 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
246 int lastIndex = 0; /* [HGM] autoinc: last game/position used in match mode */
247 int opponentKibitzes;
248 int lastSavedGame; /* [HGM] save: ID of game */
249 char chatPartner[MAX_CHAT][MSG_SIZ]; /* [HGM] chat: list of chatting partners */
250 extern int chatCount;
252 char marker[BOARD_RANKS][BOARD_FILES]; /* [HGM] marks for target squares */
254 /* States for ics_getting_history */
256 #define H_REQUESTED 1
257 #define H_GOT_REQ_HEADER 2
258 #define H_GOT_UNREQ_HEADER 3
259 #define H_GETTING_MOVES 4
260 #define H_GOT_UNWANTED_HEADER 5
262 /* whosays values for GameEnds */
271 /* Maximum number of games in a cmail message */
272 #define CMAIL_MAX_GAMES 20
274 /* Different types of move when calling RegisterMove */
276 #define CMAIL_RESIGN 1
278 #define CMAIL_ACCEPT 3
280 /* Different types of result to remember for each game */
281 #define CMAIL_NOT_RESULT 0
282 #define CMAIL_OLD_RESULT 1
283 #define CMAIL_NEW_RESULT 2
285 /* Telnet protocol constants */
296 static char * safeStrCpy( char * dst, const char * src, size_t count )
298 assert( dst != NULL );
299 assert( src != NULL );
302 strncpy( dst, src, count );
303 dst[ count-1 ] = '\0';
307 /* Some compiler can't cast u64 to double
308 * This function do the job for us:
310 * We use the highest bit for cast, this only
311 * works if the highest bit is not
312 * in use (This should not happen)
314 * We used this for all compiler
317 u64ToDouble(u64 value)
320 u64 tmp = value & u64Const(0x7fffffffffffffff);
321 r = (double)(s64)tmp;
322 if (value & u64Const(0x8000000000000000))
323 r += 9.2233720368547758080e18; /* 2^63 */
327 /* Fake up flags for now, as we aren't keeping track of castling
328 availability yet. [HGM] Change of logic: the flag now only
329 indicates the type of castlings allowed by the rule of the game.
330 The actual rights themselves are maintained in the array
331 castlingRights, as part of the game history, and are not probed
337 int flags = F_ALL_CASTLE_OK;
338 if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
339 switch (gameInfo.variant) {
341 flags &= ~F_ALL_CASTLE_OK;
342 case VariantGiveaway: // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
343 flags |= F_IGNORE_CHECK;
345 flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
348 flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
350 case VariantKriegspiel:
351 flags |= F_KRIEGSPIEL_CAPTURE;
353 case VariantCapaRandom:
354 case VariantFischeRandom:
355 flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
356 case VariantNoCastle:
357 case VariantShatranj:
360 flags &= ~F_ALL_CASTLE_OK;
368 FILE *gameFileFP, *debugFP;
371 [AS] Note: sometimes, the sscanf() function is used to parse the input
372 into a fixed-size buffer. Because of this, we must be prepared to
373 receive strings as long as the size of the input buffer, which is currently
374 set to 4K for Windows and 8K for the rest.
375 So, we must either allocate sufficiently large buffers here, or
376 reduce the size of the input buffer in the input reading part.
379 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
380 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
381 char thinkOutput1[MSG_SIZ*10];
383 ChessProgramState first, second;
385 /* premove variables */
388 int premoveFromX = 0;
389 int premoveFromY = 0;
390 int premovePromoChar = 0;
392 Boolean alarmSounded;
393 /* end premove variables */
395 char *ics_prefix = "$";
396 int ics_type = ICS_GENERIC;
398 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
399 int pauseExamForwardMostMove = 0;
400 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
401 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
402 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
403 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
404 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
405 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
406 int whiteFlag = FALSE, blackFlag = FALSE;
407 int userOfferedDraw = FALSE;
408 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
409 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
410 int cmailMoveType[CMAIL_MAX_GAMES];
411 long ics_clock_paused = 0;
412 ProcRef icsPR = NoProc, cmailPR = NoProc;
413 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
414 GameMode gameMode = BeginningOfGame;
415 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
416 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
417 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
418 int hiddenThinkOutputState = 0; /* [AS] */
419 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
420 int adjudicateLossPlies = 6;
421 char white_holding[64], black_holding[64];
422 TimeMark lastNodeCountTime;
423 long lastNodeCount=0;
424 int have_sent_ICS_logon = 0;
426 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement;
427 long timeControl_2; /* [AS] Allow separate time controls */
428 char *fullTimeControlString = NULL; /* [HGM] secondary TC: merge of MPS, TC and inc */
429 long timeRemaining[2][MAX_MOVES];
431 TimeMark programStartTime;
432 char ics_handle[MSG_SIZ];
433 int have_set_title = 0;
435 /* animateTraining preserves the state of appData.animate
436 * when Training mode is activated. This allows the
437 * response to be animated when appData.animate == TRUE and
438 * appData.animateDragging == TRUE.
440 Boolean animateTraining;
446 Board boards[MAX_MOVES];
447 /* [HGM] Following 7 needed for accurate legality tests: */
448 signed char castlingRank[BOARD_FILES]; // and corresponding ranks
449 signed char initialRights[BOARD_FILES];
450 int nrCastlingRights; // For TwoKings, or to implement castling-unknown status
451 int initialRulePlies, FENrulePlies;
452 FILE *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
455 int mute; // mute all sounds
457 // [HGM] vari: next 12 to save and restore variations
458 #define MAX_VARIATIONS 10
459 int framePtr = MAX_MOVES-1; // points to free stack entry
461 int savedFirst[MAX_VARIATIONS];
462 int savedLast[MAX_VARIATIONS];
463 int savedFramePtr[MAX_VARIATIONS];
464 char *savedDetails[MAX_VARIATIONS];
465 ChessMove savedResult[MAX_VARIATIONS];
467 void PushTail P((int firstMove, int lastMove));
468 Boolean PopTail P((Boolean annotate));
469 void CleanupTail P((void));
471 ChessSquare FIDEArray[2][BOARD_FILES] = {
472 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
473 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
474 { BlackRook, BlackKnight, BlackBishop, BlackQueen,
475 BlackKing, BlackBishop, BlackKnight, BlackRook }
478 ChessSquare twoKingsArray[2][BOARD_FILES] = {
479 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
480 WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
481 { BlackRook, BlackKnight, BlackBishop, BlackQueen,
482 BlackKing, BlackKing, BlackKnight, BlackRook }
485 ChessSquare KnightmateArray[2][BOARD_FILES] = {
486 { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
487 WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
488 { BlackRook, BlackMan, BlackBishop, BlackQueen,
489 BlackUnicorn, BlackBishop, BlackMan, BlackRook }
492 ChessSquare fairyArray[2][BOARD_FILES] = { /* [HGM] Queen side differs from King side */
493 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
494 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
495 { BlackLance, BlackAlfil, BlackMarshall, BlackAngel,
496 BlackKing, BlackMarshall, BlackAlfil, BlackLance }
499 ChessSquare ShatranjArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
500 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
501 WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
502 { BlackRook, BlackKnight, BlackAlfil, BlackKing,
503 BlackFerz, BlackAlfil, BlackKnight, BlackRook }
506 ChessSquare makrukArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
507 { WhiteRook, WhiteKnight, WhiteMan, WhiteKing,
508 WhiteFerz, WhiteMan, WhiteKnight, WhiteRook },
509 { BlackRook, BlackKnight, BlackMan, BlackFerz,
510 BlackKing, BlackMan, BlackKnight, BlackRook }
514 #if (BOARD_FILES>=10)
515 ChessSquare ShogiArray[2][BOARD_FILES] = {
516 { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
517 WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
518 { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
519 BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
522 ChessSquare XiangqiArray[2][BOARD_FILES] = {
523 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
524 WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
525 { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
526 BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
529 ChessSquare CapablancaArray[2][BOARD_FILES] = {
530 { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
531 WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
532 { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
533 BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
536 ChessSquare GreatArray[2][BOARD_FILES] = {
537 { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
538 WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
539 { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
540 BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
543 ChessSquare JanusArray[2][BOARD_FILES] = {
544 { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
545 WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
546 { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
547 BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
551 ChessSquare GothicArray[2][BOARD_FILES] = {
552 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
553 WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
554 { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
555 BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
558 #define GothicArray CapablancaArray
562 ChessSquare FalconArray[2][BOARD_FILES] = {
563 { WhiteRook, WhiteKnight, WhiteBishop, WhiteLance, WhiteQueen,
564 WhiteKing, WhiteLance, WhiteBishop, WhiteKnight, WhiteRook },
565 { BlackRook, BlackKnight, BlackBishop, BlackLance, BlackQueen,
566 BlackKing, BlackLance, BlackBishop, BlackKnight, BlackRook }
569 #define FalconArray CapablancaArray
572 #else // !(BOARD_FILES>=10)
573 #define XiangqiPosition FIDEArray
574 #define CapablancaArray FIDEArray
575 #define GothicArray FIDEArray
576 #define GreatArray FIDEArray
577 #endif // !(BOARD_FILES>=10)
579 #if (BOARD_FILES>=12)
580 ChessSquare CourierArray[2][BOARD_FILES] = {
581 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
582 WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
583 { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
584 BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
586 #else // !(BOARD_FILES>=12)
587 #define CourierArray CapablancaArray
588 #endif // !(BOARD_FILES>=12)
591 Board initialPosition;
594 /* Convert str to a rating. Checks for special cases of "----",
596 "++++", etc. Also strips ()'s */
598 string_to_rating(str)
601 while(*str && !isdigit(*str)) ++str;
603 return 0; /* One of the special "no rating" cases */
611 /* Init programStats */
612 programStats.movelist[0] = 0;
613 programStats.depth = 0;
614 programStats.nr_moves = 0;
615 programStats.moves_left = 0;
616 programStats.nodes = 0;
617 programStats.time = -1; // [HGM] PGNtime: make invalid to recognize engine output
618 programStats.score = 0;
619 programStats.got_only_move = 0;
620 programStats.got_fail = 0;
621 programStats.line_is_book = 0;
627 int matched, min, sec;
629 ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
631 GetTimeMark(&programStartTime);
632 srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
635 programStats.ok_to_send = 1;
636 programStats.seen_stat = 0;
639 * Initialize game list
645 * Internet chess server status
647 if (appData.icsActive) {
648 appData.matchMode = FALSE;
649 appData.matchGames = 0;
651 appData.noChessProgram = !appData.zippyPlay;
653 appData.zippyPlay = FALSE;
654 appData.zippyTalk = FALSE;
655 appData.noChessProgram = TRUE;
657 if (*appData.icsHelper != NULLCHAR) {
658 appData.useTelnet = TRUE;
659 appData.telnetProgram = appData.icsHelper;
662 appData.zippyTalk = appData.zippyPlay = FALSE;
665 /* [AS] Initialize pv info list [HGM] and game state */
669 for( i=0; i<=framePtr; i++ ) {
670 pvInfoList[i].depth = -1;
671 boards[i][EP_STATUS] = EP_NONE;
672 for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
677 * Parse timeControl resource
679 if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
680 appData.movesPerSession)) {
682 snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
683 DisplayFatalError(buf, 0, 2);
687 * Parse searchTime resource
689 if (*appData.searchTime != NULLCHAR) {
690 matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
692 searchTime = min * 60;
693 } else if (matched == 2) {
694 searchTime = min * 60 + sec;
697 snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
698 DisplayFatalError(buf, 0, 2);
702 /* [AS] Adjudication threshold */
703 adjudicateLossThreshold = appData.adjudicateLossThreshold;
705 first.which = "first";
706 second.which = "second";
707 first.maybeThinking = second.maybeThinking = FALSE;
708 first.pr = second.pr = NoProc;
709 first.isr = second.isr = NULL;
710 first.sendTime = second.sendTime = 2;
711 first.sendDrawOffers = 1;
712 if (appData.firstPlaysBlack) {
713 first.twoMachinesColor = "black\n";
714 second.twoMachinesColor = "white\n";
716 first.twoMachinesColor = "white\n";
717 second.twoMachinesColor = "black\n";
719 first.program = appData.firstChessProgram;
720 second.program = appData.secondChessProgram;
721 first.host = appData.firstHost;
722 second.host = appData.secondHost;
723 first.dir = appData.firstDirectory;
724 second.dir = appData.secondDirectory;
725 first.other = &second;
726 second.other = &first;
727 first.initString = appData.initString;
728 second.initString = appData.secondInitString;
729 first.computerString = appData.firstComputerString;
730 second.computerString = appData.secondComputerString;
731 first.useSigint = second.useSigint = TRUE;
732 first.useSigterm = second.useSigterm = TRUE;
733 first.reuse = appData.reuseFirst;
734 second.reuse = appData.reuseSecond;
735 first.nps = appData.firstNPS; // [HGM] nps: copy nodes per second
736 second.nps = appData.secondNPS;
737 first.useSetboard = second.useSetboard = FALSE;
738 first.useSAN = second.useSAN = FALSE;
739 first.usePing = second.usePing = FALSE;
740 first.lastPing = second.lastPing = 0;
741 first.lastPong = second.lastPong = 0;
742 first.usePlayother = second.usePlayother = FALSE;
743 first.useColors = second.useColors = TRUE;
744 first.useUsermove = second.useUsermove = FALSE;
745 first.sendICS = second.sendICS = FALSE;
746 first.sendName = second.sendName = appData.icsActive;
747 first.sdKludge = second.sdKludge = FALSE;
748 first.stKludge = second.stKludge = FALSE;
749 TidyProgramName(first.program, first.host, first.tidy);
750 TidyProgramName(second.program, second.host, second.tidy);
751 first.matchWins = second.matchWins = 0;
752 strcpy(first.variants, appData.variant);
753 strcpy(second.variants, appData.variant);
754 first.analysisSupport = second.analysisSupport = 2; /* detect */
755 first.analyzing = second.analyzing = FALSE;
756 first.initDone = second.initDone = FALSE;
758 /* New features added by Tord: */
759 first.useFEN960 = FALSE; second.useFEN960 = FALSE;
760 first.useOOCastle = TRUE; second.useOOCastle = TRUE;
761 /* End of new features added by Tord. */
762 first.fenOverride = appData.fenOverride1;
763 second.fenOverride = appData.fenOverride2;
765 /* [HGM] time odds: set factor for each machine */
766 first.timeOdds = appData.firstTimeOdds;
767 second.timeOdds = appData.secondTimeOdds;
769 if(appData.timeOddsMode) {
770 norm = first.timeOdds;
771 if(norm > second.timeOdds) norm = second.timeOdds;
773 first.timeOdds /= norm;
774 second.timeOdds /= norm;
777 /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
778 first.accumulateTC = appData.firstAccumulateTC;
779 second.accumulateTC = appData.secondAccumulateTC;
780 first.maxNrOfSessions = second.maxNrOfSessions = 1;
783 first.debug = second.debug = FALSE;
784 first.supportsNPS = second.supportsNPS = UNKNOWN;
787 first.optionSettings = appData.firstOptions;
788 second.optionSettings = appData.secondOptions;
790 first.scoreIsAbsolute = appData.firstScoreIsAbsolute; /* [AS] */
791 second.scoreIsAbsolute = appData.secondScoreIsAbsolute; /* [AS] */
792 first.isUCI = appData.firstIsUCI; /* [AS] */
793 second.isUCI = appData.secondIsUCI; /* [AS] */
794 first.hasOwnBookUCI = appData.firstHasOwnBookUCI; /* [AS] */
795 second.hasOwnBookUCI = appData.secondHasOwnBookUCI; /* [AS] */
797 if (appData.firstProtocolVersion > PROTOVER ||
798 appData.firstProtocolVersion < 1) {
800 sprintf(buf, _("protocol version %d not supported"),
801 appData.firstProtocolVersion);
802 DisplayFatalError(buf, 0, 2);
804 first.protocolVersion = appData.firstProtocolVersion;
807 if (appData.secondProtocolVersion > PROTOVER ||
808 appData.secondProtocolVersion < 1) {
810 sprintf(buf, _("protocol version %d not supported"),
811 appData.secondProtocolVersion);
812 DisplayFatalError(buf, 0, 2);
814 second.protocolVersion = appData.secondProtocolVersion;
817 if (appData.icsActive) {
818 appData.clockMode = TRUE; /* changes dynamically in ICS mode */
819 // } else if (*appData.searchTime != NULLCHAR || appData.noChessProgram) {
820 } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
821 appData.clockMode = FALSE;
822 first.sendTime = second.sendTime = 0;
826 /* Override some settings from environment variables, for backward
827 compatibility. Unfortunately it's not feasible to have the env
828 vars just set defaults, at least in xboard. Ugh.
830 if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
835 if (appData.noChessProgram) {
836 programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
837 sprintf(programVersion, "%s", PACKAGE_STRING);
839 /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
840 programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
841 sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
844 if (!appData.icsActive) {
846 /* Check for variants that are supported only in ICS mode,
847 or not at all. Some that are accepted here nevertheless
848 have bugs; see comments below.
850 VariantClass variant = StringToVariant(appData.variant);
852 case VariantBughouse: /* need four players and two boards */
853 case VariantKriegspiel: /* need to hide pieces and move details */
854 /* case VariantFischeRandom: (Fabien: moved below) */
855 sprintf(buf, _("Variant %s supported only in ICS mode"), appData.variant);
856 DisplayFatalError(buf, 0, 2);
860 case VariantLoadable:
870 sprintf(buf, _("Unknown variant name %s"), appData.variant);
871 DisplayFatalError(buf, 0, 2);
874 case VariantXiangqi: /* [HGM] repetition rules not implemented */
875 case VariantFairy: /* [HGM] TestLegality definitely off! */
876 case VariantGothic: /* [HGM] should work */
877 case VariantCapablanca: /* [HGM] should work */
878 case VariantCourier: /* [HGM] initial forced moves not implemented */
879 case VariantShogi: /* [HGM] drops not tested for legality */
880 case VariantKnightmate: /* [HGM] should work */
881 case VariantCylinder: /* [HGM] untested */
882 case VariantFalcon: /* [HGM] untested */
883 case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
884 offboard interposition not understood */
885 case VariantNormal: /* definitely works! */
886 case VariantWildCastle: /* pieces not automatically shuffled */
887 case VariantNoCastle: /* pieces not automatically shuffled */
888 case VariantFischeRandom: /* [HGM] works and shuffles pieces */
889 case VariantLosers: /* should work except for win condition,
890 and doesn't know captures are mandatory */
891 case VariantSuicide: /* should work except for win condition,
892 and doesn't know captures are mandatory */
893 case VariantGiveaway: /* should work except for win condition,
894 and doesn't know captures are mandatory */
895 case VariantTwoKings: /* should work */
896 case VariantAtomic: /* should work except for win condition */
897 case Variant3Check: /* should work except for win condition */
898 case VariantShatranj: /* should work except for all win conditions */
899 case VariantMakruk: /* should work except for daw countdown */
900 case VariantBerolina: /* might work if TestLegality is off */
901 case VariantCapaRandom: /* should work */
902 case VariantJanus: /* should work */
903 case VariantSuper: /* experimental */
904 case VariantGreat: /* experimental, requires legality testing to be off */
909 InitEngineUCI( installDir, &first ); // [HGM] moved here from winboard.c, to make available in xboard
910 InitEngineUCI( installDir, &second );
913 int NextIntegerFromString( char ** str, long * value )
918 while( *s == ' ' || *s == '\t' ) {
924 if( *s >= '0' && *s <= '9' ) {
925 while( *s >= '0' && *s <= '9' ) {
926 *value = *value * 10 + (*s - '0');
938 int NextTimeControlFromString( char ** str, long * value )
941 int result = NextIntegerFromString( str, &temp );
944 *value = temp * 60; /* Minutes */
947 result = NextIntegerFromString( str, &temp );
948 *value += temp; /* Seconds */
955 int NextSessionFromString( char ** str, int *moves, long * tc, long *inc)
956 { /* [HGM] routine added to read '+moves/time' for secondary time control */
957 int result = -1; long temp, temp2;
959 if(**str != '+') return -1; // old params remain in force!
961 if( NextTimeControlFromString( str, &temp ) ) return -1;
964 /* time only: incremental or sudden-death time control */
965 if(**str == '+') { /* increment follows; read it */
967 if(result = NextIntegerFromString( str, &temp2)) return -1;
970 *moves = 0; *tc = temp * 1000;
972 } else if(temp % 60 != 0) return -1; /* moves was given as min:sec */
974 (*str)++; /* classical time control */
975 result = NextTimeControlFromString( str, &temp2);
984 int GetTimeQuota(int movenr)
985 { /* [HGM] get time to add from the multi-session time-control string */
986 int moves=1; /* kludge to force reading of first session */
987 long time, increment;
988 char *s = fullTimeControlString;
990 if(appData.debugMode) fprintf(debugFP, "TC string = '%s'\n", fullTimeControlString);
992 if(moves) NextSessionFromString(&s, &moves, &time, &increment);
993 if(appData.debugMode) fprintf(debugFP, "mps=%d tc=%d inc=%d\n", moves, (int) time, (int) increment);
994 if(movenr == -1) return time; /* last move before new session */
995 if(!moves) return increment; /* current session is incremental */
996 if(movenr >= 0) movenr -= moves; /* we already finished this session */
997 } while(movenr >= -1); /* try again for next session */
999 return 0; // no new time quota on this move
1003 ParseTimeControl(tc, ti, mps)
1012 if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1015 sprintf(buf, "+%d/%s+%d", mps, tc, ti);
1016 else sprintf(buf, "+%s+%d", tc, ti);
1019 sprintf(buf, "+%d/%s", mps, tc);
1020 else sprintf(buf, "+%s", tc);
1022 fullTimeControlString = StrSave(buf);
1024 if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1029 /* Parse second time control */
1032 if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1040 timeControl_2 = tc2 * 1000;
1050 timeControl = tc1 * 1000;
1053 timeIncrement = ti * 1000; /* convert to ms */
1054 movesPerSession = 0;
1057 movesPerSession = mps;
1065 if (appData.debugMode) {
1066 fprintf(debugFP, "%s\n", programVersion);
1069 set_cont_sequence(appData.wrapContSeq);
1070 if (appData.matchGames > 0) {
1071 appData.matchMode = TRUE;
1072 } else if (appData.matchMode) {
1073 appData.matchGames = 1;
1075 if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1076 appData.matchGames = appData.sameColorGames;
1077 if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1078 if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1079 if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1082 if (appData.noChessProgram || first.protocolVersion == 1) {
1085 /* kludge: allow timeout for initial "feature" commands */
1087 DisplayMessage("", _("Starting chess program"));
1088 ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1093 InitBackEnd3 P((void))
1095 GameMode initialMode;
1099 InitChessProgram(&first, startedFromSetupPosition);
1102 if (appData.icsActive) {
1104 /* [DM] Make a console window if needed [HGM] merged ifs */
1109 if (*appData.icsCommPort != NULLCHAR) {
1110 sprintf(buf, _("Could not open comm port %s"),
1111 appData.icsCommPort);
1113 snprintf(buf, sizeof(buf), _("Could not connect to host %s, port %s"),
1114 appData.icsHost, appData.icsPort);
1116 DisplayFatalError(buf, err, 1);
1121 AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1123 AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1124 } else if (appData.noChessProgram) {
1130 if (*appData.cmailGameName != NULLCHAR) {
1132 OpenLoopback(&cmailPR);
1134 AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1138 DisplayMessage("", "");
1139 if (StrCaseCmp(appData.initialMode, "") == 0) {
1140 initialMode = BeginningOfGame;
1141 } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1142 initialMode = TwoMachinesPlay;
1143 } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1144 initialMode = AnalyzeFile;
1145 } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1146 initialMode = AnalyzeMode;
1147 } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1148 initialMode = MachinePlaysWhite;
1149 } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1150 initialMode = MachinePlaysBlack;
1151 } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1152 initialMode = EditGame;
1153 } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1154 initialMode = EditPosition;
1155 } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1156 initialMode = Training;
1158 sprintf(buf, _("Unknown initialMode %s"), appData.initialMode);
1159 DisplayFatalError(buf, 0, 2);
1163 if (appData.matchMode) {
1164 /* Set up machine vs. machine match */
1165 if (appData.noChessProgram) {
1166 DisplayFatalError(_("Can't have a match with no chess programs"),
1172 if (*appData.loadGameFile != NULLCHAR) {
1173 int index = appData.loadGameIndex; // [HGM] autoinc
1174 if(index<0) lastIndex = index = 1;
1175 if (!LoadGameFromFile(appData.loadGameFile,
1177 appData.loadGameFile, FALSE)) {
1178 DisplayFatalError(_("Bad game file"), 0, 1);
1181 } else if (*appData.loadPositionFile != NULLCHAR) {
1182 int index = appData.loadPositionIndex; // [HGM] autoinc
1183 if(index<0) lastIndex = index = 1;
1184 if (!LoadPositionFromFile(appData.loadPositionFile,
1186 appData.loadPositionFile)) {
1187 DisplayFatalError(_("Bad position file"), 0, 1);
1192 } else if (*appData.cmailGameName != NULLCHAR) {
1193 /* Set up cmail mode */
1194 ReloadCmailMsgEvent(TRUE);
1196 /* Set up other modes */
1197 if (initialMode == AnalyzeFile) {
1198 if (*appData.loadGameFile == NULLCHAR) {
1199 DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1203 if (*appData.loadGameFile != NULLCHAR) {
1204 (void) LoadGameFromFile(appData.loadGameFile,
1205 appData.loadGameIndex,
1206 appData.loadGameFile, TRUE);
1207 } else if (*appData.loadPositionFile != NULLCHAR) {
1208 (void) LoadPositionFromFile(appData.loadPositionFile,
1209 appData.loadPositionIndex,
1210 appData.loadPositionFile);
1211 /* [HGM] try to make self-starting even after FEN load */
1212 /* to allow automatic setup of fairy variants with wtm */
1213 if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1214 gameMode = BeginningOfGame;
1215 setboardSpoiledMachineBlack = 1;
1217 /* [HGM] loadPos: make that every new game uses the setup */
1218 /* from file as long as we do not switch variant */
1219 if(!blackPlaysFirst) {
1220 startedFromPositionFile = TRUE;
1221 CopyBoard(filePosition, boards[0]);
1224 if (initialMode == AnalyzeMode) {
1225 if (appData.noChessProgram) {
1226 DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1229 if (appData.icsActive) {
1230 DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1234 } else if (initialMode == AnalyzeFile) {
1235 appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1236 ShowThinkingEvent();
1238 AnalysisPeriodicEvent(1);
1239 } else if (initialMode == MachinePlaysWhite) {
1240 if (appData.noChessProgram) {
1241 DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1245 if (appData.icsActive) {
1246 DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1250 MachineWhiteEvent();
1251 } else if (initialMode == MachinePlaysBlack) {
1252 if (appData.noChessProgram) {
1253 DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1257 if (appData.icsActive) {
1258 DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1262 MachineBlackEvent();
1263 } else if (initialMode == TwoMachinesPlay) {
1264 if (appData.noChessProgram) {
1265 DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1269 if (appData.icsActive) {
1270 DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1275 } else if (initialMode == EditGame) {
1277 } else if (initialMode == EditPosition) {
1278 EditPositionEvent();
1279 } else if (initialMode == Training) {
1280 if (*appData.loadGameFile == NULLCHAR) {
1281 DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1290 * Establish will establish a contact to a remote host.port.
1291 * Sets icsPR to a ProcRef for a process (or pseudo-process)
1292 * used to talk to the host.
1293 * Returns 0 if okay, error code if not.
1300 if (*appData.icsCommPort != NULLCHAR) {
1301 /* Talk to the host through a serial comm port */
1302 return OpenCommPort(appData.icsCommPort, &icsPR);
1304 } else if (*appData.gateway != NULLCHAR) {
1305 if (*appData.remoteShell == NULLCHAR) {
1306 /* Use the rcmd protocol to run telnet program on a gateway host */
1307 snprintf(buf, sizeof(buf), "%s %s %s",
1308 appData.telnetProgram, appData.icsHost, appData.icsPort);
1309 return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1312 /* Use the rsh program to run telnet program on a gateway host */
1313 if (*appData.remoteUser == NULLCHAR) {
1314 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1315 appData.gateway, appData.telnetProgram,
1316 appData.icsHost, appData.icsPort);
1318 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1319 appData.remoteShell, appData.gateway,
1320 appData.remoteUser, appData.telnetProgram,
1321 appData.icsHost, appData.icsPort);
1323 return StartChildProcess(buf, "", &icsPR);
1326 } else if (appData.useTelnet) {
1327 return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1330 /* TCP socket interface differs somewhat between
1331 Unix and NT; handle details in the front end.
1333 return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1338 show_bytes(fp, buf, count)
1344 if (*buf < 040 || *(unsigned char *) buf > 0177) {
1345 fprintf(fp, "\\%03o", *buf & 0xff);
1354 /* Returns an errno value */
1356 OutputMaybeTelnet(pr, message, count, outError)
1362 char buf[8192], *p, *q, *buflim;
1363 int left, newcount, outcount;
1365 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1366 *appData.gateway != NULLCHAR) {
1367 if (appData.debugMode) {
1368 fprintf(debugFP, ">ICS: ");
1369 show_bytes(debugFP, message, count);
1370 fprintf(debugFP, "\n");
1372 return OutputToProcess(pr, message, count, outError);
1375 buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1382 if (appData.debugMode) {
1383 fprintf(debugFP, ">ICS: ");
1384 show_bytes(debugFP, buf, newcount);
1385 fprintf(debugFP, "\n");
1387 outcount = OutputToProcess(pr, buf, newcount, outError);
1388 if (outcount < newcount) return -1; /* to be sure */
1395 } else if (((unsigned char) *p) == TN_IAC) {
1396 *q++ = (char) TN_IAC;
1403 if (appData.debugMode) {
1404 fprintf(debugFP, ">ICS: ");
1405 show_bytes(debugFP, buf, newcount);
1406 fprintf(debugFP, "\n");
1408 outcount = OutputToProcess(pr, buf, newcount, outError);
1409 if (outcount < newcount) return -1; /* to be sure */
1414 read_from_player(isr, closure, message, count, error)
1421 int outError, outCount;
1422 static int gotEof = 0;
1424 /* Pass data read from player on to ICS */
1427 outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1428 if (outCount < count) {
1429 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1431 } else if (count < 0) {
1432 RemoveInputSource(isr);
1433 DisplayFatalError(_("Error reading from keyboard"), error, 1);
1434 } else if (gotEof++ > 0) {
1435 RemoveInputSource(isr);
1436 DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1442 { // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1443 SendToICS("date\n");
1444 if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1447 /* added routine for printf style output to ics */
1448 void ics_printf(char *format, ...)
1450 char buffer[MSG_SIZ];
1453 va_start(args, format);
1454 vsnprintf(buffer, sizeof(buffer), format, args);
1455 buffer[sizeof(buffer)-1] = '\0';
1464 int count, outCount, outError;
1466 if (icsPR == NULL) return;
1469 outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1470 if (outCount < count) {
1471 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1475 /* This is used for sending logon scripts to the ICS. Sending
1476 without a delay causes problems when using timestamp on ICC
1477 (at least on my machine). */
1479 SendToICSDelayed(s,msdelay)
1483 int count, outCount, outError;
1485 if (icsPR == NULL) return;
1488 if (appData.debugMode) {
1489 fprintf(debugFP, ">ICS: ");
1490 show_bytes(debugFP, s, count);
1491 fprintf(debugFP, "\n");
1493 outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1495 if (outCount < count) {
1496 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1501 /* Remove all highlighting escape sequences in s
1502 Also deletes any suffix starting with '('
1505 StripHighlightAndTitle(s)
1508 static char retbuf[MSG_SIZ];
1511 while (*s != NULLCHAR) {
1512 while (*s == '\033') {
1513 while (*s != NULLCHAR && !isalpha(*s)) s++;
1514 if (*s != NULLCHAR) s++;
1516 while (*s != NULLCHAR && *s != '\033') {
1517 if (*s == '(' || *s == '[') {
1528 /* Remove all highlighting escape sequences in s */
1533 static char retbuf[MSG_SIZ];
1536 while (*s != NULLCHAR) {
1537 while (*s == '\033') {
1538 while (*s != NULLCHAR && !isalpha(*s)) s++;
1539 if (*s != NULLCHAR) s++;
1541 while (*s != NULLCHAR && *s != '\033') {
1549 char *variantNames[] = VARIANT_NAMES;
1554 return variantNames[v];
1558 /* Identify a variant from the strings the chess servers use or the
1559 PGN Variant tag names we use. */
1566 VariantClass v = VariantNormal;
1567 int i, found = FALSE;
1572 /* [HGM] skip over optional board-size prefixes */
1573 if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1574 sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1575 while( *e++ != '_');
1578 if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
1582 for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
1583 if (StrCaseStr(e, variantNames[i])) {
1584 v = (VariantClass) i;
1591 if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
1592 || StrCaseStr(e, "wild/fr")
1593 || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
1594 v = VariantFischeRandom;
1595 } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
1596 (i = 1, p = StrCaseStr(e, "w"))) {
1598 while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
1605 case 0: /* FICS only, actually */
1607 /* Castling legal even if K starts on d-file */
1608 v = VariantWildCastle;
1613 /* Castling illegal even if K & R happen to start in
1614 normal positions. */
1615 v = VariantNoCastle;
1628 /* Castling legal iff K & R start in normal positions */
1634 /* Special wilds for position setup; unclear what to do here */
1635 v = VariantLoadable;
1638 /* Bizarre ICC game */
1639 v = VariantTwoKings;
1642 v = VariantKriegspiel;
1648 v = VariantFischeRandom;
1651 v = VariantCrazyhouse;
1654 v = VariantBughouse;
1660 /* Not quite the same as FICS suicide! */
1661 v = VariantGiveaway;
1667 v = VariantShatranj;
1670 /* Temporary names for future ICC types. The name *will* change in
1671 the next xboard/WinBoard release after ICC defines it. */
1709 v = VariantCapablanca;
1712 v = VariantKnightmate;
1718 v = VariantCylinder;
1724 v = VariantCapaRandom;
1727 v = VariantBerolina;
1739 /* Found "wild" or "w" in the string but no number;
1740 must assume it's normal chess. */
1744 sprintf(buf, _("Unknown wild type %d"), wnum);
1745 DisplayError(buf, 0);
1751 if (appData.debugMode) {
1752 fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
1753 e, wnum, VariantName(v));
1758 static int leftover_start = 0, leftover_len = 0;
1759 char star_match[STAR_MATCH_N][MSG_SIZ];
1761 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
1762 advance *index beyond it, and set leftover_start to the new value of
1763 *index; else return FALSE. If pattern contains the character '*', it
1764 matches any sequence of characters not containing '\r', '\n', or the
1765 character following the '*' (if any), and the matched sequence(s) are
1766 copied into star_match.
1769 looking_at(buf, index, pattern)
1774 char *bufp = &buf[*index], *patternp = pattern;
1776 char *matchp = star_match[0];
1779 if (*patternp == NULLCHAR) {
1780 *index = leftover_start = bufp - buf;
1784 if (*bufp == NULLCHAR) return FALSE;
1785 if (*patternp == '*') {
1786 if (*bufp == *(patternp + 1)) {
1788 matchp = star_match[++star_count];
1792 } else if (*bufp == '\n' || *bufp == '\r') {
1794 if (*patternp == NULLCHAR)
1799 *matchp++ = *bufp++;
1803 if (*patternp != *bufp) return FALSE;
1810 SendToPlayer(data, length)
1814 int error, outCount;
1815 outCount = OutputToProcess(NoProc, data, length, &error);
1816 if (outCount < length) {
1817 DisplayFatalError(_("Error writing to display"), error, 1);
1822 PackHolding(packed, holding)
1834 switch (runlength) {
1845 sprintf(q, "%d", runlength);
1857 /* Telnet protocol requests from the front end */
1859 TelnetRequest(ddww, option)
1860 unsigned char ddww, option;
1862 unsigned char msg[3];
1863 int outCount, outError;
1865 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
1867 if (appData.debugMode) {
1868 char buf1[8], buf2[8], *ddwwStr, *optionStr;
1884 sprintf(buf1, "%d", ddww);
1893 sprintf(buf2, "%d", option);
1896 fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
1901 outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
1903 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1910 if (!appData.icsActive) return;
1911 TelnetRequest(TN_DO, TN_ECHO);
1917 if (!appData.icsActive) return;
1918 TelnetRequest(TN_DONT, TN_ECHO);
1922 CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece)
1924 /* put the holdings sent to us by the server on the board holdings area */
1925 int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
1929 if(gameInfo.holdingsWidth < 2) return;
1930 if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
1931 return; // prevent overwriting by pre-board holdings
1933 if( (int)lowestPiece >= BlackPawn ) {
1936 holdingsStartRow = BOARD_HEIGHT-1;
1939 holdingsColumn = BOARD_WIDTH-1;
1940 countsColumn = BOARD_WIDTH-2;
1941 holdingsStartRow = 0;
1945 for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
1946 board[i][holdingsColumn] = EmptySquare;
1947 board[i][countsColumn] = (ChessSquare) 0;
1949 while( (p=*holdings++) != NULLCHAR ) {
1950 piece = CharToPiece( ToUpper(p) );
1951 if(piece == EmptySquare) continue;
1952 /*j = (int) piece - (int) WhitePawn;*/
1953 j = PieceToNumber(piece);
1954 if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
1955 if(j < 0) continue; /* should not happen */
1956 piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
1957 board[holdingsStartRow+j*direction][holdingsColumn] = piece;
1958 board[holdingsStartRow+j*direction][countsColumn]++;
1964 VariantSwitch(Board board, VariantClass newVariant)
1966 int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
1969 startedFromPositionFile = FALSE;
1970 if(gameInfo.variant == newVariant) return;
1972 /* [HGM] This routine is called each time an assignment is made to
1973 * gameInfo.variant during a game, to make sure the board sizes
1974 * are set to match the new variant. If that means adding or deleting
1975 * holdings, we shift the playing board accordingly
1976 * This kludge is needed because in ICS observe mode, we get boards
1977 * of an ongoing game without knowing the variant, and learn about the
1978 * latter only later. This can be because of the move list we requested,
1979 * in which case the game history is refilled from the beginning anyway,
1980 * but also when receiving holdings of a crazyhouse game. In the latter
1981 * case we want to add those holdings to the already received position.
1985 if (appData.debugMode) {
1986 fprintf(debugFP, "Switch board from %s to %s\n",
1987 VariantName(gameInfo.variant), VariantName(newVariant));
1988 setbuf(debugFP, NULL);
1990 shuffleOpenings = 0; /* [HGM] shuffle */
1991 gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
1995 newWidth = 9; newHeight = 9;
1996 gameInfo.holdingsSize = 7;
1997 case VariantBughouse:
1998 case VariantCrazyhouse:
1999 newHoldingsWidth = 2; break;
2003 newHoldingsWidth = 2;
2004 gameInfo.holdingsSize = 8;
2007 case VariantCapablanca:
2008 case VariantCapaRandom:
2011 newHoldingsWidth = gameInfo.holdingsSize = 0;
2014 if(newWidth != gameInfo.boardWidth ||
2015 newHeight != gameInfo.boardHeight ||
2016 newHoldingsWidth != gameInfo.holdingsWidth ) {
2018 /* shift position to new playing area, if needed */
2019 if(newHoldingsWidth > gameInfo.holdingsWidth) {
2020 for(i=0; i<BOARD_HEIGHT; i++)
2021 for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2022 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2024 for(i=0; i<newHeight; i++) {
2025 board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2026 board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2028 } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2029 for(i=0; i<BOARD_HEIGHT; i++)
2030 for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2031 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2034 gameInfo.boardWidth = newWidth;
2035 gameInfo.boardHeight = newHeight;
2036 gameInfo.holdingsWidth = newHoldingsWidth;
2037 gameInfo.variant = newVariant;
2038 InitDrawingSizes(-2, 0);
2039 } else gameInfo.variant = newVariant;
2040 CopyBoard(oldBoard, board); // remember correctly formatted board
2041 InitPosition(FALSE); /* this sets up board[0], but also other stuff */
2042 DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2045 static int loggedOn = FALSE;
2047 /*-- Game start info cache: --*/
2049 char gs_kind[MSG_SIZ];
2050 static char player1Name[128] = "";
2051 static char player2Name[128] = "";
2052 static char cont_seq[] = "\n\\ ";
2053 static int player1Rating = -1;
2054 static int player2Rating = -1;
2055 /*----------------------------*/
2057 ColorClass curColor = ColorNormal;
2058 int suppressKibitz = 0;
2061 read_from_ics(isr, closure, data, count, error)
2068 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2069 #define STARTED_NONE 0
2070 #define STARTED_MOVES 1
2071 #define STARTED_BOARD 2
2072 #define STARTED_OBSERVE 3
2073 #define STARTED_HOLDINGS 4
2074 #define STARTED_CHATTER 5
2075 #define STARTED_COMMENT 6
2076 #define STARTED_MOVES_NOHIDE 7
2078 static int started = STARTED_NONE;
2079 static char parse[20000];
2080 static int parse_pos = 0;
2081 static char buf[BUF_SIZE + 1];
2082 static int firstTime = TRUE, intfSet = FALSE;
2083 static ColorClass prevColor = ColorNormal;
2084 static int savingComment = FALSE;
2085 static int cmatch = 0; // continuation sequence match
2092 int backup; /* [DM] For zippy color lines */
2094 char talker[MSG_SIZ]; // [HGM] chat
2097 if (appData.debugMode) {
2099 fprintf(debugFP, "<ICS: ");
2100 show_bytes(debugFP, data, count);
2101 fprintf(debugFP, "\n");
2105 if (appData.debugMode) { int f = forwardMostMove;
2106 fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2107 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2108 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2111 /* If last read ended with a partial line that we couldn't parse,
2112 prepend it to the new read and try again. */
2113 if (leftover_len > 0) {
2114 for (i=0; i<leftover_len; i++)
2115 buf[i] = buf[leftover_start + i];
2118 /* copy new characters into the buffer */
2119 bp = buf + leftover_len;
2120 buf_len=leftover_len;
2121 for (i=0; i<count; i++)
2124 if (data[i] == '\r')
2127 // join lines split by ICS?
2128 if (!appData.noJoin)
2131 Joining just consists of finding matches against the
2132 continuation sequence, and discarding that sequence
2133 if found instead of copying it. So, until a match
2134 fails, there's nothing to do since it might be the
2135 complete sequence, and thus, something we don't want
2138 if (data[i] == cont_seq[cmatch])
2141 if (cmatch == strlen(cont_seq))
2143 cmatch = 0; // complete match. just reset the counter
2146 it's possible for the ICS to not include the space
2147 at the end of the last word, making our [correct]
2148 join operation fuse two separate words. the server
2149 does this when the space occurs at the width setting.
2151 if (!buf_len || buf[buf_len-1] != ' ')
2162 match failed, so we have to copy what matched before
2163 falling through and copying this character. In reality,
2164 this will only ever be just the newline character, but
2165 it doesn't hurt to be precise.
2167 strncpy(bp, cont_seq, cmatch);
2179 buf[buf_len] = NULLCHAR;
2180 // next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2185 while (i < buf_len) {
2186 /* Deal with part of the TELNET option negotiation
2187 protocol. We refuse to do anything beyond the
2188 defaults, except that we allow the WILL ECHO option,
2189 which ICS uses to turn off password echoing when we are
2190 directly connected to it. We reject this option
2191 if localLineEditing mode is on (always on in xboard)
2192 and we are talking to port 23, which might be a real
2193 telnet server that will try to keep WILL ECHO on permanently.
2195 if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2196 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2197 unsigned char option;
2199 switch ((unsigned char) buf[++i]) {
2201 if (appData.debugMode)
2202 fprintf(debugFP, "\n<WILL ");
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;
2210 if (appData.localLineEditing &&
2211 atoi(appData.icsPort) == TN_PORT) {
2212 TelnetRequest(TN_DONT, TN_ECHO);
2215 TelnetRequest(TN_DO, TN_ECHO);
2216 remoteEchoOption = TRUE;
2220 if (appData.debugMode)
2221 fprintf(debugFP, "%d ", option);
2222 /* Whatever this is, we don't want it. */
2223 TelnetRequest(TN_DONT, option);
2228 if (appData.debugMode)
2229 fprintf(debugFP, "\n<WONT ");
2230 switch (option = (unsigned char) buf[++i]) {
2232 if (appData.debugMode)
2233 fprintf(debugFP, "ECHO ");
2234 /* Reply only if this is a change, according
2235 to the protocol rules. */
2236 if (!remoteEchoOption) break;
2238 TelnetRequest(TN_DONT, TN_ECHO);
2239 remoteEchoOption = FALSE;
2242 if (appData.debugMode)
2243 fprintf(debugFP, "%d ", (unsigned char) option);
2244 /* Whatever this is, it must already be turned
2245 off, because we never agree to turn on
2246 anything non-default, so according to the
2247 protocol rules, we don't reply. */
2252 if (appData.debugMode)
2253 fprintf(debugFP, "\n<DO ");
2254 switch (option = (unsigned char) buf[++i]) {
2256 /* Whatever this is, we refuse to do it. */
2257 if (appData.debugMode)
2258 fprintf(debugFP, "%d ", option);
2259 TelnetRequest(TN_WONT, option);
2264 if (appData.debugMode)
2265 fprintf(debugFP, "\n<DONT ");
2266 switch (option = (unsigned char) buf[++i]) {
2268 if (appData.debugMode)
2269 fprintf(debugFP, "%d ", option);
2270 /* Whatever this is, we are already not doing
2271 it, because we never agree to do anything
2272 non-default, so according to the protocol
2273 rules, we don't reply. */
2278 if (appData.debugMode)
2279 fprintf(debugFP, "\n<IAC ");
2280 /* Doubled IAC; pass it through */
2284 if (appData.debugMode)
2285 fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2286 /* Drop all other telnet commands on the floor */
2289 if (oldi > next_out)
2290 SendToPlayer(&buf[next_out], oldi - next_out);
2296 /* OK, this at least will *usually* work */
2297 if (!loggedOn && looking_at(buf, &i, "ics%")) {
2301 if (loggedOn && !intfSet) {
2302 if (ics_type == ICS_ICC) {
2304 "/set-quietly interface %s\n/set-quietly style 12\n",
2306 } else if (ics_type == ICS_CHESSNET) {
2307 sprintf(str, "/style 12\n");
2309 strcpy(str, "alias $ @\n$set interface ");
2310 strcat(str, programVersion);
2311 strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2313 strcat(str, "$iset nohighlight 1\n");
2315 strcat(str, "$iset lock 1\n$style 12\n");
2318 NotifyFrontendLogin();
2322 if (started == STARTED_COMMENT) {
2323 /* Accumulate characters in comment */
2324 parse[parse_pos++] = buf[i];
2325 if (buf[i] == '\n') {
2326 parse[parse_pos] = NULLCHAR;
2327 if(chattingPartner>=0) {
2329 sprintf(mess, "%s%s", talker, parse);
2330 OutputChatMessage(chattingPartner, mess);
2331 chattingPartner = -1;
2333 if(!suppressKibitz) // [HGM] kibitz
2334 AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
2335 else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2336 int nrDigit = 0, nrAlph = 0, j;
2337 if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2338 { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2339 parse[parse_pos] = NULLCHAR;
2340 // try to be smart: if it does not look like search info, it should go to
2341 // ICS interaction window after all, not to engine-output window.
2342 for(j=0; j<parse_pos; j++) { // count letters and digits
2343 nrDigit += (parse[j] >= '0' && parse[j] <= '9');
2344 nrAlph += (parse[j] >= 'a' && parse[j] <= 'z');
2345 nrAlph += (parse[j] >= 'A' && parse[j] <= 'Z');
2347 if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2348 int depth=0; float score;
2349 if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2350 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2351 pvInfoList[forwardMostMove-1].depth = depth;
2352 pvInfoList[forwardMostMove-1].score = 100*score;
2354 OutputKibitz(suppressKibitz, parse);
2355 next_out = i+1; // [HGM] suppress printing in ICS window
2358 sprintf(tmp, _("your opponent kibitzes: %s"), parse);
2359 SendToPlayer(tmp, strlen(tmp));
2362 started = STARTED_NONE;
2364 /* Don't match patterns against characters in comment */
2369 if (started == STARTED_CHATTER) {
2370 if (buf[i] != '\n') {
2371 /* Don't match patterns against characters in chatter */
2375 started = STARTED_NONE;
2378 /* Kludge to deal with rcmd protocol */
2379 if (firstTime && looking_at(buf, &i, "\001*")) {
2380 DisplayFatalError(&buf[1], 0, 1);
2386 if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2389 if (appData.debugMode)
2390 fprintf(debugFP, "ics_type %d\n", ics_type);
2393 if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
2394 ics_type = ICS_FICS;
2396 if (appData.debugMode)
2397 fprintf(debugFP, "ics_type %d\n", ics_type);
2400 if (!loggedOn && looking_at(buf, &i, "chess.net")) {
2401 ics_type = ICS_CHESSNET;
2403 if (appData.debugMode)
2404 fprintf(debugFP, "ics_type %d\n", ics_type);
2409 (looking_at(buf, &i, "\"*\" is *a registered name") ||
2410 looking_at(buf, &i, "Logging you in as \"*\"") ||
2411 looking_at(buf, &i, "will be \"*\""))) {
2412 strcpy(ics_handle, star_match[0]);
2416 if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
2418 snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
2419 DisplayIcsInteractionTitle(buf);
2420 have_set_title = TRUE;
2423 /* skip finger notes */
2424 if (started == STARTED_NONE &&
2425 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
2426 (buf[i] == '1' && buf[i+1] == '0')) &&
2427 buf[i+2] == ':' && buf[i+3] == ' ') {
2428 started = STARTED_CHATTER;
2433 /* skip formula vars */
2434 if (started == STARTED_NONE &&
2435 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
2436 started = STARTED_CHATTER;
2442 // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
2443 if (appData.autoKibitz && started == STARTED_NONE &&
2444 !appData.icsEngineAnalyze && // [HGM] [DM] ICS analyze
2445 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
2446 if(looking_at(buf, &i, "* kibitzes: ") &&
2447 (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
2448 StrStr(star_match[0], gameInfo.black) == star_match[0] )) { // kibitz of self or opponent
2449 suppressKibitz = TRUE;
2450 if((StrStr(star_match[0], gameInfo.white) == star_match[0]
2451 && (gameMode == IcsPlayingWhite)) ||
2452 (StrStr(star_match[0], gameInfo.black) == star_match[0]
2453 && (gameMode == IcsPlayingBlack)) ) // opponent kibitz
2454 started = STARTED_CHATTER; // own kibitz we simply discard
2456 started = STARTED_COMMENT; // make sure it will be collected in parse[]
2457 parse_pos = 0; parse[0] = NULLCHAR;
2458 savingComment = TRUE;
2459 suppressKibitz = gameMode != IcsObserving ? 2 :
2460 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
2464 if(looking_at(buf, &i, "kibitzed to *\n") && atoi(star_match[0])) {
2465 // suppress the acknowledgements of our own autoKibitz
2466 SendToPlayer(star_match[0], strlen(star_match[0]));
2467 looking_at(buf, &i, "*% "); // eat prompt
2470 } // [HGM] kibitz: end of patch
2472 //if(appData.debugMode) fprintf(debugFP, "hunt for tell, buf = %s\n", buf+i);
2474 // [HGM] chat: intercept tells by users for which we have an open chat window
2476 if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
2477 looking_at(buf, &i, "* whispers:") ||
2478 looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
2479 looking_at(buf, &i, "*(*)(*):") && sscanf(star_match[2], "%d", &channel) == 1 )) {
2481 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
2482 chattingPartner = -1;
2484 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
2485 for(p=0; p<MAX_CHAT; p++) {
2486 if(channel == atoi(chatPartner[p])) {
2487 talker[0] = '['; strcat(talker, "]");
2488 chattingPartner = p; break;
2491 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
2492 for(p=0; p<MAX_CHAT; p++) {
2493 if(!strcmp("WHISPER", chatPartner[p])) {
2494 talker[0] = '['; strcat(talker, "]");
2495 chattingPartner = p; break;
2498 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
2499 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
2501 chattingPartner = p; break;
2503 if(chattingPartner<0) i = oldi; else {
2504 started = STARTED_COMMENT;
2505 parse_pos = 0; parse[0] = NULLCHAR;
2506 savingComment = TRUE;
2507 suppressKibitz = TRUE;
2509 } // [HGM] chat: end of patch
2511 if (appData.zippyTalk || appData.zippyPlay) {
2512 /* [DM] Backup address for color zippy lines */
2516 if (loggedOn == TRUE)
2517 if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
2518 (appData.zippyPlay && ZippyMatch(buf, &backup)));
2520 if (ZippyControl(buf, &i) ||
2521 ZippyConverse(buf, &i) ||
2522 (appData.zippyPlay && ZippyMatch(buf, &i))) {
2524 if (!appData.colorize) continue;
2528 } // [DM] 'else { ' deleted
2530 /* Regular tells and says */
2531 (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
2532 looking_at(buf, &i, "* (your partner) tells you: ") ||
2533 looking_at(buf, &i, "* says: ") ||
2534 /* Don't color "message" or "messages" output */
2535 (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
2536 looking_at(buf, &i, "*. * at *:*: ") ||
2537 looking_at(buf, &i, "--* (*:*): ") ||
2538 /* Message notifications (same color as tells) */
2539 looking_at(buf, &i, "* has left a message ") ||
2540 looking_at(buf, &i, "* just sent you a message:\n") ||
2541 /* Whispers and kibitzes */
2542 (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
2543 looking_at(buf, &i, "* kibitzes: ") ||
2545 (tkind = 3, looking_at(buf, &i, "*(*: "))) {
2547 if (tkind == 1 && strchr(star_match[0], ':')) {
2548 /* Avoid "tells you:" spoofs in channels */
2551 if (star_match[0][0] == NULLCHAR ||
2552 strchr(star_match[0], ' ') ||
2553 (tkind == 3 && strchr(star_match[1], ' '))) {
2554 /* Reject bogus matches */
2557 if (appData.colorize) {
2558 if (oldi > next_out) {
2559 SendToPlayer(&buf[next_out], oldi - next_out);
2564 Colorize(ColorTell, FALSE);
2565 curColor = ColorTell;
2568 Colorize(ColorKibitz, FALSE);
2569 curColor = ColorKibitz;
2572 p = strrchr(star_match[1], '(');
2579 Colorize(ColorChannel1, FALSE);
2580 curColor = ColorChannel1;
2582 Colorize(ColorChannel, FALSE);
2583 curColor = ColorChannel;
2587 curColor = ColorNormal;
2591 if (started == STARTED_NONE && appData.autoComment &&
2592 (gameMode == IcsObserving ||
2593 gameMode == IcsPlayingWhite ||
2594 gameMode == IcsPlayingBlack)) {
2595 parse_pos = i - oldi;
2596 memcpy(parse, &buf[oldi], parse_pos);
2597 parse[parse_pos] = NULLCHAR;
2598 started = STARTED_COMMENT;
2599 savingComment = TRUE;
2601 started = STARTED_CHATTER;
2602 savingComment = FALSE;
2609 if (looking_at(buf, &i, "* s-shouts: ") ||
2610 looking_at(buf, &i, "* c-shouts: ")) {
2611 if (appData.colorize) {
2612 if (oldi > next_out) {
2613 SendToPlayer(&buf[next_out], oldi - next_out);
2616 Colorize(ColorSShout, FALSE);
2617 curColor = ColorSShout;
2620 started = STARTED_CHATTER;
2624 if (looking_at(buf, &i, "--->")) {
2629 if (looking_at(buf, &i, "* shouts: ") ||
2630 looking_at(buf, &i, "--> ")) {
2631 if (appData.colorize) {
2632 if (oldi > next_out) {
2633 SendToPlayer(&buf[next_out], oldi - next_out);
2636 Colorize(ColorShout, FALSE);
2637 curColor = ColorShout;
2640 started = STARTED_CHATTER;
2644 if (looking_at( buf, &i, "Challenge:")) {
2645 if (appData.colorize) {
2646 if (oldi > next_out) {
2647 SendToPlayer(&buf[next_out], oldi - next_out);
2650 Colorize(ColorChallenge, FALSE);
2651 curColor = ColorChallenge;
2657 if (looking_at(buf, &i, "* offers you") ||
2658 looking_at(buf, &i, "* offers to be") ||
2659 looking_at(buf, &i, "* would like to") ||
2660 looking_at(buf, &i, "* requests to") ||
2661 looking_at(buf, &i, "Your opponent offers") ||
2662 looking_at(buf, &i, "Your opponent requests")) {
2664 if (appData.colorize) {
2665 if (oldi > next_out) {
2666 SendToPlayer(&buf[next_out], oldi - next_out);
2669 Colorize(ColorRequest, FALSE);
2670 curColor = ColorRequest;
2675 if (looking_at(buf, &i, "* (*) seeking")) {
2676 if (appData.colorize) {
2677 if (oldi > next_out) {
2678 SendToPlayer(&buf[next_out], oldi - next_out);
2681 Colorize(ColorSeek, FALSE);
2682 curColor = ColorSeek;
2687 if (looking_at(buf, &i, "\\ ")) {
2688 if (prevColor != ColorNormal) {
2689 if (oldi > next_out) {
2690 SendToPlayer(&buf[next_out], oldi - next_out);
2693 Colorize(prevColor, TRUE);
2694 curColor = prevColor;
2696 if (savingComment) {
2697 parse_pos = i - oldi;
2698 memcpy(parse, &buf[oldi], parse_pos);
2699 parse[parse_pos] = NULLCHAR;
2700 started = STARTED_COMMENT;
2702 started = STARTED_CHATTER;
2707 if (looking_at(buf, &i, "Black Strength :") ||
2708 looking_at(buf, &i, "<<< style 10 board >>>") ||
2709 looking_at(buf, &i, "<10>") ||
2710 looking_at(buf, &i, "#@#")) {
2711 /* Wrong board style */
2713 SendToICS(ics_prefix);
2714 SendToICS("set style 12\n");
2715 SendToICS(ics_prefix);
2716 SendToICS("refresh\n");
2720 if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
2722 have_sent_ICS_logon = 1;
2726 if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
2727 (looking_at(buf, &i, "\n<12> ") ||
2728 looking_at(buf, &i, "<12> "))) {
2730 if (oldi > next_out) {
2731 SendToPlayer(&buf[next_out], oldi - next_out);
2734 started = STARTED_BOARD;
2739 if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
2740 looking_at(buf, &i, "<b1> ")) {
2741 if (oldi > next_out) {
2742 SendToPlayer(&buf[next_out], oldi - next_out);
2745 started = STARTED_HOLDINGS;
2750 if (looking_at(buf, &i, "* *vs. * *--- *")) {
2752 /* Header for a move list -- first line */
2754 switch (ics_getting_history) {
2758 case BeginningOfGame:
2759 /* User typed "moves" or "oldmoves" while we
2760 were idle. Pretend we asked for these
2761 moves and soak them up so user can step
2762 through them and/or save them.
2765 gameMode = IcsObserving;
2768 ics_getting_history = H_GOT_UNREQ_HEADER;
2770 case EditGame: /*?*/
2771 case EditPosition: /*?*/
2772 /* Should above feature work in these modes too? */
2773 /* For now it doesn't */
2774 ics_getting_history = H_GOT_UNWANTED_HEADER;
2777 ics_getting_history = H_GOT_UNWANTED_HEADER;
2782 /* Is this the right one? */
2783 if (gameInfo.white && gameInfo.black &&
2784 strcmp(gameInfo.white, star_match[0]) == 0 &&
2785 strcmp(gameInfo.black, star_match[2]) == 0) {
2787 ics_getting_history = H_GOT_REQ_HEADER;
2790 case H_GOT_REQ_HEADER:
2791 case H_GOT_UNREQ_HEADER:
2792 case H_GOT_UNWANTED_HEADER:
2793 case H_GETTING_MOVES:
2794 /* Should not happen */
2795 DisplayError(_("Error gathering move list: two headers"), 0);
2796 ics_getting_history = H_FALSE;
2800 /* Save player ratings into gameInfo if needed */
2801 if ((ics_getting_history == H_GOT_REQ_HEADER ||
2802 ics_getting_history == H_GOT_UNREQ_HEADER) &&
2803 (gameInfo.whiteRating == -1 ||
2804 gameInfo.blackRating == -1)) {
2806 gameInfo.whiteRating = string_to_rating(star_match[1]);
2807 gameInfo.blackRating = string_to_rating(star_match[3]);
2808 if (appData.debugMode)
2809 fprintf(debugFP, _("Ratings from header: W %d, B %d\n"),
2810 gameInfo.whiteRating, gameInfo.blackRating);
2815 if (looking_at(buf, &i,
2816 "* * match, initial time: * minute*, increment: * second")) {
2817 /* Header for a move list -- second line */
2818 /* Initial board will follow if this is a wild game */
2819 if (gameInfo.event != NULL) free(gameInfo.event);
2820 sprintf(str, "ICS %s %s match", star_match[0], star_match[1]);
2821 gameInfo.event = StrSave(str);
2822 /* [HGM] we switched variant. Translate boards if needed. */
2823 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
2827 if (looking_at(buf, &i, "Move ")) {
2828 /* Beginning of a move list */
2829 switch (ics_getting_history) {
2831 /* Normally should not happen */
2832 /* Maybe user hit reset while we were parsing */
2835 /* Happens if we are ignoring a move list that is not
2836 * the one we just requested. Common if the user
2837 * tries to observe two games without turning off
2840 case H_GETTING_MOVES:
2841 /* Should not happen */
2842 DisplayError(_("Error gathering move list: nested"), 0);
2843 ics_getting_history = H_FALSE;
2845 case H_GOT_REQ_HEADER:
2846 ics_getting_history = H_GETTING_MOVES;
2847 started = STARTED_MOVES;
2849 if (oldi > next_out) {
2850 SendToPlayer(&buf[next_out], oldi - next_out);
2853 case H_GOT_UNREQ_HEADER:
2854 ics_getting_history = H_GETTING_MOVES;
2855 started = STARTED_MOVES_NOHIDE;
2858 case H_GOT_UNWANTED_HEADER:
2859 ics_getting_history = H_FALSE;
2865 if (looking_at(buf, &i, "% ") ||
2866 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
2867 && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
2868 if(suppressKibitz) next_out = i;
2869 savingComment = FALSE;
2873 case STARTED_MOVES_NOHIDE:
2874 memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
2875 parse[parse_pos + i - oldi] = NULLCHAR;
2876 ParseGameHistory(parse);
2878 if (appData.zippyPlay && first.initDone) {
2879 FeedMovesToProgram(&first, forwardMostMove);
2880 if (gameMode == IcsPlayingWhite) {
2881 if (WhiteOnMove(forwardMostMove)) {
2882 if (first.sendTime) {
2883 if (first.useColors) {
2884 SendToProgram("black\n", &first);
2886 SendTimeRemaining(&first, TRUE);
2888 if (first.useColors) {
2889 SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
2891 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
2892 first.maybeThinking = TRUE;
2894 if (first.usePlayother) {
2895 if (first.sendTime) {
2896 SendTimeRemaining(&first, TRUE);
2898 SendToProgram("playother\n", &first);
2904 } else if (gameMode == IcsPlayingBlack) {
2905 if (!WhiteOnMove(forwardMostMove)) {
2906 if (first.sendTime) {
2907 if (first.useColors) {
2908 SendToProgram("white\n", &first);
2910 SendTimeRemaining(&first, FALSE);
2912 if (first.useColors) {
2913 SendToProgram("black\n", &first);
2915 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
2916 first.maybeThinking = TRUE;
2918 if (first.usePlayother) {
2919 if (first.sendTime) {
2920 SendTimeRemaining(&first, FALSE);
2922 SendToProgram("playother\n", &first);
2931 if (gameMode == IcsObserving && ics_gamenum == -1) {
2932 /* Moves came from oldmoves or moves command
2933 while we weren't doing anything else.
2935 currentMove = forwardMostMove;
2936 ClearHighlights();/*!!could figure this out*/
2937 flipView = appData.flipView;
2938 DrawPosition(TRUE, boards[currentMove]);
2939 DisplayBothClocks();
2940 sprintf(str, "%s vs. %s",
2941 gameInfo.white, gameInfo.black);
2945 /* Moves were history of an active game */
2946 if (gameInfo.resultDetails != NULL) {
2947 free(gameInfo.resultDetails);
2948 gameInfo.resultDetails = NULL;
2951 HistorySet(parseList, backwardMostMove,
2952 forwardMostMove, currentMove-1);
2953 DisplayMove(currentMove - 1);
2954 if (started == STARTED_MOVES) next_out = i;
2955 started = STARTED_NONE;
2956 ics_getting_history = H_FALSE;
2959 case STARTED_OBSERVE:
2960 started = STARTED_NONE;
2961 SendToICS(ics_prefix);
2962 SendToICS("refresh\n");
2968 if(bookHit) { // [HGM] book: simulate book reply
2969 static char bookMove[MSG_SIZ]; // a bit generous?
2971 programStats.nodes = programStats.depth = programStats.time =
2972 programStats.score = programStats.got_only_move = 0;
2973 sprintf(programStats.movelist, "%s (xbook)", bookHit);
2975 strcpy(bookMove, "move ");
2976 strcat(bookMove, bookHit);
2977 HandleMachineMove(bookMove, &first);
2982 if ((started == STARTED_MOVES || started == STARTED_BOARD ||
2983 started == STARTED_HOLDINGS ||
2984 started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
2985 /* Accumulate characters in move list or board */
2986 parse[parse_pos++] = buf[i];
2989 /* Start of game messages. Mostly we detect start of game
2990 when the first board image arrives. On some versions
2991 of the ICS, though, we need to do a "refresh" after starting
2992 to observe in order to get the current board right away. */
2993 if (looking_at(buf, &i, "Adding game * to observation list")) {
2994 started = STARTED_OBSERVE;
2998 /* Handle auto-observe */
2999 if (appData.autoObserve &&
3000 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3001 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3003 /* Choose the player that was highlighted, if any. */
3004 if (star_match[0][0] == '\033' ||
3005 star_match[1][0] != '\033') {
3006 player = star_match[0];
3008 player = star_match[2];
3010 sprintf(str, "%sobserve %s\n",
3011 ics_prefix, StripHighlightAndTitle(player));
3014 /* Save ratings from notify string */
3015 strcpy(player1Name, star_match[0]);
3016 player1Rating = string_to_rating(star_match[1]);
3017 strcpy(player2Name, star_match[2]);
3018 player2Rating = string_to_rating(star_match[3]);
3020 if (appData.debugMode)
3022 "Ratings from 'Game notification:' %s %d, %s %d\n",
3023 player1Name, player1Rating,
3024 player2Name, player2Rating);
3029 /* Deal with automatic examine mode after a game,
3030 and with IcsObserving -> IcsExamining transition */
3031 if (looking_at(buf, &i, "Entering examine mode for game *") ||
3032 looking_at(buf, &i, "has made you an examiner of game *")) {
3034 int gamenum = atoi(star_match[0]);
3035 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3036 gamenum == ics_gamenum) {
3037 /* We were already playing or observing this game;
3038 no need to refetch history */
3039 gameMode = IcsExamining;
3041 pauseExamForwardMostMove = forwardMostMove;
3042 } else if (currentMove < forwardMostMove) {
3043 ForwardInner(forwardMostMove);
3046 /* I don't think this case really can happen */
3047 SendToICS(ics_prefix);
3048 SendToICS("refresh\n");
3053 /* Error messages */
3054 // if (ics_user_moved) {
3055 if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3056 if (looking_at(buf, &i, "Illegal move") ||
3057 looking_at(buf, &i, "Not a legal move") ||
3058 looking_at(buf, &i, "Your king is in check") ||
3059 looking_at(buf, &i, "It isn't your turn") ||
3060 looking_at(buf, &i, "It is not your move")) {
3062 if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3063 currentMove = --forwardMostMove;
3064 DisplayMove(currentMove - 1); /* before DMError */
3065 DrawPosition(FALSE, boards[currentMove]);
3067 DisplayBothClocks();
3069 DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3075 if (looking_at(buf, &i, "still have time") ||
3076 looking_at(buf, &i, "not out of time") ||
3077 looking_at(buf, &i, "either player is out of time") ||
3078 looking_at(buf, &i, "has timeseal; checking")) {
3079 /* We must have called his flag a little too soon */
3080 whiteFlag = blackFlag = FALSE;
3084 if (looking_at(buf, &i, "added * seconds to") ||
3085 looking_at(buf, &i, "seconds were added to")) {
3086 /* Update the clocks */
3087 SendToICS(ics_prefix);
3088 SendToICS("refresh\n");
3092 if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3093 ics_clock_paused = TRUE;
3098 if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3099 ics_clock_paused = FALSE;
3104 /* Grab player ratings from the Creating: message.
3105 Note we have to check for the special case when
3106 the ICS inserts things like [white] or [black]. */
3107 if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3108 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3110 0 player 1 name (not necessarily white)
3112 2 empty, white, or black (IGNORED)
3113 3 player 2 name (not necessarily black)
3116 The names/ratings are sorted out when the game
3117 actually starts (below).
3119 strcpy(player1Name, StripHighlightAndTitle(star_match[0]));
3120 player1Rating = string_to_rating(star_match[1]);
3121 strcpy(player2Name, StripHighlightAndTitle(star_match[3]));
3122 player2Rating = string_to_rating(star_match[4]);
3124 if (appData.debugMode)
3126 "Ratings from 'Creating:' %s %d, %s %d\n",
3127 player1Name, player1Rating,
3128 player2Name, player2Rating);
3133 /* Improved generic start/end-of-game messages */
3134 if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3135 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3136 /* If tkind == 0: */
3137 /* star_match[0] is the game number */
3138 /* [1] is the white player's name */
3139 /* [2] is the black player's name */
3140 /* For end-of-game: */
3141 /* [3] is the reason for the game end */
3142 /* [4] is a PGN end game-token, preceded by " " */
3143 /* For start-of-game: */
3144 /* [3] begins with "Creating" or "Continuing" */
3145 /* [4] is " *" or empty (don't care). */
3146 int gamenum = atoi(star_match[0]);
3147 char *whitename, *blackname, *why, *endtoken;
3148 ChessMove endtype = (ChessMove) 0;
3151 whitename = star_match[1];
3152 blackname = star_match[2];
3153 why = star_match[3];
3154 endtoken = star_match[4];
3156 whitename = star_match[1];
3157 blackname = star_match[3];
3158 why = star_match[5];
3159 endtoken = star_match[6];
3162 /* Game start messages */
3163 if (strncmp(why, "Creating ", 9) == 0 ||
3164 strncmp(why, "Continuing ", 11) == 0) {
3165 gs_gamenum = gamenum;
3166 strcpy(gs_kind, strchr(why, ' ') + 1);
3167 VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
3169 if (appData.zippyPlay) {
3170 ZippyGameStart(whitename, blackname);
3176 /* Game end messages */
3177 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3178 ics_gamenum != gamenum) {
3181 while (endtoken[0] == ' ') endtoken++;
3182 switch (endtoken[0]) {
3185 endtype = GameUnfinished;
3188 endtype = BlackWins;
3191 if (endtoken[1] == '/')
3192 endtype = GameIsDrawn;
3194 endtype = WhiteWins;
3197 GameEnds(endtype, why, GE_ICS);
3199 if (appData.zippyPlay && first.initDone) {
3200 ZippyGameEnd(endtype, why);
3201 if (first.pr == NULL) {
3202 /* Start the next process early so that we'll
3203 be ready for the next challenge */
3204 StartChessProgram(&first);
3206 /* Send "new" early, in case this command takes
3207 a long time to finish, so that we'll be ready
3208 for the next challenge. */
3209 gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3216 if (looking_at(buf, &i, "Removing game * from observation") ||
3217 looking_at(buf, &i, "no longer observing game *") ||
3218 looking_at(buf, &i, "Game * (*) has no examiners")) {
3219 if (gameMode == IcsObserving &&
3220 atoi(star_match[0]) == ics_gamenum)
3222 /* icsEngineAnalyze */
3223 if (appData.icsEngineAnalyze) {
3230 ics_user_moved = FALSE;
3235 if (looking_at(buf, &i, "no longer examining game *")) {
3236 if (gameMode == IcsExamining &&
3237 atoi(star_match[0]) == ics_gamenum)
3241 ics_user_moved = FALSE;
3246 /* Advance leftover_start past any newlines we find,
3247 so only partial lines can get reparsed */
3248 if (looking_at(buf, &i, "\n")) {
3249 prevColor = curColor;
3250 if (curColor != ColorNormal) {
3251 if (oldi > next_out) {
3252 SendToPlayer(&buf[next_out], oldi - next_out);
3255 Colorize(ColorNormal, FALSE);
3256 curColor = ColorNormal;
3258 if (started == STARTED_BOARD) {
3259 started = STARTED_NONE;
3260 parse[parse_pos] = NULLCHAR;
3261 ParseBoard12(parse);
3264 /* Send premove here */
3265 if (appData.premove) {
3267 if (currentMove == 0 &&
3268 gameMode == IcsPlayingWhite &&
3269 appData.premoveWhite) {
3270 sprintf(str, "%s\n", appData.premoveWhiteText);
3271 if (appData.debugMode)
3272 fprintf(debugFP, "Sending premove:\n");
3274 } else if (currentMove == 1 &&
3275 gameMode == IcsPlayingBlack &&
3276 appData.premoveBlack) {
3277 sprintf(str, "%s\n", appData.premoveBlackText);
3278 if (appData.debugMode)
3279 fprintf(debugFP, "Sending premove:\n");
3281 } else if (gotPremove) {
3283 ClearPremoveHighlights();
3284 if (appData.debugMode)
3285 fprintf(debugFP, "Sending premove:\n");
3286 UserMoveEvent(premoveFromX, premoveFromY,
3287 premoveToX, premoveToY,
3292 /* Usually suppress following prompt */
3293 if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
3294 while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
3295 if (looking_at(buf, &i, "*% ")) {
3296 savingComment = FALSE;
3301 } else if (started == STARTED_HOLDINGS) {
3303 char new_piece[MSG_SIZ];
3304 started = STARTED_NONE;
3305 parse[parse_pos] = NULLCHAR;
3306 if (appData.debugMode)
3307 fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
3308 parse, currentMove);
3309 if (sscanf(parse, " game %d", &gamenum) == 1 &&
3310 gamenum == ics_gamenum) {
3311 if (gameInfo.variant == VariantNormal) {
3312 /* [HGM] We seem to switch variant during a game!
3313 * Presumably no holdings were displayed, so we have
3314 * to move the position two files to the right to
3315 * create room for them!
3317 VariantClass newVariant;
3318 switch(gameInfo.boardWidth) { // base guess on board width
3319 case 9: newVariant = VariantShogi; break;
3320 case 10: newVariant = VariantGreat; break;
3321 default: newVariant = VariantCrazyhouse; break;
3323 VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3324 /* Get a move list just to see the header, which
3325 will tell us whether this is really bug or zh */
3326 if (ics_getting_history == H_FALSE) {
3327 ics_getting_history = H_REQUESTED;
3328 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3332 new_piece[0] = NULLCHAR;
3333 sscanf(parse, "game %d white [%s black [%s <- %s",
3334 &gamenum, white_holding, black_holding,
3336 white_holding[strlen(white_holding)-1] = NULLCHAR;
3337 black_holding[strlen(black_holding)-1] = NULLCHAR;
3338 /* [HGM] copy holdings to board holdings area */
3339 CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
3340 CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
3341 boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
3343 if (appData.zippyPlay && first.initDone) {
3344 ZippyHoldings(white_holding, black_holding,
3348 if (tinyLayout || smallLayout) {
3349 char wh[16], bh[16];
3350 PackHolding(wh, white_holding);
3351 PackHolding(bh, black_holding);
3352 sprintf(str, "[%s-%s] %s-%s", wh, bh,
3353 gameInfo.white, gameInfo.black);
3355 sprintf(str, "%s [%s] vs. %s [%s]",
3356 gameInfo.white, white_holding,
3357 gameInfo.black, black_holding);
3360 DrawPosition(FALSE, boards[currentMove]);
3363 /* Suppress following prompt */
3364 if (looking_at(buf, &i, "*% ")) {
3365 if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
3366 savingComment = FALSE;
3374 i++; /* skip unparsed character and loop back */
3377 if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
3378 // started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
3379 // SendToPlayer(&buf[next_out], i - next_out);
3380 started != STARTED_HOLDINGS && leftover_start > next_out) {
3381 SendToPlayer(&buf[next_out], leftover_start - next_out);
3385 leftover_len = buf_len - leftover_start;
3386 /* if buffer ends with something we couldn't parse,
3387 reparse it after appending the next read */
3389 } else if (count == 0) {
3390 RemoveInputSource(isr);
3391 DisplayFatalError(_("Connection closed by ICS"), 0, 0);
3393 DisplayFatalError(_("Error reading from ICS"), error, 1);
3398 /* Board style 12 looks like this:
3400 <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
3402 * The "<12> " is stripped before it gets to this routine. The two
3403 * trailing 0's (flip state and clock ticking) are later addition, and
3404 * some chess servers may not have them, or may have only the first.
3405 * Additional trailing fields may be added in the future.
3408 #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"
3410 #define RELATION_OBSERVING_PLAYED 0
3411 #define RELATION_OBSERVING_STATIC -2 /* examined, oldmoves, or smoves */
3412 #define RELATION_PLAYING_MYMOVE 1
3413 #define RELATION_PLAYING_NOTMYMOVE -1
3414 #define RELATION_EXAMINING 2
3415 #define RELATION_ISOLATED_BOARD -3
3416 #define RELATION_STARTING_POSITION -4 /* FICS only */
3419 ParseBoard12(string)
3422 GameMode newGameMode;
3423 int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
3424 int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
3425 int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
3426 char to_play, board_chars[200];
3427 char move_str[500], str[500], elapsed_time[500];
3428 char black[32], white[32];
3430 int prevMove = currentMove;
3433 int fromX, fromY, toX, toY;
3435 int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
3436 char *bookHit = NULL; // [HGM] book
3437 Boolean weird = FALSE, reqFlag = FALSE;
3439 fromX = fromY = toX = toY = -1;
3443 if (appData.debugMode)
3444 fprintf(debugFP, _("Parsing board: %s\n"), string);
3446 move_str[0] = NULLCHAR;
3447 elapsed_time[0] = NULLCHAR;
3448 { /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
3450 while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
3451 if(string[i] == ' ') { ranks++; files = 0; }
3453 if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
3456 for(j = 0; j <i; j++) board_chars[j] = string[j];
3457 board_chars[i] = '\0';
3460 n = sscanf(string, PATTERN, &to_play, &double_push,
3461 &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
3462 &gamenum, white, black, &relation, &basetime, &increment,
3463 &white_stren, &black_stren, &white_time, &black_time,
3464 &moveNum, str, elapsed_time, move_str, &ics_flip,
3468 snprintf(str, sizeof(str), _("Failed to parse board string:\n\"%s\""), string);
3469 DisplayError(str, 0);
3473 /* Convert the move number to internal form */
3474 moveNum = (moveNum - 1) * 2;
3475 if (to_play == 'B') moveNum++;
3476 if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
3477 DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
3483 case RELATION_OBSERVING_PLAYED:
3484 case RELATION_OBSERVING_STATIC:
3485 if (gamenum == -1) {
3486 /* Old ICC buglet */
3487 relation = RELATION_OBSERVING_STATIC;
3489 newGameMode = IcsObserving;
3491 case RELATION_PLAYING_MYMOVE:
3492 case RELATION_PLAYING_NOTMYMOVE:
3494 ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
3495 IcsPlayingWhite : IcsPlayingBlack;
3497 case RELATION_EXAMINING:
3498 newGameMode = IcsExamining;
3500 case RELATION_ISOLATED_BOARD:
3502 /* Just display this board. If user was doing something else,
3503 we will forget about it until the next board comes. */
3504 newGameMode = IcsIdle;
3506 case RELATION_STARTING_POSITION:
3507 newGameMode = gameMode;
3511 /* Modify behavior for initial board display on move listing
3514 switch (ics_getting_history) {
3518 case H_GOT_REQ_HEADER:
3519 case H_GOT_UNREQ_HEADER:
3520 /* This is the initial position of the current game */
3521 gamenum = ics_gamenum;
3522 moveNum = 0; /* old ICS bug workaround */
3523 if (to_play == 'B') {
3524 startedFromSetupPosition = TRUE;
3525 blackPlaysFirst = TRUE;
3527 if (forwardMostMove == 0) forwardMostMove = 1;
3528 if (backwardMostMove == 0) backwardMostMove = 1;
3529 if (currentMove == 0) currentMove = 1;
3531 newGameMode = gameMode;
3532 relation = RELATION_STARTING_POSITION; /* ICC needs this */
3534 case H_GOT_UNWANTED_HEADER:
3535 /* This is an initial board that we don't want */
3537 case H_GETTING_MOVES:
3538 /* Should not happen */
3539 DisplayError(_("Error gathering move list: extra board"), 0);
3540 ics_getting_history = H_FALSE;
3544 if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
3545 weird && (int)gameInfo.variant <= (int)VariantShogi) {
3546 /* [HGM] We seem to have switched variant unexpectedly
3547 * Try to guess new variant from board size
3549 VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
3550 if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
3551 if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
3552 if(ranks == 8 && files == 12) newVariant = VariantCourier; else
3553 if(ranks == 9 && files == 9) newVariant = VariantShogi; else
3554 if(!weird) newVariant = VariantNormal;
3555 VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3556 /* Get a move list just to see the header, which
3557 will tell us whether this is really bug or zh */
3558 if (ics_getting_history == H_FALSE) {
3559 ics_getting_history = H_REQUESTED; reqFlag = TRUE;
3560 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3565 /* Take action if this is the first board of a new game, or of a
3566 different game than is currently being displayed. */
3567 if (gamenum != ics_gamenum || newGameMode != gameMode ||
3568 relation == RELATION_ISOLATED_BOARD) {
3570 /* Forget the old game and get the history (if any) of the new one */
3571 if (gameMode != BeginningOfGame) {
3575 if (appData.autoRaiseBoard) BoardToTop();
3577 if (gamenum == -1) {
3578 newGameMode = IcsIdle;
3579 } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
3580 appData.getMoveList && !reqFlag) {
3581 /* Need to get game history */
3582 ics_getting_history = H_REQUESTED;
3583 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3587 /* Initially flip the board to have black on the bottom if playing
3588 black or if the ICS flip flag is set, but let the user change
3589 it with the Flip View button. */
3590 flipView = appData.autoFlipView ?
3591 (newGameMode == IcsPlayingBlack) || ics_flip :
3594 /* Done with values from previous mode; copy in new ones */
3595 gameMode = newGameMode;
3597 ics_gamenum = gamenum;
3598 if (gamenum == gs_gamenum) {
3599 int klen = strlen(gs_kind);
3600 if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
3601 sprintf(str, "ICS %s", gs_kind);
3602 gameInfo.event = StrSave(str);
3604 gameInfo.event = StrSave("ICS game");
3606 gameInfo.site = StrSave(appData.icsHost);
3607 gameInfo.date = PGNDate();
3608 gameInfo.round = StrSave("-");
3609 gameInfo.white = StrSave(white);
3610 gameInfo.black = StrSave(black);
3611 timeControl = basetime * 60 * 1000;
3613 timeIncrement = increment * 1000;
3614 movesPerSession = 0;
3615 gameInfo.timeControl = TimeControlTagValue();
3616 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
3617 if (appData.debugMode) {
3618 fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
3619 fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
3620 setbuf(debugFP, NULL);
3623 gameInfo.outOfBook = NULL;
3625 /* Do we have the ratings? */
3626 if (strcmp(player1Name, white) == 0 &&
3627 strcmp(player2Name, black) == 0) {
3628 if (appData.debugMode)
3629 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3630 player1Rating, player2Rating);
3631 gameInfo.whiteRating = player1Rating;
3632 gameInfo.blackRating = player2Rating;
3633 } else if (strcmp(player2Name, white) == 0 &&
3634 strcmp(player1Name, black) == 0) {
3635 if (appData.debugMode)
3636 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3637 player2Rating, player1Rating);
3638 gameInfo.whiteRating = player2Rating;
3639 gameInfo.blackRating = player1Rating;
3641 player1Name[0] = player2Name[0] = NULLCHAR;
3643 /* Silence shouts if requested */
3644 if (appData.quietPlay &&
3645 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
3646 SendToICS(ics_prefix);
3647 SendToICS("set shout 0\n");
3651 /* Deal with midgame name changes */
3653 if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
3654 if (gameInfo.white) free(gameInfo.white);
3655 gameInfo.white = StrSave(white);
3657 if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
3658 if (gameInfo.black) free(gameInfo.black);
3659 gameInfo.black = StrSave(black);
3663 /* Throw away game result if anything actually changes in examine mode */
3664 if (gameMode == IcsExamining && !newGame) {
3665 gameInfo.result = GameUnfinished;
3666 if (gameInfo.resultDetails != NULL) {
3667 free(gameInfo.resultDetails);
3668 gameInfo.resultDetails = NULL;
3672 /* In pausing && IcsExamining mode, we ignore boards coming
3673 in if they are in a different variation than we are. */
3674 if (pauseExamInvalid) return;
3675 if (pausing && gameMode == IcsExamining) {
3676 if (moveNum <= pauseExamForwardMostMove) {
3677 pauseExamInvalid = TRUE;
3678 forwardMostMove = pauseExamForwardMostMove;
3683 if (appData.debugMode) {
3684 fprintf(debugFP, "load %dx%d board\n", files, ranks);
3686 /* Parse the board */
3687 for (k = 0; k < ranks; k++) {
3688 for (j = 0; j < files; j++)
3689 board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
3690 if(gameInfo.holdingsWidth > 1) {
3691 board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
3692 board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
3695 CopyBoard(boards[moveNum], board);
3696 boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
3698 startedFromSetupPosition =
3699 !CompareBoards(board, initialPosition);
3700 if(startedFromSetupPosition)
3701 initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
3704 /* [HGM] Set castling rights. Take the outermost Rooks,
3705 to make it also work for FRC opening positions. Note that board12
3706 is really defective for later FRC positions, as it has no way to
3707 indicate which Rook can castle if they are on the same side of King.
3708 For the initial position we grant rights to the outermost Rooks,
3709 and remember thos rights, and we then copy them on positions
3710 later in an FRC game. This means WB might not recognize castlings with
3711 Rooks that have moved back to their original position as illegal,
3712 but in ICS mode that is not its job anyway.
3714 if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
3715 { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
3717 for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
3718 if(board[0][i] == WhiteRook) j = i;
3719 initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
3720 for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
3721 if(board[0][i] == WhiteRook) j = i;
3722 initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
3723 for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
3724 if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
3725 initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
3726 for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
3727 if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
3728 initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
3730 if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
3731 for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
3732 if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
3733 for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
3734 if(board[BOARD_HEIGHT-1][k] == bKing)
3735 initialRights[5] = boards[moveNum][CASTLING][5] = k;
3736 if(gameInfo.variant == VariantTwoKings) {
3737 // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
3738 if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
3739 if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
3742 r = boards[moveNum][CASTLING][0] = initialRights[0];
3743 if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
3744 r = boards[moveNum][CASTLING][1] = initialRights[1];
3745 if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
3746 r = boards[moveNum][CASTLING][3] = initialRights[3];
3747 if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
3748 r = boards[moveNum][CASTLING][4] = initialRights[4];
3749 if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
3750 /* wildcastle kludge: always assume King has rights */
3751 r = boards[moveNum][CASTLING][2] = initialRights[2];
3752 r = boards[moveNum][CASTLING][5] = initialRights[5];
3754 /* [HGM] e.p. rights. Assume that ICS sends file number here? */
3755 boards[moveNum][EP_STATUS] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
3758 if (ics_getting_history == H_GOT_REQ_HEADER ||
3759 ics_getting_history == H_GOT_UNREQ_HEADER) {
3760 /* This was an initial position from a move list, not
3761 the current position */
3765 /* Update currentMove and known move number limits */
3766 newMove = newGame || moveNum > forwardMostMove;
3769 forwardMostMove = backwardMostMove = currentMove = moveNum;
3770 if (gameMode == IcsExamining && moveNum == 0) {
3771 /* Workaround for ICS limitation: we are not told the wild
3772 type when starting to examine a game. But if we ask for
3773 the move list, the move list header will tell us */
3774 ics_getting_history = H_REQUESTED;
3775 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3778 } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
3779 || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
3781 /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
3782 /* [HGM] applied this also to an engine that is silently watching */
3783 if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
3784 (gameMode == IcsObserving || gameMode == IcsExamining) &&
3785 gameInfo.variant == currentlyInitializedVariant) {
3786 takeback = forwardMostMove - moveNum;
3787 for (i = 0; i < takeback; i++) {
3788 if (appData.debugMode) fprintf(debugFP, "take back move\n");
3789 SendToProgram("undo\n", &first);
3794 forwardMostMove = moveNum;
3795 if (!pausing || currentMove > forwardMostMove)
3796 currentMove = forwardMostMove;
3798 /* New part of history that is not contiguous with old part */
3799 if (pausing && gameMode == IcsExamining) {
3800 pauseExamInvalid = TRUE;
3801 forwardMostMove = pauseExamForwardMostMove;
3804 if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
3806 if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
3807 // [HGM] when we will receive the move list we now request, it will be
3808 // fed to the engine from the first move on. So if the engine is not
3809 // in the initial position now, bring it there.
3810 InitChessProgram(&first, 0);
3813 ics_getting_history = H_REQUESTED;
3814 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3817 forwardMostMove = backwardMostMove = currentMove = moveNum;
3820 /* Update the clocks */
3821 if (strchr(elapsed_time, '.')) {
3823 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
3824 timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
3826 /* Time is in seconds */
3827 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
3828 timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
3833 if (appData.zippyPlay && newGame &&
3834 gameMode != IcsObserving && gameMode != IcsIdle &&
3835 gameMode != IcsExamining)
3836 ZippyFirstBoard(moveNum, basetime, increment);
3839 /* Put the move on the move list, first converting
3840 to canonical algebraic form. */
3842 if (appData.debugMode) {
3843 if (appData.debugMode) { int f = forwardMostMove;
3844 fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
3845 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
3846 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
3848 fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
3849 fprintf(debugFP, "moveNum = %d\n", moveNum);
3850 fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
3851 setbuf(debugFP, NULL);
3853 if (moveNum <= backwardMostMove) {
3854 /* We don't know what the board looked like before
3856 strcpy(parseList[moveNum - 1], move_str);
3857 strcat(parseList[moveNum - 1], " ");
3858 strcat(parseList[moveNum - 1], elapsed_time);
3859 moveList[moveNum - 1][0] = NULLCHAR;
3860 } else if (strcmp(move_str, "none") == 0) {
3861 // [HGM] long SAN: swapped order; test for 'none' before parsing move
3862 /* Again, we don't know what the board looked like;
3863 this is really the start of the game. */
3864 parseList[moveNum - 1][0] = NULLCHAR;
3865 moveList[moveNum - 1][0] = NULLCHAR;
3866 backwardMostMove = moveNum;
3867 startedFromSetupPosition = TRUE;
3868 fromX = fromY = toX = toY = -1;
3870 // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
3871 // So we parse the long-algebraic move string in stead of the SAN move
3872 int valid; char buf[MSG_SIZ], *prom;
3874 // str looks something like "Q/a1-a2"; kill the slash
3876 sprintf(buf, "%c%s", str[0], str+2);
3877 else strcpy(buf, str); // might be castling
3878 if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
3879 strcat(buf, prom); // long move lacks promo specification!
3880 if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
3881 if(appData.debugMode)
3882 fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
3883 strcpy(move_str, buf);
3885 valid = ParseOneMove(move_str, moveNum - 1, &moveType,
3886 &fromX, &fromY, &toX, &toY, &promoChar)
3887 || ParseOneMove(buf, moveNum - 1, &moveType,
3888 &fromX, &fromY, &toX, &toY, &promoChar);
3889 // end of long SAN patch
3891 (void) CoordsToAlgebraic(boards[moveNum - 1],
3892 PosFlags(moveNum - 1),
3893 fromY, fromX, toY, toX, promoChar,
3894 parseList[moveNum-1]);
3895 switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
3901 if(gameInfo.variant != VariantShogi)
3902 strcat(parseList[moveNum - 1], "+");
3905 case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
3906 strcat(parseList[moveNum - 1], "#");
3909 strcat(parseList[moveNum - 1], " ");
3910 strcat(parseList[moveNum - 1], elapsed_time);
3911 /* currentMoveString is set as a side-effect of ParseOneMove */
3912 strcpy(moveList[moveNum - 1], currentMoveString);
3913 strcat(moveList[moveNum - 1], "\n");
3915 /* Move from ICS was illegal!? Punt. */
3916 if (appData.debugMode) {
3917 fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
3918 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
3920 strcpy(parseList[moveNum - 1], move_str);
3921 strcat(parseList[moveNum - 1], " ");
3922 strcat(parseList[moveNum - 1], elapsed_time);
3923 moveList[moveNum - 1][0] = NULLCHAR;
3924 fromX = fromY = toX = toY = -1;
3927 if (appData.debugMode) {
3928 fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
3929 setbuf(debugFP, NULL);
3933 /* Send move to chess program (BEFORE animating it). */
3934 if (appData.zippyPlay && !newGame && newMove &&
3935 (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
3937 if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
3938 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
3939 if (moveList[moveNum - 1][0] == NULLCHAR) {
3940 sprintf(str, _("Couldn't parse move \"%s\" from ICS"),
3942 DisplayError(str, 0);
3944 if (first.sendTime) {
3945 SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
3947 bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
3948 if (firstMove && !bookHit) {
3950 if (first.useColors) {
3951 SendToProgram(gameMode == IcsPlayingWhite ?
3953 "black\ngo\n", &first);
3955 SendToProgram("go\n", &first);
3957 first.maybeThinking = TRUE;
3960 } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
3961 if (moveList[moveNum - 1][0] == NULLCHAR) {
3962 sprintf(str, _("Couldn't parse move \"%s\" from ICS"), move_str);
3963 DisplayError(str, 0);
3965 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
3966 SendMoveToProgram(moveNum - 1, &first);
3973 if (moveNum > 0 && !gotPremove && !appData.noGUI) {
3974 /* If move comes from a remote source, animate it. If it
3975 isn't remote, it will have already been animated. */
3976 if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
3977 AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
3979 if (!pausing && appData.highlightLastMove) {
3980 SetHighlights(fromX, fromY, toX, toY);
3984 /* Start the clocks */
3985 whiteFlag = blackFlag = FALSE;
3986 appData.clockMode = !(basetime == 0 && increment == 0);
3988 ics_clock_paused = TRUE;
3990 } else if (ticking == 1) {
3991 ics_clock_paused = FALSE;
3993 if (gameMode == IcsIdle ||
3994 relation == RELATION_OBSERVING_STATIC ||
3995 relation == RELATION_EXAMINING ||
3997 DisplayBothClocks();
4001 /* Display opponents and material strengths */
4002 if (gameInfo.variant != VariantBughouse &&
4003 gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4004 if (tinyLayout || smallLayout) {
4005 if(gameInfo.variant == VariantNormal)
4006 sprintf(str, "%s(%d) %s(%d) {%d %d}",
4007 gameInfo.white, white_stren, gameInfo.black, black_stren,
4008 basetime, increment);
4010 sprintf(str, "%s(%d) %s(%d) {%d %d w%d}",
4011 gameInfo.white, white_stren, gameInfo.black, black_stren,
4012 basetime, increment, (int) gameInfo.variant);
4014 if(gameInfo.variant == VariantNormal)
4015 sprintf(str, "%s (%d) vs. %s (%d) {%d %d}",
4016 gameInfo.white, white_stren, gameInfo.black, black_stren,
4017 basetime, increment);
4019 sprintf(str, "%s (%d) vs. %s (%d) {%d %d %s}",
4020 gameInfo.white, white_stren, gameInfo.black, black_stren,
4021 basetime, increment, VariantName(gameInfo.variant));
4024 if (appData.debugMode) {
4025 fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
4030 /* Display the board */
4031 if (!pausing && !appData.noGUI) {
4033 if (appData.premove)
4035 ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
4036 ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
4037 ClearPremoveHighlights();
4039 DrawPosition(FALSE, boards[currentMove]);
4040 DisplayMove(moveNum - 1);
4041 if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4042 !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4043 (gameMode == IcsPlayingBlack) && (WhiteOnMove(moveNum)) ) ) {
4044 if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4048 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4050 if(bookHit) { // [HGM] book: simulate book reply
4051 static char bookMove[MSG_SIZ]; // a bit generous?
4053 programStats.nodes = programStats.depth = programStats.time =
4054 programStats.score = programStats.got_only_move = 0;
4055 sprintf(programStats.movelist, "%s (xbook)", bookHit);
4057 strcpy(bookMove, "move ");
4058 strcat(bookMove, bookHit);
4059 HandleMachineMove(bookMove, &first);
4068 if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4069 ics_getting_history = H_REQUESTED;
4070 sprintf(buf, "%smoves %d\n", ics_prefix, ics_gamenum);
4076 AnalysisPeriodicEvent(force)
4079 if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4080 && !force) || !appData.periodicUpdates)
4083 /* Send . command to Crafty to collect stats */
4084 SendToProgram(".\n", &first);
4086 /* Don't send another until we get a response (this makes
4087 us stop sending to old Crafty's which don't understand
4088 the "." command (sending illegal cmds resets node count & time,
4089 which looks bad)) */
4090 programStats.ok_to_send = 0;
4093 void ics_update_width(new_width)
4096 ics_printf("set width %d\n", new_width);
4100 SendMoveToProgram(moveNum, cps)
4102 ChessProgramState *cps;
4106 if (cps->useUsermove) {
4107 SendToProgram("usermove ", cps);
4111 if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4112 int len = space - parseList[moveNum];
4113 memcpy(buf, parseList[moveNum], len);
4115 buf[len] = NULLCHAR;
4117 sprintf(buf, "%s\n", parseList[moveNum]);
4119 SendToProgram(buf, cps);
4121 if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4122 AlphaRank(moveList[moveNum], 4);
4123 SendToProgram(moveList[moveNum], cps);
4124 AlphaRank(moveList[moveNum], 4); // and back
4126 /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
4127 * the engine. It would be nice to have a better way to identify castle
4129 if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
4130 && cps->useOOCastle) {
4131 int fromX = moveList[moveNum][0] - AAA;
4132 int fromY = moveList[moveNum][1] - ONE;
4133 int toX = moveList[moveNum][2] - AAA;
4134 int toY = moveList[moveNum][3] - ONE;
4135 if((boards[moveNum][fromY][fromX] == WhiteKing
4136 && boards[moveNum][toY][toX] == WhiteRook)
4137 || (boards[moveNum][fromY][fromX] == BlackKing
4138 && boards[moveNum][toY][toX] == BlackRook)) {
4139 if(toX > fromX) SendToProgram("O-O\n", cps);
4140 else SendToProgram("O-O-O\n", cps);
4142 else SendToProgram(moveList[moveNum], cps);
4144 else SendToProgram(moveList[moveNum], cps);
4145 /* End of additions by Tord */
4148 /* [HGM] setting up the opening has brought engine in force mode! */
4149 /* Send 'go' if we are in a mode where machine should play. */
4150 if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4151 (gameMode == TwoMachinesPlay ||
4153 gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite ||
4155 gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4156 SendToProgram("go\n", cps);
4157 if (appData.debugMode) {
4158 fprintf(debugFP, "(extra)\n");
4161 setboardSpoiledMachineBlack = 0;
4165 SendMoveToICS(moveType, fromX, fromY, toX, toY)
4167 int fromX, fromY, toX, toY;
4169 char user_move[MSG_SIZ];
4173 sprintf(user_move, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4174 (int)moveType, fromX, fromY, toX, toY);
4175 DisplayError(user_move + strlen("say "), 0);
4177 case WhiteKingSideCastle:
4178 case BlackKingSideCastle:
4179 case WhiteQueenSideCastleWild:
4180 case BlackQueenSideCastleWild:
4182 case WhiteHSideCastleFR:
4183 case BlackHSideCastleFR:
4185 sprintf(user_move, "o-o\n");
4187 case WhiteQueenSideCastle:
4188 case BlackQueenSideCastle:
4189 case WhiteKingSideCastleWild:
4190 case BlackKingSideCastleWild:
4192 case WhiteASideCastleFR:
4193 case BlackASideCastleFR:
4195 sprintf(user_move, "o-o-o\n");
4197 case WhitePromotionQueen:
4198 case BlackPromotionQueen:
4199 case WhitePromotionRook:
4200 case BlackPromotionRook:
4201 case WhitePromotionBishop:
4202 case BlackPromotionBishop:
4203 case WhitePromotionKnight:
4204 case BlackPromotionKnight:
4205 case WhitePromotionKing:
4206 case BlackPromotionKing:
4207 case WhitePromotionChancellor:
4208 case BlackPromotionChancellor:
4209 case WhitePromotionArchbishop:
4210 case BlackPromotionArchbishop:
4211 if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
4212 sprintf(user_move, "%c%c%c%c=%c\n",
4213 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4214 PieceToChar(WhiteFerz));
4215 else if(gameInfo.variant == VariantGreat)
4216 sprintf(user_move, "%c%c%c%c=%c\n",
4217 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4218 PieceToChar(WhiteMan));
4220 sprintf(user_move, "%c%c%c%c=%c\n",
4221 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4222 PieceToChar(PromoPiece(moveType)));
4226 sprintf(user_move, "%c@%c%c\n",
4227 ToUpper(PieceToChar((ChessSquare) fromX)),
4228 AAA + toX, ONE + toY);
4231 case WhiteCapturesEnPassant:
4232 case BlackCapturesEnPassant:
4233 case IllegalMove: /* could be a variant we don't quite understand */
4234 sprintf(user_move, "%c%c%c%c\n",
4235 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4238 SendToICS(user_move);
4239 if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
4240 ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
4244 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
4249 if (rf == DROP_RANK) {
4250 sprintf(move, "%c@%c%c\n",
4251 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
4253 if (promoChar == 'x' || promoChar == NULLCHAR) {
4254 sprintf(move, "%c%c%c%c\n",
4255 AAA + ff, ONE + rf, AAA + ft, ONE + rt);
4257 sprintf(move, "%c%c%c%c%c\n",
4258 AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
4264 ProcessICSInitScript(f)
4269 while (fgets(buf, MSG_SIZ, f)) {
4270 SendToICSDelayed(buf,(long)appData.msLoginDelay);
4277 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
4279 AlphaRank(char *move, int n)
4281 // char *p = move, c; int x, y;
4283 if (appData.debugMode) {
4284 fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
4288 move[2]>='0' && move[2]<='9' &&
4289 move[3]>='a' && move[3]<='x' ) {
4291 move[2] = BOARD_RGHT -1 - (move[2]-'1') + AAA;
4292 move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4294 if(move[0]>='0' && move[0]<='9' &&
4295 move[1]>='a' && move[1]<='x' &&
4296 move[2]>='0' && move[2]<='9' &&
4297 move[3]>='a' && move[3]<='x' ) {
4298 /* input move, Shogi -> normal */
4299 move[0] = BOARD_RGHT -1 - (move[0]-'1') + AAA;
4300 move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
4301 move[2] = BOARD_RGHT -1 - (move[2]-'1') + AAA;
4302 move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4305 move[3]>='0' && move[3]<='9' &&
4306 move[2]>='a' && move[2]<='x' ) {
4308 move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4309 move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4312 move[0]>='a' && move[0]<='x' &&
4313 move[3]>='0' && move[3]<='9' &&
4314 move[2]>='a' && move[2]<='x' ) {
4315 /* output move, normal -> Shogi */
4316 move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
4317 move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
4318 move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4319 move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4320 if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
4322 if (appData.debugMode) {
4323 fprintf(debugFP, " out = '%s'\n", move);
4327 /* Parser for moves from gnuchess, ICS, or user typein box */
4329 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
4332 ChessMove *moveType;
4333 int *fromX, *fromY, *toX, *toY;
4336 if (appData.debugMode) {
4337 fprintf(debugFP, "move to parse: %s\n", move);
4339 *moveType = yylexstr(moveNum, move);
4341 switch (*moveType) {
4342 case WhitePromotionChancellor:
4343 case BlackPromotionChancellor:
4344 case WhitePromotionArchbishop:
4345 case BlackPromotionArchbishop:
4346 case WhitePromotionQueen:
4347 case BlackPromotionQueen:
4348 case WhitePromotionRook:
4349 case BlackPromotionRook:
4350 case WhitePromotionBishop:
4351 case BlackPromotionBishop:
4352 case WhitePromotionKnight:
4353 case BlackPromotionKnight:
4354 case WhitePromotionKing:
4355 case BlackPromotionKing:
4357 case WhiteCapturesEnPassant:
4358 case BlackCapturesEnPassant:
4359 case WhiteKingSideCastle:
4360 case WhiteQueenSideCastle:
4361 case BlackKingSideCastle:
4362 case BlackQueenSideCastle:
4363 case WhiteKingSideCastleWild:
4364 case WhiteQueenSideCastleWild:
4365 case BlackKingSideCastleWild:
4366 case BlackQueenSideCastleWild:
4367 /* Code added by Tord: */
4368 case WhiteHSideCastleFR:
4369 case WhiteASideCastleFR:
4370 case BlackHSideCastleFR:
4371 case BlackASideCastleFR:
4372 /* End of code added by Tord */
4373 case IllegalMove: /* bug or odd chess variant */
4374 *fromX = currentMoveString[0] - AAA;
4375 *fromY = currentMoveString[1] - ONE;
4376 *toX = currentMoveString[2] - AAA;
4377 *toY = currentMoveString[3] - ONE;
4378 *promoChar = currentMoveString[4];
4379 if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
4380 *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
4381 if (appData.debugMode) {
4382 fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
4384 *fromX = *fromY = *toX = *toY = 0;
4387 if (appData.testLegality) {
4388 return (*moveType != IllegalMove);
4390 return !(*fromX == *toX && *fromY == *toY) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
4391 WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
4396 *fromX = *moveType == WhiteDrop ?
4397 (int) CharToPiece(ToUpper(currentMoveString[0])) :
4398 (int) CharToPiece(ToLower(currentMoveString[0]));
4400 *toX = currentMoveString[2] - AAA;
4401 *toY = currentMoveString[3] - ONE;
4402 *promoChar = NULLCHAR;
4406 case ImpossibleMove:
4407 case (ChessMove) 0: /* end of file */
4416 if (appData.debugMode) {
4417 fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
4420 *fromX = *fromY = *toX = *toY = 0;
4421 *promoChar = NULLCHAR;
4429 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
4430 int fromX, fromY, toX, toY; char promoChar;
4435 endPV = forwardMostMove;
4437 while(*pv == ' ') pv++;
4438 if(*pv == '(') pv++; // first (ponder) move can be in parentheses
4439 valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
4440 if(appData.debugMode){
4441 fprintf(debugFP,"parsePV: %d %c%c%c%c '%s'\n", valid, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, pv);
4443 if(!valid && nr == 0 &&
4444 ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
4445 nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
4447 while(*pv && *pv++ != ' '); // skip what we parsed; assume space separators
4448 if(moveType == Comment) { valid++; continue; } // allow comments in PV
4450 if(endPV+1 > framePtr) break; // no space, truncate
4453 CopyBoard(boards[endPV], boards[endPV-1]);
4454 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
4455 moveList[endPV-1][0] = fromX + AAA;
4456 moveList[endPV-1][1] = fromY + ONE;
4457 moveList[endPV-1][2] = toX + AAA;
4458 moveList[endPV-1][3] = toY + ONE;
4459 parseList[endPV-1][0] = NULLCHAR;
4461 currentMove = endPV;
4462 if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
4463 SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
4464 moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
4465 DrawPosition(TRUE, boards[currentMove]);
4468 static int lastX, lastY;
4471 LoadMultiPV(int x, int y, char *buf, int index, int *start, int *end)
4475 if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
4476 lastX = x; lastY = y;
4477 while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
4479 while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
4481 while(buf[index] && buf[index] != '\n') index++;
4483 ParsePV(buf+startPV);
4484 *start = startPV; *end = index-1;
4489 LoadPV(int x, int y)
4490 { // called on right mouse click to load PV
4491 int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
4492 lastX = x; lastY = y;
4493 ParsePV(lastPV[which]); // load the PV of the thinking engine in the boards array.
4500 if(endPV < 0) return;
4502 currentMove = forwardMostMove;
4503 ClearPremoveHighlights();
4504 DrawPosition(TRUE, boards[currentMove]);
4508 MovePV(int x, int y, int h)
4509 { // step through PV based on mouse coordinates (called on mouse move)
4510 int margin = h>>3, step = 0;
4512 if(endPV < 0) return;
4513 // we must somehow check if right button is still down (might be released off board!)
4514 if(y < margin && (abs(x - lastX) > 6 || abs(y - lastY) > 6)) step = 1; else
4515 if(y > h - margin && (abs(x - lastX) > 6 || abs(y - lastY) > 6)) step = -1; else
4516 if( y > lastY + 6 ) step = -1; else if(y < lastY - 6) step = 1;
4518 lastX = x; lastY = y;
4519 if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
4520 currentMove += step;
4521 if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
4522 SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
4523 moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
4524 DrawPosition(FALSE, boards[currentMove]);
4528 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
4529 // All positions will have equal probability, but the current method will not provide a unique
4530 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
4536 int piecesLeft[(int)BlackPawn];
4537 int seed, nrOfShuffles;
4539 void GetPositionNumber()
4540 { // sets global variable seed
4543 seed = appData.defaultFrcPosition;
4544 if(seed < 0) { // randomize based on time for negative FRC position numbers
4545 for(i=0; i<50; i++) seed += random();
4546 seed = random() ^ random() >> 8 ^ random() << 8;
4547 if(seed<0) seed = -seed;
4551 int put(Board board, int pieceType, int rank, int n, int shade)
4552 // put the piece on the (n-1)-th empty squares of the given shade
4556 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
4557 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
4558 board[rank][i] = (ChessSquare) pieceType;
4559 squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
4561 piecesLeft[pieceType]--;
4569 void AddOnePiece(Board board, int pieceType, int rank, int shade)
4570 // calculate where the next piece goes, (any empty square), and put it there
4574 i = seed % squaresLeft[shade];
4575 nrOfShuffles *= squaresLeft[shade];
4576 seed /= squaresLeft[shade];
4577 put(board, pieceType, rank, i, shade);
4580 void AddTwoPieces(Board board, int pieceType, int rank)
4581 // calculate where the next 2 identical pieces go, (any empty square), and put it there
4583 int i, n=squaresLeft[ANY], j=n-1, k;
4585 k = n*(n-1)/2; // nr of possibilities, not counting permutations
4586 i = seed % k; // pick one
4589 while(i >= j) i -= j--;
4590 j = n - 1 - j; i += j;
4591 put(board, pieceType, rank, j, ANY);
4592 put(board, pieceType, rank, i, ANY);
4595 void SetUpShuffle(Board board, int number)
4599 GetPositionNumber(); nrOfShuffles = 1;
4601 squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
4602 squaresLeft[ANY] = BOARD_RGHT - BOARD_LEFT;
4603 squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
4605 for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
4607 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
4608 p = (int) board[0][i];
4609 if(p < (int) BlackPawn) piecesLeft[p] ++;
4610 board[0][i] = EmptySquare;
4613 if(PosFlags(0) & F_ALL_CASTLE_OK) {
4614 // shuffles restricted to allow normal castling put KRR first
4615 if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
4616 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
4617 else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
4618 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
4619 if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
4620 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
4621 if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
4622 put(board, WhiteRook, 0, 0, ANY);
4623 // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
4626 if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
4627 // only for even boards make effort to put pairs of colorbound pieces on opposite colors
4628 for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
4629 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
4630 while(piecesLeft[p] >= 2) {
4631 AddOnePiece(board, p, 0, LITE);
4632 AddOnePiece(board, p, 0, DARK);
4634 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
4637 for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
4638 // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
4639 // but we leave King and Rooks for last, to possibly obey FRC restriction
4640 if(p == (int)WhiteRook) continue;
4641 while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
4642 if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY); // add the odd piece
4645 // now everything is placed, except perhaps King (Unicorn) and Rooks
4647 if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
4648 // Last King gets castling rights
4649 while(piecesLeft[(int)WhiteUnicorn]) {
4650 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
4651 initialRights[2] = initialRights[5] = board[CASTLING][2] = board[CASTLING][5] = i;
4654 while(piecesLeft[(int)WhiteKing]) {
4655 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
4656 initialRights[2] = initialRights[5] = board[CASTLING][2] = board[CASTLING][5] = i;
4661 while(piecesLeft[(int)WhiteKing]) AddOnePiece(board, WhiteKing, 0, ANY);
4662 while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
4665 // Only Rooks can be left; simply place them all
4666 while(piecesLeft[(int)WhiteRook]) {
4667 i = put(board, WhiteRook, 0, 0, ANY);
4668 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
4671 initialRights[1] = initialRights[4] = board[CASTLING][1] = board[CASTLING][4] = i;
4673 initialRights[0] = initialRights[3] = board[CASTLING][0] = board[CASTLING][3] = i;
4676 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
4677 board[BOARD_HEIGHT-1][i] = (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
4680 if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
4683 int SetCharTable( char *table, const char * map )
4684 /* [HGM] moved here from winboard.c because of its general usefulness */
4685 /* Basically a safe strcpy that uses the last character as King */
4687 int result = FALSE; int NrPieces;
4689 if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
4690 && NrPieces >= 12 && !(NrPieces&1)) {
4691 int i; /* [HGM] Accept even length from 12 to 34 */
4693 for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
4694 for( i=0; i<NrPieces/2-1; i++ ) {
4696 table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
4698 table[(int) WhiteKing] = map[NrPieces/2-1];
4699 table[(int) BlackKing] = map[NrPieces-1];
4707 void Prelude(Board board)
4708 { // [HGM] superchess: random selection of exo-pieces
4709 int i, j, k; ChessSquare p;
4710 static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
4712 GetPositionNumber(); // use FRC position number
4714 if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
4715 SetCharTable(pieceToChar, appData.pieceToCharTable);
4716 for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
4717 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
4720 j = seed%4; seed /= 4;
4721 p = board[0][BOARD_LEFT+j]; board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
4722 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
4723 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
4724 j = seed%3 + (seed%3 >= j); seed /= 3;
4725 p = board[0][BOARD_LEFT+j]; board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
4726 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
4727 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
4728 j = seed%3; seed /= 3;
4729 p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
4730 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
4731 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
4732 j = seed%2 + (seed%2 >= j); seed /= 2;
4733 p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
4734 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
4735 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
4736 j = seed%4; seed /= 4; put(board, exoPieces[3], 0, j, ANY);
4737 j = seed%3; seed /= 3; put(board, exoPieces[2], 0, j, ANY);
4738 j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
4739 put(board, exoPieces[0], 0, 0, ANY);
4740 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
4744 InitPosition(redraw)
4747 ChessSquare (* pieces)[BOARD_FILES];
4748 int i, j, pawnRow, overrule,
4749 oldx = gameInfo.boardWidth,
4750 oldy = gameInfo.boardHeight,
4751 oldh = gameInfo.holdingsWidth,
4752 oldv = gameInfo.variant;
4754 if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
4756 /* [AS] Initialize pv info list [HGM] and game status */
4758 for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
4759 pvInfoList[i].depth = 0;
4760 boards[i][EP_STATUS] = EP_NONE;
4761 for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
4764 initialRulePlies = 0; /* 50-move counter start */
4766 castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
4767 castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
4771 /* [HGM] logic here is completely changed. In stead of full positions */
4772 /* the initialized data only consist of the two backranks. The switch */
4773 /* selects which one we will use, which is than copied to the Board */
4774 /* initialPosition, which for the rest is initialized by Pawns and */
4775 /* empty squares. This initial position is then copied to boards[0], */
4776 /* possibly after shuffling, so that it remains available. */
4778 gameInfo.holdingsWidth = 0; /* default board sizes */
4779 gameInfo.boardWidth = 8;
4780 gameInfo.boardHeight = 8;
4781 gameInfo.holdingsSize = 0;
4782 nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
4783 for(i=0; i<BOARD_FILES-2; i++)
4784 initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
4785 initialPosition[EP_STATUS] = EP_NONE;
4786 SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
4788 switch (gameInfo.variant) {
4789 case VariantFischeRandom:
4790 shuffleOpenings = TRUE;
4794 case VariantShatranj:
4795 pieces = ShatranjArray;
4796 nrCastlingRights = 0;
4797 SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
4800 pieces = makrukArray;
4801 nrCastlingRights = 0;
4802 startedFromSetupPosition = TRUE;
4803 SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
4805 case VariantTwoKings:
4806 pieces = twoKingsArray;
4808 case VariantCapaRandom:
4809 shuffleOpenings = TRUE;
4810 case VariantCapablanca:
4811 pieces = CapablancaArray;
4812 gameInfo.boardWidth = 10;
4813 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
4816 pieces = GothicArray;
4817 gameInfo.boardWidth = 10;
4818 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
4821 pieces = JanusArray;
4822 gameInfo.boardWidth = 10;
4823 SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
4824 nrCastlingRights = 6;
4825 initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
4826 initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
4827 initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
4828 initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
4829 initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
4830 initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
4833 pieces = FalconArray;
4834 gameInfo.boardWidth = 10;
4835 SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
4837 case VariantXiangqi:
4838 pieces = XiangqiArray;
4839 gameInfo.boardWidth = 9;
4840 gameInfo.boardHeight = 10;
4841 nrCastlingRights = 0;
4842 SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
4845 pieces = ShogiArray;
4846 gameInfo.boardWidth = 9;
4847 gameInfo.boardHeight = 9;
4848 gameInfo.holdingsSize = 7;
4849 nrCastlingRights = 0;
4850 SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
4852 case VariantCourier:
4853 pieces = CourierArray;
4854 gameInfo.boardWidth = 12;
4855 nrCastlingRights = 0;
4856 SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
4858 case VariantKnightmate:
4859 pieces = KnightmateArray;
4860 SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
4863 pieces = fairyArray;
4864 SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
4867 pieces = GreatArray;
4868 gameInfo.boardWidth = 10;
4869 SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
4870 gameInfo.holdingsSize = 8;
4874 SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
4875 gameInfo.holdingsSize = 8;
4876 startedFromSetupPosition = TRUE;
4878 case VariantCrazyhouse:
4879 case VariantBughouse:
4881 SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
4882 gameInfo.holdingsSize = 5;
4884 case VariantWildCastle:
4886 /* !!?shuffle with kings guaranteed to be on d or e file */
4887 shuffleOpenings = 1;
4889 case VariantNoCastle:
4891 nrCastlingRights = 0;
4892 /* !!?unconstrained back-rank shuffle */
4893 shuffleOpenings = 1;
4898 if(appData.NrFiles >= 0) {
4899 if(gameInfo.boardWidth != appData.NrFiles) overrule++;
4900 gameInfo.boardWidth = appData.NrFiles;
4902 if(appData.NrRanks >= 0) {
4903 gameInfo.boardHeight = appData.NrRanks;
4905 if(appData.holdingsSize >= 0) {
4906 i = appData.holdingsSize;
4907 if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
4908 gameInfo.holdingsSize = i;
4910 if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
4911 if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
4912 DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
4914 pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
4915 if(pawnRow < 1) pawnRow = 1;
4916 if(gameInfo.variant == VariantMakruk) pawnRow = 2;
4918 /* User pieceToChar list overrules defaults */
4919 if(appData.pieceToCharTable != NULL)
4920 SetCharTable(pieceToChar, appData.pieceToCharTable);
4922 for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
4924 if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
4925 s = (ChessSquare) 0; /* account holding counts in guard band */
4926 for( i=0; i<BOARD_HEIGHT; i++ )
4927 initialPosition[i][j] = s;
4929 if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
4930 initialPosition[0][j] = pieces[0][j-gameInfo.holdingsWidth];
4931 initialPosition[pawnRow][j] = WhitePawn;
4932 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = BlackPawn;
4933 if(gameInfo.variant == VariantXiangqi) {
4935 initialPosition[pawnRow][j] =
4936 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
4937 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
4938 initialPosition[2][j] = WhiteCannon;
4939 initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
4943 initialPosition[BOARD_HEIGHT-1][j] = pieces[1][j-gameInfo.holdingsWidth];
4945 if( (gameInfo.variant == VariantShogi) && !overrule ) {
4948 initialPosition[1][j] = WhiteBishop;
4949 initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
4951 initialPosition[1][j] = WhiteRook;
4952 initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
4955 if( nrCastlingRights == -1) {
4956 /* [HGM] Build normal castling rights (must be done after board sizing!) */
4957 /* This sets default castling rights from none to normal corners */
4958 /* Variants with other castling rights must set them themselves above */
4959 nrCastlingRights = 6;
4961 initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
4962 initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
4963 initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
4964 initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
4965 initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
4966 initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
4969 if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
4970 if(gameInfo.variant == VariantGreat) { // promotion commoners
4971 initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
4972 initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
4973 initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
4974 initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
4976 if (appData.debugMode) {
4977 fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
4979 if(shuffleOpenings) {
4980 SetUpShuffle(initialPosition, appData.defaultFrcPosition);
4981 startedFromSetupPosition = TRUE;
4983 if(startedFromPositionFile) {
4984 /* [HGM] loadPos: use PositionFile for every new game */
4985 CopyBoard(initialPosition, filePosition);
4986 for(i=0; i<nrCastlingRights; i++)
4987 initialRights[i] = filePosition[CASTLING][i];
4988 startedFromSetupPosition = TRUE;
4991 CopyBoard(boards[0], initialPosition);
4993 if(oldx != gameInfo.boardWidth ||
4994 oldy != gameInfo.boardHeight ||
4995 oldh != gameInfo.holdingsWidth
4997 || oldv == VariantGothic || // For licensing popups
4998 gameInfo.variant == VariantGothic
5001 || oldv == VariantFalcon ||
5002 gameInfo.variant == VariantFalcon
5005 InitDrawingSizes(-2 ,0);
5008 DrawPosition(TRUE, boards[currentMove]);
5012 SendBoard(cps, moveNum)
5013 ChessProgramState *cps;
5016 char message[MSG_SIZ];
5018 if (cps->useSetboard) {
5019 char* fen = PositionToFEN(moveNum, cps->fenOverride);
5020 sprintf(message, "setboard %s\n", fen);
5021 SendToProgram(message, cps);
5027 /* Kludge to set black to move, avoiding the troublesome and now
5028 * deprecated "black" command.
5030 if (!WhiteOnMove(moveNum)) SendToProgram("a2a3\n", cps);
5032 SendToProgram("edit\n", cps);
5033 SendToProgram("#\n", cps);
5034 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
5035 bp = &boards[moveNum][i][BOARD_LEFT];
5036 for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
5037 if ((int) *bp < (int) BlackPawn) {
5038 sprintf(message, "%c%c%c\n", PieceToChar(*bp),
5040 if(message[0] == '+' || message[0] == '~') {
5041 sprintf(message, "%c%c%c+\n",
5042 PieceToChar((ChessSquare)(DEMOTED *bp)),
5045 if(cps->alphaRank) { /* [HGM] shogi: translate coords */
5046 message[1] = BOARD_RGHT - 1 - j + '1';
5047 message[2] = BOARD_HEIGHT - 1 - i + 'a';
5049 SendToProgram(message, cps);
5054 SendToProgram("c\n", cps);
5055 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
5056 bp = &boards[moveNum][i][BOARD_LEFT];
5057 for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
5058 if (((int) *bp != (int) EmptySquare)
5059 && ((int) *bp >= (int) BlackPawn)) {
5060 sprintf(message, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
5062 if(message[0] == '+' || message[0] == '~') {
5063 sprintf(message, "%c%c%c+\n",
5064 PieceToChar((ChessSquare)(DEMOTED *bp)),
5067 if(cps->alphaRank) { /* [HGM] shogi: translate coords */
5068 message[1] = BOARD_RGHT - 1 - j + '1';
5069 message[2] = BOARD_HEIGHT - 1 - i + 'a';
5071 SendToProgram(message, cps);
5076 SendToProgram(".\n", cps);
5078 setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
5082 HasPromotionChoice(int fromX, int fromY, int toX, int toY, char *promoChoice)
5084 /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
5085 /* [HGM] add Shogi promotions */
5086 int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
5091 if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
5092 if(toX < BOARD_LEFT || toX >= BOARD_RGHT) return FALSE; // move into holdings
5094 if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
5095 !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
5098 piece = boards[currentMove][fromY][fromX];
5099 if(gameInfo.variant == VariantShogi) {
5100 promotionZoneSize = 3;
5101 highestPromotingPiece = (int)WhiteFerz;
5102 } else if(gameInfo.variant == VariantMakruk) {
5103 promotionZoneSize = 3;
5106 // next weed out all moves that do not touch the promotion zone at all
5107 if((int)piece >= BlackPawn) {
5108 if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
5110 highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
5112 if( toY < BOARD_HEIGHT - promotionZoneSize &&
5113 fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
5116 if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
5118 // weed out mandatory Shogi promotions
5119 if(gameInfo.variant == VariantShogi) {
5120 if(piece >= BlackPawn) {
5121 if(toY == 0 && piece == BlackPawn ||
5122 toY == 0 && piece == BlackQueen ||
5123 toY <= 1 && piece == BlackKnight) {
5128 if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
5129 toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
5130 toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
5137 // weed out obviously illegal Pawn moves
5138 if(appData.testLegality && (piece == WhitePawn || piece == BlackPawn) ) {
5139 if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
5140 if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
5141 if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
5142 if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
5143 // note we are not allowed to test for valid (non-)capture, due to premove
5146 // we either have a choice what to promote to, or (in Shogi) whether to promote
5147 if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk) {
5148 *promoChoice = PieceToChar(BlackFerz); // no choice
5151 if(appData.alwaysPromoteToQueen) { // predetermined
5152 if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantLosers)
5153 *promoChoice = PieceToChar(BlackKing); // in Suicide Q is the last thing we want
5154 else *promoChoice = PieceToChar(BlackQueen);
5158 // suppress promotion popup on illegal moves that are not premoves
5159 premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
5160 gameMode == IcsPlayingBlack && WhiteOnMove(currentMove);
5161 if(appData.testLegality && !premove) {
5162 moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5163 fromY, fromX, toY, toX, NULLCHAR);
5164 if(moveType != WhitePromotionQueen && moveType != BlackPromotionQueen &&
5165 moveType != WhitePromotionKnight && moveType != BlackPromotionKnight)
5173 InPalace(row, column)
5175 { /* [HGM] for Xiangqi */
5176 if( (row < 3 || row > BOARD_HEIGHT-4) &&
5177 column < (BOARD_WIDTH + 4)/2 &&
5178 column > (BOARD_WIDTH - 5)/2 ) return TRUE;
5183 PieceForSquare (x, y)
5187 if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
5190 return boards[currentMove][y][x];
5194 OKToStartUserMove(x, y)
5197 ChessSquare from_piece;
5200 if (matchMode) return FALSE;
5201 if (gameMode == EditPosition) return TRUE;
5203 if (x >= 0 && y >= 0)
5204 from_piece = boards[currentMove][y][x];
5206 from_piece = EmptySquare;
5208 if (from_piece == EmptySquare) return FALSE;
5210 white_piece = (int)from_piece >= (int)WhitePawn &&
5211 (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
5214 case PlayFromGameFile:
5216 case TwoMachinesPlay:
5224 case MachinePlaysWhite:
5225 case IcsPlayingBlack:
5226 if (appData.zippyPlay) return FALSE;
5228 DisplayMoveError(_("You are playing Black"));
5233 case MachinePlaysBlack:
5234 case IcsPlayingWhite:
5235 if (appData.zippyPlay) return FALSE;
5237 DisplayMoveError(_("You are playing White"));
5243 if (!white_piece && WhiteOnMove(currentMove)) {
5244 DisplayMoveError(_("It is White's turn"));
5247 if (white_piece && !WhiteOnMove(currentMove)) {
5248 DisplayMoveError(_("It is Black's turn"));
5251 if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
5252 /* Editing correspondence game history */
5253 /* Could disallow this or prompt for confirmation */
5258 case BeginningOfGame:
5259 if (appData.icsActive) return FALSE;
5260 if (!appData.noChessProgram) {
5262 DisplayMoveError(_("You are playing White"));
5269 if (!white_piece && WhiteOnMove(currentMove)) {
5270 DisplayMoveError(_("It is White's turn"));
5273 if (white_piece && !WhiteOnMove(currentMove)) {
5274 DisplayMoveError(_("It is Black's turn"));
5283 if (currentMove != forwardMostMove && gameMode != AnalyzeMode
5284 && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
5285 && gameMode != AnalyzeFile && gameMode != Training) {
5286 DisplayMoveError(_("Displayed position is not current"));
5292 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
5293 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
5294 int lastLoadGameUseList = FALSE;
5295 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
5296 ChessMove lastLoadGameStart = (ChessMove) 0;
5299 UserMoveTest(fromX, fromY, toX, toY, promoChar, captureOwn)
5300 int fromX, fromY, toX, toY;
5305 ChessSquare pdown, pup;
5307 /* Check if the user is playing in turn. This is complicated because we
5308 let the user "pick up" a piece before it is his turn. So the piece he
5309 tried to pick up may have been captured by the time he puts it down!
5310 Therefore we use the color the user is supposed to be playing in this
5311 test, not the color of the piece that is currently on the starting
5312 square---except in EditGame mode, where the user is playing both
5313 sides; fortunately there the capture race can't happen. (It can
5314 now happen in IcsExamining mode, but that's just too bad. The user
5315 will get a somewhat confusing message in that case.)
5319 case PlayFromGameFile:
5321 case TwoMachinesPlay:
5325 /* We switched into a game mode where moves are not accepted,
5326 perhaps while the mouse button was down. */
5327 return ImpossibleMove;
5329 case MachinePlaysWhite:
5330 /* User is moving for Black */
5331 if (WhiteOnMove(currentMove)) {
5332 DisplayMoveError(_("It is White's turn"));
5333 return ImpossibleMove;
5337 case MachinePlaysBlack:
5338 /* User is moving for White */
5339 if (!WhiteOnMove(currentMove)) {
5340 DisplayMoveError(_("It is Black's turn"));
5341 return ImpossibleMove;
5347 case BeginningOfGame:
5350 if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
5351 (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
5352 /* User is moving for Black */
5353 if (WhiteOnMove(currentMove)) {
5354 DisplayMoveError(_("It is White's turn"));
5355 return ImpossibleMove;
5358 /* User is moving for White */
5359 if (!WhiteOnMove(currentMove)) {
5360 DisplayMoveError(_("It is Black's turn"));
5361 return ImpossibleMove;
5366 case IcsPlayingBlack:
5367 /* User is moving for Black */
5368 if (WhiteOnMove(currentMove)) {
5369 if (!appData.premove) {
5370 DisplayMoveError(_("It is White's turn"));
5371 } else if (toX >= 0 && toY >= 0) {
5374 premoveFromX = fromX;
5375 premoveFromY = fromY;
5376 premovePromoChar = promoChar;
5378 if (appData.debugMode)
5379 fprintf(debugFP, "Got premove: fromX %d,"
5380 "fromY %d, toX %d, toY %d\n",
5381 fromX, fromY, toX, toY);
5383 return ImpossibleMove;
5387 case IcsPlayingWhite:
5388 /* User is moving for White */
5389 if (!WhiteOnMove(currentMove)) {
5390 if (!appData.premove) {
5391 DisplayMoveError(_("It is Black's turn"));
5392 } else if (toX >= 0 && toY >= 0) {
5395 premoveFromX = fromX;
5396 premoveFromY = fromY;
5397 premovePromoChar = promoChar;
5399 if (appData.debugMode)
5400 fprintf(debugFP, "Got premove: fromX %d,"
5401 "fromY %d, toX %d, toY %d\n",
5402 fromX, fromY, toX, toY);
5404 return ImpossibleMove;
5412 /* EditPosition, empty square, or different color piece;
5413 click-click move is possible */
5414 if (toX == -2 || toY == -2) {
5415 boards[0][fromY][fromX] = EmptySquare;
5416 return AmbiguousMove;
5417 } else if (toX >= 0 && toY >= 0) {
5418 boards[0][toY][toX] = boards[0][fromY][fromX];
5419 if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
5420 if(boards[0][fromY][0] != EmptySquare) {
5421 if(boards[0][fromY][1]) boards[0][fromY][1]--;
5422 if(boards[0][fromY][1] == 0) boards[0][fromY][0] = EmptySquare;
5425 if(fromX == BOARD_RGHT+1) {
5426 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
5427 if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
5428 if(boards[0][fromY][BOARD_WIDTH-2] == 0) boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
5431 boards[0][fromY][fromX] = EmptySquare;
5432 return AmbiguousMove;
5434 return ImpossibleMove;
5437 if(toX < 0 || toY < 0) return ImpossibleMove;
5438 pdown = boards[currentMove][fromY][fromX];
5439 pup = boards[currentMove][toY][toX];
5441 /* [HGM] If move started in holdings, it means a drop */
5442 if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) {
5443 if( pup != EmptySquare ) return ImpossibleMove;
5444 if(appData.testLegality) {
5445 /* it would be more logical if LegalityTest() also figured out
5446 * which drops are legal. For now we forbid pawns on back rank.
5447 * Shogi is on its own here...
5449 if( (pdown == WhitePawn || pdown == BlackPawn) &&
5450 (toY == 0 || toY == BOARD_HEIGHT -1 ) )
5451 return(ImpossibleMove); /* no pawn drops on 1st/8th */
5453 return WhiteDrop; /* Not needed to specify white or black yet */
5456 userOfferedDraw = FALSE;
5458 /* [HGM] always test for legality, to get promotion info */
5459 moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5460 fromY, fromX, toY, toX, promoChar);
5461 /* [HGM] but possibly ignore an IllegalMove result */
5462 if (appData.testLegality) {
5463 if (moveType == IllegalMove || moveType == ImpossibleMove) {
5464 DisplayMoveError(_("Illegal move"));
5465 return ImpossibleMove;
5470 /* [HGM] <popupFix> in stead of calling FinishMove directly, this
5471 function is made into one that returns an OK move type if FinishMove
5472 should be called. This to give the calling driver routine the
5473 opportunity to finish the userMove input with a promotion popup,
5474 without bothering the user with this for invalid or illegal moves */
5476 /* FinishMove(moveType, fromX, fromY, toX, toY, promoChar); */
5479 /* Common tail of UserMoveEvent and DropMenuEvent */
5481 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
5483 int fromX, fromY, toX, toY;
5484 /*char*/int promoChar;
5488 if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) && promoChar != NULLCHAR) {
5489 // [HGM] superchess: suppress promotions to non-available piece
5490 int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
5491 if(WhiteOnMove(currentMove)) {
5492 if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
5494 if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
5498 /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
5499 move type in caller when we know the move is a legal promotion */
5500 if(moveType == NormalMove && promoChar)
5501 moveType = PromoCharToMoveType(WhiteOnMove(currentMove), promoChar);
5503 /* [HGM] convert drag-and-drop piece drops to standard form */
5504 if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ){
5505 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
5506 if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n",
5507 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
5508 // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
5509 if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
5510 fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
5511 while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++;
5515 /* [HGM] <popupFix> The following if has been moved here from
5516 UserMoveEvent(). Because it seemed to belong here (why not allow
5517 piece drops in training games?), and because it can only be
5518 performed after it is known to what we promote. */
5519 if (gameMode == Training) {
5520 /* compare the move played on the board to the next move in the
5521 * game. If they match, display the move and the opponent's response.
5522 * If they don't match, display an error message.
5526 CopyBoard(testBoard, boards[currentMove]);
5527 ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
5529 if (CompareBoards(testBoard, boards[currentMove+1])) {
5530 ForwardInner(currentMove+1);
5532 /* Autoplay the opponent's response.
5533 * if appData.animate was TRUE when Training mode was entered,
5534 * the response will be animated.
5536 saveAnimate = appData.animate;
5537 appData.animate = animateTraining;
5538 ForwardInner(currentMove+1);
5539 appData.animate = saveAnimate;
5541 /* check for the end of the game */
5542 if (currentMove >= forwardMostMove) {
5543 gameMode = PlayFromGameFile;
5545 SetTrainingModeOff();
5546 DisplayInformation(_("End of game"));
5549 DisplayError(_("Incorrect move"), 0);
5554 /* Ok, now we know that the move is good, so we can kill
5555 the previous line in Analysis Mode */
5556 if ((gameMode == AnalyzeMode || gameMode == EditGame)
5557 && currentMove < forwardMostMove) {
5558 PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
5561 /* If we need the chess program but it's dead, restart it */
5562 ResurrectChessProgram();
5564 /* A user move restarts a paused game*/
5568 thinkOutput[0] = NULLCHAR;
5570 MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
5572 if (gameMode == BeginningOfGame) {
5573 if (appData.noChessProgram) {
5574 gameMode = EditGame;
5578 gameMode = MachinePlaysBlack;
5581 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
5583 if (first.sendName) {
5584 sprintf(buf, "name %s\n", gameInfo.white);
5585 SendToProgram(buf, &first);
5592 /* Relay move to ICS or chess engine */
5593 if (appData.icsActive) {
5594 if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
5595 gameMode == IcsExamining) {
5596 SendMoveToICS(moveType, fromX, fromY, toX, toY);
5600 if (first.sendTime && (gameMode == BeginningOfGame ||
5601 gameMode == MachinePlaysWhite ||
5602 gameMode == MachinePlaysBlack)) {
5603 SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
5605 if (gameMode != EditGame && gameMode != PlayFromGameFile) {
5606 // [HGM] book: if program might be playing, let it use book
5607 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
5608 first.maybeThinking = TRUE;
5609 } else SendMoveToProgram(forwardMostMove-1, &first);
5610 if (currentMove == cmailOldMove + 1) {
5611 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
5615 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5619 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
5625 if (WhiteOnMove(currentMove)) {
5626 GameEnds(BlackWins, "Black mates", GE_PLAYER);
5628 GameEnds(WhiteWins, "White mates", GE_PLAYER);
5632 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
5637 case MachinePlaysBlack:
5638 case MachinePlaysWhite:
5639 /* disable certain menu options while machine is thinking */
5640 SetMachineThinkingEnables();
5647 if(bookHit) { // [HGM] book: simulate book reply
5648 static char bookMove[MSG_SIZ]; // a bit generous?
5650 programStats.nodes = programStats.depth = programStats.time =
5651 programStats.score = programStats.got_only_move = 0;
5652 sprintf(programStats.movelist, "%s (xbook)", bookHit);
5654 strcpy(bookMove, "move ");
5655 strcat(bookMove, bookHit);
5656 HandleMachineMove(bookMove, &first);
5662 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
5663 int fromX, fromY, toX, toY;
5666 /* [HGM] This routine was added to allow calling of its two logical
5667 parts from other modules in the old way. Before, UserMoveEvent()
5668 automatically called FinishMove() if the move was OK, and returned
5669 otherwise. I separated the two, in order to make it possible to
5670 slip a promotion popup in between. But that it always needs two
5671 calls, to the first part, (now called UserMoveTest() ), and to
5672 FinishMove if the first part succeeded. Calls that do not need
5673 to do anything in between, can call this routine the old way.
5675 ChessMove moveType = UserMoveTest(fromX, fromY, toX, toY, promoChar, FALSE);
5676 if(appData.debugMode) fprintf(debugFP, "moveType 4 = %d, promochar = %x\n", moveType, promoChar);
5677 if(moveType == AmbiguousMove)
5678 DrawPosition(FALSE, boards[currentMove]);
5679 else if(moveType != ImpossibleMove && moveType != Comment)
5680 FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
5684 Mark(board, flags, kind, rf, ff, rt, ft, closure)
5691 typedef char Markers[BOARD_RANKS][BOARD_FILES];
5692 Markers *m = (Markers *) closure;
5693 if(rf == fromY && ff == fromX)
5694 (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
5695 || kind == WhiteCapturesEnPassant
5696 || kind == BlackCapturesEnPassant);
5697 else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
5701 MarkTargetSquares(int clear)
5704 if(!appData.markers || !appData.highlightDragging ||
5705 !appData.testLegality || gameMode == EditPosition) return;
5707 for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) marker[y][x] = 0;
5710 GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker);
5711 if(PosFlags(0) & F_MANDATORY_CAPTURE) {
5712 for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
5714 for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
5717 DrawPosition(TRUE, NULL);
5720 void LeftClick(ClickType clickType, int xPix, int yPix)
5723 Boolean saveAnimate;
5724 static int second = 0, promotionChoice = 0;
5725 char promoChoice = NULLCHAR;
5727 if (clickType == Press) ErrorPopDown();
5728 MarkTargetSquares(1);
5730 x = EventToSquare(xPix, BOARD_WIDTH);
5731 y = EventToSquare(yPix, BOARD_HEIGHT);
5732 if (!flipView && y >= 0) {
5733 y = BOARD_HEIGHT - 1 - y;
5735 if (flipView && x >= 0) {
5736 x = BOARD_WIDTH - 1 - x;
5739 if(promotionChoice) { // we are waiting for a click to indicate promotion piece
5740 if(clickType == Release) return; // ignore upclick of click-click destination
5741 promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
5742 if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
5743 if(gameInfo.holdingsWidth &&
5744 (WhiteOnMove(currentMove)
5745 ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y > 0
5746 : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT-1) ) {
5747 // click in right holdings, for determining promotion piece
5748 ChessSquare p = boards[currentMove][y][x];
5749 if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
5750 if(p != EmptySquare) {
5751 FinishMove(NormalMove, fromX, fromY, toX, toY, ToLower(PieceToChar(p)));
5756 DrawPosition(FALSE, boards[currentMove]);
5760 /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
5761 if(clickType == Press
5762 && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
5763 || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
5764 || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
5768 if (clickType == Press) {
5770 if (OKToStartUserMove(x, y)) {
5774 MarkTargetSquares(0);
5775 DragPieceBegin(xPix, yPix);
5776 if (appData.highlightDragging) {
5777 SetHighlights(x, y, -1, -1);
5785 if (clickType == Press && gameMode != EditPosition) {
5790 // ignore off-board to clicks
5791 if(y < 0 || x < 0) return;
5793 /* Check if clicking again on the same color piece */
5794 fromP = boards[currentMove][fromY][fromX];
5795 toP = boards[currentMove][y][x];
5796 frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom;
5797 if ((WhitePawn <= fromP && fromP <= WhiteKing &&
5798 WhitePawn <= toP && toP <= WhiteKing &&
5799 !(fromP == WhiteKing && toP == WhiteRook && frc) &&
5800 !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
5801 (BlackPawn <= fromP && fromP <= BlackKing &&
5802 BlackPawn <= toP && toP <= BlackKing &&
5803 !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
5804 !(fromP == BlackKing && toP == BlackRook && frc))) {
5805 /* Clicked again on same color piece -- changed his mind */
5806 second = (x == fromX && y == fromY);
5807 if (appData.highlightDragging) {
5808 SetHighlights(x, y, -1, -1);
5812 if (OKToStartUserMove(x, y)) {
5815 MarkTargetSquares(0);
5816 DragPieceBegin(xPix, yPix);
5820 // ignore clicks on holdings
5821 if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
5824 if (clickType == Release && x == fromX && y == fromY) {
5825 DragPieceEnd(xPix, yPix);
5826 if (appData.animateDragging) {
5827 /* Undo animation damage if any */
5828 DrawPosition(FALSE, NULL);
5831 /* Second up/down in same square; just abort move */
5836 ClearPremoveHighlights();
5838 /* First upclick in same square; start click-click mode */
5839 SetHighlights(x, y, -1, -1);
5844 /* we now have a different from- and (possibly off-board) to-square */
5845 /* Completed move */
5848 saveAnimate = appData.animate;
5849 if (clickType == Press) {
5850 /* Finish clickclick move */
5851 if (appData.animate || appData.highlightLastMove) {
5852 SetHighlights(fromX, fromY, toX, toY);
5857 /* Finish drag move */
5858 if (appData.highlightLastMove) {
5859 SetHighlights(fromX, fromY, toX, toY);
5863 DragPieceEnd(xPix, yPix);
5864 /* Don't animate move and drag both */
5865 appData.animate = FALSE;
5868 // moves into holding are invalid for now (except in EditPosition, adapting to-square)
5869 if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
5870 ChessSquare piece = boards[currentMove][fromY][fromX];
5871 if(gameMode == EditPosition && piece != EmptySquare &&
5872 fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
5875 if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
5876 n = PieceToNumber(piece - (int)BlackPawn);
5877 if(n > gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
5878 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
5879 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
5881 if(x == BOARD_RGHT+1 && piece < BlackPawn) {
5882 n = PieceToNumber(piece);
5883 if(n > gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
5884 boards[currentMove][n][BOARD_WIDTH-1] = piece;
5885 boards[currentMove][n][BOARD_WIDTH-2]++;
5887 boards[currentMove][fromY][fromX] = EmptySquare;
5891 DrawPosition(TRUE, boards[currentMove]);
5895 // off-board moves should not be highlighted
5896 if(x < 0 || x < 0) ClearHighlights();
5898 if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice)) {
5899 SetHighlights(fromX, fromY, toX, toY);
5900 if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
5901 // [HGM] super: promotion to captured piece selected from holdings
5902 ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
5903 promotionChoice = TRUE;
5904 // kludge follows to temporarily execute move on display, without promoting yet
5905 boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
5906 boards[currentMove][toY][toX] = p;
5907 DrawPosition(FALSE, boards[currentMove]);
5908 boards[currentMove][fromY][fromX] = p; // take back, but display stays
5909 boards[currentMove][toY][toX] = q;
5910 DisplayMessage("Click in holdings to choose piece", "");
5915 UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
5916 if (!appData.highlightLastMove || gotPremove) ClearHighlights();
5917 if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
5920 appData.animate = saveAnimate;
5921 if (appData.animate || appData.animateDragging) {
5922 /* Undo animation damage if needed */
5923 DrawPosition(FALSE, NULL);
5927 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
5929 // char * hint = lastHint;
5930 FrontEndProgramStats stats;
5932 stats.which = cps == &first ? 0 : 1;
5933 stats.depth = cpstats->depth;
5934 stats.nodes = cpstats->nodes;
5935 stats.score = cpstats->score;
5936 stats.time = cpstats->time;
5937 stats.pv = cpstats->movelist;
5938 stats.hint = lastHint;
5939 stats.an_move_index = 0;
5940 stats.an_move_count = 0;
5942 if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
5943 stats.hint = cpstats->move_name;
5944 stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
5945 stats.an_move_count = cpstats->nr_moves;
5948 if(stats.pv && stats.pv[0]) strcpy(lastPV[stats.which], stats.pv); // [HGM] pv: remember last PV of each
5950 SetProgramStats( &stats );
5953 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
5954 { // [HGM] book: this routine intercepts moves to simulate book replies
5955 char *bookHit = NULL;
5957 //first determine if the incoming move brings opponent into his book
5958 if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
5959 bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
5960 if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
5961 if(bookHit != NULL && !cps->bookSuspend) {
5962 // make sure opponent is not going to reply after receiving move to book position
5963 SendToProgram("force\n", cps);
5964 cps->bookSuspend = TRUE; // flag indicating it has to be restarted
5966 if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
5967 // now arrange restart after book miss
5969 // after a book hit we never send 'go', and the code after the call to this routine
5970 // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
5972 if (cps->useUsermove) sprintf(buf, "usermove "); // sorry, no SAN yet :(
5973 sprintf(buf, "%s\n", bookHit); // force book move into program supposed to play it
5974 SendToProgram(buf, cps);
5975 if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
5976 } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
5977 SendToProgram("go\n", cps);
5978 cps->bookSuspend = FALSE; // after a 'go' we are never suspended
5979 } else { // 'go' might be sent based on 'firstMove' after this routine returns
5980 if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
5981 SendToProgram("go\n", cps);
5982 cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
5984 return bookHit; // notify caller of hit, so it can take action to send move to opponent
5988 ChessProgramState *savedState;
5989 void DeferredBookMove(void)
5991 if(savedState->lastPing != savedState->lastPong)
5992 ScheduleDelayedEvent(DeferredBookMove, 10);
5994 HandleMachineMove(savedMessage, savedState);
5998 HandleMachineMove(message, cps)
6000 ChessProgramState *cps;
6002 char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
6003 char realname[MSG_SIZ];
6004 int fromX, fromY, toX, toY;
6013 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
6015 * Kludge to ignore BEL characters
6017 while (*message == '\007') message++;
6020 * [HGM] engine debug message: ignore lines starting with '#' character
6022 if(cps->debug && *message == '#') return;
6025 * Look for book output
6027 if (cps == &first && bookRequested) {
6028 if (message[0] == '\t' || message[0] == ' ') {
6029 /* Part of the book output is here; append it */
6030 strcat(bookOutput, message);
6031 strcat(bookOutput, " \n");
6033 } else if (bookOutput[0] != NULLCHAR) {
6034 /* All of book output has arrived; display it */
6035 char *p = bookOutput;
6036 while (*p != NULLCHAR) {
6037 if (*p == '\t') *p = ' ';
6040 DisplayInformation(bookOutput);
6041 bookRequested = FALSE;
6042 /* Fall through to parse the current output */
6047 * Look for machine move.
6049 if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
6050 (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
6052 /* This method is only useful on engines that support ping */
6053 if (cps->lastPing != cps->lastPong) {
6054 if (gameMode == BeginningOfGame) {
6055 /* Extra move from before last new; ignore */
6056 if (appData.debugMode) {
6057 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
6060 if (appData.debugMode) {
6061 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
6062 cps->which, gameMode);
6065 SendToProgram("undo\n", cps);
6071 case BeginningOfGame:
6072 /* Extra move from before last reset; ignore */
6073 if (appData.debugMode) {
6074 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
6081 /* Extra move after we tried to stop. The mode test is
6082 not a reliable way of detecting this problem, but it's
6083 the best we can do on engines that don't support ping.
6085 if (appData.debugMode) {
6086 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
6087 cps->which, gameMode);
6089 SendToProgram("undo\n", cps);
6092 case MachinePlaysWhite:
6093 case IcsPlayingWhite:
6094 machineWhite = TRUE;
6097 case MachinePlaysBlack:
6098 case IcsPlayingBlack:
6099 machineWhite = FALSE;
6102 case TwoMachinesPlay:
6103 machineWhite = (cps->twoMachinesColor[0] == 'w');
6106 if (WhiteOnMove(forwardMostMove) != machineWhite) {
6107 if (appData.debugMode) {
6109 "Ignoring move out of turn by %s, gameMode %d"
6110 ", forwardMost %d\n",
6111 cps->which, gameMode, forwardMostMove);
6116 if (appData.debugMode) { int f = forwardMostMove;
6117 fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
6118 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
6119 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
6121 if(cps->alphaRank) AlphaRank(machineMove, 4);
6122 if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
6123 &fromX, &fromY, &toX, &toY, &promoChar)) {
6124 /* Machine move could not be parsed; ignore it. */
6125 sprintf(buf1, _("Illegal move \"%s\" from %s machine"),
6126 machineMove, cps->which);
6127 DisplayError(buf1, 0);
6128 sprintf(buf1, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
6129 machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
6130 if (gameMode == TwoMachinesPlay) {
6131 GameEnds(machineWhite ? BlackWins : WhiteWins,
6137 /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
6138 /* So we have to redo legality test with true e.p. status here, */
6139 /* to make sure an illegal e.p. capture does not slip through, */
6140 /* to cause a forfeit on a justified illegal-move complaint */
6141 /* of the opponent. */
6142 if( gameMode==TwoMachinesPlay && appData.testLegality
6143 && fromY != DROP_RANK /* [HGM] temporary; should still add legality test for drops */
6146 moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
6147 fromY, fromX, toY, toX, promoChar);
6148 if (appData.debugMode) {
6150 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
6151 boards[forwardMostMove][CASTLING][i], castlingRank[i]);
6152 fprintf(debugFP, "castling rights\n");
6154 if(moveType == IllegalMove) {
6155 sprintf(buf1, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
6156 machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
6157 GameEnds(machineWhite ? BlackWins : WhiteWins,
6160 } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
6161 /* [HGM] Kludge to handle engines that send FRC-style castling
6162 when they shouldn't (like TSCP-Gothic) */
6164 case WhiteASideCastleFR:
6165 case BlackASideCastleFR:
6167 currentMoveString[2]++;
6169 case WhiteHSideCastleFR:
6170 case BlackHSideCastleFR:
6172 currentMoveString[2]--;
6174 default: ; // nothing to do, but suppresses warning of pedantic compilers
6177 hintRequested = FALSE;
6178 lastHint[0] = NULLCHAR;
6179 bookRequested = FALSE;
6180 /* Program may be pondering now */
6181 cps->maybeThinking = TRUE;
6182 if (cps->sendTime == 2) cps->sendTime = 1;
6183 if (cps->offeredDraw) cps->offeredDraw--;
6186 if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
6188 SendMoveToICS(moveType, fromX, fromY, toX, toY);
6190 if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
6191 char buf[3*MSG_SIZ];
6193 sprintf(buf, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
6194 programStats.score / 100.,
6196 programStats.time / 100.,
6197 (unsigned int)programStats.nodes,
6198 (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
6199 programStats.movelist);
6201 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
6205 /* currentMoveString is set as a side-effect of ParseOneMove */
6206 strcpy(machineMove, currentMoveString);
6207 strcat(machineMove, "\n");
6208 strcpy(moveList[forwardMostMove], machineMove);
6210 /* [AS] Save move info and clear stats for next move */
6211 pvInfoList[ forwardMostMove ].score = programStats.score;
6212 pvInfoList[ forwardMostMove ].depth = programStats.depth;
6213 pvInfoList[ forwardMostMove ].time = programStats.time; // [HGM] PGNtime: take time from engine stats
6214 ClearProgramStats();
6215 thinkOutput[0] = NULLCHAR;
6216 hiddenThinkOutputState = 0;
6218 MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
6220 /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
6221 if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
6224 while( count < adjudicateLossPlies ) {
6225 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
6228 score = -score; /* Flip score for winning side */
6231 if( score > adjudicateLossThreshold ) {
6238 if( count >= adjudicateLossPlies ) {
6239 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6241 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
6242 "Xboard adjudication",
6249 if( gameMode == TwoMachinesPlay ) {
6250 // [HGM] some adjudications useful with buggy engines
6251 int k, count = 0; static int bare = 1;
6252 if(gameInfo.holdingsSize == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
6255 if( appData.testLegality )
6256 { /* [HGM] Some more adjudications for obstinate engines */
6257 int NrWN=0, NrBN=0, NrWB=0, NrBB=0, NrWR=0, NrBR=0,
6258 NrWQ=0, NrBQ=0, NrW=0, NrK=0, bishopsColor = 0,
6259 NrPieces=0, NrPawns=0, PawnAdvance=0, i, j;
6260 static int moveCount = 6;
6262 char *reason = NULL;
6264 /* Count what is on board. */
6265 for(i=0; i<BOARD_HEIGHT; i++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
6266 { ChessSquare p = boards[forwardMostMove][i][j];
6270 { /* count B,N,R and other of each side */
6273 NrK++; break; // [HGM] atomic: count Kings
6277 case WhiteFerz: // [HGM] shatranj: kludge to mke it work in shatranj
6278 bishopsColor |= 1 << ((i^j)&1);
6283 case BlackFerz: // [HGM] shatranj: kludge to mke it work in shatranj
6284 bishopsColor |= 1 << ((i^j)&1);
6299 PawnAdvance += m; NrPawns++;
6301 NrPieces += (p != EmptySquare);
6302 NrW += ((int)p < (int)BlackPawn);
6303 if(gameInfo.variant == VariantXiangqi &&
6304 (p == WhiteFerz || p == WhiteAlfil || p == BlackFerz || p == BlackAlfil)) {
6305 NrPieces--; // [HGM] XQ: do not count purely defensive pieces
6306 NrW -= ((int)p < (int)BlackPawn);
6310 /* Some material-based adjudications that have to be made before stalemate test */
6311 if(gameInfo.variant == VariantAtomic && NrK < 2) {
6312 // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
6313 boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
6314 if(appData.checkMates) {
6315 SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets move
6316 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6317 GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
6318 "Xboard adjudication: King destroyed", GE_XBOARD );
6323 /* Bare King in Shatranj (loses) or Losers (wins) */
6324 if( NrW == 1 || NrPieces - NrW == 1) {
6325 if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
6326 boards[forwardMostMove][EP_STATUS] = EP_WINS; // mark as win, so it becomes claimable
6327 if(appData.checkMates) {
6328 SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets to see move
6329 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6330 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
6331 "Xboard adjudication: Bare king", GE_XBOARD );
6335 if( gameInfo.variant == VariantShatranj && --bare < 0)
6337 boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
6338 if(appData.checkMates) {
6339 /* but only adjudicate if adjudication enabled */
6340 SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets move
6341 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6342 GameEnds( NrW > 1 ? WhiteWins : NrPieces - NrW > 1 ? BlackWins : GameIsDrawn,
6343 "Xboard adjudication: Bare king", GE_XBOARD );
6350 // don't wait for engine to announce game end if we can judge ourselves
6351 switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
6353 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
6354 int i, checkCnt = 0; // (should really be done by making nr of checks part of game state)
6355 for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
6356 if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
6359 reason = "Xboard adjudication: 3rd check";
6360 boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
6370 reason = "Xboard adjudication: Stalemate";
6371 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
6372 boards[forwardMostMove][EP_STATUS] = EP_STALEMATE; // default result for stalemate is draw
6373 if(gameInfo.variant == VariantLosers || gameInfo.variant == VariantGiveaway) // [HGM] losers:
6374 boards[forwardMostMove][EP_STATUS] = EP_WINS; // in these variants stalemated is always a win
6375 else if(gameInfo.variant == VariantSuicide) // in suicide it depends
6376 boards[forwardMostMove][EP_STATUS] = NrW == NrPieces-NrW ? EP_STALEMATE :
6377 ((NrW < NrPieces-NrW) != WhiteOnMove(forwardMostMove) ?
6378 EP_CHECKMATE : EP_WINS);
6379 else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
6380 boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
6384 reason = "Xboard adjudication: Checkmate";
6385 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
6389 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
6391 result = GameIsDrawn; break;
6393 result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
6395 result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
6397 result = (ChessMove) 0;
6399 if(appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
6400 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6401 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6402 GameEnds( result, reason, GE_XBOARD );
6406 /* Next absolutely insufficient mating material. */
6407 if( NrPieces == 2 || gameInfo.variant != VariantXiangqi &&
6408 gameInfo.variant != VariantShatranj && // [HGM] baring will remain possible
6409 (NrPieces == 3 && NrWN+NrBN+NrWB+NrBB == 1 ||
6410 NrPieces == NrBB+NrWB+2 && bishopsColor != 3)) // [HGM] all Bishops (Ferz!) same color
6411 { /* KBK, KNK, KK of KBKB with like Bishops */
6413 /* always flag draws, for judging claims */
6414 boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
6416 if(appData.materialDraws) {
6417 /* but only adjudicate them if adjudication enabled */
6418 SendToProgram("force\n", cps->other); // suppress reply
6419 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see last move */
6420 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6421 GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
6426 /* Then some trivial draws (only adjudicate, cannot be claimed) */
6428 ( NrWR == 1 && NrBR == 1 /* KRKR */
6429 || NrWQ==1 && NrBQ==1 /* KQKQ */
6430 || NrWN==2 || NrBN==2 /* KNNK */
6431 || NrWN+NrWB == 1 && NrBN+NrBB == 1 /* KBKN, KBKB, KNKN */
6433 if(--moveCount < 0 && appData.trivialDraws)
6434 { /* if the first 3 moves do not show a tactical win, declare draw */
6435 SendToProgram("force\n", cps->other); // suppress reply
6436 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6437 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6438 GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
6441 } else moveCount = 6;
6445 if (appData.debugMode) { int i;
6446 fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
6447 forwardMostMove, backwardMostMove, boards[backwardMostMove][EP_STATUS],
6448 appData.drawRepeats);
6449 for( i=forwardMostMove; i>=backwardMostMove; i-- )
6450 fprintf(debugFP, "%d ep=%d\n", i, (signed char)boards[i][EP_STATUS]);
6454 /* Check for rep-draws */
6456 for(k = forwardMostMove-2;
6457 k>=backwardMostMove && k>=forwardMostMove-100 &&
6458 (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
6459 (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE;
6462 if(CompareBoards(boards[k], boards[forwardMostMove])) {
6463 /* compare castling rights */
6464 if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
6465 (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
6466 rights++; /* King lost rights, while rook still had them */
6467 if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
6468 if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
6469 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
6470 rights++; /* but at least one rook lost them */
6472 if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
6473 (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
6475 if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
6476 if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
6477 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
6480 if( rights == 0 && ++count > appData.drawRepeats-2
6481 && appData.drawRepeats > 1) {
6482 /* adjudicate after user-specified nr of repeats */
6483 SendToProgram("force\n", cps->other); // suppress reply
6484 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6485 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6486 if(gameInfo.variant == VariantXiangqi && appData.testLegality) {
6487 // [HGM] xiangqi: check for forbidden perpetuals
6488 int m, ourPerpetual = 1, hisPerpetual = 1;
6489 for(m=forwardMostMove; m>k; m-=2) {
6490 if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
6491 ourPerpetual = 0; // the current mover did not always check
6492 if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
6493 hisPerpetual = 0; // the opponent did not always check
6495 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
6496 ourPerpetual, hisPerpetual);
6497 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
6498 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
6499 "Xboard adjudication: perpetual checking", GE_XBOARD );
6502 if(hisPerpetual && !ourPerpetual) // he is checking us, but did not repeat yet
6503 break; // (or we would have caught him before). Abort repetition-checking loop.
6504 // Now check for perpetual chases
6505 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
6506 hisPerpetual = PerpetualChase(k, forwardMostMove);
6507 ourPerpetual = PerpetualChase(k+1, forwardMostMove);
6508 if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
6509 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
6510 "Xboard adjudication: perpetual chasing", GE_XBOARD );
6513 if(hisPerpetual && !ourPerpetual) // he is chasing us, but did not repeat yet
6514 break; // Abort repetition-checking loop.
6516 // if neither of us is checking or chasing all the time, or both are, it is draw
6518 GameEnds( GameIsDrawn, "Xboard adjudication: repetition draw", GE_XBOARD );
6521 if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
6522 boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
6526 /* Now we test for 50-move draws. Determine ply count */
6527 count = forwardMostMove;
6528 /* look for last irreversble move */
6529 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
6531 /* if we hit starting position, add initial plies */
6532 if( count == backwardMostMove )
6533 count -= initialRulePlies;
6534 count = forwardMostMove - count;
6536 boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
6537 /* this is used to judge if draw claims are legal */
6538 if(appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
6539 SendToProgram("force\n", cps->other); // suppress reply
6540 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6541 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6542 GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
6546 /* if draw offer is pending, treat it as a draw claim
6547 * when draw condition present, to allow engines a way to
6548 * claim draws before making their move to avoid a race
6549 * condition occurring after their move
6551 if( cps->other->offeredDraw || cps->offeredDraw ) {
6553 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
6554 p = "Draw claim: 50-move rule";
6555 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
6556 p = "Draw claim: 3-fold repetition";
6557 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
6558 p = "Draw claim: insufficient mating material";
6560 SendToProgram("force\n", cps->other); // suppress reply
6561 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6562 GameEnds( GameIsDrawn, p, GE_XBOARD );
6563 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6569 if( appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
6570 SendToProgram("force\n", cps->other); // suppress reply
6571 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6572 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6574 GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
6581 if (gameMode == TwoMachinesPlay) {
6582 /* [HGM] relaying draw offers moved to after reception of move */
6583 /* and interpreting offer as claim if it brings draw condition */
6584 if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
6585 SendToProgram("draw\n", cps->other);
6587 if (cps->other->sendTime) {
6588 SendTimeRemaining(cps->other,
6589 cps->other->twoMachinesColor[0] == 'w');
6591 bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
6592 if (firstMove && !bookHit) {
6594 if (cps->other->useColors) {
6595 SendToProgram(cps->other->twoMachinesColor, cps->other);
6597 SendToProgram("go\n", cps->other);
6599 cps->other->maybeThinking = TRUE;
6602 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6604 if (!pausing && appData.ringBellAfterMoves) {
6609 * Reenable menu items that were disabled while
6610 * machine was thinking
6612 if (gameMode != TwoMachinesPlay)
6613 SetUserThinkingEnables();
6615 // [HGM] book: after book hit opponent has received move and is now in force mode
6616 // force the book reply into it, and then fake that it outputted this move by jumping
6617 // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
6619 static char bookMove[MSG_SIZ]; // a bit generous?
6621 strcpy(bookMove, "move ");
6622 strcat(bookMove, bookHit);
6625 programStats.nodes = programStats.depth = programStats.time =
6626 programStats.score = programStats.got_only_move = 0;
6627 sprintf(programStats.movelist, "%s (xbook)", bookHit);
6629 if(cps->lastPing != cps->lastPong) {
6630 savedMessage = message; // args for deferred call
6632 ScheduleDelayedEvent(DeferredBookMove, 10);
6641 /* Set special modes for chess engines. Later something general
6642 * could be added here; for now there is just one kludge feature,
6643 * needed because Crafty 15.10 and earlier don't ignore SIGINT
6644 * when "xboard" is given as an interactive command.
6646 if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
6647 cps->useSigint = FALSE;
6648 cps->useSigterm = FALSE;
6650 if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
6651 ParseFeatures(message+8, cps);
6652 return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
6655 /* [HGM] Allow engine to set up a position. Don't ask me why one would
6656 * want this, I was asked to put it in, and obliged.
6658 if (!strncmp(message, "setboard ", 9)) {
6659 Board initial_position;
6661 GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
6663 if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
6664 DisplayError(_("Bad FEN received from engine"), 0);
6668 CopyBoard(boards[0], initial_position);
6669 initialRulePlies = FENrulePlies;
6670 if(blackPlaysFirst) gameMode = MachinePlaysWhite;
6671 else gameMode = MachinePlaysBlack;
6672 DrawPosition(FALSE, boards[currentMove]);
6678 * Look for communication commands
6680 if (!strncmp(message, "telluser ", 9)) {
6681 DisplayNote(message + 9);
6684 if (!strncmp(message, "tellusererror ", 14)) {
6686 DisplayError(message + 14, 0);
6689 if (!strncmp(message, "tellopponent ", 13)) {
6690 if (appData.icsActive) {
6692 snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
6696 DisplayNote(message + 13);
6700 if (!strncmp(message, "tellothers ", 11)) {
6701 if (appData.icsActive) {
6703 snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
6709 if (!strncmp(message, "tellall ", 8)) {
6710 if (appData.icsActive) {
6712 snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
6716 DisplayNote(message + 8);
6720 if (strncmp(message, "warning", 7) == 0) {
6721 /* Undocumented feature, use tellusererror in new code */
6722 DisplayError(message, 0);
6725 if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
6726 strcpy(realname, cps->tidy);
6727 strcat(realname, " query");
6728 AskQuestion(realname, buf2, buf1, cps->pr);
6731 /* Commands from the engine directly to ICS. We don't allow these to be
6732 * sent until we are logged on. Crafty kibitzes have been known to
6733 * interfere with the login process.
6736 if (!strncmp(message, "tellics ", 8)) {
6737 SendToICS(message + 8);
6741 if (!strncmp(message, "tellicsnoalias ", 15)) {
6742 SendToICS(ics_prefix);
6743 SendToICS(message + 15);
6747 /* The following are for backward compatibility only */
6748 if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
6749 !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
6750 SendToICS(ics_prefix);
6756 if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
6760 * If the move is illegal, cancel it and redraw the board.
6761 * Also deal with other error cases. Matching is rather loose
6762 * here to accommodate engines written before the spec.
6764 if (strncmp(message + 1, "llegal move", 11) == 0 ||
6765 strncmp(message, "Error", 5) == 0) {
6766 if (StrStr(message, "name") ||
6767 StrStr(message, "rating") || StrStr(message, "?") ||
6768 StrStr(message, "result") || StrStr(message, "board") ||
6769 StrStr(message, "bk") || StrStr(message, "computer") ||
6770 StrStr(message, "variant") || StrStr(message, "hint") ||
6771 StrStr(message, "random") || StrStr(message, "depth") ||
6772 StrStr(message, "accepted")) {
6775 if (StrStr(message, "protover")) {
6776 /* Program is responding to input, so it's apparently done
6777 initializing, and this error message indicates it is
6778 protocol version 1. So we don't need to wait any longer
6779 for it to initialize and send feature commands. */
6780 FeatureDone(cps, 1);
6781 cps->protocolVersion = 1;
6784 cps->maybeThinking = FALSE;
6786 if (StrStr(message, "draw")) {
6787 /* Program doesn't have "draw" command */
6788 cps->sendDrawOffers = 0;
6791 if (cps->sendTime != 1 &&
6792 (StrStr(message, "time") || StrStr(message, "otim"))) {
6793 /* Program apparently doesn't have "time" or "otim" command */
6797 if (StrStr(message, "analyze")) {
6798 cps->analysisSupport = FALSE;
6799 cps->analyzing = FALSE;
6801 sprintf(buf2, _("%s does not support analysis"), cps->tidy);
6802 DisplayError(buf2, 0);
6805 if (StrStr(message, "(no matching move)st")) {
6806 /* Special kludge for GNU Chess 4 only */
6807 cps->stKludge = TRUE;
6808 SendTimeControl(cps, movesPerSession, timeControl,
6809 timeIncrement, appData.searchDepth,
6813 if (StrStr(message, "(no matching move)sd")) {
6814 /* Special kludge for GNU Chess 4 only */
6815 cps->sdKludge = TRUE;
6816 SendTimeControl(cps, movesPerSession, timeControl,
6817 timeIncrement, appData.searchDepth,
6821 if (!StrStr(message, "llegal")) {
6824 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
6825 gameMode == IcsIdle) return;
6826 if (forwardMostMove <= backwardMostMove) return;
6827 if (pausing) PauseEvent();
6828 if(appData.forceIllegal) {
6829 // [HGM] illegal: machine refused move; force position after move into it
6830 SendToProgram("force\n", cps);
6831 if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
6832 // we have a real problem now, as SendBoard will use the a2a3 kludge
6833 // when black is to move, while there might be nothing on a2 or black
6834 // might already have the move. So send the board as if white has the move.
6835 // But first we must change the stm of the engine, as it refused the last move
6836 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
6837 if(WhiteOnMove(forwardMostMove)) {
6838 SendToProgram("a7a6\n", cps); // for the engine black still had the move
6839 SendBoard(cps, forwardMostMove); // kludgeless board
6841 SendToProgram("a2a3\n", cps); // for the engine white still had the move
6842 CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
6843 SendBoard(cps, forwardMostMove+1); // kludgeless board
6845 } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
6846 if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
6847 gameMode == TwoMachinesPlay)
6848 SendToProgram("go\n", cps);
6851 if (gameMode == PlayFromGameFile) {
6852 /* Stop reading this game file */
6853 gameMode = EditGame;
6856 currentMove = --forwardMostMove;
6857 DisplayMove(currentMove-1); /* before DisplayMoveError */
6859 DisplayBothClocks();
6860 sprintf(buf1, _("Illegal move \"%s\" (rejected by %s chess program)"),
6861 parseList[currentMove], cps->which);
6862 DisplayMoveError(buf1);
6863 DrawPosition(FALSE, boards[currentMove]);
6865 /* [HGM] illegal-move claim should forfeit game when Xboard */
6866 /* only passes fully legal moves */
6867 if( appData.testLegality && gameMode == TwoMachinesPlay ) {
6868 GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
6869 "False illegal-move claim", GE_XBOARD );
6873 if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
6874 /* Program has a broken "time" command that
6875 outputs a string not ending in newline.
6881 * If chess program startup fails, exit with an error message.
6882 * Attempts to recover here are futile.
6884 if ((StrStr(message, "unknown host") != NULL)
6885 || (StrStr(message, "No remote directory") != NULL)
6886 || (StrStr(message, "not found") != NULL)
6887 || (StrStr(message, "No such file") != NULL)
6888 || (StrStr(message, "can't alloc") != NULL)
6889 || (StrStr(message, "Permission denied") != NULL)) {
6891 cps->maybeThinking = FALSE;
6892 snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
6893 cps->which, cps->program, cps->host, message);
6894 RemoveInputSource(cps->isr);
6895 DisplayFatalError(buf1, 0, 1);
6900 * Look for hint output
6902 if (sscanf(message, "Hint: %s", buf1) == 1) {
6903 if (cps == &first && hintRequested) {
6904 hintRequested = FALSE;
6905 if (ParseOneMove(buf1, forwardMostMove, &moveType,
6906 &fromX, &fromY, &toX, &toY, &promoChar)) {
6907 (void) CoordsToAlgebraic(boards[forwardMostMove],
6908 PosFlags(forwardMostMove),
6909 fromY, fromX, toY, toX, promoChar, buf1);
6910 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
6911 DisplayInformation(buf2);
6913 /* Hint move could not be parsed!? */
6914 snprintf(buf2, sizeof(buf2),
6915 _("Illegal hint move \"%s\"\nfrom %s chess program"),
6917 DisplayError(buf2, 0);
6920 strcpy(lastHint, buf1);
6926 * Ignore other messages if game is not in progress
6928 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
6929 gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
6932 * look for win, lose, draw, or draw offer
6934 if (strncmp(message, "1-0", 3) == 0) {
6935 char *p, *q, *r = "";
6936 p = strchr(message, '{');
6944 GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
6946 } else if (strncmp(message, "0-1", 3) == 0) {
6947 char *p, *q, *r = "";
6948 p = strchr(message, '{');
6956 /* Kludge for Arasan 4.1 bug */
6957 if (strcmp(r, "Black resigns") == 0) {
6958 GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
6961 GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
6963 } else if (strncmp(message, "1/2", 3) == 0) {
6964 char *p, *q, *r = "";
6965 p = strchr(message, '{');
6974 GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
6977 } else if (strncmp(message, "White resign", 12) == 0) {
6978 GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
6980 } else if (strncmp(message, "Black resign", 12) == 0) {
6981 GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
6983 } else if (strncmp(message, "White matches", 13) == 0 ||
6984 strncmp(message, "Black matches", 13) == 0 ) {
6985 /* [HGM] ignore GNUShogi noises */
6987 } else if (strncmp(message, "White", 5) == 0 &&
6988 message[5] != '(' &&
6989 StrStr(message, "Black") == NULL) {
6990 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6992 } else if (strncmp(message, "Black", 5) == 0 &&
6993 message[5] != '(') {
6994 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6996 } else if (strcmp(message, "resign") == 0 ||
6997 strcmp(message, "computer resigns") == 0) {
6999 case MachinePlaysBlack:
7000 case IcsPlayingBlack:
7001 GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
7003 case MachinePlaysWhite:
7004 case IcsPlayingWhite:
7005 GameEnds(BlackWins, "White resigns", GE_ENGINE);
7007 case TwoMachinesPlay:
7008 if (cps->twoMachinesColor[0] == 'w')
7009 GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
7011 GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
7018 } else if (strncmp(message, "opponent mates", 14) == 0) {
7020 case MachinePlaysBlack:
7021 case IcsPlayingBlack:
7022 GameEnds(WhiteWins, "White mates", GE_ENGINE);
7024 case MachinePlaysWhite:
7025 case IcsPlayingWhite:
7026 GameEnds(BlackWins, "Black mates", GE_ENGINE);
7028 case TwoMachinesPlay:
7029 if (cps->twoMachinesColor[0] == 'w')
7030 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7032 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7039 } else if (strncmp(message, "computer mates", 14) == 0) {
7041 case MachinePlaysBlack:
7042 case IcsPlayingBlack:
7043 GameEnds(BlackWins, "Black mates", GE_ENGINE1);
7045 case MachinePlaysWhite:
7046 case IcsPlayingWhite:
7047 GameEnds(WhiteWins, "White mates", GE_ENGINE);
7049 case TwoMachinesPlay:
7050 if (cps->twoMachinesColor[0] == 'w')
7051 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7053 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7060 } else if (strncmp(message, "checkmate", 9) == 0) {
7061 if (WhiteOnMove(forwardMostMove)) {
7062 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7064 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7067 } else if (strstr(message, "Draw") != NULL ||
7068 strstr(message, "game is a draw") != NULL) {
7069 GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
7071 } else if (strstr(message, "offer") != NULL &&
7072 strstr(message, "draw") != NULL) {
7074 if (appData.zippyPlay && first.initDone) {
7075 /* Relay offer to ICS */
7076 SendToICS(ics_prefix);
7077 SendToICS("draw\n");
7080 cps->offeredDraw = 2; /* valid until this engine moves twice */
7081 if (gameMode == TwoMachinesPlay) {
7082 if (cps->other->offeredDraw) {
7083 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
7084 /* [HGM] in two-machine mode we delay relaying draw offer */
7085 /* until after we also have move, to see if it is really claim */
7087 } else if (gameMode == MachinePlaysWhite ||
7088 gameMode == MachinePlaysBlack) {
7089 if (userOfferedDraw) {
7090 DisplayInformation(_("Machine accepts your draw offer"));
7091 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
7093 DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
7100 * Look for thinking output
7102 if ( appData.showThinking // [HGM] thinking: test all options that cause this output
7103 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
7105 int plylev, mvleft, mvtot, curscore, time;
7106 char mvname[MOVE_LEN];
7110 int prefixHint = FALSE;
7111 mvname[0] = NULLCHAR;
7114 case MachinePlaysBlack:
7115 case IcsPlayingBlack:
7116 if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
7118 case MachinePlaysWhite:
7119 case IcsPlayingWhite:
7120 if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
7125 case IcsObserving: /* [DM] icsEngineAnalyze */
7126 if (!appData.icsEngineAnalyze) ignore = TRUE;
7128 case TwoMachinesPlay:
7129 if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
7140 if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
7141 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
7143 if (plyext != ' ' && plyext != '\t') {
7147 /* [AS] Negate score if machine is playing black and reporting absolute scores */
7148 if( cps->scoreIsAbsolute &&
7149 ( gameMode == MachinePlaysBlack ||
7150 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
7151 gameMode == IcsPlayingBlack || // [HGM] also add other situations where engine should report black POV
7152 (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
7153 !WhiteOnMove(currentMove)
7156 curscore = -curscore;
7160 programStats.depth = plylev;
7161 programStats.nodes = nodes;
7162 programStats.time = time;
7163 programStats.score = curscore;
7164 programStats.got_only_move = 0;
7166 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
7169 if(cps->nps == 0) ticklen = 10*time; // use engine reported time
7170 else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
7171 if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
7172 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
7173 whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
7174 if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
7175 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
7176 blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
7179 /* Buffer overflow protection */
7180 if (buf1[0] != NULLCHAR) {
7181 if (strlen(buf1) >= sizeof(programStats.movelist)
7182 && appData.debugMode) {
7184 "PV is too long; using the first %u bytes.\n",
7185 (unsigned) sizeof(programStats.movelist) - 1);
7188 safeStrCpy( programStats.movelist, buf1, sizeof(programStats.movelist) );
7190 sprintf(programStats.movelist, " no PV\n");
7193 if (programStats.seen_stat) {
7194 programStats.ok_to_send = 1;
7197 if (strchr(programStats.movelist, '(') != NULL) {
7198 programStats.line_is_book = 1;
7199 programStats.nr_moves = 0;
7200 programStats.moves_left = 0;
7202 programStats.line_is_book = 0;
7205 SendProgramStatsToFrontend( cps, &programStats );
7208 [AS] Protect the thinkOutput buffer from overflow... this
7209 is only useful if buf1 hasn't overflowed first!
7211 sprintf(thinkOutput, "[%d]%c%+.2f %s%s",
7213 (gameMode == TwoMachinesPlay ?
7214 ToUpper(cps->twoMachinesColor[0]) : ' '),
7215 ((double) curscore) / 100.0,
7216 prefixHint ? lastHint : "",
7217 prefixHint ? " " : "" );
7219 if( buf1[0] != NULLCHAR ) {
7220 unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
7222 if( strlen(buf1) > max_len ) {
7223 if( appData.debugMode) {
7224 fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
7226 buf1[max_len+1] = '\0';
7229 strcat( thinkOutput, buf1 );
7232 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
7233 || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7234 DisplayMove(currentMove - 1);
7238 } else if ((p=StrStr(message, "(only move)")) != NULL) {
7239 /* crafty (9.25+) says "(only move) <move>"
7240 * if there is only 1 legal move
7242 sscanf(p, "(only move) %s", buf1);
7243 sprintf(thinkOutput, "%s (only move)", buf1);
7244 sprintf(programStats.movelist, "%s (only move)", buf1);
7245 programStats.depth = 1;
7246 programStats.nr_moves = 1;
7247 programStats.moves_left = 1;
7248 programStats.nodes = 1;
7249 programStats.time = 1;
7250 programStats.got_only_move = 1;
7252 /* Not really, but we also use this member to
7253 mean "line isn't going to change" (Crafty
7254 isn't searching, so stats won't change) */
7255 programStats.line_is_book = 1;
7257 SendProgramStatsToFrontend( cps, &programStats );
7259 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
7260 gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7261 DisplayMove(currentMove - 1);
7264 } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
7265 &time, &nodes, &plylev, &mvleft,
7266 &mvtot, mvname) >= 5) {
7267 /* The stat01: line is from Crafty (9.29+) in response
7268 to the "." command */
7269 programStats.seen_stat = 1;
7270 cps->maybeThinking = TRUE;
7272 if (programStats.got_only_move || !appData.periodicUpdates)
7275 programStats.depth = plylev;
7276 programStats.time = time;
7277 programStats.nodes = nodes;
7278 programStats.moves_left = mvleft;
7279 programStats.nr_moves = mvtot;
7280 strcpy(programStats.move_name, mvname);
7281 programStats.ok_to_send = 1;
7282 programStats.movelist[0] = '\0';
7284 SendProgramStatsToFrontend( cps, &programStats );
7288 } else if (strncmp(message,"++",2) == 0) {
7289 /* Crafty 9.29+ outputs this */
7290 programStats.got_fail = 2;
7293 } else if (strncmp(message,"--",2) == 0) {
7294 /* Crafty 9.29+ outputs this */
7295 programStats.got_fail = 1;
7298 } else if (thinkOutput[0] != NULLCHAR &&
7299 strncmp(message, " ", 4) == 0) {
7300 unsigned message_len;
7303 while (*p && *p == ' ') p++;
7305 message_len = strlen( p );
7307 /* [AS] Avoid buffer overflow */
7308 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
7309 strcat(thinkOutput, " ");
7310 strcat(thinkOutput, p);
7313 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
7314 strcat(programStats.movelist, " ");
7315 strcat(programStats.movelist, p);
7318 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
7319 gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7320 DisplayMove(currentMove - 1);
7328 if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
7329 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
7331 ChessProgramStats cpstats;
7333 if (plyext != ' ' && plyext != '\t') {
7337 /* [AS] Negate score if machine is playing black and reporting absolute scores */
7338 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
7339 curscore = -curscore;
7342 cpstats.depth = plylev;
7343 cpstats.nodes = nodes;
7344 cpstats.time = time;
7345 cpstats.score = curscore;
7346 cpstats.got_only_move = 0;
7347 cpstats.movelist[0] = '\0';
7349 if (buf1[0] != NULLCHAR) {
7350 safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist) );
7353 cpstats.ok_to_send = 0;
7354 cpstats.line_is_book = 0;
7355 cpstats.nr_moves = 0;
7356 cpstats.moves_left = 0;
7358 SendProgramStatsToFrontend( cps, &cpstats );
7365 /* Parse a game score from the character string "game", and
7366 record it as the history of the current game. The game
7367 score is NOT assumed to start from the standard position.
7368 The display is not updated in any way.
7371 ParseGameHistory(game)
7375 int fromX, fromY, toX, toY, boardIndex;
7380 if (appData.debugMode)
7381 fprintf(debugFP, "Parsing game history: %s\n", game);
7383 if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
7384 gameInfo.site = StrSave(appData.icsHost);
7385 gameInfo.date = PGNDate();
7386 gameInfo.round = StrSave("-");
7388 /* Parse out names of players */
7389 while (*game == ' ') game++;
7391 while (*game != ' ') *p++ = *game++;
7393 gameInfo.white = StrSave(buf);
7394 while (*game == ' ') game++;
7396 while (*game != ' ' && *game != '\n') *p++ = *game++;
7398 gameInfo.black = StrSave(buf);
7401 boardIndex = blackPlaysFirst ? 1 : 0;
7404 yyboardindex = boardIndex;
7405 moveType = (ChessMove) yylex();
7407 case IllegalMove: /* maybe suicide chess, etc. */
7408 if (appData.debugMode) {
7409 fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
7410 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7411 setbuf(debugFP, NULL);
7413 case WhitePromotionChancellor:
7414 case BlackPromotionChancellor:
7415 case WhitePromotionArchbishop:
7416 case BlackPromotionArchbishop:
7417 case WhitePromotionQueen:
7418 case BlackPromotionQueen:
7419 case WhitePromotionRook:
7420 case BlackPromotionRook:
7421 case WhitePromotionBishop:
7422 case BlackPromotionBishop:
7423 case WhitePromotionKnight:
7424 case BlackPromotionKnight:
7425 case WhitePromotionKing:
7426 case BlackPromotionKing:
7428 case WhiteCapturesEnPassant:
7429 case BlackCapturesEnPassant:
7430 case WhiteKingSideCastle:
7431 case WhiteQueenSideCastle:
7432 case BlackKingSideCastle:
7433 case BlackQueenSideCastle:
7434 case WhiteKingSideCastleWild:
7435 case WhiteQueenSideCastleWild:
7436 case BlackKingSideCastleWild:
7437 case BlackQueenSideCastleWild:
7439 case WhiteHSideCastleFR:
7440 case WhiteASideCastleFR:
7441 case BlackHSideCastleFR:
7442 case BlackASideCastleFR:
7444 fromX = currentMoveString[0] - AAA;
7445 fromY = currentMoveString[1] - ONE;
7446 toX = currentMoveString[2] - AAA;
7447 toY = currentMoveString[3] - ONE;
7448 promoChar = currentMoveString[4];
7452 fromX = moveType == WhiteDrop ?
7453 (int) CharToPiece(ToUpper(currentMoveString[0])) :
7454 (int) CharToPiece(ToLower(currentMoveString[0]));
7456 toX = currentMoveString[2] - AAA;
7457 toY = currentMoveString[3] - ONE;
7458 promoChar = NULLCHAR;
7462 sprintf(buf, _("Ambiguous move in ICS output: \"%s\""), yy_text);
7463 if (appData.debugMode) {
7464 fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
7465 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7466 setbuf(debugFP, NULL);
7468 DisplayError(buf, 0);
7470 case ImpossibleMove:
7472 sprintf(buf, _("Illegal move in ICS output: \"%s\""), yy_text);
7473 if (appData.debugMode) {
7474 fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
7475 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7476 setbuf(debugFP, NULL);
7478 DisplayError(buf, 0);
7480 case (ChessMove) 0: /* end of file */
7481 if (boardIndex < backwardMostMove) {
7482 /* Oops, gap. How did that happen? */
7483 DisplayError(_("Gap in move list"), 0);
7486 backwardMostMove = blackPlaysFirst ? 1 : 0;
7487 if (boardIndex > forwardMostMove) {
7488 forwardMostMove = boardIndex;
7492 if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
7493 strcat(parseList[boardIndex-1], " ");
7494 strcat(parseList[boardIndex-1], yy_text);
7506 case GameUnfinished:
7507 if (gameMode == IcsExamining) {
7508 if (boardIndex < backwardMostMove) {
7509 /* Oops, gap. How did that happen? */
7512 backwardMostMove = blackPlaysFirst ? 1 : 0;
7515 gameInfo.result = moveType;
7516 p = strchr(yy_text, '{');
7517 if (p == NULL) p = strchr(yy_text, '(');
7520 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
7522 q = strchr(p, *p == '{' ? '}' : ')');
7523 if (q != NULL) *q = NULLCHAR;
7526 gameInfo.resultDetails = StrSave(p);
7529 if (boardIndex >= forwardMostMove &&
7530 !(gameMode == IcsObserving && ics_gamenum == -1)) {
7531 backwardMostMove = blackPlaysFirst ? 1 : 0;
7534 (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
7535 fromY, fromX, toY, toX, promoChar,
7536 parseList[boardIndex]);
7537 CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
7538 /* currentMoveString is set as a side-effect of yylex */
7539 strcpy(moveList[boardIndex], currentMoveString);
7540 strcat(moveList[boardIndex], "\n");
7542 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
7543 switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
7549 if(gameInfo.variant != VariantShogi)
7550 strcat(parseList[boardIndex - 1], "+");
7554 strcat(parseList[boardIndex - 1], "#");
7561 /* Apply a move to the given board */
7563 ApplyMove(fromX, fromY, toX, toY, promoChar, board)
7564 int fromX, fromY, toX, toY;
7568 ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
7569 int promoRank = gameInfo.variant == VariantMakruk ? 3 : 1;
7571 /* [HGM] compute & store e.p. status and castling rights for new position */
7572 /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
7575 if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
7576 oldEP = (signed char)board[EP_STATUS];
7577 board[EP_STATUS] = EP_NONE;
7579 if( board[toY][toX] != EmptySquare )
7580 board[EP_STATUS] = EP_CAPTURE;
7582 if( board[fromY][fromX] == WhitePawn ) {
7583 if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
7584 board[EP_STATUS] = EP_PAWN_MOVE;
7586 if(toX>BOARD_LEFT && board[toY][toX-1] == BlackPawn &&
7587 gameInfo.variant != VariantBerolina || toX < fromX)
7588 board[EP_STATUS] = toX | berolina;
7589 if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
7590 gameInfo.variant != VariantBerolina || toX > fromX)
7591 board[EP_STATUS] = toX;
7594 if( board[fromY][fromX] == BlackPawn ) {
7595 if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
7596 board[EP_STATUS] = EP_PAWN_MOVE;
7597 if( toY-fromY== -2) {
7598 if(toX>BOARD_LEFT && board[toY][toX-1] == WhitePawn &&
7599 gameInfo.variant != VariantBerolina || toX < fromX)
7600 board[EP_STATUS] = toX | berolina;
7601 if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
7602 gameInfo.variant != VariantBerolina || toX > fromX)
7603 board[EP_STATUS] = toX;
7607 for(i=0; i<nrCastlingRights; i++) {
7608 if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
7609 board[CASTLING][i] == toX && castlingRank[i] == toY
7610 ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
7615 /* [HGM] In Shatranj and Courier all promotions are to Ferz */
7616 if((gameInfo.variant==VariantShatranj || gameInfo.variant==VariantCourier || gameInfo.variant == VariantMakruk)
7617 && promoChar != 0) promoChar = PieceToChar(WhiteFerz);
7619 if (fromX == toX && fromY == toY) return;
7621 if (fromY == DROP_RANK) {
7623 piece = board[toY][toX] = (ChessSquare) fromX;
7625 piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
7626 king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
7627 if(gameInfo.variant == VariantKnightmate)
7628 king += (int) WhiteUnicorn - (int) WhiteKing;
7630 /* Code added by Tord: */
7631 /* FRC castling assumed when king captures friendly rook. */
7632 if (board[fromY][fromX] == WhiteKing &&
7633 board[toY][toX] == WhiteRook) {
7634 board[fromY][fromX] = EmptySquare;
7635 board[toY][toX] = EmptySquare;
7637 board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
7639 board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
7641 } else if (board[fromY][fromX] == BlackKing &&
7642 board[toY][toX] == BlackRook) {
7643 board[fromY][fromX] = EmptySquare;
7644 board[toY][toX] = EmptySquare;
7646 board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
7648 board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
7650 /* End of code added by Tord */
7652 } else if (board[fromY][fromX] == king
7653 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7654 && toY == fromY && toX > fromX+1) {
7655 board[fromY][fromX] = EmptySquare;
7656 board[toY][toX] = king;
7657 board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
7658 board[fromY][BOARD_RGHT-1] = EmptySquare;
7659 } else if (board[fromY][fromX] == king
7660 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7661 && toY == fromY && toX < fromX-1) {
7662 board[fromY][fromX] = EmptySquare;
7663 board[toY][toX] = king;
7664 board[toY][toX+1] = board[fromY][BOARD_LEFT];
7665 board[fromY][BOARD_LEFT] = EmptySquare;
7666 } else if (board[fromY][fromX] == WhitePawn
7667 && toY >= BOARD_HEIGHT-promoRank
7668 && gameInfo.variant != VariantXiangqi
7670 /* white pawn promotion */
7671 board[toY][toX] = CharToPiece(ToUpper(promoChar));
7672 if (board[toY][toX] == EmptySquare) {
7673 board[toY][toX] = WhiteQueen;
7675 if(gameInfo.variant==VariantBughouse ||
7676 gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
7677 board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
7678 board[fromY][fromX] = EmptySquare;
7679 } else if ((fromY == BOARD_HEIGHT-4)
7681 && gameInfo.variant != VariantXiangqi
7682 && gameInfo.variant != VariantBerolina
7683 && (board[fromY][fromX] == WhitePawn)
7684 && (board[toY][toX] == EmptySquare)) {
7685 board[fromY][fromX] = EmptySquare;
7686 board[toY][toX] = WhitePawn;
7687 captured = board[toY - 1][toX];
7688 board[toY - 1][toX] = EmptySquare;
7689 } else if ((fromY == BOARD_HEIGHT-4)
7691 && gameInfo.variant == VariantBerolina
7692 && (board[fromY][fromX] == WhitePawn)
7693 && (board[toY][toX] == EmptySquare)) {
7694 board[fromY][fromX] = EmptySquare;
7695 board[toY][toX] = WhitePawn;
7696 if(oldEP & EP_BEROLIN_A) {
7697 captured = board[fromY][fromX-1];
7698 board[fromY][fromX-1] = EmptySquare;
7699 }else{ captured = board[fromY][fromX+1];
7700 board[fromY][fromX+1] = EmptySquare;
7702 } else if (board[fromY][fromX] == king
7703 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7704 && toY == fromY && toX > fromX+1) {
7705 board[fromY][fromX] = EmptySquare;
7706 board[toY][toX] = king;
7707 board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
7708 board[fromY][BOARD_RGHT-1] = EmptySquare;
7709 } else if (board[fromY][fromX] == king
7710 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7711 && toY == fromY && toX < fromX-1) {
7712 board[fromY][fromX] = EmptySquare;
7713 board[toY][toX] = king;
7714 board[toY][toX+1] = board[fromY][BOARD_LEFT];
7715 board[fromY][BOARD_LEFT] = EmptySquare;
7716 } else if (fromY == 7 && fromX == 3
7717 && board[fromY][fromX] == BlackKing
7718 && toY == 7 && toX == 5) {
7719 board[fromY][fromX] = EmptySquare;
7720 board[toY][toX] = BlackKing;
7721 board[fromY][7] = EmptySquare;
7722 board[toY][4] = BlackRook;
7723 } else if (fromY == 7 && fromX == 3
7724 && board[fromY][fromX] == BlackKing
7725 && toY == 7 && toX == 1) {
7726 board[fromY][fromX] = EmptySquare;
7727 board[toY][toX] = BlackKing;
7728 board[fromY][0] = EmptySquare;
7729 board[toY][2] = BlackRook;
7730 } else if (board[fromY][fromX] == BlackPawn
7732 && gameInfo.variant != VariantXiangqi
7734 /* black pawn promotion */
7735 board[toY][toX] = CharToPiece(ToLower(promoChar));
7736 if (board[toY][toX] == EmptySquare) {
7737 board[toY][toX] = BlackQueen;
7739 if(gameInfo.variant==VariantBughouse ||
7740 gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
7741 board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
7742 board[fromY][fromX] = EmptySquare;
7743 } else if ((fromY == 3)
7745 && gameInfo.variant != VariantXiangqi
7746 && gameInfo.variant != VariantBerolina
7747 && (board[fromY][fromX] == BlackPawn)
7748 && (board[toY][toX] == EmptySquare)) {
7749 board[fromY][fromX] = EmptySquare;
7750 board[toY][toX] = BlackPawn;
7751 captured = board[toY + 1][toX];
7752 board[toY + 1][toX] = EmptySquare;
7753 } else if ((fromY == 3)
7755 && gameInfo.variant == VariantBerolina
7756 && (board[fromY][fromX] == BlackPawn)
7757 && (board[toY][toX] == EmptySquare)) {
7758 board[fromY][fromX] = EmptySquare;
7759 board[toY][toX] = BlackPawn;
7760 if(oldEP & EP_BEROLIN_A) {
7761 captured = board[fromY][fromX-1];
7762 board[fromY][fromX-1] = EmptySquare;
7763 }else{ captured = board[fromY][fromX+1];
7764 board[fromY][fromX+1] = EmptySquare;
7767 board[toY][toX] = board[fromY][fromX];
7768 board[fromY][fromX] = EmptySquare;
7771 /* [HGM] now we promote for Shogi, if needed */
7772 if(gameInfo.variant == VariantShogi && promoChar == 'q')
7773 board[toY][toX] = (ChessSquare) (PROMOTED piece);
7776 if (gameInfo.holdingsWidth != 0) {
7778 /* !!A lot more code needs to be written to support holdings */
7779 /* [HGM] OK, so I have written it. Holdings are stored in the */
7780 /* penultimate board files, so they are automaticlly stored */
7781 /* in the game history. */
7782 if (fromY == DROP_RANK) {
7783 /* Delete from holdings, by decreasing count */
7784 /* and erasing image if necessary */
7786 if(p < (int) BlackPawn) { /* white drop */
7787 p -= (int)WhitePawn;
7788 p = PieceToNumber((ChessSquare)p);
7789 if(p >= gameInfo.holdingsSize) p = 0;
7790 if(--board[p][BOARD_WIDTH-2] <= 0)
7791 board[p][BOARD_WIDTH-1] = EmptySquare;
7792 if((int)board[p][BOARD_WIDTH-2] < 0)
7793 board[p][BOARD_WIDTH-2] = 0;
7794 } else { /* black drop */
7795 p -= (int)BlackPawn;
7796 p = PieceToNumber((ChessSquare)p);
7797 if(p >= gameInfo.holdingsSize) p = 0;
7798 if(--board[BOARD_HEIGHT-1-p][1] <= 0)
7799 board[BOARD_HEIGHT-1-p][0] = EmptySquare;
7800 if((int)board[BOARD_HEIGHT-1-p][1] < 0)
7801 board[BOARD_HEIGHT-1-p][1] = 0;
7804 if (captured != EmptySquare && gameInfo.holdingsSize > 0
7805 && gameInfo.variant != VariantBughouse ) {
7806 /* [HGM] holdings: Add to holdings, if holdings exist */
7807 if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
7808 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
7809 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
7812 if (p >= (int) BlackPawn) {
7813 p -= (int)BlackPawn;
7814 if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
7815 /* in Shogi restore piece to its original first */
7816 captured = (ChessSquare) (DEMOTED captured);
7819 p = PieceToNumber((ChessSquare)p);
7820 if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
7821 board[p][BOARD_WIDTH-2]++;
7822 board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
7824 p -= (int)WhitePawn;
7825 if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
7826 captured = (ChessSquare) (DEMOTED captured);
7829 p = PieceToNumber((ChessSquare)p);
7830 if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
7831 board[BOARD_HEIGHT-1-p][1]++;
7832 board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
7835 } else if (gameInfo.variant == VariantAtomic) {
7836 if (captured != EmptySquare) {
7838 for (y = toY-1; y <= toY+1; y++) {
7839 for (x = toX-1; x <= toX+1; x++) {
7840 if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
7841 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
7842 board[y][x] = EmptySquare;
7846 board[toY][toX] = EmptySquare;
7849 if(gameInfo.variant == VariantShogi && promoChar != NULLCHAR && promoChar != '=') {
7850 /* [HGM] Shogi promotions */
7851 board[toY][toX] = (ChessSquare) (PROMOTED piece);
7854 if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
7855 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
7856 // [HGM] superchess: take promotion piece out of holdings
7857 int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
7858 if((int)piece < (int)BlackPawn) { // determine stm from piece color
7859 if(!--board[k][BOARD_WIDTH-2])
7860 board[k][BOARD_WIDTH-1] = EmptySquare;
7862 if(!--board[BOARD_HEIGHT-1-k][1])
7863 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
7869 /* Updates forwardMostMove */
7871 MakeMove(fromX, fromY, toX, toY, promoChar)
7872 int fromX, fromY, toX, toY;
7875 // forwardMostMove++; // [HGM] bare: moved downstream
7877 if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
7878 int timeLeft; static int lastLoadFlag=0; int king, piece;
7879 piece = boards[forwardMostMove][fromY][fromX];
7880 king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
7881 if(gameInfo.variant == VariantKnightmate)
7882 king += (int) WhiteUnicorn - (int) WhiteKing;
7883 if(forwardMostMove == 0) {
7885 fprintf(serverMoves, "%s;", second.tidy);
7886 fprintf(serverMoves, "%s;", first.tidy);
7887 if(!blackPlaysFirst)
7888 fprintf(serverMoves, "%s;", second.tidy);
7889 } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
7890 lastLoadFlag = loadFlag;
7892 fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
7893 // print castling suffix
7894 if( toY == fromY && piece == king ) {
7896 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
7898 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
7901 if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
7902 boards[forwardMostMove][fromY][fromX] == BlackPawn ) &&
7903 boards[forwardMostMove][toY][toX] == EmptySquare
7905 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
7907 if(promoChar != NULLCHAR)
7908 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);
7910 fprintf(serverMoves, "/%d/%d",
7911 pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
7912 if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
7913 else timeLeft = blackTimeRemaining/1000;
7914 fprintf(serverMoves, "/%d", timeLeft);
7916 fflush(serverMoves);
7919 if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations
7920 DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
7924 UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
7925 if (commentList[forwardMostMove+1] != NULL) {
7926 free(commentList[forwardMostMove+1]);
7927 commentList[forwardMostMove+1] = NULL;
7929 CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
7930 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
7931 forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
7932 SwitchClocks(); // uses forwardMostMove, so must be done after incrementing it !
7933 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
7934 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
7935 gameInfo.result = GameUnfinished;
7936 if (gameInfo.resultDetails != NULL) {
7937 free(gameInfo.resultDetails);
7938 gameInfo.resultDetails = NULL;
7940 CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
7941 moveList[forwardMostMove - 1]);
7942 (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
7943 PosFlags(forwardMostMove - 1),
7944 fromY, fromX, toY, toX, promoChar,
7945 parseList[forwardMostMove - 1]);
7946 switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
7952 if(gameInfo.variant != VariantShogi)
7953 strcat(parseList[forwardMostMove - 1], "+");
7957 strcat(parseList[forwardMostMove - 1], "#");
7960 if (appData.debugMode) {
7961 fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
7966 /* Updates currentMove if not pausing */
7968 ShowMove(fromX, fromY, toX, toY)
7970 int instant = (gameMode == PlayFromGameFile) ?
7971 (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
7972 if(appData.noGUI) return;
7973 if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
7975 if (forwardMostMove == currentMove + 1) {
7976 AnimateMove(boards[forwardMostMove - 1],
7977 fromX, fromY, toX, toY);
7979 if (appData.highlightLastMove) {
7980 SetHighlights(fromX, fromY, toX, toY);
7983 currentMove = forwardMostMove;
7986 if (instant) return;
7988 DisplayMove(currentMove - 1);
7989 DrawPosition(FALSE, boards[currentMove]);
7990 DisplayBothClocks();
7991 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
7994 void SendEgtPath(ChessProgramState *cps)
7995 { /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
7996 char buf[MSG_SIZ], name[MSG_SIZ], *p;
7998 if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
8001 char c, *q = name+1, *r, *s;
8003 name[0] = ','; // extract next format name from feature and copy with prefixed ','
8004 while(*p && *p != ',') *q++ = *p++;
8006 if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
8007 strcmp(name, ",nalimov:") == 0 ) {
8008 // take nalimov path from the menu-changeable option first, if it is defined
8009 sprintf(buf, "egtpath nalimov %s\n", appData.defaultPathEGTB);
8010 SendToProgram(buf,cps); // send egtbpath command for nalimov
8012 if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
8013 (s = StrStr(appData.egtFormats, name)) != NULL) {
8014 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
8015 s = r = StrStr(s, ":") + 1; // beginning of path info
8016 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
8017 c = *r; *r = 0; // temporarily null-terminate path info
8018 *--q = 0; // strip of trailig ':' from name
8019 sprintf(buf, "egtpath %s %s\n", name+1, s);
8021 SendToProgram(buf,cps); // send egtbpath command for this format
8023 if(*p == ',') p++; // read away comma to position for next format name
8028 InitChessProgram(cps, setup)
8029 ChessProgramState *cps;
8030 int setup; /* [HGM] needed to setup FRC opening position */
8032 char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
8033 if (appData.noChessProgram) return;
8034 hintRequested = FALSE;
8035 bookRequested = FALSE;
8037 /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
8038 /* moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
8039 if(cps->memSize) { /* [HGM] memory */
8040 sprintf(buf, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
8041 SendToProgram(buf, cps);
8043 SendEgtPath(cps); /* [HGM] EGT */
8044 if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
8045 sprintf(buf, "cores %d\n", appData.smpCores);
8046 SendToProgram(buf, cps);
8049 SendToProgram(cps->initString, cps);
8050 if (gameInfo.variant != VariantNormal &&
8051 gameInfo.variant != VariantLoadable
8052 /* [HGM] also send variant if board size non-standard */
8053 || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
8055 char *v = VariantName(gameInfo.variant);
8056 if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
8057 /* [HGM] in protocol 1 we have to assume all variants valid */
8058 sprintf(buf, _("Variant %s not supported by %s"), v, cps->tidy);
8059 DisplayFatalError(buf, 0, 1);
8063 /* [HGM] make prefix for non-standard board size. Awkward testing... */
8064 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8065 if( gameInfo.variant == VariantXiangqi )
8066 overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
8067 if( gameInfo.variant == VariantShogi )
8068 overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
8069 if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
8070 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
8071 if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
8072 gameInfo.variant == VariantGothic || gameInfo.variant == VariantFalcon )
8073 overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8074 if( gameInfo.variant == VariantCourier )
8075 overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8076 if( gameInfo.variant == VariantSuper )
8077 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
8078 if( gameInfo.variant == VariantGreat )
8079 overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
8082 sprintf(b, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
8083 gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
8084 /* [HGM] varsize: try first if this defiant size variant is specifically known */
8085 if(StrStr(cps->variants, b) == NULL) {
8086 // specific sized variant not known, check if general sizing allowed
8087 if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
8088 if(StrStr(cps->variants, "boardsize") == NULL) {
8089 sprintf(buf, "Board size %dx%d+%d not supported by %s",
8090 gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
8091 DisplayFatalError(buf, 0, 1);
8094 /* [HGM] here we really should compare with the maximum supported board size */
8097 } else sprintf(b, "%s", VariantName(gameInfo.variant));
8098 sprintf(buf, "variant %s\n", b);
8099 SendToProgram(buf, cps);
8101 currentlyInitializedVariant = gameInfo.variant;
8103 /* [HGM] send opening position in FRC to first engine */
8105 SendToProgram("force\n", cps);
8107 /* engine is now in force mode! Set flag to wake it up after first move. */
8108 setboardSpoiledMachineBlack = 1;
8112 snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
8113 SendToProgram(buf, cps);
8115 cps->maybeThinking = FALSE;
8116 cps->offeredDraw = 0;
8117 if (!appData.icsActive) {
8118 SendTimeControl(cps, movesPerSession, timeControl,
8119 timeIncrement, appData.searchDepth,
8122 if (appData.showThinking
8123 // [HGM] thinking: four options require thinking output to be sent
8124 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
8126 SendToProgram("post\n", cps);
8128 SendToProgram("hard\n", cps);
8129 if (!appData.ponderNextMove) {
8130 /* Warning: "easy" is a toggle in GNU Chess, so don't send
8131 it without being sure what state we are in first. "hard"
8132 is not a toggle, so that one is OK.
8134 SendToProgram("easy\n", cps);
8137 sprintf(buf, "ping %d\n", ++cps->lastPing);
8138 SendToProgram(buf, cps);
8140 cps->initDone = TRUE;
8145 StartChessProgram(cps)
8146 ChessProgramState *cps;
8151 if (appData.noChessProgram) return;
8152 cps->initDone = FALSE;
8154 if (strcmp(cps->host, "localhost") == 0) {
8155 err = StartChildProcess(cps->program, cps->dir, &cps->pr);
8156 } else if (*appData.remoteShell == NULLCHAR) {
8157 err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
8159 if (*appData.remoteUser == NULLCHAR) {
8160 snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
8163 snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
8164 cps->host, appData.remoteUser, cps->program);
8166 err = StartChildProcess(buf, "", &cps->pr);
8170 sprintf(buf, _("Startup failure on '%s'"), cps->program);
8171 DisplayFatalError(buf, err, 1);
8177 cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
8178 if (cps->protocolVersion > 1) {
8179 sprintf(buf, "xboard\nprotover %d\n", cps->protocolVersion);
8180 cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
8181 cps->comboCnt = 0; // and values of combo boxes
8182 SendToProgram(buf, cps);
8184 SendToProgram("xboard\n", cps);
8190 TwoMachinesEventIfReady P((void))
8192 if (first.lastPing != first.lastPong) {
8193 DisplayMessage("", _("Waiting for first chess program"));
8194 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
8197 if (second.lastPing != second.lastPong) {
8198 DisplayMessage("", _("Waiting for second chess program"));
8199 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
8207 NextMatchGame P((void))
8209 int index; /* [HGM] autoinc: step load index during match */
8211 if (*appData.loadGameFile != NULLCHAR) {
8212 index = appData.loadGameIndex;
8213 if(index < 0) { // [HGM] autoinc
8214 lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
8215 if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
8217 LoadGameFromFile(appData.loadGameFile,
8219 appData.loadGameFile, FALSE);
8220 } else if (*appData.loadPositionFile != NULLCHAR) {
8221 index = appData.loadPositionIndex;
8222 if(index < 0) { // [HGM] autoinc
8223 lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
8224 if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
8226 LoadPositionFromFile(appData.loadPositionFile,
8228 appData.loadPositionFile);
8230 TwoMachinesEventIfReady();
8233 void UserAdjudicationEvent( int result )
8235 ChessMove gameResult = GameIsDrawn;
8238 gameResult = WhiteWins;
8240 else if( result < 0 ) {
8241 gameResult = BlackWins;
8244 if( gameMode == TwoMachinesPlay ) {
8245 GameEnds( gameResult, "User adjudication", GE_XBOARD );
8250 // [HGM] save: calculate checksum of game to make games easily identifiable
8251 int StringCheckSum(char *s)
8254 if(s==NULL) return 0;
8255 while(*s) i = i*259 + *s++;
8262 for(i=backwardMostMove; i<forwardMostMove; i++) {
8263 sum += pvInfoList[i].depth;
8264 sum += StringCheckSum(parseList[i]);
8265 sum += StringCheckSum(commentList[i]);
8268 if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
8269 return sum + StringCheckSum(commentList[i]);
8270 } // end of save patch
8273 GameEnds(result, resultDetails, whosays)
8275 char *resultDetails;
8278 GameMode nextGameMode;
8282 if(endingGame) return; /* [HGM] crash: forbid recursion */
8285 if (appData.debugMode) {
8286 fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
8287 result, resultDetails ? resultDetails : "(null)", whosays);
8290 if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
8291 /* If we are playing on ICS, the server decides when the
8292 game is over, but the engine can offer to draw, claim
8296 if (appData.zippyPlay && first.initDone) {
8297 if (result == GameIsDrawn) {
8298 /* In case draw still needs to be claimed */
8299 SendToICS(ics_prefix);
8300 SendToICS("draw\n");
8301 } else if (StrCaseStr(resultDetails, "resign")) {
8302 SendToICS(ics_prefix);
8303 SendToICS("resign\n");
8307 endingGame = 0; /* [HGM] crash */
8311 /* If we're loading the game from a file, stop */
8312 if (whosays == GE_FILE) {
8313 (void) StopLoadGameTimer();
8317 /* Cancel draw offers */
8318 first.offeredDraw = second.offeredDraw = 0;
8320 /* If this is an ICS game, only ICS can really say it's done;
8321 if not, anyone can. */
8322 isIcsGame = (gameMode == IcsPlayingWhite ||
8323 gameMode == IcsPlayingBlack ||
8324 gameMode == IcsObserving ||
8325 gameMode == IcsExamining);
8327 if (!isIcsGame || whosays == GE_ICS) {
8328 /* OK -- not an ICS game, or ICS said it was done */
8330 if (!isIcsGame && !appData.noChessProgram)
8331 SetUserThinkingEnables();
8333 /* [HGM] if a machine claims the game end we verify this claim */
8334 if(gameMode == TwoMachinesPlay && appData.testClaims) {
8335 if(appData.testLegality && whosays >= GE_ENGINE1 ) {
8337 ChessMove trueResult = (ChessMove) -1;
8339 claimer = whosays == GE_ENGINE1 ? /* color of claimer */
8340 first.twoMachinesColor[0] :
8341 second.twoMachinesColor[0] ;
8343 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
8344 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
8345 /* [HGM] verify: engine mate claims accepted if they were flagged */
8346 trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
8348 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
8349 /* [HGM] verify: engine mate claims accepted if they were flagged */
8350 trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8352 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
8353 trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
8356 // now verify win claims, but not in drop games, as we don't understand those yet
8357 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
8358 || gameInfo.variant == VariantGreat) &&
8359 (result == WhiteWins && claimer == 'w' ||
8360 result == BlackWins && claimer == 'b' ) ) { // case to verify: engine claims own win
8361 if (appData.debugMode) {
8362 fprintf(debugFP, "result=%d sp=%d move=%d\n",
8363 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
8365 if(result != trueResult) {
8366 sprintf(buf, "False win claim: '%s'", resultDetails);
8367 result = claimer == 'w' ? BlackWins : WhiteWins;
8368 resultDetails = buf;
8371 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
8372 && (forwardMostMove <= backwardMostMove ||
8373 (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
8374 (claimer=='b')==(forwardMostMove&1))
8376 /* [HGM] verify: draws that were not flagged are false claims */
8377 sprintf(buf, "False draw claim: '%s'", resultDetails);
8378 result = claimer == 'w' ? BlackWins : WhiteWins;
8379 resultDetails = buf;
8381 /* (Claiming a loss is accepted no questions asked!) */
8383 /* [HGM] bare: don't allow bare King to win */
8384 if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
8385 && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
8386 && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
8387 && result != GameIsDrawn)
8388 { int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
8389 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
8390 int p = (signed char)boards[forwardMostMove][i][j] - color;
8391 if(p >= 0 && p <= (int)WhiteKing) k++;
8393 if (appData.debugMode) {
8394 fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
8395 result, resultDetails ? resultDetails : "(null)", whosays, k, color);
8398 result = GameIsDrawn;
8399 sprintf(buf, "%s but bare king", resultDetails);
8400 resultDetails = buf;
8406 if(serverMoves != NULL && !loadFlag) { char c = '=';
8407 if(result==WhiteWins) c = '+';
8408 if(result==BlackWins) c = '-';
8409 if(resultDetails != NULL)
8410 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
8412 if (resultDetails != NULL) {
8413 gameInfo.result = result;
8414 gameInfo.resultDetails = StrSave(resultDetails);
8416 /* display last move only if game was not loaded from file */
8417 if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
8418 DisplayMove(currentMove - 1);
8420 if (forwardMostMove != 0) {
8421 if (gameMode != PlayFromGameFile && gameMode != EditGame
8422 && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
8424 if (*appData.saveGameFile != NULLCHAR) {
8425 SaveGameToFile(appData.saveGameFile, TRUE);
8426 } else if (appData.autoSaveGames) {
8429 if (*appData.savePositionFile != NULLCHAR) {
8430 SavePositionToFile(appData.savePositionFile);
8435 /* Tell program how game ended in case it is learning */
8436 /* [HGM] Moved this to after saving the PGN, just in case */
8437 /* engine died and we got here through time loss. In that */
8438 /* case we will get a fatal error writing the pipe, which */
8439 /* would otherwise lose us the PGN. */
8440 /* [HGM] crash: not needed anymore, but doesn't hurt; */
8441 /* output during GameEnds should never be fatal anymore */
8442 if (gameMode == MachinePlaysWhite ||
8443 gameMode == MachinePlaysBlack ||
8444 gameMode == TwoMachinesPlay ||
8445 gameMode == IcsPlayingWhite ||
8446 gameMode == IcsPlayingBlack ||
8447 gameMode == BeginningOfGame) {
8449 sprintf(buf, "result %s {%s}\n", PGNResult(result),
8451 if (first.pr != NoProc) {
8452 SendToProgram(buf, &first);
8454 if (second.pr != NoProc &&
8455 gameMode == TwoMachinesPlay) {
8456 SendToProgram(buf, &second);
8461 if (appData.icsActive) {
8462 if (appData.quietPlay &&
8463 (gameMode == IcsPlayingWhite ||
8464 gameMode == IcsPlayingBlack)) {
8465 SendToICS(ics_prefix);
8466 SendToICS("set shout 1\n");
8468 nextGameMode = IcsIdle;
8469 ics_user_moved = FALSE;
8470 /* clean up premove. It's ugly when the game has ended and the
8471 * premove highlights are still on the board.
8475 ClearPremoveHighlights();
8476 DrawPosition(FALSE, boards[currentMove]);
8478 if (whosays == GE_ICS) {
8481 if (gameMode == IcsPlayingWhite)
8483 else if(gameMode == IcsPlayingBlack)
8487 if (gameMode == IcsPlayingBlack)
8489 else if(gameMode == IcsPlayingWhite)
8496 PlayIcsUnfinishedSound();
8499 } else if (gameMode == EditGame ||
8500 gameMode == PlayFromGameFile ||
8501 gameMode == AnalyzeMode ||
8502 gameMode == AnalyzeFile) {
8503 nextGameMode = gameMode;
8505 nextGameMode = EndOfGame;
8510 nextGameMode = gameMode;
8513 if (appData.noChessProgram) {
8514 gameMode = nextGameMode;
8516 endingGame = 0; /* [HGM] crash */
8521 /* Put first chess program into idle state */
8522 if (first.pr != NoProc &&
8523 (gameMode == MachinePlaysWhite ||
8524 gameMode == MachinePlaysBlack ||
8525 gameMode == TwoMachinesPlay ||
8526 gameMode == IcsPlayingWhite ||
8527 gameMode == IcsPlayingBlack ||
8528 gameMode == BeginningOfGame)) {
8529 SendToProgram("force\n", &first);
8530 if (first.usePing) {
8532 sprintf(buf, "ping %d\n", ++first.lastPing);
8533 SendToProgram(buf, &first);
8536 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
8537 /* Kill off first chess program */
8538 if (first.isr != NULL)
8539 RemoveInputSource(first.isr);
8542 if (first.pr != NoProc) {
8544 DoSleep( appData.delayBeforeQuit );
8545 SendToProgram("quit\n", &first);
8546 DoSleep( appData.delayAfterQuit );
8547 DestroyChildProcess(first.pr, first.useSigterm);
8552 /* Put second chess program into idle state */
8553 if (second.pr != NoProc &&
8554 gameMode == TwoMachinesPlay) {
8555 SendToProgram("force\n", &second);
8556 if (second.usePing) {
8558 sprintf(buf, "ping %d\n", ++second.lastPing);
8559 SendToProgram(buf, &second);
8562 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
8563 /* Kill off second chess program */
8564 if (second.isr != NULL)
8565 RemoveInputSource(second.isr);
8568 if (second.pr != NoProc) {
8569 DoSleep( appData.delayBeforeQuit );
8570 SendToProgram("quit\n", &second);
8571 DoSleep( appData.delayAfterQuit );
8572 DestroyChildProcess(second.pr, second.useSigterm);
8577 if (matchMode && gameMode == TwoMachinesPlay) {
8580 if (first.twoMachinesColor[0] == 'w') {
8587 if (first.twoMachinesColor[0] == 'b') {
8596 if (matchGame < appData.matchGames) {
8598 if(appData.sameColorGames <= 1) { /* [HGM] alternate: suppress color swap */
8599 tmp = first.twoMachinesColor;
8600 first.twoMachinesColor = second.twoMachinesColor;
8601 second.twoMachinesColor = tmp;
8603 gameMode = nextGameMode;
8605 if(appData.matchPause>10000 || appData.matchPause<10)
8606 appData.matchPause = 10000; /* [HGM] make pause adjustable */
8607 ScheduleDelayedEvent(NextMatchGame, appData.matchPause);
8608 endingGame = 0; /* [HGM] crash */
8612 gameMode = nextGameMode;
8613 sprintf(buf, _("Match %s vs. %s: final score %d-%d-%d"),
8614 first.tidy, second.tidy,
8615 first.matchWins, second.matchWins,
8616 appData.matchGames - (first.matchWins + second.matchWins));
8617 DisplayFatalError(buf, 0, 0);
8620 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
8621 !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
8623 gameMode = nextGameMode;
8625 endingGame = 0; /* [HGM] crash */
8628 /* Assumes program was just initialized (initString sent).
8629 Leaves program in force mode. */
8631 FeedMovesToProgram(cps, upto)
8632 ChessProgramState *cps;
8637 if (appData.debugMode)
8638 fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
8639 startedFromSetupPosition ? "position and " : "",
8640 backwardMostMove, upto, cps->which);
8641 if(currentlyInitializedVariant != gameInfo.variant) { char buf[MSG_SIZ];
8642 // [HGM] variantswitch: make engine aware of new variant
8643 if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
8644 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
8645 sprintf(buf, "variant %s\n", VariantName(gameInfo.variant));
8646 SendToProgram(buf, cps);
8647 currentlyInitializedVariant = gameInfo.variant;
8649 SendToProgram("force\n", cps);
8650 if (startedFromSetupPosition) {
8651 SendBoard(cps, backwardMostMove);
8652 if (appData.debugMode) {
8653 fprintf(debugFP, "feedMoves\n");
8656 for (i = backwardMostMove; i < upto; i++) {
8657 SendMoveToProgram(i, cps);
8663 ResurrectChessProgram()
8665 /* The chess program may have exited.
8666 If so, restart it and feed it all the moves made so far. */
8668 if (appData.noChessProgram || first.pr != NoProc) return;
8670 StartChessProgram(&first);
8671 InitChessProgram(&first, FALSE);
8672 FeedMovesToProgram(&first, currentMove);
8674 if (!first.sendTime) {
8675 /* can't tell gnuchess what its clock should read,
8676 so we bow to its notion. */
8678 timeRemaining[0][currentMove] = whiteTimeRemaining;
8679 timeRemaining[1][currentMove] = blackTimeRemaining;
8682 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
8683 appData.icsEngineAnalyze) && first.analysisSupport) {
8684 SendToProgram("analyze\n", &first);
8685 first.analyzing = TRUE;
8698 if (appData.debugMode) {
8699 fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
8700 redraw, init, gameMode);
8702 CleanupTail(); // [HGM] vari: delete any stored variations
8703 pausing = pauseExamInvalid = FALSE;
8704 startedFromSetupPosition = blackPlaysFirst = FALSE;
8706 whiteFlag = blackFlag = FALSE;
8707 userOfferedDraw = FALSE;
8708 hintRequested = bookRequested = FALSE;
8709 first.maybeThinking = FALSE;
8710 second.maybeThinking = FALSE;
8711 first.bookSuspend = FALSE; // [HGM] book
8712 second.bookSuspend = FALSE;
8713 thinkOutput[0] = NULLCHAR;
8714 lastHint[0] = NULLCHAR;
8715 ClearGameInfo(&gameInfo);
8716 gameInfo.variant = StringToVariant(appData.variant);
8717 ics_user_moved = ics_clock_paused = FALSE;
8718 ics_getting_history = H_FALSE;
8720 white_holding[0] = black_holding[0] = NULLCHAR;
8721 ClearProgramStats();
8722 opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
8726 flipView = appData.flipView;
8727 ClearPremoveHighlights();
8729 alarmSounded = FALSE;
8731 GameEnds((ChessMove) 0, NULL, GE_PLAYER);
8732 if(appData.serverMovesName != NULL) {
8733 /* [HGM] prepare to make moves file for broadcasting */
8734 clock_t t = clock();
8735 if(serverMoves != NULL) fclose(serverMoves);
8736 serverMoves = fopen(appData.serverMovesName, "r");
8737 if(serverMoves != NULL) {
8738 fclose(serverMoves);
8739 /* delay 15 sec before overwriting, so all clients can see end */
8740 while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
8742 serverMoves = fopen(appData.serverMovesName, "w");
8746 gameMode = BeginningOfGame;
8748 if(appData.icsActive) gameInfo.variant = VariantNormal;
8749 currentMove = forwardMostMove = backwardMostMove = 0;
8750 InitPosition(redraw);
8751 for (i = 0; i < MAX_MOVES; i++) {
8752 if (commentList[i] != NULL) {
8753 free(commentList[i]);
8754 commentList[i] = NULL;
8758 timeRemaining[0][0] = whiteTimeRemaining;
8759 timeRemaining[1][0] = blackTimeRemaining;
8760 if (first.pr == NULL) {
8761 StartChessProgram(&first);
8764 InitChessProgram(&first, startedFromSetupPosition);
8767 DisplayMessage("", "");
8768 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
8769 lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
8776 if (!AutoPlayOneMove())
8778 if (matchMode || appData.timeDelay == 0)
8780 if (appData.timeDelay < 0 || gameMode == AnalyzeFile)
8782 StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
8791 int fromX, fromY, toX, toY;
8793 if (appData.debugMode) {
8794 fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
8797 if (gameMode != PlayFromGameFile)
8800 if (currentMove >= forwardMostMove) {
8801 gameMode = EditGame;
8804 /* [AS] Clear current move marker at the end of a game */
8805 /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
8810 toX = moveList[currentMove][2] - AAA;
8811 toY = moveList[currentMove][3] - ONE;
8813 if (moveList[currentMove][1] == '@') {
8814 if (appData.highlightLastMove) {
8815 SetHighlights(-1, -1, toX, toY);
8818 fromX = moveList[currentMove][0] - AAA;
8819 fromY = moveList[currentMove][1] - ONE;
8821 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
8823 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
8825 if (appData.highlightLastMove) {
8826 SetHighlights(fromX, fromY, toX, toY);
8829 DisplayMove(currentMove);
8830 SendMoveToProgram(currentMove++, &first);
8831 DisplayBothClocks();
8832 DrawPosition(FALSE, boards[currentMove]);
8833 // [HGM] PV info: always display, routine tests if empty
8834 DisplayComment(currentMove - 1, commentList[currentMove]);
8840 LoadGameOneMove(readAhead)
8841 ChessMove readAhead;
8843 int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
8844 char promoChar = NULLCHAR;
8849 if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
8850 gameMode != AnalyzeMode && gameMode != Training) {
8855 yyboardindex = forwardMostMove;
8856 if (readAhead != (ChessMove)0) {
8857 moveType = readAhead;
8859 if (gameFileFP == NULL)
8861 moveType = (ChessMove) yylex();
8867 if (appData.debugMode)
8868 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
8871 /* append the comment but don't display it */
8872 AppendComment(currentMove, p, FALSE);
8875 case WhiteCapturesEnPassant:
8876 case BlackCapturesEnPassant:
8877 case WhitePromotionChancellor:
8878 case BlackPromotionChancellor:
8879 case WhitePromotionArchbishop:
8880 case BlackPromotionArchbishop:
8881 case WhitePromotionCentaur:
8882 case BlackPromotionCentaur:
8883 case WhitePromotionQueen:
8884 case BlackPromotionQueen:
8885 case WhitePromotionRook:
8886 case BlackPromotionRook:
8887 case WhitePromotionBishop:
8888 case BlackPromotionBishop:
8889 case WhitePromotionKnight:
8890 case BlackPromotionKnight:
8891 case WhitePromotionKing:
8892 case BlackPromotionKing:
8894 case WhiteKingSideCastle:
8895 case WhiteQueenSideCastle:
8896 case BlackKingSideCastle:
8897 case BlackQueenSideCastle:
8898 case WhiteKingSideCastleWild:
8899 case WhiteQueenSideCastleWild:
8900 case BlackKingSideCastleWild:
8901 case BlackQueenSideCastleWild:
8903 case WhiteHSideCastleFR:
8904 case WhiteASideCastleFR:
8905 case BlackHSideCastleFR:
8906 case BlackASideCastleFR:
8908 if (appData.debugMode)
8909 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
8910 fromX = currentMoveString[0] - AAA;
8911 fromY = currentMoveString[1] - ONE;
8912 toX = currentMoveString[2] - AAA;
8913 toY = currentMoveString[3] - ONE;
8914 promoChar = currentMoveString[4];
8919 if (appData.debugMode)
8920 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
8921 fromX = moveType == WhiteDrop ?
8922 (int) CharToPiece(ToUpper(currentMoveString[0])) :
8923 (int) CharToPiece(ToLower(currentMoveString[0]));
8925 toX = currentMoveString[2] - AAA;
8926 toY = currentMoveString[3] - ONE;
8932 case GameUnfinished:
8933 if (appData.debugMode)
8934 fprintf(debugFP, "Parsed game end: %s\n", yy_text);
8935 p = strchr(yy_text, '{');
8936 if (p == NULL) p = strchr(yy_text, '(');
8939 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
8941 q = strchr(p, *p == '{' ? '}' : ')');
8942 if (q != NULL) *q = NULLCHAR;
8945 GameEnds(moveType, p, GE_FILE);
8947 if (cmailMsgLoaded) {
8949 flipView = WhiteOnMove(currentMove);
8950 if (moveType == GameUnfinished) flipView = !flipView;
8951 if (appData.debugMode)
8952 fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
8956 case (ChessMove) 0: /* end of file */
8957 if (appData.debugMode)
8958 fprintf(debugFP, "Parser hit end of file\n");
8959 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
8965 if (WhiteOnMove(currentMove)) {
8966 GameEnds(BlackWins, "Black mates", GE_FILE);
8968 GameEnds(WhiteWins, "White mates", GE_FILE);
8972 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
8979 if (lastLoadGameStart == GNUChessGame) {
8980 /* GNUChessGames have numbers, but they aren't move numbers */
8981 if (appData.debugMode)
8982 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
8983 yy_text, (int) moveType);
8984 return LoadGameOneMove((ChessMove)0); /* tail recursion */
8986 /* else fall thru */
8991 /* Reached start of next game in file */
8992 if (appData.debugMode)
8993 fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
8994 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9000 if (WhiteOnMove(currentMove)) {
9001 GameEnds(BlackWins, "Black mates", GE_FILE);
9003 GameEnds(WhiteWins, "White mates", GE_FILE);
9007 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
9013 case PositionDiagram: /* should not happen; ignore */
9014 case ElapsedTime: /* ignore */
9015 case NAG: /* ignore */
9016 if (appData.debugMode)
9017 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
9018 yy_text, (int) moveType);
9019 return LoadGameOneMove((ChessMove)0); /* tail recursion */
9022 if (appData.testLegality) {
9023 if (appData.debugMode)
9024 fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
9025 sprintf(move, _("Illegal move: %d.%s%s"),
9026 (forwardMostMove / 2) + 1,
9027 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9028 DisplayError(move, 0);
9031 if (appData.debugMode)
9032 fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
9033 yy_text, currentMoveString);
9034 fromX = currentMoveString[0] - AAA;
9035 fromY = currentMoveString[1] - ONE;
9036 toX = currentMoveString[2] - AAA;
9037 toY = currentMoveString[3] - ONE;
9038 promoChar = currentMoveString[4];
9043 if (appData.debugMode)
9044 fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
9045 sprintf(move, _("Ambiguous move: %d.%s%s"),
9046 (forwardMostMove / 2) + 1,
9047 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9048 DisplayError(move, 0);
9053 case ImpossibleMove:
9054 if (appData.debugMode)
9055 fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
9056 sprintf(move, _("Illegal move: %d.%s%s"),
9057 (forwardMostMove / 2) + 1,
9058 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9059 DisplayError(move, 0);
9065 if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
9066 DrawPosition(FALSE, boards[currentMove]);
9067 DisplayBothClocks();
9068 if (!appData.matchMode) // [HGM] PV info: routine tests if empty
9069 DisplayComment(currentMove - 1, commentList[currentMove]);
9071 (void) StopLoadGameTimer();
9073 cmailOldMove = forwardMostMove;
9076 /* currentMoveString is set as a side-effect of yylex */
9077 strcat(currentMoveString, "\n");
9078 strcpy(moveList[forwardMostMove], currentMoveString);
9080 thinkOutput[0] = NULLCHAR;
9081 MakeMove(fromX, fromY, toX, toY, promoChar);
9082 currentMove = forwardMostMove;
9087 /* Load the nth game from the given file */
9089 LoadGameFromFile(filename, n, title, useList)
9093 /*Boolean*/ int useList;
9098 if (strcmp(filename, "-") == 0) {
9102 f = fopen(filename, "rb");
9104 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9105 DisplayError(buf, errno);
9109 if (fseek(f, 0, 0) == -1) {
9110 /* f is not seekable; probably a pipe */
9113 if (useList && n == 0) {
9114 int error = GameListBuild(f);
9116 DisplayError(_("Cannot build game list"), error);
9117 } else if (!ListEmpty(&gameList) &&
9118 ((ListGame *) gameList.tailPred)->number > 1) {
9119 GameListPopUp(f, title);
9126 return LoadGame(f, n, title, FALSE);
9131 MakeRegisteredMove()
9133 int fromX, fromY, toX, toY;
9135 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
9136 switch (cmailMoveType[lastLoadGameNumber - 1]) {
9139 if (appData.debugMode)
9140 fprintf(debugFP, "Restoring %s for game %d\n",
9141 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
9143 thinkOutput[0] = NULLCHAR;
9144 strcpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1]);
9145 fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
9146 fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
9147 toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
9148 toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
9149 promoChar = cmailMove[lastLoadGameNumber - 1][4];
9150 MakeMove(fromX, fromY, toX, toY, promoChar);
9151 ShowMove(fromX, fromY, toX, toY);
9153 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9160 if (WhiteOnMove(currentMove)) {
9161 GameEnds(BlackWins, "Black mates", GE_PLAYER);
9163 GameEnds(WhiteWins, "White mates", GE_PLAYER);
9168 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
9175 if (WhiteOnMove(currentMove)) {
9176 GameEnds(BlackWins, "White resigns", GE_PLAYER);
9178 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
9183 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
9194 /* Wrapper around LoadGame for use when a Cmail message is loaded */
9196 CmailLoadGame(f, gameNumber, title, useList)
9204 if (gameNumber > nCmailGames) {
9205 DisplayError(_("No more games in this message"), 0);
9208 if (f == lastLoadGameFP) {
9209 int offset = gameNumber - lastLoadGameNumber;
9211 cmailMsg[0] = NULLCHAR;
9212 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
9213 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
9214 nCmailMovesRegistered--;
9216 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
9217 if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
9218 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
9221 if (! RegisterMove()) return FALSE;
9225 retVal = LoadGame(f, gameNumber, title, useList);
9227 /* Make move registered during previous look at this game, if any */
9228 MakeRegisteredMove();
9230 if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
9231 commentList[currentMove]
9232 = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
9233 DisplayComment(currentMove - 1, commentList[currentMove]);
9239 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
9244 int gameNumber = lastLoadGameNumber + offset;
9245 if (lastLoadGameFP == NULL) {
9246 DisplayError(_("No game has been loaded yet"), 0);
9249 if (gameNumber <= 0) {
9250 DisplayError(_("Can't back up any further"), 0);
9253 if (cmailMsgLoaded) {
9254 return CmailLoadGame(lastLoadGameFP, gameNumber,
9255 lastLoadGameTitle, lastLoadGameUseList);
9257 return LoadGame(lastLoadGameFP, gameNumber,
9258 lastLoadGameTitle, lastLoadGameUseList);
9264 /* Load the nth game from open file f */
9266 LoadGame(f, gameNumber, title, useList)
9274 int gn = gameNumber;
9275 ListGame *lg = NULL;
9278 GameMode oldGameMode;
9279 VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
9281 if (appData.debugMode)
9282 fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
9284 if (gameMode == Training )
9285 SetTrainingModeOff();
9287 oldGameMode = gameMode;
9288 if (gameMode != BeginningOfGame) {
9293 if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
9294 fclose(lastLoadGameFP);
9298 lg = (ListGame *) ListElem(&gameList, gameNumber-1);
9301 fseek(f, lg->offset, 0);
9302 GameListHighlight(gameNumber);
9306 DisplayError(_("Game number out of range"), 0);
9311 if (fseek(f, 0, 0) == -1) {
9312 if (f == lastLoadGameFP ?
9313 gameNumber == lastLoadGameNumber + 1 :
9317 DisplayError(_("Can't seek on game file"), 0);
9323 lastLoadGameNumber = gameNumber;
9324 strcpy(lastLoadGameTitle, title);
9325 lastLoadGameUseList = useList;
9329 if (lg && lg->gameInfo.white && lg->gameInfo.black) {
9330 snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
9331 lg->gameInfo.black);
9333 } else if (*title != NULLCHAR) {
9334 if (gameNumber > 1) {
9335 sprintf(buf, "%s %d", title, gameNumber);
9338 DisplayTitle(title);
9342 if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
9343 gameMode = PlayFromGameFile;
9347 currentMove = forwardMostMove = backwardMostMove = 0;
9348 CopyBoard(boards[0], initialPosition);
9352 * Skip the first gn-1 games in the file.
9353 * Also skip over anything that precedes an identifiable
9354 * start of game marker, to avoid being confused by
9355 * garbage at the start of the file. Currently
9356 * recognized start of game markers are the move number "1",
9357 * the pattern "gnuchess .* game", the pattern
9358 * "^[#;%] [^ ]* game file", and a PGN tag block.
9359 * A game that starts with one of the latter two patterns
9360 * will also have a move number 1, possibly
9361 * following a position diagram.
9362 * 5-4-02: Let's try being more lenient and allowing a game to
9363 * start with an unnumbered move. Does that break anything?
9365 cm = lastLoadGameStart = (ChessMove) 0;
9367 yyboardindex = forwardMostMove;
9368 cm = (ChessMove) yylex();
9371 if (cmailMsgLoaded) {
9372 nCmailGames = CMAIL_MAX_GAMES - gn;
9375 DisplayError(_("Game not found in file"), 0);
9382 lastLoadGameStart = cm;
9386 switch (lastLoadGameStart) {
9393 gn--; /* count this game */
9394 lastLoadGameStart = cm;
9403 switch (lastLoadGameStart) {
9408 gn--; /* count this game */
9409 lastLoadGameStart = cm;
9412 lastLoadGameStart = cm; /* game counted already */
9420 yyboardindex = forwardMostMove;
9421 cm = (ChessMove) yylex();
9422 } while (cm == PGNTag || cm == Comment);
9429 if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
9430 if ( cmailResult[CMAIL_MAX_GAMES - gn - 1]
9431 != CMAIL_OLD_RESULT) {
9433 cmailResult[ CMAIL_MAX_GAMES
9434 - gn - 1] = CMAIL_OLD_RESULT;
9440 /* Only a NormalMove can be at the start of a game
9441 * without a position diagram. */
9442 if (lastLoadGameStart == (ChessMove) 0) {
9444 lastLoadGameStart = MoveNumberOne;
9453 if (appData.debugMode)
9454 fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
9456 if (cm == XBoardGame) {
9457 /* Skip any header junk before position diagram and/or move 1 */
9459 yyboardindex = forwardMostMove;
9460 cm = (ChessMove) yylex();
9462 if (cm == (ChessMove) 0 ||
9463 cm == GNUChessGame || cm == XBoardGame) {
9464 /* Empty game; pretend end-of-file and handle later */
9469 if (cm == MoveNumberOne || cm == PositionDiagram ||
9470 cm == PGNTag || cm == Comment)
9473 } else if (cm == GNUChessGame) {
9474 if (gameInfo.event != NULL) {
9475 free(gameInfo.event);
9477 gameInfo.event = StrSave(yy_text);
9480 startedFromSetupPosition = FALSE;
9481 while (cm == PGNTag) {
9482 if (appData.debugMode)
9483 fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
9484 err = ParsePGNTag(yy_text, &gameInfo);
9485 if (!err) numPGNTags++;
9487 /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
9488 if(gameInfo.variant != oldVariant) {
9489 startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
9491 oldVariant = gameInfo.variant;
9492 if (appData.debugMode)
9493 fprintf(debugFP, "New variant %d\n", (int) oldVariant);
9497 if (gameInfo.fen != NULL) {
9498 Board initial_position;
9499 startedFromSetupPosition = TRUE;
9500 if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
9502 DisplayError(_("Bad FEN position in file"), 0);
9505 CopyBoard(boards[0], initial_position);
9506 if (blackPlaysFirst) {
9507 currentMove = forwardMostMove = backwardMostMove = 1;
9508 CopyBoard(boards[1], initial_position);
9509 strcpy(moveList[0], "");
9510 strcpy(parseList[0], "");
9511 timeRemaining[0][1] = whiteTimeRemaining;
9512 timeRemaining[1][1] = blackTimeRemaining;
9513 if (commentList[0] != NULL) {
9514 commentList[1] = commentList[0];
9515 commentList[0] = NULL;
9518 currentMove = forwardMostMove = backwardMostMove = 0;
9520 /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
9522 initialRulePlies = FENrulePlies;
9523 for( i=0; i< nrCastlingRights; i++ )
9524 initialRights[i] = initial_position[CASTLING][i];
9526 yyboardindex = forwardMostMove;
9528 gameInfo.fen = NULL;
9531 yyboardindex = forwardMostMove;
9532 cm = (ChessMove) yylex();
9534 /* Handle comments interspersed among the tags */
9535 while (cm == Comment) {
9537 if (appData.debugMode)
9538 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9540 AppendComment(currentMove, p, FALSE);
9541 yyboardindex = forwardMostMove;
9542 cm = (ChessMove) yylex();
9546 /* don't rely on existence of Event tag since if game was
9547 * pasted from clipboard the Event tag may not exist
9549 if (numPGNTags > 0){
9551 if (gameInfo.variant == VariantNormal) {
9552 gameInfo.variant = StringToVariant(gameInfo.event);
9555 if( appData.autoDisplayTags ) {
9556 tags = PGNTags(&gameInfo);
9557 TagsPopUp(tags, CmailMsg());
9562 /* Make something up, but don't display it now */
9567 if (cm == PositionDiagram) {
9570 Board initial_position;
9572 if (appData.debugMode)
9573 fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
9575 if (!startedFromSetupPosition) {
9577 for (i = BOARD_HEIGHT - 1; i >= 0; i--)
9578 for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
9588 initial_position[i][j++] = CharToPiece(*p);
9591 while (*p == ' ' || *p == '\t' ||
9592 *p == '\n' || *p == '\r') p++;
9594 if (strncmp(p, "black", strlen("black"))==0)
9595 blackPlaysFirst = TRUE;
9597 blackPlaysFirst = FALSE;
9598 startedFromSetupPosition = TRUE;
9600 CopyBoard(boards[0], initial_position);
9601 if (blackPlaysFirst) {
9602 currentMove = forwardMostMove = backwardMostMove = 1;
9603 CopyBoard(boards[1], initial_position);
9604 strcpy(moveList[0], "");
9605 strcpy(parseList[0], "");
9606 timeRemaining[0][1] = whiteTimeRemaining;
9607 timeRemaining[1][1] = blackTimeRemaining;
9608 if (commentList[0] != NULL) {
9609 commentList[1] = commentList[0];
9610 commentList[0] = NULL;
9613 currentMove = forwardMostMove = backwardMostMove = 0;
9616 yyboardindex = forwardMostMove;
9617 cm = (ChessMove) yylex();
9620 if (first.pr == NoProc) {
9621 StartChessProgram(&first);
9623 InitChessProgram(&first, FALSE);
9624 SendToProgram("force\n", &first);
9625 if (startedFromSetupPosition) {
9626 SendBoard(&first, forwardMostMove);
9627 if (appData.debugMode) {
9628 fprintf(debugFP, "Load Game\n");
9630 DisplayBothClocks();
9633 /* [HGM] server: flag to write setup moves in broadcast file as one */
9634 loadFlag = appData.suppressLoadMoves;
9636 while (cm == Comment) {
9638 if (appData.debugMode)
9639 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9641 AppendComment(currentMove, p, FALSE);
9642 yyboardindex = forwardMostMove;
9643 cm = (ChessMove) yylex();
9646 if ((cm == (ChessMove) 0 && lastLoadGameStart != (ChessMove) 0) ||
9647 cm == WhiteWins || cm == BlackWins ||
9648 cm == GameIsDrawn || cm == GameUnfinished) {
9649 DisplayMessage("", _("No moves in game"));
9650 if (cmailMsgLoaded) {
9651 if (appData.debugMode)
9652 fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
9656 DrawPosition(FALSE, boards[currentMove]);
9657 DisplayBothClocks();
9658 gameMode = EditGame;
9665 // [HGM] PV info: routine tests if comment empty
9666 if (!matchMode && (pausing || appData.timeDelay != 0)) {
9667 DisplayComment(currentMove - 1, commentList[currentMove]);
9669 if (!matchMode && appData.timeDelay != 0)
9670 DrawPosition(FALSE, boards[currentMove]);
9672 if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
9673 programStats.ok_to_send = 1;
9676 /* if the first token after the PGN tags is a move
9677 * and not move number 1, retrieve it from the parser
9679 if (cm != MoveNumberOne)
9680 LoadGameOneMove(cm);
9682 /* load the remaining moves from the file */
9683 while (LoadGameOneMove((ChessMove)0)) {
9684 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
9685 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
9688 /* rewind to the start of the game */
9689 currentMove = backwardMostMove;
9691 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
9693 if (oldGameMode == AnalyzeFile ||
9694 oldGameMode == AnalyzeMode) {
9698 if (matchMode || appData.timeDelay == 0) {
9700 gameMode = EditGame;
9702 } else if (appData.timeDelay > 0) {
9706 if (appData.debugMode)
9707 fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
9709 loadFlag = 0; /* [HGM] true game starts */
9713 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
9715 ReloadPosition(offset)
9718 int positionNumber = lastLoadPositionNumber + offset;
9719 if (lastLoadPositionFP == NULL) {
9720 DisplayError(_("No position has been loaded yet"), 0);
9723 if (positionNumber <= 0) {
9724 DisplayError(_("Can't back up any further"), 0);
9727 return LoadPosition(lastLoadPositionFP, positionNumber,
9728 lastLoadPositionTitle);
9731 /* Load the nth position from the given file */
9733 LoadPositionFromFile(filename, n, title)
9741 if (strcmp(filename, "-") == 0) {
9742 return LoadPosition(stdin, n, "stdin");
9744 f = fopen(filename, "rb");
9746 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9747 DisplayError(buf, errno);
9750 return LoadPosition(f, n, title);
9755 /* Load the nth position from the given open file, and close it */
9757 LoadPosition(f, positionNumber, title)
9762 char *p, line[MSG_SIZ];
9763 Board initial_position;
9764 int i, j, fenMode, pn;
9766 if (gameMode == Training )
9767 SetTrainingModeOff();
9769 if (gameMode != BeginningOfGame) {
9772 if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
9773 fclose(lastLoadPositionFP);
9775 if (positionNumber == 0) positionNumber = 1;
9776 lastLoadPositionFP = f;
9777 lastLoadPositionNumber = positionNumber;
9778 strcpy(lastLoadPositionTitle, title);
9779 if (first.pr == NoProc) {
9780 StartChessProgram(&first);
9781 InitChessProgram(&first, FALSE);
9783 pn = positionNumber;
9784 if (positionNumber < 0) {
9785 /* Negative position number means to seek to that byte offset */
9786 if (fseek(f, -positionNumber, 0) == -1) {
9787 DisplayError(_("Can't seek on position file"), 0);
9792 if (fseek(f, 0, 0) == -1) {
9793 if (f == lastLoadPositionFP ?
9794 positionNumber == lastLoadPositionNumber + 1 :
9795 positionNumber == 1) {
9798 DisplayError(_("Can't seek on position file"), 0);
9803 /* See if this file is FEN or old-style xboard */
9804 if (fgets(line, MSG_SIZ, f) == NULL) {
9805 DisplayError(_("Position not found in file"), 0);
9808 // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
9809 fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
9812 if (fenMode || line[0] == '#') pn--;
9814 /* skip positions before number pn */
9815 if (fgets(line, MSG_SIZ, f) == NULL) {
9817 DisplayError(_("Position not found in file"), 0);
9820 if (fenMode || line[0] == '#') pn--;
9825 if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
9826 DisplayError(_("Bad FEN position in file"), 0);
9830 (void) fgets(line, MSG_SIZ, f);
9831 (void) fgets(line, MSG_SIZ, f);
9833 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
9834 (void) fgets(line, MSG_SIZ, f);
9835 for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
9838 initial_position[i][j++] = CharToPiece(*p);
9842 blackPlaysFirst = FALSE;
9844 (void) fgets(line, MSG_SIZ, f);
9845 if (strncmp(line, "black", strlen("black"))==0)
9846 blackPlaysFirst = TRUE;
9849 startedFromSetupPosition = TRUE;
9851 SendToProgram("force\n", &first);
9852 CopyBoard(boards[0], initial_position);
9853 if (blackPlaysFirst) {
9854 currentMove = forwardMostMove = backwardMostMove = 1;
9855 strcpy(moveList[0], "");
9856 strcpy(parseList[0], "");
9857 CopyBoard(boards[1], initial_position);
9858 DisplayMessage("", _("Black to play"));
9860 currentMove = forwardMostMove = backwardMostMove = 0;
9861 DisplayMessage("", _("White to play"));
9863 initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
9864 SendBoard(&first, forwardMostMove);
9865 if (appData.debugMode) {
9867 for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
9868 for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
9869 fprintf(debugFP, "Load Position\n");
9872 if (positionNumber > 1) {
9873 sprintf(line, "%s %d", title, positionNumber);
9876 DisplayTitle(title);
9878 gameMode = EditGame;
9881 timeRemaining[0][1] = whiteTimeRemaining;
9882 timeRemaining[1][1] = blackTimeRemaining;
9883 DrawPosition(FALSE, boards[currentMove]);
9890 CopyPlayerNameIntoFileName(dest, src)
9893 while (*src != NULLCHAR && *src != ',') {
9898 *(*dest)++ = *src++;
9903 char *DefaultFileName(ext)
9906 static char def[MSG_SIZ];
9909 if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
9911 CopyPlayerNameIntoFileName(&p, gameInfo.white);
9913 CopyPlayerNameIntoFileName(&p, gameInfo.black);
9922 /* Save the current game to the given file */
9924 SaveGameToFile(filename, append)
9931 if (strcmp(filename, "-") == 0) {
9932 return SaveGame(stdout, 0, NULL);
9934 f = fopen(filename, append ? "a" : "w");
9936 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9937 DisplayError(buf, errno);
9940 return SaveGame(f, 0, NULL);
9949 static char buf[MSG_SIZ];
9952 p = strchr(str, ' ');
9953 if (p == NULL) return str;
9954 strncpy(buf, str, p - str);
9955 buf[p - str] = NULLCHAR;
9959 #define PGN_MAX_LINE 75
9961 #define PGN_SIDE_WHITE 0
9962 #define PGN_SIDE_BLACK 1
9965 static int FindFirstMoveOutOfBook( int side )
9969 if( backwardMostMove == 0 && ! startedFromSetupPosition) {
9970 int index = backwardMostMove;
9971 int has_book_hit = 0;
9973 if( (index % 2) != side ) {
9977 while( index < forwardMostMove ) {
9978 /* Check to see if engine is in book */
9979 int depth = pvInfoList[index].depth;
9980 int score = pvInfoList[index].score;
9986 else if( score == 0 && depth == 63 ) {
9987 in_book = 1; /* Zappa */
9989 else if( score == 2 && depth == 99 ) {
9990 in_book = 1; /* Abrok */
9993 has_book_hit += in_book;
10009 void GetOutOfBookInfo( char * buf )
10013 int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10015 oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
10016 oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
10020 if( oob[0] >= 0 || oob[1] >= 0 ) {
10021 for( i=0; i<2; i++ ) {
10025 if( i > 0 && oob[0] >= 0 ) {
10026 strcat( buf, " " );
10029 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
10030 sprintf( buf+strlen(buf), "%s%.2f",
10031 pvInfoList[idx].score >= 0 ? "+" : "",
10032 pvInfoList[idx].score / 100.0 );
10038 /* Save game in PGN style and close the file */
10043 int i, offset, linelen, newblock;
10047 int movelen, numlen, blank;
10048 char move_buffer[100]; /* [AS] Buffer for move+PV info */
10050 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10052 tm = time((time_t *) NULL);
10054 PrintPGNTags(f, &gameInfo);
10056 if (backwardMostMove > 0 || startedFromSetupPosition) {
10057 char *fen = PositionToFEN(backwardMostMove, NULL);
10058 fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
10059 fprintf(f, "\n{--------------\n");
10060 PrintPosition(f, backwardMostMove);
10061 fprintf(f, "--------------}\n");
10065 /* [AS] Out of book annotation */
10066 if( appData.saveOutOfBookInfo ) {
10069 GetOutOfBookInfo( buf );
10071 if( buf[0] != '\0' ) {
10072 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
10079 i = backwardMostMove;
10083 while (i < forwardMostMove) {
10084 /* Print comments preceding this move */
10085 if (commentList[i] != NULL) {
10086 if (linelen > 0) fprintf(f, "\n");
10087 fprintf(f, "%s", commentList[i]);
10092 /* Format move number */
10093 if ((i % 2) == 0) {
10094 sprintf(numtext, "%d.", (i - offset)/2 + 1);
10097 sprintf(numtext, "%d...", (i - offset)/2 + 1);
10099 numtext[0] = NULLCHAR;
10102 numlen = strlen(numtext);
10105 /* Print move number */
10106 blank = linelen > 0 && numlen > 0;
10107 if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
10116 fprintf(f, "%s", numtext);
10120 strcpy(move_buffer, SavePart(parseList[i])); // [HGM] pgn: print move via buffer, so it can be edited
10121 movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
10124 blank = linelen > 0 && movelen > 0;
10125 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
10134 fprintf(f, "%s", move_buffer);
10135 linelen += movelen;
10137 /* [AS] Add PV info if present */
10138 if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
10139 /* [HGM] add time */
10140 char buf[MSG_SIZ]; int seconds;
10142 seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
10144 if( seconds <= 0) buf[0] = 0; else
10145 if( seconds < 30 ) sprintf(buf, " %3.1f%c", seconds/10., 0); else {
10146 seconds = (seconds + 4)/10; // round to full seconds
10147 if( seconds < 60 ) sprintf(buf, " %d%c", seconds, 0); else
10148 sprintf(buf, " %d:%02d%c", seconds/60, seconds%60, 0);
10151 sprintf( move_buffer, "{%s%.2f/%d%s}",
10152 pvInfoList[i].score >= 0 ? "+" : "",
10153 pvInfoList[i].score / 100.0,
10154 pvInfoList[i].depth,
10157 movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
10159 /* Print score/depth */
10160 blank = linelen > 0 && movelen > 0;
10161 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
10170 fprintf(f, "%s", move_buffer);
10171 linelen += movelen;
10177 /* Start a new line */
10178 if (linelen > 0) fprintf(f, "\n");
10180 /* Print comments after last move */
10181 if (commentList[i] != NULL) {
10182 fprintf(f, "%s\n", commentList[i]);
10186 if (gameInfo.resultDetails != NULL &&
10187 gameInfo.resultDetails[0] != NULLCHAR) {
10188 fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
10189 PGNResult(gameInfo.result));
10191 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
10195 lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
10199 /* Save game in old style and close the file */
10201 SaveGameOldStyle(f)
10207 tm = time((time_t *) NULL);
10209 fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
10212 if (backwardMostMove > 0 || startedFromSetupPosition) {
10213 fprintf(f, "\n[--------------\n");
10214 PrintPosition(f, backwardMostMove);
10215 fprintf(f, "--------------]\n");
10220 i = backwardMostMove;
10221 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10223 while (i < forwardMostMove) {
10224 if (commentList[i] != NULL) {
10225 fprintf(f, "[%s]\n", commentList[i]);
10228 if ((i % 2) == 1) {
10229 fprintf(f, "%d. ... %s\n", (i - offset)/2 + 1, parseList[i]);
10232 fprintf(f, "%d. %s ", (i - offset)/2 + 1, parseList[i]);
10234 if (commentList[i] != NULL) {
10238 if (i >= forwardMostMove) {
10242 fprintf(f, "%s\n", parseList[i]);
10247 if (commentList[i] != NULL) {
10248 fprintf(f, "[%s]\n", commentList[i]);
10251 /* This isn't really the old style, but it's close enough */
10252 if (gameInfo.resultDetails != NULL &&
10253 gameInfo.resultDetails[0] != NULLCHAR) {
10254 fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
10255 gameInfo.resultDetails);
10257 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
10264 /* Save the current game to open file f and close the file */
10266 SaveGame(f, dummy, dummy2)
10271 if (gameMode == EditPosition) EditPositionDone(TRUE);
10272 lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
10273 if (appData.oldSaveStyle)
10274 return SaveGameOldStyle(f);
10276 return SaveGamePGN(f);
10279 /* Save the current position to the given file */
10281 SavePositionToFile(filename)
10287 if (strcmp(filename, "-") == 0) {
10288 return SavePosition(stdout, 0, NULL);
10290 f = fopen(filename, "a");
10292 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10293 DisplayError(buf, errno);
10296 SavePosition(f, 0, NULL);
10302 /* Save the current position to the given open file and close the file */
10304 SavePosition(f, dummy, dummy2)
10312 if (gameMode == EditPosition) EditPositionDone(TRUE);
10313 if (appData.oldSaveStyle) {
10314 tm = time((time_t *) NULL);
10316 fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
10318 fprintf(f, "[--------------\n");
10319 PrintPosition(f, currentMove);
10320 fprintf(f, "--------------]\n");
10322 fen = PositionToFEN(currentMove, NULL);
10323 fprintf(f, "%s\n", fen);
10331 ReloadCmailMsgEvent(unregister)
10335 static char *inFilename = NULL;
10336 static char *outFilename;
10338 struct stat inbuf, outbuf;
10341 /* Any registered moves are unregistered if unregister is set, */
10342 /* i.e. invoked by the signal handler */
10344 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
10345 cmailMoveRegistered[i] = FALSE;
10346 if (cmailCommentList[i] != NULL) {
10347 free(cmailCommentList[i]);
10348 cmailCommentList[i] = NULL;
10351 nCmailMovesRegistered = 0;
10354 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
10355 cmailResult[i] = CMAIL_NOT_RESULT;
10359 if (inFilename == NULL) {
10360 /* Because the filenames are static they only get malloced once */
10361 /* and they never get freed */
10362 inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
10363 sprintf(inFilename, "%s.game.in", appData.cmailGameName);
10365 outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
10366 sprintf(outFilename, "%s.out", appData.cmailGameName);
10369 status = stat(outFilename, &outbuf);
10371 cmailMailedMove = FALSE;
10373 status = stat(inFilename, &inbuf);
10374 cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
10377 /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
10378 counts the games, notes how each one terminated, etc.
10380 It would be nice to remove this kludge and instead gather all
10381 the information while building the game list. (And to keep it
10382 in the game list nodes instead of having a bunch of fixed-size
10383 parallel arrays.) Note this will require getting each game's
10384 termination from the PGN tags, as the game list builder does
10385 not process the game moves. --mann
10387 cmailMsgLoaded = TRUE;
10388 LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
10390 /* Load first game in the file or popup game menu */
10391 LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
10393 #endif /* !WIN32 */
10401 char string[MSG_SIZ];
10403 if ( cmailMailedMove
10404 || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
10405 return TRUE; /* Allow free viewing */
10408 /* Unregister move to ensure that we don't leave RegisterMove */
10409 /* with the move registered when the conditions for registering no */
10411 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
10412 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
10413 nCmailMovesRegistered --;
10415 if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
10417 free(cmailCommentList[lastLoadGameNumber - 1]);
10418 cmailCommentList[lastLoadGameNumber - 1] = NULL;
10422 if (cmailOldMove == -1) {
10423 DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
10427 if (currentMove > cmailOldMove + 1) {
10428 DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
10432 if (currentMove < cmailOldMove) {
10433 DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
10437 if (forwardMostMove > currentMove) {
10438 /* Silently truncate extra moves */
10442 if ( (currentMove == cmailOldMove + 1)
10443 || ( (currentMove == cmailOldMove)
10444 && ( (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
10445 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
10446 if (gameInfo.result != GameUnfinished) {
10447 cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
10450 if (commentList[currentMove] != NULL) {
10451 cmailCommentList[lastLoadGameNumber - 1]
10452 = StrSave(commentList[currentMove]);
10454 strcpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1]);
10456 if (appData.debugMode)
10457 fprintf(debugFP, "Saving %s for game %d\n",
10458 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
10461 "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
10463 f = fopen(string, "w");
10464 if (appData.oldSaveStyle) {
10465 SaveGameOldStyle(f); /* also closes the file */
10467 sprintf(string, "%s.pos.out", appData.cmailGameName);
10468 f = fopen(string, "w");
10469 SavePosition(f, 0, NULL); /* also closes the file */
10471 fprintf(f, "{--------------\n");
10472 PrintPosition(f, currentMove);
10473 fprintf(f, "--------------}\n\n");
10475 SaveGame(f, 0, NULL); /* also closes the file*/
10478 cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
10479 nCmailMovesRegistered ++;
10480 } else if (nCmailGames == 1) {
10481 DisplayError(_("You have not made a move yet"), 0);
10492 static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
10493 FILE *commandOutput;
10494 char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
10495 int nBytes = 0; /* Suppress warnings on uninitialized variables */
10501 if (! cmailMsgLoaded) {
10502 DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
10506 if (nCmailGames == nCmailResults) {
10507 DisplayError(_("No unfinished games"), 0);
10511 #if CMAIL_PROHIBIT_REMAIL
10512 if (cmailMailedMove) {
10513 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);
10514 DisplayError(msg, 0);
10519 if (! (cmailMailedMove || RegisterMove())) return;
10521 if ( cmailMailedMove
10522 || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
10523 sprintf(string, partCommandString,
10524 appData.debugMode ? " -v" : "", appData.cmailGameName);
10525 commandOutput = popen(string, "r");
10527 if (commandOutput == NULL) {
10528 DisplayError(_("Failed to invoke cmail"), 0);
10530 for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
10531 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
10533 if (nBuffers > 1) {
10534 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
10535 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
10536 nBytes = MSG_SIZ - 1;
10538 (void) memcpy(msg, buffer, nBytes);
10540 *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
10542 if(StrStr(msg, "Mailed cmail message to ") != NULL) {
10543 cmailMailedMove = TRUE; /* Prevent >1 moves */
10546 for (i = 0; i < nCmailGames; i ++) {
10547 if (cmailResult[i] == CMAIL_NOT_RESULT) {
10552 && ( (arcDir = (char *) getenv("CMAIL_ARCDIR"))
10554 sprintf(buffer, "%s/%s.%s.archive",
10556 appData.cmailGameName,
10558 LoadGameFromFile(buffer, 1, buffer, FALSE);
10559 cmailMsgLoaded = FALSE;
10563 DisplayInformation(msg);
10564 pclose(commandOutput);
10567 if ((*cmailMsg) != '\0') {
10568 DisplayInformation(cmailMsg);
10573 #endif /* !WIN32 */
10582 int prependComma = 0;
10584 char string[MSG_SIZ]; /* Space for game-list */
10587 if (!cmailMsgLoaded) return "";
10589 if (cmailMailedMove) {
10590 sprintf(cmailMsg, _("Waiting for reply from opponent\n"));
10592 /* Create a list of games left */
10593 sprintf(string, "[");
10594 for (i = 0; i < nCmailGames; i ++) {
10595 if (! ( cmailMoveRegistered[i]
10596 || (cmailResult[i] == CMAIL_OLD_RESULT))) {
10597 if (prependComma) {
10598 sprintf(number, ",%d", i + 1);
10600 sprintf(number, "%d", i + 1);
10604 strcat(string, number);
10607 strcat(string, "]");
10609 if (nCmailMovesRegistered + nCmailResults == 0) {
10610 switch (nCmailGames) {
10613 _("Still need to make move for game\n"));
10618 _("Still need to make moves for both games\n"));
10623 _("Still need to make moves for all %d games\n"),
10628 switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
10631 _("Still need to make a move for game %s\n"),
10636 if (nCmailResults == nCmailGames) {
10637 sprintf(cmailMsg, _("No unfinished games\n"));
10639 sprintf(cmailMsg, _("Ready to send mail\n"));
10645 _("Still need to make moves for games %s\n"),
10657 if (gameMode == Training)
10658 SetTrainingModeOff();
10661 cmailMsgLoaded = FALSE;
10662 if (appData.icsActive) {
10663 SendToICS(ics_prefix);
10664 SendToICS("refresh\n");
10674 /* Give up on clean exit */
10678 /* Keep trying for clean exit */
10682 if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
10684 if (telnetISR != NULL) {
10685 RemoveInputSource(telnetISR);
10687 if (icsPR != NoProc) {
10688 DestroyChildProcess(icsPR, TRUE);
10691 /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
10692 GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
10694 /* [HGM] crash: the above GameEnds() is a dud if another one was running */
10695 /* make sure this other one finishes before killing it! */
10696 if(endingGame) { int count = 0;
10697 if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
10698 while(endingGame && count++ < 10) DoSleep(1);
10699 if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
10702 /* Kill off chess programs */
10703 if (first.pr != NoProc) {
10706 DoSleep( appData.delayBeforeQuit );
10707 SendToProgram("quit\n", &first);
10708 DoSleep( appData.delayAfterQuit );
10709 DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
10711 if (second.pr != NoProc) {
10712 DoSleep( appData.delayBeforeQuit );
10713 SendToProgram("quit\n", &second);
10714 DoSleep( appData.delayAfterQuit );
10715 DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
10717 if (first.isr != NULL) {
10718 RemoveInputSource(first.isr);
10720 if (second.isr != NULL) {
10721 RemoveInputSource(second.isr);
10724 ShutDownFrontEnd();
10731 if (appData.debugMode)
10732 fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
10736 if (gameMode == MachinePlaysWhite ||
10737 gameMode == MachinePlaysBlack) {
10740 DisplayBothClocks();
10742 if (gameMode == PlayFromGameFile) {
10743 if (appData.timeDelay >= 0)
10744 AutoPlayGameLoop();
10745 } else if (gameMode == IcsExamining && pauseExamInvalid) {
10746 Reset(FALSE, TRUE);
10747 SendToICS(ics_prefix);
10748 SendToICS("refresh\n");
10749 } else if (currentMove < forwardMostMove) {
10750 ForwardInner(forwardMostMove);
10752 pauseExamInvalid = FALSE;
10754 switch (gameMode) {
10758 pauseExamForwardMostMove = forwardMostMove;
10759 pauseExamInvalid = FALSE;
10762 case IcsPlayingWhite:
10763 case IcsPlayingBlack:
10767 case PlayFromGameFile:
10768 (void) StopLoadGameTimer();
10772 case BeginningOfGame:
10773 if (appData.icsActive) return;
10774 /* else fall through */
10775 case MachinePlaysWhite:
10776 case MachinePlaysBlack:
10777 case TwoMachinesPlay:
10778 if (forwardMostMove == 0)
10779 return; /* don't pause if no one has moved */
10780 if ((gameMode == MachinePlaysWhite &&
10781 !WhiteOnMove(forwardMostMove)) ||
10782 (gameMode == MachinePlaysBlack &&
10783 WhiteOnMove(forwardMostMove))) {
10796 char title[MSG_SIZ];
10798 if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
10799 strcpy(title, _("Edit comment"));
10801 sprintf(title, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
10802 WhiteOnMove(currentMove - 1) ? " " : ".. ",
10803 parseList[currentMove - 1]);
10806 EditCommentPopUp(currentMove, title, commentList[currentMove]);
10813 char *tags = PGNTags(&gameInfo);
10814 EditTagsPopUp(tags);
10821 if (appData.noChessProgram || gameMode == AnalyzeMode)
10824 if (gameMode != AnalyzeFile) {
10825 if (!appData.icsEngineAnalyze) {
10827 if (gameMode != EditGame) return;
10829 ResurrectChessProgram();
10830 SendToProgram("analyze\n", &first);
10831 first.analyzing = TRUE;
10832 /*first.maybeThinking = TRUE;*/
10833 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
10834 EngineOutputPopUp();
10836 if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
10841 StartAnalysisClock();
10842 GetTimeMark(&lastNodeCountTime);
10849 if (appData.noChessProgram || gameMode == AnalyzeFile)
10852 if (gameMode != AnalyzeMode) {
10854 if (gameMode != EditGame) return;
10855 ResurrectChessProgram();
10856 SendToProgram("analyze\n", &first);
10857 first.analyzing = TRUE;
10858 /*first.maybeThinking = TRUE;*/
10859 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
10860 EngineOutputPopUp();
10862 gameMode = AnalyzeFile;
10867 StartAnalysisClock();
10868 GetTimeMark(&lastNodeCountTime);
10873 MachineWhiteEvent()
10876 char *bookHit = NULL;
10878 if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
10882 if (gameMode == PlayFromGameFile ||
10883 gameMode == TwoMachinesPlay ||
10884 gameMode == Training ||
10885 gameMode == AnalyzeMode ||
10886 gameMode == EndOfGame)
10889 if (gameMode == EditPosition)
10890 EditPositionDone(TRUE);
10892 if (!WhiteOnMove(currentMove)) {
10893 DisplayError(_("It is not White's turn"), 0);
10897 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
10900 if (gameMode == EditGame || gameMode == AnalyzeMode ||
10901 gameMode == AnalyzeFile)
10904 ResurrectChessProgram(); /* in case it isn't running */
10905 if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
10906 gameMode = MachinePlaysWhite;
10909 gameMode = MachinePlaysWhite;
10913 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10915 if (first.sendName) {
10916 sprintf(buf, "name %s\n", gameInfo.black);
10917 SendToProgram(buf, &first);
10919 if (first.sendTime) {
10920 if (first.useColors) {
10921 SendToProgram("black\n", &first); /*gnu kludge*/
10923 SendTimeRemaining(&first, TRUE);
10925 if (first.useColors) {
10926 SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
10928 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
10929 SetMachineThinkingEnables();
10930 first.maybeThinking = TRUE;
10934 if (appData.autoFlipView && !flipView) {
10935 flipView = !flipView;
10936 DrawPosition(FALSE, NULL);
10937 DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;
10940 if(bookHit) { // [HGM] book: simulate book reply
10941 static char bookMove[MSG_SIZ]; // a bit generous?
10943 programStats.nodes = programStats.depth = programStats.time =
10944 programStats.score = programStats.got_only_move = 0;
10945 sprintf(programStats.movelist, "%s (xbook)", bookHit);
10947 strcpy(bookMove, "move ");
10948 strcat(bookMove, bookHit);
10949 HandleMachineMove(bookMove, &first);
10954 MachineBlackEvent()
10957 char *bookHit = NULL;
10959 if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
10963 if (gameMode == PlayFromGameFile ||
10964 gameMode == TwoMachinesPlay ||
10965 gameMode == Training ||
10966 gameMode == AnalyzeMode ||
10967 gameMode == EndOfGame)
10970 if (gameMode == EditPosition)
10971 EditPositionDone(TRUE);
10973 if (WhiteOnMove(currentMove)) {
10974 DisplayError(_("It is not Black's turn"), 0);
10978 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
10981 if (gameMode == EditGame || gameMode == AnalyzeMode ||
10982 gameMode == AnalyzeFile)
10985 ResurrectChessProgram(); /* in case it isn't running */
10986 gameMode = MachinePlaysBlack;
10990 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10992 if (first.sendName) {
10993 sprintf(buf, "name %s\n", gameInfo.white);
10994 SendToProgram(buf, &first);
10996 if (first.sendTime) {
10997 if (first.useColors) {
10998 SendToProgram("white\n", &first); /*gnu kludge*/
11000 SendTimeRemaining(&first, FALSE);
11002 if (first.useColors) {
11003 SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
11005 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
11006 SetMachineThinkingEnables();
11007 first.maybeThinking = TRUE;
11010 if (appData.autoFlipView && flipView) {
11011 flipView = !flipView;
11012 DrawPosition(FALSE, NULL);
11013 DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;
11015 if(bookHit) { // [HGM] book: simulate book reply
11016 static char bookMove[MSG_SIZ]; // a bit generous?
11018 programStats.nodes = programStats.depth = programStats.time =
11019 programStats.score = programStats.got_only_move = 0;
11020 sprintf(programStats.movelist, "%s (xbook)", bookHit);
11022 strcpy(bookMove, "move ");
11023 strcat(bookMove, bookHit);
11024 HandleMachineMove(bookMove, &first);
11030 DisplayTwoMachinesTitle()
11033 if (appData.matchGames > 0) {
11034 if (first.twoMachinesColor[0] == 'w') {
11035 sprintf(buf, "%s vs. %s (%d-%d-%d)",
11036 gameInfo.white, gameInfo.black,
11037 first.matchWins, second.matchWins,
11038 matchGame - 1 - (first.matchWins + second.matchWins));
11040 sprintf(buf, "%s vs. %s (%d-%d-%d)",
11041 gameInfo.white, gameInfo.black,
11042 second.matchWins, first.matchWins,
11043 matchGame - 1 - (first.matchWins + second.matchWins));
11046 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
11052 TwoMachinesEvent P((void))
11056 ChessProgramState *onmove;
11057 char *bookHit = NULL;
11059 if (appData.noChessProgram) return;
11061 switch (gameMode) {
11062 case TwoMachinesPlay:
11064 case MachinePlaysWhite:
11065 case MachinePlaysBlack:
11066 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
11067 DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
11071 case BeginningOfGame:
11072 case PlayFromGameFile:
11075 if (gameMode != EditGame) return;
11078 EditPositionDone(TRUE);
11089 // forwardMostMove = currentMove;
11090 TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
11091 ResurrectChessProgram(); /* in case first program isn't running */
11093 if (second.pr == NULL) {
11094 StartChessProgram(&second);
11095 if (second.protocolVersion == 1) {
11096 TwoMachinesEventIfReady();
11098 /* kludge: allow timeout for initial "feature" command */
11100 DisplayMessage("", _("Starting second chess program"));
11101 ScheduleDelayedEvent(TwoMachinesEventIfReady, FEATURE_TIMEOUT);
11105 DisplayMessage("", "");
11106 InitChessProgram(&second, FALSE);
11107 SendToProgram("force\n", &second);
11108 if (startedFromSetupPosition) {
11109 SendBoard(&second, backwardMostMove);
11110 if (appData.debugMode) {
11111 fprintf(debugFP, "Two Machines\n");
11114 for (i = backwardMostMove; i < forwardMostMove; i++) {
11115 SendMoveToProgram(i, &second);
11118 gameMode = TwoMachinesPlay;
11122 DisplayTwoMachinesTitle();
11124 if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
11130 SendToProgram(first.computerString, &first);
11131 if (first.sendName) {
11132 sprintf(buf, "name %s\n", second.tidy);
11133 SendToProgram(buf, &first);
11135 SendToProgram(second.computerString, &second);
11136 if (second.sendName) {
11137 sprintf(buf, "name %s\n", first.tidy);
11138 SendToProgram(buf, &second);
11142 if (!first.sendTime || !second.sendTime) {
11143 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11144 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11146 if (onmove->sendTime) {
11147 if (onmove->useColors) {
11148 SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
11150 SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
11152 if (onmove->useColors) {
11153 SendToProgram(onmove->twoMachinesColor, onmove);
11155 bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
11156 // SendToProgram("go\n", onmove);
11157 onmove->maybeThinking = TRUE;
11158 SetMachineThinkingEnables();
11162 if(bookHit) { // [HGM] book: simulate book reply
11163 static char bookMove[MSG_SIZ]; // a bit generous?
11165 programStats.nodes = programStats.depth = programStats.time =
11166 programStats.score = programStats.got_only_move = 0;
11167 sprintf(programStats.movelist, "%s (xbook)", bookHit);
11169 strcpy(bookMove, "move ");
11170 strcat(bookMove, bookHit);
11171 savedMessage = bookMove; // args for deferred call
11172 savedState = onmove;
11173 ScheduleDelayedEvent(DeferredBookMove, 1);
11180 if (gameMode == Training) {
11181 SetTrainingModeOff();
11182 gameMode = PlayFromGameFile;
11183 DisplayMessage("", _("Training mode off"));
11185 gameMode = Training;
11186 animateTraining = appData.animate;
11188 /* make sure we are not already at the end of the game */
11189 if (currentMove < forwardMostMove) {
11190 SetTrainingModeOn();
11191 DisplayMessage("", _("Training mode on"));
11193 gameMode = PlayFromGameFile;
11194 DisplayError(_("Already at end of game"), 0);
11203 if (!appData.icsActive) return;
11204 switch (gameMode) {
11205 case IcsPlayingWhite:
11206 case IcsPlayingBlack:
11209 case BeginningOfGame:
11217 EditPositionDone(TRUE);
11230 gameMode = IcsIdle;
11241 switch (gameMode) {
11243 SetTrainingModeOff();
11245 case MachinePlaysWhite:
11246 case MachinePlaysBlack:
11247 case BeginningOfGame:
11248 SendToProgram("force\n", &first);
11249 SetUserThinkingEnables();
11251 case PlayFromGameFile:
11252 (void) StopLoadGameTimer();
11253 if (gameFileFP != NULL) {
11258 EditPositionDone(TRUE);
11263 SendToProgram("force\n", &first);
11265 case TwoMachinesPlay:
11266 GameEnds((ChessMove) 0, NULL, GE_PLAYER);
11267 ResurrectChessProgram();
11268 SetUserThinkingEnables();
11271 ResurrectChessProgram();
11273 case IcsPlayingBlack:
11274 case IcsPlayingWhite:
11275 DisplayError(_("Warning: You are still playing a game"), 0);
11278 DisplayError(_("Warning: You are still observing a game"), 0);
11281 DisplayError(_("Warning: You are still examining a game"), 0);
11292 first.offeredDraw = second.offeredDraw = 0;
11294 if (gameMode == PlayFromGameFile) {
11295 whiteTimeRemaining = timeRemaining[0][currentMove];
11296 blackTimeRemaining = timeRemaining[1][currentMove];
11300 if (gameMode == MachinePlaysWhite ||
11301 gameMode == MachinePlaysBlack ||
11302 gameMode == TwoMachinesPlay ||
11303 gameMode == EndOfGame) {
11304 i = forwardMostMove;
11305 while (i > currentMove) {
11306 SendToProgram("undo\n", &first);
11309 whiteTimeRemaining = timeRemaining[0][currentMove];
11310 blackTimeRemaining = timeRemaining[1][currentMove];
11311 DisplayBothClocks();
11312 if (whiteFlag || blackFlag) {
11313 whiteFlag = blackFlag = 0;
11318 gameMode = EditGame;
11325 EditPositionEvent()
11327 if (gameMode == EditPosition) {
11333 if (gameMode != EditGame) return;
11335 gameMode = EditPosition;
11338 if (currentMove > 0)
11339 CopyBoard(boards[0], boards[currentMove]);
11341 blackPlaysFirst = !WhiteOnMove(currentMove);
11343 currentMove = forwardMostMove = backwardMostMove = 0;
11344 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11351 /* [DM] icsEngineAnalyze - possible call from other functions */
11352 if (appData.icsEngineAnalyze) {
11353 appData.icsEngineAnalyze = FALSE;
11355 DisplayMessage("",_("Close ICS engine analyze..."));
11357 if (first.analysisSupport && first.analyzing) {
11358 SendToProgram("exit\n", &first);
11359 first.analyzing = FALSE;
11361 thinkOutput[0] = NULLCHAR;
11365 EditPositionDone(Boolean fakeRights)
11367 int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
11369 startedFromSetupPosition = TRUE;
11370 InitChessProgram(&first, FALSE);
11371 if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
11372 boards[0][EP_STATUS] = EP_NONE;
11373 boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
11374 if(boards[0][0][BOARD_WIDTH>>1] == king) {
11375 boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : NoRights;
11376 boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
11377 } else boards[0][CASTLING][2] = NoRights;
11378 if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
11379 boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : NoRights;
11380 boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
11381 } else boards[0][CASTLING][5] = NoRights;
11383 SendToProgram("force\n", &first);
11384 if (blackPlaysFirst) {
11385 strcpy(moveList[0], "");
11386 strcpy(parseList[0], "");
11387 currentMove = forwardMostMove = backwardMostMove = 1;
11388 CopyBoard(boards[1], boards[0]);
11390 currentMove = forwardMostMove = backwardMostMove = 0;
11392 SendBoard(&first, forwardMostMove);
11393 if (appData.debugMode) {
11394 fprintf(debugFP, "EditPosDone\n");
11397 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11398 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11399 gameMode = EditGame;
11401 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11402 ClearHighlights(); /* [AS] */
11405 /* Pause for `ms' milliseconds */
11406 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
11416 } while (SubtractTimeMarks(&m2, &m1) < ms);
11419 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
11421 SendMultiLineToICS(buf)
11424 char temp[MSG_SIZ+1], *p;
11431 strncpy(temp, buf, len);
11436 if (*p == '\n' || *p == '\r')
11441 strcat(temp, "\n");
11443 SendToPlayer(temp, strlen(temp));
11447 SetWhiteToPlayEvent()
11449 if (gameMode == EditPosition) {
11450 blackPlaysFirst = FALSE;
11451 DisplayBothClocks(); /* works because currentMove is 0 */
11452 } else if (gameMode == IcsExamining) {
11453 SendToICS(ics_prefix);
11454 SendToICS("tomove white\n");
11459 SetBlackToPlayEvent()
11461 if (gameMode == EditPosition) {
11462 blackPlaysFirst = TRUE;
11463 currentMove = 1; /* kludge */
11464 DisplayBothClocks();
11466 } else if (gameMode == IcsExamining) {
11467 SendToICS(ics_prefix);
11468 SendToICS("tomove black\n");
11473 EditPositionMenuEvent(selection, x, y)
11474 ChessSquare selection;
11478 ChessSquare piece = boards[0][y][x];
11480 if (gameMode != EditPosition && gameMode != IcsExamining) return;
11482 switch (selection) {
11484 if (gameMode == IcsExamining && ics_type == ICS_FICS) {
11485 SendToICS(ics_prefix);
11486 SendToICS("bsetup clear\n");
11487 } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
11488 SendToICS(ics_prefix);
11489 SendToICS("clearboard\n");
11491 for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
11492 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
11493 for (y = 0; y < BOARD_HEIGHT; y++) {
11494 if (gameMode == IcsExamining) {
11495 if (boards[currentMove][y][x] != EmptySquare) {
11496 sprintf(buf, "%sx@%c%c\n", ics_prefix,
11501 boards[0][y][x] = p;
11506 if (gameMode == EditPosition) {
11507 DrawPosition(FALSE, boards[0]);
11512 SetWhiteToPlayEvent();
11516 SetBlackToPlayEvent();
11520 if (gameMode == IcsExamining) {
11521 if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
11522 sprintf(buf, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
11525 if(x < BOARD_LEFT || x >= BOARD_RGHT) {
11526 if(x == BOARD_LEFT-2) {
11527 if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
11528 boards[0][y][1] = 0;
11530 if(x == BOARD_RGHT+1) {
11531 if(y >= gameInfo.holdingsSize) break;
11532 boards[0][y][BOARD_WIDTH-2] = 0;
11535 boards[0][y][x] = EmptySquare;
11536 DrawPosition(FALSE, boards[0]);
11541 if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
11542 piece >= (int)BlackPawn && piece < (int)BlackMan ) {
11543 selection = (ChessSquare) (PROMOTED piece);
11544 } else if(piece == EmptySquare) selection = WhiteSilver;
11545 else selection = (ChessSquare)((int)piece - 1);
11549 if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
11550 piece > (int)BlackMan && piece <= (int)BlackKing ) {
11551 selection = (ChessSquare) (DEMOTED piece);
11552 } else if(piece == EmptySquare) selection = BlackSilver;
11553 else selection = (ChessSquare)((int)piece + 1);
11558 if(gameInfo.variant == VariantShatranj ||
11559 gameInfo.variant == VariantXiangqi ||
11560 gameInfo.variant == VariantCourier ||
11561 gameInfo.variant == VariantMakruk )
11562 selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
11567 if(gameInfo.variant == VariantXiangqi)
11568 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
11569 if(gameInfo.variant == VariantKnightmate)
11570 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
11573 if (gameMode == IcsExamining) {
11574 if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
11575 sprintf(buf, "%s%c@%c%c\n", ics_prefix,
11576 PieceToChar(selection), AAA + x, ONE + y);
11579 if(x < BOARD_LEFT || x >= BOARD_RGHT) {
11581 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
11582 n = PieceToNumber(selection - BlackPawn);
11583 if(n > gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
11584 boards[0][BOARD_HEIGHT-1-n][0] = selection;
11585 boards[0][BOARD_HEIGHT-1-n][1]++;
11587 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
11588 n = PieceToNumber(selection);
11589 if(n > gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
11590 boards[0][n][BOARD_WIDTH-1] = selection;
11591 boards[0][n][BOARD_WIDTH-2]++;
11594 boards[0][y][x] = selection;
11595 DrawPosition(TRUE, boards[0]);
11603 DropMenuEvent(selection, x, y)
11604 ChessSquare selection;
11607 ChessMove moveType;
11609 switch (gameMode) {
11610 case IcsPlayingWhite:
11611 case MachinePlaysBlack:
11612 if (!WhiteOnMove(currentMove)) {
11613 DisplayMoveError(_("It is Black's turn"));
11616 moveType = WhiteDrop;
11618 case IcsPlayingBlack:
11619 case MachinePlaysWhite:
11620 if (WhiteOnMove(currentMove)) {
11621 DisplayMoveError(_("It is White's turn"));
11624 moveType = BlackDrop;
11627 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
11633 if (moveType == BlackDrop && selection < BlackPawn) {
11634 selection = (ChessSquare) ((int) selection
11635 + (int) BlackPawn - (int) WhitePawn);
11637 if (boards[currentMove][y][x] != EmptySquare) {
11638 DisplayMoveError(_("That square is occupied"));
11642 FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
11648 /* Accept a pending offer of any kind from opponent */
11650 if (appData.icsActive) {
11651 SendToICS(ics_prefix);
11652 SendToICS("accept\n");
11653 } else if (cmailMsgLoaded) {
11654 if (currentMove == cmailOldMove &&
11655 commentList[cmailOldMove] != NULL &&
11656 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11657 "Black offers a draw" : "White offers a draw")) {
11659 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11660 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
11662 DisplayError(_("There is no pending offer on this move"), 0);
11663 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11666 /* Not used for offers from chess program */
11673 /* Decline a pending offer of any kind from opponent */
11675 if (appData.icsActive) {
11676 SendToICS(ics_prefix);
11677 SendToICS("decline\n");
11678 } else if (cmailMsgLoaded) {
11679 if (currentMove == cmailOldMove &&
11680 commentList[cmailOldMove] != NULL &&
11681 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11682 "Black offers a draw" : "White offers a draw")) {
11684 AppendComment(cmailOldMove, "Draw declined", TRUE);
11685 DisplayComment(cmailOldMove - 1, "Draw declined");
11688 DisplayError(_("There is no pending offer on this move"), 0);
11691 /* Not used for offers from chess program */
11698 /* Issue ICS rematch command */
11699 if (appData.icsActive) {
11700 SendToICS(ics_prefix);
11701 SendToICS("rematch\n");
11708 /* Call your opponent's flag (claim a win on time) */
11709 if (appData.icsActive) {
11710 SendToICS(ics_prefix);
11711 SendToICS("flag\n");
11713 switch (gameMode) {
11716 case MachinePlaysWhite:
11719 GameEnds(GameIsDrawn, "Both players ran out of time",
11722 GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
11724 DisplayError(_("Your opponent is not out of time"), 0);
11727 case MachinePlaysBlack:
11730 GameEnds(GameIsDrawn, "Both players ran out of time",
11733 GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
11735 DisplayError(_("Your opponent is not out of time"), 0);
11745 /* Offer draw or accept pending draw offer from opponent */
11747 if (appData.icsActive) {
11748 /* Note: tournament rules require draw offers to be
11749 made after you make your move but before you punch
11750 your clock. Currently ICS doesn't let you do that;
11751 instead, you immediately punch your clock after making
11752 a move, but you can offer a draw at any time. */
11754 SendToICS(ics_prefix);
11755 SendToICS("draw\n");
11756 } else if (cmailMsgLoaded) {
11757 if (currentMove == cmailOldMove &&
11758 commentList[cmailOldMove] != NULL &&
11759 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11760 "Black offers a draw" : "White offers a draw")) {
11761 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11762 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
11763 } else if (currentMove == cmailOldMove + 1) {
11764 char *offer = WhiteOnMove(cmailOldMove) ?
11765 "White offers a draw" : "Black offers a draw";
11766 AppendComment(currentMove, offer, TRUE);
11767 DisplayComment(currentMove - 1, offer);
11768 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
11770 DisplayError(_("You must make your move before offering a draw"), 0);
11771 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11773 } else if (first.offeredDraw) {
11774 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
11776 if (first.sendDrawOffers) {
11777 SendToProgram("draw\n", &first);
11778 userOfferedDraw = TRUE;
11786 /* Offer Adjourn or accept pending Adjourn offer from opponent */
11788 if (appData.icsActive) {
11789 SendToICS(ics_prefix);
11790 SendToICS("adjourn\n");
11792 /* Currently GNU Chess doesn't offer or accept Adjourns */
11800 /* Offer Abort or accept pending Abort offer from opponent */
11802 if (appData.icsActive) {
11803 SendToICS(ics_prefix);
11804 SendToICS("abort\n");
11806 GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
11813 /* Resign. You can do this even if it's not your turn. */
11815 if (appData.icsActive) {
11816 SendToICS(ics_prefix);
11817 SendToICS("resign\n");
11819 switch (gameMode) {
11820 case MachinePlaysWhite:
11821 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11823 case MachinePlaysBlack:
11824 GameEnds(BlackWins, "White resigns", GE_PLAYER);
11827 if (cmailMsgLoaded) {
11829 if (WhiteOnMove(cmailOldMove)) {
11830 GameEnds(BlackWins, "White resigns", GE_PLAYER);
11832 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11834 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
11845 StopObservingEvent()
11847 /* Stop observing current games */
11848 SendToICS(ics_prefix);
11849 SendToICS("unobserve\n");
11853 StopExaminingEvent()
11855 /* Stop observing current game */
11856 SendToICS(ics_prefix);
11857 SendToICS("unexamine\n");
11861 ForwardInner(target)
11866 if (appData.debugMode)
11867 fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
11868 target, currentMove, forwardMostMove);
11870 if (gameMode == EditPosition)
11873 if (gameMode == PlayFromGameFile && !pausing)
11876 if (gameMode == IcsExamining && pausing)
11877 limit = pauseExamForwardMostMove;
11879 limit = forwardMostMove;
11881 if (target > limit) target = limit;
11883 if (target > 0 && moveList[target - 1][0]) {
11884 int fromX, fromY, toX, toY;
11885 toX = moveList[target - 1][2] - AAA;
11886 toY = moveList[target - 1][3] - ONE;
11887 if (moveList[target - 1][1] == '@') {
11888 if (appData.highlightLastMove) {
11889 SetHighlights(-1, -1, toX, toY);
11892 fromX = moveList[target - 1][0] - AAA;
11893 fromY = moveList[target - 1][1] - ONE;
11894 if (target == currentMove + 1) {
11895 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
11897 if (appData.highlightLastMove) {
11898 SetHighlights(fromX, fromY, toX, toY);
11902 if (gameMode == EditGame || gameMode == AnalyzeMode ||
11903 gameMode == Training || gameMode == PlayFromGameFile ||
11904 gameMode == AnalyzeFile) {
11905 while (currentMove < target) {
11906 SendMoveToProgram(currentMove++, &first);
11909 currentMove = target;
11912 if (gameMode == EditGame || gameMode == EndOfGame) {
11913 whiteTimeRemaining = timeRemaining[0][currentMove];
11914 blackTimeRemaining = timeRemaining[1][currentMove];
11916 DisplayBothClocks();
11917 DisplayMove(currentMove - 1);
11918 DrawPosition(FALSE, boards[currentMove]);
11919 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
11920 if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
11921 DisplayComment(currentMove - 1, commentList[currentMove]);
11929 if (gameMode == IcsExamining && !pausing) {
11930 SendToICS(ics_prefix);
11931 SendToICS("forward\n");
11933 ForwardInner(currentMove + 1);
11940 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11941 /* to optimze, we temporarily turn off analysis mode while we feed
11942 * the remaining moves to the engine. Otherwise we get analysis output
11945 if (first.analysisSupport) {
11946 SendToProgram("exit\nforce\n", &first);
11947 first.analyzing = FALSE;
11951 if (gameMode == IcsExamining && !pausing) {
11952 SendToICS(ics_prefix);
11953 SendToICS("forward 999999\n");
11955 ForwardInner(forwardMostMove);
11958 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11959 /* we have fed all the moves, so reactivate analysis mode */
11960 SendToProgram("analyze\n", &first);
11961 first.analyzing = TRUE;
11962 /*first.maybeThinking = TRUE;*/
11963 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11968 BackwardInner(target)
11971 int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
11973 if (appData.debugMode)
11974 fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
11975 target, currentMove, forwardMostMove);
11977 if (gameMode == EditPosition) return;
11978 if (currentMove <= backwardMostMove) {
11980 DrawPosition(full_redraw, boards[currentMove]);
11983 if (gameMode == PlayFromGameFile && !pausing)
11986 if (moveList[target][0]) {
11987 int fromX, fromY, toX, toY;
11988 toX = moveList[target][2] - AAA;
11989 toY = moveList[target][3] - ONE;
11990 if (moveList[target][1] == '@') {
11991 if (appData.highlightLastMove) {
11992 SetHighlights(-1, -1, toX, toY);
11995 fromX = moveList[target][0] - AAA;
11996 fromY = moveList[target][1] - ONE;
11997 if (target == currentMove - 1) {
11998 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
12000 if (appData.highlightLastMove) {
12001 SetHighlights(fromX, fromY, toX, toY);
12005 if (gameMode == EditGame || gameMode==AnalyzeMode ||
12006 gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
12007 while (currentMove > target) {
12008 SendToProgram("undo\n", &first);
12012 currentMove = target;
12015 if (gameMode == EditGame || gameMode == EndOfGame) {
12016 whiteTimeRemaining = timeRemaining[0][currentMove];
12017 blackTimeRemaining = timeRemaining[1][currentMove];
12019 DisplayBothClocks();
12020 DisplayMove(currentMove - 1);
12021 DrawPosition(full_redraw, boards[currentMove]);
12022 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
12023 // [HGM] PV info: routine tests if comment empty
12024 DisplayComment(currentMove - 1, commentList[currentMove]);
12030 if (gameMode == IcsExamining && !pausing) {
12031 SendToICS(ics_prefix);
12032 SendToICS("backward\n");
12034 BackwardInner(currentMove - 1);
12041 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12042 /* to optimize, we temporarily turn off analysis mode while we undo
12043 * all the moves. Otherwise we get analysis output after each undo.
12045 if (first.analysisSupport) {
12046 SendToProgram("exit\nforce\n", &first);
12047 first.analyzing = FALSE;
12051 if (gameMode == IcsExamining && !pausing) {
12052 SendToICS(ics_prefix);
12053 SendToICS("backward 999999\n");
12055 BackwardInner(backwardMostMove);
12058 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12059 /* we have fed all the moves, so reactivate analysis mode */
12060 SendToProgram("analyze\n", &first);
12061 first.analyzing = TRUE;
12062 /*first.maybeThinking = TRUE;*/
12063 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12070 if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
12071 if (to >= forwardMostMove) to = forwardMostMove;
12072 if (to <= backwardMostMove) to = backwardMostMove;
12073 if (to < currentMove) {
12083 if(PopTail(TRUE)) { // [HGM] vari: restore old game tail
12086 if (gameMode != IcsExamining) {
12087 DisplayError(_("You are not examining a game"), 0);
12091 DisplayError(_("You can't revert while pausing"), 0);
12094 SendToICS(ics_prefix);
12095 SendToICS("revert\n");
12101 switch (gameMode) {
12102 case MachinePlaysWhite:
12103 case MachinePlaysBlack:
12104 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
12105 DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
12108 if (forwardMostMove < 2) return;
12109 currentMove = forwardMostMove = forwardMostMove - 2;
12110 whiteTimeRemaining = timeRemaining[0][currentMove];
12111 blackTimeRemaining = timeRemaining[1][currentMove];
12112 DisplayBothClocks();
12113 DisplayMove(currentMove - 1);
12114 ClearHighlights();/*!! could figure this out*/
12115 DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
12116 SendToProgram("remove\n", &first);
12117 /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
12120 case BeginningOfGame:
12124 case IcsPlayingWhite:
12125 case IcsPlayingBlack:
12126 if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
12127 SendToICS(ics_prefix);
12128 SendToICS("takeback 2\n");
12130 SendToICS(ics_prefix);
12131 SendToICS("takeback 1\n");
12140 ChessProgramState *cps;
12142 switch (gameMode) {
12143 case MachinePlaysWhite:
12144 if (!WhiteOnMove(forwardMostMove)) {
12145 DisplayError(_("It is your turn"), 0);
12150 case MachinePlaysBlack:
12151 if (WhiteOnMove(forwardMostMove)) {
12152 DisplayError(_("It is your turn"), 0);
12157 case TwoMachinesPlay:
12158 if (WhiteOnMove(forwardMostMove) ==
12159 (first.twoMachinesColor[0] == 'w')) {
12165 case BeginningOfGame:
12169 SendToProgram("?\n", cps);
12173 TruncateGameEvent()
12176 if (gameMode != EditGame) return;
12183 CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
12184 if (forwardMostMove > currentMove) {
12185 if (gameInfo.resultDetails != NULL) {
12186 free(gameInfo.resultDetails);
12187 gameInfo.resultDetails = NULL;
12188 gameInfo.result = GameUnfinished;
12190 forwardMostMove = currentMove;
12191 HistorySet(parseList, backwardMostMove, forwardMostMove,
12199 if (appData.noChessProgram) return;
12200 switch (gameMode) {
12201 case MachinePlaysWhite:
12202 if (WhiteOnMove(forwardMostMove)) {
12203 DisplayError(_("Wait until your turn"), 0);
12207 case BeginningOfGame:
12208 case MachinePlaysBlack:
12209 if (!WhiteOnMove(forwardMostMove)) {
12210 DisplayError(_("Wait until your turn"), 0);
12215 DisplayError(_("No hint available"), 0);
12218 SendToProgram("hint\n", &first);
12219 hintRequested = TRUE;
12225 if (appData.noChessProgram) return;
12226 switch (gameMode) {
12227 case MachinePlaysWhite:
12228 if (WhiteOnMove(forwardMostMove)) {
12229 DisplayError(_("Wait until your turn"), 0);
12233 case BeginningOfGame:
12234 case MachinePlaysBlack:
12235 if (!WhiteOnMove(forwardMostMove)) {
12236 DisplayError(_("Wait until your turn"), 0);
12241 EditPositionDone(TRUE);
12243 case TwoMachinesPlay:
12248 SendToProgram("bk\n", &first);
12249 bookOutput[0] = NULLCHAR;
12250 bookRequested = TRUE;
12256 char *tags = PGNTags(&gameInfo);
12257 TagsPopUp(tags, CmailMsg());
12261 /* end button procedures */
12264 PrintPosition(fp, move)
12270 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
12271 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
12272 char c = PieceToChar(boards[move][i][j]);
12273 fputc(c == 'x' ? '.' : c, fp);
12274 fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
12277 if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
12278 fprintf(fp, "white to play\n");
12280 fprintf(fp, "black to play\n");
12287 if (gameInfo.white != NULL) {
12288 fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
12294 /* Find last component of program's own name, using some heuristics */
12296 TidyProgramName(prog, host, buf)
12297 char *prog, *host, buf[MSG_SIZ];
12300 int local = (strcmp(host, "localhost") == 0);
12301 while (!local && (p = strchr(prog, ';')) != NULL) {
12303 while (*p == ' ') p++;
12306 if (*prog == '"' || *prog == '\'') {
12307 q = strchr(prog + 1, *prog);
12309 q = strchr(prog, ' ');
12311 if (q == NULL) q = prog + strlen(prog);
12313 while (p >= prog && *p != '/' && *p != '\\') p--;
12315 if(p == prog && *p == '"') p++;
12316 if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
12317 memcpy(buf, p, q - p);
12318 buf[q - p] = NULLCHAR;
12326 TimeControlTagValue()
12329 if (!appData.clockMode) {
12331 } else if (movesPerSession > 0) {
12332 sprintf(buf, "%d/%ld", movesPerSession, timeControl/1000);
12333 } else if (timeIncrement == 0) {
12334 sprintf(buf, "%ld", timeControl/1000);
12336 sprintf(buf, "%ld+%ld", timeControl/1000, timeIncrement/1000);
12338 return StrSave(buf);
12344 /* This routine is used only for certain modes */
12345 VariantClass v = gameInfo.variant;
12346 ChessMove r = GameUnfinished;
12349 if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
12350 r = gameInfo.result;
12351 p = gameInfo.resultDetails;
12352 gameInfo.resultDetails = NULL;
12354 ClearGameInfo(&gameInfo);
12355 gameInfo.variant = v;
12357 switch (gameMode) {
12358 case MachinePlaysWhite:
12359 gameInfo.event = StrSave( appData.pgnEventHeader );
12360 gameInfo.site = StrSave(HostName());
12361 gameInfo.date = PGNDate();
12362 gameInfo.round = StrSave("-");
12363 gameInfo.white = StrSave(first.tidy);
12364 gameInfo.black = StrSave(UserName());
12365 gameInfo.timeControl = TimeControlTagValue();
12368 case MachinePlaysBlack:
12369 gameInfo.event = StrSave( appData.pgnEventHeader );
12370 gameInfo.site = StrSave(HostName());
12371 gameInfo.date = PGNDate();
12372 gameInfo.round = StrSave("-");
12373 gameInfo.white = StrSave(UserName());
12374 gameInfo.black = StrSave(first.tidy);
12375 gameInfo.timeControl = TimeControlTagValue();
12378 case TwoMachinesPlay:
12379 gameInfo.event = StrSave( appData.pgnEventHeader );
12380 gameInfo.site = StrSave(HostName());
12381 gameInfo.date = PGNDate();
12382 if (matchGame > 0) {
12384 sprintf(buf, "%d", matchGame);
12385 gameInfo.round = StrSave(buf);
12387 gameInfo.round = StrSave("-");
12389 if (first.twoMachinesColor[0] == 'w') {
12390 gameInfo.white = StrSave(first.tidy);
12391 gameInfo.black = StrSave(second.tidy);
12393 gameInfo.white = StrSave(second.tidy);
12394 gameInfo.black = StrSave(first.tidy);
12396 gameInfo.timeControl = TimeControlTagValue();
12400 gameInfo.event = StrSave("Edited game");
12401 gameInfo.site = StrSave(HostName());
12402 gameInfo.date = PGNDate();
12403 gameInfo.round = StrSave("-");
12404 gameInfo.white = StrSave("-");
12405 gameInfo.black = StrSave("-");
12406 gameInfo.result = r;
12407 gameInfo.resultDetails = p;
12411 gameInfo.event = StrSave("Edited position");
12412 gameInfo.site = StrSave(HostName());
12413 gameInfo.date = PGNDate();
12414 gameInfo.round = StrSave("-");
12415 gameInfo.white = StrSave("-");
12416 gameInfo.black = StrSave("-");
12419 case IcsPlayingWhite:
12420 case IcsPlayingBlack:
12425 case PlayFromGameFile:
12426 gameInfo.event = StrSave("Game from non-PGN file");
12427 gameInfo.site = StrSave(HostName());
12428 gameInfo.date = PGNDate();
12429 gameInfo.round = StrSave("-");
12430 gameInfo.white = StrSave("?");
12431 gameInfo.black = StrSave("?");
12440 ReplaceComment(index, text)
12446 while (*text == '\n') text++;
12447 len = strlen(text);
12448 while (len > 0 && text[len - 1] == '\n') len--;
12450 if (commentList[index] != NULL)
12451 free(commentList[index]);
12454 commentList[index] = NULL;
12457 if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
12458 *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
12459 *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
12460 commentList[index] = (char *) malloc(len + 2);
12461 strncpy(commentList[index], text, len);
12462 commentList[index][len] = '\n';
12463 commentList[index][len + 1] = NULLCHAR;
12465 // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
12467 commentList[index] = (char *) malloc(len + 6);
12468 strcpy(commentList[index], "{\n");
12469 strncpy(commentList[index]+2, text, len);
12470 commentList[index][len+2] = NULLCHAR;
12471 while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
12472 strcat(commentList[index], "\n}\n");
12486 if (ch == '\r') continue;
12488 } while (ch != '\0');
12492 AppendComment(index, text, addBraces)
12495 Boolean addBraces; // [HGM] braces: tells if we should add {}
12500 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
12501 text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
12504 while (*text == '\n') text++;
12505 len = strlen(text);
12506 while (len > 0 && text[len - 1] == '\n') len--;
12508 if (len == 0) return;
12510 if (commentList[index] != NULL) {
12511 old = commentList[index];
12512 oldlen = strlen(old);
12513 while(commentList[index][oldlen-1] == '\n')
12514 commentList[index][--oldlen] = NULLCHAR;
12515 commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
12516 strcpy(commentList[index], old);
12518 // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
12519 if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces)) {
12520 if(addBraces) addBraces = FALSE; else { text++; len--; }
12521 while (*text == '\n') { text++; len--; }
12522 commentList[index][--oldlen] = NULLCHAR;
12524 if(addBraces) strcat(commentList[index], "\n{\n");
12525 else strcat(commentList[index], "\n");
12526 strcat(commentList[index], text);
12527 if(addBraces) strcat(commentList[index], "\n}\n");
12528 else strcat(commentList[index], "\n");
12530 commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
12532 strcpy(commentList[index], "{\n");
12533 else commentList[index][0] = NULLCHAR;
12534 strcat(commentList[index], text);
12535 strcat(commentList[index], "\n");
12536 if(addBraces) strcat(commentList[index], "}\n");
12540 static char * FindStr( char * text, char * sub_text )
12542 char * result = strstr( text, sub_text );
12544 if( result != NULL ) {
12545 result += strlen( sub_text );
12551 /* [AS] Try to extract PV info from PGN comment */
12552 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
12553 char *GetInfoFromComment( int index, char * text )
12557 if( text != NULL && index > 0 ) {
12560 int time = -1, sec = 0, deci;
12561 char * s_eval = FindStr( text, "[%eval " );
12562 char * s_emt = FindStr( text, "[%emt " );
12564 if( s_eval != NULL || s_emt != NULL ) {
12568 if( s_eval != NULL ) {
12569 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
12573 if( delim != ']' ) {
12578 if( s_emt != NULL ) {
12583 /* We expect something like: [+|-]nnn.nn/dd */
12586 if(*text != '{') return text; // [HGM] braces: must be normal comment
12588 sep = strchr( text, '/' );
12589 if( sep == NULL || sep < (text+4) ) {
12593 time = -1; sec = -1; deci = -1;
12594 if( sscanf( text+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
12595 sscanf( text+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
12596 sscanf( text+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
12597 sscanf( text+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3 ) {
12601 if( score_lo < 0 || score_lo >= 100 ) {
12605 if(sec >= 0) time = 600*time + 10*sec; else
12606 if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
12608 score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
12610 /* [HGM] PV time: now locate end of PV info */
12611 while( *++sep >= '0' && *sep <= '9'); // strip depth
12613 while( *++sep >= '0' && *sep <= '9'); // strip time
12615 while( *++sep >= '0' && *sep <= '9'); // strip seconds
12617 while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
12618 while(*sep == ' ') sep++;
12629 pvInfoList[index-1].depth = depth;
12630 pvInfoList[index-1].score = score;
12631 pvInfoList[index-1].time = 10*time; // centi-sec
12632 if(*sep == '}') *sep = 0; else *--sep = '{';
12638 SendToProgram(message, cps)
12640 ChessProgramState *cps;
12642 int count, outCount, error;
12645 if (cps->pr == NULL) return;
12648 if (appData.debugMode) {
12651 fprintf(debugFP, "%ld >%-6s: %s",
12652 SubtractTimeMarks(&now, &programStartTime),
12653 cps->which, message);
12656 count = strlen(message);
12657 outCount = OutputToProcess(cps->pr, message, count, &error);
12658 if (outCount < count && !exiting
12659 && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
12660 sprintf(buf, _("Error writing to %s chess program"), cps->which);
12661 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
12662 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
12663 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
12664 sprintf(buf, "%s program exits in draw position (%s)", cps->which, cps->program);
12666 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
12668 gameInfo.resultDetails = StrSave(buf);
12670 if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
12675 ReceiveFromProgram(isr, closure, message, count, error)
12676 InputSourceRef isr;
12684 ChessProgramState *cps = (ChessProgramState *)closure;
12686 if (isr != cps->isr) return; /* Killed intentionally */
12690 _("Error: %s chess program (%s) exited unexpectedly"),
12691 cps->which, cps->program);
12692 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
12693 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
12694 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
12695 sprintf(buf, _("%s program exits in draw position (%s)"), cps->which, cps->program);
12697 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
12699 gameInfo.resultDetails = StrSave(buf);
12701 RemoveInputSource(cps->isr);
12702 if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
12705 _("Error reading from %s chess program (%s)"),
12706 cps->which, cps->program);
12707 RemoveInputSource(cps->isr);
12709 /* [AS] Program is misbehaving badly... kill it */
12710 if( count == -2 ) {
12711 DestroyChildProcess( cps->pr, 9 );
12715 if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
12720 if ((end_str = strchr(message, '\r')) != NULL)
12721 *end_str = NULLCHAR;
12722 if ((end_str = strchr(message, '\n')) != NULL)
12723 *end_str = NULLCHAR;
12725 if (appData.debugMode) {
12726 TimeMark now; int print = 1;
12727 char *quote = ""; char c; int i;
12729 if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
12730 char start = message[0];
12731 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
12732 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
12733 sscanf(message, "move %c", &c)!=1 && sscanf(message, "offer%c", &c)!=1 &&
12734 sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
12735 sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
12736 sscanf(message, "tell%c", &c)!=1 && sscanf(message, "0-1 %c", &c)!=1 &&
12737 sscanf(message, "1-0 %c", &c)!=1 && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
12738 sscanf(message, "pong %c", &c)!=1 && start != '#')
12739 { quote = "# "; print = (appData.engineComments == 2); }
12740 message[0] = start; // restore original message
12744 fprintf(debugFP, "%ld <%-6s: %s%s\n",
12745 SubtractTimeMarks(&now, &programStartTime), cps->which,
12751 /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
12752 if (appData.icsEngineAnalyze) {
12753 if (strstr(message, "whisper") != NULL ||
12754 strstr(message, "kibitz") != NULL ||
12755 strstr(message, "tellics") != NULL) return;
12758 HandleMachineMove(message, cps);
12763 SendTimeControl(cps, mps, tc, inc, sd, st)
12764 ChessProgramState *cps;
12765 int mps, inc, sd, st;
12771 if( timeControl_2 > 0 ) {
12772 if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
12773 tc = timeControl_2;
12776 tc /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
12777 inc /= cps->timeOdds;
12778 st /= cps->timeOdds;
12780 seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
12783 /* Set exact time per move, normally using st command */
12784 if (cps->stKludge) {
12785 /* GNU Chess 4 has no st command; uses level in a nonstandard way */
12787 if (seconds == 0) {
12788 sprintf(buf, "level 1 %d\n", st/60);
12790 sprintf(buf, "level 1 %d:%02d\n", st/60, seconds);
12793 sprintf(buf, "st %d\n", st);
12796 /* Set conventional or incremental time control, using level command */
12797 if (seconds == 0) {
12798 /* Note old gnuchess bug -- minutes:seconds used to not work.
12799 Fixed in later versions, but still avoid :seconds
12800 when seconds is 0. */
12801 sprintf(buf, "level %d %ld %d\n", mps, tc/60000, inc/1000);
12803 sprintf(buf, "level %d %ld:%02d %d\n", mps, tc/60000,
12804 seconds, inc/1000);
12807 SendToProgram(buf, cps);
12809 /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
12810 /* Orthogonally, limit search to given depth */
12812 if (cps->sdKludge) {
12813 sprintf(buf, "depth\n%d\n", sd);
12815 sprintf(buf, "sd %d\n", sd);
12817 SendToProgram(buf, cps);
12820 if(cps->nps > 0) { /* [HGM] nps */
12821 if(cps->supportsNPS == FALSE) cps->nps = -1; // don't use if engine explicitly says not supported!
12823 sprintf(buf, "nps %d\n", cps->nps);
12824 SendToProgram(buf, cps);
12829 ChessProgramState *WhitePlayer()
12830 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
12832 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
12833 gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
12839 SendTimeRemaining(cps, machineWhite)
12840 ChessProgramState *cps;
12841 int /*boolean*/ machineWhite;
12843 char message[MSG_SIZ];
12846 /* Note: this routine must be called when the clocks are stopped
12847 or when they have *just* been set or switched; otherwise
12848 it will be off by the time since the current tick started.
12850 if (machineWhite) {
12851 time = whiteTimeRemaining / 10;
12852 otime = blackTimeRemaining / 10;
12854 time = blackTimeRemaining / 10;
12855 otime = whiteTimeRemaining / 10;
12857 /* [HGM] translate opponent's time by time-odds factor */
12858 otime = (otime * cps->other->timeOdds) / cps->timeOdds;
12859 if (appData.debugMode) {
12860 fprintf(debugFP, "time odds: %f %f \n", cps->timeOdds, cps->other->timeOdds);
12863 if (time <= 0) time = 1;
12864 if (otime <= 0) otime = 1;
12866 sprintf(message, "time %ld\n", time);
12867 SendToProgram(message, cps);
12869 sprintf(message, "otim %ld\n", otime);
12870 SendToProgram(message, cps);
12874 BoolFeature(p, name, loc, cps)
12878 ChessProgramState *cps;
12881 int len = strlen(name);
12883 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
12885 sscanf(*p, "%d", &val);
12887 while (**p && **p != ' ') (*p)++;
12888 sprintf(buf, "accepted %s\n", name);
12889 SendToProgram(buf, cps);
12896 IntFeature(p, name, loc, cps)
12900 ChessProgramState *cps;
12903 int len = strlen(name);
12904 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
12906 sscanf(*p, "%d", loc);
12907 while (**p && **p != ' ') (*p)++;
12908 sprintf(buf, "accepted %s\n", name);
12909 SendToProgram(buf, cps);
12916 StringFeature(p, name, loc, cps)
12920 ChessProgramState *cps;
12923 int len = strlen(name);
12924 if (strncmp((*p), name, len) == 0
12925 && (*p)[len] == '=' && (*p)[len+1] == '\"') {
12927 sscanf(*p, "%[^\"]", loc);
12928 while (**p && **p != '\"') (*p)++;
12929 if (**p == '\"') (*p)++;
12930 sprintf(buf, "accepted %s\n", name);
12931 SendToProgram(buf, cps);
12938 ParseOption(Option *opt, ChessProgramState *cps)
12939 // [HGM] options: process the string that defines an engine option, and determine
12940 // name, type, default value, and allowed value range
12942 char *p, *q, buf[MSG_SIZ];
12943 int n, min = (-1)<<31, max = 1<<31, def;
12945 if(p = strstr(opt->name, " -spin ")) {
12946 if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
12947 if(max < min) max = min; // enforce consistency
12948 if(def < min) def = min;
12949 if(def > max) def = max;
12954 } else if((p = strstr(opt->name, " -slider "))) {
12955 // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
12956 if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
12957 if(max < min) max = min; // enforce consistency
12958 if(def < min) def = min;
12959 if(def > max) def = max;
12963 opt->type = Spin; // Slider;
12964 } else if((p = strstr(opt->name, " -string "))) {
12965 opt->textValue = p+9;
12966 opt->type = TextBox;
12967 } else if((p = strstr(opt->name, " -file "))) {
12968 // for now -file is a synonym for -string, to already provide compatibility with future polyglots
12969 opt->textValue = p+7;
12970 opt->type = TextBox; // FileName;
12971 } else if((p = strstr(opt->name, " -path "))) {
12972 // for now -file is a synonym for -string, to already provide compatibility with future polyglots
12973 opt->textValue = p+7;
12974 opt->type = TextBox; // PathName;
12975 } else if(p = strstr(opt->name, " -check ")) {
12976 if(sscanf(p, " -check %d", &def) < 1) return FALSE;
12977 opt->value = (def != 0);
12978 opt->type = CheckBox;
12979 } else if(p = strstr(opt->name, " -combo ")) {
12980 opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
12981 cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
12982 if(*q == '*') cps->comboList[cps->comboCnt-1]++;
12983 opt->value = n = 0;
12984 while(q = StrStr(q, " /// ")) {
12985 n++; *q = 0; // count choices, and null-terminate each of them
12987 if(*q == '*') { // remember default, which is marked with * prefix
12991 cps->comboList[cps->comboCnt++] = q;
12993 cps->comboList[cps->comboCnt++] = NULL;
12995 opt->type = ComboBox;
12996 } else if(p = strstr(opt->name, " -button")) {
12997 opt->type = Button;
12998 } else if(p = strstr(opt->name, " -save")) {
12999 opt->type = SaveButton;
13000 } else return FALSE;
13001 *p = 0; // terminate option name
13002 // now look if the command-line options define a setting for this engine option.
13003 if(cps->optionSettings && cps->optionSettings[0])
13004 p = strstr(cps->optionSettings, opt->name); else p = NULL;
13005 if(p && (p == cps->optionSettings || p[-1] == ',')) {
13006 sprintf(buf, "option %s", p);
13007 if(p = strstr(buf, ",")) *p = 0;
13009 SendToProgram(buf, cps);
13015 FeatureDone(cps, val)
13016 ChessProgramState* cps;
13019 DelayedEventCallback cb = GetDelayedEvent();
13020 if ((cb == InitBackEnd3 && cps == &first) ||
13021 (cb == TwoMachinesEventIfReady && cps == &second)) {
13022 CancelDelayedEvent();
13023 ScheduleDelayedEvent(cb, val ? 1 : 3600000);
13025 cps->initDone = val;
13028 /* Parse feature command from engine */
13030 ParseFeatures(args, cps)
13032 ChessProgramState *cps;
13040 while (*p == ' ') p++;
13041 if (*p == NULLCHAR) return;
13043 if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
13044 if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
13045 if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
13046 if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
13047 if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
13048 if (BoolFeature(&p, "reuse", &val, cps)) {
13049 /* Engine can disable reuse, but can't enable it if user said no */
13050 if (!val) cps->reuse = FALSE;
13053 if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
13054 if (StringFeature(&p, "myname", &cps->tidy, cps)) {
13055 if (gameMode == TwoMachinesPlay) {
13056 DisplayTwoMachinesTitle();
13062 if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
13063 if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
13064 if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
13065 if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
13066 if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
13067 if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
13068 if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
13069 if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
13070 if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
13071 if (IntFeature(&p, "done", &val, cps)) {
13072 FeatureDone(cps, val);
13075 /* Added by Tord: */
13076 if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
13077 if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
13078 /* End of additions by Tord */
13080 /* [HGM] added features: */
13081 if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
13082 if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
13083 if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
13084 if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
13085 if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
13086 if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
13087 if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
13088 if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
13089 sprintf(buf, "rejected option %s\n", cps->option[--cps->nrOptions].name);
13090 SendToProgram(buf, cps);
13093 if(cps->nrOptions >= MAX_OPTIONS) {
13095 sprintf(buf, "%s engine has too many options\n", cps->which);
13096 DisplayError(buf, 0);
13100 if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
13101 /* End of additions by HGM */
13103 /* unknown feature: complain and skip */
13105 while (*q && *q != '=') q++;
13106 sprintf(buf, "rejected %.*s\n", (int)(q-p), p);
13107 SendToProgram(buf, cps);
13113 while (*p && *p != '\"') p++;
13114 if (*p == '\"') p++;
13116 while (*p && *p != ' ') p++;
13124 PeriodicUpdatesEvent(newState)
13127 if (newState == appData.periodicUpdates)
13130 appData.periodicUpdates=newState;
13132 /* Display type changes, so update it now */
13133 // DisplayAnalysis();
13135 /* Get the ball rolling again... */
13137 AnalysisPeriodicEvent(1);
13138 StartAnalysisClock();
13143 PonderNextMoveEvent(newState)
13146 if (newState == appData.ponderNextMove) return;
13147 if (gameMode == EditPosition) EditPositionDone(TRUE);
13149 SendToProgram("hard\n", &first);
13150 if (gameMode == TwoMachinesPlay) {
13151 SendToProgram("hard\n", &second);
13154 SendToProgram("easy\n", &first);
13155 thinkOutput[0] = NULLCHAR;
13156 if (gameMode == TwoMachinesPlay) {
13157 SendToProgram("easy\n", &second);
13160 appData.ponderNextMove = newState;
13164 NewSettingEvent(option, command, value)
13170 if (gameMode == EditPosition) EditPositionDone(TRUE);
13171 sprintf(buf, "%s%s %d\n", (option ? "option ": ""), command, value);
13172 SendToProgram(buf, &first);
13173 if (gameMode == TwoMachinesPlay) {
13174 SendToProgram(buf, &second);
13179 ShowThinkingEvent()
13180 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
13182 static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
13183 int newState = appData.showThinking
13184 // [HGM] thinking: other features now need thinking output as well
13185 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
13187 if (oldState == newState) return;
13188 oldState = newState;
13189 if (gameMode == EditPosition) EditPositionDone(TRUE);
13191 SendToProgram("post\n", &first);
13192 if (gameMode == TwoMachinesPlay) {
13193 SendToProgram("post\n", &second);
13196 SendToProgram("nopost\n", &first);
13197 thinkOutput[0] = NULLCHAR;
13198 if (gameMode == TwoMachinesPlay) {
13199 SendToProgram("nopost\n", &second);
13202 // appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
13206 AskQuestionEvent(title, question, replyPrefix, which)
13207 char *title; char *question; char *replyPrefix; char *which;
13209 ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
13210 if (pr == NoProc) return;
13211 AskQuestion(title, question, replyPrefix, pr);
13215 DisplayMove(moveNumber)
13218 char message[MSG_SIZ];
13220 char cpThinkOutput[MSG_SIZ];
13222 if(appData.noGUI) return; // [HGM] fast: suppress display of moves
13224 if (moveNumber == forwardMostMove - 1 ||
13225 gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13227 safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput));
13229 if (strchr(cpThinkOutput, '\n')) {
13230 *strchr(cpThinkOutput, '\n') = NULLCHAR;
13233 *cpThinkOutput = NULLCHAR;
13236 /* [AS] Hide thinking from human user */
13237 if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
13238 *cpThinkOutput = NULLCHAR;
13239 if( thinkOutput[0] != NULLCHAR ) {
13242 for( i=0; i<=hiddenThinkOutputState; i++ ) {
13243 cpThinkOutput[i] = '.';
13245 cpThinkOutput[i] = NULLCHAR;
13246 hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
13250 if (moveNumber == forwardMostMove - 1 &&
13251 gameInfo.resultDetails != NULL) {
13252 if (gameInfo.resultDetails[0] == NULLCHAR) {
13253 sprintf(res, " %s", PGNResult(gameInfo.result));
13255 sprintf(res, " {%s} %s",
13256 gameInfo.resultDetails, PGNResult(gameInfo.result));
13262 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
13263 DisplayMessage(res, cpThinkOutput);
13265 sprintf(message, "%d.%s%s%s", moveNumber / 2 + 1,
13266 WhiteOnMove(moveNumber) ? " " : ".. ",
13267 parseList[moveNumber], res);
13268 DisplayMessage(message, cpThinkOutput);
13273 DisplayComment(moveNumber, text)
13277 char title[MSG_SIZ];
13278 char buf[8000]; // comment can be long!
13281 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
13282 strcpy(title, "Comment");
13284 sprintf(title, "Comment on %d.%s%s", moveNumber / 2 + 1,
13285 WhiteOnMove(moveNumber) ? " " : ".. ",
13286 parseList[moveNumber]);
13288 // [HGM] PV info: display PV info together with (or as) comment
13289 if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {
13290 if(text == NULL) text = "";
13291 score = pvInfoList[moveNumber].score;
13292 sprintf(buf, "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,
13293 depth, (pvInfoList[moveNumber].time+50)/100, text);
13296 if (text != NULL && (appData.autoDisplayComment || commentUp))
13297 CommentPopUp(title, text);
13300 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
13301 * might be busy thinking or pondering. It can be omitted if your
13302 * gnuchess is configured to stop thinking immediately on any user
13303 * input. However, that gnuchess feature depends on the FIONREAD
13304 * ioctl, which does not work properly on some flavors of Unix.
13308 ChessProgramState *cps;
13311 if (!cps->useSigint) return;
13312 if (appData.noChessProgram || (cps->pr == NoProc)) return;
13313 switch (gameMode) {
13314 case MachinePlaysWhite:
13315 case MachinePlaysBlack:
13316 case TwoMachinesPlay:
13317 case IcsPlayingWhite:
13318 case IcsPlayingBlack:
13321 /* Skip if we know it isn't thinking */
13322 if (!cps->maybeThinking) return;
13323 if (appData.debugMode)
13324 fprintf(debugFP, "Interrupting %s\n", cps->which);
13325 InterruptChildProcess(cps->pr);
13326 cps->maybeThinking = FALSE;
13331 #endif /*ATTENTION*/
13337 if (whiteTimeRemaining <= 0) {
13340 if (appData.icsActive) {
13341 if (appData.autoCallFlag &&
13342 gameMode == IcsPlayingBlack && !blackFlag) {
13343 SendToICS(ics_prefix);
13344 SendToICS("flag\n");
13348 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
13350 if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
13351 if (appData.autoCallFlag) {
13352 GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
13359 if (blackTimeRemaining <= 0) {
13362 if (appData.icsActive) {
13363 if (appData.autoCallFlag &&
13364 gameMode == IcsPlayingWhite && !whiteFlag) {
13365 SendToICS(ics_prefix);
13366 SendToICS("flag\n");
13370 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
13372 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
13373 if (appData.autoCallFlag) {
13374 GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
13387 if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
13388 gameMode == PlayFromGameFile || forwardMostMove == 0) return;
13391 * add time to clocks when time control is achieved ([HGM] now also used for increment)
13393 if ( !WhiteOnMove(forwardMostMove) )
13394 /* White made time control */
13395 whiteTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
13396 /* [HGM] time odds: correct new time quota for time odds! */
13397 / WhitePlayer()->timeOdds;
13399 /* Black made time control */
13400 blackTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
13401 / WhitePlayer()->other->timeOdds;
13405 DisplayBothClocks()
13407 int wom = gameMode == EditPosition ?
13408 !blackPlaysFirst : WhiteOnMove(currentMove);
13409 DisplayWhiteClock(whiteTimeRemaining, wom);
13410 DisplayBlackClock(blackTimeRemaining, !wom);
13414 /* Timekeeping seems to be a portability nightmare. I think everyone
13415 has ftime(), but I'm really not sure, so I'm including some ifdefs
13416 to use other calls if you don't. Clocks will be less accurate if
13417 you have neither ftime nor gettimeofday.
13420 /* VS 2008 requires the #include outside of the function */
13421 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
13422 #include <sys/timeb.h>
13425 /* Get the current time as a TimeMark */
13430 #if HAVE_GETTIMEOFDAY
13432 struct timeval timeVal;
13433 struct timezone timeZone;
13435 gettimeofday(&timeVal, &timeZone);
13436 tm->sec = (long) timeVal.tv_sec;
13437 tm->ms = (int) (timeVal.tv_usec / 1000L);
13439 #else /*!HAVE_GETTIMEOFDAY*/
13442 // include <sys/timeb.h> / moved to just above start of function
13443 struct timeb timeB;
13446 tm->sec = (long) timeB.time;
13447 tm->ms = (int) timeB.millitm;
13449 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
13450 tm->sec = (long) time(NULL);
13456 /* Return the difference in milliseconds between two
13457 time marks. We assume the difference will fit in a long!
13460 SubtractTimeMarks(tm2, tm1)
13461 TimeMark *tm2, *tm1;
13463 return 1000L*(tm2->sec - tm1->sec) +
13464 (long) (tm2->ms - tm1->ms);
13469 * Code to manage the game clocks.
13471 * In tournament play, black starts the clock and then white makes a move.
13472 * We give the human user a slight advantage if he is playing white---the
13473 * clocks don't run until he makes his first move, so it takes zero time.
13474 * Also, we don't account for network lag, so we could get out of sync
13475 * with GNU Chess's clock -- but then, referees are always right.
13478 static TimeMark tickStartTM;
13479 static long intendedTickLength;
13482 NextTickLength(timeRemaining)
13483 long timeRemaining;
13485 long nominalTickLength, nextTickLength;
13487 if (timeRemaining > 0L && timeRemaining <= 10000L)
13488 nominalTickLength = 100L;
13490 nominalTickLength = 1000L;
13491 nextTickLength = timeRemaining % nominalTickLength;
13492 if (nextTickLength <= 0) nextTickLength += nominalTickLength;
13494 return nextTickLength;
13497 /* Adjust clock one minute up or down */
13499 AdjustClock(Boolean which, int dir)
13501 if(which) blackTimeRemaining += 60000*dir;
13502 else whiteTimeRemaining += 60000*dir;
13503 DisplayBothClocks();
13506 /* Stop clocks and reset to a fresh time control */
13510 (void) StopClockTimer();
13511 if (appData.icsActive) {
13512 whiteTimeRemaining = blackTimeRemaining = 0;
13513 } else if (searchTime) {
13514 whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
13515 blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
13516 } else { /* [HGM] correct new time quote for time odds */
13517 whiteTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->timeOdds;
13518 blackTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->other->timeOdds;
13520 if (whiteFlag || blackFlag) {
13522 whiteFlag = blackFlag = FALSE;
13524 DisplayBothClocks();
13527 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
13529 /* Decrement running clock by amount of time that has passed */
13533 long timeRemaining;
13534 long lastTickLength, fudge;
13537 if (!appData.clockMode) return;
13538 if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
13542 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13544 /* Fudge if we woke up a little too soon */
13545 fudge = intendedTickLength - lastTickLength;
13546 if (fudge < 0 || fudge > FUDGE) fudge = 0;
13548 if (WhiteOnMove(forwardMostMove)) {
13549 if(whiteNPS >= 0) lastTickLength = 0;
13550 timeRemaining = whiteTimeRemaining -= lastTickLength;
13551 DisplayWhiteClock(whiteTimeRemaining - fudge,
13552 WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
13554 if(blackNPS >= 0) lastTickLength = 0;
13555 timeRemaining = blackTimeRemaining -= lastTickLength;
13556 DisplayBlackClock(blackTimeRemaining - fudge,
13557 !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
13560 if (CheckFlags()) return;
13563 intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
13564 StartClockTimer(intendedTickLength);
13566 /* if the time remaining has fallen below the alarm threshold, sound the
13567 * alarm. if the alarm has sounded and (due to a takeback or time control
13568 * with increment) the time remaining has increased to a level above the
13569 * threshold, reset the alarm so it can sound again.
13572 if (appData.icsActive && appData.icsAlarm) {
13574 /* make sure we are dealing with the user's clock */
13575 if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
13576 ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
13579 if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
13580 alarmSounded = FALSE;
13581 } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
13583 alarmSounded = TRUE;
13589 /* A player has just moved, so stop the previously running
13590 clock and (if in clock mode) start the other one.
13591 We redisplay both clocks in case we're in ICS mode, because
13592 ICS gives us an update to both clocks after every move.
13593 Note that this routine is called *after* forwardMostMove
13594 is updated, so the last fractional tick must be subtracted
13595 from the color that is *not* on move now.
13600 long lastTickLength;
13602 int flagged = FALSE;
13606 if (StopClockTimer() && appData.clockMode) {
13607 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13608 if (WhiteOnMove(forwardMostMove)) {
13609 if(blackNPS >= 0) lastTickLength = 0;
13610 blackTimeRemaining -= lastTickLength;
13611 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
13612 // if(pvInfoList[forwardMostMove-1].time == -1)
13613 pvInfoList[forwardMostMove-1].time = // use GUI time
13614 (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
13616 if(whiteNPS >= 0) lastTickLength = 0;
13617 whiteTimeRemaining -= lastTickLength;
13618 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
13619 // if(pvInfoList[forwardMostMove-1].time == -1)
13620 pvInfoList[forwardMostMove-1].time =
13621 (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
13623 flagged = CheckFlags();
13625 CheckTimeControl();
13627 if (flagged || !appData.clockMode) return;
13629 switch (gameMode) {
13630 case MachinePlaysBlack:
13631 case MachinePlaysWhite:
13632 case BeginningOfGame:
13633 if (pausing) return;
13637 case PlayFromGameFile:
13645 if (searchTime) { // [HGM] st: set clock of player that has to move to max time
13646 if(WhiteOnMove(forwardMostMove))
13647 whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
13648 else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
13652 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
13653 whiteTimeRemaining : blackTimeRemaining);
13654 StartClockTimer(intendedTickLength);
13658 /* Stop both clocks */
13662 long lastTickLength;
13665 if (!StopClockTimer()) return;
13666 if (!appData.clockMode) return;
13670 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13671 if (WhiteOnMove(forwardMostMove)) {
13672 if(whiteNPS >= 0) lastTickLength = 0;
13673 whiteTimeRemaining -= lastTickLength;
13674 DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
13676 if(blackNPS >= 0) lastTickLength = 0;
13677 blackTimeRemaining -= lastTickLength;
13678 DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
13683 /* Start clock of player on move. Time may have been reset, so
13684 if clock is already running, stop and restart it. */
13688 (void) StopClockTimer(); /* in case it was running already */
13689 DisplayBothClocks();
13690 if (CheckFlags()) return;
13692 if (!appData.clockMode) return;
13693 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
13695 GetTimeMark(&tickStartTM);
13696 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
13697 whiteTimeRemaining : blackTimeRemaining);
13699 /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
13700 whiteNPS = blackNPS = -1;
13701 if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
13702 || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
13703 whiteNPS = first.nps;
13704 if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
13705 || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
13706 blackNPS = first.nps;
13707 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
13708 whiteNPS = second.nps;
13709 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
13710 blackNPS = second.nps;
13711 if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
13713 StartClockTimer(intendedTickLength);
13720 long second, minute, hour, day;
13722 static char buf[32];
13724 if (ms > 0 && ms <= 9900) {
13725 /* convert milliseconds to tenths, rounding up */
13726 double tenths = floor( ((double)(ms + 99L)) / 100.00 );
13728 sprintf(buf, " %03.1f ", tenths/10.0);
13732 /* convert milliseconds to seconds, rounding up */
13733 /* use floating point to avoid strangeness of integer division
13734 with negative dividends on many machines */
13735 second = (long) floor(((double) (ms + 999L)) / 1000.0);
13742 day = second / (60 * 60 * 24);
13743 second = second % (60 * 60 * 24);
13744 hour = second / (60 * 60);
13745 second = second % (60 * 60);
13746 minute = second / 60;
13747 second = second % 60;
13750 sprintf(buf, " %s%ld:%02ld:%02ld:%02ld ",
13751 sign, day, hour, minute, second);
13753 sprintf(buf, " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
13755 sprintf(buf, " %s%2ld:%02ld ", sign, minute, second);
13762 * This is necessary because some C libraries aren't ANSI C compliant yet.
13765 StrStr(string, match)
13766 char *string, *match;
13770 length = strlen(match);
13772 for (i = strlen(string) - length; i >= 0; i--, string++)
13773 if (!strncmp(match, string, length))
13780 StrCaseStr(string, match)
13781 char *string, *match;
13785 length = strlen(match);
13787 for (i = strlen(string) - length; i >= 0; i--, string++) {
13788 for (j = 0; j < length; j++) {
13789 if (ToLower(match[j]) != ToLower(string[j]))
13792 if (j == length) return string;
13806 c1 = ToLower(*s1++);
13807 c2 = ToLower(*s2++);
13808 if (c1 > c2) return 1;
13809 if (c1 < c2) return -1;
13810 if (c1 == NULLCHAR) return 0;
13819 return isupper(c) ? tolower(c) : c;
13827 return islower(c) ? toupper(c) : c;
13829 #endif /* !_amigados */
13837 if ((ret = (char *) malloc(strlen(s) + 1))) {
13844 StrSavePtr(s, savePtr)
13845 char *s, **savePtr;
13850 if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
13851 strcpy(*savePtr, s);
13863 clock = time((time_t *)NULL);
13864 tm = localtime(&clock);
13865 sprintf(buf, "%04d.%02d.%02d",
13866 tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
13867 return StrSave(buf);
13872 PositionToFEN(move, overrideCastling)
13874 char *overrideCastling;
13876 int i, j, fromX, fromY, toX, toY;
13883 whiteToPlay = (gameMode == EditPosition) ?
13884 !blackPlaysFirst : (move % 2 == 0);
13887 /* Piece placement data */
13888 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13890 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
13891 if (boards[move][i][j] == EmptySquare) {
13893 } else { ChessSquare piece = boards[move][i][j];
13894 if (emptycount > 0) {
13895 if(emptycount<10) /* [HGM] can be >= 10 */
13896 *p++ = '0' + emptycount;
13897 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
13900 if(PieceToChar(piece) == '+') {
13901 /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
13903 piece = (ChessSquare)(DEMOTED piece);
13905 *p++ = PieceToChar(piece);
13907 /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
13908 p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
13913 if (emptycount > 0) {
13914 if(emptycount<10) /* [HGM] can be >= 10 */
13915 *p++ = '0' + emptycount;
13916 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
13923 /* [HGM] print Crazyhouse or Shogi holdings */
13924 if( gameInfo.holdingsWidth ) {
13925 *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
13927 for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
13928 piece = boards[move][i][BOARD_WIDTH-1];
13929 if( piece != EmptySquare )
13930 for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
13931 *p++ = PieceToChar(piece);
13933 for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
13934 piece = boards[move][BOARD_HEIGHT-i-1][0];
13935 if( piece != EmptySquare )
13936 for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
13937 *p++ = PieceToChar(piece);
13940 if( q == p ) *p++ = '-';
13946 *p++ = whiteToPlay ? 'w' : 'b';
13949 if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
13950 while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
13952 if(nrCastlingRights) {
13954 if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
13955 /* [HGM] write directly from rights */
13956 if(boards[move][CASTLING][2] != NoRights &&
13957 boards[move][CASTLING][0] != NoRights )
13958 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
13959 if(boards[move][CASTLING][2] != NoRights &&
13960 boards[move][CASTLING][1] != NoRights )
13961 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
13962 if(boards[move][CASTLING][5] != NoRights &&
13963 boards[move][CASTLING][3] != NoRights )
13964 *p++ = boards[move][CASTLING][3] + AAA;
13965 if(boards[move][CASTLING][5] != NoRights &&
13966 boards[move][CASTLING][4] != NoRights )
13967 *p++ = boards[move][CASTLING][4] + AAA;
13970 /* [HGM] write true castling rights */
13971 if( nrCastlingRights == 6 ) {
13972 if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
13973 boards[move][CASTLING][2] != NoRights ) *p++ = 'K';
13974 if(boards[move][CASTLING][1] == BOARD_LEFT &&
13975 boards[move][CASTLING][2] != NoRights ) *p++ = 'Q';
13976 if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
13977 boards[move][CASTLING][5] != NoRights ) *p++ = 'k';
13978 if(boards[move][CASTLING][4] == BOARD_LEFT &&
13979 boards[move][CASTLING][5] != NoRights ) *p++ = 'q';
13982 if (q == p) *p++ = '-'; /* No castling rights */
13986 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
13987 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
13988 /* En passant target square */
13989 if (move > backwardMostMove) {
13990 fromX = moveList[move - 1][0] - AAA;
13991 fromY = moveList[move - 1][1] - ONE;
13992 toX = moveList[move - 1][2] - AAA;
13993 toY = moveList[move - 1][3] - ONE;
13994 if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
13995 toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
13996 boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
13998 /* 2-square pawn move just happened */
14000 *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
14004 } else if(move == backwardMostMove) {
14005 // [HGM] perhaps we should always do it like this, and forget the above?
14006 if((signed char)boards[move][EP_STATUS] >= 0) {
14007 *p++ = boards[move][EP_STATUS] + AAA;
14008 *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
14019 /* [HGM] find reversible plies */
14020 { int i = 0, j=move;
14022 if (appData.debugMode) { int k;
14023 fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
14024 for(k=backwardMostMove; k<=forwardMostMove; k++)
14025 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
14029 while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
14030 if( j == backwardMostMove ) i += initialRulePlies;
14031 sprintf(p, "%d ", i);
14032 p += i>=100 ? 4 : i >= 10 ? 3 : 2;
14034 /* Fullmove number */
14035 sprintf(p, "%d", (move / 2) + 1);
14037 return StrSave(buf);
14041 ParseFEN(board, blackPlaysFirst, fen)
14043 int *blackPlaysFirst;
14053 /* [HGM] by default clear Crazyhouse holdings, if present */
14054 if(gameInfo.holdingsWidth) {
14055 for(i=0; i<BOARD_HEIGHT; i++) {
14056 board[i][0] = EmptySquare; /* black holdings */
14057 board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
14058 board[i][1] = (ChessSquare) 0; /* black counts */
14059 board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
14063 /* Piece placement data */
14064 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
14067 if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
14068 if (*p == '/') p++;
14069 emptycount = gameInfo.boardWidth - j;
14070 while (emptycount--)
14071 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
14073 #if(BOARD_FILES >= 10)
14074 } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
14075 p++; emptycount=10;
14076 if (j + emptycount > gameInfo.boardWidth) return FALSE;
14077 while (emptycount--)
14078 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
14080 } else if (isdigit(*p)) {
14081 emptycount = *p++ - '0';
14082 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
14083 if (j + emptycount > gameInfo.boardWidth) return FALSE;
14084 while (emptycount--)
14085 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
14086 } else if (*p == '+' || isalpha(*p)) {
14087 if (j >= gameInfo.boardWidth) return FALSE;
14089 piece = CharToPiece(*++p);
14090 if(piece == EmptySquare) return FALSE; /* unknown piece */
14091 piece = (ChessSquare) (PROMOTED piece ); p++;
14092 if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
14093 } else piece = CharToPiece(*p++);
14095 if(piece==EmptySquare) return FALSE; /* unknown piece */
14096 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
14097 piece = (ChessSquare) (PROMOTED piece);
14098 if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
14101 board[i][(j++)+gameInfo.holdingsWidth] = piece;
14107 while (*p == '/' || *p == ' ') p++;
14109 /* [HGM] look for Crazyhouse holdings here */
14110 while(*p==' ') p++;
14111 if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
14113 if(*p == '-' ) *p++; /* empty holdings */ else {
14114 if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
14115 /* if we would allow FEN reading to set board size, we would */
14116 /* have to add holdings and shift the board read so far here */
14117 while( (piece = CharToPiece(*p) ) != EmptySquare ) {
14119 if((int) piece >= (int) BlackPawn ) {
14120 i = (int)piece - (int)BlackPawn;
14121 i = PieceToNumber((ChessSquare)i);
14122 if( i >= gameInfo.holdingsSize ) return FALSE;
14123 board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
14124 board[BOARD_HEIGHT-1-i][1]++; /* black counts */
14126 i = (int)piece - (int)WhitePawn;
14127 i = PieceToNumber((ChessSquare)i);
14128 if( i >= gameInfo.holdingsSize ) return FALSE;
14129 board[i][BOARD_WIDTH-1] = piece; /* white holdings */
14130 board[i][BOARD_WIDTH-2]++; /* black holdings */
14134 if(*p == ']') *p++;
14137 while(*p == ' ') p++;
14142 *blackPlaysFirst = FALSE;
14145 *blackPlaysFirst = TRUE;
14151 /* [HGM] We NO LONGER ignore the rest of the FEN notation */
14152 /* return the extra info in global variiables */
14154 /* set defaults in case FEN is incomplete */
14155 board[EP_STATUS] = EP_UNKNOWN;
14156 for(i=0; i<nrCastlingRights; i++ ) {
14157 board[CASTLING][i] =
14158 gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
14159 } /* assume possible unless obviously impossible */
14160 if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
14161 if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
14162 if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
14163 && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
14164 if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
14165 if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
14166 if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
14167 && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
14170 while(*p==' ') p++;
14171 if(nrCastlingRights) {
14172 if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
14173 /* castling indicator present, so default becomes no castlings */
14174 for(i=0; i<nrCastlingRights; i++ ) {
14175 board[CASTLING][i] = NoRights;
14178 while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
14179 (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
14180 ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
14181 ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth) ) {
14182 char c = *p++; int whiteKingFile=NoRights, blackKingFile=NoRights;
14184 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
14185 if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
14186 if(board[0 ][i] == WhiteKing) whiteKingFile = i;
14188 if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
14189 whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
14190 if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
14191 && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
14192 if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
14193 && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
14196 for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
14197 board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
14198 board[CASTLING][2] = whiteKingFile;
14201 for(i=BOARD_LEFT; i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
14202 board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
14203 board[CASTLING][2] = whiteKingFile;
14206 for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
14207 board[CASTLING][3] = i != blackKingFile ? i : NoRights;
14208 board[CASTLING][5] = blackKingFile;
14211 for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
14212 board[CASTLING][4] = i != blackKingFile ? i : NoRights;
14213 board[CASTLING][5] = blackKingFile;
14216 default: /* FRC castlings */
14217 if(c >= 'a') { /* black rights */
14218 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
14219 if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
14220 if(i == BOARD_RGHT) break;
14221 board[CASTLING][5] = i;
14223 if(board[BOARD_HEIGHT-1][c] < BlackPawn ||
14224 board[BOARD_HEIGHT-1][c] >= BlackKing ) break;
14226 board[CASTLING][3] = c;
14228 board[CASTLING][4] = c;
14229 } else { /* white rights */
14230 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
14231 if(board[0][i] == WhiteKing) break;
14232 if(i == BOARD_RGHT) break;
14233 board[CASTLING][2] = i;
14234 c -= AAA - 'a' + 'A';
14235 if(board[0][c] >= WhiteKing) break;
14237 board[CASTLING][0] = c;
14239 board[CASTLING][1] = c;
14243 for(i=0; i<nrCastlingRights; i++)
14244 if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
14245 if (appData.debugMode) {
14246 fprintf(debugFP, "FEN castling rights:");
14247 for(i=0; i<nrCastlingRights; i++)
14248 fprintf(debugFP, " %d", board[CASTLING][i]);
14249 fprintf(debugFP, "\n");
14252 while(*p==' ') p++;
14255 /* read e.p. field in games that know e.p. capture */
14256 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
14257 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
14259 p++; board[EP_STATUS] = EP_NONE;
14261 char c = *p++ - AAA;
14263 if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
14264 if(*p >= '0' && *p <='9') *p++;
14265 board[EP_STATUS] = c;
14270 if(sscanf(p, "%d", &i) == 1) {
14271 FENrulePlies = i; /* 50-move ply counter */
14272 /* (The move number is still ignored) */
14279 EditPositionPasteFEN(char *fen)
14282 Board initial_position;
14284 if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
14285 DisplayError(_("Bad FEN position in clipboard"), 0);
14288 int savedBlackPlaysFirst = blackPlaysFirst;
14289 EditPositionEvent();
14290 blackPlaysFirst = savedBlackPlaysFirst;
14291 CopyBoard(boards[0], initial_position);
14292 initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
14293 EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
14294 DisplayBothClocks();
14295 DrawPosition(FALSE, boards[currentMove]);
14300 static char cseq[12] = "\\ ";
14302 Boolean set_cont_sequence(char *new_seq)
14307 // handle bad attempts to set the sequence
14309 return 0; // acceptable error - no debug
14311 len = strlen(new_seq);
14312 ret = (len > 0) && (len < sizeof(cseq));
14314 strcpy(cseq, new_seq);
14315 else if (appData.debugMode)
14316 fprintf(debugFP, "Invalid continuation sequence \"%s\" (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
14321 reformat a source message so words don't cross the width boundary. internal
14322 newlines are not removed. returns the wrapped size (no null character unless
14323 included in source message). If dest is NULL, only calculate the size required
14324 for the dest buffer. lp argument indicats line position upon entry, and it's
14325 passed back upon exit.
14327 int wrap(char *dest, char *src, int count, int width, int *lp)
14329 int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
14331 cseq_len = strlen(cseq);
14332 old_line = line = *lp;
14333 ansi = len = clen = 0;
14335 for (i=0; i < count; i++)
14337 if (src[i] == '\033')
14340 // if we hit the width, back up
14341 if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
14343 // store i & len in case the word is too long
14344 old_i = i, old_len = len;
14346 // find the end of the last word
14347 while (i && src[i] != ' ' && src[i] != '\n')
14353 // word too long? restore i & len before splitting it
14354 if ((old_i-i+clen) >= width)
14361 if (i && src[i-1] == ' ')
14364 if (src[i] != ' ' && src[i] != '\n')
14371 // now append the newline and continuation sequence
14376 strncpy(dest+len, cseq, cseq_len);
14384 dest[len] = src[i];
14388 if (src[i] == '\n')
14393 if (dest && appData.debugMode)
14395 fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
14396 count, width, line, len, *lp);
14397 show_bytes(debugFP, src, count);
14398 fprintf(debugFP, "\ndest: ");
14399 show_bytes(debugFP, dest, len);
14400 fprintf(debugFP, "\n");
14402 *lp = dest ? line : old_line;
14407 // [HGM] vari: routines for shelving variations
14410 PushTail(int firstMove, int lastMove)
14412 int i, j, nrMoves = lastMove - firstMove;
14414 if(appData.icsActive) { // only in local mode
14415 forwardMostMove = currentMove; // mimic old ICS behavior
14418 if(storedGames >= MAX_VARIATIONS-1) return;
14420 // push current tail of game on stack
14421 savedResult[storedGames] = gameInfo.result;
14422 savedDetails[storedGames] = gameInfo.resultDetails;
14423 gameInfo.resultDetails = NULL;
14424 savedFirst[storedGames] = firstMove;
14425 savedLast [storedGames] = lastMove;
14426 savedFramePtr[storedGames] = framePtr;
14427 framePtr -= nrMoves; // reserve space for the boards
14428 for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
14429 CopyBoard(boards[framePtr+i], boards[firstMove+i]);
14430 for(j=0; j<MOVE_LEN; j++)
14431 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
14432 for(j=0; j<2*MOVE_LEN; j++)
14433 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
14434 timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
14435 timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
14436 pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
14437 pvInfoList[firstMove+i-1].depth = 0;
14438 commentList[framePtr+i] = commentList[firstMove+i];
14439 commentList[firstMove+i] = NULL;
14443 forwardMostMove = currentMove; // truncte game so we can start variation
14444 if(storedGames == 1) GreyRevert(FALSE);
14448 PopTail(Boolean annotate)
14451 char buf[8000], moveBuf[20];
14453 if(appData.icsActive) return FALSE; // only in local mode
14454 if(!storedGames) return FALSE; // sanity
14457 ToNrEvent(savedFirst[storedGames]); // sets currentMove
14458 nrMoves = savedLast[storedGames] - currentMove;
14461 if(!WhiteOnMove(currentMove)) sprintf(buf, "(%d...", currentMove+2>>1);
14462 else strcpy(buf, "(");
14463 for(i=currentMove; i<forwardMostMove; i++) {
14465 sprintf(moveBuf, " %d. %s", i+2>>1, SavePart(parseList[i]));
14466 else sprintf(moveBuf, " %s", SavePart(parseList[i]));
14467 strcat(buf, moveBuf);
14468 if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
14472 for(i=1; i<nrMoves; i++) { // copy last variation back
14473 CopyBoard(boards[currentMove+i], boards[framePtr+i]);
14474 for(j=0; j<MOVE_LEN; j++)
14475 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
14476 for(j=0; j<2*MOVE_LEN; j++)
14477 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
14478 timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
14479 timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
14480 pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
14481 if(commentList[currentMove+i]) free(commentList[currentMove+i]);
14482 commentList[currentMove+i] = commentList[framePtr+i];
14483 commentList[framePtr+i] = NULL;
14485 if(annotate) AppendComment(currentMove+1, buf, FALSE);
14486 framePtr = savedFramePtr[storedGames];
14487 gameInfo.result = savedResult[storedGames];
14488 if(gameInfo.resultDetails != NULL) {
14489 free(gameInfo.resultDetails);
14491 gameInfo.resultDetails = savedDetails[storedGames];
14492 forwardMostMove = currentMove + nrMoves;
14493 if(storedGames == 0) GreyRevert(TRUE);
14499 { // remove all shelved variations
14501 for(i=0; i<storedGames; i++) {
14502 if(savedDetails[i])
14503 free(savedDetails[i]);
14504 savedDetails[i] = NULL;
14506 for(i=framePtr; i<MAX_MOVES; i++) {
14507 if(commentList[i]) free(commentList[i]);
14508 commentList[i] = NULL;
14510 framePtr = MAX_MOVES-1;