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, 2010, 2011 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)
134 # define T_(s) gettext(s)
147 /* A point in time */
149 long sec; /* Assuming this is >= 32 bits */
150 int ms; /* Assuming this is >= 16 bits */
153 int establish P((void));
154 void read_from_player P((InputSourceRef isr, VOIDSTAR closure,
155 char *buf, int count, int error));
156 void read_from_ics P((InputSourceRef isr, VOIDSTAR closure,
157 char *buf, int count, int error));
158 void ics_printf P((char *format, ...));
159 void SendToICS P((char *s));
160 void SendToICSDelayed P((char *s, long msdelay));
161 void SendMoveToICS P((ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar));
162 void HandleMachineMove P((char *message, ChessProgramState *cps));
163 int AutoPlayOneMove P((void));
164 int LoadGameOneMove P((ChessMove readAhead));
165 int LoadGameFromFile P((char *filename, int n, char *title, int useList));
166 int LoadPositionFromFile P((char *filename, int n, char *title));
167 int SavePositionToFile P((char *filename));
168 void ApplyMove P((int fromX, int fromY, int toX, int toY, int promoChar,
170 void MakeMove P((int fromX, int fromY, int toX, int toY, int promoChar));
171 void ShowMove P((int fromX, int fromY, int toX, int toY));
172 int FinishMove P((ChessMove moveType, int fromX, int fromY, int toX, int toY,
173 /*char*/int promoChar));
174 void BackwardInner P((int target));
175 void ForwardInner P((int target));
176 int Adjudicate P((ChessProgramState *cps));
177 void GameEnds P((ChessMove result, char *resultDetails, int whosays));
178 void EditPositionDone P((Boolean fakeRights));
179 void PrintOpponents P((FILE *fp));
180 void PrintPosition P((FILE *fp, int move));
181 void StartChessProgram P((ChessProgramState *cps));
182 void SendToProgram P((char *message, ChessProgramState *cps));
183 void SendMoveToProgram P((int moveNum, ChessProgramState *cps));
184 void ReceiveFromProgram P((InputSourceRef isr, VOIDSTAR closure,
185 char *buf, int count, int error));
186 void SendTimeControl P((ChessProgramState *cps,
187 int mps, long tc, int inc, int sd, int st));
188 char *TimeControlTagValue P((void));
189 void Attention P((ChessProgramState *cps));
190 void FeedMovesToProgram P((ChessProgramState *cps, int upto));
191 void ResurrectChessProgram P((void));
192 void DisplayComment P((int moveNumber, char *text));
193 void DisplayMove P((int moveNumber));
195 void ParseGameHistory P((char *game));
196 void ParseBoard12 P((char *string));
197 void KeepAlive P((void));
198 void StartClocks P((void));
199 void SwitchClocks P((int nr));
200 void StopClocks P((void));
201 void ResetClocks P((void));
202 char *PGNDate P((void));
203 void SetGameInfo P((void));
204 Boolean ParseFEN P((Board board, int *blackPlaysFirst, char *fen));
205 int RegisterMove P((void));
206 void MakeRegisteredMove P((void));
207 void TruncateGame P((void));
208 int looking_at P((char *, int *, char *));
209 void CopyPlayerNameIntoFileName P((char **, char *));
210 char *SavePart P((char *));
211 int SaveGameOldStyle P((FILE *));
212 int SaveGamePGN P((FILE *));
213 void GetTimeMark P((TimeMark *));
214 long SubtractTimeMarks P((TimeMark *, TimeMark *));
215 int CheckFlags P((void));
216 long NextTickLength P((long));
217 void CheckTimeControl P((void));
218 void show_bytes P((FILE *, char *, int));
219 int string_to_rating P((char *str));
220 void ParseFeatures P((char* args, ChessProgramState *cps));
221 void InitBackEnd3 P((void));
222 void FeatureDone P((ChessProgramState* cps, int val));
223 void InitChessProgram P((ChessProgramState *cps, int setup));
224 void OutputKibitz(int window, char *text);
225 int PerpetualChase(int first, int last);
226 int EngineOutputIsUp();
227 void InitDrawingSizes(int x, int y);
230 extern void ConsoleCreate();
233 ChessProgramState *WhitePlayer();
234 void InsertIntoMemo P((int which, char *text)); // [HGM] kibitz: in engineo.c
235 int VerifyDisplayMode P(());
237 char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment
238 void InitEngineUCI( const char * iniDir, ChessProgramState * cps ); // [HGM] moved here from winboard.c
239 char *ProbeBook P((int moveNr, char *book)); // [HGM] book: returns a book move
240 char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book
241 void ics_update_width P((int new_width));
242 extern char installDir[MSG_SIZ];
243 VariantClass startVariant; /* [HGM] nicks: initial variant */
245 extern int tinyLayout, smallLayout;
246 ChessProgramStats programStats;
247 char lastPV[2][2*MSG_SIZ]; /* [HGM] pv: last PV in thinking output of each engine */
249 static int exiting = 0; /* [HGM] moved to top */
250 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
251 int startedFromPositionFile = FALSE; Board filePosition; /* [HGM] loadPos */
252 Board partnerBoard; /* [HGM] bughouse: for peeking at partner game */
253 int partnerHighlight[2];
254 Boolean partnerBoardValid = 0;
255 char partnerStatus[MSG_SIZ];
257 Boolean originalFlip;
258 Boolean twoBoards = 0;
259 char endingGame = 0; /* [HGM] crash: flag to prevent recursion of GameEnds() */
260 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS */
261 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
262 int lastIndex = 0; /* [HGM] autoinc: last game/position used in match mode */
263 Boolean connectionAlive;/* [HGM] alive: ICS connection status from probing */
264 int opponentKibitzes;
265 int lastSavedGame; /* [HGM] save: ID of game */
266 char chatPartner[MAX_CHAT][MSG_SIZ]; /* [HGM] chat: list of chatting partners */
267 extern int chatCount;
269 char marker[BOARD_RANKS][BOARD_FILES]; /* [HGM] marks for target squares */
270 ChessSquare pieceSweep = EmptySquare;
271 ChessSquare promoSweep = EmptySquare;
273 /* States for ics_getting_history */
275 #define H_REQUESTED 1
276 #define H_GOT_REQ_HEADER 2
277 #define H_GOT_UNREQ_HEADER 3
278 #define H_GETTING_MOVES 4
279 #define H_GOT_UNWANTED_HEADER 5
281 /* whosays values for GameEnds */
290 /* Maximum number of games in a cmail message */
291 #define CMAIL_MAX_GAMES 20
293 /* Different types of move when calling RegisterMove */
295 #define CMAIL_RESIGN 1
297 #define CMAIL_ACCEPT 3
299 /* Different types of result to remember for each game */
300 #define CMAIL_NOT_RESULT 0
301 #define CMAIL_OLD_RESULT 1
302 #define CMAIL_NEW_RESULT 2
304 /* Telnet protocol constants */
315 safeStrCpy( char *dst, const char *src, size_t count )
318 assert( dst != NULL );
319 assert( src != NULL );
322 for(i=0; i<count; i++) if((dst[i] = src[i]) == NULLCHAR) break;
323 if( i == count && dst[count-1] != NULLCHAR)
325 dst[ count-1 ] = '\0'; // make sure incomplete copy still null-terminated
326 if(appData.debugMode)
327 fprintf(debugFP, "safeStrCpy: copying %s into %s didn't work, not enough space %d\n",src,dst,count);
333 /* Some compiler can't cast u64 to double
334 * This function do the job for us:
336 * We use the highest bit for cast, this only
337 * works if the highest bit is not
338 * in use (This should not happen)
340 * We used this for all compiler
343 u64ToDouble(u64 value)
346 u64 tmp = value & u64Const(0x7fffffffffffffff);
347 r = (double)(s64)tmp;
348 if (value & u64Const(0x8000000000000000))
349 r += 9.2233720368547758080e18; /* 2^63 */
353 /* Fake up flags for now, as we aren't keeping track of castling
354 availability yet. [HGM] Change of logic: the flag now only
355 indicates the type of castlings allowed by the rule of the game.
356 The actual rights themselves are maintained in the array
357 castlingRights, as part of the game history, and are not probed
363 int flags = F_ALL_CASTLE_OK;
364 if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
365 switch (gameInfo.variant) {
367 flags &= ~F_ALL_CASTLE_OK;
368 case VariantGiveaway: // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
369 flags |= F_IGNORE_CHECK;
371 flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
374 flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
376 case VariantKriegspiel:
377 flags |= F_KRIEGSPIEL_CAPTURE;
379 case VariantCapaRandom:
380 case VariantFischeRandom:
381 flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
382 case VariantNoCastle:
383 case VariantShatranj:
386 flags &= ~F_ALL_CASTLE_OK;
394 FILE *gameFileFP, *debugFP;
397 [AS] Note: sometimes, the sscanf() function is used to parse the input
398 into a fixed-size buffer. Because of this, we must be prepared to
399 receive strings as long as the size of the input buffer, which is currently
400 set to 4K for Windows and 8K for the rest.
401 So, we must either allocate sufficiently large buffers here, or
402 reduce the size of the input buffer in the input reading part.
405 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
406 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
407 char thinkOutput1[MSG_SIZ*10];
409 ChessProgramState first, second;
411 /* premove variables */
414 int premoveFromX = 0;
415 int premoveFromY = 0;
416 int premovePromoChar = 0;
418 Boolean alarmSounded;
419 /* end premove variables */
421 char *ics_prefix = "$";
422 int ics_type = ICS_GENERIC;
424 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
425 int pauseExamForwardMostMove = 0;
426 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
427 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
428 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
429 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
430 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
431 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
432 int whiteFlag = FALSE, blackFlag = FALSE;
433 int userOfferedDraw = FALSE;
434 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
435 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
436 int cmailMoveType[CMAIL_MAX_GAMES];
437 long ics_clock_paused = 0;
438 ProcRef icsPR = NoProc, cmailPR = NoProc;
439 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
440 GameMode gameMode = BeginningOfGame;
441 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
442 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
443 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
444 int hiddenThinkOutputState = 0; /* [AS] */
445 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
446 int adjudicateLossPlies = 6;
447 char white_holding[64], black_holding[64];
448 TimeMark lastNodeCountTime;
449 long lastNodeCount=0;
450 int shiftKey; // [HGM] set by mouse handler
452 int have_sent_ICS_logon = 0;
454 int suddenDeath, whiteStartMove, blackStartMove; /* [HGM] for implementation of 'any per time' sessions, as in first part of byoyomi TC */
455 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement, lastWhite, lastBlack;
456 long timeControl_2; /* [AS] Allow separate time controls */
457 char *fullTimeControlString = NULL, *nextSession, *whiteTC, *blackTC; /* [HGM] secondary TC: merge of MPS, TC and inc */
458 long timeRemaining[2][MAX_MOVES];
460 TimeMark programStartTime;
461 char ics_handle[MSG_SIZ];
462 int have_set_title = 0;
464 /* animateTraining preserves the state of appData.animate
465 * when Training mode is activated. This allows the
466 * response to be animated when appData.animate == TRUE and
467 * appData.animateDragging == TRUE.
469 Boolean animateTraining;
475 Board boards[MAX_MOVES];
476 /* [HGM] Following 7 needed for accurate legality tests: */
477 signed char castlingRank[BOARD_FILES]; // and corresponding ranks
478 signed char initialRights[BOARD_FILES];
479 int nrCastlingRights; // For TwoKings, or to implement castling-unknown status
480 int initialRulePlies, FENrulePlies;
481 FILE *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
484 int mute; // mute all sounds
486 // [HGM] vari: next 12 to save and restore variations
487 #define MAX_VARIATIONS 10
488 int framePtr = MAX_MOVES-1; // points to free stack entry
490 int savedFirst[MAX_VARIATIONS];
491 int savedLast[MAX_VARIATIONS];
492 int savedFramePtr[MAX_VARIATIONS];
493 char *savedDetails[MAX_VARIATIONS];
494 ChessMove savedResult[MAX_VARIATIONS];
496 void PushTail P((int firstMove, int lastMove));
497 Boolean PopTail P((Boolean annotate));
498 void CleanupTail P((void));
500 ChessSquare FIDEArray[2][BOARD_FILES] = {
501 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
502 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
503 { BlackRook, BlackKnight, BlackBishop, BlackQueen,
504 BlackKing, BlackBishop, BlackKnight, BlackRook }
507 ChessSquare twoKingsArray[2][BOARD_FILES] = {
508 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
509 WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
510 { BlackRook, BlackKnight, BlackBishop, BlackQueen,
511 BlackKing, BlackKing, BlackKnight, BlackRook }
514 ChessSquare KnightmateArray[2][BOARD_FILES] = {
515 { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
516 WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
517 { BlackRook, BlackMan, BlackBishop, BlackQueen,
518 BlackUnicorn, BlackBishop, BlackMan, BlackRook }
521 ChessSquare SpartanArray[2][BOARD_FILES] = {
522 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
523 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
524 { BlackAlfil, BlackMarshall, BlackKing, BlackDragon,
525 BlackDragon, BlackKing, BlackAngel, BlackAlfil }
528 ChessSquare fairyArray[2][BOARD_FILES] = { /* [HGM] Queen side differs from King side */
529 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
530 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
531 { BlackCardinal, BlackAlfil, BlackMarshall, BlackAngel,
532 BlackKing, BlackMarshall, BlackAlfil, BlackCardinal }
535 ChessSquare ShatranjArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
536 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
537 WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
538 { BlackRook, BlackKnight, BlackAlfil, BlackKing,
539 BlackFerz, BlackAlfil, BlackKnight, BlackRook }
542 ChessSquare makrukArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
543 { WhiteRook, WhiteKnight, WhiteMan, WhiteKing,
544 WhiteFerz, WhiteMan, WhiteKnight, WhiteRook },
545 { BlackRook, BlackKnight, BlackMan, BlackFerz,
546 BlackKing, BlackMan, BlackKnight, BlackRook }
550 #if (BOARD_FILES>=10)
551 ChessSquare ShogiArray[2][BOARD_FILES] = {
552 { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
553 WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
554 { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
555 BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
558 ChessSquare XiangqiArray[2][BOARD_FILES] = {
559 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
560 WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
561 { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
562 BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
565 ChessSquare CapablancaArray[2][BOARD_FILES] = {
566 { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
567 WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
568 { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
569 BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
572 ChessSquare GreatArray[2][BOARD_FILES] = {
573 { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
574 WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
575 { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
576 BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
579 ChessSquare JanusArray[2][BOARD_FILES] = {
580 { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
581 WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
582 { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
583 BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
587 ChessSquare GothicArray[2][BOARD_FILES] = {
588 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
589 WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
590 { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
591 BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
594 #define GothicArray CapablancaArray
598 ChessSquare FalconArray[2][BOARD_FILES] = {
599 { WhiteRook, WhiteKnight, WhiteBishop, WhiteLance, WhiteQueen,
600 WhiteKing, WhiteLance, WhiteBishop, WhiteKnight, WhiteRook },
601 { BlackRook, BlackKnight, BlackBishop, BlackLance, BlackQueen,
602 BlackKing, BlackLance, BlackBishop, BlackKnight, BlackRook }
605 #define FalconArray CapablancaArray
608 #else // !(BOARD_FILES>=10)
609 #define XiangqiPosition FIDEArray
610 #define CapablancaArray FIDEArray
611 #define GothicArray FIDEArray
612 #define GreatArray FIDEArray
613 #endif // !(BOARD_FILES>=10)
615 #if (BOARD_FILES>=12)
616 ChessSquare CourierArray[2][BOARD_FILES] = {
617 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
618 WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
619 { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
620 BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
622 #else // !(BOARD_FILES>=12)
623 #define CourierArray CapablancaArray
624 #endif // !(BOARD_FILES>=12)
627 Board initialPosition;
630 /* Convert str to a rating. Checks for special cases of "----",
632 "++++", etc. Also strips ()'s */
634 string_to_rating(str)
637 while(*str && !isdigit(*str)) ++str;
639 return 0; /* One of the special "no rating" cases */
647 /* Init programStats */
648 programStats.movelist[0] = 0;
649 programStats.depth = 0;
650 programStats.nr_moves = 0;
651 programStats.moves_left = 0;
652 programStats.nodes = 0;
653 programStats.time = -1; // [HGM] PGNtime: make invalid to recognize engine output
654 programStats.score = 0;
655 programStats.got_only_move = 0;
656 programStats.got_fail = 0;
657 programStats.line_is_book = 0;
663 int matched, min, sec;
665 ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
666 startVariant = StringToVariant(appData.variant); // [HGM] nicks: remember original variant
668 GetTimeMark(&programStartTime);
669 srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
672 programStats.ok_to_send = 1;
673 programStats.seen_stat = 0;
676 * Initialize game list
682 * Internet chess server status
684 if (appData.icsActive) {
685 appData.matchMode = FALSE;
686 appData.matchGames = 0;
688 appData.noChessProgram = !appData.zippyPlay;
690 appData.zippyPlay = FALSE;
691 appData.zippyTalk = FALSE;
692 appData.noChessProgram = TRUE;
694 if (*appData.icsHelper != NULLCHAR) {
695 appData.useTelnet = TRUE;
696 appData.telnetProgram = appData.icsHelper;
699 appData.zippyTalk = appData.zippyPlay = FALSE;
702 /* [AS] Initialize pv info list [HGM] and game state */
706 for( i=0; i<=framePtr; i++ ) {
707 pvInfoList[i].depth = -1;
708 boards[i][EP_STATUS] = EP_NONE;
709 for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
714 * Parse timeControl resource
716 if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
717 appData.movesPerSession)) {
719 snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
720 DisplayFatalError(buf, 0, 2);
724 * Parse searchTime resource
726 if (*appData.searchTime != NULLCHAR) {
727 matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
729 searchTime = min * 60;
730 } else if (matched == 2) {
731 searchTime = min * 60 + sec;
734 snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
735 DisplayFatalError(buf, 0, 2);
739 /* [AS] Adjudication threshold */
740 adjudicateLossThreshold = appData.adjudicateLossThreshold;
742 first.which = "first";
743 second.which = "second";
744 first.maybeThinking = second.maybeThinking = FALSE;
745 first.pr = second.pr = NoProc;
746 first.isr = second.isr = NULL;
747 first.sendTime = second.sendTime = 2;
748 first.sendDrawOffers = 1;
749 if (appData.firstPlaysBlack) {
750 first.twoMachinesColor = "black\n";
751 second.twoMachinesColor = "white\n";
753 first.twoMachinesColor = "white\n";
754 second.twoMachinesColor = "black\n";
756 first.program = appData.firstChessProgram;
757 second.program = appData.secondChessProgram;
758 first.host = appData.firstHost;
759 second.host = appData.secondHost;
760 first.dir = appData.firstDirectory;
761 second.dir = appData.secondDirectory;
762 first.other = &second;
763 second.other = &first;
764 first.initString = appData.initString;
765 second.initString = appData.secondInitString;
766 first.computerString = appData.firstComputerString;
767 second.computerString = appData.secondComputerString;
768 first.useSigint = second.useSigint = TRUE;
769 first.useSigterm = second.useSigterm = TRUE;
770 first.reuse = appData.reuseFirst;
771 second.reuse = appData.reuseSecond;
772 first.nps = appData.firstNPS; // [HGM] nps: copy nodes per second
773 second.nps = appData.secondNPS;
774 first.useSetboard = second.useSetboard = FALSE;
775 first.useSAN = second.useSAN = FALSE;
776 first.usePing = second.usePing = FALSE;
777 first.lastPing = second.lastPing = 0;
778 first.lastPong = second.lastPong = 0;
779 first.usePlayother = second.usePlayother = FALSE;
780 first.useColors = second.useColors = TRUE;
781 first.useUsermove = second.useUsermove = FALSE;
782 first.sendICS = second.sendICS = FALSE;
783 first.sendName = second.sendName = appData.icsActive;
784 first.sdKludge = second.sdKludge = FALSE;
785 first.stKludge = second.stKludge = FALSE;
786 TidyProgramName(first.program, first.host, first.tidy);
787 TidyProgramName(second.program, second.host, second.tidy);
788 first.matchWins = second.matchWins = 0;
789 safeStrCpy(first.variants, appData.variant, sizeof(first.variants)/sizeof(first.variants[0]));
790 safeStrCpy(second.variants, appData.variant,sizeof(second.variants)/sizeof(second.variants[0]));
791 first.analysisSupport = second.analysisSupport = 2; /* detect */
792 first.analyzing = second.analyzing = FALSE;
793 first.initDone = second.initDone = FALSE;
795 /* New features added by Tord: */
796 first.useFEN960 = FALSE; second.useFEN960 = FALSE;
797 first.useOOCastle = TRUE; second.useOOCastle = TRUE;
798 /* End of new features added by Tord. */
799 first.fenOverride = appData.fenOverride1;
800 second.fenOverride = appData.fenOverride2;
802 /* [HGM] time odds: set factor for each machine */
803 first.timeOdds = appData.firstTimeOdds;
804 second.timeOdds = appData.secondTimeOdds;
806 if(appData.timeOddsMode) {
807 norm = first.timeOdds;
808 if(norm > second.timeOdds) norm = second.timeOdds;
810 first.timeOdds /= norm;
811 second.timeOdds /= norm;
814 /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
815 first.accumulateTC = appData.firstAccumulateTC;
816 second.accumulateTC = appData.secondAccumulateTC;
817 first.maxNrOfSessions = second.maxNrOfSessions = 1;
820 first.debug = second.debug = FALSE;
821 first.supportsNPS = second.supportsNPS = UNKNOWN;
824 first.optionSettings = appData.firstOptions;
825 second.optionSettings = appData.secondOptions;
827 first.scoreIsAbsolute = appData.firstScoreIsAbsolute; /* [AS] */
828 second.scoreIsAbsolute = appData.secondScoreIsAbsolute; /* [AS] */
829 first.isUCI = appData.firstIsUCI; /* [AS] */
830 second.isUCI = appData.secondIsUCI; /* [AS] */
831 first.hasOwnBookUCI = appData.firstHasOwnBookUCI; /* [AS] */
832 second.hasOwnBookUCI = appData.secondHasOwnBookUCI; /* [AS] */
834 if (appData.firstProtocolVersion > PROTOVER
835 || appData.firstProtocolVersion < 1)
840 len = snprintf(buf, MSG_SIZ, _("protocol version %d not supported"),
841 appData.firstProtocolVersion);
842 if( (len > MSG_SIZ) && appData.debugMode )
843 fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
845 DisplayFatalError(buf, 0, 2);
849 first.protocolVersion = appData.firstProtocolVersion;
852 if (appData.secondProtocolVersion > PROTOVER
853 || appData.secondProtocolVersion < 1)
858 len = snprintf(buf, MSG_SIZ, _("protocol version %d not supported"),
859 appData.secondProtocolVersion);
860 if( (len > MSG_SIZ) && appData.debugMode )
861 fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
863 DisplayFatalError(buf, 0, 2);
867 second.protocolVersion = appData.secondProtocolVersion;
870 if (appData.icsActive) {
871 appData.clockMode = TRUE; /* changes dynamically in ICS mode */
872 // } else if (*appData.searchTime != NULLCHAR || appData.noChessProgram) {
873 } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
874 appData.clockMode = FALSE;
875 first.sendTime = second.sendTime = 0;
879 /* Override some settings from environment variables, for backward
880 compatibility. Unfortunately it's not feasible to have the env
881 vars just set defaults, at least in xboard. Ugh.
883 if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
888 if (appData.noChessProgram) {
889 programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
890 sprintf(programVersion, "%s", PACKAGE_STRING);
892 /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
893 programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
894 sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
897 if (!appData.icsActive) {
901 /* Check for variants that are supported only in ICS mode,
902 or not at all. Some that are accepted here nevertheless
903 have bugs; see comments below.
905 VariantClass variant = StringToVariant(appData.variant);
907 case VariantBughouse: /* need four players and two boards */
908 case VariantKriegspiel: /* need to hide pieces and move details */
909 /* case VariantFischeRandom: (Fabien: moved below) */
910 len = snprintf(buf,MSG_SIZ, _("Variant %s supported only in ICS mode"), appData.variant);
911 if( (len > MSG_SIZ) && appData.debugMode )
912 fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
914 DisplayFatalError(buf, 0, 2);
918 case VariantLoadable:
928 len = snprintf(buf, MSG_SIZ, _("Unknown variant name %s"), appData.variant);
929 if( (len > MSG_SIZ) && appData.debugMode )
930 fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
932 DisplayFatalError(buf, 0, 2);
935 case VariantXiangqi: /* [HGM] repetition rules not implemented */
936 case VariantFairy: /* [HGM] TestLegality definitely off! */
937 case VariantGothic: /* [HGM] should work */
938 case VariantCapablanca: /* [HGM] should work */
939 case VariantCourier: /* [HGM] initial forced moves not implemented */
940 case VariantShogi: /* [HGM] could still mate with pawn drop */
941 case VariantKnightmate: /* [HGM] should work */
942 case VariantCylinder: /* [HGM] untested */
943 case VariantFalcon: /* [HGM] untested */
944 case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
945 offboard interposition not understood */
946 case VariantNormal: /* definitely works! */
947 case VariantWildCastle: /* pieces not automatically shuffled */
948 case VariantNoCastle: /* pieces not automatically shuffled */
949 case VariantFischeRandom: /* [HGM] works and shuffles pieces */
950 case VariantLosers: /* should work except for win condition,
951 and doesn't know captures are mandatory */
952 case VariantSuicide: /* should work except for win condition,
953 and doesn't know captures are mandatory */
954 case VariantGiveaway: /* should work except for win condition,
955 and doesn't know captures are mandatory */
956 case VariantTwoKings: /* should work */
957 case VariantAtomic: /* should work except for win condition */
958 case Variant3Check: /* should work except for win condition */
959 case VariantShatranj: /* should work except for all win conditions */
960 case VariantMakruk: /* should work except for daw countdown */
961 case VariantBerolina: /* might work if TestLegality is off */
962 case VariantCapaRandom: /* should work */
963 case VariantJanus: /* should work */
964 case VariantSuper: /* experimental */
965 case VariantGreat: /* experimental, requires legality testing to be off */
966 case VariantSChess: /* S-Chess, should work */
967 case VariantSpartan: /* should work */
972 InitEngineUCI( installDir, &first ); // [HGM] moved here from winboard.c, to make available in xboard
973 InitEngineUCI( installDir, &second );
976 int NextIntegerFromString( char ** str, long * value )
981 while( *s == ' ' || *s == '\t' ) {
987 if( *s >= '0' && *s <= '9' ) {
988 while( *s >= '0' && *s <= '9' ) {
989 *value = *value * 10 + (*s - '0');
1001 int NextTimeControlFromString( char ** str, long * value )
1004 int result = NextIntegerFromString( str, &temp );
1007 *value = temp * 60; /* Minutes */
1008 if( **str == ':' ) {
1010 result = NextIntegerFromString( str, &temp );
1011 *value += temp; /* Seconds */
1018 int NextSessionFromString( char ** str, int *moves, long * tc, long *inc, int *incType)
1019 { /* [HGM] routine added to read '+moves/time' for secondary time control. */
1020 int result = -1, type = 0; long temp, temp2;
1022 if(**str != ':') return -1; // old params remain in force!
1024 if(**str == '*') type = *(*str)++, temp = 0; // sandclock TC
1025 if( NextIntegerFromString( str, &temp ) ) return -1;
1026 if(type) { *moves = 0; *tc = temp * 500; *inc = temp * 1000; *incType = '*'; return 0; }
1029 /* time only: incremental or sudden-death time control */
1030 if(**str == '+') { /* increment follows; read it */
1032 if(**str == '!') type = *(*str)++; // Bronstein TC
1033 if(result = NextIntegerFromString( str, &temp2)) return -1;
1034 *inc = temp2 * 1000;
1035 if(**str == '.') { // read fraction of increment
1036 char *start = ++(*str);
1037 if(result = NextIntegerFromString( str, &temp2)) return -1;
1039 while(start++ < *str) temp2 /= 10;
1043 *moves = 0; *tc = temp * 1000; *incType = type;
1047 (*str)++; /* classical time control */
1048 result = NextIntegerFromString( str, &temp2); // NOTE: already converted to seconds by ParseTimeControl()
1059 int GetTimeQuota(int movenr, int lastUsed, char *tcString)
1060 { /* [HGM] get time to add from the multi-session time-control string */
1061 int incType, moves=1; /* kludge to force reading of first session */
1062 long time, increment;
1065 if(!*s) return 0; // empty TC string means we ran out of the last sudden-death version
1066 if(appData.debugMode) fprintf(debugFP, "TC string = '%s'\n", tcString);
1068 if(moves) NextSessionFromString(&s, &moves, &time, &increment, &incType);
1069 nextSession = s; suddenDeath = moves == 0 && increment == 0;
1070 if(appData.debugMode) fprintf(debugFP, "mps=%d tc=%d inc=%d\n", moves, (int) time, (int) increment);
1071 if(movenr == -1) return time; /* last move before new session */
1072 if(incType == '*') increment = 0; else // for sandclock, time is added while not thinking
1073 if(incType == '!' && lastUsed < increment) increment = lastUsed;
1074 if(!moves) return increment; /* current session is incremental */
1075 if(movenr >= 0) movenr -= moves; /* we already finished this session */
1076 } while(movenr >= -1); /* try again for next session */
1078 return 0; // no new time quota on this move
1082 ParseTimeControl(tc, ti, mps)
1089 char buf[MSG_SIZ], buf2[MSG_SIZ], *mytc = tc;
1092 if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1093 if(!strchr(tc, '+') && !strchr(tc, '/') && sscanf(tc, "%d:%d", &min, &sec) >= 1)
1094 sprintf(mytc=buf2, "%d", 60*min+sec); // convert 'classical' min:sec tc string to seconds
1098 snprintf(buf, MSG_SIZ, ":%d/%s+%g", mps, mytc, ti);
1100 snprintf(buf, MSG_SIZ, ":%s+%g", mytc, ti);
1103 snprintf(buf, MSG_SIZ, ":%d/%s", mps, mytc);
1105 snprintf(buf, MSG_SIZ, ":%s", mytc);
1107 fullTimeControlString = StrSave(buf); // this should now be in PGN format
1109 if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1114 /* Parse second time control */
1117 if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1125 timeControl_2 = tc2 * 1000;
1135 timeControl = tc1 * 1000;
1138 timeIncrement = ti * 1000; /* convert to ms */
1139 movesPerSession = 0;
1142 movesPerSession = mps;
1150 if (appData.debugMode) {
1151 fprintf(debugFP, "%s\n", programVersion);
1154 set_cont_sequence(appData.wrapContSeq);
1155 if (appData.matchGames > 0) {
1156 appData.matchMode = TRUE;
1157 } else if (appData.matchMode) {
1158 appData.matchGames = 1;
1160 if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1161 appData.matchGames = appData.sameColorGames;
1162 if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1163 if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1164 if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1167 if (appData.noChessProgram || first.protocolVersion == 1) {
1170 /* kludge: allow timeout for initial "feature" commands */
1172 DisplayMessage("", _("Starting chess program"));
1173 ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1178 InitBackEnd3 P((void))
1180 GameMode initialMode;
1184 InitChessProgram(&first, startedFromSetupPosition);
1186 if(!appData.noChessProgram) { /* [HGM] tidy: redo program version to use name from myname feature */
1187 free(programVersion);
1188 programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1189 sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1192 if (appData.icsActive) {
1194 /* [DM] Make a console window if needed [HGM] merged ifs */
1200 if (*appData.icsCommPort != NULLCHAR)
1201 len = snprintf(buf, MSG_SIZ, _("Could not open comm port %s"),
1202 appData.icsCommPort);
1204 len = snprintf(buf, MSG_SIZ, _("Could not connect to host %s, port %s"),
1205 appData.icsHost, appData.icsPort);
1207 if( (len > MSG_SIZ) && appData.debugMode )
1208 fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1210 DisplayFatalError(buf, err, 1);
1215 AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1217 AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1218 if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1219 ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1220 } else if (appData.noChessProgram) {
1226 if (*appData.cmailGameName != NULLCHAR) {
1228 OpenLoopback(&cmailPR);
1230 AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1234 DisplayMessage("", "");
1235 if (StrCaseCmp(appData.initialMode, "") == 0) {
1236 initialMode = BeginningOfGame;
1237 } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1238 initialMode = TwoMachinesPlay;
1239 } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1240 initialMode = AnalyzeFile;
1241 } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1242 initialMode = AnalyzeMode;
1243 } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1244 initialMode = MachinePlaysWhite;
1245 } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1246 initialMode = MachinePlaysBlack;
1247 } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1248 initialMode = EditGame;
1249 } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1250 initialMode = EditPosition;
1251 } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1252 initialMode = Training;
1254 len = snprintf(buf, MSG_SIZ, _("Unknown initialMode %s"), appData.initialMode);
1255 if( (len > MSG_SIZ) && appData.debugMode )
1256 fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1258 DisplayFatalError(buf, 0, 2);
1262 if (appData.matchMode) {
1263 /* Set up machine vs. machine match */
1264 if (appData.noChessProgram) {
1265 DisplayFatalError(_("Can't have a match with no chess programs"),
1271 if (*appData.loadGameFile != NULLCHAR) {
1272 int index = appData.loadGameIndex; // [HGM] autoinc
1273 if(index<0) lastIndex = index = 1;
1274 if (!LoadGameFromFile(appData.loadGameFile,
1276 appData.loadGameFile, FALSE)) {
1277 DisplayFatalError(_("Bad game file"), 0, 1);
1280 } else if (*appData.loadPositionFile != NULLCHAR) {
1281 int index = appData.loadPositionIndex; // [HGM] autoinc
1282 if(index<0) lastIndex = index = 1;
1283 if (!LoadPositionFromFile(appData.loadPositionFile,
1285 appData.loadPositionFile)) {
1286 DisplayFatalError(_("Bad position file"), 0, 1);
1291 } else if (*appData.cmailGameName != NULLCHAR) {
1292 /* Set up cmail mode */
1293 ReloadCmailMsgEvent(TRUE);
1295 /* Set up other modes */
1296 if (initialMode == AnalyzeFile) {
1297 if (*appData.loadGameFile == NULLCHAR) {
1298 DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1302 if (*appData.loadGameFile != NULLCHAR) {
1303 (void) LoadGameFromFile(appData.loadGameFile,
1304 appData.loadGameIndex,
1305 appData.loadGameFile, TRUE);
1306 } else if (*appData.loadPositionFile != NULLCHAR) {
1307 (void) LoadPositionFromFile(appData.loadPositionFile,
1308 appData.loadPositionIndex,
1309 appData.loadPositionFile);
1310 /* [HGM] try to make self-starting even after FEN load */
1311 /* to allow automatic setup of fairy variants with wtm */
1312 if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1313 gameMode = BeginningOfGame;
1314 setboardSpoiledMachineBlack = 1;
1316 /* [HGM] loadPos: make that every new game uses the setup */
1317 /* from file as long as we do not switch variant */
1318 if(!blackPlaysFirst) {
1319 startedFromPositionFile = TRUE;
1320 CopyBoard(filePosition, boards[0]);
1323 if (initialMode == AnalyzeMode) {
1324 if (appData.noChessProgram) {
1325 DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1328 if (appData.icsActive) {
1329 DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1333 } else if (initialMode == AnalyzeFile) {
1334 appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1335 ShowThinkingEvent();
1337 AnalysisPeriodicEvent(1);
1338 } else if (initialMode == MachinePlaysWhite) {
1339 if (appData.noChessProgram) {
1340 DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1344 if (appData.icsActive) {
1345 DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1349 MachineWhiteEvent();
1350 } else if (initialMode == MachinePlaysBlack) {
1351 if (appData.noChessProgram) {
1352 DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1356 if (appData.icsActive) {
1357 DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1361 MachineBlackEvent();
1362 } else if (initialMode == TwoMachinesPlay) {
1363 if (appData.noChessProgram) {
1364 DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1368 if (appData.icsActive) {
1369 DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1374 } else if (initialMode == EditGame) {
1376 } else if (initialMode == EditPosition) {
1377 EditPositionEvent();
1378 } else if (initialMode == Training) {
1379 if (*appData.loadGameFile == NULLCHAR) {
1380 DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1389 * Establish will establish a contact to a remote host.port.
1390 * Sets icsPR to a ProcRef for a process (or pseudo-process)
1391 * used to talk to the host.
1392 * Returns 0 if okay, error code if not.
1399 if (*appData.icsCommPort != NULLCHAR) {
1400 /* Talk to the host through a serial comm port */
1401 return OpenCommPort(appData.icsCommPort, &icsPR);
1403 } else if (*appData.gateway != NULLCHAR) {
1404 if (*appData.remoteShell == NULLCHAR) {
1405 /* Use the rcmd protocol to run telnet program on a gateway host */
1406 snprintf(buf, sizeof(buf), "%s %s %s",
1407 appData.telnetProgram, appData.icsHost, appData.icsPort);
1408 return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1411 /* Use the rsh program to run telnet program on a gateway host */
1412 if (*appData.remoteUser == NULLCHAR) {
1413 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1414 appData.gateway, appData.telnetProgram,
1415 appData.icsHost, appData.icsPort);
1417 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1418 appData.remoteShell, appData.gateway,
1419 appData.remoteUser, appData.telnetProgram,
1420 appData.icsHost, appData.icsPort);
1422 return StartChildProcess(buf, "", &icsPR);
1425 } else if (appData.useTelnet) {
1426 return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1429 /* TCP socket interface differs somewhat between
1430 Unix and NT; handle details in the front end.
1432 return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1436 void EscapeExpand(char *p, char *q)
1437 { // [HGM] initstring: routine to shape up string arguments
1438 while(*p++ = *q++) if(p[-1] == '\\')
1440 case 'n': p[-1] = '\n'; break;
1441 case 'r': p[-1] = '\r'; break;
1442 case 't': p[-1] = '\t'; break;
1443 case '\\': p[-1] = '\\'; break;
1444 case 0: *p = 0; return;
1445 default: p[-1] = q[-1]; break;
1450 show_bytes(fp, buf, count)
1456 if (*buf < 040 || *(unsigned char *) buf > 0177) {
1457 fprintf(fp, "\\%03o", *buf & 0xff);
1466 /* Returns an errno value */
1468 OutputMaybeTelnet(pr, message, count, outError)
1474 char buf[8192], *p, *q, *buflim;
1475 int left, newcount, outcount;
1477 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1478 *appData.gateway != NULLCHAR) {
1479 if (appData.debugMode) {
1480 fprintf(debugFP, ">ICS: ");
1481 show_bytes(debugFP, message, count);
1482 fprintf(debugFP, "\n");
1484 return OutputToProcess(pr, message, count, outError);
1487 buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1494 if (appData.debugMode) {
1495 fprintf(debugFP, ">ICS: ");
1496 show_bytes(debugFP, buf, newcount);
1497 fprintf(debugFP, "\n");
1499 outcount = OutputToProcess(pr, buf, newcount, outError);
1500 if (outcount < newcount) return -1; /* to be sure */
1507 } else if (((unsigned char) *p) == TN_IAC) {
1508 *q++ = (char) TN_IAC;
1515 if (appData.debugMode) {
1516 fprintf(debugFP, ">ICS: ");
1517 show_bytes(debugFP, buf, newcount);
1518 fprintf(debugFP, "\n");
1520 outcount = OutputToProcess(pr, buf, newcount, outError);
1521 if (outcount < newcount) return -1; /* to be sure */
1526 read_from_player(isr, closure, message, count, error)
1533 int outError, outCount;
1534 static int gotEof = 0;
1536 /* Pass data read from player on to ICS */
1539 outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1540 if (outCount < count) {
1541 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1543 } else if (count < 0) {
1544 RemoveInputSource(isr);
1545 DisplayFatalError(_("Error reading from keyboard"), error, 1);
1546 } else if (gotEof++ > 0) {
1547 RemoveInputSource(isr);
1548 DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1554 { // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1555 if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1556 connectionAlive = FALSE; // only sticks if no response to 'date' command.
1557 SendToICS("date\n");
1558 if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1561 /* added routine for printf style output to ics */
1562 void ics_printf(char *format, ...)
1564 char buffer[MSG_SIZ];
1567 va_start(args, format);
1568 vsnprintf(buffer, sizeof(buffer), format, args);
1569 buffer[sizeof(buffer)-1] = '\0';
1578 int count, outCount, outError;
1580 if (icsPR == NULL) return;
1583 outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1584 if (outCount < count) {
1585 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1589 /* This is used for sending logon scripts to the ICS. Sending
1590 without a delay causes problems when using timestamp on ICC
1591 (at least on my machine). */
1593 SendToICSDelayed(s,msdelay)
1597 int count, outCount, outError;
1599 if (icsPR == NULL) return;
1602 if (appData.debugMode) {
1603 fprintf(debugFP, ">ICS: ");
1604 show_bytes(debugFP, s, count);
1605 fprintf(debugFP, "\n");
1607 outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1609 if (outCount < count) {
1610 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1615 /* Remove all highlighting escape sequences in s
1616 Also deletes any suffix starting with '('
1619 StripHighlightAndTitle(s)
1622 static char retbuf[MSG_SIZ];
1625 while (*s != NULLCHAR) {
1626 while (*s == '\033') {
1627 while (*s != NULLCHAR && !isalpha(*s)) s++;
1628 if (*s != NULLCHAR) s++;
1630 while (*s != NULLCHAR && *s != '\033') {
1631 if (*s == '(' || *s == '[') {
1642 /* Remove all highlighting escape sequences in s */
1647 static char retbuf[MSG_SIZ];
1650 while (*s != NULLCHAR) {
1651 while (*s == '\033') {
1652 while (*s != NULLCHAR && !isalpha(*s)) s++;
1653 if (*s != NULLCHAR) s++;
1655 while (*s != NULLCHAR && *s != '\033') {
1663 char *variantNames[] = VARIANT_NAMES;
1668 return variantNames[v];
1672 /* Identify a variant from the strings the chess servers use or the
1673 PGN Variant tag names we use. */
1680 VariantClass v = VariantNormal;
1681 int i, found = FALSE;
1687 /* [HGM] skip over optional board-size prefixes */
1688 if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1689 sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1690 while( *e++ != '_');
1693 if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
1697 for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
1698 if (StrCaseStr(e, variantNames[i])) {
1699 v = (VariantClass) i;
1706 if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
1707 || StrCaseStr(e, "wild/fr")
1708 || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
1709 v = VariantFischeRandom;
1710 } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
1711 (i = 1, p = StrCaseStr(e, "w"))) {
1713 while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
1720 case 0: /* FICS only, actually */
1722 /* Castling legal even if K starts on d-file */
1723 v = VariantWildCastle;
1728 /* Castling illegal even if K & R happen to start in
1729 normal positions. */
1730 v = VariantNoCastle;
1743 /* Castling legal iff K & R start in normal positions */
1749 /* Special wilds for position setup; unclear what to do here */
1750 v = VariantLoadable;
1753 /* Bizarre ICC game */
1754 v = VariantTwoKings;
1757 v = VariantKriegspiel;
1763 v = VariantFischeRandom;
1766 v = VariantCrazyhouse;
1769 v = VariantBughouse;
1775 /* Not quite the same as FICS suicide! */
1776 v = VariantGiveaway;
1782 v = VariantShatranj;
1785 /* Temporary names for future ICC types. The name *will* change in
1786 the next xboard/WinBoard release after ICC defines it. */
1824 v = VariantCapablanca;
1827 v = VariantKnightmate;
1833 v = VariantCylinder;
1839 v = VariantCapaRandom;
1842 v = VariantBerolina;
1854 /* Found "wild" or "w" in the string but no number;
1855 must assume it's normal chess. */
1859 len = snprintf(buf, MSG_SIZ, _("Unknown wild type %d"), wnum);
1860 if( (len > MSG_SIZ) && appData.debugMode )
1861 fprintf(debugFP, "StringToVariant: buffer truncated.\n");
1863 DisplayError(buf, 0);
1869 if (appData.debugMode) {
1870 fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
1871 e, wnum, VariantName(v));
1876 static int leftover_start = 0, leftover_len = 0;
1877 char star_match[STAR_MATCH_N][MSG_SIZ];
1879 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
1880 advance *index beyond it, and set leftover_start to the new value of
1881 *index; else return FALSE. If pattern contains the character '*', it
1882 matches any sequence of characters not containing '\r', '\n', or the
1883 character following the '*' (if any), and the matched sequence(s) are
1884 copied into star_match.
1887 looking_at(buf, index, pattern)
1892 char *bufp = &buf[*index], *patternp = pattern;
1894 char *matchp = star_match[0];
1897 if (*patternp == NULLCHAR) {
1898 *index = leftover_start = bufp - buf;
1902 if (*bufp == NULLCHAR) return FALSE;
1903 if (*patternp == '*') {
1904 if (*bufp == *(patternp + 1)) {
1906 matchp = star_match[++star_count];
1910 } else if (*bufp == '\n' || *bufp == '\r') {
1912 if (*patternp == NULLCHAR)
1917 *matchp++ = *bufp++;
1921 if (*patternp != *bufp) return FALSE;
1928 SendToPlayer(data, length)
1932 int error, outCount;
1933 outCount = OutputToProcess(NoProc, data, length, &error);
1934 if (outCount < length) {
1935 DisplayFatalError(_("Error writing to display"), error, 1);
1940 PackHolding(packed, holding)
1952 switch (runlength) {
1963 sprintf(q, "%d", runlength);
1975 /* Telnet protocol requests from the front end */
1977 TelnetRequest(ddww, option)
1978 unsigned char ddww, option;
1980 unsigned char msg[3];
1981 int outCount, outError;
1983 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
1985 if (appData.debugMode) {
1986 char buf1[8], buf2[8], *ddwwStr, *optionStr;
2002 snprintf(buf1,sizeof(buf1)/sizeof(buf1[0]), "%d", ddww);
2011 snprintf(buf2,sizeof(buf2)/sizeof(buf2[0]), "%d", option);
2014 fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
2019 outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
2021 DisplayFatalError(_("Error writing to ICS"), outError, 1);
2028 if (!appData.icsActive) return;
2029 TelnetRequest(TN_DO, TN_ECHO);
2035 if (!appData.icsActive) return;
2036 TelnetRequest(TN_DONT, TN_ECHO);
2040 CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece)
2042 /* put the holdings sent to us by the server on the board holdings area */
2043 int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2047 if(gameInfo.holdingsWidth < 2) return;
2048 if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2049 return; // prevent overwriting by pre-board holdings
2051 if( (int)lowestPiece >= BlackPawn ) {
2054 holdingsStartRow = BOARD_HEIGHT-1;
2057 holdingsColumn = BOARD_WIDTH-1;
2058 countsColumn = BOARD_WIDTH-2;
2059 holdingsStartRow = 0;
2063 for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
2064 board[i][holdingsColumn] = EmptySquare;
2065 board[i][countsColumn] = (ChessSquare) 0;
2067 while( (p=*holdings++) != NULLCHAR ) {
2068 piece = CharToPiece( ToUpper(p) );
2069 if(piece == EmptySquare) continue;
2070 /*j = (int) piece - (int) WhitePawn;*/
2071 j = PieceToNumber(piece);
2072 if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2073 if(j < 0) continue; /* should not happen */
2074 piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2075 board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2076 board[holdingsStartRow+j*direction][countsColumn]++;
2082 VariantSwitch(Board board, VariantClass newVariant)
2084 int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2085 static Board oldBoard;
2087 startedFromPositionFile = FALSE;
2088 if(gameInfo.variant == newVariant) return;
2090 /* [HGM] This routine is called each time an assignment is made to
2091 * gameInfo.variant during a game, to make sure the board sizes
2092 * are set to match the new variant. If that means adding or deleting
2093 * holdings, we shift the playing board accordingly
2094 * This kludge is needed because in ICS observe mode, we get boards
2095 * of an ongoing game without knowing the variant, and learn about the
2096 * latter only later. This can be because of the move list we requested,
2097 * in which case the game history is refilled from the beginning anyway,
2098 * but also when receiving holdings of a crazyhouse game. In the latter
2099 * case we want to add those holdings to the already received position.
2103 if (appData.debugMode) {
2104 fprintf(debugFP, "Switch board from %s to %s\n",
2105 VariantName(gameInfo.variant), VariantName(newVariant));
2106 setbuf(debugFP, NULL);
2108 shuffleOpenings = 0; /* [HGM] shuffle */
2109 gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2113 newWidth = 9; newHeight = 9;
2114 gameInfo.holdingsSize = 7;
2115 case VariantBughouse:
2116 case VariantCrazyhouse:
2117 newHoldingsWidth = 2; break;
2121 newHoldingsWidth = 2;
2122 gameInfo.holdingsSize = 8;
2125 case VariantCapablanca:
2126 case VariantCapaRandom:
2129 newHoldingsWidth = gameInfo.holdingsSize = 0;
2132 if(newWidth != gameInfo.boardWidth ||
2133 newHeight != gameInfo.boardHeight ||
2134 newHoldingsWidth != gameInfo.holdingsWidth ) {
2136 /* shift position to new playing area, if needed */
2137 if(newHoldingsWidth > gameInfo.holdingsWidth) {
2138 for(i=0; i<BOARD_HEIGHT; i++)
2139 for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2140 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2142 for(i=0; i<newHeight; i++) {
2143 board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2144 board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2146 } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2147 for(i=0; i<BOARD_HEIGHT; i++)
2148 for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2149 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2152 gameInfo.boardWidth = newWidth;
2153 gameInfo.boardHeight = newHeight;
2154 gameInfo.holdingsWidth = newHoldingsWidth;
2155 gameInfo.variant = newVariant;
2156 InitDrawingSizes(-2, 0);
2157 } else gameInfo.variant = newVariant;
2158 CopyBoard(oldBoard, board); // remember correctly formatted board
2159 InitPosition(FALSE); /* this sets up board[0], but also other stuff */
2160 DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2163 static int loggedOn = FALSE;
2165 /*-- Game start info cache: --*/
2167 char gs_kind[MSG_SIZ];
2168 static char player1Name[128] = "";
2169 static char player2Name[128] = "";
2170 static char cont_seq[] = "\n\\ ";
2171 static int player1Rating = -1;
2172 static int player2Rating = -1;
2173 /*----------------------------*/
2175 ColorClass curColor = ColorNormal;
2176 int suppressKibitz = 0;
2179 Boolean soughtPending = FALSE;
2180 Boolean seekGraphUp;
2181 #define MAX_SEEK_ADS 200
2183 char *seekAdList[MAX_SEEK_ADS];
2184 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2185 float tcList[MAX_SEEK_ADS];
2186 char colorList[MAX_SEEK_ADS];
2187 int nrOfSeekAds = 0;
2188 int minRating = 1010, maxRating = 2800;
2189 int hMargin = 10, vMargin = 20, h, w;
2190 extern int squareSize, lineGap;
2195 int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2196 xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2197 if(r < minRating+100 && r >=0 ) r = minRating+100;
2198 if(r > maxRating) r = maxRating;
2199 if(tc < 1.) tc = 1.;
2200 if(tc > 95.) tc = 95.;
2201 x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2202 y = ((double)r - minRating)/(maxRating - minRating)
2203 * (h-vMargin-squareSize/8-1) + vMargin;
2204 if(ratingList[i] < 0) y = vMargin + squareSize/4;
2205 if(strstr(seekAdList[i], " u ")) color = 1;
2206 if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2207 !strstr(seekAdList[i], "bullet") &&
2208 !strstr(seekAdList[i], "blitz") &&
2209 !strstr(seekAdList[i], "standard") ) color = 2;
2210 if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2211 DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2215 AddAd(char *handle, char *rating, int base, int inc, char rated, char *type, int nr, Boolean plot)
2217 char buf[MSG_SIZ], *ext = "";
2218 VariantClass v = StringToVariant(type);
2219 if(strstr(type, "wild")) {
2220 ext = type + 4; // append wild number
2221 if(v == VariantFischeRandom) type = "chess960"; else
2222 if(v == VariantLoadable) type = "setup"; else
2223 type = VariantName(v);
2225 snprintf(buf, MSG_SIZ, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2226 if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2227 if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2228 ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2229 sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2230 tcList[nrOfSeekAds] = base + (2./3.)*inc;
2231 seekNrList[nrOfSeekAds] = nr;
2232 zList[nrOfSeekAds] = 0;
2233 seekAdList[nrOfSeekAds++] = StrSave(buf);
2234 if(plot) PlotSeekAd(nrOfSeekAds-1);
2241 int x = xList[i], y = yList[i], d=squareSize/4, k;
2242 DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2243 if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2244 // now replot every dot that overlapped
2245 for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2246 int xx = xList[k], yy = yList[k];
2247 if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2248 DrawSeekDot(xx, yy, colorList[k]);
2253 RemoveSeekAd(int nr)
2256 for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2258 if(seekAdList[i]) free(seekAdList[i]);
2259 seekAdList[i] = seekAdList[--nrOfSeekAds];
2260 seekNrList[i] = seekNrList[nrOfSeekAds];
2261 ratingList[i] = ratingList[nrOfSeekAds];
2262 colorList[i] = colorList[nrOfSeekAds];
2263 tcList[i] = tcList[nrOfSeekAds];
2264 xList[i] = xList[nrOfSeekAds];
2265 yList[i] = yList[nrOfSeekAds];
2266 zList[i] = zList[nrOfSeekAds];
2267 seekAdList[nrOfSeekAds] = NULL;
2273 MatchSoughtLine(char *line)
2275 char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2276 int nr, base, inc, u=0; char dummy;
2278 if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2279 sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2281 (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2282 sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7) ) {
2283 // match: compact and save the line
2284 AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2294 if(!seekGraphUp) return FALSE;
2295 h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
2296 w = BOARD_WIDTH * (squareSize + lineGap) + lineGap;
2298 DrawSeekBackground(0, 0, w, h);
2299 DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2300 DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2301 for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2302 int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2304 DrawSeekAxis(hMargin+5*(i%500==0), yy, hMargin-5, yy); // rating ticks
2307 snprintf(buf, MSG_SIZ, "%d", i);
2308 DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2311 DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2312 for(i=1; i<100; i+=(i<10?1:5)) {
2313 int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2314 DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2315 if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2317 snprintf(buf, MSG_SIZ, "%d", i);
2318 DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2321 for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2325 int SeekGraphClick(ClickType click, int x, int y, int moving)
2327 static int lastDown = 0, displayed = 0, lastSecond;
2328 if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2329 if(click == Release || moving) return FALSE;
2331 soughtPending = TRUE;
2332 SendToICS(ics_prefix);
2333 SendToICS("sought\n"); // should this be "sought all"?
2334 } else { // issue challenge based on clicked ad
2335 int dist = 10000; int i, closest = 0, second = 0;
2336 for(i=0; i<nrOfSeekAds; i++) {
2337 int d = (x-xList[i])*(x-xList[i]) + (y-yList[i])*(y-yList[i]) + zList[i];
2338 if(d < dist) { dist = d; closest = i; }
2339 second += (d - zList[i] < 120); // count in-range ads
2340 if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2344 second = (second > 1);
2345 if(displayed != closest || second != lastSecond) {
2346 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2347 lastSecond = second; displayed = closest;
2349 if(click == Press) {
2350 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2353 } // on press 'hit', only show info
2354 if(moving == 2) return TRUE; // ignore right up-clicks on dot
2355 snprintf(buf, MSG_SIZ, "play %d\n", seekNrList[closest]);
2356 SendToICS(ics_prefix);
2358 return TRUE; // let incoming board of started game pop down the graph
2359 } else if(click == Release) { // release 'miss' is ignored
2360 zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2361 if(moving == 2) { // right up-click
2362 nrOfSeekAds = 0; // refresh graph
2363 soughtPending = TRUE;
2364 SendToICS(ics_prefix);
2365 SendToICS("sought\n"); // should this be "sought all"?
2368 } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2369 // press miss or release hit 'pop down' seek graph
2370 seekGraphUp = FALSE;
2371 DrawPosition(TRUE, NULL);
2377 read_from_ics(isr, closure, data, count, error)
2384 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2385 #define STARTED_NONE 0
2386 #define STARTED_MOVES 1
2387 #define STARTED_BOARD 2
2388 #define STARTED_OBSERVE 3
2389 #define STARTED_HOLDINGS 4
2390 #define STARTED_CHATTER 5
2391 #define STARTED_COMMENT 6
2392 #define STARTED_MOVES_NOHIDE 7
2394 static int started = STARTED_NONE;
2395 static char parse[20000];
2396 static int parse_pos = 0;
2397 static char buf[BUF_SIZE + 1];
2398 static int firstTime = TRUE, intfSet = FALSE;
2399 static ColorClass prevColor = ColorNormal;
2400 static int savingComment = FALSE;
2401 static int cmatch = 0; // continuation sequence match
2408 int backup; /* [DM] For zippy color lines */
2410 char talker[MSG_SIZ]; // [HGM] chat
2413 connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2415 if (appData.debugMode) {
2417 fprintf(debugFP, "<ICS: ");
2418 show_bytes(debugFP, data, count);
2419 fprintf(debugFP, "\n");
2423 if (appData.debugMode) { int f = forwardMostMove;
2424 fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2425 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2426 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2429 /* If last read ended with a partial line that we couldn't parse,
2430 prepend it to the new read and try again. */
2431 if (leftover_len > 0) {
2432 for (i=0; i<leftover_len; i++)
2433 buf[i] = buf[leftover_start + i];
2436 /* copy new characters into the buffer */
2437 bp = buf + leftover_len;
2438 buf_len=leftover_len;
2439 for (i=0; i<count; i++)
2442 if (data[i] == '\r')
2445 // join lines split by ICS?
2446 if (!appData.noJoin)
2449 Joining just consists of finding matches against the
2450 continuation sequence, and discarding that sequence
2451 if found instead of copying it. So, until a match
2452 fails, there's nothing to do since it might be the
2453 complete sequence, and thus, something we don't want
2456 if (data[i] == cont_seq[cmatch])
2459 if (cmatch == strlen(cont_seq))
2461 cmatch = 0; // complete match. just reset the counter
2464 it's possible for the ICS to not include the space
2465 at the end of the last word, making our [correct]
2466 join operation fuse two separate words. the server
2467 does this when the space occurs at the width setting.
2469 if (!buf_len || buf[buf_len-1] != ' ')
2480 match failed, so we have to copy what matched before
2481 falling through and copying this character. In reality,
2482 this will only ever be just the newline character, but
2483 it doesn't hurt to be precise.
2485 strncpy(bp, cont_seq, cmatch);
2497 buf[buf_len] = NULLCHAR;
2498 // next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2503 while (i < buf_len) {
2504 /* Deal with part of the TELNET option negotiation
2505 protocol. We refuse to do anything beyond the
2506 defaults, except that we allow the WILL ECHO option,
2507 which ICS uses to turn off password echoing when we are
2508 directly connected to it. We reject this option
2509 if localLineEditing mode is on (always on in xboard)
2510 and we are talking to port 23, which might be a real
2511 telnet server that will try to keep WILL ECHO on permanently.
2513 if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2514 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2515 unsigned char option;
2517 switch ((unsigned char) buf[++i]) {
2519 if (appData.debugMode)
2520 fprintf(debugFP, "\n<WILL ");
2521 switch (option = (unsigned char) buf[++i]) {
2523 if (appData.debugMode)
2524 fprintf(debugFP, "ECHO ");
2525 /* Reply only if this is a change, according
2526 to the protocol rules. */
2527 if (remoteEchoOption) break;
2528 if (appData.localLineEditing &&
2529 atoi(appData.icsPort) == TN_PORT) {
2530 TelnetRequest(TN_DONT, TN_ECHO);
2533 TelnetRequest(TN_DO, TN_ECHO);
2534 remoteEchoOption = TRUE;
2538 if (appData.debugMode)
2539 fprintf(debugFP, "%d ", option);
2540 /* Whatever this is, we don't want it. */
2541 TelnetRequest(TN_DONT, option);
2546 if (appData.debugMode)
2547 fprintf(debugFP, "\n<WONT ");
2548 switch (option = (unsigned char) buf[++i]) {
2550 if (appData.debugMode)
2551 fprintf(debugFP, "ECHO ");
2552 /* Reply only if this is a change, according
2553 to the protocol rules. */
2554 if (!remoteEchoOption) break;
2556 TelnetRequest(TN_DONT, TN_ECHO);
2557 remoteEchoOption = FALSE;
2560 if (appData.debugMode)
2561 fprintf(debugFP, "%d ", (unsigned char) option);
2562 /* Whatever this is, it must already be turned
2563 off, because we never agree to turn on
2564 anything non-default, so according to the
2565 protocol rules, we don't reply. */
2570 if (appData.debugMode)
2571 fprintf(debugFP, "\n<DO ");
2572 switch (option = (unsigned char) buf[++i]) {
2574 /* Whatever this is, we refuse to do it. */
2575 if (appData.debugMode)
2576 fprintf(debugFP, "%d ", option);
2577 TelnetRequest(TN_WONT, option);
2582 if (appData.debugMode)
2583 fprintf(debugFP, "\n<DONT ");
2584 switch (option = (unsigned char) buf[++i]) {
2586 if (appData.debugMode)
2587 fprintf(debugFP, "%d ", option);
2588 /* Whatever this is, we are already not doing
2589 it, because we never agree to do anything
2590 non-default, so according to the protocol
2591 rules, we don't reply. */
2596 if (appData.debugMode)
2597 fprintf(debugFP, "\n<IAC ");
2598 /* Doubled IAC; pass it through */
2602 if (appData.debugMode)
2603 fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2604 /* Drop all other telnet commands on the floor */
2607 if (oldi > next_out)
2608 SendToPlayer(&buf[next_out], oldi - next_out);
2614 /* OK, this at least will *usually* work */
2615 if (!loggedOn && looking_at(buf, &i, "ics%")) {
2619 if (loggedOn && !intfSet) {
2620 if (ics_type == ICS_ICC) {
2621 snprintf(str, MSG_SIZ,
2622 "/set-quietly interface %s\n/set-quietly style 12\n",
2624 if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2625 strcat(str, "/set-2 51 1\n/set seek 1\n");
2626 } else if (ics_type == ICS_CHESSNET) {
2627 snprintf(str, MSG_SIZ, "/style 12\n");
2629 safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
2630 strcat(str, programVersion);
2631 strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2632 if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2633 strcat(str, "$iset seekremove 1\n$set seek 1\n");
2635 strcat(str, "$iset nohighlight 1\n");
2637 strcat(str, "$iset lock 1\n$style 12\n");
2640 NotifyFrontendLogin();
2644 if (started == STARTED_COMMENT) {
2645 /* Accumulate characters in comment */
2646 parse[parse_pos++] = buf[i];
2647 if (buf[i] == '\n') {
2648 parse[parse_pos] = NULLCHAR;
2649 if(chattingPartner>=0) {
2651 snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
2652 OutputChatMessage(chattingPartner, mess);
2653 chattingPartner = -1;
2654 next_out = i+1; // [HGM] suppress printing in ICS window
2656 if(!suppressKibitz) // [HGM] kibitz
2657 AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
2658 else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2659 int nrDigit = 0, nrAlph = 0, j;
2660 if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2661 { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2662 parse[parse_pos] = NULLCHAR;
2663 // try to be smart: if it does not look like search info, it should go to
2664 // ICS interaction window after all, not to engine-output window.
2665 for(j=0; j<parse_pos; j++) { // count letters and digits
2666 nrDigit += (parse[j] >= '0' && parse[j] <= '9');
2667 nrAlph += (parse[j] >= 'a' && parse[j] <= 'z');
2668 nrAlph += (parse[j] >= 'A' && parse[j] <= 'Z');
2670 if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2671 int depth=0; float score;
2672 if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2673 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2674 pvInfoList[forwardMostMove-1].depth = depth;
2675 pvInfoList[forwardMostMove-1].score = 100*score;
2677 OutputKibitz(suppressKibitz, parse);
2680 snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
2681 SendToPlayer(tmp, strlen(tmp));
2683 next_out = i+1; // [HGM] suppress printing in ICS window
2685 started = STARTED_NONE;
2687 /* Don't match patterns against characters in comment */
2692 if (started == STARTED_CHATTER) {
2693 if (buf[i] != '\n') {
2694 /* Don't match patterns against characters in chatter */
2698 started = STARTED_NONE;
2699 if(suppressKibitz) next_out = i+1;
2702 /* Kludge to deal with rcmd protocol */
2703 if (firstTime && looking_at(buf, &i, "\001*")) {
2704 DisplayFatalError(&buf[1], 0, 1);
2710 if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2713 if (appData.debugMode)
2714 fprintf(debugFP, "ics_type %d\n", ics_type);
2717 if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
2718 ics_type = ICS_FICS;
2720 if (appData.debugMode)
2721 fprintf(debugFP, "ics_type %d\n", ics_type);
2724 if (!loggedOn && looking_at(buf, &i, "chess.net")) {
2725 ics_type = ICS_CHESSNET;
2727 if (appData.debugMode)
2728 fprintf(debugFP, "ics_type %d\n", ics_type);
2733 (looking_at(buf, &i, "\"*\" is *a registered name") ||
2734 looking_at(buf, &i, "Logging you in as \"*\"") ||
2735 looking_at(buf, &i, "will be \"*\""))) {
2736 safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
2740 if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
2742 snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
2743 DisplayIcsInteractionTitle(buf);
2744 have_set_title = TRUE;
2747 /* skip finger notes */
2748 if (started == STARTED_NONE &&
2749 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
2750 (buf[i] == '1' && buf[i+1] == '0')) &&
2751 buf[i+2] == ':' && buf[i+3] == ' ') {
2752 started = STARTED_CHATTER;
2758 // [HGM] seekgraph: recognize sought lines and end-of-sought message
2759 if(appData.seekGraph) {
2760 if(soughtPending && MatchSoughtLine(buf+i)) {
2761 i = strstr(buf+i, "rated") - buf;
2762 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2763 next_out = leftover_start = i;
2764 started = STARTED_CHATTER;
2765 suppressKibitz = TRUE;
2768 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
2769 && looking_at(buf, &i, "* ads displayed")) {
2770 soughtPending = FALSE;
2775 if(appData.autoRefresh) {
2776 if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
2777 int s = (ics_type == ICS_ICC); // ICC format differs
2779 AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
2780 star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
2781 looking_at(buf, &i, "*% "); // eat prompt
2782 if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
2783 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2784 next_out = i; // suppress
2787 if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
2788 char *p = star_match[0];
2790 if(seekGraphUp) RemoveSeekAd(atoi(p));
2791 while(*p && *p++ != ' '); // next
2793 looking_at(buf, &i, "*% "); // eat prompt
2794 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2801 /* skip formula vars */
2802 if (started == STARTED_NONE &&
2803 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
2804 started = STARTED_CHATTER;
2809 // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
2810 if (appData.autoKibitz && started == STARTED_NONE &&
2811 !appData.icsEngineAnalyze && // [HGM] [DM] ICS analyze
2812 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
2813 if((looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
2814 (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
2815 StrStr(star_match[0], gameInfo.black) == star_match[0] )) { // kibitz of self or opponent
2816 suppressKibitz = TRUE;
2817 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2819 if((StrStr(star_match[0], gameInfo.white) == star_match[0]
2820 && (gameMode == IcsPlayingWhite)) ||
2821 (StrStr(star_match[0], gameInfo.black) == star_match[0]
2822 && (gameMode == IcsPlayingBlack)) ) // opponent kibitz
2823 started = STARTED_CHATTER; // own kibitz we simply discard
2825 started = STARTED_COMMENT; // make sure it will be collected in parse[]
2826 parse_pos = 0; parse[0] = NULLCHAR;
2827 savingComment = TRUE;
2828 suppressKibitz = gameMode != IcsObserving ? 2 :
2829 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
2833 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
2834 looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
2835 && atoi(star_match[0])) {
2836 // suppress the acknowledgements of our own autoKibitz
2838 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2839 if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
2840 SendToPlayer(star_match[0], strlen(star_match[0]));
2841 if(looking_at(buf, &i, "*% ")) // eat prompt
2842 suppressKibitz = FALSE;
2846 } // [HGM] kibitz: end of patch
2848 // [HGM] chat: intercept tells by users for which we have an open chat window
2850 if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
2851 looking_at(buf, &i, "* whispers:") ||
2852 looking_at(buf, &i, "* kibitzes:") ||
2853 looking_at(buf, &i, "* shouts:") ||
2854 looking_at(buf, &i, "* c-shouts:") ||
2855 looking_at(buf, &i, "--> * ") ||
2856 looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
2857 looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
2858 looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
2859 looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
2861 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
2862 chattingPartner = -1;
2864 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
2865 for(p=0; p<MAX_CHAT; p++) {
2866 if(chatPartner[p][0] >= '0' && chatPartner[p][0] <= '9' && channel == atoi(chatPartner[p])) {
2867 talker[0] = '['; strcat(talker, "] ");
2868 Colorize(channel == 1 ? ColorChannel1 : ColorChannel, FALSE);
2869 chattingPartner = p; break;
2872 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
2873 for(p=0; p<MAX_CHAT; p++) {
2874 if(!strcmp("kibitzes", chatPartner[p])) {
2875 talker[0] = '['; strcat(talker, "] ");
2876 chattingPartner = p; break;
2879 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
2880 for(p=0; p<MAX_CHAT; p++) {
2881 if(!strcmp("whispers", chatPartner[p])) {
2882 talker[0] = '['; strcat(talker, "] ");
2883 chattingPartner = p; break;
2886 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
2887 if(buf[i-8] == '-' && buf[i-3] == 't')
2888 for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
2889 if(!strcmp("c-shouts", chatPartner[p])) {
2890 talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
2891 chattingPartner = p; break;
2894 if(chattingPartner < 0)
2895 for(p=0; p<MAX_CHAT; p++) {
2896 if(!strcmp("shouts", chatPartner[p])) {
2897 if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
2898 else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
2899 else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
2900 chattingPartner = p; break;
2904 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
2905 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
2906 talker[0] = 0; Colorize(ColorTell, FALSE);
2907 chattingPartner = p; break;
2909 if(chattingPartner<0) i = oldi; else {
2910 Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
2911 if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
2912 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2913 started = STARTED_COMMENT;
2914 parse_pos = 0; parse[0] = NULLCHAR;
2915 savingComment = 3 + chattingPartner; // counts as TRUE
2916 suppressKibitz = TRUE;
2919 } // [HGM] chat: end of patch
2921 if (appData.zippyTalk || appData.zippyPlay) {
2922 /* [DM] Backup address for color zippy lines */
2925 if (loggedOn == TRUE)
2926 if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
2927 (appData.zippyPlay && ZippyMatch(buf, &backup)));
2929 } // [DM] 'else { ' deleted
2931 /* Regular tells and says */
2932 (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
2933 looking_at(buf, &i, "* (your partner) tells you: ") ||
2934 looking_at(buf, &i, "* says: ") ||
2935 /* Don't color "message" or "messages" output */
2936 (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
2937 looking_at(buf, &i, "*. * at *:*: ") ||
2938 looking_at(buf, &i, "--* (*:*): ") ||
2939 /* Message notifications (same color as tells) */
2940 looking_at(buf, &i, "* has left a message ") ||
2941 looking_at(buf, &i, "* just sent you a message:\n") ||
2942 /* Whispers and kibitzes */
2943 (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
2944 looking_at(buf, &i, "* kibitzes: ") ||
2946 (tkind = 3, looking_at(buf, &i, "*(*: "))) {
2948 if (tkind == 1 && strchr(star_match[0], ':')) {
2949 /* Avoid "tells you:" spoofs in channels */
2952 if (star_match[0][0] == NULLCHAR ||
2953 strchr(star_match[0], ' ') ||
2954 (tkind == 3 && strchr(star_match[1], ' '))) {
2955 /* Reject bogus matches */
2958 if (appData.colorize) {
2959 if (oldi > next_out) {
2960 SendToPlayer(&buf[next_out], oldi - next_out);
2965 Colorize(ColorTell, FALSE);
2966 curColor = ColorTell;
2969 Colorize(ColorKibitz, FALSE);
2970 curColor = ColorKibitz;
2973 p = strrchr(star_match[1], '(');
2980 Colorize(ColorChannel1, FALSE);
2981 curColor = ColorChannel1;
2983 Colorize(ColorChannel, FALSE);
2984 curColor = ColorChannel;
2988 curColor = ColorNormal;
2992 if (started == STARTED_NONE && appData.autoComment &&
2993 (gameMode == IcsObserving ||
2994 gameMode == IcsPlayingWhite ||
2995 gameMode == IcsPlayingBlack)) {
2996 parse_pos = i - oldi;
2997 memcpy(parse, &buf[oldi], parse_pos);
2998 parse[parse_pos] = NULLCHAR;
2999 started = STARTED_COMMENT;
3000 savingComment = TRUE;
3002 started = STARTED_CHATTER;
3003 savingComment = FALSE;
3010 if (looking_at(buf, &i, "* s-shouts: ") ||
3011 looking_at(buf, &i, "* c-shouts: ")) {
3012 if (appData.colorize) {
3013 if (oldi > next_out) {
3014 SendToPlayer(&buf[next_out], oldi - next_out);
3017 Colorize(ColorSShout, FALSE);
3018 curColor = ColorSShout;
3021 started = STARTED_CHATTER;
3025 if (looking_at(buf, &i, "--->")) {
3030 if (looking_at(buf, &i, "* shouts: ") ||
3031 looking_at(buf, &i, "--> ")) {
3032 if (appData.colorize) {
3033 if (oldi > next_out) {
3034 SendToPlayer(&buf[next_out], oldi - next_out);
3037 Colorize(ColorShout, FALSE);
3038 curColor = ColorShout;
3041 started = STARTED_CHATTER;
3045 if (looking_at( buf, &i, "Challenge:")) {
3046 if (appData.colorize) {
3047 if (oldi > next_out) {
3048 SendToPlayer(&buf[next_out], oldi - next_out);
3051 Colorize(ColorChallenge, FALSE);
3052 curColor = ColorChallenge;
3058 if (looking_at(buf, &i, "* offers you") ||
3059 looking_at(buf, &i, "* offers to be") ||
3060 looking_at(buf, &i, "* would like to") ||
3061 looking_at(buf, &i, "* requests to") ||
3062 looking_at(buf, &i, "Your opponent offers") ||
3063 looking_at(buf, &i, "Your opponent requests")) {
3065 if (appData.colorize) {
3066 if (oldi > next_out) {
3067 SendToPlayer(&buf[next_out], oldi - next_out);
3070 Colorize(ColorRequest, FALSE);
3071 curColor = ColorRequest;
3076 if (looking_at(buf, &i, "* (*) seeking")) {
3077 if (appData.colorize) {
3078 if (oldi > next_out) {
3079 SendToPlayer(&buf[next_out], oldi - next_out);
3082 Colorize(ColorSeek, FALSE);
3083 curColor = ColorSeek;
3088 if (looking_at(buf, &i, "\\ ")) {
3089 if (prevColor != ColorNormal) {
3090 if (oldi > next_out) {
3091 SendToPlayer(&buf[next_out], oldi - next_out);
3094 Colorize(prevColor, TRUE);
3095 curColor = prevColor;
3097 if (savingComment) {
3098 parse_pos = i - oldi;
3099 memcpy(parse, &buf[oldi], parse_pos);
3100 parse[parse_pos] = NULLCHAR;
3101 started = STARTED_COMMENT;
3102 if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3103 chattingPartner = savingComment - 3; // kludge to remember the box
3105 started = STARTED_CHATTER;
3110 if (looking_at(buf, &i, "Black Strength :") ||
3111 looking_at(buf, &i, "<<< style 10 board >>>") ||
3112 looking_at(buf, &i, "<10>") ||
3113 looking_at(buf, &i, "#@#")) {
3114 /* Wrong board style */
3116 SendToICS(ics_prefix);
3117 SendToICS("set style 12\n");
3118 SendToICS(ics_prefix);
3119 SendToICS("refresh\n");
3123 if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
3125 have_sent_ICS_logon = 1;
3129 if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3130 (looking_at(buf, &i, "\n<12> ") ||
3131 looking_at(buf, &i, "<12> "))) {
3133 if (oldi > next_out) {
3134 SendToPlayer(&buf[next_out], oldi - next_out);
3137 started = STARTED_BOARD;
3142 if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3143 looking_at(buf, &i, "<b1> ")) {
3144 if (oldi > next_out) {
3145 SendToPlayer(&buf[next_out], oldi - next_out);
3148 started = STARTED_HOLDINGS;
3153 if (looking_at(buf, &i, "* *vs. * *--- *")) {
3155 /* Header for a move list -- first line */
3157 switch (ics_getting_history) {
3161 case BeginningOfGame:
3162 /* User typed "moves" or "oldmoves" while we
3163 were idle. Pretend we asked for these
3164 moves and soak them up so user can step
3165 through them and/or save them.
3168 gameMode = IcsObserving;
3171 ics_getting_history = H_GOT_UNREQ_HEADER;
3173 case EditGame: /*?*/
3174 case EditPosition: /*?*/
3175 /* Should above feature work in these modes too? */
3176 /* For now it doesn't */
3177 ics_getting_history = H_GOT_UNWANTED_HEADER;
3180 ics_getting_history = H_GOT_UNWANTED_HEADER;
3185 /* Is this the right one? */
3186 if (gameInfo.white && gameInfo.black &&
3187 strcmp(gameInfo.white, star_match[0]) == 0 &&
3188 strcmp(gameInfo.black, star_match[2]) == 0) {
3190 ics_getting_history = H_GOT_REQ_HEADER;
3193 case H_GOT_REQ_HEADER:
3194 case H_GOT_UNREQ_HEADER:
3195 case H_GOT_UNWANTED_HEADER:
3196 case H_GETTING_MOVES:
3197 /* Should not happen */
3198 DisplayError(_("Error gathering move list: two headers"), 0);
3199 ics_getting_history = H_FALSE;
3203 /* Save player ratings into gameInfo if needed */
3204 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3205 ics_getting_history == H_GOT_UNREQ_HEADER) &&
3206 (gameInfo.whiteRating == -1 ||
3207 gameInfo.blackRating == -1)) {
3209 gameInfo.whiteRating = string_to_rating(star_match[1]);
3210 gameInfo.blackRating = string_to_rating(star_match[3]);
3211 if (appData.debugMode)
3212 fprintf(debugFP, _("Ratings from header: W %d, B %d\n"),
3213 gameInfo.whiteRating, gameInfo.blackRating);
3218 if (looking_at(buf, &i,
3219 "* * match, initial time: * minute*, increment: * second")) {
3220 /* Header for a move list -- second line */
3221 /* Initial board will follow if this is a wild game */
3222 if (gameInfo.event != NULL) free(gameInfo.event);
3223 snprintf(str, MSG_SIZ, "ICS %s %s match", star_match[0], star_match[1]);
3224 gameInfo.event = StrSave(str);
3225 /* [HGM] we switched variant. Translate boards if needed. */
3226 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3230 if (looking_at(buf, &i, "Move ")) {
3231 /* Beginning of a move list */
3232 switch (ics_getting_history) {
3234 /* Normally should not happen */
3235 /* Maybe user hit reset while we were parsing */
3238 /* Happens if we are ignoring a move list that is not
3239 * the one we just requested. Common if the user
3240 * tries to observe two games without turning off
3243 case H_GETTING_MOVES:
3244 /* Should not happen */
3245 DisplayError(_("Error gathering move list: nested"), 0);
3246 ics_getting_history = H_FALSE;
3248 case H_GOT_REQ_HEADER:
3249 ics_getting_history = H_GETTING_MOVES;
3250 started = STARTED_MOVES;
3252 if (oldi > next_out) {
3253 SendToPlayer(&buf[next_out], oldi - next_out);
3256 case H_GOT_UNREQ_HEADER:
3257 ics_getting_history = H_GETTING_MOVES;
3258 started = STARTED_MOVES_NOHIDE;
3261 case H_GOT_UNWANTED_HEADER:
3262 ics_getting_history = H_FALSE;
3268 if (looking_at(buf, &i, "% ") ||
3269 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3270 && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3271 if(ics_type == ICS_ICC && soughtPending) { // [HGM] seekgraph: on ICC sought-list has no termination line
3272 soughtPending = FALSE;
3276 if(suppressKibitz) next_out = i;
3277 savingComment = FALSE;
3281 case STARTED_MOVES_NOHIDE:
3282 memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3283 parse[parse_pos + i - oldi] = NULLCHAR;
3284 ParseGameHistory(parse);
3286 if (appData.zippyPlay && first.initDone) {
3287 FeedMovesToProgram(&first, forwardMostMove);
3288 if (gameMode == IcsPlayingWhite) {
3289 if (WhiteOnMove(forwardMostMove)) {
3290 if (first.sendTime) {
3291 if (first.useColors) {
3292 SendToProgram("black\n", &first);
3294 SendTimeRemaining(&first, TRUE);
3296 if (first.useColors) {
3297 SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3299 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3300 first.maybeThinking = TRUE;
3302 if (first.usePlayother) {
3303 if (first.sendTime) {
3304 SendTimeRemaining(&first, TRUE);
3306 SendToProgram("playother\n", &first);
3312 } else if (gameMode == IcsPlayingBlack) {
3313 if (!WhiteOnMove(forwardMostMove)) {
3314 if (first.sendTime) {
3315 if (first.useColors) {
3316 SendToProgram("white\n", &first);
3318 SendTimeRemaining(&first, FALSE);
3320 if (first.useColors) {
3321 SendToProgram("black\n", &first);
3323 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3324 first.maybeThinking = TRUE;
3326 if (first.usePlayother) {
3327 if (first.sendTime) {
3328 SendTimeRemaining(&first, FALSE);
3330 SendToProgram("playother\n", &first);
3339 if (gameMode == IcsObserving && ics_gamenum == -1) {
3340 /* Moves came from oldmoves or moves command
3341 while we weren't doing anything else.
3343 currentMove = forwardMostMove;
3344 ClearHighlights();/*!!could figure this out*/
3345 flipView = appData.flipView;
3346 DrawPosition(TRUE, boards[currentMove]);
3347 DisplayBothClocks();
3348 snprintf(str, MSG_SIZ, "%s vs. %s",
3349 gameInfo.white, gameInfo.black);
3353 /* Moves were history of an active game */
3354 if (gameInfo.resultDetails != NULL) {
3355 free(gameInfo.resultDetails);
3356 gameInfo.resultDetails = NULL;
3359 HistorySet(parseList, backwardMostMove,
3360 forwardMostMove, currentMove-1);
3361 DisplayMove(currentMove - 1);
3362 if (started == STARTED_MOVES) next_out = i;
3363 started = STARTED_NONE;
3364 ics_getting_history = H_FALSE;
3367 case STARTED_OBSERVE:
3368 started = STARTED_NONE;
3369 SendToICS(ics_prefix);
3370 SendToICS("refresh\n");
3376 if(bookHit) { // [HGM] book: simulate book reply
3377 static char bookMove[MSG_SIZ]; // a bit generous?
3379 programStats.nodes = programStats.depth = programStats.time =
3380 programStats.score = programStats.got_only_move = 0;
3381 sprintf(programStats.movelist, "%s (xbook)", bookHit);
3383 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
3384 strcat(bookMove, bookHit);
3385 HandleMachineMove(bookMove, &first);
3390 if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3391 started == STARTED_HOLDINGS ||
3392 started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3393 /* Accumulate characters in move list or board */
3394 parse[parse_pos++] = buf[i];
3397 /* Start of game messages. Mostly we detect start of game
3398 when the first board image arrives. On some versions
3399 of the ICS, though, we need to do a "refresh" after starting
3400 to observe in order to get the current board right away. */
3401 if (looking_at(buf, &i, "Adding game * to observation list")) {
3402 started = STARTED_OBSERVE;
3406 /* Handle auto-observe */
3407 if (appData.autoObserve &&
3408 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3409 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3411 /* Choose the player that was highlighted, if any. */
3412 if (star_match[0][0] == '\033' ||
3413 star_match[1][0] != '\033') {
3414 player = star_match[0];
3416 player = star_match[2];
3418 snprintf(str, MSG_SIZ, "%sobserve %s\n",
3419 ics_prefix, StripHighlightAndTitle(player));
3422 /* Save ratings from notify string */
3423 safeStrCpy(player1Name, star_match[0], sizeof(player1Name)/sizeof(player1Name[0]));
3424 player1Rating = string_to_rating(star_match[1]);
3425 safeStrCpy(player2Name, star_match[2], sizeof(player2Name)/sizeof(player2Name[0]));
3426 player2Rating = string_to_rating(star_match[3]);
3428 if (appData.debugMode)
3430 "Ratings from 'Game notification:' %s %d, %s %d\n",
3431 player1Name, player1Rating,
3432 player2Name, player2Rating);
3437 /* Deal with automatic examine mode after a game,
3438 and with IcsObserving -> IcsExamining transition */
3439 if (looking_at(buf, &i, "Entering examine mode for game *") ||
3440 looking_at(buf, &i, "has made you an examiner of game *")) {
3442 int gamenum = atoi(star_match[0]);
3443 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3444 gamenum == ics_gamenum) {
3445 /* We were already playing or observing this game;
3446 no need to refetch history */
3447 gameMode = IcsExamining;
3449 pauseExamForwardMostMove = forwardMostMove;
3450 } else if (currentMove < forwardMostMove) {
3451 ForwardInner(forwardMostMove);
3454 /* I don't think this case really can happen */
3455 SendToICS(ics_prefix);
3456 SendToICS("refresh\n");
3461 /* Error messages */
3462 // if (ics_user_moved) {
3463 if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3464 if (looking_at(buf, &i, "Illegal move") ||
3465 looking_at(buf, &i, "Not a legal move") ||
3466 looking_at(buf, &i, "Your king is in check") ||
3467 looking_at(buf, &i, "It isn't your turn") ||
3468 looking_at(buf, &i, "It is not your move")) {
3470 if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3471 currentMove = forwardMostMove-1;
3472 DisplayMove(currentMove - 1); /* before DMError */
3473 DrawPosition(FALSE, boards[currentMove]);
3474 SwitchClocks(forwardMostMove-1); // [HGM] race
3475 DisplayBothClocks();
3477 DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3483 if (looking_at(buf, &i, "still have time") ||
3484 looking_at(buf, &i, "not out of time") ||
3485 looking_at(buf, &i, "either player is out of time") ||
3486 looking_at(buf, &i, "has timeseal; checking")) {
3487 /* We must have called his flag a little too soon */
3488 whiteFlag = blackFlag = FALSE;
3492 if (looking_at(buf, &i, "added * seconds to") ||
3493 looking_at(buf, &i, "seconds were added to")) {
3494 /* Update the clocks */
3495 SendToICS(ics_prefix);
3496 SendToICS("refresh\n");
3500 if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3501 ics_clock_paused = TRUE;
3506 if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3507 ics_clock_paused = FALSE;
3512 /* Grab player ratings from the Creating: message.
3513 Note we have to check for the special case when
3514 the ICS inserts things like [white] or [black]. */
3515 if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3516 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3518 0 player 1 name (not necessarily white)
3520 2 empty, white, or black (IGNORED)
3521 3 player 2 name (not necessarily black)
3524 The names/ratings are sorted out when the game
3525 actually starts (below).
3527 safeStrCpy(player1Name, StripHighlightAndTitle(star_match[0]), sizeof(player1Name)/sizeof(player1Name[0]));
3528 player1Rating = string_to_rating(star_match[1]);
3529 safeStrCpy(player2Name, StripHighlightAndTitle(star_match[3]), sizeof(player2Name)/sizeof(player2Name[0]));
3530 player2Rating = string_to_rating(star_match[4]);
3532 if (appData.debugMode)
3534 "Ratings from 'Creating:' %s %d, %s %d\n",
3535 player1Name, player1Rating,
3536 player2Name, player2Rating);
3541 /* Improved generic start/end-of-game messages */
3542 if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3543 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3544 /* If tkind == 0: */
3545 /* star_match[0] is the game number */
3546 /* [1] is the white player's name */
3547 /* [2] is the black player's name */
3548 /* For end-of-game: */
3549 /* [3] is the reason for the game end */
3550 /* [4] is a PGN end game-token, preceded by " " */
3551 /* For start-of-game: */
3552 /* [3] begins with "Creating" or "Continuing" */
3553 /* [4] is " *" or empty (don't care). */
3554 int gamenum = atoi(star_match[0]);
3555 char *whitename, *blackname, *why, *endtoken;
3556 ChessMove endtype = EndOfFile;
3559 whitename = star_match[1];
3560 blackname = star_match[2];
3561 why = star_match[3];
3562 endtoken = star_match[4];
3564 whitename = star_match[1];
3565 blackname = star_match[3];
3566 why = star_match[5];
3567 endtoken = star_match[6];
3570 /* Game start messages */
3571 if (strncmp(why, "Creating ", 9) == 0 ||
3572 strncmp(why, "Continuing ", 11) == 0) {
3573 gs_gamenum = gamenum;
3574 safeStrCpy(gs_kind, strchr(why, ' ') + 1,sizeof(gs_kind)/sizeof(gs_kind[0]));
3575 VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
3577 if (appData.zippyPlay) {
3578 ZippyGameStart(whitename, blackname);
3581 partnerBoardValid = FALSE; // [HGM] bughouse
3585 /* Game end messages */
3586 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3587 ics_gamenum != gamenum) {
3590 while (endtoken[0] == ' ') endtoken++;
3591 switch (endtoken[0]) {
3594 endtype = GameUnfinished;
3597 endtype = BlackWins;
3600 if (endtoken[1] == '/')
3601 endtype = GameIsDrawn;
3603 endtype = WhiteWins;
3606 GameEnds(endtype, why, GE_ICS);
3608 if (appData.zippyPlay && first.initDone) {
3609 ZippyGameEnd(endtype, why);
3610 if (first.pr == NULL) {
3611 /* Start the next process early so that we'll
3612 be ready for the next challenge */
3613 StartChessProgram(&first);
3615 /* Send "new" early, in case this command takes
3616 a long time to finish, so that we'll be ready
3617 for the next challenge. */
3618 gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3622 if(appData.bgObserve && partnerBoardValid) DrawPosition(TRUE, partnerBoard);
3626 if (looking_at(buf, &i, "Removing game * from observation") ||
3627 looking_at(buf, &i, "no longer observing game *") ||
3628 looking_at(buf, &i, "Game * (*) has no examiners")) {
3629 if (gameMode == IcsObserving &&
3630 atoi(star_match[0]) == ics_gamenum)
3632 /* icsEngineAnalyze */
3633 if (appData.icsEngineAnalyze) {
3640 ics_user_moved = FALSE;
3645 if (looking_at(buf, &i, "no longer examining game *")) {
3646 if (gameMode == IcsExamining &&
3647 atoi(star_match[0]) == ics_gamenum)
3651 ics_user_moved = FALSE;
3656 /* Advance leftover_start past any newlines we find,
3657 so only partial lines can get reparsed */
3658 if (looking_at(buf, &i, "\n")) {
3659 prevColor = curColor;
3660 if (curColor != ColorNormal) {
3661 if (oldi > next_out) {
3662 SendToPlayer(&buf[next_out], oldi - next_out);
3665 Colorize(ColorNormal, FALSE);
3666 curColor = ColorNormal;
3668 if (started == STARTED_BOARD) {
3669 started = STARTED_NONE;
3670 parse[parse_pos] = NULLCHAR;
3671 ParseBoard12(parse);
3674 /* Send premove here */
3675 if (appData.premove) {
3677 if (currentMove == 0 &&
3678 gameMode == IcsPlayingWhite &&
3679 appData.premoveWhite) {
3680 snprintf(str, MSG_SIZ, "%s\n", appData.premoveWhiteText);
3681 if (appData.debugMode)
3682 fprintf(debugFP, "Sending premove:\n");
3684 } else if (currentMove == 1 &&
3685 gameMode == IcsPlayingBlack &&
3686 appData.premoveBlack) {
3687 snprintf(str, MSG_SIZ, "%s\n", appData.premoveBlackText);
3688 if (appData.debugMode)
3689 fprintf(debugFP, "Sending premove:\n");
3691 } else if (gotPremove) {
3693 ClearPremoveHighlights();
3694 if (appData.debugMode)
3695 fprintf(debugFP, "Sending premove:\n");
3696 UserMoveEvent(premoveFromX, premoveFromY,
3697 premoveToX, premoveToY,
3702 /* Usually suppress following prompt */
3703 if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
3704 while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
3705 if (looking_at(buf, &i, "*% ")) {
3706 savingComment = FALSE;
3711 } else if (started == STARTED_HOLDINGS) {
3713 char new_piece[MSG_SIZ];
3714 started = STARTED_NONE;
3715 parse[parse_pos] = NULLCHAR;
3716 if (appData.debugMode)
3717 fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
3718 parse, currentMove);
3719 if (sscanf(parse, " game %d", &gamenum) == 1) {
3720 if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game
3721 if (gameInfo.variant == VariantNormal) {
3722 /* [HGM] We seem to switch variant during a game!
3723 * Presumably no holdings were displayed, so we have
3724 * to move the position two files to the right to
3725 * create room for them!
3727 VariantClass newVariant;
3728 switch(gameInfo.boardWidth) { // base guess on board width
3729 case 9: newVariant = VariantShogi; break;
3730 case 10: newVariant = VariantGreat; break;
3731 default: newVariant = VariantCrazyhouse; break;
3733 VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3734 /* Get a move list just to see the header, which
3735 will tell us whether this is really bug or zh */
3736 if (ics_getting_history == H_FALSE) {
3737 ics_getting_history = H_REQUESTED;
3738 snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
3742 new_piece[0] = NULLCHAR;
3743 sscanf(parse, "game %d white [%s black [%s <- %s",
3744 &gamenum, white_holding, black_holding,
3746 white_holding[strlen(white_holding)-1] = NULLCHAR;
3747 black_holding[strlen(black_holding)-1] = NULLCHAR;
3748 /* [HGM] copy holdings to board holdings area */
3749 CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
3750 CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
3751 boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
3753 if (appData.zippyPlay && first.initDone) {
3754 ZippyHoldings(white_holding, black_holding,
3758 if (tinyLayout || smallLayout) {
3759 char wh[16], bh[16];
3760 PackHolding(wh, white_holding);
3761 PackHolding(bh, black_holding);
3762 snprintf(str, MSG_SIZ,"[%s-%s] %s-%s", wh, bh,
3763 gameInfo.white, gameInfo.black);
3765 snprintf(str, MSG_SIZ, "%s [%s] vs. %s [%s]",
3766 gameInfo.white, white_holding,
3767 gameInfo.black, black_holding);
3769 if(!partnerUp) // [HGM] bughouse: when peeking at partner game we already know what he captured...
3770 DrawPosition(FALSE, boards[currentMove]);
3772 } else if(appData.bgObserve) { // [HGM] bughouse: holdings of other game => background
3773 sscanf(parse, "game %d white [%s black [%s <- %s",
3774 &gamenum, white_holding, black_holding,
3776 white_holding[strlen(white_holding)-1] = NULLCHAR;
3777 black_holding[strlen(black_holding)-1] = NULLCHAR;
3778 /* [HGM] copy holdings to partner-board holdings area */
3779 CopyHoldings(partnerBoard, white_holding, WhitePawn);
3780 CopyHoldings(partnerBoard, black_holding, BlackPawn);
3781 if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual: always draw
3782 if(partnerUp) DrawPosition(FALSE, partnerBoard);
3783 if(twoBoards) { partnerUp = 0; flipView = !flipView; }
3786 /* Suppress following prompt */
3787 if (looking_at(buf, &i, "*% ")) {
3788 if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
3789 savingComment = FALSE;
3797 i++; /* skip unparsed character and loop back */
3800 if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
3801 // started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
3802 // SendToPlayer(&buf[next_out], i - next_out);
3803 started != STARTED_HOLDINGS && leftover_start > next_out) {
3804 SendToPlayer(&buf[next_out], leftover_start - next_out);
3808 leftover_len = buf_len - leftover_start;
3809 /* if buffer ends with something we couldn't parse,
3810 reparse it after appending the next read */
3812 } else if (count == 0) {
3813 RemoveInputSource(isr);
3814 DisplayFatalError(_("Connection closed by ICS"), 0, 0);
3816 DisplayFatalError(_("Error reading from ICS"), error, 1);
3821 /* Board style 12 looks like this:
3823 <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
3825 * The "<12> " is stripped before it gets to this routine. The two
3826 * trailing 0's (flip state and clock ticking) are later addition, and
3827 * some chess servers may not have them, or may have only the first.
3828 * Additional trailing fields may be added in the future.
3831 #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"
3833 #define RELATION_OBSERVING_PLAYED 0
3834 #define RELATION_OBSERVING_STATIC -2 /* examined, oldmoves, or smoves */
3835 #define RELATION_PLAYING_MYMOVE 1
3836 #define RELATION_PLAYING_NOTMYMOVE -1
3837 #define RELATION_EXAMINING 2
3838 #define RELATION_ISOLATED_BOARD -3
3839 #define RELATION_STARTING_POSITION -4 /* FICS only */
3842 ParseBoard12(string)
3845 GameMode newGameMode;
3846 int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
3847 int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
3848 int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
3849 char to_play, board_chars[200];
3850 char move_str[MSG_SIZ], str[MSG_SIZ], elapsed_time[MSG_SIZ];
3851 char black[32], white[32];
3853 int prevMove = currentMove;
3856 int fromX, fromY, toX, toY;
3858 int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
3859 char *bookHit = NULL; // [HGM] book
3860 Boolean weird = FALSE, reqFlag = FALSE;
3862 fromX = fromY = toX = toY = -1;
3866 if (appData.debugMode)
3867 fprintf(debugFP, _("Parsing board: %s\n"), string);
3869 move_str[0] = NULLCHAR;
3870 elapsed_time[0] = NULLCHAR;
3871 { /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
3873 while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
3874 if(string[i] == ' ') { ranks++; files = 0; }
3876 if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
3879 for(j = 0; j <i; j++) board_chars[j] = string[j];
3880 board_chars[i] = '\0';
3883 n = sscanf(string, PATTERN, &to_play, &double_push,
3884 &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
3885 &gamenum, white, black, &relation, &basetime, &increment,
3886 &white_stren, &black_stren, &white_time, &black_time,
3887 &moveNum, str, elapsed_time, move_str, &ics_flip,
3891 snprintf(str, MSG_SIZ, _("Failed to parse board string:\n\"%s\""), string);
3892 DisplayError(str, 0);
3896 /* Convert the move number to internal form */
3897 moveNum = (moveNum - 1) * 2;
3898 if (to_play == 'B') moveNum++;
3899 if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
3900 DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
3906 case RELATION_OBSERVING_PLAYED:
3907 case RELATION_OBSERVING_STATIC:
3908 if (gamenum == -1) {
3909 /* Old ICC buglet */
3910 relation = RELATION_OBSERVING_STATIC;
3912 newGameMode = IcsObserving;
3914 case RELATION_PLAYING_MYMOVE:
3915 case RELATION_PLAYING_NOTMYMOVE:
3917 ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
3918 IcsPlayingWhite : IcsPlayingBlack;
3920 case RELATION_EXAMINING:
3921 newGameMode = IcsExamining;
3923 case RELATION_ISOLATED_BOARD:
3925 /* Just display this board. If user was doing something else,
3926 we will forget about it until the next board comes. */
3927 newGameMode = IcsIdle;
3929 case RELATION_STARTING_POSITION:
3930 newGameMode = gameMode;
3934 if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
3935 && newGameMode == IcsObserving && gamenum != ics_gamenum && appData.bgObserve) {
3936 // [HGM] bughouse: don't act on alien boards while we play. Just parse the board and save it */
3938 for (k = 0; k < ranks; k++) {
3939 for (j = 0; j < files; j++)
3940 board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
3941 if(gameInfo.holdingsWidth > 1) {
3942 board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
3943 board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
3946 CopyBoard(partnerBoard, board);
3947 if(toSqr = strchr(str, '/')) { // extract highlights from long move
3948 partnerBoard[EP_STATUS-3] = toSqr[1] - AAA; // kludge: hide highlighting info in board
3949 partnerBoard[EP_STATUS-4] = toSqr[2] - ONE;
3950 } else partnerBoard[EP_STATUS-4] = partnerBoard[EP_STATUS-3] = -1;
3951 if(toSqr = strchr(str, '-')) {
3952 partnerBoard[EP_STATUS-1] = toSqr[1] - AAA;
3953 partnerBoard[EP_STATUS-2] = toSqr[2] - ONE;
3954 } else partnerBoard[EP_STATUS-1] = partnerBoard[EP_STATUS-2] = -1;
3955 if(appData.dualBoard && !twoBoards) { twoBoards = 1; InitDrawingSizes(-2,0); }
3956 if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual
3957 if(partnerUp) DrawPosition(FALSE, partnerBoard);
3958 if(twoBoards) { partnerUp = 0; flipView = !flipView; } // [HGM] dual
3959 snprintf(partnerStatus, MSG_SIZ,"W: %d:%02d B: %d:%02d (%d-%d) %c", white_time/60000, (white_time%60000)/1000,
3960 (black_time/60000), (black_time%60000)/1000, white_stren, black_stren, to_play);
3961 DisplayMessage(partnerStatus, "");
3962 partnerBoardValid = TRUE;
3966 /* Modify behavior for initial board display on move listing
3969 switch (ics_getting_history) {
3973 case H_GOT_REQ_HEADER:
3974 case H_GOT_UNREQ_HEADER:
3975 /* This is the initial position of the current game */
3976 gamenum = ics_gamenum;
3977 moveNum = 0; /* old ICS bug workaround */
3978 if (to_play == 'B') {
3979 startedFromSetupPosition = TRUE;
3980 blackPlaysFirst = TRUE;
3982 if (forwardMostMove == 0) forwardMostMove = 1;
3983 if (backwardMostMove == 0) backwardMostMove = 1;
3984 if (currentMove == 0) currentMove = 1;
3986 newGameMode = gameMode;
3987 relation = RELATION_STARTING_POSITION; /* ICC needs this */
3989 case H_GOT_UNWANTED_HEADER:
3990 /* This is an initial board that we don't want */
3992 case H_GETTING_MOVES:
3993 /* Should not happen */
3994 DisplayError(_("Error gathering move list: extra board"), 0);
3995 ics_getting_history = H_FALSE;
3999 if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
4000 weird && (int)gameInfo.variant < (int)VariantShogi) {
4001 /* [HGM] We seem to have switched variant unexpectedly
4002 * Try to guess new variant from board size
4004 VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
4005 if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
4006 if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
4007 if(ranks == 8 && files == 12) newVariant = VariantCourier; else
4008 if(ranks == 9 && files == 9) newVariant = VariantShogi; else
4009 if(!weird) newVariant = VariantNormal;
4010 VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4011 /* Get a move list just to see the header, which
4012 will tell us whether this is really bug or zh */
4013 if (ics_getting_history == H_FALSE) {
4014 ics_getting_history = H_REQUESTED; reqFlag = TRUE;
4015 snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4020 /* Take action if this is the first board of a new game, or of a
4021 different game than is currently being displayed. */
4022 if (gamenum != ics_gamenum || newGameMode != gameMode ||
4023 relation == RELATION_ISOLATED_BOARD) {
4025 /* Forget the old game and get the history (if any) of the new one */
4026 if (gameMode != BeginningOfGame) {
4030 if (appData.autoRaiseBoard) BoardToTop();
4032 if (gamenum == -1) {
4033 newGameMode = IcsIdle;
4034 } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
4035 appData.getMoveList && !reqFlag) {
4036 /* Need to get game history */
4037 ics_getting_history = H_REQUESTED;
4038 snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4042 /* Initially flip the board to have black on the bottom if playing
4043 black or if the ICS flip flag is set, but let the user change
4044 it with the Flip View button. */
4045 flipView = appData.autoFlipView ?
4046 (newGameMode == IcsPlayingBlack) || ics_flip :
4049 /* Done with values from previous mode; copy in new ones */
4050 gameMode = newGameMode;
4052 ics_gamenum = gamenum;
4053 if (gamenum == gs_gamenum) {
4054 int klen = strlen(gs_kind);
4055 if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
4056 snprintf(str, MSG_SIZ, "ICS %s", gs_kind);
4057 gameInfo.event = StrSave(str);
4059 gameInfo.event = StrSave("ICS game");
4061 gameInfo.site = StrSave(appData.icsHost);
4062 gameInfo.date = PGNDate();
4063 gameInfo.round = StrSave("-");
4064 gameInfo.white = StrSave(white);
4065 gameInfo.black = StrSave(black);
4066 timeControl = basetime * 60 * 1000;
4068 timeIncrement = increment * 1000;
4069 movesPerSession = 0;
4070 gameInfo.timeControl = TimeControlTagValue();
4071 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
4072 if (appData.debugMode) {
4073 fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
4074 fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
4075 setbuf(debugFP, NULL);
4078 gameInfo.outOfBook = NULL;
4080 /* Do we have the ratings? */
4081 if (strcmp(player1Name, white) == 0 &&
4082 strcmp(player2Name, black) == 0) {
4083 if (appData.debugMode)
4084 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4085 player1Rating, player2Rating);
4086 gameInfo.whiteRating = player1Rating;
4087 gameInfo.blackRating = player2Rating;
4088 } else if (strcmp(player2Name, white) == 0 &&
4089 strcmp(player1Name, black) == 0) {
4090 if (appData.debugMode)
4091 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4092 player2Rating, player1Rating);
4093 gameInfo.whiteRating = player2Rating;
4094 gameInfo.blackRating = player1Rating;
4096 player1Name[0] = player2Name[0] = NULLCHAR;
4098 /* Silence shouts if requested */
4099 if (appData.quietPlay &&
4100 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
4101 SendToICS(ics_prefix);
4102 SendToICS("set shout 0\n");
4106 /* Deal with midgame name changes */
4108 if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
4109 if (gameInfo.white) free(gameInfo.white);
4110 gameInfo.white = StrSave(white);
4112 if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
4113 if (gameInfo.black) free(gameInfo.black);
4114 gameInfo.black = StrSave(black);
4118 /* Throw away game result if anything actually changes in examine mode */
4119 if (gameMode == IcsExamining && !newGame) {
4120 gameInfo.result = GameUnfinished;
4121 if (gameInfo.resultDetails != NULL) {
4122 free(gameInfo.resultDetails);
4123 gameInfo.resultDetails = NULL;
4127 /* In pausing && IcsExamining mode, we ignore boards coming
4128 in if they are in a different variation than we are. */
4129 if (pauseExamInvalid) return;
4130 if (pausing && gameMode == IcsExamining) {
4131 if (moveNum <= pauseExamForwardMostMove) {
4132 pauseExamInvalid = TRUE;
4133 forwardMostMove = pauseExamForwardMostMove;
4138 if (appData.debugMode) {
4139 fprintf(debugFP, "load %dx%d board\n", files, ranks);
4141 /* Parse the board */
4142 for (k = 0; k < ranks; k++) {
4143 for (j = 0; j < files; j++)
4144 board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4145 if(gameInfo.holdingsWidth > 1) {
4146 board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4147 board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4150 CopyBoard(boards[moveNum], board);
4151 boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
4153 startedFromSetupPosition =
4154 !CompareBoards(board, initialPosition);
4155 if(startedFromSetupPosition)
4156 initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
4159 /* [HGM] Set castling rights. Take the outermost Rooks,
4160 to make it also work for FRC opening positions. Note that board12
4161 is really defective for later FRC positions, as it has no way to
4162 indicate which Rook can castle if they are on the same side of King.
4163 For the initial position we grant rights to the outermost Rooks,
4164 and remember thos rights, and we then copy them on positions
4165 later in an FRC game. This means WB might not recognize castlings with
4166 Rooks that have moved back to their original position as illegal,
4167 but in ICS mode that is not its job anyway.
4169 if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
4170 { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
4172 for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4173 if(board[0][i] == WhiteRook) j = i;
4174 initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4175 for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4176 if(board[0][i] == WhiteRook) j = i;
4177 initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4178 for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4179 if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4180 initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4181 for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4182 if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4183 initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4185 if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
4186 for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4187 if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
4188 for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4189 if(board[BOARD_HEIGHT-1][k] == bKing)
4190 initialRights[5] = boards[moveNum][CASTLING][5] = k;
4191 if(gameInfo.variant == VariantTwoKings) {
4192 // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
4193 if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
4194 if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
4197 r = boards[moveNum][CASTLING][0] = initialRights[0];
4198 if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4199 r = boards[moveNum][CASTLING][1] = initialRights[1];
4200 if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4201 r = boards[moveNum][CASTLING][3] = initialRights[3];
4202 if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4203 r = boards[moveNum][CASTLING][4] = initialRights[4];
4204 if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4205 /* wildcastle kludge: always assume King has rights */
4206 r = boards[moveNum][CASTLING][2] = initialRights[2];
4207 r = boards[moveNum][CASTLING][5] = initialRights[5];
4209 /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4210 boards[moveNum][EP_STATUS] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
4213 if (ics_getting_history == H_GOT_REQ_HEADER ||
4214 ics_getting_history == H_GOT_UNREQ_HEADER) {
4215 /* This was an initial position from a move list, not
4216 the current position */
4220 /* Update currentMove and known move number limits */
4221 newMove = newGame || moveNum > forwardMostMove;
4224 forwardMostMove = backwardMostMove = currentMove = moveNum;
4225 if (gameMode == IcsExamining && moveNum == 0) {
4226 /* Workaround for ICS limitation: we are not told the wild
4227 type when starting to examine a game. But if we ask for
4228 the move list, the move list header will tell us */
4229 ics_getting_history = H_REQUESTED;
4230 snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4233 } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4234 || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4236 /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4237 /* [HGM] applied this also to an engine that is silently watching */
4238 if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4239 (gameMode == IcsObserving || gameMode == IcsExamining) &&
4240 gameInfo.variant == currentlyInitializedVariant) {
4241 takeback = forwardMostMove - moveNum;
4242 for (i = 0; i < takeback; i++) {
4243 if (appData.debugMode) fprintf(debugFP, "take back move\n");
4244 SendToProgram("undo\n", &first);
4249 forwardMostMove = moveNum;
4250 if (!pausing || currentMove > forwardMostMove)
4251 currentMove = forwardMostMove;
4253 /* New part of history that is not contiguous with old part */
4254 if (pausing && gameMode == IcsExamining) {
4255 pauseExamInvalid = TRUE;
4256 forwardMostMove = pauseExamForwardMostMove;
4259 if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4261 if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4262 // [HGM] when we will receive the move list we now request, it will be
4263 // fed to the engine from the first move on. So if the engine is not
4264 // in the initial position now, bring it there.
4265 InitChessProgram(&first, 0);
4268 ics_getting_history = H_REQUESTED;
4269 snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4272 forwardMostMove = backwardMostMove = currentMove = moveNum;
4275 /* Update the clocks */
4276 if (strchr(elapsed_time, '.')) {
4278 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4279 timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4281 /* Time is in seconds */
4282 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4283 timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4288 if (appData.zippyPlay && newGame &&
4289 gameMode != IcsObserving && gameMode != IcsIdle &&
4290 gameMode != IcsExamining)
4291 ZippyFirstBoard(moveNum, basetime, increment);
4294 /* Put the move on the move list, first converting
4295 to canonical algebraic form. */
4297 if (appData.debugMode) {
4298 if (appData.debugMode) { int f = forwardMostMove;
4299 fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4300 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4301 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4303 fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4304 fprintf(debugFP, "moveNum = %d\n", moveNum);
4305 fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4306 setbuf(debugFP, NULL);
4308 if (moveNum <= backwardMostMove) {
4309 /* We don't know what the board looked like before
4311 safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4312 strcat(parseList[moveNum - 1], " ");
4313 strcat(parseList[moveNum - 1], elapsed_time);
4314 moveList[moveNum - 1][0] = NULLCHAR;
4315 } else if (strcmp(move_str, "none") == 0) {
4316 // [HGM] long SAN: swapped order; test for 'none' before parsing move
4317 /* Again, we don't know what the board looked like;
4318 this is really the start of the game. */
4319 parseList[moveNum - 1][0] = NULLCHAR;
4320 moveList[moveNum - 1][0] = NULLCHAR;
4321 backwardMostMove = moveNum;
4322 startedFromSetupPosition = TRUE;
4323 fromX = fromY = toX = toY = -1;
4325 // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
4326 // So we parse the long-algebraic move string in stead of the SAN move
4327 int valid; char buf[MSG_SIZ], *prom;
4329 if(gameInfo.variant == VariantShogi && !strchr(move_str, '=') && !strchr(move_str, '@'))
4330 strcat(move_str, "="); // if ICS does not say 'promote' on non-drop, we defer.
4331 // str looks something like "Q/a1-a2"; kill the slash
4333 snprintf(buf, MSG_SIZ,"%c%s", str[0], str+2);
4334 else safeStrCpy(buf, str, sizeof(buf)/sizeof(buf[0])); // might be castling
4335 if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
4336 strcat(buf, prom); // long move lacks promo specification!
4337 if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4338 if(appData.debugMode)
4339 fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4340 safeStrCpy(move_str, buf, MSG_SIZ);
4342 valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4343 &fromX, &fromY, &toX, &toY, &promoChar)
4344 || ParseOneMove(buf, moveNum - 1, &moveType,
4345 &fromX, &fromY, &toX, &toY, &promoChar);
4346 // end of long SAN patch
4348 (void) CoordsToAlgebraic(boards[moveNum - 1],
4349 PosFlags(moveNum - 1),
4350 fromY, fromX, toY, toX, promoChar,
4351 parseList[moveNum-1]);
4352 switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4358 if(gameInfo.variant != VariantShogi)
4359 strcat(parseList[moveNum - 1], "+");
4362 case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4363 strcat(parseList[moveNum - 1], "#");
4366 strcat(parseList[moveNum - 1], " ");
4367 strcat(parseList[moveNum - 1], elapsed_time);
4368 /* currentMoveString is set as a side-effect of ParseOneMove */
4369 if(gameInfo.variant == VariantShogi && currentMoveString[4]) currentMoveString[4] = '^';
4370 safeStrCpy(moveList[moveNum - 1], currentMoveString, sizeof(moveList[moveNum - 1])/sizeof(moveList[moveNum - 1][0]));
4371 strcat(moveList[moveNum - 1], "\n");
4373 if(gameInfo.holdingsWidth && !appData.disguise && gameInfo.variant != VariantSuper
4374 && gameInfo.variant != VariantGreat) // inherit info that ICS does not give from previous board
4375 for(k=0; k<ranks; k++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
4376 ChessSquare old, new = boards[moveNum][k][j];
4377 if(fromY == DROP_RANK && k==toY && j==toX) continue; // dropped pieces always stand for themselves
4378 old = (k==toY && j==toX) ? boards[moveNum-1][fromY][fromX] : boards[moveNum-1][k][j]; // trace back mover
4379 if(old == new) continue;
4380 if(old == PROMOTED new) boards[moveNum][k][j] = old; // prevent promoted pieces to revert to primordial ones
4381 else if(new == WhiteWazir || new == BlackWazir) {
4382 if(old < WhiteCannon || old >= BlackPawn && old < BlackCannon)
4383 boards[moveNum][k][j] = PROMOTED old; // choose correct type of Gold in promotion
4384 else boards[moveNum][k][j] = old; // preserve type of Gold
4385 } else if((old == WhitePawn || old == BlackPawn) && new != EmptySquare) // Pawn promotions (but not e.p.capture!)
4386 boards[moveNum][k][j] = PROMOTED new; // use non-primordial representation of chosen piece
4389 /* Move from ICS was illegal!? Punt. */
4390 if (appData.debugMode) {
4391 fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4392 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4394 safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4395 strcat(parseList[moveNum - 1], " ");
4396 strcat(parseList[moveNum - 1], elapsed_time);
4397 moveList[moveNum - 1][0] = NULLCHAR;
4398 fromX = fromY = toX = toY = -1;
4401 if (appData.debugMode) {
4402 fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4403 setbuf(debugFP, NULL);
4407 /* Send move to chess program (BEFORE animating it). */
4408 if (appData.zippyPlay && !newGame && newMove &&
4409 (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4411 if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4412 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4413 if (moveList[moveNum - 1][0] == NULLCHAR) {
4414 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"),
4416 DisplayError(str, 0);
4418 if (first.sendTime) {
4419 SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4421 bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4422 if (firstMove && !bookHit) {
4424 if (first.useColors) {
4425 SendToProgram(gameMode == IcsPlayingWhite ?
4427 "black\ngo\n", &first);
4429 SendToProgram("go\n", &first);
4431 first.maybeThinking = TRUE;
4434 } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4435 if (moveList[moveNum - 1][0] == NULLCHAR) {
4436 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"), move_str);
4437 DisplayError(str, 0);
4439 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4440 SendMoveToProgram(moveNum - 1, &first);
4447 if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4448 /* If move comes from a remote source, animate it. If it
4449 isn't remote, it will have already been animated. */
4450 if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4451 AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4453 if (!pausing && appData.highlightLastMove) {
4454 SetHighlights(fromX, fromY, toX, toY);
4458 /* Start the clocks */
4459 whiteFlag = blackFlag = FALSE;
4460 appData.clockMode = !(basetime == 0 && increment == 0);
4462 ics_clock_paused = TRUE;
4464 } else if (ticking == 1) {
4465 ics_clock_paused = FALSE;
4467 if (gameMode == IcsIdle ||
4468 relation == RELATION_OBSERVING_STATIC ||
4469 relation == RELATION_EXAMINING ||
4471 DisplayBothClocks();
4475 /* Display opponents and material strengths */
4476 if (gameInfo.variant != VariantBughouse &&
4477 gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4478 if (tinyLayout || smallLayout) {
4479 if(gameInfo.variant == VariantNormal)
4480 snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d}",
4481 gameInfo.white, white_stren, gameInfo.black, black_stren,
4482 basetime, increment);
4484 snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d w%d}",
4485 gameInfo.white, white_stren, gameInfo.black, black_stren,
4486 basetime, increment, (int) gameInfo.variant);
4488 if(gameInfo.variant == VariantNormal)
4489 snprintf(str, MSG_SIZ, "%s (%d) vs. %s (%d) {%d %d}",
4490 gameInfo.white, white_stren, gameInfo.black, black_stren,
4491 basetime, increment);
4493 snprintf(str, MSG_SIZ, "%s (%d) vs. %s (%d) {%d %d %s}",
4494 gameInfo.white, white_stren, gameInfo.black, black_stren,
4495 basetime, increment, VariantName(gameInfo.variant));
4498 if (appData.debugMode) {
4499 fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
4504 /* Display the board */
4505 if (!pausing && !appData.noGUI) {
4507 if (appData.premove)
4509 ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
4510 ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
4511 ClearPremoveHighlights();
4513 j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
4514 if(partnerUp) { flipView = originalFlip; partnerUp = FALSE; j = TRUE; } // [HGM] bughouse: restore view
4515 DrawPosition(j, boards[currentMove]);
4517 DisplayMove(moveNum - 1);
4518 if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4519 !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4520 (gameMode == IcsPlayingBlack) && (WhiteOnMove(moveNum)) ) ) {
4521 if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4525 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4527 if(bookHit) { // [HGM] book: simulate book reply
4528 static char bookMove[MSG_SIZ]; // a bit generous?
4530 programStats.nodes = programStats.depth = programStats.time =
4531 programStats.score = programStats.got_only_move = 0;
4532 sprintf(programStats.movelist, "%s (xbook)", bookHit);
4534 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
4535 strcat(bookMove, bookHit);
4536 HandleMachineMove(bookMove, &first);
4545 if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4546 ics_getting_history = H_REQUESTED;
4547 snprintf(buf, MSG_SIZ, "%smoves %d\n", ics_prefix, ics_gamenum);
4553 AnalysisPeriodicEvent(force)
4556 if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4557 && !force) || !appData.periodicUpdates)
4560 /* Send . command to Crafty to collect stats */
4561 SendToProgram(".\n", &first);
4563 /* Don't send another until we get a response (this makes
4564 us stop sending to old Crafty's which don't understand
4565 the "." command (sending illegal cmds resets node count & time,
4566 which looks bad)) */
4567 programStats.ok_to_send = 0;
4570 void ics_update_width(new_width)
4573 ics_printf("set width %d\n", new_width);
4577 SendMoveToProgram(moveNum, cps)
4579 ChessProgramState *cps;
4583 if (cps->useUsermove) {
4584 SendToProgram("usermove ", cps);
4588 if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4589 int len = space - parseList[moveNum];
4590 memcpy(buf, parseList[moveNum], len);
4592 buf[len] = NULLCHAR;
4594 snprintf(buf, MSG_SIZ,"%s\n", parseList[moveNum]);
4596 SendToProgram(buf, cps);
4598 if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4599 AlphaRank(moveList[moveNum], 4);
4600 SendToProgram(moveList[moveNum], cps);
4601 AlphaRank(moveList[moveNum], 4); // and back
4603 /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
4604 * the engine. It would be nice to have a better way to identify castle
4606 if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
4607 && cps->useOOCastle) {
4608 int fromX = moveList[moveNum][0] - AAA;
4609 int fromY = moveList[moveNum][1] - ONE;
4610 int toX = moveList[moveNum][2] - AAA;
4611 int toY = moveList[moveNum][3] - ONE;
4612 if((boards[moveNum][fromY][fromX] == WhiteKing
4613 && boards[moveNum][toY][toX] == WhiteRook)
4614 || (boards[moveNum][fromY][fromX] == BlackKing
4615 && boards[moveNum][toY][toX] == BlackRook)) {
4616 if(toX > fromX) SendToProgram("O-O\n", cps);
4617 else SendToProgram("O-O-O\n", cps);
4619 else SendToProgram(moveList[moveNum], cps);
4621 else SendToProgram(moveList[moveNum], cps);
4622 /* End of additions by Tord */
4625 /* [HGM] setting up the opening has brought engine in force mode! */
4626 /* Send 'go' if we are in a mode where machine should play. */
4627 if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4628 (gameMode == TwoMachinesPlay ||
4630 gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite ||
4632 gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4633 SendToProgram("go\n", cps);
4634 if (appData.debugMode) {
4635 fprintf(debugFP, "(extra)\n");
4638 setboardSpoiledMachineBlack = 0;
4642 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar)
4644 int fromX, fromY, toX, toY;
4647 char user_move[MSG_SIZ];
4651 snprintf(user_move, MSG_SIZ, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4652 (int)moveType, fromX, fromY, toX, toY);
4653 DisplayError(user_move + strlen("say "), 0);
4655 case WhiteKingSideCastle:
4656 case BlackKingSideCastle:
4657 case WhiteQueenSideCastleWild:
4658 case BlackQueenSideCastleWild:
4660 case WhiteHSideCastleFR:
4661 case BlackHSideCastleFR:
4663 snprintf(user_move, MSG_SIZ, "o-o\n");
4665 case WhiteQueenSideCastle:
4666 case BlackQueenSideCastle:
4667 case WhiteKingSideCastleWild:
4668 case BlackKingSideCastleWild:
4670 case WhiteASideCastleFR:
4671 case BlackASideCastleFR:
4673 snprintf(user_move, MSG_SIZ, "o-o-o\n");
4675 case WhiteNonPromotion:
4676 case BlackNonPromotion:
4677 sprintf(user_move, "%c%c%c%c==\n", AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4679 case WhitePromotion:
4680 case BlackPromotion:
4681 if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
4682 snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
4683 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4684 PieceToChar(WhiteFerz));
4685 else if(gameInfo.variant == VariantGreat)
4686 snprintf(user_move, MSG_SIZ,"%c%c%c%c=%c\n",
4687 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4688 PieceToChar(WhiteMan));
4690 snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
4691 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4697 snprintf(user_move, MSG_SIZ, "%c@%c%c\n",
4698 ToUpper(PieceToChar((ChessSquare) fromX)),
4699 AAA + toX, ONE + toY);
4701 case IllegalMove: /* could be a variant we don't quite understand */
4702 if(fromY == DROP_RANK) goto drop; // We need 'IllegalDrop' move type?
4704 case WhiteCapturesEnPassant:
4705 case BlackCapturesEnPassant:
4706 snprintf(user_move, MSG_SIZ,"%c%c%c%c\n",
4707 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4710 SendToICS(user_move);
4711 if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
4712 ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
4717 { // [HGM] upload: send entire stored game to ICS as long-algebraic moves.
4718 int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
4719 static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
4720 if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
4721 DisplayError("You cannot do this while you are playing or observing", 0);
4724 if(gameMode != IcsExamining) { // is this ever not the case?
4725 char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
4727 if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
4728 snprintf(command,MSG_SIZ, "match %s", ics_handle);
4729 } else { // on FICS we must first go to general examine mode
4730 safeStrCpy(command, "examine\nbsetup", sizeof(command)/sizeof(command[0])); // and specify variant within it with bsetups
4732 if(gameInfo.variant != VariantNormal) {
4733 // try figure out wild number, as xboard names are not always valid on ICS
4734 for(i=1; i<=36; i++) {
4735 snprintf(buf, MSG_SIZ, "wild/%d", i);
4736 if(StringToVariant(buf) == gameInfo.variant) break;
4738 if(i<=36 && ics_type == ICS_ICC) snprintf(buf, MSG_SIZ,"%s w%d\n", command, i);
4739 else if(i == 22) snprintf(buf,MSG_SIZ, "%s fr\n", command);
4740 else snprintf(buf, MSG_SIZ,"%s %s\n", command, VariantName(gameInfo.variant));
4741 } else snprintf(buf, MSG_SIZ,"%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
4742 SendToICS(ics_prefix);
4744 if(startedFromSetupPosition || backwardMostMove != 0) {
4745 fen = PositionToFEN(backwardMostMove, NULL);
4746 if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
4747 snprintf(buf, MSG_SIZ,"loadfen %s\n", fen);
4749 } else { // FICS: everything has to set by separate bsetup commands
4750 p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board
4751 snprintf(buf, MSG_SIZ,"bsetup fen %s\n", fen);
4753 if(!WhiteOnMove(backwardMostMove)) {
4754 SendToICS("bsetup tomove black\n");
4756 i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL);
4757 snprintf(buf, MSG_SIZ,"bsetup wcastle %s\n", castlingStrings[i]);
4759 i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
4760 snprintf(buf, MSG_SIZ, "bsetup bcastle %s\n", castlingStrings[i]);
4762 i = boards[backwardMostMove][EP_STATUS];
4763 if(i >= 0) { // set e.p.
4764 snprintf(buf, MSG_SIZ,"bsetup eppos %c\n", i+AAA);
4770 if(bsetup || ics_type != ICS_ICC && gameInfo.variant != VariantNormal)
4771 SendToICS("bsetup done\n"); // switch to normal examining.
4773 for(i = backwardMostMove; i<last; i++) {
4775 snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s\n", parseList[i]);
4778 SendToICS(ics_prefix);
4779 SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
4783 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
4788 if (rf == DROP_RANK) {
4789 sprintf(move, "%c@%c%c\n",
4790 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
4792 if (promoChar == 'x' || promoChar == NULLCHAR) {
4793 sprintf(move, "%c%c%c%c\n",
4794 AAA + ff, ONE + rf, AAA + ft, ONE + rt);
4796 sprintf(move, "%c%c%c%c%c\n",
4797 AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
4803 ProcessICSInitScript(f)
4808 while (fgets(buf, MSG_SIZ, f)) {
4809 SendToICSDelayed(buf,(long)appData.msLoginDelay);
4819 ChessSquare piece = boards[currentMove][toY][toX];
4820 ChessSquare king = WhiteKing, pawn = WhitePawn, last = promoSweep;
4821 if(gameInfo.variant == VariantKnightmate) king = WhiteUnicorn;
4822 if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway) king = EmptySquare;
4823 if(promoSweep >= BlackPawn) king = WHITE_TO_BLACK king, pawn = WHITE_TO_BLACK pawn;
4824 if(gameInfo.variant == VariantSpartan && pawn == BlackPawn) pawn = BlackLance, king = EmptySquare;
4825 if(gameInfo.variant == VariantShogi) pawn = EmptySquare;
4828 if(promoSweep == EmptySquare) promoSweep = BlackPawn; // wrap
4829 else if((int)promoSweep == -1) promoSweep = WhiteKing;
4830 else if(promoSweep == BlackPawn && step > 0) promoSweep = WhitePawn;
4831 else if(promoSweep == WhiteKing && step < 0) promoSweep = BlackKing;
4833 } while(promoSweep == king || promoSweep == pawn || PieceToChar(promoSweep) == '.'
4834 || gameInfo.variant == VariantShogi && promoSweep != PROMOTED last && last != PROMOTED promoSweep && last != promoSweep);
4835 boards[currentMove][toY][toX] = promoSweep;
4836 DrawPosition(FALSE, boards[currentMove]);
4837 boards[currentMove][toY][toX] = piece;
4840 static int lastX, lastY;
4842 void PromoScroll(int x, int y)
4845 if(abs(x - lastX) < 7 && abs(y - lastY) < 7) return;
4846 if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
4848 lastX = x; lastY = y;
4850 if(promoSweep == EmptySquare) return;
4857 ChessSquare piece = boards[currentMove][toY][toX];
4860 if(pieceSweep == EmptySquare) pieceSweep = WhitePawn; // wrap
4861 if((int)pieceSweep == -1) pieceSweep = BlackKing;
4863 } while(PieceToChar(pieceSweep) == '.');
4864 boards[currentMove][toY][toX] = pieceSweep;
4865 DrawPosition(FALSE, boards[currentMove]);
4866 boards[currentMove][toY][toX] = piece;
4868 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
4870 AlphaRank(char *move, int n)
4872 // char *p = move, c; int x, y;
4874 if (appData.debugMode) {
4875 fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
4879 move[2]>='0' && move[2]<='9' &&
4880 move[3]>='a' && move[3]<='x' ) {
4882 move[2] = BOARD_RGHT -1 - (move[2]-'1') + AAA;
4883 move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4885 if(move[0]>='0' && move[0]<='9' &&
4886 move[1]>='a' && move[1]<='x' &&
4887 move[2]>='0' && move[2]<='9' &&
4888 move[3]>='a' && move[3]<='x' ) {
4889 /* input move, Shogi -> normal */
4890 move[0] = BOARD_RGHT -1 - (move[0]-'1') + AAA;
4891 move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
4892 move[2] = BOARD_RGHT -1 - (move[2]-'1') + AAA;
4893 move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4896 move[3]>='0' && move[3]<='9' &&
4897 move[2]>='a' && move[2]<='x' ) {
4899 move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4900 move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4903 move[0]>='a' && move[0]<='x' &&
4904 move[3]>='0' && move[3]<='9' &&
4905 move[2]>='a' && move[2]<='x' ) {
4906 /* output move, normal -> Shogi */
4907 move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
4908 move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
4909 move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4910 move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4911 if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
4913 if (appData.debugMode) {
4914 fprintf(debugFP, " out = '%s'\n", move);
4918 char yy_textstr[8000];
4920 /* Parser for moves from gnuchess, ICS, or user typein box */
4922 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
4925 ChessMove *moveType;
4926 int *fromX, *fromY, *toX, *toY;
4929 *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
4931 switch (*moveType) {
4932 case WhitePromotion:
4933 case BlackPromotion:
4934 case WhiteNonPromotion:
4935 case BlackNonPromotion:
4937 case WhiteCapturesEnPassant:
4938 case BlackCapturesEnPassant:
4939 case WhiteKingSideCastle:
4940 case WhiteQueenSideCastle:
4941 case BlackKingSideCastle:
4942 case BlackQueenSideCastle:
4943 case WhiteKingSideCastleWild:
4944 case WhiteQueenSideCastleWild:
4945 case BlackKingSideCastleWild:
4946 case BlackQueenSideCastleWild:
4947 /* Code added by Tord: */
4948 case WhiteHSideCastleFR:
4949 case WhiteASideCastleFR:
4950 case BlackHSideCastleFR:
4951 case BlackASideCastleFR:
4952 /* End of code added by Tord */
4953 case IllegalMove: /* bug or odd chess variant */
4954 *fromX = currentMoveString[0] - AAA;
4955 *fromY = currentMoveString[1] - ONE;
4956 *toX = currentMoveString[2] - AAA;
4957 *toY = currentMoveString[3] - ONE;
4958 *promoChar = currentMoveString[4];
4959 if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
4960 *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
4961 if (appData.debugMode) {
4962 fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
4964 *fromX = *fromY = *toX = *toY = 0;
4967 if (appData.testLegality) {
4968 return (*moveType != IllegalMove);
4970 return !(*fromX == *toX && *fromY == *toY) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
4971 WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
4976 *fromX = *moveType == WhiteDrop ?
4977 (int) CharToPiece(ToUpper(currentMoveString[0])) :
4978 (int) CharToPiece(ToLower(currentMoveString[0]));
4980 *toX = currentMoveString[2] - AAA;
4981 *toY = currentMoveString[3] - ONE;
4982 *promoChar = NULLCHAR;
4986 case ImpossibleMove:
4996 if (appData.debugMode) {
4997 fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
5000 *fromX = *fromY = *toX = *toY = 0;
5001 *promoChar = NULLCHAR;
5008 ParsePV(char *pv, Boolean storeComments)
5009 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
5010 int fromX, fromY, toX, toY; char promoChar;
5015 endPV = forwardMostMove;
5017 while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
5018 if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
5019 valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
5020 if(appData.debugMode){
5021 fprintf(debugFP,"parsePV: %d %c%c%c%c yy='%s'\nPV = '%s'\n", valid, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, yy_textstr, pv);
5023 if(!valid && nr == 0 &&
5024 ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
5025 nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
5026 // Hande case where played move is different from leading PV move
5027 CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
5028 CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
5029 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
5030 if(!CompareBoards(boards[endPV], boards[endPV+2])) {
5031 endPV += 2; // if position different, keep this
5032 moveList[endPV-1][0] = fromX + AAA;
5033 moveList[endPV-1][1] = fromY + ONE;
5034 moveList[endPV-1][2] = toX + AAA;
5035 moveList[endPV-1][3] = toY + ONE;
5036 parseList[endPV-1][0] = NULLCHAR;
5037 safeStrCpy(moveList[endPV-2], "_0_0", sizeof(moveList[endPV-2])/sizeof(moveList[endPV-2][0])); // suppress premove highlight on takeback move
5040 pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
5041 if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
5042 if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
5043 if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
5044 valid++; // allow comments in PV
5048 if(endPV+1 > framePtr) break; // no space, truncate
5051 CopyBoard(boards[endPV], boards[endPV-1]);
5052 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
5053 moveList[endPV-1][0] = fromX + AAA;
5054 moveList[endPV-1][1] = fromY + ONE;
5055 moveList[endPV-1][2] = toX + AAA;
5056 moveList[endPV-1][3] = toY + ONE;
5057 moveList[endPV-1][4] = promoChar;
5058 moveList[endPV-1][5] = NULLCHAR;
5059 strncat(moveList[endPV-1], "\n", MOVE_LEN);
5061 CoordsToAlgebraic(boards[endPV - 1],
5062 PosFlags(endPV - 1),
5063 fromY, fromX, toY, toX, promoChar,
5064 parseList[endPV - 1]);
5066 parseList[endPV-1][0] = NULLCHAR;
5068 currentMove = endPV;
5069 if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5070 SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5071 moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5072 DrawPosition(TRUE, boards[currentMove]);
5076 LoadMultiPV(int x, int y, char *buf, int index, int *start, int *end)
5081 if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
5082 lastX = x; lastY = y;
5083 while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
5085 while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
5086 if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
5088 do{ while(buf[index] && buf[index] != '\n') index++;
5089 } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
5091 ParsePV(buf+startPV, FALSE);
5092 *start = startPV; *end = index-1;
5097 LoadPV(int x, int y)
5098 { // called on right mouse click to load PV
5099 int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
5100 lastX = x; lastY = y;
5101 ParsePV(lastPV[which], FALSE); // load the PV of the thinking engine in the boards array.
5108 if(endPV < 0) return;
5110 currentMove = forwardMostMove;
5111 ClearPremoveHighlights();
5112 DrawPosition(TRUE, boards[currentMove]);
5116 MovePV(int x, int y, int h)
5117 { // step through PV based on mouse coordinates (called on mouse move)
5118 int margin = h>>3, step = 0, dist;
5120 // we must somehow check if right button is still down (might be released off board!)
5121 if(abs(x - lastX) < 7 && abs(y - lastY) < 7) return;
5122 if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5124 lastX = x; lastY = y;
5126 if(pieceSweep != EmptySquare) { NextPiece(step); return; }
5127 if(endPV < 0) return;
5128 if(y < margin) step = 1; else
5129 if(y > h - margin) step = -1;
5130 if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
5131 currentMove += step;
5132 if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5133 SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5134 moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5135 DrawPosition(FALSE, boards[currentMove]);
5139 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
5140 // All positions will have equal probability, but the current method will not provide a unique
5141 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
5147 int piecesLeft[(int)BlackPawn];
5148 int seed, nrOfShuffles;
5150 void GetPositionNumber()
5151 { // sets global variable seed
5154 seed = appData.defaultFrcPosition;
5155 if(seed < 0) { // randomize based on time for negative FRC position numbers
5156 for(i=0; i<50; i++) seed += random();
5157 seed = random() ^ random() >> 8 ^ random() << 8;
5158 if(seed<0) seed = -seed;
5162 int put(Board board, int pieceType, int rank, int n, int shade)
5163 // put the piece on the (n-1)-th empty squares of the given shade
5167 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5168 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5169 board[rank][i] = (ChessSquare) pieceType;
5170 squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5172 piecesLeft[pieceType]--;
5180 void AddOnePiece(Board board, int pieceType, int rank, int shade)
5181 // calculate where the next piece goes, (any empty square), and put it there
5185 i = seed % squaresLeft[shade];
5186 nrOfShuffles *= squaresLeft[shade];
5187 seed /= squaresLeft[shade];
5188 put(board, pieceType, rank, i, shade);
5191 void AddTwoPieces(Board board, int pieceType, int rank)
5192 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5194 int i, n=squaresLeft[ANY], j=n-1, k;
5196 k = n*(n-1)/2; // nr of possibilities, not counting permutations
5197 i = seed % k; // pick one
5200 while(i >= j) i -= j--;
5201 j = n - 1 - j; i += j;
5202 put(board, pieceType, rank, j, ANY);
5203 put(board, pieceType, rank, i, ANY);
5206 void SetUpShuffle(Board board, int number)
5210 GetPositionNumber(); nrOfShuffles = 1;
5212 squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5213 squaresLeft[ANY] = BOARD_RGHT - BOARD_LEFT;
5214 squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5216 for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5218 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5219 p = (int) board[0][i];
5220 if(p < (int) BlackPawn) piecesLeft[p] ++;
5221 board[0][i] = EmptySquare;
5224 if(PosFlags(0) & F_ALL_CASTLE_OK) {
5225 // shuffles restricted to allow normal castling put KRR first
5226 if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5227 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5228 else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5229 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5230 if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5231 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5232 if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5233 put(board, WhiteRook, 0, 0, ANY);
5234 // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5237 if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5238 // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5239 for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5240 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5241 while(piecesLeft[p] >= 2) {
5242 AddOnePiece(board, p, 0, LITE);
5243 AddOnePiece(board, p, 0, DARK);
5245 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5248 for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5249 // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5250 // but we leave King and Rooks for last, to possibly obey FRC restriction
5251 if(p == (int)WhiteRook) continue;
5252 while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5253 if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY); // add the odd piece
5256 // now everything is placed, except perhaps King (Unicorn) and Rooks
5258 if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5259 // Last King gets castling rights
5260 while(piecesLeft[(int)WhiteUnicorn]) {
5261 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5262 initialRights[2] = initialRights[5] = board[CASTLING][2] = board[CASTLING][5] = i;
5265 while(piecesLeft[(int)WhiteKing]) {
5266 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5267 initialRights[2] = initialRights[5] = board[CASTLING][2] = board[CASTLING][5] = i;
5272 while(piecesLeft[(int)WhiteKing]) AddOnePiece(board, WhiteKing, 0, ANY);
5273 while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
5276 // Only Rooks can be left; simply place them all
5277 while(piecesLeft[(int)WhiteRook]) {
5278 i = put(board, WhiteRook, 0, 0, ANY);
5279 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
5282 initialRights[1] = initialRights[4] = board[CASTLING][1] = board[CASTLING][4] = i;
5284 initialRights[0] = initialRights[3] = board[CASTLING][0] = board[CASTLING][3] = i;
5287 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
5288 board[BOARD_HEIGHT-1][i] = (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
5291 if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
5294 int SetCharTable( char *table, const char * map )
5295 /* [HGM] moved here from winboard.c because of its general usefulness */
5296 /* Basically a safe strcpy that uses the last character as King */
5298 int result = FALSE; int NrPieces;
5300 if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
5301 && NrPieces >= 12 && !(NrPieces&1)) {
5302 int i; /* [HGM] Accept even length from 12 to 34 */
5304 for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
5305 for( i=0; i<NrPieces/2-1; i++ ) {
5307 table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
5309 table[(int) WhiteKing] = map[NrPieces/2-1];
5310 table[(int) BlackKing] = map[NrPieces-1];
5318 void Prelude(Board board)
5319 { // [HGM] superchess: random selection of exo-pieces
5320 int i, j, k; ChessSquare p;
5321 static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
5323 GetPositionNumber(); // use FRC position number
5325 if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
5326 SetCharTable(pieceToChar, appData.pieceToCharTable);
5327 for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
5328 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
5331 j = seed%4; seed /= 4;
5332 p = board[0][BOARD_LEFT+j]; board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5333 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
5334 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
5335 j = seed%3 + (seed%3 >= j); seed /= 3;
5336 p = board[0][BOARD_LEFT+j]; board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5337 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
5338 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
5339 j = seed%3; seed /= 3;
5340 p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5341 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
5342 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
5343 j = seed%2 + (seed%2 >= j); seed /= 2;
5344 p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5345 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
5346 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
5347 j = seed%4; seed /= 4; put(board, exoPieces[3], 0, j, ANY);
5348 j = seed%3; seed /= 3; put(board, exoPieces[2], 0, j, ANY);
5349 j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
5350 put(board, exoPieces[0], 0, 0, ANY);
5351 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
5355 InitPosition(redraw)
5358 ChessSquare (* pieces)[BOARD_FILES];
5359 int i, j, pawnRow, overrule,
5360 oldx = gameInfo.boardWidth,
5361 oldy = gameInfo.boardHeight,
5362 oldh = gameInfo.holdingsWidth;
5365 if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
5367 /* [AS] Initialize pv info list [HGM] and game status */
5369 for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
5370 pvInfoList[i].depth = 0;
5371 boards[i][EP_STATUS] = EP_NONE;
5372 for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
5375 initialRulePlies = 0; /* 50-move counter start */
5377 castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
5378 castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
5382 /* [HGM] logic here is completely changed. In stead of full positions */
5383 /* the initialized data only consist of the two backranks. The switch */
5384 /* selects which one we will use, which is than copied to the Board */
5385 /* initialPosition, which for the rest is initialized by Pawns and */
5386 /* empty squares. This initial position is then copied to boards[0], */
5387 /* possibly after shuffling, so that it remains available. */
5389 gameInfo.holdingsWidth = 0; /* default board sizes */
5390 gameInfo.boardWidth = 8;
5391 gameInfo.boardHeight = 8;
5392 gameInfo.holdingsSize = 0;
5393 nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
5394 for(i=0; i<BOARD_FILES-2; i++)
5395 initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
5396 initialPosition[EP_STATUS] = EP_NONE;
5397 SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
5398 if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
5399 SetCharTable(pieceNickName, appData.pieceNickNames);
5400 else SetCharTable(pieceNickName, "............");
5403 switch (gameInfo.variant) {
5404 case VariantFischeRandom:
5405 shuffleOpenings = TRUE;
5408 case VariantShatranj:
5409 pieces = ShatranjArray;
5410 nrCastlingRights = 0;
5411 SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
5414 pieces = makrukArray;
5415 nrCastlingRights = 0;
5416 startedFromSetupPosition = TRUE;
5417 SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
5419 case VariantTwoKings:
5420 pieces = twoKingsArray;
5422 case VariantCapaRandom:
5423 shuffleOpenings = TRUE;
5424 case VariantCapablanca:
5425 pieces = CapablancaArray;
5426 gameInfo.boardWidth = 10;
5427 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5430 pieces = GothicArray;
5431 gameInfo.boardWidth = 10;
5432 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5435 SetCharTable(pieceToChar, "PNBRQ..HEKpnbrq..hek");
5436 gameInfo.holdingsSize = 7;
5439 pieces = JanusArray;
5440 gameInfo.boardWidth = 10;
5441 SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
5442 nrCastlingRights = 6;
5443 initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5444 initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5445 initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
5446 initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5447 initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5448 initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
5451 pieces = FalconArray;
5452 gameInfo.boardWidth = 10;
5453 SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
5455 case VariantXiangqi:
5456 pieces = XiangqiArray;
5457 gameInfo.boardWidth = 9;
5458 gameInfo.boardHeight = 10;
5459 nrCastlingRights = 0;
5460 SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
5463 pieces = ShogiArray;
5464 gameInfo.boardWidth = 9;
5465 gameInfo.boardHeight = 9;
5466 gameInfo.holdingsSize = 7;
5467 nrCastlingRights = 0;
5468 SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
5470 case VariantCourier:
5471 pieces = CourierArray;
5472 gameInfo.boardWidth = 12;
5473 nrCastlingRights = 0;
5474 SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
5476 case VariantKnightmate:
5477 pieces = KnightmateArray;
5478 SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
5480 case VariantSpartan:
5481 pieces = SpartanArray;
5482 SetCharTable(pieceToChar, "PNBRQ................K......lwg.....c...h..k");
5485 pieces = fairyArray;
5486 SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
5489 pieces = GreatArray;
5490 gameInfo.boardWidth = 10;
5491 SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
5492 gameInfo.holdingsSize = 8;
5496 SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
5497 gameInfo.holdingsSize = 8;
5498 startedFromSetupPosition = TRUE;
5500 case VariantCrazyhouse:
5501 case VariantBughouse:
5503 SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
5504 gameInfo.holdingsSize = 5;
5506 case VariantWildCastle:
5508 /* !!?shuffle with kings guaranteed to be on d or e file */
5509 shuffleOpenings = 1;
5511 case VariantNoCastle:
5513 nrCastlingRights = 0;
5514 /* !!?unconstrained back-rank shuffle */
5515 shuffleOpenings = 1;
5520 if(appData.NrFiles >= 0) {
5521 if(gameInfo.boardWidth != appData.NrFiles) overrule++;
5522 gameInfo.boardWidth = appData.NrFiles;
5524 if(appData.NrRanks >= 0) {
5525 gameInfo.boardHeight = appData.NrRanks;
5527 if(appData.holdingsSize >= 0) {
5528 i = appData.holdingsSize;
5529 if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
5530 gameInfo.holdingsSize = i;
5532 if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
5533 if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
5534 DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
5536 pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
5537 if(pawnRow < 1) pawnRow = 1;
5538 if(gameInfo.variant == VariantMakruk) pawnRow = 2;
5540 /* User pieceToChar list overrules defaults */
5541 if(appData.pieceToCharTable != NULL)
5542 SetCharTable(pieceToChar, appData.pieceToCharTable);
5544 for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
5546 if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
5547 s = (ChessSquare) 0; /* account holding counts in guard band */
5548 for( i=0; i<BOARD_HEIGHT; i++ )
5549 initialPosition[i][j] = s;
5551 if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
5552 initialPosition[0][j] = pieces[0][j-gameInfo.holdingsWidth];
5553 initialPosition[pawnRow][j] = WhitePawn;
5554 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = gameInfo.variant == VariantSpartan ? BlackLance : BlackPawn;
5555 if(gameInfo.variant == VariantXiangqi) {
5557 initialPosition[pawnRow][j] =
5558 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
5559 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
5560 initialPosition[2][j] = WhiteCannon;
5561 initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
5565 initialPosition[BOARD_HEIGHT-1][j] = pieces[1][j-gameInfo.holdingsWidth];
5567 if( (gameInfo.variant == VariantShogi) && !overrule ) {
5570 initialPosition[1][j] = WhiteBishop;
5571 initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
5573 initialPosition[1][j] = WhiteRook;
5574 initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
5577 if( nrCastlingRights == -1) {
5578 /* [HGM] Build normal castling rights (must be done after board sizing!) */
5579 /* This sets default castling rights from none to normal corners */
5580 /* Variants with other castling rights must set them themselves above */
5581 nrCastlingRights = 6;
5583 initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5584 initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5585 initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
5586 initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5587 initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5588 initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
5591 if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
5592 if(gameInfo.variant == VariantGreat) { // promotion commoners
5593 initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
5594 initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
5595 initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
5596 initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
5598 if( gameInfo.variant == VariantSChess ) {
5599 initialPosition[1][0] = BlackMarshall;
5600 initialPosition[2][0] = BlackAngel;
5601 initialPosition[6][BOARD_WIDTH-1] = WhiteMarshall;
5602 initialPosition[5][BOARD_WIDTH-1] = WhiteAngel;
5603 initialPosition[1][1] = initialPosition[2][1] =
5604 initialPosition[6][BOARD_WIDTH-2] = initialPosition[5][BOARD_WIDTH-2] = 1;
5606 if (appData.debugMode) {
5607 fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
5609 if(shuffleOpenings) {
5610 SetUpShuffle(initialPosition, appData.defaultFrcPosition);
5611 startedFromSetupPosition = TRUE;
5613 if(startedFromPositionFile) {
5614 /* [HGM] loadPos: use PositionFile for every new game */
5615 CopyBoard(initialPosition, filePosition);
5616 for(i=0; i<nrCastlingRights; i++)
5617 initialRights[i] = filePosition[CASTLING][i];
5618 startedFromSetupPosition = TRUE;
5621 CopyBoard(boards[0], initialPosition);
5623 if(oldx != gameInfo.boardWidth ||
5624 oldy != gameInfo.boardHeight ||
5625 oldv != gameInfo.variant ||
5626 oldh != gameInfo.holdingsWidth
5628 InitDrawingSizes(-2 ,0);
5630 oldv = gameInfo.variant;
5632 DrawPosition(TRUE, boards[currentMove]);
5636 SendBoard(cps, moveNum)
5637 ChessProgramState *cps;
5640 char message[MSG_SIZ];
5642 if (cps->useSetboard) {
5643 char* fen = PositionToFEN(moveNum, cps->fenOverride);
5644 snprintf(message, MSG_SIZ,"setboard %s\n", fen);
5645 SendToProgram(message, cps);
5651 /* Kludge to set black to move, avoiding the troublesome and now
5652 * deprecated "black" command.
5654 if (!WhiteOnMove(moveNum)) // [HGM] but better a deprecated command than an illegal move...
5655 SendToProgram(boards[0][1][BOARD_LEFT] == WhitePawn ? "a2a3\n" : "black\n", cps);
5657 SendToProgram("edit\n", cps);
5658 SendToProgram("#\n", cps);
5659 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
5660 bp = &boards[moveNum][i][BOARD_LEFT];
5661 for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
5662 if ((int) *bp < (int) BlackPawn) {
5663 snprintf(message, MSG_SIZ, "%c%c%c\n", PieceToChar(*bp),
5665 if(message[0] == '+' || message[0] == '~') {
5666 snprintf(message, MSG_SIZ,"%c%c%c+\n",
5667 PieceToChar((ChessSquare)(DEMOTED *bp)),
5670 if(cps->alphaRank) { /* [HGM] shogi: translate coords */
5671 message[1] = BOARD_RGHT - 1 - j + '1';
5672 message[2] = BOARD_HEIGHT - 1 - i + 'a';
5674 SendToProgram(message, cps);
5679 SendToProgram("c\n", cps);
5680 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
5681 bp = &boards[moveNum][i][BOARD_LEFT];
5682 for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
5683 if (((int) *bp != (int) EmptySquare)
5684 && ((int) *bp >= (int) BlackPawn)) {
5685 snprintf(message,MSG_SIZ, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
5687 if(message[0] == '+' || message[0] == '~') {
5688 snprintf(message, MSG_SIZ,"%c%c%c+\n",
5689 PieceToChar((ChessSquare)(DEMOTED *bp)),
5692 if(cps->alphaRank) { /* [HGM] shogi: translate coords */
5693 message[1] = BOARD_RGHT - 1 - j + '1';
5694 message[2] = BOARD_HEIGHT - 1 - i + 'a';
5696 SendToProgram(message, cps);
5701 SendToProgram(".\n", cps);
5703 setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
5706 static int autoQueen; // [HGM] oneclick
5709 HasPromotionChoice(int fromX, int fromY, int toX, int toY, char *promoChoice)
5711 /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
5712 /* [HGM] add Shogi promotions */
5713 int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
5718 if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
5719 if(toX < BOARD_LEFT || toX >= BOARD_RGHT) return FALSE; // move into holdings
5721 if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
5722 !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
5725 piece = boards[currentMove][fromY][fromX];
5726 if(gameInfo.variant == VariantShogi) {
5727 promotionZoneSize = BOARD_HEIGHT/3;
5728 highestPromotingPiece = (int)WhiteFerz;
5729 } else if(gameInfo.variant == VariantMakruk) {
5730 promotionZoneSize = 3;
5733 // Treat Lance as Pawn when it is not representing Amazon
5734 if(gameInfo.variant != VariantSuper) {
5735 if(piece == WhiteLance) piece = WhitePawn; else
5736 if(piece == BlackLance) piece = BlackPawn;
5739 // next weed out all moves that do not touch the promotion zone at all
5740 if((int)piece >= BlackPawn) {
5741 if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
5743 highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
5745 if( toY < BOARD_HEIGHT - promotionZoneSize &&
5746 fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
5749 if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
5751 // weed out mandatory Shogi promotions
5752 if(gameInfo.variant == VariantShogi) {
5753 if(piece >= BlackPawn) {
5754 if(toY == 0 && piece == BlackPawn ||
5755 toY == 0 && piece == BlackQueen ||
5756 toY <= 1 && piece == BlackKnight) {
5761 if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
5762 toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
5763 toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
5770 // weed out obviously illegal Pawn moves
5771 if(appData.testLegality && (piece == WhitePawn || piece == BlackPawn) ) {
5772 if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
5773 if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
5774 if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
5775 if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
5776 // note we are not allowed to test for valid (non-)capture, due to premove
5779 // we either have a choice what to promote to, or (in Shogi) whether to promote
5780 if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk) {
5781 *promoChoice = PieceToChar(BlackFerz); // no choice
5784 // no sense asking what we must promote to if it is going to explode...
5785 if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) {
5786 *promoChoice = PieceToChar(BlackQueen); // Queen as good as any
5789 // give caller the default choice even if we will not make it
5790 if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway)
5791 *promoChoice = PieceToChar(BlackKing); // in Suicide Q is the last thing we want
5792 else if(gameInfo.variant == VariantSpartan)
5793 *promoChoice = ToLower(PieceToChar(toY ? WhiteQueen : BlackAngel));
5794 else if(gameInfo.variant == VariantShogi)
5796 else *promoChoice = PieceToChar(BlackQueen);
5797 if(autoQueen) return FALSE; // predetermined
5799 // suppress promotion popup on illegal moves that are not premoves
5800 premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
5801 gameMode == IcsPlayingBlack && WhiteOnMove(currentMove);
5802 if(appData.testLegality && !premove) {
5803 moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5804 fromY, fromX, toY, toX, gameInfo.variant == VariantShogi ? '+' : NULLCHAR);
5805 if(moveType != WhitePromotion && moveType != BlackPromotion)
5813 InPalace(row, column)
5815 { /* [HGM] for Xiangqi */
5816 if( (row < 3 || row > BOARD_HEIGHT-4) &&
5817 column < (BOARD_WIDTH + 4)/2 &&
5818 column > (BOARD_WIDTH - 5)/2 ) return TRUE;
5823 PieceForSquare (x, y)
5827 if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
5830 return boards[currentMove][y][x];
5834 OKToStartUserMove(x, y)
5837 ChessSquare from_piece;
5840 if (matchMode) return FALSE;
5841 if (gameMode == EditPosition) return TRUE;
5843 if (x >= 0 && y >= 0)
5844 from_piece = boards[currentMove][y][x];
5846 from_piece = EmptySquare;
5848 if (from_piece == EmptySquare) return FALSE;
5850 white_piece = (int)from_piece >= (int)WhitePawn &&
5851 (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
5854 case PlayFromGameFile:
5856 case TwoMachinesPlay:
5864 case MachinePlaysWhite:
5865 case IcsPlayingBlack:
5866 if (appData.zippyPlay) return FALSE;
5868 DisplayMoveError(_("You are playing Black"));
5873 case MachinePlaysBlack:
5874 case IcsPlayingWhite:
5875 if (appData.zippyPlay) return FALSE;
5877 DisplayMoveError(_("You are playing White"));
5883 if (!white_piece && WhiteOnMove(currentMove)) {
5884 DisplayMoveError(_("It is White's turn"));
5887 if (white_piece && !WhiteOnMove(currentMove)) {
5888 DisplayMoveError(_("It is Black's turn"));
5891 if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
5892 /* Editing correspondence game history */
5893 /* Could disallow this or prompt for confirmation */
5898 case BeginningOfGame:
5899 if (appData.icsActive) return FALSE;
5900 if (!appData.noChessProgram) {
5902 DisplayMoveError(_("You are playing White"));
5909 if (!white_piece && WhiteOnMove(currentMove)) {
5910 DisplayMoveError(_("It is White's turn"));
5913 if (white_piece && !WhiteOnMove(currentMove)) {
5914 DisplayMoveError(_("It is Black's turn"));
5923 if (currentMove != forwardMostMove && gameMode != AnalyzeMode
5924 && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
5925 && gameMode != AnalyzeFile && gameMode != Training) {
5926 DisplayMoveError(_("Displayed position is not current"));
5933 OnlyMove(int *x, int *y, Boolean captures) {
5934 DisambiguateClosure cl;
5935 if (appData.zippyPlay) return FALSE;
5937 case MachinePlaysBlack:
5938 case IcsPlayingWhite:
5939 case BeginningOfGame:
5940 if(!WhiteOnMove(currentMove)) return FALSE;
5942 case MachinePlaysWhite:
5943 case IcsPlayingBlack:
5944 if(WhiteOnMove(currentMove)) return FALSE;
5951 cl.pieceIn = EmptySquare;
5956 cl.promoCharIn = NULLCHAR;
5957 Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
5958 if( cl.kind == NormalMove ||
5959 cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
5960 cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
5961 cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
5968 if(cl.kind != ImpossibleMove) return FALSE;
5969 cl.pieceIn = EmptySquare;
5974 cl.promoCharIn = NULLCHAR;
5975 Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
5976 if( cl.kind == NormalMove ||
5977 cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
5978 cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
5979 cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
5984 autoQueen = TRUE; // act as if autoQueen on when we click to-square
5990 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
5991 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
5992 int lastLoadGameUseList = FALSE;
5993 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
5994 ChessMove lastLoadGameStart = EndOfFile;
5997 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
5998 int fromX, fromY, toX, toY;
6002 ChessSquare pdown, pup;
6004 /* Check if the user is playing in turn. This is complicated because we
6005 let the user "pick up" a piece before it is his turn. So the piece he
6006 tried to pick up may have been captured by the time he puts it down!
6007 Therefore we use the color the user is supposed to be playing in this
6008 test, not the color of the piece that is currently on the starting
6009 square---except in EditGame mode, where the user is playing both
6010 sides; fortunately there the capture race can't happen. (It can
6011 now happen in IcsExamining mode, but that's just too bad. The user
6012 will get a somewhat confusing message in that case.)
6016 case PlayFromGameFile:
6018 case TwoMachinesPlay:
6022 /* We switched into a game mode where moves are not accepted,
6023 perhaps while the mouse button was down. */
6026 case MachinePlaysWhite:
6027 /* User is moving for Black */
6028 if (WhiteOnMove(currentMove)) {
6029 DisplayMoveError(_("It is White's turn"));
6034 case MachinePlaysBlack:
6035 /* User is moving for White */
6036 if (!WhiteOnMove(currentMove)) {
6037 DisplayMoveError(_("It is Black's turn"));
6044 case BeginningOfGame:
6047 if(fromY == DROP_RANK) break; // [HGM] drop moves (entered through move type-in) are automatically assigned to side-to-move
6048 if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
6049 (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
6050 /* User is moving for Black */
6051 if (WhiteOnMove(currentMove)) {
6052 DisplayMoveError(_("It is White's turn"));
6056 /* User is moving for White */
6057 if (!WhiteOnMove(currentMove)) {
6058 DisplayMoveError(_("It is Black's turn"));
6064 case IcsPlayingBlack:
6065 /* User is moving for Black */
6066 if (WhiteOnMove(currentMove)) {
6067 if (!appData.premove) {
6068 DisplayMoveError(_("It is White's turn"));
6069 } else if (toX >= 0 && toY >= 0) {
6072 premoveFromX = fromX;
6073 premoveFromY = fromY;
6074 premovePromoChar = promoChar;
6076 if (appData.debugMode)
6077 fprintf(debugFP, "Got premove: fromX %d,"
6078 "fromY %d, toX %d, toY %d\n",
6079 fromX, fromY, toX, toY);
6085 case IcsPlayingWhite:
6086 /* User is moving for White */
6087 if (!WhiteOnMove(currentMove)) {
6088 if (!appData.premove) {
6089 DisplayMoveError(_("It is Black's turn"));
6090 } else if (toX >= 0 && toY >= 0) {
6093 premoveFromX = fromX;
6094 premoveFromY = fromY;
6095 premovePromoChar = promoChar;
6097 if (appData.debugMode)
6098 fprintf(debugFP, "Got premove: fromX %d,"
6099 "fromY %d, toX %d, toY %d\n",
6100 fromX, fromY, toX, toY);
6110 /* EditPosition, empty square, or different color piece;
6111 click-click move is possible */
6112 if (toX == -2 || toY == -2) {
6113 boards[0][fromY][fromX] = EmptySquare;
6114 DrawPosition(FALSE, boards[currentMove]);
6116 } else if (toX >= 0 && toY >= 0) {
6117 boards[0][toY][toX] = boards[0][fromY][fromX];
6118 if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
6119 if(boards[0][fromY][0] != EmptySquare) {
6120 if(boards[0][fromY][1]) boards[0][fromY][1]--;
6121 if(boards[0][fromY][1] == 0) boards[0][fromY][0] = EmptySquare;
6124 if(fromX == BOARD_RGHT+1) {
6125 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
6126 if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
6127 if(boards[0][fromY][BOARD_WIDTH-2] == 0) boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
6130 boards[0][fromY][fromX] = EmptySquare;
6131 DrawPosition(FALSE, boards[currentMove]);
6137 if(toX < 0 || toY < 0) return;
6138 pdown = boards[currentMove][fromY][fromX];
6139 pup = boards[currentMove][toY][toX];
6141 /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
6142 if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ) {
6143 if( pup != EmptySquare ) return;
6144 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
6145 if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n",
6146 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
6147 // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
6148 if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
6149 fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
6150 while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++;
6154 /* [HGM] always test for legality, to get promotion info */
6155 moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6156 fromY, fromX, toY, toX, promoChar);
6157 /* [HGM] but possibly ignore an IllegalMove result */
6158 if (appData.testLegality) {
6159 if (moveType == IllegalMove || moveType == ImpossibleMove) {
6160 DisplayMoveError(_("Illegal move"));
6165 FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
6168 /* Common tail of UserMoveEvent and DropMenuEvent */
6170 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
6172 int fromX, fromY, toX, toY;
6173 /*char*/int promoChar;
6177 if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) && promoChar != NULLCHAR) {
6178 // [HGM] superchess: suppress promotions to non-available piece
6179 int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
6180 if(WhiteOnMove(currentMove)) {
6181 if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
6183 if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
6187 /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
6188 move type in caller when we know the move is a legal promotion */
6189 if(moveType == NormalMove && promoChar)
6190 moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
6192 /* [HGM] <popupFix> The following if has been moved here from
6193 UserMoveEvent(). Because it seemed to belong here (why not allow
6194 piece drops in training games?), and because it can only be
6195 performed after it is known to what we promote. */
6196 if (gameMode == Training) {
6197 /* compare the move played on the board to the next move in the
6198 * game. If they match, display the move and the opponent's response.
6199 * If they don't match, display an error message.
6203 CopyBoard(testBoard, boards[currentMove]);
6204 ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
6206 if (CompareBoards(testBoard, boards[currentMove+1])) {
6207 ForwardInner(currentMove+1);
6209 /* Autoplay the opponent's response.
6210 * if appData.animate was TRUE when Training mode was entered,
6211 * the response will be animated.
6213 saveAnimate = appData.animate;
6214 appData.animate = animateTraining;
6215 ForwardInner(currentMove+1);
6216 appData.animate = saveAnimate;
6218 /* check for the end of the game */
6219 if (currentMove >= forwardMostMove) {
6220 gameMode = PlayFromGameFile;
6222 SetTrainingModeOff();
6223 DisplayInformation(_("End of game"));
6226 DisplayError(_("Incorrect move"), 0);
6231 /* Ok, now we know that the move is good, so we can kill
6232 the previous line in Analysis Mode */
6233 if ((gameMode == AnalyzeMode || gameMode == EditGame)
6234 && currentMove < forwardMostMove) {
6235 if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
6236 else forwardMostMove = currentMove;
6239 /* If we need the chess program but it's dead, restart it */
6240 ResurrectChessProgram();
6242 /* A user move restarts a paused game*/
6246 thinkOutput[0] = NULLCHAR;
6248 MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
6250 if(Adjudicate(NULL)) { // [HGM] adjudicate: take care of automatic game end
6251 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6255 if (gameMode == BeginningOfGame) {
6256 if (appData.noChessProgram) {
6257 gameMode = EditGame;
6261 gameMode = MachinePlaysBlack;
6264 snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
6266 if (first.sendName) {
6267 snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
6268 SendToProgram(buf, &first);
6275 /* Relay move to ICS or chess engine */
6276 if (appData.icsActive) {
6277 if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
6278 gameMode == IcsExamining) {
6279 if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
6280 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
6282 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6284 // also send plain move, in case ICS does not understand atomic claims
6285 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6289 if (first.sendTime && (gameMode == BeginningOfGame ||
6290 gameMode == MachinePlaysWhite ||
6291 gameMode == MachinePlaysBlack)) {
6292 SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
6294 if (gameMode != EditGame && gameMode != PlayFromGameFile) {
6295 // [HGM] book: if program might be playing, let it use book
6296 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
6297 first.maybeThinking = TRUE;
6298 } else SendMoveToProgram(forwardMostMove-1, &first);
6299 if (currentMove == cmailOldMove + 1) {
6300 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
6304 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6308 if(appData.testLegality)
6309 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
6315 if (WhiteOnMove(currentMove)) {
6316 GameEnds(BlackWins, "Black mates", GE_PLAYER);
6318 GameEnds(WhiteWins, "White mates", GE_PLAYER);
6322 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
6327 case MachinePlaysBlack:
6328 case MachinePlaysWhite:
6329 /* disable certain menu options while machine is thinking */
6330 SetMachineThinkingEnables();
6337 userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
6339 if(bookHit) { // [HGM] book: simulate book reply
6340 static char bookMove[MSG_SIZ]; // a bit generous?
6342 programStats.nodes = programStats.depth = programStats.time =
6343 programStats.score = programStats.got_only_move = 0;
6344 sprintf(programStats.movelist, "%s (xbook)", bookHit);
6346 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
6347 strcat(bookMove, bookHit);
6348 HandleMachineMove(bookMove, &first);
6354 Mark(board, flags, kind, rf, ff, rt, ft, closure)
6361 typedef char Markers[BOARD_RANKS][BOARD_FILES];
6362 Markers *m = (Markers *) closure;
6363 if(rf == fromY && ff == fromX)
6364 (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
6365 || kind == WhiteCapturesEnPassant
6366 || kind == BlackCapturesEnPassant);
6367 else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
6371 MarkTargetSquares(int clear)
6374 if(!appData.markers || !appData.highlightDragging ||
6375 !appData.testLegality || gameMode == EditPosition) return;
6377 for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) marker[y][x] = 0;
6380 GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker);
6381 if(PosFlags(0) & F_MANDATORY_CAPTURE) {
6382 for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
6384 for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
6387 DrawPosition(TRUE, NULL);
6391 Explode(Board board, int fromX, int fromY, int toX, int toY)
6393 if(gameInfo.variant == VariantAtomic &&
6394 (board[toY][toX] != EmptySquare || // capture?
6395 toX != fromX && (board[fromY][fromX] == WhitePawn || // e.p. ?
6396 board[fromY][fromX] == BlackPawn )
6398 AnimateAtomicCapture(board, fromX, fromY, toX, toY);
6404 ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
6406 void LeftClick(ClickType clickType, int xPix, int yPix)
6409 Boolean saveAnimate;
6410 static int second = 0, promotionChoice = 0, dragging = 0;
6411 char promoChoice = NULLCHAR;
6413 if(appData.seekGraph && appData.icsActive && loggedOn &&
6414 (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
6415 SeekGraphClick(clickType, xPix, yPix, 0);
6419 if (clickType == Press) ErrorPopDown();
6420 MarkTargetSquares(1);
6422 x = EventToSquare(xPix, BOARD_WIDTH);
6423 y = EventToSquare(yPix, BOARD_HEIGHT);
6424 if (!flipView && y >= 0) {
6425 y = BOARD_HEIGHT - 1 - y;
6427 if (flipView && x >= 0) {
6428 x = BOARD_WIDTH - 1 - x;
6431 if(promoSweep != EmptySquare) {
6432 char promoChar = ToLower(PieceToChar(promoSweep));
6433 if(gameInfo.variant == VariantShogi) promoChar = promoSweep == boards[currentMove][fromY][fromX] ? '=' : '+';
6434 saveAnimate = appData.animate; appData.animate = FALSE;
6435 UserMoveEvent(fromX, fromY, toX, toY, promoChar);
6436 appData.animate = saveAnimate;
6437 promoSweep = EmptySquare;
6438 DrawPosition(FALSE, boards[currentMove]);
6443 if(promotionChoice) { // we are waiting for a click to indicate promotion piece
6444 if(clickType == Release) return; // ignore upclick of click-click destination
6445 promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
6446 if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
6447 if(gameInfo.holdingsWidth &&
6448 (WhiteOnMove(currentMove)
6449 ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y > 0
6450 : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT-1) ) {
6451 // click in right holdings, for determining promotion piece
6452 ChessSquare p = boards[currentMove][y][x];
6453 if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
6454 if(p != EmptySquare) {
6455 FinishMove(NormalMove, fromX, fromY, toX, toY, ToLower(PieceToChar(p)));
6460 DrawPosition(FALSE, boards[currentMove]);
6464 /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
6465 if(clickType == Press
6466 && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
6467 || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
6468 || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
6471 autoQueen = appData.alwaysPromoteToQueen;
6474 gatingPiece = EmptySquare;
6475 if (clickType != Press) {
6476 if(dragging) { // [HGM] from-square must have been reset due to game end since last press
6477 DragPieceEnd(xPix, yPix); dragging = 0;
6478 DrawPosition(FALSE, NULL);
6482 if(!appData.oneClick || !OnlyMove(&x, &y, FALSE)) {
6484 if (OKToStartUserMove(x, y)) {
6488 MarkTargetSquares(0);
6489 DragPieceBegin(xPix, yPix); dragging = 1;
6490 if (appData.highlightDragging) {
6491 SetHighlights(x, y, -1, -1);
6499 if (clickType == Press && gameMode != EditPosition) {
6504 // ignore off-board to clicks
6505 if(y < 0 || x < 0) return;
6507 /* Check if clicking again on the same color piece */
6508 fromP = boards[currentMove][fromY][fromX];
6509 toP = boards[currentMove][y][x];
6510 frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess;
6511 if ((WhitePawn <= fromP && fromP <= WhiteKing &&
6512 WhitePawn <= toP && toP <= WhiteKing &&
6513 !(fromP == WhiteKing && toP == WhiteRook && frc) &&
6514 !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
6515 (BlackPawn <= fromP && fromP <= BlackKing &&
6516 BlackPawn <= toP && toP <= BlackKing &&
6517 !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
6518 !(fromP == BlackKing && toP == BlackRook && frc))) {
6519 /* Clicked again on same color piece -- changed his mind */
6520 second = (x == fromX && y == fromY);
6521 if(!second || appData.oneClick && !OnlyMove(&x, &y, TRUE)) {
6522 if (appData.highlightDragging) {
6523 SetHighlights(x, y, -1, -1);
6527 if (OKToStartUserMove(x, y)) {
6528 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
6529 (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
6530 y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
6531 gatingPiece = boards[currentMove][fromY][fromX];
6532 else gatingPiece = EmptySquare;
6534 fromY = y; dragging = 1;
6535 MarkTargetSquares(0);
6536 DragPieceBegin(xPix, yPix);
6539 if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
6542 // ignore clicks on holdings
6543 if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
6546 if (clickType == Release && x == fromX && y == fromY) {
6547 DragPieceEnd(xPix, yPix); dragging = 0;
6548 if (appData.animateDragging) {
6549 /* Undo animation damage if any */
6550 DrawPosition(FALSE, NULL);
6553 /* Second up/down in same square; just abort move */
6556 gatingPiece = EmptySquare;
6559 ClearPremoveHighlights();
6561 /* First upclick in same square; start click-click mode */
6562 SetHighlights(x, y, -1, -1);
6567 /* we now have a different from- and (possibly off-board) to-square */
6568 /* Completed move */
6571 saveAnimate = appData.animate;
6572 if (clickType == Press) {
6573 /* Finish clickclick move */
6574 if (appData.animate || appData.highlightLastMove) {
6575 SetHighlights(fromX, fromY, toX, toY);
6580 /* Finish drag move */
6581 if (appData.highlightLastMove) {
6582 SetHighlights(fromX, fromY, toX, toY);
6586 DragPieceEnd(xPix, yPix); dragging = 0;
6587 /* Don't animate move and drag both */
6588 appData.animate = FALSE;
6591 // moves into holding are invalid for now (except in EditPosition, adapting to-square)
6592 if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
6593 ChessSquare piece = boards[currentMove][fromY][fromX];
6594 if(gameMode == EditPosition && piece != EmptySquare &&
6595 fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
6598 if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
6599 n = PieceToNumber(piece - (int)BlackPawn);
6600 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
6601 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
6602 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
6604 if(x == BOARD_RGHT+1 && piece < BlackPawn) {
6605 n = PieceToNumber(piece);
6606 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
6607 boards[currentMove][n][BOARD_WIDTH-1] = piece;
6608 boards[currentMove][n][BOARD_WIDTH-2]++;
6610 boards[currentMove][fromY][fromX] = EmptySquare;
6614 DrawPosition(TRUE, boards[currentMove]);
6618 // off-board moves should not be highlighted
6619 if(x < 0 || y < 0) ClearHighlights();
6621 if(gatingPiece != EmptySquare) promoChoice = ToLower(PieceToChar(gatingPiece));
6623 if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice)) {
6624 SetHighlights(fromX, fromY, toX, toY);
6625 if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
6626 // [HGM] super: promotion to captured piece selected from holdings
6627 ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
6628 promotionChoice = TRUE;
6629 // kludge follows to temporarily execute move on display, without promoting yet
6630 boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
6631 boards[currentMove][toY][toX] = p;
6632 DrawPosition(FALSE, boards[currentMove]);
6633 boards[currentMove][fromY][fromX] = p; // take back, but display stays
6634 boards[currentMove][toY][toX] = q;
6635 DisplayMessage("Click in holdings to choose piece", "");
6638 if(appData.sweepSelect && clickType == Press) {
6639 lastX = xPix; lastY = yPix;
6640 ChessSquare piece = boards[currentMove][fromY][fromX];
6641 promoSweep = CharToPiece((piece >= BlackPawn ? ToLower : ToUpper)(promoChoice));
6642 if(promoChoice == '+') promoSweep = PROMOTED piece;
6644 } else PromotionPopUp();
6646 int oldMove = currentMove;
6647 UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
6648 if (!appData.highlightLastMove || gotPremove) ClearHighlights();
6649 if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
6650 if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
6651 Explode(boards[currentMove-1], fromX, fromY, toX, toY))
6652 DrawPosition(TRUE, boards[currentMove]);
6655 appData.animate = saveAnimate;
6656 if (appData.animate || appData.animateDragging) {
6657 /* Undo animation damage if needed */
6658 DrawPosition(FALSE, NULL);
6662 int RightClick(ClickType action, int x, int y, int *fromX, int *fromY)
6663 { // front-end-free part taken out of PieceMenuPopup
6664 int whichMenu; int xSqr, ySqr;
6666 if(seekGraphUp) { // [HGM] seekgraph
6667 if(action == Press) SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
6668 if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
6672 if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
6673 && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
6674 if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
6675 if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
6676 if(action == Press) {
6677 originalFlip = flipView;
6678 flipView = !flipView; // temporarily flip board to see game from partners perspective
6679 DrawPosition(TRUE, partnerBoard);
6680 DisplayMessage(partnerStatus, "");
6682 } else if(action == Release) {
6683 flipView = originalFlip;
6684 DrawPosition(TRUE, boards[currentMove]);
6690 xSqr = EventToSquare(x, BOARD_WIDTH);
6691 ySqr = EventToSquare(y, BOARD_HEIGHT);
6692 if (action == Release) {
6693 if(pieceSweep != EmptySquare) {
6694 EditPositionMenuEvent(pieceSweep, toX, toY);
6695 pieceSweep = EmptySquare;
6696 } else UnLoadPV(); // [HGM] pv
6698 if (action != Press) return -2; // return code to be ignored
6701 if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;
\r
6703 if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;
\r
6704 if (xSqr < 0 || ySqr < 0) return -1;
6705 if(appData.pieceMenu) { whichMenu = 0; break; } // edit-position menu
6706 pieceSweep = shiftKey ? BlackPawn : WhitePawn; // [HGM] sweep: prepare selecting piece by mouse sweep
6707 toX = xSqr; toY = ySqr; lastX = x, lastY = y;
6708 if(flipView) toX = BOARD_WIDTH - 1 - toX; else toY = BOARD_HEIGHT - 1 - toY;
6712 if(!appData.icsEngineAnalyze) return -1;
6713 case IcsPlayingWhite:
6714 case IcsPlayingBlack:
6715 if(!appData.zippyPlay) goto noZip;
6718 case MachinePlaysWhite:
6719 case MachinePlaysBlack:
6720 case TwoMachinesPlay: // [HGM] pv: use for showing PV
6721 if (!appData.dropMenu) {
6723 return 2; // flag front-end to grab mouse events
6725 if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
6726 gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
6729 if (xSqr < 0 || ySqr < 0) return -1;
6730 if (!appData.dropMenu || appData.testLegality &&
6731 gameInfo.variant != VariantBughouse &&
6732 gameInfo.variant != VariantCrazyhouse) return -1;
6733 whichMenu = 1; // drop menu
6739 if (((*fromX = xSqr) < 0) ||
6740 ((*fromY = ySqr) < 0)) {
6741 *fromX = *fromY = -1;
6745 *fromX = BOARD_WIDTH - 1 - *fromX;
6747 *fromY = BOARD_HEIGHT - 1 - *fromY;
6752 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
6754 // char * hint = lastHint;
6755 FrontEndProgramStats stats;
6757 stats.which = cps == &first ? 0 : 1;
6758 stats.depth = cpstats->depth;
6759 stats.nodes = cpstats->nodes;
6760 stats.score = cpstats->score;
6761 stats.time = cpstats->time;
6762 stats.pv = cpstats->movelist;
6763 stats.hint = lastHint;
6764 stats.an_move_index = 0;
6765 stats.an_move_count = 0;
6767 if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
6768 stats.hint = cpstats->move_name;
6769 stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
6770 stats.an_move_count = cpstats->nr_moves;
6773 if(stats.pv && stats.pv[0]) safeStrCpy(lastPV[stats.which], stats.pv, sizeof(lastPV[stats.which])/sizeof(lastPV[stats.which][0])); // [HGM] pv: remember last PV of each
6775 SetProgramStats( &stats );
6779 Count(Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
6780 { // count all piece types
6782 *nB = *nW = *wStale = *bStale = *bishopColor = 0;
6783 for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
6784 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
6787 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
6788 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
6789 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
6790 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
6791 p == BlackBishop || p == BlackFerz || p == BlackAlfil )
6792 *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
6797 SufficientDefence(int pCnt[], int side, int nMine, int nHis)
6799 int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
6800 int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
6802 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
6803 if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
6804 if(myPawns == 2 && nMine == 3) // KPP
6805 return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
6806 if(myPawns == 1 && nMine == 2) // KP
6807 return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
6808 if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
6809 return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
6810 if(myPawns) return FALSE;
6811 if(pCnt[WhiteRook+side])
6812 return pCnt[BlackRook-side] ||
6813 pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
6814 pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
6815 pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
6816 if(pCnt[WhiteCannon+side]) {
6817 if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
6818 return majorDefense || pCnt[BlackAlfil-side] >= 2;
6820 if(pCnt[WhiteKnight+side])
6821 return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
6826 MatingPotential(int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
6828 VariantClass v = gameInfo.variant;
6830 if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
6831 if(v == VariantShatranj) return TRUE; // always winnable through baring
6832 if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
6833 if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
6835 if(v == VariantXiangqi) {
6836 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
6838 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
6839 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
6840 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
6841 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
6842 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
6843 if(stale) // we have at least one last-rank P plus perhaps C
6844 return majors // KPKX
6845 || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
6847 return pCnt[WhiteFerz+side] // KCAK
6848 || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
6849 || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
6850 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
6852 } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
6853 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
6855 if(nMine == 1) return FALSE; // bare King
6856 if(nBishops && bisColor == 3) return TRUE; // There must be a second B/A/F, which can either block (his) or attack (mine) the escape square
6857 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
6858 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
6859 // by now we have King + 1 piece (or multiple Bishops on the same color)
6860 if(pCnt[WhiteKnight+side])
6861 return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
6862 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
6863 || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
6865 return (pCnt[BlackKnight-side]); // KBKN, KFKN
6866 if(pCnt[WhiteAlfil+side])
6867 return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
6868 if(pCnt[WhiteWazir+side])
6869 return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
6876 Adjudicate(ChessProgramState *cps)
6877 { // [HGM] some adjudications useful with buggy engines
6878 // [HGM] adjudicate: made into separate routine, which now can be called after every move
6879 // In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
6880 // Actually ending the game is now based on the additional internal condition canAdjudicate.
6881 // Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
6882 int k, count = 0; static int bare = 1;
6883 ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
6884 Boolean canAdjudicate = !appData.icsActive;
6886 // most tests only when we understand the game, i.e. legality-checking on
6887 if( appData.testLegality )
6888 { /* [HGM] Some more adjudications for obstinate engines */
6889 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+1], i;
6890 static int moveCount = 6;
6892 char *reason = NULL;
6894 /* Count what is on board. */
6895 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
6897 /* Some material-based adjudications that have to be made before stalemate test */
6898 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
6899 // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
6900 boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
6901 if(canAdjudicate && appData.checkMates) {
6903 SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
6904 GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
6905 "Xboard adjudication: King destroyed", GE_XBOARD );
6910 /* Bare King in Shatranj (loses) or Losers (wins) */
6911 if( nrW == 1 || nrB == 1) {
6912 if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
6913 boards[forwardMostMove][EP_STATUS] = EP_WINS; // mark as win, so it becomes claimable
6914 if(canAdjudicate && appData.checkMates) {
6916 SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
6917 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
6918 "Xboard adjudication: Bare king", GE_XBOARD );
6922 if( gameInfo.variant == VariantShatranj && --bare < 0)
6924 boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
6925 if(canAdjudicate && appData.checkMates) {
6926 /* but only adjudicate if adjudication enabled */
6928 SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
6929 GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
6930 "Xboard adjudication: Bare king", GE_XBOARD );
6937 // don't wait for engine to announce game end if we can judge ourselves
6938 switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
6940 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
6941 int i, checkCnt = 0; // (should really be done by making nr of checks part of game state)
6942 for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
6943 if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
6946 reason = "Xboard adjudication: 3rd check";
6947 boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
6957 reason = "Xboard adjudication: Stalemate";
6958 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
6959 boards[forwardMostMove][EP_STATUS] = EP_STALEMATE; // default result for stalemate is draw
6960 if(gameInfo.variant == VariantLosers || gameInfo.variant == VariantGiveaway) // [HGM] losers:
6961 boards[forwardMostMove][EP_STATUS] = EP_WINS; // in these variants stalemated is always a win
6962 else if(gameInfo.variant == VariantSuicide) // in suicide it depends
6963 boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
6964 ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
6965 EP_CHECKMATE : EP_WINS);
6966 else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
6967 boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
6971 reason = "Xboard adjudication: Checkmate";
6972 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
6976 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
6978 result = GameIsDrawn; break;
6980 result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
6982 result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
6986 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
6988 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6989 GameEnds( result, reason, GE_XBOARD );
6993 /* Next absolutely insufficient mating material. */
6994 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
6995 !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
6996 { /* includes KBK, KNK, KK of KBKB with like Bishops */
6998 /* always flag draws, for judging claims */
6999 boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
7001 if(canAdjudicate && appData.materialDraws) {
7002 /* but only adjudicate them if adjudication enabled */
7003 if(engineOpponent) {
7004 SendToProgram("force\n", engineOpponent); // suppress reply
7005 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
7007 GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
7012 /* Then some trivial draws (only adjudicate, cannot be claimed) */
7013 if(gameInfo.variant == VariantXiangqi ?
7014 SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
7016 ( nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
7017 || nr[WhiteQueen] && nr[BlackQueen]==1 /* KQKQ */
7018 || nr[WhiteKnight]==2 || nr[BlackKnight]==2 /* KNNK */
7019 || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
7021 if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
7022 { /* if the first 3 moves do not show a tactical win, declare draw */
7023 if(engineOpponent) {
7024 SendToProgram("force\n", engineOpponent); // suppress reply
7025 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7027 GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
7030 } else moveCount = 6;
7032 if (appData.debugMode) { int i;
7033 fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
7034 forwardMostMove, backwardMostMove, boards[backwardMostMove][EP_STATUS],
7035 appData.drawRepeats);
7036 for( i=forwardMostMove; i>=backwardMostMove; i-- )
7037 fprintf(debugFP, "%d ep=%d\n", i, (signed char)boards[i][EP_STATUS]);
7041 // Repetition draws and 50-move rule can be applied independently of legality testing
7043 /* Check for rep-draws */
7045 for(k = forwardMostMove-2;
7046 k>=backwardMostMove && k>=forwardMostMove-100 &&
7047 (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
7048 (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE;
7051 if(CompareBoards(boards[k], boards[forwardMostMove])) {
7052 /* compare castling rights */
7053 if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
7054 (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
7055 rights++; /* King lost rights, while rook still had them */
7056 if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
7057 if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
7058 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
7059 rights++; /* but at least one rook lost them */
7061 if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
7062 (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
7064 if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
7065 if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
7066 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
7069 if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
7070 && appData.drawRepeats > 1) {
7071 /* adjudicate after user-specified nr of repeats */
7072 int result = GameIsDrawn;
7073 char *details = "XBoard adjudication: repetition draw";
7074 if(gameInfo.variant == VariantXiangqi && appData.testLegality) {
7075 // [HGM] xiangqi: check for forbidden perpetuals
7076 int m, ourPerpetual = 1, hisPerpetual = 1;
7077 for(m=forwardMostMove; m>k; m-=2) {
7078 if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
7079 ourPerpetual = 0; // the current mover did not always check
7080 if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
7081 hisPerpetual = 0; // the opponent did not always check
7083 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
7084 ourPerpetual, hisPerpetual);
7085 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
7086 result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7087 details = "Xboard adjudication: perpetual checking";
7089 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
7090 break; // (or we would have caught him before). Abort repetition-checking loop.
7092 // Now check for perpetual chases
7093 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
7094 hisPerpetual = PerpetualChase(k, forwardMostMove);
7095 ourPerpetual = PerpetualChase(k+1, forwardMostMove);
7096 if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
7097 result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7098 details = "Xboard adjudication: perpetual chasing";
7100 if(hisPerpetual && !ourPerpetual) // he is chasing us, but did not repeat yet
7101 break; // Abort repetition-checking loop.
7103 // if neither of us is checking or chasing all the time, or both are, it is draw
7105 if(engineOpponent) {
7106 SendToProgram("force\n", engineOpponent); // suppress reply
7107 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7109 GameEnds( result, details, GE_XBOARD );
7112 if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
7113 boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
7117 /* Now we test for 50-move draws. Determine ply count */
7118 count = forwardMostMove;
7119 /* look for last irreversble move */
7120 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
7122 /* if we hit starting position, add initial plies */
7123 if( count == backwardMostMove )
7124 count -= initialRulePlies;
7125 count = forwardMostMove - count;
7126 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
7127 // adjust reversible move counter for checks in Xiangqi
7128 int i = forwardMostMove - count, inCheck = 0, lastCheck;
7129 if(i < backwardMostMove) i = backwardMostMove;
7130 while(i <= forwardMostMove) {
7131 lastCheck = inCheck; // check evasion does not count
7132 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
7133 if(inCheck || lastCheck) count--; // check does not count
7138 boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
7139 /* this is used to judge if draw claims are legal */
7140 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
7141 if(engineOpponent) {
7142 SendToProgram("force\n", engineOpponent); // suppress reply
7143 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7145 GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
7149 /* if draw offer is pending, treat it as a draw claim
7150 * when draw condition present, to allow engines a way to
7151 * claim draws before making their move to avoid a race
7152 * condition occurring after their move
7154 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
7156 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
7157 p = "Draw claim: 50-move rule";
7158 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
7159 p = "Draw claim: 3-fold repetition";
7160 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
7161 p = "Draw claim: insufficient mating material";
7162 if( p != NULL && canAdjudicate) {
7163 if(engineOpponent) {
7164 SendToProgram("force\n", engineOpponent); // suppress reply
7165 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7167 GameEnds( GameIsDrawn, p, GE_XBOARD );
7172 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
7173 if(engineOpponent) {
7174 SendToProgram("force\n", engineOpponent); // suppress reply
7175 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7177 GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
7183 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
7184 { // [HGM] book: this routine intercepts moves to simulate book replies
7185 char *bookHit = NULL;
7187 //first determine if the incoming move brings opponent into his book
7188 if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
7189 bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
7190 if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
7191 if(bookHit != NULL && !cps->bookSuspend) {
7192 // make sure opponent is not going to reply after receiving move to book position
7193 SendToProgram("force\n", cps);
7194 cps->bookSuspend = TRUE; // flag indicating it has to be restarted
7196 if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
7197 // now arrange restart after book miss
7199 // after a book hit we never send 'go', and the code after the call to this routine
7200 // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
7202 snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), bookHit); // force book move into program supposed to play it
7203 SendToProgram(buf, cps);
7204 if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
7205 } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
7206 SendToProgram("go\n", cps);
7207 cps->bookSuspend = FALSE; // after a 'go' we are never suspended
7208 } else { // 'go' might be sent based on 'firstMove' after this routine returns
7209 if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
7210 SendToProgram("go\n", cps);
7211 cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
7213 return bookHit; // notify caller of hit, so it can take action to send move to opponent
7217 ChessProgramState *savedState;
7218 void DeferredBookMove(void)
7220 if(savedState->lastPing != savedState->lastPong)
7221 ScheduleDelayedEvent(DeferredBookMove, 10);
7223 HandleMachineMove(savedMessage, savedState);
7227 HandleMachineMove(message, cps)
7229 ChessProgramState *cps;
7231 char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
7232 char realname[MSG_SIZ];
7233 int fromX, fromY, toX, toY;
7242 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
7244 * Kludge to ignore BEL characters
7246 while (*message == '\007') message++;
7249 * [HGM] engine debug message: ignore lines starting with '#' character
7251 if(cps->debug && *message == '#') return;
7254 * Look for book output
7256 if (cps == &first && bookRequested) {
7257 if (message[0] == '\t' || message[0] == ' ') {
7258 /* Part of the book output is here; append it */
7259 strcat(bookOutput, message);
7260 strcat(bookOutput, " \n");
7262 } else if (bookOutput[0] != NULLCHAR) {
7263 /* All of book output has arrived; display it */
7264 char *p = bookOutput;
7265 while (*p != NULLCHAR) {
7266 if (*p == '\t') *p = ' ';
7269 DisplayInformation(bookOutput);
7270 bookRequested = FALSE;
7271 /* Fall through to parse the current output */
7276 * Look for machine move.
7278 if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
7279 (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
7281 /* This method is only useful on engines that support ping */
7282 if (cps->lastPing != cps->lastPong) {
7283 if (gameMode == BeginningOfGame) {
7284 /* Extra move from before last new; ignore */
7285 if (appData.debugMode) {
7286 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7289 if (appData.debugMode) {
7290 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7291 cps->which, gameMode);
7294 SendToProgram("undo\n", cps);
7300 case BeginningOfGame:
7301 /* Extra move from before last reset; ignore */
7302 if (appData.debugMode) {
7303 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7310 /* Extra move after we tried to stop. The mode test is
7311 not a reliable way of detecting this problem, but it's
7312 the best we can do on engines that don't support ping.
7314 if (appData.debugMode) {
7315 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7316 cps->which, gameMode);
7318 SendToProgram("undo\n", cps);
7321 case MachinePlaysWhite:
7322 case IcsPlayingWhite:
7323 machineWhite = TRUE;
7326 case MachinePlaysBlack:
7327 case IcsPlayingBlack:
7328 machineWhite = FALSE;
7331 case TwoMachinesPlay:
7332 machineWhite = (cps->twoMachinesColor[0] == 'w');
7335 if (WhiteOnMove(forwardMostMove) != machineWhite) {
7336 if (appData.debugMode) {
7338 "Ignoring move out of turn by %s, gameMode %d"
7339 ", forwardMost %d\n",
7340 cps->which, gameMode, forwardMostMove);
7345 if (appData.debugMode) { int f = forwardMostMove;
7346 fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
7347 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
7348 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
7350 if(cps->alphaRank) AlphaRank(machineMove, 4);
7351 if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
7352 &fromX, &fromY, &toX, &toY, &promoChar)) {
7353 /* Machine move could not be parsed; ignore it. */
7354 snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
7355 machineMove, _(cps->which));
7356 DisplayError(buf1, 0);
7357 snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
7358 machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
7359 if (gameMode == TwoMachinesPlay) {
7360 GameEnds(machineWhite ? BlackWins : WhiteWins,
7366 /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
7367 /* So we have to redo legality test with true e.p. status here, */
7368 /* to make sure an illegal e.p. capture does not slip through, */
7369 /* to cause a forfeit on a justified illegal-move complaint */
7370 /* of the opponent. */
7371 if( gameMode==TwoMachinesPlay && appData.testLegality ) {
7373 moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
7374 fromY, fromX, toY, toX, promoChar);
7375 if (appData.debugMode) {
7377 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
7378 boards[forwardMostMove][CASTLING][i], castlingRank[i]);
7379 fprintf(debugFP, "castling rights\n");
7381 if(moveType == IllegalMove) {
7382 snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
7383 machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
7384 GameEnds(machineWhite ? BlackWins : WhiteWins,
7387 } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
7388 /* [HGM] Kludge to handle engines that send FRC-style castling
7389 when they shouldn't (like TSCP-Gothic) */
7391 case WhiteASideCastleFR:
7392 case BlackASideCastleFR:
7394 currentMoveString[2]++;
7396 case WhiteHSideCastleFR:
7397 case BlackHSideCastleFR:
7399 currentMoveString[2]--;
7401 default: ; // nothing to do, but suppresses warning of pedantic compilers
7404 hintRequested = FALSE;
7405 lastHint[0] = NULLCHAR;
7406 bookRequested = FALSE;
7407 /* Program may be pondering now */
7408 cps->maybeThinking = TRUE;
7409 if (cps->sendTime == 2) cps->sendTime = 1;
7410 if (cps->offeredDraw) cps->offeredDraw--;
7412 /* [AS] Save move info*/
7413 pvInfoList[ forwardMostMove ].score = programStats.score;
7414 pvInfoList[ forwardMostMove ].depth = programStats.depth;
7415 pvInfoList[ forwardMostMove ].time = programStats.time; // [HGM] PGNtime: take time from engine stats
7417 MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
7419 /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
7420 if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
7423 while( count < adjudicateLossPlies ) {
7424 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
7427 score = -score; /* Flip score for winning side */
7430 if( score > adjudicateLossThreshold ) {
7437 if( count >= adjudicateLossPlies ) {
7438 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7440 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7441 "Xboard adjudication",
7448 if(Adjudicate(cps)) {
7449 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7450 return; // [HGM] adjudicate: for all automatic game ends
7454 if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
7456 if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
7457 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
7459 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7461 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7463 if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
7464 char buf[3*MSG_SIZ];
7466 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
7467 programStats.score / 100.,
7469 programStats.time / 100.,
7470 (unsigned int)programStats.nodes,
7471 (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
7472 programStats.movelist);
7474 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
7479 /* [AS] Clear stats for next move */
7480 ClearProgramStats();
7481 thinkOutput[0] = NULLCHAR;
7482 hiddenThinkOutputState = 0;
7485 if (gameMode == TwoMachinesPlay) {
7486 /* [HGM] relaying draw offers moved to after reception of move */
7487 /* and interpreting offer as claim if it brings draw condition */
7488 if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
7489 SendToProgram("draw\n", cps->other);
7491 if (cps->other->sendTime) {
7492 SendTimeRemaining(cps->other,
7493 cps->other->twoMachinesColor[0] == 'w');
7495 bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
7496 if (firstMove && !bookHit) {
7498 if (cps->other->useColors) {
7499 SendToProgram(cps->other->twoMachinesColor, cps->other);
7501 SendToProgram("go\n", cps->other);
7503 cps->other->maybeThinking = TRUE;
7506 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7508 if (!pausing && appData.ringBellAfterMoves) {
7513 * Reenable menu items that were disabled while
7514 * machine was thinking
7516 if (gameMode != TwoMachinesPlay)
7517 SetUserThinkingEnables();
7519 // [HGM] book: after book hit opponent has received move and is now in force mode
7520 // force the book reply into it, and then fake that it outputted this move by jumping
7521 // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
7523 static char bookMove[MSG_SIZ]; // a bit generous?
7525 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
7526 strcat(bookMove, bookHit);
7529 programStats.nodes = programStats.depth = programStats.time =
7530 programStats.score = programStats.got_only_move = 0;
7531 sprintf(programStats.movelist, "%s (xbook)", bookHit);
7533 if(cps->lastPing != cps->lastPong) {
7534 savedMessage = message; // args for deferred call
7536 ScheduleDelayedEvent(DeferredBookMove, 10);
7545 /* Set special modes for chess engines. Later something general
7546 * could be added here; for now there is just one kludge feature,
7547 * needed because Crafty 15.10 and earlier don't ignore SIGINT
7548 * when "xboard" is given as an interactive command.
7550 if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
7551 cps->useSigint = FALSE;
7552 cps->useSigterm = FALSE;
7554 if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
7555 ParseFeatures(message+8, cps);
7556 return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
7559 if (!appData.testLegality && !strncmp(message, "setup ", 6)) { // [HGM] allow first engine to define opening position
7560 int dummy, s=6; char buf[MSG_SIZ];
7561 if(appData.icsActive || forwardMostMove != 0 || cps != &first || startedFromSetupPosition) return;
7562 if(sscanf(message, "setup (%s", buf) == 1) s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTable(pieceToChar, buf);
7563 ParseFEN(boards[0], &dummy, message+s);
7564 DrawPosition(TRUE, boards[0]);
7565 startedFromSetupPosition = TRUE;
7568 /* [HGM] Allow engine to set up a position. Don't ask me why one would
7569 * want this, I was asked to put it in, and obliged.
7571 if (!strncmp(message, "setboard ", 9)) {
7572 Board initial_position;
7574 GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
7576 if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
7577 DisplayError(_("Bad FEN received from engine"), 0);
7581 CopyBoard(boards[0], initial_position);
7582 initialRulePlies = FENrulePlies;
7583 if(blackPlaysFirst) gameMode = MachinePlaysWhite;
7584 else gameMode = MachinePlaysBlack;
7585 DrawPosition(FALSE, boards[currentMove]);
7591 * Look for communication commands
7593 if (!strncmp(message, "telluser ", 9)) {
7594 if(message[9] == '\\' && message[10] == '\\')
7595 EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
7596 DisplayNote(message + 9);
7599 if (!strncmp(message, "tellusererror ", 14)) {
7601 if(message[14] == '\\' && message[15] == '\\')
7602 EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
7603 DisplayError(message + 14, 0);
7606 if (!strncmp(message, "tellopponent ", 13)) {
7607 if (appData.icsActive) {
7609 snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
7613 DisplayNote(message + 13);
7617 if (!strncmp(message, "tellothers ", 11)) {
7618 if (appData.icsActive) {
7620 snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
7626 if (!strncmp(message, "tellall ", 8)) {
7627 if (appData.icsActive) {
7629 snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
7633 DisplayNote(message + 8);
7637 if (strncmp(message, "warning", 7) == 0) {
7638 /* Undocumented feature, use tellusererror in new code */
7639 DisplayError(message, 0);
7642 if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
7643 safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
7644 strcat(realname, " query");
7645 AskQuestion(realname, buf2, buf1, cps->pr);
7648 /* Commands from the engine directly to ICS. We don't allow these to be
7649 * sent until we are logged on. Crafty kibitzes have been known to
7650 * interfere with the login process.
7653 if (!strncmp(message, "tellics ", 8)) {
7654 SendToICS(message + 8);
7658 if (!strncmp(message, "tellicsnoalias ", 15)) {
7659 SendToICS(ics_prefix);
7660 SendToICS(message + 15);
7664 /* The following are for backward compatibility only */
7665 if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
7666 !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
7667 SendToICS(ics_prefix);
7673 if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
7677 * If the move is illegal, cancel it and redraw the board.
7678 * Also deal with other error cases. Matching is rather loose
7679 * here to accommodate engines written before the spec.
7681 if (strncmp(message + 1, "llegal move", 11) == 0 ||
7682 strncmp(message, "Error", 5) == 0) {
7683 if (StrStr(message, "name") ||
7684 StrStr(message, "rating") || StrStr(message, "?") ||
7685 StrStr(message, "result") || StrStr(message, "board") ||
7686 StrStr(message, "bk") || StrStr(message, "computer") ||
7687 StrStr(message, "variant") || StrStr(message, "hint") ||
7688 StrStr(message, "random") || StrStr(message, "depth") ||
7689 StrStr(message, "accepted")) {
7692 if (StrStr(message, "protover")) {
7693 /* Program is responding to input, so it's apparently done
7694 initializing, and this error message indicates it is
7695 protocol version 1. So we don't need to wait any longer
7696 for it to initialize and send feature commands. */
7697 FeatureDone(cps, 1);
7698 cps->protocolVersion = 1;
7701 cps->maybeThinking = FALSE;
7703 if (StrStr(message, "draw")) {
7704 /* Program doesn't have "draw" command */
7705 cps->sendDrawOffers = 0;
7708 if (cps->sendTime != 1 &&
7709 (StrStr(message, "time") || StrStr(message, "otim"))) {
7710 /* Program apparently doesn't have "time" or "otim" command */
7714 if (StrStr(message, "analyze")) {
7715 cps->analysisSupport = FALSE;
7716 cps->analyzing = FALSE;
7718 snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
7719 DisplayError(buf2, 0);
7722 if (StrStr(message, "(no matching move)st")) {
7723 /* Special kludge for GNU Chess 4 only */
7724 cps->stKludge = TRUE;
7725 SendTimeControl(cps, movesPerSession, timeControl,
7726 timeIncrement, appData.searchDepth,
7730 if (StrStr(message, "(no matching move)sd")) {
7731 /* Special kludge for GNU Chess 4 only */
7732 cps->sdKludge = TRUE;
7733 SendTimeControl(cps, movesPerSession, timeControl,
7734 timeIncrement, appData.searchDepth,
7738 if (!StrStr(message, "llegal")) {
7741 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
7742 gameMode == IcsIdle) return;
7743 if (forwardMostMove <= backwardMostMove) return;
7744 if (pausing) PauseEvent();
7745 if(appData.forceIllegal) {
7746 // [HGM] illegal: machine refused move; force position after move into it
7747 SendToProgram("force\n", cps);
7748 if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
7749 // we have a real problem now, as SendBoard will use the a2a3 kludge
7750 // when black is to move, while there might be nothing on a2 or black
7751 // might already have the move. So send the board as if white has the move.
7752 // But first we must change the stm of the engine, as it refused the last move
7753 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
7754 if(WhiteOnMove(forwardMostMove)) {
7755 SendToProgram("a7a6\n", cps); // for the engine black still had the move
7756 SendBoard(cps, forwardMostMove); // kludgeless board
7758 SendToProgram("a2a3\n", cps); // for the engine white still had the move
7759 CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
7760 SendBoard(cps, forwardMostMove+1); // kludgeless board
7762 } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
7763 if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
7764 gameMode == TwoMachinesPlay)
7765 SendToProgram("go\n", cps);
7768 if (gameMode == PlayFromGameFile) {
7769 /* Stop reading this game file */
7770 gameMode = EditGame;
7773 /* [HGM] illegal-move claim should forfeit game when Xboard */
7774 /* only passes fully legal moves */
7775 if( appData.testLegality && gameMode == TwoMachinesPlay ) {
7776 GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
7777 "False illegal-move claim", GE_XBOARD );
7778 return; // do not take back move we tested as valid
7780 currentMove = forwardMostMove-1;
7781 DisplayMove(currentMove-1); /* before DisplayMoveError */
7782 SwitchClocks(forwardMostMove-1); // [HGM] race
7783 DisplayBothClocks();
7784 snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
7785 parseList[currentMove], _(cps->which));
7786 DisplayMoveError(buf1);
7787 DrawPosition(FALSE, boards[currentMove]);
7790 if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
7791 /* Program has a broken "time" command that
7792 outputs a string not ending in newline.
7798 * If chess program startup fails, exit with an error message.
7799 * Attempts to recover here are futile.
7801 if ((StrStr(message, "unknown host") != NULL)
7802 || (StrStr(message, "No remote directory") != NULL)
7803 || (StrStr(message, "not found") != NULL)
7804 || (StrStr(message, "No such file") != NULL)
7805 || (StrStr(message, "can't alloc") != NULL)
7806 || (StrStr(message, "Permission denied") != NULL)) {
7808 cps->maybeThinking = FALSE;
7809 snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
7810 _(cps->which), cps->program, cps->host, message);
7811 RemoveInputSource(cps->isr);
7812 DisplayFatalError(buf1, 0, 1);
7817 * Look for hint output
7819 if (sscanf(message, "Hint: %s", buf1) == 1) {
7820 if (cps == &first && hintRequested) {
7821 hintRequested = FALSE;
7822 if (ParseOneMove(buf1, forwardMostMove, &moveType,
7823 &fromX, &fromY, &toX, &toY, &promoChar)) {
7824 (void) CoordsToAlgebraic(boards[forwardMostMove],
7825 PosFlags(forwardMostMove),
7826 fromY, fromX, toY, toX, promoChar, buf1);
7827 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
7828 DisplayInformation(buf2);
7830 /* Hint move could not be parsed!? */
7831 snprintf(buf2, sizeof(buf2),
7832 _("Illegal hint move \"%s\"\nfrom %s chess program"),
7833 buf1, _(cps->which));
7834 DisplayError(buf2, 0);
7837 safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
7843 * Ignore other messages if game is not in progress
7845 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
7846 gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
7849 * look for win, lose, draw, or draw offer
7851 if (strncmp(message, "1-0", 3) == 0) {
7852 char *p, *q, *r = "";
7853 p = strchr(message, '{');
7861 GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
7863 } else if (strncmp(message, "0-1", 3) == 0) {
7864 char *p, *q, *r = "";
7865 p = strchr(message, '{');
7873 /* Kludge for Arasan 4.1 bug */
7874 if (strcmp(r, "Black resigns") == 0) {
7875 GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
7878 GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
7880 } else if (strncmp(message, "1/2", 3) == 0) {
7881 char *p, *q, *r = "";
7882 p = strchr(message, '{');
7891 GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
7894 } else if (strncmp(message, "White resign", 12) == 0) {
7895 GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
7897 } else if (strncmp(message, "Black resign", 12) == 0) {
7898 GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
7900 } else if (strncmp(message, "White matches", 13) == 0 ||
7901 strncmp(message, "Black matches", 13) == 0 ) {
7902 /* [HGM] ignore GNUShogi noises */
7904 } else if (strncmp(message, "White", 5) == 0 &&
7905 message[5] != '(' &&
7906 StrStr(message, "Black") == NULL) {
7907 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7909 } else if (strncmp(message, "Black", 5) == 0 &&
7910 message[5] != '(') {
7911 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7913 } else if (strcmp(message, "resign") == 0 ||
7914 strcmp(message, "computer resigns") == 0) {
7916 case MachinePlaysBlack:
7917 case IcsPlayingBlack:
7918 GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
7920 case MachinePlaysWhite:
7921 case IcsPlayingWhite:
7922 GameEnds(BlackWins, "White resigns", GE_ENGINE);
7924 case TwoMachinesPlay:
7925 if (cps->twoMachinesColor[0] == 'w')
7926 GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
7928 GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
7935 } else if (strncmp(message, "opponent mates", 14) == 0) {
7937 case MachinePlaysBlack:
7938 case IcsPlayingBlack:
7939 GameEnds(WhiteWins, "White mates", GE_ENGINE);
7941 case MachinePlaysWhite:
7942 case IcsPlayingWhite:
7943 GameEnds(BlackWins, "Black mates", GE_ENGINE);
7945 case TwoMachinesPlay:
7946 if (cps->twoMachinesColor[0] == 'w')
7947 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7949 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7956 } else if (strncmp(message, "computer mates", 14) == 0) {
7958 case MachinePlaysBlack:
7959 case IcsPlayingBlack:
7960 GameEnds(BlackWins, "Black mates", GE_ENGINE1);
7962 case MachinePlaysWhite:
7963 case IcsPlayingWhite:
7964 GameEnds(WhiteWins, "White mates", GE_ENGINE);
7966 case TwoMachinesPlay:
7967 if (cps->twoMachinesColor[0] == 'w')
7968 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7970 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7977 } else if (strncmp(message, "checkmate", 9) == 0) {
7978 if (WhiteOnMove(forwardMostMove)) {
7979 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7981 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7984 } else if (strstr(message, "Draw") != NULL ||
7985 strstr(message, "game is a draw") != NULL) {
7986 GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
7988 } else if (strstr(message, "offer") != NULL &&
7989 strstr(message, "draw") != NULL) {
7991 if (appData.zippyPlay && first.initDone) {
7992 /* Relay offer to ICS */
7993 SendToICS(ics_prefix);
7994 SendToICS("draw\n");
7997 cps->offeredDraw = 2; /* valid until this engine moves twice */
7998 if (gameMode == TwoMachinesPlay) {
7999 if (cps->other->offeredDraw) {
8000 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8001 /* [HGM] in two-machine mode we delay relaying draw offer */
8002 /* until after we also have move, to see if it is really claim */
8004 } else if (gameMode == MachinePlaysWhite ||
8005 gameMode == MachinePlaysBlack) {
8006 if (userOfferedDraw) {
8007 DisplayInformation(_("Machine accepts your draw offer"));
8008 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8010 DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
8017 * Look for thinking output
8019 if ( appData.showThinking // [HGM] thinking: test all options that cause this output
8020 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
8022 int plylev, mvleft, mvtot, curscore, time;
8023 char mvname[MOVE_LEN];
8027 int prefixHint = FALSE;
8028 mvname[0] = NULLCHAR;
8031 case MachinePlaysBlack:
8032 case IcsPlayingBlack:
8033 if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8035 case MachinePlaysWhite:
8036 case IcsPlayingWhite:
8037 if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8042 case IcsObserving: /* [DM] icsEngineAnalyze */
8043 if (!appData.icsEngineAnalyze) ignore = TRUE;
8045 case TwoMachinesPlay:
8046 if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
8056 ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
8058 if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8059 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
8061 if (plyext != ' ' && plyext != '\t') {
8065 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8066 if( cps->scoreIsAbsolute &&
8067 ( gameMode == MachinePlaysBlack ||
8068 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
8069 gameMode == IcsPlayingBlack || // [HGM] also add other situations where engine should report black POV
8070 (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
8071 !WhiteOnMove(currentMove)
8074 curscore = -curscore;
8078 tempStats.depth = plylev;
8079 tempStats.nodes = nodes;
8080 tempStats.time = time;
8081 tempStats.score = curscore;
8082 tempStats.got_only_move = 0;
8084 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
8087 if(cps->nps == 0) ticklen = 10*time; // use engine reported time
8088 else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
8089 if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
8090 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
8091 whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
8092 if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
8093 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
8094 blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
8097 /* Buffer overflow protection */
8098 if (buf1[0] != NULLCHAR) {
8099 if (strlen(buf1) >= sizeof(tempStats.movelist)
8100 && appData.debugMode) {
8102 "PV is too long; using the first %u bytes.\n",
8103 (unsigned) sizeof(tempStats.movelist) - 1);
8106 safeStrCpy( tempStats.movelist, buf1, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
8108 sprintf(tempStats.movelist, " no PV\n");
8111 if (tempStats.seen_stat) {
8112 tempStats.ok_to_send = 1;
8115 if (strchr(tempStats.movelist, '(') != NULL) {
8116 tempStats.line_is_book = 1;
8117 tempStats.nr_moves = 0;
8118 tempStats.moves_left = 0;
8120 tempStats.line_is_book = 0;
8123 if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
8124 programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
8126 SendProgramStatsToFrontend( cps, &tempStats );
8129 [AS] Protect the thinkOutput buffer from overflow... this
8130 is only useful if buf1 hasn't overflowed first!
8132 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%+.2f %s%s",
8134 (gameMode == TwoMachinesPlay ?
8135 ToUpper(cps->twoMachinesColor[0]) : ' '),
8136 ((double) curscore) / 100.0,
8137 prefixHint ? lastHint : "",
8138 prefixHint ? " " : "" );
8140 if( buf1[0] != NULLCHAR ) {
8141 unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
8143 if( strlen(buf1) > max_len ) {
8144 if( appData.debugMode) {
8145 fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
8147 buf1[max_len+1] = '\0';
8150 strcat( thinkOutput, buf1 );
8153 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
8154 || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8155 DisplayMove(currentMove - 1);
8159 } else if ((p=StrStr(message, "(only move)")) != NULL) {
8160 /* crafty (9.25+) says "(only move) <move>"
8161 * if there is only 1 legal move
8163 sscanf(p, "(only move) %s", buf1);
8164 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
8165 sprintf(programStats.movelist, "%s (only move)", buf1);
8166 programStats.depth = 1;
8167 programStats.nr_moves = 1;
8168 programStats.moves_left = 1;
8169 programStats.nodes = 1;
8170 programStats.time = 1;
8171 programStats.got_only_move = 1;
8173 /* Not really, but we also use this member to
8174 mean "line isn't going to change" (Crafty
8175 isn't searching, so stats won't change) */
8176 programStats.line_is_book = 1;
8178 SendProgramStatsToFrontend( cps, &programStats );
8180 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8181 gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8182 DisplayMove(currentMove - 1);
8185 } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
8186 &time, &nodes, &plylev, &mvleft,
8187 &mvtot, mvname) >= 5) {
8188 /* The stat01: line is from Crafty (9.29+) in response
8189 to the "." command */
8190 programStats.seen_stat = 1;
8191 cps->maybeThinking = TRUE;
8193 if (programStats.got_only_move || !appData.periodicUpdates)
8196 programStats.depth = plylev;
8197 programStats.time = time;
8198 programStats.nodes = nodes;
8199 programStats.moves_left = mvleft;
8200 programStats.nr_moves = mvtot;
8201 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
8202 programStats.ok_to_send = 1;
8203 programStats.movelist[0] = '\0';
8205 SendProgramStatsToFrontend( cps, &programStats );
8209 } else if (strncmp(message,"++",2) == 0) {
8210 /* Crafty 9.29+ outputs this */
8211 programStats.got_fail = 2;
8214 } else if (strncmp(message,"--",2) == 0) {
8215 /* Crafty 9.29+ outputs this */
8216 programStats.got_fail = 1;
8219 } else if (thinkOutput[0] != NULLCHAR &&
8220 strncmp(message, " ", 4) == 0) {
8221 unsigned message_len;
8224 while (*p && *p == ' ') p++;
8226 message_len = strlen( p );
8228 /* [AS] Avoid buffer overflow */
8229 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
8230 strcat(thinkOutput, " ");
8231 strcat(thinkOutput, p);
8234 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
8235 strcat(programStats.movelist, " ");
8236 strcat(programStats.movelist, p);
8239 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8240 gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8241 DisplayMove(currentMove - 1);
8249 if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8250 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
8252 ChessProgramStats cpstats;
8254 if (plyext != ' ' && plyext != '\t') {
8258 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8259 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
8260 curscore = -curscore;
8263 cpstats.depth = plylev;
8264 cpstats.nodes = nodes;
8265 cpstats.time = time;
8266 cpstats.score = curscore;
8267 cpstats.got_only_move = 0;
8268 cpstats.movelist[0] = '\0';
8270 if (buf1[0] != NULLCHAR) {
8271 safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
8274 cpstats.ok_to_send = 0;
8275 cpstats.line_is_book = 0;
8276 cpstats.nr_moves = 0;
8277 cpstats.moves_left = 0;
8279 SendProgramStatsToFrontend( cps, &cpstats );
8286 /* Parse a game score from the character string "game", and
8287 record it as the history of the current game. The game
8288 score is NOT assumed to start from the standard position.
8289 The display is not updated in any way.
8292 ParseGameHistory(game)
8296 int fromX, fromY, toX, toY, boardIndex;
8301 if (appData.debugMode)
8302 fprintf(debugFP, "Parsing game history: %s\n", game);
8304 if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
8305 gameInfo.site = StrSave(appData.icsHost);
8306 gameInfo.date = PGNDate();
8307 gameInfo.round = StrSave("-");
8309 /* Parse out names of players */
8310 while (*game == ' ') game++;
8312 while (*game != ' ') *p++ = *game++;
8314 gameInfo.white = StrSave(buf);
8315 while (*game == ' ') game++;
8317 while (*game != ' ' && *game != '\n') *p++ = *game++;
8319 gameInfo.black = StrSave(buf);
8322 boardIndex = blackPlaysFirst ? 1 : 0;
8325 yyboardindex = boardIndex;
8326 moveType = (ChessMove) Myylex();
8328 case IllegalMove: /* maybe suicide chess, etc. */
8329 if (appData.debugMode) {
8330 fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
8331 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8332 setbuf(debugFP, NULL);
8334 case WhitePromotion:
8335 case BlackPromotion:
8336 case WhiteNonPromotion:
8337 case BlackNonPromotion:
8339 case WhiteCapturesEnPassant:
8340 case BlackCapturesEnPassant:
8341 case WhiteKingSideCastle:
8342 case WhiteQueenSideCastle:
8343 case BlackKingSideCastle:
8344 case BlackQueenSideCastle:
8345 case WhiteKingSideCastleWild:
8346 case WhiteQueenSideCastleWild:
8347 case BlackKingSideCastleWild:
8348 case BlackQueenSideCastleWild:
8350 case WhiteHSideCastleFR:
8351 case WhiteASideCastleFR:
8352 case BlackHSideCastleFR:
8353 case BlackASideCastleFR:
8355 fromX = currentMoveString[0] - AAA;
8356 fromY = currentMoveString[1] - ONE;
8357 toX = currentMoveString[2] - AAA;
8358 toY = currentMoveString[3] - ONE;
8359 promoChar = currentMoveString[4];
8363 fromX = moveType == WhiteDrop ?
8364 (int) CharToPiece(ToUpper(currentMoveString[0])) :
8365 (int) CharToPiece(ToLower(currentMoveString[0]));
8367 toX = currentMoveString[2] - AAA;
8368 toY = currentMoveString[3] - ONE;
8369 promoChar = NULLCHAR;
8373 snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
8374 if (appData.debugMode) {
8375 fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
8376 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8377 setbuf(debugFP, NULL);
8379 DisplayError(buf, 0);
8381 case ImpossibleMove:
8383 snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
8384 if (appData.debugMode) {
8385 fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
8386 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8387 setbuf(debugFP, NULL);
8389 DisplayError(buf, 0);
8392 if (boardIndex < backwardMostMove) {
8393 /* Oops, gap. How did that happen? */
8394 DisplayError(_("Gap in move list"), 0);
8397 backwardMostMove = blackPlaysFirst ? 1 : 0;
8398 if (boardIndex > forwardMostMove) {
8399 forwardMostMove = boardIndex;
8403 if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
8404 strcat(parseList[boardIndex-1], " ");
8405 strcat(parseList[boardIndex-1], yy_text);
8417 case GameUnfinished:
8418 if (gameMode == IcsExamining) {
8419 if (boardIndex < backwardMostMove) {
8420 /* Oops, gap. How did that happen? */
8423 backwardMostMove = blackPlaysFirst ? 1 : 0;
8426 gameInfo.result = moveType;
8427 p = strchr(yy_text, '{');
8428 if (p == NULL) p = strchr(yy_text, '(');
8431 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
8433 q = strchr(p, *p == '{' ? '}' : ')');
8434 if (q != NULL) *q = NULLCHAR;
8437 while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
8438 gameInfo.resultDetails = StrSave(p);
8441 if (boardIndex >= forwardMostMove &&
8442 !(gameMode == IcsObserving && ics_gamenum == -1)) {
8443 backwardMostMove = blackPlaysFirst ? 1 : 0;
8446 (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
8447 fromY, fromX, toY, toX, promoChar,
8448 parseList[boardIndex]);
8449 CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
8450 /* currentMoveString is set as a side-effect of yylex */
8451 safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
8452 strcat(moveList[boardIndex], "\n");
8454 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
8455 switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
8461 if(gameInfo.variant != VariantShogi)
8462 strcat(parseList[boardIndex - 1], "+");
8466 strcat(parseList[boardIndex - 1], "#");
8473 /* Apply a move to the given board */
8475 ApplyMove(fromX, fromY, toX, toY, promoChar, board)
8476 int fromX, fromY, toX, toY;
8480 ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
8481 int promoRank = gameInfo.variant == VariantMakruk ? 3 : 1;
8483 /* [HGM] compute & store e.p. status and castling rights for new position */
8484 /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
8486 if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
8487 oldEP = (signed char)board[EP_STATUS];
8488 board[EP_STATUS] = EP_NONE;
8490 if( board[toY][toX] != EmptySquare )
8491 board[EP_STATUS] = EP_CAPTURE;
8493 if (fromY == DROP_RANK) {
8495 piece = board[toY][toX] = (ChessSquare) fromX;
8499 if( board[fromY][fromX] == WhiteLance || board[fromY][fromX] == BlackLance ) {
8500 if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi )
8501 board[EP_STATUS] = EP_PAWN_MOVE; // Lance is Pawn-like in most variants
8503 if( board[fromY][fromX] == WhitePawn ) {
8504 if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
8505 board[EP_STATUS] = EP_PAWN_MOVE;
8507 if(toX>BOARD_LEFT && board[toY][toX-1] == BlackPawn &&
8508 gameInfo.variant != VariantBerolina || toX < fromX)
8509 board[EP_STATUS] = toX | berolina;
8510 if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
8511 gameInfo.variant != VariantBerolina || toX > fromX)
8512 board[EP_STATUS] = toX;
8515 if( board[fromY][fromX] == BlackPawn ) {
8516 if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
8517 board[EP_STATUS] = EP_PAWN_MOVE;
8518 if( toY-fromY== -2) {
8519 if(toX>BOARD_LEFT && board[toY][toX-1] == WhitePawn &&
8520 gameInfo.variant != VariantBerolina || toX < fromX)
8521 board[EP_STATUS] = toX | berolina;
8522 if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
8523 gameInfo.variant != VariantBerolina || toX > fromX)
8524 board[EP_STATUS] = toX;
8528 for(i=0; i<nrCastlingRights; i++) {
8529 if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
8530 board[CASTLING][i] == toX && castlingRank[i] == toY
8531 ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
8534 if (fromX == toX && fromY == toY) return;
8536 piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
8537 king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
8538 if(gameInfo.variant == VariantKnightmate)
8539 king += (int) WhiteUnicorn - (int) WhiteKing;
8541 /* Code added by Tord: */
8542 /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
8543 if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
8544 board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
8545 board[fromY][fromX] = EmptySquare;
8546 board[toY][toX] = EmptySquare;
8547 if((toX > fromX) != (piece == WhiteRook)) {
8548 board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
8550 board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
8552 } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
8553 board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
8554 board[fromY][fromX] = EmptySquare;
8555 board[toY][toX] = EmptySquare;
8556 if((toX > fromX) != (piece == BlackRook)) {
8557 board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
8559 board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
8561 /* End of code added by Tord */
8563 } else if (board[fromY][fromX] == king
8564 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8565 && toY == fromY && toX > fromX+1) {
8566 board[fromY][fromX] = EmptySquare;
8567 board[toY][toX] = king;
8568 board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
8569 board[fromY][BOARD_RGHT-1] = EmptySquare;
8570 } else if (board[fromY][fromX] == king
8571 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8572 && toY == fromY && toX < fromX-1) {
8573 board[fromY][fromX] = EmptySquare;
8574 board[toY][toX] = king;
8575 board[toY][toX+1] = board[fromY][BOARD_LEFT];
8576 board[fromY][BOARD_LEFT] = EmptySquare;
8577 } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
8578 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
8579 && toY >= BOARD_HEIGHT-promoRank
8581 /* white pawn promotion */
8582 board[toY][toX] = CharToPiece(ToUpper(promoChar));
8583 if (board[toY][toX] == EmptySquare) {
8584 board[toY][toX] = WhiteQueen;
8586 if(gameInfo.variant==VariantBughouse ||
8587 gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
8588 board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
8589 board[fromY][fromX] = EmptySquare;
8590 } else if ((fromY == BOARD_HEIGHT-4)
8592 && gameInfo.variant != VariantXiangqi
8593 && gameInfo.variant != VariantBerolina
8594 && (board[fromY][fromX] == WhitePawn)
8595 && (board[toY][toX] == EmptySquare)) {
8596 board[fromY][fromX] = EmptySquare;
8597 board[toY][toX] = WhitePawn;
8598 captured = board[toY - 1][toX];
8599 board[toY - 1][toX] = EmptySquare;
8600 } else if ((fromY == BOARD_HEIGHT-4)
8602 && gameInfo.variant == VariantBerolina
8603 && (board[fromY][fromX] == WhitePawn)
8604 && (board[toY][toX] == EmptySquare)) {
8605 board[fromY][fromX] = EmptySquare;
8606 board[toY][toX] = WhitePawn;
8607 if(oldEP & EP_BEROLIN_A) {
8608 captured = board[fromY][fromX-1];
8609 board[fromY][fromX-1] = EmptySquare;
8610 }else{ captured = board[fromY][fromX+1];
8611 board[fromY][fromX+1] = EmptySquare;
8613 } else if (board[fromY][fromX] == king
8614 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8615 && toY == fromY && toX > fromX+1) {
8616 board[fromY][fromX] = EmptySquare;
8617 board[toY][toX] = king;
8618 board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
8619 board[fromY][BOARD_RGHT-1] = EmptySquare;
8620 } else if (board[fromY][fromX] == king
8621 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8622 && toY == fromY && toX < fromX-1) {
8623 board[fromY][fromX] = EmptySquare;
8624 board[toY][toX] = king;
8625 board[toY][toX+1] = board[fromY][BOARD_LEFT];
8626 board[fromY][BOARD_LEFT] = EmptySquare;
8627 } else if (fromY == 7 && fromX == 3
8628 && board[fromY][fromX] == BlackKing
8629 && toY == 7 && toX == 5) {
8630 board[fromY][fromX] = EmptySquare;
8631 board[toY][toX] = BlackKing;
8632 board[fromY][7] = EmptySquare;
8633 board[toY][4] = BlackRook;
8634 } else if (fromY == 7 && fromX == 3
8635 && board[fromY][fromX] == BlackKing
8636 && toY == 7 && toX == 1) {
8637 board[fromY][fromX] = EmptySquare;
8638 board[toY][toX] = BlackKing;
8639 board[fromY][0] = EmptySquare;
8640 board[toY][2] = BlackRook;
8641 } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
8642 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
8645 /* black pawn promotion */
8646 board[toY][toX] = CharToPiece(ToLower(promoChar));
8647 if (board[toY][toX] == EmptySquare) {
8648 board[toY][toX] = BlackQueen;
8650 if(gameInfo.variant==VariantBughouse ||
8651 gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
8652 board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
8653 board[fromY][fromX] = EmptySquare;
8654 } else if ((fromY == 3)
8656 && gameInfo.variant != VariantXiangqi
8657 && gameInfo.variant != VariantBerolina
8658 && (board[fromY][fromX] == BlackPawn)
8659 && (board[toY][toX] == EmptySquare)) {
8660 board[fromY][fromX] = EmptySquare;
8661 board[toY][toX] = BlackPawn;
8662 captured = board[toY + 1][toX];
8663 board[toY + 1][toX] = EmptySquare;
8664 } else if ((fromY == 3)
8666 && gameInfo.variant == VariantBerolina
8667 && (board[fromY][fromX] == BlackPawn)
8668 && (board[toY][toX] == EmptySquare)) {
8669 board[fromY][fromX] = EmptySquare;
8670 board[toY][toX] = BlackPawn;
8671 if(oldEP & EP_BEROLIN_A) {
8672 captured = board[fromY][fromX-1];
8673 board[fromY][fromX-1] = EmptySquare;
8674 }else{ captured = board[fromY][fromX+1];
8675 board[fromY][fromX+1] = EmptySquare;
8678 board[toY][toX] = board[fromY][fromX];
8679 board[fromY][fromX] = EmptySquare;
8683 if (gameInfo.holdingsWidth != 0) {
8685 /* !!A lot more code needs to be written to support holdings */
8686 /* [HGM] OK, so I have written it. Holdings are stored in the */
8687 /* penultimate board files, so they are automaticlly stored */
8688 /* in the game history. */
8689 if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
8690 && promoChar && piece != WhitePawn && piece != BlackPawn) {
8691 /* Delete from holdings, by decreasing count */
8692 /* and erasing image if necessary */
8693 p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
8694 if(p < (int) BlackPawn) { /* white drop */
8695 p -= (int)WhitePawn;
8696 p = PieceToNumber((ChessSquare)p);
8697 if(p >= gameInfo.holdingsSize) p = 0;
8698 if(--board[p][BOARD_WIDTH-2] <= 0)
8699 board[p][BOARD_WIDTH-1] = EmptySquare;
8700 if((int)board[p][BOARD_WIDTH-2] < 0)
8701 board[p][BOARD_WIDTH-2] = 0;
8702 } else { /* black drop */
8703 p -= (int)BlackPawn;
8704 p = PieceToNumber((ChessSquare)p);
8705 if(p >= gameInfo.holdingsSize) p = 0;
8706 if(--board[BOARD_HEIGHT-1-p][1] <= 0)
8707 board[BOARD_HEIGHT-1-p][0] = EmptySquare;
8708 if((int)board[BOARD_HEIGHT-1-p][1] < 0)
8709 board[BOARD_HEIGHT-1-p][1] = 0;
8712 if (captured != EmptySquare && gameInfo.holdingsSize > 0
8713 && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess ) {
8714 /* [HGM] holdings: Add to holdings, if holdings exist */
8715 if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
8716 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
8717 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
8720 if (p >= (int) BlackPawn) {
8721 p -= (int)BlackPawn;
8722 if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
8723 /* in Shogi restore piece to its original first */
8724 captured = (ChessSquare) (DEMOTED captured);
8727 p = PieceToNumber((ChessSquare)p);
8728 if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
8729 board[p][BOARD_WIDTH-2]++;
8730 board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
8732 p -= (int)WhitePawn;
8733 if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
8734 captured = (ChessSquare) (DEMOTED captured);
8737 p = PieceToNumber((ChessSquare)p);
8738 if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
8739 board[BOARD_HEIGHT-1-p][1]++;
8740 board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
8743 } else if (gameInfo.variant == VariantAtomic) {
8744 if (captured != EmptySquare) {
8746 for (y = toY-1; y <= toY+1; y++) {
8747 for (x = toX-1; x <= toX+1; x++) {
8748 if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
8749 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
8750 board[y][x] = EmptySquare;
8754 board[toY][toX] = EmptySquare;
8757 if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
8758 board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
8760 if(promoChar == '+') {
8761 /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite orinary Pawn promotion) */
8762 board[toY][toX] = (ChessSquare) (PROMOTED piece);
8763 } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
8764 board[toY][toX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
8766 if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
8767 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
8768 // [HGM] superchess: take promotion piece out of holdings
8769 int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
8770 if((int)piece < (int)BlackPawn) { // determine stm from piece color
8771 if(!--board[k][BOARD_WIDTH-2])
8772 board[k][BOARD_WIDTH-1] = EmptySquare;
8774 if(!--board[BOARD_HEIGHT-1-k][1])
8775 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
8781 /* Updates forwardMostMove */
8783 MakeMove(fromX, fromY, toX, toY, promoChar)
8784 int fromX, fromY, toX, toY;
8787 // forwardMostMove++; // [HGM] bare: moved downstream
8789 if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
8790 int timeLeft; static int lastLoadFlag=0; int king, piece;
8791 piece = boards[forwardMostMove][fromY][fromX];
8792 king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
8793 if(gameInfo.variant == VariantKnightmate)
8794 king += (int) WhiteUnicorn - (int) WhiteKing;
8795 if(forwardMostMove == 0) {
8797 fprintf(serverMoves, "%s;", second.tidy);
8798 fprintf(serverMoves, "%s;", first.tidy);
8799 if(!blackPlaysFirst)
8800 fprintf(serverMoves, "%s;", second.tidy);
8801 } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
8802 lastLoadFlag = loadFlag;
8804 fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
8805 // print castling suffix
8806 if( toY == fromY && piece == king ) {
8808 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
8810 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
8813 if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
8814 boards[forwardMostMove][fromY][fromX] == BlackPawn ) &&
8815 boards[forwardMostMove][toY][toX] == EmptySquare
8816 && fromX != toX && fromY != toY)
8817 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
8819 if(promoChar != NULLCHAR)
8820 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);
8822 fprintf(serverMoves, "/%d/%d",
8823 pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
8824 if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
8825 else timeLeft = blackTimeRemaining/1000;
8826 fprintf(serverMoves, "/%d", timeLeft);
8828 fflush(serverMoves);
8831 if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations
8832 DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
8836 UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
8837 if (commentList[forwardMostMove+1] != NULL) {
8838 free(commentList[forwardMostMove+1]);
8839 commentList[forwardMostMove+1] = NULL;
8841 CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
8842 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
8843 // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
8844 SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
8845 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
8846 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
8847 gameInfo.result = GameUnfinished;
8848 if (gameInfo.resultDetails != NULL) {
8849 free(gameInfo.resultDetails);
8850 gameInfo.resultDetails = NULL;
8852 CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
8853 moveList[forwardMostMove - 1]);
8854 (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
8855 PosFlags(forwardMostMove - 1),
8856 fromY, fromX, toY, toX, promoChar,
8857 parseList[forwardMostMove - 1]);
8858 switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
8864 if(gameInfo.variant != VariantShogi)
8865 strcat(parseList[forwardMostMove - 1], "+");
8869 strcat(parseList[forwardMostMove - 1], "#");
8872 if (appData.debugMode) {
8873 fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
8878 /* Updates currentMove if not pausing */
8880 ShowMove(fromX, fromY, toX, toY)
8882 int instant = (gameMode == PlayFromGameFile) ?
8883 (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
8884 if(appData.noGUI) return;
8885 if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
8887 if (forwardMostMove == currentMove + 1) {
8888 AnimateMove(boards[forwardMostMove - 1],
8889 fromX, fromY, toX, toY);
8891 if (appData.highlightLastMove) {
8892 SetHighlights(fromX, fromY, toX, toY);
8895 currentMove = forwardMostMove;
8898 if (instant) return;
8900 DisplayMove(currentMove - 1);
8901 DrawPosition(FALSE, boards[currentMove]);
8902 DisplayBothClocks();
8903 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
8906 void SendEgtPath(ChessProgramState *cps)
8907 { /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
8908 char buf[MSG_SIZ], name[MSG_SIZ], *p;
8910 if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
8913 char c, *q = name+1, *r, *s;
8915 name[0] = ','; // extract next format name from feature and copy with prefixed ','
8916 while(*p && *p != ',') *q++ = *p++;
8918 if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
8919 strcmp(name, ",nalimov:") == 0 ) {
8920 // take nalimov path from the menu-changeable option first, if it is defined
8921 snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
8922 SendToProgram(buf,cps); // send egtbpath command for nalimov
8924 if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
8925 (s = StrStr(appData.egtFormats, name)) != NULL) {
8926 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
8927 s = r = StrStr(s, ":") + 1; // beginning of path info
8928 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
8929 c = *r; *r = 0; // temporarily null-terminate path info
8930 *--q = 0; // strip of trailig ':' from name
8931 snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
8933 SendToProgram(buf,cps); // send egtbpath command for this format
8935 if(*p == ',') p++; // read away comma to position for next format name
8940 InitChessProgram(cps, setup)
8941 ChessProgramState *cps;
8942 int setup; /* [HGM] needed to setup FRC opening position */
8944 char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
8945 if (appData.noChessProgram) return;
8946 hintRequested = FALSE;
8947 bookRequested = FALSE;
8949 /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
8950 /* moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
8951 if(cps->memSize) { /* [HGM] memory */
8952 snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
8953 SendToProgram(buf, cps);
8955 SendEgtPath(cps); /* [HGM] EGT */
8956 if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
8957 snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
8958 SendToProgram(buf, cps);
8961 SendToProgram(cps->initString, cps);
8962 if (gameInfo.variant != VariantNormal &&
8963 gameInfo.variant != VariantLoadable
8964 /* [HGM] also send variant if board size non-standard */
8965 || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
8967 char *v = VariantName(gameInfo.variant);
8968 if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
8969 /* [HGM] in protocol 1 we have to assume all variants valid */
8970 snprintf(buf, MSG_SIZ, _("Variant %s not supported by %s"), v, cps->tidy);
8971 DisplayFatalError(buf, 0, 1);
8975 /* [HGM] make prefix for non-standard board size. Awkward testing... */
8976 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8977 if( gameInfo.variant == VariantXiangqi )
8978 overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
8979 if( gameInfo.variant == VariantShogi )
8980 overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
8981 if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
8982 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
8983 if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
8984 gameInfo.variant == VariantGothic || gameInfo.variant == VariantFalcon || gameInfo.variant == VariantJanus )
8985 overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8986 if( gameInfo.variant == VariantCourier )
8987 overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8988 if( gameInfo.variant == VariantSuper )
8989 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
8990 if( gameInfo.variant == VariantGreat )
8991 overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
8992 if( gameInfo.variant == VariantSChess )
8993 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 7;
8996 snprintf(b, MSG_SIZ, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
8997 gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
8998 /* [HGM] varsize: try first if this defiant size variant is specifically known */
8999 if(StrStr(cps->variants, b) == NULL) {
9000 // specific sized variant not known, check if general sizing allowed
9001 if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
9002 if(StrStr(cps->variants, "boardsize") == NULL) {
9003 snprintf(buf, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
9004 gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
9005 DisplayFatalError(buf, 0, 1);
9008 /* [HGM] here we really should compare with the maximum supported board size */
9011 } else snprintf(b, MSG_SIZ,"%s", VariantName(gameInfo.variant));
9012 snprintf(buf, MSG_SIZ, "variant %s\n", b);
9013 SendToProgram(buf, cps);
9015 currentlyInitializedVariant = gameInfo.variant;
9017 /* [HGM] send opening position in FRC to first engine */
9019 SendToProgram("force\n", cps);
9021 /* engine is now in force mode! Set flag to wake it up after first move. */
9022 setboardSpoiledMachineBlack = 1;
9026 snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
9027 SendToProgram(buf, cps);
9029 cps->maybeThinking = FALSE;
9030 cps->offeredDraw = 0;
9031 if (!appData.icsActive) {
9032 SendTimeControl(cps, movesPerSession, timeControl,
9033 timeIncrement, appData.searchDepth,
9036 if (appData.showThinking
9037 // [HGM] thinking: four options require thinking output to be sent
9038 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
9040 SendToProgram("post\n", cps);
9042 SendToProgram("hard\n", cps);
9043 if (!appData.ponderNextMove) {
9044 /* Warning: "easy" is a toggle in GNU Chess, so don't send
9045 it without being sure what state we are in first. "hard"
9046 is not a toggle, so that one is OK.
9048 SendToProgram("easy\n", cps);
9051 snprintf(buf, MSG_SIZ, "ping %d\n", ++cps->lastPing);
9052 SendToProgram(buf, cps);
9054 cps->initDone = TRUE;
9059 StartChessProgram(cps)
9060 ChessProgramState *cps;
9065 if (appData.noChessProgram) return;
9066 cps->initDone = FALSE;
9068 if (strcmp(cps->host, "localhost") == 0) {
9069 err = StartChildProcess(cps->program, cps->dir, &cps->pr);
9070 } else if (*appData.remoteShell == NULLCHAR) {
9071 err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
9073 if (*appData.remoteUser == NULLCHAR) {
9074 snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
9077 snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
9078 cps->host, appData.remoteUser, cps->program);
9080 err = StartChildProcess(buf, "", &cps->pr);
9084 snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
9085 DisplayFatalError(buf, err, 1);
9091 cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
9092 if (cps->protocolVersion > 1) {
9093 snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
9094 cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
9095 cps->comboCnt = 0; // and values of combo boxes
9096 SendToProgram(buf, cps);
9098 SendToProgram("xboard\n", cps);
9104 TwoMachinesEventIfReady P((void))
9106 if (first.lastPing != first.lastPong) {
9107 DisplayMessage("", _("Waiting for first chess program"));
9108 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9111 if (second.lastPing != second.lastPong) {
9112 DisplayMessage("", _("Waiting for second chess program"));
9113 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9121 NextMatchGame P((void))
9123 int index; /* [HGM] autoinc: step load index during match */
9125 if (*appData.loadGameFile != NULLCHAR) {
9126 index = appData.loadGameIndex;
9127 if(index < 0) { // [HGM] autoinc
9128 lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
9129 if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
9131 LoadGameFromFile(appData.loadGameFile,
9133 appData.loadGameFile, FALSE);
9134 } else if (*appData.loadPositionFile != NULLCHAR) {
9135 index = appData.loadPositionIndex;
9136 if(index < 0) { // [HGM] autoinc
9137 lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
9138 if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
9140 LoadPositionFromFile(appData.loadPositionFile,
9142 appData.loadPositionFile);
9144 TwoMachinesEventIfReady();
9147 void UserAdjudicationEvent( int result )
9149 ChessMove gameResult = GameIsDrawn;
9152 gameResult = WhiteWins;
9154 else if( result < 0 ) {
9155 gameResult = BlackWins;
9158 if( gameMode == TwoMachinesPlay ) {
9159 GameEnds( gameResult, "User adjudication", GE_XBOARD );
9164 // [HGM] save: calculate checksum of game to make games easily identifiable
9165 int StringCheckSum(char *s)
9168 if(s==NULL) return 0;
9169 while(*s) i = i*259 + *s++;
9176 for(i=backwardMostMove; i<forwardMostMove; i++) {
9177 sum += pvInfoList[i].depth;
9178 sum += StringCheckSum(parseList[i]);
9179 sum += StringCheckSum(commentList[i]);
9182 if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
9183 return sum + StringCheckSum(commentList[i]);
9184 } // end of save patch
9187 GameEnds(result, resultDetails, whosays)
9189 char *resultDetails;
9192 GameMode nextGameMode;
9194 char buf[MSG_SIZ], popupRequested = 0;
9196 if(endingGame) return; /* [HGM] crash: forbid recursion */
9198 if(twoBoards) { // [HGM] dual: switch back to one board
9199 twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
9200 DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
9202 if (appData.debugMode) {
9203 fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
9204 result, resultDetails ? resultDetails : "(null)", whosays);
9207 fromX = fromY = -1; // [HGM] abort any move the user is entering.
9209 if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
9210 /* If we are playing on ICS, the server decides when the
9211 game is over, but the engine can offer to draw, claim
9215 if (appData.zippyPlay && first.initDone) {
9216 if (result == GameIsDrawn) {
9217 /* In case draw still needs to be claimed */
9218 SendToICS(ics_prefix);
9219 SendToICS("draw\n");
9220 } else if (StrCaseStr(resultDetails, "resign")) {
9221 SendToICS(ics_prefix);
9222 SendToICS("resign\n");
9226 endingGame = 0; /* [HGM] crash */
9230 /* If we're loading the game from a file, stop */
9231 if (whosays == GE_FILE) {
9232 (void) StopLoadGameTimer();
9236 /* Cancel draw offers */
9237 first.offeredDraw = second.offeredDraw = 0;
9239 /* If this is an ICS game, only ICS can really say it's done;
9240 if not, anyone can. */
9241 isIcsGame = (gameMode == IcsPlayingWhite ||
9242 gameMode == IcsPlayingBlack ||
9243 gameMode == IcsObserving ||
9244 gameMode == IcsExamining);
9246 if (!isIcsGame || whosays == GE_ICS) {
9247 /* OK -- not an ICS game, or ICS said it was done */
9249 if (!isIcsGame && !appData.noChessProgram)
9250 SetUserThinkingEnables();
9252 /* [HGM] if a machine claims the game end we verify this claim */
9253 if(gameMode == TwoMachinesPlay && appData.testClaims) {
9254 if(appData.testLegality && whosays >= GE_ENGINE1 ) {
9256 ChessMove trueResult = (ChessMove) -1;
9258 claimer = whosays == GE_ENGINE1 ? /* color of claimer */
9259 first.twoMachinesColor[0] :
9260 second.twoMachinesColor[0] ;
9262 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
9263 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
9264 /* [HGM] verify: engine mate claims accepted if they were flagged */
9265 trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
9267 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
9268 /* [HGM] verify: engine mate claims accepted if they were flagged */
9269 trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
9271 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
9272 trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
9275 // now verify win claims, but not in drop games, as we don't understand those yet
9276 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
9277 || gameInfo.variant == VariantGreat) &&
9278 (result == WhiteWins && claimer == 'w' ||
9279 result == BlackWins && claimer == 'b' ) ) { // case to verify: engine claims own win
9280 if (appData.debugMode) {
9281 fprintf(debugFP, "result=%d sp=%d move=%d\n",
9282 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
9284 if(result != trueResult) {
9285 snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
9286 result = claimer == 'w' ? BlackWins : WhiteWins;
9287 resultDetails = buf;
9290 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
9291 && (forwardMostMove <= backwardMostMove ||
9292 (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
9293 (claimer=='b')==(forwardMostMove&1))
9295 /* [HGM] verify: draws that were not flagged are false claims */
9296 snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
9297 result = claimer == 'w' ? BlackWins : WhiteWins;
9298 resultDetails = buf;
9300 /* (Claiming a loss is accepted no questions asked!) */
9302 /* [HGM] bare: don't allow bare King to win */
9303 if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
9304 && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
9305 && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
9306 && result != GameIsDrawn)
9307 { int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
9308 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
9309 int p = (signed char)boards[forwardMostMove][i][j] - color;
9310 if(p >= 0 && p <= (int)WhiteKing) k++;
9312 if (appData.debugMode) {
9313 fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
9314 result, resultDetails ? resultDetails : "(null)", whosays, k, color);
9317 result = GameIsDrawn;
9318 snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
9319 resultDetails = buf;
9325 if(serverMoves != NULL && !loadFlag) { char c = '=';
9326 if(result==WhiteWins) c = '+';
9327 if(result==BlackWins) c = '-';
9328 if(resultDetails != NULL)
9329 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
9331 if (resultDetails != NULL) {
9332 gameInfo.result = result;
9333 gameInfo.resultDetails = StrSave(resultDetails);
9335 /* display last move only if game was not loaded from file */
9336 if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
9337 DisplayMove(currentMove - 1);
9339 if (forwardMostMove != 0) {
9340 if (gameMode != PlayFromGameFile && gameMode != EditGame
9341 && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
9343 if (*appData.saveGameFile != NULLCHAR) {
9344 SaveGameToFile(appData.saveGameFile, TRUE);
9345 } else if (appData.autoSaveGames) {
9348 if (*appData.savePositionFile != NULLCHAR) {
9349 SavePositionToFile(appData.savePositionFile);
9354 /* Tell program how game ended in case it is learning */
9355 /* [HGM] Moved this to after saving the PGN, just in case */
9356 /* engine died and we got here through time loss. In that */
9357 /* case we will get a fatal error writing the pipe, which */
9358 /* would otherwise lose us the PGN. */
9359 /* [HGM] crash: not needed anymore, but doesn't hurt; */
9360 /* output during GameEnds should never be fatal anymore */
9361 if (gameMode == MachinePlaysWhite ||
9362 gameMode == MachinePlaysBlack ||
9363 gameMode == TwoMachinesPlay ||
9364 gameMode == IcsPlayingWhite ||
9365 gameMode == IcsPlayingBlack ||
9366 gameMode == BeginningOfGame) {
9368 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
9370 if (first.pr != NoProc) {
9371 SendToProgram(buf, &first);
9373 if (second.pr != NoProc &&
9374 gameMode == TwoMachinesPlay) {
9375 SendToProgram(buf, &second);
9380 if (appData.icsActive) {
9381 if (appData.quietPlay &&
9382 (gameMode == IcsPlayingWhite ||
9383 gameMode == IcsPlayingBlack)) {
9384 SendToICS(ics_prefix);
9385 SendToICS("set shout 1\n");
9387 nextGameMode = IcsIdle;
9388 ics_user_moved = FALSE;
9389 /* clean up premove. It's ugly when the game has ended and the
9390 * premove highlights are still on the board.
9394 ClearPremoveHighlights();
9395 DrawPosition(FALSE, boards[currentMove]);
9397 if (whosays == GE_ICS) {
9400 if (gameMode == IcsPlayingWhite)
9402 else if(gameMode == IcsPlayingBlack)
9406 if (gameMode == IcsPlayingBlack)
9408 else if(gameMode == IcsPlayingWhite)
9415 PlayIcsUnfinishedSound();
9418 } else if (gameMode == EditGame ||
9419 gameMode == PlayFromGameFile ||
9420 gameMode == AnalyzeMode ||
9421 gameMode == AnalyzeFile) {
9422 nextGameMode = gameMode;
9424 nextGameMode = EndOfGame;
9429 nextGameMode = gameMode;
9432 if (appData.noChessProgram) {
9433 gameMode = nextGameMode;
9435 endingGame = 0; /* [HGM] crash */
9440 /* Put first chess program into idle state */
9441 if (first.pr != NoProc &&
9442 (gameMode == MachinePlaysWhite ||
9443 gameMode == MachinePlaysBlack ||
9444 gameMode == TwoMachinesPlay ||
9445 gameMode == IcsPlayingWhite ||
9446 gameMode == IcsPlayingBlack ||
9447 gameMode == BeginningOfGame)) {
9448 SendToProgram("force\n", &first);
9449 if (first.usePing) {
9451 snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
9452 SendToProgram(buf, &first);
9455 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
9456 /* Kill off first chess program */
9457 if (first.isr != NULL)
9458 RemoveInputSource(first.isr);
9461 if (first.pr != NoProc) {
9463 DoSleep( appData.delayBeforeQuit );
9464 SendToProgram("quit\n", &first);
9465 DoSleep( appData.delayAfterQuit );
9466 DestroyChildProcess(first.pr, first.useSigterm);
9471 /* Put second chess program into idle state */
9472 if (second.pr != NoProc &&
9473 gameMode == TwoMachinesPlay) {
9474 SendToProgram("force\n", &second);
9475 if (second.usePing) {
9477 snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
9478 SendToProgram(buf, &second);
9481 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
9482 /* Kill off second chess program */
9483 if (second.isr != NULL)
9484 RemoveInputSource(second.isr);
9487 if (second.pr != NoProc) {
9488 DoSleep( appData.delayBeforeQuit );
9489 SendToProgram("quit\n", &second);
9490 DoSleep( appData.delayAfterQuit );
9491 DestroyChildProcess(second.pr, second.useSigterm);
9496 if (matchMode && gameMode == TwoMachinesPlay) {
9499 if (first.twoMachinesColor[0] == 'w') {
9506 if (first.twoMachinesColor[0] == 'b') {
9515 if (matchGame < appData.matchGames) {
9517 if(appData.sameColorGames <= 1) { /* [HGM] alternate: suppress color swap */
9518 tmp = first.twoMachinesColor;
9519 first.twoMachinesColor = second.twoMachinesColor;
9520 second.twoMachinesColor = tmp;
9522 gameMode = nextGameMode;
9524 if(appData.matchPause>10000 || appData.matchPause<10)
9525 appData.matchPause = 10000; /* [HGM] make pause adjustable */
9526 ScheduleDelayedEvent(NextMatchGame, appData.matchPause);
9527 endingGame = 0; /* [HGM] crash */
9530 gameMode = nextGameMode;
9531 snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
9532 first.tidy, second.tidy,
9533 first.matchWins, second.matchWins,
9534 appData.matchGames - (first.matchWins + second.matchWins));
9535 popupRequested++; // [HGM] crash: postpone to after resetting endingGame
9536 if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
9537 first.twoMachinesColor = "black\n";
9538 second.twoMachinesColor = "white\n";
9540 first.twoMachinesColor = "white\n";
9541 second.twoMachinesColor = "black\n";
9545 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
9546 !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
9548 gameMode = nextGameMode;
9550 endingGame = 0; /* [HGM] crash */
9551 if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
9552 if(matchMode == TRUE) DisplayFatalError(buf, 0, 0); else {
9553 matchMode = FALSE; appData.matchGames = matchGame = 0;
9559 /* Assumes program was just initialized (initString sent).
9560 Leaves program in force mode. */
9562 FeedMovesToProgram(cps, upto)
9563 ChessProgramState *cps;
9568 if (appData.debugMode)
9569 fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
9570 startedFromSetupPosition ? "position and " : "",
9571 backwardMostMove, upto, cps->which);
9572 if(currentlyInitializedVariant != gameInfo.variant) {
9574 // [HGM] variantswitch: make engine aware of new variant
9575 if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
9576 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
9577 snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
9578 SendToProgram(buf, cps);
9579 currentlyInitializedVariant = gameInfo.variant;
9581 SendToProgram("force\n", cps);
9582 if (startedFromSetupPosition) {
9583 SendBoard(cps, backwardMostMove);
9584 if (appData.debugMode) {
9585 fprintf(debugFP, "feedMoves\n");
9588 for (i = backwardMostMove; i < upto; i++) {
9589 SendMoveToProgram(i, cps);
9595 ResurrectChessProgram()
9597 /* The chess program may have exited.
9598 If so, restart it and feed it all the moves made so far. */
9600 if (appData.noChessProgram || first.pr != NoProc) return;
9602 StartChessProgram(&first);
9603 InitChessProgram(&first, FALSE);
9604 FeedMovesToProgram(&first, currentMove);
9606 if (!first.sendTime) {
9607 /* can't tell gnuchess what its clock should read,
9608 so we bow to its notion. */
9610 timeRemaining[0][currentMove] = whiteTimeRemaining;
9611 timeRemaining[1][currentMove] = blackTimeRemaining;
9614 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
9615 appData.icsEngineAnalyze) && first.analysisSupport) {
9616 SendToProgram("analyze\n", &first);
9617 first.analyzing = TRUE;
9630 if (appData.debugMode) {
9631 fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
9632 redraw, init, gameMode);
9634 CleanupTail(); // [HGM] vari: delete any stored variations
9635 pausing = pauseExamInvalid = FALSE;
9636 startedFromSetupPosition = blackPlaysFirst = FALSE;
9638 whiteFlag = blackFlag = FALSE;
9639 userOfferedDraw = FALSE;
9640 hintRequested = bookRequested = FALSE;
9641 first.maybeThinking = FALSE;
9642 second.maybeThinking = FALSE;
9643 first.bookSuspend = FALSE; // [HGM] book
9644 second.bookSuspend = FALSE;
9645 thinkOutput[0] = NULLCHAR;
9646 lastHint[0] = NULLCHAR;
9647 ClearGameInfo(&gameInfo);
9648 gameInfo.variant = StringToVariant(appData.variant);
9649 ics_user_moved = ics_clock_paused = FALSE;
9650 ics_getting_history = H_FALSE;
9652 white_holding[0] = black_holding[0] = NULLCHAR;
9653 ClearProgramStats();
9654 opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
9658 flipView = appData.flipView;
9659 ClearPremoveHighlights();
9661 alarmSounded = FALSE;
9663 GameEnds(EndOfFile, NULL, GE_PLAYER);
9664 if(appData.serverMovesName != NULL) {
9665 /* [HGM] prepare to make moves file for broadcasting */
9666 clock_t t = clock();
9667 if(serverMoves != NULL) fclose(serverMoves);
9668 serverMoves = fopen(appData.serverMovesName, "r");
9669 if(serverMoves != NULL) {
9670 fclose(serverMoves);
9671 /* delay 15 sec before overwriting, so all clients can see end */
9672 while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
9674 serverMoves = fopen(appData.serverMovesName, "w");
9678 gameMode = BeginningOfGame;
9680 if(appData.icsActive) gameInfo.variant = VariantNormal;
9681 currentMove = forwardMostMove = backwardMostMove = 0;
9682 InitPosition(redraw);
9683 for (i = 0; i < MAX_MOVES; i++) {
9684 if (commentList[i] != NULL) {
9685 free(commentList[i]);
9686 commentList[i] = NULL;
9690 timeRemaining[0][0] = whiteTimeRemaining;
9691 timeRemaining[1][0] = blackTimeRemaining;
9692 if (first.pr == NULL) {
9693 StartChessProgram(&first);
9696 InitChessProgram(&first, startedFromSetupPosition);
9699 DisplayMessage("", "");
9700 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
9701 lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
9708 if (!AutoPlayOneMove())
9710 if (matchMode || appData.timeDelay == 0)
9712 if (appData.timeDelay < 0)
9714 StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
9723 int fromX, fromY, toX, toY;
9725 if (appData.debugMode) {
9726 fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
9729 if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
9732 if (gameMode == AnalyzeFile && currentMove > backwardMostMove) {
9733 pvInfoList[currentMove].depth = programStats.depth;
9734 pvInfoList[currentMove].score = programStats.score;
9735 pvInfoList[currentMove].time = 0;
9736 if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
9739 if (currentMove >= forwardMostMove) {
9740 if(gameMode == AnalyzeFile) { ExitAnalyzeMode(); SendToProgram("force\n", &first); }
9741 gameMode = EditGame;
9744 /* [AS] Clear current move marker at the end of a game */
9745 /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
9750 toX = moveList[currentMove][2] - AAA;
9751 toY = moveList[currentMove][3] - ONE;
9753 if (moveList[currentMove][1] == '@') {
9754 if (appData.highlightLastMove) {
9755 SetHighlights(-1, -1, toX, toY);
9758 fromX = moveList[currentMove][0] - AAA;
9759 fromY = moveList[currentMove][1] - ONE;
9761 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
9763 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
9765 if (appData.highlightLastMove) {
9766 SetHighlights(fromX, fromY, toX, toY);
9769 DisplayMove(currentMove);
9770 SendMoveToProgram(currentMove++, &first);
9771 DisplayBothClocks();
9772 DrawPosition(FALSE, boards[currentMove]);
9773 // [HGM] PV info: always display, routine tests if empty
9774 DisplayComment(currentMove - 1, commentList[currentMove]);
9780 LoadGameOneMove(readAhead)
9781 ChessMove readAhead;
9783 int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
9784 char promoChar = NULLCHAR;
9789 if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
9790 gameMode != AnalyzeMode && gameMode != Training) {
9795 yyboardindex = forwardMostMove;
9796 if (readAhead != EndOfFile) {
9797 moveType = readAhead;
9799 if (gameFileFP == NULL)
9801 moveType = (ChessMove) Myylex();
9807 if (appData.debugMode)
9808 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9811 /* append the comment but don't display it */
9812 AppendComment(currentMove, p, FALSE);
9815 case WhiteCapturesEnPassant:
9816 case BlackCapturesEnPassant:
9817 case WhitePromotion:
9818 case BlackPromotion:
9819 case WhiteNonPromotion:
9820 case BlackNonPromotion:
9822 case WhiteKingSideCastle:
9823 case WhiteQueenSideCastle:
9824 case BlackKingSideCastle:
9825 case BlackQueenSideCastle:
9826 case WhiteKingSideCastleWild:
9827 case WhiteQueenSideCastleWild:
9828 case BlackKingSideCastleWild:
9829 case BlackQueenSideCastleWild:
9831 case WhiteHSideCastleFR:
9832 case WhiteASideCastleFR:
9833 case BlackHSideCastleFR:
9834 case BlackASideCastleFR:
9836 if (appData.debugMode)
9837 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
9838 fromX = currentMoveString[0] - AAA;
9839 fromY = currentMoveString[1] - ONE;
9840 toX = currentMoveString[2] - AAA;
9841 toY = currentMoveString[3] - ONE;
9842 promoChar = currentMoveString[4];
9847 if (appData.debugMode)
9848 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
9849 fromX = moveType == WhiteDrop ?
9850 (int) CharToPiece(ToUpper(currentMoveString[0])) :
9851 (int) CharToPiece(ToLower(currentMoveString[0]));
9853 toX = currentMoveString[2] - AAA;
9854 toY = currentMoveString[3] - ONE;
9860 case GameUnfinished:
9861 if (appData.debugMode)
9862 fprintf(debugFP, "Parsed game end: %s\n", yy_text);
9863 p = strchr(yy_text, '{');
9864 if (p == NULL) p = strchr(yy_text, '(');
9867 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
9869 q = strchr(p, *p == '{' ? '}' : ')');
9870 if (q != NULL) *q = NULLCHAR;
9873 while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
9874 GameEnds(moveType, p, GE_FILE);
9876 if (cmailMsgLoaded) {
9878 flipView = WhiteOnMove(currentMove);
9879 if (moveType == GameUnfinished) flipView = !flipView;
9880 if (appData.debugMode)
9881 fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
9886 if (appData.debugMode)
9887 fprintf(debugFP, "Parser hit end of file\n");
9888 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9894 if (WhiteOnMove(currentMove)) {
9895 GameEnds(BlackWins, "Black mates", GE_FILE);
9897 GameEnds(WhiteWins, "White mates", GE_FILE);
9901 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
9908 if (lastLoadGameStart == GNUChessGame) {
9909 /* GNUChessGames have numbers, but they aren't move numbers */
9910 if (appData.debugMode)
9911 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
9912 yy_text, (int) moveType);
9913 return LoadGameOneMove(EndOfFile); /* tail recursion */
9915 /* else fall thru */
9920 /* Reached start of next game in file */
9921 if (appData.debugMode)
9922 fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
9923 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9929 if (WhiteOnMove(currentMove)) {
9930 GameEnds(BlackWins, "Black mates", GE_FILE);
9932 GameEnds(WhiteWins, "White mates", GE_FILE);
9936 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
9942 case PositionDiagram: /* should not happen; ignore */
9943 case ElapsedTime: /* ignore */
9944 case NAG: /* ignore */
9945 if (appData.debugMode)
9946 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
9947 yy_text, (int) moveType);
9948 return LoadGameOneMove(EndOfFile); /* tail recursion */
9951 if (appData.testLegality) {
9952 if (appData.debugMode)
9953 fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
9954 snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
9955 (forwardMostMove / 2) + 1,
9956 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9957 DisplayError(move, 0);
9960 if (appData.debugMode)
9961 fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
9962 yy_text, currentMoveString);
9963 fromX = currentMoveString[0] - AAA;
9964 fromY = currentMoveString[1] - ONE;
9965 toX = currentMoveString[2] - AAA;
9966 toY = currentMoveString[3] - ONE;
9967 promoChar = currentMoveString[4];
9972 if (appData.debugMode)
9973 fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
9974 snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
9975 (forwardMostMove / 2) + 1,
9976 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9977 DisplayError(move, 0);
9982 case ImpossibleMove:
9983 if (appData.debugMode)
9984 fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
9985 snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
9986 (forwardMostMove / 2) + 1,
9987 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9988 DisplayError(move, 0);
9994 if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
9995 DrawPosition(FALSE, boards[currentMove]);
9996 DisplayBothClocks();
9997 if (!appData.matchMode) // [HGM] PV info: routine tests if empty
9998 DisplayComment(currentMove - 1, commentList[currentMove]);
10000 (void) StopLoadGameTimer();
10002 cmailOldMove = forwardMostMove;
10005 /* currentMoveString is set as a side-effect of yylex */
10007 thinkOutput[0] = NULLCHAR;
10008 MakeMove(fromX, fromY, toX, toY, promoChar);
10009 currentMove = forwardMostMove;
10014 /* Load the nth game from the given file */
10016 LoadGameFromFile(filename, n, title, useList)
10020 /*Boolean*/ int useList;
10025 if (strcmp(filename, "-") == 0) {
10029 f = fopen(filename, "rb");
10031 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10032 DisplayError(buf, errno);
10036 if (fseek(f, 0, 0) == -1) {
10037 /* f is not seekable; probably a pipe */
10040 if (useList && n == 0) {
10041 int error = GameListBuild(f);
10043 DisplayError(_("Cannot build game list"), error);
10044 } else if (!ListEmpty(&gameList) &&
10045 ((ListGame *) gameList.tailPred)->number > 1) {
10046 GameListPopUp(f, title);
10053 return LoadGame(f, n, title, FALSE);
10058 MakeRegisteredMove()
10060 int fromX, fromY, toX, toY;
10062 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
10063 switch (cmailMoveType[lastLoadGameNumber - 1]) {
10066 if (appData.debugMode)
10067 fprintf(debugFP, "Restoring %s for game %d\n",
10068 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
10070 thinkOutput[0] = NULLCHAR;
10071 safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
10072 fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
10073 fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
10074 toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
10075 toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
10076 promoChar = cmailMove[lastLoadGameNumber - 1][4];
10077 MakeMove(fromX, fromY, toX, toY, promoChar);
10078 ShowMove(fromX, fromY, toX, toY);
10080 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
10087 if (WhiteOnMove(currentMove)) {
10088 GameEnds(BlackWins, "Black mates", GE_PLAYER);
10090 GameEnds(WhiteWins, "White mates", GE_PLAYER);
10095 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
10102 if (WhiteOnMove(currentMove)) {
10103 GameEnds(BlackWins, "White resigns", GE_PLAYER);
10105 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
10110 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
10121 /* Wrapper around LoadGame for use when a Cmail message is loaded */
10123 CmailLoadGame(f, gameNumber, title, useList)
10131 if (gameNumber > nCmailGames) {
10132 DisplayError(_("No more games in this message"), 0);
10135 if (f == lastLoadGameFP) {
10136 int offset = gameNumber - lastLoadGameNumber;
10138 cmailMsg[0] = NULLCHAR;
10139 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
10140 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
10141 nCmailMovesRegistered--;
10143 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
10144 if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
10145 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
10148 if (! RegisterMove()) return FALSE;
10152 retVal = LoadGame(f, gameNumber, title, useList);
10154 /* Make move registered during previous look at this game, if any */
10155 MakeRegisteredMove();
10157 if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
10158 commentList[currentMove]
10159 = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
10160 DisplayComment(currentMove - 1, commentList[currentMove]);
10166 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
10171 int gameNumber = lastLoadGameNumber + offset;
10172 if (lastLoadGameFP == NULL) {
10173 DisplayError(_("No game has been loaded yet"), 0);
10176 if (gameNumber <= 0) {
10177 DisplayError(_("Can't back up any further"), 0);
10180 if (cmailMsgLoaded) {
10181 return CmailLoadGame(lastLoadGameFP, gameNumber,
10182 lastLoadGameTitle, lastLoadGameUseList);
10184 return LoadGame(lastLoadGameFP, gameNumber,
10185 lastLoadGameTitle, lastLoadGameUseList);
10191 /* Load the nth game from open file f */
10193 LoadGame(f, gameNumber, title, useList)
10201 int gn = gameNumber;
10202 ListGame *lg = NULL;
10203 int numPGNTags = 0;
10205 GameMode oldGameMode;
10206 VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
10208 if (appData.debugMode)
10209 fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
10211 if (gameMode == Training )
10212 SetTrainingModeOff();
10214 oldGameMode = gameMode;
10215 if (gameMode != BeginningOfGame) {
10216 Reset(FALSE, TRUE);
10220 if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
10221 fclose(lastLoadGameFP);
10225 lg = (ListGame *) ListElem(&gameList, gameNumber-1);
10228 fseek(f, lg->offset, 0);
10229 GameListHighlight(gameNumber);
10233 DisplayError(_("Game number out of range"), 0);
10238 if (fseek(f, 0, 0) == -1) {
10239 if (f == lastLoadGameFP ?
10240 gameNumber == lastLoadGameNumber + 1 :
10244 DisplayError(_("Can't seek on game file"), 0);
10249 lastLoadGameFP = f;
10250 lastLoadGameNumber = gameNumber;
10251 safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
10252 lastLoadGameUseList = useList;
10256 if (lg && lg->gameInfo.white && lg->gameInfo.black) {
10257 snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
10258 lg->gameInfo.black);
10260 } else if (*title != NULLCHAR) {
10261 if (gameNumber > 1) {
10262 snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
10265 DisplayTitle(title);
10269 if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
10270 gameMode = PlayFromGameFile;
10274 currentMove = forwardMostMove = backwardMostMove = 0;
10275 CopyBoard(boards[0], initialPosition);
10279 * Skip the first gn-1 games in the file.
10280 * Also skip over anything that precedes an identifiable
10281 * start of game marker, to avoid being confused by
10282 * garbage at the start of the file. Currently
10283 * recognized start of game markers are the move number "1",
10284 * the pattern "gnuchess .* game", the pattern
10285 * "^[#;%] [^ ]* game file", and a PGN tag block.
10286 * A game that starts with one of the latter two patterns
10287 * will also have a move number 1, possibly
10288 * following a position diagram.
10289 * 5-4-02: Let's try being more lenient and allowing a game to
10290 * start with an unnumbered move. Does that break anything?
10292 cm = lastLoadGameStart = EndOfFile;
10294 yyboardindex = forwardMostMove;
10295 cm = (ChessMove) Myylex();
10298 if (cmailMsgLoaded) {
10299 nCmailGames = CMAIL_MAX_GAMES - gn;
10302 DisplayError(_("Game not found in file"), 0);
10309 lastLoadGameStart = cm;
10312 case MoveNumberOne:
10313 switch (lastLoadGameStart) {
10318 case MoveNumberOne:
10320 gn--; /* count this game */
10321 lastLoadGameStart = cm;
10330 switch (lastLoadGameStart) {
10333 case MoveNumberOne:
10335 gn--; /* count this game */
10336 lastLoadGameStart = cm;
10339 lastLoadGameStart = cm; /* game counted already */
10347 yyboardindex = forwardMostMove;
10348 cm = (ChessMove) Myylex();
10349 } while (cm == PGNTag || cm == Comment);
10356 if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
10357 if ( cmailResult[CMAIL_MAX_GAMES - gn - 1]
10358 != CMAIL_OLD_RESULT) {
10360 cmailResult[ CMAIL_MAX_GAMES
10361 - gn - 1] = CMAIL_OLD_RESULT;
10367 /* Only a NormalMove can be at the start of a game
10368 * without a position diagram. */
10369 if (lastLoadGameStart == EndOfFile ) {
10371 lastLoadGameStart = MoveNumberOne;
10380 if (appData.debugMode)
10381 fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
10383 if (cm == XBoardGame) {
10384 /* Skip any header junk before position diagram and/or move 1 */
10386 yyboardindex = forwardMostMove;
10387 cm = (ChessMove) Myylex();
10389 if (cm == EndOfFile ||
10390 cm == GNUChessGame || cm == XBoardGame) {
10391 /* Empty game; pretend end-of-file and handle later */
10396 if (cm == MoveNumberOne || cm == PositionDiagram ||
10397 cm == PGNTag || cm == Comment)
10400 } else if (cm == GNUChessGame) {
10401 if (gameInfo.event != NULL) {
10402 free(gameInfo.event);
10404 gameInfo.event = StrSave(yy_text);
10407 startedFromSetupPosition = FALSE;
10408 while (cm == PGNTag) {
10409 if (appData.debugMode)
10410 fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
10411 err = ParsePGNTag(yy_text, &gameInfo);
10412 if (!err) numPGNTags++;
10414 /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
10415 if(gameInfo.variant != oldVariant) {
10416 startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
10417 ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
10418 InitPosition(TRUE);
10419 oldVariant = gameInfo.variant;
10420 if (appData.debugMode)
10421 fprintf(debugFP, "New variant %d\n", (int) oldVariant);
10425 if (gameInfo.fen != NULL) {
10426 Board initial_position;
10427 startedFromSetupPosition = TRUE;
10428 if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
10430 DisplayError(_("Bad FEN position in file"), 0);
10433 CopyBoard(boards[0], initial_position);
10434 if (blackPlaysFirst) {
10435 currentMove = forwardMostMove = backwardMostMove = 1;
10436 CopyBoard(boards[1], initial_position);
10437 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
10438 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
10439 timeRemaining[0][1] = whiteTimeRemaining;
10440 timeRemaining[1][1] = blackTimeRemaining;
10441 if (commentList[0] != NULL) {
10442 commentList[1] = commentList[0];
10443 commentList[0] = NULL;
10446 currentMove = forwardMostMove = backwardMostMove = 0;
10448 /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
10450 initialRulePlies = FENrulePlies;
10451 for( i=0; i< nrCastlingRights; i++ )
10452 initialRights[i] = initial_position[CASTLING][i];
10454 yyboardindex = forwardMostMove;
10455 free(gameInfo.fen);
10456 gameInfo.fen = NULL;
10459 yyboardindex = forwardMostMove;
10460 cm = (ChessMove) Myylex();
10462 /* Handle comments interspersed among the tags */
10463 while (cm == Comment) {
10465 if (appData.debugMode)
10466 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10468 AppendComment(currentMove, p, FALSE);
10469 yyboardindex = forwardMostMove;
10470 cm = (ChessMove) Myylex();
10474 /* don't rely on existence of Event tag since if game was
10475 * pasted from clipboard the Event tag may not exist
10477 if (numPGNTags > 0){
10479 if (gameInfo.variant == VariantNormal) {
10480 VariantClass v = StringToVariant(gameInfo.event);
10481 // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
10482 if(v < VariantShogi) gameInfo.variant = v;
10485 if( appData.autoDisplayTags ) {
10486 tags = PGNTags(&gameInfo);
10487 TagsPopUp(tags, CmailMsg());
10492 /* Make something up, but don't display it now */
10497 if (cm == PositionDiagram) {
10500 Board initial_position;
10502 if (appData.debugMode)
10503 fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
10505 if (!startedFromSetupPosition) {
10507 for (i = BOARD_HEIGHT - 1; i >= 0; i--)
10508 for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
10519 initial_position[i][j++] = CharToPiece(*p);
10522 while (*p == ' ' || *p == '\t' ||
10523 *p == '\n' || *p == '\r') p++;
10525 if (strncmp(p, "black", strlen("black"))==0)
10526 blackPlaysFirst = TRUE;
10528 blackPlaysFirst = FALSE;
10529 startedFromSetupPosition = TRUE;
10531 CopyBoard(boards[0], initial_position);
10532 if (blackPlaysFirst) {
10533 currentMove = forwardMostMove = backwardMostMove = 1;
10534 CopyBoard(boards[1], initial_position);
10535 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
10536 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
10537 timeRemaining[0][1] = whiteTimeRemaining;
10538 timeRemaining[1][1] = blackTimeRemaining;
10539 if (commentList[0] != NULL) {
10540 commentList[1] = commentList[0];
10541 commentList[0] = NULL;
10544 currentMove = forwardMostMove = backwardMostMove = 0;
10547 yyboardindex = forwardMostMove;
10548 cm = (ChessMove) Myylex();
10551 if (first.pr == NoProc) {
10552 StartChessProgram(&first);
10554 InitChessProgram(&first, FALSE);
10555 SendToProgram("force\n", &first);
10556 if (startedFromSetupPosition) {
10557 SendBoard(&first, forwardMostMove);
10558 if (appData.debugMode) {
10559 fprintf(debugFP, "Load Game\n");
10561 DisplayBothClocks();
10564 /* [HGM] server: flag to write setup moves in broadcast file as one */
10565 loadFlag = appData.suppressLoadMoves;
10567 while (cm == Comment) {
10569 if (appData.debugMode)
10570 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10572 AppendComment(currentMove, p, FALSE);
10573 yyboardindex = forwardMostMove;
10574 cm = (ChessMove) Myylex();
10577 if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
10578 cm == WhiteWins || cm == BlackWins ||
10579 cm == GameIsDrawn || cm == GameUnfinished) {
10580 DisplayMessage("", _("No moves in game"));
10581 if (cmailMsgLoaded) {
10582 if (appData.debugMode)
10583 fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
10587 DrawPosition(FALSE, boards[currentMove]);
10588 DisplayBothClocks();
10589 gameMode = EditGame;
10596 // [HGM] PV info: routine tests if comment empty
10597 if (!matchMode && (pausing || appData.timeDelay != 0)) {
10598 DisplayComment(currentMove - 1, commentList[currentMove]);
10600 if (!matchMode && appData.timeDelay != 0)
10601 DrawPosition(FALSE, boards[currentMove]);
10603 if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
10604 programStats.ok_to_send = 1;
10607 /* if the first token after the PGN tags is a move
10608 * and not move number 1, retrieve it from the parser
10610 if (cm != MoveNumberOne)
10611 LoadGameOneMove(cm);
10613 /* load the remaining moves from the file */
10614 while (LoadGameOneMove(EndOfFile)) {
10615 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10616 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10619 /* rewind to the start of the game */
10620 currentMove = backwardMostMove;
10622 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10624 if (oldGameMode == AnalyzeFile ||
10625 oldGameMode == AnalyzeMode) {
10626 AnalyzeFileEvent();
10629 if (matchMode || appData.timeDelay == 0) {
10631 gameMode = EditGame;
10633 } else if (appData.timeDelay > 0) {
10634 AutoPlayGameLoop();
10637 if (appData.debugMode)
10638 fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
10640 loadFlag = 0; /* [HGM] true game starts */
10644 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
10646 ReloadPosition(offset)
10649 int positionNumber = lastLoadPositionNumber + offset;
10650 if (lastLoadPositionFP == NULL) {
10651 DisplayError(_("No position has been loaded yet"), 0);
10654 if (positionNumber <= 0) {
10655 DisplayError(_("Can't back up any further"), 0);
10658 return LoadPosition(lastLoadPositionFP, positionNumber,
10659 lastLoadPositionTitle);
10662 /* Load the nth position from the given file */
10664 LoadPositionFromFile(filename, n, title)
10672 if (strcmp(filename, "-") == 0) {
10673 return LoadPosition(stdin, n, "stdin");
10675 f = fopen(filename, "rb");
10677 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10678 DisplayError(buf, errno);
10681 return LoadPosition(f, n, title);
10686 /* Load the nth position from the given open file, and close it */
10688 LoadPosition(f, positionNumber, title)
10690 int positionNumber;
10693 char *p, line[MSG_SIZ];
10694 Board initial_position;
10695 int i, j, fenMode, pn;
10697 if (gameMode == Training )
10698 SetTrainingModeOff();
10700 if (gameMode != BeginningOfGame) {
10701 Reset(FALSE, TRUE);
10703 if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
10704 fclose(lastLoadPositionFP);
10706 if (positionNumber == 0) positionNumber = 1;
10707 lastLoadPositionFP = f;
10708 lastLoadPositionNumber = positionNumber;
10709 safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
10710 if (first.pr == NoProc) {
10711 StartChessProgram(&first);
10712 InitChessProgram(&first, FALSE);
10714 pn = positionNumber;
10715 if (positionNumber < 0) {
10716 /* Negative position number means to seek to that byte offset */
10717 if (fseek(f, -positionNumber, 0) == -1) {
10718 DisplayError(_("Can't seek on position file"), 0);
10723 if (fseek(f, 0, 0) == -1) {
10724 if (f == lastLoadPositionFP ?
10725 positionNumber == lastLoadPositionNumber + 1 :
10726 positionNumber == 1) {
10729 DisplayError(_("Can't seek on position file"), 0);
10734 /* See if this file is FEN or old-style xboard */
10735 if (fgets(line, MSG_SIZ, f) == NULL) {
10736 DisplayError(_("Position not found in file"), 0);
10739 // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
10740 fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
10743 if (fenMode || line[0] == '#') pn--;
10745 /* skip positions before number pn */
10746 if (fgets(line, MSG_SIZ, f) == NULL) {
10748 DisplayError(_("Position not found in file"), 0);
10751 if (fenMode || line[0] == '#') pn--;
10756 if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
10757 DisplayError(_("Bad FEN position in file"), 0);
10761 (void) fgets(line, MSG_SIZ, f);
10762 (void) fgets(line, MSG_SIZ, f);
10764 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
10765 (void) fgets(line, MSG_SIZ, f);
10766 for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
10769 initial_position[i][j++] = CharToPiece(*p);
10773 blackPlaysFirst = FALSE;
10775 (void) fgets(line, MSG_SIZ, f);
10776 if (strncmp(line, "black", strlen("black"))==0)
10777 blackPlaysFirst = TRUE;
10780 startedFromSetupPosition = TRUE;
10782 SendToProgram("force\n", &first);
10783 CopyBoard(boards[0], initial_position);
10784 if (blackPlaysFirst) {
10785 currentMove = forwardMostMove = backwardMostMove = 1;
10786 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
10787 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
10788 CopyBoard(boards[1], initial_position);
10789 DisplayMessage("", _("Black to play"));
10791 currentMove = forwardMostMove = backwardMostMove = 0;
10792 DisplayMessage("", _("White to play"));
10794 initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
10795 SendBoard(&first, forwardMostMove);
10796 if (appData.debugMode) {
10798 for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
10799 for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
10800 fprintf(debugFP, "Load Position\n");
10803 if (positionNumber > 1) {
10804 snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
10805 DisplayTitle(line);
10807 DisplayTitle(title);
10809 gameMode = EditGame;
10812 timeRemaining[0][1] = whiteTimeRemaining;
10813 timeRemaining[1][1] = blackTimeRemaining;
10814 DrawPosition(FALSE, boards[currentMove]);
10821 CopyPlayerNameIntoFileName(dest, src)
10824 while (*src != NULLCHAR && *src != ',') {
10829 *(*dest)++ = *src++;
10834 char *DefaultFileName(ext)
10837 static char def[MSG_SIZ];
10840 if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
10842 CopyPlayerNameIntoFileName(&p, gameInfo.white);
10844 CopyPlayerNameIntoFileName(&p, gameInfo.black);
10846 safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
10853 /* Save the current game to the given file */
10855 SaveGameToFile(filename, append)
10862 if (strcmp(filename, "-") == 0) {
10863 return SaveGame(stdout, 0, NULL);
10865 f = fopen(filename, append ? "a" : "w");
10867 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10868 DisplayError(buf, errno);
10871 return SaveGame(f, 0, NULL);
10880 static char buf[MSG_SIZ];
10883 p = strchr(str, ' ');
10884 if (p == NULL) return str;
10885 strncpy(buf, str, p - str);
10886 buf[p - str] = NULLCHAR;
10890 #define PGN_MAX_LINE 75
10892 #define PGN_SIDE_WHITE 0
10893 #define PGN_SIDE_BLACK 1
10896 static int FindFirstMoveOutOfBook( int side )
10900 if( backwardMostMove == 0 && ! startedFromSetupPosition) {
10901 int index = backwardMostMove;
10902 int has_book_hit = 0;
10904 if( (index % 2) != side ) {
10908 while( index < forwardMostMove ) {
10909 /* Check to see if engine is in book */
10910 int depth = pvInfoList[index].depth;
10911 int score = pvInfoList[index].score;
10917 else if( score == 0 && depth == 63 ) {
10918 in_book = 1; /* Zappa */
10920 else if( score == 2 && depth == 99 ) {
10921 in_book = 1; /* Abrok */
10924 has_book_hit += in_book;
10940 void GetOutOfBookInfo( char * buf )
10944 int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10946 oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
10947 oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
10951 if( oob[0] >= 0 || oob[1] >= 0 ) {
10952 for( i=0; i<2; i++ ) {
10956 if( i > 0 && oob[0] >= 0 ) {
10957 strcat( buf, " " );
10960 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
10961 sprintf( buf+strlen(buf), "%s%.2f",
10962 pvInfoList[idx].score >= 0 ? "+" : "",
10963 pvInfoList[idx].score / 100.0 );
10969 /* Save game in PGN style and close the file */
10974 int i, offset, linelen, newblock;
10978 int movelen, numlen, blank;
10979 char move_buffer[100]; /* [AS] Buffer for move+PV info */
10981 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10983 tm = time((time_t *) NULL);
10985 PrintPGNTags(f, &gameInfo);
10987 if (backwardMostMove > 0 || startedFromSetupPosition) {
10988 char *fen = PositionToFEN(backwardMostMove, NULL);
10989 fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
10990 fprintf(f, "\n{--------------\n");
10991 PrintPosition(f, backwardMostMove);
10992 fprintf(f, "--------------}\n");
10996 /* [AS] Out of book annotation */
10997 if( appData.saveOutOfBookInfo ) {
11000 GetOutOfBookInfo( buf );
11002 if( buf[0] != '\0' ) {
11003 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
11010 i = backwardMostMove;
11014 while (i < forwardMostMove) {
11015 /* Print comments preceding this move */
11016 if (commentList[i] != NULL) {
11017 if (linelen > 0) fprintf(f, "\n");
11018 fprintf(f, "%s", commentList[i]);
11023 /* Format move number */
11025 snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
11028 snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
11030 numtext[0] = NULLCHAR;
11032 numlen = strlen(numtext);
11035 /* Print move number */
11036 blank = linelen > 0 && numlen > 0;
11037 if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
11046 fprintf(f, "%s", numtext);
11050 safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
11051 movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
11054 blank = linelen > 0 && movelen > 0;
11055 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
11064 fprintf(f, "%s", move_buffer);
11065 linelen += movelen;
11067 /* [AS] Add PV info if present */
11068 if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
11069 /* [HGM] add time */
11070 char buf[MSG_SIZ]; int seconds;
11072 seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
11078 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
11081 seconds = (seconds + 4)/10; // round to full seconds
11083 snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
11085 snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
11088 snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
11089 pvInfoList[i].score >= 0 ? "+" : "",
11090 pvInfoList[i].score / 100.0,
11091 pvInfoList[i].depth,
11094 movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
11096 /* Print score/depth */
11097 blank = linelen > 0 && movelen > 0;
11098 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
11107 fprintf(f, "%s", move_buffer);
11108 linelen += movelen;
11114 /* Start a new line */
11115 if (linelen > 0) fprintf(f, "\n");
11117 /* Print comments after last move */
11118 if (commentList[i] != NULL) {
11119 fprintf(f, "%s\n", commentList[i]);
11123 if (gameInfo.resultDetails != NULL &&
11124 gameInfo.resultDetails[0] != NULLCHAR) {
11125 fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
11126 PGNResult(gameInfo.result));
11128 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
11132 lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
11136 /* Save game in old style and close the file */
11138 SaveGameOldStyle(f)
11144 tm = time((time_t *) NULL);
11146 fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
11149 if (backwardMostMove > 0 || startedFromSetupPosition) {
11150 fprintf(f, "\n[--------------\n");
11151 PrintPosition(f, backwardMostMove);
11152 fprintf(f, "--------------]\n");
11157 i = backwardMostMove;
11158 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
11160 while (i < forwardMostMove) {
11161 if (commentList[i] != NULL) {
11162 fprintf(f, "[%s]\n", commentList[i]);
11165 if ((i % 2) == 1) {
11166 fprintf(f, "%d. ... %s\n", (i - offset)/2 + 1, parseList[i]);
11169 fprintf(f, "%d. %s ", (i - offset)/2 + 1, parseList[i]);
11171 if (commentList[i] != NULL) {
11175 if (i >= forwardMostMove) {
11179 fprintf(f, "%s\n", parseList[i]);
11184 if (commentList[i] != NULL) {
11185 fprintf(f, "[%s]\n", commentList[i]);
11188 /* This isn't really the old style, but it's close enough */
11189 if (gameInfo.resultDetails != NULL &&
11190 gameInfo.resultDetails[0] != NULLCHAR) {
11191 fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
11192 gameInfo.resultDetails);
11194 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
11201 /* Save the current game to open file f and close the file */
11203 SaveGame(f, dummy, dummy2)
11208 if (gameMode == EditPosition) EditPositionDone(TRUE);
11209 lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
11210 if (appData.oldSaveStyle)
11211 return SaveGameOldStyle(f);
11213 return SaveGamePGN(f);
11216 /* Save the current position to the given file */
11218 SavePositionToFile(filename)
11224 if (strcmp(filename, "-") == 0) {
11225 return SavePosition(stdout, 0, NULL);
11227 f = fopen(filename, "a");
11229 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
11230 DisplayError(buf, errno);
11233 SavePosition(f, 0, NULL);
11239 /* Save the current position to the given open file and close the file */
11241 SavePosition(f, dummy, dummy2)
11249 if (gameMode == EditPosition) EditPositionDone(TRUE);
11250 if (appData.oldSaveStyle) {
11251 tm = time((time_t *) NULL);
11253 fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
11255 fprintf(f, "[--------------\n");
11256 PrintPosition(f, currentMove);
11257 fprintf(f, "--------------]\n");
11259 fen = PositionToFEN(currentMove, NULL);
11260 fprintf(f, "%s\n", fen);
11268 ReloadCmailMsgEvent(unregister)
11272 static char *inFilename = NULL;
11273 static char *outFilename;
11275 struct stat inbuf, outbuf;
11278 /* Any registered moves are unregistered if unregister is set, */
11279 /* i.e. invoked by the signal handler */
11281 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
11282 cmailMoveRegistered[i] = FALSE;
11283 if (cmailCommentList[i] != NULL) {
11284 free(cmailCommentList[i]);
11285 cmailCommentList[i] = NULL;
11288 nCmailMovesRegistered = 0;
11291 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
11292 cmailResult[i] = CMAIL_NOT_RESULT;
11296 if (inFilename == NULL) {
11297 /* Because the filenames are static they only get malloced once */
11298 /* and they never get freed */
11299 inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
11300 sprintf(inFilename, "%s.game.in", appData.cmailGameName);
11302 outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
11303 sprintf(outFilename, "%s.out", appData.cmailGameName);
11306 status = stat(outFilename, &outbuf);
11308 cmailMailedMove = FALSE;
11310 status = stat(inFilename, &inbuf);
11311 cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
11314 /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
11315 counts the games, notes how each one terminated, etc.
11317 It would be nice to remove this kludge and instead gather all
11318 the information while building the game list. (And to keep it
11319 in the game list nodes instead of having a bunch of fixed-size
11320 parallel arrays.) Note this will require getting each game's
11321 termination from the PGN tags, as the game list builder does
11322 not process the game moves. --mann
11324 cmailMsgLoaded = TRUE;
11325 LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
11327 /* Load first game in the file or popup game menu */
11328 LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
11330 #endif /* !WIN32 */
11338 char string[MSG_SIZ];
11340 if ( cmailMailedMove
11341 || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
11342 return TRUE; /* Allow free viewing */
11345 /* Unregister move to ensure that we don't leave RegisterMove */
11346 /* with the move registered when the conditions for registering no */
11348 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11349 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
11350 nCmailMovesRegistered --;
11352 if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
11354 free(cmailCommentList[lastLoadGameNumber - 1]);
11355 cmailCommentList[lastLoadGameNumber - 1] = NULL;
11359 if (cmailOldMove == -1) {
11360 DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
11364 if (currentMove > cmailOldMove + 1) {
11365 DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
11369 if (currentMove < cmailOldMove) {
11370 DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
11374 if (forwardMostMove > currentMove) {
11375 /* Silently truncate extra moves */
11379 if ( (currentMove == cmailOldMove + 1)
11380 || ( (currentMove == cmailOldMove)
11381 && ( (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
11382 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
11383 if (gameInfo.result != GameUnfinished) {
11384 cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
11387 if (commentList[currentMove] != NULL) {
11388 cmailCommentList[lastLoadGameNumber - 1]
11389 = StrSave(commentList[currentMove]);
11391 safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
11393 if (appData.debugMode)
11394 fprintf(debugFP, "Saving %s for game %d\n",
11395 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
11397 snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
11399 f = fopen(string, "w");
11400 if (appData.oldSaveStyle) {
11401 SaveGameOldStyle(f); /* also closes the file */
11403 snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
11404 f = fopen(string, "w");
11405 SavePosition(f, 0, NULL); /* also closes the file */
11407 fprintf(f, "{--------------\n");
11408 PrintPosition(f, currentMove);
11409 fprintf(f, "--------------}\n\n");
11411 SaveGame(f, 0, NULL); /* also closes the file*/
11414 cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
11415 nCmailMovesRegistered ++;
11416 } else if (nCmailGames == 1) {
11417 DisplayError(_("You have not made a move yet"), 0);
11428 static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
11429 FILE *commandOutput;
11430 char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
11431 int nBytes = 0; /* Suppress warnings on uninitialized variables */
11437 if (! cmailMsgLoaded) {
11438 DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
11442 if (nCmailGames == nCmailResults) {
11443 DisplayError(_("No unfinished games"), 0);
11447 #if CMAIL_PROHIBIT_REMAIL
11448 if (cmailMailedMove) {
11449 snprintf(msg, MSG_SIZ, _("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);
11450 DisplayError(msg, 0);
11455 if (! (cmailMailedMove || RegisterMove())) return;
11457 if ( cmailMailedMove
11458 || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
11459 snprintf(string, MSG_SIZ, partCommandString,
11460 appData.debugMode ? " -v" : "", appData.cmailGameName);
11461 commandOutput = popen(string, "r");
11463 if (commandOutput == NULL) {
11464 DisplayError(_("Failed to invoke cmail"), 0);
11466 for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
11467 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
11469 if (nBuffers > 1) {
11470 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
11471 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
11472 nBytes = MSG_SIZ - 1;
11474 (void) memcpy(msg, buffer, nBytes);
11476 *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
11478 if(StrStr(msg, "Mailed cmail message to ") != NULL) {
11479 cmailMailedMove = TRUE; /* Prevent >1 moves */
11482 for (i = 0; i < nCmailGames; i ++) {
11483 if (cmailResult[i] == CMAIL_NOT_RESULT) {
11488 && ( (arcDir = (char *) getenv("CMAIL_ARCDIR"))
11490 snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
11492 appData.cmailGameName,
11494 LoadGameFromFile(buffer, 1, buffer, FALSE);
11495 cmailMsgLoaded = FALSE;
11499 DisplayInformation(msg);
11500 pclose(commandOutput);
11503 if ((*cmailMsg) != '\0') {
11504 DisplayInformation(cmailMsg);
11509 #endif /* !WIN32 */
11518 int prependComma = 0;
11520 char string[MSG_SIZ]; /* Space for game-list */
11523 if (!cmailMsgLoaded) return "";
11525 if (cmailMailedMove) {
11526 snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
11528 /* Create a list of games left */
11529 snprintf(string, MSG_SIZ, "[");
11530 for (i = 0; i < nCmailGames; i ++) {
11531 if (! ( cmailMoveRegistered[i]
11532 || (cmailResult[i] == CMAIL_OLD_RESULT))) {
11533 if (prependComma) {
11534 snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
11536 snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
11540 strcat(string, number);
11543 strcat(string, "]");
11545 if (nCmailMovesRegistered + nCmailResults == 0) {
11546 switch (nCmailGames) {
11548 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
11552 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
11556 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
11561 switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
11563 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
11568 if (nCmailResults == nCmailGames) {
11569 snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
11571 snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
11576 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
11588 if (gameMode == Training)
11589 SetTrainingModeOff();
11592 cmailMsgLoaded = FALSE;
11593 if (appData.icsActive) {
11594 SendToICS(ics_prefix);
11595 SendToICS("refresh\n");
11605 /* Give up on clean exit */
11609 /* Keep trying for clean exit */
11613 if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
11615 if (telnetISR != NULL) {
11616 RemoveInputSource(telnetISR);
11618 if (icsPR != NoProc) {
11619 DestroyChildProcess(icsPR, TRUE);
11622 /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
11623 GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
11625 /* [HGM] crash: the above GameEnds() is a dud if another one was running */
11626 /* make sure this other one finishes before killing it! */
11627 if(endingGame) { int count = 0;
11628 if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
11629 while(endingGame && count++ < 10) DoSleep(1);
11630 if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
11633 /* Kill off chess programs */
11634 if (first.pr != NoProc) {
11637 DoSleep( appData.delayBeforeQuit );
11638 SendToProgram("quit\n", &first);
11639 DoSleep( appData.delayAfterQuit );
11640 DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
11642 if (second.pr != NoProc) {
11643 DoSleep( appData.delayBeforeQuit );
11644 SendToProgram("quit\n", &second);
11645 DoSleep( appData.delayAfterQuit );
11646 DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
11648 if (first.isr != NULL) {
11649 RemoveInputSource(first.isr);
11651 if (second.isr != NULL) {
11652 RemoveInputSource(second.isr);
11655 ShutDownFrontEnd();
11662 if (appData.debugMode)
11663 fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
11667 if (gameMode == MachinePlaysWhite ||
11668 gameMode == MachinePlaysBlack) {
11671 DisplayBothClocks();
11673 if (gameMode == PlayFromGameFile) {
11674 if (appData.timeDelay >= 0)
11675 AutoPlayGameLoop();
11676 } else if (gameMode == IcsExamining && pauseExamInvalid) {
11677 Reset(FALSE, TRUE);
11678 SendToICS(ics_prefix);
11679 SendToICS("refresh\n");
11680 } else if (currentMove < forwardMostMove) {
11681 ForwardInner(forwardMostMove);
11683 pauseExamInvalid = FALSE;
11685 switch (gameMode) {
11689 pauseExamForwardMostMove = forwardMostMove;
11690 pauseExamInvalid = FALSE;
11693 case IcsPlayingWhite:
11694 case IcsPlayingBlack:
11698 case PlayFromGameFile:
11699 (void) StopLoadGameTimer();
11703 case BeginningOfGame:
11704 if (appData.icsActive) return;
11705 /* else fall through */
11706 case MachinePlaysWhite:
11707 case MachinePlaysBlack:
11708 case TwoMachinesPlay:
11709 if (forwardMostMove == 0)
11710 return; /* don't pause if no one has moved */
11711 if ((gameMode == MachinePlaysWhite &&
11712 !WhiteOnMove(forwardMostMove)) ||
11713 (gameMode == MachinePlaysBlack &&
11714 WhiteOnMove(forwardMostMove))) {
11727 char title[MSG_SIZ];
11729 if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
11730 safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
11732 snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
11733 WhiteOnMove(currentMove - 1) ? " " : ".. ",
11734 parseList[currentMove - 1]);
11737 EditCommentPopUp(currentMove, title, commentList[currentMove]);
11744 char *tags = PGNTags(&gameInfo);
11745 EditTagsPopUp(tags, NULL);
11752 if (appData.noChessProgram || gameMode == AnalyzeMode)
11755 if (gameMode != AnalyzeFile) {
11756 if (!appData.icsEngineAnalyze) {
11758 if (gameMode != EditGame) return;
11760 ResurrectChessProgram();
11761 SendToProgram("analyze\n", &first);
11762 first.analyzing = TRUE;
11763 /*first.maybeThinking = TRUE;*/
11764 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11765 EngineOutputPopUp();
11767 if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
11772 StartAnalysisClock();
11773 GetTimeMark(&lastNodeCountTime);
11780 if (appData.noChessProgram || gameMode == AnalyzeFile)
11783 if (gameMode != AnalyzeMode) {
11785 if (gameMode != EditGame) return;
11786 ResurrectChessProgram();
11787 SendToProgram("analyze\n", &first);
11788 first.analyzing = TRUE;
11789 /*first.maybeThinking = TRUE;*/
11790 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11791 EngineOutputPopUp();
11793 gameMode = AnalyzeFile;
11798 StartAnalysisClock();
11799 GetTimeMark(&lastNodeCountTime);
11804 MachineWhiteEvent()
11807 char *bookHit = NULL;
11809 if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
11813 if (gameMode == PlayFromGameFile ||
11814 gameMode == TwoMachinesPlay ||
11815 gameMode == Training ||
11816 gameMode == AnalyzeMode ||
11817 gameMode == EndOfGame)
11820 if (gameMode == EditPosition)
11821 EditPositionDone(TRUE);
11823 if (!WhiteOnMove(currentMove)) {
11824 DisplayError(_("It is not White's turn"), 0);
11828 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
11831 if (gameMode == EditGame || gameMode == AnalyzeMode ||
11832 gameMode == AnalyzeFile)
11835 ResurrectChessProgram(); /* in case it isn't running */
11836 if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
11837 gameMode = MachinePlaysWhite;
11840 gameMode = MachinePlaysWhite;
11844 snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
11846 if (first.sendName) {
11847 snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
11848 SendToProgram(buf, &first);
11850 if (first.sendTime) {
11851 if (first.useColors) {
11852 SendToProgram("black\n", &first); /*gnu kludge*/
11854 SendTimeRemaining(&first, TRUE);
11856 if (first.useColors) {
11857 SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
11859 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
11860 SetMachineThinkingEnables();
11861 first.maybeThinking = TRUE;
11865 if (appData.autoFlipView && !flipView) {
11866 flipView = !flipView;
11867 DrawPosition(FALSE, NULL);
11868 DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;
11871 if(bookHit) { // [HGM] book: simulate book reply
11872 static char bookMove[MSG_SIZ]; // a bit generous?
11874 programStats.nodes = programStats.depth = programStats.time =
11875 programStats.score = programStats.got_only_move = 0;
11876 sprintf(programStats.movelist, "%s (xbook)", bookHit);
11878 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
11879 strcat(bookMove, bookHit);
11880 HandleMachineMove(bookMove, &first);
11885 MachineBlackEvent()
11888 char *bookHit = NULL;
11890 if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
11894 if (gameMode == PlayFromGameFile ||
11895 gameMode == TwoMachinesPlay ||
11896 gameMode == Training ||
11897 gameMode == AnalyzeMode ||
11898 gameMode == EndOfGame)
11901 if (gameMode == EditPosition)
11902 EditPositionDone(TRUE);
11904 if (WhiteOnMove(currentMove)) {
11905 DisplayError(_("It is not Black's turn"), 0);
11909 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
11912 if (gameMode == EditGame || gameMode == AnalyzeMode ||
11913 gameMode == AnalyzeFile)
11916 ResurrectChessProgram(); /* in case it isn't running */
11917 gameMode = MachinePlaysBlack;
11921 snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
11923 if (first.sendName) {
11924 snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
11925 SendToProgram(buf, &first);
11927 if (first.sendTime) {
11928 if (first.useColors) {
11929 SendToProgram("white\n", &first); /*gnu kludge*/
11931 SendTimeRemaining(&first, FALSE);
11933 if (first.useColors) {
11934 SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
11936 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
11937 SetMachineThinkingEnables();
11938 first.maybeThinking = TRUE;
11941 if (appData.autoFlipView && flipView) {
11942 flipView = !flipView;
11943 DrawPosition(FALSE, NULL);
11944 DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;
11946 if(bookHit) { // [HGM] book: simulate book reply
11947 static char bookMove[MSG_SIZ]; // a bit generous?
11949 programStats.nodes = programStats.depth = programStats.time =
11950 programStats.score = programStats.got_only_move = 0;
11951 sprintf(programStats.movelist, "%s (xbook)", bookHit);
11953 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
11954 strcat(bookMove, bookHit);
11955 HandleMachineMove(bookMove, &first);
11961 DisplayTwoMachinesTitle()
11964 if (appData.matchGames > 0) {
11965 if (first.twoMachinesColor[0] == 'w') {
11966 snprintf(buf, MSG_SIZ, "%s vs. %s (%d-%d-%d)",
11967 gameInfo.white, gameInfo.black,
11968 first.matchWins, second.matchWins,
11969 matchGame - 1 - (first.matchWins + second.matchWins));
11971 snprintf(buf, MSG_SIZ, "%s vs. %s (%d-%d-%d)",
11972 gameInfo.white, gameInfo.black,
11973 second.matchWins, first.matchWins,
11974 matchGame - 1 - (first.matchWins + second.matchWins));
11977 snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
11983 SettingsMenuIfReady()
11985 if (second.lastPing != second.lastPong) {
11986 DisplayMessage("", _("Waiting for second chess program"));
11987 ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
11991 DisplayMessage("", "");
11992 SettingsPopUp(&second);
11996 WaitForSecond(DelayedEventCallback retry)
11998 if (second.pr == NULL) {
11999 StartChessProgram(&second);
12000 if (second.protocolVersion == 1) {
12003 /* kludge: allow timeout for initial "feature" command */
12005 DisplayMessage("", _("Starting second chess program"));
12006 ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
12014 TwoMachinesEvent P((void))
12018 ChessProgramState *onmove;
12019 char *bookHit = NULL;
12021 if (appData.noChessProgram) return;
12023 switch (gameMode) {
12024 case TwoMachinesPlay:
12026 case MachinePlaysWhite:
12027 case MachinePlaysBlack:
12028 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
12029 DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
12033 case BeginningOfGame:
12034 case PlayFromGameFile:
12037 if (gameMode != EditGame) return;
12040 EditPositionDone(TRUE);
12051 // forwardMostMove = currentMove;
12052 TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
12053 ResurrectChessProgram(); /* in case first program isn't running */
12055 if(WaitForSecond(TwoMachinesEventIfReady)) return;
12056 DisplayMessage("", "");
12057 InitChessProgram(&second, FALSE);
12058 SendToProgram("force\n", &second);
12059 if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
12060 ScheduleDelayedEvent(TwoMachinesEvent, 10);
12063 if (startedFromSetupPosition) {
12064 SendBoard(&second, backwardMostMove);
12065 if (appData.debugMode) {
12066 fprintf(debugFP, "Two Machines\n");
12069 for (i = backwardMostMove; i < forwardMostMove; i++) {
12070 SendMoveToProgram(i, &second);
12073 gameMode = TwoMachinesPlay;
12077 DisplayTwoMachinesTitle();
12079 if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
12085 SendToProgram(first.computerString, &first);
12086 if (first.sendName) {
12087 snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
12088 SendToProgram(buf, &first);
12090 SendToProgram(second.computerString, &second);
12091 if (second.sendName) {
12092 snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
12093 SendToProgram(buf, &second);
12097 if (!first.sendTime || !second.sendTime) {
12098 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12099 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12101 if (onmove->sendTime) {
12102 if (onmove->useColors) {
12103 SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
12105 SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
12107 if (onmove->useColors) {
12108 SendToProgram(onmove->twoMachinesColor, onmove);
12110 bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
12111 // SendToProgram("go\n", onmove);
12112 onmove->maybeThinking = TRUE;
12113 SetMachineThinkingEnables();
12117 if(bookHit) { // [HGM] book: simulate book reply
12118 static char bookMove[MSG_SIZ]; // a bit generous?
12120 programStats.nodes = programStats.depth = programStats.time =
12121 programStats.score = programStats.got_only_move = 0;
12122 sprintf(programStats.movelist, "%s (xbook)", bookHit);
12124 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
12125 strcat(bookMove, bookHit);
12126 savedMessage = bookMove; // args for deferred call
12127 savedState = onmove;
12128 ScheduleDelayedEvent(DeferredBookMove, 1);
12135 if (gameMode == Training) {
12136 SetTrainingModeOff();
12137 gameMode = PlayFromGameFile;
12138 DisplayMessage("", _("Training mode off"));
12140 gameMode = Training;
12141 animateTraining = appData.animate;
12143 /* make sure we are not already at the end of the game */
12144 if (currentMove < forwardMostMove) {
12145 SetTrainingModeOn();
12146 DisplayMessage("", _("Training mode on"));
12148 gameMode = PlayFromGameFile;
12149 DisplayError(_("Already at end of game"), 0);
12158 if (!appData.icsActive) return;
12159 switch (gameMode) {
12160 case IcsPlayingWhite:
12161 case IcsPlayingBlack:
12164 case BeginningOfGame:
12172 EditPositionDone(TRUE);
12185 gameMode = IcsIdle;
12196 switch (gameMode) {
12198 SetTrainingModeOff();
12200 case MachinePlaysWhite:
12201 case MachinePlaysBlack:
12202 case BeginningOfGame:
12203 SendToProgram("force\n", &first);
12204 SetUserThinkingEnables();
12206 case PlayFromGameFile:
12207 (void) StopLoadGameTimer();
12208 if (gameFileFP != NULL) {
12213 EditPositionDone(TRUE);
12218 SendToProgram("force\n", &first);
12220 case TwoMachinesPlay:
12221 GameEnds(EndOfFile, NULL, GE_PLAYER);
12222 ResurrectChessProgram();
12223 SetUserThinkingEnables();
12226 ResurrectChessProgram();
12228 case IcsPlayingBlack:
12229 case IcsPlayingWhite:
12230 DisplayError(_("Warning: You are still playing a game"), 0);
12233 DisplayError(_("Warning: You are still observing a game"), 0);
12236 DisplayError(_("Warning: You are still examining a game"), 0);
12247 first.offeredDraw = second.offeredDraw = 0;
12249 if (gameMode == PlayFromGameFile) {
12250 whiteTimeRemaining = timeRemaining[0][currentMove];
12251 blackTimeRemaining = timeRemaining[1][currentMove];
12255 if (gameMode == MachinePlaysWhite ||
12256 gameMode == MachinePlaysBlack ||
12257 gameMode == TwoMachinesPlay ||
12258 gameMode == EndOfGame) {
12259 i = forwardMostMove;
12260 while (i > currentMove) {
12261 SendToProgram("undo\n", &first);
12264 whiteTimeRemaining = timeRemaining[0][currentMove];
12265 blackTimeRemaining = timeRemaining[1][currentMove];
12266 DisplayBothClocks();
12267 if (whiteFlag || blackFlag) {
12268 whiteFlag = blackFlag = 0;
12273 gameMode = EditGame;
12280 EditPositionEvent()
12282 if (gameMode == EditPosition) {
12288 if (gameMode != EditGame) return;
12290 gameMode = EditPosition;
12293 if (currentMove > 0)
12294 CopyBoard(boards[0], boards[currentMove]);
12296 blackPlaysFirst = !WhiteOnMove(currentMove);
12298 currentMove = forwardMostMove = backwardMostMove = 0;
12299 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12306 /* [DM] icsEngineAnalyze - possible call from other functions */
12307 if (appData.icsEngineAnalyze) {
12308 appData.icsEngineAnalyze = FALSE;
12310 DisplayMessage("",_("Close ICS engine analyze..."));
12312 if (first.analysisSupport && first.analyzing) {
12313 SendToProgram("exit\n", &first);
12314 first.analyzing = FALSE;
12316 thinkOutput[0] = NULLCHAR;
12320 EditPositionDone(Boolean fakeRights)
12322 int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
12324 startedFromSetupPosition = TRUE;
12325 InitChessProgram(&first, FALSE);
12326 if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
12327 boards[0][EP_STATUS] = EP_NONE;
12328 boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
12329 if(boards[0][0][BOARD_WIDTH>>1] == king) {
12330 boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : NoRights;
12331 boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
12332 } else boards[0][CASTLING][2] = NoRights;
12333 if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
12334 boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : NoRights;
12335 boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
12336 } else boards[0][CASTLING][5] = NoRights;
12338 SendToProgram("force\n", &first);
12339 if (blackPlaysFirst) {
12340 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12341 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12342 currentMove = forwardMostMove = backwardMostMove = 1;
12343 CopyBoard(boards[1], boards[0]);
12345 currentMove = forwardMostMove = backwardMostMove = 0;
12347 SendBoard(&first, forwardMostMove);
12348 if (appData.debugMode) {
12349 fprintf(debugFP, "EditPosDone\n");
12352 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12353 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12354 gameMode = EditGame;
12356 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12357 ClearHighlights(); /* [AS] */
12360 /* Pause for `ms' milliseconds */
12361 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
12371 } while (SubtractTimeMarks(&m2, &m1) < ms);
12374 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
12376 SendMultiLineToICS(buf)
12379 char temp[MSG_SIZ+1], *p;
12386 strncpy(temp, buf, len);
12391 if (*p == '\n' || *p == '\r')
12396 strcat(temp, "\n");
12398 SendToPlayer(temp, strlen(temp));
12402 SetWhiteToPlayEvent()
12404 if (gameMode == EditPosition) {
12405 blackPlaysFirst = FALSE;
12406 DisplayBothClocks(); /* works because currentMove is 0 */
12407 } else if (gameMode == IcsExamining) {
12408 SendToICS(ics_prefix);
12409 SendToICS("tomove white\n");
12414 SetBlackToPlayEvent()
12416 if (gameMode == EditPosition) {
12417 blackPlaysFirst = TRUE;
12418 currentMove = 1; /* kludge */
12419 DisplayBothClocks();
12421 } else if (gameMode == IcsExamining) {
12422 SendToICS(ics_prefix);
12423 SendToICS("tomove black\n");
12428 EditPositionMenuEvent(selection, x, y)
12429 ChessSquare selection;
12433 ChessSquare piece = boards[0][y][x];
12435 if (gameMode != EditPosition && gameMode != IcsExamining) return;
12437 switch (selection) {
12439 if (gameMode == IcsExamining && ics_type == ICS_FICS) {
12440 SendToICS(ics_prefix);
12441 SendToICS("bsetup clear\n");
12442 } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
12443 SendToICS(ics_prefix);
12444 SendToICS("clearboard\n");
12446 for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
12447 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
12448 for (y = 0; y < BOARD_HEIGHT; y++) {
12449 if (gameMode == IcsExamining) {
12450 if (boards[currentMove][y][x] != EmptySquare) {
12451 snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
12456 boards[0][y][x] = p;
12461 if (gameMode == EditPosition) {
12462 DrawPosition(FALSE, boards[0]);
12467 SetWhiteToPlayEvent();
12471 SetBlackToPlayEvent();
12475 if (gameMode == IcsExamining) {
12476 if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
12477 snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
12480 if(x < BOARD_LEFT || x >= BOARD_RGHT) {
12481 if(x == BOARD_LEFT-2) {
12482 if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
12483 boards[0][y][1] = 0;
12485 if(x == BOARD_RGHT+1) {
12486 if(y >= gameInfo.holdingsSize) break;
12487 boards[0][y][BOARD_WIDTH-2] = 0;
12490 boards[0][y][x] = EmptySquare;
12491 DrawPosition(FALSE, boards[0]);
12496 if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
12497 piece >= (int)BlackPawn && piece < (int)BlackMan ) {
12498 selection = (ChessSquare) (PROMOTED piece);
12499 } else if(piece == EmptySquare) selection = WhiteSilver;
12500 else selection = (ChessSquare)((int)piece - 1);
12504 if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
12505 piece > (int)BlackMan && piece <= (int)BlackKing ) {
12506 selection = (ChessSquare) (DEMOTED piece);
12507 } else if(piece == EmptySquare) selection = BlackSilver;
12508 else selection = (ChessSquare)((int)piece + 1);
12513 if(gameInfo.variant == VariantShatranj ||
12514 gameInfo.variant == VariantXiangqi ||
12515 gameInfo.variant == VariantCourier ||
12516 gameInfo.variant == VariantMakruk )
12517 selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
12522 if(gameInfo.variant == VariantXiangqi)
12523 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
12524 if(gameInfo.variant == VariantKnightmate)
12525 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
12528 if (gameMode == IcsExamining) {
12529 if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
12530 snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
12531 PieceToChar(selection), AAA + x, ONE + y);
12534 if(x < BOARD_LEFT || x >= BOARD_RGHT) {
12536 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
12537 n = PieceToNumber(selection - BlackPawn);
12538 if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
12539 boards[0][BOARD_HEIGHT-1-n][0] = selection;
12540 boards[0][BOARD_HEIGHT-1-n][1]++;
12542 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
12543 n = PieceToNumber(selection);
12544 if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
12545 boards[0][n][BOARD_WIDTH-1] = selection;
12546 boards[0][n][BOARD_WIDTH-2]++;
12549 boards[0][y][x] = selection;
12550 DrawPosition(TRUE, boards[0]);
12558 DropMenuEvent(selection, x, y)
12559 ChessSquare selection;
12562 ChessMove moveType;
12564 switch (gameMode) {
12565 case IcsPlayingWhite:
12566 case MachinePlaysBlack:
12567 if (!WhiteOnMove(currentMove)) {
12568 DisplayMoveError(_("It is Black's turn"));
12571 moveType = WhiteDrop;
12573 case IcsPlayingBlack:
12574 case MachinePlaysWhite:
12575 if (WhiteOnMove(currentMove)) {
12576 DisplayMoveError(_("It is White's turn"));
12579 moveType = BlackDrop;
12582 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
12588 if (moveType == BlackDrop && selection < BlackPawn) {
12589 selection = (ChessSquare) ((int) selection
12590 + (int) BlackPawn - (int) WhitePawn);
12592 if (boards[currentMove][y][x] != EmptySquare) {
12593 DisplayMoveError(_("That square is occupied"));
12597 FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
12603 /* Accept a pending offer of any kind from opponent */
12605 if (appData.icsActive) {
12606 SendToICS(ics_prefix);
12607 SendToICS("accept\n");
12608 } else if (cmailMsgLoaded) {
12609 if (currentMove == cmailOldMove &&
12610 commentList[cmailOldMove] != NULL &&
12611 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12612 "Black offers a draw" : "White offers a draw")) {
12614 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
12615 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
12617 DisplayError(_("There is no pending offer on this move"), 0);
12618 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12621 /* Not used for offers from chess program */
12628 /* Decline a pending offer of any kind from opponent */
12630 if (appData.icsActive) {
12631 SendToICS(ics_prefix);
12632 SendToICS("decline\n");
12633 } else if (cmailMsgLoaded) {
12634 if (currentMove == cmailOldMove &&
12635 commentList[cmailOldMove] != NULL &&
12636 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12637 "Black offers a draw" : "White offers a draw")) {
12639 AppendComment(cmailOldMove, "Draw declined", TRUE);
12640 DisplayComment(cmailOldMove - 1, "Draw declined");
12643 DisplayError(_("There is no pending offer on this move"), 0);
12646 /* Not used for offers from chess program */
12653 /* Issue ICS rematch command */
12654 if (appData.icsActive) {
12655 SendToICS(ics_prefix);
12656 SendToICS("rematch\n");
12663 /* Call your opponent's flag (claim a win on time) */
12664 if (appData.icsActive) {
12665 SendToICS(ics_prefix);
12666 SendToICS("flag\n");
12668 switch (gameMode) {
12671 case MachinePlaysWhite:
12674 GameEnds(GameIsDrawn, "Both players ran out of time",
12677 GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
12679 DisplayError(_("Your opponent is not out of time"), 0);
12682 case MachinePlaysBlack:
12685 GameEnds(GameIsDrawn, "Both players ran out of time",
12688 GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
12690 DisplayError(_("Your opponent is not out of time"), 0);
12698 ClockClick(int which)
12699 { // [HGM] code moved to back-end from winboard.c
12700 if(which) { // black clock
12701 if (gameMode == EditPosition || gameMode == IcsExamining) {
12702 if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
12703 SetBlackToPlayEvent();
12704 } else if (gameMode == EditGame || shiftKey) {
12705 AdjustClock(which, -1);
12706 } else if (gameMode == IcsPlayingWhite ||
12707 gameMode == MachinePlaysBlack) {
12710 } else { // white clock
12711 if (gameMode == EditPosition || gameMode == IcsExamining) {
12712 if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
12713 SetWhiteToPlayEvent();
12714 } else if (gameMode == EditGame || shiftKey) {
12715 AdjustClock(which, -1);
12716 } else if (gameMode == IcsPlayingBlack ||
12717 gameMode == MachinePlaysWhite) {
12726 /* Offer draw or accept pending draw offer from opponent */
12728 if (appData.icsActive) {
12729 /* Note: tournament rules require draw offers to be
12730 made after you make your move but before you punch
12731 your clock. Currently ICS doesn't let you do that;
12732 instead, you immediately punch your clock after making
12733 a move, but you can offer a draw at any time. */
12735 SendToICS(ics_prefix);
12736 SendToICS("draw\n");
12737 userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
12738 } else if (cmailMsgLoaded) {
12739 if (currentMove == cmailOldMove &&
12740 commentList[cmailOldMove] != NULL &&
12741 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12742 "Black offers a draw" : "White offers a draw")) {
12743 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
12744 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
12745 } else if (currentMove == cmailOldMove + 1) {
12746 char *offer = WhiteOnMove(cmailOldMove) ?
12747 "White offers a draw" : "Black offers a draw";
12748 AppendComment(currentMove, offer, TRUE);
12749 DisplayComment(currentMove - 1, offer);
12750 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
12752 DisplayError(_("You must make your move before offering a draw"), 0);
12753 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12755 } else if (first.offeredDraw) {
12756 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
12758 if (first.sendDrawOffers) {
12759 SendToProgram("draw\n", &first);
12760 userOfferedDraw = TRUE;
12768 /* Offer Adjourn or accept pending Adjourn offer from opponent */
12770 if (appData.icsActive) {
12771 SendToICS(ics_prefix);
12772 SendToICS("adjourn\n");
12774 /* Currently GNU Chess doesn't offer or accept Adjourns */
12782 /* Offer Abort or accept pending Abort offer from opponent */
12784 if (appData.icsActive) {
12785 SendToICS(ics_prefix);
12786 SendToICS("abort\n");
12788 GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
12795 /* Resign. You can do this even if it's not your turn. */
12797 if (appData.icsActive) {
12798 SendToICS(ics_prefix);
12799 SendToICS("resign\n");
12801 switch (gameMode) {
12802 case MachinePlaysWhite:
12803 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12805 case MachinePlaysBlack:
12806 GameEnds(BlackWins, "White resigns", GE_PLAYER);
12809 if (cmailMsgLoaded) {
12811 if (WhiteOnMove(cmailOldMove)) {
12812 GameEnds(BlackWins, "White resigns", GE_PLAYER);
12814 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12816 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
12827 StopObservingEvent()
12829 /* Stop observing current games */
12830 SendToICS(ics_prefix);
12831 SendToICS("unobserve\n");
12835 StopExaminingEvent()
12837 /* Stop observing current game */
12838 SendToICS(ics_prefix);
12839 SendToICS("unexamine\n");
12843 ForwardInner(target)
12848 if (appData.debugMode)
12849 fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
12850 target, currentMove, forwardMostMove);
12852 if (gameMode == EditPosition)
12855 if (gameMode == PlayFromGameFile && !pausing)
12858 if (gameMode == IcsExamining && pausing)
12859 limit = pauseExamForwardMostMove;
12861 limit = forwardMostMove;
12863 if (target > limit) target = limit;
12865 if (target > 0 && moveList[target - 1][0]) {
12866 int fromX, fromY, toX, toY;
12867 toX = moveList[target - 1][2] - AAA;
12868 toY = moveList[target - 1][3] - ONE;
12869 if (moveList[target - 1][1] == '@') {
12870 if (appData.highlightLastMove) {
12871 SetHighlights(-1, -1, toX, toY);
12874 fromX = moveList[target - 1][0] - AAA;
12875 fromY = moveList[target - 1][1] - ONE;
12876 if (target == currentMove + 1) {
12877 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
12879 if (appData.highlightLastMove) {
12880 SetHighlights(fromX, fromY, toX, toY);
12884 if (gameMode == EditGame || gameMode == AnalyzeMode ||
12885 gameMode == Training || gameMode == PlayFromGameFile ||
12886 gameMode == AnalyzeFile) {
12887 while (currentMove < target) {
12888 SendMoveToProgram(currentMove++, &first);
12891 currentMove = target;
12894 if (gameMode == EditGame || gameMode == EndOfGame) {
12895 whiteTimeRemaining = timeRemaining[0][currentMove];
12896 blackTimeRemaining = timeRemaining[1][currentMove];
12898 DisplayBothClocks();
12899 DisplayMove(currentMove - 1);
12900 DrawPosition(FALSE, boards[currentMove]);
12901 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
12902 if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
12903 DisplayComment(currentMove - 1, commentList[currentMove]);
12911 if (gameMode == IcsExamining && !pausing) {
12912 SendToICS(ics_prefix);
12913 SendToICS("forward\n");
12915 ForwardInner(currentMove + 1);
12922 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12923 /* to optimze, we temporarily turn off analysis mode while we feed
12924 * the remaining moves to the engine. Otherwise we get analysis output
12927 if (first.analysisSupport) {
12928 SendToProgram("exit\nforce\n", &first);
12929 first.analyzing = FALSE;
12933 if (gameMode == IcsExamining && !pausing) {
12934 SendToICS(ics_prefix);
12935 SendToICS("forward 999999\n");
12937 ForwardInner(forwardMostMove);
12940 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12941 /* we have fed all the moves, so reactivate analysis mode */
12942 SendToProgram("analyze\n", &first);
12943 first.analyzing = TRUE;
12944 /*first.maybeThinking = TRUE;*/
12945 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12950 BackwardInner(target)
12953 int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
12955 if (appData.debugMode)
12956 fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
12957 target, currentMove, forwardMostMove);
12959 if (gameMode == EditPosition) return;
12960 if (currentMove <= backwardMostMove) {
12962 DrawPosition(full_redraw, boards[currentMove]);
12965 if (gameMode == PlayFromGameFile && !pausing)
12968 if (moveList[target][0]) {
12969 int fromX, fromY, toX, toY;
12970 toX = moveList[target][2] - AAA;
12971 toY = moveList[target][3] - ONE;
12972 if (moveList[target][1] == '@') {
12973 if (appData.highlightLastMove) {
12974 SetHighlights(-1, -1, toX, toY);
12977 fromX = moveList[target][0] - AAA;
12978 fromY = moveList[target][1] - ONE;
12979 if (target == currentMove - 1) {
12980 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
12982 if (appData.highlightLastMove) {
12983 SetHighlights(fromX, fromY, toX, toY);
12987 if (gameMode == EditGame || gameMode==AnalyzeMode ||
12988 gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
12989 while (currentMove > target) {
12990 SendToProgram("undo\n", &first);
12994 currentMove = target;
12997 if (gameMode == EditGame || gameMode == EndOfGame) {
12998 whiteTimeRemaining = timeRemaining[0][currentMove];
12999 blackTimeRemaining = timeRemaining[1][currentMove];
13001 DisplayBothClocks();
13002 DisplayMove(currentMove - 1);
13003 DrawPosition(full_redraw, boards[currentMove]);
13004 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
13005 // [HGM] PV info: routine tests if comment empty
13006 DisplayComment(currentMove - 1, commentList[currentMove]);
13012 if (gameMode == IcsExamining && !pausing) {
13013 SendToICS(ics_prefix);
13014 SendToICS("backward\n");
13016 BackwardInner(currentMove - 1);
13023 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13024 /* to optimize, we temporarily turn off analysis mode while we undo
13025 * all the moves. Otherwise we get analysis output after each undo.
13027 if (first.analysisSupport) {
13028 SendToProgram("exit\nforce\n", &first);
13029 first.analyzing = FALSE;
13033 if (gameMode == IcsExamining && !pausing) {
13034 SendToICS(ics_prefix);
13035 SendToICS("backward 999999\n");
13037 BackwardInner(backwardMostMove);
13040 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13041 /* we have fed all the moves, so reactivate analysis mode */
13042 SendToProgram("analyze\n", &first);
13043 first.analyzing = TRUE;
13044 /*first.maybeThinking = TRUE;*/
13045 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13052 if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
13053 if (to >= forwardMostMove) to = forwardMostMove;
13054 if (to <= backwardMostMove) to = backwardMostMove;
13055 if (to < currentMove) {
13063 RevertEvent(Boolean annotate)
13065 if(PopTail(annotate)) { // [HGM] vari: restore old game tail
13068 if (gameMode != IcsExamining) {
13069 DisplayError(_("You are not examining a game"), 0);
13073 DisplayError(_("You can't revert while pausing"), 0);
13076 SendToICS(ics_prefix);
13077 SendToICS("revert\n");
13083 switch (gameMode) {
13084 case MachinePlaysWhite:
13085 case MachinePlaysBlack:
13086 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
13087 DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
13090 if (forwardMostMove < 2) return;
13091 currentMove = forwardMostMove = forwardMostMove - 2;
13092 whiteTimeRemaining = timeRemaining[0][currentMove];
13093 blackTimeRemaining = timeRemaining[1][currentMove];
13094 DisplayBothClocks();
13095 DisplayMove(currentMove - 1);
13096 ClearHighlights();/*!! could figure this out*/
13097 DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
13098 SendToProgram("remove\n", &first);
13099 /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
13102 case BeginningOfGame:
13106 case IcsPlayingWhite:
13107 case IcsPlayingBlack:
13108 if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
13109 SendToICS(ics_prefix);
13110 SendToICS("takeback 2\n");
13112 SendToICS(ics_prefix);
13113 SendToICS("takeback 1\n");
13122 ChessProgramState *cps;
13124 switch (gameMode) {
13125 case MachinePlaysWhite:
13126 if (!WhiteOnMove(forwardMostMove)) {
13127 DisplayError(_("It is your turn"), 0);
13132 case MachinePlaysBlack:
13133 if (WhiteOnMove(forwardMostMove)) {
13134 DisplayError(_("It is your turn"), 0);
13139 case TwoMachinesPlay:
13140 if (WhiteOnMove(forwardMostMove) ==
13141 (first.twoMachinesColor[0] == 'w')) {
13147 case BeginningOfGame:
13151 SendToProgram("?\n", cps);
13155 TruncateGameEvent()
13158 if (gameMode != EditGame) return;
13165 CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
13166 if (forwardMostMove > currentMove) {
13167 if (gameInfo.resultDetails != NULL) {
13168 free(gameInfo.resultDetails);
13169 gameInfo.resultDetails = NULL;
13170 gameInfo.result = GameUnfinished;
13172 forwardMostMove = currentMove;
13173 HistorySet(parseList, backwardMostMove, forwardMostMove,
13181 if (appData.noChessProgram) return;
13182 switch (gameMode) {
13183 case MachinePlaysWhite:
13184 if (WhiteOnMove(forwardMostMove)) {
13185 DisplayError(_("Wait until your turn"), 0);
13189 case BeginningOfGame:
13190 case MachinePlaysBlack:
13191 if (!WhiteOnMove(forwardMostMove)) {
13192 DisplayError(_("Wait until your turn"), 0);
13197 DisplayError(_("No hint available"), 0);
13200 SendToProgram("hint\n", &first);
13201 hintRequested = TRUE;
13207 if (appData.noChessProgram) return;
13208 switch (gameMode) {
13209 case MachinePlaysWhite:
13210 if (WhiteOnMove(forwardMostMove)) {
13211 DisplayError(_("Wait until your turn"), 0);
13215 case BeginningOfGame:
13216 case MachinePlaysBlack:
13217 if (!WhiteOnMove(forwardMostMove)) {
13218 DisplayError(_("Wait until your turn"), 0);
13223 EditPositionDone(TRUE);
13225 case TwoMachinesPlay:
13230 SendToProgram("bk\n", &first);
13231 bookOutput[0] = NULLCHAR;
13232 bookRequested = TRUE;
13238 char *tags = PGNTags(&gameInfo);
13239 TagsPopUp(tags, CmailMsg());
13243 /* end button procedures */
13246 PrintPosition(fp, move)
13252 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13253 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
13254 char c = PieceToChar(boards[move][i][j]);
13255 fputc(c == 'x' ? '.' : c, fp);
13256 fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
13259 if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
13260 fprintf(fp, "white to play\n");
13262 fprintf(fp, "black to play\n");
13269 if (gameInfo.white != NULL) {
13270 fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
13276 /* Find last component of program's own name, using some heuristics */
13278 TidyProgramName(prog, host, buf)
13279 char *prog, *host, buf[MSG_SIZ];
13282 int local = (strcmp(host, "localhost") == 0);
13283 while (!local && (p = strchr(prog, ';')) != NULL) {
13285 while (*p == ' ') p++;
13288 if (*prog == '"' || *prog == '\'') {
13289 q = strchr(prog + 1, *prog);
13291 q = strchr(prog, ' ');
13293 if (q == NULL) q = prog + strlen(prog);
13295 while (p >= prog && *p != '/' && *p != '\\') p--;
13297 if(p == prog && *p == '"') p++;
13298 if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
13299 memcpy(buf, p, q - p);
13300 buf[q - p] = NULLCHAR;
13308 TimeControlTagValue()
13311 if (!appData.clockMode) {
13312 safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
13313 } else if (movesPerSession > 0) {
13314 snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
13315 } else if (timeIncrement == 0) {
13316 snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
13318 snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
13320 return StrSave(buf);
13326 /* This routine is used only for certain modes */
13327 VariantClass v = gameInfo.variant;
13328 ChessMove r = GameUnfinished;
13331 if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
13332 r = gameInfo.result;
13333 p = gameInfo.resultDetails;
13334 gameInfo.resultDetails = NULL;
13336 ClearGameInfo(&gameInfo);
13337 gameInfo.variant = v;
13339 switch (gameMode) {
13340 case MachinePlaysWhite:
13341 gameInfo.event = StrSave( appData.pgnEventHeader );
13342 gameInfo.site = StrSave(HostName());
13343 gameInfo.date = PGNDate();
13344 gameInfo.round = StrSave("-");
13345 gameInfo.white = StrSave(first.tidy);
13346 gameInfo.black = StrSave(UserName());
13347 gameInfo.timeControl = TimeControlTagValue();
13350 case MachinePlaysBlack:
13351 gameInfo.event = StrSave( appData.pgnEventHeader );
13352 gameInfo.site = StrSave(HostName());
13353 gameInfo.date = PGNDate();
13354 gameInfo.round = StrSave("-");
13355 gameInfo.white = StrSave(UserName());
13356 gameInfo.black = StrSave(first.tidy);
13357 gameInfo.timeControl = TimeControlTagValue();
13360 case TwoMachinesPlay:
13361 gameInfo.event = StrSave( appData.pgnEventHeader );
13362 gameInfo.site = StrSave(HostName());
13363 gameInfo.date = PGNDate();
13364 if (matchGame > 0) {
13366 snprintf(buf, MSG_SIZ, "%d", matchGame);
13367 gameInfo.round = StrSave(buf);
13369 gameInfo.round = StrSave("-");
13371 if (first.twoMachinesColor[0] == 'w') {
13372 gameInfo.white = StrSave(first.tidy);
13373 gameInfo.black = StrSave(second.tidy);
13375 gameInfo.white = StrSave(second.tidy);
13376 gameInfo.black = StrSave(first.tidy);
13378 gameInfo.timeControl = TimeControlTagValue();
13382 gameInfo.event = StrSave("Edited game");
13383 gameInfo.site = StrSave(HostName());
13384 gameInfo.date = PGNDate();
13385 gameInfo.round = StrSave("-");
13386 gameInfo.white = StrSave("-");
13387 gameInfo.black = StrSave("-");
13388 gameInfo.result = r;
13389 gameInfo.resultDetails = p;
13393 gameInfo.event = StrSave("Edited position");
13394 gameInfo.site = StrSave(HostName());
13395 gameInfo.date = PGNDate();
13396 gameInfo.round = StrSave("-");
13397 gameInfo.white = StrSave("-");
13398 gameInfo.black = StrSave("-");
13401 case IcsPlayingWhite:
13402 case IcsPlayingBlack:
13407 case PlayFromGameFile:
13408 gameInfo.event = StrSave("Game from non-PGN file");
13409 gameInfo.site = StrSave(HostName());
13410 gameInfo.date = PGNDate();
13411 gameInfo.round = StrSave("-");
13412 gameInfo.white = StrSave("?");
13413 gameInfo.black = StrSave("?");
13422 ReplaceComment(index, text)
13430 if(index && sscanf(text, "%f/%d", &score, &len) == 2 &&
13431 pvInfoList[index-1].depth == len &&
13432 fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
13433 (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
13434 while (*text == '\n') text++;
13435 len = strlen(text);
13436 while (len > 0 && text[len - 1] == '\n') len--;
13438 if (commentList[index] != NULL)
13439 free(commentList[index]);
13442 commentList[index] = NULL;
13445 if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
13446 *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
13447 *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
13448 commentList[index] = (char *) malloc(len + 2);
13449 strncpy(commentList[index], text, len);
13450 commentList[index][len] = '\n';
13451 commentList[index][len + 1] = NULLCHAR;
13453 // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
13455 commentList[index] = (char *) malloc(len + 7);
13456 safeStrCpy(commentList[index], "{\n", 3);
13457 safeStrCpy(commentList[index]+2, text, len+1);
13458 commentList[index][len+2] = NULLCHAR;
13459 while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
13460 strcat(commentList[index], "\n}\n");
13474 if (ch == '\r') continue;
13476 } while (ch != '\0');
13480 AppendComment(index, text, addBraces)
13483 Boolean addBraces; // [HGM] braces: tells if we should add {}
13488 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
13489 text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
13492 while (*text == '\n') text++;
13493 len = strlen(text);
13494 while (len > 0 && text[len - 1] == '\n') len--;
13496 if (len == 0) return;
13498 if (commentList[index] != NULL) {
13499 old = commentList[index];
13500 oldlen = strlen(old);
13501 while(commentList[index][oldlen-1] == '\n')
13502 commentList[index][--oldlen] = NULLCHAR;
13503 commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
13504 safeStrCpy(commentList[index], old, oldlen + len + 6);
13506 // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
13507 if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
13508 if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
13509 while (*text == '\n') { text++; len--; }
13510 commentList[index][--oldlen] = NULLCHAR;
13512 if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
13513 else strcat(commentList[index], "\n");
13514 strcat(commentList[index], text);
13515 if(addBraces) strcat(commentList[index], addBraces == 2 ? ")\n" : "\n}\n");
13516 else strcat(commentList[index], "\n");
13518 commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
13520 safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
13521 else commentList[index][0] = NULLCHAR;
13522 strcat(commentList[index], text);
13523 strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
13524 if(addBraces == TRUE) strcat(commentList[index], "}\n");
13528 static char * FindStr( char * text, char * sub_text )
13530 char * result = strstr( text, sub_text );
13532 if( result != NULL ) {
13533 result += strlen( sub_text );
13539 /* [AS] Try to extract PV info from PGN comment */
13540 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
13541 char *GetInfoFromComment( int index, char * text )
13543 char * sep = text, *p;
13545 if( text != NULL && index > 0 ) {
13548 int time = -1, sec = 0, deci;
13549 char * s_eval = FindStr( text, "[%eval " );
13550 char * s_emt = FindStr( text, "[%emt " );
13552 if( s_eval != NULL || s_emt != NULL ) {
13556 if( s_eval != NULL ) {
13557 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
13561 if( delim != ']' ) {
13566 if( s_emt != NULL ) {
13571 /* We expect something like: [+|-]nnn.nn/dd */
13574 if(*text != '{') return text; // [HGM] braces: must be normal comment
13576 sep = strchr( text, '/' );
13577 if( sep == NULL || sep < (text+4) ) {
13582 if(p[1] == '(') { // comment starts with PV
13583 p = strchr(p, ')'); // locate end of PV
13584 if(p == NULL || sep < p+5) return text;
13585 // at this point we have something like "{(.*) +0.23/6 ..."
13586 p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
13587 *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
13588 // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
13590 time = -1; sec = -1; deci = -1;
13591 if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
13592 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
13593 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
13594 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3 ) {
13598 if( score_lo < 0 || score_lo >= 100 ) {
13602 if(sec >= 0) time = 600*time + 10*sec; else
13603 if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
13605 score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
13607 /* [HGM] PV time: now locate end of PV info */
13608 while( *++sep >= '0' && *sep <= '9'); // strip depth
13610 while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
13612 while( *++sep >= '0' && *sep <= '9'); // strip seconds
13614 while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
13615 while(*sep == ' ') sep++;
13626 pvInfoList[index-1].depth = depth;
13627 pvInfoList[index-1].score = score;
13628 pvInfoList[index-1].time = 10*time; // centi-sec
13629 if(*sep == '}') *sep = 0; else *--sep = '{';
13630 if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
13636 SendToProgram(message, cps)
13638 ChessProgramState *cps;
13640 int count, outCount, error;
13643 if (cps->pr == NULL) return;
13646 if (appData.debugMode) {
13649 fprintf(debugFP, "%ld >%-6s: %s",
13650 SubtractTimeMarks(&now, &programStartTime),
13651 cps->which, message);
13654 count = strlen(message);
13655 outCount = OutputToProcess(cps->pr, message, count, &error);
13656 if (outCount < count && !exiting
13657 && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
13658 snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
13659 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
13660 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
13661 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
13662 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
13664 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
13666 gameInfo.resultDetails = StrSave(buf);
13668 if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
13673 ReceiveFromProgram(isr, closure, message, count, error)
13674 InputSourceRef isr;
13682 ChessProgramState *cps = (ChessProgramState *)closure;
13684 if (isr != cps->isr) return; /* Killed intentionally */
13687 snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
13688 _(cps->which), cps->program);
13689 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
13690 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
13691 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
13692 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
13694 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
13696 gameInfo.resultDetails = StrSave(buf);
13698 RemoveInputSource(cps->isr);
13699 if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
13701 snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
13702 _(cps->which), cps->program);
13703 RemoveInputSource(cps->isr);
13705 /* [AS] Program is misbehaving badly... kill it */
13706 if( count == -2 ) {
13707 DestroyChildProcess( cps->pr, 9 );
13711 if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
13716 if ((end_str = strchr(message, '\r')) != NULL)
13717 *end_str = NULLCHAR;
13718 if ((end_str = strchr(message, '\n')) != NULL)
13719 *end_str = NULLCHAR;
13721 if (appData.debugMode) {
13722 TimeMark now; int print = 1;
13723 char *quote = ""; char c; int i;
13725 if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
13726 char start = message[0];
13727 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
13728 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
13729 sscanf(message, "move %c", &c)!=1 && sscanf(message, "offer%c", &c)!=1 &&
13730 sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
13731 sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
13732 sscanf(message, "tell%c", &c)!=1 && sscanf(message, "0-1 %c", &c)!=1 &&
13733 sscanf(message, "1-0 %c", &c)!=1 && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
13734 sscanf(message, "setboard %c", &c)!=1 && sscanf(message, "setup %c", &c)!=1 &&
13735 sscanf(message, "hint: %c", &c)!=1 &&
13736 sscanf(message, "pong %c", &c)!=1 && start != '#') {
13737 quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
13738 print = (appData.engineComments >= 2);
13740 message[0] = start; // restore original message
13744 fprintf(debugFP, "%ld <%-6s: %s%s\n",
13745 SubtractTimeMarks(&now, &programStartTime), cps->which,
13751 /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
13752 if (appData.icsEngineAnalyze) {
13753 if (strstr(message, "whisper") != NULL ||
13754 strstr(message, "kibitz") != NULL ||
13755 strstr(message, "tellics") != NULL) return;
13758 HandleMachineMove(message, cps);
13763 SendTimeControl(cps, mps, tc, inc, sd, st)
13764 ChessProgramState *cps;
13765 int mps, inc, sd, st;
13771 if( timeControl_2 > 0 ) {
13772 if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
13773 tc = timeControl_2;
13776 tc /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
13777 inc /= cps->timeOdds;
13778 st /= cps->timeOdds;
13780 seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
13783 /* Set exact time per move, normally using st command */
13784 if (cps->stKludge) {
13785 /* GNU Chess 4 has no st command; uses level in a nonstandard way */
13787 if (seconds == 0) {
13788 snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
13790 snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
13793 snprintf(buf, MSG_SIZ, "st %d\n", st);
13796 /* Set conventional or incremental time control, using level command */
13797 if (seconds == 0) {
13798 /* Note old gnuchess bug -- minutes:seconds used to not work.
13799 Fixed in later versions, but still avoid :seconds
13800 when seconds is 0. */
13801 snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
13803 snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
13804 seconds, inc/1000.);
13807 SendToProgram(buf, cps);
13809 /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
13810 /* Orthogonally, limit search to given depth */
13812 if (cps->sdKludge) {
13813 snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
13815 snprintf(buf, MSG_SIZ, "sd %d\n", sd);
13817 SendToProgram(buf, cps);
13820 if(cps->nps >= 0) { /* [HGM] nps */
13821 if(cps->supportsNPS == FALSE)
13822 cps->nps = -1; // don't use if engine explicitly says not supported!
13824 snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
13825 SendToProgram(buf, cps);
13830 ChessProgramState *WhitePlayer()
13831 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
13833 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
13834 gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
13840 SendTimeRemaining(cps, machineWhite)
13841 ChessProgramState *cps;
13842 int /*boolean*/ machineWhite;
13844 char message[MSG_SIZ];
13847 /* Note: this routine must be called when the clocks are stopped
13848 or when they have *just* been set or switched; otherwise
13849 it will be off by the time since the current tick started.
13851 if (machineWhite) {
13852 time = whiteTimeRemaining / 10;
13853 otime = blackTimeRemaining / 10;
13855 time = blackTimeRemaining / 10;
13856 otime = whiteTimeRemaining / 10;
13858 /* [HGM] translate opponent's time by time-odds factor */
13859 otime = (otime * cps->other->timeOdds) / cps->timeOdds;
13860 if (appData.debugMode) {
13861 fprintf(debugFP, "time odds: %f %f \n", cps->timeOdds, cps->other->timeOdds);
13864 if (time <= 0) time = 1;
13865 if (otime <= 0) otime = 1;
13867 snprintf(message, MSG_SIZ, "time %ld\n", time);
13868 SendToProgram(message, cps);
13870 snprintf(message, MSG_SIZ, "otim %ld\n", otime);
13871 SendToProgram(message, cps);
13875 BoolFeature(p, name, loc, cps)
13879 ChessProgramState *cps;
13882 int len = strlen(name);
13885 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
13887 sscanf(*p, "%d", &val);
13889 while (**p && **p != ' ')
13891 snprintf(buf, MSG_SIZ, "accepted %s\n", name);
13892 SendToProgram(buf, cps);
13899 IntFeature(p, name, loc, cps)
13903 ChessProgramState *cps;
13906 int len = strlen(name);
13907 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
13909 sscanf(*p, "%d", loc);
13910 while (**p && **p != ' ') (*p)++;
13911 snprintf(buf, MSG_SIZ, "accepted %s\n", name);
13912 SendToProgram(buf, cps);
13919 StringFeature(p, name, loc, cps)
13923 ChessProgramState *cps;
13926 int len = strlen(name);
13927 if (strncmp((*p), name, len) == 0
13928 && (*p)[len] == '=' && (*p)[len+1] == '\"') {
13930 sscanf(*p, "%[^\"]", loc);
13931 while (**p && **p != '\"') (*p)++;
13932 if (**p == '\"') (*p)++;
13933 snprintf(buf, MSG_SIZ, "accepted %s\n", name);
13934 SendToProgram(buf, cps);
13941 ParseOption(Option *opt, ChessProgramState *cps)
13942 // [HGM] options: process the string that defines an engine option, and determine
13943 // name, type, default value, and allowed value range
13945 char *p, *q, buf[MSG_SIZ];
13946 int n, min = (-1)<<31, max = 1<<31, def;
13948 if(p = strstr(opt->name, " -spin ")) {
13949 if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
13950 if(max < min) max = min; // enforce consistency
13951 if(def < min) def = min;
13952 if(def > max) def = max;
13957 } else if((p = strstr(opt->name, " -slider "))) {
13958 // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
13959 if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
13960 if(max < min) max = min; // enforce consistency
13961 if(def < min) def = min;
13962 if(def > max) def = max;
13966 opt->type = Spin; // Slider;
13967 } else if((p = strstr(opt->name, " -string "))) {
13968 opt->textValue = p+9;
13969 opt->type = TextBox;
13970 } else if((p = strstr(opt->name, " -file "))) {
13971 // for now -file is a synonym for -string, to already provide compatibility with future polyglots
13972 opt->textValue = p+7;
13973 opt->type = FileName; // FileName;
13974 } else if((p = strstr(opt->name, " -path "))) {
13975 // for now -file is a synonym for -string, to already provide compatibility with future polyglots
13976 opt->textValue = p+7;
13977 opt->type = PathName; // PathName;
13978 } else if(p = strstr(opt->name, " -check ")) {
13979 if(sscanf(p, " -check %d", &def) < 1) return FALSE;
13980 opt->value = (def != 0);
13981 opt->type = CheckBox;
13982 } else if(p = strstr(opt->name, " -combo ")) {
13983 opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
13984 cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
13985 if(*q == '*') cps->comboList[cps->comboCnt-1]++;
13986 opt->value = n = 0;
13987 while(q = StrStr(q, " /// ")) {
13988 n++; *q = 0; // count choices, and null-terminate each of them
13990 if(*q == '*') { // remember default, which is marked with * prefix
13994 cps->comboList[cps->comboCnt++] = q;
13996 cps->comboList[cps->comboCnt++] = NULL;
13998 opt->type = ComboBox;
13999 } else if(p = strstr(opt->name, " -button")) {
14000 opt->type = Button;
14001 } else if(p = strstr(opt->name, " -save")) {
14002 opt->type = SaveButton;
14003 } else return FALSE;
14004 *p = 0; // terminate option name
14005 // now look if the command-line options define a setting for this engine option.
14006 if(cps->optionSettings && cps->optionSettings[0])
14007 p = strstr(cps->optionSettings, opt->name); else p = NULL;
14008 if(p && (p == cps->optionSettings || p[-1] == ',')) {
14009 snprintf(buf, MSG_SIZ, "option %s", p);
14010 if(p = strstr(buf, ",")) *p = 0;
14011 if(q = strchr(buf, '=')) switch(opt->type) {
14013 for(n=0; n<opt->max; n++)
14014 if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
14017 safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
14021 opt->value = atoi(q+1);
14026 SendToProgram(buf, cps);
14032 FeatureDone(cps, val)
14033 ChessProgramState* cps;
14036 DelayedEventCallback cb = GetDelayedEvent();
14037 if ((cb == InitBackEnd3 && cps == &first) ||
14038 (cb == SettingsMenuIfReady && cps == &second) ||
14039 (cb == TwoMachinesEventIfReady && cps == &second)) {
14040 CancelDelayedEvent();
14041 ScheduleDelayedEvent(cb, val ? 1 : 3600000);
14043 cps->initDone = val;
14046 /* Parse feature command from engine */
14048 ParseFeatures(args, cps)
14050 ChessProgramState *cps;
14058 while (*p == ' ') p++;
14059 if (*p == NULLCHAR) return;
14061 if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
14062 if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
14063 if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
14064 if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
14065 if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
14066 if (BoolFeature(&p, "reuse", &val, cps)) {
14067 /* Engine can disable reuse, but can't enable it if user said no */
14068 if (!val) cps->reuse = FALSE;
14071 if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
14072 if (StringFeature(&p, "myname", &cps->tidy, cps)) {
14073 if (gameMode == TwoMachinesPlay) {
14074 DisplayTwoMachinesTitle();
14080 if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
14081 if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
14082 if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
14083 if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
14084 if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
14085 if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
14086 if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
14087 if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
14088 if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
14089 if (IntFeature(&p, "done", &val, cps)) {
14090 FeatureDone(cps, val);
14093 /* Added by Tord: */
14094 if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
14095 if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
14096 /* End of additions by Tord */
14098 /* [HGM] added features: */
14099 if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
14100 if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
14101 if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
14102 if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
14103 if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
14104 if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
14105 if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
14106 if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
14107 snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
14108 SendToProgram(buf, cps);
14111 if(cps->nrOptions >= MAX_OPTIONS) {
14113 snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
14114 DisplayError(buf, 0);
14118 /* End of additions by HGM */
14120 /* unknown feature: complain and skip */
14122 while (*q && *q != '=') q++;
14123 snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
14124 SendToProgram(buf, cps);
14130 while (*p && *p != '\"') p++;
14131 if (*p == '\"') p++;
14133 while (*p && *p != ' ') p++;
14141 PeriodicUpdatesEvent(newState)
14144 if (newState == appData.periodicUpdates)
14147 appData.periodicUpdates=newState;
14149 /* Display type changes, so update it now */
14150 // DisplayAnalysis();
14152 /* Get the ball rolling again... */
14154 AnalysisPeriodicEvent(1);
14155 StartAnalysisClock();
14160 PonderNextMoveEvent(newState)
14163 if (newState == appData.ponderNextMove) return;
14164 if (gameMode == EditPosition) EditPositionDone(TRUE);
14166 SendToProgram("hard\n", &first);
14167 if (gameMode == TwoMachinesPlay) {
14168 SendToProgram("hard\n", &second);
14171 SendToProgram("easy\n", &first);
14172 thinkOutput[0] = NULLCHAR;
14173 if (gameMode == TwoMachinesPlay) {
14174 SendToProgram("easy\n", &second);
14177 appData.ponderNextMove = newState;
14181 NewSettingEvent(option, feature, command, value)
14183 int option, value, *feature;
14187 if (gameMode == EditPosition) EditPositionDone(TRUE);
14188 snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
14189 if(feature == NULL || *feature) SendToProgram(buf, &first);
14190 if (gameMode == TwoMachinesPlay) {
14191 if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
14196 ShowThinkingEvent()
14197 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
14199 static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
14200 int newState = appData.showThinking
14201 // [HGM] thinking: other features now need thinking output as well
14202 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
14204 if (oldState == newState) return;
14205 oldState = newState;
14206 if (gameMode == EditPosition) EditPositionDone(TRUE);
14208 SendToProgram("post\n", &first);
14209 if (gameMode == TwoMachinesPlay) {
14210 SendToProgram("post\n", &second);
14213 SendToProgram("nopost\n", &first);
14214 thinkOutput[0] = NULLCHAR;
14215 if (gameMode == TwoMachinesPlay) {
14216 SendToProgram("nopost\n", &second);
14219 // appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
14223 AskQuestionEvent(title, question, replyPrefix, which)
14224 char *title; char *question; char *replyPrefix; char *which;
14226 ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
14227 if (pr == NoProc) return;
14228 AskQuestion(title, question, replyPrefix, pr);
14232 DisplayMove(moveNumber)
14235 char message[MSG_SIZ];
14237 char cpThinkOutput[MSG_SIZ];
14239 if(appData.noGUI) return; // [HGM] fast: suppress display of moves
14241 if (moveNumber == forwardMostMove - 1 ||
14242 gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14244 safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
14246 if (strchr(cpThinkOutput, '\n')) {
14247 *strchr(cpThinkOutput, '\n') = NULLCHAR;
14250 *cpThinkOutput = NULLCHAR;
14253 /* [AS] Hide thinking from human user */
14254 if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
14255 *cpThinkOutput = NULLCHAR;
14256 if( thinkOutput[0] != NULLCHAR ) {
14259 for( i=0; i<=hiddenThinkOutputState; i++ ) {
14260 cpThinkOutput[i] = '.';
14262 cpThinkOutput[i] = NULLCHAR;
14263 hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
14267 if (moveNumber == forwardMostMove - 1 &&
14268 gameInfo.resultDetails != NULL) {
14269 if (gameInfo.resultDetails[0] == NULLCHAR) {
14270 snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
14272 snprintf(res, MSG_SIZ, " {%s} %s",
14273 T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
14279 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
14280 DisplayMessage(res, cpThinkOutput);
14282 snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
14283 WhiteOnMove(moveNumber) ? " " : ".. ",
14284 parseList[moveNumber], res);
14285 DisplayMessage(message, cpThinkOutput);
14290 DisplayComment(moveNumber, text)
14294 char title[MSG_SIZ];
14295 char buf[8000]; // comment can be long!
14298 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
14299 safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
14301 snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
14302 WhiteOnMove(moveNumber) ? " " : ".. ",
14303 parseList[moveNumber]);
14305 // [HGM] PV info: display PV info together with (or as) comment
14306 if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {
14307 if(text == NULL) text = "";
14308 score = pvInfoList[moveNumber].score;
14309 snprintf(buf,sizeof(buf)/sizeof(buf[0]), "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,
14310 depth, (pvInfoList[moveNumber].time+50)/100, text);
14313 if (text != NULL && (appData.autoDisplayComment || commentUp))
14314 CommentPopUp(title, text);
14317 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
14318 * might be busy thinking or pondering. It can be omitted if your
14319 * gnuchess is configured to stop thinking immediately on any user
14320 * input. However, that gnuchess feature depends on the FIONREAD
14321 * ioctl, which does not work properly on some flavors of Unix.
14325 ChessProgramState *cps;
14328 if (!cps->useSigint) return;
14329 if (appData.noChessProgram || (cps->pr == NoProc)) return;
14330 switch (gameMode) {
14331 case MachinePlaysWhite:
14332 case MachinePlaysBlack:
14333 case TwoMachinesPlay:
14334 case IcsPlayingWhite:
14335 case IcsPlayingBlack:
14338 /* Skip if we know it isn't thinking */
14339 if (!cps->maybeThinking) return;
14340 if (appData.debugMode)
14341 fprintf(debugFP, "Interrupting %s\n", cps->which);
14342 InterruptChildProcess(cps->pr);
14343 cps->maybeThinking = FALSE;
14348 #endif /*ATTENTION*/
14354 if (whiteTimeRemaining <= 0) {
14357 if (appData.icsActive) {
14358 if (appData.autoCallFlag &&
14359 gameMode == IcsPlayingBlack && !blackFlag) {
14360 SendToICS(ics_prefix);
14361 SendToICS("flag\n");
14365 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
14367 if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
14368 if (appData.autoCallFlag) {
14369 GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
14376 if (blackTimeRemaining <= 0) {
14379 if (appData.icsActive) {
14380 if (appData.autoCallFlag &&
14381 gameMode == IcsPlayingWhite && !whiteFlag) {
14382 SendToICS(ics_prefix);
14383 SendToICS("flag\n");
14387 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
14389 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
14390 if (appData.autoCallFlag) {
14391 GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
14404 if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
14405 gameMode == PlayFromGameFile || forwardMostMove == 0) return;
14408 * add time to clocks when time control is achieved ([HGM] now also used for increment)
14410 if ( !WhiteOnMove(forwardMostMove) ) {
14411 /* White made time control */
14412 lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
14413 whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
14414 /* [HGM] time odds: correct new time quota for time odds! */
14415 / WhitePlayer()->timeOdds;
14416 lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
14418 lastBlack -= blackTimeRemaining;
14419 /* Black made time control */
14420 blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
14421 / WhitePlayer()->other->timeOdds;
14422 lastWhite = whiteTimeRemaining;
14427 DisplayBothClocks()
14429 int wom = gameMode == EditPosition ?
14430 !blackPlaysFirst : WhiteOnMove(currentMove);
14431 DisplayWhiteClock(whiteTimeRemaining, wom);
14432 DisplayBlackClock(blackTimeRemaining, !wom);
14436 /* Timekeeping seems to be a portability nightmare. I think everyone
14437 has ftime(), but I'm really not sure, so I'm including some ifdefs
14438 to use other calls if you don't. Clocks will be less accurate if
14439 you have neither ftime nor gettimeofday.
14442 /* VS 2008 requires the #include outside of the function */
14443 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
14444 #include <sys/timeb.h>
14447 /* Get the current time as a TimeMark */
14452 #if HAVE_GETTIMEOFDAY
14454 struct timeval timeVal;
14455 struct timezone timeZone;
14457 gettimeofday(&timeVal, &timeZone);
14458 tm->sec = (long) timeVal.tv_sec;
14459 tm->ms = (int) (timeVal.tv_usec / 1000L);
14461 #else /*!HAVE_GETTIMEOFDAY*/
14464 // include <sys/timeb.h> / moved to just above start of function
14465 struct timeb timeB;
14468 tm->sec = (long) timeB.time;
14469 tm->ms = (int) timeB.millitm;
14471 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
14472 tm->sec = (long) time(NULL);
14478 /* Return the difference in milliseconds between two
14479 time marks. We assume the difference will fit in a long!
14482 SubtractTimeMarks(tm2, tm1)
14483 TimeMark *tm2, *tm1;
14485 return 1000L*(tm2->sec - tm1->sec) +
14486 (long) (tm2->ms - tm1->ms);
14491 * Code to manage the game clocks.
14493 * In tournament play, black starts the clock and then white makes a move.
14494 * We give the human user a slight advantage if he is playing white---the
14495 * clocks don't run until he makes his first move, so it takes zero time.
14496 * Also, we don't account for network lag, so we could get out of sync
14497 * with GNU Chess's clock -- but then, referees are always right.
14500 static TimeMark tickStartTM;
14501 static long intendedTickLength;
14504 NextTickLength(timeRemaining)
14505 long timeRemaining;
14507 long nominalTickLength, nextTickLength;
14509 if (timeRemaining > 0L && timeRemaining <= 10000L)
14510 nominalTickLength = 100L;
14512 nominalTickLength = 1000L;
14513 nextTickLength = timeRemaining % nominalTickLength;
14514 if (nextTickLength <= 0) nextTickLength += nominalTickLength;
14516 return nextTickLength;
14519 /* Adjust clock one minute up or down */
14521 AdjustClock(Boolean which, int dir)
14523 if(which) blackTimeRemaining += 60000*dir;
14524 else whiteTimeRemaining += 60000*dir;
14525 DisplayBothClocks();
14528 /* Stop clocks and reset to a fresh time control */
14532 (void) StopClockTimer();
14533 if (appData.icsActive) {
14534 whiteTimeRemaining = blackTimeRemaining = 0;
14535 } else if (searchTime) {
14536 whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
14537 blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
14538 } else { /* [HGM] correct new time quote for time odds */
14539 whiteTC = blackTC = fullTimeControlString;
14540 whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
14541 blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
14543 if (whiteFlag || blackFlag) {
14545 whiteFlag = blackFlag = FALSE;
14547 lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
14548 DisplayBothClocks();
14551 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
14553 /* Decrement running clock by amount of time that has passed */
14557 long timeRemaining;
14558 long lastTickLength, fudge;
14561 if (!appData.clockMode) return;
14562 if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
14566 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
14568 /* Fudge if we woke up a little too soon */
14569 fudge = intendedTickLength - lastTickLength;
14570 if (fudge < 0 || fudge > FUDGE) fudge = 0;
14572 if (WhiteOnMove(forwardMostMove)) {
14573 if(whiteNPS >= 0) lastTickLength = 0;
14574 timeRemaining = whiteTimeRemaining -= lastTickLength;
14575 if(timeRemaining < 0 && !appData.icsActive) {
14576 GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
14577 if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
14578 whiteStartMove = forwardMostMove; whiteTC = nextSession;
14579 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
14582 DisplayWhiteClock(whiteTimeRemaining - fudge,
14583 WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
14585 if(blackNPS >= 0) lastTickLength = 0;
14586 timeRemaining = blackTimeRemaining -= lastTickLength;
14587 if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
14588 GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
14590 blackStartMove = forwardMostMove;
14591 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
14594 DisplayBlackClock(blackTimeRemaining - fudge,
14595 !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
14597 if (CheckFlags()) return;
14600 intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
14601 StartClockTimer(intendedTickLength);
14603 /* if the time remaining has fallen below the alarm threshold, sound the
14604 * alarm. if the alarm has sounded and (due to a takeback or time control
14605 * with increment) the time remaining has increased to a level above the
14606 * threshold, reset the alarm so it can sound again.
14609 if (appData.icsActive && appData.icsAlarm) {
14611 /* make sure we are dealing with the user's clock */
14612 if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
14613 ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
14616 if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
14617 alarmSounded = FALSE;
14618 } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
14620 alarmSounded = TRUE;
14626 /* A player has just moved, so stop the previously running
14627 clock and (if in clock mode) start the other one.
14628 We redisplay both clocks in case we're in ICS mode, because
14629 ICS gives us an update to both clocks after every move.
14630 Note that this routine is called *after* forwardMostMove
14631 is updated, so the last fractional tick must be subtracted
14632 from the color that is *not* on move now.
14635 SwitchClocks(int newMoveNr)
14637 long lastTickLength;
14639 int flagged = FALSE;
14643 if (StopClockTimer() && appData.clockMode) {
14644 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
14645 if (!WhiteOnMove(forwardMostMove)) {
14646 if(blackNPS >= 0) lastTickLength = 0;
14647 blackTimeRemaining -= lastTickLength;
14648 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
14649 // if(pvInfoList[forwardMostMove].time == -1)
14650 pvInfoList[forwardMostMove].time = // use GUI time
14651 (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
14653 if(whiteNPS >= 0) lastTickLength = 0;
14654 whiteTimeRemaining -= lastTickLength;
14655 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
14656 // if(pvInfoList[forwardMostMove].time == -1)
14657 pvInfoList[forwardMostMove].time =
14658 (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
14660 flagged = CheckFlags();
14662 forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
14663 CheckTimeControl();
14665 if (flagged || !appData.clockMode) return;
14667 switch (gameMode) {
14668 case MachinePlaysBlack:
14669 case MachinePlaysWhite:
14670 case BeginningOfGame:
14671 if (pausing) return;
14675 case PlayFromGameFile:
14683 if (searchTime) { // [HGM] st: set clock of player that has to move to max time
14684 if(WhiteOnMove(forwardMostMove))
14685 whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
14686 else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
14690 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
14691 whiteTimeRemaining : blackTimeRemaining);
14692 StartClockTimer(intendedTickLength);
14696 /* Stop both clocks */
14700 long lastTickLength;
14703 if (!StopClockTimer()) return;
14704 if (!appData.clockMode) return;
14708 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
14709 if (WhiteOnMove(forwardMostMove)) {
14710 if(whiteNPS >= 0) lastTickLength = 0;
14711 whiteTimeRemaining -= lastTickLength;
14712 DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
14714 if(blackNPS >= 0) lastTickLength = 0;
14715 blackTimeRemaining -= lastTickLength;
14716 DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
14721 /* Start clock of player on move. Time may have been reset, so
14722 if clock is already running, stop and restart it. */
14726 (void) StopClockTimer(); /* in case it was running already */
14727 DisplayBothClocks();
14728 if (CheckFlags()) return;
14730 if (!appData.clockMode) return;
14731 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
14733 GetTimeMark(&tickStartTM);
14734 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
14735 whiteTimeRemaining : blackTimeRemaining);
14737 /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
14738 whiteNPS = blackNPS = -1;
14739 if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
14740 || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
14741 whiteNPS = first.nps;
14742 if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
14743 || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
14744 blackNPS = first.nps;
14745 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
14746 whiteNPS = second.nps;
14747 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
14748 blackNPS = second.nps;
14749 if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
14751 StartClockTimer(intendedTickLength);
14758 long second, minute, hour, day;
14760 static char buf[32];
14762 if (ms > 0 && ms <= 9900) {
14763 /* convert milliseconds to tenths, rounding up */
14764 double tenths = floor( ((double)(ms + 99L)) / 100.00 );
14766 snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
14770 /* convert milliseconds to seconds, rounding up */
14771 /* use floating point to avoid strangeness of integer division
14772 with negative dividends on many machines */
14773 second = (long) floor(((double) (ms + 999L)) / 1000.0);
14780 day = second / (60 * 60 * 24);
14781 second = second % (60 * 60 * 24);
14782 hour = second / (60 * 60);
14783 second = second % (60 * 60);
14784 minute = second / 60;
14785 second = second % 60;
14788 snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
14789 sign, day, hour, minute, second);
14791 snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
14793 snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
14800 * This is necessary because some C libraries aren't ANSI C compliant yet.
14803 StrStr(string, match)
14804 char *string, *match;
14808 length = strlen(match);
14810 for (i = strlen(string) - length; i >= 0; i--, string++)
14811 if (!strncmp(match, string, length))
14818 StrCaseStr(string, match)
14819 char *string, *match;
14823 length = strlen(match);
14825 for (i = strlen(string) - length; i >= 0; i--, string++) {
14826 for (j = 0; j < length; j++) {
14827 if (ToLower(match[j]) != ToLower(string[j]))
14830 if (j == length) return string;
14844 c1 = ToLower(*s1++);
14845 c2 = ToLower(*s2++);
14846 if (c1 > c2) return 1;
14847 if (c1 < c2) return -1;
14848 if (c1 == NULLCHAR) return 0;
14857 return isupper(c) ? tolower(c) : c;
14865 return islower(c) ? toupper(c) : c;
14867 #endif /* !_amigados */
14875 if ((ret = (char *) malloc(strlen(s) + 1)))
14877 safeStrCpy(ret, s, strlen(s)+1);
14883 StrSavePtr(s, savePtr)
14884 char *s, **savePtr;
14889 if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
14890 safeStrCpy(*savePtr, s, strlen(s)+1);
14902 clock = time((time_t *)NULL);
14903 tm = localtime(&clock);
14904 snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
14905 tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
14906 return StrSave(buf);
14911 PositionToFEN(move, overrideCastling)
14913 char *overrideCastling;
14915 int i, j, fromX, fromY, toX, toY;
14922 whiteToPlay = (gameMode == EditPosition) ?
14923 !blackPlaysFirst : (move % 2 == 0);
14926 /* Piece placement data */
14927 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
14929 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
14930 if (boards[move][i][j] == EmptySquare) {
14932 } else { ChessSquare piece = boards[move][i][j];
14933 if (emptycount > 0) {
14934 if(emptycount<10) /* [HGM] can be >= 10 */
14935 *p++ = '0' + emptycount;
14936 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
14939 if(PieceToChar(piece) == '+') {
14940 /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
14942 piece = (ChessSquare)(DEMOTED piece);
14944 *p++ = PieceToChar(piece);
14946 /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
14947 p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
14952 if (emptycount > 0) {
14953 if(emptycount<10) /* [HGM] can be >= 10 */
14954 *p++ = '0' + emptycount;
14955 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
14962 /* [HGM] print Crazyhouse or Shogi holdings */
14963 if( gameInfo.holdingsWidth ) {
14964 *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
14966 for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
14967 piece = boards[move][i][BOARD_WIDTH-1];
14968 if( piece != EmptySquare )
14969 for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
14970 *p++ = PieceToChar(piece);
14972 for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
14973 piece = boards[move][BOARD_HEIGHT-i-1][0];
14974 if( piece != EmptySquare )
14975 for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
14976 *p++ = PieceToChar(piece);
14979 if( q == p ) *p++ = '-';
14985 *p++ = whiteToPlay ? 'w' : 'b';
14988 if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
14989 while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
14991 if(nrCastlingRights) {
14993 if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
14994 /* [HGM] write directly from rights */
14995 if(boards[move][CASTLING][2] != NoRights &&
14996 boards[move][CASTLING][0] != NoRights )
14997 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
14998 if(boards[move][CASTLING][2] != NoRights &&
14999 boards[move][CASTLING][1] != NoRights )
15000 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
15001 if(boards[move][CASTLING][5] != NoRights &&
15002 boards[move][CASTLING][3] != NoRights )
15003 *p++ = boards[move][CASTLING][3] + AAA;
15004 if(boards[move][CASTLING][5] != NoRights &&
15005 boards[move][CASTLING][4] != NoRights )
15006 *p++ = boards[move][CASTLING][4] + AAA;
15009 /* [HGM] write true castling rights */
15010 if( nrCastlingRights == 6 ) {
15011 if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
15012 boards[move][CASTLING][2] != NoRights ) *p++ = 'K';
15013 if(boards[move][CASTLING][1] == BOARD_LEFT &&
15014 boards[move][CASTLING][2] != NoRights ) *p++ = 'Q';
15015 if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
15016 boards[move][CASTLING][5] != NoRights ) *p++ = 'k';
15017 if(boards[move][CASTLING][4] == BOARD_LEFT &&
15018 boards[move][CASTLING][5] != NoRights ) *p++ = 'q';
15021 if (q == p) *p++ = '-'; /* No castling rights */
15025 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
15026 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
15027 /* En passant target square */
15028 if (move > backwardMostMove) {
15029 fromX = moveList[move - 1][0] - AAA;
15030 fromY = moveList[move - 1][1] - ONE;
15031 toX = moveList[move - 1][2] - AAA;
15032 toY = moveList[move - 1][3] - ONE;
15033 if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
15034 toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
15035 boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
15037 /* 2-square pawn move just happened */
15039 *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
15043 } else if(move == backwardMostMove) {
15044 // [HGM] perhaps we should always do it like this, and forget the above?
15045 if((signed char)boards[move][EP_STATUS] >= 0) {
15046 *p++ = boards[move][EP_STATUS] + AAA;
15047 *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
15058 /* [HGM] find reversible plies */
15059 { int i = 0, j=move;
15061 if (appData.debugMode) { int k;
15062 fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
15063 for(k=backwardMostMove; k<=forwardMostMove; k++)
15064 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
15068 while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
15069 if( j == backwardMostMove ) i += initialRulePlies;
15070 sprintf(p, "%d ", i);
15071 p += i>=100 ? 4 : i >= 10 ? 3 : 2;
15073 /* Fullmove number */
15074 sprintf(p, "%d", (move / 2) + 1);
15076 return StrSave(buf);
15080 ParseFEN(board, blackPlaysFirst, fen)
15082 int *blackPlaysFirst;
15092 /* [HGM] by default clear Crazyhouse holdings, if present */
15093 if(gameInfo.holdingsWidth) {
15094 for(i=0; i<BOARD_HEIGHT; i++) {
15095 board[i][0] = EmptySquare; /* black holdings */
15096 board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
15097 board[i][1] = (ChessSquare) 0; /* black counts */
15098 board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
15102 /* Piece placement data */
15103 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
15106 if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
15107 if (*p == '/') p++;
15108 emptycount = gameInfo.boardWidth - j;
15109 while (emptycount--)
15110 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
15112 #if(BOARD_FILES >= 10)
15113 } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
15114 p++; emptycount=10;
15115 if (j + emptycount > gameInfo.boardWidth) return FALSE;
15116 while (emptycount--)
15117 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
15119 } else if (isdigit(*p)) {
15120 emptycount = *p++ - '0';
15121 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
15122 if (j + emptycount > gameInfo.boardWidth) return FALSE;
15123 while (emptycount--)
15124 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
15125 } else if (*p == '+' || isalpha(*p)) {
15126 if (j >= gameInfo.boardWidth) return FALSE;
15128 piece = CharToPiece(*++p);
15129 if(piece == EmptySquare) return FALSE; /* unknown piece */
15130 piece = (ChessSquare) (PROMOTED piece ); p++;
15131 if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
15132 } else piece = CharToPiece(*p++);
15134 if(piece==EmptySquare) return FALSE; /* unknown piece */
15135 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
15136 piece = (ChessSquare) (PROMOTED piece);
15137 if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
15140 board[i][(j++)+gameInfo.holdingsWidth] = piece;
15146 while (*p == '/' || *p == ' ') p++;
15148 /* [HGM] look for Crazyhouse holdings here */
15149 while(*p==' ') p++;
15150 if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
15152 if(*p == '-' ) p++; /* empty holdings */ else {
15153 if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
15154 /* if we would allow FEN reading to set board size, we would */
15155 /* have to add holdings and shift the board read so far here */
15156 while( (piece = CharToPiece(*p) ) != EmptySquare ) {
15158 if((int) piece >= (int) BlackPawn ) {
15159 i = (int)piece - (int)BlackPawn;
15160 i = PieceToNumber((ChessSquare)i);
15161 if( i >= gameInfo.holdingsSize ) return FALSE;
15162 board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
15163 board[BOARD_HEIGHT-1-i][1]++; /* black counts */
15165 i = (int)piece - (int)WhitePawn;
15166 i = PieceToNumber((ChessSquare)i);
15167 if( i >= gameInfo.holdingsSize ) return FALSE;
15168 board[i][BOARD_WIDTH-1] = piece; /* white holdings */
15169 board[i][BOARD_WIDTH-2]++; /* black holdings */
15176 while(*p == ' ') p++;
15180 if(appData.colorNickNames) {
15181 if( c == appData.colorNickNames[0] ) c = 'w'; else
15182 if( c == appData.colorNickNames[1] ) c = 'b';
15186 *blackPlaysFirst = FALSE;
15189 *blackPlaysFirst = TRUE;
15195 /* [HGM] We NO LONGER ignore the rest of the FEN notation */
15196 /* return the extra info in global variiables */
15198 /* set defaults in case FEN is incomplete */
15199 board[EP_STATUS] = EP_UNKNOWN;
15200 for(i=0; i<nrCastlingRights; i++ ) {
15201 board[CASTLING][i] =
15202 gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
15203 } /* assume possible unless obviously impossible */
15204 if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
15205 if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
15206 if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
15207 && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
15208 if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
15209 if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
15210 if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
15211 && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
15214 while(*p==' ') p++;
15215 if(nrCastlingRights) {
15216 if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
15217 /* castling indicator present, so default becomes no castlings */
15218 for(i=0; i<nrCastlingRights; i++ ) {
15219 board[CASTLING][i] = NoRights;
15222 while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
15223 (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
15224 ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
15225 ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth) ) {
15226 char c = *p++; int whiteKingFile=NoRights, blackKingFile=NoRights;
15228 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
15229 if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
15230 if(board[0 ][i] == WhiteKing) whiteKingFile = i;
15232 if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
15233 whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
15234 if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
15235 && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
15236 if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
15237 && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
15240 for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
15241 board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
15242 board[CASTLING][2] = whiteKingFile;
15245 for(i=BOARD_LEFT; i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
15246 board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
15247 board[CASTLING][2] = whiteKingFile;
15250 for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
15251 board[CASTLING][3] = i != blackKingFile ? i : NoRights;
15252 board[CASTLING][5] = blackKingFile;
15255 for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
15256 board[CASTLING][4] = i != blackKingFile ? i : NoRights;
15257 board[CASTLING][5] = blackKingFile;
15260 default: /* FRC castlings */
15261 if(c >= 'a') { /* black rights */
15262 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
15263 if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
15264 if(i == BOARD_RGHT) break;
15265 board[CASTLING][5] = i;
15267 if(board[BOARD_HEIGHT-1][c] < BlackPawn ||
15268 board[BOARD_HEIGHT-1][c] >= BlackKing ) break;
15270 board[CASTLING][3] = c;
15272 board[CASTLING][4] = c;
15273 } else { /* white rights */
15274 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
15275 if(board[0][i] == WhiteKing) break;
15276 if(i == BOARD_RGHT) break;
15277 board[CASTLING][2] = i;
15278 c -= AAA - 'a' + 'A';
15279 if(board[0][c] >= WhiteKing) break;
15281 board[CASTLING][0] = c;
15283 board[CASTLING][1] = c;
15287 for(i=0; i<nrCastlingRights; i++)
15288 if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
15289 if (appData.debugMode) {
15290 fprintf(debugFP, "FEN castling rights:");
15291 for(i=0; i<nrCastlingRights; i++)
15292 fprintf(debugFP, " %d", board[CASTLING][i]);
15293 fprintf(debugFP, "\n");
15296 while(*p==' ') p++;
15299 /* read e.p. field in games that know e.p. capture */
15300 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
15301 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
15303 p++; board[EP_STATUS] = EP_NONE;
15305 char c = *p++ - AAA;
15307 if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
15308 if(*p >= '0' && *p <='9') p++;
15309 board[EP_STATUS] = c;
15314 if(sscanf(p, "%d", &i) == 1) {
15315 FENrulePlies = i; /* 50-move ply counter */
15316 /* (The move number is still ignored) */
15323 EditPositionPasteFEN(char *fen)
15326 Board initial_position;
15328 if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
15329 DisplayError(_("Bad FEN position in clipboard"), 0);
15332 int savedBlackPlaysFirst = blackPlaysFirst;
15333 EditPositionEvent();
15334 blackPlaysFirst = savedBlackPlaysFirst;
15335 CopyBoard(boards[0], initial_position);
15336 initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
15337 EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
15338 DisplayBothClocks();
15339 DrawPosition(FALSE, boards[currentMove]);
15344 static char cseq[12] = "\\ ";
15346 Boolean set_cont_sequence(char *new_seq)
15351 // handle bad attempts to set the sequence
15353 return 0; // acceptable error - no debug
15355 len = strlen(new_seq);
15356 ret = (len > 0) && (len < sizeof(cseq));
15358 safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
15359 else if (appData.debugMode)
15360 fprintf(debugFP, "Invalid continuation sequence \"%s\" (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
15365 reformat a source message so words don't cross the width boundary. internal
15366 newlines are not removed. returns the wrapped size (no null character unless
15367 included in source message). If dest is NULL, only calculate the size required
15368 for the dest buffer. lp argument indicats line position upon entry, and it's
15369 passed back upon exit.
15371 int wrap(char *dest, char *src, int count, int width, int *lp)
15373 int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
15375 cseq_len = strlen(cseq);
15376 old_line = line = *lp;
15377 ansi = len = clen = 0;
15379 for (i=0; i < count; i++)
15381 if (src[i] == '\033')
15384 // if we hit the width, back up
15385 if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
15387 // store i & len in case the word is too long
15388 old_i = i, old_len = len;
15390 // find the end of the last word
15391 while (i && src[i] != ' ' && src[i] != '\n')
15397 // word too long? restore i & len before splitting it
15398 if ((old_i-i+clen) >= width)
15405 if (i && src[i-1] == ' ')
15408 if (src[i] != ' ' && src[i] != '\n')
15415 // now append the newline and continuation sequence
15420 strncpy(dest+len, cseq, cseq_len);
15428 dest[len] = src[i];
15432 if (src[i] == '\n')
15437 if (dest && appData.debugMode)
15439 fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
15440 count, width, line, len, *lp);
15441 show_bytes(debugFP, src, count);
15442 fprintf(debugFP, "\ndest: ");
15443 show_bytes(debugFP, dest, len);
15444 fprintf(debugFP, "\n");
15446 *lp = dest ? line : old_line;
15451 // [HGM] vari: routines for shelving variations
15454 PushTail(int firstMove, int lastMove)
15456 int i, j, nrMoves = lastMove - firstMove;
15458 if(appData.icsActive) { // only in local mode
15459 forwardMostMove = currentMove; // mimic old ICS behavior
15462 if(storedGames >= MAX_VARIATIONS-1) return;
15464 // push current tail of game on stack
15465 savedResult[storedGames] = gameInfo.result;
15466 savedDetails[storedGames] = gameInfo.resultDetails;
15467 gameInfo.resultDetails = NULL;
15468 savedFirst[storedGames] = firstMove;
15469 savedLast [storedGames] = lastMove;
15470 savedFramePtr[storedGames] = framePtr;
15471 framePtr -= nrMoves; // reserve space for the boards
15472 for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
15473 CopyBoard(boards[framePtr+i], boards[firstMove+i]);
15474 for(j=0; j<MOVE_LEN; j++)
15475 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
15476 for(j=0; j<2*MOVE_LEN; j++)
15477 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
15478 timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
15479 timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
15480 pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
15481 pvInfoList[firstMove+i-1].depth = 0;
15482 commentList[framePtr+i] = commentList[firstMove+i];
15483 commentList[firstMove+i] = NULL;
15487 forwardMostMove = firstMove; // truncate game so we can start variation
15488 if(storedGames == 1) GreyRevert(FALSE);
15492 PopTail(Boolean annotate)
15495 char buf[8000], moveBuf[20];
15497 if(appData.icsActive) return FALSE; // only in local mode
15498 if(!storedGames) return FALSE; // sanity
15499 CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
15502 ToNrEvent(savedFirst[storedGames]); // sets currentMove
15503 nrMoves = savedLast[storedGames] - currentMove;
15506 if(!WhiteOnMove(currentMove))
15507 snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
15508 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
15509 for(i=currentMove; i<forwardMostMove; i++) {
15511 snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
15512 else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
15513 strcat(buf, moveBuf);
15514 if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
15515 if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
15519 for(i=1; i<=nrMoves; i++) { // copy last variation back
15520 CopyBoard(boards[currentMove+i], boards[framePtr+i]);
15521 for(j=0; j<MOVE_LEN; j++)
15522 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
15523 for(j=0; j<2*MOVE_LEN; j++)
15524 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
15525 timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
15526 timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
15527 pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
15528 if(commentList[currentMove+i]) free(commentList[currentMove+i]);
15529 commentList[currentMove+i] = commentList[framePtr+i];
15530 commentList[framePtr+i] = NULL;
15532 if(annotate) AppendComment(currentMove+1, buf, FALSE);
15533 framePtr = savedFramePtr[storedGames];
15534 gameInfo.result = savedResult[storedGames];
15535 if(gameInfo.resultDetails != NULL) {
15536 free(gameInfo.resultDetails);
15538 gameInfo.resultDetails = savedDetails[storedGames];
15539 forwardMostMove = currentMove + nrMoves;
15540 if(storedGames == 0) GreyRevert(TRUE);
15546 { // remove all shelved variations
15548 for(i=0; i<storedGames; i++) {
15549 if(savedDetails[i])
15550 free(savedDetails[i]);
15551 savedDetails[i] = NULL;
15553 for(i=framePtr; i<MAX_MOVES; i++) {
15554 if(commentList[i]) free(commentList[i]);
15555 commentList[i] = NULL;
15557 framePtr = MAX_MOVES-1;
15562 LoadVariation(int index, char *text)
15563 { // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
15564 char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
15565 int level = 0, move;
15567 if(gameMode != EditGame && gameMode != AnalyzeMode) return;
15568 // first find outermost bracketing variation
15569 while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
15570 if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
15571 if(*p == '{') wait = '}'; else
15572 if(*p == '[') wait = ']'; else
15573 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
15574 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
15576 if(*p == wait) wait = NULLCHAR; // closing ]} found
15579 if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
15580 if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
15581 end[1] = NULLCHAR; // clip off comment beyond variation
15582 ToNrEvent(currentMove-1);
15583 PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
15584 // kludge: use ParsePV() to append variation to game
15585 move = currentMove;
15586 ParsePV(start, TRUE);
15587 forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
15588 ClearPremoveHighlights();
15590 ToNrEvent(currentMove+1);