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)
1107 if (*appData.icsCommPort != NULLCHAR)
1109 sprintf(buf, _("Could not open comm port %s"),
1110 appData.icsCommPort);
1114 snprintf(buf, sizeof(buf), _("Could not connect to host %s, port %s"),
1115 appData.icsHost, appData.icsPort);
1117 DisplayFatalError(buf, err, 1);
1122 AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1124 AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1126 else if (appData.noChessProgram)
1135 if (*appData.cmailGameName != NULLCHAR)
1138 OpenLoopback(&cmailPR);
1140 AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1144 DisplayMessage("", "");
1145 if (StrCaseCmp(appData.initialMode, "") == 0)
1147 initialMode = BeginningOfGame;
1149 else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0)
1151 initialMode = TwoMachinesPlay;
1153 else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0)
1155 initialMode = AnalyzeFile;
1157 else if (StrCaseCmp(appData.initialMode, "Analysis") == 0)
1159 initialMode = AnalyzeMode;
1161 else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0)
1163 initialMode = MachinePlaysWhite;
1165 else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0)
1167 initialMode = MachinePlaysBlack;
1169 else if (StrCaseCmp(appData.initialMode, "EditGame") == 0)
1171 initialMode = EditGame;
1173 else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0)
1175 initialMode = EditPosition;
1177 else if (StrCaseCmp(appData.initialMode, "Training") == 0)
1179 initialMode = Training;
1183 sprintf(buf, _("Unknown initialMode %s"), appData.initialMode);
1184 DisplayFatalError(buf, 0, 2);
1188 if (appData.matchMode)
1190 /* Set up machine vs. machine match */
1191 if (appData.noChessProgram)
1193 DisplayFatalError(_("Can't have a match with no chess programs"),
1199 if (*appData.loadGameFile != NULLCHAR)
1201 int index = appData.loadGameIndex; // [HGM] autoinc
1202 if(index<0) lastIndex = index = 1;
1203 if (!LoadGameFromFile(appData.loadGameFile,
1205 appData.loadGameFile, FALSE))
1207 DisplayFatalError(_("Bad game file"), 0, 1);
1211 else if (*appData.loadPositionFile != NULLCHAR)
1213 int index = appData.loadPositionIndex; // [HGM] autoinc
1214 if(index<0) lastIndex = index = 1;
1215 if (!LoadPositionFromFile(appData.loadPositionFile,
1217 appData.loadPositionFile))
1219 DisplayFatalError(_("Bad position file"), 0, 1);
1225 else if (*appData.cmailGameName != NULLCHAR)
1227 /* Set up cmail mode */
1228 ReloadCmailMsgEvent(TRUE);
1232 /* Set up other modes */
1233 if (initialMode == AnalyzeFile)
1235 if (*appData.loadGameFile == NULLCHAR)
1237 DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1241 if (*appData.loadGameFile != NULLCHAR)
1243 (void) LoadGameFromFile(appData.loadGameFile,
1244 appData.loadGameIndex,
1245 appData.loadGameFile, TRUE);
1247 else if (*appData.loadPositionFile != NULLCHAR)
1249 (void) LoadPositionFromFile(appData.loadPositionFile,
1250 appData.loadPositionIndex,
1251 appData.loadPositionFile);
1252 /* [HGM] try to make self-starting even after FEN load */
1253 /* to allow automatic setup of fairy variants with wtm */
1254 if(initialMode == BeginningOfGame && !blackPlaysFirst)
1256 gameMode = BeginningOfGame;
1257 setboardSpoiledMachineBlack = 1;
1259 /* [HGM] loadPos: make that every new game uses the setup */
1260 /* from file as long as we do not switch variant */
1261 if(!blackPlaysFirst)
1263 startedFromPositionFile = TRUE;
1264 CopyBoard(filePosition, boards[0]);
1267 if (initialMode == AnalyzeMode)
1269 if (appData.noChessProgram)
1271 DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1274 if (appData.icsActive)
1276 DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1281 else if (initialMode == AnalyzeFile)
1283 appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1284 ShowThinkingEvent();
1286 AnalysisPeriodicEvent(1);
1288 else if (initialMode == MachinePlaysWhite)
1290 if (appData.noChessProgram)
1292 DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1296 if (appData.icsActive)
1298 DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1302 MachineWhiteEvent();
1304 else if (initialMode == MachinePlaysBlack)
1306 if (appData.noChessProgram)
1308 DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1312 if (appData.icsActive)
1314 DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1318 MachineBlackEvent();
1320 else if (initialMode == TwoMachinesPlay)
1322 if (appData.noChessProgram)
1324 DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1328 if (appData.icsActive)
1330 DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1336 else if (initialMode == EditGame)
1340 else if (initialMode == EditPosition)
1342 EditPositionEvent();
1344 else if (initialMode == Training)
1346 if (*appData.loadGameFile == NULLCHAR)
1348 DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1359 * Establish will establish a contact to a remote host.port.
1360 * Sets icsPR to a ProcRef for a process (or pseudo-process)
1361 * used to talk to the host.
1362 * Returns 0 if okay, error code if not.
1369 if (*appData.icsCommPort != NULLCHAR) {
1370 /* Talk to the host through a serial comm port */
1371 return OpenCommPort(appData.icsCommPort, &icsPR);
1373 } else if (*appData.gateway != NULLCHAR) {
1374 if (*appData.remoteShell == NULLCHAR) {
1375 /* Use the rcmd protocol to run telnet program on a gateway host */
1376 snprintf(buf, sizeof(buf), "%s %s %s",
1377 appData.telnetProgram, appData.icsHost, appData.icsPort);
1378 return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1381 /* Use the rsh program to run telnet program on a gateway host */
1382 if (*appData.remoteUser == NULLCHAR) {
1383 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1384 appData.gateway, appData.telnetProgram,
1385 appData.icsHost, appData.icsPort);
1387 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1388 appData.remoteShell, appData.gateway,
1389 appData.remoteUser, appData.telnetProgram,
1390 appData.icsHost, appData.icsPort);
1392 return StartChildProcess(buf, "", &icsPR);
1395 } else if (appData.useTelnet) {
1396 return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1399 /* TCP socket interface differs somewhat between
1400 Unix and NT; handle details in the front end.
1402 return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1407 show_bytes(fp, buf, count)
1413 if (*buf < 040 || *(unsigned char *) buf > 0177) {
1414 fprintf(fp, "\\%03o", *buf & 0xff);
1423 /* Returns an errno value */
1425 OutputMaybeTelnet(pr, message, count, outError)
1431 char buf[8192], *p, *q, *buflim;
1432 int left, newcount, outcount;
1434 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1435 *appData.gateway != NULLCHAR) {
1436 if (appData.debugMode) {
1437 fprintf(debugFP, ">ICS: ");
1438 show_bytes(debugFP, message, count);
1439 fprintf(debugFP, "\n");
1441 return OutputToProcess(pr, message, count, outError);
1444 buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1451 if (appData.debugMode) {
1452 fprintf(debugFP, ">ICS: ");
1453 show_bytes(debugFP, buf, newcount);
1454 fprintf(debugFP, "\n");
1456 outcount = OutputToProcess(pr, buf, newcount, outError);
1457 if (outcount < newcount) return -1; /* to be sure */
1464 } else if (((unsigned char) *p) == TN_IAC) {
1465 *q++ = (char) TN_IAC;
1472 if (appData.debugMode) {
1473 fprintf(debugFP, ">ICS: ");
1474 show_bytes(debugFP, buf, newcount);
1475 fprintf(debugFP, "\n");
1477 outcount = OutputToProcess(pr, buf, newcount, outError);
1478 if (outcount < newcount) return -1; /* to be sure */
1483 read_from_player(isr, closure, message, count, error)
1490 int outError, outCount;
1491 static int gotEof = 0;
1493 /* Pass data read from player on to ICS */
1496 outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1497 if (outCount < count) {
1498 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1500 } else if (count < 0) {
1501 RemoveInputSource(isr);
1502 DisplayFatalError(_("Error reading from keyboard"), error, 1);
1503 } else if (gotEof++ > 0) {
1504 RemoveInputSource(isr);
1505 DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1511 { // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1512 SendToICS("date\n");
1513 if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1516 /* added routine for printf style output to ics */
1517 void ics_printf(char *format, ...)
1519 char buffer[MSG_SIZ];
1522 va_start(args, format);
1523 vsnprintf(buffer, sizeof(buffer), format, args);
1524 buffer[sizeof(buffer)-1] = '\0';
1533 int count, outCount, outError;
1535 if (icsPR == NULL) return;
1538 outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1539 if (outCount < count) {
1540 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1544 /* This is used for sending logon scripts to the ICS. Sending
1545 without a delay causes problems when using timestamp on ICC
1546 (at least on my machine). */
1548 SendToICSDelayed(s,msdelay)
1552 int count, outCount, outError;
1554 if (icsPR == NULL) return;
1557 if (appData.debugMode) {
1558 fprintf(debugFP, ">ICS: ");
1559 show_bytes(debugFP, s, count);
1560 fprintf(debugFP, "\n");
1562 outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1564 if (outCount < count) {
1565 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1570 /* Remove all highlighting escape sequences in s
1571 Also deletes any suffix starting with '('
1574 StripHighlightAndTitle(s)
1577 static char retbuf[MSG_SIZ];
1580 while (*s != NULLCHAR) {
1581 while (*s == '\033') {
1582 while (*s != NULLCHAR && !isalpha(*s)) s++;
1583 if (*s != NULLCHAR) s++;
1585 while (*s != NULLCHAR && *s != '\033') {
1586 if (*s == '(' || *s == '[') {
1597 /* Remove all highlighting escape sequences in s */
1602 static char retbuf[MSG_SIZ];
1605 while (*s != NULLCHAR) {
1606 while (*s == '\033') {
1607 while (*s != NULLCHAR && !isalpha(*s)) s++;
1608 if (*s != NULLCHAR) s++;
1610 while (*s != NULLCHAR && *s != '\033') {
1618 char *variantNames[] = VARIANT_NAMES;
1623 return variantNames[v];
1627 /* Identify a variant from the strings the chess servers use or the
1628 PGN Variant tag names we use. */
1635 VariantClass v = VariantNormal;
1636 int i, found = FALSE;
1641 /* [HGM] skip over optional board-size prefixes */
1642 if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1643 sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1644 while( *e++ != '_');
1647 if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
1651 for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
1652 if (StrCaseStr(e, variantNames[i])) {
1653 v = (VariantClass) i;
1660 if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
1661 || StrCaseStr(e, "wild/fr")
1662 || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
1663 v = VariantFischeRandom;
1664 } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
1665 (i = 1, p = StrCaseStr(e, "w"))) {
1667 while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
1674 case 0: /* FICS only, actually */
1676 /* Castling legal even if K starts on d-file */
1677 v = VariantWildCastle;
1682 /* Castling illegal even if K & R happen to start in
1683 normal positions. */
1684 v = VariantNoCastle;
1697 /* Castling legal iff K & R start in normal positions */
1703 /* Special wilds for position setup; unclear what to do here */
1704 v = VariantLoadable;
1707 /* Bizarre ICC game */
1708 v = VariantTwoKings;
1711 v = VariantKriegspiel;
1717 v = VariantFischeRandom;
1720 v = VariantCrazyhouse;
1723 v = VariantBughouse;
1729 /* Not quite the same as FICS suicide! */
1730 v = VariantGiveaway;
1736 v = VariantShatranj;
1739 /* Temporary names for future ICC types. The name *will* change in
1740 the next xboard/WinBoard release after ICC defines it. */
1778 v = VariantCapablanca;
1781 v = VariantKnightmate;
1787 v = VariantCylinder;
1793 v = VariantCapaRandom;
1796 v = VariantBerolina;
1808 /* Found "wild" or "w" in the string but no number;
1809 must assume it's normal chess. */
1813 sprintf(buf, _("Unknown wild type %d"), wnum);
1814 DisplayError(buf, 0);
1820 if (appData.debugMode) {
1821 fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
1822 e, wnum, VariantName(v));
1827 static int leftover_start = 0, leftover_len = 0;
1828 char star_match[STAR_MATCH_N][MSG_SIZ];
1830 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
1831 advance *index beyond it, and set leftover_start to the new value of
1832 *index; else return FALSE. If pattern contains the character '*', it
1833 matches any sequence of characters not containing '\r', '\n', or the
1834 character following the '*' (if any), and the matched sequence(s) are
1835 copied into star_match.
1838 looking_at(buf, index, pattern)
1843 char *bufp = &buf[*index], *patternp = pattern;
1845 char *matchp = star_match[0];
1848 if (*patternp == NULLCHAR) {
1849 *index = leftover_start = bufp - buf;
1853 if (*bufp == NULLCHAR) return FALSE;
1854 if (*patternp == '*') {
1855 if (*bufp == *(patternp + 1)) {
1857 matchp = star_match[++star_count];
1861 } else if (*bufp == '\n' || *bufp == '\r') {
1863 if (*patternp == NULLCHAR)
1868 *matchp++ = *bufp++;
1872 if (*patternp != *bufp) return FALSE;
1879 SendToPlayer(data, length)
1883 int error, outCount;
1884 outCount = OutputToProcess(NoProc, data, length, &error);
1885 if (outCount < length) {
1886 DisplayFatalError(_("Error writing to display"), error, 1);
1891 PackHolding(packed, holding)
1903 switch (runlength) {
1914 sprintf(q, "%d", runlength);
1926 /* Telnet protocol requests from the front end */
1928 TelnetRequest(ddww, option)
1929 unsigned char ddww, option;
1931 unsigned char msg[3];
1932 int outCount, outError;
1934 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
1936 if (appData.debugMode) {
1937 char buf1[8], buf2[8], *ddwwStr, *optionStr;
1953 sprintf(buf1, "%d", ddww);
1962 sprintf(buf2, "%d", option);
1965 fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
1970 outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
1972 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1979 if (!appData.icsActive) return;
1980 TelnetRequest(TN_DO, TN_ECHO);
1986 if (!appData.icsActive) return;
1987 TelnetRequest(TN_DONT, TN_ECHO);
1991 CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece)
1993 /* put the holdings sent to us by the server on the board holdings area */
1994 int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
1998 if(gameInfo.holdingsWidth < 2) return;
1999 if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2000 return; // prevent overwriting by pre-board holdings
2002 if( (int)lowestPiece >= BlackPawn ) {
2005 holdingsStartRow = BOARD_HEIGHT-1;
2008 holdingsColumn = BOARD_WIDTH-1;
2009 countsColumn = BOARD_WIDTH-2;
2010 holdingsStartRow = 0;
2014 for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
2015 board[i][holdingsColumn] = EmptySquare;
2016 board[i][countsColumn] = (ChessSquare) 0;
2018 while( (p=*holdings++) != NULLCHAR ) {
2019 piece = CharToPiece( ToUpper(p) );
2020 if(piece == EmptySquare) continue;
2021 /*j = (int) piece - (int) WhitePawn;*/
2022 j = PieceToNumber(piece);
2023 if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2024 if(j < 0) continue; /* should not happen */
2025 piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2026 board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2027 board[holdingsStartRow+j*direction][countsColumn]++;
2033 VariantSwitch(Board board, VariantClass newVariant)
2035 int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2038 startedFromPositionFile = FALSE;
2039 if(gameInfo.variant == newVariant) return;
2041 /* [HGM] This routine is called each time an assignment is made to
2042 * gameInfo.variant during a game, to make sure the board sizes
2043 * are set to match the new variant. If that means adding or deleting
2044 * holdings, we shift the playing board accordingly
2045 * This kludge is needed because in ICS observe mode, we get boards
2046 * of an ongoing game without knowing the variant, and learn about the
2047 * latter only later. This can be because of the move list we requested,
2048 * in which case the game history is refilled from the beginning anyway,
2049 * but also when receiving holdings of a crazyhouse game. In the latter
2050 * case we want to add those holdings to the already received position.
2053 if (appData.debugMode) {
2054 fprintf(debugFP, "Switch board from %s to %s\n",
2055 VariantName(gameInfo.variant), VariantName(newVariant));
2056 setbuf(debugFP, NULL);
2058 shuffleOpenings = 0; /* [HGM] shuffle */
2059 gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2063 newWidth = 9; newHeight = 9;
2064 gameInfo.holdingsSize = 7;
2065 case VariantBughouse:
2066 case VariantCrazyhouse:
2067 newHoldingsWidth = 2; break;
2071 newHoldingsWidth = 2;
2072 gameInfo.holdingsSize = 8;
2075 case VariantCapablanca:
2076 case VariantCapaRandom:
2079 newHoldingsWidth = gameInfo.holdingsSize = 0;
2082 if(newWidth != gameInfo.boardWidth ||
2083 newHeight != gameInfo.boardHeight ||
2084 newHoldingsWidth != gameInfo.holdingsWidth ) {
2086 /* shift position to new playing area, if needed */
2087 if(newHoldingsWidth > gameInfo.holdingsWidth) {
2088 for(i=0; i<BOARD_HEIGHT; i++)
2089 for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2090 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2092 for(i=0; i<newHeight; i++) {
2093 board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2094 board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2096 } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2097 for(i=0; i<BOARD_HEIGHT; i++)
2098 for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2099 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2102 gameInfo.boardWidth = newWidth;
2103 gameInfo.boardHeight = newHeight;
2104 gameInfo.holdingsWidth = newHoldingsWidth;
2105 gameInfo.variant = newVariant;
2106 InitDrawingSizes(-2, 0);
2107 } else gameInfo.variant = newVariant;
2108 CopyBoard(oldBoard, board); // remember correctly formatted board
2109 InitPosition(FALSE); /* this sets up board[0], but also other stuff */
2110 DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2113 static int loggedOn = FALSE;
2115 /*-- Game start info cache: --*/
2117 char gs_kind[MSG_SIZ];
2118 static char player1Name[128] = "";
2119 static char player2Name[128] = "";
2120 static char cont_seq[] = "\n\\ ";
2121 static int player1Rating = -1;
2122 static int player2Rating = -1;
2123 /*----------------------------*/
2125 ColorClass curColor = ColorNormal;
2126 int suppressKibitz = 0;
2129 read_from_ics(isr, closure, data, count, error)
2136 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2137 #define STARTED_NONE 0
2138 #define STARTED_MOVES 1
2139 #define STARTED_BOARD 2
2140 #define STARTED_OBSERVE 3
2141 #define STARTED_HOLDINGS 4
2142 #define STARTED_CHATTER 5
2143 #define STARTED_COMMENT 6
2144 #define STARTED_MOVES_NOHIDE 7
2146 static int started = STARTED_NONE;
2147 static char parse[20000];
2148 static int parse_pos = 0;
2149 static char buf[BUF_SIZE + 1];
2150 static int firstTime = TRUE, intfSet = FALSE;
2151 static ColorClass prevColor = ColorNormal;
2152 static int savingComment = FALSE;
2153 static int cmatch = 0; // continuation sequence match
2160 int backup; /* [DM] For zippy color lines */
2162 char talker[MSG_SIZ]; // [HGM] chat
2165 if (appData.debugMode) {
2167 fprintf(debugFP, "<ICS: ");
2168 show_bytes(debugFP, data, count);
2169 fprintf(debugFP, "\n");
2173 if (appData.debugMode) { int f = forwardMostMove;
2174 fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2175 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2176 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2179 /* If last read ended with a partial line that we couldn't parse,
2180 prepend it to the new read and try again. */
2181 if (leftover_len > 0) {
2182 for (i=0; i<leftover_len; i++)
2183 buf[i] = buf[leftover_start + i];
2186 /* copy new characters into the buffer */
2187 bp = buf + leftover_len;
2188 buf_len=leftover_len;
2189 for (i=0; i<count; i++)
2192 if (data[i] == '\r')
2195 // join lines split by ICS?
2196 if (!appData.noJoin)
2199 Joining just consists of finding matches against the
2200 continuation sequence, and discarding that sequence
2201 if found instead of copying it. So, until a match
2202 fails, there's nothing to do since it might be the
2203 complete sequence, and thus, something we don't want
2206 if (data[i] == cont_seq[cmatch])
2209 if (cmatch == strlen(cont_seq))
2211 cmatch = 0; // complete match. just reset the counter
2214 it's possible for the ICS to not include the space
2215 at the end of the last word, making our [correct]
2216 join operation fuse two separate words. the server
2217 does this when the space occurs at the width setting.
2219 if (!buf_len || buf[buf_len-1] != ' ')
2230 match failed, so we have to copy what matched before
2231 falling through and copying this character. In reality,
2232 this will only ever be just the newline character, but
2233 it doesn't hurt to be precise.
2235 strncpy(bp, cont_seq, cmatch);
2247 buf[buf_len] = NULLCHAR;
2248 // next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2253 while (i < buf_len) {
2254 /* Deal with part of the TELNET option negotiation
2255 protocol. We refuse to do anything beyond the
2256 defaults, except that we allow the WILL ECHO option,
2257 which ICS uses to turn off password echoing when we are
2258 directly connected to it. We reject this option
2259 if localLineEditing mode is on (always on in xboard)
2260 and we are talking to port 23, which might be a real
2261 telnet server that will try to keep WILL ECHO on permanently.
2263 if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2264 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2265 unsigned char option;
2267 switch ((unsigned char) buf[++i]) {
2269 if (appData.debugMode)
2270 fprintf(debugFP, "\n<WILL ");
2271 switch (option = (unsigned char) buf[++i]) {
2273 if (appData.debugMode)
2274 fprintf(debugFP, "ECHO ");
2275 /* Reply only if this is a change, according
2276 to the protocol rules. */
2277 if (remoteEchoOption) break;
2278 if (appData.localLineEditing &&
2279 atoi(appData.icsPort) == TN_PORT) {
2280 TelnetRequest(TN_DONT, TN_ECHO);
2283 TelnetRequest(TN_DO, TN_ECHO);
2284 remoteEchoOption = TRUE;
2288 if (appData.debugMode)
2289 fprintf(debugFP, "%d ", option);
2290 /* Whatever this is, we don't want it. */
2291 TelnetRequest(TN_DONT, option);
2296 if (appData.debugMode)
2297 fprintf(debugFP, "\n<WONT ");
2298 switch (option = (unsigned char) buf[++i]) {
2300 if (appData.debugMode)
2301 fprintf(debugFP, "ECHO ");
2302 /* Reply only if this is a change, according
2303 to the protocol rules. */
2304 if (!remoteEchoOption) break;
2306 TelnetRequest(TN_DONT, TN_ECHO);
2307 remoteEchoOption = FALSE;
2310 if (appData.debugMode)
2311 fprintf(debugFP, "%d ", (unsigned char) option);
2312 /* Whatever this is, it must already be turned
2313 off, because we never agree to turn on
2314 anything non-default, so according to the
2315 protocol rules, we don't reply. */
2320 if (appData.debugMode)
2321 fprintf(debugFP, "\n<DO ");
2322 switch (option = (unsigned char) buf[++i]) {
2324 /* Whatever this is, we refuse to do it. */
2325 if (appData.debugMode)
2326 fprintf(debugFP, "%d ", option);
2327 TelnetRequest(TN_WONT, option);
2332 if (appData.debugMode)
2333 fprintf(debugFP, "\n<DONT ");
2334 switch (option = (unsigned char) buf[++i]) {
2336 if (appData.debugMode)
2337 fprintf(debugFP, "%d ", option);
2338 /* Whatever this is, we are already not doing
2339 it, because we never agree to do anything
2340 non-default, so according to the protocol
2341 rules, we don't reply. */
2346 if (appData.debugMode)
2347 fprintf(debugFP, "\n<IAC ");
2348 /* Doubled IAC; pass it through */
2352 if (appData.debugMode)
2353 fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2354 /* Drop all other telnet commands on the floor */
2357 if (oldi > next_out)
2358 SendToPlayer(&buf[next_out], oldi - next_out);
2364 /* OK, this at least will *usually* work */
2365 if (!loggedOn && looking_at(buf, &i, "ics%")) {
2369 if (loggedOn && !intfSet) {
2370 if (ics_type == ICS_ICC) {
2372 "/set-quietly interface %s\n/set-quietly style 12\n",
2374 } else if (ics_type == ICS_CHESSNET) {
2375 sprintf(str, "/style 12\n");
2377 strcpy(str, "alias $ @\n$set interface ");
2378 strcat(str, programVersion);
2379 strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2381 strcat(str, "$iset nohighlight 1\n");
2383 strcat(str, "$iset lock 1\n$style 12\n");
2386 NotifyFrontendLogin();
2390 if (started == STARTED_COMMENT) {
2391 /* Accumulate characters in comment */
2392 parse[parse_pos++] = buf[i];
2393 if (buf[i] == '\n') {
2394 parse[parse_pos] = NULLCHAR;
2395 if(chattingPartner>=0) {
2397 sprintf(mess, "%s%s", talker, parse);
2398 OutputChatMessage(chattingPartner, mess);
2399 chattingPartner = -1;
2401 if(!suppressKibitz) // [HGM] kibitz
2402 AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
2403 else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2404 int nrDigit = 0, nrAlph = 0, j;
2405 if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2406 { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2407 parse[parse_pos] = NULLCHAR;
2408 // try to be smart: if it does not look like search info, it should go to
2409 // ICS interaction window after all, not to engine-output window.
2410 for(j=0; j<parse_pos; j++) { // count letters and digits
2411 nrDigit += (parse[j] >= '0' && parse[j] <= '9');
2412 nrAlph += (parse[j] >= 'a' && parse[j] <= 'z');
2413 nrAlph += (parse[j] >= 'A' && parse[j] <= 'Z');
2415 if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2416 int depth=0; float score;
2417 if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2418 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2419 pvInfoList[forwardMostMove-1].depth = depth;
2420 pvInfoList[forwardMostMove-1].score = 100*score;
2422 OutputKibitz(suppressKibitz, parse);
2423 next_out = i+1; // [HGM] suppress printing in ICS window
2426 sprintf(tmp, _("your opponent kibitzes: %s"), parse);
2427 SendToPlayer(tmp, strlen(tmp));
2430 started = STARTED_NONE;
2432 /* Don't match patterns against characters in comment */
2437 if (started == STARTED_CHATTER) {
2438 if (buf[i] != '\n') {
2439 /* Don't match patterns against characters in chatter */
2443 started = STARTED_NONE;
2446 /* Kludge to deal with rcmd protocol */
2447 if (firstTime && looking_at(buf, &i, "\001*")) {
2448 DisplayFatalError(&buf[1], 0, 1);
2454 if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2457 if (appData.debugMode)
2458 fprintf(debugFP, "ics_type %d\n", ics_type);
2461 if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
2462 ics_type = ICS_FICS;
2464 if (appData.debugMode)
2465 fprintf(debugFP, "ics_type %d\n", ics_type);
2468 if (!loggedOn && looking_at(buf, &i, "chess.net")) {
2469 ics_type = ICS_CHESSNET;
2471 if (appData.debugMode)
2472 fprintf(debugFP, "ics_type %d\n", ics_type);
2477 (looking_at(buf, &i, "\"*\" is *a registered name") ||
2478 looking_at(buf, &i, "Logging you in as \"*\"") ||
2479 looking_at(buf, &i, "will be \"*\""))) {
2480 strcpy(ics_handle, star_match[0]);
2484 if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
2486 snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
2487 DisplayIcsInteractionTitle(buf);
2488 have_set_title = TRUE;
2491 /* skip finger notes */
2492 if (started == STARTED_NONE &&
2493 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
2494 (buf[i] == '1' && buf[i+1] == '0')) &&
2495 buf[i+2] == ':' && buf[i+3] == ' ') {
2496 started = STARTED_CHATTER;
2501 /* skip formula vars */
2502 if (started == STARTED_NONE &&
2503 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
2504 started = STARTED_CHATTER;
2510 // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
2511 if (appData.autoKibitz && started == STARTED_NONE &&
2512 !appData.icsEngineAnalyze && // [HGM] [DM] ICS analyze
2513 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
2514 if(looking_at(buf, &i, "* kibitzes: ") &&
2515 (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
2516 StrStr(star_match[0], gameInfo.black) == star_match[0] )) { // kibitz of self or opponent
2517 suppressKibitz = TRUE;
2518 if((StrStr(star_match[0], gameInfo.white) == star_match[0]
2519 && (gameMode == IcsPlayingWhite)) ||
2520 (StrStr(star_match[0], gameInfo.black) == star_match[0]
2521 && (gameMode == IcsPlayingBlack)) ) // opponent kibitz
2522 started = STARTED_CHATTER; // own kibitz we simply discard
2524 started = STARTED_COMMENT; // make sure it will be collected in parse[]
2525 parse_pos = 0; parse[0] = NULLCHAR;
2526 savingComment = TRUE;
2527 suppressKibitz = gameMode != IcsObserving ? 2 :
2528 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
2532 if(looking_at(buf, &i, "kibitzed to *\n") && atoi(star_match[0])) {
2533 // suppress the acknowledgements of our own autoKibitz
2534 SendToPlayer(star_match[0], strlen(star_match[0]));
2535 looking_at(buf, &i, "*% "); // eat prompt
2538 } // [HGM] kibitz: end of patch
2540 //if(appData.debugMode) fprintf(debugFP, "hunt for tell, buf = %s\n", buf+i);
2542 // [HGM] chat: intercept tells by users for which we have an open chat window
2544 if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
2545 looking_at(buf, &i, "* whispers:") ||
2546 looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
2547 looking_at(buf, &i, "*(*)(*):") && sscanf(star_match[2], "%d", &channel) == 1 )) {
2549 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
2550 chattingPartner = -1;
2552 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
2553 for(p=0; p<MAX_CHAT; p++) {
2554 if(channel == atoi(chatPartner[p])) {
2555 talker[0] = '['; strcat(talker, "]");
2556 chattingPartner = p; break;
2559 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
2560 for(p=0; p<MAX_CHAT; p++) {
2561 if(!strcmp("WHISPER", chatPartner[p])) {
2562 talker[0] = '['; strcat(talker, "]");
2563 chattingPartner = p; break;
2566 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
2567 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
2569 chattingPartner = p; break;
2571 if(chattingPartner<0) i = oldi; else {
2572 started = STARTED_COMMENT;
2573 parse_pos = 0; parse[0] = NULLCHAR;
2574 savingComment = TRUE;
2575 suppressKibitz = TRUE;
2577 } // [HGM] chat: end of patch
2579 if (appData.zippyTalk || appData.zippyPlay) {
2580 /* [DM] Backup address for color zippy lines */
2584 if (loggedOn == TRUE)
2585 if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
2586 (appData.zippyPlay && ZippyMatch(buf, &backup)));
2588 if (ZippyControl(buf, &i) ||
2589 ZippyConverse(buf, &i) ||
2590 (appData.zippyPlay && ZippyMatch(buf, &i))) {
2592 if (!appData.colorize) continue;
2596 } // [DM] 'else { ' deleted
2598 /* Regular tells and says */
2599 (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
2600 looking_at(buf, &i, "* (your partner) tells you: ") ||
2601 looking_at(buf, &i, "* says: ") ||
2602 /* Don't color "message" or "messages" output */
2603 (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
2604 looking_at(buf, &i, "*. * at *:*: ") ||
2605 looking_at(buf, &i, "--* (*:*): ") ||
2606 /* Message notifications (same color as tells) */
2607 looking_at(buf, &i, "* has left a message ") ||
2608 looking_at(buf, &i, "* just sent you a message:\n") ||
2609 /* Whispers and kibitzes */
2610 (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
2611 looking_at(buf, &i, "* kibitzes: ") ||
2613 (tkind = 3, looking_at(buf, &i, "*(*: "))) {
2615 if (tkind == 1 && strchr(star_match[0], ':')) {
2616 /* Avoid "tells you:" spoofs in channels */
2619 if (star_match[0][0] == NULLCHAR ||
2620 strchr(star_match[0], ' ') ||
2621 (tkind == 3 && strchr(star_match[1], ' '))) {
2622 /* Reject bogus matches */
2625 if (appData.colorize) {
2626 if (oldi > next_out) {
2627 SendToPlayer(&buf[next_out], oldi - next_out);
2632 Colorize(ColorTell, FALSE);
2633 curColor = ColorTell;
2636 Colorize(ColorKibitz, FALSE);
2637 curColor = ColorKibitz;
2640 p = strrchr(star_match[1], '(');
2647 Colorize(ColorChannel1, FALSE);
2648 curColor = ColorChannel1;
2650 Colorize(ColorChannel, FALSE);
2651 curColor = ColorChannel;
2655 curColor = ColorNormal;
2659 if (started == STARTED_NONE && appData.autoComment &&
2660 (gameMode == IcsObserving ||
2661 gameMode == IcsPlayingWhite ||
2662 gameMode == IcsPlayingBlack)) {
2663 parse_pos = i - oldi;
2664 memcpy(parse, &buf[oldi], parse_pos);
2665 parse[parse_pos] = NULLCHAR;
2666 started = STARTED_COMMENT;
2667 savingComment = TRUE;
2669 started = STARTED_CHATTER;
2670 savingComment = FALSE;
2677 if (looking_at(buf, &i, "* s-shouts: ") ||
2678 looking_at(buf, &i, "* c-shouts: ")) {
2679 if (appData.colorize) {
2680 if (oldi > next_out) {
2681 SendToPlayer(&buf[next_out], oldi - next_out);
2684 Colorize(ColorSShout, FALSE);
2685 curColor = ColorSShout;
2688 started = STARTED_CHATTER;
2692 if (looking_at(buf, &i, "--->")) {
2697 if (looking_at(buf, &i, "* shouts: ") ||
2698 looking_at(buf, &i, "--> ")) {
2699 if (appData.colorize) {
2700 if (oldi > next_out) {
2701 SendToPlayer(&buf[next_out], oldi - next_out);
2704 Colorize(ColorShout, FALSE);
2705 curColor = ColorShout;
2708 started = STARTED_CHATTER;
2712 if (looking_at( buf, &i, "Challenge:")) {
2713 if (appData.colorize) {
2714 if (oldi > next_out) {
2715 SendToPlayer(&buf[next_out], oldi - next_out);
2718 Colorize(ColorChallenge, FALSE);
2719 curColor = ColorChallenge;
2725 if (looking_at(buf, &i, "* offers you") ||
2726 looking_at(buf, &i, "* offers to be") ||
2727 looking_at(buf, &i, "* would like to") ||
2728 looking_at(buf, &i, "* requests to") ||
2729 looking_at(buf, &i, "Your opponent offers") ||
2730 looking_at(buf, &i, "Your opponent requests")) {
2732 if (appData.colorize) {
2733 if (oldi > next_out) {
2734 SendToPlayer(&buf[next_out], oldi - next_out);
2737 Colorize(ColorRequest, FALSE);
2738 curColor = ColorRequest;
2743 if (looking_at(buf, &i, "* (*) seeking")) {
2744 if (appData.colorize) {
2745 if (oldi > next_out) {
2746 SendToPlayer(&buf[next_out], oldi - next_out);
2749 Colorize(ColorSeek, FALSE);
2750 curColor = ColorSeek;
2755 if (looking_at(buf, &i, "\\ ")) {
2756 if (prevColor != ColorNormal) {
2757 if (oldi > next_out) {
2758 SendToPlayer(&buf[next_out], oldi - next_out);
2761 Colorize(prevColor, TRUE);
2762 curColor = prevColor;
2764 if (savingComment) {
2765 parse_pos = i - oldi;
2766 memcpy(parse, &buf[oldi], parse_pos);
2767 parse[parse_pos] = NULLCHAR;
2768 started = STARTED_COMMENT;
2770 started = STARTED_CHATTER;
2775 if (looking_at(buf, &i, "Black Strength :") ||
2776 looking_at(buf, &i, "<<< style 10 board >>>") ||
2777 looking_at(buf, &i, "<10>") ||
2778 looking_at(buf, &i, "#@#")) {
2779 /* Wrong board style */
2781 SendToICS(ics_prefix);
2782 SendToICS("set style 12\n");
2783 SendToICS(ics_prefix);
2784 SendToICS("refresh\n");
2788 if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
2790 have_sent_ICS_logon = 1;
2794 if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
2795 (looking_at(buf, &i, "\n<12> ") ||
2796 looking_at(buf, &i, "<12> "))) {
2798 if (oldi > next_out) {
2799 SendToPlayer(&buf[next_out], oldi - next_out);
2802 started = STARTED_BOARD;
2807 if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
2808 looking_at(buf, &i, "<b1> ")) {
2809 if (oldi > next_out) {
2810 SendToPlayer(&buf[next_out], oldi - next_out);
2813 started = STARTED_HOLDINGS;
2818 if (looking_at(buf, &i, "* *vs. * *--- *")) {
2820 /* Header for a move list -- first line */
2822 switch (ics_getting_history) {
2826 case BeginningOfGame:
2827 /* User typed "moves" or "oldmoves" while we
2828 were idle. Pretend we asked for these
2829 moves and soak them up so user can step
2830 through them and/or save them.
2833 gameMode = IcsObserving;
2836 ics_getting_history = H_GOT_UNREQ_HEADER;
2838 case EditGame: /*?*/
2839 case EditPosition: /*?*/
2840 /* Should above feature work in these modes too? */
2841 /* For now it doesn't */
2842 ics_getting_history = H_GOT_UNWANTED_HEADER;
2845 ics_getting_history = H_GOT_UNWANTED_HEADER;
2850 /* Is this the right one? */
2851 if (gameInfo.white && gameInfo.black &&
2852 strcmp(gameInfo.white, star_match[0]) == 0 &&
2853 strcmp(gameInfo.black, star_match[2]) == 0) {
2855 ics_getting_history = H_GOT_REQ_HEADER;
2858 case H_GOT_REQ_HEADER:
2859 case H_GOT_UNREQ_HEADER:
2860 case H_GOT_UNWANTED_HEADER:
2861 case H_GETTING_MOVES:
2862 /* Should not happen */
2863 DisplayError(_("Error gathering move list: two headers"), 0);
2864 ics_getting_history = H_FALSE;
2868 /* Save player ratings into gameInfo if needed */
2869 if ((ics_getting_history == H_GOT_REQ_HEADER ||
2870 ics_getting_history == H_GOT_UNREQ_HEADER) &&
2871 (gameInfo.whiteRating == -1 ||
2872 gameInfo.blackRating == -1)) {
2874 gameInfo.whiteRating = string_to_rating(star_match[1]);
2875 gameInfo.blackRating = string_to_rating(star_match[3]);
2876 if (appData.debugMode)
2877 fprintf(debugFP, _("Ratings from header: W %d, B %d\n"),
2878 gameInfo.whiteRating, gameInfo.blackRating);
2883 if (looking_at(buf, &i,
2884 "* * match, initial time: * minute*, increment: * second")) {
2885 /* Header for a move list -- second line */
2886 /* Initial board will follow if this is a wild game */
2887 if (gameInfo.event != NULL) free(gameInfo.event);
2888 sprintf(str, "ICS %s %s match", star_match[0], star_match[1]);
2889 gameInfo.event = StrSave(str);
2890 /* [HGM] we switched variant. Translate boards if needed. */
2891 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
2895 if (looking_at(buf, &i, "Move ")) {
2896 /* Beginning of a move list */
2897 switch (ics_getting_history) {
2899 /* Normally should not happen */
2900 /* Maybe user hit reset while we were parsing */
2903 /* Happens if we are ignoring a move list that is not
2904 * the one we just requested. Common if the user
2905 * tries to observe two games without turning off
2908 case H_GETTING_MOVES:
2909 /* Should not happen */
2910 DisplayError(_("Error gathering move list: nested"), 0);
2911 ics_getting_history = H_FALSE;
2913 case H_GOT_REQ_HEADER:
2914 ics_getting_history = H_GETTING_MOVES;
2915 started = STARTED_MOVES;
2917 if (oldi > next_out) {
2918 SendToPlayer(&buf[next_out], oldi - next_out);
2921 case H_GOT_UNREQ_HEADER:
2922 ics_getting_history = H_GETTING_MOVES;
2923 started = STARTED_MOVES_NOHIDE;
2926 case H_GOT_UNWANTED_HEADER:
2927 ics_getting_history = H_FALSE;
2933 if (looking_at(buf, &i, "% ") ||
2934 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
2935 && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
2936 if(suppressKibitz) next_out = i;
2937 savingComment = FALSE;
2941 case STARTED_MOVES_NOHIDE:
2942 memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
2943 parse[parse_pos + i - oldi] = NULLCHAR;
2944 ParseGameHistory(parse);
2946 if (appData.zippyPlay && first.initDone) {
2947 FeedMovesToProgram(&first, forwardMostMove);
2948 if (gameMode == IcsPlayingWhite) {
2949 if (WhiteOnMove(forwardMostMove)) {
2950 if (first.sendTime) {
2951 if (first.useColors) {
2952 SendToProgram("black\n", &first);
2954 SendTimeRemaining(&first, TRUE);
2956 if (first.useColors) {
2957 SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
2959 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
2960 first.maybeThinking = TRUE;
2962 if (first.usePlayother) {
2963 if (first.sendTime) {
2964 SendTimeRemaining(&first, TRUE);
2966 SendToProgram("playother\n", &first);
2972 } else if (gameMode == IcsPlayingBlack) {
2973 if (!WhiteOnMove(forwardMostMove)) {
2974 if (first.sendTime) {
2975 if (first.useColors) {
2976 SendToProgram("white\n", &first);
2978 SendTimeRemaining(&first, FALSE);
2980 if (first.useColors) {
2981 SendToProgram("black\n", &first);
2983 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
2984 first.maybeThinking = TRUE;
2986 if (first.usePlayother) {
2987 if (first.sendTime) {
2988 SendTimeRemaining(&first, FALSE);
2990 SendToProgram("playother\n", &first);
2999 if (gameMode == IcsObserving && ics_gamenum == -1) {
3000 /* Moves came from oldmoves or moves command
3001 while we weren't doing anything else.
3003 currentMove = forwardMostMove;
3004 ClearHighlights();/*!!could figure this out*/
3005 flipView = appData.flipView;
3006 DrawPosition(TRUE, boards[currentMove]);
3007 DisplayBothClocks();
3008 sprintf(str, "%s vs. %s",
3009 gameInfo.white, gameInfo.black);
3013 /* Moves were history of an active game */
3014 if (gameInfo.resultDetails != NULL) {
3015 free(gameInfo.resultDetails);
3016 gameInfo.resultDetails = NULL;
3019 HistorySet(parseList, backwardMostMove,
3020 forwardMostMove, currentMove-1);
3021 DisplayMove(currentMove - 1);
3022 if (started == STARTED_MOVES) next_out = i;
3023 started = STARTED_NONE;
3024 ics_getting_history = H_FALSE;
3027 case STARTED_OBSERVE:
3028 started = STARTED_NONE;
3029 SendToICS(ics_prefix);
3030 SendToICS("refresh\n");
3036 if(bookHit) { // [HGM] book: simulate book reply
3037 static char bookMove[MSG_SIZ]; // a bit generous?
3039 programStats.nodes = programStats.depth = programStats.time =
3040 programStats.score = programStats.got_only_move = 0;
3041 sprintf(programStats.movelist, "%s (xbook)", bookHit);
3043 strcpy(bookMove, "move ");
3044 strcat(bookMove, bookHit);
3045 HandleMachineMove(bookMove, &first);
3050 if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3051 started == STARTED_HOLDINGS ||
3052 started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3053 /* Accumulate characters in move list or board */
3054 parse[parse_pos++] = buf[i];
3057 /* Start of game messages. Mostly we detect start of game
3058 when the first board image arrives. On some versions
3059 of the ICS, though, we need to do a "refresh" after starting
3060 to observe in order to get the current board right away. */
3061 if (looking_at(buf, &i, "Adding game * to observation list")) {
3062 started = STARTED_OBSERVE;
3066 /* Handle auto-observe */
3067 if (appData.autoObserve &&
3068 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3069 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3071 /* Choose the player that was highlighted, if any. */
3072 if (star_match[0][0] == '\033' ||
3073 star_match[1][0] != '\033') {
3074 player = star_match[0];
3076 player = star_match[2];
3078 sprintf(str, "%sobserve %s\n",
3079 ics_prefix, StripHighlightAndTitle(player));
3082 /* Save ratings from notify string */
3083 strcpy(player1Name, star_match[0]);
3084 player1Rating = string_to_rating(star_match[1]);
3085 strcpy(player2Name, star_match[2]);
3086 player2Rating = string_to_rating(star_match[3]);
3088 if (appData.debugMode)
3090 "Ratings from 'Game notification:' %s %d, %s %d\n",
3091 player1Name, player1Rating,
3092 player2Name, player2Rating);
3097 /* Deal with automatic examine mode after a game,
3098 and with IcsObserving -> IcsExamining transition */
3099 if (looking_at(buf, &i, "Entering examine mode for game *") ||
3100 looking_at(buf, &i, "has made you an examiner of game *")) {
3102 int gamenum = atoi(star_match[0]);
3103 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3104 gamenum == ics_gamenum) {
3105 /* We were already playing or observing this game;
3106 no need to refetch history */
3107 gameMode = IcsExamining;
3109 pauseExamForwardMostMove = forwardMostMove;
3110 } else if (currentMove < forwardMostMove) {
3111 ForwardInner(forwardMostMove);
3114 /* I don't think this case really can happen */
3115 SendToICS(ics_prefix);
3116 SendToICS("refresh\n");
3121 /* Error messages */
3122 // if (ics_user_moved) {
3123 if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3124 if (looking_at(buf, &i, "Illegal move") ||
3125 looking_at(buf, &i, "Not a legal move") ||
3126 looking_at(buf, &i, "Your king is in check") ||
3127 looking_at(buf, &i, "It isn't your turn") ||
3128 looking_at(buf, &i, "It is not your move")) {
3130 if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3131 currentMove = --forwardMostMove;
3132 DisplayMove(currentMove - 1); /* before DMError */
3133 DrawPosition(FALSE, boards[currentMove]);
3135 DisplayBothClocks();
3137 DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3143 if (looking_at(buf, &i, "still have time") ||
3144 looking_at(buf, &i, "not out of time") ||
3145 looking_at(buf, &i, "either player is out of time") ||
3146 looking_at(buf, &i, "has timeseal; checking")) {
3147 /* We must have called his flag a little too soon */
3148 whiteFlag = blackFlag = FALSE;
3152 if (looking_at(buf, &i, "added * seconds to") ||
3153 looking_at(buf, &i, "seconds were added to")) {
3154 /* Update the clocks */
3155 SendToICS(ics_prefix);
3156 SendToICS("refresh\n");
3160 if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3161 ics_clock_paused = TRUE;
3166 if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3167 ics_clock_paused = FALSE;
3172 /* Grab player ratings from the Creating: message.
3173 Note we have to check for the special case when
3174 the ICS inserts things like [white] or [black]. */
3175 if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3176 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3178 0 player 1 name (not necessarily white)
3180 2 empty, white, or black (IGNORED)
3181 3 player 2 name (not necessarily black)
3184 The names/ratings are sorted out when the game
3185 actually starts (below).
3187 strcpy(player1Name, StripHighlightAndTitle(star_match[0]));
3188 player1Rating = string_to_rating(star_match[1]);
3189 strcpy(player2Name, StripHighlightAndTitle(star_match[3]));
3190 player2Rating = string_to_rating(star_match[4]);
3192 if (appData.debugMode)
3194 "Ratings from 'Creating:' %s %d, %s %d\n",
3195 player1Name, player1Rating,
3196 player2Name, player2Rating);
3201 /* Improved generic start/end-of-game messages */
3202 if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3203 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3204 /* If tkind == 0: */
3205 /* star_match[0] is the game number */
3206 /* [1] is the white player's name */
3207 /* [2] is the black player's name */
3208 /* For end-of-game: */
3209 /* [3] is the reason for the game end */
3210 /* [4] is a PGN end game-token, preceded by " " */
3211 /* For start-of-game: */
3212 /* [3] begins with "Creating" or "Continuing" */
3213 /* [4] is " *" or empty (don't care). */
3214 int gamenum = atoi(star_match[0]);
3215 char *whitename, *blackname, *why, *endtoken;
3216 ChessMove endtype = (ChessMove) 0;
3219 whitename = star_match[1];
3220 blackname = star_match[2];
3221 why = star_match[3];
3222 endtoken = star_match[4];
3224 whitename = star_match[1];
3225 blackname = star_match[3];
3226 why = star_match[5];
3227 endtoken = star_match[6];
3230 /* Game start messages */
3231 if (strncmp(why, "Creating ", 9) == 0 ||
3232 strncmp(why, "Continuing ", 11) == 0) {
3233 gs_gamenum = gamenum;
3234 strcpy(gs_kind, strchr(why, ' ') + 1);
3235 VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
3237 if (appData.zippyPlay) {
3238 ZippyGameStart(whitename, blackname);
3244 /* Game end messages */
3245 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3246 ics_gamenum != gamenum) {
3249 while (endtoken[0] == ' ') endtoken++;
3250 switch (endtoken[0]) {
3253 endtype = GameUnfinished;
3256 endtype = BlackWins;
3259 if (endtoken[1] == '/')
3260 endtype = GameIsDrawn;
3262 endtype = WhiteWins;
3265 GameEnds(endtype, why, GE_ICS);
3267 if (appData.zippyPlay && first.initDone) {
3268 ZippyGameEnd(endtype, why);
3269 if (first.pr == NULL) {
3270 /* Start the next process early so that we'll
3271 be ready for the next challenge */
3272 StartChessProgram(&first);
3274 /* Send "new" early, in case this command takes
3275 a long time to finish, so that we'll be ready
3276 for the next challenge. */
3277 gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3284 if (looking_at(buf, &i, "Removing game * from observation") ||
3285 looking_at(buf, &i, "no longer observing game *") ||
3286 looking_at(buf, &i, "Game * (*) has no examiners")) {
3287 if (gameMode == IcsObserving &&
3288 atoi(star_match[0]) == ics_gamenum)
3290 /* icsEngineAnalyze */
3291 if (appData.icsEngineAnalyze) {
3298 ics_user_moved = FALSE;
3303 if (looking_at(buf, &i, "no longer examining game *")) {
3304 if (gameMode == IcsExamining &&
3305 atoi(star_match[0]) == ics_gamenum)
3309 ics_user_moved = FALSE;
3314 /* Advance leftover_start past any newlines we find,
3315 so only partial lines can get reparsed */
3316 if (looking_at(buf, &i, "\n")) {
3317 prevColor = curColor;
3318 if (curColor != ColorNormal) {
3319 if (oldi > next_out) {
3320 SendToPlayer(&buf[next_out], oldi - next_out);
3323 Colorize(ColorNormal, FALSE);
3324 curColor = ColorNormal;
3326 if (started == STARTED_BOARD) {
3327 started = STARTED_NONE;
3328 parse[parse_pos] = NULLCHAR;
3329 ParseBoard12(parse);
3332 /* Send premove here */
3333 if (appData.premove) {
3335 if (currentMove == 0 &&
3336 gameMode == IcsPlayingWhite &&
3337 appData.premoveWhite) {
3338 sprintf(str, "%s\n", appData.premoveWhiteText);
3339 if (appData.debugMode)
3340 fprintf(debugFP, "Sending premove:\n");
3342 } else if (currentMove == 1 &&
3343 gameMode == IcsPlayingBlack &&
3344 appData.premoveBlack) {
3345 sprintf(str, "%s\n", appData.premoveBlackText);
3346 if (appData.debugMode)
3347 fprintf(debugFP, "Sending premove:\n");
3349 } else if (gotPremove) {
3351 ClearPremoveHighlights();
3352 if (appData.debugMode)
3353 fprintf(debugFP, "Sending premove:\n");
3354 UserMoveEvent(premoveFromX, premoveFromY,
3355 premoveToX, premoveToY,
3360 /* Usually suppress following prompt */
3361 if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
3362 while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
3363 if (looking_at(buf, &i, "*% ")) {
3364 savingComment = FALSE;
3369 } else if (started == STARTED_HOLDINGS) {
3371 char new_piece[MSG_SIZ];
3372 started = STARTED_NONE;
3373 parse[parse_pos] = NULLCHAR;
3374 if (appData.debugMode)
3375 fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
3376 parse, currentMove);
3377 if (sscanf(parse, " game %d", &gamenum) == 1 &&
3378 gamenum == ics_gamenum) {
3379 if (gameInfo.variant == VariantNormal) {
3380 /* [HGM] We seem to switch variant during a game!
3381 * Presumably no holdings were displayed, so we have
3382 * to move the position two files to the right to
3383 * create room for them!
3385 VariantClass newVariant;
3386 switch(gameInfo.boardWidth) { // base guess on board width
3387 case 9: newVariant = VariantShogi; break;
3388 case 10: newVariant = VariantGreat; break;
3389 default: newVariant = VariantCrazyhouse; break;
3391 VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3392 /* Get a move list just to see the header, which
3393 will tell us whether this is really bug or zh */
3394 if (ics_getting_history == H_FALSE) {
3395 ics_getting_history = H_REQUESTED;
3396 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3400 new_piece[0] = NULLCHAR;
3401 sscanf(parse, "game %d white [%s black [%s <- %s",
3402 &gamenum, white_holding, black_holding,
3404 white_holding[strlen(white_holding)-1] = NULLCHAR;
3405 black_holding[strlen(black_holding)-1] = NULLCHAR;
3406 /* [HGM] copy holdings to board holdings area */
3407 CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
3408 CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
3409 boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
3411 if (appData.zippyPlay && first.initDone) {
3412 ZippyHoldings(white_holding, black_holding,
3416 if (tinyLayout || smallLayout) {
3417 char wh[16], bh[16];
3418 PackHolding(wh, white_holding);
3419 PackHolding(bh, black_holding);
3420 sprintf(str, "[%s-%s] %s-%s", wh, bh,
3421 gameInfo.white, gameInfo.black);
3423 sprintf(str, "%s [%s] vs. %s [%s]",
3424 gameInfo.white, white_holding,
3425 gameInfo.black, black_holding);
3428 DrawPosition(FALSE, boards[currentMove]);
3431 /* Suppress following prompt */
3432 if (looking_at(buf, &i, "*% ")) {
3433 if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
3434 savingComment = FALSE;
3442 i++; /* skip unparsed character and loop back */
3445 if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
3446 // started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
3447 // SendToPlayer(&buf[next_out], i - next_out);
3448 started != STARTED_HOLDINGS && leftover_start > next_out) {
3449 SendToPlayer(&buf[next_out], leftover_start - next_out);
3453 leftover_len = buf_len - leftover_start;
3454 /* if buffer ends with something we couldn't parse,
3455 reparse it after appending the next read */
3457 } else if (count == 0) {
3458 RemoveInputSource(isr);
3459 DisplayFatalError(_("Connection closed by ICS"), 0, 0);
3461 DisplayFatalError(_("Error reading from ICS"), error, 1);
3466 /* Board style 12 looks like this:
3468 <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
3470 * The "<12> " is stripped before it gets to this routine. The two
3471 * trailing 0's (flip state and clock ticking) are later addition, and
3472 * some chess servers may not have them, or may have only the first.
3473 * Additional trailing fields may be added in the future.
3476 #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"
3478 #define RELATION_OBSERVING_PLAYED 0
3479 #define RELATION_OBSERVING_STATIC -2 /* examined, oldmoves, or smoves */
3480 #define RELATION_PLAYING_MYMOVE 1
3481 #define RELATION_PLAYING_NOTMYMOVE -1
3482 #define RELATION_EXAMINING 2
3483 #define RELATION_ISOLATED_BOARD -3
3484 #define RELATION_STARTING_POSITION -4 /* FICS only */
3487 ParseBoard12(string)
3490 GameMode newGameMode;
3491 int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
3492 int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
3493 int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
3494 char to_play, board_chars[200];
3495 char move_str[500], str[500], elapsed_time[500];
3496 char black[32], white[32];
3498 int prevMove = currentMove;
3501 int fromX, fromY, toX, toY;
3503 int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
3504 char *bookHit = NULL; // [HGM] book
3505 Boolean weird = FALSE, reqFlag = FALSE;
3507 fromX = fromY = toX = toY = -1;
3511 if (appData.debugMode)
3512 fprintf(debugFP, _("Parsing board: %s\n"), string);
3514 move_str[0] = NULLCHAR;
3515 elapsed_time[0] = NULLCHAR;
3516 { /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
3518 while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
3519 if(string[i] == ' ') { ranks++; files = 0; }
3521 if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
3524 for(j = 0; j <i; j++) board_chars[j] = string[j];
3525 board_chars[i] = '\0';
3528 n = sscanf(string, PATTERN, &to_play, &double_push,
3529 &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
3530 &gamenum, white, black, &relation, &basetime, &increment,
3531 &white_stren, &black_stren, &white_time, &black_time,
3532 &moveNum, str, elapsed_time, move_str, &ics_flip,
3536 snprintf(str, sizeof(str), _("Failed to parse board string:\n\"%s\""), string);
3537 DisplayError(str, 0);
3541 /* Convert the move number to internal form */
3542 moveNum = (moveNum - 1) * 2;
3543 if (to_play == 'B') moveNum++;
3544 if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
3545 DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
3551 case RELATION_OBSERVING_PLAYED:
3552 case RELATION_OBSERVING_STATIC:
3553 if (gamenum == -1) {
3554 /* Old ICC buglet */
3555 relation = RELATION_OBSERVING_STATIC;
3557 newGameMode = IcsObserving;
3559 case RELATION_PLAYING_MYMOVE:
3560 case RELATION_PLAYING_NOTMYMOVE:
3562 ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
3563 IcsPlayingWhite : IcsPlayingBlack;
3565 case RELATION_EXAMINING:
3566 newGameMode = IcsExamining;
3568 case RELATION_ISOLATED_BOARD:
3570 /* Just display this board. If user was doing something else,
3571 we will forget about it until the next board comes. */
3572 newGameMode = IcsIdle;
3574 case RELATION_STARTING_POSITION:
3575 newGameMode = gameMode;
3579 /* Modify behavior for initial board display on move listing
3582 switch (ics_getting_history) {
3586 case H_GOT_REQ_HEADER:
3587 case H_GOT_UNREQ_HEADER:
3588 /* This is the initial position of the current game */
3589 gamenum = ics_gamenum;
3590 moveNum = 0; /* old ICS bug workaround */
3591 if (to_play == 'B') {
3592 startedFromSetupPosition = TRUE;
3593 blackPlaysFirst = TRUE;
3595 if (forwardMostMove == 0) forwardMostMove = 1;
3596 if (backwardMostMove == 0) backwardMostMove = 1;
3597 if (currentMove == 0) currentMove = 1;
3599 newGameMode = gameMode;
3600 relation = RELATION_STARTING_POSITION; /* ICC needs this */
3602 case H_GOT_UNWANTED_HEADER:
3603 /* This is an initial board that we don't want */
3605 case H_GETTING_MOVES:
3606 /* Should not happen */
3607 DisplayError(_("Error gathering move list: extra board"), 0);
3608 ics_getting_history = H_FALSE;
3612 if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
3613 weird && (int)gameInfo.variant <= (int)VariantShogi) {
3614 /* [HGM] We seem to have switched variant unexpectedly
3615 * Try to guess new variant from board size
3617 VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
3618 if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
3619 if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
3620 if(ranks == 8 && files == 12) newVariant = VariantCourier; else
3621 if(ranks == 9 && files == 9) newVariant = VariantShogi; else
3622 if(!weird) newVariant = VariantNormal;
3623 VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3624 /* Get a move list just to see the header, which
3625 will tell us whether this is really bug or zh */
3626 if (ics_getting_history == H_FALSE) {
3627 ics_getting_history = H_REQUESTED; reqFlag = TRUE;
3628 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3633 /* Take action if this is the first board of a new game, or of a
3634 different game than is currently being displayed. */
3635 if (gamenum != ics_gamenum || newGameMode != gameMode ||
3636 relation == RELATION_ISOLATED_BOARD) {
3638 /* Forget the old game and get the history (if any) of the new one */
3639 if (gameMode != BeginningOfGame) {
3643 if (appData.autoRaiseBoard) BoardToTop();
3645 if (gamenum == -1) {
3646 newGameMode = IcsIdle;
3647 } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
3648 appData.getMoveList && !reqFlag) {
3649 /* Need to get game history */
3650 ics_getting_history = H_REQUESTED;
3651 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3655 /* Initially flip the board to have black on the bottom if playing
3656 black or if the ICS flip flag is set, but let the user change
3657 it with the Flip View button. */
3658 flipView = appData.autoFlipView ?
3659 (newGameMode == IcsPlayingBlack) || ics_flip :
3662 /* Done with values from previous mode; copy in new ones */
3663 gameMode = newGameMode;
3665 ics_gamenum = gamenum;
3666 if (gamenum == gs_gamenum) {
3667 int klen = strlen(gs_kind);
3668 if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
3669 sprintf(str, "ICS %s", gs_kind);
3670 gameInfo.event = StrSave(str);
3672 gameInfo.event = StrSave("ICS game");
3674 gameInfo.site = StrSave(appData.icsHost);
3675 gameInfo.date = PGNDate();
3676 gameInfo.round = StrSave("-");
3677 gameInfo.white = StrSave(white);
3678 gameInfo.black = StrSave(black);
3679 timeControl = basetime * 60 * 1000;
3681 timeIncrement = increment * 1000;
3682 movesPerSession = 0;
3683 gameInfo.timeControl = TimeControlTagValue();
3684 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
3685 if (appData.debugMode) {
3686 fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
3687 fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
3688 setbuf(debugFP, NULL);
3691 gameInfo.outOfBook = NULL;
3693 /* Do we have the ratings? */
3694 if (strcmp(player1Name, white) == 0 &&
3695 strcmp(player2Name, black) == 0) {
3696 if (appData.debugMode)
3697 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3698 player1Rating, player2Rating);
3699 gameInfo.whiteRating = player1Rating;
3700 gameInfo.blackRating = player2Rating;
3701 } else if (strcmp(player2Name, white) == 0 &&
3702 strcmp(player1Name, black) == 0) {
3703 if (appData.debugMode)
3704 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3705 player2Rating, player1Rating);
3706 gameInfo.whiteRating = player2Rating;
3707 gameInfo.blackRating = player1Rating;
3709 player1Name[0] = player2Name[0] = NULLCHAR;
3711 /* Silence shouts if requested */
3712 if (appData.quietPlay &&
3713 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
3714 SendToICS(ics_prefix);
3715 SendToICS("set shout 0\n");
3719 /* Deal with midgame name changes */
3721 if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
3722 if (gameInfo.white) free(gameInfo.white);
3723 gameInfo.white = StrSave(white);
3725 if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
3726 if (gameInfo.black) free(gameInfo.black);
3727 gameInfo.black = StrSave(black);
3731 /* Throw away game result if anything actually changes in examine mode */
3732 if (gameMode == IcsExamining && !newGame) {
3733 gameInfo.result = GameUnfinished;
3734 if (gameInfo.resultDetails != NULL) {
3735 free(gameInfo.resultDetails);
3736 gameInfo.resultDetails = NULL;
3740 /* In pausing && IcsExamining mode, we ignore boards coming
3741 in if they are in a different variation than we are. */
3742 if (pauseExamInvalid) return;
3743 if (pausing && gameMode == IcsExamining) {
3744 if (moveNum <= pauseExamForwardMostMove) {
3745 pauseExamInvalid = TRUE;
3746 forwardMostMove = pauseExamForwardMostMove;
3751 if (appData.debugMode) {
3752 fprintf(debugFP, "load %dx%d board\n", files, ranks);
3754 /* Parse the board */
3755 for (k = 0; k < ranks; k++) {
3756 for (j = 0; j < files; j++)
3757 board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
3758 if(gameInfo.holdingsWidth > 1) {
3759 board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
3760 board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
3763 CopyBoard(boards[moveNum], board);
3764 boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
3766 startedFromSetupPosition =
3767 !CompareBoards(board, initialPosition);
3768 if(startedFromSetupPosition)
3769 initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
3772 /* [HGM] Set castling rights. Take the outermost Rooks,
3773 to make it also work for FRC opening positions. Note that board12
3774 is really defective for later FRC positions, as it has no way to
3775 indicate which Rook can castle if they are on the same side of King.
3776 For the initial position we grant rights to the outermost Rooks,
3777 and remember thos rights, and we then copy them on positions
3778 later in an FRC game. This means WB might not recognize castlings with
3779 Rooks that have moved back to their original position as illegal,
3780 but in ICS mode that is not its job anyway.
3782 if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
3783 { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
3785 for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
3786 if(board[0][i] == WhiteRook) j = i;
3787 initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
3788 for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
3789 if(board[0][i] == WhiteRook) j = i;
3790 initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
3791 for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
3792 if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
3793 initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
3794 for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
3795 if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
3796 initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
3798 if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
3799 for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
3800 if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
3801 for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
3802 if(board[BOARD_HEIGHT-1][k] == bKing)
3803 initialRights[5] = boards[moveNum][CASTLING][5] = k;
3804 if(gameInfo.variant == VariantTwoKings) {
3805 // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
3806 if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
3807 if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
3810 r = boards[moveNum][CASTLING][0] = initialRights[0];
3811 if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
3812 r = boards[moveNum][CASTLING][1] = initialRights[1];
3813 if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
3814 r = boards[moveNum][CASTLING][3] = initialRights[3];
3815 if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
3816 r = boards[moveNum][CASTLING][4] = initialRights[4];
3817 if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
3818 /* wildcastle kludge: always assume King has rights */
3819 r = boards[moveNum][CASTLING][2] = initialRights[2];
3820 r = boards[moveNum][CASTLING][5] = initialRights[5];
3822 /* [HGM] e.p. rights. Assume that ICS sends file number here? */
3823 boards[moveNum][EP_STATUS] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
3826 if (ics_getting_history == H_GOT_REQ_HEADER ||
3827 ics_getting_history == H_GOT_UNREQ_HEADER) {
3828 /* This was an initial position from a move list, not
3829 the current position */
3833 /* Update currentMove and known move number limits */
3834 newMove = newGame || moveNum > forwardMostMove;
3837 forwardMostMove = backwardMostMove = currentMove = moveNum;
3838 if (gameMode == IcsExamining && moveNum == 0) {
3839 /* Workaround for ICS limitation: we are not told the wild
3840 type when starting to examine a game. But if we ask for
3841 the move list, the move list header will tell us */
3842 ics_getting_history = H_REQUESTED;
3843 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3846 } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
3847 || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
3849 /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
3850 /* [HGM] applied this also to an engine that is silently watching */
3851 if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
3852 (gameMode == IcsObserving || gameMode == IcsExamining) &&
3853 gameInfo.variant == currentlyInitializedVariant) {
3854 takeback = forwardMostMove - moveNum;
3855 for (i = 0; i < takeback; i++) {
3856 if (appData.debugMode) fprintf(debugFP, "take back move\n");
3857 SendToProgram("undo\n", &first);
3862 forwardMostMove = moveNum;
3863 if (!pausing || currentMove > forwardMostMove)
3864 currentMove = forwardMostMove;
3866 /* New part of history that is not contiguous with old part */
3867 if (pausing && gameMode == IcsExamining) {
3868 pauseExamInvalid = TRUE;
3869 forwardMostMove = pauseExamForwardMostMove;
3872 if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
3874 if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
3875 // [HGM] when we will receive the move list we now request, it will be
3876 // fed to the engine from the first move on. So if the engine is not
3877 // in the initial position now, bring it there.
3878 InitChessProgram(&first, 0);
3881 ics_getting_history = H_REQUESTED;
3882 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3885 forwardMostMove = backwardMostMove = currentMove = moveNum;
3888 /* Update the clocks */
3889 if (strchr(elapsed_time, '.')) {
3891 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
3892 timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
3894 /* Time is in seconds */
3895 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
3896 timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
3901 if (appData.zippyPlay && newGame &&
3902 gameMode != IcsObserving && gameMode != IcsIdle &&
3903 gameMode != IcsExamining)
3904 ZippyFirstBoard(moveNum, basetime, increment);
3907 /* Put the move on the move list, first converting
3908 to canonical algebraic form. */
3910 if (appData.debugMode) {
3911 if (appData.debugMode) { int f = forwardMostMove;
3912 fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
3913 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
3914 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
3916 fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
3917 fprintf(debugFP, "moveNum = %d\n", moveNum);
3918 fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
3919 setbuf(debugFP, NULL);
3921 if (moveNum <= backwardMostMove) {
3922 /* We don't know what the board looked like before
3924 strcpy(parseList[moveNum - 1], move_str);
3925 strcat(parseList[moveNum - 1], " ");
3926 strcat(parseList[moveNum - 1], elapsed_time);
3927 moveList[moveNum - 1][0] = NULLCHAR;
3928 } else if (strcmp(move_str, "none") == 0) {
3929 // [HGM] long SAN: swapped order; test for 'none' before parsing move
3930 /* Again, we don't know what the board looked like;
3931 this is really the start of the game. */
3932 parseList[moveNum - 1][0] = NULLCHAR;
3933 moveList[moveNum - 1][0] = NULLCHAR;
3934 backwardMostMove = moveNum;
3935 startedFromSetupPosition = TRUE;
3936 fromX = fromY = toX = toY = -1;
3938 // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
3939 // So we parse the long-algebraic move string in stead of the SAN move
3940 int valid; char buf[MSG_SIZ], *prom;
3942 // str looks something like "Q/a1-a2"; kill the slash
3944 sprintf(buf, "%c%s", str[0], str+2);
3945 else strcpy(buf, str); // might be castling
3946 if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
3947 strcat(buf, prom); // long move lacks promo specification!
3948 if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
3949 if(appData.debugMode)
3950 fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
3951 strcpy(move_str, buf);
3953 valid = ParseOneMove(move_str, moveNum - 1, &moveType,
3954 &fromX, &fromY, &toX, &toY, &promoChar)
3955 || ParseOneMove(buf, moveNum - 1, &moveType,
3956 &fromX, &fromY, &toX, &toY, &promoChar);
3957 // end of long SAN patch
3959 (void) CoordsToAlgebraic(boards[moveNum - 1],
3960 PosFlags(moveNum - 1),
3961 fromY, fromX, toY, toX, promoChar,
3962 parseList[moveNum-1]);
3963 switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
3969 if(gameInfo.variant != VariantShogi)
3970 strcat(parseList[moveNum - 1], "+");
3973 case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
3974 strcat(parseList[moveNum - 1], "#");
3977 strcat(parseList[moveNum - 1], " ");
3978 strcat(parseList[moveNum - 1], elapsed_time);
3979 /* currentMoveString is set as a side-effect of ParseOneMove */
3980 strcpy(moveList[moveNum - 1], currentMoveString);
3981 strcat(moveList[moveNum - 1], "\n");
3983 /* Move from ICS was illegal!? Punt. */
3984 if (appData.debugMode) {
3985 fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
3986 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
3988 strcpy(parseList[moveNum - 1], move_str);
3989 strcat(parseList[moveNum - 1], " ");
3990 strcat(parseList[moveNum - 1], elapsed_time);
3991 moveList[moveNum - 1][0] = NULLCHAR;
3992 fromX = fromY = toX = toY = -1;
3995 if (appData.debugMode) {
3996 fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
3997 setbuf(debugFP, NULL);
4001 /* Send move to chess program (BEFORE animating it). */
4002 if (appData.zippyPlay && !newGame && newMove &&
4003 (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4005 if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4006 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4007 if (moveList[moveNum - 1][0] == NULLCHAR) {
4008 sprintf(str, _("Couldn't parse move \"%s\" from ICS"),
4010 DisplayError(str, 0);
4012 if (first.sendTime) {
4013 SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4015 bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4016 if (firstMove && !bookHit) {
4018 if (first.useColors) {
4019 SendToProgram(gameMode == IcsPlayingWhite ?
4021 "black\ngo\n", &first);
4023 SendToProgram("go\n", &first);
4025 first.maybeThinking = TRUE;
4028 } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4029 if (moveList[moveNum - 1][0] == NULLCHAR) {
4030 sprintf(str, _("Couldn't parse move \"%s\" from ICS"), move_str);
4031 DisplayError(str, 0);
4033 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4034 SendMoveToProgram(moveNum - 1, &first);
4041 if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4042 /* If move comes from a remote source, animate it. If it
4043 isn't remote, it will have already been animated. */
4044 if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4045 AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4047 if (!pausing && appData.highlightLastMove) {
4048 SetHighlights(fromX, fromY, toX, toY);
4052 /* Start the clocks */
4053 whiteFlag = blackFlag = FALSE;
4054 appData.clockMode = !(basetime == 0 && increment == 0);
4056 ics_clock_paused = TRUE;
4058 } else if (ticking == 1) {
4059 ics_clock_paused = FALSE;
4061 if (gameMode == IcsIdle ||
4062 relation == RELATION_OBSERVING_STATIC ||
4063 relation == RELATION_EXAMINING ||
4065 DisplayBothClocks();
4069 /* Display opponents and material strengths */
4070 if (gameInfo.variant != VariantBughouse &&
4071 gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4072 if (tinyLayout || smallLayout) {
4073 if(gameInfo.variant == VariantNormal)
4074 sprintf(str, "%s(%d) %s(%d) {%d %d}",
4075 gameInfo.white, white_stren, gameInfo.black, black_stren,
4076 basetime, increment);
4078 sprintf(str, "%s(%d) %s(%d) {%d %d w%d}",
4079 gameInfo.white, white_stren, gameInfo.black, black_stren,
4080 basetime, increment, (int) gameInfo.variant);
4082 if(gameInfo.variant == VariantNormal)
4083 sprintf(str, "%s (%d) vs. %s (%d) {%d %d}",
4084 gameInfo.white, white_stren, gameInfo.black, black_stren,
4085 basetime, increment);
4087 sprintf(str, "%s (%d) vs. %s (%d) {%d %d %s}",
4088 gameInfo.white, white_stren, gameInfo.black, black_stren,
4089 basetime, increment, VariantName(gameInfo.variant));
4092 if (appData.debugMode) {
4093 fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
4098 /* Display the board */
4099 if (!pausing && !appData.noGUI) {
4100 if (appData.premove)
4102 ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
4103 ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
4104 ClearPremoveHighlights();
4106 DrawPosition(FALSE, boards[currentMove]);
4107 DisplayMove(moveNum - 1);
4108 if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4109 !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4110 (gameMode == IcsPlayingBlack) && (WhiteOnMove(moveNum)) ) ) {
4111 if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4115 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4117 if(bookHit) { // [HGM] book: simulate book reply
4118 static char bookMove[MSG_SIZ]; // a bit generous?
4120 programStats.nodes = programStats.depth = programStats.time =
4121 programStats.score = programStats.got_only_move = 0;
4122 sprintf(programStats.movelist, "%s (xbook)", bookHit);
4124 strcpy(bookMove, "move ");
4125 strcat(bookMove, bookHit);
4126 HandleMachineMove(bookMove, &first);
4135 if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4136 ics_getting_history = H_REQUESTED;
4137 sprintf(buf, "%smoves %d\n", ics_prefix, ics_gamenum);
4143 AnalysisPeriodicEvent(force)
4146 if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4147 && !force) || !appData.periodicUpdates)
4150 /* Send . command to Crafty to collect stats */
4151 SendToProgram(".\n", &first);
4153 /* Don't send another until we get a response (this makes
4154 us stop sending to old Crafty's which don't understand
4155 the "." command (sending illegal cmds resets node count & time,
4156 which looks bad)) */
4157 programStats.ok_to_send = 0;
4160 void ics_update_width(new_width)
4163 ics_printf("set width %d\n", new_width);
4167 SendMoveToProgram(moveNum, cps)
4169 ChessProgramState *cps;
4173 if (cps->useUsermove) {
4174 SendToProgram("usermove ", cps);
4178 if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4179 int len = space - parseList[moveNum];
4180 memcpy(buf, parseList[moveNum], len);
4182 buf[len] = NULLCHAR;
4184 sprintf(buf, "%s\n", parseList[moveNum]);
4186 SendToProgram(buf, cps);
4188 if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4189 AlphaRank(moveList[moveNum], 4);
4190 SendToProgram(moveList[moveNum], cps);
4191 AlphaRank(moveList[moveNum], 4); // and back
4193 /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
4194 * the engine. It would be nice to have a better way to identify castle
4196 if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
4197 && cps->useOOCastle) {
4198 int fromX = moveList[moveNum][0] - AAA;
4199 int fromY = moveList[moveNum][1] - ONE;
4200 int toX = moveList[moveNum][2] - AAA;
4201 int toY = moveList[moveNum][3] - ONE;
4202 if((boards[moveNum][fromY][fromX] == WhiteKing
4203 && boards[moveNum][toY][toX] == WhiteRook)
4204 || (boards[moveNum][fromY][fromX] == BlackKing
4205 && boards[moveNum][toY][toX] == BlackRook)) {
4206 if(toX > fromX) SendToProgram("O-O\n", cps);
4207 else SendToProgram("O-O-O\n", cps);
4209 else SendToProgram(moveList[moveNum], cps);
4211 else SendToProgram(moveList[moveNum], cps);
4212 /* End of additions by Tord */
4215 /* [HGM] setting up the opening has brought engine in force mode! */
4216 /* Send 'go' if we are in a mode where machine should play. */
4217 if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4218 (gameMode == TwoMachinesPlay ||
4220 gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite ||
4222 gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4223 SendToProgram("go\n", cps);
4224 if (appData.debugMode) {
4225 fprintf(debugFP, "(extra)\n");
4228 setboardSpoiledMachineBlack = 0;
4232 SendMoveToICS(moveType, fromX, fromY, toX, toY)
4234 int fromX, fromY, toX, toY;
4236 char user_move[MSG_SIZ];
4240 sprintf(user_move, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4241 (int)moveType, fromX, fromY, toX, toY);
4242 DisplayError(user_move + strlen("say "), 0);
4244 case WhiteKingSideCastle:
4245 case BlackKingSideCastle:
4246 case WhiteQueenSideCastleWild:
4247 case BlackQueenSideCastleWild:
4249 case WhiteHSideCastleFR:
4250 case BlackHSideCastleFR:
4252 sprintf(user_move, "o-o\n");
4254 case WhiteQueenSideCastle:
4255 case BlackQueenSideCastle:
4256 case WhiteKingSideCastleWild:
4257 case BlackKingSideCastleWild:
4259 case WhiteASideCastleFR:
4260 case BlackASideCastleFR:
4262 sprintf(user_move, "o-o-o\n");
4264 case WhitePromotionQueen:
4265 case BlackPromotionQueen:
4266 case WhitePromotionRook:
4267 case BlackPromotionRook:
4268 case WhitePromotionBishop:
4269 case BlackPromotionBishop:
4270 case WhitePromotionKnight:
4271 case BlackPromotionKnight:
4272 case WhitePromotionKing:
4273 case BlackPromotionKing:
4274 case WhitePromotionChancellor:
4275 case BlackPromotionChancellor:
4276 case WhitePromotionArchbishop:
4277 case BlackPromotionArchbishop:
4278 if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
4279 sprintf(user_move, "%c%c%c%c=%c\n",
4280 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4281 PieceToChar(WhiteFerz));
4282 else if(gameInfo.variant == VariantGreat)
4283 sprintf(user_move, "%c%c%c%c=%c\n",
4284 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4285 PieceToChar(WhiteMan));
4287 sprintf(user_move, "%c%c%c%c=%c\n",
4288 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4289 PieceToChar(PromoPiece(moveType)));
4293 sprintf(user_move, "%c@%c%c\n",
4294 ToUpper(PieceToChar((ChessSquare) fromX)),
4295 AAA + toX, ONE + toY);
4298 case WhiteCapturesEnPassant:
4299 case BlackCapturesEnPassant:
4300 case IllegalMove: /* could be a variant we don't quite understand */
4301 sprintf(user_move, "%c%c%c%c\n",
4302 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4305 SendToICS(user_move);
4306 if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
4307 ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
4311 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
4316 if (rf == DROP_RANK) {
4317 sprintf(move, "%c@%c%c\n",
4318 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
4320 if (promoChar == 'x' || promoChar == NULLCHAR) {
4321 sprintf(move, "%c%c%c%c\n",
4322 AAA + ff, ONE + rf, AAA + ft, ONE + rt);
4324 sprintf(move, "%c%c%c%c%c\n",
4325 AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
4331 ProcessICSInitScript(f)
4336 while (fgets(buf, MSG_SIZ, f)) {
4337 SendToICSDelayed(buf,(long)appData.msLoginDelay);
4344 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
4346 AlphaRank(char *move, int n)
4348 // char *p = move, c; int x, y;
4350 if (appData.debugMode) {
4351 fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
4355 move[2]>='0' && move[2]<='9' &&
4356 move[3]>='a' && move[3]<='x' ) {
4358 move[2] = BOARD_RGHT -1 - (move[2]-'1') + AAA;
4359 move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4361 if(move[0]>='0' && move[0]<='9' &&
4362 move[1]>='a' && move[1]<='x' &&
4363 move[2]>='0' && move[2]<='9' &&
4364 move[3]>='a' && move[3]<='x' ) {
4365 /* input move, Shogi -> normal */
4366 move[0] = BOARD_RGHT -1 - (move[0]-'1') + AAA;
4367 move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
4368 move[2] = BOARD_RGHT -1 - (move[2]-'1') + AAA;
4369 move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4372 move[3]>='0' && move[3]<='9' &&
4373 move[2]>='a' && move[2]<='x' ) {
4375 move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4376 move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4379 move[0]>='a' && move[0]<='x' &&
4380 move[3]>='0' && move[3]<='9' &&
4381 move[2]>='a' && move[2]<='x' ) {
4382 /* output move, normal -> Shogi */
4383 move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
4384 move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
4385 move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4386 move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4387 if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
4389 if (appData.debugMode) {
4390 fprintf(debugFP, " out = '%s'\n", move);
4394 /* Parser for moves from gnuchess, ICS, or user typein box */
4396 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
4399 ChessMove *moveType;
4400 int *fromX, *fromY, *toX, *toY;
4403 if (appData.debugMode) {
4404 fprintf(debugFP, "move to parse: %s\n", move);
4406 *moveType = yylexstr(moveNum, move);
4408 switch (*moveType) {
4409 case WhitePromotionChancellor:
4410 case BlackPromotionChancellor:
4411 case WhitePromotionArchbishop:
4412 case BlackPromotionArchbishop:
4413 case WhitePromotionQueen:
4414 case BlackPromotionQueen:
4415 case WhitePromotionRook:
4416 case BlackPromotionRook:
4417 case WhitePromotionBishop:
4418 case BlackPromotionBishop:
4419 case WhitePromotionKnight:
4420 case BlackPromotionKnight:
4421 case WhitePromotionKing:
4422 case BlackPromotionKing:
4424 case WhiteCapturesEnPassant:
4425 case BlackCapturesEnPassant:
4426 case WhiteKingSideCastle:
4427 case WhiteQueenSideCastle:
4428 case BlackKingSideCastle:
4429 case BlackQueenSideCastle:
4430 case WhiteKingSideCastleWild:
4431 case WhiteQueenSideCastleWild:
4432 case BlackKingSideCastleWild:
4433 case BlackQueenSideCastleWild:
4434 /* Code added by Tord: */
4435 case WhiteHSideCastleFR:
4436 case WhiteASideCastleFR:
4437 case BlackHSideCastleFR:
4438 case BlackASideCastleFR:
4439 /* End of code added by Tord */
4440 case IllegalMove: /* bug or odd chess variant */
4441 *fromX = currentMoveString[0] - AAA;
4442 *fromY = currentMoveString[1] - ONE;
4443 *toX = currentMoveString[2] - AAA;
4444 *toY = currentMoveString[3] - ONE;
4445 *promoChar = currentMoveString[4];
4446 if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
4447 *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
4448 if (appData.debugMode) {
4449 fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
4451 *fromX = *fromY = *toX = *toY = 0;
4454 if (appData.testLegality) {
4455 return (*moveType != IllegalMove);
4457 return !(*fromX == *toX && *fromY == *toY) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
4458 WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
4463 *fromX = *moveType == WhiteDrop ?
4464 (int) CharToPiece(ToUpper(currentMoveString[0])) :
4465 (int) CharToPiece(ToLower(currentMoveString[0]));
4467 *toX = currentMoveString[2] - AAA;
4468 *toY = currentMoveString[3] - ONE;
4469 *promoChar = NULLCHAR;
4473 case ImpossibleMove:
4474 case (ChessMove) 0: /* end of file */
4483 if (appData.debugMode) {
4484 fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
4487 *fromX = *fromY = *toX = *toY = 0;
4488 *promoChar = NULLCHAR;
4496 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
4497 int fromX, fromY, toX, toY; char promoChar;
4502 endPV = forwardMostMove;
4504 while(*pv == ' ') pv++;
4505 if(*pv == '(') pv++; // first (ponder) move can be in parentheses
4506 valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
4507 if(appData.debugMode){
4508 fprintf(debugFP,"parsePV: %d %c%c%c%c '%s'\n", valid, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, pv);
4510 if(!valid && nr == 0 &&
4511 ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
4512 nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
4514 while(*pv && *pv++ != ' '); // skip what we parsed; assume space separators
4515 if(moveType == Comment) { valid++; continue; } // allow comments in PV
4517 if(endPV+1 > framePtr) break; // no space, truncate
4520 CopyBoard(boards[endPV], boards[endPV-1]);
4521 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
4522 moveList[endPV-1][0] = fromX + AAA;
4523 moveList[endPV-1][1] = fromY + ONE;
4524 moveList[endPV-1][2] = toX + AAA;
4525 moveList[endPV-1][3] = toY + ONE;
4526 parseList[endPV-1][0] = NULLCHAR;
4528 currentMove = endPV;
4529 if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
4530 SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
4531 moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
4532 DrawPosition(TRUE, boards[currentMove]);
4535 static int lastX, lastY;
4538 LoadMultiPV(int x, int y, char *buf, int index, int *start, int *end)
4542 if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
4543 lastX = x; lastY = y;
4544 while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
4546 while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
4548 while(buf[index] && buf[index] != '\n') index++;
4550 ParsePV(buf+startPV);
4551 *start = startPV; *end = index-1;
4556 LoadPV(int x, int y)
4557 { // called on right mouse click to load PV
4558 int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
4559 lastX = x; lastY = y;
4560 ParsePV(lastPV[which]); // load the PV of the thinking engine in the boards array.
4567 if(endPV < 0) return;
4569 currentMove = forwardMostMove;
4570 ClearPremoveHighlights();
4571 DrawPosition(TRUE, boards[currentMove]);
4575 MovePV(int x, int y, int h)
4576 { // step through PV based on mouse coordinates (called on mouse move)
4577 int margin = h>>3, step = 0;
4579 if(endPV < 0) return;
4580 // we must somehow check if right button is still down (might be released off board!)
4581 if(y < margin && (abs(x - lastX) > 6 || abs(y - lastY) > 6)) step = 1; else
4582 if(y > h - margin && (abs(x - lastX) > 6 || abs(y - lastY) > 6)) step = -1; else
4583 if( y > lastY + 6 ) step = -1; else if(y < lastY - 6) step = 1;
4585 lastX = x; lastY = y;
4586 if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
4587 currentMove += step;
4588 if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
4589 SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
4590 moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
4591 DrawPosition(FALSE, boards[currentMove]);
4595 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
4596 // All positions will have equal probability, but the current method will not provide a unique
4597 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
4603 int piecesLeft[(int)BlackPawn];
4604 int seed, nrOfShuffles;
4606 void GetPositionNumber()
4607 { // sets global variable seed
4610 seed = appData.defaultFrcPosition;
4611 if(seed < 0) { // randomize based on time for negative FRC position numbers
4612 for(i=0; i<50; i++) seed += random();
4613 seed = random() ^ random() >> 8 ^ random() << 8;
4614 if(seed<0) seed = -seed;
4618 int put(Board board, int pieceType, int rank, int n, int shade)
4619 // put the piece on the (n-1)-th empty squares of the given shade
4623 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
4624 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
4625 board[rank][i] = (ChessSquare) pieceType;
4626 squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
4628 piecesLeft[pieceType]--;
4636 void AddOnePiece(Board board, int pieceType, int rank, int shade)
4637 // calculate where the next piece goes, (any empty square), and put it there
4641 i = seed % squaresLeft[shade];
4642 nrOfShuffles *= squaresLeft[shade];
4643 seed /= squaresLeft[shade];
4644 put(board, pieceType, rank, i, shade);
4647 void AddTwoPieces(Board board, int pieceType, int rank)
4648 // calculate where the next 2 identical pieces go, (any empty square), and put it there
4650 int i, n=squaresLeft[ANY], j=n-1, k;
4652 k = n*(n-1)/2; // nr of possibilities, not counting permutations
4653 i = seed % k; // pick one
4656 while(i >= j) i -= j--;
4657 j = n - 1 - j; i += j;
4658 put(board, pieceType, rank, j, ANY);
4659 put(board, pieceType, rank, i, ANY);
4662 void SetUpShuffle(Board board, int number)
4666 GetPositionNumber(); nrOfShuffles = 1;
4668 squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
4669 squaresLeft[ANY] = BOARD_RGHT - BOARD_LEFT;
4670 squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
4672 for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
4674 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
4675 p = (int) board[0][i];
4676 if(p < (int) BlackPawn) piecesLeft[p] ++;
4677 board[0][i] = EmptySquare;
4680 if(PosFlags(0) & F_ALL_CASTLE_OK) {
4681 // shuffles restricted to allow normal castling put KRR first
4682 if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
4683 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
4684 else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
4685 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
4686 if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
4687 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
4688 if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
4689 put(board, WhiteRook, 0, 0, ANY);
4690 // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
4693 if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
4694 // only for even boards make effort to put pairs of colorbound pieces on opposite colors
4695 for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
4696 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
4697 while(piecesLeft[p] >= 2) {
4698 AddOnePiece(board, p, 0, LITE);
4699 AddOnePiece(board, p, 0, DARK);
4701 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
4704 for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
4705 // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
4706 // but we leave King and Rooks for last, to possibly obey FRC restriction
4707 if(p == (int)WhiteRook) continue;
4708 while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
4709 if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY); // add the odd piece
4712 // now everything is placed, except perhaps King (Unicorn) and Rooks
4714 if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
4715 // Last King gets castling rights
4716 while(piecesLeft[(int)WhiteUnicorn]) {
4717 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
4718 initialRights[2] = initialRights[5] = board[CASTLING][2] = board[CASTLING][5] = i;
4721 while(piecesLeft[(int)WhiteKing]) {
4722 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
4723 initialRights[2] = initialRights[5] = board[CASTLING][2] = board[CASTLING][5] = i;
4728 while(piecesLeft[(int)WhiteKing]) AddOnePiece(board, WhiteKing, 0, ANY);
4729 while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
4732 // Only Rooks can be left; simply place them all
4733 while(piecesLeft[(int)WhiteRook]) {
4734 i = put(board, WhiteRook, 0, 0, ANY);
4735 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
4738 initialRights[1] = initialRights[4] = board[CASTLING][1] = board[CASTLING][4] = i;
4740 initialRights[0] = initialRights[3] = board[CASTLING][0] = board[CASTLING][3] = i;
4743 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
4744 board[BOARD_HEIGHT-1][i] = (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
4747 if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
4750 int SetCharTable( char *table, const char * map )
4751 /* [HGM] moved here from winboard.c because of its general usefulness */
4752 /* Basically a safe strcpy that uses the last character as King */
4754 int result = FALSE; int NrPieces;
4756 if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
4757 && NrPieces >= 12 && !(NrPieces&1)) {
4758 int i; /* [HGM] Accept even length from 12 to 34 */
4760 for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
4761 for( i=0; i<NrPieces/2-1; i++ ) {
4763 table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
4765 table[(int) WhiteKing] = map[NrPieces/2-1];
4766 table[(int) BlackKing] = map[NrPieces-1];
4774 void Prelude(Board board)
4775 { // [HGM] superchess: random selection of exo-pieces
4776 int i, j, k; ChessSquare p;
4777 static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
4779 GetPositionNumber(); // use FRC position number
4781 if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
4782 SetCharTable(pieceToChar, appData.pieceToCharTable);
4783 for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
4784 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
4787 j = seed%4; seed /= 4;
4788 p = board[0][BOARD_LEFT+j]; board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
4789 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
4790 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
4791 j = seed%3 + (seed%3 >= j); seed /= 3;
4792 p = board[0][BOARD_LEFT+j]; board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
4793 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
4794 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
4795 j = seed%3; seed /= 3;
4796 p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
4797 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
4798 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
4799 j = seed%2 + (seed%2 >= j); seed /= 2;
4800 p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
4801 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
4802 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
4803 j = seed%4; seed /= 4; put(board, exoPieces[3], 0, j, ANY);
4804 j = seed%3; seed /= 3; put(board, exoPieces[2], 0, j, ANY);
4805 j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
4806 put(board, exoPieces[0], 0, 0, ANY);
4807 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
4811 InitPosition(redraw)
4814 ChessSquare (* pieces)[BOARD_FILES];
4815 int i, j, pawnRow, overrule,
4816 oldx = gameInfo.boardWidth,
4817 oldy = gameInfo.boardHeight,
4818 oldh = gameInfo.holdingsWidth,
4819 oldv = gameInfo.variant;
4821 if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
4823 /* [AS] Initialize pv info list [HGM] and game status */
4825 for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
4826 pvInfoList[i].depth = 0;
4827 boards[i][EP_STATUS] = EP_NONE;
4828 for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
4831 initialRulePlies = 0; /* 50-move counter start */
4833 castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
4834 castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
4838 /* [HGM] logic here is completely changed. In stead of full positions */
4839 /* the initialized data only consist of the two backranks. The switch */
4840 /* selects which one we will use, which is than copied to the Board */
4841 /* initialPosition, which for the rest is initialized by Pawns and */
4842 /* empty squares. This initial position is then copied to boards[0], */
4843 /* possibly after shuffling, so that it remains available. */
4845 gameInfo.holdingsWidth = 0; /* default board sizes */
4846 gameInfo.boardWidth = 8;
4847 gameInfo.boardHeight = 8;
4848 gameInfo.holdingsSize = 0;
4849 nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
4850 for(i=0; i<BOARD_FILES-2; i++)
4851 initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
4852 initialPosition[EP_STATUS] = EP_NONE;
4853 SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
4855 switch (gameInfo.variant) {
4856 case VariantFischeRandom:
4857 shuffleOpenings = TRUE;
4861 case VariantShatranj:
4862 pieces = ShatranjArray;
4863 nrCastlingRights = 0;
4864 SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
4867 pieces = makrukArray;
4868 nrCastlingRights = 0;
4869 startedFromSetupPosition = TRUE;
4870 SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
4872 case VariantTwoKings:
4873 pieces = twoKingsArray;
4875 case VariantCapaRandom:
4876 shuffleOpenings = TRUE;
4877 case VariantCapablanca:
4878 pieces = CapablancaArray;
4879 gameInfo.boardWidth = 10;
4880 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
4883 pieces = GothicArray;
4884 gameInfo.boardWidth = 10;
4885 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
4888 pieces = JanusArray;
4889 gameInfo.boardWidth = 10;
4890 SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
4891 nrCastlingRights = 6;
4892 initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
4893 initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
4894 initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
4895 initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
4896 initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
4897 initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
4900 pieces = FalconArray;
4901 gameInfo.boardWidth = 10;
4902 SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
4904 case VariantXiangqi:
4905 pieces = XiangqiArray;
4906 gameInfo.boardWidth = 9;
4907 gameInfo.boardHeight = 10;
4908 nrCastlingRights = 0;
4909 SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
4912 pieces = ShogiArray;
4913 gameInfo.boardWidth = 9;
4914 gameInfo.boardHeight = 9;
4915 gameInfo.holdingsSize = 7;
4916 nrCastlingRights = 0;
4917 SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
4919 case VariantCourier:
4920 pieces = CourierArray;
4921 gameInfo.boardWidth = 12;
4922 nrCastlingRights = 0;
4923 SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
4925 case VariantKnightmate:
4926 pieces = KnightmateArray;
4927 SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
4930 pieces = fairyArray;
4931 SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
4934 pieces = GreatArray;
4935 gameInfo.boardWidth = 10;
4936 SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
4937 gameInfo.holdingsSize = 8;
4941 SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
4942 gameInfo.holdingsSize = 8;
4943 startedFromSetupPosition = TRUE;
4945 case VariantCrazyhouse:
4946 case VariantBughouse:
4948 SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
4949 gameInfo.holdingsSize = 5;
4951 case VariantWildCastle:
4953 /* !!?shuffle with kings guaranteed to be on d or e file */
4954 shuffleOpenings = 1;
4956 case VariantNoCastle:
4958 nrCastlingRights = 0;
4959 /* !!?unconstrained back-rank shuffle */
4960 shuffleOpenings = 1;
4965 if(appData.NrFiles >= 0) {
4966 if(gameInfo.boardWidth != appData.NrFiles) overrule++;
4967 gameInfo.boardWidth = appData.NrFiles;
4969 if(appData.NrRanks >= 0) {
4970 gameInfo.boardHeight = appData.NrRanks;
4972 if(appData.holdingsSize >= 0) {
4973 i = appData.holdingsSize;
4974 if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
4975 gameInfo.holdingsSize = i;
4977 if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
4978 if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
4979 DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
4981 pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
4982 if(pawnRow < 1) pawnRow = 1;
4983 if(gameInfo.variant == VariantMakruk) pawnRow = 2;
4985 /* User pieceToChar list overrules defaults */
4986 if(appData.pieceToCharTable != NULL)
4987 SetCharTable(pieceToChar, appData.pieceToCharTable);
4989 for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
4991 if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
4992 s = (ChessSquare) 0; /* account holding counts in guard band */
4993 for( i=0; i<BOARD_HEIGHT; i++ )
4994 initialPosition[i][j] = s;
4996 if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
4997 initialPosition[0][j] = pieces[0][j-gameInfo.holdingsWidth];
4998 initialPosition[pawnRow][j] = WhitePawn;
4999 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = BlackPawn;
5000 if(gameInfo.variant == VariantXiangqi) {
5002 initialPosition[pawnRow][j] =
5003 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
5004 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
5005 initialPosition[2][j] = WhiteCannon;
5006 initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
5010 initialPosition[BOARD_HEIGHT-1][j] = pieces[1][j-gameInfo.holdingsWidth];
5012 if( (gameInfo.variant == VariantShogi) && !overrule ) {
5015 initialPosition[1][j] = WhiteBishop;
5016 initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
5018 initialPosition[1][j] = WhiteRook;
5019 initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
5022 if( nrCastlingRights == -1) {
5023 /* [HGM] Build normal castling rights (must be done after board sizing!) */
5024 /* This sets default castling rights from none to normal corners */
5025 /* Variants with other castling rights must set them themselves above */
5026 nrCastlingRights = 6;
5027 initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5028 initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5029 initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
5030 initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5031 initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5032 initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
5035 if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
5036 if(gameInfo.variant == VariantGreat) { // promotion commoners
5037 initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
5038 initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
5039 initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
5040 initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
5042 if (appData.debugMode) {
5043 fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
5045 if(shuffleOpenings) {
5046 SetUpShuffle(initialPosition, appData.defaultFrcPosition);
5047 startedFromSetupPosition = TRUE;
5049 if(startedFromPositionFile) {
5050 /* [HGM] loadPos: use PositionFile for every new game */
5051 CopyBoard(initialPosition, filePosition);
5052 for(i=0; i<nrCastlingRights; i++)
5053 initialRights[i] = filePosition[CASTLING][i];
5054 startedFromSetupPosition = TRUE;
5057 CopyBoard(boards[0], initialPosition);
5058 if(oldx != gameInfo.boardWidth ||
5059 oldy != gameInfo.boardHeight ||
5060 oldh != gameInfo.holdingsWidth
5062 || oldv == VariantGothic || // For licensing popups
5063 gameInfo.variant == VariantGothic
5066 || oldv == VariantFalcon ||
5067 gameInfo.variant == VariantFalcon
5071 InitDrawingSizes(-2 ,0);
5075 DrawPosition(TRUE, boards[currentMove]);
5080 SendBoard(cps, moveNum)
5081 ChessProgramState *cps;
5084 char message[MSG_SIZ];
5086 if (cps->useSetboard) {
5087 char* fen = PositionToFEN(moveNum, cps->fenOverride);
5088 sprintf(message, "setboard %s\n", fen);
5089 SendToProgram(message, cps);
5095 /* Kludge to set black to move, avoiding the troublesome and now
5096 * deprecated "black" command.
5098 if (!WhiteOnMove(moveNum)) SendToProgram("a2a3\n", cps);
5100 SendToProgram("edit\n", cps);
5101 SendToProgram("#\n", cps);
5102 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
5103 bp = &boards[moveNum][i][BOARD_LEFT];
5104 for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
5105 if ((int) *bp < (int) BlackPawn) {
5106 sprintf(message, "%c%c%c\n", PieceToChar(*bp),
5108 if(message[0] == '+' || message[0] == '~') {
5109 sprintf(message, "%c%c%c+\n",
5110 PieceToChar((ChessSquare)(DEMOTED *bp)),
5113 if(cps->alphaRank) { /* [HGM] shogi: translate coords */
5114 message[1] = BOARD_RGHT - 1 - j + '1';
5115 message[2] = BOARD_HEIGHT - 1 - i + 'a';
5117 SendToProgram(message, cps);
5122 SendToProgram("c\n", cps);
5123 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
5124 bp = &boards[moveNum][i][BOARD_LEFT];
5125 for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
5126 if (((int) *bp != (int) EmptySquare)
5127 && ((int) *bp >= (int) BlackPawn)) {
5128 sprintf(message, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
5130 if(message[0] == '+' || message[0] == '~') {
5131 sprintf(message, "%c%c%c+\n",
5132 PieceToChar((ChessSquare)(DEMOTED *bp)),
5135 if(cps->alphaRank) { /* [HGM] shogi: translate coords */
5136 message[1] = BOARD_RGHT - 1 - j + '1';
5137 message[2] = BOARD_HEIGHT - 1 - i + 'a';
5139 SendToProgram(message, cps);
5144 SendToProgram(".\n", cps);
5146 setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
5150 HasPromotionChoice(int fromX, int fromY, int toX, int toY, char *promoChoice)
5152 /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
5153 /* [HGM] add Shogi promotions */
5154 int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
5159 if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
5160 if(toX < BOARD_LEFT || toX >= BOARD_RGHT) return FALSE; // move into holdings
5162 if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
5163 !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
5166 piece = boards[currentMove][fromY][fromX];
5167 if(gameInfo.variant == VariantShogi) {
5168 promotionZoneSize = 3;
5169 highestPromotingPiece = (int)WhiteFerz;
5170 } else if(gameInfo.variant == VariantMakruk) {
5171 promotionZoneSize = 3;
5174 // next weed out all moves that do not touch the promotion zone at all
5175 if((int)piece >= BlackPawn) {
5176 if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
5178 highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
5180 if( toY < BOARD_HEIGHT - promotionZoneSize &&
5181 fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
5184 if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
5186 // weed out mandatory Shogi promotions
5187 if(gameInfo.variant == VariantShogi) {
5188 if(piece >= BlackPawn) {
5189 if(toY == 0 && piece == BlackPawn ||
5190 toY == 0 && piece == BlackQueen ||
5191 toY <= 1 && piece == BlackKnight) {
5196 if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
5197 toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
5198 toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
5205 // weed out obviously illegal Pawn moves
5206 if(appData.testLegality && (piece == WhitePawn || piece == BlackPawn) ) {
5207 if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
5208 if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
5209 if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
5210 if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
5211 // note we are not allowed to test for valid (non-)capture, due to premove
5214 // we either have a choice what to promote to, or (in Shogi) whether to promote
5215 if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk) {
5216 *promoChoice = PieceToChar(BlackFerz); // no choice
5219 if(appData.alwaysPromoteToQueen) { // predetermined
5220 if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantLosers)
5221 *promoChoice = PieceToChar(BlackKing); // in Suicide Q is the last thing we want
5222 else *promoChoice = PieceToChar(BlackQueen);
5226 // suppress promotion popup on illegal moves that are not premoves
5227 premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
5228 gameMode == IcsPlayingBlack && WhiteOnMove(currentMove);
5229 if(appData.testLegality && !premove) {
5230 moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5231 fromY, fromX, toY, toX, NULLCHAR);
5232 if(moveType != WhitePromotionQueen && moveType != BlackPromotionQueen &&
5233 moveType != WhitePromotionKnight && moveType != BlackPromotionKnight)
5241 InPalace(row, column)
5243 { /* [HGM] for Xiangqi */
5244 if( (row < 3 || row > BOARD_HEIGHT-4) &&
5245 column < (BOARD_WIDTH + 4)/2 &&
5246 column > (BOARD_WIDTH - 5)/2 ) return TRUE;
5251 PieceForSquare (x, y)
5255 if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
5258 return boards[currentMove][y][x];
5262 OKToStartUserMove(x, y)
5265 ChessSquare from_piece;
5268 if (matchMode) return FALSE;
5269 if (gameMode == EditPosition) return TRUE;
5271 if (x >= 0 && y >= 0)
5272 from_piece = boards[currentMove][y][x];
5274 from_piece = EmptySquare;
5276 if (from_piece == EmptySquare) return FALSE;
5278 white_piece = (int)from_piece >= (int)WhitePawn &&
5279 (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
5282 case PlayFromGameFile:
5284 case TwoMachinesPlay:
5292 case MachinePlaysWhite:
5293 case IcsPlayingBlack:
5294 if (appData.zippyPlay) return FALSE;
5296 DisplayMoveError(_("You are playing Black"));
5301 case MachinePlaysBlack:
5302 case IcsPlayingWhite:
5303 if (appData.zippyPlay) return FALSE;
5305 DisplayMoveError(_("You are playing White"));
5311 if (!white_piece && WhiteOnMove(currentMove)) {
5312 DisplayMoveError(_("It is White's turn"));
5315 if (white_piece && !WhiteOnMove(currentMove)) {
5316 DisplayMoveError(_("It is Black's turn"));
5319 if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
5320 /* Editing correspondence game history */
5321 /* Could disallow this or prompt for confirmation */
5326 case BeginningOfGame:
5327 if (appData.icsActive) return FALSE;
5328 if (!appData.noChessProgram) {
5330 DisplayMoveError(_("You are playing White"));
5337 if (!white_piece && WhiteOnMove(currentMove)) {
5338 DisplayMoveError(_("It is White's turn"));
5341 if (white_piece && !WhiteOnMove(currentMove)) {
5342 DisplayMoveError(_("It is Black's turn"));
5351 if (currentMove != forwardMostMove && gameMode != AnalyzeMode
5352 && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
5353 && gameMode != AnalyzeFile && gameMode != Training) {
5354 DisplayMoveError(_("Displayed position is not current"));
5360 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
5361 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
5362 int lastLoadGameUseList = FALSE;
5363 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
5364 ChessMove lastLoadGameStart = (ChessMove) 0;
5367 UserMoveTest(fromX, fromY, toX, toY, promoChar, captureOwn)
5368 int fromX, fromY, toX, toY;
5373 ChessSquare pdown, pup;
5375 /* Check if the user is playing in turn. This is complicated because we
5376 let the user "pick up" a piece before it is his turn. So the piece he
5377 tried to pick up may have been captured by the time he puts it down!
5378 Therefore we use the color the user is supposed to be playing in this
5379 test, not the color of the piece that is currently on the starting
5380 square---except in EditGame mode, where the user is playing both
5381 sides; fortunately there the capture race can't happen. (It can
5382 now happen in IcsExamining mode, but that's just too bad. The user
5383 will get a somewhat confusing message in that case.)
5387 case PlayFromGameFile:
5389 case TwoMachinesPlay:
5393 /* We switched into a game mode where moves are not accepted,
5394 perhaps while the mouse button was down. */
5395 return ImpossibleMove;
5397 case MachinePlaysWhite:
5398 /* User is moving for Black */
5399 if (WhiteOnMove(currentMove)) {
5400 DisplayMoveError(_("It is White's turn"));
5401 return ImpossibleMove;
5405 case MachinePlaysBlack:
5406 /* User is moving for White */
5407 if (!WhiteOnMove(currentMove)) {
5408 DisplayMoveError(_("It is Black's turn"));
5409 return ImpossibleMove;
5415 case BeginningOfGame:
5418 if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
5419 (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
5420 /* User is moving for Black */
5421 if (WhiteOnMove(currentMove)) {
5422 DisplayMoveError(_("It is White's turn"));
5423 return ImpossibleMove;
5426 /* User is moving for White */
5427 if (!WhiteOnMove(currentMove)) {
5428 DisplayMoveError(_("It is Black's turn"));
5429 return ImpossibleMove;
5434 case IcsPlayingBlack:
5435 /* User is moving for Black */
5436 if (WhiteOnMove(currentMove)) {
5437 if (!appData.premove) {
5438 DisplayMoveError(_("It is White's turn"));
5439 } else if (toX >= 0 && toY >= 0) {
5442 premoveFromX = fromX;
5443 premoveFromY = fromY;
5444 premovePromoChar = promoChar;
5446 if (appData.debugMode)
5447 fprintf(debugFP, "Got premove: fromX %d,"
5448 "fromY %d, toX %d, toY %d\n",
5449 fromX, fromY, toX, toY);
5451 return ImpossibleMove;
5455 case IcsPlayingWhite:
5456 /* User is moving for White */
5457 if (!WhiteOnMove(currentMove)) {
5458 if (!appData.premove) {
5459 DisplayMoveError(_("It is Black's turn"));
5460 } else if (toX >= 0 && toY >= 0) {
5463 premoveFromX = fromX;
5464 premoveFromY = fromY;
5465 premovePromoChar = promoChar;
5467 if (appData.debugMode)
5468 fprintf(debugFP, "Got premove: fromX %d,"
5469 "fromY %d, toX %d, toY %d\n",
5470 fromX, fromY, toX, toY);
5472 return ImpossibleMove;
5480 /* EditPosition, empty square, or different color piece;
5481 click-click move is possible */
5482 if (toX == -2 || toY == -2) {
5483 boards[0][fromY][fromX] = EmptySquare;
5484 return AmbiguousMove;
5485 } else if (toX >= 0 && toY >= 0) {
5486 boards[0][toY][toX] = boards[0][fromY][fromX];
5487 if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
5488 if(boards[0][fromY][0] != EmptySquare) {
5489 if(boards[0][fromY][1]) boards[0][fromY][1]--;
5490 if(boards[0][fromY][1] == 0) boards[0][fromY][0] = EmptySquare;
5493 if(fromX == BOARD_RGHT+1) {
5494 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
5495 if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
5496 if(boards[0][fromY][BOARD_WIDTH-2] == 0) boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
5499 boards[0][fromY][fromX] = EmptySquare;
5500 return AmbiguousMove;
5502 return ImpossibleMove;
5505 if(toX < 0 || toY < 0) return ImpossibleMove;
5506 pdown = boards[currentMove][fromY][fromX];
5507 pup = boards[currentMove][toY][toX];
5509 /* [HGM] If move started in holdings, it means a drop */
5510 if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) {
5511 if( pup != EmptySquare ) return ImpossibleMove;
5512 if(appData.testLegality) {
5513 /* it would be more logical if LegalityTest() also figured out
5514 * which drops are legal. For now we forbid pawns on back rank.
5515 * Shogi is on its own here...
5517 if( (pdown == WhitePawn || pdown == BlackPawn) &&
5518 (toY == 0 || toY == BOARD_HEIGHT -1 ) )
5519 return(ImpossibleMove); /* no pawn drops on 1st/8th */
5521 return WhiteDrop; /* Not needed to specify white or black yet */
5524 userOfferedDraw = FALSE;
5526 /* [HGM] always test for legality, to get promotion info */
5527 moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5528 fromY, fromX, toY, toX, promoChar);
5529 /* [HGM] but possibly ignore an IllegalMove result */
5530 if (appData.testLegality) {
5531 if (moveType == IllegalMove || moveType == ImpossibleMove) {
5532 DisplayMoveError(_("Illegal move"));
5533 return ImpossibleMove;
5538 /* [HGM] <popupFix> in stead of calling FinishMove directly, this
5539 function is made into one that returns an OK move type if FinishMove
5540 should be called. This to give the calling driver routine the
5541 opportunity to finish the userMove input with a promotion popup,
5542 without bothering the user with this for invalid or illegal moves */
5544 /* FinishMove(moveType, fromX, fromY, toX, toY, promoChar); */
5547 /* Common tail of UserMoveEvent and DropMenuEvent */
5549 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
5551 int fromX, fromY, toX, toY;
5552 /*char*/int promoChar;
5556 if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) && promoChar != NULLCHAR)
5558 // [HGM] superchess: suppress promotions to non-available piece
5559 int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
5560 if(WhiteOnMove(currentMove))
5562 if(!boards[currentMove][k][BOARD_WIDTH-2])
5567 if(!boards[currentMove][BOARD_HEIGHT-1-k][1])
5572 /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
5573 move type in caller when we know the move is a legal promotion */
5574 if(moveType == NormalMove && promoChar)
5575 moveType = PromoCharToMoveType(WhiteOnMove(currentMove), promoChar);
5577 /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
5578 move type in caller when we know the move is a legal promotion */
5579 if(moveType == NormalMove && promoChar)
5580 moveType = PromoCharToMoveType(WhiteOnMove(currentMove), promoChar);
5582 /* [HGM] convert drag-and-drop piece drops to standard form */
5583 if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK )
5585 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
5586 if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n",
5587 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
5588 // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
5589 if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
5590 fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
5591 while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++;
5595 /* [HGM] <popupFix> The following if has been moved here from
5596 UserMoveEvent(). Because it seemed to belong here (why not allow
5597 piece drops in training games?), and because it can only be
5598 performed after it is known to what we promote. */
5599 if (gameMode == Training)
5601 /* compare the move played on the board to the next move in the
5602 * game. If they match, display the move and the opponent's response.
5603 * If they don't match, display an error message.
5607 CopyBoard(testBoard, boards[currentMove]);
5608 ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
5610 if (CompareBoards(testBoard, boards[currentMove+1]))
5612 ForwardInner(currentMove+1);
5614 /* Autoplay the opponent's response.
5615 * if appData.animate was TRUE when Training mode was entered,
5616 * the response will be animated.
5618 saveAnimate = appData.animate;
5619 appData.animate = animateTraining;
5620 ForwardInner(currentMove+1);
5621 appData.animate = saveAnimate;
5623 /* check for the end of the game */
5624 if (currentMove >= forwardMostMove)
5626 gameMode = PlayFromGameFile;
5628 SetTrainingModeOff();
5629 DisplayInformation(_("End of game"));
5634 DisplayError(_("Incorrect move"), 0);
5639 /* Ok, now we know that the move is good, so we can kill
5640 the previous line in Analysis Mode */
5641 if ((gameMode == AnalyzeMode || gameMode == EditGame)
5642 && currentMove < forwardMostMove) {
5643 PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
5646 /* If we need the chess program but it's dead, restart it */
5647 ResurrectChessProgram();
5649 /* A user move restarts a paused game*/
5653 thinkOutput[0] = NULLCHAR;
5655 MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
5657 if (gameMode == BeginningOfGame)
5659 if (appData.noChessProgram)
5661 gameMode = EditGame;
5667 gameMode = MachinePlaysBlack;
5670 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
5674 sprintf(buf, "name %s\n", gameInfo.white);
5675 SendToProgram(buf, &first);
5682 /* Relay move to ICS or chess engine */
5683 if (appData.icsActive)
5685 if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
5686 gameMode == IcsExamining)
5688 SendMoveToICS(moveType, fromX, fromY, toX, toY);
5694 if (first.sendTime && (gameMode == BeginningOfGame ||
5695 gameMode == MachinePlaysWhite ||
5696 gameMode == MachinePlaysBlack))
5698 SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
5700 if (gameMode != EditGame && gameMode != PlayFromGameFile)
5702 // [HGM] book: if program might be playing, let it use book
5703 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
5704 first.maybeThinking = TRUE;
5707 SendMoveToProgram(forwardMostMove-1, &first);
5708 if (currentMove == cmailOldMove + 1)
5710 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
5714 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5719 switch (MateTest(boards[currentMove], PosFlags(currentMove)) )
5726 if (WhiteOnMove(currentMove)) {
5727 GameEnds(BlackWins, "Black mates", GE_PLAYER);
5729 GameEnds(WhiteWins, "White mates", GE_PLAYER);
5733 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
5738 case MachinePlaysBlack:
5739 case MachinePlaysWhite:
5740 /* disable certain menu options while machine is thinking */
5741 SetMachineThinkingEnables();
5749 { // [HGM] book: simulate book reply
5750 static char bookMove[MSG_SIZ]; // a bit generous?
5752 programStats.nodes = programStats.depth = programStats.time =
5753 programStats.score = programStats.got_only_move = 0;
5754 sprintf(programStats.movelist, "%s (xbook)", bookHit);
5756 strcpy(bookMove, "move ");
5757 strcat(bookMove, bookHit);
5758 HandleMachineMove(bookMove, &first);
5765 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
5766 int fromX, fromY, toX, toY;
5769 /* [HGM] This routine was added to allow calling of its two logical
5770 parts from other modules in the old way. Before, UserMoveEvent()
5771 automatically called FinishMove() if the move was OK, and returned
5772 otherwise. I separated the two, in order to make it possible to
5773 slip a promotion popup in between. But that it always needs two
5774 calls, to the first part, (now called UserMoveTest() ), and to
5775 FinishMove if the first part succeeded. Calls that do not need
5776 to do anything in between, can call this routine the old way.
5778 ChessMove moveType = UserMoveTest(fromX, fromY, toX, toY, promoChar, FALSE);
5779 if(appData.debugMode) fprintf(debugFP, "moveType 4 = %d, promochar = %x\n", moveType, promoChar);
5780 if(moveType == AmbiguousMove)
5781 DrawPosition(FALSE, boards[currentMove]);
5782 else if(moveType != ImpossibleMove && moveType != Comment)
5783 FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
5787 Mark(board, flags, kind, rf, ff, rt, ft, closure)
5794 typedef char Markers[BOARD_RANKS][BOARD_FILES];
5795 Markers *m = (Markers *) closure;
5796 if(rf == fromY && ff == fromX)
5797 (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
5798 || kind == WhiteCapturesEnPassant
5799 || kind == BlackCapturesEnPassant);
5800 else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
5804 MarkTargetSquares(int clear)
5807 if(!appData.markers || !appData.highlightDragging ||
5808 !appData.testLegality || gameMode == EditPosition) return;
5810 for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) marker[y][x] = 0;
5813 GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker);
5814 if(PosFlags(0) & F_MANDATORY_CAPTURE) {
5815 for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
5817 for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
5820 DrawPosition(TRUE, NULL);
5823 void LeftClick(ClickType clickType, int xPix, int yPix)
5826 Boolean saveAnimate;
5827 static int second = 0, promotionChoice = 0;
5828 char promoChoice = NULLCHAR;
5830 if (clickType == Press) ErrorPopDown();
5831 MarkTargetSquares(1);
5833 x = EventToSquare(xPix, BOARD_WIDTH);
5834 y = EventToSquare(yPix, BOARD_HEIGHT);
5835 if (!flipView && y >= 0) {
5836 y = BOARD_HEIGHT - 1 - y;
5838 if (flipView && x >= 0) {
5839 x = BOARD_WIDTH - 1 - x;
5842 if(promotionChoice) { // we are waiting for a click to indicate promotion piece
5843 if(clickType == Release) return; // ignore upclick of click-click destination
5844 promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
5845 if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
5846 if(gameInfo.holdingsWidth &&
5847 (WhiteOnMove(currentMove)
5848 ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y > 0
5849 : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT-1) ) {
5850 // click in right holdings, for determining promotion piece
5851 ChessSquare p = boards[currentMove][y][x];
5852 if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
5853 if(p != EmptySquare) {
5854 FinishMove(NormalMove, fromX, fromY, toX, toY, ToLower(PieceToChar(p)));
5859 DrawPosition(FALSE, boards[currentMove]);
5863 /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
5864 if(clickType == Press
5865 && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
5866 || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
5867 || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
5871 if (clickType == Press) {
5873 if (OKToStartUserMove(x, y)) {
5877 MarkTargetSquares(0);
5878 DragPieceBegin(xPix, yPix);
5879 if (appData.highlightDragging) {
5880 SetHighlights(x, y, -1, -1);
5888 if (clickType == Press && gameMode != EditPosition) {
5893 // ignore off-board to clicks
5894 if(y < 0 || x < 0) return;
5896 /* Check if clicking again on the same color piece */
5897 fromP = boards[currentMove][fromY][fromX];
5898 toP = boards[currentMove][y][x];
5899 frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom;
5900 if ((WhitePawn <= fromP && fromP <= WhiteKing &&
5901 WhitePawn <= toP && toP <= WhiteKing &&
5902 !(fromP == WhiteKing && toP == WhiteRook && frc) &&
5903 !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
5904 (BlackPawn <= fromP && fromP <= BlackKing &&
5905 BlackPawn <= toP && toP <= BlackKing &&
5906 !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
5907 !(fromP == BlackKing && toP == BlackRook && frc))) {
5908 /* Clicked again on same color piece -- changed his mind */
5909 second = (x == fromX && y == fromY);
5910 if (appData.highlightDragging) {
5911 SetHighlights(x, y, -1, -1);
5915 if (OKToStartUserMove(x, y)) {
5918 MarkTargetSquares(0);
5919 DragPieceBegin(xPix, yPix);
5923 // ignore clicks on holdings
5924 if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
5927 if (clickType == Release && x == fromX && y == fromY) {
5928 DragPieceEnd(xPix, yPix);
5929 if (appData.animateDragging) {
5930 /* Undo animation damage if any */
5931 DrawPosition(FALSE, NULL);
5934 /* Second up/down in same square; just abort move */
5939 ClearPremoveHighlights();
5941 /* First upclick in same square; start click-click mode */
5942 SetHighlights(x, y, -1, -1);
5947 /* we now have a different from- and (possibly off-board) to-square */
5948 /* Completed move */
5951 saveAnimate = appData.animate;
5952 if (clickType == Press) {
5953 /* Finish clickclick move */
5954 if (appData.animate || appData.highlightLastMove) {
5955 SetHighlights(fromX, fromY, toX, toY);
5960 /* Finish drag move */
5961 if (appData.highlightLastMove) {
5962 SetHighlights(fromX, fromY, toX, toY);
5966 DragPieceEnd(xPix, yPix);
5967 /* Don't animate move and drag both */
5968 appData.animate = FALSE;
5971 // moves into holding are invalid for now (except in EditPosition, adapting to-square)
5972 if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
5973 ChessSquare piece = boards[currentMove][fromY][fromX];
5974 if(gameMode == EditPosition && piece != EmptySquare &&
5975 fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
5978 if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
5979 n = PieceToNumber(piece - (int)BlackPawn);
5980 if(n > gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
5981 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
5982 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
5984 if(x == BOARD_RGHT+1 && piece < BlackPawn) {
5985 n = PieceToNumber(piece);
5986 if(n > gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
5987 boards[currentMove][n][BOARD_WIDTH-1] = piece;
5988 boards[currentMove][n][BOARD_WIDTH-2]++;
5990 boards[currentMove][fromY][fromX] = EmptySquare;
5994 DrawPosition(TRUE, boards[currentMove]);
5998 // off-board moves should not be highlighted
5999 if(x < 0 || x < 0) ClearHighlights();
6001 if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice)) {
6002 SetHighlights(fromX, fromY, toX, toY);
6003 if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
6004 // [HGM] super: promotion to captured piece selected from holdings
6005 ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
6006 promotionChoice = TRUE;
6007 // kludge follows to temporarily execute move on display, without promoting yet
6008 boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
6009 boards[currentMove][toY][toX] = p;
6010 DrawPosition(FALSE, boards[currentMove]);
6011 boards[currentMove][fromY][fromX] = p; // take back, but display stays
6012 boards[currentMove][toY][toX] = q;
6013 DisplayMessage("Click in holdings to choose piece", "");
6018 UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
6019 if (!appData.highlightLastMove || gotPremove) ClearHighlights();
6020 if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
6023 appData.animate = saveAnimate;
6024 if (appData.animate || appData.animateDragging) {
6025 /* Undo animation damage if needed */
6026 DrawPosition(FALSE, NULL);
6030 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
6032 // char * hint = lastHint;
6033 FrontEndProgramStats stats;
6035 stats.which = cps == &first ? 0 : 1;
6036 stats.depth = cpstats->depth;
6037 stats.nodes = cpstats->nodes;
6038 stats.score = cpstats->score;
6039 stats.time = cpstats->time;
6040 stats.pv = cpstats->movelist;
6041 stats.hint = lastHint;
6042 stats.an_move_index = 0;
6043 stats.an_move_count = 0;
6045 if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
6046 stats.hint = cpstats->move_name;
6047 stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
6048 stats.an_move_count = cpstats->nr_moves;
6051 if(stats.pv && stats.pv[0]) strcpy(lastPV[stats.which], stats.pv); // [HGM] pv: remember last PV of each
6053 SetProgramStats( &stats );
6056 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
6057 { // [HGM] book: this routine intercepts moves to simulate book replies
6058 char *bookHit = NULL;
6060 //first determine if the incoming move brings opponent into his book
6061 if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
6062 bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
6063 if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
6064 if(bookHit != NULL && !cps->bookSuspend) {
6065 // make sure opponent is not going to reply after receiving move to book position
6066 SendToProgram("force\n", cps);
6067 cps->bookSuspend = TRUE; // flag indicating it has to be restarted
6069 if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
6070 // now arrange restart after book miss
6072 // after a book hit we never send 'go', and the code after the call to this routine
6073 // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
6075 if (cps->useUsermove) sprintf(buf, "usermove "); // sorry, no SAN yet :(
6076 sprintf(buf, "%s\n", bookHit); // force book move into program supposed to play it
6077 SendToProgram(buf, cps);
6078 if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
6079 } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
6080 SendToProgram("go\n", cps);
6081 cps->bookSuspend = FALSE; // after a 'go' we are never suspended
6082 } else { // 'go' might be sent based on 'firstMove' after this routine returns
6083 if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
6084 SendToProgram("go\n", cps);
6085 cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
6087 return bookHit; // notify caller of hit, so it can take action to send move to opponent
6091 ChessProgramState *savedState;
6092 void DeferredBookMove(void)
6094 if(savedState->lastPing != savedState->lastPong)
6095 ScheduleDelayedEvent(DeferredBookMove, 10);
6097 HandleMachineMove(savedMessage, savedState);
6101 HandleMachineMove(message, cps)
6103 ChessProgramState *cps;
6105 char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
6106 char realname[MSG_SIZ];
6107 int fromX, fromY, toX, toY;
6116 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
6118 * Kludge to ignore BEL characters
6120 while (*message == '\007') message++;
6123 * [HGM] engine debug message: ignore lines starting with '#' character
6125 if(cps->debug && *message == '#') return;
6128 * Look for book output
6130 if (cps == &first && bookRequested) {
6131 if (message[0] == '\t' || message[0] == ' ') {
6132 /* Part of the book output is here; append it */
6133 strcat(bookOutput, message);
6134 strcat(bookOutput, " \n");
6136 } else if (bookOutput[0] != NULLCHAR) {
6137 /* All of book output has arrived; display it */
6138 char *p = bookOutput;
6139 while (*p != NULLCHAR) {
6140 if (*p == '\t') *p = ' ';
6143 DisplayInformation(bookOutput);
6144 bookRequested = FALSE;
6145 /* Fall through to parse the current output */
6150 * Look for machine move.
6152 if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
6153 (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
6155 /* This method is only useful on engines that support ping */
6156 if (cps->lastPing != cps->lastPong) {
6157 if (gameMode == BeginningOfGame) {
6158 /* Extra move from before last new; ignore */
6159 if (appData.debugMode) {
6160 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
6163 if (appData.debugMode) {
6164 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
6165 cps->which, gameMode);
6168 SendToProgram("undo\n", cps);
6174 case BeginningOfGame:
6175 /* Extra move from before last reset; ignore */
6176 if (appData.debugMode) {
6177 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
6184 /* Extra move after we tried to stop. The mode test is
6185 not a reliable way of detecting this problem, but it's
6186 the best we can do on engines that don't support ping.
6188 if (appData.debugMode) {
6189 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
6190 cps->which, gameMode);
6192 SendToProgram("undo\n", cps);
6195 case MachinePlaysWhite:
6196 case IcsPlayingWhite:
6197 machineWhite = TRUE;
6200 case MachinePlaysBlack:
6201 case IcsPlayingBlack:
6202 machineWhite = FALSE;
6205 case TwoMachinesPlay:
6206 machineWhite = (cps->twoMachinesColor[0] == 'w');
6209 if (WhiteOnMove(forwardMostMove) != machineWhite) {
6210 if (appData.debugMode) {
6212 "Ignoring move out of turn by %s, gameMode %d"
6213 ", forwardMost %d\n",
6214 cps->which, gameMode, forwardMostMove);
6219 if (appData.debugMode) { int f = forwardMostMove;
6220 fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
6221 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
6222 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
6224 if(cps->alphaRank) AlphaRank(machineMove, 4);
6225 if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
6226 &fromX, &fromY, &toX, &toY, &promoChar)) {
6227 /* Machine move could not be parsed; ignore it. */
6228 sprintf(buf1, _("Illegal move \"%s\" from %s machine"),
6229 machineMove, cps->which);
6230 DisplayError(buf1, 0);
6231 sprintf(buf1, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
6232 machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
6233 if (gameMode == TwoMachinesPlay) {
6234 GameEnds(machineWhite ? BlackWins : WhiteWins,
6240 /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
6241 /* So we have to redo legality test with true e.p. status here, */
6242 /* to make sure an illegal e.p. capture does not slip through, */
6243 /* to cause a forfeit on a justified illegal-move complaint */
6244 /* of the opponent. */
6245 if( gameMode==TwoMachinesPlay && appData.testLegality
6246 && fromY != DROP_RANK /* [HGM] temporary; should still add legality test for drops */
6249 moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
6250 fromY, fromX, toY, toX, promoChar);
6251 if (appData.debugMode) {
6253 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
6254 boards[forwardMostMove][CASTLING][i], castlingRank[i]);
6255 fprintf(debugFP, "castling rights\n");
6257 if(moveType == IllegalMove) {
6258 sprintf(buf1, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
6259 machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
6260 GameEnds(machineWhite ? BlackWins : WhiteWins,
6263 } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
6264 /* [HGM] Kludge to handle engines that send FRC-style castling
6265 when they shouldn't (like TSCP-Gothic) */
6267 case WhiteASideCastleFR:
6268 case BlackASideCastleFR:
6270 currentMoveString[2]++;
6272 case WhiteHSideCastleFR:
6273 case BlackHSideCastleFR:
6275 currentMoveString[2]--;
6277 default: ; // nothing to do, but suppresses warning of pedantic compilers
6280 hintRequested = FALSE;
6281 lastHint[0] = NULLCHAR;
6282 bookRequested = FALSE;
6283 /* Program may be pondering now */
6284 cps->maybeThinking = TRUE;
6285 if (cps->sendTime == 2) cps->sendTime = 1;
6286 if (cps->offeredDraw) cps->offeredDraw--;
6289 if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
6291 SendMoveToICS(moveType, fromX, fromY, toX, toY);
6293 if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
6294 char buf[3*MSG_SIZ];
6296 sprintf(buf, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
6297 programStats.score / 100.,
6299 programStats.time / 100.,
6300 (unsigned int)programStats.nodes,
6301 (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
6302 programStats.movelist);
6304 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
6308 /* currentMoveString is set as a side-effect of ParseOneMove */
6309 strcpy(machineMove, currentMoveString);
6310 strcat(machineMove, "\n");
6311 strcpy(moveList[forwardMostMove], machineMove);
6313 /* [AS] Save move info and clear stats for next move */
6314 pvInfoList[ forwardMostMove ].score = programStats.score;
6315 pvInfoList[ forwardMostMove ].depth = programStats.depth;
6316 pvInfoList[ forwardMostMove ].time = programStats.time; // [HGM] PGNtime: take time from engine stats
6317 ClearProgramStats();
6318 thinkOutput[0] = NULLCHAR;
6319 hiddenThinkOutputState = 0;
6321 MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
6323 /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
6324 if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
6327 while( count < adjudicateLossPlies ) {
6328 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
6331 score = -score; /* Flip score for winning side */
6334 if( score > adjudicateLossThreshold ) {
6341 if( count >= adjudicateLossPlies ) {
6342 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6344 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
6345 "Xboard adjudication",
6352 if( gameMode == TwoMachinesPlay ) {
6353 // [HGM] some adjudications useful with buggy engines
6354 int k, count = 0; static int bare = 1;
6355 if(gameInfo.holdingsSize == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
6358 if( appData.testLegality )
6359 { /* [HGM] Some more adjudications for obstinate engines */
6360 int NrWN=0, NrBN=0, NrWB=0, NrBB=0, NrWR=0, NrBR=0,
6361 NrWQ=0, NrBQ=0, NrW=0, NrK=0, bishopsColor = 0,
6362 NrPieces=0, NrPawns=0, PawnAdvance=0, i, j;
6363 static int moveCount = 6;
6365 char *reason = NULL;
6367 /* Count what is on board. */
6368 for(i=0; i<BOARD_HEIGHT; i++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
6369 { ChessSquare p = boards[forwardMostMove][i][j];
6373 { /* count B,N,R and other of each side */
6376 NrK++; break; // [HGM] atomic: count Kings
6380 case WhiteFerz: // [HGM] shatranj: kludge to mke it work in shatranj
6381 bishopsColor |= 1 << ((i^j)&1);
6386 case BlackFerz: // [HGM] shatranj: kludge to mke it work in shatranj
6387 bishopsColor |= 1 << ((i^j)&1);
6402 PawnAdvance += m; NrPawns++;
6404 NrPieces += (p != EmptySquare);
6405 NrW += ((int)p < (int)BlackPawn);
6406 if(gameInfo.variant == VariantXiangqi &&
6407 (p == WhiteFerz || p == WhiteAlfil || p == BlackFerz || p == BlackAlfil)) {
6408 NrPieces--; // [HGM] XQ: do not count purely defensive pieces
6409 NrW -= ((int)p < (int)BlackPawn);
6413 /* Some material-based adjudications that have to be made before stalemate test */
6414 if(gameInfo.variant == VariantAtomic && NrK < 2) {
6415 // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
6416 boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
6417 if(appData.checkMates) {
6418 SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets move
6419 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6420 GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
6421 "Xboard adjudication: King destroyed", GE_XBOARD );
6426 /* Bare King in Shatranj (loses) or Losers (wins) */
6427 if( NrW == 1 || NrPieces - NrW == 1) {
6428 if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
6429 boards[forwardMostMove][EP_STATUS] = EP_WINS; // mark as win, so it becomes claimable
6430 if(appData.checkMates) {
6431 SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets to see move
6432 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6433 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
6434 "Xboard adjudication: Bare king", GE_XBOARD );
6438 if( gameInfo.variant == VariantShatranj && --bare < 0)
6440 boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
6441 if(appData.checkMates) {
6442 /* but only adjudicate if adjudication enabled */
6443 SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets move
6444 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6445 GameEnds( NrW > 1 ? WhiteWins : NrPieces - NrW > 1 ? BlackWins : GameIsDrawn,
6446 "Xboard adjudication: Bare king", GE_XBOARD );
6453 // don't wait for engine to announce game end if we can judge ourselves
6454 switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
6456 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
6457 int i, checkCnt = 0; // (should really be done by making nr of checks part of game state)
6458 for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
6459 if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
6462 reason = "Xboard adjudication: 3rd check";
6463 boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
6473 reason = "Xboard adjudication: Stalemate";
6474 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
6475 boards[forwardMostMove][EP_STATUS] = EP_STALEMATE; // default result for stalemate is draw
6476 if(gameInfo.variant == VariantLosers || gameInfo.variant == VariantGiveaway) // [HGM] losers:
6477 boards[forwardMostMove][EP_STATUS] = EP_WINS; // in these variants stalemated is always a win
6478 else if(gameInfo.variant == VariantSuicide) // in suicide it depends
6479 boards[forwardMostMove][EP_STATUS] = NrW == NrPieces-NrW ? EP_STALEMATE :
6480 ((NrW < NrPieces-NrW) != WhiteOnMove(forwardMostMove) ?
6481 EP_CHECKMATE : EP_WINS);
6482 else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
6483 boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
6487 reason = "Xboard adjudication: Checkmate";
6488 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
6492 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
6494 result = GameIsDrawn; break;
6496 result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
6498 result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
6500 result = (ChessMove) 0;
6502 if(appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
6503 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6504 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6505 GameEnds( result, reason, GE_XBOARD );
6509 /* Next absolutely insufficient mating material. */
6510 if( NrPieces == 2 || gameInfo.variant != VariantXiangqi &&
6511 gameInfo.variant != VariantShatranj && // [HGM] baring will remain possible
6512 (NrPieces == 3 && NrWN+NrBN+NrWB+NrBB == 1 ||
6513 NrPieces == NrBB+NrWB+2 && bishopsColor != 3)) // [HGM] all Bishops (Ferz!) same color
6514 { /* KBK, KNK, KK of KBKB with like Bishops */
6516 /* always flag draws, for judging claims */
6517 boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
6519 if(appData.materialDraws) {
6520 /* but only adjudicate them if adjudication enabled */
6521 SendToProgram("force\n", cps->other); // suppress reply
6522 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see last move */
6523 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6524 GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
6529 /* Then some trivial draws (only adjudicate, cannot be claimed) */
6531 ( NrWR == 1 && NrBR == 1 /* KRKR */
6532 || NrWQ==1 && NrBQ==1 /* KQKQ */
6533 || NrWN==2 || NrBN==2 /* KNNK */
6534 || NrWN+NrWB == 1 && NrBN+NrBB == 1 /* KBKN, KBKB, KNKN */
6536 if(--moveCount < 0 && appData.trivialDraws)
6537 { /* if the first 3 moves do not show a tactical win, declare draw */
6538 SendToProgram("force\n", cps->other); // suppress reply
6539 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6540 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6541 GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
6544 } else moveCount = 6;
6548 if (appData.debugMode) { int i;
6549 fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
6550 forwardMostMove, backwardMostMove, boards[backwardMostMove][EP_STATUS],
6551 appData.drawRepeats);
6552 for( i=forwardMostMove; i>=backwardMostMove; i-- )
6553 fprintf(debugFP, "%d ep=%d\n", i, (signed char)boards[i][EP_STATUS]);
6557 /* Check for rep-draws */
6559 for(k = forwardMostMove-2;
6560 k>=backwardMostMove && k>=forwardMostMove-100 &&
6561 (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
6562 (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE;
6565 if(CompareBoards(boards[k], boards[forwardMostMove])) {
6566 /* compare castling rights */
6567 if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
6568 (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
6569 rights++; /* King lost rights, while rook still had them */
6570 if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
6571 if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
6572 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
6573 rights++; /* but at least one rook lost them */
6575 if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
6576 (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
6578 if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
6579 if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
6580 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
6583 if( rights == 0 && ++count > appData.drawRepeats-2
6584 && appData.drawRepeats > 1) {
6585 /* adjudicate after user-specified nr of repeats */
6586 SendToProgram("force\n", cps->other); // suppress reply
6587 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6588 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6589 if(gameInfo.variant == VariantXiangqi && appData.testLegality) {
6590 // [HGM] xiangqi: check for forbidden perpetuals
6591 int m, ourPerpetual = 1, hisPerpetual = 1;
6592 for(m=forwardMostMove; m>k; m-=2) {
6593 if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
6594 ourPerpetual = 0; // the current mover did not always check
6595 if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
6596 hisPerpetual = 0; // the opponent did not always check
6598 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
6599 ourPerpetual, hisPerpetual);
6600 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
6601 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
6602 "Xboard adjudication: perpetual checking", GE_XBOARD );
6605 if(hisPerpetual && !ourPerpetual) // he is checking us, but did not repeat yet
6606 break; // (or we would have caught him before). Abort repetition-checking loop.
6607 // Now check for perpetual chases
6608 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
6609 hisPerpetual = PerpetualChase(k, forwardMostMove);
6610 ourPerpetual = PerpetualChase(k+1, forwardMostMove);
6611 if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
6612 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
6613 "Xboard adjudication: perpetual chasing", GE_XBOARD );
6616 if(hisPerpetual && !ourPerpetual) // he is chasing us, but did not repeat yet
6617 break; // Abort repetition-checking loop.
6619 // if neither of us is checking or chasing all the time, or both are, it is draw
6621 GameEnds( GameIsDrawn, "Xboard adjudication: repetition draw", GE_XBOARD );
6624 if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
6625 boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
6629 /* Now we test for 50-move draws. Determine ply count */
6630 count = forwardMostMove;
6631 /* look for last irreversble move */
6632 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
6634 /* if we hit starting position, add initial plies */
6635 if( count == backwardMostMove )
6636 count -= initialRulePlies;
6637 count = forwardMostMove - count;
6639 boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
6640 /* this is used to judge if draw claims are legal */
6641 if(appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
6642 SendToProgram("force\n", cps->other); // suppress reply
6643 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6644 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6645 GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
6649 /* if draw offer is pending, treat it as a draw claim
6650 * when draw condition present, to allow engines a way to
6651 * claim draws before making their move to avoid a race
6652 * condition occurring after their move
6654 if( cps->other->offeredDraw || cps->offeredDraw ) {
6656 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
6657 p = "Draw claim: 50-move rule";
6658 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
6659 p = "Draw claim: 3-fold repetition";
6660 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
6661 p = "Draw claim: insufficient mating material";
6663 SendToProgram("force\n", cps->other); // suppress reply
6664 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6665 GameEnds( GameIsDrawn, p, GE_XBOARD );
6666 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6672 if( appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
6673 SendToProgram("force\n", cps->other); // suppress reply
6674 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6675 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6677 GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
6684 if (gameMode == TwoMachinesPlay) {
6685 /* [HGM] relaying draw offers moved to after reception of move */
6686 /* and interpreting offer as claim if it brings draw condition */
6687 if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
6688 SendToProgram("draw\n", cps->other);
6690 if (cps->other->sendTime) {
6691 SendTimeRemaining(cps->other,
6692 cps->other->twoMachinesColor[0] == 'w');
6694 bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
6695 if (firstMove && !bookHit) {
6697 if (cps->other->useColors) {
6698 SendToProgram(cps->other->twoMachinesColor, cps->other);
6700 SendToProgram("go\n", cps->other);
6702 cps->other->maybeThinking = TRUE;
6705 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6707 if (!pausing && appData.ringBellAfterMoves) {
6712 * Reenable menu items that were disabled while
6713 * machine was thinking
6715 if (gameMode != TwoMachinesPlay)
6716 SetUserThinkingEnables();
6718 // [HGM] book: after book hit opponent has received move and is now in force mode
6719 // force the book reply into it, and then fake that it outputted this move by jumping
6720 // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
6722 static char bookMove[MSG_SIZ]; // a bit generous?
6724 strcpy(bookMove, "move ");
6725 strcat(bookMove, bookHit);
6728 programStats.nodes = programStats.depth = programStats.time =
6729 programStats.score = programStats.got_only_move = 0;
6730 sprintf(programStats.movelist, "%s (xbook)", bookHit);
6732 if(cps->lastPing != cps->lastPong) {
6733 savedMessage = message; // args for deferred call
6735 ScheduleDelayedEvent(DeferredBookMove, 10);
6744 /* Set special modes for chess engines. Later something general
6745 * could be added here; for now there is just one kludge feature,
6746 * needed because Crafty 15.10 and earlier don't ignore SIGINT
6747 * when "xboard" is given as an interactive command.
6749 if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
6750 cps->useSigint = FALSE;
6751 cps->useSigterm = FALSE;
6753 if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
6754 ParseFeatures(message+8, cps);
6755 return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
6758 /* [HGM] Allow engine to set up a position. Don't ask me why one would
6759 * want this, I was asked to put it in, and obliged.
6761 if (!strncmp(message, "setboard ", 9)) {
6762 Board initial_position;
6764 GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
6766 if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
6767 DisplayError(_("Bad FEN received from engine"), 0);
6771 CopyBoard(boards[0], initial_position);
6772 initialRulePlies = FENrulePlies;
6773 if(blackPlaysFirst) gameMode = MachinePlaysWhite;
6774 else gameMode = MachinePlaysBlack;
6775 DrawPosition(FALSE, boards[currentMove]);
6781 * Look for communication commands
6783 if (!strncmp(message, "telluser ", 9)) {
6784 DisplayNote(message + 9);
6787 if (!strncmp(message, "tellusererror ", 14)) {
6789 DisplayError(message + 14, 0);
6792 if (!strncmp(message, "tellopponent ", 13)) {
6793 if (appData.icsActive) {
6795 snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
6799 DisplayNote(message + 13);
6803 if (!strncmp(message, "tellothers ", 11)) {
6804 if (appData.icsActive) {
6806 snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
6812 if (!strncmp(message, "tellall ", 8)) {
6813 if (appData.icsActive) {
6815 snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
6819 DisplayNote(message + 8);
6823 if (strncmp(message, "warning", 7) == 0) {
6824 /* Undocumented feature, use tellusererror in new code */
6825 DisplayError(message, 0);
6828 if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
6829 strcpy(realname, cps->tidy);
6830 strcat(realname, " query");
6831 AskQuestion(realname, buf2, buf1, cps->pr);
6834 /* Commands from the engine directly to ICS. We don't allow these to be
6835 * sent until we are logged on. Crafty kibitzes have been known to
6836 * interfere with the login process.
6839 if (!strncmp(message, "tellics ", 8)) {
6840 SendToICS(message + 8);
6844 if (!strncmp(message, "tellicsnoalias ", 15)) {
6845 SendToICS(ics_prefix);
6846 SendToICS(message + 15);
6850 /* The following are for backward compatibility only */
6851 if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
6852 !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
6853 SendToICS(ics_prefix);
6859 if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
6863 * If the move is illegal, cancel it and redraw the board.
6864 * Also deal with other error cases. Matching is rather loose
6865 * here to accommodate engines written before the spec.
6867 if (strncmp(message + 1, "llegal move", 11) == 0 ||
6868 strncmp(message, "Error", 5) == 0) {
6869 if (StrStr(message, "name") ||
6870 StrStr(message, "rating") || StrStr(message, "?") ||
6871 StrStr(message, "result") || StrStr(message, "board") ||
6872 StrStr(message, "bk") || StrStr(message, "computer") ||
6873 StrStr(message, "variant") || StrStr(message, "hint") ||
6874 StrStr(message, "random") || StrStr(message, "depth") ||
6875 StrStr(message, "accepted")) {
6878 if (StrStr(message, "protover")) {
6879 /* Program is responding to input, so it's apparently done
6880 initializing, and this error message indicates it is
6881 protocol version 1. So we don't need to wait any longer
6882 for it to initialize and send feature commands. */
6883 FeatureDone(cps, 1);
6884 cps->protocolVersion = 1;
6887 cps->maybeThinking = FALSE;
6889 if (StrStr(message, "draw")) {
6890 /* Program doesn't have "draw" command */
6891 cps->sendDrawOffers = 0;
6894 if (cps->sendTime != 1 &&
6895 (StrStr(message, "time") || StrStr(message, "otim"))) {
6896 /* Program apparently doesn't have "time" or "otim" command */
6900 if (StrStr(message, "analyze")) {
6901 cps->analysisSupport = FALSE;
6902 cps->analyzing = FALSE;
6904 sprintf(buf2, _("%s does not support analysis"), cps->tidy);
6905 DisplayError(buf2, 0);
6908 if (StrStr(message, "(no matching move)st")) {
6909 /* Special kludge for GNU Chess 4 only */
6910 cps->stKludge = TRUE;
6911 SendTimeControl(cps, movesPerSession, timeControl,
6912 timeIncrement, appData.searchDepth,
6916 if (StrStr(message, "(no matching move)sd")) {
6917 /* Special kludge for GNU Chess 4 only */
6918 cps->sdKludge = TRUE;
6919 SendTimeControl(cps, movesPerSession, timeControl,
6920 timeIncrement, appData.searchDepth,
6924 if (!StrStr(message, "llegal")) {
6927 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
6928 gameMode == IcsIdle) return;
6929 if (forwardMostMove <= backwardMostMove) return;
6930 if (pausing) PauseEvent();
6931 if(appData.forceIllegal) {
6932 // [HGM] illegal: machine refused move; force position after move into it
6933 SendToProgram("force\n", cps);
6934 if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
6935 // we have a real problem now, as SendBoard will use the a2a3 kludge
6936 // when black is to move, while there might be nothing on a2 or black
6937 // might already have the move. So send the board as if white has the move.
6938 // But first we must change the stm of the engine, as it refused the last move
6939 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
6940 if(WhiteOnMove(forwardMostMove)) {
6941 SendToProgram("a7a6\n", cps); // for the engine black still had the move
6942 SendBoard(cps, forwardMostMove); // kludgeless board
6944 SendToProgram("a2a3\n", cps); // for the engine white still had the move
6945 CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
6946 SendBoard(cps, forwardMostMove+1); // kludgeless board
6948 } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
6949 if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
6950 gameMode == TwoMachinesPlay)
6951 SendToProgram("go\n", cps);
6954 if (gameMode == PlayFromGameFile) {
6955 /* Stop reading this game file */
6956 gameMode = EditGame;
6959 currentMove = --forwardMostMove;
6960 DisplayMove(currentMove-1); /* before DisplayMoveError */
6962 DisplayBothClocks();
6963 sprintf(buf1, _("Illegal move \"%s\" (rejected by %s chess program)"),
6964 parseList[currentMove], cps->which);
6965 DisplayMoveError(buf1);
6966 DrawPosition(FALSE, boards[currentMove]);
6968 /* [HGM] illegal-move claim should forfeit game when Xboard */
6969 /* only passes fully legal moves */
6970 if( appData.testLegality && gameMode == TwoMachinesPlay ) {
6971 GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
6972 "False illegal-move claim", GE_XBOARD );
6976 if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
6977 /* Program has a broken "time" command that
6978 outputs a string not ending in newline.
6984 * If chess program startup fails, exit with an error message.
6985 * Attempts to recover here are futile.
6987 if ((StrStr(message, "unknown host") != NULL)
6988 || (StrStr(message, "No remote directory") != NULL)
6989 || (StrStr(message, "not found") != NULL)
6990 || (StrStr(message, "No such file") != NULL)
6991 || (StrStr(message, "can't alloc") != NULL)
6992 || (StrStr(message, "Permission denied") != NULL)) {
6994 cps->maybeThinking = FALSE;
6995 snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
6996 cps->which, cps->program, cps->host, message);
6997 RemoveInputSource(cps->isr);
6998 DisplayFatalError(buf1, 0, 1);
7003 * Look for hint output
7005 if (sscanf(message, "Hint: %s", buf1) == 1) {
7006 if (cps == &first && hintRequested) {
7007 hintRequested = FALSE;
7008 if (ParseOneMove(buf1, forwardMostMove, &moveType,
7009 &fromX, &fromY, &toX, &toY, &promoChar)) {
7010 (void) CoordsToAlgebraic(boards[forwardMostMove],
7011 PosFlags(forwardMostMove),
7012 fromY, fromX, toY, toX, promoChar, buf1);
7013 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
7014 DisplayInformation(buf2);
7016 /* Hint move could not be parsed!? */
7017 snprintf(buf2, sizeof(buf2),
7018 _("Illegal hint move \"%s\"\nfrom %s chess program"),
7020 DisplayError(buf2, 0);
7023 strcpy(lastHint, buf1);
7029 * Ignore other messages if game is not in progress
7031 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
7032 gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
7035 * look for win, lose, draw, or draw offer
7037 if (strncmp(message, "1-0", 3) == 0) {
7038 char *p, *q, *r = "";
7039 p = strchr(message, '{');
7047 GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
7049 } else if (strncmp(message, "0-1", 3) == 0) {
7050 char *p, *q, *r = "";
7051 p = strchr(message, '{');
7059 /* Kludge for Arasan 4.1 bug */
7060 if (strcmp(r, "Black resigns") == 0) {
7061 GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
7064 GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
7066 } else if (strncmp(message, "1/2", 3) == 0) {
7067 char *p, *q, *r = "";
7068 p = strchr(message, '{');
7077 GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
7080 } else if (strncmp(message, "White resign", 12) == 0) {
7081 GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
7083 } else if (strncmp(message, "Black resign", 12) == 0) {
7084 GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
7086 } else if (strncmp(message, "White matches", 13) == 0 ||
7087 strncmp(message, "Black matches", 13) == 0 ) {
7088 /* [HGM] ignore GNUShogi noises */
7090 } else if (strncmp(message, "White", 5) == 0 &&
7091 message[5] != '(' &&
7092 StrStr(message, "Black") == NULL) {
7093 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7095 } else if (strncmp(message, "Black", 5) == 0 &&
7096 message[5] != '(') {
7097 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7099 } else if (strcmp(message, "resign") == 0 ||
7100 strcmp(message, "computer resigns") == 0) {
7102 case MachinePlaysBlack:
7103 case IcsPlayingBlack:
7104 GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
7106 case MachinePlaysWhite:
7107 case IcsPlayingWhite:
7108 GameEnds(BlackWins, "White resigns", GE_ENGINE);
7110 case TwoMachinesPlay:
7111 if (cps->twoMachinesColor[0] == 'w')
7112 GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
7114 GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
7121 } else if (strncmp(message, "opponent mates", 14) == 0) {
7123 case MachinePlaysBlack:
7124 case IcsPlayingBlack:
7125 GameEnds(WhiteWins, "White mates", GE_ENGINE);
7127 case MachinePlaysWhite:
7128 case IcsPlayingWhite:
7129 GameEnds(BlackWins, "Black mates", GE_ENGINE);
7131 case TwoMachinesPlay:
7132 if (cps->twoMachinesColor[0] == 'w')
7133 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7135 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7142 } else if (strncmp(message, "computer mates", 14) == 0) {
7144 case MachinePlaysBlack:
7145 case IcsPlayingBlack:
7146 GameEnds(BlackWins, "Black mates", GE_ENGINE1);
7148 case MachinePlaysWhite:
7149 case IcsPlayingWhite:
7150 GameEnds(WhiteWins, "White mates", GE_ENGINE);
7152 case TwoMachinesPlay:
7153 if (cps->twoMachinesColor[0] == 'w')
7154 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7156 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7163 } else if (strncmp(message, "checkmate", 9) == 0) {
7164 if (WhiteOnMove(forwardMostMove)) {
7165 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7167 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7170 } else if (strstr(message, "Draw") != NULL ||
7171 strstr(message, "game is a draw") != NULL) {
7172 GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
7174 } else if (strstr(message, "offer") != NULL &&
7175 strstr(message, "draw") != NULL) {
7177 if (appData.zippyPlay && first.initDone) {
7178 /* Relay offer to ICS */
7179 SendToICS(ics_prefix);
7180 SendToICS("draw\n");
7183 cps->offeredDraw = 2; /* valid until this engine moves twice */
7184 if (gameMode == TwoMachinesPlay) {
7185 if (cps->other->offeredDraw) {
7186 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
7187 /* [HGM] in two-machine mode we delay relaying draw offer */
7188 /* until after we also have move, to see if it is really claim */
7190 } else if (gameMode == MachinePlaysWhite ||
7191 gameMode == MachinePlaysBlack) {
7192 if (userOfferedDraw) {
7193 DisplayInformation(_("Machine accepts your draw offer"));
7194 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
7196 DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
7203 * Look for thinking output
7205 if ( appData.showThinking // [HGM] thinking: test all options that cause this output
7206 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
7208 int plylev, mvleft, mvtot, curscore, time;
7209 char mvname[MOVE_LEN];
7213 int prefixHint = FALSE;
7214 mvname[0] = NULLCHAR;
7217 case MachinePlaysBlack:
7218 case IcsPlayingBlack:
7219 if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
7221 case MachinePlaysWhite:
7222 case IcsPlayingWhite:
7223 if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
7228 case IcsObserving: /* [DM] icsEngineAnalyze */
7229 if (!appData.icsEngineAnalyze) ignore = TRUE;
7231 case TwoMachinesPlay:
7232 if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
7243 if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
7244 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
7246 if (plyext != ' ' && plyext != '\t') {
7250 /* [AS] Negate score if machine is playing black and reporting absolute scores */
7251 if( cps->scoreIsAbsolute &&
7252 ( gameMode == MachinePlaysBlack ||
7253 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
7254 gameMode == IcsPlayingBlack || // [HGM] also add other situations where engine should report black POV
7255 (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
7256 !WhiteOnMove(currentMove)
7259 curscore = -curscore;
7263 programStats.depth = plylev;
7264 programStats.nodes = nodes;
7265 programStats.time = time;
7266 programStats.score = curscore;
7267 programStats.got_only_move = 0;
7269 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
7272 if(cps->nps == 0) ticklen = 10*time; // use engine reported time
7273 else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
7274 if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
7275 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
7276 whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
7277 if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
7278 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
7279 blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
7282 /* Buffer overflow protection */
7283 if (buf1[0] != NULLCHAR) {
7284 if (strlen(buf1) >= sizeof(programStats.movelist)
7285 && appData.debugMode) {
7287 "PV is too long; using the first %u bytes.\n",
7288 (unsigned) sizeof(programStats.movelist) - 1);
7291 safeStrCpy( programStats.movelist, buf1, sizeof(programStats.movelist) );
7293 sprintf(programStats.movelist, " no PV\n");
7296 if (programStats.seen_stat) {
7297 programStats.ok_to_send = 1;
7300 if (strchr(programStats.movelist, '(') != NULL) {
7301 programStats.line_is_book = 1;
7302 programStats.nr_moves = 0;
7303 programStats.moves_left = 0;
7305 programStats.line_is_book = 0;
7308 SendProgramStatsToFrontend( cps, &programStats );
7311 [AS] Protect the thinkOutput buffer from overflow... this
7312 is only useful if buf1 hasn't overflowed first!
7314 sprintf(thinkOutput, "[%d]%c%+.2f %s%s",
7316 (gameMode == TwoMachinesPlay ?
7317 ToUpper(cps->twoMachinesColor[0]) : ' '),
7318 ((double) curscore) / 100.0,
7319 prefixHint ? lastHint : "",
7320 prefixHint ? " " : "" );
7322 if( buf1[0] != NULLCHAR ) {
7323 unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
7325 if( strlen(buf1) > max_len ) {
7326 if( appData.debugMode) {
7327 fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
7329 buf1[max_len+1] = '\0';
7332 strcat( thinkOutput, buf1 );
7335 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
7336 || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7337 DisplayMove(currentMove - 1);
7341 } else if ((p=StrStr(message, "(only move)")) != NULL) {
7342 /* crafty (9.25+) says "(only move) <move>"
7343 * if there is only 1 legal move
7345 sscanf(p, "(only move) %s", buf1);
7346 sprintf(thinkOutput, "%s (only move)", buf1);
7347 sprintf(programStats.movelist, "%s (only move)", buf1);
7348 programStats.depth = 1;
7349 programStats.nr_moves = 1;
7350 programStats.moves_left = 1;
7351 programStats.nodes = 1;
7352 programStats.time = 1;
7353 programStats.got_only_move = 1;
7355 /* Not really, but we also use this member to
7356 mean "line isn't going to change" (Crafty
7357 isn't searching, so stats won't change) */
7358 programStats.line_is_book = 1;
7360 SendProgramStatsToFrontend( cps, &programStats );
7362 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
7363 gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7364 DisplayMove(currentMove - 1);
7367 } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
7368 &time, &nodes, &plylev, &mvleft,
7369 &mvtot, mvname) >= 5) {
7370 /* The stat01: line is from Crafty (9.29+) in response
7371 to the "." command */
7372 programStats.seen_stat = 1;
7373 cps->maybeThinking = TRUE;
7375 if (programStats.got_only_move || !appData.periodicUpdates)
7378 programStats.depth = plylev;
7379 programStats.time = time;
7380 programStats.nodes = nodes;
7381 programStats.moves_left = mvleft;
7382 programStats.nr_moves = mvtot;
7383 strcpy(programStats.move_name, mvname);
7384 programStats.ok_to_send = 1;
7385 programStats.movelist[0] = '\0';
7387 SendProgramStatsToFrontend( cps, &programStats );
7391 } else if (strncmp(message,"++",2) == 0) {
7392 /* Crafty 9.29+ outputs this */
7393 programStats.got_fail = 2;
7396 } else if (strncmp(message,"--",2) == 0) {
7397 /* Crafty 9.29+ outputs this */
7398 programStats.got_fail = 1;
7401 } else if (thinkOutput[0] != NULLCHAR &&
7402 strncmp(message, " ", 4) == 0) {
7403 unsigned message_len;
7406 while (*p && *p == ' ') p++;
7408 message_len = strlen( p );
7410 /* [AS] Avoid buffer overflow */
7411 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
7412 strcat(thinkOutput, " ");
7413 strcat(thinkOutput, p);
7416 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
7417 strcat(programStats.movelist, " ");
7418 strcat(programStats.movelist, p);
7421 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
7422 gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7423 DisplayMove(currentMove - 1);
7431 if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
7432 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
7434 ChessProgramStats cpstats;
7436 if (plyext != ' ' && plyext != '\t') {
7440 /* [AS] Negate score if machine is playing black and reporting absolute scores */
7441 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
7442 curscore = -curscore;
7445 cpstats.depth = plylev;
7446 cpstats.nodes = nodes;
7447 cpstats.time = time;
7448 cpstats.score = curscore;
7449 cpstats.got_only_move = 0;
7450 cpstats.movelist[0] = '\0';
7452 if (buf1[0] != NULLCHAR) {
7453 safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist) );
7456 cpstats.ok_to_send = 0;
7457 cpstats.line_is_book = 0;
7458 cpstats.nr_moves = 0;
7459 cpstats.moves_left = 0;
7461 SendProgramStatsToFrontend( cps, &cpstats );
7468 /* Parse a game score from the character string "game", and
7469 record it as the history of the current game. The game
7470 score is NOT assumed to start from the standard position.
7471 The display is not updated in any way.
7474 ParseGameHistory(game)
7478 int fromX, fromY, toX, toY, boardIndex;
7483 if (appData.debugMode)
7484 fprintf(debugFP, "Parsing game history: %s\n", game);
7486 if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
7487 gameInfo.site = StrSave(appData.icsHost);
7488 gameInfo.date = PGNDate();
7489 gameInfo.round = StrSave("-");
7491 /* Parse out names of players */
7492 while (*game == ' ') game++;
7494 while (*game != ' ') *p++ = *game++;
7496 gameInfo.white = StrSave(buf);
7497 while (*game == ' ') game++;
7499 while (*game != ' ' && *game != '\n') *p++ = *game++;
7501 gameInfo.black = StrSave(buf);
7504 boardIndex = blackPlaysFirst ? 1 : 0;
7507 yyboardindex = boardIndex;
7508 moveType = (ChessMove) yylex();
7510 case IllegalMove: /* maybe suicide chess, etc. */
7511 if (appData.debugMode) {
7512 fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
7513 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7514 setbuf(debugFP, NULL);
7516 case WhitePromotionChancellor:
7517 case BlackPromotionChancellor:
7518 case WhitePromotionArchbishop:
7519 case BlackPromotionArchbishop:
7520 case WhitePromotionQueen:
7521 case BlackPromotionQueen:
7522 case WhitePromotionRook:
7523 case BlackPromotionRook:
7524 case WhitePromotionBishop:
7525 case BlackPromotionBishop:
7526 case WhitePromotionKnight:
7527 case BlackPromotionKnight:
7528 case WhitePromotionKing:
7529 case BlackPromotionKing:
7531 case WhiteCapturesEnPassant:
7532 case BlackCapturesEnPassant:
7533 case WhiteKingSideCastle:
7534 case WhiteQueenSideCastle:
7535 case BlackKingSideCastle:
7536 case BlackQueenSideCastle:
7537 case WhiteKingSideCastleWild:
7538 case WhiteQueenSideCastleWild:
7539 case BlackKingSideCastleWild:
7540 case BlackQueenSideCastleWild:
7542 case WhiteHSideCastleFR:
7543 case WhiteASideCastleFR:
7544 case BlackHSideCastleFR:
7545 case BlackASideCastleFR:
7547 fromX = currentMoveString[0] - AAA;
7548 fromY = currentMoveString[1] - ONE;
7549 toX = currentMoveString[2] - AAA;
7550 toY = currentMoveString[3] - ONE;
7551 promoChar = currentMoveString[4];
7555 fromX = moveType == WhiteDrop ?
7556 (int) CharToPiece(ToUpper(currentMoveString[0])) :
7557 (int) CharToPiece(ToLower(currentMoveString[0]));
7559 toX = currentMoveString[2] - AAA;
7560 toY = currentMoveString[3] - ONE;
7561 promoChar = NULLCHAR;
7565 sprintf(buf, _("Ambiguous move in ICS output: \"%s\""), yy_text);
7566 if (appData.debugMode) {
7567 fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
7568 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7569 setbuf(debugFP, NULL);
7571 DisplayError(buf, 0);
7573 case ImpossibleMove:
7575 sprintf(buf, _("Illegal move in ICS output: \"%s\""), yy_text);
7576 if (appData.debugMode) {
7577 fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
7578 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7579 setbuf(debugFP, NULL);
7581 DisplayError(buf, 0);
7583 case (ChessMove) 0: /* end of file */
7584 if (boardIndex < backwardMostMove) {
7585 /* Oops, gap. How did that happen? */
7586 DisplayError(_("Gap in move list"), 0);
7589 backwardMostMove = blackPlaysFirst ? 1 : 0;
7590 if (boardIndex > forwardMostMove) {
7591 forwardMostMove = boardIndex;
7595 if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
7596 strcat(parseList[boardIndex-1], " ");
7597 strcat(parseList[boardIndex-1], yy_text);
7609 case GameUnfinished:
7610 if (gameMode == IcsExamining) {
7611 if (boardIndex < backwardMostMove) {
7612 /* Oops, gap. How did that happen? */
7615 backwardMostMove = blackPlaysFirst ? 1 : 0;
7618 gameInfo.result = moveType;
7619 p = strchr(yy_text, '{');
7620 if (p == NULL) p = strchr(yy_text, '(');
7623 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
7625 q = strchr(p, *p == '{' ? '}' : ')');
7626 if (q != NULL) *q = NULLCHAR;
7629 gameInfo.resultDetails = StrSave(p);
7632 if (boardIndex >= forwardMostMove &&
7633 !(gameMode == IcsObserving && ics_gamenum == -1)) {
7634 backwardMostMove = blackPlaysFirst ? 1 : 0;
7637 (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
7638 fromY, fromX, toY, toX, promoChar,
7639 parseList[boardIndex]);
7640 CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
7641 /* currentMoveString is set as a side-effect of yylex */
7642 strcpy(moveList[boardIndex], currentMoveString);
7643 strcat(moveList[boardIndex], "\n");
7645 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
7646 switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
7652 if(gameInfo.variant != VariantShogi)
7653 strcat(parseList[boardIndex - 1], "+");
7657 strcat(parseList[boardIndex - 1], "#");
7664 /* Apply a move to the given board */
7666 ApplyMove(fromX, fromY, toX, toY, promoChar, board)
7667 int fromX, fromY, toX, toY;
7671 ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
7672 int promoRank = gameInfo.variant == VariantMakruk ? 3 : 1;
7674 /* [HGM] compute & store e.p. status and castling rights for new position */
7675 /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
7678 if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
7679 oldEP = (signed char)board[EP_STATUS];
7680 board[EP_STATUS] = EP_NONE;
7682 if( board[toY][toX] != EmptySquare )
7683 board[EP_STATUS] = EP_CAPTURE;
7685 if( board[fromY][fromX] == WhitePawn ) {
7686 if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
7687 board[EP_STATUS] = EP_PAWN_MOVE;
7689 if(toX>BOARD_LEFT && board[toY][toX-1] == BlackPawn &&
7690 gameInfo.variant != VariantBerolina || toX < fromX)
7691 board[EP_STATUS] = toX | berolina;
7692 if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
7693 gameInfo.variant != VariantBerolina || toX > fromX)
7694 board[EP_STATUS] = toX;
7697 if( board[fromY][fromX] == BlackPawn ) {
7698 if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
7699 board[EP_STATUS] = EP_PAWN_MOVE;
7700 if( toY-fromY== -2) {
7701 if(toX>BOARD_LEFT && board[toY][toX-1] == WhitePawn &&
7702 gameInfo.variant != VariantBerolina || toX < fromX)
7703 board[EP_STATUS] = toX | berolina;
7704 if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
7705 gameInfo.variant != VariantBerolina || toX > fromX)
7706 board[EP_STATUS] = toX;
7710 for(i=0; i<nrCastlingRights; i++) {
7711 if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
7712 board[CASTLING][i] == toX && castlingRank[i] == toY
7713 ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
7718 /* [HGM] In Shatranj and Courier all promotions are to Ferz */
7719 if((gameInfo.variant==VariantShatranj || gameInfo.variant==VariantCourier || gameInfo.variant == VariantMakruk)
7720 && promoChar != 0) promoChar = PieceToChar(WhiteFerz);
7722 if (fromX == toX && fromY == toY) return;
7724 if (fromY == DROP_RANK) {
7726 piece = board[toY][toX] = (ChessSquare) fromX;
7728 piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
7729 king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
7730 if(gameInfo.variant == VariantKnightmate)
7731 king += (int) WhiteUnicorn - (int) WhiteKing;
7733 /* Code added by Tord: */
7734 /* FRC castling assumed when king captures friendly rook. */
7735 if (board[fromY][fromX] == WhiteKing &&
7736 board[toY][toX] == WhiteRook) {
7737 board[fromY][fromX] = EmptySquare;
7738 board[toY][toX] = EmptySquare;
7740 board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
7742 board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
7744 } else if (board[fromY][fromX] == BlackKing &&
7745 board[toY][toX] == BlackRook) {
7746 board[fromY][fromX] = EmptySquare;
7747 board[toY][toX] = EmptySquare;
7749 board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
7751 board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
7753 /* End of code added by Tord */
7755 } else if (board[fromY][fromX] == king
7756 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7757 && toY == fromY && toX > fromX+1) {
7758 board[fromY][fromX] = EmptySquare;
7759 board[toY][toX] = king;
7760 board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
7761 board[fromY][BOARD_RGHT-1] = EmptySquare;
7762 } else if (board[fromY][fromX] == king
7763 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7764 && toY == fromY && toX < fromX-1) {
7765 board[fromY][fromX] = EmptySquare;
7766 board[toY][toX] = king;
7767 board[toY][toX+1] = board[fromY][BOARD_LEFT];
7768 board[fromY][BOARD_LEFT] = EmptySquare;
7769 } else if (board[fromY][fromX] == WhitePawn
7770 && toY >= BOARD_HEIGHT-promoRank
7771 && gameInfo.variant != VariantXiangqi
7773 /* white pawn promotion */
7774 board[toY][toX] = CharToPiece(ToUpper(promoChar));
7775 if (board[toY][toX] == EmptySquare) {
7776 board[toY][toX] = WhiteQueen;
7778 if(gameInfo.variant==VariantBughouse ||
7779 gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
7780 board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
7781 board[fromY][fromX] = EmptySquare;
7782 } else if ((fromY == BOARD_HEIGHT-4)
7784 && gameInfo.variant != VariantXiangqi
7785 && gameInfo.variant != VariantBerolina
7786 && (board[fromY][fromX] == WhitePawn)
7787 && (board[toY][toX] == EmptySquare)) {
7788 board[fromY][fromX] = EmptySquare;
7789 board[toY][toX] = WhitePawn;
7790 captured = board[toY - 1][toX];
7791 board[toY - 1][toX] = EmptySquare;
7792 } else if ((fromY == BOARD_HEIGHT-4)
7794 && gameInfo.variant == VariantBerolina
7795 && (board[fromY][fromX] == WhitePawn)
7796 && (board[toY][toX] == EmptySquare)) {
7797 board[fromY][fromX] = EmptySquare;
7798 board[toY][toX] = WhitePawn;
7799 if(oldEP & EP_BEROLIN_A) {
7800 captured = board[fromY][fromX-1];
7801 board[fromY][fromX-1] = EmptySquare;
7802 }else{ captured = board[fromY][fromX+1];
7803 board[fromY][fromX+1] = EmptySquare;
7805 } else if (board[fromY][fromX] == king
7806 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7807 && toY == fromY && toX > fromX+1) {
7808 board[fromY][fromX] = EmptySquare;
7809 board[toY][toX] = king;
7810 board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
7811 board[fromY][BOARD_RGHT-1] = EmptySquare;
7812 } else if (board[fromY][fromX] == king
7813 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7814 && toY == fromY && toX < fromX-1) {
7815 board[fromY][fromX] = EmptySquare;
7816 board[toY][toX] = king;
7817 board[toY][toX+1] = board[fromY][BOARD_LEFT];
7818 board[fromY][BOARD_LEFT] = EmptySquare;
7819 } else if (fromY == 7 && fromX == 3
7820 && board[fromY][fromX] == BlackKing
7821 && toY == 7 && toX == 5) {
7822 board[fromY][fromX] = EmptySquare;
7823 board[toY][toX] = BlackKing;
7824 board[fromY][7] = EmptySquare;
7825 board[toY][4] = BlackRook;
7826 } else if (fromY == 7 && fromX == 3
7827 && board[fromY][fromX] == BlackKing
7828 && toY == 7 && toX == 1) {
7829 board[fromY][fromX] = EmptySquare;
7830 board[toY][toX] = BlackKing;
7831 board[fromY][0] = EmptySquare;
7832 board[toY][2] = BlackRook;
7833 } else if (board[fromY][fromX] == BlackPawn
7835 && gameInfo.variant != VariantXiangqi
7837 /* black pawn promotion */
7838 board[toY][toX] = CharToPiece(ToLower(promoChar));
7839 if (board[toY][toX] == EmptySquare) {
7840 board[toY][toX] = BlackQueen;
7842 if(gameInfo.variant==VariantBughouse ||
7843 gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
7844 board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
7845 board[fromY][fromX] = EmptySquare;
7846 } else if ((fromY == 3)
7848 && gameInfo.variant != VariantXiangqi
7849 && gameInfo.variant != VariantBerolina
7850 && (board[fromY][fromX] == BlackPawn)
7851 && (board[toY][toX] == EmptySquare)) {
7852 board[fromY][fromX] = EmptySquare;
7853 board[toY][toX] = BlackPawn;
7854 captured = board[toY + 1][toX];
7855 board[toY + 1][toX] = EmptySquare;
7856 } else if ((fromY == 3)
7858 && gameInfo.variant == VariantBerolina
7859 && (board[fromY][fromX] == BlackPawn)
7860 && (board[toY][toX] == EmptySquare)) {
7861 board[fromY][fromX] = EmptySquare;
7862 board[toY][toX] = BlackPawn;
7863 if(oldEP & EP_BEROLIN_A) {
7864 captured = board[fromY][fromX-1];
7865 board[fromY][fromX-1] = EmptySquare;
7866 }else{ captured = board[fromY][fromX+1];
7867 board[fromY][fromX+1] = EmptySquare;
7870 board[toY][toX] = board[fromY][fromX];
7871 board[fromY][fromX] = EmptySquare;
7874 /* [HGM] now we promote for Shogi, if needed */
7875 if(gameInfo.variant == VariantShogi && promoChar == 'q')
7876 board[toY][toX] = (ChessSquare) (PROMOTED piece);
7879 if (gameInfo.holdingsWidth != 0) {
7881 /* !!A lot more code needs to be written to support holdings */
7882 /* [HGM] OK, so I have written it. Holdings are stored in the */
7883 /* penultimate board files, so they are automaticlly stored */
7884 /* in the game history. */
7885 if (fromY == DROP_RANK) {
7886 /* Delete from holdings, by decreasing count */
7887 /* and erasing image if necessary */
7889 if(p < (int) BlackPawn) { /* white drop */
7890 p -= (int)WhitePawn;
7891 p = PieceToNumber((ChessSquare)p);
7892 if(p >= gameInfo.holdingsSize) p = 0;
7893 if(--board[p][BOARD_WIDTH-2] <= 0)
7894 board[p][BOARD_WIDTH-1] = EmptySquare;
7895 if((int)board[p][BOARD_WIDTH-2] < 0)
7896 board[p][BOARD_WIDTH-2] = 0;
7897 } else { /* black drop */
7898 p -= (int)BlackPawn;
7899 p = PieceToNumber((ChessSquare)p);
7900 if(p >= gameInfo.holdingsSize) p = 0;
7901 if(--board[BOARD_HEIGHT-1-p][1] <= 0)
7902 board[BOARD_HEIGHT-1-p][0] = EmptySquare;
7903 if((int)board[BOARD_HEIGHT-1-p][1] < 0)
7904 board[BOARD_HEIGHT-1-p][1] = 0;
7907 if (captured != EmptySquare && gameInfo.holdingsSize > 0
7908 && gameInfo.variant != VariantBughouse ) {
7909 /* [HGM] holdings: Add to holdings, if holdings exist */
7910 if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
7911 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
7912 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
7915 if (p >= (int) BlackPawn) {
7916 p -= (int)BlackPawn;
7917 if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
7918 /* in Shogi restore piece to its original first */
7919 captured = (ChessSquare) (DEMOTED captured);
7922 p = PieceToNumber((ChessSquare)p);
7923 if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
7924 board[p][BOARD_WIDTH-2]++;
7925 board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
7927 p -= (int)WhitePawn;
7928 if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
7929 captured = (ChessSquare) (DEMOTED captured);
7932 p = PieceToNumber((ChessSquare)p);
7933 if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
7934 board[BOARD_HEIGHT-1-p][1]++;
7935 board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
7938 } else if (gameInfo.variant == VariantAtomic) {
7939 if (captured != EmptySquare) {
7941 for (y = toY-1; y <= toY+1; y++) {
7942 for (x = toX-1; x <= toX+1; x++) {
7943 if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
7944 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
7945 board[y][x] = EmptySquare;
7949 board[toY][toX] = EmptySquare;
7952 if(gameInfo.variant == VariantShogi && promoChar != NULLCHAR && promoChar != '=') {
7953 /* [HGM] Shogi promotions */
7954 board[toY][toX] = (ChessSquare) (PROMOTED piece);
7957 if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
7958 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
7959 // [HGM] superchess: take promotion piece out of holdings
7960 int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
7961 if((int)piece < (int)BlackPawn) { // determine stm from piece color
7962 if(!--board[k][BOARD_WIDTH-2])
7963 board[k][BOARD_WIDTH-1] = EmptySquare;
7965 if(!--board[BOARD_HEIGHT-1-k][1])
7966 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
7972 /* Updates forwardMostMove */
7974 MakeMove(fromX, fromY, toX, toY, promoChar)
7975 int fromX, fromY, toX, toY;
7978 // forwardMostMove++; // [HGM] bare: moved downstream
7980 if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
7981 int timeLeft; static int lastLoadFlag=0; int king, piece;
7982 piece = boards[forwardMostMove][fromY][fromX];
7983 king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
7984 if(gameInfo.variant == VariantKnightmate)
7985 king += (int) WhiteUnicorn - (int) WhiteKing;
7986 if(forwardMostMove == 0) {
7988 fprintf(serverMoves, "%s;", second.tidy);
7989 fprintf(serverMoves, "%s;", first.tidy);
7990 if(!blackPlaysFirst)
7991 fprintf(serverMoves, "%s;", second.tidy);
7992 } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
7993 lastLoadFlag = loadFlag;
7995 fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
7996 // print castling suffix
7997 if( toY == fromY && piece == king ) {
7999 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
8001 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
8004 if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
8005 boards[forwardMostMove][fromY][fromX] == BlackPawn ) &&
8006 boards[forwardMostMove][toY][toX] == EmptySquare
8008 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
8010 if(promoChar != NULLCHAR)
8011 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);
8013 fprintf(serverMoves, "/%d/%d",
8014 pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
8015 if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
8016 else timeLeft = blackTimeRemaining/1000;
8017 fprintf(serverMoves, "/%d", timeLeft);
8019 fflush(serverMoves);
8022 if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations
8023 DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
8027 UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
8028 if (commentList[forwardMostMove+1] != NULL) {
8029 free(commentList[forwardMostMove+1]);
8030 commentList[forwardMostMove+1] = NULL;
8032 CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
8033 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
8034 forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
8035 SwitchClocks(); // uses forwardMostMove, so must be done after incrementing it !
8036 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
8037 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
8038 gameInfo.result = GameUnfinished;
8039 if (gameInfo.resultDetails != NULL) {
8040 free(gameInfo.resultDetails);
8041 gameInfo.resultDetails = NULL;
8043 CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
8044 moveList[forwardMostMove - 1]);
8045 (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
8046 PosFlags(forwardMostMove - 1),
8047 fromY, fromX, toY, toX, promoChar,
8048 parseList[forwardMostMove - 1]);
8049 switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
8055 if(gameInfo.variant != VariantShogi)
8056 strcat(parseList[forwardMostMove - 1], "+");
8060 strcat(parseList[forwardMostMove - 1], "#");
8063 if (appData.debugMode) {
8064 fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
8069 /* Updates currentMove if not pausing */
8071 ShowMove(fromX, fromY, toX, toY)
8073 int instant = (gameMode == PlayFromGameFile) ?
8074 (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
8076 if(appData.noGUI) return;
8078 if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile)
8082 if (forwardMostMove == currentMove + 1)
8085 // AnimateMove(boards[forwardMostMove - 1],
8086 // fromX, fromY, toX, toY);
8088 if (appData.highlightLastMove)
8090 SetHighlights(fromX, fromY, toX, toY);
8093 currentMove = forwardMostMove;
8096 if (instant) return;
8098 DisplayMove(currentMove - 1);
8099 DrawPosition(FALSE, boards[currentMove]);
8100 DisplayBothClocks();
8101 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
8106 void SendEgtPath(ChessProgramState *cps)
8107 { /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
8108 char buf[MSG_SIZ], name[MSG_SIZ], *p;
8110 if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
8113 char c, *q = name+1, *r, *s;
8115 name[0] = ','; // extract next format name from feature and copy with prefixed ','
8116 while(*p && *p != ',') *q++ = *p++;
8118 if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
8119 strcmp(name, ",nalimov:") == 0 ) {
8120 // take nalimov path from the menu-changeable option first, if it is defined
8121 sprintf(buf, "egtpath nalimov %s\n", appData.defaultPathEGTB);
8122 SendToProgram(buf,cps); // send egtbpath command for nalimov
8124 if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
8125 (s = StrStr(appData.egtFormats, name)) != NULL) {
8126 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
8127 s = r = StrStr(s, ":") + 1; // beginning of path info
8128 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
8129 c = *r; *r = 0; // temporarily null-terminate path info
8130 *--q = 0; // strip of trailig ':' from name
8131 sprintf(buf, "egtpath %s %s\n", name+1, s);
8133 SendToProgram(buf,cps); // send egtbpath command for this format
8135 if(*p == ',') p++; // read away comma to position for next format name
8140 InitChessProgram(cps, setup)
8141 ChessProgramState *cps;
8142 int setup; /* [HGM] needed to setup FRC opening position */
8144 char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
8145 if (appData.noChessProgram) return;
8146 hintRequested = FALSE;
8147 bookRequested = FALSE;
8149 /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
8150 /* moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
8151 if(cps->memSize) { /* [HGM] memory */
8152 sprintf(buf, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
8153 SendToProgram(buf, cps);
8155 SendEgtPath(cps); /* [HGM] EGT */
8156 if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
8157 sprintf(buf, "cores %d\n", appData.smpCores);
8158 SendToProgram(buf, cps);
8161 SendToProgram(cps->initString, cps);
8162 if (gameInfo.variant != VariantNormal &&
8163 gameInfo.variant != VariantLoadable
8164 /* [HGM] also send variant if board size non-standard */
8165 || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
8167 char *v = VariantName(gameInfo.variant);
8168 if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
8169 /* [HGM] in protocol 1 we have to assume all variants valid */
8170 sprintf(buf, _("Variant %s not supported by %s"), v, cps->tidy);
8171 DisplayFatalError(buf, 0, 1);
8175 /* [HGM] make prefix for non-standard board size. Awkward testing... */
8176 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8177 if( gameInfo.variant == VariantXiangqi )
8178 overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
8179 if( gameInfo.variant == VariantShogi )
8180 overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
8181 if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
8182 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
8183 if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
8184 gameInfo.variant == VariantGothic || gameInfo.variant == VariantFalcon )
8185 overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8186 if( gameInfo.variant == VariantCourier )
8187 overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8188 if( gameInfo.variant == VariantSuper )
8189 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
8190 if( gameInfo.variant == VariantGreat )
8191 overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
8194 sprintf(b, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
8195 gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
8196 /* [HGM] varsize: try first if this defiant size variant is specifically known */
8197 if(StrStr(cps->variants, b) == NULL) {
8198 // specific sized variant not known, check if general sizing allowed
8199 if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
8200 if(StrStr(cps->variants, "boardsize") == NULL) {
8201 sprintf(buf, "Board size %dx%d+%d not supported by %s",
8202 gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
8203 DisplayFatalError(buf, 0, 1);
8206 /* [HGM] here we really should compare with the maximum supported board size */
8209 } else sprintf(b, "%s", VariantName(gameInfo.variant));
8210 sprintf(buf, "variant %s\n", b);
8211 SendToProgram(buf, cps);
8213 currentlyInitializedVariant = gameInfo.variant;
8215 /* [HGM] send opening position in FRC to first engine */
8217 SendToProgram("force\n", cps);
8219 /* engine is now in force mode! Set flag to wake it up after first move. */
8220 setboardSpoiledMachineBlack = 1;
8224 snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
8225 SendToProgram(buf, cps);
8227 cps->maybeThinking = FALSE;
8228 cps->offeredDraw = 0;
8229 if (!appData.icsActive) {
8230 SendTimeControl(cps, movesPerSession, timeControl,
8231 timeIncrement, appData.searchDepth,
8234 if (appData.showThinking
8235 // [HGM] thinking: four options require thinking output to be sent
8236 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
8238 SendToProgram("post\n", cps);
8240 SendToProgram("hard\n", cps);
8241 if (!appData.ponderNextMove) {
8242 /* Warning: "easy" is a toggle in GNU Chess, so don't send
8243 it without being sure what state we are in first. "hard"
8244 is not a toggle, so that one is OK.
8246 SendToProgram("easy\n", cps);
8249 sprintf(buf, "ping %d\n", ++cps->lastPing);
8250 SendToProgram(buf, cps);
8252 cps->initDone = TRUE;
8257 StartChessProgram(cps)
8258 ChessProgramState *cps;
8263 if (appData.noChessProgram) return;
8264 cps->initDone = FALSE;
8266 if (strcmp(cps->host, "localhost") == 0) {
8267 err = StartChildProcess(cps->program, cps->dir, &cps->pr);
8268 } else if (*appData.remoteShell == NULLCHAR) {
8269 err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
8271 if (*appData.remoteUser == NULLCHAR) {
8272 snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
8275 snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
8276 cps->host, appData.remoteUser, cps->program);
8278 err = StartChildProcess(buf, "", &cps->pr);
8282 sprintf(buf, _("Startup failure on '%s'"), cps->program);
8283 DisplayFatalError(buf, err, 1);
8289 cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
8290 if (cps->protocolVersion > 1) {
8291 sprintf(buf, "xboard\nprotover %d\n", cps->protocolVersion);
8292 cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
8293 cps->comboCnt = 0; // and values of combo boxes
8294 SendToProgram(buf, cps);
8296 SendToProgram("xboard\n", cps);
8302 TwoMachinesEventIfReady P((void))
8304 if (first.lastPing != first.lastPong) {
8305 DisplayMessage("", _("Waiting for first chess program"));
8306 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
8309 if (second.lastPing != second.lastPong) {
8310 DisplayMessage("", _("Waiting for second chess program"));
8311 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
8319 NextMatchGame P((void))
8321 int index; /* [HGM] autoinc: step load index during match */
8323 if (*appData.loadGameFile != NULLCHAR) {
8324 index = appData.loadGameIndex;
8325 if(index < 0) { // [HGM] autoinc
8326 lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
8327 if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
8329 LoadGameFromFile(appData.loadGameFile,
8331 appData.loadGameFile, FALSE);
8332 } else if (*appData.loadPositionFile != NULLCHAR) {
8333 index = appData.loadPositionIndex;
8334 if(index < 0) { // [HGM] autoinc
8335 lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
8336 if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
8338 LoadPositionFromFile(appData.loadPositionFile,
8340 appData.loadPositionFile);
8342 TwoMachinesEventIfReady();
8345 void UserAdjudicationEvent( int result )
8347 ChessMove gameResult = GameIsDrawn;
8350 gameResult = WhiteWins;
8352 else if( result < 0 ) {
8353 gameResult = BlackWins;
8356 if( gameMode == TwoMachinesPlay ) {
8357 GameEnds( gameResult, "User adjudication", GE_XBOARD );
8362 // [HGM] save: calculate checksum of game to make games easily identifiable
8363 int StringCheckSum(char *s)
8366 if(s==NULL) return 0;
8367 while(*s) i = i*259 + *s++;
8374 for(i=backwardMostMove; i<forwardMostMove; i++) {
8375 sum += pvInfoList[i].depth;
8376 sum += StringCheckSum(parseList[i]);
8377 sum += StringCheckSum(commentList[i]);
8380 if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
8381 return sum + StringCheckSum(commentList[i]);
8382 } // end of save patch
8385 GameEnds(result, resultDetails, whosays)
8387 char *resultDetails;
8390 GameMode nextGameMode;
8394 if(endingGame) return; /* [HGM] crash: forbid recursion */
8397 if (appData.debugMode) {
8398 fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
8399 result, resultDetails ? resultDetails : "(null)", whosays);
8402 if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
8403 /* If we are playing on ICS, the server decides when the
8404 game is over, but the engine can offer to draw, claim
8408 if (appData.zippyPlay && first.initDone) {
8409 if (result == GameIsDrawn) {
8410 /* In case draw still needs to be claimed */
8411 SendToICS(ics_prefix);
8412 SendToICS("draw\n");
8413 } else if (StrCaseStr(resultDetails, "resign")) {
8414 SendToICS(ics_prefix);
8415 SendToICS("resign\n");
8419 endingGame = 0; /* [HGM] crash */
8423 /* If we're loading the game from a file, stop */
8424 if (whosays == GE_FILE) {
8425 (void) StopLoadGameTimer();
8429 /* Cancel draw offers */
8430 first.offeredDraw = second.offeredDraw = 0;
8432 /* If this is an ICS game, only ICS can really say it's done;
8433 if not, anyone can. */
8434 isIcsGame = (gameMode == IcsPlayingWhite ||
8435 gameMode == IcsPlayingBlack ||
8436 gameMode == IcsObserving ||
8437 gameMode == IcsExamining);
8439 if (!isIcsGame || whosays == GE_ICS) {
8440 /* OK -- not an ICS game, or ICS said it was done */
8442 if (!isIcsGame && !appData.noChessProgram)
8443 SetUserThinkingEnables();
8445 /* [HGM] if a machine claims the game end we verify this claim */
8446 if(gameMode == TwoMachinesPlay && appData.testClaims) {
8447 if(appData.testLegality && whosays >= GE_ENGINE1 ) {
8449 ChessMove trueResult = (ChessMove) -1;
8451 claimer = whosays == GE_ENGINE1 ? /* color of claimer */
8452 first.twoMachinesColor[0] :
8453 second.twoMachinesColor[0] ;
8455 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
8456 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
8457 /* [HGM] verify: engine mate claims accepted if they were flagged */
8458 trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
8460 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
8461 /* [HGM] verify: engine mate claims accepted if they were flagged */
8462 trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8464 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
8465 trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
8468 // now verify win claims, but not in drop games, as we don't understand those yet
8469 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
8470 || gameInfo.variant == VariantGreat) &&
8471 (result == WhiteWins && claimer == 'w' ||
8472 result == BlackWins && claimer == 'b' ) ) { // case to verify: engine claims own win
8473 if (appData.debugMode) {
8474 fprintf(debugFP, "result=%d sp=%d move=%d\n",
8475 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
8477 if(result != trueResult) {
8478 sprintf(buf, "False win claim: '%s'", resultDetails);
8479 result = claimer == 'w' ? BlackWins : WhiteWins;
8480 resultDetails = buf;
8483 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
8484 && (forwardMostMove <= backwardMostMove ||
8485 (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
8486 (claimer=='b')==(forwardMostMove&1))
8488 /* [HGM] verify: draws that were not flagged are false claims */
8489 sprintf(buf, "False draw claim: '%s'", resultDetails);
8490 result = claimer == 'w' ? BlackWins : WhiteWins;
8491 resultDetails = buf;
8493 /* (Claiming a loss is accepted no questions asked!) */
8496 /* [HGM] bare: don't allow bare King to win */
8497 if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
8498 && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
8499 && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
8500 && result != GameIsDrawn)
8501 { int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
8502 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
8503 int p = (signed char)boards[forwardMostMove][i][j] - color;
8504 if(p >= 0 && p <= (int)WhiteKing) k++;
8506 if (appData.debugMode) {
8507 fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
8508 result, resultDetails ? resultDetails : "(null)", whosays, k, color);
8511 result = GameIsDrawn;
8512 sprintf(buf, "%s but bare king", resultDetails);
8513 resultDetails = buf;
8518 if(serverMoves != NULL && !loadFlag) { char c = '=';
8519 if(result==WhiteWins) c = '+';
8520 if(result==BlackWins) c = '-';
8521 if(resultDetails != NULL)
8522 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
8524 if (resultDetails != NULL) {
8525 gameInfo.result = result;
8526 gameInfo.resultDetails = StrSave(resultDetails);
8528 /* display last move only if game was not loaded from file */
8529 if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
8530 DisplayMove(currentMove - 1);
8532 if (forwardMostMove != 0) {
8533 if (gameMode != PlayFromGameFile && gameMode != EditGame
8534 && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
8536 if (*appData.saveGameFile != NULLCHAR) {
8537 SaveGameToFile(appData.saveGameFile, TRUE);
8538 } else if (appData.autoSaveGames) {
8541 if (*appData.savePositionFile != NULLCHAR) {
8542 SavePositionToFile(appData.savePositionFile);
8547 /* Tell program how game ended in case it is learning */
8548 /* [HGM] Moved this to after saving the PGN, just in case */
8549 /* engine died and we got here through time loss. In that */
8550 /* case we will get a fatal error writing the pipe, which */
8551 /* would otherwise lose us the PGN. */
8552 /* [HGM] crash: not needed anymore, but doesn't hurt; */
8553 /* output during GameEnds should never be fatal anymore */
8554 if (gameMode == MachinePlaysWhite ||
8555 gameMode == MachinePlaysBlack ||
8556 gameMode == TwoMachinesPlay ||
8557 gameMode == IcsPlayingWhite ||
8558 gameMode == IcsPlayingBlack ||
8559 gameMode == BeginningOfGame) {
8561 sprintf(buf, "result %s {%s}\n", PGNResult(result),
8563 if (first.pr != NoProc) {
8564 SendToProgram(buf, &first);
8566 if (second.pr != NoProc &&
8567 gameMode == TwoMachinesPlay) {
8568 SendToProgram(buf, &second);
8573 if (appData.icsActive) {
8574 if (appData.quietPlay &&
8575 (gameMode == IcsPlayingWhite ||
8576 gameMode == IcsPlayingBlack)) {
8577 SendToICS(ics_prefix);
8578 SendToICS("set shout 1\n");
8580 nextGameMode = IcsIdle;
8581 ics_user_moved = FALSE;
8582 /* clean up premove. It's ugly when the game has ended and the
8583 * premove highlights are still on the board.
8587 ClearPremoveHighlights();
8588 DrawPosition(FALSE, boards[currentMove]);
8590 if (whosays == GE_ICS) {
8593 if (gameMode == IcsPlayingWhite)
8595 else if(gameMode == IcsPlayingBlack)
8599 if (gameMode == IcsPlayingBlack)
8601 else if(gameMode == IcsPlayingWhite)
8608 PlayIcsUnfinishedSound();
8611 } else if (gameMode == EditGame ||
8612 gameMode == PlayFromGameFile ||
8613 gameMode == AnalyzeMode ||
8614 gameMode == AnalyzeFile) {
8615 nextGameMode = gameMode;
8617 nextGameMode = EndOfGame;
8622 nextGameMode = gameMode;
8625 if (appData.noChessProgram) {
8626 gameMode = nextGameMode;
8628 endingGame = 0; /* [HGM] crash */
8633 /* Put first chess program into idle state */
8634 if (first.pr != NoProc &&
8635 (gameMode == MachinePlaysWhite ||
8636 gameMode == MachinePlaysBlack ||
8637 gameMode == TwoMachinesPlay ||
8638 gameMode == IcsPlayingWhite ||
8639 gameMode == IcsPlayingBlack ||
8640 gameMode == BeginningOfGame)) {
8641 SendToProgram("force\n", &first);
8642 if (first.usePing) {
8644 sprintf(buf, "ping %d\n", ++first.lastPing);
8645 SendToProgram(buf, &first);
8648 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
8649 /* Kill off first chess program */
8650 if (first.isr != NULL)
8651 RemoveInputSource(first.isr);
8654 if (first.pr != NoProc) {
8656 DoSleep( appData.delayBeforeQuit );
8657 SendToProgram("quit\n", &first);
8658 DoSleep( appData.delayAfterQuit );
8659 DestroyChildProcess(first.pr, first.useSigterm);
8664 /* Put second chess program into idle state */
8665 if (second.pr != NoProc &&
8666 gameMode == TwoMachinesPlay) {
8667 SendToProgram("force\n", &second);
8668 if (second.usePing) {
8670 sprintf(buf, "ping %d\n", ++second.lastPing);
8671 SendToProgram(buf, &second);
8674 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
8675 /* Kill off second chess program */
8676 if (second.isr != NULL)
8677 RemoveInputSource(second.isr);
8680 if (second.pr != NoProc) {
8681 DoSleep( appData.delayBeforeQuit );
8682 SendToProgram("quit\n", &second);
8683 DoSleep( appData.delayAfterQuit );
8684 DestroyChildProcess(second.pr, second.useSigterm);
8689 if (matchMode && gameMode == TwoMachinesPlay) {
8692 if (first.twoMachinesColor[0] == 'w') {
8699 if (first.twoMachinesColor[0] == 'b') {
8708 if (matchGame < appData.matchGames) {
8710 if(appData.sameColorGames <= 1) { /* [HGM] alternate: suppress color swap */
8711 tmp = first.twoMachinesColor;
8712 first.twoMachinesColor = second.twoMachinesColor;
8713 second.twoMachinesColor = tmp;
8715 gameMode = nextGameMode;
8717 if(appData.matchPause>10000 || appData.matchPause<10)
8718 appData.matchPause = 10000; /* [HGM] make pause adjustable */
8719 ScheduleDelayedEvent(NextMatchGame, appData.matchPause);
8720 endingGame = 0; /* [HGM] crash */
8724 gameMode = nextGameMode;
8725 sprintf(buf, _("Match %s vs. %s: final score %d-%d-%d"),
8726 first.tidy, second.tidy,
8727 first.matchWins, second.matchWins,
8728 appData.matchGames - (first.matchWins + second.matchWins));
8729 DisplayFatalError(buf, 0, 0);
8732 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
8733 !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
8735 gameMode = nextGameMode;
8737 endingGame = 0; /* [HGM] crash */
8740 /* Assumes program was just initialized (initString sent).
8741 Leaves program in force mode. */
8743 FeedMovesToProgram(cps, upto)
8744 ChessProgramState *cps;
8749 if (appData.debugMode)
8750 fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
8751 startedFromSetupPosition ? "position and " : "",
8752 backwardMostMove, upto, cps->which);
8753 if(currentlyInitializedVariant != gameInfo.variant) { char buf[MSG_SIZ];
8754 // [HGM] variantswitch: make engine aware of new variant
8755 if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
8756 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
8757 sprintf(buf, "variant %s\n", VariantName(gameInfo.variant));
8758 SendToProgram(buf, cps);
8759 currentlyInitializedVariant = gameInfo.variant;
8761 SendToProgram("force\n", cps);
8762 if (startedFromSetupPosition) {
8763 SendBoard(cps, backwardMostMove);
8764 if (appData.debugMode) {
8765 fprintf(debugFP, "feedMoves\n");
8768 for (i = backwardMostMove; i < upto; i++) {
8769 SendMoveToProgram(i, cps);
8775 ResurrectChessProgram()
8777 /* The chess program may have exited.
8778 If so, restart it and feed it all the moves made so far. */
8780 if (appData.noChessProgram || first.pr != NoProc) return;
8782 StartChessProgram(&first);
8783 InitChessProgram(&first, FALSE);
8784 FeedMovesToProgram(&first, currentMove);
8786 if (!first.sendTime) {
8787 /* can't tell gnuchess what its clock should read,
8788 so we bow to its notion. */
8790 timeRemaining[0][currentMove] = whiteTimeRemaining;
8791 timeRemaining[1][currentMove] = blackTimeRemaining;
8794 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
8795 appData.icsEngineAnalyze) && first.analysisSupport) {
8796 SendToProgram("analyze\n", &first);
8797 first.analyzing = TRUE;
8810 if (appData.debugMode) {
8811 fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
8812 redraw, init, gameMode);
8814 CleanupTail(); // [HGM] vari: delete any stored variations
8815 pausing = pauseExamInvalid = FALSE;
8816 startedFromSetupPosition = blackPlaysFirst = FALSE;
8818 whiteFlag = blackFlag = FALSE;
8819 userOfferedDraw = FALSE;
8820 hintRequested = bookRequested = FALSE;
8821 first.maybeThinking = FALSE;
8822 second.maybeThinking = FALSE;
8823 first.bookSuspend = FALSE; // [HGM] book
8824 second.bookSuspend = FALSE;
8825 thinkOutput[0] = NULLCHAR;
8826 lastHint[0] = NULLCHAR;
8827 ClearGameInfo(&gameInfo);
8828 gameInfo.variant = StringToVariant(appData.variant);
8829 ics_user_moved = ics_clock_paused = FALSE;
8830 ics_getting_history = H_FALSE;
8832 white_holding[0] = black_holding[0] = NULLCHAR;
8833 ClearProgramStats();
8834 opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
8838 flipView = appData.flipView;
8839 ClearPremoveHighlights();
8841 alarmSounded = FALSE;
8843 GameEnds((ChessMove) 0, NULL, GE_PLAYER);
8844 if(appData.serverMovesName != NULL) {
8845 /* [HGM] prepare to make moves file for broadcasting */
8846 clock_t t = clock();
8847 if(serverMoves != NULL) fclose(serverMoves);
8848 serverMoves = fopen(appData.serverMovesName, "r");
8849 if(serverMoves != NULL) {
8850 fclose(serverMoves);
8851 /* delay 15 sec before overwriting, so all clients can see end */
8852 while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
8854 serverMoves = fopen(appData.serverMovesName, "w");
8858 gameMode = BeginningOfGame;
8861 if(appData.icsActive) gameInfo.variant = VariantNormal;
8862 currentMove = forwardMostMove = backwardMostMove = 0;
8863 InitPosition(redraw);
8864 for (i = 0; i < MAX_MOVES; i++) {
8865 if (commentList[i] != NULL) {
8866 free(commentList[i]);
8867 commentList[i] = NULL;
8872 timeRemaining[0][0] = whiteTimeRemaining;
8873 timeRemaining[1][0] = blackTimeRemaining;
8874 if (first.pr == NULL) {
8875 StartChessProgram(&first);
8878 InitChessProgram(&first, startedFromSetupPosition);
8882 DisplayMessage("", "");
8883 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
8884 lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
8892 if (!AutoPlayOneMove())
8894 if (matchMode || appData.timeDelay == 0)
8896 if (appData.timeDelay < 0 || gameMode == AnalyzeFile)
8898 StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
8907 int fromX, fromY, toX, toY;
8909 if (appData.debugMode) {
8910 fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
8913 if (gameMode != PlayFromGameFile)
8916 if (currentMove >= forwardMostMove) {
8917 gameMode = EditGame;
8920 /* [AS] Clear current move marker at the end of a game */
8921 /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
8926 toX = moveList[currentMove][2] - AAA;
8927 toY = moveList[currentMove][3] - ONE;
8929 if (moveList[currentMove][1] == '@') {
8930 if (appData.highlightLastMove) {
8931 SetHighlights(-1, -1, toX, toY);
8934 fromX = moveList[currentMove][0] - AAA;
8935 fromY = moveList[currentMove][1] - ONE;
8937 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
8939 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
8941 if (appData.highlightLastMove) {
8942 SetHighlights(fromX, fromY, toX, toY);
8945 DisplayMove(currentMove);
8946 SendMoveToProgram(currentMove++, &first);
8947 DisplayBothClocks();
8948 DrawPosition(FALSE, boards[currentMove]);
8949 // [HGM] PV info: always display, routine tests if empty
8950 DisplayComment(currentMove - 1, commentList[currentMove]);
8956 LoadGameOneMove(readAhead)
8957 ChessMove readAhead;
8959 int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
8960 char promoChar = NULLCHAR;
8965 if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
8966 gameMode != AnalyzeMode && gameMode != Training) {
8971 yyboardindex = forwardMostMove;
8972 if (readAhead != (ChessMove)0) {
8973 moveType = readAhead;
8975 if (gameFileFP == NULL)
8977 moveType = (ChessMove) yylex();
8983 if (appData.debugMode)
8984 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
8987 /* append the comment but don't display it */
8988 AppendComment(currentMove, p, FALSE);
8991 case WhiteCapturesEnPassant:
8992 case BlackCapturesEnPassant:
8993 case WhitePromotionChancellor:
8994 case BlackPromotionChancellor:
8995 case WhitePromotionArchbishop:
8996 case BlackPromotionArchbishop:
8997 case WhitePromotionCentaur:
8998 case BlackPromotionCentaur:
8999 case WhitePromotionQueen:
9000 case BlackPromotionQueen:
9001 case WhitePromotionRook:
9002 case BlackPromotionRook:
9003 case WhitePromotionBishop:
9004 case BlackPromotionBishop:
9005 case WhitePromotionKnight:
9006 case BlackPromotionKnight:
9007 case WhitePromotionKing:
9008 case BlackPromotionKing:
9010 case WhiteKingSideCastle:
9011 case WhiteQueenSideCastle:
9012 case BlackKingSideCastle:
9013 case BlackQueenSideCastle:
9014 case WhiteKingSideCastleWild:
9015 case WhiteQueenSideCastleWild:
9016 case BlackKingSideCastleWild:
9017 case BlackQueenSideCastleWild:
9019 case WhiteHSideCastleFR:
9020 case WhiteASideCastleFR:
9021 case BlackHSideCastleFR:
9022 case BlackASideCastleFR:
9024 if (appData.debugMode)
9025 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
9026 fromX = currentMoveString[0] - AAA;
9027 fromY = currentMoveString[1] - ONE;
9028 toX = currentMoveString[2] - AAA;
9029 toY = currentMoveString[3] - ONE;
9030 promoChar = currentMoveString[4];
9035 if (appData.debugMode)
9036 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
9037 fromX = moveType == WhiteDrop ?
9038 (int) CharToPiece(ToUpper(currentMoveString[0])) :
9039 (int) CharToPiece(ToLower(currentMoveString[0]));
9041 toX = currentMoveString[2] - AAA;
9042 toY = currentMoveString[3] - ONE;
9048 case GameUnfinished:
9049 if (appData.debugMode)
9050 fprintf(debugFP, "Parsed game end: %s\n", yy_text);
9051 p = strchr(yy_text, '{');
9052 if (p == NULL) p = strchr(yy_text, '(');
9055 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
9057 q = strchr(p, *p == '{' ? '}' : ')');
9058 if (q != NULL) *q = NULLCHAR;
9061 GameEnds(moveType, p, GE_FILE);
9063 if (cmailMsgLoaded) {
9065 flipView = WhiteOnMove(currentMove);
9066 if (moveType == GameUnfinished) flipView = !flipView;
9067 if (appData.debugMode)
9068 fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
9072 case (ChessMove) 0: /* end of file */
9073 if (appData.debugMode)
9074 fprintf(debugFP, "Parser hit end of file\n");
9075 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9081 if (WhiteOnMove(currentMove)) {
9082 GameEnds(BlackWins, "Black mates", GE_FILE);
9084 GameEnds(WhiteWins, "White mates", GE_FILE);
9088 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
9095 if (lastLoadGameStart == GNUChessGame) {
9096 /* GNUChessGames have numbers, but they aren't move numbers */
9097 if (appData.debugMode)
9098 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
9099 yy_text, (int) moveType);
9100 return LoadGameOneMove((ChessMove)0); /* tail recursion */
9102 /* else fall thru */
9107 /* Reached start of next game in file */
9108 if (appData.debugMode)
9109 fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
9110 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9116 if (WhiteOnMove(currentMove)) {
9117 GameEnds(BlackWins, "Black mates", GE_FILE);
9119 GameEnds(WhiteWins, "White mates", GE_FILE);
9123 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
9129 case PositionDiagram: /* should not happen; ignore */
9130 case ElapsedTime: /* ignore */
9131 case NAG: /* ignore */
9132 if (appData.debugMode)
9133 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
9134 yy_text, (int) moveType);
9135 return LoadGameOneMove((ChessMove)0); /* tail recursion */
9138 if (appData.testLegality) {
9139 if (appData.debugMode)
9140 fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
9141 sprintf(move, _("Illegal move: %d.%s%s"),
9142 (forwardMostMove / 2) + 1,
9143 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9144 DisplayError(move, 0);
9147 if (appData.debugMode)
9148 fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
9149 yy_text, currentMoveString);
9150 fromX = currentMoveString[0] - AAA;
9151 fromY = currentMoveString[1] - ONE;
9152 toX = currentMoveString[2] - AAA;
9153 toY = currentMoveString[3] - ONE;
9154 promoChar = currentMoveString[4];
9159 if (appData.debugMode)
9160 fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
9161 sprintf(move, _("Ambiguous move: %d.%s%s"),
9162 (forwardMostMove / 2) + 1,
9163 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9164 DisplayError(move, 0);
9169 case ImpossibleMove:
9170 if (appData.debugMode)
9171 fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
9172 sprintf(move, _("Illegal move: %d.%s%s"),
9173 (forwardMostMove / 2) + 1,
9174 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9175 DisplayError(move, 0);
9181 if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
9182 DrawPosition(FALSE, boards[currentMove]);
9183 DisplayBothClocks();
9184 if (!appData.matchMode) // [HGM] PV info: routine tests if empty
9185 DisplayComment(currentMove - 1, commentList[currentMove]);
9187 (void) StopLoadGameTimer();
9189 cmailOldMove = forwardMostMove;
9192 /* currentMoveString is set as a side-effect of yylex */
9193 strcat(currentMoveString, "\n");
9194 strcpy(moveList[forwardMostMove], currentMoveString);
9196 thinkOutput[0] = NULLCHAR;
9197 MakeMove(fromX, fromY, toX, toY, promoChar);
9198 currentMove = forwardMostMove;
9203 /* Load the nth game from the given file */
9205 LoadGameFromFile(filename, n, title, useList)
9209 /*Boolean*/ int useList;
9214 if (strcmp(filename, "-") == 0) {
9218 f = fopen(filename, "rb");
9220 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9221 DisplayError(buf, errno);
9225 if (fseek(f, 0, 0) == -1) {
9226 /* f is not seekable; probably a pipe */
9229 if (useList && n == 0) {
9230 int error = GameListBuild(f);
9232 DisplayError(_("Cannot build game list"), error);
9233 } else if (!ListEmpty(&gameList) &&
9234 ((ListGame *) gameList.tailPred)->number > 1) {
9235 // TODO convert to GTK
9236 // GameListPopUp(f, title);
9243 return LoadGame(f, n, title, FALSE);
9248 MakeRegisteredMove()
9250 int fromX, fromY, toX, toY;
9252 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
9253 switch (cmailMoveType[lastLoadGameNumber - 1]) {
9256 if (appData.debugMode)
9257 fprintf(debugFP, "Restoring %s for game %d\n",
9258 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
9260 thinkOutput[0] = NULLCHAR;
9261 strcpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1]);
9262 fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
9263 fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
9264 toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
9265 toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
9266 promoChar = cmailMove[lastLoadGameNumber - 1][4];
9267 MakeMove(fromX, fromY, toX, toY, promoChar);
9268 ShowMove(fromX, fromY, toX, toY);
9269 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9276 if (WhiteOnMove(currentMove)) {
9277 GameEnds(BlackWins, "Black mates", GE_PLAYER);
9279 GameEnds(WhiteWins, "White mates", GE_PLAYER);
9284 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
9291 if (WhiteOnMove(currentMove)) {
9292 GameEnds(BlackWins, "White resigns", GE_PLAYER);
9294 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
9299 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
9310 /* Wrapper around LoadGame for use when a Cmail message is loaded */
9312 CmailLoadGame(f, gameNumber, title, useList)
9320 if (gameNumber > nCmailGames) {
9321 DisplayError(_("No more games in this message"), 0);
9324 if (f == lastLoadGameFP) {
9325 int offset = gameNumber - lastLoadGameNumber;
9327 cmailMsg[0] = NULLCHAR;
9328 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
9329 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
9330 nCmailMovesRegistered--;
9332 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
9333 if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
9334 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
9337 if (! RegisterMove()) return FALSE;
9341 retVal = LoadGame(f, gameNumber, title, useList);
9343 /* Make move registered during previous look at this game, if any */
9344 MakeRegisteredMove();
9346 if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
9347 commentList[currentMove]
9348 = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
9349 DisplayComment(currentMove - 1, commentList[currentMove]);
9355 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
9360 int gameNumber = lastLoadGameNumber + offset;
9361 if (lastLoadGameFP == NULL) {
9362 DisplayError(_("No game has been loaded yet"), 0);
9365 if (gameNumber <= 0) {
9366 DisplayError(_("Can't back up any further"), 0);
9369 if (cmailMsgLoaded) {
9370 return CmailLoadGame(lastLoadGameFP, gameNumber,
9371 lastLoadGameTitle, lastLoadGameUseList);
9373 return LoadGame(lastLoadGameFP, gameNumber,
9374 lastLoadGameTitle, lastLoadGameUseList);
9380 /* Load the nth game from open file f */
9382 LoadGame(f, gameNumber, title, useList)
9390 int gn = gameNumber;
9391 ListGame *lg = NULL;
9394 GameMode oldGameMode;
9395 VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
9397 if (appData.debugMode)
9398 fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
9400 if (gameMode == Training )
9401 SetTrainingModeOff();
9403 oldGameMode = gameMode;
9404 if (gameMode != BeginningOfGame)
9410 if (lastLoadGameFP != NULL && lastLoadGameFP != f)
9412 fclose(lastLoadGameFP);
9417 lg = (ListGame *) ListElem(&gameList, gameNumber-1);
9421 fseek(f, lg->offset, 0);
9422 GameListHighlight(gameNumber);
9427 DisplayError(_("Game number out of range"), 0);
9434 if (fseek(f, 0, 0) == -1)
9436 if (f == lastLoadGameFP ?
9437 gameNumber == lastLoadGameNumber + 1 :
9444 DisplayError(_("Can't seek on game file"), 0);
9451 lastLoadGameNumber = gameNumber;
9452 strcpy(lastLoadGameTitle, title);
9453 lastLoadGameUseList = useList;
9457 if (lg && lg->gameInfo.white && lg->gameInfo.black)
9459 snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
9460 lg->gameInfo.black);
9463 else if (*title != NULLCHAR)
9467 sprintf(buf, "%s %d", title, gameNumber);
9472 DisplayTitle(title);
9476 if (gameMode != AnalyzeFile && gameMode != AnalyzeMode)
9478 gameMode = PlayFromGameFile;
9482 currentMove = forwardMostMove = backwardMostMove = 0;
9483 CopyBoard(boards[0], initialPosition);
9487 * Skip the first gn-1 games in the file.
9488 * Also skip over anything that precedes an identifiable
9489 * start of game marker, to avoid being confused by
9490 * garbage at the start of the file. Currently
9491 * recognized start of game markers are the move number "1",
9492 * the pattern "gnuchess .* game", the pattern
9493 * "^[#;%] [^ ]* game file", and a PGN tag block.
9494 * A game that starts with one of the latter two patterns
9495 * will also have a move number 1, possibly
9496 * following a position diagram.
9497 * 5-4-02: Let's try being more lenient and allowing a game to
9498 * start with an unnumbered move. Does that break anything?
9500 cm = lastLoadGameStart = (ChessMove) 0;
9502 yyboardindex = forwardMostMove;
9503 cm = (ChessMove) yylex();
9506 if (cmailMsgLoaded) {
9507 nCmailGames = CMAIL_MAX_GAMES - gn;
9510 DisplayError(_("Game not found in file"), 0);
9517 lastLoadGameStart = cm;
9521 switch (lastLoadGameStart) {
9528 gn--; /* count this game */
9529 lastLoadGameStart = cm;
9538 switch (lastLoadGameStart) {
9543 gn--; /* count this game */
9544 lastLoadGameStart = cm;
9547 lastLoadGameStart = cm; /* game counted already */
9555 yyboardindex = forwardMostMove;
9556 cm = (ChessMove) yylex();
9557 } while (cm == PGNTag || cm == Comment);
9564 if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
9565 if ( cmailResult[CMAIL_MAX_GAMES - gn - 1]
9566 != CMAIL_OLD_RESULT) {
9568 cmailResult[ CMAIL_MAX_GAMES
9569 - gn - 1] = CMAIL_OLD_RESULT;
9575 /* Only a NormalMove can be at the start of a game
9576 * without a position diagram. */
9577 if (lastLoadGameStart == (ChessMove) 0) {
9579 lastLoadGameStart = MoveNumberOne;
9588 if (appData.debugMode)
9589 fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
9591 if (cm == XBoardGame) {
9592 /* Skip any header junk before position diagram and/or move 1 */
9594 yyboardindex = forwardMostMove;
9595 cm = (ChessMove) yylex();
9597 if (cm == (ChessMove) 0 ||
9598 cm == GNUChessGame || cm == XBoardGame) {
9599 /* Empty game; pretend end-of-file and handle later */
9604 if (cm == MoveNumberOne || cm == PositionDiagram ||
9605 cm == PGNTag || cm == Comment)
9608 } else if (cm == GNUChessGame) {
9609 if (gameInfo.event != NULL) {
9610 free(gameInfo.event);
9612 gameInfo.event = StrSave(yy_text);
9615 startedFromSetupPosition = FALSE;
9616 while (cm == PGNTag) {
9617 if (appData.debugMode)
9618 fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
9619 err = ParsePGNTag(yy_text, &gameInfo);
9620 if (!err) numPGNTags++;
9622 /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
9623 if(gameInfo.variant != oldVariant) {
9624 startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
9626 oldVariant = gameInfo.variant;
9627 if (appData.debugMode)
9628 fprintf(debugFP, "New variant %d\n", (int) oldVariant);
9632 if (gameInfo.fen != NULL) {
9633 Board initial_position;
9634 startedFromSetupPosition = TRUE;
9635 if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
9637 DisplayError(_("Bad FEN position in file"), 0);
9640 CopyBoard(boards[0], initial_position);
9641 if (blackPlaysFirst) {
9642 currentMove = forwardMostMove = backwardMostMove = 1;
9643 CopyBoard(boards[1], initial_position);
9644 strcpy(moveList[0], "");
9645 strcpy(parseList[0], "");
9646 timeRemaining[0][1] = whiteTimeRemaining;
9647 timeRemaining[1][1] = blackTimeRemaining;
9648 if (commentList[0] != NULL) {
9649 commentList[1] = commentList[0];
9650 commentList[0] = NULL;
9653 currentMove = forwardMostMove = backwardMostMove = 0;
9655 /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
9657 initialRulePlies = FENrulePlies;
9658 for( i=0; i< nrCastlingRights; i++ )
9659 initialRights[i] = initial_position[CASTLING][i];
9661 yyboardindex = forwardMostMove;
9663 gameInfo.fen = NULL;
9666 yyboardindex = forwardMostMove;
9667 cm = (ChessMove) yylex();
9669 /* Handle comments interspersed among the tags */
9670 while (cm == Comment) {
9672 if (appData.debugMode)
9673 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9675 AppendComment(currentMove, p, FALSE);
9676 yyboardindex = forwardMostMove;
9677 cm = (ChessMove) yylex();
9681 /* don't rely on existence of Event tag since if game was
9682 * pasted from clipboard the Event tag may not exist
9684 if (numPGNTags > 0){
9686 if (gameInfo.variant == VariantNormal) {
9687 gameInfo.variant = StringToVariant(gameInfo.event);
9690 if( appData.autoDisplayTags ) {
9691 tags = PGNTags(&gameInfo);
9692 TagsPopUp(tags, CmailMsg());
9697 /* Make something up, but don't display it now */
9702 if (cm == PositionDiagram) {
9705 Board initial_position;
9707 if (appData.debugMode)
9708 fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
9710 if (!startedFromSetupPosition) {
9712 for (i = BOARD_HEIGHT - 1; i >= 0; i--)
9713 for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
9723 initial_position[i][j++] = CharToPiece(*p);
9726 while (*p == ' ' || *p == '\t' ||
9727 *p == '\n' || *p == '\r') p++;
9729 if (strncmp(p, "black", strlen("black"))==0)
9730 blackPlaysFirst = TRUE;
9732 blackPlaysFirst = FALSE;
9733 startedFromSetupPosition = TRUE;
9735 CopyBoard(boards[0], initial_position);
9736 if (blackPlaysFirst) {
9737 currentMove = forwardMostMove = backwardMostMove = 1;
9738 CopyBoard(boards[1], initial_position);
9739 strcpy(moveList[0], "");
9740 strcpy(parseList[0], "");
9741 timeRemaining[0][1] = whiteTimeRemaining;
9742 timeRemaining[1][1] = blackTimeRemaining;
9743 if (commentList[0] != NULL) {
9744 commentList[1] = commentList[0];
9745 commentList[0] = NULL;
9748 currentMove = forwardMostMove = backwardMostMove = 0;
9751 yyboardindex = forwardMostMove;
9752 cm = (ChessMove) yylex();
9755 if (first.pr == NoProc) {
9756 StartChessProgram(&first);
9758 InitChessProgram(&first, FALSE);
9759 SendToProgram("force\n", &first);
9760 if (startedFromSetupPosition) {
9761 SendBoard(&first, forwardMostMove);
9762 if (appData.debugMode) {
9763 fprintf(debugFP, "Load Game\n");
9765 DisplayBothClocks();
9768 /* [HGM] server: flag to write setup moves in broadcast file as one */
9769 loadFlag = appData.suppressLoadMoves;
9771 while (cm == Comment) {
9773 if (appData.debugMode)
9774 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9776 AppendComment(currentMove, p, FALSE);
9777 yyboardindex = forwardMostMove;
9778 cm = (ChessMove) yylex();
9781 if ((cm == (ChessMove) 0 && lastLoadGameStart != (ChessMove) 0) ||
9782 cm == WhiteWins || cm == BlackWins ||
9783 cm == GameIsDrawn || cm == GameUnfinished) {
9784 DisplayMessage("", _("No moves in game"));
9785 if (cmailMsgLoaded) {
9786 if (appData.debugMode)
9787 fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
9791 DrawPosition(FALSE, boards[currentMove]);
9792 DisplayBothClocks();
9793 gameMode = EditGame;
9800 // [HGM] PV info: routine tests if comment empty
9801 if (!matchMode && (pausing || appData.timeDelay != 0)) {
9802 DisplayComment(currentMove - 1, commentList[currentMove]);
9804 if (!matchMode && appData.timeDelay != 0)
9805 DrawPosition(FALSE, boards[currentMove]);
9807 if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
9808 programStats.ok_to_send = 1;
9811 /* if the first token after the PGN tags is a move
9812 * and not move number 1, retrieve it from the parser
9814 if (cm != MoveNumberOne)
9815 LoadGameOneMove(cm);
9817 /* load the remaining moves from the file */
9818 while (LoadGameOneMove((ChessMove)0)) {
9819 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
9820 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
9823 /* rewind to the start of the game */
9824 currentMove = backwardMostMove;
9826 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
9828 if (oldGameMode == AnalyzeFile ||
9829 oldGameMode == AnalyzeMode) {
9833 if (matchMode || appData.timeDelay == 0) {
9835 gameMode = EditGame;
9837 } else if (appData.timeDelay > 0) {
9841 if (appData.debugMode)
9842 fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
9844 loadFlag = 0; /* [HGM] true game starts */
9848 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
9850 ReloadPosition(offset)
9853 int positionNumber = lastLoadPositionNumber + offset;
9854 if (lastLoadPositionFP == NULL) {
9855 DisplayError(_("No position has been loaded yet"), 0);
9858 if (positionNumber <= 0) {
9859 DisplayError(_("Can't back up any further"), 0);
9862 return LoadPosition(lastLoadPositionFP, positionNumber,
9863 lastLoadPositionTitle);
9866 /* Load the nth position from the given file */
9868 LoadPositionFromFile(filename, n, title)
9876 if (strcmp(filename, "-") == 0) {
9877 return LoadPosition(stdin, n, "stdin");
9879 f = fopen(filename, "rb");
9881 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9882 DisplayError(buf, errno);
9885 return LoadPosition(f, n, title);
9890 /* Load the nth position from the given open file, and close it */
9892 LoadPosition(f, positionNumber, title)
9897 char *p, line[MSG_SIZ];
9898 Board initial_position;
9899 int i, j, fenMode, pn;
9901 if (gameMode == Training )
9902 SetTrainingModeOff();
9904 if (gameMode != BeginningOfGame) {
9907 if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
9908 fclose(lastLoadPositionFP);
9910 if (positionNumber == 0) positionNumber = 1;
9911 lastLoadPositionFP = f;
9912 lastLoadPositionNumber = positionNumber;
9913 strcpy(lastLoadPositionTitle, title);
9914 if (first.pr == NoProc) {
9915 StartChessProgram(&first);
9916 InitChessProgram(&first, FALSE);
9918 pn = positionNumber;
9919 if (positionNumber < 0) {
9920 /* Negative position number means to seek to that byte offset */
9921 if (fseek(f, -positionNumber, 0) == -1) {
9922 DisplayError(_("Can't seek on position file"), 0);
9927 if (fseek(f, 0, 0) == -1) {
9928 if (f == lastLoadPositionFP ?
9929 positionNumber == lastLoadPositionNumber + 1 :
9930 positionNumber == 1) {
9933 DisplayError(_("Can't seek on position file"), 0);
9938 /* See if this file is FEN or old-style xboard */
9939 if (fgets(line, MSG_SIZ, f) == NULL) {
9940 DisplayError(_("Position not found in file"), 0);
9943 // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
9944 fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
9947 if (fenMode || line[0] == '#') pn--;
9949 /* skip positions before number pn */
9950 if (fgets(line, MSG_SIZ, f) == NULL) {
9952 DisplayError(_("Position not found in file"), 0);
9955 if (fenMode || line[0] == '#') pn--;
9960 if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
9961 DisplayError(_("Bad FEN position in file"), 0);
9965 (void) fgets(line, MSG_SIZ, f);
9966 (void) fgets(line, MSG_SIZ, f);
9968 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
9969 (void) fgets(line, MSG_SIZ, f);
9970 for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
9973 initial_position[i][j++] = CharToPiece(*p);
9977 blackPlaysFirst = FALSE;
9979 (void) fgets(line, MSG_SIZ, f);
9980 if (strncmp(line, "black", strlen("black"))==0)
9981 blackPlaysFirst = TRUE;
9984 startedFromSetupPosition = TRUE;
9986 SendToProgram("force\n", &first);
9987 CopyBoard(boards[0], initial_position);
9988 if (blackPlaysFirst) {
9989 currentMove = forwardMostMove = backwardMostMove = 1;
9990 strcpy(moveList[0], "");
9991 strcpy(parseList[0], "");
9992 CopyBoard(boards[1], initial_position);
9993 DisplayMessage("", _("Black to play"));
9995 currentMove = forwardMostMove = backwardMostMove = 0;
9996 DisplayMessage("", _("White to play"));
9998 initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
9999 SendBoard(&first, forwardMostMove);
10000 if (appData.debugMode) {
10002 for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
10003 for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
10004 fprintf(debugFP, "Load Position\n");
10007 if (positionNumber > 1) {
10008 sprintf(line, "%s %d", title, positionNumber);
10009 DisplayTitle(line);
10011 DisplayTitle(title);
10013 gameMode = EditGame;
10016 timeRemaining[0][1] = whiteTimeRemaining;
10017 timeRemaining[1][1] = blackTimeRemaining;
10018 DrawPosition(FALSE, boards[currentMove]);
10025 CopyPlayerNameIntoFileName(dest, src)
10028 while (*src != NULLCHAR && *src != ',') {
10033 *(*dest)++ = *src++;
10038 char *DefaultFileName(ext)
10041 static char def[MSG_SIZ];
10044 if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
10046 CopyPlayerNameIntoFileName(&p, gameInfo.white);
10048 CopyPlayerNameIntoFileName(&p, gameInfo.black);
10057 /* Save the current game to the given file */
10059 SaveGameToFile(filename, append)
10066 if (strcmp(filename, "-") == 0) {
10067 return SaveGame(stdout, 0, NULL);
10069 f = fopen(filename, append ? "a" : "w");
10071 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10072 DisplayError(buf, errno);
10075 return SaveGame(f, 0, NULL);
10084 static char buf[MSG_SIZ];
10087 p = strchr(str, ' ');
10088 if (p == NULL) return str;
10089 strncpy(buf, str, p - str);
10090 buf[p - str] = NULLCHAR;
10094 #define PGN_MAX_LINE 75
10096 #define PGN_SIDE_WHITE 0
10097 #define PGN_SIDE_BLACK 1
10100 static int FindFirstMoveOutOfBook( int side )
10104 if( backwardMostMove == 0 && ! startedFromSetupPosition) {
10105 int index = backwardMostMove;
10106 int has_book_hit = 0;
10108 if( (index % 2) != side ) {
10112 while( index < forwardMostMove ) {
10113 /* Check to see if engine is in book */
10114 int depth = pvInfoList[index].depth;
10115 int score = pvInfoList[index].score;
10121 else if( score == 0 && depth == 63 ) {
10122 in_book = 1; /* Zappa */
10124 else if( score == 2 && depth == 99 ) {
10125 in_book = 1; /* Abrok */
10128 has_book_hit += in_book;
10144 void GetOutOfBookInfo( char * buf )
10148 int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10150 oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
10151 oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
10155 if( oob[0] >= 0 || oob[1] >= 0 ) {
10156 for( i=0; i<2; i++ ) {
10160 if( i > 0 && oob[0] >= 0 ) {
10161 strcat( buf, " " );
10164 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
10165 sprintf( buf+strlen(buf), "%s%.2f",
10166 pvInfoList[idx].score >= 0 ? "+" : "",
10167 pvInfoList[idx].score / 100.0 );
10173 /* Save game in PGN style and close the file */
10178 int i, offset, linelen, newblock;
10182 int movelen, numlen, blank;
10183 char move_buffer[100]; /* [AS] Buffer for move+PV info */
10185 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10187 tm = time((time_t *) NULL);
10189 PrintPGNTags(f, &gameInfo);
10191 if (backwardMostMove > 0 || startedFromSetupPosition) {
10192 char *fen = PositionToFEN(backwardMostMove, NULL);
10193 fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
10194 fprintf(f, "\n{--------------\n");
10195 PrintPosition(f, backwardMostMove);
10196 fprintf(f, "--------------}\n");
10200 /* [AS] Out of book annotation */
10201 if( appData.saveOutOfBookInfo ) {
10204 GetOutOfBookInfo( buf );
10206 if( buf[0] != '\0' ) {
10207 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
10214 i = backwardMostMove;
10218 while (i < forwardMostMove) {
10219 /* Print comments preceding this move */
10220 if (commentList[i] != NULL) {
10221 if (linelen > 0) fprintf(f, "\n");
10222 fprintf(f, "%s", commentList[i]);
10227 /* Format move number */
10228 if ((i % 2) == 0) {
10229 sprintf(numtext, "%d.", (i - offset)/2 + 1);
10232 sprintf(numtext, "%d...", (i - offset)/2 + 1);
10234 numtext[0] = NULLCHAR;
10237 numlen = strlen(numtext);
10240 /* Print move number */
10241 blank = linelen > 0 && numlen > 0;
10242 if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
10251 fprintf(f, "%s", numtext);
10255 strcpy(move_buffer, SavePart(parseList[i])); // [HGM] pgn: print move via buffer, so it can be edited
10256 movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
10259 blank = linelen > 0 && movelen > 0;
10260 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
10269 fprintf(f, "%s", move_buffer);
10270 linelen += movelen;
10272 /* [AS] Add PV info if present */
10273 if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
10274 /* [HGM] add time */
10275 char buf[MSG_SIZ]; int seconds;
10277 seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
10279 if( seconds <= 0) buf[0] = 0; else
10280 if( seconds < 30 ) sprintf(buf, " %3.1f%c", seconds/10., 0); else {
10281 seconds = (seconds + 4)/10; // round to full seconds
10282 if( seconds < 60 ) sprintf(buf, " %d%c", seconds, 0); else
10283 sprintf(buf, " %d:%02d%c", seconds/60, seconds%60, 0);
10286 sprintf( move_buffer, "{%s%.2f/%d%s}",
10287 pvInfoList[i].score >= 0 ? "+" : "",
10288 pvInfoList[i].score / 100.0,
10289 pvInfoList[i].depth,
10292 movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
10294 /* Print score/depth */
10295 blank = linelen > 0 && movelen > 0;
10296 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
10305 fprintf(f, "%s", move_buffer);
10306 linelen += movelen;
10312 /* Start a new line */
10313 if (linelen > 0) fprintf(f, "\n");
10315 /* Print comments after last move */
10316 if (commentList[i] != NULL) {
10317 fprintf(f, "%s\n", commentList[i]);
10321 if (gameInfo.resultDetails != NULL &&
10322 gameInfo.resultDetails[0] != NULLCHAR) {
10323 fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
10324 PGNResult(gameInfo.result));
10326 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
10330 lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
10334 /* Save game in old style and close the file */
10336 SaveGameOldStyle(f)
10342 tm = time((time_t *) NULL);
10344 fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
10347 if (backwardMostMove > 0 || startedFromSetupPosition) {
10348 fprintf(f, "\n[--------------\n");
10349 PrintPosition(f, backwardMostMove);
10350 fprintf(f, "--------------]\n");
10355 i = backwardMostMove;
10356 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10358 while (i < forwardMostMove) {
10359 if (commentList[i] != NULL) {
10360 fprintf(f, "[%s]\n", commentList[i]);
10363 if ((i % 2) == 1) {
10364 fprintf(f, "%d. ... %s\n", (i - offset)/2 + 1, parseList[i]);
10367 fprintf(f, "%d. %s ", (i - offset)/2 + 1, parseList[i]);
10369 if (commentList[i] != NULL) {
10373 if (i >= forwardMostMove) {
10377 fprintf(f, "%s\n", parseList[i]);
10382 if (commentList[i] != NULL) {
10383 fprintf(f, "[%s]\n", commentList[i]);
10386 /* This isn't really the old style, but it's close enough */
10387 if (gameInfo.resultDetails != NULL &&
10388 gameInfo.resultDetails[0] != NULLCHAR) {
10389 fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
10390 gameInfo.resultDetails);
10392 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
10399 /* Save the current game to open file f and close the file */
10401 SaveGame(f, dummy, dummy2)
10406 if (gameMode == EditPosition) EditPositionDone(TRUE);
10407 lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
10408 if (appData.oldSaveStyle)
10409 return SaveGameOldStyle(f);
10411 return SaveGamePGN(f);
10414 /* Save the current position to the given file */
10416 SavePositionToFile(filename)
10422 if (strcmp(filename, "-") == 0) {
10423 return SavePosition(stdout, 0, NULL);
10425 f = fopen(filename, "a");
10427 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10428 DisplayError(buf, errno);
10431 SavePosition(f, 0, NULL);
10437 /* Save the current position to the given open file and close the file */
10439 SavePosition(f, dummy, dummy2)
10446 if (gameMode == EditPosition) EditPositionDone(TRUE);
10447 if (appData.oldSaveStyle) {
10448 tm = time((time_t *) NULL);
10450 fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
10452 fprintf(f, "[--------------\n");
10453 PrintPosition(f, currentMove);
10454 fprintf(f, "--------------]\n");
10456 fen = PositionToFEN(currentMove, NULL);
10457 fprintf(f, "%s\n", fen);
10465 ReloadCmailMsgEvent(unregister)
10469 static char *inFilename = NULL;
10470 static char *outFilename;
10472 struct stat inbuf, outbuf;
10475 /* Any registered moves are unregistered if unregister is set, */
10476 /* i.e. invoked by the signal handler */
10478 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
10479 cmailMoveRegistered[i] = FALSE;
10480 if (cmailCommentList[i] != NULL) {
10481 free(cmailCommentList[i]);
10482 cmailCommentList[i] = NULL;
10485 nCmailMovesRegistered = 0;
10488 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
10489 cmailResult[i] = CMAIL_NOT_RESULT;
10493 if (inFilename == NULL) {
10494 /* Because the filenames are static they only get malloced once */
10495 /* and they never get freed */
10496 inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
10497 sprintf(inFilename, "%s.game.in", appData.cmailGameName);
10499 outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
10500 sprintf(outFilename, "%s.out", appData.cmailGameName);
10503 status = stat(outFilename, &outbuf);
10505 cmailMailedMove = FALSE;
10507 status = stat(inFilename, &inbuf);
10508 cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
10511 /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
10512 counts the games, notes how each one terminated, etc.
10514 It would be nice to remove this kludge and instead gather all
10515 the information while building the game list. (And to keep it
10516 in the game list nodes instead of having a bunch of fixed-size
10517 parallel arrays.) Note this will require getting each game's
10518 termination from the PGN tags, as the game list builder does
10519 not process the game moves. --mann
10521 cmailMsgLoaded = TRUE;
10522 LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
10524 /* Load first game in the file or popup game menu */
10525 LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
10527 #endif /* !WIN32 */
10535 char string[MSG_SIZ];
10537 if ( cmailMailedMove
10538 || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
10539 return TRUE; /* Allow free viewing */
10542 /* Unregister move to ensure that we don't leave RegisterMove */
10543 /* with the move registered when the conditions for registering no */
10545 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
10546 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
10547 nCmailMovesRegistered --;
10549 if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
10551 free(cmailCommentList[lastLoadGameNumber - 1]);
10552 cmailCommentList[lastLoadGameNumber - 1] = NULL;
10556 if (cmailOldMove == -1) {
10557 DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
10561 if (currentMove > cmailOldMove + 1) {
10562 DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
10566 if (currentMove < cmailOldMove) {
10567 DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
10571 if (forwardMostMove > currentMove) {
10572 /* Silently truncate extra moves */
10576 if ( (currentMove == cmailOldMove + 1)
10577 || ( (currentMove == cmailOldMove)
10578 && ( (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
10579 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
10580 if (gameInfo.result != GameUnfinished) {
10581 cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
10584 if (commentList[currentMove] != NULL) {
10585 cmailCommentList[lastLoadGameNumber - 1]
10586 = StrSave(commentList[currentMove]);
10588 strcpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1]);
10590 if (appData.debugMode)
10591 fprintf(debugFP, "Saving %s for game %d\n",
10592 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
10595 "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
10597 f = fopen(string, "w");
10598 if (appData.oldSaveStyle) {
10599 SaveGameOldStyle(f); /* also closes the file */
10601 sprintf(string, "%s.pos.out", appData.cmailGameName);
10602 f = fopen(string, "w");
10603 SavePosition(f, 0, NULL); /* also closes the file */
10605 fprintf(f, "{--------------\n");
10606 PrintPosition(f, currentMove);
10607 fprintf(f, "--------------}\n\n");
10609 SaveGame(f, 0, NULL); /* also closes the file*/
10612 cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
10613 nCmailMovesRegistered ++;
10614 } else if (nCmailGames == 1) {
10615 DisplayError(_("You have not made a move yet"), 0);
10626 static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
10627 FILE *commandOutput;
10628 char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
10629 int nBytes = 0; /* Suppress warnings on uninitialized variables */
10635 if (! cmailMsgLoaded) {
10636 DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
10640 if (nCmailGames == nCmailResults) {
10641 DisplayError(_("No unfinished games"), 0);
10645 #if CMAIL_PROHIBIT_REMAIL
10646 if (cmailMailedMove) {
10647 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);
10648 DisplayError(msg, 0);
10653 if (! (cmailMailedMove || RegisterMove())) return;
10655 if ( cmailMailedMove
10656 || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
10657 sprintf(string, partCommandString,
10658 appData.debugMode ? " -v" : "", appData.cmailGameName);
10659 commandOutput = popen(string, "r");
10661 if (commandOutput == NULL) {
10662 DisplayError(_("Failed to invoke cmail"), 0);
10664 for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
10665 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
10667 if (nBuffers > 1) {
10668 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
10669 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
10670 nBytes = MSG_SIZ - 1;
10672 (void) memcpy(msg, buffer, nBytes);
10674 *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
10676 if(StrStr(msg, "Mailed cmail message to ") != NULL) {
10677 cmailMailedMove = TRUE; /* Prevent >1 moves */
10680 for (i = 0; i < nCmailGames; i ++) {
10681 if (cmailResult[i] == CMAIL_NOT_RESULT) {
10686 && ( (arcDir = (char *) getenv("CMAIL_ARCDIR"))
10688 sprintf(buffer, "%s/%s.%s.archive",
10690 appData.cmailGameName,
10692 LoadGameFromFile(buffer, 1, buffer, FALSE);
10693 cmailMsgLoaded = FALSE;
10697 DisplayInformation(msg);
10698 pclose(commandOutput);
10701 if ((*cmailMsg) != '\0') {
10702 DisplayInformation(cmailMsg);
10707 #endif /* !WIN32 */
10716 int prependComma = 0;
10718 char string[MSG_SIZ]; /* Space for game-list */
10721 if (!cmailMsgLoaded) return "";
10723 if (cmailMailedMove) {
10724 sprintf(cmailMsg, _("Waiting for reply from opponent\n"));
10726 /* Create a list of games left */
10727 sprintf(string, "[");
10728 for (i = 0; i < nCmailGames; i ++) {
10729 if (! ( cmailMoveRegistered[i]
10730 || (cmailResult[i] == CMAIL_OLD_RESULT))) {
10731 if (prependComma) {
10732 sprintf(number, ",%d", i + 1);
10734 sprintf(number, "%d", i + 1);
10738 strcat(string, number);
10741 strcat(string, "]");
10743 if (nCmailMovesRegistered + nCmailResults == 0) {
10744 switch (nCmailGames) {
10747 _("Still need to make move for game\n"));
10752 _("Still need to make moves for both games\n"));
10757 _("Still need to make moves for all %d games\n"),
10762 switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
10765 _("Still need to make a move for game %s\n"),
10770 if (nCmailResults == nCmailGames) {
10771 sprintf(cmailMsg, _("No unfinished games\n"));
10773 sprintf(cmailMsg, _("Ready to send mail\n"));
10779 _("Still need to make moves for games %s\n"),
10791 if (gameMode == Training)
10792 SetTrainingModeOff();
10795 cmailMsgLoaded = FALSE;
10796 if (appData.icsActive) {
10797 SendToICS(ics_prefix);
10798 SendToICS("refresh\n");
10808 /* Give up on clean exit */
10812 /* Keep trying for clean exit */
10816 if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
10818 if (telnetISR != NULL) {
10819 RemoveInputSource(telnetISR);
10821 if (icsPR != NoProc) {
10822 DestroyChildProcess(icsPR, TRUE);
10825 /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
10826 GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
10828 /* [HGM] crash: the above GameEnds() is a dud if another one was running */
10829 /* make sure this other one finishes before killing it! */
10830 if(endingGame) { int count = 0;
10831 if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
10832 while(endingGame && count++ < 10) DoSleep(1);
10833 if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
10836 /* Kill off chess programs */
10837 if (first.pr != NoProc) {
10840 DoSleep( appData.delayBeforeQuit );
10841 SendToProgram("quit\n", &first);
10842 DoSleep( appData.delayAfterQuit );
10843 DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
10845 if (second.pr != NoProc) {
10846 DoSleep( appData.delayBeforeQuit );
10847 SendToProgram("quit\n", &second);
10848 DoSleep( appData.delayAfterQuit );
10849 DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
10851 if (first.isr != NULL) {
10852 RemoveInputSource(first.isr);
10854 if (second.isr != NULL) {
10855 RemoveInputSource(second.isr);
10858 ShutDownFrontEnd();
10865 if (appData.debugMode)
10866 fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
10870 if (gameMode == MachinePlaysWhite ||
10871 gameMode == MachinePlaysBlack) {
10874 DisplayBothClocks();
10876 if (gameMode == PlayFromGameFile) {
10877 if (appData.timeDelay >= 0)
10878 AutoPlayGameLoop();
10879 } else if (gameMode == IcsExamining && pauseExamInvalid) {
10880 Reset(FALSE, TRUE);
10881 SendToICS(ics_prefix);
10882 SendToICS("refresh\n");
10883 } else if (currentMove < forwardMostMove) {
10884 ForwardInner(forwardMostMove);
10886 pauseExamInvalid = FALSE;
10888 switch (gameMode) {
10892 pauseExamForwardMostMove = forwardMostMove;
10893 pauseExamInvalid = FALSE;
10896 case IcsPlayingWhite:
10897 case IcsPlayingBlack:
10901 case PlayFromGameFile:
10902 (void) StopLoadGameTimer();
10906 case BeginningOfGame:
10907 if (appData.icsActive) return;
10908 /* else fall through */
10909 case MachinePlaysWhite:
10910 case MachinePlaysBlack:
10911 case TwoMachinesPlay:
10912 if (forwardMostMove == 0)
10913 return; /* don't pause if no one has moved */
10914 if ((gameMode == MachinePlaysWhite &&
10915 !WhiteOnMove(forwardMostMove)) ||
10916 (gameMode == MachinePlaysBlack &&
10917 WhiteOnMove(forwardMostMove))) {
10930 char title[MSG_SIZ];
10932 if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
10933 strcpy(title, _("Edit comment"));
10935 sprintf(title, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
10936 WhiteOnMove(currentMove - 1) ? " " : ".. ",
10937 parseList[currentMove - 1]);
10940 EditCommentPopUp(currentMove, title, commentList[currentMove]);
10947 char *tags = PGNTags(&gameInfo);
10948 EditTagsPopUp(tags);
10955 if (appData.noChessProgram || gameMode == AnalyzeMode)
10958 if (gameMode != AnalyzeFile) {
10959 if (!appData.icsEngineAnalyze) {
10961 if (gameMode != EditGame) return;
10963 ResurrectChessProgram();
10964 SendToProgram("analyze\n", &first);
10965 first.analyzing = TRUE;
10966 /*first.maybeThinking = TRUE;*/
10967 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
10968 EngineOutputPopUp();
10970 if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
10975 StartAnalysisClock();
10976 GetTimeMark(&lastNodeCountTime);
10983 if (appData.noChessProgram || gameMode == AnalyzeFile)
10986 if (gameMode != AnalyzeMode) {
10988 if (gameMode != EditGame) return;
10989 ResurrectChessProgram();
10990 SendToProgram("analyze\n", &first);
10991 first.analyzing = TRUE;
10992 /*first.maybeThinking = TRUE;*/
10993 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
10994 EngineOutputPopUp();
10996 gameMode = AnalyzeFile;
11001 StartAnalysisClock();
11002 GetTimeMark(&lastNodeCountTime);
11007 MachineWhiteEvent()
11010 char *bookHit = NULL;
11012 if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
11016 if (gameMode == PlayFromGameFile ||
11017 gameMode == TwoMachinesPlay ||
11018 gameMode == Training ||
11019 gameMode == AnalyzeMode ||
11020 gameMode == EndOfGame)
11023 if (gameMode == EditPosition)
11024 EditPositionDone(TRUE);
11026 if (!WhiteOnMove(currentMove)) {
11027 DisplayError(_("It is not White's turn"), 0);
11031 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
11034 if (gameMode == EditGame || gameMode == AnalyzeMode ||
11035 gameMode == AnalyzeFile)
11038 ResurrectChessProgram(); /* in case it isn't running */
11039 if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
11040 gameMode = MachinePlaysWhite;
11043 gameMode = MachinePlaysWhite;
11047 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
11049 if (first.sendName) {
11050 sprintf(buf, "name %s\n", gameInfo.black);
11051 SendToProgram(buf, &first);
11053 if (first.sendTime) {
11054 if (first.useColors) {
11055 SendToProgram("black\n", &first); /*gnu kludge*/
11057 SendTimeRemaining(&first, TRUE);
11059 if (first.useColors) {
11060 SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
11062 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
11063 SetMachineThinkingEnables();
11064 first.maybeThinking = TRUE;
11068 if (appData.autoFlipView && !flipView) {
11069 flipView = !flipView;
11070 DrawPosition(FALSE, NULL);
11071 DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;
11074 if(bookHit) { // [HGM] book: simulate book reply
11075 static char bookMove[MSG_SIZ]; // a bit generous?
11077 programStats.nodes = programStats.depth = programStats.time =
11078 programStats.score = programStats.got_only_move = 0;
11079 sprintf(programStats.movelist, "%s (xbook)", bookHit);
11081 strcpy(bookMove, "move ");
11082 strcat(bookMove, bookHit);
11083 HandleMachineMove(bookMove, &first);
11088 MachineBlackEvent()
11091 char *bookHit = NULL;
11093 if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
11097 if (gameMode == PlayFromGameFile
11098 || gameMode == TwoMachinesPlay
11099 || gameMode == Training
11100 || gameMode == AnalyzeMode
11101 || gameMode == EndOfGame)
11104 if (gameMode == EditPosition)
11105 EditPositionDone(TRUE);
11107 if (WhiteOnMove(currentMove))
11109 DisplayError(_("It is not Black's turn"), 0);
11113 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
11116 if (gameMode == EditGame || gameMode == AnalyzeMode
11117 || gameMode == AnalyzeFile)
11120 ResurrectChessProgram(); /* in case it isn't running */
11121 gameMode = MachinePlaysBlack;
11125 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
11127 if (first.sendName)
11129 sprintf(buf, "name %s\n", gameInfo.white);
11130 SendToProgram(buf, &first);
11132 if (first.sendTime)
11134 if (first.useColors)
11136 SendToProgram("white\n", &first); /*gnu kludge*/
11138 SendTimeRemaining(&first, FALSE);
11140 if (first.useColors)
11142 SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
11144 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
11145 SetMachineThinkingEnables();
11146 first.maybeThinking = TRUE;
11149 if (appData.autoFlipView && flipView)
11151 flipView = !flipView;
11152 DrawPosition(FALSE, NULL);
11153 DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;
11156 { // [HGM] book: simulate book reply
11157 static char bookMove[MSG_SIZ]; // a bit generous?
11159 programStats.nodes = programStats.depth = programStats.time
11160 = programStats.score = programStats.got_only_move = 0;
11161 sprintf(programStats.movelist, "%s (xbook)", bookHit);
11163 strcpy(bookMove, "move ");
11164 strcat(bookMove, bookHit);
11165 HandleMachineMove(bookMove, &first);
11172 DisplayTwoMachinesTitle()
11175 if (appData.matchGames > 0) {
11176 if (first.twoMachinesColor[0] == 'w') {
11177 sprintf(buf, "%s vs. %s (%d-%d-%d)",
11178 gameInfo.white, gameInfo.black,
11179 first.matchWins, second.matchWins,
11180 matchGame - 1 - (first.matchWins + second.matchWins));
11182 sprintf(buf, "%s vs. %s (%d-%d-%d)",
11183 gameInfo.white, gameInfo.black,
11184 second.matchWins, first.matchWins,
11185 matchGame - 1 - (first.matchWins + second.matchWins));
11188 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
11194 TwoMachinesEvent P((void))
11198 ChessProgramState *onmove;
11199 char *bookHit = NULL;
11201 if (appData.noChessProgram) return;
11203 switch (gameMode) {
11204 case TwoMachinesPlay:
11206 case MachinePlaysWhite:
11207 case MachinePlaysBlack:
11208 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
11209 DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
11213 case BeginningOfGame:
11214 case PlayFromGameFile:
11217 if (gameMode != EditGame) return;
11220 EditPositionDone(TRUE);
11231 // forwardMostMove = currentMove;
11232 TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
11233 ResurrectChessProgram(); /* in case first program isn't running */
11235 if (second.pr == NULL) {
11236 StartChessProgram(&second);
11237 if (second.protocolVersion == 1) {
11238 TwoMachinesEventIfReady();
11240 /* kludge: allow timeout for initial "feature" command */
11242 DisplayMessage("", _("Starting second chess program"));
11243 ScheduleDelayedEvent(TwoMachinesEventIfReady, FEATURE_TIMEOUT);
11247 DisplayMessage("", "");
11248 InitChessProgram(&second, FALSE);
11249 SendToProgram("force\n", &second);
11250 if (startedFromSetupPosition) {
11251 SendBoard(&second, backwardMostMove);
11252 if (appData.debugMode) {
11253 fprintf(debugFP, "Two Machines\n");
11256 for (i = backwardMostMove; i < forwardMostMove; i++) {
11257 SendMoveToProgram(i, &second);
11260 gameMode = TwoMachinesPlay;
11264 DisplayTwoMachinesTitle();
11266 if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
11272 SendToProgram(first.computerString, &first);
11273 if (first.sendName) {
11274 sprintf(buf, "name %s\n", second.tidy);
11275 SendToProgram(buf, &first);
11277 SendToProgram(second.computerString, &second);
11278 if (second.sendName) {
11279 sprintf(buf, "name %s\n", first.tidy);
11280 SendToProgram(buf, &second);
11284 if (!first.sendTime || !second.sendTime) {
11285 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11286 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11288 if (onmove->sendTime) {
11289 if (onmove->useColors) {
11290 SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
11292 SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
11294 if (onmove->useColors) {
11295 SendToProgram(onmove->twoMachinesColor, onmove);
11297 bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
11298 // SendToProgram("go\n", onmove);
11299 onmove->maybeThinking = TRUE;
11300 SetMachineThinkingEnables();
11304 if(bookHit) { // [HGM] book: simulate book reply
11305 static char bookMove[MSG_SIZ]; // a bit generous?
11307 programStats.nodes = programStats.depth = programStats.time =
11308 programStats.score = programStats.got_only_move = 0;
11309 sprintf(programStats.movelist, "%s (xbook)", bookHit);
11311 strcpy(bookMove, "move ");
11312 strcat(bookMove, bookHit);
11313 savedMessage = bookMove; // args for deferred call
11314 savedState = onmove;
11315 ScheduleDelayedEvent(DeferredBookMove, 1);
11322 if (gameMode == Training) {
11323 SetTrainingModeOff();
11324 gameMode = PlayFromGameFile;
11325 DisplayMessage("", _("Training mode off"));
11327 gameMode = Training;
11328 animateTraining = appData.animate;
11330 /* make sure we are not already at the end of the game */
11331 if (currentMove < forwardMostMove) {
11332 SetTrainingModeOn();
11333 DisplayMessage("", _("Training mode on"));
11335 gameMode = PlayFromGameFile;
11336 DisplayError(_("Already at end of game"), 0);
11345 if (!appData.icsActive) return;
11346 switch (gameMode) {
11347 case IcsPlayingWhite:
11348 case IcsPlayingBlack:
11351 case BeginningOfGame:
11359 EditPositionDone(TRUE);
11372 gameMode = IcsIdle;
11383 switch (gameMode) {
11385 SetTrainingModeOff();
11387 case MachinePlaysWhite:
11388 case MachinePlaysBlack:
11389 case BeginningOfGame:
11390 SendToProgram("force\n", &first);
11391 SetUserThinkingEnables();
11393 case PlayFromGameFile:
11394 (void) StopLoadGameTimer();
11395 if (gameFileFP != NULL) {
11400 EditPositionDone(TRUE);
11405 SendToProgram("force\n", &first);
11407 case TwoMachinesPlay:
11408 GameEnds((ChessMove) 0, NULL, GE_PLAYER);
11409 ResurrectChessProgram();
11410 SetUserThinkingEnables();
11413 ResurrectChessProgram();
11415 case IcsPlayingBlack:
11416 case IcsPlayingWhite:
11417 DisplayError(_("Warning: You are still playing a game"), 0);
11420 DisplayError(_("Warning: You are still observing a game"), 0);
11423 DisplayError(_("Warning: You are still examining a game"), 0);
11434 first.offeredDraw = second.offeredDraw = 0;
11436 if (gameMode == PlayFromGameFile) {
11437 whiteTimeRemaining = timeRemaining[0][currentMove];
11438 blackTimeRemaining = timeRemaining[1][currentMove];
11442 if (gameMode == MachinePlaysWhite ||
11443 gameMode == MachinePlaysBlack ||
11444 gameMode == TwoMachinesPlay ||
11445 gameMode == EndOfGame) {
11446 i = forwardMostMove;
11447 while (i > currentMove) {
11448 SendToProgram("undo\n", &first);
11451 whiteTimeRemaining = timeRemaining[0][currentMove];
11452 blackTimeRemaining = timeRemaining[1][currentMove];
11453 DisplayBothClocks();
11454 if (whiteFlag || blackFlag) {
11455 whiteFlag = blackFlag = 0;
11460 gameMode = EditGame;
11467 EditPositionEvent()
11469 if (gameMode == EditPosition) {
11475 if (gameMode != EditGame) return;
11477 gameMode = EditPosition;
11480 if (currentMove > 0)
11481 CopyBoard(boards[0], boards[currentMove]);
11483 blackPlaysFirst = !WhiteOnMove(currentMove);
11485 currentMove = forwardMostMove = backwardMostMove = 0;
11486 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11493 /* [DM] icsEngineAnalyze - possible call from other functions */
11494 if (appData.icsEngineAnalyze) {
11495 appData.icsEngineAnalyze = FALSE;
11497 DisplayMessage("",_("Close ICS engine analyze..."));
11499 if (first.analysisSupport && first.analyzing) {
11500 SendToProgram("exit\n", &first);
11501 first.analyzing = FALSE;
11503 thinkOutput[0] = NULLCHAR;
11507 EditPositionDone(Boolean fakeRights)
11509 int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
11511 startedFromSetupPosition = TRUE;
11512 InitChessProgram(&first, FALSE);
11513 if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
11514 boards[0][EP_STATUS] = EP_NONE;
11515 boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
11516 if(boards[0][0][BOARD_WIDTH>>1] == king) {
11517 boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : NoRights;
11518 boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
11519 } else boards[0][CASTLING][2] = NoRights;
11520 if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
11521 boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : NoRights;
11522 boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
11523 } else boards[0][CASTLING][5] = NoRights;
11525 SendToProgram("force\n", &first);
11526 if (blackPlaysFirst) {
11527 strcpy(moveList[0], "");
11528 strcpy(parseList[0], "");
11529 currentMove = forwardMostMove = backwardMostMove = 1;
11530 CopyBoard(boards[1], boards[0]);
11532 currentMove = forwardMostMove = backwardMostMove = 0;
11534 SendBoard(&first, forwardMostMove);
11535 if (appData.debugMode) {
11536 fprintf(debugFP, "EditPosDone\n");
11539 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11540 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11541 gameMode = EditGame;
11543 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11544 ClearHighlights(); /* [AS] */
11547 /* Pause for `ms' milliseconds */
11548 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
11558 } while (SubtractTimeMarks(&m2, &m1) < ms);
11561 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
11563 SendMultiLineToICS(buf)
11566 char temp[MSG_SIZ+1], *p;
11573 strncpy(temp, buf, len);
11578 if (*p == '\n' || *p == '\r')
11583 strcat(temp, "\n");
11585 SendToPlayer(temp, strlen(temp));
11589 SetWhiteToPlayEvent()
11591 if (gameMode == EditPosition) {
11592 blackPlaysFirst = FALSE;
11593 DisplayBothClocks(); /* works because currentMove is 0 */
11594 } else if (gameMode == IcsExamining) {
11595 SendToICS(ics_prefix);
11596 SendToICS("tomove white\n");
11601 SetBlackToPlayEvent()
11603 if (gameMode == EditPosition) {
11604 blackPlaysFirst = TRUE;
11605 currentMove = 1; /* kludge */
11606 DisplayBothClocks();
11608 } else if (gameMode == IcsExamining) {
11609 SendToICS(ics_prefix);
11610 SendToICS("tomove black\n");
11615 EditPositionMenuEvent(selection, x, y)
11616 ChessSquare selection;
11620 ChessSquare piece = boards[0][y][x];
11622 if (gameMode != EditPosition && gameMode != IcsExamining) return;
11624 switch (selection) {
11626 if (gameMode == IcsExamining && ics_type == ICS_FICS) {
11627 SendToICS(ics_prefix);
11628 SendToICS("bsetup clear\n");
11629 } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
11630 SendToICS(ics_prefix);
11631 SendToICS("clearboard\n");
11633 for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
11634 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
11635 for (y = 0; y < BOARD_HEIGHT; y++) {
11636 if (gameMode == IcsExamining) {
11637 if (boards[currentMove][y][x] != EmptySquare) {
11638 sprintf(buf, "%sx@%c%c\n", ics_prefix,
11643 boards[0][y][x] = p;
11648 if (gameMode == EditPosition) {
11649 DrawPosition(FALSE, boards[0]);
11654 SetWhiteToPlayEvent();
11658 SetBlackToPlayEvent();
11662 if (gameMode == IcsExamining) {
11663 if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
11664 sprintf(buf, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
11667 if(x < BOARD_LEFT || x >= BOARD_RGHT) {
11668 if(x == BOARD_LEFT-2) {
11669 if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
11670 boards[0][y][1] = 0;
11672 if(x == BOARD_RGHT+1) {
11673 if(y >= gameInfo.holdingsSize) break;
11674 boards[0][y][BOARD_WIDTH-2] = 0;
11677 boards[0][y][x] = EmptySquare;
11678 DrawPosition(FALSE, boards[0]);
11683 if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
11684 piece >= (int)BlackPawn && piece < (int)BlackMan ) {
11685 selection = (ChessSquare) (PROMOTED piece);
11686 } else if(piece == EmptySquare) selection = WhiteSilver;
11687 else selection = (ChessSquare)((int)piece - 1);
11691 if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
11692 piece > (int)BlackMan && piece <= (int)BlackKing ) {
11693 selection = (ChessSquare) (DEMOTED piece);
11694 } else if(piece == EmptySquare) selection = BlackSilver;
11695 else selection = (ChessSquare)((int)piece + 1);
11700 if(gameInfo.variant == VariantShatranj ||
11701 gameInfo.variant == VariantXiangqi ||
11702 gameInfo.variant == VariantCourier ||
11703 gameInfo.variant == VariantMakruk )
11704 selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
11709 if(gameInfo.variant == VariantXiangqi)
11710 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
11711 if(gameInfo.variant == VariantKnightmate)
11712 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
11715 if (gameMode == IcsExamining) {
11716 if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
11717 sprintf(buf, "%s%c@%c%c\n", ics_prefix,
11718 PieceToChar(selection), AAA + x, ONE + y);
11721 if(x < BOARD_LEFT || x >= BOARD_RGHT) {
11723 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
11724 n = PieceToNumber(selection - BlackPawn);
11725 if(n > gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
11726 boards[0][BOARD_HEIGHT-1-n][0] = selection;
11727 boards[0][BOARD_HEIGHT-1-n][1]++;
11729 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
11730 n = PieceToNumber(selection);
11731 if(n > gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
11732 boards[0][n][BOARD_WIDTH-1] = selection;
11733 boards[0][n][BOARD_WIDTH-2]++;
11736 boards[0][y][x] = selection;
11737 DrawPosition(TRUE, boards[0]);
11745 DropMenuEvent(selection, x, y)
11746 ChessSquare selection;
11749 ChessMove moveType;
11751 switch (gameMode) {
11752 case IcsPlayingWhite:
11753 case MachinePlaysBlack:
11754 if (!WhiteOnMove(currentMove)) {
11755 DisplayMoveError(_("It is Black's turn"));
11758 moveType = WhiteDrop;
11760 case IcsPlayingBlack:
11761 case MachinePlaysWhite:
11762 if (WhiteOnMove(currentMove)) {
11763 DisplayMoveError(_("It is White's turn"));
11766 moveType = BlackDrop;
11769 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
11775 if (moveType == BlackDrop && selection < BlackPawn) {
11776 selection = (ChessSquare) ((int) selection
11777 + (int) BlackPawn - (int) WhitePawn);
11779 if (boards[currentMove][y][x] != EmptySquare) {
11780 DisplayMoveError(_("That square is occupied"));
11784 FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
11790 /* Accept a pending offer of any kind from opponent */
11792 if (appData.icsActive) {
11793 SendToICS(ics_prefix);
11794 SendToICS("accept\n");
11795 } else if (cmailMsgLoaded) {
11796 if (currentMove == cmailOldMove &&
11797 commentList[cmailOldMove] != NULL &&
11798 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11799 "Black offers a draw" : "White offers a draw")) {
11801 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11802 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
11804 DisplayError(_("There is no pending offer on this move"), 0);
11805 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11808 /* Not used for offers from chess program */
11815 /* Decline a pending offer of any kind from opponent */
11817 if (appData.icsActive) {
11818 SendToICS(ics_prefix);
11819 SendToICS("decline\n");
11820 } else if (cmailMsgLoaded) {
11821 if (currentMove == cmailOldMove &&
11822 commentList[cmailOldMove] != NULL &&
11823 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11824 "Black offers a draw" : "White offers a draw")) {
11826 AppendComment(cmailOldMove, "Draw declined", TRUE);
11827 DisplayComment(cmailOldMove - 1, "Draw declined");
11830 DisplayError(_("There is no pending offer on this move"), 0);
11833 /* Not used for offers from chess program */
11840 /* Issue ICS rematch command */
11841 if (appData.icsActive) {
11842 SendToICS(ics_prefix);
11843 SendToICS("rematch\n");
11850 /* Call your opponent's flag (claim a win on time) */
11851 if (appData.icsActive) {
11852 SendToICS(ics_prefix);
11853 SendToICS("flag\n");
11855 switch (gameMode) {
11858 case MachinePlaysWhite:
11861 GameEnds(GameIsDrawn, "Both players ran out of time",
11864 GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
11866 DisplayError(_("Your opponent is not out of time"), 0);
11869 case MachinePlaysBlack:
11872 GameEnds(GameIsDrawn, "Both players ran out of time",
11875 GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
11877 DisplayError(_("Your opponent is not out of time"), 0);
11887 /* Offer draw or accept pending draw offer from opponent */
11889 if (appData.icsActive) {
11890 /* Note: tournament rules require draw offers to be
11891 made after you make your move but before you punch
11892 your clock. Currently ICS doesn't let you do that;
11893 instead, you immediately punch your clock after making
11894 a move, but you can offer a draw at any time. */
11896 SendToICS(ics_prefix);
11897 SendToICS("draw\n");
11898 } else if (cmailMsgLoaded) {
11899 if (currentMove == cmailOldMove &&
11900 commentList[cmailOldMove] != NULL &&
11901 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11902 "Black offers a draw" : "White offers a draw")) {
11903 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11904 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
11905 } else if (currentMove == cmailOldMove + 1) {
11906 char *offer = WhiteOnMove(cmailOldMove) ?
11907 "White offers a draw" : "Black offers a draw";
11908 AppendComment(currentMove, offer, TRUE);
11909 DisplayComment(currentMove - 1, offer);
11910 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
11912 DisplayError(_("You must make your move before offering a draw"), 0);
11913 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11915 } else if (first.offeredDraw) {
11916 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
11918 if (first.sendDrawOffers) {
11919 SendToProgram("draw\n", &first);
11920 userOfferedDraw = TRUE;
11928 /* Offer Adjourn or accept pending Adjourn offer from opponent */
11930 if (appData.icsActive) {
11931 SendToICS(ics_prefix);
11932 SendToICS("adjourn\n");
11934 /* Currently GNU Chess doesn't offer or accept Adjourns */
11942 /* Offer Abort or accept pending Abort offer from opponent */
11944 if (appData.icsActive) {
11945 SendToICS(ics_prefix);
11946 SendToICS("abort\n");
11948 GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
11955 /* Resign. You can do this even if it's not your turn. */
11957 if (appData.icsActive) {
11958 SendToICS(ics_prefix);
11959 SendToICS("resign\n");
11961 switch (gameMode) {
11962 case MachinePlaysWhite:
11963 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11965 case MachinePlaysBlack:
11966 GameEnds(BlackWins, "White resigns", GE_PLAYER);
11969 if (cmailMsgLoaded) {
11971 if (WhiteOnMove(cmailOldMove)) {
11972 GameEnds(BlackWins, "White resigns", GE_PLAYER);
11974 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11976 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
11987 StopObservingEvent()
11989 /* Stop observing current games */
11990 SendToICS(ics_prefix);
11991 SendToICS("unobserve\n");
11995 StopExaminingEvent()
11997 /* Stop observing current game */
11998 SendToICS(ics_prefix);
11999 SendToICS("unexamine\n");
12003 ForwardInner(target)
12008 if (appData.debugMode)
12009 fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
12010 target, currentMove, forwardMostMove);
12012 if (gameMode == EditPosition)
12015 if (gameMode == PlayFromGameFile && !pausing)
12018 if (gameMode == IcsExamining && pausing)
12019 limit = pauseExamForwardMostMove;
12021 limit = forwardMostMove;
12023 if (target > limit) target = limit;
12025 if (target > 0 && moveList[target - 1][0]) {
12026 int fromX, fromY, toX, toY;
12027 toX = moveList[target - 1][2] - AAA;
12028 toY = moveList[target - 1][3] - ONE;
12029 if (moveList[target - 1][1] == '@') {
12030 if (appData.highlightLastMove) {
12031 SetHighlights(-1, -1, toX, toY);
12034 fromX = moveList[target - 1][0] - AAA;
12035 fromY = moveList[target - 1][1] - ONE;
12036 if (target == currentMove + 1) {
12037 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
12039 if (appData.highlightLastMove) {
12040 SetHighlights(fromX, fromY, toX, toY);
12044 if (gameMode == EditGame || gameMode == AnalyzeMode ||
12045 gameMode == Training || gameMode == PlayFromGameFile ||
12046 gameMode == AnalyzeFile) {
12047 while (currentMove < target) {
12048 SendMoveToProgram(currentMove++, &first);
12051 currentMove = target;
12054 if (gameMode == EditGame || gameMode == EndOfGame) {
12055 whiteTimeRemaining = timeRemaining[0][currentMove];
12056 blackTimeRemaining = timeRemaining[1][currentMove];
12058 DisplayBothClocks();
12059 DisplayMove(currentMove - 1);
12060 DrawPosition(FALSE, boards[currentMove]);
12061 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
12062 if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
12063 DisplayComment(currentMove - 1, commentList[currentMove]);
12071 if (gameMode == IcsExamining && !pausing) {
12072 SendToICS(ics_prefix);
12073 SendToICS("forward\n");
12075 ForwardInner(currentMove + 1);
12082 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12083 /* to optimze, we temporarily turn off analysis mode while we feed
12084 * the remaining moves to the engine. Otherwise we get analysis output
12087 if (first.analysisSupport) {
12088 SendToProgram("exit\nforce\n", &first);
12089 first.analyzing = FALSE;
12093 if (gameMode == IcsExamining && !pausing) {
12094 SendToICS(ics_prefix);
12095 SendToICS("forward 999999\n");
12097 ForwardInner(forwardMostMove);
12100 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12101 /* we have fed all the moves, so reactivate analysis mode */
12102 SendToProgram("analyze\n", &first);
12103 first.analyzing = TRUE;
12104 /*first.maybeThinking = TRUE;*/
12105 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12110 BackwardInner(target)
12113 int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
12115 if (appData.debugMode)
12116 fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
12117 target, currentMove, forwardMostMove);
12119 if (gameMode == EditPosition) return;
12120 if (currentMove <= backwardMostMove) {
12122 DrawPosition(full_redraw, boards[currentMove]);
12125 if (gameMode == PlayFromGameFile && !pausing)
12128 if (moveList[target][0]) {
12129 int fromX, fromY, toX, toY;
12130 toX = moveList[target][2] - AAA;
12131 toY = moveList[target][3] - ONE;
12132 if (moveList[target][1] == '@') {
12133 if (appData.highlightLastMove) {
12134 SetHighlights(-1, -1, toX, toY);
12137 fromX = moveList[target][0] - AAA;
12138 fromY = moveList[target][1] - ONE;
12139 if (target == currentMove - 1) {
12140 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
12142 if (appData.highlightLastMove) {
12143 SetHighlights(fromX, fromY, toX, toY);
12147 if (gameMode == EditGame || gameMode==AnalyzeMode ||
12148 gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
12149 while (currentMove > target) {
12150 SendToProgram("undo\n", &first);
12154 currentMove = target;
12157 if (gameMode == EditGame || gameMode == EndOfGame) {
12158 whiteTimeRemaining = timeRemaining[0][currentMove];
12159 blackTimeRemaining = timeRemaining[1][currentMove];
12161 DisplayBothClocks();
12162 DisplayMove(currentMove - 1);
12163 DrawPosition(full_redraw, boards[currentMove]);
12164 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
12165 // [HGM] PV info: routine tests if comment empty
12166 DisplayComment(currentMove - 1, commentList[currentMove]);
12172 if (gameMode == IcsExamining && !pausing) {
12173 SendToICS(ics_prefix);
12174 SendToICS("backward\n");
12176 BackwardInner(currentMove - 1);
12183 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12184 /* to optimize, we temporarily turn off analysis mode while we undo
12185 * all the moves. Otherwise we get analysis output after each undo.
12187 if (first.analysisSupport) {
12188 SendToProgram("exit\nforce\n", &first);
12189 first.analyzing = FALSE;
12193 if (gameMode == IcsExamining && !pausing) {
12194 SendToICS(ics_prefix);
12195 SendToICS("backward 999999\n");
12197 BackwardInner(backwardMostMove);
12200 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12201 /* we have fed all the moves, so reactivate analysis mode */
12202 SendToProgram("analyze\n", &first);
12203 first.analyzing = TRUE;
12204 /*first.maybeThinking = TRUE;*/
12205 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12212 if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
12213 if (to >= forwardMostMove) to = forwardMostMove;
12214 if (to <= backwardMostMove) to = backwardMostMove;
12215 if (to < currentMove) {
12225 if(PopTail(TRUE)) { // [HGM] vari: restore old game tail
12228 if (gameMode != IcsExamining) {
12229 DisplayError(_("You are not examining a game"), 0);
12233 DisplayError(_("You can't revert while pausing"), 0);
12236 SendToICS(ics_prefix);
12237 SendToICS("revert\n");
12243 switch (gameMode) {
12244 case MachinePlaysWhite:
12245 case MachinePlaysBlack:
12246 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
12247 DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
12250 if (forwardMostMove < 2) return;
12251 currentMove = forwardMostMove = forwardMostMove - 2;
12252 whiteTimeRemaining = timeRemaining[0][currentMove];
12253 blackTimeRemaining = timeRemaining[1][currentMove];
12254 DisplayBothClocks();
12255 DisplayMove(currentMove - 1);
12256 ClearHighlights();/*!! could figure this out*/
12257 DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
12258 SendToProgram("remove\n", &first);
12259 /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
12262 case BeginningOfGame:
12266 case IcsPlayingWhite:
12267 case IcsPlayingBlack:
12268 if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
12269 SendToICS(ics_prefix);
12270 SendToICS("takeback 2\n");
12272 SendToICS(ics_prefix);
12273 SendToICS("takeback 1\n");
12282 ChessProgramState *cps;
12284 switch (gameMode) {
12285 case MachinePlaysWhite:
12286 if (!WhiteOnMove(forwardMostMove)) {
12287 DisplayError(_("It is your turn"), 0);
12292 case MachinePlaysBlack:
12293 if (WhiteOnMove(forwardMostMove)) {
12294 DisplayError(_("It is your turn"), 0);
12299 case TwoMachinesPlay:
12300 if (WhiteOnMove(forwardMostMove) ==
12301 (first.twoMachinesColor[0] == 'w')) {
12307 case BeginningOfGame:
12311 SendToProgram("?\n", cps);
12315 TruncateGameEvent()
12318 if (gameMode != EditGame) return;
12325 CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
12326 if (forwardMostMove > currentMove) {
12327 if (gameInfo.resultDetails != NULL) {
12328 free(gameInfo.resultDetails);
12329 gameInfo.resultDetails = NULL;
12330 gameInfo.result = GameUnfinished;
12332 forwardMostMove = currentMove;
12333 HistorySet(parseList, backwardMostMove, forwardMostMove,
12341 if (appData.noChessProgram) return;
12342 switch (gameMode) {
12343 case MachinePlaysWhite:
12344 if (WhiteOnMove(forwardMostMove)) {
12345 DisplayError(_("Wait until your turn"), 0);
12349 case BeginningOfGame:
12350 case MachinePlaysBlack:
12351 if (!WhiteOnMove(forwardMostMove)) {
12352 DisplayError(_("Wait until your turn"), 0);
12357 DisplayError(_("No hint available"), 0);
12360 SendToProgram("hint\n", &first);
12361 hintRequested = TRUE;
12367 if (appData.noChessProgram) return;
12368 switch (gameMode) {
12369 case MachinePlaysWhite:
12370 if (WhiteOnMove(forwardMostMove)) {
12371 DisplayError(_("Wait until your turn"), 0);
12375 case BeginningOfGame:
12376 case MachinePlaysBlack:
12377 if (!WhiteOnMove(forwardMostMove)) {
12378 DisplayError(_("Wait until your turn"), 0);
12383 EditPositionDone(TRUE);
12385 case TwoMachinesPlay:
12390 SendToProgram("bk\n", &first);
12391 bookOutput[0] = NULLCHAR;
12392 bookRequested = TRUE;
12398 char *tags = PGNTags(&gameInfo);
12399 TagsPopUp(tags, CmailMsg());
12403 /* end button procedures */
12406 PrintPosition(fp, move)
12412 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
12413 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
12414 char c = PieceToChar(boards[move][i][j]);
12415 fputc(c == 'x' ? '.' : c, fp);
12416 fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
12419 if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
12420 fprintf(fp, "white to play\n");
12422 fprintf(fp, "black to play\n");
12429 if (gameInfo.white != NULL) {
12430 fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
12436 /* Find last component of program's own name, using some heuristics */
12438 TidyProgramName(prog, host, buf)
12439 char *prog, *host, buf[MSG_SIZ];
12442 int local = (strcmp(host, "localhost") == 0);
12443 while (!local && (p = strchr(prog, ';')) != NULL) {
12445 while (*p == ' ') p++;
12448 if (*prog == '"' || *prog == '\'') {
12449 q = strchr(prog + 1, *prog);
12451 q = strchr(prog, ' ');
12453 if (q == NULL) q = prog + strlen(prog);
12455 while (p >= prog && *p != '/' && *p != '\\') p--;
12457 if(p == prog && *p == '"') p++;
12458 if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
12459 memcpy(buf, p, q - p);
12460 buf[q - p] = NULLCHAR;
12468 TimeControlTagValue()
12471 if (!appData.clockMode) {
12473 } else if (movesPerSession > 0) {
12474 sprintf(buf, "%d/%ld", movesPerSession, timeControl/1000);
12475 } else if (timeIncrement == 0) {
12476 sprintf(buf, "%ld", timeControl/1000);
12478 sprintf(buf, "%ld+%ld", timeControl/1000, timeIncrement/1000);
12480 return StrSave(buf);
12486 /* This routine is used only for certain modes */
12487 VariantClass v = gameInfo.variant;
12488 ChessMove r = GameUnfinished;
12491 if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
12492 r = gameInfo.result;
12493 p = gameInfo.resultDetails;
12494 gameInfo.resultDetails = NULL;
12496 ClearGameInfo(&gameInfo);
12497 gameInfo.variant = v;
12499 switch (gameMode) {
12500 case MachinePlaysWhite:
12501 gameInfo.event = StrSave( appData.pgnEventHeader );
12502 gameInfo.site = StrSave(HostName());
12503 gameInfo.date = PGNDate();
12504 gameInfo.round = StrSave("-");
12505 gameInfo.white = StrSave(first.tidy);
12506 gameInfo.black = StrSave(UserName());
12507 gameInfo.timeControl = TimeControlTagValue();
12510 case MachinePlaysBlack:
12511 gameInfo.event = StrSave( appData.pgnEventHeader );
12512 gameInfo.site = StrSave(HostName());
12513 gameInfo.date = PGNDate();
12514 gameInfo.round = StrSave("-");
12515 gameInfo.white = StrSave(UserName());
12516 gameInfo.black = StrSave(first.tidy);
12517 gameInfo.timeControl = TimeControlTagValue();
12520 case TwoMachinesPlay:
12521 gameInfo.event = StrSave( appData.pgnEventHeader );
12522 gameInfo.site = StrSave(HostName());
12523 gameInfo.date = PGNDate();
12524 if (matchGame > 0) {
12526 sprintf(buf, "%d", matchGame);
12527 gameInfo.round = StrSave(buf);
12529 gameInfo.round = StrSave("-");
12531 if (first.twoMachinesColor[0] == 'w') {
12532 gameInfo.white = StrSave(first.tidy);
12533 gameInfo.black = StrSave(second.tidy);
12535 gameInfo.white = StrSave(second.tidy);
12536 gameInfo.black = StrSave(first.tidy);
12538 gameInfo.timeControl = TimeControlTagValue();
12542 gameInfo.event = StrSave("Edited game");
12543 gameInfo.site = StrSave(HostName());
12544 gameInfo.date = PGNDate();
12545 gameInfo.round = StrSave("-");
12546 gameInfo.white = StrSave("-");
12547 gameInfo.black = StrSave("-");
12548 gameInfo.result = r;
12549 gameInfo.resultDetails = p;
12553 gameInfo.event = StrSave("Edited position");
12554 gameInfo.site = StrSave(HostName());
12555 gameInfo.date = PGNDate();
12556 gameInfo.round = StrSave("-");
12557 gameInfo.white = StrSave("-");
12558 gameInfo.black = StrSave("-");
12561 case IcsPlayingWhite:
12562 case IcsPlayingBlack:
12567 case PlayFromGameFile:
12568 gameInfo.event = StrSave("Game from non-PGN file");
12569 gameInfo.site = StrSave(HostName());
12570 gameInfo.date = PGNDate();
12571 gameInfo.round = StrSave("-");
12572 gameInfo.white = StrSave("?");
12573 gameInfo.black = StrSave("?");
12582 ReplaceComment(index, text)
12588 while (*text == '\n') text++;
12589 len = strlen(text);
12590 while (len > 0 && text[len - 1] == '\n') len--;
12592 if (commentList[index] != NULL)
12593 free(commentList[index]);
12596 commentList[index] = NULL;
12599 if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
12600 *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
12601 *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
12602 commentList[index] = (char *) malloc(len + 2);
12603 strncpy(commentList[index], text, len);
12604 commentList[index][len] = '\n';
12605 commentList[index][len + 1] = NULLCHAR;
12607 // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
12609 commentList[index] = (char *) malloc(len + 6);
12610 strcpy(commentList[index], "{\n");
12611 strncpy(commentList[index]+2, text, len);
12612 commentList[index][len+2] = NULLCHAR;
12613 while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
12614 strcat(commentList[index], "\n}\n");
12628 if (ch == '\r') continue;
12630 } while (ch != '\0');
12634 AppendComment(index, text, addBraces)
12637 Boolean addBraces; // [HGM] braces: tells if we should add {}
12642 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
12643 text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
12646 while (*text == '\n') text++;
12647 len = strlen(text);
12648 while (len > 0 && text[len - 1] == '\n') len--;
12650 if (len == 0) return;
12652 if (commentList[index] != NULL) {
12653 old = commentList[index];
12654 oldlen = strlen(old);
12655 while(commentList[index][oldlen-1] == '\n')
12656 commentList[index][--oldlen] = NULLCHAR;
12657 commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
12658 strcpy(commentList[index], old);
12660 // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
12661 if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces)) {
12662 if(addBraces) addBraces = FALSE; else { text++; len--; }
12663 while (*text == '\n') { text++; len--; }
12664 commentList[index][--oldlen] = NULLCHAR;
12666 if(addBraces) strcat(commentList[index], "\n{\n");
12667 else strcat(commentList[index], "\n");
12668 strcat(commentList[index], text);
12669 if(addBraces) strcat(commentList[index], "\n}\n");
12670 else strcat(commentList[index], "\n");
12672 commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
12674 strcpy(commentList[index], "{\n");
12675 else commentList[index][0] = NULLCHAR;
12676 strcat(commentList[index], text);
12677 strcat(commentList[index], "\n");
12678 if(addBraces) strcat(commentList[index], "}\n");
12682 static char * FindStr( char * text, char * sub_text )
12684 char * result = strstr( text, sub_text );
12686 if( result != NULL ) {
12687 result += strlen( sub_text );
12693 /* [AS] Try to extract PV info from PGN comment */
12694 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
12695 char *GetInfoFromComment( int index, char * text )
12699 if( text != NULL && index > 0 ) {
12702 int time = -1, sec = 0, deci;
12703 char * s_eval = FindStr( text, "[%eval " );
12704 char * s_emt = FindStr( text, "[%emt " );
12706 if( s_eval != NULL || s_emt != NULL ) {
12710 if( s_eval != NULL ) {
12711 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
12715 if( delim != ']' ) {
12720 if( s_emt != NULL ) {
12725 /* We expect something like: [+|-]nnn.nn/dd */
12728 if(*text != '{') return text; // [HGM] braces: must be normal comment
12730 sep = strchr( text, '/' );
12731 if( sep == NULL || sep < (text+4) ) {
12735 time = -1; sec = -1; deci = -1;
12736 if( sscanf( text+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
12737 sscanf( text+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
12738 sscanf( text+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
12739 sscanf( text+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3 ) {
12743 if( score_lo < 0 || score_lo >= 100 ) {
12747 if(sec >= 0) time = 600*time + 10*sec; else
12748 if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
12750 score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
12752 /* [HGM] PV time: now locate end of PV info */
12753 while( *++sep >= '0' && *sep <= '9'); // strip depth
12755 while( *++sep >= '0' && *sep <= '9'); // strip time
12757 while( *++sep >= '0' && *sep <= '9'); // strip seconds
12759 while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
12760 while(*sep == ' ') sep++;
12771 pvInfoList[index-1].depth = depth;
12772 pvInfoList[index-1].score = score;
12773 pvInfoList[index-1].time = 10*time; // centi-sec
12774 if(*sep == '}') *sep = 0; else *--sep = '{';
12780 SendToProgram(message, cps)
12782 ChessProgramState *cps;
12784 int count, outCount, error;
12787 if (cps->pr == NULL) return;
12790 if (appData.debugMode) {
12793 fprintf(debugFP, "%ld >%-6s: %s",
12794 SubtractTimeMarks(&now, &programStartTime),
12795 cps->which, message);
12798 count = strlen(message);
12799 outCount = OutputToProcess(cps->pr, message, count, &error);
12800 if (outCount < count && !exiting
12801 && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
12802 sprintf(buf, _("Error writing to %s chess program"), cps->which);
12803 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
12804 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
12805 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
12806 sprintf(buf, "%s program exits in draw position (%s)", cps->which, cps->program);
12808 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
12810 gameInfo.resultDetails = StrSave(buf);
12812 if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
12817 ReceiveFromProgram(isr, closure, message, count, error)
12818 InputSourceRef isr;
12826 ChessProgramState *cps = (ChessProgramState *)closure;
12828 if (isr != cps->isr) return; /* Killed intentionally */
12832 _("Error: %s chess program (%s) exited unexpectedly"),
12833 cps->which, cps->program);
12834 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
12835 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
12836 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
12837 sprintf(buf, _("%s program exits in draw position (%s)"), cps->which, cps->program);
12839 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
12841 gameInfo.resultDetails = StrSave(buf);
12843 RemoveInputSource(cps->isr);
12844 if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
12847 _("Error reading from %s chess program (%s)"),
12848 cps->which, cps->program);
12849 RemoveInputSource(cps->isr);
12851 /* [AS] Program is misbehaving badly... kill it */
12852 if( count == -2 ) {
12853 DestroyChildProcess( cps->pr, 9 );
12857 if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
12862 if ((end_str = strchr(message, '\r')) != NULL)
12863 *end_str = NULLCHAR;
12864 if ((end_str = strchr(message, '\n')) != NULL)
12865 *end_str = NULLCHAR;
12867 if (appData.debugMode) {
12868 TimeMark now; int print = 1;
12869 char *quote = ""; char c; int i;
12871 if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
12872 char start = message[0];
12873 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
12874 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
12875 sscanf(message, "move %c", &c)!=1 && sscanf(message, "offer%c", &c)!=1 &&
12876 sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
12877 sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
12878 sscanf(message, "tell%c", &c)!=1 && sscanf(message, "0-1 %c", &c)!=1 &&
12879 sscanf(message, "1-0 %c", &c)!=1 && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
12880 sscanf(message, "pong %c", &c)!=1 && start != '#')
12881 { quote = "# "; print = (appData.engineComments == 2); }
12882 message[0] = start; // restore original message
12886 fprintf(debugFP, "%ld <%-6s: %s%s\n",
12887 SubtractTimeMarks(&now, &programStartTime), cps->which,
12893 /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
12894 if (appData.icsEngineAnalyze) {
12895 if (strstr(message, "whisper") != NULL ||
12896 strstr(message, "kibitz") != NULL ||
12897 strstr(message, "tellics") != NULL) return;
12900 HandleMachineMove(message, cps);
12905 SendTimeControl(cps, mps, tc, inc, sd, st)
12906 ChessProgramState *cps;
12907 int mps, inc, sd, st;
12913 if( timeControl_2 > 0 ) {
12914 if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
12915 tc = timeControl_2;
12918 tc /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
12919 inc /= cps->timeOdds;
12920 st /= cps->timeOdds;
12922 seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
12925 /* Set exact time per move, normally using st command */
12926 if (cps->stKludge) {
12927 /* GNU Chess 4 has no st command; uses level in a nonstandard way */
12929 if (seconds == 0) {
12930 sprintf(buf, "level 1 %d\n", st/60);
12932 sprintf(buf, "level 1 %d:%02d\n", st/60, seconds);
12935 sprintf(buf, "st %d\n", st);
12938 /* Set conventional or incremental time control, using level command */
12939 if (seconds == 0) {
12940 /* Note old gnuchess bug -- minutes:seconds used to not work.
12941 Fixed in later versions, but still avoid :seconds
12942 when seconds is 0. */
12943 sprintf(buf, "level %d %ld %d\n", mps, tc/60000, inc/1000);
12945 sprintf(buf, "level %d %ld:%02d %d\n", mps, tc/60000,
12946 seconds, inc/1000);
12949 SendToProgram(buf, cps);
12951 /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
12952 /* Orthogonally, limit search to given depth */
12954 if (cps->sdKludge) {
12955 sprintf(buf, "depth\n%d\n", sd);
12957 sprintf(buf, "sd %d\n", sd);
12959 SendToProgram(buf, cps);
12962 if(cps->nps > 0) { /* [HGM] nps */
12963 if(cps->supportsNPS == FALSE) cps->nps = -1; // don't use if engine explicitly says not supported!
12965 sprintf(buf, "nps %d\n", cps->nps);
12966 SendToProgram(buf, cps);
12971 ChessProgramState *WhitePlayer()
12972 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
12974 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
12975 gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
12981 SendTimeRemaining(cps, machineWhite)
12982 ChessProgramState *cps;
12983 int /*boolean*/ machineWhite;
12985 char message[MSG_SIZ];
12988 /* Note: this routine must be called when the clocks are stopped
12989 or when they have *just* been set or switched; otherwise
12990 it will be off by the time since the current tick started.
12992 if (machineWhite) {
12993 time = whiteTimeRemaining / 10;
12994 otime = blackTimeRemaining / 10;
12996 time = blackTimeRemaining / 10;
12997 otime = whiteTimeRemaining / 10;
12999 /* [HGM] translate opponent's time by time-odds factor */
13000 otime = (otime * cps->other->timeOdds) / cps->timeOdds;
13001 if (appData.debugMode) {
13002 fprintf(debugFP, "time odds: %f %f \n", cps->timeOdds, cps->other->timeOdds);
13005 if (time <= 0) time = 1;
13006 if (otime <= 0) otime = 1;
13008 sprintf(message, "time %ld\n", time);
13009 SendToProgram(message, cps);
13011 sprintf(message, "otim %ld\n", otime);
13012 SendToProgram(message, cps);
13016 BoolFeature(p, name, loc, cps)
13020 ChessProgramState *cps;
13023 int len = strlen(name);
13025 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
13027 sscanf(*p, "%d", &val);
13029 while (**p && **p != ' ') (*p)++;
13030 sprintf(buf, "accepted %s\n", name);
13031 SendToProgram(buf, cps);
13038 IntFeature(p, name, loc, cps)
13042 ChessProgramState *cps;
13045 int len = strlen(name);
13046 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
13048 sscanf(*p, "%d", loc);
13049 while (**p && **p != ' ') (*p)++;
13050 sprintf(buf, "accepted %s\n", name);
13051 SendToProgram(buf, cps);
13058 StringFeature(p, name, loc, cps)
13062 ChessProgramState *cps;
13065 int len = strlen(name);
13066 if (strncmp((*p), name, len) == 0
13067 && (*p)[len] == '=' && (*p)[len+1] == '\"') {
13069 sscanf(*p, "%[^\"]", loc);
13070 while (**p && **p != '\"') (*p)++;
13071 if (**p == '\"') (*p)++;
13072 sprintf(buf, "accepted %s\n", name);
13073 SendToProgram(buf, cps);
13080 ParseOption(Option *opt, ChessProgramState *cps)
13081 // [HGM] options: process the string that defines an engine option, and determine
13082 // name, type, default value, and allowed value range
13084 char *p, *q, buf[MSG_SIZ];
13085 int n, min = (-1)<<31, max = 1<<31, def;
13087 if(p = strstr(opt->name, " -spin ")) {
13088 if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
13089 if(max < min) max = min; // enforce consistency
13090 if(def < min) def = min;
13091 if(def > max) def = max;
13096 } else if((p = strstr(opt->name, " -slider "))) {
13097 // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
13098 if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
13099 if(max < min) max = min; // enforce consistency
13100 if(def < min) def = min;
13101 if(def > max) def = max;
13105 opt->type = Spin; // Slider;
13106 } else if((p = strstr(opt->name, " -string "))) {
13107 opt->textValue = p+9;
13108 opt->type = TextBox;
13109 } else if((p = strstr(opt->name, " -file "))) {
13110 // for now -file is a synonym for -string, to already provide compatibility with future polyglots
13111 opt->textValue = p+7;
13112 opt->type = TextBox; // FileName;
13113 } else if((p = strstr(opt->name, " -path "))) {
13114 // for now -file is a synonym for -string, to already provide compatibility with future polyglots
13115 opt->textValue = p+7;
13116 opt->type = TextBox; // PathName;
13117 } else if(p = strstr(opt->name, " -check ")) {
13118 if(sscanf(p, " -check %d", &def) < 1) return FALSE;
13119 opt->value = (def != 0);
13120 opt->type = CheckBox;
13121 } else if(p = strstr(opt->name, " -combo ")) {
13122 opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
13123 cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
13124 if(*q == '*') cps->comboList[cps->comboCnt-1]++;
13125 opt->value = n = 0;
13126 while(q = StrStr(q, " /// ")) {
13127 n++; *q = 0; // count choices, and null-terminate each of them
13129 if(*q == '*') { // remember default, which is marked with * prefix
13133 cps->comboList[cps->comboCnt++] = q;
13135 cps->comboList[cps->comboCnt++] = NULL;
13137 opt->type = ComboBox;
13138 } else if(p = strstr(opt->name, " -button")) {
13139 opt->type = Button;
13140 } else if(p = strstr(opt->name, " -save")) {
13141 opt->type = SaveButton;
13142 } else return FALSE;
13143 *p = 0; // terminate option name
13144 // now look if the command-line options define a setting for this engine option.
13145 if(cps->optionSettings && cps->optionSettings[0])
13146 p = strstr(cps->optionSettings, opt->name); else p = NULL;
13147 if(p && (p == cps->optionSettings || p[-1] == ',')) {
13148 sprintf(buf, "option %s", p);
13149 if(p = strstr(buf, ",")) *p = 0;
13151 SendToProgram(buf, cps);
13157 FeatureDone(cps, val)
13158 ChessProgramState* cps;
13161 DelayedEventCallback cb = GetDelayedEvent();
13162 if ((cb == InitBackEnd3 && cps == &first) ||
13163 (cb == TwoMachinesEventIfReady && cps == &second)) {
13164 CancelDelayedEvent();
13165 ScheduleDelayedEvent(cb, val ? 1 : 3600000);
13167 cps->initDone = val;
13170 /* Parse feature command from engine */
13172 ParseFeatures(args, cps)
13174 ChessProgramState *cps;
13182 while (*p == ' ') p++;
13183 if (*p == NULLCHAR) return;
13185 if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
13186 if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
13187 if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
13188 if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
13189 if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
13190 if (BoolFeature(&p, "reuse", &val, cps)) {
13191 /* Engine can disable reuse, but can't enable it if user said no */
13192 if (!val) cps->reuse = FALSE;
13195 if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
13196 if (StringFeature(&p, "myname", &cps->tidy, cps)) {
13197 if (gameMode == TwoMachinesPlay) {
13198 DisplayTwoMachinesTitle();
13204 if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
13205 if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
13206 if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
13207 if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
13208 if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
13209 if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
13210 if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
13211 if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
13212 if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
13213 if (IntFeature(&p, "done", &val, cps)) {
13214 FeatureDone(cps, val);
13217 /* Added by Tord: */
13218 if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
13219 if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
13220 /* End of additions by Tord */
13222 /* [HGM] added features: */
13223 if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
13224 if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
13225 if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
13226 if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
13227 if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
13228 if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
13229 if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
13230 if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
13231 sprintf(buf, "rejected option %s\n", cps->option[--cps->nrOptions].name);
13232 SendToProgram(buf, cps);
13235 if(cps->nrOptions >= MAX_OPTIONS) {
13237 sprintf(buf, "%s engine has too many options\n", cps->which);
13238 DisplayError(buf, 0);
13242 if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
13243 /* End of additions by HGM */
13245 /* unknown feature: complain and skip */
13247 while (*q && *q != '=') q++;
13248 sprintf(buf, "rejected %.*s\n", (int)(q-p), p);
13249 SendToProgram(buf, cps);
13255 while (*p && *p != '\"') p++;
13256 if (*p == '\"') p++;
13258 while (*p && *p != ' ') p++;
13266 PeriodicUpdatesEvent(newState)
13269 if (newState == appData.periodicUpdates)
13272 appData.periodicUpdates=newState;
13274 /* Display type changes, so update it now */
13275 // DisplayAnalysis();
13277 /* Get the ball rolling again... */
13279 AnalysisPeriodicEvent(1);
13280 StartAnalysisClock();
13285 PonderNextMoveEvent(newState)
13288 if (newState == appData.ponderNextMove) return;
13289 if (gameMode == EditPosition) EditPositionDone(TRUE);
13291 SendToProgram("hard\n", &first);
13292 if (gameMode == TwoMachinesPlay) {
13293 SendToProgram("hard\n", &second);
13296 SendToProgram("easy\n", &first);
13297 thinkOutput[0] = NULLCHAR;
13298 if (gameMode == TwoMachinesPlay) {
13299 SendToProgram("easy\n", &second);
13302 appData.ponderNextMove = newState;
13306 NewSettingEvent(option, command, value)
13312 if (gameMode == EditPosition) EditPositionDone(TRUE);
13313 sprintf(buf, "%s%s %d\n", (option ? "option ": ""), command, value);
13314 SendToProgram(buf, &first);
13315 if (gameMode == TwoMachinesPlay) {
13316 SendToProgram(buf, &second);
13321 ShowThinkingEvent()
13322 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
13324 static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
13325 int newState = appData.showThinking
13326 // [HGM] thinking: other features now need thinking output as well
13327 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
13329 if (oldState == newState) return;
13330 oldState = newState;
13331 if (gameMode == EditPosition) EditPositionDone(TRUE);
13333 SendToProgram("post\n", &first);
13334 if (gameMode == TwoMachinesPlay) {
13335 SendToProgram("post\n", &second);
13338 SendToProgram("nopost\n", &first);
13339 thinkOutput[0] = NULLCHAR;
13340 if (gameMode == TwoMachinesPlay) {
13341 SendToProgram("nopost\n", &second);
13344 // appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
13348 AskQuestionEvent(title, question, replyPrefix, which)
13349 char *title; char *question; char *replyPrefix; char *which;
13351 ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
13352 if (pr == NoProc) return;
13353 AskQuestion(title, question, replyPrefix, pr);
13357 DisplayMove(moveNumber)
13360 char message[MSG_SIZ];
13362 char cpThinkOutput[MSG_SIZ];
13364 if(appData.noGUI) return; // [HGM] fast: suppress display of moves
13366 if (moveNumber == forwardMostMove - 1 ||
13367 gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13369 safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput));
13371 if (strchr(cpThinkOutput, '\n')) {
13372 *strchr(cpThinkOutput, '\n') = NULLCHAR;
13375 *cpThinkOutput = NULLCHAR;
13378 /* [AS] Hide thinking from human user */
13379 if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
13380 *cpThinkOutput = NULLCHAR;
13381 if( thinkOutput[0] != NULLCHAR ) {
13384 for( i=0; i<=hiddenThinkOutputState; i++ ) {
13385 cpThinkOutput[i] = '.';
13387 cpThinkOutput[i] = NULLCHAR;
13388 hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
13392 if (moveNumber == forwardMostMove - 1 &&
13393 gameInfo.resultDetails != NULL) {
13394 if (gameInfo.resultDetails[0] == NULLCHAR) {
13395 sprintf(res, " %s", PGNResult(gameInfo.result));
13397 sprintf(res, " {%s} %s",
13398 gameInfo.resultDetails, PGNResult(gameInfo.result));
13404 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
13405 DisplayMessage(res, cpThinkOutput);
13407 sprintf(message, "%d.%s%s%s", moveNumber / 2 + 1,
13408 WhiteOnMove(moveNumber) ? " " : ".. ",
13409 parseList[moveNumber], res);
13410 DisplayMessage(message, cpThinkOutput);
13415 DisplayComment(moveNumber, text)
13419 char title[MSG_SIZ];
13420 char buf[8000]; // comment can be long!
13422 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
13423 strcpy(title, "Comment");
13425 sprintf(title, "Comment on %d.%s%s", moveNumber / 2 + 1,
13426 WhiteOnMove(moveNumber) ? " " : ".. ",
13427 parseList[moveNumber]);
13429 // [HGM] PV info: display PV info together with (or as) comment
13430 if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {
13431 if(text == NULL) text = "";
13432 score = pvInfoList[moveNumber].score;
13433 sprintf(buf, "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,
13434 depth, (pvInfoList[moveNumber].time+50)/100, text);
13437 if (text != NULL && (appData.autoDisplayComment || commentUp))
13438 CommentPopUp(title, text);
13441 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
13442 * might be busy thinking or pondering. It can be omitted if your
13443 * gnuchess is configured to stop thinking immediately on any user
13444 * input. However, that gnuchess feature depends on the FIONREAD
13445 * ioctl, which does not work properly on some flavors of Unix.
13449 ChessProgramState *cps;
13452 if (!cps->useSigint) return;
13453 if (appData.noChessProgram || (cps->pr == NoProc)) return;
13454 switch (gameMode) {
13455 case MachinePlaysWhite:
13456 case MachinePlaysBlack:
13457 case TwoMachinesPlay:
13458 case IcsPlayingWhite:
13459 case IcsPlayingBlack:
13462 /* Skip if we know it isn't thinking */
13463 if (!cps->maybeThinking) return;
13464 if (appData.debugMode)
13465 fprintf(debugFP, "Interrupting %s\n", cps->which);
13466 InterruptChildProcess(cps->pr);
13467 cps->maybeThinking = FALSE;
13472 #endif /*ATTENTION*/
13478 if (whiteTimeRemaining <= 0) {
13481 if (appData.icsActive) {
13482 if (appData.autoCallFlag &&
13483 gameMode == IcsPlayingBlack && !blackFlag) {
13484 SendToICS(ics_prefix);
13485 SendToICS("flag\n");
13489 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
13491 if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
13492 if (appData.autoCallFlag) {
13493 GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
13500 if (blackTimeRemaining <= 0) {
13503 if (appData.icsActive) {
13504 if (appData.autoCallFlag &&
13505 gameMode == IcsPlayingWhite && !whiteFlag) {
13506 SendToICS(ics_prefix);
13507 SendToICS("flag\n");
13511 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
13513 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
13514 if (appData.autoCallFlag) {
13515 GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
13528 if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
13529 gameMode == PlayFromGameFile || forwardMostMove == 0) return;
13532 * add time to clocks when time control is achieved ([HGM] now also used for increment)
13534 if ( !WhiteOnMove(forwardMostMove) )
13535 /* White made time control */
13536 whiteTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
13537 /* [HGM] time odds: correct new time quota for time odds! */
13538 / WhitePlayer()->timeOdds;
13540 /* Black made time control */
13541 blackTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
13542 / WhitePlayer()->other->timeOdds;
13546 DisplayBothClocks()
13548 int wom = gameMode == EditPosition ?
13549 !blackPlaysFirst : WhiteOnMove(currentMove);
13550 DisplayWhiteClock(whiteTimeRemaining, wom);
13551 DisplayBlackClock(blackTimeRemaining, !wom);
13555 /* Timekeeping seems to be a portability nightmare. I think everyone
13556 has ftime(), but I'm really not sure, so I'm including some ifdefs
13557 to use other calls if you don't. Clocks will be less accurate if
13558 you have neither ftime nor gettimeofday.
13561 /* VS 2008 requires the #include outside of the function */
13562 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
13563 #include <sys/timeb.h>
13566 /* Get the current time as a TimeMark */
13571 #if HAVE_GETTIMEOFDAY
13573 struct timeval timeVal;
13574 struct timezone timeZone;
13576 gettimeofday(&timeVal, &timeZone);
13577 tm->sec = (long) timeVal.tv_sec;
13578 tm->ms = (int) (timeVal.tv_usec / 1000L);
13580 #else /*!HAVE_GETTIMEOFDAY*/
13583 // include <sys/timeb.h> / moved to just above start of function
13584 struct timeb timeB;
13587 tm->sec = (long) timeB.time;
13588 tm->ms = (int) timeB.millitm;
13590 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
13591 tm->sec = (long) time(NULL);
13597 /* Return the difference in milliseconds between two
13598 time marks. We assume the difference will fit in a long!
13601 SubtractTimeMarks(tm2, tm1)
13602 TimeMark *tm2, *tm1;
13604 return 1000L*(tm2->sec - tm1->sec) +
13605 (long) (tm2->ms - tm1->ms);
13610 * Code to manage the game clocks.
13612 * In tournament play, black starts the clock and then white makes a move.
13613 * We give the human user a slight advantage if he is playing white---the
13614 * clocks don't run until he makes his first move, so it takes zero time.
13615 * Also, we don't account for network lag, so we could get out of sync
13616 * with GNU Chess's clock -- but then, referees are always right.
13619 static TimeMark tickStartTM;
13620 static long intendedTickLength;
13623 NextTickLength(timeRemaining)
13624 long timeRemaining;
13626 long nominalTickLength, nextTickLength;
13628 if (timeRemaining > 0L && timeRemaining <= 10000L)
13629 nominalTickLength = 100L;
13631 nominalTickLength = 1000L;
13632 nextTickLength = timeRemaining % nominalTickLength;
13633 if (nextTickLength <= 0) nextTickLength += nominalTickLength;
13635 return nextTickLength;
13638 /* Adjust clock one minute up or down */
13640 AdjustClock(Boolean which, int dir)
13642 if(which) blackTimeRemaining += 60000*dir;
13643 else whiteTimeRemaining += 60000*dir;
13644 DisplayBothClocks();
13647 /* Stop clocks and reset to a fresh time control */
13651 (void) StopClockTimer();
13652 if (appData.icsActive) {
13653 whiteTimeRemaining = blackTimeRemaining = 0;
13654 } else if (searchTime) {
13655 whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
13656 blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
13657 } else { /* [HGM] correct new time quote for time odds */
13658 whiteTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->timeOdds;
13659 blackTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->other->timeOdds;
13661 if (whiteFlag || blackFlag) {
13663 whiteFlag = blackFlag = FALSE;
13665 DisplayBothClocks();
13668 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
13670 /* Decrement running clock by amount of time that has passed */
13674 long timeRemaining;
13675 long lastTickLength, fudge;
13678 if (!appData.clockMode) return;
13679 if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
13683 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13685 /* Fudge if we woke up a little too soon */
13686 fudge = intendedTickLength - lastTickLength;
13687 if (fudge < 0 || fudge > FUDGE) fudge = 0;
13689 if (WhiteOnMove(forwardMostMove)) {
13690 if(whiteNPS >= 0) lastTickLength = 0;
13691 timeRemaining = whiteTimeRemaining -= lastTickLength;
13692 DisplayWhiteClock(whiteTimeRemaining - fudge,
13693 WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
13695 if(blackNPS >= 0) lastTickLength = 0;
13696 timeRemaining = blackTimeRemaining -= lastTickLength;
13697 DisplayBlackClock(blackTimeRemaining - fudge,
13698 !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
13701 if (CheckFlags()) return;
13704 intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
13705 StartClockTimer(intendedTickLength);
13707 /* if the time remaining has fallen below the alarm threshold, sound the
13708 * alarm. if the alarm has sounded and (due to a takeback or time control
13709 * with increment) the time remaining has increased to a level above the
13710 * threshold, reset the alarm so it can sound again.
13713 if (appData.icsActive && appData.icsAlarm) {
13715 /* make sure we are dealing with the user's clock */
13716 if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
13717 ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
13720 if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
13721 alarmSounded = FALSE;
13722 } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
13724 alarmSounded = TRUE;
13730 /* A player has just moved, so stop the previously running
13731 clock and (if in clock mode) start the other one.
13732 We redisplay both clocks in case we're in ICS mode, because
13733 ICS gives us an update to both clocks after every move.
13734 Note that this routine is called *after* forwardMostMove
13735 is updated, so the last fractional tick must be subtracted
13736 from the color that is *not* on move now.
13741 long lastTickLength;
13743 int flagged = FALSE;
13747 if (StopClockTimer() && appData.clockMode) {
13748 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13749 if (WhiteOnMove(forwardMostMove)) {
13750 if(blackNPS >= 0) lastTickLength = 0;
13751 blackTimeRemaining -= lastTickLength;
13752 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
13753 // if(pvInfoList[forwardMostMove-1].time == -1)
13754 pvInfoList[forwardMostMove-1].time = // use GUI time
13755 (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
13757 if(whiteNPS >= 0) lastTickLength = 0;
13758 whiteTimeRemaining -= lastTickLength;
13759 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
13760 // if(pvInfoList[forwardMostMove-1].time == -1)
13761 pvInfoList[forwardMostMove-1].time =
13762 (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
13764 flagged = CheckFlags();
13766 CheckTimeControl();
13768 if (flagged || !appData.clockMode) return;
13770 switch (gameMode) {
13771 case MachinePlaysBlack:
13772 case MachinePlaysWhite:
13773 case BeginningOfGame:
13774 if (pausing) return;
13778 case PlayFromGameFile:
13786 if (searchTime) { // [HGM] st: set clock of player that has to move to max time
13787 if(WhiteOnMove(forwardMostMove))
13788 whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
13789 else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
13793 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
13794 whiteTimeRemaining : blackTimeRemaining);
13795 StartClockTimer(intendedTickLength);
13799 /* Stop both clocks */
13803 long lastTickLength;
13806 if (!StopClockTimer()) return;
13807 if (!appData.clockMode) return;
13811 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13812 if (WhiteOnMove(forwardMostMove)) {
13813 if(whiteNPS >= 0) lastTickLength = 0;
13814 whiteTimeRemaining -= lastTickLength;
13815 DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
13817 if(blackNPS >= 0) lastTickLength = 0;
13818 blackTimeRemaining -= lastTickLength;
13819 DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
13824 /* Start clock of player on move. Time may have been reset, so
13825 if clock is already running, stop and restart it. */
13829 (void) StopClockTimer(); /* in case it was running already */
13830 DisplayBothClocks();
13831 if (CheckFlags()) return;
13833 if (!appData.clockMode) return;
13834 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
13836 GetTimeMark(&tickStartTM);
13837 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
13838 whiteTimeRemaining : blackTimeRemaining);
13840 /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
13841 whiteNPS = blackNPS = -1;
13842 if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
13843 || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
13844 whiteNPS = first.nps;
13845 if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
13846 || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
13847 blackNPS = first.nps;
13848 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
13849 whiteNPS = second.nps;
13850 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
13851 blackNPS = second.nps;
13852 if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
13854 StartClockTimer(intendedTickLength);
13861 long second, minute, hour, day;
13863 static char buf[32];
13865 if (ms > 0 && ms <= 9900) {
13866 /* convert milliseconds to tenths, rounding up */
13867 double tenths = floor( ((double)(ms + 99L)) / 100.00 );
13869 sprintf(buf, " %03.1f ", tenths/10.0);
13873 /* convert milliseconds to seconds, rounding up */
13874 /* use floating point to avoid strangeness of integer division
13875 with negative dividends on many machines */
13876 second = (long) floor(((double) (ms + 999L)) / 1000.0);
13883 day = second / (60 * 60 * 24);
13884 second = second % (60 * 60 * 24);
13885 hour = second / (60 * 60);
13886 second = second % (60 * 60);
13887 minute = second / 60;
13888 second = second % 60;
13891 sprintf(buf, " %s%ld:%02ld:%02ld:%02ld ",
13892 sign, day, hour, minute, second);
13894 sprintf(buf, " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
13896 sprintf(buf, " %s%2ld:%02ld ", sign, minute, second);
13903 * This is necessary because some C libraries aren't ANSI C compliant yet.
13906 StrStr(string, match)
13907 char *string, *match;
13911 length = strlen(match);
13913 for (i = strlen(string) - length; i >= 0; i--, string++)
13914 if (!strncmp(match, string, length))
13921 StrCaseStr(string, match)
13922 char *string, *match;
13926 length = strlen(match);
13928 for (i = strlen(string) - length; i >= 0; i--, string++) {
13929 for (j = 0; j < length; j++) {
13930 if (ToLower(match[j]) != ToLower(string[j]))
13933 if (j == length) return string;
13947 c1 = ToLower(*s1++);
13948 c2 = ToLower(*s2++);
13949 if (c1 > c2) return 1;
13950 if (c1 < c2) return -1;
13951 if (c1 == NULLCHAR) return 0;
13960 return isupper(c) ? tolower(c) : c;
13968 return islower(c) ? toupper(c) : c;
13970 #endif /* !_amigados */
13978 if ((ret = (char *) malloc(strlen(s) + 1))) {
13985 StrSavePtr(s, savePtr)
13986 char *s, **savePtr;
13991 if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
13992 strcpy(*savePtr, s);
14004 clock = time((time_t *)NULL);
14005 tm = localtime(&clock);
14006 sprintf(buf, "%04d.%02d.%02d",
14007 tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
14008 return StrSave(buf);
14013 PositionToFEN(move, overrideCastling)
14015 char *overrideCastling;
14017 int i, j, fromX, fromY, toX, toY;
14024 whiteToPlay = (gameMode == EditPosition) ?
14025 !blackPlaysFirst : (move % 2 == 0);
14028 /* Piece placement data */
14029 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
14031 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
14032 if (boards[move][i][j] == EmptySquare) {
14034 } else { ChessSquare piece = boards[move][i][j];
14035 if (emptycount > 0) {
14036 if(emptycount<10) /* [HGM] can be >= 10 */
14037 *p++ = '0' + emptycount;
14038 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
14041 if(PieceToChar(piece) == '+') {
14042 /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
14044 piece = (ChessSquare)(DEMOTED piece);
14046 *p++ = PieceToChar(piece);
14048 /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
14049 p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
14054 if (emptycount > 0) {
14055 if(emptycount<10) /* [HGM] can be >= 10 */
14056 *p++ = '0' + emptycount;
14057 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
14064 /* [HGM] print Crazyhouse or Shogi holdings */
14065 if( gameInfo.holdingsWidth ) {
14066 *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
14068 for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
14069 piece = boards[move][i][BOARD_WIDTH-1];
14070 if( piece != EmptySquare )
14071 for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
14072 *p++ = PieceToChar(piece);
14074 for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
14075 piece = boards[move][BOARD_HEIGHT-i-1][0];
14076 if( piece != EmptySquare )
14077 for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
14078 *p++ = PieceToChar(piece);
14081 if( q == p ) *p++ = '-';
14087 *p++ = whiteToPlay ? 'w' : 'b';
14090 if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
14091 while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
14093 if(nrCastlingRights) {
14095 if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
14096 /* [HGM] write directly from rights */
14097 if(boards[move][CASTLING][2] != NoRights &&
14098 boards[move][CASTLING][0] != NoRights )
14099 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
14100 if(boards[move][CASTLING][2] != NoRights &&
14101 boards[move][CASTLING][1] != NoRights )
14102 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
14103 if(boards[move][CASTLING][5] != NoRights &&
14104 boards[move][CASTLING][3] != NoRights )
14105 *p++ = boards[move][CASTLING][3] + AAA;
14106 if(boards[move][CASTLING][5] != NoRights &&
14107 boards[move][CASTLING][4] != NoRights )
14108 *p++ = boards[move][CASTLING][4] + AAA;
14111 /* [HGM] write true castling rights */
14112 if( nrCastlingRights == 6 ) {
14113 if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
14114 boards[move][CASTLING][2] != NoRights ) *p++ = 'K';
14115 if(boards[move][CASTLING][1] == BOARD_LEFT &&
14116 boards[move][CASTLING][2] != NoRights ) *p++ = 'Q';
14117 if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
14118 boards[move][CASTLING][5] != NoRights ) *p++ = 'k';
14119 if(boards[move][CASTLING][4] == BOARD_LEFT &&
14120 boards[move][CASTLING][5] != NoRights ) *p++ = 'q';
14123 if (q == p) *p++ = '-'; /* No castling rights */
14127 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
14128 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
14129 /* En passant target square */
14130 if (move > backwardMostMove) {
14131 fromX = moveList[move - 1][0] - AAA;
14132 fromY = moveList[move - 1][1] - ONE;
14133 toX = moveList[move - 1][2] - AAA;
14134 toY = moveList[move - 1][3] - ONE;
14135 if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
14136 toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
14137 boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
14139 /* 2-square pawn move just happened */
14141 *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
14145 } else if(move == backwardMostMove) {
14146 // [HGM] perhaps we should always do it like this, and forget the above?
14147 if((signed char)boards[move][EP_STATUS] >= 0) {
14148 *p++ = boards[move][EP_STATUS] + AAA;
14149 *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
14160 /* [HGM] find reversible plies */
14161 { int i = 0, j=move;
14163 if (appData.debugMode) { int k;
14164 fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
14165 for(k=backwardMostMove; k<=forwardMostMove; k++)
14166 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
14170 while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
14171 if( j == backwardMostMove ) i += initialRulePlies;
14172 sprintf(p, "%d ", i);
14173 p += i>=100 ? 4 : i >= 10 ? 3 : 2;
14175 /* Fullmove number */
14176 sprintf(p, "%d", (move / 2) + 1);
14178 return StrSave(buf);
14182 ParseFEN(board, blackPlaysFirst, fen)
14184 int *blackPlaysFirst;
14194 /* [HGM] by default clear Crazyhouse holdings, if present */
14195 if(gameInfo.holdingsWidth) {
14196 for(i=0; i<BOARD_HEIGHT; i++) {
14197 board[i][0] = EmptySquare; /* black holdings */
14198 board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
14199 board[i][1] = (ChessSquare) 0; /* black counts */
14200 board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
14204 /* Piece placement data */
14205 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
14208 if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
14209 if (*p == '/') p++;
14210 emptycount = gameInfo.boardWidth - j;
14211 while (emptycount--)
14212 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
14214 #if(BOARD_FILES >= 10)
14215 } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
14216 p++; emptycount=10;
14217 if (j + emptycount > gameInfo.boardWidth) return FALSE;
14218 while (emptycount--)
14219 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
14221 } else if (isdigit(*p)) {
14222 emptycount = *p++ - '0';
14223 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
14224 if (j + emptycount > gameInfo.boardWidth) return FALSE;
14225 while (emptycount--)
14226 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
14227 } else if (*p == '+' || isalpha(*p)) {
14228 if (j >= gameInfo.boardWidth) return FALSE;
14230 piece = CharToPiece(*++p);
14231 if(piece == EmptySquare) return FALSE; /* unknown piece */
14232 piece = (ChessSquare) (PROMOTED piece ); p++;
14233 if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
14234 } else piece = CharToPiece(*p++);
14236 if(piece==EmptySquare) return FALSE; /* unknown piece */
14237 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
14238 piece = (ChessSquare) (PROMOTED piece);
14239 if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
14242 board[i][(j++)+gameInfo.holdingsWidth] = piece;
14248 while (*p == '/' || *p == ' ') p++;
14250 /* [HGM] look for Crazyhouse holdings here */
14251 while(*p==' ') p++;
14252 if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
14254 if(*p == '-' ) *p++; /* empty holdings */ else {
14255 if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
14256 /* if we would allow FEN reading to set board size, we would */
14257 /* have to add holdings and shift the board read so far here */
14258 while( (piece = CharToPiece(*p) ) != EmptySquare ) {
14260 if((int) piece >= (int) BlackPawn ) {
14261 i = (int)piece - (int)BlackPawn;
14262 i = PieceToNumber((ChessSquare)i);
14263 if( i >= gameInfo.holdingsSize ) return FALSE;
14264 board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
14265 board[BOARD_HEIGHT-1-i][1]++; /* black counts */
14267 i = (int)piece - (int)WhitePawn;
14268 i = PieceToNumber((ChessSquare)i);
14269 if( i >= gameInfo.holdingsSize ) return FALSE;
14270 board[i][BOARD_WIDTH-1] = piece; /* white holdings */
14271 board[i][BOARD_WIDTH-2]++; /* black holdings */
14275 if(*p == ']') *p++;
14278 while(*p == ' ') p++;
14283 *blackPlaysFirst = FALSE;
14286 *blackPlaysFirst = TRUE;
14292 /* [HGM] We NO LONGER ignore the rest of the FEN notation */
14293 /* return the extra info in global variiables */
14295 /* set defaults in case FEN is incomplete */
14296 board[EP_STATUS] = EP_UNKNOWN;
14297 for(i=0; i<nrCastlingRights; i++ ) {
14298 board[CASTLING][i] =
14299 gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
14300 } /* assume possible unless obviously impossible */
14301 if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
14302 if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
14303 if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
14304 && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
14305 if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
14306 if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
14307 if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
14308 && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
14311 while(*p==' ') p++;
14312 if(nrCastlingRights) {
14313 if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
14314 /* castling indicator present, so default becomes no castlings */
14315 for(i=0; i<nrCastlingRights; i++ ) {
14316 board[CASTLING][i] = NoRights;
14319 while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
14320 (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
14321 ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
14322 ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth) ) {
14323 char c = *p++; int whiteKingFile=NoRights, blackKingFile=NoRights;
14325 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
14326 if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
14327 if(board[0 ][i] == WhiteKing) whiteKingFile = i;
14329 if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
14330 whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
14331 if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
14332 && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
14333 if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
14334 && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
14337 for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
14338 board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
14339 board[CASTLING][2] = whiteKingFile;
14342 for(i=BOARD_LEFT; i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
14343 board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
14344 board[CASTLING][2] = whiteKingFile;
14347 for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
14348 board[CASTLING][3] = i != blackKingFile ? i : NoRights;
14349 board[CASTLING][5] = blackKingFile;
14352 for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
14353 board[CASTLING][4] = i != blackKingFile ? i : NoRights;
14354 board[CASTLING][5] = blackKingFile;
14357 default: /* FRC castlings */
14358 if(c >= 'a') { /* black rights */
14359 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
14360 if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
14361 if(i == BOARD_RGHT) break;
14362 board[CASTLING][5] = i;
14364 if(board[BOARD_HEIGHT-1][c] < BlackPawn ||
14365 board[BOARD_HEIGHT-1][c] >= BlackKing ) break;
14367 board[CASTLING][3] = c;
14369 board[CASTLING][4] = c;
14370 } else { /* white rights */
14371 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
14372 if(board[0][i] == WhiteKing) break;
14373 if(i == BOARD_RGHT) break;
14374 board[CASTLING][2] = i;
14375 c -= AAA - 'a' + 'A';
14376 if(board[0][c] >= WhiteKing) break;
14378 board[CASTLING][0] = c;
14380 board[CASTLING][1] = c;
14384 for(i=0; i<nrCastlingRights; i++)
14385 if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
14386 if (appData.debugMode) {
14387 fprintf(debugFP, "FEN castling rights:");
14388 for(i=0; i<nrCastlingRights; i++)
14389 fprintf(debugFP, " %d", board[CASTLING][i]);
14390 fprintf(debugFP, "\n");
14393 while(*p==' ') p++;
14396 /* read e.p. field in games that know e.p. capture */
14397 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
14398 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
14400 p++; board[EP_STATUS] = EP_NONE;
14402 char c = *p++ - AAA;
14404 if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
14405 if(*p >= '0' && *p <='9') *p++;
14406 board[EP_STATUS] = c;
14411 if(sscanf(p, "%d", &i) == 1) {
14412 FENrulePlies = i; /* 50-move ply counter */
14413 /* (The move number is still ignored) */
14420 EditPositionPasteFEN(char *fen)
14423 Board initial_position;
14425 if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
14426 DisplayError(_("Bad FEN position in clipboard"), 0);
14429 int savedBlackPlaysFirst = blackPlaysFirst;
14430 EditPositionEvent();
14431 blackPlaysFirst = savedBlackPlaysFirst;
14432 CopyBoard(boards[0], initial_position);
14433 initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
14434 EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
14435 DisplayBothClocks();
14436 DrawPosition(FALSE, boards[currentMove]);
14441 static char cseq[12] = "\\ ";
14443 Boolean set_cont_sequence(char *new_seq)
14448 // handle bad attempts to set the sequence
14450 return 0; // acceptable error - no debug
14452 len = strlen(new_seq);
14453 ret = (len > 0) && (len < sizeof(cseq));
14455 strcpy(cseq, new_seq);
14456 else if (appData.debugMode)
14457 fprintf(debugFP, "Invalid continuation sequence \"%s\" (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
14462 reformat a source message so words don't cross the width boundary. internal
14463 newlines are not removed. returns the wrapped size (no null character unless
14464 included in source message). If dest is NULL, only calculate the size required
14465 for the dest buffer. lp argument indicats line position upon entry, and it's
14466 passed back upon exit.
14468 int wrap(char *dest, char *src, int count, int width, int *lp)
14470 int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
14472 cseq_len = strlen(cseq);
14473 old_line = line = *lp;
14474 ansi = len = clen = 0;
14476 for (i=0; i < count; i++)
14478 if (src[i] == '\033')
14481 // if we hit the width, back up
14482 if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
14484 // store i & len in case the word is too long
14485 old_i = i, old_len = len;
14487 // find the end of the last word
14488 while (i && src[i] != ' ' && src[i] != '\n')
14494 // word too long? restore i & len before splitting it
14495 if ((old_i-i+clen) >= width)
14502 if (i && src[i-1] == ' ')
14505 if (src[i] != ' ' && src[i] != '\n')
14512 // now append the newline and continuation sequence
14517 strncpy(dest+len, cseq, cseq_len);
14525 dest[len] = src[i];
14529 if (src[i] == '\n')
14534 if (dest && appData.debugMode)
14536 fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
14537 count, width, line, len, *lp);
14538 show_bytes(debugFP, src, count);
14539 fprintf(debugFP, "\ndest: ");
14540 show_bytes(debugFP, dest, len);
14541 fprintf(debugFP, "\n");
14543 *lp = dest ? line : old_line;
14548 // [HGM] vari: routines for shelving variations
14551 PushTail(int firstMove, int lastMove)
14553 int i, j, nrMoves = lastMove - firstMove;
14555 if(appData.icsActive) { // only in local mode
14556 forwardMostMove = currentMove; // mimic old ICS behavior
14559 if(storedGames >= MAX_VARIATIONS-1) return;
14561 // push current tail of game on stack
14562 savedResult[storedGames] = gameInfo.result;
14563 savedDetails[storedGames] = gameInfo.resultDetails;
14564 gameInfo.resultDetails = NULL;
14565 savedFirst[storedGames] = firstMove;
14566 savedLast [storedGames] = lastMove;
14567 savedFramePtr[storedGames] = framePtr;
14568 framePtr -= nrMoves; // reserve space for the boards
14569 for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
14570 CopyBoard(boards[framePtr+i], boards[firstMove+i]);
14571 for(j=0; j<MOVE_LEN; j++)
14572 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
14573 for(j=0; j<2*MOVE_LEN; j++)
14574 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
14575 timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
14576 timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
14577 pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
14578 pvInfoList[firstMove+i-1].depth = 0;
14579 commentList[framePtr+i] = commentList[firstMove+i];
14580 commentList[firstMove+i] = NULL;
14584 forwardMostMove = currentMove; // truncte game so we can start variation
14585 if(storedGames == 1) GreyRevert(FALSE);
14589 PopTail(Boolean annotate)
14592 char buf[8000], moveBuf[20];
14594 if(appData.icsActive) return FALSE; // only in local mode
14595 if(!storedGames) return FALSE; // sanity
14598 ToNrEvent(savedFirst[storedGames]); // sets currentMove
14599 nrMoves = savedLast[storedGames] - currentMove;
14602 if(!WhiteOnMove(currentMove)) sprintf(buf, "(%d...", currentMove+2>>1);
14603 else strcpy(buf, "(");
14604 for(i=currentMove; i<forwardMostMove; i++) {
14606 sprintf(moveBuf, " %d. %s", i+2>>1, SavePart(parseList[i]));
14607 else sprintf(moveBuf, " %s", SavePart(parseList[i]));
14608 strcat(buf, moveBuf);
14609 if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
14613 for(i=1; i<nrMoves; i++) { // copy last variation back
14614 CopyBoard(boards[currentMove+i], boards[framePtr+i]);
14615 for(j=0; j<MOVE_LEN; j++)
14616 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
14617 for(j=0; j<2*MOVE_LEN; j++)
14618 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
14619 timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
14620 timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
14621 pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
14622 if(commentList[currentMove+i]) free(commentList[currentMove+i]);
14623 commentList[currentMove+i] = commentList[framePtr+i];
14624 commentList[framePtr+i] = NULL;
14626 if(annotate) AppendComment(currentMove+1, buf, FALSE);
14627 framePtr = savedFramePtr[storedGames];
14628 gameInfo.result = savedResult[storedGames];
14629 if(gameInfo.resultDetails != NULL) {
14630 free(gameInfo.resultDetails);
14632 gameInfo.resultDetails = savedDetails[storedGames];
14633 forwardMostMove = currentMove + nrMoves;
14634 if(storedGames == 0) GreyRevert(TRUE);
14640 { // remove all shelved variations
14642 for(i=0; i<storedGames; i++) {
14643 if(savedDetails[i])
14644 free(savedDetails[i]);
14645 savedDetails[i] = NULL;
14647 for(i=framePtr; i<MAX_MOVES; i++) {
14648 if(commentList[i]) free(commentList[i]);
14649 commentList[i] = NULL;
14651 framePtr = MAX_MOVES-1;