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 int Adjudicate P((ChessProgramState *cps));
171 void GameEnds P((ChessMove result, char *resultDetails, int whosays));
172 void EditPositionDone P((Boolean fakeRights));
173 void PrintOpponents P((FILE *fp));
174 void PrintPosition P((FILE *fp, int move));
175 void StartChessProgram P((ChessProgramState *cps));
176 void SendToProgram P((char *message, ChessProgramState *cps));
177 void SendMoveToProgram P((int moveNum, ChessProgramState *cps));
178 void ReceiveFromProgram P((InputSourceRef isr, VOIDSTAR closure,
179 char *buf, int count, int error));
180 void SendTimeControl P((ChessProgramState *cps,
181 int mps, long tc, int inc, int sd, int st));
182 char *TimeControlTagValue P((void));
183 void Attention P((ChessProgramState *cps));
184 void FeedMovesToProgram P((ChessProgramState *cps, int upto));
185 void ResurrectChessProgram P((void));
186 void DisplayComment P((int moveNumber, char *text));
187 void DisplayMove P((int moveNumber));
189 void ParseGameHistory P((char *game));
190 void ParseBoard12 P((char *string));
191 void KeepAlive P((void));
192 void StartClocks P((void));
193 void SwitchClocks P((void));
194 void StopClocks P((void));
195 void ResetClocks P((void));
196 char *PGNDate P((void));
197 void SetGameInfo P((void));
198 Boolean ParseFEN P((Board board, int *blackPlaysFirst, char *fen));
199 int RegisterMove P((void));
200 void MakeRegisteredMove P((void));
201 void TruncateGame P((void));
202 int looking_at P((char *, int *, char *));
203 void CopyPlayerNameIntoFileName P((char **, char *));
204 char *SavePart P((char *));
205 int SaveGameOldStyle P((FILE *));
206 int SaveGamePGN P((FILE *));
207 void GetTimeMark P((TimeMark *));
208 long SubtractTimeMarks P((TimeMark *, TimeMark *));
209 int CheckFlags P((void));
210 long NextTickLength P((long));
211 void CheckTimeControl P((void));
212 void show_bytes P((FILE *, char *, int));
213 int string_to_rating P((char *str));
214 void ParseFeatures P((char* args, ChessProgramState *cps));
215 void InitBackEnd3 P((void));
216 void FeatureDone P((ChessProgramState* cps, int val));
217 void InitChessProgram P((ChessProgramState *cps, int setup));
218 void OutputKibitz(int window, char *text);
219 int PerpetualChase(int first, int last);
220 int EngineOutputIsUp();
221 void InitDrawingSizes(int x, int y);
224 extern void ConsoleCreate();
227 ChessProgramState *WhitePlayer();
228 void InsertIntoMemo P((int which, char *text)); // [HGM] kibitz: in engineo.c
229 int VerifyDisplayMode P(());
231 char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment
232 void InitEngineUCI( const char * iniDir, ChessProgramState * cps ); // [HGM] moved here from winboard.c
233 char *ProbeBook P((int moveNr, char *book)); // [HGM] book: returns a book move
234 char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book
235 void ics_update_width P((int new_width));
236 extern char installDir[MSG_SIZ];
238 extern int tinyLayout, smallLayout;
239 ChessProgramStats programStats;
240 char lastPV[2][2*MSG_SIZ]; /* [HGM] pv: last PV in thinking output of each engine */
242 static int exiting = 0; /* [HGM] moved to top */
243 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
244 int startedFromPositionFile = FALSE; Board filePosition; /* [HGM] loadPos */
245 char endingGame = 0; /* [HGM] crash: flag to prevent recursion of GameEnds() */
246 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS */
247 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
248 int lastIndex = 0; /* [HGM] autoinc: last game/position used in match mode */
249 Boolean connectionAlive;/* [HGM] alive: ICS connection status from probing */
250 int opponentKibitzes;
251 int lastSavedGame; /* [HGM] save: ID of game */
252 char chatPartner[MAX_CHAT][MSG_SIZ]; /* [HGM] chat: list of chatting partners */
253 extern int chatCount;
255 char marker[BOARD_RANKS][BOARD_FILES]; /* [HGM] marks for target squares */
257 /* States for ics_getting_history */
259 #define H_REQUESTED 1
260 #define H_GOT_REQ_HEADER 2
261 #define H_GOT_UNREQ_HEADER 3
262 #define H_GETTING_MOVES 4
263 #define H_GOT_UNWANTED_HEADER 5
265 /* whosays values for GameEnds */
274 /* Maximum number of games in a cmail message */
275 #define CMAIL_MAX_GAMES 20
277 /* Different types of move when calling RegisterMove */
279 #define CMAIL_RESIGN 1
281 #define CMAIL_ACCEPT 3
283 /* Different types of result to remember for each game */
284 #define CMAIL_NOT_RESULT 0
285 #define CMAIL_OLD_RESULT 1
286 #define CMAIL_NEW_RESULT 2
288 /* Telnet protocol constants */
299 static char * safeStrCpy( char * dst, const char * src, size_t count )
301 assert( dst != NULL );
302 assert( src != NULL );
305 strncpy( dst, src, count );
306 dst[ count-1 ] = '\0';
310 /* Some compiler can't cast u64 to double
311 * This function do the job for us:
313 * We use the highest bit for cast, this only
314 * works if the highest bit is not
315 * in use (This should not happen)
317 * We used this for all compiler
320 u64ToDouble(u64 value)
323 u64 tmp = value & u64Const(0x7fffffffffffffff);
324 r = (double)(s64)tmp;
325 if (value & u64Const(0x8000000000000000))
326 r += 9.2233720368547758080e18; /* 2^63 */
330 /* Fake up flags for now, as we aren't keeping track of castling
331 availability yet. [HGM] Change of logic: the flag now only
332 indicates the type of castlings allowed by the rule of the game.
333 The actual rights themselves are maintained in the array
334 castlingRights, as part of the game history, and are not probed
340 int flags = F_ALL_CASTLE_OK;
341 if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
342 switch (gameInfo.variant) {
344 flags &= ~F_ALL_CASTLE_OK;
345 case VariantGiveaway: // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
346 flags |= F_IGNORE_CHECK;
348 flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
351 flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
353 case VariantKriegspiel:
354 flags |= F_KRIEGSPIEL_CAPTURE;
356 case VariantCapaRandom:
357 case VariantFischeRandom:
358 flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
359 case VariantNoCastle:
360 case VariantShatranj:
363 flags &= ~F_ALL_CASTLE_OK;
371 FILE *gameFileFP, *debugFP;
374 [AS] Note: sometimes, the sscanf() function is used to parse the input
375 into a fixed-size buffer. Because of this, we must be prepared to
376 receive strings as long as the size of the input buffer, which is currently
377 set to 4K for Windows and 8K for the rest.
378 So, we must either allocate sufficiently large buffers here, or
379 reduce the size of the input buffer in the input reading part.
382 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
383 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
384 char thinkOutput1[MSG_SIZ*10];
386 ChessProgramState first, second;
388 /* premove variables */
391 int premoveFromX = 0;
392 int premoveFromY = 0;
393 int premovePromoChar = 0;
395 Boolean alarmSounded;
396 /* end premove variables */
398 char *ics_prefix = "$";
399 int ics_type = ICS_GENERIC;
401 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
402 int pauseExamForwardMostMove = 0;
403 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
404 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
405 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
406 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
407 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
408 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
409 int whiteFlag = FALSE, blackFlag = FALSE;
410 int userOfferedDraw = FALSE;
411 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
412 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
413 int cmailMoveType[CMAIL_MAX_GAMES];
414 long ics_clock_paused = 0;
415 ProcRef icsPR = NoProc, cmailPR = NoProc;
416 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
417 GameMode gameMode = BeginningOfGame;
418 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
419 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
420 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
421 int hiddenThinkOutputState = 0; /* [AS] */
422 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
423 int adjudicateLossPlies = 6;
424 char white_holding[64], black_holding[64];
425 TimeMark lastNodeCountTime;
426 long lastNodeCount=0;
427 int have_sent_ICS_logon = 0;
429 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement;
430 long timeControl_2; /* [AS] Allow separate time controls */
431 char *fullTimeControlString = NULL; /* [HGM] secondary TC: merge of MPS, TC and inc */
432 long timeRemaining[2][MAX_MOVES];
434 TimeMark programStartTime;
435 char ics_handle[MSG_SIZ];
436 int have_set_title = 0;
438 /* animateTraining preserves the state of appData.animate
439 * when Training mode is activated. This allows the
440 * response to be animated when appData.animate == TRUE and
441 * appData.animateDragging == TRUE.
443 Boolean animateTraining;
449 Board boards[MAX_MOVES];
450 /* [HGM] Following 7 needed for accurate legality tests: */
451 signed char castlingRank[BOARD_FILES]; // and corresponding ranks
452 signed char initialRights[BOARD_FILES];
453 int nrCastlingRights; // For TwoKings, or to implement castling-unknown status
454 int initialRulePlies, FENrulePlies;
455 FILE *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
458 int mute; // mute all sounds
460 // [HGM] vari: next 12 to save and restore variations
461 #define MAX_VARIATIONS 10
462 int framePtr = MAX_MOVES-1; // points to free stack entry
464 int savedFirst[MAX_VARIATIONS];
465 int savedLast[MAX_VARIATIONS];
466 int savedFramePtr[MAX_VARIATIONS];
467 char *savedDetails[MAX_VARIATIONS];
468 ChessMove savedResult[MAX_VARIATIONS];
470 void PushTail P((int firstMove, int lastMove));
471 Boolean PopTail P((Boolean annotate));
472 void CleanupTail P((void));
474 ChessSquare FIDEArray[2][BOARD_FILES] = {
475 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
476 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
477 { BlackRook, BlackKnight, BlackBishop, BlackQueen,
478 BlackKing, BlackBishop, BlackKnight, BlackRook }
481 ChessSquare twoKingsArray[2][BOARD_FILES] = {
482 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
483 WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
484 { BlackRook, BlackKnight, BlackBishop, BlackQueen,
485 BlackKing, BlackKing, BlackKnight, BlackRook }
488 ChessSquare KnightmateArray[2][BOARD_FILES] = {
489 { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
490 WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
491 { BlackRook, BlackMan, BlackBishop, BlackQueen,
492 BlackUnicorn, BlackBishop, BlackMan, BlackRook }
495 ChessSquare fairyArray[2][BOARD_FILES] = { /* [HGM] Queen side differs from King side */
496 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
497 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
498 { BlackLance, BlackAlfil, BlackMarshall, BlackAngel,
499 BlackKing, BlackMarshall, BlackAlfil, BlackLance }
502 ChessSquare ShatranjArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
503 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
504 WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
505 { BlackRook, BlackKnight, BlackAlfil, BlackKing,
506 BlackFerz, BlackAlfil, BlackKnight, BlackRook }
509 ChessSquare makrukArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
510 { WhiteRook, WhiteKnight, WhiteMan, WhiteKing,
511 WhiteFerz, WhiteMan, WhiteKnight, WhiteRook },
512 { BlackRook, BlackKnight, BlackMan, BlackFerz,
513 BlackKing, BlackMan, BlackKnight, BlackRook }
517 #if (BOARD_FILES>=10)
518 ChessSquare ShogiArray[2][BOARD_FILES] = {
519 { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
520 WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
521 { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
522 BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
525 ChessSquare XiangqiArray[2][BOARD_FILES] = {
526 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
527 WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
528 { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
529 BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
532 ChessSquare CapablancaArray[2][BOARD_FILES] = {
533 { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
534 WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
535 { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
536 BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
539 ChessSquare GreatArray[2][BOARD_FILES] = {
540 { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
541 WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
542 { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
543 BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
546 ChessSquare JanusArray[2][BOARD_FILES] = {
547 { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
548 WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
549 { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
550 BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
554 ChessSquare GothicArray[2][BOARD_FILES] = {
555 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
556 WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
557 { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
558 BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
561 #define GothicArray CapablancaArray
565 ChessSquare FalconArray[2][BOARD_FILES] = {
566 { WhiteRook, WhiteKnight, WhiteBishop, WhiteLance, WhiteQueen,
567 WhiteKing, WhiteLance, WhiteBishop, WhiteKnight, WhiteRook },
568 { BlackRook, BlackKnight, BlackBishop, BlackLance, BlackQueen,
569 BlackKing, BlackLance, BlackBishop, BlackKnight, BlackRook }
572 #define FalconArray CapablancaArray
575 #else // !(BOARD_FILES>=10)
576 #define XiangqiPosition FIDEArray
577 #define CapablancaArray FIDEArray
578 #define GothicArray FIDEArray
579 #define GreatArray FIDEArray
580 #endif // !(BOARD_FILES>=10)
582 #if (BOARD_FILES>=12)
583 ChessSquare CourierArray[2][BOARD_FILES] = {
584 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
585 WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
586 { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
587 BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
589 #else // !(BOARD_FILES>=12)
590 #define CourierArray CapablancaArray
591 #endif // !(BOARD_FILES>=12)
594 Board initialPosition;
597 /* Convert str to a rating. Checks for special cases of "----",
599 "++++", etc. Also strips ()'s */
601 string_to_rating(str)
604 while(*str && !isdigit(*str)) ++str;
606 return 0; /* One of the special "no rating" cases */
614 /* Init programStats */
615 programStats.movelist[0] = 0;
616 programStats.depth = 0;
617 programStats.nr_moves = 0;
618 programStats.moves_left = 0;
619 programStats.nodes = 0;
620 programStats.time = -1; // [HGM] PGNtime: make invalid to recognize engine output
621 programStats.score = 0;
622 programStats.got_only_move = 0;
623 programStats.got_fail = 0;
624 programStats.line_is_book = 0;
630 int matched, min, sec;
632 ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
634 GetTimeMark(&programStartTime);
635 srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
638 programStats.ok_to_send = 1;
639 programStats.seen_stat = 0;
642 * Initialize game list
648 * Internet chess server status
650 if (appData.icsActive) {
651 appData.matchMode = FALSE;
652 appData.matchGames = 0;
654 appData.noChessProgram = !appData.zippyPlay;
656 appData.zippyPlay = FALSE;
657 appData.zippyTalk = FALSE;
658 appData.noChessProgram = TRUE;
660 if (*appData.icsHelper != NULLCHAR) {
661 appData.useTelnet = TRUE;
662 appData.telnetProgram = appData.icsHelper;
665 appData.zippyTalk = appData.zippyPlay = FALSE;
668 /* [AS] Initialize pv info list [HGM] and game state */
672 for( i=0; i<=framePtr; i++ ) {
673 pvInfoList[i].depth = -1;
674 boards[i][EP_STATUS] = EP_NONE;
675 for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
680 * Parse timeControl resource
682 if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
683 appData.movesPerSession)) {
685 snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
686 DisplayFatalError(buf, 0, 2);
690 * Parse searchTime resource
692 if (*appData.searchTime != NULLCHAR) {
693 matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
695 searchTime = min * 60;
696 } else if (matched == 2) {
697 searchTime = min * 60 + sec;
700 snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
701 DisplayFatalError(buf, 0, 2);
705 /* [AS] Adjudication threshold */
706 adjudicateLossThreshold = appData.adjudicateLossThreshold;
708 first.which = "first";
709 second.which = "second";
710 first.maybeThinking = second.maybeThinking = FALSE;
711 first.pr = second.pr = NoProc;
712 first.isr = second.isr = NULL;
713 first.sendTime = second.sendTime = 2;
714 first.sendDrawOffers = 1;
715 if (appData.firstPlaysBlack) {
716 first.twoMachinesColor = "black\n";
717 second.twoMachinesColor = "white\n";
719 first.twoMachinesColor = "white\n";
720 second.twoMachinesColor = "black\n";
722 first.program = appData.firstChessProgram;
723 second.program = appData.secondChessProgram;
724 first.host = appData.firstHost;
725 second.host = appData.secondHost;
726 first.dir = appData.firstDirectory;
727 second.dir = appData.secondDirectory;
728 first.other = &second;
729 second.other = &first;
730 first.initString = appData.initString;
731 second.initString = appData.secondInitString;
732 first.computerString = appData.firstComputerString;
733 second.computerString = appData.secondComputerString;
734 first.useSigint = second.useSigint = TRUE;
735 first.useSigterm = second.useSigterm = TRUE;
736 first.reuse = appData.reuseFirst;
737 second.reuse = appData.reuseSecond;
738 first.nps = appData.firstNPS; // [HGM] nps: copy nodes per second
739 second.nps = appData.secondNPS;
740 first.useSetboard = second.useSetboard = FALSE;
741 first.useSAN = second.useSAN = FALSE;
742 first.usePing = second.usePing = FALSE;
743 first.lastPing = second.lastPing = 0;
744 first.lastPong = second.lastPong = 0;
745 first.usePlayother = second.usePlayother = FALSE;
746 first.useColors = second.useColors = TRUE;
747 first.useUsermove = second.useUsermove = FALSE;
748 first.sendICS = second.sendICS = FALSE;
749 first.sendName = second.sendName = appData.icsActive;
750 first.sdKludge = second.sdKludge = FALSE;
751 first.stKludge = second.stKludge = FALSE;
752 TidyProgramName(first.program, first.host, first.tidy);
753 TidyProgramName(second.program, second.host, second.tidy);
754 first.matchWins = second.matchWins = 0;
755 strcpy(first.variants, appData.variant);
756 strcpy(second.variants, appData.variant);
757 first.analysisSupport = second.analysisSupport = 2; /* detect */
758 first.analyzing = second.analyzing = FALSE;
759 first.initDone = second.initDone = FALSE;
761 /* New features added by Tord: */
762 first.useFEN960 = FALSE; second.useFEN960 = FALSE;
763 first.useOOCastle = TRUE; second.useOOCastle = TRUE;
764 /* End of new features added by Tord. */
765 first.fenOverride = appData.fenOverride1;
766 second.fenOverride = appData.fenOverride2;
768 /* [HGM] time odds: set factor for each machine */
769 first.timeOdds = appData.firstTimeOdds;
770 second.timeOdds = appData.secondTimeOdds;
772 if(appData.timeOddsMode) {
773 norm = first.timeOdds;
774 if(norm > second.timeOdds) norm = second.timeOdds;
776 first.timeOdds /= norm;
777 second.timeOdds /= norm;
780 /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
781 first.accumulateTC = appData.firstAccumulateTC;
782 second.accumulateTC = appData.secondAccumulateTC;
783 first.maxNrOfSessions = second.maxNrOfSessions = 1;
786 first.debug = second.debug = FALSE;
787 first.supportsNPS = second.supportsNPS = UNKNOWN;
790 first.optionSettings = appData.firstOptions;
791 second.optionSettings = appData.secondOptions;
793 first.scoreIsAbsolute = appData.firstScoreIsAbsolute; /* [AS] */
794 second.scoreIsAbsolute = appData.secondScoreIsAbsolute; /* [AS] */
795 first.isUCI = appData.firstIsUCI; /* [AS] */
796 second.isUCI = appData.secondIsUCI; /* [AS] */
797 first.hasOwnBookUCI = appData.firstHasOwnBookUCI; /* [AS] */
798 second.hasOwnBookUCI = appData.secondHasOwnBookUCI; /* [AS] */
800 if (appData.firstProtocolVersion > PROTOVER ||
801 appData.firstProtocolVersion < 1) {
803 sprintf(buf, _("protocol version %d not supported"),
804 appData.firstProtocolVersion);
805 DisplayFatalError(buf, 0, 2);
807 first.protocolVersion = appData.firstProtocolVersion;
810 if (appData.secondProtocolVersion > PROTOVER ||
811 appData.secondProtocolVersion < 1) {
813 sprintf(buf, _("protocol version %d not supported"),
814 appData.secondProtocolVersion);
815 DisplayFatalError(buf, 0, 2);
817 second.protocolVersion = appData.secondProtocolVersion;
820 if (appData.icsActive) {
821 appData.clockMode = TRUE; /* changes dynamically in ICS mode */
822 // } else if (*appData.searchTime != NULLCHAR || appData.noChessProgram) {
823 } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
824 appData.clockMode = FALSE;
825 first.sendTime = second.sendTime = 0;
829 /* Override some settings from environment variables, for backward
830 compatibility. Unfortunately it's not feasible to have the env
831 vars just set defaults, at least in xboard. Ugh.
833 if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
838 if (appData.noChessProgram) {
839 programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
840 sprintf(programVersion, "%s", PACKAGE_STRING);
842 /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
843 programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
844 sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
847 if (!appData.icsActive) {
849 /* Check for variants that are supported only in ICS mode,
850 or not at all. Some that are accepted here nevertheless
851 have bugs; see comments below.
853 VariantClass variant = StringToVariant(appData.variant);
855 case VariantBughouse: /* need four players and two boards */
856 case VariantKriegspiel: /* need to hide pieces and move details */
857 /* case VariantFischeRandom: (Fabien: moved below) */
858 sprintf(buf, _("Variant %s supported only in ICS mode"), appData.variant);
859 DisplayFatalError(buf, 0, 2);
863 case VariantLoadable:
873 sprintf(buf, _("Unknown variant name %s"), appData.variant);
874 DisplayFatalError(buf, 0, 2);
877 case VariantXiangqi: /* [HGM] repetition rules not implemented */
878 case VariantFairy: /* [HGM] TestLegality definitely off! */
879 case VariantGothic: /* [HGM] should work */
880 case VariantCapablanca: /* [HGM] should work */
881 case VariantCourier: /* [HGM] initial forced moves not implemented */
882 case VariantShogi: /* [HGM] drops not tested for legality */
883 case VariantKnightmate: /* [HGM] should work */
884 case VariantCylinder: /* [HGM] untested */
885 case VariantFalcon: /* [HGM] untested */
886 case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
887 offboard interposition not understood */
888 case VariantNormal: /* definitely works! */
889 case VariantWildCastle: /* pieces not automatically shuffled */
890 case VariantNoCastle: /* pieces not automatically shuffled */
891 case VariantFischeRandom: /* [HGM] works and shuffles pieces */
892 case VariantLosers: /* should work except for win condition,
893 and doesn't know captures are mandatory */
894 case VariantSuicide: /* should work except for win condition,
895 and doesn't know captures are mandatory */
896 case VariantGiveaway: /* should work except for win condition,
897 and doesn't know captures are mandatory */
898 case VariantTwoKings: /* should work */
899 case VariantAtomic: /* should work except for win condition */
900 case Variant3Check: /* should work except for win condition */
901 case VariantShatranj: /* should work except for all win conditions */
902 case VariantMakruk: /* should work except for daw countdown */
903 case VariantBerolina: /* might work if TestLegality is off */
904 case VariantCapaRandom: /* should work */
905 case VariantJanus: /* should work */
906 case VariantSuper: /* experimental */
907 case VariantGreat: /* experimental, requires legality testing to be off */
912 InitEngineUCI( installDir, &first ); // [HGM] moved here from winboard.c, to make available in xboard
913 InitEngineUCI( installDir, &second );
916 int NextIntegerFromString( char ** str, long * value )
921 while( *s == ' ' || *s == '\t' ) {
927 if( *s >= '0' && *s <= '9' ) {
928 while( *s >= '0' && *s <= '9' ) {
929 *value = *value * 10 + (*s - '0');
941 int NextTimeControlFromString( char ** str, long * value )
944 int result = NextIntegerFromString( str, &temp );
947 *value = temp * 60; /* Minutes */
950 result = NextIntegerFromString( str, &temp );
951 *value += temp; /* Seconds */
958 int NextSessionFromString( char ** str, int *moves, long * tc, long *inc)
959 { /* [HGM] routine added to read '+moves/time' for secondary time control */
960 int result = -1; long temp, temp2;
962 if(**str != '+') return -1; // old params remain in force!
964 if( NextTimeControlFromString( str, &temp ) ) return -1;
967 /* time only: incremental or sudden-death time control */
968 if(**str == '+') { /* increment follows; read it */
970 if(result = NextIntegerFromString( str, &temp2)) return -1;
973 *moves = 0; *tc = temp * 1000;
975 } else if(temp % 60 != 0) return -1; /* moves was given as min:sec */
977 (*str)++; /* classical time control */
978 result = NextTimeControlFromString( str, &temp2);
987 int GetTimeQuota(int movenr)
988 { /* [HGM] get time to add from the multi-session time-control string */
989 int moves=1; /* kludge to force reading of first session */
990 long time, increment;
991 char *s = fullTimeControlString;
993 if(appData.debugMode) fprintf(debugFP, "TC string = '%s'\n", fullTimeControlString);
995 if(moves) NextSessionFromString(&s, &moves, &time, &increment);
996 if(appData.debugMode) fprintf(debugFP, "mps=%d tc=%d inc=%d\n", moves, (int) time, (int) increment);
997 if(movenr == -1) return time; /* last move before new session */
998 if(!moves) return increment; /* current session is incremental */
999 if(movenr >= 0) movenr -= moves; /* we already finished this session */
1000 } while(movenr >= -1); /* try again for next session */
1002 return 0; // no new time quota on this move
1006 ParseTimeControl(tc, ti, mps)
1015 if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1018 sprintf(buf, "+%d/%s+%d", mps, tc, ti);
1019 else sprintf(buf, "+%s+%d", tc, ti);
1022 sprintf(buf, "+%d/%s", mps, tc);
1023 else sprintf(buf, "+%s", tc);
1025 fullTimeControlString = StrSave(buf);
1027 if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1032 /* Parse second time control */
1035 if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1043 timeControl_2 = tc2 * 1000;
1053 timeControl = tc1 * 1000;
1056 timeIncrement = ti * 1000; /* convert to ms */
1057 movesPerSession = 0;
1060 movesPerSession = mps;
1068 if (appData.debugMode) {
1069 fprintf(debugFP, "%s\n", programVersion);
1072 set_cont_sequence(appData.wrapContSeq);
1073 if (appData.matchGames > 0) {
1074 appData.matchMode = TRUE;
1075 } else if (appData.matchMode) {
1076 appData.matchGames = 1;
1078 if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1079 appData.matchGames = appData.sameColorGames;
1080 if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1081 if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1082 if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1085 if (appData.noChessProgram || first.protocolVersion == 1) {
1088 /* kludge: allow timeout for initial "feature" commands */
1090 DisplayMessage("", _("Starting chess program"));
1091 ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1096 InitBackEnd3 P((void))
1098 GameMode initialMode;
1102 InitChessProgram(&first, startedFromSetupPosition);
1105 if (appData.icsActive) {
1107 /* [DM] Make a console window if needed [HGM] merged ifs */
1112 if (*appData.icsCommPort != NULLCHAR) {
1113 sprintf(buf, _("Could not open comm port %s"),
1114 appData.icsCommPort);
1116 snprintf(buf, sizeof(buf), _("Could not connect to host %s, port %s"),
1117 appData.icsHost, appData.icsPort);
1119 DisplayFatalError(buf, err, 1);
1124 AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1126 AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1127 if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1128 ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1129 } else if (appData.noChessProgram) {
1135 if (*appData.cmailGameName != NULLCHAR) {
1137 OpenLoopback(&cmailPR);
1139 AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1143 DisplayMessage("", "");
1144 if (StrCaseCmp(appData.initialMode, "") == 0) {
1145 initialMode = BeginningOfGame;
1146 } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1147 initialMode = TwoMachinesPlay;
1148 } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1149 initialMode = AnalyzeFile;
1150 } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1151 initialMode = AnalyzeMode;
1152 } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1153 initialMode = MachinePlaysWhite;
1154 } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1155 initialMode = MachinePlaysBlack;
1156 } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1157 initialMode = EditGame;
1158 } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1159 initialMode = EditPosition;
1160 } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1161 initialMode = Training;
1163 sprintf(buf, _("Unknown initialMode %s"), appData.initialMode);
1164 DisplayFatalError(buf, 0, 2);
1168 if (appData.matchMode) {
1169 /* Set up machine vs. machine match */
1170 if (appData.noChessProgram) {
1171 DisplayFatalError(_("Can't have a match with no chess programs"),
1177 if (*appData.loadGameFile != NULLCHAR) {
1178 int index = appData.loadGameIndex; // [HGM] autoinc
1179 if(index<0) lastIndex = index = 1;
1180 if (!LoadGameFromFile(appData.loadGameFile,
1182 appData.loadGameFile, FALSE)) {
1183 DisplayFatalError(_("Bad game file"), 0, 1);
1186 } else if (*appData.loadPositionFile != NULLCHAR) {
1187 int index = appData.loadPositionIndex; // [HGM] autoinc
1188 if(index<0) lastIndex = index = 1;
1189 if (!LoadPositionFromFile(appData.loadPositionFile,
1191 appData.loadPositionFile)) {
1192 DisplayFatalError(_("Bad position file"), 0, 1);
1197 } else if (*appData.cmailGameName != NULLCHAR) {
1198 /* Set up cmail mode */
1199 ReloadCmailMsgEvent(TRUE);
1201 /* Set up other modes */
1202 if (initialMode == AnalyzeFile) {
1203 if (*appData.loadGameFile == NULLCHAR) {
1204 DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1208 if (*appData.loadGameFile != NULLCHAR) {
1209 (void) LoadGameFromFile(appData.loadGameFile,
1210 appData.loadGameIndex,
1211 appData.loadGameFile, TRUE);
1212 } else if (*appData.loadPositionFile != NULLCHAR) {
1213 (void) LoadPositionFromFile(appData.loadPositionFile,
1214 appData.loadPositionIndex,
1215 appData.loadPositionFile);
1216 /* [HGM] try to make self-starting even after FEN load */
1217 /* to allow automatic setup of fairy variants with wtm */
1218 if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1219 gameMode = BeginningOfGame;
1220 setboardSpoiledMachineBlack = 1;
1222 /* [HGM] loadPos: make that every new game uses the setup */
1223 /* from file as long as we do not switch variant */
1224 if(!blackPlaysFirst) {
1225 startedFromPositionFile = TRUE;
1226 CopyBoard(filePosition, boards[0]);
1229 if (initialMode == AnalyzeMode) {
1230 if (appData.noChessProgram) {
1231 DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1234 if (appData.icsActive) {
1235 DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1239 } else if (initialMode == AnalyzeFile) {
1240 appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1241 ShowThinkingEvent();
1243 AnalysisPeriodicEvent(1);
1244 } else if (initialMode == MachinePlaysWhite) {
1245 if (appData.noChessProgram) {
1246 DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1250 if (appData.icsActive) {
1251 DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1255 MachineWhiteEvent();
1256 } else if (initialMode == MachinePlaysBlack) {
1257 if (appData.noChessProgram) {
1258 DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1262 if (appData.icsActive) {
1263 DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1267 MachineBlackEvent();
1268 } else if (initialMode == TwoMachinesPlay) {
1269 if (appData.noChessProgram) {
1270 DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1274 if (appData.icsActive) {
1275 DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1280 } else if (initialMode == EditGame) {
1282 } else if (initialMode == EditPosition) {
1283 EditPositionEvent();
1284 } else if (initialMode == Training) {
1285 if (*appData.loadGameFile == NULLCHAR) {
1286 DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1295 * Establish will establish a contact to a remote host.port.
1296 * Sets icsPR to a ProcRef for a process (or pseudo-process)
1297 * used to talk to the host.
1298 * Returns 0 if okay, error code if not.
1305 if (*appData.icsCommPort != NULLCHAR) {
1306 /* Talk to the host through a serial comm port */
1307 return OpenCommPort(appData.icsCommPort, &icsPR);
1309 } else if (*appData.gateway != NULLCHAR) {
1310 if (*appData.remoteShell == NULLCHAR) {
1311 /* Use the rcmd protocol to run telnet program on a gateway host */
1312 snprintf(buf, sizeof(buf), "%s %s %s",
1313 appData.telnetProgram, appData.icsHost, appData.icsPort);
1314 return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1317 /* Use the rsh program to run telnet program on a gateway host */
1318 if (*appData.remoteUser == NULLCHAR) {
1319 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1320 appData.gateway, appData.telnetProgram,
1321 appData.icsHost, appData.icsPort);
1323 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1324 appData.remoteShell, appData.gateway,
1325 appData.remoteUser, appData.telnetProgram,
1326 appData.icsHost, appData.icsPort);
1328 return StartChildProcess(buf, "", &icsPR);
1331 } else if (appData.useTelnet) {
1332 return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1335 /* TCP socket interface differs somewhat between
1336 Unix and NT; handle details in the front end.
1338 return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1343 show_bytes(fp, buf, count)
1349 if (*buf < 040 || *(unsigned char *) buf > 0177) {
1350 fprintf(fp, "\\%03o", *buf & 0xff);
1359 /* Returns an errno value */
1361 OutputMaybeTelnet(pr, message, count, outError)
1367 char buf[8192], *p, *q, *buflim;
1368 int left, newcount, outcount;
1370 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1371 *appData.gateway != NULLCHAR) {
1372 if (appData.debugMode) {
1373 fprintf(debugFP, ">ICS: ");
1374 show_bytes(debugFP, message, count);
1375 fprintf(debugFP, "\n");
1377 return OutputToProcess(pr, message, count, outError);
1380 buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1387 if (appData.debugMode) {
1388 fprintf(debugFP, ">ICS: ");
1389 show_bytes(debugFP, buf, newcount);
1390 fprintf(debugFP, "\n");
1392 outcount = OutputToProcess(pr, buf, newcount, outError);
1393 if (outcount < newcount) return -1; /* to be sure */
1400 } else if (((unsigned char) *p) == TN_IAC) {
1401 *q++ = (char) TN_IAC;
1408 if (appData.debugMode) {
1409 fprintf(debugFP, ">ICS: ");
1410 show_bytes(debugFP, buf, newcount);
1411 fprintf(debugFP, "\n");
1413 outcount = OutputToProcess(pr, buf, newcount, outError);
1414 if (outcount < newcount) return -1; /* to be sure */
1419 read_from_player(isr, closure, message, count, error)
1426 int outError, outCount;
1427 static int gotEof = 0;
1429 /* Pass data read from player on to ICS */
1432 outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1433 if (outCount < count) {
1434 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1436 } else if (count < 0) {
1437 RemoveInputSource(isr);
1438 DisplayFatalError(_("Error reading from keyboard"), error, 1);
1439 } else if (gotEof++ > 0) {
1440 RemoveInputSource(isr);
1441 DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1447 { // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1448 if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1449 connectionAlive = FALSE; // only sticks if no response to 'date' command.
1450 SendToICS("date\n");
1451 if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1454 /* added routine for printf style output to ics */
1455 void ics_printf(char *format, ...)
1457 char buffer[MSG_SIZ];
1460 va_start(args, format);
1461 vsnprintf(buffer, sizeof(buffer), format, args);
1462 buffer[sizeof(buffer)-1] = '\0';
1471 int count, outCount, outError;
1473 if (icsPR == NULL) return;
1476 outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1477 if (outCount < count) {
1478 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1482 /* This is used for sending logon scripts to the ICS. Sending
1483 without a delay causes problems when using timestamp on ICC
1484 (at least on my machine). */
1486 SendToICSDelayed(s,msdelay)
1490 int count, outCount, outError;
1492 if (icsPR == NULL) return;
1495 if (appData.debugMode) {
1496 fprintf(debugFP, ">ICS: ");
1497 show_bytes(debugFP, s, count);
1498 fprintf(debugFP, "\n");
1500 outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1502 if (outCount < count) {
1503 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1508 /* Remove all highlighting escape sequences in s
1509 Also deletes any suffix starting with '('
1512 StripHighlightAndTitle(s)
1515 static char retbuf[MSG_SIZ];
1518 while (*s != NULLCHAR) {
1519 while (*s == '\033') {
1520 while (*s != NULLCHAR && !isalpha(*s)) s++;
1521 if (*s != NULLCHAR) s++;
1523 while (*s != NULLCHAR && *s != '\033') {
1524 if (*s == '(' || *s == '[') {
1535 /* Remove all highlighting escape sequences in s */
1540 static char retbuf[MSG_SIZ];
1543 while (*s != NULLCHAR) {
1544 while (*s == '\033') {
1545 while (*s != NULLCHAR && !isalpha(*s)) s++;
1546 if (*s != NULLCHAR) s++;
1548 while (*s != NULLCHAR && *s != '\033') {
1556 char *variantNames[] = VARIANT_NAMES;
1561 return variantNames[v];
1565 /* Identify a variant from the strings the chess servers use or the
1566 PGN Variant tag names we use. */
1573 VariantClass v = VariantNormal;
1574 int i, found = FALSE;
1579 /* [HGM] skip over optional board-size prefixes */
1580 if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1581 sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1582 while( *e++ != '_');
1585 if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
1589 for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
1590 if (StrCaseStr(e, variantNames[i])) {
1591 v = (VariantClass) i;
1598 if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
1599 || StrCaseStr(e, "wild/fr")
1600 || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
1601 v = VariantFischeRandom;
1602 } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
1603 (i = 1, p = StrCaseStr(e, "w"))) {
1605 while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
1612 case 0: /* FICS only, actually */
1614 /* Castling legal even if K starts on d-file */
1615 v = VariantWildCastle;
1620 /* Castling illegal even if K & R happen to start in
1621 normal positions. */
1622 v = VariantNoCastle;
1635 /* Castling legal iff K & R start in normal positions */
1641 /* Special wilds for position setup; unclear what to do here */
1642 v = VariantLoadable;
1645 /* Bizarre ICC game */
1646 v = VariantTwoKings;
1649 v = VariantKriegspiel;
1655 v = VariantFischeRandom;
1658 v = VariantCrazyhouse;
1661 v = VariantBughouse;
1667 /* Not quite the same as FICS suicide! */
1668 v = VariantGiveaway;
1674 v = VariantShatranj;
1677 /* Temporary names for future ICC types. The name *will* change in
1678 the next xboard/WinBoard release after ICC defines it. */
1716 v = VariantCapablanca;
1719 v = VariantKnightmate;
1725 v = VariantCylinder;
1731 v = VariantCapaRandom;
1734 v = VariantBerolina;
1746 /* Found "wild" or "w" in the string but no number;
1747 must assume it's normal chess. */
1751 sprintf(buf, _("Unknown wild type %d"), wnum);
1752 DisplayError(buf, 0);
1758 if (appData.debugMode) {
1759 fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
1760 e, wnum, VariantName(v));
1765 static int leftover_start = 0, leftover_len = 0;
1766 char star_match[STAR_MATCH_N][MSG_SIZ];
1768 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
1769 advance *index beyond it, and set leftover_start to the new value of
1770 *index; else return FALSE. If pattern contains the character '*', it
1771 matches any sequence of characters not containing '\r', '\n', or the
1772 character following the '*' (if any), and the matched sequence(s) are
1773 copied into star_match.
1776 looking_at(buf, index, pattern)
1781 char *bufp = &buf[*index], *patternp = pattern;
1783 char *matchp = star_match[0];
1786 if (*patternp == NULLCHAR) {
1787 *index = leftover_start = bufp - buf;
1791 if (*bufp == NULLCHAR) return FALSE;
1792 if (*patternp == '*') {
1793 if (*bufp == *(patternp + 1)) {
1795 matchp = star_match[++star_count];
1799 } else if (*bufp == '\n' || *bufp == '\r') {
1801 if (*patternp == NULLCHAR)
1806 *matchp++ = *bufp++;
1810 if (*patternp != *bufp) return FALSE;
1817 SendToPlayer(data, length)
1821 int error, outCount;
1822 outCount = OutputToProcess(NoProc, data, length, &error);
1823 if (outCount < length) {
1824 DisplayFatalError(_("Error writing to display"), error, 1);
1829 PackHolding(packed, holding)
1841 switch (runlength) {
1852 sprintf(q, "%d", runlength);
1864 /* Telnet protocol requests from the front end */
1866 TelnetRequest(ddww, option)
1867 unsigned char ddww, option;
1869 unsigned char msg[3];
1870 int outCount, outError;
1872 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
1874 if (appData.debugMode) {
1875 char buf1[8], buf2[8], *ddwwStr, *optionStr;
1891 sprintf(buf1, "%d", ddww);
1900 sprintf(buf2, "%d", option);
1903 fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
1908 outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
1910 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1917 if (!appData.icsActive) return;
1918 TelnetRequest(TN_DO, TN_ECHO);
1924 if (!appData.icsActive) return;
1925 TelnetRequest(TN_DONT, TN_ECHO);
1929 CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece)
1931 /* put the holdings sent to us by the server on the board holdings area */
1932 int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
1936 if(gameInfo.holdingsWidth < 2) return;
1937 if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
1938 return; // prevent overwriting by pre-board holdings
1940 if( (int)lowestPiece >= BlackPawn ) {
1943 holdingsStartRow = BOARD_HEIGHT-1;
1946 holdingsColumn = BOARD_WIDTH-1;
1947 countsColumn = BOARD_WIDTH-2;
1948 holdingsStartRow = 0;
1952 for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
1953 board[i][holdingsColumn] = EmptySquare;
1954 board[i][countsColumn] = (ChessSquare) 0;
1956 while( (p=*holdings++) != NULLCHAR ) {
1957 piece = CharToPiece( ToUpper(p) );
1958 if(piece == EmptySquare) continue;
1959 /*j = (int) piece - (int) WhitePawn;*/
1960 j = PieceToNumber(piece);
1961 if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
1962 if(j < 0) continue; /* should not happen */
1963 piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
1964 board[holdingsStartRow+j*direction][holdingsColumn] = piece;
1965 board[holdingsStartRow+j*direction][countsColumn]++;
1971 VariantSwitch(Board board, VariantClass newVariant)
1973 int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
1976 startedFromPositionFile = FALSE;
1977 if(gameInfo.variant == newVariant) return;
1979 /* [HGM] This routine is called each time an assignment is made to
1980 * gameInfo.variant during a game, to make sure the board sizes
1981 * are set to match the new variant. If that means adding or deleting
1982 * holdings, we shift the playing board accordingly
1983 * This kludge is needed because in ICS observe mode, we get boards
1984 * of an ongoing game without knowing the variant, and learn about the
1985 * latter only later. This can be because of the move list we requested,
1986 * in which case the game history is refilled from the beginning anyway,
1987 * but also when receiving holdings of a crazyhouse game. In the latter
1988 * case we want to add those holdings to the already received position.
1992 if (appData.debugMode) {
1993 fprintf(debugFP, "Switch board from %s to %s\n",
1994 VariantName(gameInfo.variant), VariantName(newVariant));
1995 setbuf(debugFP, NULL);
1997 shuffleOpenings = 0; /* [HGM] shuffle */
1998 gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2002 newWidth = 9; newHeight = 9;
2003 gameInfo.holdingsSize = 7;
2004 case VariantBughouse:
2005 case VariantCrazyhouse:
2006 newHoldingsWidth = 2; break;
2010 newHoldingsWidth = 2;
2011 gameInfo.holdingsSize = 8;
2014 case VariantCapablanca:
2015 case VariantCapaRandom:
2018 newHoldingsWidth = gameInfo.holdingsSize = 0;
2021 if(newWidth != gameInfo.boardWidth ||
2022 newHeight != gameInfo.boardHeight ||
2023 newHoldingsWidth != gameInfo.holdingsWidth ) {
2025 /* shift position to new playing area, if needed */
2026 if(newHoldingsWidth > gameInfo.holdingsWidth) {
2027 for(i=0; i<BOARD_HEIGHT; i++)
2028 for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2029 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2031 for(i=0; i<newHeight; i++) {
2032 board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2033 board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2035 } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2036 for(i=0; i<BOARD_HEIGHT; i++)
2037 for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2038 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2041 gameInfo.boardWidth = newWidth;
2042 gameInfo.boardHeight = newHeight;
2043 gameInfo.holdingsWidth = newHoldingsWidth;
2044 gameInfo.variant = newVariant;
2045 InitDrawingSizes(-2, 0);
2046 } else gameInfo.variant = newVariant;
2047 CopyBoard(oldBoard, board); // remember correctly formatted board
2048 InitPosition(FALSE); /* this sets up board[0], but also other stuff */
2049 DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2052 static int loggedOn = FALSE;
2054 /*-- Game start info cache: --*/
2056 char gs_kind[MSG_SIZ];
2057 static char player1Name[128] = "";
2058 static char player2Name[128] = "";
2059 static char cont_seq[] = "\n\\ ";
2060 static int player1Rating = -1;
2061 static int player2Rating = -1;
2062 /*----------------------------*/
2064 ColorClass curColor = ColorNormal;
2065 int suppressKibitz = 0;
2068 read_from_ics(isr, closure, data, count, error)
2075 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2076 #define STARTED_NONE 0
2077 #define STARTED_MOVES 1
2078 #define STARTED_BOARD 2
2079 #define STARTED_OBSERVE 3
2080 #define STARTED_HOLDINGS 4
2081 #define STARTED_CHATTER 5
2082 #define STARTED_COMMENT 6
2083 #define STARTED_MOVES_NOHIDE 7
2085 static int started = STARTED_NONE;
2086 static char parse[20000];
2087 static int parse_pos = 0;
2088 static char buf[BUF_SIZE + 1];
2089 static int firstTime = TRUE, intfSet = FALSE;
2090 static ColorClass prevColor = ColorNormal;
2091 static int savingComment = FALSE;
2092 static int cmatch = 0; // continuation sequence match
2099 int backup; /* [DM] For zippy color lines */
2101 char talker[MSG_SIZ]; // [HGM] chat
2104 connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2106 if (appData.debugMode) {
2108 fprintf(debugFP, "<ICS: ");
2109 show_bytes(debugFP, data, count);
2110 fprintf(debugFP, "\n");
2114 if (appData.debugMode) { int f = forwardMostMove;
2115 fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2116 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2117 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2120 /* If last read ended with a partial line that we couldn't parse,
2121 prepend it to the new read and try again. */
2122 if (leftover_len > 0) {
2123 for (i=0; i<leftover_len; i++)
2124 buf[i] = buf[leftover_start + i];
2127 /* copy new characters into the buffer */
2128 bp = buf + leftover_len;
2129 buf_len=leftover_len;
2130 for (i=0; i<count; i++)
2133 if (data[i] == '\r')
2136 // join lines split by ICS?
2137 if (!appData.noJoin)
2140 Joining just consists of finding matches against the
2141 continuation sequence, and discarding that sequence
2142 if found instead of copying it. So, until a match
2143 fails, there's nothing to do since it might be the
2144 complete sequence, and thus, something we don't want
2147 if (data[i] == cont_seq[cmatch])
2150 if (cmatch == strlen(cont_seq))
2152 cmatch = 0; // complete match. just reset the counter
2155 it's possible for the ICS to not include the space
2156 at the end of the last word, making our [correct]
2157 join operation fuse two separate words. the server
2158 does this when the space occurs at the width setting.
2160 if (!buf_len || buf[buf_len-1] != ' ')
2171 match failed, so we have to copy what matched before
2172 falling through and copying this character. In reality,
2173 this will only ever be just the newline character, but
2174 it doesn't hurt to be precise.
2176 strncpy(bp, cont_seq, cmatch);
2188 buf[buf_len] = NULLCHAR;
2189 // next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2194 while (i < buf_len) {
2195 /* Deal with part of the TELNET option negotiation
2196 protocol. We refuse to do anything beyond the
2197 defaults, except that we allow the WILL ECHO option,
2198 which ICS uses to turn off password echoing when we are
2199 directly connected to it. We reject this option
2200 if localLineEditing mode is on (always on in xboard)
2201 and we are talking to port 23, which might be a real
2202 telnet server that will try to keep WILL ECHO on permanently.
2204 if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2205 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2206 unsigned char option;
2208 switch ((unsigned char) buf[++i]) {
2210 if (appData.debugMode)
2211 fprintf(debugFP, "\n<WILL ");
2212 switch (option = (unsigned char) buf[++i]) {
2214 if (appData.debugMode)
2215 fprintf(debugFP, "ECHO ");
2216 /* Reply only if this is a change, according
2217 to the protocol rules. */
2218 if (remoteEchoOption) break;
2219 if (appData.localLineEditing &&
2220 atoi(appData.icsPort) == TN_PORT) {
2221 TelnetRequest(TN_DONT, TN_ECHO);
2224 TelnetRequest(TN_DO, TN_ECHO);
2225 remoteEchoOption = TRUE;
2229 if (appData.debugMode)
2230 fprintf(debugFP, "%d ", option);
2231 /* Whatever this is, we don't want it. */
2232 TelnetRequest(TN_DONT, option);
2237 if (appData.debugMode)
2238 fprintf(debugFP, "\n<WONT ");
2239 switch (option = (unsigned char) buf[++i]) {
2241 if (appData.debugMode)
2242 fprintf(debugFP, "ECHO ");
2243 /* Reply only if this is a change, according
2244 to the protocol rules. */
2245 if (!remoteEchoOption) break;
2247 TelnetRequest(TN_DONT, TN_ECHO);
2248 remoteEchoOption = FALSE;
2251 if (appData.debugMode)
2252 fprintf(debugFP, "%d ", (unsigned char) option);
2253 /* Whatever this is, it must already be turned
2254 off, because we never agree to turn on
2255 anything non-default, so according to the
2256 protocol rules, we don't reply. */
2261 if (appData.debugMode)
2262 fprintf(debugFP, "\n<DO ");
2263 switch (option = (unsigned char) buf[++i]) {
2265 /* Whatever this is, we refuse to do it. */
2266 if (appData.debugMode)
2267 fprintf(debugFP, "%d ", option);
2268 TelnetRequest(TN_WONT, option);
2273 if (appData.debugMode)
2274 fprintf(debugFP, "\n<DONT ");
2275 switch (option = (unsigned char) buf[++i]) {
2277 if (appData.debugMode)
2278 fprintf(debugFP, "%d ", option);
2279 /* Whatever this is, we are already not doing
2280 it, because we never agree to do anything
2281 non-default, so according to the protocol
2282 rules, we don't reply. */
2287 if (appData.debugMode)
2288 fprintf(debugFP, "\n<IAC ");
2289 /* Doubled IAC; pass it through */
2293 if (appData.debugMode)
2294 fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2295 /* Drop all other telnet commands on the floor */
2298 if (oldi > next_out)
2299 SendToPlayer(&buf[next_out], oldi - next_out);
2305 /* OK, this at least will *usually* work */
2306 if (!loggedOn && looking_at(buf, &i, "ics%")) {
2310 if (loggedOn && !intfSet) {
2311 if (ics_type == ICS_ICC) {
2313 "/set-quietly interface %s\n/set-quietly style 12\n",
2315 } else if (ics_type == ICS_CHESSNET) {
2316 sprintf(str, "/style 12\n");
2318 strcpy(str, "alias $ @\n$set interface ");
2319 strcat(str, programVersion);
2320 strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2322 strcat(str, "$iset nohighlight 1\n");
2324 strcat(str, "$iset lock 1\n$style 12\n");
2327 NotifyFrontendLogin();
2331 if (started == STARTED_COMMENT) {
2332 /* Accumulate characters in comment */
2333 parse[parse_pos++] = buf[i];
2334 if (buf[i] == '\n') {
2335 parse[parse_pos] = NULLCHAR;
2336 if(chattingPartner>=0) {
2338 sprintf(mess, "%s%s", talker, parse);
2339 OutputChatMessage(chattingPartner, mess);
2340 chattingPartner = -1;
2342 if(!suppressKibitz) // [HGM] kibitz
2343 AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
2344 else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2345 int nrDigit = 0, nrAlph = 0, j;
2346 if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2347 { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2348 parse[parse_pos] = NULLCHAR;
2349 // try to be smart: if it does not look like search info, it should go to
2350 // ICS interaction window after all, not to engine-output window.
2351 for(j=0; j<parse_pos; j++) { // count letters and digits
2352 nrDigit += (parse[j] >= '0' && parse[j] <= '9');
2353 nrAlph += (parse[j] >= 'a' && parse[j] <= 'z');
2354 nrAlph += (parse[j] >= 'A' && parse[j] <= 'Z');
2356 if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2357 int depth=0; float score;
2358 if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2359 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2360 pvInfoList[forwardMostMove-1].depth = depth;
2361 pvInfoList[forwardMostMove-1].score = 100*score;
2363 OutputKibitz(suppressKibitz, parse);
2364 next_out = i+1; // [HGM] suppress printing in ICS window
2367 sprintf(tmp, _("your opponent kibitzes: %s"), parse);
2368 SendToPlayer(tmp, strlen(tmp));
2371 started = STARTED_NONE;
2373 /* Don't match patterns against characters in comment */
2378 if (started == STARTED_CHATTER) {
2379 if (buf[i] != '\n') {
2380 /* Don't match patterns against characters in chatter */
2384 started = STARTED_NONE;
2387 /* Kludge to deal with rcmd protocol */
2388 if (firstTime && looking_at(buf, &i, "\001*")) {
2389 DisplayFatalError(&buf[1], 0, 1);
2395 if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2398 if (appData.debugMode)
2399 fprintf(debugFP, "ics_type %d\n", ics_type);
2402 if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
2403 ics_type = ICS_FICS;
2405 if (appData.debugMode)
2406 fprintf(debugFP, "ics_type %d\n", ics_type);
2409 if (!loggedOn && looking_at(buf, &i, "chess.net")) {
2410 ics_type = ICS_CHESSNET;
2412 if (appData.debugMode)
2413 fprintf(debugFP, "ics_type %d\n", ics_type);
2418 (looking_at(buf, &i, "\"*\" is *a registered name") ||
2419 looking_at(buf, &i, "Logging you in as \"*\"") ||
2420 looking_at(buf, &i, "will be \"*\""))) {
2421 strcpy(ics_handle, star_match[0]);
2425 if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
2427 snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
2428 DisplayIcsInteractionTitle(buf);
2429 have_set_title = TRUE;
2432 /* skip finger notes */
2433 if (started == STARTED_NONE &&
2434 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
2435 (buf[i] == '1' && buf[i+1] == '0')) &&
2436 buf[i+2] == ':' && buf[i+3] == ' ') {
2437 started = STARTED_CHATTER;
2442 /* skip formula vars */
2443 if (started == STARTED_NONE &&
2444 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
2445 started = STARTED_CHATTER;
2451 // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
2452 if (appData.autoKibitz && started == STARTED_NONE &&
2453 !appData.icsEngineAnalyze && // [HGM] [DM] ICS analyze
2454 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
2455 if(looking_at(buf, &i, "* kibitzes: ") &&
2456 (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
2457 StrStr(star_match[0], gameInfo.black) == star_match[0] )) { // kibitz of self or opponent
2458 suppressKibitz = TRUE;
2459 if((StrStr(star_match[0], gameInfo.white) == star_match[0]
2460 && (gameMode == IcsPlayingWhite)) ||
2461 (StrStr(star_match[0], gameInfo.black) == star_match[0]
2462 && (gameMode == IcsPlayingBlack)) ) // opponent kibitz
2463 started = STARTED_CHATTER; // own kibitz we simply discard
2465 started = STARTED_COMMENT; // make sure it will be collected in parse[]
2466 parse_pos = 0; parse[0] = NULLCHAR;
2467 savingComment = TRUE;
2468 suppressKibitz = gameMode != IcsObserving ? 2 :
2469 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
2473 if(looking_at(buf, &i, "kibitzed to *\n") && atoi(star_match[0])) {
2474 // suppress the acknowledgements of our own autoKibitz
2476 if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
2477 SendToPlayer(star_match[0], strlen(star_match[0]));
2478 looking_at(buf, &i, "*% "); // eat prompt
2481 } // [HGM] kibitz: end of patch
2483 //if(appData.debugMode) fprintf(debugFP, "hunt for tell, buf = %s\n", buf+i);
2485 // [HGM] chat: intercept tells by users for which we have an open chat window
2487 if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
2488 looking_at(buf, &i, "* whispers:") ||
2489 looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
2490 looking_at(buf, &i, "*(*)(*):") && sscanf(star_match[2], "%d", &channel) == 1 )) {
2492 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
2493 chattingPartner = -1;
2495 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
2496 for(p=0; p<MAX_CHAT; p++) {
2497 if(channel == atoi(chatPartner[p])) {
2498 talker[0] = '['; strcat(talker, "] ");
2499 chattingPartner = p; break;
2502 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
2503 for(p=0; p<MAX_CHAT; p++) {
2504 if(!strcmp("WHISPER", chatPartner[p])) {
2505 talker[0] = '['; strcat(talker, "] ");
2506 chattingPartner = p; break;
2509 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
2510 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
2512 chattingPartner = p; break;
2514 if(chattingPartner<0) i = oldi; else {
2515 started = STARTED_COMMENT;
2516 parse_pos = 0; parse[0] = NULLCHAR;
2517 savingComment = 3 + chattingPartner; // counts as TRUE
2518 suppressKibitz = TRUE;
2520 } // [HGM] chat: end of patch
2522 if (appData.zippyTalk || appData.zippyPlay) {
2523 /* [DM] Backup address for color zippy lines */
2527 if (loggedOn == TRUE)
2528 if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
2529 (appData.zippyPlay && ZippyMatch(buf, &backup)));
2531 if (ZippyControl(buf, &i) ||
2532 ZippyConverse(buf, &i) ||
2533 (appData.zippyPlay && ZippyMatch(buf, &i))) {
2535 if (!appData.colorize) continue;
2539 } // [DM] 'else { ' deleted
2541 /* Regular tells and says */
2542 (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
2543 looking_at(buf, &i, "* (your partner) tells you: ") ||
2544 looking_at(buf, &i, "* says: ") ||
2545 /* Don't color "message" or "messages" output */
2546 (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
2547 looking_at(buf, &i, "*. * at *:*: ") ||
2548 looking_at(buf, &i, "--* (*:*): ") ||
2549 /* Message notifications (same color as tells) */
2550 looking_at(buf, &i, "* has left a message ") ||
2551 looking_at(buf, &i, "* just sent you a message:\n") ||
2552 /* Whispers and kibitzes */
2553 (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
2554 looking_at(buf, &i, "* kibitzes: ") ||
2556 (tkind = 3, looking_at(buf, &i, "*(*: "))) {
2558 if (tkind == 1 && strchr(star_match[0], ':')) {
2559 /* Avoid "tells you:" spoofs in channels */
2562 if (star_match[0][0] == NULLCHAR ||
2563 strchr(star_match[0], ' ') ||
2564 (tkind == 3 && strchr(star_match[1], ' '))) {
2565 /* Reject bogus matches */
2568 if (appData.colorize) {
2569 if (oldi > next_out) {
2570 SendToPlayer(&buf[next_out], oldi - next_out);
2575 Colorize(ColorTell, FALSE);
2576 curColor = ColorTell;
2579 Colorize(ColorKibitz, FALSE);
2580 curColor = ColorKibitz;
2583 p = strrchr(star_match[1], '(');
2590 Colorize(ColorChannel1, FALSE);
2591 curColor = ColorChannel1;
2593 Colorize(ColorChannel, FALSE);
2594 curColor = ColorChannel;
2598 curColor = ColorNormal;
2602 if (started == STARTED_NONE && appData.autoComment &&
2603 (gameMode == IcsObserving ||
2604 gameMode == IcsPlayingWhite ||
2605 gameMode == IcsPlayingBlack)) {
2606 parse_pos = i - oldi;
2607 memcpy(parse, &buf[oldi], parse_pos);
2608 parse[parse_pos] = NULLCHAR;
2609 started = STARTED_COMMENT;
2610 savingComment = TRUE;
2612 started = STARTED_CHATTER;
2613 savingComment = FALSE;
2620 if (looking_at(buf, &i, "* s-shouts: ") ||
2621 looking_at(buf, &i, "* c-shouts: ")) {
2622 if (appData.colorize) {
2623 if (oldi > next_out) {
2624 SendToPlayer(&buf[next_out], oldi - next_out);
2627 Colorize(ColorSShout, FALSE);
2628 curColor = ColorSShout;
2631 started = STARTED_CHATTER;
2635 if (looking_at(buf, &i, "--->")) {
2640 if (looking_at(buf, &i, "* shouts: ") ||
2641 looking_at(buf, &i, "--> ")) {
2642 if (appData.colorize) {
2643 if (oldi > next_out) {
2644 SendToPlayer(&buf[next_out], oldi - next_out);
2647 Colorize(ColorShout, FALSE);
2648 curColor = ColorShout;
2651 started = STARTED_CHATTER;
2655 if (looking_at( buf, &i, "Challenge:")) {
2656 if (appData.colorize) {
2657 if (oldi > next_out) {
2658 SendToPlayer(&buf[next_out], oldi - next_out);
2661 Colorize(ColorChallenge, FALSE);
2662 curColor = ColorChallenge;
2668 if (looking_at(buf, &i, "* offers you") ||
2669 looking_at(buf, &i, "* offers to be") ||
2670 looking_at(buf, &i, "* would like to") ||
2671 looking_at(buf, &i, "* requests to") ||
2672 looking_at(buf, &i, "Your opponent offers") ||
2673 looking_at(buf, &i, "Your opponent requests")) {
2675 if (appData.colorize) {
2676 if (oldi > next_out) {
2677 SendToPlayer(&buf[next_out], oldi - next_out);
2680 Colorize(ColorRequest, FALSE);
2681 curColor = ColorRequest;
2686 if (looking_at(buf, &i, "* (*) seeking")) {
2687 if (appData.colorize) {
2688 if (oldi > next_out) {
2689 SendToPlayer(&buf[next_out], oldi - next_out);
2692 Colorize(ColorSeek, FALSE);
2693 curColor = ColorSeek;
2698 if (looking_at(buf, &i, "\\ ")) {
2699 if (prevColor != ColorNormal) {
2700 if (oldi > next_out) {
2701 SendToPlayer(&buf[next_out], oldi - next_out);
2704 Colorize(prevColor, TRUE);
2705 curColor = prevColor;
2707 if (savingComment) {
2708 parse_pos = i - oldi;
2709 memcpy(parse, &buf[oldi], parse_pos);
2710 parse[parse_pos] = NULLCHAR;
2711 started = STARTED_COMMENT;
2712 if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
2713 chattingPartner = savingComment - 3; // kludge to remember the box
2715 started = STARTED_CHATTER;
2720 if (looking_at(buf, &i, "Black Strength :") ||
2721 looking_at(buf, &i, "<<< style 10 board >>>") ||
2722 looking_at(buf, &i, "<10>") ||
2723 looking_at(buf, &i, "#@#")) {
2724 /* Wrong board style */
2726 SendToICS(ics_prefix);
2727 SendToICS("set style 12\n");
2728 SendToICS(ics_prefix);
2729 SendToICS("refresh\n");
2733 if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
2735 have_sent_ICS_logon = 1;
2739 if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
2740 (looking_at(buf, &i, "\n<12> ") ||
2741 looking_at(buf, &i, "<12> "))) {
2743 if (oldi > next_out) {
2744 SendToPlayer(&buf[next_out], oldi - next_out);
2747 started = STARTED_BOARD;
2752 if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
2753 looking_at(buf, &i, "<b1> ")) {
2754 if (oldi > next_out) {
2755 SendToPlayer(&buf[next_out], oldi - next_out);
2758 started = STARTED_HOLDINGS;
2763 if (looking_at(buf, &i, "* *vs. * *--- *")) {
2765 /* Header for a move list -- first line */
2767 switch (ics_getting_history) {
2771 case BeginningOfGame:
2772 /* User typed "moves" or "oldmoves" while we
2773 were idle. Pretend we asked for these
2774 moves and soak them up so user can step
2775 through them and/or save them.
2778 gameMode = IcsObserving;
2781 ics_getting_history = H_GOT_UNREQ_HEADER;
2783 case EditGame: /*?*/
2784 case EditPosition: /*?*/
2785 /* Should above feature work in these modes too? */
2786 /* For now it doesn't */
2787 ics_getting_history = H_GOT_UNWANTED_HEADER;
2790 ics_getting_history = H_GOT_UNWANTED_HEADER;
2795 /* Is this the right one? */
2796 if (gameInfo.white && gameInfo.black &&
2797 strcmp(gameInfo.white, star_match[0]) == 0 &&
2798 strcmp(gameInfo.black, star_match[2]) == 0) {
2800 ics_getting_history = H_GOT_REQ_HEADER;
2803 case H_GOT_REQ_HEADER:
2804 case H_GOT_UNREQ_HEADER:
2805 case H_GOT_UNWANTED_HEADER:
2806 case H_GETTING_MOVES:
2807 /* Should not happen */
2808 DisplayError(_("Error gathering move list: two headers"), 0);
2809 ics_getting_history = H_FALSE;
2813 /* Save player ratings into gameInfo if needed */
2814 if ((ics_getting_history == H_GOT_REQ_HEADER ||
2815 ics_getting_history == H_GOT_UNREQ_HEADER) &&
2816 (gameInfo.whiteRating == -1 ||
2817 gameInfo.blackRating == -1)) {
2819 gameInfo.whiteRating = string_to_rating(star_match[1]);
2820 gameInfo.blackRating = string_to_rating(star_match[3]);
2821 if (appData.debugMode)
2822 fprintf(debugFP, _("Ratings from header: W %d, B %d\n"),
2823 gameInfo.whiteRating, gameInfo.blackRating);
2828 if (looking_at(buf, &i,
2829 "* * match, initial time: * minute*, increment: * second")) {
2830 /* Header for a move list -- second line */
2831 /* Initial board will follow if this is a wild game */
2832 if (gameInfo.event != NULL) free(gameInfo.event);
2833 sprintf(str, "ICS %s %s match", star_match[0], star_match[1]);
2834 gameInfo.event = StrSave(str);
2835 /* [HGM] we switched variant. Translate boards if needed. */
2836 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
2840 if (looking_at(buf, &i, "Move ")) {
2841 /* Beginning of a move list */
2842 switch (ics_getting_history) {
2844 /* Normally should not happen */
2845 /* Maybe user hit reset while we were parsing */
2848 /* Happens if we are ignoring a move list that is not
2849 * the one we just requested. Common if the user
2850 * tries to observe two games without turning off
2853 case H_GETTING_MOVES:
2854 /* Should not happen */
2855 DisplayError(_("Error gathering move list: nested"), 0);
2856 ics_getting_history = H_FALSE;
2858 case H_GOT_REQ_HEADER:
2859 ics_getting_history = H_GETTING_MOVES;
2860 started = STARTED_MOVES;
2862 if (oldi > next_out) {
2863 SendToPlayer(&buf[next_out], oldi - next_out);
2866 case H_GOT_UNREQ_HEADER:
2867 ics_getting_history = H_GETTING_MOVES;
2868 started = STARTED_MOVES_NOHIDE;
2871 case H_GOT_UNWANTED_HEADER:
2872 ics_getting_history = H_FALSE;
2878 if (looking_at(buf, &i, "% ") ||
2879 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
2880 && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
2881 if(suppressKibitz) next_out = i;
2882 savingComment = FALSE;
2886 case STARTED_MOVES_NOHIDE:
2887 memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
2888 parse[parse_pos + i - oldi] = NULLCHAR;
2889 ParseGameHistory(parse);
2891 if (appData.zippyPlay && first.initDone) {
2892 FeedMovesToProgram(&first, forwardMostMove);
2893 if (gameMode == IcsPlayingWhite) {
2894 if (WhiteOnMove(forwardMostMove)) {
2895 if (first.sendTime) {
2896 if (first.useColors) {
2897 SendToProgram("black\n", &first);
2899 SendTimeRemaining(&first, TRUE);
2901 if (first.useColors) {
2902 SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
2904 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
2905 first.maybeThinking = TRUE;
2907 if (first.usePlayother) {
2908 if (first.sendTime) {
2909 SendTimeRemaining(&first, TRUE);
2911 SendToProgram("playother\n", &first);
2917 } else if (gameMode == IcsPlayingBlack) {
2918 if (!WhiteOnMove(forwardMostMove)) {
2919 if (first.sendTime) {
2920 if (first.useColors) {
2921 SendToProgram("white\n", &first);
2923 SendTimeRemaining(&first, FALSE);
2925 if (first.useColors) {
2926 SendToProgram("black\n", &first);
2928 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
2929 first.maybeThinking = TRUE;
2931 if (first.usePlayother) {
2932 if (first.sendTime) {
2933 SendTimeRemaining(&first, FALSE);
2935 SendToProgram("playother\n", &first);
2944 if (gameMode == IcsObserving && ics_gamenum == -1) {
2945 /* Moves came from oldmoves or moves command
2946 while we weren't doing anything else.
2948 currentMove = forwardMostMove;
2949 ClearHighlights();/*!!could figure this out*/
2950 flipView = appData.flipView;
2951 DrawPosition(TRUE, boards[currentMove]);
2952 DisplayBothClocks();
2953 sprintf(str, "%s vs. %s",
2954 gameInfo.white, gameInfo.black);
2958 /* Moves were history of an active game */
2959 if (gameInfo.resultDetails != NULL) {
2960 free(gameInfo.resultDetails);
2961 gameInfo.resultDetails = NULL;
2964 HistorySet(parseList, backwardMostMove,
2965 forwardMostMove, currentMove-1);
2966 DisplayMove(currentMove - 1);
2967 if (started == STARTED_MOVES) next_out = i;
2968 started = STARTED_NONE;
2969 ics_getting_history = H_FALSE;
2972 case STARTED_OBSERVE:
2973 started = STARTED_NONE;
2974 SendToICS(ics_prefix);
2975 SendToICS("refresh\n");
2981 if(bookHit) { // [HGM] book: simulate book reply
2982 static char bookMove[MSG_SIZ]; // a bit generous?
2984 programStats.nodes = programStats.depth = programStats.time =
2985 programStats.score = programStats.got_only_move = 0;
2986 sprintf(programStats.movelist, "%s (xbook)", bookHit);
2988 strcpy(bookMove, "move ");
2989 strcat(bookMove, bookHit);
2990 HandleMachineMove(bookMove, &first);
2995 if ((started == STARTED_MOVES || started == STARTED_BOARD ||
2996 started == STARTED_HOLDINGS ||
2997 started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
2998 /* Accumulate characters in move list or board */
2999 parse[parse_pos++] = buf[i];
3002 /* Start of game messages. Mostly we detect start of game
3003 when the first board image arrives. On some versions
3004 of the ICS, though, we need to do a "refresh" after starting
3005 to observe in order to get the current board right away. */
3006 if (looking_at(buf, &i, "Adding game * to observation list")) {
3007 started = STARTED_OBSERVE;
3011 /* Handle auto-observe */
3012 if (appData.autoObserve &&
3013 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3014 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3016 /* Choose the player that was highlighted, if any. */
3017 if (star_match[0][0] == '\033' ||
3018 star_match[1][0] != '\033') {
3019 player = star_match[0];
3021 player = star_match[2];
3023 sprintf(str, "%sobserve %s\n",
3024 ics_prefix, StripHighlightAndTitle(player));
3027 /* Save ratings from notify string */
3028 strcpy(player1Name, star_match[0]);
3029 player1Rating = string_to_rating(star_match[1]);
3030 strcpy(player2Name, star_match[2]);
3031 player2Rating = string_to_rating(star_match[3]);
3033 if (appData.debugMode)
3035 "Ratings from 'Game notification:' %s %d, %s %d\n",
3036 player1Name, player1Rating,
3037 player2Name, player2Rating);
3042 /* Deal with automatic examine mode after a game,
3043 and with IcsObserving -> IcsExamining transition */
3044 if (looking_at(buf, &i, "Entering examine mode for game *") ||
3045 looking_at(buf, &i, "has made you an examiner of game *")) {
3047 int gamenum = atoi(star_match[0]);
3048 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3049 gamenum == ics_gamenum) {
3050 /* We were already playing or observing this game;
3051 no need to refetch history */
3052 gameMode = IcsExamining;
3054 pauseExamForwardMostMove = forwardMostMove;
3055 } else if (currentMove < forwardMostMove) {
3056 ForwardInner(forwardMostMove);
3059 /* I don't think this case really can happen */
3060 SendToICS(ics_prefix);
3061 SendToICS("refresh\n");
3066 /* Error messages */
3067 // if (ics_user_moved) {
3068 if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3069 if (looking_at(buf, &i, "Illegal move") ||
3070 looking_at(buf, &i, "Not a legal move") ||
3071 looking_at(buf, &i, "Your king is in check") ||
3072 looking_at(buf, &i, "It isn't your turn") ||
3073 looking_at(buf, &i, "It is not your move")) {
3075 if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3076 currentMove = --forwardMostMove;
3077 DisplayMove(currentMove - 1); /* before DMError */
3078 DrawPosition(FALSE, boards[currentMove]);
3080 DisplayBothClocks();
3082 DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3088 if (looking_at(buf, &i, "still have time") ||
3089 looking_at(buf, &i, "not out of time") ||
3090 looking_at(buf, &i, "either player is out of time") ||
3091 looking_at(buf, &i, "has timeseal; checking")) {
3092 /* We must have called his flag a little too soon */
3093 whiteFlag = blackFlag = FALSE;
3097 if (looking_at(buf, &i, "added * seconds to") ||
3098 looking_at(buf, &i, "seconds were added to")) {
3099 /* Update the clocks */
3100 SendToICS(ics_prefix);
3101 SendToICS("refresh\n");
3105 if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3106 ics_clock_paused = TRUE;
3111 if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3112 ics_clock_paused = FALSE;
3117 /* Grab player ratings from the Creating: message.
3118 Note we have to check for the special case when
3119 the ICS inserts things like [white] or [black]. */
3120 if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3121 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3123 0 player 1 name (not necessarily white)
3125 2 empty, white, or black (IGNORED)
3126 3 player 2 name (not necessarily black)
3129 The names/ratings are sorted out when the game
3130 actually starts (below).
3132 strcpy(player1Name, StripHighlightAndTitle(star_match[0]));
3133 player1Rating = string_to_rating(star_match[1]);
3134 strcpy(player2Name, StripHighlightAndTitle(star_match[3]));
3135 player2Rating = string_to_rating(star_match[4]);
3137 if (appData.debugMode)
3139 "Ratings from 'Creating:' %s %d, %s %d\n",
3140 player1Name, player1Rating,
3141 player2Name, player2Rating);
3146 /* Improved generic start/end-of-game messages */
3147 if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3148 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3149 /* If tkind == 0: */
3150 /* star_match[0] is the game number */
3151 /* [1] is the white player's name */
3152 /* [2] is the black player's name */
3153 /* For end-of-game: */
3154 /* [3] is the reason for the game end */
3155 /* [4] is a PGN end game-token, preceded by " " */
3156 /* For start-of-game: */
3157 /* [3] begins with "Creating" or "Continuing" */
3158 /* [4] is " *" or empty (don't care). */
3159 int gamenum = atoi(star_match[0]);
3160 char *whitename, *blackname, *why, *endtoken;
3161 ChessMove endtype = (ChessMove) 0;
3164 whitename = star_match[1];
3165 blackname = star_match[2];
3166 why = star_match[3];
3167 endtoken = star_match[4];
3169 whitename = star_match[1];
3170 blackname = star_match[3];
3171 why = star_match[5];
3172 endtoken = star_match[6];
3175 /* Game start messages */
3176 if (strncmp(why, "Creating ", 9) == 0 ||
3177 strncmp(why, "Continuing ", 11) == 0) {
3178 gs_gamenum = gamenum;
3179 strcpy(gs_kind, strchr(why, ' ') + 1);
3180 VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
3182 if (appData.zippyPlay) {
3183 ZippyGameStart(whitename, blackname);
3189 /* Game end messages */
3190 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3191 ics_gamenum != gamenum) {
3194 while (endtoken[0] == ' ') endtoken++;
3195 switch (endtoken[0]) {
3198 endtype = GameUnfinished;
3201 endtype = BlackWins;
3204 if (endtoken[1] == '/')
3205 endtype = GameIsDrawn;
3207 endtype = WhiteWins;
3210 GameEnds(endtype, why, GE_ICS);
3212 if (appData.zippyPlay && first.initDone) {
3213 ZippyGameEnd(endtype, why);
3214 if (first.pr == NULL) {
3215 /* Start the next process early so that we'll
3216 be ready for the next challenge */
3217 StartChessProgram(&first);
3219 /* Send "new" early, in case this command takes
3220 a long time to finish, so that we'll be ready
3221 for the next challenge. */
3222 gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3229 if (looking_at(buf, &i, "Removing game * from observation") ||
3230 looking_at(buf, &i, "no longer observing game *") ||
3231 looking_at(buf, &i, "Game * (*) has no examiners")) {
3232 if (gameMode == IcsObserving &&
3233 atoi(star_match[0]) == ics_gamenum)
3235 /* icsEngineAnalyze */
3236 if (appData.icsEngineAnalyze) {
3243 ics_user_moved = FALSE;
3248 if (looking_at(buf, &i, "no longer examining game *")) {
3249 if (gameMode == IcsExamining &&
3250 atoi(star_match[0]) == ics_gamenum)
3254 ics_user_moved = FALSE;
3259 /* Advance leftover_start past any newlines we find,
3260 so only partial lines can get reparsed */
3261 if (looking_at(buf, &i, "\n")) {
3262 prevColor = curColor;
3263 if (curColor != ColorNormal) {
3264 if (oldi > next_out) {
3265 SendToPlayer(&buf[next_out], oldi - next_out);
3268 Colorize(ColorNormal, FALSE);
3269 curColor = ColorNormal;
3271 if (started == STARTED_BOARD) {
3272 started = STARTED_NONE;
3273 parse[parse_pos] = NULLCHAR;
3274 ParseBoard12(parse);
3277 /* Send premove here */
3278 if (appData.premove) {
3280 if (currentMove == 0 &&
3281 gameMode == IcsPlayingWhite &&
3282 appData.premoveWhite) {
3283 sprintf(str, "%s\n", appData.premoveWhiteText);
3284 if (appData.debugMode)
3285 fprintf(debugFP, "Sending premove:\n");
3287 } else if (currentMove == 1 &&
3288 gameMode == IcsPlayingBlack &&
3289 appData.premoveBlack) {
3290 sprintf(str, "%s\n", appData.premoveBlackText);
3291 if (appData.debugMode)
3292 fprintf(debugFP, "Sending premove:\n");
3294 } else if (gotPremove) {
3296 ClearPremoveHighlights();
3297 if (appData.debugMode)
3298 fprintf(debugFP, "Sending premove:\n");
3299 UserMoveEvent(premoveFromX, premoveFromY,
3300 premoveToX, premoveToY,
3305 /* Usually suppress following prompt */
3306 if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
3307 while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
3308 if (looking_at(buf, &i, "*% ")) {
3309 savingComment = FALSE;
3314 } else if (started == STARTED_HOLDINGS) {
3316 char new_piece[MSG_SIZ];
3317 started = STARTED_NONE;
3318 parse[parse_pos] = NULLCHAR;
3319 if (appData.debugMode)
3320 fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
3321 parse, currentMove);
3322 if (sscanf(parse, " game %d", &gamenum) == 1 &&
3323 gamenum == ics_gamenum) {
3324 if (gameInfo.variant == VariantNormal) {
3325 /* [HGM] We seem to switch variant during a game!
3326 * Presumably no holdings were displayed, so we have
3327 * to move the position two files to the right to
3328 * create room for them!
3330 VariantClass newVariant;
3331 switch(gameInfo.boardWidth) { // base guess on board width
3332 case 9: newVariant = VariantShogi; break;
3333 case 10: newVariant = VariantGreat; break;
3334 default: newVariant = VariantCrazyhouse; break;
3336 VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3337 /* Get a move list just to see the header, which
3338 will tell us whether this is really bug or zh */
3339 if (ics_getting_history == H_FALSE) {
3340 ics_getting_history = H_REQUESTED;
3341 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3345 new_piece[0] = NULLCHAR;
3346 sscanf(parse, "game %d white [%s black [%s <- %s",
3347 &gamenum, white_holding, black_holding,
3349 white_holding[strlen(white_holding)-1] = NULLCHAR;
3350 black_holding[strlen(black_holding)-1] = NULLCHAR;
3351 /* [HGM] copy holdings to board holdings area */
3352 CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
3353 CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
3354 boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
3356 if (appData.zippyPlay && first.initDone) {
3357 ZippyHoldings(white_holding, black_holding,
3361 if (tinyLayout || smallLayout) {
3362 char wh[16], bh[16];
3363 PackHolding(wh, white_holding);
3364 PackHolding(bh, black_holding);
3365 sprintf(str, "[%s-%s] %s-%s", wh, bh,
3366 gameInfo.white, gameInfo.black);
3368 sprintf(str, "%s [%s] vs. %s [%s]",
3369 gameInfo.white, white_holding,
3370 gameInfo.black, black_holding);
3373 DrawPosition(FALSE, boards[currentMove]);
3376 /* Suppress following prompt */
3377 if (looking_at(buf, &i, "*% ")) {
3378 if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
3379 savingComment = FALSE;
3387 i++; /* skip unparsed character and loop back */
3390 if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
3391 // started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
3392 // SendToPlayer(&buf[next_out], i - next_out);
3393 started != STARTED_HOLDINGS && leftover_start > next_out) {
3394 SendToPlayer(&buf[next_out], leftover_start - next_out);
3398 leftover_len = buf_len - leftover_start;
3399 /* if buffer ends with something we couldn't parse,
3400 reparse it after appending the next read */
3402 } else if (count == 0) {
3403 RemoveInputSource(isr);
3404 DisplayFatalError(_("Connection closed by ICS"), 0, 0);
3406 DisplayFatalError(_("Error reading from ICS"), error, 1);
3411 /* Board style 12 looks like this:
3413 <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
3415 * The "<12> " is stripped before it gets to this routine. The two
3416 * trailing 0's (flip state and clock ticking) are later addition, and
3417 * some chess servers may not have them, or may have only the first.
3418 * Additional trailing fields may be added in the future.
3421 #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"
3423 #define RELATION_OBSERVING_PLAYED 0
3424 #define RELATION_OBSERVING_STATIC -2 /* examined, oldmoves, or smoves */
3425 #define RELATION_PLAYING_MYMOVE 1
3426 #define RELATION_PLAYING_NOTMYMOVE -1
3427 #define RELATION_EXAMINING 2
3428 #define RELATION_ISOLATED_BOARD -3
3429 #define RELATION_STARTING_POSITION -4 /* FICS only */
3432 ParseBoard12(string)
3435 GameMode newGameMode;
3436 int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
3437 int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
3438 int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
3439 char to_play, board_chars[200];
3440 char move_str[500], str[500], elapsed_time[500];
3441 char black[32], white[32];
3443 int prevMove = currentMove;
3446 int fromX, fromY, toX, toY;
3448 int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
3449 char *bookHit = NULL; // [HGM] book
3450 Boolean weird = FALSE, reqFlag = FALSE;
3452 fromX = fromY = toX = toY = -1;
3456 if (appData.debugMode)
3457 fprintf(debugFP, _("Parsing board: %s\n"), string);
3459 move_str[0] = NULLCHAR;
3460 elapsed_time[0] = NULLCHAR;
3461 { /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
3463 while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
3464 if(string[i] == ' ') { ranks++; files = 0; }
3466 if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
3469 for(j = 0; j <i; j++) board_chars[j] = string[j];
3470 board_chars[i] = '\0';
3473 n = sscanf(string, PATTERN, &to_play, &double_push,
3474 &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
3475 &gamenum, white, black, &relation, &basetime, &increment,
3476 &white_stren, &black_stren, &white_time, &black_time,
3477 &moveNum, str, elapsed_time, move_str, &ics_flip,
3481 snprintf(str, sizeof(str), _("Failed to parse board string:\n\"%s\""), string);
3482 DisplayError(str, 0);
3486 /* Convert the move number to internal form */
3487 moveNum = (moveNum - 1) * 2;
3488 if (to_play == 'B') moveNum++;
3489 if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
3490 DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
3496 case RELATION_OBSERVING_PLAYED:
3497 case RELATION_OBSERVING_STATIC:
3498 if (gamenum == -1) {
3499 /* Old ICC buglet */
3500 relation = RELATION_OBSERVING_STATIC;
3502 newGameMode = IcsObserving;
3504 case RELATION_PLAYING_MYMOVE:
3505 case RELATION_PLAYING_NOTMYMOVE:
3507 ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
3508 IcsPlayingWhite : IcsPlayingBlack;
3510 case RELATION_EXAMINING:
3511 newGameMode = IcsExamining;
3513 case RELATION_ISOLATED_BOARD:
3515 /* Just display this board. If user was doing something else,
3516 we will forget about it until the next board comes. */
3517 newGameMode = IcsIdle;
3519 case RELATION_STARTING_POSITION:
3520 newGameMode = gameMode;
3524 /* Modify behavior for initial board display on move listing
3527 switch (ics_getting_history) {
3531 case H_GOT_REQ_HEADER:
3532 case H_GOT_UNREQ_HEADER:
3533 /* This is the initial position of the current game */
3534 gamenum = ics_gamenum;
3535 moveNum = 0; /* old ICS bug workaround */
3536 if (to_play == 'B') {
3537 startedFromSetupPosition = TRUE;
3538 blackPlaysFirst = TRUE;
3540 if (forwardMostMove == 0) forwardMostMove = 1;
3541 if (backwardMostMove == 0) backwardMostMove = 1;
3542 if (currentMove == 0) currentMove = 1;
3544 newGameMode = gameMode;
3545 relation = RELATION_STARTING_POSITION; /* ICC needs this */
3547 case H_GOT_UNWANTED_HEADER:
3548 /* This is an initial board that we don't want */
3550 case H_GETTING_MOVES:
3551 /* Should not happen */
3552 DisplayError(_("Error gathering move list: extra board"), 0);
3553 ics_getting_history = H_FALSE;
3557 if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
3558 weird && (int)gameInfo.variant <= (int)VariantShogi) {
3559 /* [HGM] We seem to have switched variant unexpectedly
3560 * Try to guess new variant from board size
3562 VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
3563 if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
3564 if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
3565 if(ranks == 8 && files == 12) newVariant = VariantCourier; else
3566 if(ranks == 9 && files == 9) newVariant = VariantShogi; else
3567 if(!weird) newVariant = VariantNormal;
3568 VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3569 /* Get a move list just to see the header, which
3570 will tell us whether this is really bug or zh */
3571 if (ics_getting_history == H_FALSE) {
3572 ics_getting_history = H_REQUESTED; reqFlag = TRUE;
3573 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3578 /* Take action if this is the first board of a new game, or of a
3579 different game than is currently being displayed. */
3580 if (gamenum != ics_gamenum || newGameMode != gameMode ||
3581 relation == RELATION_ISOLATED_BOARD) {
3583 /* Forget the old game and get the history (if any) of the new one */
3584 if (gameMode != BeginningOfGame) {
3588 if (appData.autoRaiseBoard) BoardToTop();
3590 if (gamenum == -1) {
3591 newGameMode = IcsIdle;
3592 } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
3593 appData.getMoveList && !reqFlag) {
3594 /* Need to get game history */
3595 ics_getting_history = H_REQUESTED;
3596 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3600 /* Initially flip the board to have black on the bottom if playing
3601 black or if the ICS flip flag is set, but let the user change
3602 it with the Flip View button. */
3603 flipView = appData.autoFlipView ?
3604 (newGameMode == IcsPlayingBlack) || ics_flip :
3607 /* Done with values from previous mode; copy in new ones */
3608 gameMode = newGameMode;
3610 ics_gamenum = gamenum;
3611 if (gamenum == gs_gamenum) {
3612 int klen = strlen(gs_kind);
3613 if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
3614 sprintf(str, "ICS %s", gs_kind);
3615 gameInfo.event = StrSave(str);
3617 gameInfo.event = StrSave("ICS game");
3619 gameInfo.site = StrSave(appData.icsHost);
3620 gameInfo.date = PGNDate();
3621 gameInfo.round = StrSave("-");
3622 gameInfo.white = StrSave(white);
3623 gameInfo.black = StrSave(black);
3624 timeControl = basetime * 60 * 1000;
3626 timeIncrement = increment * 1000;
3627 movesPerSession = 0;
3628 gameInfo.timeControl = TimeControlTagValue();
3629 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
3630 if (appData.debugMode) {
3631 fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
3632 fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
3633 setbuf(debugFP, NULL);
3636 gameInfo.outOfBook = NULL;
3638 /* Do we have the ratings? */
3639 if (strcmp(player1Name, white) == 0 &&
3640 strcmp(player2Name, black) == 0) {
3641 if (appData.debugMode)
3642 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3643 player1Rating, player2Rating);
3644 gameInfo.whiteRating = player1Rating;
3645 gameInfo.blackRating = player2Rating;
3646 } else if (strcmp(player2Name, white) == 0 &&
3647 strcmp(player1Name, black) == 0) {
3648 if (appData.debugMode)
3649 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3650 player2Rating, player1Rating);
3651 gameInfo.whiteRating = player2Rating;
3652 gameInfo.blackRating = player1Rating;
3654 player1Name[0] = player2Name[0] = NULLCHAR;
3656 /* Silence shouts if requested */
3657 if (appData.quietPlay &&
3658 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
3659 SendToICS(ics_prefix);
3660 SendToICS("set shout 0\n");
3664 /* Deal with midgame name changes */
3666 if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
3667 if (gameInfo.white) free(gameInfo.white);
3668 gameInfo.white = StrSave(white);
3670 if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
3671 if (gameInfo.black) free(gameInfo.black);
3672 gameInfo.black = StrSave(black);
3676 /* Throw away game result if anything actually changes in examine mode */
3677 if (gameMode == IcsExamining && !newGame) {
3678 gameInfo.result = GameUnfinished;
3679 if (gameInfo.resultDetails != NULL) {
3680 free(gameInfo.resultDetails);
3681 gameInfo.resultDetails = NULL;
3685 /* In pausing && IcsExamining mode, we ignore boards coming
3686 in if they are in a different variation than we are. */
3687 if (pauseExamInvalid) return;
3688 if (pausing && gameMode == IcsExamining) {
3689 if (moveNum <= pauseExamForwardMostMove) {
3690 pauseExamInvalid = TRUE;
3691 forwardMostMove = pauseExamForwardMostMove;
3696 if (appData.debugMode) {
3697 fprintf(debugFP, "load %dx%d board\n", files, ranks);
3699 /* Parse the board */
3700 for (k = 0; k < ranks; k++) {
3701 for (j = 0; j < files; j++)
3702 board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
3703 if(gameInfo.holdingsWidth > 1) {
3704 board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
3705 board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
3708 CopyBoard(boards[moveNum], board);
3709 boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
3711 startedFromSetupPosition =
3712 !CompareBoards(board, initialPosition);
3713 if(startedFromSetupPosition)
3714 initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
3717 /* [HGM] Set castling rights. Take the outermost Rooks,
3718 to make it also work for FRC opening positions. Note that board12
3719 is really defective for later FRC positions, as it has no way to
3720 indicate which Rook can castle if they are on the same side of King.
3721 For the initial position we grant rights to the outermost Rooks,
3722 and remember thos rights, and we then copy them on positions
3723 later in an FRC game. This means WB might not recognize castlings with
3724 Rooks that have moved back to their original position as illegal,
3725 but in ICS mode that is not its job anyway.
3727 if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
3728 { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
3730 for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
3731 if(board[0][i] == WhiteRook) j = i;
3732 initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
3733 for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
3734 if(board[0][i] == WhiteRook) j = i;
3735 initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
3736 for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
3737 if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
3738 initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
3739 for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
3740 if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
3741 initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
3743 if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
3744 for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
3745 if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
3746 for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
3747 if(board[BOARD_HEIGHT-1][k] == bKing)
3748 initialRights[5] = boards[moveNum][CASTLING][5] = k;
3749 if(gameInfo.variant == VariantTwoKings) {
3750 // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
3751 if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
3752 if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
3755 r = boards[moveNum][CASTLING][0] = initialRights[0];
3756 if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
3757 r = boards[moveNum][CASTLING][1] = initialRights[1];
3758 if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
3759 r = boards[moveNum][CASTLING][3] = initialRights[3];
3760 if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
3761 r = boards[moveNum][CASTLING][4] = initialRights[4];
3762 if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
3763 /* wildcastle kludge: always assume King has rights */
3764 r = boards[moveNum][CASTLING][2] = initialRights[2];
3765 r = boards[moveNum][CASTLING][5] = initialRights[5];
3767 /* [HGM] e.p. rights. Assume that ICS sends file number here? */
3768 boards[moveNum][EP_STATUS] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
3771 if (ics_getting_history == H_GOT_REQ_HEADER ||
3772 ics_getting_history == H_GOT_UNREQ_HEADER) {
3773 /* This was an initial position from a move list, not
3774 the current position */
3778 /* Update currentMove and known move number limits */
3779 newMove = newGame || moveNum > forwardMostMove;
3782 forwardMostMove = backwardMostMove = currentMove = moveNum;
3783 if (gameMode == IcsExamining && moveNum == 0) {
3784 /* Workaround for ICS limitation: we are not told the wild
3785 type when starting to examine a game. But if we ask for
3786 the move list, the move list header will tell us */
3787 ics_getting_history = H_REQUESTED;
3788 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3791 } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
3792 || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
3794 /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
3795 /* [HGM] applied this also to an engine that is silently watching */
3796 if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
3797 (gameMode == IcsObserving || gameMode == IcsExamining) &&
3798 gameInfo.variant == currentlyInitializedVariant) {
3799 takeback = forwardMostMove - moveNum;
3800 for (i = 0; i < takeback; i++) {
3801 if (appData.debugMode) fprintf(debugFP, "take back move\n");
3802 SendToProgram("undo\n", &first);
3807 forwardMostMove = moveNum;
3808 if (!pausing || currentMove > forwardMostMove)
3809 currentMove = forwardMostMove;
3811 /* New part of history that is not contiguous with old part */
3812 if (pausing && gameMode == IcsExamining) {
3813 pauseExamInvalid = TRUE;
3814 forwardMostMove = pauseExamForwardMostMove;
3817 if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
3819 if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
3820 // [HGM] when we will receive the move list we now request, it will be
3821 // fed to the engine from the first move on. So if the engine is not
3822 // in the initial position now, bring it there.
3823 InitChessProgram(&first, 0);
3826 ics_getting_history = H_REQUESTED;
3827 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3830 forwardMostMove = backwardMostMove = currentMove = moveNum;
3833 /* Update the clocks */
3834 if (strchr(elapsed_time, '.')) {
3836 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
3837 timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
3839 /* Time is in seconds */
3840 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
3841 timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
3846 if (appData.zippyPlay && newGame &&
3847 gameMode != IcsObserving && gameMode != IcsIdle &&
3848 gameMode != IcsExamining)
3849 ZippyFirstBoard(moveNum, basetime, increment);
3852 /* Put the move on the move list, first converting
3853 to canonical algebraic form. */
3855 if (appData.debugMode) {
3856 if (appData.debugMode) { int f = forwardMostMove;
3857 fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
3858 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
3859 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
3861 fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
3862 fprintf(debugFP, "moveNum = %d\n", moveNum);
3863 fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
3864 setbuf(debugFP, NULL);
3866 if (moveNum <= backwardMostMove) {
3867 /* We don't know what the board looked like before
3869 strcpy(parseList[moveNum - 1], move_str);
3870 strcat(parseList[moveNum - 1], " ");
3871 strcat(parseList[moveNum - 1], elapsed_time);
3872 moveList[moveNum - 1][0] = NULLCHAR;
3873 } else if (strcmp(move_str, "none") == 0) {
3874 // [HGM] long SAN: swapped order; test for 'none' before parsing move
3875 /* Again, we don't know what the board looked like;
3876 this is really the start of the game. */
3877 parseList[moveNum - 1][0] = NULLCHAR;
3878 moveList[moveNum - 1][0] = NULLCHAR;
3879 backwardMostMove = moveNum;
3880 startedFromSetupPosition = TRUE;
3881 fromX = fromY = toX = toY = -1;
3883 // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
3884 // So we parse the long-algebraic move string in stead of the SAN move
3885 int valid; char buf[MSG_SIZ], *prom;
3887 // str looks something like "Q/a1-a2"; kill the slash
3889 sprintf(buf, "%c%s", str[0], str+2);
3890 else strcpy(buf, str); // might be castling
3891 if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
3892 strcat(buf, prom); // long move lacks promo specification!
3893 if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
3894 if(appData.debugMode)
3895 fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
3896 strcpy(move_str, buf);
3898 valid = ParseOneMove(move_str, moveNum - 1, &moveType,
3899 &fromX, &fromY, &toX, &toY, &promoChar)
3900 || ParseOneMove(buf, moveNum - 1, &moveType,
3901 &fromX, &fromY, &toX, &toY, &promoChar);
3902 // end of long SAN patch
3904 (void) CoordsToAlgebraic(boards[moveNum - 1],
3905 PosFlags(moveNum - 1),
3906 fromY, fromX, toY, toX, promoChar,
3907 parseList[moveNum-1]);
3908 switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
3914 if(gameInfo.variant != VariantShogi)
3915 strcat(parseList[moveNum - 1], "+");
3918 case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
3919 strcat(parseList[moveNum - 1], "#");
3922 strcat(parseList[moveNum - 1], " ");
3923 strcat(parseList[moveNum - 1], elapsed_time);
3924 /* currentMoveString is set as a side-effect of ParseOneMove */
3925 strcpy(moveList[moveNum - 1], currentMoveString);
3926 strcat(moveList[moveNum - 1], "\n");
3928 /* Move from ICS was illegal!? Punt. */
3929 if (appData.debugMode) {
3930 fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
3931 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
3933 strcpy(parseList[moveNum - 1], move_str);
3934 strcat(parseList[moveNum - 1], " ");
3935 strcat(parseList[moveNum - 1], elapsed_time);
3936 moveList[moveNum - 1][0] = NULLCHAR;
3937 fromX = fromY = toX = toY = -1;
3940 if (appData.debugMode) {
3941 fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
3942 setbuf(debugFP, NULL);
3946 /* Send move to chess program (BEFORE animating it). */
3947 if (appData.zippyPlay && !newGame && newMove &&
3948 (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
3950 if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
3951 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
3952 if (moveList[moveNum - 1][0] == NULLCHAR) {
3953 sprintf(str, _("Couldn't parse move \"%s\" from ICS"),
3955 DisplayError(str, 0);
3957 if (first.sendTime) {
3958 SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
3960 bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
3961 if (firstMove && !bookHit) {
3963 if (first.useColors) {
3964 SendToProgram(gameMode == IcsPlayingWhite ?
3966 "black\ngo\n", &first);
3968 SendToProgram("go\n", &first);
3970 first.maybeThinking = TRUE;
3973 } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
3974 if (moveList[moveNum - 1][0] == NULLCHAR) {
3975 sprintf(str, _("Couldn't parse move \"%s\" from ICS"), move_str);
3976 DisplayError(str, 0);
3978 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
3979 SendMoveToProgram(moveNum - 1, &first);
3986 if (moveNum > 0 && !gotPremove && !appData.noGUI) {
3987 /* If move comes from a remote source, animate it. If it
3988 isn't remote, it will have already been animated. */
3989 if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
3990 AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
3992 if (!pausing && appData.highlightLastMove) {
3993 SetHighlights(fromX, fromY, toX, toY);
3997 /* Start the clocks */
3998 whiteFlag = blackFlag = FALSE;
3999 appData.clockMode = !(basetime == 0 && increment == 0);
4001 ics_clock_paused = TRUE;
4003 } else if (ticking == 1) {
4004 ics_clock_paused = FALSE;
4006 if (gameMode == IcsIdle ||
4007 relation == RELATION_OBSERVING_STATIC ||
4008 relation == RELATION_EXAMINING ||
4010 DisplayBothClocks();
4014 /* Display opponents and material strengths */
4015 if (gameInfo.variant != VariantBughouse &&
4016 gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4017 if (tinyLayout || smallLayout) {
4018 if(gameInfo.variant == VariantNormal)
4019 sprintf(str, "%s(%d) %s(%d) {%d %d}",
4020 gameInfo.white, white_stren, gameInfo.black, black_stren,
4021 basetime, increment);
4023 sprintf(str, "%s(%d) %s(%d) {%d %d w%d}",
4024 gameInfo.white, white_stren, gameInfo.black, black_stren,
4025 basetime, increment, (int) gameInfo.variant);
4027 if(gameInfo.variant == VariantNormal)
4028 sprintf(str, "%s (%d) vs. %s (%d) {%d %d}",
4029 gameInfo.white, white_stren, gameInfo.black, black_stren,
4030 basetime, increment);
4032 sprintf(str, "%s (%d) vs. %s (%d) {%d %d %s}",
4033 gameInfo.white, white_stren, gameInfo.black, black_stren,
4034 basetime, increment, VariantName(gameInfo.variant));
4037 if (appData.debugMode) {
4038 fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
4043 /* Display the board */
4044 if (!pausing && !appData.noGUI) {
4046 if (appData.premove)
4048 ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
4049 ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
4050 ClearPremoveHighlights();
4052 DrawPosition(FALSE, boards[currentMove]);
4053 DisplayMove(moveNum - 1);
4054 if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4055 !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4056 (gameMode == IcsPlayingBlack) && (WhiteOnMove(moveNum)) ) ) {
4057 if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4061 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4063 if(bookHit) { // [HGM] book: simulate book reply
4064 static char bookMove[MSG_SIZ]; // a bit generous?
4066 programStats.nodes = programStats.depth = programStats.time =
4067 programStats.score = programStats.got_only_move = 0;
4068 sprintf(programStats.movelist, "%s (xbook)", bookHit);
4070 strcpy(bookMove, "move ");
4071 strcat(bookMove, bookHit);
4072 HandleMachineMove(bookMove, &first);
4081 if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4082 ics_getting_history = H_REQUESTED;
4083 sprintf(buf, "%smoves %d\n", ics_prefix, ics_gamenum);
4089 AnalysisPeriodicEvent(force)
4092 if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4093 && !force) || !appData.periodicUpdates)
4096 /* Send . command to Crafty to collect stats */
4097 SendToProgram(".\n", &first);
4099 /* Don't send another until we get a response (this makes
4100 us stop sending to old Crafty's which don't understand
4101 the "." command (sending illegal cmds resets node count & time,
4102 which looks bad)) */
4103 programStats.ok_to_send = 0;
4106 void ics_update_width(new_width)
4109 ics_printf("set width %d\n", new_width);
4113 SendMoveToProgram(moveNum, cps)
4115 ChessProgramState *cps;
4119 if (cps->useUsermove) {
4120 SendToProgram("usermove ", cps);
4124 if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4125 int len = space - parseList[moveNum];
4126 memcpy(buf, parseList[moveNum], len);
4128 buf[len] = NULLCHAR;
4130 sprintf(buf, "%s\n", parseList[moveNum]);
4132 SendToProgram(buf, cps);
4134 if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4135 AlphaRank(moveList[moveNum], 4);
4136 SendToProgram(moveList[moveNum], cps);
4137 AlphaRank(moveList[moveNum], 4); // and back
4139 /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
4140 * the engine. It would be nice to have a better way to identify castle
4142 if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
4143 && cps->useOOCastle) {
4144 int fromX = moveList[moveNum][0] - AAA;
4145 int fromY = moveList[moveNum][1] - ONE;
4146 int toX = moveList[moveNum][2] - AAA;
4147 int toY = moveList[moveNum][3] - ONE;
4148 if((boards[moveNum][fromY][fromX] == WhiteKing
4149 && boards[moveNum][toY][toX] == WhiteRook)
4150 || (boards[moveNum][fromY][fromX] == BlackKing
4151 && boards[moveNum][toY][toX] == BlackRook)) {
4152 if(toX > fromX) SendToProgram("O-O\n", cps);
4153 else SendToProgram("O-O-O\n", cps);
4155 else SendToProgram(moveList[moveNum], cps);
4157 else SendToProgram(moveList[moveNum], cps);
4158 /* End of additions by Tord */
4161 /* [HGM] setting up the opening has brought engine in force mode! */
4162 /* Send 'go' if we are in a mode where machine should play. */
4163 if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4164 (gameMode == TwoMachinesPlay ||
4166 gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite ||
4168 gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4169 SendToProgram("go\n", cps);
4170 if (appData.debugMode) {
4171 fprintf(debugFP, "(extra)\n");
4174 setboardSpoiledMachineBlack = 0;
4178 SendMoveToICS(moveType, fromX, fromY, toX, toY)
4180 int fromX, fromY, toX, toY;
4182 char user_move[MSG_SIZ];
4186 sprintf(user_move, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4187 (int)moveType, fromX, fromY, toX, toY);
4188 DisplayError(user_move + strlen("say "), 0);
4190 case WhiteKingSideCastle:
4191 case BlackKingSideCastle:
4192 case WhiteQueenSideCastleWild:
4193 case BlackQueenSideCastleWild:
4195 case WhiteHSideCastleFR:
4196 case BlackHSideCastleFR:
4198 sprintf(user_move, "o-o\n");
4200 case WhiteQueenSideCastle:
4201 case BlackQueenSideCastle:
4202 case WhiteKingSideCastleWild:
4203 case BlackKingSideCastleWild:
4205 case WhiteASideCastleFR:
4206 case BlackASideCastleFR:
4208 sprintf(user_move, "o-o-o\n");
4210 case WhitePromotionQueen:
4211 case BlackPromotionQueen:
4212 case WhitePromotionRook:
4213 case BlackPromotionRook:
4214 case WhitePromotionBishop:
4215 case BlackPromotionBishop:
4216 case WhitePromotionKnight:
4217 case BlackPromotionKnight:
4218 case WhitePromotionKing:
4219 case BlackPromotionKing:
4220 case WhitePromotionChancellor:
4221 case BlackPromotionChancellor:
4222 case WhitePromotionArchbishop:
4223 case BlackPromotionArchbishop:
4224 if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
4225 sprintf(user_move, "%c%c%c%c=%c\n",
4226 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4227 PieceToChar(WhiteFerz));
4228 else if(gameInfo.variant == VariantGreat)
4229 sprintf(user_move, "%c%c%c%c=%c\n",
4230 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4231 PieceToChar(WhiteMan));
4233 sprintf(user_move, "%c%c%c%c=%c\n",
4234 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4235 PieceToChar(PromoPiece(moveType)));
4239 sprintf(user_move, "%c@%c%c\n",
4240 ToUpper(PieceToChar((ChessSquare) fromX)),
4241 AAA + toX, ONE + toY);
4244 case WhiteCapturesEnPassant:
4245 case BlackCapturesEnPassant:
4246 case IllegalMove: /* could be a variant we don't quite understand */
4247 sprintf(user_move, "%c%c%c%c\n",
4248 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4251 SendToICS(user_move);
4252 if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
4253 ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
4257 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
4262 if (rf == DROP_RANK) {
4263 sprintf(move, "%c@%c%c\n",
4264 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
4266 if (promoChar == 'x' || promoChar == NULLCHAR) {
4267 sprintf(move, "%c%c%c%c\n",
4268 AAA + ff, ONE + rf, AAA + ft, ONE + rt);
4270 sprintf(move, "%c%c%c%c%c\n",
4271 AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
4277 ProcessICSInitScript(f)
4282 while (fgets(buf, MSG_SIZ, f)) {
4283 SendToICSDelayed(buf,(long)appData.msLoginDelay);
4290 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
4292 AlphaRank(char *move, int n)
4294 // char *p = move, c; int x, y;
4296 if (appData.debugMode) {
4297 fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
4301 move[2]>='0' && move[2]<='9' &&
4302 move[3]>='a' && move[3]<='x' ) {
4304 move[2] = BOARD_RGHT -1 - (move[2]-'1') + AAA;
4305 move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4307 if(move[0]>='0' && move[0]<='9' &&
4308 move[1]>='a' && move[1]<='x' &&
4309 move[2]>='0' && move[2]<='9' &&
4310 move[3]>='a' && move[3]<='x' ) {
4311 /* input move, Shogi -> normal */
4312 move[0] = BOARD_RGHT -1 - (move[0]-'1') + AAA;
4313 move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
4314 move[2] = BOARD_RGHT -1 - (move[2]-'1') + AAA;
4315 move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4318 move[3]>='0' && move[3]<='9' &&
4319 move[2]>='a' && move[2]<='x' ) {
4321 move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4322 move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4325 move[0]>='a' && move[0]<='x' &&
4326 move[3]>='0' && move[3]<='9' &&
4327 move[2]>='a' && move[2]<='x' ) {
4328 /* output move, normal -> Shogi */
4329 move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
4330 move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
4331 move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4332 move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4333 if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
4335 if (appData.debugMode) {
4336 fprintf(debugFP, " out = '%s'\n", move);
4340 /* Parser for moves from gnuchess, ICS, or user typein box */
4342 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
4345 ChessMove *moveType;
4346 int *fromX, *fromY, *toX, *toY;
4349 if (appData.debugMode) {
4350 fprintf(debugFP, "move to parse: %s\n", move);
4352 *moveType = yylexstr(moveNum, move);
4354 switch (*moveType) {
4355 case WhitePromotionChancellor:
4356 case BlackPromotionChancellor:
4357 case WhitePromotionArchbishop:
4358 case BlackPromotionArchbishop:
4359 case WhitePromotionQueen:
4360 case BlackPromotionQueen:
4361 case WhitePromotionRook:
4362 case BlackPromotionRook:
4363 case WhitePromotionBishop:
4364 case BlackPromotionBishop:
4365 case WhitePromotionKnight:
4366 case BlackPromotionKnight:
4367 case WhitePromotionKing:
4368 case BlackPromotionKing:
4370 case WhiteCapturesEnPassant:
4371 case BlackCapturesEnPassant:
4372 case WhiteKingSideCastle:
4373 case WhiteQueenSideCastle:
4374 case BlackKingSideCastle:
4375 case BlackQueenSideCastle:
4376 case WhiteKingSideCastleWild:
4377 case WhiteQueenSideCastleWild:
4378 case BlackKingSideCastleWild:
4379 case BlackQueenSideCastleWild:
4380 /* Code added by Tord: */
4381 case WhiteHSideCastleFR:
4382 case WhiteASideCastleFR:
4383 case BlackHSideCastleFR:
4384 case BlackASideCastleFR:
4385 /* End of code added by Tord */
4386 case IllegalMove: /* bug or odd chess variant */
4387 *fromX = currentMoveString[0] - AAA;
4388 *fromY = currentMoveString[1] - ONE;
4389 *toX = currentMoveString[2] - AAA;
4390 *toY = currentMoveString[3] - ONE;
4391 *promoChar = currentMoveString[4];
4392 if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
4393 *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
4394 if (appData.debugMode) {
4395 fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
4397 *fromX = *fromY = *toX = *toY = 0;
4400 if (appData.testLegality) {
4401 return (*moveType != IllegalMove);
4403 return !(*fromX == *toX && *fromY == *toY) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
4404 WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
4409 *fromX = *moveType == WhiteDrop ?
4410 (int) CharToPiece(ToUpper(currentMoveString[0])) :
4411 (int) CharToPiece(ToLower(currentMoveString[0]));
4413 *toX = currentMoveString[2] - AAA;
4414 *toY = currentMoveString[3] - ONE;
4415 *promoChar = NULLCHAR;
4419 case ImpossibleMove:
4420 case (ChessMove) 0: /* end of file */
4429 if (appData.debugMode) {
4430 fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
4433 *fromX = *fromY = *toX = *toY = 0;
4434 *promoChar = NULLCHAR;
4442 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
4443 int fromX, fromY, toX, toY; char promoChar;
4448 endPV = forwardMostMove;
4450 while(*pv == ' ') pv++;
4451 if(*pv == '(') pv++; // first (ponder) move can be in parentheses
4452 valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
4453 if(appData.debugMode){
4454 fprintf(debugFP,"parsePV: %d %c%c%c%c '%s'\n", valid, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, pv);
4456 if(!valid && nr == 0 &&
4457 ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
4458 nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
4460 while(*pv && *pv++ != ' '); // skip what we parsed; assume space separators
4461 if(moveType == Comment) { valid++; continue; } // allow comments in PV
4463 if(endPV+1 > framePtr) break; // no space, truncate
4466 CopyBoard(boards[endPV], boards[endPV-1]);
4467 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
4468 moveList[endPV-1][0] = fromX + AAA;
4469 moveList[endPV-1][1] = fromY + ONE;
4470 moveList[endPV-1][2] = toX + AAA;
4471 moveList[endPV-1][3] = toY + ONE;
4472 parseList[endPV-1][0] = NULLCHAR;
4474 currentMove = endPV;
4475 if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
4476 SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
4477 moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
4478 DrawPosition(TRUE, boards[currentMove]);
4481 static int lastX, lastY;
4484 LoadMultiPV(int x, int y, char *buf, int index, int *start, int *end)
4488 if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
4489 lastX = x; lastY = y;
4490 while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
4492 while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
4494 while(buf[index] && buf[index] != '\n') index++;
4496 ParsePV(buf+startPV);
4497 *start = startPV; *end = index-1;
4502 LoadPV(int x, int y)
4503 { // called on right mouse click to load PV
4504 int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
4505 lastX = x; lastY = y;
4506 ParsePV(lastPV[which]); // load the PV of the thinking engine in the boards array.
4513 if(endPV < 0) return;
4515 currentMove = forwardMostMove;
4516 ClearPremoveHighlights();
4517 DrawPosition(TRUE, boards[currentMove]);
4521 MovePV(int x, int y, int h)
4522 { // step through PV based on mouse coordinates (called on mouse move)
4523 int margin = h>>3, step = 0;
4525 if(endPV < 0) return;
4526 // we must somehow check if right button is still down (might be released off board!)
4527 if(y < margin && (abs(x - lastX) > 6 || abs(y - lastY) > 6)) step = 1; else
4528 if(y > h - margin && (abs(x - lastX) > 6 || abs(y - lastY) > 6)) step = -1; else
4529 if( y > lastY + 6 ) step = -1; else if(y < lastY - 6) step = 1;
4531 lastX = x; lastY = y;
4532 if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
4533 currentMove += step;
4534 if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
4535 SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
4536 moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
4537 DrawPosition(FALSE, boards[currentMove]);
4541 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
4542 // All positions will have equal probability, but the current method will not provide a unique
4543 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
4549 int piecesLeft[(int)BlackPawn];
4550 int seed, nrOfShuffles;
4552 void GetPositionNumber()
4553 { // sets global variable seed
4556 seed = appData.defaultFrcPosition;
4557 if(seed < 0) { // randomize based on time for negative FRC position numbers
4558 for(i=0; i<50; i++) seed += random();
4559 seed = random() ^ random() >> 8 ^ random() << 8;
4560 if(seed<0) seed = -seed;
4564 int put(Board board, int pieceType, int rank, int n, int shade)
4565 // put the piece on the (n-1)-th empty squares of the given shade
4569 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
4570 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
4571 board[rank][i] = (ChessSquare) pieceType;
4572 squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
4574 piecesLeft[pieceType]--;
4582 void AddOnePiece(Board board, int pieceType, int rank, int shade)
4583 // calculate where the next piece goes, (any empty square), and put it there
4587 i = seed % squaresLeft[shade];
4588 nrOfShuffles *= squaresLeft[shade];
4589 seed /= squaresLeft[shade];
4590 put(board, pieceType, rank, i, shade);
4593 void AddTwoPieces(Board board, int pieceType, int rank)
4594 // calculate where the next 2 identical pieces go, (any empty square), and put it there
4596 int i, n=squaresLeft[ANY], j=n-1, k;
4598 k = n*(n-1)/2; // nr of possibilities, not counting permutations
4599 i = seed % k; // pick one
4602 while(i >= j) i -= j--;
4603 j = n - 1 - j; i += j;
4604 put(board, pieceType, rank, j, ANY);
4605 put(board, pieceType, rank, i, ANY);
4608 void SetUpShuffle(Board board, int number)
4612 GetPositionNumber(); nrOfShuffles = 1;
4614 squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
4615 squaresLeft[ANY] = BOARD_RGHT - BOARD_LEFT;
4616 squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
4618 for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
4620 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
4621 p = (int) board[0][i];
4622 if(p < (int) BlackPawn) piecesLeft[p] ++;
4623 board[0][i] = EmptySquare;
4626 if(PosFlags(0) & F_ALL_CASTLE_OK) {
4627 // shuffles restricted to allow normal castling put KRR first
4628 if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
4629 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
4630 else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
4631 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
4632 if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
4633 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
4634 if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
4635 put(board, WhiteRook, 0, 0, ANY);
4636 // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
4639 if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
4640 // only for even boards make effort to put pairs of colorbound pieces on opposite colors
4641 for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
4642 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
4643 while(piecesLeft[p] >= 2) {
4644 AddOnePiece(board, p, 0, LITE);
4645 AddOnePiece(board, p, 0, DARK);
4647 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
4650 for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
4651 // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
4652 // but we leave King and Rooks for last, to possibly obey FRC restriction
4653 if(p == (int)WhiteRook) continue;
4654 while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
4655 if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY); // add the odd piece
4658 // now everything is placed, except perhaps King (Unicorn) and Rooks
4660 if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
4661 // Last King gets castling rights
4662 while(piecesLeft[(int)WhiteUnicorn]) {
4663 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
4664 initialRights[2] = initialRights[5] = board[CASTLING][2] = board[CASTLING][5] = i;
4667 while(piecesLeft[(int)WhiteKing]) {
4668 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
4669 initialRights[2] = initialRights[5] = board[CASTLING][2] = board[CASTLING][5] = i;
4674 while(piecesLeft[(int)WhiteKing]) AddOnePiece(board, WhiteKing, 0, ANY);
4675 while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
4678 // Only Rooks can be left; simply place them all
4679 while(piecesLeft[(int)WhiteRook]) {
4680 i = put(board, WhiteRook, 0, 0, ANY);
4681 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
4684 initialRights[1] = initialRights[4] = board[CASTLING][1] = board[CASTLING][4] = i;
4686 initialRights[0] = initialRights[3] = board[CASTLING][0] = board[CASTLING][3] = i;
4689 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
4690 board[BOARD_HEIGHT-1][i] = (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
4693 if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
4696 int SetCharTable( char *table, const char * map )
4697 /* [HGM] moved here from winboard.c because of its general usefulness */
4698 /* Basically a safe strcpy that uses the last character as King */
4700 int result = FALSE; int NrPieces;
4702 if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
4703 && NrPieces >= 12 && !(NrPieces&1)) {
4704 int i; /* [HGM] Accept even length from 12 to 34 */
4706 for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
4707 for( i=0; i<NrPieces/2-1; i++ ) {
4709 table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
4711 table[(int) WhiteKing] = map[NrPieces/2-1];
4712 table[(int) BlackKing] = map[NrPieces-1];
4720 void Prelude(Board board)
4721 { // [HGM] superchess: random selection of exo-pieces
4722 int i, j, k; ChessSquare p;
4723 static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
4725 GetPositionNumber(); // use FRC position number
4727 if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
4728 SetCharTable(pieceToChar, appData.pieceToCharTable);
4729 for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
4730 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
4733 j = seed%4; seed /= 4;
4734 p = board[0][BOARD_LEFT+j]; board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
4735 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
4736 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
4737 j = seed%3 + (seed%3 >= j); seed /= 3;
4738 p = board[0][BOARD_LEFT+j]; board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
4739 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
4740 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
4741 j = seed%3; seed /= 3;
4742 p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
4743 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
4744 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
4745 j = seed%2 + (seed%2 >= j); seed /= 2;
4746 p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
4747 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
4748 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
4749 j = seed%4; seed /= 4; put(board, exoPieces[3], 0, j, ANY);
4750 j = seed%3; seed /= 3; put(board, exoPieces[2], 0, j, ANY);
4751 j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
4752 put(board, exoPieces[0], 0, 0, ANY);
4753 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
4757 InitPosition(redraw)
4760 ChessSquare (* pieces)[BOARD_FILES];
4761 int i, j, pawnRow, overrule,
4762 oldx = gameInfo.boardWidth,
4763 oldy = gameInfo.boardHeight,
4764 oldh = gameInfo.holdingsWidth,
4765 oldv = gameInfo.variant;
4767 if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
4769 /* [AS] Initialize pv info list [HGM] and game status */
4771 for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
4772 pvInfoList[i].depth = 0;
4773 boards[i][EP_STATUS] = EP_NONE;
4774 for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
4777 initialRulePlies = 0; /* 50-move counter start */
4779 castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
4780 castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
4784 /* [HGM] logic here is completely changed. In stead of full positions */
4785 /* the initialized data only consist of the two backranks. The switch */
4786 /* selects which one we will use, which is than copied to the Board */
4787 /* initialPosition, which for the rest is initialized by Pawns and */
4788 /* empty squares. This initial position is then copied to boards[0], */
4789 /* possibly after shuffling, so that it remains available. */
4791 gameInfo.holdingsWidth = 0; /* default board sizes */
4792 gameInfo.boardWidth = 8;
4793 gameInfo.boardHeight = 8;
4794 gameInfo.holdingsSize = 0;
4795 nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
4796 for(i=0; i<BOARD_FILES-2; i++)
4797 initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
4798 initialPosition[EP_STATUS] = EP_NONE;
4799 SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
4801 switch (gameInfo.variant) {
4802 case VariantFischeRandom:
4803 shuffleOpenings = TRUE;
4807 case VariantShatranj:
4808 pieces = ShatranjArray;
4809 nrCastlingRights = 0;
4810 SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
4813 pieces = makrukArray;
4814 nrCastlingRights = 0;
4815 startedFromSetupPosition = TRUE;
4816 SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
4818 case VariantTwoKings:
4819 pieces = twoKingsArray;
4821 case VariantCapaRandom:
4822 shuffleOpenings = TRUE;
4823 case VariantCapablanca:
4824 pieces = CapablancaArray;
4825 gameInfo.boardWidth = 10;
4826 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
4829 pieces = GothicArray;
4830 gameInfo.boardWidth = 10;
4831 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
4834 pieces = JanusArray;
4835 gameInfo.boardWidth = 10;
4836 SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
4837 nrCastlingRights = 6;
4838 initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
4839 initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
4840 initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
4841 initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
4842 initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
4843 initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
4846 pieces = FalconArray;
4847 gameInfo.boardWidth = 10;
4848 SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
4850 case VariantXiangqi:
4851 pieces = XiangqiArray;
4852 gameInfo.boardWidth = 9;
4853 gameInfo.boardHeight = 10;
4854 nrCastlingRights = 0;
4855 SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
4858 pieces = ShogiArray;
4859 gameInfo.boardWidth = 9;
4860 gameInfo.boardHeight = 9;
4861 gameInfo.holdingsSize = 7;
4862 nrCastlingRights = 0;
4863 SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
4865 case VariantCourier:
4866 pieces = CourierArray;
4867 gameInfo.boardWidth = 12;
4868 nrCastlingRights = 0;
4869 SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
4871 case VariantKnightmate:
4872 pieces = KnightmateArray;
4873 SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
4876 pieces = fairyArray;
4877 SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
4880 pieces = GreatArray;
4881 gameInfo.boardWidth = 10;
4882 SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
4883 gameInfo.holdingsSize = 8;
4887 SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
4888 gameInfo.holdingsSize = 8;
4889 startedFromSetupPosition = TRUE;
4891 case VariantCrazyhouse:
4892 case VariantBughouse:
4894 SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
4895 gameInfo.holdingsSize = 5;
4897 case VariantWildCastle:
4899 /* !!?shuffle with kings guaranteed to be on d or e file */
4900 shuffleOpenings = 1;
4902 case VariantNoCastle:
4904 nrCastlingRights = 0;
4905 /* !!?unconstrained back-rank shuffle */
4906 shuffleOpenings = 1;
4911 if(appData.NrFiles >= 0) {
4912 if(gameInfo.boardWidth != appData.NrFiles) overrule++;
4913 gameInfo.boardWidth = appData.NrFiles;
4915 if(appData.NrRanks >= 0) {
4916 gameInfo.boardHeight = appData.NrRanks;
4918 if(appData.holdingsSize >= 0) {
4919 i = appData.holdingsSize;
4920 if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
4921 gameInfo.holdingsSize = i;
4923 if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
4924 if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
4925 DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
4927 pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
4928 if(pawnRow < 1) pawnRow = 1;
4929 if(gameInfo.variant == VariantMakruk) pawnRow = 2;
4931 /* User pieceToChar list overrules defaults */
4932 if(appData.pieceToCharTable != NULL)
4933 SetCharTable(pieceToChar, appData.pieceToCharTable);
4935 for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
4937 if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
4938 s = (ChessSquare) 0; /* account holding counts in guard band */
4939 for( i=0; i<BOARD_HEIGHT; i++ )
4940 initialPosition[i][j] = s;
4942 if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
4943 initialPosition[0][j] = pieces[0][j-gameInfo.holdingsWidth];
4944 initialPosition[pawnRow][j] = WhitePawn;
4945 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = BlackPawn;
4946 if(gameInfo.variant == VariantXiangqi) {
4948 initialPosition[pawnRow][j] =
4949 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
4950 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
4951 initialPosition[2][j] = WhiteCannon;
4952 initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
4956 initialPosition[BOARD_HEIGHT-1][j] = pieces[1][j-gameInfo.holdingsWidth];
4958 if( (gameInfo.variant == VariantShogi) && !overrule ) {
4961 initialPosition[1][j] = WhiteBishop;
4962 initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
4964 initialPosition[1][j] = WhiteRook;
4965 initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
4968 if( nrCastlingRights == -1) {
4969 /* [HGM] Build normal castling rights (must be done after board sizing!) */
4970 /* This sets default castling rights from none to normal corners */
4971 /* Variants with other castling rights must set them themselves above */
4972 nrCastlingRights = 6;
4974 initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
4975 initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
4976 initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
4977 initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
4978 initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
4979 initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
4982 if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
4983 if(gameInfo.variant == VariantGreat) { // promotion commoners
4984 initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
4985 initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
4986 initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
4987 initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
4989 if (appData.debugMode) {
4990 fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
4992 if(shuffleOpenings) {
4993 SetUpShuffle(initialPosition, appData.defaultFrcPosition);
4994 startedFromSetupPosition = TRUE;
4996 if(startedFromPositionFile) {
4997 /* [HGM] loadPos: use PositionFile for every new game */
4998 CopyBoard(initialPosition, filePosition);
4999 for(i=0; i<nrCastlingRights; i++)
5000 initialRights[i] = filePosition[CASTLING][i];
5001 startedFromSetupPosition = TRUE;
5004 CopyBoard(boards[0], initialPosition);
5006 if(oldx != gameInfo.boardWidth ||
5007 oldy != gameInfo.boardHeight ||
5008 oldh != gameInfo.holdingsWidth
5010 || oldv == VariantGothic || // For licensing popups
5011 gameInfo.variant == VariantGothic
5014 || oldv == VariantFalcon ||
5015 gameInfo.variant == VariantFalcon
5018 InitDrawingSizes(-2 ,0);
5021 DrawPosition(TRUE, boards[currentMove]);
5025 SendBoard(cps, moveNum)
5026 ChessProgramState *cps;
5029 char message[MSG_SIZ];
5031 if (cps->useSetboard) {
5032 char* fen = PositionToFEN(moveNum, cps->fenOverride);
5033 sprintf(message, "setboard %s\n", fen);
5034 SendToProgram(message, cps);
5040 /* Kludge to set black to move, avoiding the troublesome and now
5041 * deprecated "black" command.
5043 if (!WhiteOnMove(moveNum)) SendToProgram("a2a3\n", cps);
5045 SendToProgram("edit\n", cps);
5046 SendToProgram("#\n", cps);
5047 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
5048 bp = &boards[moveNum][i][BOARD_LEFT];
5049 for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
5050 if ((int) *bp < (int) BlackPawn) {
5051 sprintf(message, "%c%c%c\n", PieceToChar(*bp),
5053 if(message[0] == '+' || message[0] == '~') {
5054 sprintf(message, "%c%c%c+\n",
5055 PieceToChar((ChessSquare)(DEMOTED *bp)),
5058 if(cps->alphaRank) { /* [HGM] shogi: translate coords */
5059 message[1] = BOARD_RGHT - 1 - j + '1';
5060 message[2] = BOARD_HEIGHT - 1 - i + 'a';
5062 SendToProgram(message, cps);
5067 SendToProgram("c\n", cps);
5068 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
5069 bp = &boards[moveNum][i][BOARD_LEFT];
5070 for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
5071 if (((int) *bp != (int) EmptySquare)
5072 && ((int) *bp >= (int) BlackPawn)) {
5073 sprintf(message, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
5075 if(message[0] == '+' || message[0] == '~') {
5076 sprintf(message, "%c%c%c+\n",
5077 PieceToChar((ChessSquare)(DEMOTED *bp)),
5080 if(cps->alphaRank) { /* [HGM] shogi: translate coords */
5081 message[1] = BOARD_RGHT - 1 - j + '1';
5082 message[2] = BOARD_HEIGHT - 1 - i + 'a';
5084 SendToProgram(message, cps);
5089 SendToProgram(".\n", cps);
5091 setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
5095 HasPromotionChoice(int fromX, int fromY, int toX, int toY, char *promoChoice)
5097 /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
5098 /* [HGM] add Shogi promotions */
5099 int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
5104 if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
5105 if(toX < BOARD_LEFT || toX >= BOARD_RGHT) return FALSE; // move into holdings
5107 if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
5108 !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
5111 piece = boards[currentMove][fromY][fromX];
5112 if(gameInfo.variant == VariantShogi) {
5113 promotionZoneSize = 3;
5114 highestPromotingPiece = (int)WhiteFerz;
5115 } else if(gameInfo.variant == VariantMakruk) {
5116 promotionZoneSize = 3;
5119 // next weed out all moves that do not touch the promotion zone at all
5120 if((int)piece >= BlackPawn) {
5121 if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
5123 highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
5125 if( toY < BOARD_HEIGHT - promotionZoneSize &&
5126 fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
5129 if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
5131 // weed out mandatory Shogi promotions
5132 if(gameInfo.variant == VariantShogi) {
5133 if(piece >= BlackPawn) {
5134 if(toY == 0 && piece == BlackPawn ||
5135 toY == 0 && piece == BlackQueen ||
5136 toY <= 1 && piece == BlackKnight) {
5141 if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
5142 toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
5143 toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
5150 // weed out obviously illegal Pawn moves
5151 if(appData.testLegality && (piece == WhitePawn || piece == BlackPawn) ) {
5152 if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
5153 if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
5154 if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
5155 if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
5156 // note we are not allowed to test for valid (non-)capture, due to premove
5159 // we either have a choice what to promote to, or (in Shogi) whether to promote
5160 if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk) {
5161 *promoChoice = PieceToChar(BlackFerz); // no choice
5164 if(appData.alwaysPromoteToQueen) { // predetermined
5165 if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantLosers)
5166 *promoChoice = PieceToChar(BlackKing); // in Suicide Q is the last thing we want
5167 else *promoChoice = PieceToChar(BlackQueen);
5171 // suppress promotion popup on illegal moves that are not premoves
5172 premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
5173 gameMode == IcsPlayingBlack && WhiteOnMove(currentMove);
5174 if(appData.testLegality && !premove) {
5175 moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5176 fromY, fromX, toY, toX, NULLCHAR);
5177 if(moveType != WhitePromotionQueen && moveType != BlackPromotionQueen &&
5178 moveType != WhitePromotionKnight && moveType != BlackPromotionKnight)
5186 InPalace(row, column)
5188 { /* [HGM] for Xiangqi */
5189 if( (row < 3 || row > BOARD_HEIGHT-4) &&
5190 column < (BOARD_WIDTH + 4)/2 &&
5191 column > (BOARD_WIDTH - 5)/2 ) return TRUE;
5196 PieceForSquare (x, y)
5200 if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
5203 return boards[currentMove][y][x];
5207 OKToStartUserMove(x, y)
5210 ChessSquare from_piece;
5213 if (matchMode) return FALSE;
5214 if (gameMode == EditPosition) return TRUE;
5216 if (x >= 0 && y >= 0)
5217 from_piece = boards[currentMove][y][x];
5219 from_piece = EmptySquare;
5221 if (from_piece == EmptySquare) return FALSE;
5223 white_piece = (int)from_piece >= (int)WhitePawn &&
5224 (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
5227 case PlayFromGameFile:
5229 case TwoMachinesPlay:
5237 case MachinePlaysWhite:
5238 case IcsPlayingBlack:
5239 if (appData.zippyPlay) return FALSE;
5241 DisplayMoveError(_("You are playing Black"));
5246 case MachinePlaysBlack:
5247 case IcsPlayingWhite:
5248 if (appData.zippyPlay) return FALSE;
5250 DisplayMoveError(_("You are playing White"));
5256 if (!white_piece && WhiteOnMove(currentMove)) {
5257 DisplayMoveError(_("It is White's turn"));
5260 if (white_piece && !WhiteOnMove(currentMove)) {
5261 DisplayMoveError(_("It is Black's turn"));
5264 if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
5265 /* Editing correspondence game history */
5266 /* Could disallow this or prompt for confirmation */
5271 case BeginningOfGame:
5272 if (appData.icsActive) return FALSE;
5273 if (!appData.noChessProgram) {
5275 DisplayMoveError(_("You are playing White"));
5282 if (!white_piece && WhiteOnMove(currentMove)) {
5283 DisplayMoveError(_("It is White's turn"));
5286 if (white_piece && !WhiteOnMove(currentMove)) {
5287 DisplayMoveError(_("It is Black's turn"));
5296 if (currentMove != forwardMostMove && gameMode != AnalyzeMode
5297 && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
5298 && gameMode != AnalyzeFile && gameMode != Training) {
5299 DisplayMoveError(_("Displayed position is not current"));
5305 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
5306 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
5307 int lastLoadGameUseList = FALSE;
5308 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
5309 ChessMove lastLoadGameStart = (ChessMove) 0;
5312 UserMoveTest(fromX, fromY, toX, toY, promoChar, captureOwn)
5313 int fromX, fromY, toX, toY;
5318 ChessSquare pdown, pup;
5320 /* Check if the user is playing in turn. This is complicated because we
5321 let the user "pick up" a piece before it is his turn. So the piece he
5322 tried to pick up may have been captured by the time he puts it down!
5323 Therefore we use the color the user is supposed to be playing in this
5324 test, not the color of the piece that is currently on the starting
5325 square---except in EditGame mode, where the user is playing both
5326 sides; fortunately there the capture race can't happen. (It can
5327 now happen in IcsExamining mode, but that's just too bad. The user
5328 will get a somewhat confusing message in that case.)
5332 case PlayFromGameFile:
5334 case TwoMachinesPlay:
5338 /* We switched into a game mode where moves are not accepted,
5339 perhaps while the mouse button was down. */
5340 return ImpossibleMove;
5342 case MachinePlaysWhite:
5343 /* User is moving for Black */
5344 if (WhiteOnMove(currentMove)) {
5345 DisplayMoveError(_("It is White's turn"));
5346 return ImpossibleMove;
5350 case MachinePlaysBlack:
5351 /* User is moving for White */
5352 if (!WhiteOnMove(currentMove)) {
5353 DisplayMoveError(_("It is Black's turn"));
5354 return ImpossibleMove;
5360 case BeginningOfGame:
5363 if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
5364 (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
5365 /* User is moving for Black */
5366 if (WhiteOnMove(currentMove)) {
5367 DisplayMoveError(_("It is White's turn"));
5368 return ImpossibleMove;
5371 /* User is moving for White */
5372 if (!WhiteOnMove(currentMove)) {
5373 DisplayMoveError(_("It is Black's turn"));
5374 return ImpossibleMove;
5379 case IcsPlayingBlack:
5380 /* User is moving for Black */
5381 if (WhiteOnMove(currentMove)) {
5382 if (!appData.premove) {
5383 DisplayMoveError(_("It is White's turn"));
5384 } else if (toX >= 0 && toY >= 0) {
5387 premoveFromX = fromX;
5388 premoveFromY = fromY;
5389 premovePromoChar = promoChar;
5391 if (appData.debugMode)
5392 fprintf(debugFP, "Got premove: fromX %d,"
5393 "fromY %d, toX %d, toY %d\n",
5394 fromX, fromY, toX, toY);
5396 return ImpossibleMove;
5400 case IcsPlayingWhite:
5401 /* User is moving for White */
5402 if (!WhiteOnMove(currentMove)) {
5403 if (!appData.premove) {
5404 DisplayMoveError(_("It is Black's turn"));
5405 } else if (toX >= 0 && toY >= 0) {
5408 premoveFromX = fromX;
5409 premoveFromY = fromY;
5410 premovePromoChar = promoChar;
5412 if (appData.debugMode)
5413 fprintf(debugFP, "Got premove: fromX %d,"
5414 "fromY %d, toX %d, toY %d\n",
5415 fromX, fromY, toX, toY);
5417 return ImpossibleMove;
5425 /* EditPosition, empty square, or different color piece;
5426 click-click move is possible */
5427 if (toX == -2 || toY == -2) {
5428 boards[0][fromY][fromX] = EmptySquare;
5429 return AmbiguousMove;
5430 } else if (toX >= 0 && toY >= 0) {
5431 boards[0][toY][toX] = boards[0][fromY][fromX];
5432 if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
5433 if(boards[0][fromY][0] != EmptySquare) {
5434 if(boards[0][fromY][1]) boards[0][fromY][1]--;
5435 if(boards[0][fromY][1] == 0) boards[0][fromY][0] = EmptySquare;
5438 if(fromX == BOARD_RGHT+1) {
5439 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
5440 if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
5441 if(boards[0][fromY][BOARD_WIDTH-2] == 0) boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
5444 boards[0][fromY][fromX] = EmptySquare;
5445 return AmbiguousMove;
5447 return ImpossibleMove;
5450 if(toX < 0 || toY < 0) return ImpossibleMove;
5451 pdown = boards[currentMove][fromY][fromX];
5452 pup = boards[currentMove][toY][toX];
5454 /* [HGM] If move started in holdings, it means a drop */
5455 if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) {
5456 if( pup != EmptySquare ) return ImpossibleMove;
5457 if(appData.testLegality) {
5458 /* it would be more logical if LegalityTest() also figured out
5459 * which drops are legal. For now we forbid pawns on back rank.
5460 * Shogi is on its own here...
5462 if( (pdown == WhitePawn || pdown == BlackPawn) &&
5463 (toY == 0 || toY == BOARD_HEIGHT -1 ) )
5464 return(ImpossibleMove); /* no pawn drops on 1st/8th */
5466 return WhiteDrop; /* Not needed to specify white or black yet */
5469 /* [HGM] always test for legality, to get promotion info */
5470 moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5471 fromY, fromX, toY, toX, promoChar);
5472 /* [HGM] but possibly ignore an IllegalMove result */
5473 if (appData.testLegality) {
5474 if (moveType == IllegalMove || moveType == ImpossibleMove) {
5475 DisplayMoveError(_("Illegal move"));
5476 return ImpossibleMove;
5481 /* [HGM] <popupFix> in stead of calling FinishMove directly, this
5482 function is made into one that returns an OK move type if FinishMove
5483 should be called. This to give the calling driver routine the
5484 opportunity to finish the userMove input with a promotion popup,
5485 without bothering the user with this for invalid or illegal moves */
5487 /* FinishMove(moveType, fromX, fromY, toX, toY, promoChar); */
5490 /* Common tail of UserMoveEvent and DropMenuEvent */
5492 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
5494 int fromX, fromY, toX, toY;
5495 /*char*/int promoChar;
5499 if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) && promoChar != NULLCHAR) {
5500 // [HGM] superchess: suppress promotions to non-available piece
5501 int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
5502 if(WhiteOnMove(currentMove)) {
5503 if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
5505 if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
5509 /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
5510 move type in caller when we know the move is a legal promotion */
5511 if(moveType == NormalMove && promoChar)
5512 moveType = PromoCharToMoveType(WhiteOnMove(currentMove), promoChar);
5514 /* [HGM] convert drag-and-drop piece drops to standard form */
5515 if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ){
5516 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
5517 if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n",
5518 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
5519 // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
5520 if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
5521 fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
5522 while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++;
5526 /* [HGM] <popupFix> The following if has been moved here from
5527 UserMoveEvent(). Because it seemed to belong here (why not allow
5528 piece drops in training games?), and because it can only be
5529 performed after it is known to what we promote. */
5530 if (gameMode == Training) {
5531 /* compare the move played on the board to the next move in the
5532 * game. If they match, display the move and the opponent's response.
5533 * If they don't match, display an error message.
5537 CopyBoard(testBoard, boards[currentMove]);
5538 ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
5540 if (CompareBoards(testBoard, boards[currentMove+1])) {
5541 ForwardInner(currentMove+1);
5543 /* Autoplay the opponent's response.
5544 * if appData.animate was TRUE when Training mode was entered,
5545 * the response will be animated.
5547 saveAnimate = appData.animate;
5548 appData.animate = animateTraining;
5549 ForwardInner(currentMove+1);
5550 appData.animate = saveAnimate;
5552 /* check for the end of the game */
5553 if (currentMove >= forwardMostMove) {
5554 gameMode = PlayFromGameFile;
5556 SetTrainingModeOff();
5557 DisplayInformation(_("End of game"));
5560 DisplayError(_("Incorrect move"), 0);
5565 /* Ok, now we know that the move is good, so we can kill
5566 the previous line in Analysis Mode */
5567 if ((gameMode == AnalyzeMode || gameMode == EditGame)
5568 && currentMove < forwardMostMove) {
5569 PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
5572 /* If we need the chess program but it's dead, restart it */
5573 ResurrectChessProgram();
5575 /* A user move restarts a paused game*/
5579 thinkOutput[0] = NULLCHAR;
5581 MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
5583 if(Adjudicate(NULL)) return 1; // [HGM] adjudicate: take care of automtic game end
5585 if (gameMode == BeginningOfGame) {
5586 if (appData.noChessProgram) {
5587 gameMode = EditGame;
5591 gameMode = MachinePlaysBlack;
5594 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
5596 if (first.sendName) {
5597 sprintf(buf, "name %s\n", gameInfo.white);
5598 SendToProgram(buf, &first);
5605 /* Relay move to ICS or chess engine */
5606 if (appData.icsActive) {
5607 if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
5608 gameMode == IcsExamining) {
5609 if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
5610 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
5612 SendMoveToICS(moveType, fromX, fromY, toX, toY);
5614 // also send plain move, in case ICS does not understand atomic claims
5615 SendMoveToICS(moveType, fromX, fromY, toX, toY);
5619 if (first.sendTime && (gameMode == BeginningOfGame ||
5620 gameMode == MachinePlaysWhite ||
5621 gameMode == MachinePlaysBlack)) {
5622 SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
5624 if (gameMode != EditGame && gameMode != PlayFromGameFile) {
5625 // [HGM] book: if program might be playing, let it use book
5626 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
5627 first.maybeThinking = TRUE;
5628 } else SendMoveToProgram(forwardMostMove-1, &first);
5629 if (currentMove == cmailOldMove + 1) {
5630 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
5634 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5638 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
5644 if (WhiteOnMove(currentMove)) {
5645 GameEnds(BlackWins, "Black mates", GE_PLAYER);
5647 GameEnds(WhiteWins, "White mates", GE_PLAYER);
5651 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
5656 case MachinePlaysBlack:
5657 case MachinePlaysWhite:
5658 /* disable certain menu options while machine is thinking */
5659 SetMachineThinkingEnables();
5666 userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
5668 if(bookHit) { // [HGM] book: simulate book reply
5669 static char bookMove[MSG_SIZ]; // a bit generous?
5671 programStats.nodes = programStats.depth = programStats.time =
5672 programStats.score = programStats.got_only_move = 0;
5673 sprintf(programStats.movelist, "%s (xbook)", bookHit);
5675 strcpy(bookMove, "move ");
5676 strcat(bookMove, bookHit);
5677 HandleMachineMove(bookMove, &first);
5683 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
5684 int fromX, fromY, toX, toY;
5687 /* [HGM] This routine was added to allow calling of its two logical
5688 parts from other modules in the old way. Before, UserMoveEvent()
5689 automatically called FinishMove() if the move was OK, and returned
5690 otherwise. I separated the two, in order to make it possible to
5691 slip a promotion popup in between. But that it always needs two
5692 calls, to the first part, (now called UserMoveTest() ), and to
5693 FinishMove if the first part succeeded. Calls that do not need
5694 to do anything in between, can call this routine the old way.
5696 ChessMove moveType = UserMoveTest(fromX, fromY, toX, toY, promoChar, FALSE);
5697 if(appData.debugMode) fprintf(debugFP, "moveType 4 = %d, promochar = %x\n", moveType, promoChar);
5698 if(moveType == AmbiguousMove)
5699 DrawPosition(FALSE, boards[currentMove]);
5700 else if(moveType != ImpossibleMove && moveType != Comment)
5701 FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
5705 Mark(board, flags, kind, rf, ff, rt, ft, closure)
5712 typedef char Markers[BOARD_RANKS][BOARD_FILES];
5713 Markers *m = (Markers *) closure;
5714 if(rf == fromY && ff == fromX)
5715 (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
5716 || kind == WhiteCapturesEnPassant
5717 || kind == BlackCapturesEnPassant);
5718 else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
5722 MarkTargetSquares(int clear)
5725 if(!appData.markers || !appData.highlightDragging ||
5726 !appData.testLegality || gameMode == EditPosition) return;
5728 for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) marker[y][x] = 0;
5731 GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker);
5732 if(PosFlags(0) & F_MANDATORY_CAPTURE) {
5733 for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
5735 for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
5738 DrawPosition(TRUE, NULL);
5741 void LeftClick(ClickType clickType, int xPix, int yPix)
5744 Boolean saveAnimate;
5745 static int second = 0, promotionChoice = 0;
5746 char promoChoice = NULLCHAR;
5748 if (clickType == Press) ErrorPopDown();
5749 MarkTargetSquares(1);
5751 x = EventToSquare(xPix, BOARD_WIDTH);
5752 y = EventToSquare(yPix, BOARD_HEIGHT);
5753 if (!flipView && y >= 0) {
5754 y = BOARD_HEIGHT - 1 - y;
5756 if (flipView && x >= 0) {
5757 x = BOARD_WIDTH - 1 - x;
5760 if(promotionChoice) { // we are waiting for a click to indicate promotion piece
5761 if(clickType == Release) return; // ignore upclick of click-click destination
5762 promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
5763 if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
5764 if(gameInfo.holdingsWidth &&
5765 (WhiteOnMove(currentMove)
5766 ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y > 0
5767 : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT-1) ) {
5768 // click in right holdings, for determining promotion piece
5769 ChessSquare p = boards[currentMove][y][x];
5770 if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
5771 if(p != EmptySquare) {
5772 FinishMove(NormalMove, fromX, fromY, toX, toY, ToLower(PieceToChar(p)));
5777 DrawPosition(FALSE, boards[currentMove]);
5781 /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
5782 if(clickType == Press
5783 && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
5784 || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
5785 || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
5789 if (clickType == Press) {
5791 if (OKToStartUserMove(x, y)) {
5795 MarkTargetSquares(0);
5796 DragPieceBegin(xPix, yPix);
5797 if (appData.highlightDragging) {
5798 SetHighlights(x, y, -1, -1);
5806 if (clickType == Press && gameMode != EditPosition) {
5811 // ignore off-board to clicks
5812 if(y < 0 || x < 0) return;
5814 /* Check if clicking again on the same color piece */
5815 fromP = boards[currentMove][fromY][fromX];
5816 toP = boards[currentMove][y][x];
5817 frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom;
5818 if ((WhitePawn <= fromP && fromP <= WhiteKing &&
5819 WhitePawn <= toP && toP <= WhiteKing &&
5820 !(fromP == WhiteKing && toP == WhiteRook && frc) &&
5821 !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
5822 (BlackPawn <= fromP && fromP <= BlackKing &&
5823 BlackPawn <= toP && toP <= BlackKing &&
5824 !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
5825 !(fromP == BlackKing && toP == BlackRook && frc))) {
5826 /* Clicked again on same color piece -- changed his mind */
5827 second = (x == fromX && y == fromY);
5828 if (appData.highlightDragging) {
5829 SetHighlights(x, y, -1, -1);
5833 if (OKToStartUserMove(x, y)) {
5836 MarkTargetSquares(0);
5837 DragPieceBegin(xPix, yPix);
5841 // ignore clicks on holdings
5842 if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
5845 if (clickType == Release && x == fromX && y == fromY) {
5846 DragPieceEnd(xPix, yPix);
5847 if (appData.animateDragging) {
5848 /* Undo animation damage if any */
5849 DrawPosition(FALSE, NULL);
5852 /* Second up/down in same square; just abort move */
5857 ClearPremoveHighlights();
5859 /* First upclick in same square; start click-click mode */
5860 SetHighlights(x, y, -1, -1);
5865 /* we now have a different from- and (possibly off-board) to-square */
5866 /* Completed move */
5869 saveAnimate = appData.animate;
5870 if (clickType == Press) {
5871 /* Finish clickclick move */
5872 if (appData.animate || appData.highlightLastMove) {
5873 SetHighlights(fromX, fromY, toX, toY);
5878 /* Finish drag move */
5879 if (appData.highlightLastMove) {
5880 SetHighlights(fromX, fromY, toX, toY);
5884 DragPieceEnd(xPix, yPix);
5885 /* Don't animate move and drag both */
5886 appData.animate = FALSE;
5889 // moves into holding are invalid for now (except in EditPosition, adapting to-square)
5890 if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
5891 ChessSquare piece = boards[currentMove][fromY][fromX];
5892 if(gameMode == EditPosition && piece != EmptySquare &&
5893 fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
5896 if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
5897 n = PieceToNumber(piece - (int)BlackPawn);
5898 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
5899 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
5900 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
5902 if(x == BOARD_RGHT+1 && piece < BlackPawn) {
5903 n = PieceToNumber(piece);
5904 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
5905 boards[currentMove][n][BOARD_WIDTH-1] = piece;
5906 boards[currentMove][n][BOARD_WIDTH-2]++;
5908 boards[currentMove][fromY][fromX] = EmptySquare;
5912 DrawPosition(TRUE, boards[currentMove]);
5916 // off-board moves should not be highlighted
5917 if(x < 0 || x < 0) ClearHighlights();
5919 if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice)) {
5920 SetHighlights(fromX, fromY, toX, toY);
5921 if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
5922 // [HGM] super: promotion to captured piece selected from holdings
5923 ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
5924 promotionChoice = TRUE;
5925 // kludge follows to temporarily execute move on display, without promoting yet
5926 boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
5927 boards[currentMove][toY][toX] = p;
5928 DrawPosition(FALSE, boards[currentMove]);
5929 boards[currentMove][fromY][fromX] = p; // take back, but display stays
5930 boards[currentMove][toY][toX] = q;
5931 DisplayMessage("Click in holdings to choose piece", "");
5936 UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
5937 if (!appData.highlightLastMove || gotPremove) ClearHighlights();
5938 if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
5941 appData.animate = saveAnimate;
5942 if (appData.animate || appData.animateDragging) {
5943 /* Undo animation damage if needed */
5944 DrawPosition(FALSE, NULL);
5948 int RightClick(ClickType action, int x, int y, int *fromX, int *fromY)
5949 { // front-end-free part taken out of PieceMenuPopup
5950 int whichMenu; int xSqr, ySqr;
5952 xSqr = EventToSquare(x, BOARD_WIDTH);
5953 ySqr = EventToSquare(y, BOARD_HEIGHT);
5954 if (action == Release) UnLoadPV(); // [HGM] pv
5955 if (action != Press) return -2;
5958 if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;
\r
5960 if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;
\r
5961 if (xSqr < 0 || ySqr < 0) return -1;
\r
5962 whichMenu = 0; // edit-position menu
5965 if(!appData.icsEngineAnalyze) return -1;
5966 case IcsPlayingWhite:
5967 case IcsPlayingBlack:
5968 if(!appData.zippyPlay) goto noZip;
5971 case MachinePlaysWhite:
5972 case MachinePlaysBlack:
5973 case TwoMachinesPlay: // [HGM] pv: use for showing PV
5974 if (!appData.dropMenu) {
5976 return 2; // flag front-end to grab mouse events
5978 if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
5979 gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
5982 if (xSqr < 0 || ySqr < 0) return -1;
5983 if (!appData.dropMenu || appData.testLegality &&
5984 gameInfo.variant != VariantBughouse &&
5985 gameInfo.variant != VariantCrazyhouse) return -1;
5986 whichMenu = 1; // drop menu
5992 if (((*fromX = xSqr) < 0) ||
5993 ((*fromY = ySqr) < 0)) {
5994 *fromX = *fromY = -1;
5998 *fromX = BOARD_WIDTH - 1 - *fromX;
6000 *fromY = BOARD_HEIGHT - 1 - *fromY;
6005 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
6007 // char * hint = lastHint;
6008 FrontEndProgramStats stats;
6010 stats.which = cps == &first ? 0 : 1;
6011 stats.depth = cpstats->depth;
6012 stats.nodes = cpstats->nodes;
6013 stats.score = cpstats->score;
6014 stats.time = cpstats->time;
6015 stats.pv = cpstats->movelist;
6016 stats.hint = lastHint;
6017 stats.an_move_index = 0;
6018 stats.an_move_count = 0;
6020 if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
6021 stats.hint = cpstats->move_name;
6022 stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
6023 stats.an_move_count = cpstats->nr_moves;
6026 if(stats.pv && stats.pv[0]) strcpy(lastPV[stats.which], stats.pv); // [HGM] pv: remember last PV of each
6028 SetProgramStats( &stats );
6032 Adjudicate(ChessProgramState *cps)
6033 { // [HGM] some adjudications useful with buggy engines
6034 // [HGM] adjudicate: made into separate routine, which now can be called after every move
6035 // In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
6036 // Actually ending the game is now based on the additional internal condition canAdjudicate.
6037 // Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
6038 int k, count = 0; static int bare = 1;
6039 ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
6040 Boolean canAdjudicate = !appData.icsActive;
6042 // most tests only when we understand the game, i.e. legality-checking on, and (for the time being) no piece drops
6043 if(gameInfo.holdingsSize == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
6044 if( appData.testLegality )
6045 { /* [HGM] Some more adjudications for obstinate engines */
6046 int NrWN=0, NrBN=0, NrWB=0, NrBB=0, NrWR=0, NrBR=0,
6047 NrWQ=0, NrBQ=0, NrW=0, NrK=0, bishopsColor = 0,
6048 NrPieces=0, NrPawns=0, PawnAdvance=0, i, j;
6049 static int moveCount = 6;
6051 char *reason = NULL;
6053 /* Count what is on board. */
6054 for(i=0; i<BOARD_HEIGHT; i++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
6055 { ChessSquare p = boards[forwardMostMove][i][j];
6059 { /* count B,N,R and other of each side */
6062 NrK++; break; // [HGM] atomic: count Kings
6066 case WhiteFerz: // [HGM] shatranj: kludge to mke it work in shatranj
6067 bishopsColor |= 1 << ((i^j)&1);
6072 case BlackFerz: // [HGM] shatranj: kludge to mke it work in shatranj
6073 bishopsColor |= 1 << ((i^j)&1);
6088 PawnAdvance += m; NrPawns++;
6090 NrPieces += (p != EmptySquare);
6091 NrW += ((int)p < (int)BlackPawn);
6092 if(gameInfo.variant == VariantXiangqi &&
6093 (p == WhiteFerz || p == WhiteAlfil || p == BlackFerz || p == BlackAlfil)) {
6094 NrPieces--; // [HGM] XQ: do not count purely defensive pieces
6095 NrW -= ((int)p < (int)BlackPawn);
6099 /* Some material-based adjudications that have to be made before stalemate test */
6100 if(gameInfo.variant == VariantAtomic && NrK < 2) {
6101 // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
6102 boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
6103 if(canAdjudicate && appData.checkMates) {
6105 SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
6106 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6107 GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
6108 "Xboard adjudication: King destroyed", GE_XBOARD );
6113 /* Bare King in Shatranj (loses) or Losers (wins) */
6114 if( NrW == 1 || NrPieces - NrW == 1) {
6115 if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
6116 boards[forwardMostMove][EP_STATUS] = EP_WINS; // mark as win, so it becomes claimable
6117 if(canAdjudicate && appData.checkMates) {
6119 SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
6120 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6121 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
6122 "Xboard adjudication: Bare king", GE_XBOARD );
6126 if( gameInfo.variant == VariantShatranj && --bare < 0)
6128 boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
6129 if(canAdjudicate && appData.checkMates) {
6130 /* but only adjudicate if adjudication enabled */
6132 SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
6133 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6134 GameEnds( NrW > 1 ? WhiteWins : NrPieces - NrW > 1 ? BlackWins : GameIsDrawn,
6135 "Xboard adjudication: Bare king", GE_XBOARD );
6142 // don't wait for engine to announce game end if we can judge ourselves
6143 switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
6145 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
6146 int i, checkCnt = 0; // (should really be done by making nr of checks part of game state)
6147 for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
6148 if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
6151 reason = "Xboard adjudication: 3rd check";
6152 boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
6162 reason = "Xboard adjudication: Stalemate";
6163 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
6164 boards[forwardMostMove][EP_STATUS] = EP_STALEMATE; // default result for stalemate is draw
6165 if(gameInfo.variant == VariantLosers || gameInfo.variant == VariantGiveaway) // [HGM] losers:
6166 boards[forwardMostMove][EP_STATUS] = EP_WINS; // in these variants stalemated is always a win
6167 else if(gameInfo.variant == VariantSuicide) // in suicide it depends
6168 boards[forwardMostMove][EP_STATUS] = NrW == NrPieces-NrW ? EP_STALEMATE :
6169 ((NrW < NrPieces-NrW) != WhiteOnMove(forwardMostMove) ?
6170 EP_CHECKMATE : EP_WINS);
6171 else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
6172 boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
6176 reason = "Xboard adjudication: Checkmate";
6177 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
6181 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
6183 result = GameIsDrawn; break;
6185 result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
6187 result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
6189 result = (ChessMove) 0;
6191 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
6193 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6194 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6195 GameEnds( result, reason, GE_XBOARD );
6199 /* Next absolutely insufficient mating material. */
6200 if( NrPieces == 2 || gameInfo.variant != VariantXiangqi &&
6201 gameInfo.variant != VariantShatranj && // [HGM] baring will remain possible
6202 (NrPieces == 3 && NrWN+NrBN+NrWB+NrBB == 1 ||
6203 NrPieces == NrBB+NrWB+2 && bishopsColor != 3)) // [HGM] all Bishops (Ferz!) same color
6204 { /* KBK, KNK, KK of KBKB with like Bishops */
6206 /* always flag draws, for judging claims */
6207 boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
6209 if(canAdjudicate && appData.materialDraws) {
6210 /* but only adjudicate them if adjudication enabled */
6211 if(engineOpponent) {
6212 SendToProgram("force\n", engineOpponent); // suppress reply
6213 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
6215 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6216 GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
6221 /* Then some trivial draws (only adjudicate, cannot be claimed) */
6223 ( NrWR == 1 && NrBR == 1 /* KRKR */
6224 || NrWQ==1 && NrBQ==1 /* KQKQ */
6225 || NrWN==2 || NrBN==2 /* KNNK */
6226 || NrWN+NrWB == 1 && NrBN+NrBB == 1 /* KBKN, KBKB, KNKN */
6228 if(canAdjudicate && --moveCount < 0 && appData.trivialDraws)
6229 { /* if the first 3 moves do not show a tactical win, declare draw */
6230 if(engineOpponent) {
6231 SendToProgram("force\n", engineOpponent); // suppress reply
6232 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6234 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6235 GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
6238 } else moveCount = 6;
6242 if (appData.debugMode) { int i;
6243 fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
6244 forwardMostMove, backwardMostMove, boards[backwardMostMove][EP_STATUS],
6245 appData.drawRepeats);
6246 for( i=forwardMostMove; i>=backwardMostMove; i-- )
6247 fprintf(debugFP, "%d ep=%d\n", i, (signed char)boards[i][EP_STATUS]);
6251 // Repetition draws and 50-move rule can be applied independently of legality testing
6253 /* Check for rep-draws */
6255 for(k = forwardMostMove-2;
6256 k>=backwardMostMove && k>=forwardMostMove-100 &&
6257 (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
6258 (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE;
6261 if(CompareBoards(boards[k], boards[forwardMostMove])) {
6262 /* compare castling rights */
6263 if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
6264 (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
6265 rights++; /* King lost rights, while rook still had them */
6266 if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
6267 if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
6268 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
6269 rights++; /* but at least one rook lost them */
6271 if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
6272 (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
6274 if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
6275 if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
6276 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
6279 if( canAdjudicate && rights == 0 && ++count > appData.drawRepeats-2
6280 && appData.drawRepeats > 1) {
6281 /* adjudicate after user-specified nr of repeats */
6282 if(engineOpponent) {
6283 SendToProgram("force\n", engineOpponent); // suppress reply
6284 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6286 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6287 if(gameInfo.variant == VariantXiangqi && appData.testLegality) {
6288 // [HGM] xiangqi: check for forbidden perpetuals
6289 int m, ourPerpetual = 1, hisPerpetual = 1;
6290 for(m=forwardMostMove; m>k; m-=2) {
6291 if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
6292 ourPerpetual = 0; // the current mover did not always check
6293 if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
6294 hisPerpetual = 0; // the opponent did not always check
6296 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
6297 ourPerpetual, hisPerpetual);
6298 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
6299 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
6300 "Xboard adjudication: perpetual checking", GE_XBOARD );
6303 if(hisPerpetual && !ourPerpetual) // he is checking us, but did not repeat yet
6304 break; // (or we would have caught him before). Abort repetition-checking loop.
6305 // Now check for perpetual chases
6306 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
6307 hisPerpetual = PerpetualChase(k, forwardMostMove);
6308 ourPerpetual = PerpetualChase(k+1, forwardMostMove);
6309 if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
6310 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
6311 "Xboard adjudication: perpetual chasing", GE_XBOARD );
6314 if(hisPerpetual && !ourPerpetual) // he is chasing us, but did not repeat yet
6315 break; // Abort repetition-checking loop.
6317 // if neither of us is checking or chasing all the time, or both are, it is draw
6319 GameEnds( GameIsDrawn, "Xboard adjudication: repetition draw", GE_XBOARD );
6322 if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
6323 boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
6327 /* Now we test for 50-move draws. Determine ply count */
6328 count = forwardMostMove;
6329 /* look for last irreversble move */
6330 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
6332 /* if we hit starting position, add initial plies */
6333 if( count == backwardMostMove )
6334 count -= initialRulePlies;
6335 count = forwardMostMove - count;
6337 boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
6338 /* this is used to judge if draw claims are legal */
6339 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
6340 if(engineOpponent) {
6341 SendToProgram("force\n", engineOpponent); // suppress reply
6342 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6344 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6345 GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
6349 /* if draw offer is pending, treat it as a draw claim
6350 * when draw condition present, to allow engines a way to
6351 * claim draws before making their move to avoid a race
6352 * condition occurring after their move
6354 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
6356 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
6357 p = "Draw claim: 50-move rule";
6358 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
6359 p = "Draw claim: 3-fold repetition";
6360 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
6361 p = "Draw claim: insufficient mating material";
6362 if( p != NULL && canAdjudicate) {
6363 if(engineOpponent) {
6364 SendToProgram("force\n", engineOpponent); // suppress reply
6365 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6367 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6368 GameEnds( GameIsDrawn, p, GE_XBOARD );
6373 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
6374 if(engineOpponent) {
6375 SendToProgram("force\n", engineOpponent); // suppress reply
6376 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6378 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6379 GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
6385 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
6386 { // [HGM] book: this routine intercepts moves to simulate book replies
6387 char *bookHit = NULL;
6389 //first determine if the incoming move brings opponent into his book
6390 if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
6391 bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
6392 if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
6393 if(bookHit != NULL && !cps->bookSuspend) {
6394 // make sure opponent is not going to reply after receiving move to book position
6395 SendToProgram("force\n", cps);
6396 cps->bookSuspend = TRUE; // flag indicating it has to be restarted
6398 if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
6399 // now arrange restart after book miss
6401 // after a book hit we never send 'go', and the code after the call to this routine
6402 // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
6404 if (cps->useUsermove) sprintf(buf, "usermove "); // sorry, no SAN yet :(
6405 sprintf(buf, "%s\n", bookHit); // force book move into program supposed to play it
6406 SendToProgram(buf, cps);
6407 if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
6408 } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
6409 SendToProgram("go\n", cps);
6410 cps->bookSuspend = FALSE; // after a 'go' we are never suspended
6411 } else { // 'go' might be sent based on 'firstMove' after this routine returns
6412 if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
6413 SendToProgram("go\n", cps);
6414 cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
6416 return bookHit; // notify caller of hit, so it can take action to send move to opponent
6420 ChessProgramState *savedState;
6421 void DeferredBookMove(void)
6423 if(savedState->lastPing != savedState->lastPong)
6424 ScheduleDelayedEvent(DeferredBookMove, 10);
6426 HandleMachineMove(savedMessage, savedState);
6430 HandleMachineMove(message, cps)
6432 ChessProgramState *cps;
6434 char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
6435 char realname[MSG_SIZ];
6436 int fromX, fromY, toX, toY;
6445 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
6447 * Kludge to ignore BEL characters
6449 while (*message == '\007') message++;
6452 * [HGM] engine debug message: ignore lines starting with '#' character
6454 if(cps->debug && *message == '#') return;
6457 * Look for book output
6459 if (cps == &first && bookRequested) {
6460 if (message[0] == '\t' || message[0] == ' ') {
6461 /* Part of the book output is here; append it */
6462 strcat(bookOutput, message);
6463 strcat(bookOutput, " \n");
6465 } else if (bookOutput[0] != NULLCHAR) {
6466 /* All of book output has arrived; display it */
6467 char *p = bookOutput;
6468 while (*p != NULLCHAR) {
6469 if (*p == '\t') *p = ' ';
6472 DisplayInformation(bookOutput);
6473 bookRequested = FALSE;
6474 /* Fall through to parse the current output */
6479 * Look for machine move.
6481 if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
6482 (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
6484 /* This method is only useful on engines that support ping */
6485 if (cps->lastPing != cps->lastPong) {
6486 if (gameMode == BeginningOfGame) {
6487 /* Extra move from before last new; ignore */
6488 if (appData.debugMode) {
6489 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
6492 if (appData.debugMode) {
6493 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
6494 cps->which, gameMode);
6497 SendToProgram("undo\n", cps);
6503 case BeginningOfGame:
6504 /* Extra move from before last reset; ignore */
6505 if (appData.debugMode) {
6506 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
6513 /* Extra move after we tried to stop. The mode test is
6514 not a reliable way of detecting this problem, but it's
6515 the best we can do on engines that don't support ping.
6517 if (appData.debugMode) {
6518 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
6519 cps->which, gameMode);
6521 SendToProgram("undo\n", cps);
6524 case MachinePlaysWhite:
6525 case IcsPlayingWhite:
6526 machineWhite = TRUE;
6529 case MachinePlaysBlack:
6530 case IcsPlayingBlack:
6531 machineWhite = FALSE;
6534 case TwoMachinesPlay:
6535 machineWhite = (cps->twoMachinesColor[0] == 'w');
6538 if (WhiteOnMove(forwardMostMove) != machineWhite) {
6539 if (appData.debugMode) {
6541 "Ignoring move out of turn by %s, gameMode %d"
6542 ", forwardMost %d\n",
6543 cps->which, gameMode, forwardMostMove);
6548 if (appData.debugMode) { int f = forwardMostMove;
6549 fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
6550 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
6551 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
6553 if(cps->alphaRank) AlphaRank(machineMove, 4);
6554 if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
6555 &fromX, &fromY, &toX, &toY, &promoChar)) {
6556 /* Machine move could not be parsed; ignore it. */
6557 sprintf(buf1, _("Illegal move \"%s\" from %s machine"),
6558 machineMove, cps->which);
6559 DisplayError(buf1, 0);
6560 sprintf(buf1, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
6561 machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
6562 if (gameMode == TwoMachinesPlay) {
6563 GameEnds(machineWhite ? BlackWins : WhiteWins,
6569 /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
6570 /* So we have to redo legality test with true e.p. status here, */
6571 /* to make sure an illegal e.p. capture does not slip through, */
6572 /* to cause a forfeit on a justified illegal-move complaint */
6573 /* of the opponent. */
6574 if( gameMode==TwoMachinesPlay && appData.testLegality
6575 && fromY != DROP_RANK /* [HGM] temporary; should still add legality test for drops */
6578 moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
6579 fromY, fromX, toY, toX, promoChar);
6580 if (appData.debugMode) {
6582 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
6583 boards[forwardMostMove][CASTLING][i], castlingRank[i]);
6584 fprintf(debugFP, "castling rights\n");
6586 if(moveType == IllegalMove) {
6587 sprintf(buf1, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
6588 machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
6589 GameEnds(machineWhite ? BlackWins : WhiteWins,
6592 } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
6593 /* [HGM] Kludge to handle engines that send FRC-style castling
6594 when they shouldn't (like TSCP-Gothic) */
6596 case WhiteASideCastleFR:
6597 case BlackASideCastleFR:
6599 currentMoveString[2]++;
6601 case WhiteHSideCastleFR:
6602 case BlackHSideCastleFR:
6604 currentMoveString[2]--;
6606 default: ; // nothing to do, but suppresses warning of pedantic compilers
6609 hintRequested = FALSE;
6610 lastHint[0] = NULLCHAR;
6611 bookRequested = FALSE;
6612 /* Program may be pondering now */
6613 cps->maybeThinking = TRUE;
6614 if (cps->sendTime == 2) cps->sendTime = 1;
6615 if (cps->offeredDraw) cps->offeredDraw--;
6617 /* currentMoveString is set as a side-effect of ParseOneMove */
6618 strcpy(machineMove, currentMoveString);
6619 strcat(machineMove, "\n");
6620 strcpy(moveList[forwardMostMove], machineMove);
6622 MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
6624 /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
6625 if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
6628 while( count < adjudicateLossPlies ) {
6629 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
6632 score = -score; /* Flip score for winning side */
6635 if( score > adjudicateLossThreshold ) {
6642 if( count >= adjudicateLossPlies ) {
6643 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6645 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
6646 "Xboard adjudication",
6653 if(Adjudicate(cps)) return; // [HGM] adjudicate: for all automatic game ends
6656 if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
6658 if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
6659 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
6661 SendMoveToICS(moveType, fromX, fromY, toX, toY);
6663 SendMoveToICS(moveType, fromX, fromY, toX, toY);
6665 if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
6666 char buf[3*MSG_SIZ];
6668 sprintf(buf, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
6669 programStats.score / 100.,
6671 programStats.time / 100.,
6672 (unsigned int)programStats.nodes,
6673 (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
6674 programStats.movelist);
6676 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
6681 /* [AS] Save move info and clear stats for next move */
6682 pvInfoList[ forwardMostMove-1 ].score = programStats.score;
6683 pvInfoList[ forwardMostMove-1 ].depth = programStats.depth;
6684 pvInfoList[ forwardMostMove-1 ].time = programStats.time; // [HGM] PGNtime: take time from engine stats
6685 ClearProgramStats();
6686 thinkOutput[0] = NULLCHAR;
6687 hiddenThinkOutputState = 0;
6690 if (gameMode == TwoMachinesPlay) {
6691 /* [HGM] relaying draw offers moved to after reception of move */
6692 /* and interpreting offer as claim if it brings draw condition */
6693 if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
6694 SendToProgram("draw\n", cps->other);
6696 if (cps->other->sendTime) {
6697 SendTimeRemaining(cps->other,
6698 cps->other->twoMachinesColor[0] == 'w');
6700 bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
6701 if (firstMove && !bookHit) {
6703 if (cps->other->useColors) {
6704 SendToProgram(cps->other->twoMachinesColor, cps->other);
6706 SendToProgram("go\n", cps->other);
6708 cps->other->maybeThinking = TRUE;
6711 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6713 if (!pausing && appData.ringBellAfterMoves) {
6718 * Reenable menu items that were disabled while
6719 * machine was thinking
6721 if (gameMode != TwoMachinesPlay)
6722 SetUserThinkingEnables();
6724 // [HGM] book: after book hit opponent has received move and is now in force mode
6725 // force the book reply into it, and then fake that it outputted this move by jumping
6726 // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
6728 static char bookMove[MSG_SIZ]; // a bit generous?
6730 strcpy(bookMove, "move ");
6731 strcat(bookMove, bookHit);
6734 programStats.nodes = programStats.depth = programStats.time =
6735 programStats.score = programStats.got_only_move = 0;
6736 sprintf(programStats.movelist, "%s (xbook)", bookHit);
6738 if(cps->lastPing != cps->lastPong) {
6739 savedMessage = message; // args for deferred call
6741 ScheduleDelayedEvent(DeferredBookMove, 10);
6750 /* Set special modes for chess engines. Later something general
6751 * could be added here; for now there is just one kludge feature,
6752 * needed because Crafty 15.10 and earlier don't ignore SIGINT
6753 * when "xboard" is given as an interactive command.
6755 if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
6756 cps->useSigint = FALSE;
6757 cps->useSigterm = FALSE;
6759 if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
6760 ParseFeatures(message+8, cps);
6761 return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
6764 /* [HGM] Allow engine to set up a position. Don't ask me why one would
6765 * want this, I was asked to put it in, and obliged.
6767 if (!strncmp(message, "setboard ", 9)) {
6768 Board initial_position;
6770 GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
6772 if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
6773 DisplayError(_("Bad FEN received from engine"), 0);
6777 CopyBoard(boards[0], initial_position);
6778 initialRulePlies = FENrulePlies;
6779 if(blackPlaysFirst) gameMode = MachinePlaysWhite;
6780 else gameMode = MachinePlaysBlack;
6781 DrawPosition(FALSE, boards[currentMove]);
6787 * Look for communication commands
6789 if (!strncmp(message, "telluser ", 9)) {
6790 DisplayNote(message + 9);
6793 if (!strncmp(message, "tellusererror ", 14)) {
6795 DisplayError(message + 14, 0);
6798 if (!strncmp(message, "tellopponent ", 13)) {
6799 if (appData.icsActive) {
6801 snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
6805 DisplayNote(message + 13);
6809 if (!strncmp(message, "tellothers ", 11)) {
6810 if (appData.icsActive) {
6812 snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
6818 if (!strncmp(message, "tellall ", 8)) {
6819 if (appData.icsActive) {
6821 snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
6825 DisplayNote(message + 8);
6829 if (strncmp(message, "warning", 7) == 0) {
6830 /* Undocumented feature, use tellusererror in new code */
6831 DisplayError(message, 0);
6834 if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
6835 strcpy(realname, cps->tidy);
6836 strcat(realname, " query");
6837 AskQuestion(realname, buf2, buf1, cps->pr);
6840 /* Commands from the engine directly to ICS. We don't allow these to be
6841 * sent until we are logged on. Crafty kibitzes have been known to
6842 * interfere with the login process.
6845 if (!strncmp(message, "tellics ", 8)) {
6846 SendToICS(message + 8);
6850 if (!strncmp(message, "tellicsnoalias ", 15)) {
6851 SendToICS(ics_prefix);
6852 SendToICS(message + 15);
6856 /* The following are for backward compatibility only */
6857 if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
6858 !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
6859 SendToICS(ics_prefix);
6865 if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
6869 * If the move is illegal, cancel it and redraw the board.
6870 * Also deal with other error cases. Matching is rather loose
6871 * here to accommodate engines written before the spec.
6873 if (strncmp(message + 1, "llegal move", 11) == 0 ||
6874 strncmp(message, "Error", 5) == 0) {
6875 if (StrStr(message, "name") ||
6876 StrStr(message, "rating") || StrStr(message, "?") ||
6877 StrStr(message, "result") || StrStr(message, "board") ||
6878 StrStr(message, "bk") || StrStr(message, "computer") ||
6879 StrStr(message, "variant") || StrStr(message, "hint") ||
6880 StrStr(message, "random") || StrStr(message, "depth") ||
6881 StrStr(message, "accepted")) {
6884 if (StrStr(message, "protover")) {
6885 /* Program is responding to input, so it's apparently done
6886 initializing, and this error message indicates it is
6887 protocol version 1. So we don't need to wait any longer
6888 for it to initialize and send feature commands. */
6889 FeatureDone(cps, 1);
6890 cps->protocolVersion = 1;
6893 cps->maybeThinking = FALSE;
6895 if (StrStr(message, "draw")) {
6896 /* Program doesn't have "draw" command */
6897 cps->sendDrawOffers = 0;
6900 if (cps->sendTime != 1 &&
6901 (StrStr(message, "time") || StrStr(message, "otim"))) {
6902 /* Program apparently doesn't have "time" or "otim" command */
6906 if (StrStr(message, "analyze")) {
6907 cps->analysisSupport = FALSE;
6908 cps->analyzing = FALSE;
6910 sprintf(buf2, _("%s does not support analysis"), cps->tidy);
6911 DisplayError(buf2, 0);
6914 if (StrStr(message, "(no matching move)st")) {
6915 /* Special kludge for GNU Chess 4 only */
6916 cps->stKludge = TRUE;
6917 SendTimeControl(cps, movesPerSession, timeControl,
6918 timeIncrement, appData.searchDepth,
6922 if (StrStr(message, "(no matching move)sd")) {
6923 /* Special kludge for GNU Chess 4 only */
6924 cps->sdKludge = TRUE;
6925 SendTimeControl(cps, movesPerSession, timeControl,
6926 timeIncrement, appData.searchDepth,
6930 if (!StrStr(message, "llegal")) {
6933 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
6934 gameMode == IcsIdle) return;
6935 if (forwardMostMove <= backwardMostMove) return;
6936 if (pausing) PauseEvent();
6937 if(appData.forceIllegal) {
6938 // [HGM] illegal: machine refused move; force position after move into it
6939 SendToProgram("force\n", cps);
6940 if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
6941 // we have a real problem now, as SendBoard will use the a2a3 kludge
6942 // when black is to move, while there might be nothing on a2 or black
6943 // might already have the move. So send the board as if white has the move.
6944 // But first we must change the stm of the engine, as it refused the last move
6945 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
6946 if(WhiteOnMove(forwardMostMove)) {
6947 SendToProgram("a7a6\n", cps); // for the engine black still had the move
6948 SendBoard(cps, forwardMostMove); // kludgeless board
6950 SendToProgram("a2a3\n", cps); // for the engine white still had the move
6951 CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
6952 SendBoard(cps, forwardMostMove+1); // kludgeless board
6954 } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
6955 if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
6956 gameMode == TwoMachinesPlay)
6957 SendToProgram("go\n", cps);
6960 if (gameMode == PlayFromGameFile) {
6961 /* Stop reading this game file */
6962 gameMode = EditGame;
6965 currentMove = --forwardMostMove;
6966 DisplayMove(currentMove-1); /* before DisplayMoveError */
6968 DisplayBothClocks();
6969 sprintf(buf1, _("Illegal move \"%s\" (rejected by %s chess program)"),
6970 parseList[currentMove], cps->which);
6971 DisplayMoveError(buf1);
6972 DrawPosition(FALSE, boards[currentMove]);
6974 /* [HGM] illegal-move claim should forfeit game when Xboard */
6975 /* only passes fully legal moves */
6976 if( appData.testLegality && gameMode == TwoMachinesPlay ) {
6977 GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
6978 "False illegal-move claim", GE_XBOARD );
6982 if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
6983 /* Program has a broken "time" command that
6984 outputs a string not ending in newline.
6990 * If chess program startup fails, exit with an error message.
6991 * Attempts to recover here are futile.
6993 if ((StrStr(message, "unknown host") != NULL)
6994 || (StrStr(message, "No remote directory") != NULL)
6995 || (StrStr(message, "not found") != NULL)
6996 || (StrStr(message, "No such file") != NULL)
6997 || (StrStr(message, "can't alloc") != NULL)
6998 || (StrStr(message, "Permission denied") != NULL)) {
7000 cps->maybeThinking = FALSE;
7001 snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
7002 cps->which, cps->program, cps->host, message);
7003 RemoveInputSource(cps->isr);
7004 DisplayFatalError(buf1, 0, 1);
7009 * Look for hint output
7011 if (sscanf(message, "Hint: %s", buf1) == 1) {
7012 if (cps == &first && hintRequested) {
7013 hintRequested = FALSE;
7014 if (ParseOneMove(buf1, forwardMostMove, &moveType,
7015 &fromX, &fromY, &toX, &toY, &promoChar)) {
7016 (void) CoordsToAlgebraic(boards[forwardMostMove],
7017 PosFlags(forwardMostMove),
7018 fromY, fromX, toY, toX, promoChar, buf1);
7019 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
7020 DisplayInformation(buf2);
7022 /* Hint move could not be parsed!? */
7023 snprintf(buf2, sizeof(buf2),
7024 _("Illegal hint move \"%s\"\nfrom %s chess program"),
7026 DisplayError(buf2, 0);
7029 strcpy(lastHint, buf1);
7035 * Ignore other messages if game is not in progress
7037 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
7038 gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
7041 * look for win, lose, draw, or draw offer
7043 if (strncmp(message, "1-0", 3) == 0) {
7044 char *p, *q, *r = "";
7045 p = strchr(message, '{');
7053 GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
7055 } else if (strncmp(message, "0-1", 3) == 0) {
7056 char *p, *q, *r = "";
7057 p = strchr(message, '{');
7065 /* Kludge for Arasan 4.1 bug */
7066 if (strcmp(r, "Black resigns") == 0) {
7067 GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
7070 GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
7072 } else if (strncmp(message, "1/2", 3) == 0) {
7073 char *p, *q, *r = "";
7074 p = strchr(message, '{');
7083 GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
7086 } else if (strncmp(message, "White resign", 12) == 0) {
7087 GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
7089 } else if (strncmp(message, "Black resign", 12) == 0) {
7090 GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
7092 } else if (strncmp(message, "White matches", 13) == 0 ||
7093 strncmp(message, "Black matches", 13) == 0 ) {
7094 /* [HGM] ignore GNUShogi noises */
7096 } else if (strncmp(message, "White", 5) == 0 &&
7097 message[5] != '(' &&
7098 StrStr(message, "Black") == NULL) {
7099 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7101 } else if (strncmp(message, "Black", 5) == 0 &&
7102 message[5] != '(') {
7103 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7105 } else if (strcmp(message, "resign") == 0 ||
7106 strcmp(message, "computer resigns") == 0) {
7108 case MachinePlaysBlack:
7109 case IcsPlayingBlack:
7110 GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
7112 case MachinePlaysWhite:
7113 case IcsPlayingWhite:
7114 GameEnds(BlackWins, "White resigns", GE_ENGINE);
7116 case TwoMachinesPlay:
7117 if (cps->twoMachinesColor[0] == 'w')
7118 GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
7120 GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
7127 } else if (strncmp(message, "opponent mates", 14) == 0) {
7129 case MachinePlaysBlack:
7130 case IcsPlayingBlack:
7131 GameEnds(WhiteWins, "White mates", GE_ENGINE);
7133 case MachinePlaysWhite:
7134 case IcsPlayingWhite:
7135 GameEnds(BlackWins, "Black mates", GE_ENGINE);
7137 case TwoMachinesPlay:
7138 if (cps->twoMachinesColor[0] == 'w')
7139 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7141 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7148 } else if (strncmp(message, "computer mates", 14) == 0) {
7150 case MachinePlaysBlack:
7151 case IcsPlayingBlack:
7152 GameEnds(BlackWins, "Black mates", GE_ENGINE1);
7154 case MachinePlaysWhite:
7155 case IcsPlayingWhite:
7156 GameEnds(WhiteWins, "White mates", GE_ENGINE);
7158 case TwoMachinesPlay:
7159 if (cps->twoMachinesColor[0] == 'w')
7160 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7162 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7169 } else if (strncmp(message, "checkmate", 9) == 0) {
7170 if (WhiteOnMove(forwardMostMove)) {
7171 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7173 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7176 } else if (strstr(message, "Draw") != NULL ||
7177 strstr(message, "game is a draw") != NULL) {
7178 GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
7180 } else if (strstr(message, "offer") != NULL &&
7181 strstr(message, "draw") != NULL) {
7183 if (appData.zippyPlay && first.initDone) {
7184 /* Relay offer to ICS */
7185 SendToICS(ics_prefix);
7186 SendToICS("draw\n");
7189 cps->offeredDraw = 2; /* valid until this engine moves twice */
7190 if (gameMode == TwoMachinesPlay) {
7191 if (cps->other->offeredDraw) {
7192 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
7193 /* [HGM] in two-machine mode we delay relaying draw offer */
7194 /* until after we also have move, to see if it is really claim */
7196 } else if (gameMode == MachinePlaysWhite ||
7197 gameMode == MachinePlaysBlack) {
7198 if (userOfferedDraw) {
7199 DisplayInformation(_("Machine accepts your draw offer"));
7200 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
7202 DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
7209 * Look for thinking output
7211 if ( appData.showThinking // [HGM] thinking: test all options that cause this output
7212 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
7214 int plylev, mvleft, mvtot, curscore, time;
7215 char mvname[MOVE_LEN];
7219 int prefixHint = FALSE;
7220 mvname[0] = NULLCHAR;
7223 case MachinePlaysBlack:
7224 case IcsPlayingBlack:
7225 if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
7227 case MachinePlaysWhite:
7228 case IcsPlayingWhite:
7229 if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
7234 case IcsObserving: /* [DM] icsEngineAnalyze */
7235 if (!appData.icsEngineAnalyze) ignore = TRUE;
7237 case TwoMachinesPlay:
7238 if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
7249 if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
7250 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
7252 if (plyext != ' ' && plyext != '\t') {
7256 /* [AS] Negate score if machine is playing black and reporting absolute scores */
7257 if( cps->scoreIsAbsolute &&
7258 ( gameMode == MachinePlaysBlack ||
7259 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
7260 gameMode == IcsPlayingBlack || // [HGM] also add other situations where engine should report black POV
7261 (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
7262 !WhiteOnMove(currentMove)
7265 curscore = -curscore;
7269 programStats.depth = plylev;
7270 programStats.nodes = nodes;
7271 programStats.time = time;
7272 programStats.score = curscore;
7273 programStats.got_only_move = 0;
7275 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
7278 if(cps->nps == 0) ticklen = 10*time; // use engine reported time
7279 else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
7280 if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
7281 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
7282 whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
7283 if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
7284 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
7285 blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
7288 /* Buffer overflow protection */
7289 if (buf1[0] != NULLCHAR) {
7290 if (strlen(buf1) >= sizeof(programStats.movelist)
7291 && appData.debugMode) {
7293 "PV is too long; using the first %u bytes.\n",
7294 (unsigned) sizeof(programStats.movelist) - 1);
7297 safeStrCpy( programStats.movelist, buf1, sizeof(programStats.movelist) );
7299 sprintf(programStats.movelist, " no PV\n");
7302 if (programStats.seen_stat) {
7303 programStats.ok_to_send = 1;
7306 if (strchr(programStats.movelist, '(') != NULL) {
7307 programStats.line_is_book = 1;
7308 programStats.nr_moves = 0;
7309 programStats.moves_left = 0;
7311 programStats.line_is_book = 0;
7314 SendProgramStatsToFrontend( cps, &programStats );
7317 [AS] Protect the thinkOutput buffer from overflow... this
7318 is only useful if buf1 hasn't overflowed first!
7320 sprintf(thinkOutput, "[%d]%c%+.2f %s%s",
7322 (gameMode == TwoMachinesPlay ?
7323 ToUpper(cps->twoMachinesColor[0]) : ' '),
7324 ((double) curscore) / 100.0,
7325 prefixHint ? lastHint : "",
7326 prefixHint ? " " : "" );
7328 if( buf1[0] != NULLCHAR ) {
7329 unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
7331 if( strlen(buf1) > max_len ) {
7332 if( appData.debugMode) {
7333 fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
7335 buf1[max_len+1] = '\0';
7338 strcat( thinkOutput, buf1 );
7341 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
7342 || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7343 DisplayMove(currentMove - 1);
7347 } else if ((p=StrStr(message, "(only move)")) != NULL) {
7348 /* crafty (9.25+) says "(only move) <move>"
7349 * if there is only 1 legal move
7351 sscanf(p, "(only move) %s", buf1);
7352 sprintf(thinkOutput, "%s (only move)", buf1);
7353 sprintf(programStats.movelist, "%s (only move)", buf1);
7354 programStats.depth = 1;
7355 programStats.nr_moves = 1;
7356 programStats.moves_left = 1;
7357 programStats.nodes = 1;
7358 programStats.time = 1;
7359 programStats.got_only_move = 1;
7361 /* Not really, but we also use this member to
7362 mean "line isn't going to change" (Crafty
7363 isn't searching, so stats won't change) */
7364 programStats.line_is_book = 1;
7366 SendProgramStatsToFrontend( cps, &programStats );
7368 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
7369 gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7370 DisplayMove(currentMove - 1);
7373 } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
7374 &time, &nodes, &plylev, &mvleft,
7375 &mvtot, mvname) >= 5) {
7376 /* The stat01: line is from Crafty (9.29+) in response
7377 to the "." command */
7378 programStats.seen_stat = 1;
7379 cps->maybeThinking = TRUE;
7381 if (programStats.got_only_move || !appData.periodicUpdates)
7384 programStats.depth = plylev;
7385 programStats.time = time;
7386 programStats.nodes = nodes;
7387 programStats.moves_left = mvleft;
7388 programStats.nr_moves = mvtot;
7389 strcpy(programStats.move_name, mvname);
7390 programStats.ok_to_send = 1;
7391 programStats.movelist[0] = '\0';
7393 SendProgramStatsToFrontend( cps, &programStats );
7397 } else if (strncmp(message,"++",2) == 0) {
7398 /* Crafty 9.29+ outputs this */
7399 programStats.got_fail = 2;
7402 } else if (strncmp(message,"--",2) == 0) {
7403 /* Crafty 9.29+ outputs this */
7404 programStats.got_fail = 1;
7407 } else if (thinkOutput[0] != NULLCHAR &&
7408 strncmp(message, " ", 4) == 0) {
7409 unsigned message_len;
7412 while (*p && *p == ' ') p++;
7414 message_len = strlen( p );
7416 /* [AS] Avoid buffer overflow */
7417 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
7418 strcat(thinkOutput, " ");
7419 strcat(thinkOutput, p);
7422 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
7423 strcat(programStats.movelist, " ");
7424 strcat(programStats.movelist, p);
7427 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
7428 gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7429 DisplayMove(currentMove - 1);
7437 if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
7438 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
7440 ChessProgramStats cpstats;
7442 if (plyext != ' ' && plyext != '\t') {
7446 /* [AS] Negate score if machine is playing black and reporting absolute scores */
7447 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
7448 curscore = -curscore;
7451 cpstats.depth = plylev;
7452 cpstats.nodes = nodes;
7453 cpstats.time = time;
7454 cpstats.score = curscore;
7455 cpstats.got_only_move = 0;
7456 cpstats.movelist[0] = '\0';
7458 if (buf1[0] != NULLCHAR) {
7459 safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist) );
7462 cpstats.ok_to_send = 0;
7463 cpstats.line_is_book = 0;
7464 cpstats.nr_moves = 0;
7465 cpstats.moves_left = 0;
7467 SendProgramStatsToFrontend( cps, &cpstats );
7474 /* Parse a game score from the character string "game", and
7475 record it as the history of the current game. The game
7476 score is NOT assumed to start from the standard position.
7477 The display is not updated in any way.
7480 ParseGameHistory(game)
7484 int fromX, fromY, toX, toY, boardIndex;
7489 if (appData.debugMode)
7490 fprintf(debugFP, "Parsing game history: %s\n", game);
7492 if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
7493 gameInfo.site = StrSave(appData.icsHost);
7494 gameInfo.date = PGNDate();
7495 gameInfo.round = StrSave("-");
7497 /* Parse out names of players */
7498 while (*game == ' ') game++;
7500 while (*game != ' ') *p++ = *game++;
7502 gameInfo.white = StrSave(buf);
7503 while (*game == ' ') game++;
7505 while (*game != ' ' && *game != '\n') *p++ = *game++;
7507 gameInfo.black = StrSave(buf);
7510 boardIndex = blackPlaysFirst ? 1 : 0;
7513 yyboardindex = boardIndex;
7514 moveType = (ChessMove) yylex();
7516 case IllegalMove: /* maybe suicide chess, etc. */
7517 if (appData.debugMode) {
7518 fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
7519 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7520 setbuf(debugFP, NULL);
7522 case WhitePromotionChancellor:
7523 case BlackPromotionChancellor:
7524 case WhitePromotionArchbishop:
7525 case BlackPromotionArchbishop:
7526 case WhitePromotionQueen:
7527 case BlackPromotionQueen:
7528 case WhitePromotionRook:
7529 case BlackPromotionRook:
7530 case WhitePromotionBishop:
7531 case BlackPromotionBishop:
7532 case WhitePromotionKnight:
7533 case BlackPromotionKnight:
7534 case WhitePromotionKing:
7535 case BlackPromotionKing:
7537 case WhiteCapturesEnPassant:
7538 case BlackCapturesEnPassant:
7539 case WhiteKingSideCastle:
7540 case WhiteQueenSideCastle:
7541 case BlackKingSideCastle:
7542 case BlackQueenSideCastle:
7543 case WhiteKingSideCastleWild:
7544 case WhiteQueenSideCastleWild:
7545 case BlackKingSideCastleWild:
7546 case BlackQueenSideCastleWild:
7548 case WhiteHSideCastleFR:
7549 case WhiteASideCastleFR:
7550 case BlackHSideCastleFR:
7551 case BlackASideCastleFR:
7553 fromX = currentMoveString[0] - AAA;
7554 fromY = currentMoveString[1] - ONE;
7555 toX = currentMoveString[2] - AAA;
7556 toY = currentMoveString[3] - ONE;
7557 promoChar = currentMoveString[4];
7561 fromX = moveType == WhiteDrop ?
7562 (int) CharToPiece(ToUpper(currentMoveString[0])) :
7563 (int) CharToPiece(ToLower(currentMoveString[0]));
7565 toX = currentMoveString[2] - AAA;
7566 toY = currentMoveString[3] - ONE;
7567 promoChar = NULLCHAR;
7571 sprintf(buf, _("Ambiguous move in ICS output: \"%s\""), yy_text);
7572 if (appData.debugMode) {
7573 fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
7574 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7575 setbuf(debugFP, NULL);
7577 DisplayError(buf, 0);
7579 case ImpossibleMove:
7581 sprintf(buf, _("Illegal move in ICS output: \"%s\""), yy_text);
7582 if (appData.debugMode) {
7583 fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
7584 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7585 setbuf(debugFP, NULL);
7587 DisplayError(buf, 0);
7589 case (ChessMove) 0: /* end of file */
7590 if (boardIndex < backwardMostMove) {
7591 /* Oops, gap. How did that happen? */
7592 DisplayError(_("Gap in move list"), 0);
7595 backwardMostMove = blackPlaysFirst ? 1 : 0;
7596 if (boardIndex > forwardMostMove) {
7597 forwardMostMove = boardIndex;
7601 if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
7602 strcat(parseList[boardIndex-1], " ");
7603 strcat(parseList[boardIndex-1], yy_text);
7615 case GameUnfinished:
7616 if (gameMode == IcsExamining) {
7617 if (boardIndex < backwardMostMove) {
7618 /* Oops, gap. How did that happen? */
7621 backwardMostMove = blackPlaysFirst ? 1 : 0;
7624 gameInfo.result = moveType;
7625 p = strchr(yy_text, '{');
7626 if (p == NULL) p = strchr(yy_text, '(');
7629 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
7631 q = strchr(p, *p == '{' ? '}' : ')');
7632 if (q != NULL) *q = NULLCHAR;
7635 gameInfo.resultDetails = StrSave(p);
7638 if (boardIndex >= forwardMostMove &&
7639 !(gameMode == IcsObserving && ics_gamenum == -1)) {
7640 backwardMostMove = blackPlaysFirst ? 1 : 0;
7643 (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
7644 fromY, fromX, toY, toX, promoChar,
7645 parseList[boardIndex]);
7646 CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
7647 /* currentMoveString is set as a side-effect of yylex */
7648 strcpy(moveList[boardIndex], currentMoveString);
7649 strcat(moveList[boardIndex], "\n");
7651 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
7652 switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
7658 if(gameInfo.variant != VariantShogi)
7659 strcat(parseList[boardIndex - 1], "+");
7663 strcat(parseList[boardIndex - 1], "#");
7670 /* Apply a move to the given board */
7672 ApplyMove(fromX, fromY, toX, toY, promoChar, board)
7673 int fromX, fromY, toX, toY;
7677 ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
7678 int promoRank = gameInfo.variant == VariantMakruk ? 3 : 1;
7680 /* [HGM] compute & store e.p. status and castling rights for new position */
7681 /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
7684 if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
7685 oldEP = (signed char)board[EP_STATUS];
7686 board[EP_STATUS] = EP_NONE;
7688 if( board[toY][toX] != EmptySquare )
7689 board[EP_STATUS] = EP_CAPTURE;
7691 if( board[fromY][fromX] == WhitePawn ) {
7692 if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
7693 board[EP_STATUS] = EP_PAWN_MOVE;
7695 if(toX>BOARD_LEFT && board[toY][toX-1] == BlackPawn &&
7696 gameInfo.variant != VariantBerolina || toX < fromX)
7697 board[EP_STATUS] = toX | berolina;
7698 if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
7699 gameInfo.variant != VariantBerolina || toX > fromX)
7700 board[EP_STATUS] = toX;
7703 if( board[fromY][fromX] == BlackPawn ) {
7704 if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
7705 board[EP_STATUS] = EP_PAWN_MOVE;
7706 if( toY-fromY== -2) {
7707 if(toX>BOARD_LEFT && board[toY][toX-1] == WhitePawn &&
7708 gameInfo.variant != VariantBerolina || toX < fromX)
7709 board[EP_STATUS] = toX | berolina;
7710 if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
7711 gameInfo.variant != VariantBerolina || toX > fromX)
7712 board[EP_STATUS] = toX;
7716 for(i=0; i<nrCastlingRights; i++) {
7717 if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
7718 board[CASTLING][i] == toX && castlingRank[i] == toY
7719 ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
7724 /* [HGM] In Shatranj and Courier all promotions are to Ferz */
7725 if((gameInfo.variant==VariantShatranj || gameInfo.variant==VariantCourier || gameInfo.variant == VariantMakruk)
7726 && promoChar != 0) promoChar = PieceToChar(WhiteFerz);
7728 if (fromX == toX && fromY == toY) return;
7730 if (fromY == DROP_RANK) {
7732 piece = board[toY][toX] = (ChessSquare) fromX;
7734 piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
7735 king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
7736 if(gameInfo.variant == VariantKnightmate)
7737 king += (int) WhiteUnicorn - (int) WhiteKing;
7739 /* Code added by Tord: */
7740 /* FRC castling assumed when king captures friendly rook. */
7741 if (board[fromY][fromX] == WhiteKing &&
7742 board[toY][toX] == WhiteRook) {
7743 board[fromY][fromX] = EmptySquare;
7744 board[toY][toX] = EmptySquare;
7746 board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
7748 board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
7750 } else if (board[fromY][fromX] == BlackKing &&
7751 board[toY][toX] == BlackRook) {
7752 board[fromY][fromX] = EmptySquare;
7753 board[toY][toX] = EmptySquare;
7755 board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
7757 board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
7759 /* End of code added by Tord */
7761 } else if (board[fromY][fromX] == king
7762 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7763 && toY == fromY && toX > fromX+1) {
7764 board[fromY][fromX] = EmptySquare;
7765 board[toY][toX] = king;
7766 board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
7767 board[fromY][BOARD_RGHT-1] = EmptySquare;
7768 } else if (board[fromY][fromX] == king
7769 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7770 && toY == fromY && toX < fromX-1) {
7771 board[fromY][fromX] = EmptySquare;
7772 board[toY][toX] = king;
7773 board[toY][toX+1] = board[fromY][BOARD_LEFT];
7774 board[fromY][BOARD_LEFT] = EmptySquare;
7775 } else if (board[fromY][fromX] == WhitePawn
7776 && toY >= BOARD_HEIGHT-promoRank
7777 && gameInfo.variant != VariantXiangqi
7779 /* white pawn promotion */
7780 board[toY][toX] = CharToPiece(ToUpper(promoChar));
7781 if (board[toY][toX] == EmptySquare) {
7782 board[toY][toX] = WhiteQueen;
7784 if(gameInfo.variant==VariantBughouse ||
7785 gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
7786 board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
7787 board[fromY][fromX] = EmptySquare;
7788 } else if ((fromY == BOARD_HEIGHT-4)
7790 && gameInfo.variant != VariantXiangqi
7791 && gameInfo.variant != VariantBerolina
7792 && (board[fromY][fromX] == WhitePawn)
7793 && (board[toY][toX] == EmptySquare)) {
7794 board[fromY][fromX] = EmptySquare;
7795 board[toY][toX] = WhitePawn;
7796 captured = board[toY - 1][toX];
7797 board[toY - 1][toX] = EmptySquare;
7798 } else if ((fromY == BOARD_HEIGHT-4)
7800 && gameInfo.variant == VariantBerolina
7801 && (board[fromY][fromX] == WhitePawn)
7802 && (board[toY][toX] == EmptySquare)) {
7803 board[fromY][fromX] = EmptySquare;
7804 board[toY][toX] = WhitePawn;
7805 if(oldEP & EP_BEROLIN_A) {
7806 captured = board[fromY][fromX-1];
7807 board[fromY][fromX-1] = EmptySquare;
7808 }else{ captured = board[fromY][fromX+1];
7809 board[fromY][fromX+1] = EmptySquare;
7811 } else if (board[fromY][fromX] == king
7812 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7813 && toY == fromY && toX > fromX+1) {
7814 board[fromY][fromX] = EmptySquare;
7815 board[toY][toX] = king;
7816 board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
7817 board[fromY][BOARD_RGHT-1] = EmptySquare;
7818 } else if (board[fromY][fromX] == king
7819 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7820 && toY == fromY && toX < fromX-1) {
7821 board[fromY][fromX] = EmptySquare;
7822 board[toY][toX] = king;
7823 board[toY][toX+1] = board[fromY][BOARD_LEFT];
7824 board[fromY][BOARD_LEFT] = EmptySquare;
7825 } else if (fromY == 7 && fromX == 3
7826 && board[fromY][fromX] == BlackKing
7827 && toY == 7 && toX == 5) {
7828 board[fromY][fromX] = EmptySquare;
7829 board[toY][toX] = BlackKing;
7830 board[fromY][7] = EmptySquare;
7831 board[toY][4] = BlackRook;
7832 } else if (fromY == 7 && fromX == 3
7833 && board[fromY][fromX] == BlackKing
7834 && toY == 7 && toX == 1) {
7835 board[fromY][fromX] = EmptySquare;
7836 board[toY][toX] = BlackKing;
7837 board[fromY][0] = EmptySquare;
7838 board[toY][2] = BlackRook;
7839 } else if (board[fromY][fromX] == BlackPawn
7841 && gameInfo.variant != VariantXiangqi
7843 /* black pawn promotion */
7844 board[toY][toX] = CharToPiece(ToLower(promoChar));
7845 if (board[toY][toX] == EmptySquare) {
7846 board[toY][toX] = BlackQueen;
7848 if(gameInfo.variant==VariantBughouse ||
7849 gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
7850 board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
7851 board[fromY][fromX] = EmptySquare;
7852 } else if ((fromY == 3)
7854 && gameInfo.variant != VariantXiangqi
7855 && gameInfo.variant != VariantBerolina
7856 && (board[fromY][fromX] == BlackPawn)
7857 && (board[toY][toX] == EmptySquare)) {
7858 board[fromY][fromX] = EmptySquare;
7859 board[toY][toX] = BlackPawn;
7860 captured = board[toY + 1][toX];
7861 board[toY + 1][toX] = EmptySquare;
7862 } else if ((fromY == 3)
7864 && gameInfo.variant == VariantBerolina
7865 && (board[fromY][fromX] == BlackPawn)
7866 && (board[toY][toX] == EmptySquare)) {
7867 board[fromY][fromX] = EmptySquare;
7868 board[toY][toX] = BlackPawn;
7869 if(oldEP & EP_BEROLIN_A) {
7870 captured = board[fromY][fromX-1];
7871 board[fromY][fromX-1] = EmptySquare;
7872 }else{ captured = board[fromY][fromX+1];
7873 board[fromY][fromX+1] = EmptySquare;
7876 board[toY][toX] = board[fromY][fromX];
7877 board[fromY][fromX] = EmptySquare;
7880 /* [HGM] now we promote for Shogi, if needed */
7881 if(gameInfo.variant == VariantShogi && promoChar == 'q')
7882 board[toY][toX] = (ChessSquare) (PROMOTED piece);
7885 if (gameInfo.holdingsWidth != 0) {
7887 /* !!A lot more code needs to be written to support holdings */
7888 /* [HGM] OK, so I have written it. Holdings are stored in the */
7889 /* penultimate board files, so they are automaticlly stored */
7890 /* in the game history. */
7891 if (fromY == DROP_RANK) {
7892 /* Delete from holdings, by decreasing count */
7893 /* and erasing image if necessary */
7895 if(p < (int) BlackPawn) { /* white drop */
7896 p -= (int)WhitePawn;
7897 p = PieceToNumber((ChessSquare)p);
7898 if(p >= gameInfo.holdingsSize) p = 0;
7899 if(--board[p][BOARD_WIDTH-2] <= 0)
7900 board[p][BOARD_WIDTH-1] = EmptySquare;
7901 if((int)board[p][BOARD_WIDTH-2] < 0)
7902 board[p][BOARD_WIDTH-2] = 0;
7903 } else { /* black drop */
7904 p -= (int)BlackPawn;
7905 p = PieceToNumber((ChessSquare)p);
7906 if(p >= gameInfo.holdingsSize) p = 0;
7907 if(--board[BOARD_HEIGHT-1-p][1] <= 0)
7908 board[BOARD_HEIGHT-1-p][0] = EmptySquare;
7909 if((int)board[BOARD_HEIGHT-1-p][1] < 0)
7910 board[BOARD_HEIGHT-1-p][1] = 0;
7913 if (captured != EmptySquare && gameInfo.holdingsSize > 0
7914 && gameInfo.variant != VariantBughouse ) {
7915 /* [HGM] holdings: Add to holdings, if holdings exist */
7916 if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
7917 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
7918 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
7921 if (p >= (int) BlackPawn) {
7922 p -= (int)BlackPawn;
7923 if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
7924 /* in Shogi restore piece to its original first */
7925 captured = (ChessSquare) (DEMOTED captured);
7928 p = PieceToNumber((ChessSquare)p);
7929 if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
7930 board[p][BOARD_WIDTH-2]++;
7931 board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
7933 p -= (int)WhitePawn;
7934 if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
7935 captured = (ChessSquare) (DEMOTED captured);
7938 p = PieceToNumber((ChessSquare)p);
7939 if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
7940 board[BOARD_HEIGHT-1-p][1]++;
7941 board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
7944 } else if (gameInfo.variant == VariantAtomic) {
7945 if (captured != EmptySquare) {
7947 for (y = toY-1; y <= toY+1; y++) {
7948 for (x = toX-1; x <= toX+1; x++) {
7949 if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
7950 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
7951 board[y][x] = EmptySquare;
7955 board[toY][toX] = EmptySquare;
7958 if(gameInfo.variant == VariantShogi && promoChar != NULLCHAR && promoChar != '=') {
7959 /* [HGM] Shogi promotions */
7960 board[toY][toX] = (ChessSquare) (PROMOTED piece);
7963 if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
7964 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
7965 // [HGM] superchess: take promotion piece out of holdings
7966 int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
7967 if((int)piece < (int)BlackPawn) { // determine stm from piece color
7968 if(!--board[k][BOARD_WIDTH-2])
7969 board[k][BOARD_WIDTH-1] = EmptySquare;
7971 if(!--board[BOARD_HEIGHT-1-k][1])
7972 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
7978 /* Updates forwardMostMove */
7980 MakeMove(fromX, fromY, toX, toY, promoChar)
7981 int fromX, fromY, toX, toY;
7984 // forwardMostMove++; // [HGM] bare: moved downstream
7986 if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
7987 int timeLeft; static int lastLoadFlag=0; int king, piece;
7988 piece = boards[forwardMostMove][fromY][fromX];
7989 king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
7990 if(gameInfo.variant == VariantKnightmate)
7991 king += (int) WhiteUnicorn - (int) WhiteKing;
7992 if(forwardMostMove == 0) {
7994 fprintf(serverMoves, "%s;", second.tidy);
7995 fprintf(serverMoves, "%s;", first.tidy);
7996 if(!blackPlaysFirst)
7997 fprintf(serverMoves, "%s;", second.tidy);
7998 } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
7999 lastLoadFlag = loadFlag;
8001 fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
8002 // print castling suffix
8003 if( toY == fromY && piece == king ) {
8005 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
8007 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
8010 if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
8011 boards[forwardMostMove][fromY][fromX] == BlackPawn ) &&
8012 boards[forwardMostMove][toY][toX] == EmptySquare
8014 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
8016 if(promoChar != NULLCHAR)
8017 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);
8019 fprintf(serverMoves, "/%d/%d",
8020 pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
8021 if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
8022 else timeLeft = blackTimeRemaining/1000;
8023 fprintf(serverMoves, "/%d", timeLeft);
8025 fflush(serverMoves);
8028 if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations
8029 DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
8033 UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
8034 if (commentList[forwardMostMove+1] != NULL) {
8035 free(commentList[forwardMostMove+1]);
8036 commentList[forwardMostMove+1] = NULL;
8038 CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
8039 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
8040 forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
8041 SwitchClocks(); // uses forwardMostMove, so must be done after incrementing it !
8042 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
8043 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
8044 gameInfo.result = GameUnfinished;
8045 if (gameInfo.resultDetails != NULL) {
8046 free(gameInfo.resultDetails);
8047 gameInfo.resultDetails = NULL;
8049 CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
8050 moveList[forwardMostMove - 1]);
8051 (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
8052 PosFlags(forwardMostMove - 1),
8053 fromY, fromX, toY, toX, promoChar,
8054 parseList[forwardMostMove - 1]);
8055 switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
8061 if(gameInfo.variant != VariantShogi)
8062 strcat(parseList[forwardMostMove - 1], "+");
8066 strcat(parseList[forwardMostMove - 1], "#");
8069 if (appData.debugMode) {
8070 fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
8075 /* Updates currentMove if not pausing */
8077 ShowMove(fromX, fromY, toX, toY)
8079 int instant = (gameMode == PlayFromGameFile) ?
8080 (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
8081 if(appData.noGUI) return;
8082 if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
8084 if (forwardMostMove == currentMove + 1) {
8085 AnimateMove(boards[forwardMostMove - 1],
8086 fromX, fromY, toX, toY);
8088 if (appData.highlightLastMove) {
8089 SetHighlights(fromX, fromY, toX, toY);
8092 currentMove = forwardMostMove;
8095 if (instant) return;
8097 DisplayMove(currentMove - 1);
8098 DrawPosition(FALSE, boards[currentMove]);
8099 DisplayBothClocks();
8100 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
8103 void SendEgtPath(ChessProgramState *cps)
8104 { /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
8105 char buf[MSG_SIZ], name[MSG_SIZ], *p;
8107 if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
8110 char c, *q = name+1, *r, *s;
8112 name[0] = ','; // extract next format name from feature and copy with prefixed ','
8113 while(*p && *p != ',') *q++ = *p++;
8115 if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
8116 strcmp(name, ",nalimov:") == 0 ) {
8117 // take nalimov path from the menu-changeable option first, if it is defined
8118 sprintf(buf, "egtpath nalimov %s\n", appData.defaultPathEGTB);
8119 SendToProgram(buf,cps); // send egtbpath command for nalimov
8121 if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
8122 (s = StrStr(appData.egtFormats, name)) != NULL) {
8123 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
8124 s = r = StrStr(s, ":") + 1; // beginning of path info
8125 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
8126 c = *r; *r = 0; // temporarily null-terminate path info
8127 *--q = 0; // strip of trailig ':' from name
8128 sprintf(buf, "egtpath %s %s\n", name+1, s);
8130 SendToProgram(buf,cps); // send egtbpath command for this format
8132 if(*p == ',') p++; // read away comma to position for next format name
8137 InitChessProgram(cps, setup)
8138 ChessProgramState *cps;
8139 int setup; /* [HGM] needed to setup FRC opening position */
8141 char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
8142 if (appData.noChessProgram) return;
8143 hintRequested = FALSE;
8144 bookRequested = FALSE;
8146 /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
8147 /* moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
8148 if(cps->memSize) { /* [HGM] memory */
8149 sprintf(buf, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
8150 SendToProgram(buf, cps);
8152 SendEgtPath(cps); /* [HGM] EGT */
8153 if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
8154 sprintf(buf, "cores %d\n", appData.smpCores);
8155 SendToProgram(buf, cps);
8158 SendToProgram(cps->initString, cps);
8159 if (gameInfo.variant != VariantNormal &&
8160 gameInfo.variant != VariantLoadable
8161 /* [HGM] also send variant if board size non-standard */
8162 || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
8164 char *v = VariantName(gameInfo.variant);
8165 if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
8166 /* [HGM] in protocol 1 we have to assume all variants valid */
8167 sprintf(buf, _("Variant %s not supported by %s"), v, cps->tidy);
8168 DisplayFatalError(buf, 0, 1);
8172 /* [HGM] make prefix for non-standard board size. Awkward testing... */
8173 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8174 if( gameInfo.variant == VariantXiangqi )
8175 overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
8176 if( gameInfo.variant == VariantShogi )
8177 overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
8178 if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
8179 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
8180 if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
8181 gameInfo.variant == VariantGothic || gameInfo.variant == VariantFalcon )
8182 overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8183 if( gameInfo.variant == VariantCourier )
8184 overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8185 if( gameInfo.variant == VariantSuper )
8186 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
8187 if( gameInfo.variant == VariantGreat )
8188 overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
8191 sprintf(b, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
8192 gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
8193 /* [HGM] varsize: try first if this defiant size variant is specifically known */
8194 if(StrStr(cps->variants, b) == NULL) {
8195 // specific sized variant not known, check if general sizing allowed
8196 if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
8197 if(StrStr(cps->variants, "boardsize") == NULL) {
8198 sprintf(buf, "Board size %dx%d+%d not supported by %s",
8199 gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
8200 DisplayFatalError(buf, 0, 1);
8203 /* [HGM] here we really should compare with the maximum supported board size */
8206 } else sprintf(b, "%s", VariantName(gameInfo.variant));
8207 sprintf(buf, "variant %s\n", b);
8208 SendToProgram(buf, cps);
8210 currentlyInitializedVariant = gameInfo.variant;
8212 /* [HGM] send opening position in FRC to first engine */
8214 SendToProgram("force\n", cps);
8216 /* engine is now in force mode! Set flag to wake it up after first move. */
8217 setboardSpoiledMachineBlack = 1;
8221 snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
8222 SendToProgram(buf, cps);
8224 cps->maybeThinking = FALSE;
8225 cps->offeredDraw = 0;
8226 if (!appData.icsActive) {
8227 SendTimeControl(cps, movesPerSession, timeControl,
8228 timeIncrement, appData.searchDepth,
8231 if (appData.showThinking
8232 // [HGM] thinking: four options require thinking output to be sent
8233 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
8235 SendToProgram("post\n", cps);
8237 SendToProgram("hard\n", cps);
8238 if (!appData.ponderNextMove) {
8239 /* Warning: "easy" is a toggle in GNU Chess, so don't send
8240 it without being sure what state we are in first. "hard"
8241 is not a toggle, so that one is OK.
8243 SendToProgram("easy\n", cps);
8246 sprintf(buf, "ping %d\n", ++cps->lastPing);
8247 SendToProgram(buf, cps);
8249 cps->initDone = TRUE;
8254 StartChessProgram(cps)
8255 ChessProgramState *cps;
8260 if (appData.noChessProgram) return;
8261 cps->initDone = FALSE;
8263 if (strcmp(cps->host, "localhost") == 0) {
8264 err = StartChildProcess(cps->program, cps->dir, &cps->pr);
8265 } else if (*appData.remoteShell == NULLCHAR) {
8266 err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
8268 if (*appData.remoteUser == NULLCHAR) {
8269 snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
8272 snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
8273 cps->host, appData.remoteUser, cps->program);
8275 err = StartChildProcess(buf, "", &cps->pr);
8279 sprintf(buf, _("Startup failure on '%s'"), cps->program);
8280 DisplayFatalError(buf, err, 1);
8286 cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
8287 if (cps->protocolVersion > 1) {
8288 sprintf(buf, "xboard\nprotover %d\n", cps->protocolVersion);
8289 cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
8290 cps->comboCnt = 0; // and values of combo boxes
8291 SendToProgram(buf, cps);
8293 SendToProgram("xboard\n", cps);
8299 TwoMachinesEventIfReady P((void))
8301 if (first.lastPing != first.lastPong) {
8302 DisplayMessage("", _("Waiting for first chess program"));
8303 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
8306 if (second.lastPing != second.lastPong) {
8307 DisplayMessage("", _("Waiting for second chess program"));
8308 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
8316 NextMatchGame P((void))
8318 int index; /* [HGM] autoinc: step load index during match */
8320 if (*appData.loadGameFile != NULLCHAR) {
8321 index = appData.loadGameIndex;
8322 if(index < 0) { // [HGM] autoinc
8323 lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
8324 if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
8326 LoadGameFromFile(appData.loadGameFile,
8328 appData.loadGameFile, FALSE);
8329 } else if (*appData.loadPositionFile != NULLCHAR) {
8330 index = appData.loadPositionIndex;
8331 if(index < 0) { // [HGM] autoinc
8332 lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
8333 if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
8335 LoadPositionFromFile(appData.loadPositionFile,
8337 appData.loadPositionFile);
8339 TwoMachinesEventIfReady();
8342 void UserAdjudicationEvent( int result )
8344 ChessMove gameResult = GameIsDrawn;
8347 gameResult = WhiteWins;
8349 else if( result < 0 ) {
8350 gameResult = BlackWins;
8353 if( gameMode == TwoMachinesPlay ) {
8354 GameEnds( gameResult, "User adjudication", GE_XBOARD );
8359 // [HGM] save: calculate checksum of game to make games easily identifiable
8360 int StringCheckSum(char *s)
8363 if(s==NULL) return 0;
8364 while(*s) i = i*259 + *s++;
8371 for(i=backwardMostMove; i<forwardMostMove; i++) {
8372 sum += pvInfoList[i].depth;
8373 sum += StringCheckSum(parseList[i]);
8374 sum += StringCheckSum(commentList[i]);
8377 if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
8378 return sum + StringCheckSum(commentList[i]);
8379 } // end of save patch
8382 GameEnds(result, resultDetails, whosays)
8384 char *resultDetails;
8387 GameMode nextGameMode;
8391 if(endingGame) return; /* [HGM] crash: forbid recursion */
8394 if (appData.debugMode) {
8395 fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
8396 result, resultDetails ? resultDetails : "(null)", whosays);
8399 if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
8400 /* If we are playing on ICS, the server decides when the
8401 game is over, but the engine can offer to draw, claim
8405 if (appData.zippyPlay && first.initDone) {
8406 if (result == GameIsDrawn) {
8407 /* In case draw still needs to be claimed */
8408 SendToICS(ics_prefix);
8409 SendToICS("draw\n");
8410 } else if (StrCaseStr(resultDetails, "resign")) {
8411 SendToICS(ics_prefix);
8412 SendToICS("resign\n");
8416 endingGame = 0; /* [HGM] crash */
8420 /* If we're loading the game from a file, stop */
8421 if (whosays == GE_FILE) {
8422 (void) StopLoadGameTimer();
8426 /* Cancel draw offers */
8427 first.offeredDraw = second.offeredDraw = 0;
8429 /* If this is an ICS game, only ICS can really say it's done;
8430 if not, anyone can. */
8431 isIcsGame = (gameMode == IcsPlayingWhite ||
8432 gameMode == IcsPlayingBlack ||
8433 gameMode == IcsObserving ||
8434 gameMode == IcsExamining);
8436 if (!isIcsGame || whosays == GE_ICS) {
8437 /* OK -- not an ICS game, or ICS said it was done */
8439 if (!isIcsGame && !appData.noChessProgram)
8440 SetUserThinkingEnables();
8442 /* [HGM] if a machine claims the game end we verify this claim */
8443 if(gameMode == TwoMachinesPlay && appData.testClaims) {
8444 if(appData.testLegality && whosays >= GE_ENGINE1 ) {
8446 ChessMove trueResult = (ChessMove) -1;
8448 claimer = whosays == GE_ENGINE1 ? /* color of claimer */
8449 first.twoMachinesColor[0] :
8450 second.twoMachinesColor[0] ;
8452 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
8453 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
8454 /* [HGM] verify: engine mate claims accepted if they were flagged */
8455 trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
8457 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
8458 /* [HGM] verify: engine mate claims accepted if they were flagged */
8459 trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8461 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
8462 trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
8465 // now verify win claims, but not in drop games, as we don't understand those yet
8466 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
8467 || gameInfo.variant == VariantGreat) &&
8468 (result == WhiteWins && claimer == 'w' ||
8469 result == BlackWins && claimer == 'b' ) ) { // case to verify: engine claims own win
8470 if (appData.debugMode) {
8471 fprintf(debugFP, "result=%d sp=%d move=%d\n",
8472 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
8474 if(result != trueResult) {
8475 sprintf(buf, "False win claim: '%s'", resultDetails);
8476 result = claimer == 'w' ? BlackWins : WhiteWins;
8477 resultDetails = buf;
8480 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
8481 && (forwardMostMove <= backwardMostMove ||
8482 (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
8483 (claimer=='b')==(forwardMostMove&1))
8485 /* [HGM] verify: draws that were not flagged are false claims */
8486 sprintf(buf, "False draw claim: '%s'", resultDetails);
8487 result = claimer == 'w' ? BlackWins : WhiteWins;
8488 resultDetails = buf;
8490 /* (Claiming a loss is accepted no questions asked!) */
8492 /* [HGM] bare: don't allow bare King to win */
8493 if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
8494 && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
8495 && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
8496 && result != GameIsDrawn)
8497 { int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
8498 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
8499 int p = (signed char)boards[forwardMostMove][i][j] - color;
8500 if(p >= 0 && p <= (int)WhiteKing) k++;
8502 if (appData.debugMode) {
8503 fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
8504 result, resultDetails ? resultDetails : "(null)", whosays, k, color);
8507 result = GameIsDrawn;
8508 sprintf(buf, "%s but bare king", resultDetails);
8509 resultDetails = buf;
8515 if(serverMoves != NULL && !loadFlag) { char c = '=';
8516 if(result==WhiteWins) c = '+';
8517 if(result==BlackWins) c = '-';
8518 if(resultDetails != NULL)
8519 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
8521 if (resultDetails != NULL) {
8522 gameInfo.result = result;
8523 gameInfo.resultDetails = StrSave(resultDetails);
8525 /* display last move only if game was not loaded from file */
8526 if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
8527 DisplayMove(currentMove - 1);
8529 if (forwardMostMove != 0) {
8530 if (gameMode != PlayFromGameFile && gameMode != EditGame
8531 && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
8533 if (*appData.saveGameFile != NULLCHAR) {
8534 SaveGameToFile(appData.saveGameFile, TRUE);
8535 } else if (appData.autoSaveGames) {
8538 if (*appData.savePositionFile != NULLCHAR) {
8539 SavePositionToFile(appData.savePositionFile);
8544 /* Tell program how game ended in case it is learning */
8545 /* [HGM] Moved this to after saving the PGN, just in case */
8546 /* engine died and we got here through time loss. In that */
8547 /* case we will get a fatal error writing the pipe, which */
8548 /* would otherwise lose us the PGN. */
8549 /* [HGM] crash: not needed anymore, but doesn't hurt; */
8550 /* output during GameEnds should never be fatal anymore */
8551 if (gameMode == MachinePlaysWhite ||
8552 gameMode == MachinePlaysBlack ||
8553 gameMode == TwoMachinesPlay ||
8554 gameMode == IcsPlayingWhite ||
8555 gameMode == IcsPlayingBlack ||
8556 gameMode == BeginningOfGame) {
8558 sprintf(buf, "result %s {%s}\n", PGNResult(result),
8560 if (first.pr != NoProc) {
8561 SendToProgram(buf, &first);
8563 if (second.pr != NoProc &&
8564 gameMode == TwoMachinesPlay) {
8565 SendToProgram(buf, &second);
8570 if (appData.icsActive) {
8571 if (appData.quietPlay &&
8572 (gameMode == IcsPlayingWhite ||
8573 gameMode == IcsPlayingBlack)) {
8574 SendToICS(ics_prefix);
8575 SendToICS("set shout 1\n");
8577 nextGameMode = IcsIdle;
8578 ics_user_moved = FALSE;
8579 /* clean up premove. It's ugly when the game has ended and the
8580 * premove highlights are still on the board.
8584 ClearPremoveHighlights();
8585 DrawPosition(FALSE, boards[currentMove]);
8587 if (whosays == GE_ICS) {
8590 if (gameMode == IcsPlayingWhite)
8592 else if(gameMode == IcsPlayingBlack)
8596 if (gameMode == IcsPlayingBlack)
8598 else if(gameMode == IcsPlayingWhite)
8605 PlayIcsUnfinishedSound();
8608 } else if (gameMode == EditGame ||
8609 gameMode == PlayFromGameFile ||
8610 gameMode == AnalyzeMode ||
8611 gameMode == AnalyzeFile) {
8612 nextGameMode = gameMode;
8614 nextGameMode = EndOfGame;
8619 nextGameMode = gameMode;
8622 if (appData.noChessProgram) {
8623 gameMode = nextGameMode;
8625 endingGame = 0; /* [HGM] crash */
8630 /* Put first chess program into idle state */
8631 if (first.pr != NoProc &&
8632 (gameMode == MachinePlaysWhite ||
8633 gameMode == MachinePlaysBlack ||
8634 gameMode == TwoMachinesPlay ||
8635 gameMode == IcsPlayingWhite ||
8636 gameMode == IcsPlayingBlack ||
8637 gameMode == BeginningOfGame)) {
8638 SendToProgram("force\n", &first);
8639 if (first.usePing) {
8641 sprintf(buf, "ping %d\n", ++first.lastPing);
8642 SendToProgram(buf, &first);
8645 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
8646 /* Kill off first chess program */
8647 if (first.isr != NULL)
8648 RemoveInputSource(first.isr);
8651 if (first.pr != NoProc) {
8653 DoSleep( appData.delayBeforeQuit );
8654 SendToProgram("quit\n", &first);
8655 DoSleep( appData.delayAfterQuit );
8656 DestroyChildProcess(first.pr, first.useSigterm);
8661 /* Put second chess program into idle state */
8662 if (second.pr != NoProc &&
8663 gameMode == TwoMachinesPlay) {
8664 SendToProgram("force\n", &second);
8665 if (second.usePing) {
8667 sprintf(buf, "ping %d\n", ++second.lastPing);
8668 SendToProgram(buf, &second);
8671 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
8672 /* Kill off second chess program */
8673 if (second.isr != NULL)
8674 RemoveInputSource(second.isr);
8677 if (second.pr != NoProc) {
8678 DoSleep( appData.delayBeforeQuit );
8679 SendToProgram("quit\n", &second);
8680 DoSleep( appData.delayAfterQuit );
8681 DestroyChildProcess(second.pr, second.useSigterm);
8686 if (matchMode && gameMode == TwoMachinesPlay) {
8689 if (first.twoMachinesColor[0] == 'w') {
8696 if (first.twoMachinesColor[0] == 'b') {
8705 if (matchGame < appData.matchGames) {
8707 if(appData.sameColorGames <= 1) { /* [HGM] alternate: suppress color swap */
8708 tmp = first.twoMachinesColor;
8709 first.twoMachinesColor = second.twoMachinesColor;
8710 second.twoMachinesColor = tmp;
8712 gameMode = nextGameMode;
8714 if(appData.matchPause>10000 || appData.matchPause<10)
8715 appData.matchPause = 10000; /* [HGM] make pause adjustable */
8716 ScheduleDelayedEvent(NextMatchGame, appData.matchPause);
8717 endingGame = 0; /* [HGM] crash */
8721 gameMode = nextGameMode;
8722 sprintf(buf, _("Match %s vs. %s: final score %d-%d-%d"),
8723 first.tidy, second.tidy,
8724 first.matchWins, second.matchWins,
8725 appData.matchGames - (first.matchWins + second.matchWins));
8726 DisplayFatalError(buf, 0, 0);
8729 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
8730 !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
8732 gameMode = nextGameMode;
8734 endingGame = 0; /* [HGM] crash */
8737 /* Assumes program was just initialized (initString sent).
8738 Leaves program in force mode. */
8740 FeedMovesToProgram(cps, upto)
8741 ChessProgramState *cps;
8746 if (appData.debugMode)
8747 fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
8748 startedFromSetupPosition ? "position and " : "",
8749 backwardMostMove, upto, cps->which);
8750 if(currentlyInitializedVariant != gameInfo.variant) { char buf[MSG_SIZ];
8751 // [HGM] variantswitch: make engine aware of new variant
8752 if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
8753 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
8754 sprintf(buf, "variant %s\n", VariantName(gameInfo.variant));
8755 SendToProgram(buf, cps);
8756 currentlyInitializedVariant = gameInfo.variant;
8758 SendToProgram("force\n", cps);
8759 if (startedFromSetupPosition) {
8760 SendBoard(cps, backwardMostMove);
8761 if (appData.debugMode) {
8762 fprintf(debugFP, "feedMoves\n");
8765 for (i = backwardMostMove; i < upto; i++) {
8766 SendMoveToProgram(i, cps);
8772 ResurrectChessProgram()
8774 /* The chess program may have exited.
8775 If so, restart it and feed it all the moves made so far. */
8777 if (appData.noChessProgram || first.pr != NoProc) return;
8779 StartChessProgram(&first);
8780 InitChessProgram(&first, FALSE);
8781 FeedMovesToProgram(&first, currentMove);
8783 if (!first.sendTime) {
8784 /* can't tell gnuchess what its clock should read,
8785 so we bow to its notion. */
8787 timeRemaining[0][currentMove] = whiteTimeRemaining;
8788 timeRemaining[1][currentMove] = blackTimeRemaining;
8791 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
8792 appData.icsEngineAnalyze) && first.analysisSupport) {
8793 SendToProgram("analyze\n", &first);
8794 first.analyzing = TRUE;
8807 if (appData.debugMode) {
8808 fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
8809 redraw, init, gameMode);
8811 CleanupTail(); // [HGM] vari: delete any stored variations
8812 pausing = pauseExamInvalid = FALSE;
8813 startedFromSetupPosition = blackPlaysFirst = FALSE;
8815 whiteFlag = blackFlag = FALSE;
8816 userOfferedDraw = FALSE;
8817 hintRequested = bookRequested = FALSE;
8818 first.maybeThinking = FALSE;
8819 second.maybeThinking = FALSE;
8820 first.bookSuspend = FALSE; // [HGM] book
8821 second.bookSuspend = FALSE;
8822 thinkOutput[0] = NULLCHAR;
8823 lastHint[0] = NULLCHAR;
8824 ClearGameInfo(&gameInfo);
8825 gameInfo.variant = StringToVariant(appData.variant);
8826 ics_user_moved = ics_clock_paused = FALSE;
8827 ics_getting_history = H_FALSE;
8829 white_holding[0] = black_holding[0] = NULLCHAR;
8830 ClearProgramStats();
8831 opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
8835 flipView = appData.flipView;
8836 ClearPremoveHighlights();
8838 alarmSounded = FALSE;
8840 GameEnds((ChessMove) 0, NULL, GE_PLAYER);
8841 if(appData.serverMovesName != NULL) {
8842 /* [HGM] prepare to make moves file for broadcasting */
8843 clock_t t = clock();
8844 if(serverMoves != NULL) fclose(serverMoves);
8845 serverMoves = fopen(appData.serverMovesName, "r");
8846 if(serverMoves != NULL) {
8847 fclose(serverMoves);
8848 /* delay 15 sec before overwriting, so all clients can see end */
8849 while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
8851 serverMoves = fopen(appData.serverMovesName, "w");
8855 gameMode = BeginningOfGame;
8857 if(appData.icsActive) gameInfo.variant = VariantNormal;
8858 currentMove = forwardMostMove = backwardMostMove = 0;
8859 InitPosition(redraw);
8860 for (i = 0; i < MAX_MOVES; i++) {
8861 if (commentList[i] != NULL) {
8862 free(commentList[i]);
8863 commentList[i] = NULL;
8867 timeRemaining[0][0] = whiteTimeRemaining;
8868 timeRemaining[1][0] = blackTimeRemaining;
8869 if (first.pr == NULL) {
8870 StartChessProgram(&first);
8873 InitChessProgram(&first, startedFromSetupPosition);
8876 DisplayMessage("", "");
8877 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
8878 lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
8885 if (!AutoPlayOneMove())
8887 if (matchMode || appData.timeDelay == 0)
8889 if (appData.timeDelay < 0 || gameMode == AnalyzeFile)
8891 StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
8900 int fromX, fromY, toX, toY;
8902 if (appData.debugMode) {
8903 fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
8906 if (gameMode != PlayFromGameFile)
8909 if (currentMove >= forwardMostMove) {
8910 gameMode = EditGame;
8913 /* [AS] Clear current move marker at the end of a game */
8914 /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
8919 toX = moveList[currentMove][2] - AAA;
8920 toY = moveList[currentMove][3] - ONE;
8922 if (moveList[currentMove][1] == '@') {
8923 if (appData.highlightLastMove) {
8924 SetHighlights(-1, -1, toX, toY);
8927 fromX = moveList[currentMove][0] - AAA;
8928 fromY = moveList[currentMove][1] - ONE;
8930 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
8932 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
8934 if (appData.highlightLastMove) {
8935 SetHighlights(fromX, fromY, toX, toY);
8938 DisplayMove(currentMove);
8939 SendMoveToProgram(currentMove++, &first);
8940 DisplayBothClocks();
8941 DrawPosition(FALSE, boards[currentMove]);
8942 // [HGM] PV info: always display, routine tests if empty
8943 DisplayComment(currentMove - 1, commentList[currentMove]);
8949 LoadGameOneMove(readAhead)
8950 ChessMove readAhead;
8952 int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
8953 char promoChar = NULLCHAR;
8958 if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
8959 gameMode != AnalyzeMode && gameMode != Training) {
8964 yyboardindex = forwardMostMove;
8965 if (readAhead != (ChessMove)0) {
8966 moveType = readAhead;
8968 if (gameFileFP == NULL)
8970 moveType = (ChessMove) yylex();
8976 if (appData.debugMode)
8977 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
8980 /* append the comment but don't display it */
8981 AppendComment(currentMove, p, FALSE);
8984 case WhiteCapturesEnPassant:
8985 case BlackCapturesEnPassant:
8986 case WhitePromotionChancellor:
8987 case BlackPromotionChancellor:
8988 case WhitePromotionArchbishop:
8989 case BlackPromotionArchbishop:
8990 case WhitePromotionCentaur:
8991 case BlackPromotionCentaur:
8992 case WhitePromotionQueen:
8993 case BlackPromotionQueen:
8994 case WhitePromotionRook:
8995 case BlackPromotionRook:
8996 case WhitePromotionBishop:
8997 case BlackPromotionBishop:
8998 case WhitePromotionKnight:
8999 case BlackPromotionKnight:
9000 case WhitePromotionKing:
9001 case BlackPromotionKing:
9003 case WhiteKingSideCastle:
9004 case WhiteQueenSideCastle:
9005 case BlackKingSideCastle:
9006 case BlackQueenSideCastle:
9007 case WhiteKingSideCastleWild:
9008 case WhiteQueenSideCastleWild:
9009 case BlackKingSideCastleWild:
9010 case BlackQueenSideCastleWild:
9012 case WhiteHSideCastleFR:
9013 case WhiteASideCastleFR:
9014 case BlackHSideCastleFR:
9015 case BlackASideCastleFR:
9017 if (appData.debugMode)
9018 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
9019 fromX = currentMoveString[0] - AAA;
9020 fromY = currentMoveString[1] - ONE;
9021 toX = currentMoveString[2] - AAA;
9022 toY = currentMoveString[3] - ONE;
9023 promoChar = currentMoveString[4];
9028 if (appData.debugMode)
9029 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
9030 fromX = moveType == WhiteDrop ?
9031 (int) CharToPiece(ToUpper(currentMoveString[0])) :
9032 (int) CharToPiece(ToLower(currentMoveString[0]));
9034 toX = currentMoveString[2] - AAA;
9035 toY = currentMoveString[3] - ONE;
9041 case GameUnfinished:
9042 if (appData.debugMode)
9043 fprintf(debugFP, "Parsed game end: %s\n", yy_text);
9044 p = strchr(yy_text, '{');
9045 if (p == NULL) p = strchr(yy_text, '(');
9048 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
9050 q = strchr(p, *p == '{' ? '}' : ')');
9051 if (q != NULL) *q = NULLCHAR;
9054 GameEnds(moveType, p, GE_FILE);
9056 if (cmailMsgLoaded) {
9058 flipView = WhiteOnMove(currentMove);
9059 if (moveType == GameUnfinished) flipView = !flipView;
9060 if (appData.debugMode)
9061 fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
9065 case (ChessMove) 0: /* end of file */
9066 if (appData.debugMode)
9067 fprintf(debugFP, "Parser hit end of file\n");
9068 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9074 if (WhiteOnMove(currentMove)) {
9075 GameEnds(BlackWins, "Black mates", GE_FILE);
9077 GameEnds(WhiteWins, "White mates", GE_FILE);
9081 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
9088 if (lastLoadGameStart == GNUChessGame) {
9089 /* GNUChessGames have numbers, but they aren't move numbers */
9090 if (appData.debugMode)
9091 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
9092 yy_text, (int) moveType);
9093 return LoadGameOneMove((ChessMove)0); /* tail recursion */
9095 /* else fall thru */
9100 /* Reached start of next game in file */
9101 if (appData.debugMode)
9102 fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
9103 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9109 if (WhiteOnMove(currentMove)) {
9110 GameEnds(BlackWins, "Black mates", GE_FILE);
9112 GameEnds(WhiteWins, "White mates", GE_FILE);
9116 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
9122 case PositionDiagram: /* should not happen; ignore */
9123 case ElapsedTime: /* ignore */
9124 case NAG: /* ignore */
9125 if (appData.debugMode)
9126 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
9127 yy_text, (int) moveType);
9128 return LoadGameOneMove((ChessMove)0); /* tail recursion */
9131 if (appData.testLegality) {
9132 if (appData.debugMode)
9133 fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
9134 sprintf(move, _("Illegal move: %d.%s%s"),
9135 (forwardMostMove / 2) + 1,
9136 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9137 DisplayError(move, 0);
9140 if (appData.debugMode)
9141 fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
9142 yy_text, currentMoveString);
9143 fromX = currentMoveString[0] - AAA;
9144 fromY = currentMoveString[1] - ONE;
9145 toX = currentMoveString[2] - AAA;
9146 toY = currentMoveString[3] - ONE;
9147 promoChar = currentMoveString[4];
9152 if (appData.debugMode)
9153 fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
9154 sprintf(move, _("Ambiguous move: %d.%s%s"),
9155 (forwardMostMove / 2) + 1,
9156 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9157 DisplayError(move, 0);
9162 case ImpossibleMove:
9163 if (appData.debugMode)
9164 fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
9165 sprintf(move, _("Illegal move: %d.%s%s"),
9166 (forwardMostMove / 2) + 1,
9167 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9168 DisplayError(move, 0);
9174 if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
9175 DrawPosition(FALSE, boards[currentMove]);
9176 DisplayBothClocks();
9177 if (!appData.matchMode) // [HGM] PV info: routine tests if empty
9178 DisplayComment(currentMove - 1, commentList[currentMove]);
9180 (void) StopLoadGameTimer();
9182 cmailOldMove = forwardMostMove;
9185 /* currentMoveString is set as a side-effect of yylex */
9186 strcat(currentMoveString, "\n");
9187 strcpy(moveList[forwardMostMove], currentMoveString);
9189 thinkOutput[0] = NULLCHAR;
9190 MakeMove(fromX, fromY, toX, toY, promoChar);
9191 currentMove = forwardMostMove;
9196 /* Load the nth game from the given file */
9198 LoadGameFromFile(filename, n, title, useList)
9202 /*Boolean*/ int useList;
9207 if (strcmp(filename, "-") == 0) {
9211 f = fopen(filename, "rb");
9213 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9214 DisplayError(buf, errno);
9218 if (fseek(f, 0, 0) == -1) {
9219 /* f is not seekable; probably a pipe */
9222 if (useList && n == 0) {
9223 int error = GameListBuild(f);
9225 DisplayError(_("Cannot build game list"), error);
9226 } else if (!ListEmpty(&gameList) &&
9227 ((ListGame *) gameList.tailPred)->number > 1) {
9228 GameListPopUp(f, title);
9235 return LoadGame(f, n, title, FALSE);
9240 MakeRegisteredMove()
9242 int fromX, fromY, toX, toY;
9244 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
9245 switch (cmailMoveType[lastLoadGameNumber - 1]) {
9248 if (appData.debugMode)
9249 fprintf(debugFP, "Restoring %s for game %d\n",
9250 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
9252 thinkOutput[0] = NULLCHAR;
9253 strcpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1]);
9254 fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
9255 fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
9256 toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
9257 toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
9258 promoChar = cmailMove[lastLoadGameNumber - 1][4];
9259 MakeMove(fromX, fromY, toX, toY, promoChar);
9260 ShowMove(fromX, fromY, toX, toY);
9262 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9269 if (WhiteOnMove(currentMove)) {
9270 GameEnds(BlackWins, "Black mates", GE_PLAYER);
9272 GameEnds(WhiteWins, "White mates", GE_PLAYER);
9277 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
9284 if (WhiteOnMove(currentMove)) {
9285 GameEnds(BlackWins, "White resigns", GE_PLAYER);
9287 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
9292 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
9303 /* Wrapper around LoadGame for use when a Cmail message is loaded */
9305 CmailLoadGame(f, gameNumber, title, useList)
9313 if (gameNumber > nCmailGames) {
9314 DisplayError(_("No more games in this message"), 0);
9317 if (f == lastLoadGameFP) {
9318 int offset = gameNumber - lastLoadGameNumber;
9320 cmailMsg[0] = NULLCHAR;
9321 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
9322 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
9323 nCmailMovesRegistered--;
9325 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
9326 if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
9327 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
9330 if (! RegisterMove()) return FALSE;
9334 retVal = LoadGame(f, gameNumber, title, useList);
9336 /* Make move registered during previous look at this game, if any */
9337 MakeRegisteredMove();
9339 if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
9340 commentList[currentMove]
9341 = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
9342 DisplayComment(currentMove - 1, commentList[currentMove]);
9348 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
9353 int gameNumber = lastLoadGameNumber + offset;
9354 if (lastLoadGameFP == NULL) {
9355 DisplayError(_("No game has been loaded yet"), 0);
9358 if (gameNumber <= 0) {
9359 DisplayError(_("Can't back up any further"), 0);
9362 if (cmailMsgLoaded) {
9363 return CmailLoadGame(lastLoadGameFP, gameNumber,
9364 lastLoadGameTitle, lastLoadGameUseList);
9366 return LoadGame(lastLoadGameFP, gameNumber,
9367 lastLoadGameTitle, lastLoadGameUseList);
9373 /* Load the nth game from open file f */
9375 LoadGame(f, gameNumber, title, useList)
9383 int gn = gameNumber;
9384 ListGame *lg = NULL;
9387 GameMode oldGameMode;
9388 VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
9390 if (appData.debugMode)
9391 fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
9393 if (gameMode == Training )
9394 SetTrainingModeOff();
9396 oldGameMode = gameMode;
9397 if (gameMode != BeginningOfGame) {
9402 if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
9403 fclose(lastLoadGameFP);
9407 lg = (ListGame *) ListElem(&gameList, gameNumber-1);
9410 fseek(f, lg->offset, 0);
9411 GameListHighlight(gameNumber);
9415 DisplayError(_("Game number out of range"), 0);
9420 if (fseek(f, 0, 0) == -1) {
9421 if (f == lastLoadGameFP ?
9422 gameNumber == lastLoadGameNumber + 1 :
9426 DisplayError(_("Can't seek on game file"), 0);
9432 lastLoadGameNumber = gameNumber;
9433 strcpy(lastLoadGameTitle, title);
9434 lastLoadGameUseList = useList;
9438 if (lg && lg->gameInfo.white && lg->gameInfo.black) {
9439 snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
9440 lg->gameInfo.black);
9442 } else if (*title != NULLCHAR) {
9443 if (gameNumber > 1) {
9444 sprintf(buf, "%s %d", title, gameNumber);
9447 DisplayTitle(title);
9451 if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
9452 gameMode = PlayFromGameFile;
9456 currentMove = forwardMostMove = backwardMostMove = 0;
9457 CopyBoard(boards[0], initialPosition);
9461 * Skip the first gn-1 games in the file.
9462 * Also skip over anything that precedes an identifiable
9463 * start of game marker, to avoid being confused by
9464 * garbage at the start of the file. Currently
9465 * recognized start of game markers are the move number "1",
9466 * the pattern "gnuchess .* game", the pattern
9467 * "^[#;%] [^ ]* game file", and a PGN tag block.
9468 * A game that starts with one of the latter two patterns
9469 * will also have a move number 1, possibly
9470 * following a position diagram.
9471 * 5-4-02: Let's try being more lenient and allowing a game to
9472 * start with an unnumbered move. Does that break anything?
9474 cm = lastLoadGameStart = (ChessMove) 0;
9476 yyboardindex = forwardMostMove;
9477 cm = (ChessMove) yylex();
9480 if (cmailMsgLoaded) {
9481 nCmailGames = CMAIL_MAX_GAMES - gn;
9484 DisplayError(_("Game not found in file"), 0);
9491 lastLoadGameStart = cm;
9495 switch (lastLoadGameStart) {
9502 gn--; /* count this game */
9503 lastLoadGameStart = cm;
9512 switch (lastLoadGameStart) {
9517 gn--; /* count this game */
9518 lastLoadGameStart = cm;
9521 lastLoadGameStart = cm; /* game counted already */
9529 yyboardindex = forwardMostMove;
9530 cm = (ChessMove) yylex();
9531 } while (cm == PGNTag || cm == Comment);
9538 if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
9539 if ( cmailResult[CMAIL_MAX_GAMES - gn - 1]
9540 != CMAIL_OLD_RESULT) {
9542 cmailResult[ CMAIL_MAX_GAMES
9543 - gn - 1] = CMAIL_OLD_RESULT;
9549 /* Only a NormalMove can be at the start of a game
9550 * without a position diagram. */
9551 if (lastLoadGameStart == (ChessMove) 0) {
9553 lastLoadGameStart = MoveNumberOne;
9562 if (appData.debugMode)
9563 fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
9565 if (cm == XBoardGame) {
9566 /* Skip any header junk before position diagram and/or move 1 */
9568 yyboardindex = forwardMostMove;
9569 cm = (ChessMove) yylex();
9571 if (cm == (ChessMove) 0 ||
9572 cm == GNUChessGame || cm == XBoardGame) {
9573 /* Empty game; pretend end-of-file and handle later */
9578 if (cm == MoveNumberOne || cm == PositionDiagram ||
9579 cm == PGNTag || cm == Comment)
9582 } else if (cm == GNUChessGame) {
9583 if (gameInfo.event != NULL) {
9584 free(gameInfo.event);
9586 gameInfo.event = StrSave(yy_text);
9589 startedFromSetupPosition = FALSE;
9590 while (cm == PGNTag) {
9591 if (appData.debugMode)
9592 fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
9593 err = ParsePGNTag(yy_text, &gameInfo);
9594 if (!err) numPGNTags++;
9596 /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
9597 if(gameInfo.variant != oldVariant) {
9598 startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
9600 oldVariant = gameInfo.variant;
9601 if (appData.debugMode)
9602 fprintf(debugFP, "New variant %d\n", (int) oldVariant);
9606 if (gameInfo.fen != NULL) {
9607 Board initial_position;
9608 startedFromSetupPosition = TRUE;
9609 if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
9611 DisplayError(_("Bad FEN position in file"), 0);
9614 CopyBoard(boards[0], initial_position);
9615 if (blackPlaysFirst) {
9616 currentMove = forwardMostMove = backwardMostMove = 1;
9617 CopyBoard(boards[1], initial_position);
9618 strcpy(moveList[0], "");
9619 strcpy(parseList[0], "");
9620 timeRemaining[0][1] = whiteTimeRemaining;
9621 timeRemaining[1][1] = blackTimeRemaining;
9622 if (commentList[0] != NULL) {
9623 commentList[1] = commentList[0];
9624 commentList[0] = NULL;
9627 currentMove = forwardMostMove = backwardMostMove = 0;
9629 /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
9631 initialRulePlies = FENrulePlies;
9632 for( i=0; i< nrCastlingRights; i++ )
9633 initialRights[i] = initial_position[CASTLING][i];
9635 yyboardindex = forwardMostMove;
9637 gameInfo.fen = NULL;
9640 yyboardindex = forwardMostMove;
9641 cm = (ChessMove) yylex();
9643 /* Handle comments interspersed among the tags */
9644 while (cm == Comment) {
9646 if (appData.debugMode)
9647 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9649 AppendComment(currentMove, p, FALSE);
9650 yyboardindex = forwardMostMove;
9651 cm = (ChessMove) yylex();
9655 /* don't rely on existence of Event tag since if game was
9656 * pasted from clipboard the Event tag may not exist
9658 if (numPGNTags > 0){
9660 if (gameInfo.variant == VariantNormal) {
9661 gameInfo.variant = StringToVariant(gameInfo.event);
9664 if( appData.autoDisplayTags ) {
9665 tags = PGNTags(&gameInfo);
9666 TagsPopUp(tags, CmailMsg());
9671 /* Make something up, but don't display it now */
9676 if (cm == PositionDiagram) {
9679 Board initial_position;
9681 if (appData.debugMode)
9682 fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
9684 if (!startedFromSetupPosition) {
9686 for (i = BOARD_HEIGHT - 1; i >= 0; i--)
9687 for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
9697 initial_position[i][j++] = CharToPiece(*p);
9700 while (*p == ' ' || *p == '\t' ||
9701 *p == '\n' || *p == '\r') p++;
9703 if (strncmp(p, "black", strlen("black"))==0)
9704 blackPlaysFirst = TRUE;
9706 blackPlaysFirst = FALSE;
9707 startedFromSetupPosition = TRUE;
9709 CopyBoard(boards[0], initial_position);
9710 if (blackPlaysFirst) {
9711 currentMove = forwardMostMove = backwardMostMove = 1;
9712 CopyBoard(boards[1], initial_position);
9713 strcpy(moveList[0], "");
9714 strcpy(parseList[0], "");
9715 timeRemaining[0][1] = whiteTimeRemaining;
9716 timeRemaining[1][1] = blackTimeRemaining;
9717 if (commentList[0] != NULL) {
9718 commentList[1] = commentList[0];
9719 commentList[0] = NULL;
9722 currentMove = forwardMostMove = backwardMostMove = 0;
9725 yyboardindex = forwardMostMove;
9726 cm = (ChessMove) yylex();
9729 if (first.pr == NoProc) {
9730 StartChessProgram(&first);
9732 InitChessProgram(&first, FALSE);
9733 SendToProgram("force\n", &first);
9734 if (startedFromSetupPosition) {
9735 SendBoard(&first, forwardMostMove);
9736 if (appData.debugMode) {
9737 fprintf(debugFP, "Load Game\n");
9739 DisplayBothClocks();
9742 /* [HGM] server: flag to write setup moves in broadcast file as one */
9743 loadFlag = appData.suppressLoadMoves;
9745 while (cm == Comment) {
9747 if (appData.debugMode)
9748 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9750 AppendComment(currentMove, p, FALSE);
9751 yyboardindex = forwardMostMove;
9752 cm = (ChessMove) yylex();
9755 if ((cm == (ChessMove) 0 && lastLoadGameStart != (ChessMove) 0) ||
9756 cm == WhiteWins || cm == BlackWins ||
9757 cm == GameIsDrawn || cm == GameUnfinished) {
9758 DisplayMessage("", _("No moves in game"));
9759 if (cmailMsgLoaded) {
9760 if (appData.debugMode)
9761 fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
9765 DrawPosition(FALSE, boards[currentMove]);
9766 DisplayBothClocks();
9767 gameMode = EditGame;
9774 // [HGM] PV info: routine tests if comment empty
9775 if (!matchMode && (pausing || appData.timeDelay != 0)) {
9776 DisplayComment(currentMove - 1, commentList[currentMove]);
9778 if (!matchMode && appData.timeDelay != 0)
9779 DrawPosition(FALSE, boards[currentMove]);
9781 if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
9782 programStats.ok_to_send = 1;
9785 /* if the first token after the PGN tags is a move
9786 * and not move number 1, retrieve it from the parser
9788 if (cm != MoveNumberOne)
9789 LoadGameOneMove(cm);
9791 /* load the remaining moves from the file */
9792 while (LoadGameOneMove((ChessMove)0)) {
9793 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
9794 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
9797 /* rewind to the start of the game */
9798 currentMove = backwardMostMove;
9800 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
9802 if (oldGameMode == AnalyzeFile ||
9803 oldGameMode == AnalyzeMode) {
9807 if (matchMode || appData.timeDelay == 0) {
9809 gameMode = EditGame;
9811 } else if (appData.timeDelay > 0) {
9815 if (appData.debugMode)
9816 fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
9818 loadFlag = 0; /* [HGM] true game starts */
9822 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
9824 ReloadPosition(offset)
9827 int positionNumber = lastLoadPositionNumber + offset;
9828 if (lastLoadPositionFP == NULL) {
9829 DisplayError(_("No position has been loaded yet"), 0);
9832 if (positionNumber <= 0) {
9833 DisplayError(_("Can't back up any further"), 0);
9836 return LoadPosition(lastLoadPositionFP, positionNumber,
9837 lastLoadPositionTitle);
9840 /* Load the nth position from the given file */
9842 LoadPositionFromFile(filename, n, title)
9850 if (strcmp(filename, "-") == 0) {
9851 return LoadPosition(stdin, n, "stdin");
9853 f = fopen(filename, "rb");
9855 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9856 DisplayError(buf, errno);
9859 return LoadPosition(f, n, title);
9864 /* Load the nth position from the given open file, and close it */
9866 LoadPosition(f, positionNumber, title)
9871 char *p, line[MSG_SIZ];
9872 Board initial_position;
9873 int i, j, fenMode, pn;
9875 if (gameMode == Training )
9876 SetTrainingModeOff();
9878 if (gameMode != BeginningOfGame) {
9881 if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
9882 fclose(lastLoadPositionFP);
9884 if (positionNumber == 0) positionNumber = 1;
9885 lastLoadPositionFP = f;
9886 lastLoadPositionNumber = positionNumber;
9887 strcpy(lastLoadPositionTitle, title);
9888 if (first.pr == NoProc) {
9889 StartChessProgram(&first);
9890 InitChessProgram(&first, FALSE);
9892 pn = positionNumber;
9893 if (positionNumber < 0) {
9894 /* Negative position number means to seek to that byte offset */
9895 if (fseek(f, -positionNumber, 0) == -1) {
9896 DisplayError(_("Can't seek on position file"), 0);
9901 if (fseek(f, 0, 0) == -1) {
9902 if (f == lastLoadPositionFP ?
9903 positionNumber == lastLoadPositionNumber + 1 :
9904 positionNumber == 1) {
9907 DisplayError(_("Can't seek on position file"), 0);
9912 /* See if this file is FEN or old-style xboard */
9913 if (fgets(line, MSG_SIZ, f) == NULL) {
9914 DisplayError(_("Position not found in file"), 0);
9917 // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
9918 fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
9921 if (fenMode || line[0] == '#') pn--;
9923 /* skip positions before number pn */
9924 if (fgets(line, MSG_SIZ, f) == NULL) {
9926 DisplayError(_("Position not found in file"), 0);
9929 if (fenMode || line[0] == '#') pn--;
9934 if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
9935 DisplayError(_("Bad FEN position in file"), 0);
9939 (void) fgets(line, MSG_SIZ, f);
9940 (void) fgets(line, MSG_SIZ, f);
9942 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
9943 (void) fgets(line, MSG_SIZ, f);
9944 for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
9947 initial_position[i][j++] = CharToPiece(*p);
9951 blackPlaysFirst = FALSE;
9953 (void) fgets(line, MSG_SIZ, f);
9954 if (strncmp(line, "black", strlen("black"))==0)
9955 blackPlaysFirst = TRUE;
9958 startedFromSetupPosition = TRUE;
9960 SendToProgram("force\n", &first);
9961 CopyBoard(boards[0], initial_position);
9962 if (blackPlaysFirst) {
9963 currentMove = forwardMostMove = backwardMostMove = 1;
9964 strcpy(moveList[0], "");
9965 strcpy(parseList[0], "");
9966 CopyBoard(boards[1], initial_position);
9967 DisplayMessage("", _("Black to play"));
9969 currentMove = forwardMostMove = backwardMostMove = 0;
9970 DisplayMessage("", _("White to play"));
9972 initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
9973 SendBoard(&first, forwardMostMove);
9974 if (appData.debugMode) {
9976 for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
9977 for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
9978 fprintf(debugFP, "Load Position\n");
9981 if (positionNumber > 1) {
9982 sprintf(line, "%s %d", title, positionNumber);
9985 DisplayTitle(title);
9987 gameMode = EditGame;
9990 timeRemaining[0][1] = whiteTimeRemaining;
9991 timeRemaining[1][1] = blackTimeRemaining;
9992 DrawPosition(FALSE, boards[currentMove]);
9999 CopyPlayerNameIntoFileName(dest, src)
10002 while (*src != NULLCHAR && *src != ',') {
10007 *(*dest)++ = *src++;
10012 char *DefaultFileName(ext)
10015 static char def[MSG_SIZ];
10018 if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
10020 CopyPlayerNameIntoFileName(&p, gameInfo.white);
10022 CopyPlayerNameIntoFileName(&p, gameInfo.black);
10031 /* Save the current game to the given file */
10033 SaveGameToFile(filename, append)
10040 if (strcmp(filename, "-") == 0) {
10041 return SaveGame(stdout, 0, NULL);
10043 f = fopen(filename, append ? "a" : "w");
10045 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10046 DisplayError(buf, errno);
10049 return SaveGame(f, 0, NULL);
10058 static char buf[MSG_SIZ];
10061 p = strchr(str, ' ');
10062 if (p == NULL) return str;
10063 strncpy(buf, str, p - str);
10064 buf[p - str] = NULLCHAR;
10068 #define PGN_MAX_LINE 75
10070 #define PGN_SIDE_WHITE 0
10071 #define PGN_SIDE_BLACK 1
10074 static int FindFirstMoveOutOfBook( int side )
10078 if( backwardMostMove == 0 && ! startedFromSetupPosition) {
10079 int index = backwardMostMove;
10080 int has_book_hit = 0;
10082 if( (index % 2) != side ) {
10086 while( index < forwardMostMove ) {
10087 /* Check to see if engine is in book */
10088 int depth = pvInfoList[index].depth;
10089 int score = pvInfoList[index].score;
10095 else if( score == 0 && depth == 63 ) {
10096 in_book = 1; /* Zappa */
10098 else if( score == 2 && depth == 99 ) {
10099 in_book = 1; /* Abrok */
10102 has_book_hit += in_book;
10118 void GetOutOfBookInfo( char * buf )
10122 int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10124 oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
10125 oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
10129 if( oob[0] >= 0 || oob[1] >= 0 ) {
10130 for( i=0; i<2; i++ ) {
10134 if( i > 0 && oob[0] >= 0 ) {
10135 strcat( buf, " " );
10138 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
10139 sprintf( buf+strlen(buf), "%s%.2f",
10140 pvInfoList[idx].score >= 0 ? "+" : "",
10141 pvInfoList[idx].score / 100.0 );
10147 /* Save game in PGN style and close the file */
10152 int i, offset, linelen, newblock;
10156 int movelen, numlen, blank;
10157 char move_buffer[100]; /* [AS] Buffer for move+PV info */
10159 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10161 tm = time((time_t *) NULL);
10163 PrintPGNTags(f, &gameInfo);
10165 if (backwardMostMove > 0 || startedFromSetupPosition) {
10166 char *fen = PositionToFEN(backwardMostMove, NULL);
10167 fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
10168 fprintf(f, "\n{--------------\n");
10169 PrintPosition(f, backwardMostMove);
10170 fprintf(f, "--------------}\n");
10174 /* [AS] Out of book annotation */
10175 if( appData.saveOutOfBookInfo ) {
10178 GetOutOfBookInfo( buf );
10180 if( buf[0] != '\0' ) {
10181 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
10188 i = backwardMostMove;
10192 while (i < forwardMostMove) {
10193 /* Print comments preceding this move */
10194 if (commentList[i] != NULL) {
10195 if (linelen > 0) fprintf(f, "\n");
10196 fprintf(f, "%s", commentList[i]);
10201 /* Format move number */
10202 if ((i % 2) == 0) {
10203 sprintf(numtext, "%d.", (i - offset)/2 + 1);
10206 sprintf(numtext, "%d...", (i - offset)/2 + 1);
10208 numtext[0] = NULLCHAR;
10211 numlen = strlen(numtext);
10214 /* Print move number */
10215 blank = linelen > 0 && numlen > 0;
10216 if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
10225 fprintf(f, "%s", numtext);
10229 strcpy(move_buffer, SavePart(parseList[i])); // [HGM] pgn: print move via buffer, so it can be edited
10230 movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
10233 blank = linelen > 0 && movelen > 0;
10234 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
10243 fprintf(f, "%s", move_buffer);
10244 linelen += movelen;
10246 /* [AS] Add PV info if present */
10247 if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
10248 /* [HGM] add time */
10249 char buf[MSG_SIZ]; int seconds;
10251 seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
10253 if( seconds <= 0) buf[0] = 0; else
10254 if( seconds < 30 ) sprintf(buf, " %3.1f%c", seconds/10., 0); else {
10255 seconds = (seconds + 4)/10; // round to full seconds
10256 if( seconds < 60 ) sprintf(buf, " %d%c", seconds, 0); else
10257 sprintf(buf, " %d:%02d%c", seconds/60, seconds%60, 0);
10260 sprintf( move_buffer, "{%s%.2f/%d%s}",
10261 pvInfoList[i].score >= 0 ? "+" : "",
10262 pvInfoList[i].score / 100.0,
10263 pvInfoList[i].depth,
10266 movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
10268 /* Print score/depth */
10269 blank = linelen > 0 && movelen > 0;
10270 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
10279 fprintf(f, "%s", move_buffer);
10280 linelen += movelen;
10286 /* Start a new line */
10287 if (linelen > 0) fprintf(f, "\n");
10289 /* Print comments after last move */
10290 if (commentList[i] != NULL) {
10291 fprintf(f, "%s\n", commentList[i]);
10295 if (gameInfo.resultDetails != NULL &&
10296 gameInfo.resultDetails[0] != NULLCHAR) {
10297 fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
10298 PGNResult(gameInfo.result));
10300 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
10304 lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
10308 /* Save game in old style and close the file */
10310 SaveGameOldStyle(f)
10316 tm = time((time_t *) NULL);
10318 fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
10321 if (backwardMostMove > 0 || startedFromSetupPosition) {
10322 fprintf(f, "\n[--------------\n");
10323 PrintPosition(f, backwardMostMove);
10324 fprintf(f, "--------------]\n");
10329 i = backwardMostMove;
10330 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10332 while (i < forwardMostMove) {
10333 if (commentList[i] != NULL) {
10334 fprintf(f, "[%s]\n", commentList[i]);
10337 if ((i % 2) == 1) {
10338 fprintf(f, "%d. ... %s\n", (i - offset)/2 + 1, parseList[i]);
10341 fprintf(f, "%d. %s ", (i - offset)/2 + 1, parseList[i]);
10343 if (commentList[i] != NULL) {
10347 if (i >= forwardMostMove) {
10351 fprintf(f, "%s\n", parseList[i]);
10356 if (commentList[i] != NULL) {
10357 fprintf(f, "[%s]\n", commentList[i]);
10360 /* This isn't really the old style, but it's close enough */
10361 if (gameInfo.resultDetails != NULL &&
10362 gameInfo.resultDetails[0] != NULLCHAR) {
10363 fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
10364 gameInfo.resultDetails);
10366 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
10373 /* Save the current game to open file f and close the file */
10375 SaveGame(f, dummy, dummy2)
10380 if (gameMode == EditPosition) EditPositionDone(TRUE);
10381 lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
10382 if (appData.oldSaveStyle)
10383 return SaveGameOldStyle(f);
10385 return SaveGamePGN(f);
10388 /* Save the current position to the given file */
10390 SavePositionToFile(filename)
10396 if (strcmp(filename, "-") == 0) {
10397 return SavePosition(stdout, 0, NULL);
10399 f = fopen(filename, "a");
10401 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10402 DisplayError(buf, errno);
10405 SavePosition(f, 0, NULL);
10411 /* Save the current position to the given open file and close the file */
10413 SavePosition(f, dummy, dummy2)
10421 if (gameMode == EditPosition) EditPositionDone(TRUE);
10422 if (appData.oldSaveStyle) {
10423 tm = time((time_t *) NULL);
10425 fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
10427 fprintf(f, "[--------------\n");
10428 PrintPosition(f, currentMove);
10429 fprintf(f, "--------------]\n");
10431 fen = PositionToFEN(currentMove, NULL);
10432 fprintf(f, "%s\n", fen);
10440 ReloadCmailMsgEvent(unregister)
10444 static char *inFilename = NULL;
10445 static char *outFilename;
10447 struct stat inbuf, outbuf;
10450 /* Any registered moves are unregistered if unregister is set, */
10451 /* i.e. invoked by the signal handler */
10453 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
10454 cmailMoveRegistered[i] = FALSE;
10455 if (cmailCommentList[i] != NULL) {
10456 free(cmailCommentList[i]);
10457 cmailCommentList[i] = NULL;
10460 nCmailMovesRegistered = 0;
10463 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
10464 cmailResult[i] = CMAIL_NOT_RESULT;
10468 if (inFilename == NULL) {
10469 /* Because the filenames are static they only get malloced once */
10470 /* and they never get freed */
10471 inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
10472 sprintf(inFilename, "%s.game.in", appData.cmailGameName);
10474 outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
10475 sprintf(outFilename, "%s.out", appData.cmailGameName);
10478 status = stat(outFilename, &outbuf);
10480 cmailMailedMove = FALSE;
10482 status = stat(inFilename, &inbuf);
10483 cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
10486 /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
10487 counts the games, notes how each one terminated, etc.
10489 It would be nice to remove this kludge and instead gather all
10490 the information while building the game list. (And to keep it
10491 in the game list nodes instead of having a bunch of fixed-size
10492 parallel arrays.) Note this will require getting each game's
10493 termination from the PGN tags, as the game list builder does
10494 not process the game moves. --mann
10496 cmailMsgLoaded = TRUE;
10497 LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
10499 /* Load first game in the file or popup game menu */
10500 LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
10502 #endif /* !WIN32 */
10510 char string[MSG_SIZ];
10512 if ( cmailMailedMove
10513 || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
10514 return TRUE; /* Allow free viewing */
10517 /* Unregister move to ensure that we don't leave RegisterMove */
10518 /* with the move registered when the conditions for registering no */
10520 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
10521 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
10522 nCmailMovesRegistered --;
10524 if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
10526 free(cmailCommentList[lastLoadGameNumber - 1]);
10527 cmailCommentList[lastLoadGameNumber - 1] = NULL;
10531 if (cmailOldMove == -1) {
10532 DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
10536 if (currentMove > cmailOldMove + 1) {
10537 DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
10541 if (currentMove < cmailOldMove) {
10542 DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
10546 if (forwardMostMove > currentMove) {
10547 /* Silently truncate extra moves */
10551 if ( (currentMove == cmailOldMove + 1)
10552 || ( (currentMove == cmailOldMove)
10553 && ( (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
10554 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
10555 if (gameInfo.result != GameUnfinished) {
10556 cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
10559 if (commentList[currentMove] != NULL) {
10560 cmailCommentList[lastLoadGameNumber - 1]
10561 = StrSave(commentList[currentMove]);
10563 strcpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1]);
10565 if (appData.debugMode)
10566 fprintf(debugFP, "Saving %s for game %d\n",
10567 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
10570 "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
10572 f = fopen(string, "w");
10573 if (appData.oldSaveStyle) {
10574 SaveGameOldStyle(f); /* also closes the file */
10576 sprintf(string, "%s.pos.out", appData.cmailGameName);
10577 f = fopen(string, "w");
10578 SavePosition(f, 0, NULL); /* also closes the file */
10580 fprintf(f, "{--------------\n");
10581 PrintPosition(f, currentMove);
10582 fprintf(f, "--------------}\n\n");
10584 SaveGame(f, 0, NULL); /* also closes the file*/
10587 cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
10588 nCmailMovesRegistered ++;
10589 } else if (nCmailGames == 1) {
10590 DisplayError(_("You have not made a move yet"), 0);
10601 static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
10602 FILE *commandOutput;
10603 char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
10604 int nBytes = 0; /* Suppress warnings on uninitialized variables */
10610 if (! cmailMsgLoaded) {
10611 DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
10615 if (nCmailGames == nCmailResults) {
10616 DisplayError(_("No unfinished games"), 0);
10620 #if CMAIL_PROHIBIT_REMAIL
10621 if (cmailMailedMove) {
10622 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);
10623 DisplayError(msg, 0);
10628 if (! (cmailMailedMove || RegisterMove())) return;
10630 if ( cmailMailedMove
10631 || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
10632 sprintf(string, partCommandString,
10633 appData.debugMode ? " -v" : "", appData.cmailGameName);
10634 commandOutput = popen(string, "r");
10636 if (commandOutput == NULL) {
10637 DisplayError(_("Failed to invoke cmail"), 0);
10639 for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
10640 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
10642 if (nBuffers > 1) {
10643 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
10644 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
10645 nBytes = MSG_SIZ - 1;
10647 (void) memcpy(msg, buffer, nBytes);
10649 *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
10651 if(StrStr(msg, "Mailed cmail message to ") != NULL) {
10652 cmailMailedMove = TRUE; /* Prevent >1 moves */
10655 for (i = 0; i < nCmailGames; i ++) {
10656 if (cmailResult[i] == CMAIL_NOT_RESULT) {
10661 && ( (arcDir = (char *) getenv("CMAIL_ARCDIR"))
10663 sprintf(buffer, "%s/%s.%s.archive",
10665 appData.cmailGameName,
10667 LoadGameFromFile(buffer, 1, buffer, FALSE);
10668 cmailMsgLoaded = FALSE;
10672 DisplayInformation(msg);
10673 pclose(commandOutput);
10676 if ((*cmailMsg) != '\0') {
10677 DisplayInformation(cmailMsg);
10682 #endif /* !WIN32 */
10691 int prependComma = 0;
10693 char string[MSG_SIZ]; /* Space for game-list */
10696 if (!cmailMsgLoaded) return "";
10698 if (cmailMailedMove) {
10699 sprintf(cmailMsg, _("Waiting for reply from opponent\n"));
10701 /* Create a list of games left */
10702 sprintf(string, "[");
10703 for (i = 0; i < nCmailGames; i ++) {
10704 if (! ( cmailMoveRegistered[i]
10705 || (cmailResult[i] == CMAIL_OLD_RESULT))) {
10706 if (prependComma) {
10707 sprintf(number, ",%d", i + 1);
10709 sprintf(number, "%d", i + 1);
10713 strcat(string, number);
10716 strcat(string, "]");
10718 if (nCmailMovesRegistered + nCmailResults == 0) {
10719 switch (nCmailGames) {
10722 _("Still need to make move for game\n"));
10727 _("Still need to make moves for both games\n"));
10732 _("Still need to make moves for all %d games\n"),
10737 switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
10740 _("Still need to make a move for game %s\n"),
10745 if (nCmailResults == nCmailGames) {
10746 sprintf(cmailMsg, _("No unfinished games\n"));
10748 sprintf(cmailMsg, _("Ready to send mail\n"));
10754 _("Still need to make moves for games %s\n"),
10766 if (gameMode == Training)
10767 SetTrainingModeOff();
10770 cmailMsgLoaded = FALSE;
10771 if (appData.icsActive) {
10772 SendToICS(ics_prefix);
10773 SendToICS("refresh\n");
10783 /* Give up on clean exit */
10787 /* Keep trying for clean exit */
10791 if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
10793 if (telnetISR != NULL) {
10794 RemoveInputSource(telnetISR);
10796 if (icsPR != NoProc) {
10797 DestroyChildProcess(icsPR, TRUE);
10800 /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
10801 GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
10803 /* [HGM] crash: the above GameEnds() is a dud if another one was running */
10804 /* make sure this other one finishes before killing it! */
10805 if(endingGame) { int count = 0;
10806 if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
10807 while(endingGame && count++ < 10) DoSleep(1);
10808 if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
10811 /* Kill off chess programs */
10812 if (first.pr != NoProc) {
10815 DoSleep( appData.delayBeforeQuit );
10816 SendToProgram("quit\n", &first);
10817 DoSleep( appData.delayAfterQuit );
10818 DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
10820 if (second.pr != NoProc) {
10821 DoSleep( appData.delayBeforeQuit );
10822 SendToProgram("quit\n", &second);
10823 DoSleep( appData.delayAfterQuit );
10824 DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
10826 if (first.isr != NULL) {
10827 RemoveInputSource(first.isr);
10829 if (second.isr != NULL) {
10830 RemoveInputSource(second.isr);
10833 ShutDownFrontEnd();
10840 if (appData.debugMode)
10841 fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
10845 if (gameMode == MachinePlaysWhite ||
10846 gameMode == MachinePlaysBlack) {
10849 DisplayBothClocks();
10851 if (gameMode == PlayFromGameFile) {
10852 if (appData.timeDelay >= 0)
10853 AutoPlayGameLoop();
10854 } else if (gameMode == IcsExamining && pauseExamInvalid) {
10855 Reset(FALSE, TRUE);
10856 SendToICS(ics_prefix);
10857 SendToICS("refresh\n");
10858 } else if (currentMove < forwardMostMove) {
10859 ForwardInner(forwardMostMove);
10861 pauseExamInvalid = FALSE;
10863 switch (gameMode) {
10867 pauseExamForwardMostMove = forwardMostMove;
10868 pauseExamInvalid = FALSE;
10871 case IcsPlayingWhite:
10872 case IcsPlayingBlack:
10876 case PlayFromGameFile:
10877 (void) StopLoadGameTimer();
10881 case BeginningOfGame:
10882 if (appData.icsActive) return;
10883 /* else fall through */
10884 case MachinePlaysWhite:
10885 case MachinePlaysBlack:
10886 case TwoMachinesPlay:
10887 if (forwardMostMove == 0)
10888 return; /* don't pause if no one has moved */
10889 if ((gameMode == MachinePlaysWhite &&
10890 !WhiteOnMove(forwardMostMove)) ||
10891 (gameMode == MachinePlaysBlack &&
10892 WhiteOnMove(forwardMostMove))) {
10905 char title[MSG_SIZ];
10907 if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
10908 strcpy(title, _("Edit comment"));
10910 sprintf(title, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
10911 WhiteOnMove(currentMove - 1) ? " " : ".. ",
10912 parseList[currentMove - 1]);
10915 EditCommentPopUp(currentMove, title, commentList[currentMove]);
10922 char *tags = PGNTags(&gameInfo);
10923 EditTagsPopUp(tags);
10930 if (appData.noChessProgram || gameMode == AnalyzeMode)
10933 if (gameMode != AnalyzeFile) {
10934 if (!appData.icsEngineAnalyze) {
10936 if (gameMode != EditGame) return;
10938 ResurrectChessProgram();
10939 SendToProgram("analyze\n", &first);
10940 first.analyzing = TRUE;
10941 /*first.maybeThinking = TRUE;*/
10942 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
10943 EngineOutputPopUp();
10945 if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
10950 StartAnalysisClock();
10951 GetTimeMark(&lastNodeCountTime);
10958 if (appData.noChessProgram || gameMode == AnalyzeFile)
10961 if (gameMode != AnalyzeMode) {
10963 if (gameMode != EditGame) return;
10964 ResurrectChessProgram();
10965 SendToProgram("analyze\n", &first);
10966 first.analyzing = TRUE;
10967 /*first.maybeThinking = TRUE;*/
10968 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
10969 EngineOutputPopUp();
10971 gameMode = AnalyzeFile;
10976 StartAnalysisClock();
10977 GetTimeMark(&lastNodeCountTime);
10982 MachineWhiteEvent()
10985 char *bookHit = NULL;
10987 if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
10991 if (gameMode == PlayFromGameFile ||
10992 gameMode == TwoMachinesPlay ||
10993 gameMode == Training ||
10994 gameMode == AnalyzeMode ||
10995 gameMode == EndOfGame)
10998 if (gameMode == EditPosition)
10999 EditPositionDone(TRUE);
11001 if (!WhiteOnMove(currentMove)) {
11002 DisplayError(_("It is not White's turn"), 0);
11006 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
11009 if (gameMode == EditGame || gameMode == AnalyzeMode ||
11010 gameMode == AnalyzeFile)
11013 ResurrectChessProgram(); /* in case it isn't running */
11014 if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
11015 gameMode = MachinePlaysWhite;
11018 gameMode = MachinePlaysWhite;
11022 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
11024 if (first.sendName) {
11025 sprintf(buf, "name %s\n", gameInfo.black);
11026 SendToProgram(buf, &first);
11028 if (first.sendTime) {
11029 if (first.useColors) {
11030 SendToProgram("black\n", &first); /*gnu kludge*/
11032 SendTimeRemaining(&first, TRUE);
11034 if (first.useColors) {
11035 SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
11037 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
11038 SetMachineThinkingEnables();
11039 first.maybeThinking = TRUE;
11043 if (appData.autoFlipView && !flipView) {
11044 flipView = !flipView;
11045 DrawPosition(FALSE, NULL);
11046 DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;
11049 if(bookHit) { // [HGM] book: simulate book reply
11050 static char bookMove[MSG_SIZ]; // a bit generous?
11052 programStats.nodes = programStats.depth = programStats.time =
11053 programStats.score = programStats.got_only_move = 0;
11054 sprintf(programStats.movelist, "%s (xbook)", bookHit);
11056 strcpy(bookMove, "move ");
11057 strcat(bookMove, bookHit);
11058 HandleMachineMove(bookMove, &first);
11063 MachineBlackEvent()
11066 char *bookHit = NULL;
11068 if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
11072 if (gameMode == PlayFromGameFile ||
11073 gameMode == TwoMachinesPlay ||
11074 gameMode == Training ||
11075 gameMode == AnalyzeMode ||
11076 gameMode == EndOfGame)
11079 if (gameMode == EditPosition)
11080 EditPositionDone(TRUE);
11082 if (WhiteOnMove(currentMove)) {
11083 DisplayError(_("It is not Black's turn"), 0);
11087 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
11090 if (gameMode == EditGame || gameMode == AnalyzeMode ||
11091 gameMode == AnalyzeFile)
11094 ResurrectChessProgram(); /* in case it isn't running */
11095 gameMode = MachinePlaysBlack;
11099 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
11101 if (first.sendName) {
11102 sprintf(buf, "name %s\n", gameInfo.white);
11103 SendToProgram(buf, &first);
11105 if (first.sendTime) {
11106 if (first.useColors) {
11107 SendToProgram("white\n", &first); /*gnu kludge*/
11109 SendTimeRemaining(&first, FALSE);
11111 if (first.useColors) {
11112 SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
11114 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
11115 SetMachineThinkingEnables();
11116 first.maybeThinking = TRUE;
11119 if (appData.autoFlipView && flipView) {
11120 flipView = !flipView;
11121 DrawPosition(FALSE, NULL);
11122 DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;
11124 if(bookHit) { // [HGM] book: simulate book reply
11125 static char bookMove[MSG_SIZ]; // a bit generous?
11127 programStats.nodes = programStats.depth = programStats.time =
11128 programStats.score = programStats.got_only_move = 0;
11129 sprintf(programStats.movelist, "%s (xbook)", bookHit);
11131 strcpy(bookMove, "move ");
11132 strcat(bookMove, bookHit);
11133 HandleMachineMove(bookMove, &first);
11139 DisplayTwoMachinesTitle()
11142 if (appData.matchGames > 0) {
11143 if (first.twoMachinesColor[0] == 'w') {
11144 sprintf(buf, "%s vs. %s (%d-%d-%d)",
11145 gameInfo.white, gameInfo.black,
11146 first.matchWins, second.matchWins,
11147 matchGame - 1 - (first.matchWins + second.matchWins));
11149 sprintf(buf, "%s vs. %s (%d-%d-%d)",
11150 gameInfo.white, gameInfo.black,
11151 second.matchWins, first.matchWins,
11152 matchGame - 1 - (first.matchWins + second.matchWins));
11155 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
11161 TwoMachinesEvent P((void))
11165 ChessProgramState *onmove;
11166 char *bookHit = NULL;
11168 if (appData.noChessProgram) return;
11170 switch (gameMode) {
11171 case TwoMachinesPlay:
11173 case MachinePlaysWhite:
11174 case MachinePlaysBlack:
11175 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
11176 DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
11180 case BeginningOfGame:
11181 case PlayFromGameFile:
11184 if (gameMode != EditGame) return;
11187 EditPositionDone(TRUE);
11198 // forwardMostMove = currentMove;
11199 TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
11200 ResurrectChessProgram(); /* in case first program isn't running */
11202 if (second.pr == NULL) {
11203 StartChessProgram(&second);
11204 if (second.protocolVersion == 1) {
11205 TwoMachinesEventIfReady();
11207 /* kludge: allow timeout for initial "feature" command */
11209 DisplayMessage("", _("Starting second chess program"));
11210 ScheduleDelayedEvent(TwoMachinesEventIfReady, FEATURE_TIMEOUT);
11214 DisplayMessage("", "");
11215 InitChessProgram(&second, FALSE);
11216 SendToProgram("force\n", &second);
11217 if (startedFromSetupPosition) {
11218 SendBoard(&second, backwardMostMove);
11219 if (appData.debugMode) {
11220 fprintf(debugFP, "Two Machines\n");
11223 for (i = backwardMostMove; i < forwardMostMove; i++) {
11224 SendMoveToProgram(i, &second);
11227 gameMode = TwoMachinesPlay;
11231 DisplayTwoMachinesTitle();
11233 if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
11239 SendToProgram(first.computerString, &first);
11240 if (first.sendName) {
11241 sprintf(buf, "name %s\n", second.tidy);
11242 SendToProgram(buf, &first);
11244 SendToProgram(second.computerString, &second);
11245 if (second.sendName) {
11246 sprintf(buf, "name %s\n", first.tidy);
11247 SendToProgram(buf, &second);
11251 if (!first.sendTime || !second.sendTime) {
11252 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11253 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11255 if (onmove->sendTime) {
11256 if (onmove->useColors) {
11257 SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
11259 SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
11261 if (onmove->useColors) {
11262 SendToProgram(onmove->twoMachinesColor, onmove);
11264 bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
11265 // SendToProgram("go\n", onmove);
11266 onmove->maybeThinking = TRUE;
11267 SetMachineThinkingEnables();
11271 if(bookHit) { // [HGM] book: simulate book reply
11272 static char bookMove[MSG_SIZ]; // a bit generous?
11274 programStats.nodes = programStats.depth = programStats.time =
11275 programStats.score = programStats.got_only_move = 0;
11276 sprintf(programStats.movelist, "%s (xbook)", bookHit);
11278 strcpy(bookMove, "move ");
11279 strcat(bookMove, bookHit);
11280 savedMessage = bookMove; // args for deferred call
11281 savedState = onmove;
11282 ScheduleDelayedEvent(DeferredBookMove, 1);
11289 if (gameMode == Training) {
11290 SetTrainingModeOff();
11291 gameMode = PlayFromGameFile;
11292 DisplayMessage("", _("Training mode off"));
11294 gameMode = Training;
11295 animateTraining = appData.animate;
11297 /* make sure we are not already at the end of the game */
11298 if (currentMove < forwardMostMove) {
11299 SetTrainingModeOn();
11300 DisplayMessage("", _("Training mode on"));
11302 gameMode = PlayFromGameFile;
11303 DisplayError(_("Already at end of game"), 0);
11312 if (!appData.icsActive) return;
11313 switch (gameMode) {
11314 case IcsPlayingWhite:
11315 case IcsPlayingBlack:
11318 case BeginningOfGame:
11326 EditPositionDone(TRUE);
11339 gameMode = IcsIdle;
11350 switch (gameMode) {
11352 SetTrainingModeOff();
11354 case MachinePlaysWhite:
11355 case MachinePlaysBlack:
11356 case BeginningOfGame:
11357 SendToProgram("force\n", &first);
11358 SetUserThinkingEnables();
11360 case PlayFromGameFile:
11361 (void) StopLoadGameTimer();
11362 if (gameFileFP != NULL) {
11367 EditPositionDone(TRUE);
11372 SendToProgram("force\n", &first);
11374 case TwoMachinesPlay:
11375 GameEnds((ChessMove) 0, NULL, GE_PLAYER);
11376 ResurrectChessProgram();
11377 SetUserThinkingEnables();
11380 ResurrectChessProgram();
11382 case IcsPlayingBlack:
11383 case IcsPlayingWhite:
11384 DisplayError(_("Warning: You are still playing a game"), 0);
11387 DisplayError(_("Warning: You are still observing a game"), 0);
11390 DisplayError(_("Warning: You are still examining a game"), 0);
11401 first.offeredDraw = second.offeredDraw = 0;
11403 if (gameMode == PlayFromGameFile) {
11404 whiteTimeRemaining = timeRemaining[0][currentMove];
11405 blackTimeRemaining = timeRemaining[1][currentMove];
11409 if (gameMode == MachinePlaysWhite ||
11410 gameMode == MachinePlaysBlack ||
11411 gameMode == TwoMachinesPlay ||
11412 gameMode == EndOfGame) {
11413 i = forwardMostMove;
11414 while (i > currentMove) {
11415 SendToProgram("undo\n", &first);
11418 whiteTimeRemaining = timeRemaining[0][currentMove];
11419 blackTimeRemaining = timeRemaining[1][currentMove];
11420 DisplayBothClocks();
11421 if (whiteFlag || blackFlag) {
11422 whiteFlag = blackFlag = 0;
11427 gameMode = EditGame;
11434 EditPositionEvent()
11436 if (gameMode == EditPosition) {
11442 if (gameMode != EditGame) return;
11444 gameMode = EditPosition;
11447 if (currentMove > 0)
11448 CopyBoard(boards[0], boards[currentMove]);
11450 blackPlaysFirst = !WhiteOnMove(currentMove);
11452 currentMove = forwardMostMove = backwardMostMove = 0;
11453 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11460 /* [DM] icsEngineAnalyze - possible call from other functions */
11461 if (appData.icsEngineAnalyze) {
11462 appData.icsEngineAnalyze = FALSE;
11464 DisplayMessage("",_("Close ICS engine analyze..."));
11466 if (first.analysisSupport && first.analyzing) {
11467 SendToProgram("exit\n", &first);
11468 first.analyzing = FALSE;
11470 thinkOutput[0] = NULLCHAR;
11474 EditPositionDone(Boolean fakeRights)
11476 int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
11478 startedFromSetupPosition = TRUE;
11479 InitChessProgram(&first, FALSE);
11480 if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
11481 boards[0][EP_STATUS] = EP_NONE;
11482 boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
11483 if(boards[0][0][BOARD_WIDTH>>1] == king) {
11484 boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : NoRights;
11485 boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
11486 } else boards[0][CASTLING][2] = NoRights;
11487 if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
11488 boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : NoRights;
11489 boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
11490 } else boards[0][CASTLING][5] = NoRights;
11492 SendToProgram("force\n", &first);
11493 if (blackPlaysFirst) {
11494 strcpy(moveList[0], "");
11495 strcpy(parseList[0], "");
11496 currentMove = forwardMostMove = backwardMostMove = 1;
11497 CopyBoard(boards[1], boards[0]);
11499 currentMove = forwardMostMove = backwardMostMove = 0;
11501 SendBoard(&first, forwardMostMove);
11502 if (appData.debugMode) {
11503 fprintf(debugFP, "EditPosDone\n");
11506 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11507 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11508 gameMode = EditGame;
11510 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11511 ClearHighlights(); /* [AS] */
11514 /* Pause for `ms' milliseconds */
11515 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
11525 } while (SubtractTimeMarks(&m2, &m1) < ms);
11528 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
11530 SendMultiLineToICS(buf)
11533 char temp[MSG_SIZ+1], *p;
11540 strncpy(temp, buf, len);
11545 if (*p == '\n' || *p == '\r')
11550 strcat(temp, "\n");
11552 SendToPlayer(temp, strlen(temp));
11556 SetWhiteToPlayEvent()
11558 if (gameMode == EditPosition) {
11559 blackPlaysFirst = FALSE;
11560 DisplayBothClocks(); /* works because currentMove is 0 */
11561 } else if (gameMode == IcsExamining) {
11562 SendToICS(ics_prefix);
11563 SendToICS("tomove white\n");
11568 SetBlackToPlayEvent()
11570 if (gameMode == EditPosition) {
11571 blackPlaysFirst = TRUE;
11572 currentMove = 1; /* kludge */
11573 DisplayBothClocks();
11575 } else if (gameMode == IcsExamining) {
11576 SendToICS(ics_prefix);
11577 SendToICS("tomove black\n");
11582 EditPositionMenuEvent(selection, x, y)
11583 ChessSquare selection;
11587 ChessSquare piece = boards[0][y][x];
11589 if (gameMode != EditPosition && gameMode != IcsExamining) return;
11591 switch (selection) {
11593 if (gameMode == IcsExamining && ics_type == ICS_FICS) {
11594 SendToICS(ics_prefix);
11595 SendToICS("bsetup clear\n");
11596 } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
11597 SendToICS(ics_prefix);
11598 SendToICS("clearboard\n");
11600 for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
11601 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
11602 for (y = 0; y < BOARD_HEIGHT; y++) {
11603 if (gameMode == IcsExamining) {
11604 if (boards[currentMove][y][x] != EmptySquare) {
11605 sprintf(buf, "%sx@%c%c\n", ics_prefix,
11610 boards[0][y][x] = p;
11615 if (gameMode == EditPosition) {
11616 DrawPosition(FALSE, boards[0]);
11621 SetWhiteToPlayEvent();
11625 SetBlackToPlayEvent();
11629 if (gameMode == IcsExamining) {
11630 if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
11631 sprintf(buf, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
11634 if(x < BOARD_LEFT || x >= BOARD_RGHT) {
11635 if(x == BOARD_LEFT-2) {
11636 if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
11637 boards[0][y][1] = 0;
11639 if(x == BOARD_RGHT+1) {
11640 if(y >= gameInfo.holdingsSize) break;
11641 boards[0][y][BOARD_WIDTH-2] = 0;
11644 boards[0][y][x] = EmptySquare;
11645 DrawPosition(FALSE, boards[0]);
11650 if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
11651 piece >= (int)BlackPawn && piece < (int)BlackMan ) {
11652 selection = (ChessSquare) (PROMOTED piece);
11653 } else if(piece == EmptySquare) selection = WhiteSilver;
11654 else selection = (ChessSquare)((int)piece - 1);
11658 if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
11659 piece > (int)BlackMan && piece <= (int)BlackKing ) {
11660 selection = (ChessSquare) (DEMOTED piece);
11661 } else if(piece == EmptySquare) selection = BlackSilver;
11662 else selection = (ChessSquare)((int)piece + 1);
11667 if(gameInfo.variant == VariantShatranj ||
11668 gameInfo.variant == VariantXiangqi ||
11669 gameInfo.variant == VariantCourier ||
11670 gameInfo.variant == VariantMakruk )
11671 selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
11676 if(gameInfo.variant == VariantXiangqi)
11677 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
11678 if(gameInfo.variant == VariantKnightmate)
11679 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
11682 if (gameMode == IcsExamining) {
11683 if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
11684 sprintf(buf, "%s%c@%c%c\n", ics_prefix,
11685 PieceToChar(selection), AAA + x, ONE + y);
11688 if(x < BOARD_LEFT || x >= BOARD_RGHT) {
11690 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
11691 n = PieceToNumber(selection - BlackPawn);
11692 if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
11693 boards[0][BOARD_HEIGHT-1-n][0] = selection;
11694 boards[0][BOARD_HEIGHT-1-n][1]++;
11696 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
11697 n = PieceToNumber(selection);
11698 if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
11699 boards[0][n][BOARD_WIDTH-1] = selection;
11700 boards[0][n][BOARD_WIDTH-2]++;
11703 boards[0][y][x] = selection;
11704 DrawPosition(TRUE, boards[0]);
11712 DropMenuEvent(selection, x, y)
11713 ChessSquare selection;
11716 ChessMove moveType;
11718 switch (gameMode) {
11719 case IcsPlayingWhite:
11720 case MachinePlaysBlack:
11721 if (!WhiteOnMove(currentMove)) {
11722 DisplayMoveError(_("It is Black's turn"));
11725 moveType = WhiteDrop;
11727 case IcsPlayingBlack:
11728 case MachinePlaysWhite:
11729 if (WhiteOnMove(currentMove)) {
11730 DisplayMoveError(_("It is White's turn"));
11733 moveType = BlackDrop;
11736 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
11742 if (moveType == BlackDrop && selection < BlackPawn) {
11743 selection = (ChessSquare) ((int) selection
11744 + (int) BlackPawn - (int) WhitePawn);
11746 if (boards[currentMove][y][x] != EmptySquare) {
11747 DisplayMoveError(_("That square is occupied"));
11751 FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
11757 /* Accept a pending offer of any kind from opponent */
11759 if (appData.icsActive) {
11760 SendToICS(ics_prefix);
11761 SendToICS("accept\n");
11762 } else if (cmailMsgLoaded) {
11763 if (currentMove == cmailOldMove &&
11764 commentList[cmailOldMove] != NULL &&
11765 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11766 "Black offers a draw" : "White offers a draw")) {
11768 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11769 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
11771 DisplayError(_("There is no pending offer on this move"), 0);
11772 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11775 /* Not used for offers from chess program */
11782 /* Decline a pending offer of any kind from opponent */
11784 if (appData.icsActive) {
11785 SendToICS(ics_prefix);
11786 SendToICS("decline\n");
11787 } else if (cmailMsgLoaded) {
11788 if (currentMove == cmailOldMove &&
11789 commentList[cmailOldMove] != NULL &&
11790 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11791 "Black offers a draw" : "White offers a draw")) {
11793 AppendComment(cmailOldMove, "Draw declined", TRUE);
11794 DisplayComment(cmailOldMove - 1, "Draw declined");
11797 DisplayError(_("There is no pending offer on this move"), 0);
11800 /* Not used for offers from chess program */
11807 /* Issue ICS rematch command */
11808 if (appData.icsActive) {
11809 SendToICS(ics_prefix);
11810 SendToICS("rematch\n");
11817 /* Call your opponent's flag (claim a win on time) */
11818 if (appData.icsActive) {
11819 SendToICS(ics_prefix);
11820 SendToICS("flag\n");
11822 switch (gameMode) {
11825 case MachinePlaysWhite:
11828 GameEnds(GameIsDrawn, "Both players ran out of time",
11831 GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
11833 DisplayError(_("Your opponent is not out of time"), 0);
11836 case MachinePlaysBlack:
11839 GameEnds(GameIsDrawn, "Both players ran out of time",
11842 GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
11844 DisplayError(_("Your opponent is not out of time"), 0);
11854 /* Offer draw or accept pending draw offer from opponent */
11856 if (appData.icsActive) {
11857 /* Note: tournament rules require draw offers to be
11858 made after you make your move but before you punch
11859 your clock. Currently ICS doesn't let you do that;
11860 instead, you immediately punch your clock after making
11861 a move, but you can offer a draw at any time. */
11863 SendToICS(ics_prefix);
11864 SendToICS("draw\n");
11865 userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
11866 } else if (cmailMsgLoaded) {
11867 if (currentMove == cmailOldMove &&
11868 commentList[cmailOldMove] != NULL &&
11869 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11870 "Black offers a draw" : "White offers a draw")) {
11871 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11872 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
11873 } else if (currentMove == cmailOldMove + 1) {
11874 char *offer = WhiteOnMove(cmailOldMove) ?
11875 "White offers a draw" : "Black offers a draw";
11876 AppendComment(currentMove, offer, TRUE);
11877 DisplayComment(currentMove - 1, offer);
11878 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
11880 DisplayError(_("You must make your move before offering a draw"), 0);
11881 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11883 } else if (first.offeredDraw) {
11884 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
11886 if (first.sendDrawOffers) {
11887 SendToProgram("draw\n", &first);
11888 userOfferedDraw = TRUE;
11896 /* Offer Adjourn or accept pending Adjourn offer from opponent */
11898 if (appData.icsActive) {
11899 SendToICS(ics_prefix);
11900 SendToICS("adjourn\n");
11902 /* Currently GNU Chess doesn't offer or accept Adjourns */
11910 /* Offer Abort or accept pending Abort offer from opponent */
11912 if (appData.icsActive) {
11913 SendToICS(ics_prefix);
11914 SendToICS("abort\n");
11916 GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
11923 /* Resign. You can do this even if it's not your turn. */
11925 if (appData.icsActive) {
11926 SendToICS(ics_prefix);
11927 SendToICS("resign\n");
11929 switch (gameMode) {
11930 case MachinePlaysWhite:
11931 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11933 case MachinePlaysBlack:
11934 GameEnds(BlackWins, "White resigns", GE_PLAYER);
11937 if (cmailMsgLoaded) {
11939 if (WhiteOnMove(cmailOldMove)) {
11940 GameEnds(BlackWins, "White resigns", GE_PLAYER);
11942 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11944 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
11955 StopObservingEvent()
11957 /* Stop observing current games */
11958 SendToICS(ics_prefix);
11959 SendToICS("unobserve\n");
11963 StopExaminingEvent()
11965 /* Stop observing current game */
11966 SendToICS(ics_prefix);
11967 SendToICS("unexamine\n");
11971 ForwardInner(target)
11976 if (appData.debugMode)
11977 fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
11978 target, currentMove, forwardMostMove);
11980 if (gameMode == EditPosition)
11983 if (gameMode == PlayFromGameFile && !pausing)
11986 if (gameMode == IcsExamining && pausing)
11987 limit = pauseExamForwardMostMove;
11989 limit = forwardMostMove;
11991 if (target > limit) target = limit;
11993 if (target > 0 && moveList[target - 1][0]) {
11994 int fromX, fromY, toX, toY;
11995 toX = moveList[target - 1][2] - AAA;
11996 toY = moveList[target - 1][3] - ONE;
11997 if (moveList[target - 1][1] == '@') {
11998 if (appData.highlightLastMove) {
11999 SetHighlights(-1, -1, toX, toY);
12002 fromX = moveList[target - 1][0] - AAA;
12003 fromY = moveList[target - 1][1] - ONE;
12004 if (target == currentMove + 1) {
12005 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
12007 if (appData.highlightLastMove) {
12008 SetHighlights(fromX, fromY, toX, toY);
12012 if (gameMode == EditGame || gameMode == AnalyzeMode ||
12013 gameMode == Training || gameMode == PlayFromGameFile ||
12014 gameMode == AnalyzeFile) {
12015 while (currentMove < target) {
12016 SendMoveToProgram(currentMove++, &first);
12019 currentMove = target;
12022 if (gameMode == EditGame || gameMode == EndOfGame) {
12023 whiteTimeRemaining = timeRemaining[0][currentMove];
12024 blackTimeRemaining = timeRemaining[1][currentMove];
12026 DisplayBothClocks();
12027 DisplayMove(currentMove - 1);
12028 DrawPosition(FALSE, boards[currentMove]);
12029 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
12030 if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
12031 DisplayComment(currentMove - 1, commentList[currentMove]);
12039 if (gameMode == IcsExamining && !pausing) {
12040 SendToICS(ics_prefix);
12041 SendToICS("forward\n");
12043 ForwardInner(currentMove + 1);
12050 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12051 /* to optimze, we temporarily turn off analysis mode while we feed
12052 * the remaining moves to the engine. Otherwise we get analysis output
12055 if (first.analysisSupport) {
12056 SendToProgram("exit\nforce\n", &first);
12057 first.analyzing = FALSE;
12061 if (gameMode == IcsExamining && !pausing) {
12062 SendToICS(ics_prefix);
12063 SendToICS("forward 999999\n");
12065 ForwardInner(forwardMostMove);
12068 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12069 /* we have fed all the moves, so reactivate analysis mode */
12070 SendToProgram("analyze\n", &first);
12071 first.analyzing = TRUE;
12072 /*first.maybeThinking = TRUE;*/
12073 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12078 BackwardInner(target)
12081 int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
12083 if (appData.debugMode)
12084 fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
12085 target, currentMove, forwardMostMove);
12087 if (gameMode == EditPosition) return;
12088 if (currentMove <= backwardMostMove) {
12090 DrawPosition(full_redraw, boards[currentMove]);
12093 if (gameMode == PlayFromGameFile && !pausing)
12096 if (moveList[target][0]) {
12097 int fromX, fromY, toX, toY;
12098 toX = moveList[target][2] - AAA;
12099 toY = moveList[target][3] - ONE;
12100 if (moveList[target][1] == '@') {
12101 if (appData.highlightLastMove) {
12102 SetHighlights(-1, -1, toX, toY);
12105 fromX = moveList[target][0] - AAA;
12106 fromY = moveList[target][1] - ONE;
12107 if (target == currentMove - 1) {
12108 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
12110 if (appData.highlightLastMove) {
12111 SetHighlights(fromX, fromY, toX, toY);
12115 if (gameMode == EditGame || gameMode==AnalyzeMode ||
12116 gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
12117 while (currentMove > target) {
12118 SendToProgram("undo\n", &first);
12122 currentMove = target;
12125 if (gameMode == EditGame || gameMode == EndOfGame) {
12126 whiteTimeRemaining = timeRemaining[0][currentMove];
12127 blackTimeRemaining = timeRemaining[1][currentMove];
12129 DisplayBothClocks();
12130 DisplayMove(currentMove - 1);
12131 DrawPosition(full_redraw, boards[currentMove]);
12132 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
12133 // [HGM] PV info: routine tests if comment empty
12134 DisplayComment(currentMove - 1, commentList[currentMove]);
12140 if (gameMode == IcsExamining && !pausing) {
12141 SendToICS(ics_prefix);
12142 SendToICS("backward\n");
12144 BackwardInner(currentMove - 1);
12151 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12152 /* to optimize, we temporarily turn off analysis mode while we undo
12153 * all the moves. Otherwise we get analysis output after each undo.
12155 if (first.analysisSupport) {
12156 SendToProgram("exit\nforce\n", &first);
12157 first.analyzing = FALSE;
12161 if (gameMode == IcsExamining && !pausing) {
12162 SendToICS(ics_prefix);
12163 SendToICS("backward 999999\n");
12165 BackwardInner(backwardMostMove);
12168 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12169 /* we have fed all the moves, so reactivate analysis mode */
12170 SendToProgram("analyze\n", &first);
12171 first.analyzing = TRUE;
12172 /*first.maybeThinking = TRUE;*/
12173 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12180 if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
12181 if (to >= forwardMostMove) to = forwardMostMove;
12182 if (to <= backwardMostMove) to = backwardMostMove;
12183 if (to < currentMove) {
12193 if(PopTail(TRUE)) { // [HGM] vari: restore old game tail
12196 if (gameMode != IcsExamining) {
12197 DisplayError(_("You are not examining a game"), 0);
12201 DisplayError(_("You can't revert while pausing"), 0);
12204 SendToICS(ics_prefix);
12205 SendToICS("revert\n");
12211 switch (gameMode) {
12212 case MachinePlaysWhite:
12213 case MachinePlaysBlack:
12214 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
12215 DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
12218 if (forwardMostMove < 2) return;
12219 currentMove = forwardMostMove = forwardMostMove - 2;
12220 whiteTimeRemaining = timeRemaining[0][currentMove];
12221 blackTimeRemaining = timeRemaining[1][currentMove];
12222 DisplayBothClocks();
12223 DisplayMove(currentMove - 1);
12224 ClearHighlights();/*!! could figure this out*/
12225 DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
12226 SendToProgram("remove\n", &first);
12227 /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
12230 case BeginningOfGame:
12234 case IcsPlayingWhite:
12235 case IcsPlayingBlack:
12236 if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
12237 SendToICS(ics_prefix);
12238 SendToICS("takeback 2\n");
12240 SendToICS(ics_prefix);
12241 SendToICS("takeback 1\n");
12250 ChessProgramState *cps;
12252 switch (gameMode) {
12253 case MachinePlaysWhite:
12254 if (!WhiteOnMove(forwardMostMove)) {
12255 DisplayError(_("It is your turn"), 0);
12260 case MachinePlaysBlack:
12261 if (WhiteOnMove(forwardMostMove)) {
12262 DisplayError(_("It is your turn"), 0);
12267 case TwoMachinesPlay:
12268 if (WhiteOnMove(forwardMostMove) ==
12269 (first.twoMachinesColor[0] == 'w')) {
12275 case BeginningOfGame:
12279 SendToProgram("?\n", cps);
12283 TruncateGameEvent()
12286 if (gameMode != EditGame) return;
12293 CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
12294 if (forwardMostMove > currentMove) {
12295 if (gameInfo.resultDetails != NULL) {
12296 free(gameInfo.resultDetails);
12297 gameInfo.resultDetails = NULL;
12298 gameInfo.result = GameUnfinished;
12300 forwardMostMove = currentMove;
12301 HistorySet(parseList, backwardMostMove, forwardMostMove,
12309 if (appData.noChessProgram) return;
12310 switch (gameMode) {
12311 case MachinePlaysWhite:
12312 if (WhiteOnMove(forwardMostMove)) {
12313 DisplayError(_("Wait until your turn"), 0);
12317 case BeginningOfGame:
12318 case MachinePlaysBlack:
12319 if (!WhiteOnMove(forwardMostMove)) {
12320 DisplayError(_("Wait until your turn"), 0);
12325 DisplayError(_("No hint available"), 0);
12328 SendToProgram("hint\n", &first);
12329 hintRequested = TRUE;
12335 if (appData.noChessProgram) return;
12336 switch (gameMode) {
12337 case MachinePlaysWhite:
12338 if (WhiteOnMove(forwardMostMove)) {
12339 DisplayError(_("Wait until your turn"), 0);
12343 case BeginningOfGame:
12344 case MachinePlaysBlack:
12345 if (!WhiteOnMove(forwardMostMove)) {
12346 DisplayError(_("Wait until your turn"), 0);
12351 EditPositionDone(TRUE);
12353 case TwoMachinesPlay:
12358 SendToProgram("bk\n", &first);
12359 bookOutput[0] = NULLCHAR;
12360 bookRequested = TRUE;
12366 char *tags = PGNTags(&gameInfo);
12367 TagsPopUp(tags, CmailMsg());
12371 /* end button procedures */
12374 PrintPosition(fp, move)
12380 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
12381 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
12382 char c = PieceToChar(boards[move][i][j]);
12383 fputc(c == 'x' ? '.' : c, fp);
12384 fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
12387 if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
12388 fprintf(fp, "white to play\n");
12390 fprintf(fp, "black to play\n");
12397 if (gameInfo.white != NULL) {
12398 fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
12404 /* Find last component of program's own name, using some heuristics */
12406 TidyProgramName(prog, host, buf)
12407 char *prog, *host, buf[MSG_SIZ];
12410 int local = (strcmp(host, "localhost") == 0);
12411 while (!local && (p = strchr(prog, ';')) != NULL) {
12413 while (*p == ' ') p++;
12416 if (*prog == '"' || *prog == '\'') {
12417 q = strchr(prog + 1, *prog);
12419 q = strchr(prog, ' ');
12421 if (q == NULL) q = prog + strlen(prog);
12423 while (p >= prog && *p != '/' && *p != '\\') p--;
12425 if(p == prog && *p == '"') p++;
12426 if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
12427 memcpy(buf, p, q - p);
12428 buf[q - p] = NULLCHAR;
12436 TimeControlTagValue()
12439 if (!appData.clockMode) {
12441 } else if (movesPerSession > 0) {
12442 sprintf(buf, "%d/%ld", movesPerSession, timeControl/1000);
12443 } else if (timeIncrement == 0) {
12444 sprintf(buf, "%ld", timeControl/1000);
12446 sprintf(buf, "%ld+%ld", timeControl/1000, timeIncrement/1000);
12448 return StrSave(buf);
12454 /* This routine is used only for certain modes */
12455 VariantClass v = gameInfo.variant;
12456 ChessMove r = GameUnfinished;
12459 if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
12460 r = gameInfo.result;
12461 p = gameInfo.resultDetails;
12462 gameInfo.resultDetails = NULL;
12464 ClearGameInfo(&gameInfo);
12465 gameInfo.variant = v;
12467 switch (gameMode) {
12468 case MachinePlaysWhite:
12469 gameInfo.event = StrSave( appData.pgnEventHeader );
12470 gameInfo.site = StrSave(HostName());
12471 gameInfo.date = PGNDate();
12472 gameInfo.round = StrSave("-");
12473 gameInfo.white = StrSave(first.tidy);
12474 gameInfo.black = StrSave(UserName());
12475 gameInfo.timeControl = TimeControlTagValue();
12478 case MachinePlaysBlack:
12479 gameInfo.event = StrSave( appData.pgnEventHeader );
12480 gameInfo.site = StrSave(HostName());
12481 gameInfo.date = PGNDate();
12482 gameInfo.round = StrSave("-");
12483 gameInfo.white = StrSave(UserName());
12484 gameInfo.black = StrSave(first.tidy);
12485 gameInfo.timeControl = TimeControlTagValue();
12488 case TwoMachinesPlay:
12489 gameInfo.event = StrSave( appData.pgnEventHeader );
12490 gameInfo.site = StrSave(HostName());
12491 gameInfo.date = PGNDate();
12492 if (matchGame > 0) {
12494 sprintf(buf, "%d", matchGame);
12495 gameInfo.round = StrSave(buf);
12497 gameInfo.round = StrSave("-");
12499 if (first.twoMachinesColor[0] == 'w') {
12500 gameInfo.white = StrSave(first.tidy);
12501 gameInfo.black = StrSave(second.tidy);
12503 gameInfo.white = StrSave(second.tidy);
12504 gameInfo.black = StrSave(first.tidy);
12506 gameInfo.timeControl = TimeControlTagValue();
12510 gameInfo.event = StrSave("Edited game");
12511 gameInfo.site = StrSave(HostName());
12512 gameInfo.date = PGNDate();
12513 gameInfo.round = StrSave("-");
12514 gameInfo.white = StrSave("-");
12515 gameInfo.black = StrSave("-");
12516 gameInfo.result = r;
12517 gameInfo.resultDetails = p;
12521 gameInfo.event = StrSave("Edited position");
12522 gameInfo.site = StrSave(HostName());
12523 gameInfo.date = PGNDate();
12524 gameInfo.round = StrSave("-");
12525 gameInfo.white = StrSave("-");
12526 gameInfo.black = StrSave("-");
12529 case IcsPlayingWhite:
12530 case IcsPlayingBlack:
12535 case PlayFromGameFile:
12536 gameInfo.event = StrSave("Game from non-PGN file");
12537 gameInfo.site = StrSave(HostName());
12538 gameInfo.date = PGNDate();
12539 gameInfo.round = StrSave("-");
12540 gameInfo.white = StrSave("?");
12541 gameInfo.black = StrSave("?");
12550 ReplaceComment(index, text)
12556 while (*text == '\n') text++;
12557 len = strlen(text);
12558 while (len > 0 && text[len - 1] == '\n') len--;
12560 if (commentList[index] != NULL)
12561 free(commentList[index]);
12564 commentList[index] = NULL;
12567 if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
12568 *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
12569 *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
12570 commentList[index] = (char *) malloc(len + 2);
12571 strncpy(commentList[index], text, len);
12572 commentList[index][len] = '\n';
12573 commentList[index][len + 1] = NULLCHAR;
12575 // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
12577 commentList[index] = (char *) malloc(len + 6);
12578 strcpy(commentList[index], "{\n");
12579 strncpy(commentList[index]+2, text, len);
12580 commentList[index][len+2] = NULLCHAR;
12581 while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
12582 strcat(commentList[index], "\n}\n");
12596 if (ch == '\r') continue;
12598 } while (ch != '\0');
12602 AppendComment(index, text, addBraces)
12605 Boolean addBraces; // [HGM] braces: tells if we should add {}
12610 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
12611 text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
12614 while (*text == '\n') text++;
12615 len = strlen(text);
12616 while (len > 0 && text[len - 1] == '\n') len--;
12618 if (len == 0) return;
12620 if (commentList[index] != NULL) {
12621 old = commentList[index];
12622 oldlen = strlen(old);
12623 while(commentList[index][oldlen-1] == '\n')
12624 commentList[index][--oldlen] = NULLCHAR;
12625 commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
12626 strcpy(commentList[index], old);
12628 // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
12629 if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces)) {
12630 if(addBraces) addBraces = FALSE; else { text++; len--; }
12631 while (*text == '\n') { text++; len--; }
12632 commentList[index][--oldlen] = NULLCHAR;
12634 if(addBraces) strcat(commentList[index], "\n{\n");
12635 else strcat(commentList[index], "\n");
12636 strcat(commentList[index], text);
12637 if(addBraces) strcat(commentList[index], "\n}\n");
12638 else strcat(commentList[index], "\n");
12640 commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
12642 strcpy(commentList[index], "{\n");
12643 else commentList[index][0] = NULLCHAR;
12644 strcat(commentList[index], text);
12645 strcat(commentList[index], "\n");
12646 if(addBraces) strcat(commentList[index], "}\n");
12650 static char * FindStr( char * text, char * sub_text )
12652 char * result = strstr( text, sub_text );
12654 if( result != NULL ) {
12655 result += strlen( sub_text );
12661 /* [AS] Try to extract PV info from PGN comment */
12662 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
12663 char *GetInfoFromComment( int index, char * text )
12667 if( text != NULL && index > 0 ) {
12670 int time = -1, sec = 0, deci;
12671 char * s_eval = FindStr( text, "[%eval " );
12672 char * s_emt = FindStr( text, "[%emt " );
12674 if( s_eval != NULL || s_emt != NULL ) {
12678 if( s_eval != NULL ) {
12679 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
12683 if( delim != ']' ) {
12688 if( s_emt != NULL ) {
12693 /* We expect something like: [+|-]nnn.nn/dd */
12696 if(*text != '{') return text; // [HGM] braces: must be normal comment
12698 sep = strchr( text, '/' );
12699 if( sep == NULL || sep < (text+4) ) {
12703 time = -1; sec = -1; deci = -1;
12704 if( sscanf( text+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
12705 sscanf( text+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
12706 sscanf( text+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
12707 sscanf( text+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3 ) {
12711 if( score_lo < 0 || score_lo >= 100 ) {
12715 if(sec >= 0) time = 600*time + 10*sec; else
12716 if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
12718 score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
12720 /* [HGM] PV time: now locate end of PV info */
12721 while( *++sep >= '0' && *sep <= '9'); // strip depth
12723 while( *++sep >= '0' && *sep <= '9'); // strip time
12725 while( *++sep >= '0' && *sep <= '9'); // strip seconds
12727 while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
12728 while(*sep == ' ') sep++;
12739 pvInfoList[index-1].depth = depth;
12740 pvInfoList[index-1].score = score;
12741 pvInfoList[index-1].time = 10*time; // centi-sec
12742 if(*sep == '}') *sep = 0; else *--sep = '{';
12748 SendToProgram(message, cps)
12750 ChessProgramState *cps;
12752 int count, outCount, error;
12755 if (cps->pr == NULL) return;
12758 if (appData.debugMode) {
12761 fprintf(debugFP, "%ld >%-6s: %s",
12762 SubtractTimeMarks(&now, &programStartTime),
12763 cps->which, message);
12766 count = strlen(message);
12767 outCount = OutputToProcess(cps->pr, message, count, &error);
12768 if (outCount < count && !exiting
12769 && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
12770 sprintf(buf, _("Error writing to %s chess program"), cps->which);
12771 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
12772 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
12773 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
12774 sprintf(buf, "%s program exits in draw position (%s)", cps->which, cps->program);
12776 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
12778 gameInfo.resultDetails = StrSave(buf);
12780 if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
12785 ReceiveFromProgram(isr, closure, message, count, error)
12786 InputSourceRef isr;
12794 ChessProgramState *cps = (ChessProgramState *)closure;
12796 if (isr != cps->isr) return; /* Killed intentionally */
12800 _("Error: %s chess program (%s) exited unexpectedly"),
12801 cps->which, cps->program);
12802 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
12803 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
12804 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
12805 sprintf(buf, _("%s program exits in draw position (%s)"), cps->which, cps->program);
12807 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
12809 gameInfo.resultDetails = StrSave(buf);
12811 RemoveInputSource(cps->isr);
12812 if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
12815 _("Error reading from %s chess program (%s)"),
12816 cps->which, cps->program);
12817 RemoveInputSource(cps->isr);
12819 /* [AS] Program is misbehaving badly... kill it */
12820 if( count == -2 ) {
12821 DestroyChildProcess( cps->pr, 9 );
12825 if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
12830 if ((end_str = strchr(message, '\r')) != NULL)
12831 *end_str = NULLCHAR;
12832 if ((end_str = strchr(message, '\n')) != NULL)
12833 *end_str = NULLCHAR;
12835 if (appData.debugMode) {
12836 TimeMark now; int print = 1;
12837 char *quote = ""; char c; int i;
12839 if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
12840 char start = message[0];
12841 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
12842 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
12843 sscanf(message, "move %c", &c)!=1 && sscanf(message, "offer%c", &c)!=1 &&
12844 sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
12845 sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
12846 sscanf(message, "tell%c", &c)!=1 && sscanf(message, "0-1 %c", &c)!=1 &&
12847 sscanf(message, "1-0 %c", &c)!=1 && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
12848 sscanf(message, "pong %c", &c)!=1 && start != '#')
12849 { quote = "# "; print = (appData.engineComments == 2); }
12850 message[0] = start; // restore original message
12854 fprintf(debugFP, "%ld <%-6s: %s%s\n",
12855 SubtractTimeMarks(&now, &programStartTime), cps->which,
12861 /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
12862 if (appData.icsEngineAnalyze) {
12863 if (strstr(message, "whisper") != NULL ||
12864 strstr(message, "kibitz") != NULL ||
12865 strstr(message, "tellics") != NULL) return;
12868 HandleMachineMove(message, cps);
12873 SendTimeControl(cps, mps, tc, inc, sd, st)
12874 ChessProgramState *cps;
12875 int mps, inc, sd, st;
12881 if( timeControl_2 > 0 ) {
12882 if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
12883 tc = timeControl_2;
12886 tc /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
12887 inc /= cps->timeOdds;
12888 st /= cps->timeOdds;
12890 seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
12893 /* Set exact time per move, normally using st command */
12894 if (cps->stKludge) {
12895 /* GNU Chess 4 has no st command; uses level in a nonstandard way */
12897 if (seconds == 0) {
12898 sprintf(buf, "level 1 %d\n", st/60);
12900 sprintf(buf, "level 1 %d:%02d\n", st/60, seconds);
12903 sprintf(buf, "st %d\n", st);
12906 /* Set conventional or incremental time control, using level command */
12907 if (seconds == 0) {
12908 /* Note old gnuchess bug -- minutes:seconds used to not work.
12909 Fixed in later versions, but still avoid :seconds
12910 when seconds is 0. */
12911 sprintf(buf, "level %d %ld %d\n", mps, tc/60000, inc/1000);
12913 sprintf(buf, "level %d %ld:%02d %d\n", mps, tc/60000,
12914 seconds, inc/1000);
12917 SendToProgram(buf, cps);
12919 /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
12920 /* Orthogonally, limit search to given depth */
12922 if (cps->sdKludge) {
12923 sprintf(buf, "depth\n%d\n", sd);
12925 sprintf(buf, "sd %d\n", sd);
12927 SendToProgram(buf, cps);
12930 if(cps->nps > 0) { /* [HGM] nps */
12931 if(cps->supportsNPS == FALSE) cps->nps = -1; // don't use if engine explicitly says not supported!
12933 sprintf(buf, "nps %d\n", cps->nps);
12934 SendToProgram(buf, cps);
12939 ChessProgramState *WhitePlayer()
12940 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
12942 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
12943 gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
12949 SendTimeRemaining(cps, machineWhite)
12950 ChessProgramState *cps;
12951 int /*boolean*/ machineWhite;
12953 char message[MSG_SIZ];
12956 /* Note: this routine must be called when the clocks are stopped
12957 or when they have *just* been set or switched; otherwise
12958 it will be off by the time since the current tick started.
12960 if (machineWhite) {
12961 time = whiteTimeRemaining / 10;
12962 otime = blackTimeRemaining / 10;
12964 time = blackTimeRemaining / 10;
12965 otime = whiteTimeRemaining / 10;
12967 /* [HGM] translate opponent's time by time-odds factor */
12968 otime = (otime * cps->other->timeOdds) / cps->timeOdds;
12969 if (appData.debugMode) {
12970 fprintf(debugFP, "time odds: %f %f \n", cps->timeOdds, cps->other->timeOdds);
12973 if (time <= 0) time = 1;
12974 if (otime <= 0) otime = 1;
12976 sprintf(message, "time %ld\n", time);
12977 SendToProgram(message, cps);
12979 sprintf(message, "otim %ld\n", otime);
12980 SendToProgram(message, cps);
12984 BoolFeature(p, name, loc, cps)
12988 ChessProgramState *cps;
12991 int len = strlen(name);
12993 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
12995 sscanf(*p, "%d", &val);
12997 while (**p && **p != ' ') (*p)++;
12998 sprintf(buf, "accepted %s\n", name);
12999 SendToProgram(buf, cps);
13006 IntFeature(p, name, loc, cps)
13010 ChessProgramState *cps;
13013 int len = strlen(name);
13014 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
13016 sscanf(*p, "%d", loc);
13017 while (**p && **p != ' ') (*p)++;
13018 sprintf(buf, "accepted %s\n", name);
13019 SendToProgram(buf, cps);
13026 StringFeature(p, name, loc, cps)
13030 ChessProgramState *cps;
13033 int len = strlen(name);
13034 if (strncmp((*p), name, len) == 0
13035 && (*p)[len] == '=' && (*p)[len+1] == '\"') {
13037 sscanf(*p, "%[^\"]", loc);
13038 while (**p && **p != '\"') (*p)++;
13039 if (**p == '\"') (*p)++;
13040 sprintf(buf, "accepted %s\n", name);
13041 SendToProgram(buf, cps);
13048 ParseOption(Option *opt, ChessProgramState *cps)
13049 // [HGM] options: process the string that defines an engine option, and determine
13050 // name, type, default value, and allowed value range
13052 char *p, *q, buf[MSG_SIZ];
13053 int n, min = (-1)<<31, max = 1<<31, def;
13055 if(p = strstr(opt->name, " -spin ")) {
13056 if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
13057 if(max < min) max = min; // enforce consistency
13058 if(def < min) def = min;
13059 if(def > max) def = max;
13064 } else if((p = strstr(opt->name, " -slider "))) {
13065 // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
13066 if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
13067 if(max < min) max = min; // enforce consistency
13068 if(def < min) def = min;
13069 if(def > max) def = max;
13073 opt->type = Spin; // Slider;
13074 } else if((p = strstr(opt->name, " -string "))) {
13075 opt->textValue = p+9;
13076 opt->type = TextBox;
13077 } else if((p = strstr(opt->name, " -file "))) {
13078 // for now -file is a synonym for -string, to already provide compatibility with future polyglots
13079 opt->textValue = p+7;
13080 opt->type = TextBox; // FileName;
13081 } else if((p = strstr(opt->name, " -path "))) {
13082 // for now -file is a synonym for -string, to already provide compatibility with future polyglots
13083 opt->textValue = p+7;
13084 opt->type = TextBox; // PathName;
13085 } else if(p = strstr(opt->name, " -check ")) {
13086 if(sscanf(p, " -check %d", &def) < 1) return FALSE;
13087 opt->value = (def != 0);
13088 opt->type = CheckBox;
13089 } else if(p = strstr(opt->name, " -combo ")) {
13090 opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
13091 cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
13092 if(*q == '*') cps->comboList[cps->comboCnt-1]++;
13093 opt->value = n = 0;
13094 while(q = StrStr(q, " /// ")) {
13095 n++; *q = 0; // count choices, and null-terminate each of them
13097 if(*q == '*') { // remember default, which is marked with * prefix
13101 cps->comboList[cps->comboCnt++] = q;
13103 cps->comboList[cps->comboCnt++] = NULL;
13105 opt->type = ComboBox;
13106 } else if(p = strstr(opt->name, " -button")) {
13107 opt->type = Button;
13108 } else if(p = strstr(opt->name, " -save")) {
13109 opt->type = SaveButton;
13110 } else return FALSE;
13111 *p = 0; // terminate option name
13112 // now look if the command-line options define a setting for this engine option.
13113 if(cps->optionSettings && cps->optionSettings[0])
13114 p = strstr(cps->optionSettings, opt->name); else p = NULL;
13115 if(p && (p == cps->optionSettings || p[-1] == ',')) {
13116 sprintf(buf, "option %s", p);
13117 if(p = strstr(buf, ",")) *p = 0;
13119 SendToProgram(buf, cps);
13125 FeatureDone(cps, val)
13126 ChessProgramState* cps;
13129 DelayedEventCallback cb = GetDelayedEvent();
13130 if ((cb == InitBackEnd3 && cps == &first) ||
13131 (cb == TwoMachinesEventIfReady && cps == &second)) {
13132 CancelDelayedEvent();
13133 ScheduleDelayedEvent(cb, val ? 1 : 3600000);
13135 cps->initDone = val;
13138 /* Parse feature command from engine */
13140 ParseFeatures(args, cps)
13142 ChessProgramState *cps;
13150 while (*p == ' ') p++;
13151 if (*p == NULLCHAR) return;
13153 if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
13154 if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
13155 if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
13156 if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
13157 if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
13158 if (BoolFeature(&p, "reuse", &val, cps)) {
13159 /* Engine can disable reuse, but can't enable it if user said no */
13160 if (!val) cps->reuse = FALSE;
13163 if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
13164 if (StringFeature(&p, "myname", &cps->tidy, cps)) {
13165 if (gameMode == TwoMachinesPlay) {
13166 DisplayTwoMachinesTitle();
13172 if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
13173 if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
13174 if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
13175 if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
13176 if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
13177 if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
13178 if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
13179 if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
13180 if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
13181 if (IntFeature(&p, "done", &val, cps)) {
13182 FeatureDone(cps, val);
13185 /* Added by Tord: */
13186 if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
13187 if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
13188 /* End of additions by Tord */
13190 /* [HGM] added features: */
13191 if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
13192 if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
13193 if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
13194 if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
13195 if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
13196 if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
13197 if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
13198 if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
13199 sprintf(buf, "rejected option %s\n", cps->option[--cps->nrOptions].name);
13200 SendToProgram(buf, cps);
13203 if(cps->nrOptions >= MAX_OPTIONS) {
13205 sprintf(buf, "%s engine has too many options\n", cps->which);
13206 DisplayError(buf, 0);
13210 if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
13211 /* End of additions by HGM */
13213 /* unknown feature: complain and skip */
13215 while (*q && *q != '=') q++;
13216 sprintf(buf, "rejected %.*s\n", (int)(q-p), p);
13217 SendToProgram(buf, cps);
13223 while (*p && *p != '\"') p++;
13224 if (*p == '\"') p++;
13226 while (*p && *p != ' ') p++;
13234 PeriodicUpdatesEvent(newState)
13237 if (newState == appData.periodicUpdates)
13240 appData.periodicUpdates=newState;
13242 /* Display type changes, so update it now */
13243 // DisplayAnalysis();
13245 /* Get the ball rolling again... */
13247 AnalysisPeriodicEvent(1);
13248 StartAnalysisClock();
13253 PonderNextMoveEvent(newState)
13256 if (newState == appData.ponderNextMove) return;
13257 if (gameMode == EditPosition) EditPositionDone(TRUE);
13259 SendToProgram("hard\n", &first);
13260 if (gameMode == TwoMachinesPlay) {
13261 SendToProgram("hard\n", &second);
13264 SendToProgram("easy\n", &first);
13265 thinkOutput[0] = NULLCHAR;
13266 if (gameMode == TwoMachinesPlay) {
13267 SendToProgram("easy\n", &second);
13270 appData.ponderNextMove = newState;
13274 NewSettingEvent(option, command, value)
13280 if (gameMode == EditPosition) EditPositionDone(TRUE);
13281 sprintf(buf, "%s%s %d\n", (option ? "option ": ""), command, value);
13282 SendToProgram(buf, &first);
13283 if (gameMode == TwoMachinesPlay) {
13284 SendToProgram(buf, &second);
13289 ShowThinkingEvent()
13290 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
13292 static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
13293 int newState = appData.showThinking
13294 // [HGM] thinking: other features now need thinking output as well
13295 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
13297 if (oldState == newState) return;
13298 oldState = newState;
13299 if (gameMode == EditPosition) EditPositionDone(TRUE);
13301 SendToProgram("post\n", &first);
13302 if (gameMode == TwoMachinesPlay) {
13303 SendToProgram("post\n", &second);
13306 SendToProgram("nopost\n", &first);
13307 thinkOutput[0] = NULLCHAR;
13308 if (gameMode == TwoMachinesPlay) {
13309 SendToProgram("nopost\n", &second);
13312 // appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
13316 AskQuestionEvent(title, question, replyPrefix, which)
13317 char *title; char *question; char *replyPrefix; char *which;
13319 ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
13320 if (pr == NoProc) return;
13321 AskQuestion(title, question, replyPrefix, pr);
13325 DisplayMove(moveNumber)
13328 char message[MSG_SIZ];
13330 char cpThinkOutput[MSG_SIZ];
13332 if(appData.noGUI) return; // [HGM] fast: suppress display of moves
13334 if (moveNumber == forwardMostMove - 1 ||
13335 gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13337 safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput));
13339 if (strchr(cpThinkOutput, '\n')) {
13340 *strchr(cpThinkOutput, '\n') = NULLCHAR;
13343 *cpThinkOutput = NULLCHAR;
13346 /* [AS] Hide thinking from human user */
13347 if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
13348 *cpThinkOutput = NULLCHAR;
13349 if( thinkOutput[0] != NULLCHAR ) {
13352 for( i=0; i<=hiddenThinkOutputState; i++ ) {
13353 cpThinkOutput[i] = '.';
13355 cpThinkOutput[i] = NULLCHAR;
13356 hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
13360 if (moveNumber == forwardMostMove - 1 &&
13361 gameInfo.resultDetails != NULL) {
13362 if (gameInfo.resultDetails[0] == NULLCHAR) {
13363 sprintf(res, " %s", PGNResult(gameInfo.result));
13365 sprintf(res, " {%s} %s",
13366 gameInfo.resultDetails, PGNResult(gameInfo.result));
13372 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
13373 DisplayMessage(res, cpThinkOutput);
13375 sprintf(message, "%d.%s%s%s", moveNumber / 2 + 1,
13376 WhiteOnMove(moveNumber) ? " " : ".. ",
13377 parseList[moveNumber], res);
13378 DisplayMessage(message, cpThinkOutput);
13383 DisplayComment(moveNumber, text)
13387 char title[MSG_SIZ];
13388 char buf[8000]; // comment can be long!
13391 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
13392 strcpy(title, "Comment");
13394 sprintf(title, "Comment on %d.%s%s", moveNumber / 2 + 1,
13395 WhiteOnMove(moveNumber) ? " " : ".. ",
13396 parseList[moveNumber]);
13398 // [HGM] PV info: display PV info together with (or as) comment
13399 if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {
13400 if(text == NULL) text = "";
13401 score = pvInfoList[moveNumber].score;
13402 sprintf(buf, "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,
13403 depth, (pvInfoList[moveNumber].time+50)/100, text);
13406 if (text != NULL && (appData.autoDisplayComment || commentUp))
13407 CommentPopUp(title, text);
13410 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
13411 * might be busy thinking or pondering. It can be omitted if your
13412 * gnuchess is configured to stop thinking immediately on any user
13413 * input. However, that gnuchess feature depends on the FIONREAD
13414 * ioctl, which does not work properly on some flavors of Unix.
13418 ChessProgramState *cps;
13421 if (!cps->useSigint) return;
13422 if (appData.noChessProgram || (cps->pr == NoProc)) return;
13423 switch (gameMode) {
13424 case MachinePlaysWhite:
13425 case MachinePlaysBlack:
13426 case TwoMachinesPlay:
13427 case IcsPlayingWhite:
13428 case IcsPlayingBlack:
13431 /* Skip if we know it isn't thinking */
13432 if (!cps->maybeThinking) return;
13433 if (appData.debugMode)
13434 fprintf(debugFP, "Interrupting %s\n", cps->which);
13435 InterruptChildProcess(cps->pr);
13436 cps->maybeThinking = FALSE;
13441 #endif /*ATTENTION*/
13447 if (whiteTimeRemaining <= 0) {
13450 if (appData.icsActive) {
13451 if (appData.autoCallFlag &&
13452 gameMode == IcsPlayingBlack && !blackFlag) {
13453 SendToICS(ics_prefix);
13454 SendToICS("flag\n");
13458 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
13460 if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
13461 if (appData.autoCallFlag) {
13462 GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
13469 if (blackTimeRemaining <= 0) {
13472 if (appData.icsActive) {
13473 if (appData.autoCallFlag &&
13474 gameMode == IcsPlayingWhite && !whiteFlag) {
13475 SendToICS(ics_prefix);
13476 SendToICS("flag\n");
13480 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
13482 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
13483 if (appData.autoCallFlag) {
13484 GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
13497 if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
13498 gameMode == PlayFromGameFile || forwardMostMove == 0) return;
13501 * add time to clocks when time control is achieved ([HGM] now also used for increment)
13503 if ( !WhiteOnMove(forwardMostMove) )
13504 /* White made time control */
13505 whiteTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
13506 /* [HGM] time odds: correct new time quota for time odds! */
13507 / WhitePlayer()->timeOdds;
13509 /* Black made time control */
13510 blackTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
13511 / WhitePlayer()->other->timeOdds;
13515 DisplayBothClocks()
13517 int wom = gameMode == EditPosition ?
13518 !blackPlaysFirst : WhiteOnMove(currentMove);
13519 DisplayWhiteClock(whiteTimeRemaining, wom);
13520 DisplayBlackClock(blackTimeRemaining, !wom);
13524 /* Timekeeping seems to be a portability nightmare. I think everyone
13525 has ftime(), but I'm really not sure, so I'm including some ifdefs
13526 to use other calls if you don't. Clocks will be less accurate if
13527 you have neither ftime nor gettimeofday.
13530 /* VS 2008 requires the #include outside of the function */
13531 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
13532 #include <sys/timeb.h>
13535 /* Get the current time as a TimeMark */
13540 #if HAVE_GETTIMEOFDAY
13542 struct timeval timeVal;
13543 struct timezone timeZone;
13545 gettimeofday(&timeVal, &timeZone);
13546 tm->sec = (long) timeVal.tv_sec;
13547 tm->ms = (int) (timeVal.tv_usec / 1000L);
13549 #else /*!HAVE_GETTIMEOFDAY*/
13552 // include <sys/timeb.h> / moved to just above start of function
13553 struct timeb timeB;
13556 tm->sec = (long) timeB.time;
13557 tm->ms = (int) timeB.millitm;
13559 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
13560 tm->sec = (long) time(NULL);
13566 /* Return the difference in milliseconds between two
13567 time marks. We assume the difference will fit in a long!
13570 SubtractTimeMarks(tm2, tm1)
13571 TimeMark *tm2, *tm1;
13573 return 1000L*(tm2->sec - tm1->sec) +
13574 (long) (tm2->ms - tm1->ms);
13579 * Code to manage the game clocks.
13581 * In tournament play, black starts the clock and then white makes a move.
13582 * We give the human user a slight advantage if he is playing white---the
13583 * clocks don't run until he makes his first move, so it takes zero time.
13584 * Also, we don't account for network lag, so we could get out of sync
13585 * with GNU Chess's clock -- but then, referees are always right.
13588 static TimeMark tickStartTM;
13589 static long intendedTickLength;
13592 NextTickLength(timeRemaining)
13593 long timeRemaining;
13595 long nominalTickLength, nextTickLength;
13597 if (timeRemaining > 0L && timeRemaining <= 10000L)
13598 nominalTickLength = 100L;
13600 nominalTickLength = 1000L;
13601 nextTickLength = timeRemaining % nominalTickLength;
13602 if (nextTickLength <= 0) nextTickLength += nominalTickLength;
13604 return nextTickLength;
13607 /* Adjust clock one minute up or down */
13609 AdjustClock(Boolean which, int dir)
13611 if(which) blackTimeRemaining += 60000*dir;
13612 else whiteTimeRemaining += 60000*dir;
13613 DisplayBothClocks();
13616 /* Stop clocks and reset to a fresh time control */
13620 (void) StopClockTimer();
13621 if (appData.icsActive) {
13622 whiteTimeRemaining = blackTimeRemaining = 0;
13623 } else if (searchTime) {
13624 whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
13625 blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
13626 } else { /* [HGM] correct new time quote for time odds */
13627 whiteTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->timeOdds;
13628 blackTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->other->timeOdds;
13630 if (whiteFlag || blackFlag) {
13632 whiteFlag = blackFlag = FALSE;
13634 DisplayBothClocks();
13637 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
13639 /* Decrement running clock by amount of time that has passed */
13643 long timeRemaining;
13644 long lastTickLength, fudge;
13647 if (!appData.clockMode) return;
13648 if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
13652 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13654 /* Fudge if we woke up a little too soon */
13655 fudge = intendedTickLength - lastTickLength;
13656 if (fudge < 0 || fudge > FUDGE) fudge = 0;
13658 if (WhiteOnMove(forwardMostMove)) {
13659 if(whiteNPS >= 0) lastTickLength = 0;
13660 timeRemaining = whiteTimeRemaining -= lastTickLength;
13661 DisplayWhiteClock(whiteTimeRemaining - fudge,
13662 WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
13664 if(blackNPS >= 0) lastTickLength = 0;
13665 timeRemaining = blackTimeRemaining -= lastTickLength;
13666 DisplayBlackClock(blackTimeRemaining - fudge,
13667 !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
13670 if (CheckFlags()) return;
13673 intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
13674 StartClockTimer(intendedTickLength);
13676 /* if the time remaining has fallen below the alarm threshold, sound the
13677 * alarm. if the alarm has sounded and (due to a takeback or time control
13678 * with increment) the time remaining has increased to a level above the
13679 * threshold, reset the alarm so it can sound again.
13682 if (appData.icsActive && appData.icsAlarm) {
13684 /* make sure we are dealing with the user's clock */
13685 if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
13686 ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
13689 if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
13690 alarmSounded = FALSE;
13691 } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
13693 alarmSounded = TRUE;
13699 /* A player has just moved, so stop the previously running
13700 clock and (if in clock mode) start the other one.
13701 We redisplay both clocks in case we're in ICS mode, because
13702 ICS gives us an update to both clocks after every move.
13703 Note that this routine is called *after* forwardMostMove
13704 is updated, so the last fractional tick must be subtracted
13705 from the color that is *not* on move now.
13710 long lastTickLength;
13712 int flagged = FALSE;
13716 if (StopClockTimer() && appData.clockMode) {
13717 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13718 if (WhiteOnMove(forwardMostMove)) {
13719 if(blackNPS >= 0) lastTickLength = 0;
13720 blackTimeRemaining -= lastTickLength;
13721 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
13722 // if(pvInfoList[forwardMostMove-1].time == -1)
13723 pvInfoList[forwardMostMove-1].time = // use GUI time
13724 (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
13726 if(whiteNPS >= 0) lastTickLength = 0;
13727 whiteTimeRemaining -= lastTickLength;
13728 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
13729 // if(pvInfoList[forwardMostMove-1].time == -1)
13730 pvInfoList[forwardMostMove-1].time =
13731 (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
13733 flagged = CheckFlags();
13735 CheckTimeControl();
13737 if (flagged || !appData.clockMode) return;
13739 switch (gameMode) {
13740 case MachinePlaysBlack:
13741 case MachinePlaysWhite:
13742 case BeginningOfGame:
13743 if (pausing) return;
13747 case PlayFromGameFile:
13755 if (searchTime) { // [HGM] st: set clock of player that has to move to max time
13756 if(WhiteOnMove(forwardMostMove))
13757 whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
13758 else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
13762 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
13763 whiteTimeRemaining : blackTimeRemaining);
13764 StartClockTimer(intendedTickLength);
13768 /* Stop both clocks */
13772 long lastTickLength;
13775 if (!StopClockTimer()) return;
13776 if (!appData.clockMode) return;
13780 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13781 if (WhiteOnMove(forwardMostMove)) {
13782 if(whiteNPS >= 0) lastTickLength = 0;
13783 whiteTimeRemaining -= lastTickLength;
13784 DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
13786 if(blackNPS >= 0) lastTickLength = 0;
13787 blackTimeRemaining -= lastTickLength;
13788 DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
13793 /* Start clock of player on move. Time may have been reset, so
13794 if clock is already running, stop and restart it. */
13798 (void) StopClockTimer(); /* in case it was running already */
13799 DisplayBothClocks();
13800 if (CheckFlags()) return;
13802 if (!appData.clockMode) return;
13803 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
13805 GetTimeMark(&tickStartTM);
13806 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
13807 whiteTimeRemaining : blackTimeRemaining);
13809 /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
13810 whiteNPS = blackNPS = -1;
13811 if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
13812 || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
13813 whiteNPS = first.nps;
13814 if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
13815 || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
13816 blackNPS = first.nps;
13817 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
13818 whiteNPS = second.nps;
13819 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
13820 blackNPS = second.nps;
13821 if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
13823 StartClockTimer(intendedTickLength);
13830 long second, minute, hour, day;
13832 static char buf[32];
13834 if (ms > 0 && ms <= 9900) {
13835 /* convert milliseconds to tenths, rounding up */
13836 double tenths = floor( ((double)(ms + 99L)) / 100.00 );
13838 sprintf(buf, " %03.1f ", tenths/10.0);
13842 /* convert milliseconds to seconds, rounding up */
13843 /* use floating point to avoid strangeness of integer division
13844 with negative dividends on many machines */
13845 second = (long) floor(((double) (ms + 999L)) / 1000.0);
13852 day = second / (60 * 60 * 24);
13853 second = second % (60 * 60 * 24);
13854 hour = second / (60 * 60);
13855 second = second % (60 * 60);
13856 minute = second / 60;
13857 second = second % 60;
13860 sprintf(buf, " %s%ld:%02ld:%02ld:%02ld ",
13861 sign, day, hour, minute, second);
13863 sprintf(buf, " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
13865 sprintf(buf, " %s%2ld:%02ld ", sign, minute, second);
13872 * This is necessary because some C libraries aren't ANSI C compliant yet.
13875 StrStr(string, match)
13876 char *string, *match;
13880 length = strlen(match);
13882 for (i = strlen(string) - length; i >= 0; i--, string++)
13883 if (!strncmp(match, string, length))
13890 StrCaseStr(string, match)
13891 char *string, *match;
13895 length = strlen(match);
13897 for (i = strlen(string) - length; i >= 0; i--, string++) {
13898 for (j = 0; j < length; j++) {
13899 if (ToLower(match[j]) != ToLower(string[j]))
13902 if (j == length) return string;
13916 c1 = ToLower(*s1++);
13917 c2 = ToLower(*s2++);
13918 if (c1 > c2) return 1;
13919 if (c1 < c2) return -1;
13920 if (c1 == NULLCHAR) return 0;
13929 return isupper(c) ? tolower(c) : c;
13937 return islower(c) ? toupper(c) : c;
13939 #endif /* !_amigados */
13947 if ((ret = (char *) malloc(strlen(s) + 1))) {
13954 StrSavePtr(s, savePtr)
13955 char *s, **savePtr;
13960 if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
13961 strcpy(*savePtr, s);
13973 clock = time((time_t *)NULL);
13974 tm = localtime(&clock);
13975 sprintf(buf, "%04d.%02d.%02d",
13976 tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
13977 return StrSave(buf);
13982 PositionToFEN(move, overrideCastling)
13984 char *overrideCastling;
13986 int i, j, fromX, fromY, toX, toY;
13993 whiteToPlay = (gameMode == EditPosition) ?
13994 !blackPlaysFirst : (move % 2 == 0);
13997 /* Piece placement data */
13998 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
14000 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
14001 if (boards[move][i][j] == EmptySquare) {
14003 } else { ChessSquare piece = boards[move][i][j];
14004 if (emptycount > 0) {
14005 if(emptycount<10) /* [HGM] can be >= 10 */
14006 *p++ = '0' + emptycount;
14007 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
14010 if(PieceToChar(piece) == '+') {
14011 /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
14013 piece = (ChessSquare)(DEMOTED piece);
14015 *p++ = PieceToChar(piece);
14017 /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
14018 p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
14023 if (emptycount > 0) {
14024 if(emptycount<10) /* [HGM] can be >= 10 */
14025 *p++ = '0' + emptycount;
14026 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
14033 /* [HGM] print Crazyhouse or Shogi holdings */
14034 if( gameInfo.holdingsWidth ) {
14035 *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
14037 for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
14038 piece = boards[move][i][BOARD_WIDTH-1];
14039 if( piece != EmptySquare )
14040 for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
14041 *p++ = PieceToChar(piece);
14043 for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
14044 piece = boards[move][BOARD_HEIGHT-i-1][0];
14045 if( piece != EmptySquare )
14046 for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
14047 *p++ = PieceToChar(piece);
14050 if( q == p ) *p++ = '-';
14056 *p++ = whiteToPlay ? 'w' : 'b';
14059 if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
14060 while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
14062 if(nrCastlingRights) {
14064 if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
14065 /* [HGM] write directly from rights */
14066 if(boards[move][CASTLING][2] != NoRights &&
14067 boards[move][CASTLING][0] != NoRights )
14068 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
14069 if(boards[move][CASTLING][2] != NoRights &&
14070 boards[move][CASTLING][1] != NoRights )
14071 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
14072 if(boards[move][CASTLING][5] != NoRights &&
14073 boards[move][CASTLING][3] != NoRights )
14074 *p++ = boards[move][CASTLING][3] + AAA;
14075 if(boards[move][CASTLING][5] != NoRights &&
14076 boards[move][CASTLING][4] != NoRights )
14077 *p++ = boards[move][CASTLING][4] + AAA;
14080 /* [HGM] write true castling rights */
14081 if( nrCastlingRights == 6 ) {
14082 if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
14083 boards[move][CASTLING][2] != NoRights ) *p++ = 'K';
14084 if(boards[move][CASTLING][1] == BOARD_LEFT &&
14085 boards[move][CASTLING][2] != NoRights ) *p++ = 'Q';
14086 if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
14087 boards[move][CASTLING][5] != NoRights ) *p++ = 'k';
14088 if(boards[move][CASTLING][4] == BOARD_LEFT &&
14089 boards[move][CASTLING][5] != NoRights ) *p++ = 'q';
14092 if (q == p) *p++ = '-'; /* No castling rights */
14096 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
14097 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
14098 /* En passant target square */
14099 if (move > backwardMostMove) {
14100 fromX = moveList[move - 1][0] - AAA;
14101 fromY = moveList[move - 1][1] - ONE;
14102 toX = moveList[move - 1][2] - AAA;
14103 toY = moveList[move - 1][3] - ONE;
14104 if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
14105 toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
14106 boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
14108 /* 2-square pawn move just happened */
14110 *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
14114 } else if(move == backwardMostMove) {
14115 // [HGM] perhaps we should always do it like this, and forget the above?
14116 if((signed char)boards[move][EP_STATUS] >= 0) {
14117 *p++ = boards[move][EP_STATUS] + AAA;
14118 *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
14129 /* [HGM] find reversible plies */
14130 { int i = 0, j=move;
14132 if (appData.debugMode) { int k;
14133 fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
14134 for(k=backwardMostMove; k<=forwardMostMove; k++)
14135 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
14139 while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
14140 if( j == backwardMostMove ) i += initialRulePlies;
14141 sprintf(p, "%d ", i);
14142 p += i>=100 ? 4 : i >= 10 ? 3 : 2;
14144 /* Fullmove number */
14145 sprintf(p, "%d", (move / 2) + 1);
14147 return StrSave(buf);
14151 ParseFEN(board, blackPlaysFirst, fen)
14153 int *blackPlaysFirst;
14163 /* [HGM] by default clear Crazyhouse holdings, if present */
14164 if(gameInfo.holdingsWidth) {
14165 for(i=0; i<BOARD_HEIGHT; i++) {
14166 board[i][0] = EmptySquare; /* black holdings */
14167 board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
14168 board[i][1] = (ChessSquare) 0; /* black counts */
14169 board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
14173 /* Piece placement data */
14174 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
14177 if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
14178 if (*p == '/') p++;
14179 emptycount = gameInfo.boardWidth - j;
14180 while (emptycount--)
14181 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
14183 #if(BOARD_FILES >= 10)
14184 } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
14185 p++; emptycount=10;
14186 if (j + emptycount > gameInfo.boardWidth) return FALSE;
14187 while (emptycount--)
14188 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
14190 } else if (isdigit(*p)) {
14191 emptycount = *p++ - '0';
14192 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
14193 if (j + emptycount > gameInfo.boardWidth) return FALSE;
14194 while (emptycount--)
14195 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
14196 } else if (*p == '+' || isalpha(*p)) {
14197 if (j >= gameInfo.boardWidth) return FALSE;
14199 piece = CharToPiece(*++p);
14200 if(piece == EmptySquare) return FALSE; /* unknown piece */
14201 piece = (ChessSquare) (PROMOTED piece ); p++;
14202 if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
14203 } else piece = CharToPiece(*p++);
14205 if(piece==EmptySquare) return FALSE; /* unknown piece */
14206 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
14207 piece = (ChessSquare) (PROMOTED piece);
14208 if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
14211 board[i][(j++)+gameInfo.holdingsWidth] = piece;
14217 while (*p == '/' || *p == ' ') p++;
14219 /* [HGM] look for Crazyhouse holdings here */
14220 while(*p==' ') p++;
14221 if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
14223 if(*p == '-' ) *p++; /* empty holdings */ else {
14224 if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
14225 /* if we would allow FEN reading to set board size, we would */
14226 /* have to add holdings and shift the board read so far here */
14227 while( (piece = CharToPiece(*p) ) != EmptySquare ) {
14229 if((int) piece >= (int) BlackPawn ) {
14230 i = (int)piece - (int)BlackPawn;
14231 i = PieceToNumber((ChessSquare)i);
14232 if( i >= gameInfo.holdingsSize ) return FALSE;
14233 board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
14234 board[BOARD_HEIGHT-1-i][1]++; /* black counts */
14236 i = (int)piece - (int)WhitePawn;
14237 i = PieceToNumber((ChessSquare)i);
14238 if( i >= gameInfo.holdingsSize ) return FALSE;
14239 board[i][BOARD_WIDTH-1] = piece; /* white holdings */
14240 board[i][BOARD_WIDTH-2]++; /* black holdings */
14244 if(*p == ']') *p++;
14247 while(*p == ' ') p++;
14252 *blackPlaysFirst = FALSE;
14255 *blackPlaysFirst = TRUE;
14261 /* [HGM] We NO LONGER ignore the rest of the FEN notation */
14262 /* return the extra info in global variiables */
14264 /* set defaults in case FEN is incomplete */
14265 board[EP_STATUS] = EP_UNKNOWN;
14266 for(i=0; i<nrCastlingRights; i++ ) {
14267 board[CASTLING][i] =
14268 gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
14269 } /* assume possible unless obviously impossible */
14270 if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
14271 if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
14272 if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
14273 && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
14274 if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
14275 if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
14276 if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
14277 && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
14280 while(*p==' ') p++;
14281 if(nrCastlingRights) {
14282 if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
14283 /* castling indicator present, so default becomes no castlings */
14284 for(i=0; i<nrCastlingRights; i++ ) {
14285 board[CASTLING][i] = NoRights;
14288 while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
14289 (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
14290 ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
14291 ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth) ) {
14292 char c = *p++; int whiteKingFile=NoRights, blackKingFile=NoRights;
14294 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
14295 if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
14296 if(board[0 ][i] == WhiteKing) whiteKingFile = i;
14298 if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
14299 whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
14300 if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
14301 && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
14302 if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
14303 && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
14306 for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
14307 board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
14308 board[CASTLING][2] = whiteKingFile;
14311 for(i=BOARD_LEFT; i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
14312 board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
14313 board[CASTLING][2] = whiteKingFile;
14316 for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
14317 board[CASTLING][3] = i != blackKingFile ? i : NoRights;
14318 board[CASTLING][5] = blackKingFile;
14321 for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
14322 board[CASTLING][4] = i != blackKingFile ? i : NoRights;
14323 board[CASTLING][5] = blackKingFile;
14326 default: /* FRC castlings */
14327 if(c >= 'a') { /* black rights */
14328 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
14329 if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
14330 if(i == BOARD_RGHT) break;
14331 board[CASTLING][5] = i;
14333 if(board[BOARD_HEIGHT-1][c] < BlackPawn ||
14334 board[BOARD_HEIGHT-1][c] >= BlackKing ) break;
14336 board[CASTLING][3] = c;
14338 board[CASTLING][4] = c;
14339 } else { /* white rights */
14340 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
14341 if(board[0][i] == WhiteKing) break;
14342 if(i == BOARD_RGHT) break;
14343 board[CASTLING][2] = i;
14344 c -= AAA - 'a' + 'A';
14345 if(board[0][c] >= WhiteKing) break;
14347 board[CASTLING][0] = c;
14349 board[CASTLING][1] = c;
14353 for(i=0; i<nrCastlingRights; i++)
14354 if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
14355 if (appData.debugMode) {
14356 fprintf(debugFP, "FEN castling rights:");
14357 for(i=0; i<nrCastlingRights; i++)
14358 fprintf(debugFP, " %d", board[CASTLING][i]);
14359 fprintf(debugFP, "\n");
14362 while(*p==' ') p++;
14365 /* read e.p. field in games that know e.p. capture */
14366 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
14367 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
14369 p++; board[EP_STATUS] = EP_NONE;
14371 char c = *p++ - AAA;
14373 if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
14374 if(*p >= '0' && *p <='9') *p++;
14375 board[EP_STATUS] = c;
14380 if(sscanf(p, "%d", &i) == 1) {
14381 FENrulePlies = i; /* 50-move ply counter */
14382 /* (The move number is still ignored) */
14389 EditPositionPasteFEN(char *fen)
14392 Board initial_position;
14394 if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
14395 DisplayError(_("Bad FEN position in clipboard"), 0);
14398 int savedBlackPlaysFirst = blackPlaysFirst;
14399 EditPositionEvent();
14400 blackPlaysFirst = savedBlackPlaysFirst;
14401 CopyBoard(boards[0], initial_position);
14402 initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
14403 EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
14404 DisplayBothClocks();
14405 DrawPosition(FALSE, boards[currentMove]);
14410 static char cseq[12] = "\\ ";
14412 Boolean set_cont_sequence(char *new_seq)
14417 // handle bad attempts to set the sequence
14419 return 0; // acceptable error - no debug
14421 len = strlen(new_seq);
14422 ret = (len > 0) && (len < sizeof(cseq));
14424 strcpy(cseq, new_seq);
14425 else if (appData.debugMode)
14426 fprintf(debugFP, "Invalid continuation sequence \"%s\" (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
14431 reformat a source message so words don't cross the width boundary. internal
14432 newlines are not removed. returns the wrapped size (no null character unless
14433 included in source message). If dest is NULL, only calculate the size required
14434 for the dest buffer. lp argument indicats line position upon entry, and it's
14435 passed back upon exit.
14437 int wrap(char *dest, char *src, int count, int width, int *lp)
14439 int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
14441 cseq_len = strlen(cseq);
14442 old_line = line = *lp;
14443 ansi = len = clen = 0;
14445 for (i=0; i < count; i++)
14447 if (src[i] == '\033')
14450 // if we hit the width, back up
14451 if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
14453 // store i & len in case the word is too long
14454 old_i = i, old_len = len;
14456 // find the end of the last word
14457 while (i && src[i] != ' ' && src[i] != '\n')
14463 // word too long? restore i & len before splitting it
14464 if ((old_i-i+clen) >= width)
14471 if (i && src[i-1] == ' ')
14474 if (src[i] != ' ' && src[i] != '\n')
14481 // now append the newline and continuation sequence
14486 strncpy(dest+len, cseq, cseq_len);
14494 dest[len] = src[i];
14498 if (src[i] == '\n')
14503 if (dest && appData.debugMode)
14505 fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
14506 count, width, line, len, *lp);
14507 show_bytes(debugFP, src, count);
14508 fprintf(debugFP, "\ndest: ");
14509 show_bytes(debugFP, dest, len);
14510 fprintf(debugFP, "\n");
14512 *lp = dest ? line : old_line;
14517 // [HGM] vari: routines for shelving variations
14520 PushTail(int firstMove, int lastMove)
14522 int i, j, nrMoves = lastMove - firstMove;
14524 if(appData.icsActive) { // only in local mode
14525 forwardMostMove = currentMove; // mimic old ICS behavior
14528 if(storedGames >= MAX_VARIATIONS-1) return;
14530 // push current tail of game on stack
14531 savedResult[storedGames] = gameInfo.result;
14532 savedDetails[storedGames] = gameInfo.resultDetails;
14533 gameInfo.resultDetails = NULL;
14534 savedFirst[storedGames] = firstMove;
14535 savedLast [storedGames] = lastMove;
14536 savedFramePtr[storedGames] = framePtr;
14537 framePtr -= nrMoves; // reserve space for the boards
14538 for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
14539 CopyBoard(boards[framePtr+i], boards[firstMove+i]);
14540 for(j=0; j<MOVE_LEN; j++)
14541 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
14542 for(j=0; j<2*MOVE_LEN; j++)
14543 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
14544 timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
14545 timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
14546 pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
14547 pvInfoList[firstMove+i-1].depth = 0;
14548 commentList[framePtr+i] = commentList[firstMove+i];
14549 commentList[firstMove+i] = NULL;
14553 forwardMostMove = currentMove; // truncte game so we can start variation
14554 if(storedGames == 1) GreyRevert(FALSE);
14558 PopTail(Boolean annotate)
14561 char buf[8000], moveBuf[20];
14563 if(appData.icsActive) return FALSE; // only in local mode
14564 if(!storedGames) return FALSE; // sanity
14567 ToNrEvent(savedFirst[storedGames]); // sets currentMove
14568 nrMoves = savedLast[storedGames] - currentMove;
14571 if(!WhiteOnMove(currentMove)) sprintf(buf, "(%d...", currentMove+2>>1);
14572 else strcpy(buf, "(");
14573 for(i=currentMove; i<forwardMostMove; i++) {
14575 sprintf(moveBuf, " %d. %s", i+2>>1, SavePart(parseList[i]));
14576 else sprintf(moveBuf, " %s", SavePart(parseList[i]));
14577 strcat(buf, moveBuf);
14578 if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
14582 for(i=1; i<nrMoves; i++) { // copy last variation back
14583 CopyBoard(boards[currentMove+i], boards[framePtr+i]);
14584 for(j=0; j<MOVE_LEN; j++)
14585 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
14586 for(j=0; j<2*MOVE_LEN; j++)
14587 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
14588 timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
14589 timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
14590 pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
14591 if(commentList[currentMove+i]) free(commentList[currentMove+i]);
14592 commentList[currentMove+i] = commentList[framePtr+i];
14593 commentList[framePtr+i] = NULL;
14595 if(annotate) AppendComment(currentMove+1, buf, FALSE);
14596 framePtr = savedFramePtr[storedGames];
14597 gameInfo.result = savedResult[storedGames];
14598 if(gameInfo.resultDetails != NULL) {
14599 free(gameInfo.resultDetails);
14601 gameInfo.resultDetails = savedDetails[storedGames];
14602 forwardMostMove = currentMove + nrMoves;
14603 if(storedGames == 0) GreyRevert(TRUE);
14609 { // remove all shelved variations
14611 for(i=0; i<storedGames; i++) {
14612 if(savedDetails[i])
14613 free(savedDetails[i]);
14614 savedDetails[i] = NULL;
14616 for(i=framePtr; i<MAX_MOVES; i++) {
14617 if(commentList[i]) free(commentList[i]);
14618 commentList[i] = NULL;
14620 framePtr = MAX_MOVES-1;