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(toY != BOARD_HEIGHT-1 && toY != 0) 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(PieceToChar(promoSweep) == '.' || PieceToChar(promoSweep) == '~' || promoSweep == pawn ||
4834 appData.testLegality && (promoSweep == king ||
4835 gameInfo.variant == VariantShogi && promoSweep != PROMOTED last && last != PROMOTED promoSweep && last != promoSweep));
4836 boards[currentMove][toY][toX] = promoSweep;
4837 DrawPosition(FALSE, boards[currentMove]);
4838 boards[currentMove][toY][toX] = piece;
4841 static int lastX, lastY;
4843 void PromoScroll(int x, int y)
4846 if(abs(x - lastX) < 7 && abs(y - lastY) < 7) return;
4847 if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
4849 lastX = x; lastY = y;
4851 if(promoSweep == EmptySquare) return;
4858 ChessSquare piece = boards[currentMove][toY][toX];
4861 if(pieceSweep == EmptySquare) pieceSweep = WhitePawn; // wrap
4862 if((int)pieceSweep == -1) pieceSweep = BlackKing;
4863 if(!step) step = -1;
4864 } while(PieceToChar(pieceSweep) == '.');
4865 boards[currentMove][toY][toX] = pieceSweep;
4866 DrawPosition(FALSE, boards[currentMove]);
4867 boards[currentMove][toY][toX] = piece;
4869 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
4871 AlphaRank(char *move, int n)
4873 // char *p = move, c; int x, y;
4875 if (appData.debugMode) {
4876 fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
4880 move[2]>='0' && move[2]<='9' &&
4881 move[3]>='a' && move[3]<='x' ) {
4883 move[2] = BOARD_RGHT -1 - (move[2]-'1') + AAA;
4884 move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4886 if(move[0]>='0' && move[0]<='9' &&
4887 move[1]>='a' && move[1]<='x' &&
4888 move[2]>='0' && move[2]<='9' &&
4889 move[3]>='a' && move[3]<='x' ) {
4890 /* input move, Shogi -> normal */
4891 move[0] = BOARD_RGHT -1 - (move[0]-'1') + AAA;
4892 move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
4893 move[2] = BOARD_RGHT -1 - (move[2]-'1') + AAA;
4894 move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4897 move[3]>='0' && move[3]<='9' &&
4898 move[2]>='a' && move[2]<='x' ) {
4900 move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4901 move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4904 move[0]>='a' && move[0]<='x' &&
4905 move[3]>='0' && move[3]<='9' &&
4906 move[2]>='a' && move[2]<='x' ) {
4907 /* output move, normal -> Shogi */
4908 move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
4909 move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
4910 move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4911 move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4912 if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
4914 if (appData.debugMode) {
4915 fprintf(debugFP, " out = '%s'\n", move);
4919 char yy_textstr[8000];
4921 /* Parser for moves from gnuchess, ICS, or user typein box */
4923 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
4926 ChessMove *moveType;
4927 int *fromX, *fromY, *toX, *toY;
4930 *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
4932 switch (*moveType) {
4933 case WhitePromotion:
4934 case BlackPromotion:
4935 case WhiteNonPromotion:
4936 case BlackNonPromotion:
4938 case WhiteCapturesEnPassant:
4939 case BlackCapturesEnPassant:
4940 case WhiteKingSideCastle:
4941 case WhiteQueenSideCastle:
4942 case BlackKingSideCastle:
4943 case BlackQueenSideCastle:
4944 case WhiteKingSideCastleWild:
4945 case WhiteQueenSideCastleWild:
4946 case BlackKingSideCastleWild:
4947 case BlackQueenSideCastleWild:
4948 /* Code added by Tord: */
4949 case WhiteHSideCastleFR:
4950 case WhiteASideCastleFR:
4951 case BlackHSideCastleFR:
4952 case BlackASideCastleFR:
4953 /* End of code added by Tord */
4954 case IllegalMove: /* bug or odd chess variant */
4955 *fromX = currentMoveString[0] - AAA;
4956 *fromY = currentMoveString[1] - ONE;
4957 *toX = currentMoveString[2] - AAA;
4958 *toY = currentMoveString[3] - ONE;
4959 *promoChar = currentMoveString[4];
4960 if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
4961 *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
4962 if (appData.debugMode) {
4963 fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
4965 *fromX = *fromY = *toX = *toY = 0;
4968 if (appData.testLegality) {
4969 return (*moveType != IllegalMove);
4971 return !(*fromX == *toX && *fromY == *toY) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
4972 WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
4977 *fromX = *moveType == WhiteDrop ?
4978 (int) CharToPiece(ToUpper(currentMoveString[0])) :
4979 (int) CharToPiece(ToLower(currentMoveString[0]));
4981 *toX = currentMoveString[2] - AAA;
4982 *toY = currentMoveString[3] - ONE;
4983 *promoChar = NULLCHAR;
4987 case ImpossibleMove:
4997 if (appData.debugMode) {
4998 fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
5001 *fromX = *fromY = *toX = *toY = 0;
5002 *promoChar = NULLCHAR;
5009 ParsePV(char *pv, Boolean storeComments)
5010 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
5011 int fromX, fromY, toX, toY; char promoChar;
5016 endPV = forwardMostMove;
5018 while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
5019 if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
5020 valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
5021 if(appData.debugMode){
5022 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);
5024 if(!valid && nr == 0 &&
5025 ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
5026 nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
5027 // Hande case where played move is different from leading PV move
5028 CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
5029 CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
5030 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
5031 if(!CompareBoards(boards[endPV], boards[endPV+2])) {
5032 endPV += 2; // if position different, keep this
5033 moveList[endPV-1][0] = fromX + AAA;
5034 moveList[endPV-1][1] = fromY + ONE;
5035 moveList[endPV-1][2] = toX + AAA;
5036 moveList[endPV-1][3] = toY + ONE;
5037 parseList[endPV-1][0] = NULLCHAR;
5038 safeStrCpy(moveList[endPV-2], "_0_0", sizeof(moveList[endPV-2])/sizeof(moveList[endPV-2][0])); // suppress premove highlight on takeback move
5041 pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
5042 if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
5043 if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
5044 if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
5045 valid++; // allow comments in PV
5049 if(endPV+1 > framePtr) break; // no space, truncate
5052 CopyBoard(boards[endPV], boards[endPV-1]);
5053 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
5054 moveList[endPV-1][0] = fromX + AAA;
5055 moveList[endPV-1][1] = fromY + ONE;
5056 moveList[endPV-1][2] = toX + AAA;
5057 moveList[endPV-1][3] = toY + ONE;
5058 moveList[endPV-1][4] = promoChar;
5059 moveList[endPV-1][5] = NULLCHAR;
5060 strncat(moveList[endPV-1], "\n", MOVE_LEN);
5062 CoordsToAlgebraic(boards[endPV - 1],
5063 PosFlags(endPV - 1),
5064 fromY, fromX, toY, toX, promoChar,
5065 parseList[endPV - 1]);
5067 parseList[endPV-1][0] = NULLCHAR;
5069 currentMove = endPV;
5070 if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5071 SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5072 moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5073 DrawPosition(TRUE, boards[currentMove]);
5077 LoadMultiPV(int x, int y, char *buf, int index, int *start, int *end)
5082 if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
5083 lastX = x; lastY = y;
5084 while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
5086 while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
5087 if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
5089 do{ while(buf[index] && buf[index] != '\n') index++;
5090 } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
5092 ParsePV(buf+startPV, FALSE);
5093 *start = startPV; *end = index-1;
5098 LoadPV(int x, int y)
5099 { // called on right mouse click to load PV
5100 int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
5101 lastX = x; lastY = y;
5102 ParsePV(lastPV[which], FALSE); // load the PV of the thinking engine in the boards array.
5109 if(endPV < 0) return;
5111 currentMove = forwardMostMove;
5112 ClearPremoveHighlights();
5113 DrawPosition(TRUE, boards[currentMove]);
5117 MovePV(int x, int y, int h)
5118 { // step through PV based on mouse coordinates (called on mouse move)
5119 int margin = h>>3, step = 0, dist;
5121 // we must somehow check if right button is still down (might be released off board!)
5122 if(abs(x - lastX) < 7 && abs(y - lastY) < 7) return;
5123 if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5125 lastX = x; lastY = y;
5127 if(pieceSweep != EmptySquare) { NextPiece(step); return; }
5128 if(endPV < 0) return;
5129 if(y < margin) step = 1; else
5130 if(y > h - margin) step = -1;
5131 if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
5132 currentMove += step;
5133 if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5134 SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5135 moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5136 DrawPosition(FALSE, boards[currentMove]);
5140 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
5141 // All positions will have equal probability, but the current method will not provide a unique
5142 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
5148 int piecesLeft[(int)BlackPawn];
5149 int seed, nrOfShuffles;
5151 void GetPositionNumber()
5152 { // sets global variable seed
5155 seed = appData.defaultFrcPosition;
5156 if(seed < 0) { // randomize based on time for negative FRC position numbers
5157 for(i=0; i<50; i++) seed += random();
5158 seed = random() ^ random() >> 8 ^ random() << 8;
5159 if(seed<0) seed = -seed;
5163 int put(Board board, int pieceType, int rank, int n, int shade)
5164 // put the piece on the (n-1)-th empty squares of the given shade
5168 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5169 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5170 board[rank][i] = (ChessSquare) pieceType;
5171 squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5173 piecesLeft[pieceType]--;
5181 void AddOnePiece(Board board, int pieceType, int rank, int shade)
5182 // calculate where the next piece goes, (any empty square), and put it there
5186 i = seed % squaresLeft[shade];
5187 nrOfShuffles *= squaresLeft[shade];
5188 seed /= squaresLeft[shade];
5189 put(board, pieceType, rank, i, shade);
5192 void AddTwoPieces(Board board, int pieceType, int rank)
5193 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5195 int i, n=squaresLeft[ANY], j=n-1, k;
5197 k = n*(n-1)/2; // nr of possibilities, not counting permutations
5198 i = seed % k; // pick one
5201 while(i >= j) i -= j--;
5202 j = n - 1 - j; i += j;
5203 put(board, pieceType, rank, j, ANY);
5204 put(board, pieceType, rank, i, ANY);
5207 void SetUpShuffle(Board board, int number)
5211 GetPositionNumber(); nrOfShuffles = 1;
5213 squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5214 squaresLeft[ANY] = BOARD_RGHT - BOARD_LEFT;
5215 squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5217 for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5219 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5220 p = (int) board[0][i];
5221 if(p < (int) BlackPawn) piecesLeft[p] ++;
5222 board[0][i] = EmptySquare;
5225 if(PosFlags(0) & F_ALL_CASTLE_OK) {
5226 // shuffles restricted to allow normal castling put KRR first
5227 if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5228 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5229 else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5230 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5231 if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5232 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5233 if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5234 put(board, WhiteRook, 0, 0, ANY);
5235 // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5238 if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5239 // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5240 for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5241 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5242 while(piecesLeft[p] >= 2) {
5243 AddOnePiece(board, p, 0, LITE);
5244 AddOnePiece(board, p, 0, DARK);
5246 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5249 for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5250 // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5251 // but we leave King and Rooks for last, to possibly obey FRC restriction
5252 if(p == (int)WhiteRook) continue;
5253 while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5254 if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY); // add the odd piece
5257 // now everything is placed, except perhaps King (Unicorn) and Rooks
5259 if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5260 // Last King gets castling rights
5261 while(piecesLeft[(int)WhiteUnicorn]) {
5262 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5263 initialRights[2] = initialRights[5] = board[CASTLING][2] = board[CASTLING][5] = i;
5266 while(piecesLeft[(int)WhiteKing]) {
5267 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5268 initialRights[2] = initialRights[5] = board[CASTLING][2] = board[CASTLING][5] = i;
5273 while(piecesLeft[(int)WhiteKing]) AddOnePiece(board, WhiteKing, 0, ANY);
5274 while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
5277 // Only Rooks can be left; simply place them all
5278 while(piecesLeft[(int)WhiteRook]) {
5279 i = put(board, WhiteRook, 0, 0, ANY);
5280 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
5283 initialRights[1] = initialRights[4] = board[CASTLING][1] = board[CASTLING][4] = i;
5285 initialRights[0] = initialRights[3] = board[CASTLING][0] = board[CASTLING][3] = i;
5288 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
5289 board[BOARD_HEIGHT-1][i] = (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
5292 if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
5295 int SetCharTable( char *table, const char * map )
5296 /* [HGM] moved here from winboard.c because of its general usefulness */
5297 /* Basically a safe strcpy that uses the last character as King */
5299 int result = FALSE; int NrPieces;
5301 if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
5302 && NrPieces >= 12 && !(NrPieces&1)) {
5303 int i; /* [HGM] Accept even length from 12 to 34 */
5305 for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
5306 for( i=0; i<NrPieces/2-1; i++ ) {
5308 table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
5310 table[(int) WhiteKing] = map[NrPieces/2-1];
5311 table[(int) BlackKing] = map[NrPieces-1];
5319 void Prelude(Board board)
5320 { // [HGM] superchess: random selection of exo-pieces
5321 int i, j, k; ChessSquare p;
5322 static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
5324 GetPositionNumber(); // use FRC position number
5326 if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
5327 SetCharTable(pieceToChar, appData.pieceToCharTable);
5328 for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
5329 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
5332 j = seed%4; seed /= 4;
5333 p = board[0][BOARD_LEFT+j]; board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5334 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
5335 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
5336 j = seed%3 + (seed%3 >= j); seed /= 3;
5337 p = board[0][BOARD_LEFT+j]; board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5338 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
5339 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
5340 j = seed%3; seed /= 3;
5341 p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5342 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
5343 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
5344 j = seed%2 + (seed%2 >= j); seed /= 2;
5345 p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5346 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
5347 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
5348 j = seed%4; seed /= 4; put(board, exoPieces[3], 0, j, ANY);
5349 j = seed%3; seed /= 3; put(board, exoPieces[2], 0, j, ANY);
5350 j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
5351 put(board, exoPieces[0], 0, 0, ANY);
5352 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
5356 InitPosition(redraw)
5359 ChessSquare (* pieces)[BOARD_FILES];
5360 int i, j, pawnRow, overrule,
5361 oldx = gameInfo.boardWidth,
5362 oldy = gameInfo.boardHeight,
5363 oldh = gameInfo.holdingsWidth;
5366 if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
5368 /* [AS] Initialize pv info list [HGM] and game status */
5370 for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
5371 pvInfoList[i].depth = 0;
5372 boards[i][EP_STATUS] = EP_NONE;
5373 for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
5376 initialRulePlies = 0; /* 50-move counter start */
5378 castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
5379 castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
5383 /* [HGM] logic here is completely changed. In stead of full positions */
5384 /* the initialized data only consist of the two backranks. The switch */
5385 /* selects which one we will use, which is than copied to the Board */
5386 /* initialPosition, which for the rest is initialized by Pawns and */
5387 /* empty squares. This initial position is then copied to boards[0], */
5388 /* possibly after shuffling, so that it remains available. */
5390 gameInfo.holdingsWidth = 0; /* default board sizes */
5391 gameInfo.boardWidth = 8;
5392 gameInfo.boardHeight = 8;
5393 gameInfo.holdingsSize = 0;
5394 nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
5395 for(i=0; i<BOARD_FILES-2; i++)
5396 initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
5397 initialPosition[EP_STATUS] = EP_NONE;
5398 SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
5399 if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
5400 SetCharTable(pieceNickName, appData.pieceNickNames);
5401 else SetCharTable(pieceNickName, "............");
5404 switch (gameInfo.variant) {
5405 case VariantFischeRandom:
5406 shuffleOpenings = TRUE;
5409 case VariantShatranj:
5410 pieces = ShatranjArray;
5411 nrCastlingRights = 0;
5412 SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
5415 pieces = makrukArray;
5416 nrCastlingRights = 0;
5417 startedFromSetupPosition = TRUE;
5418 SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
5420 case VariantTwoKings:
5421 pieces = twoKingsArray;
5423 case VariantCapaRandom:
5424 shuffleOpenings = TRUE;
5425 case VariantCapablanca:
5426 pieces = CapablancaArray;
5427 gameInfo.boardWidth = 10;
5428 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5431 pieces = GothicArray;
5432 gameInfo.boardWidth = 10;
5433 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5436 SetCharTable(pieceToChar, "PNBRQ..HEKpnbrq..hek");
5437 gameInfo.holdingsSize = 7;
5440 pieces = JanusArray;
5441 gameInfo.boardWidth = 10;
5442 SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
5443 nrCastlingRights = 6;
5444 initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5445 initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5446 initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
5447 initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5448 initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5449 initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
5452 pieces = FalconArray;
5453 gameInfo.boardWidth = 10;
5454 SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
5456 case VariantXiangqi:
5457 pieces = XiangqiArray;
5458 gameInfo.boardWidth = 9;
5459 gameInfo.boardHeight = 10;
5460 nrCastlingRights = 0;
5461 SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
5464 pieces = ShogiArray;
5465 gameInfo.boardWidth = 9;
5466 gameInfo.boardHeight = 9;
5467 gameInfo.holdingsSize = 7;
5468 nrCastlingRights = 0;
5469 SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
5471 case VariantCourier:
5472 pieces = CourierArray;
5473 gameInfo.boardWidth = 12;
5474 nrCastlingRights = 0;
5475 SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
5477 case VariantKnightmate:
5478 pieces = KnightmateArray;
5479 SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
5481 case VariantSpartan:
5482 pieces = SpartanArray;
5483 SetCharTable(pieceToChar, "PNBRQ................K......lwg.....c...h..k");
5486 pieces = fairyArray;
5487 SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
5490 pieces = GreatArray;
5491 gameInfo.boardWidth = 10;
5492 SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
5493 gameInfo.holdingsSize = 8;
5497 SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
5498 gameInfo.holdingsSize = 8;
5499 startedFromSetupPosition = TRUE;
5501 case VariantCrazyhouse:
5502 case VariantBughouse:
5504 SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
5505 gameInfo.holdingsSize = 5;
5507 case VariantWildCastle:
5509 /* !!?shuffle with kings guaranteed to be on d or e file */
5510 shuffleOpenings = 1;
5512 case VariantNoCastle:
5514 nrCastlingRights = 0;
5515 /* !!?unconstrained back-rank shuffle */
5516 shuffleOpenings = 1;
5521 if(appData.NrFiles >= 0) {
5522 if(gameInfo.boardWidth != appData.NrFiles) overrule++;
5523 gameInfo.boardWidth = appData.NrFiles;
5525 if(appData.NrRanks >= 0) {
5526 gameInfo.boardHeight = appData.NrRanks;
5528 if(appData.holdingsSize >= 0) {
5529 i = appData.holdingsSize;
5530 if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
5531 gameInfo.holdingsSize = i;
5533 if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
5534 if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
5535 DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
5537 pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
5538 if(pawnRow < 1) pawnRow = 1;
5539 if(gameInfo.variant == VariantMakruk) pawnRow = 2;
5541 /* User pieceToChar list overrules defaults */
5542 if(appData.pieceToCharTable != NULL)
5543 SetCharTable(pieceToChar, appData.pieceToCharTable);
5545 for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
5547 if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
5548 s = (ChessSquare) 0; /* account holding counts in guard band */
5549 for( i=0; i<BOARD_HEIGHT; i++ )
5550 initialPosition[i][j] = s;
5552 if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
5553 initialPosition[0][j] = pieces[0][j-gameInfo.holdingsWidth];
5554 initialPosition[pawnRow][j] = WhitePawn;
5555 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = gameInfo.variant == VariantSpartan ? BlackLance : BlackPawn;
5556 if(gameInfo.variant == VariantXiangqi) {
5558 initialPosition[pawnRow][j] =
5559 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
5560 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
5561 initialPosition[2][j] = WhiteCannon;
5562 initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
5566 initialPosition[BOARD_HEIGHT-1][j] = pieces[1][j-gameInfo.holdingsWidth];
5568 if( (gameInfo.variant == VariantShogi) && !overrule ) {
5571 initialPosition[1][j] = WhiteBishop;
5572 initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
5574 initialPosition[1][j] = WhiteRook;
5575 initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
5578 if( nrCastlingRights == -1) {
5579 /* [HGM] Build normal castling rights (must be done after board sizing!) */
5580 /* This sets default castling rights from none to normal corners */
5581 /* Variants with other castling rights must set them themselves above */
5582 nrCastlingRights = 6;
5584 initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5585 initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5586 initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
5587 initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5588 initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5589 initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
5592 if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
5593 if(gameInfo.variant == VariantGreat) { // promotion commoners
5594 initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
5595 initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
5596 initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
5597 initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
5599 if( gameInfo.variant == VariantSChess ) {
5600 initialPosition[1][0] = BlackMarshall;
5601 initialPosition[2][0] = BlackAngel;
5602 initialPosition[6][BOARD_WIDTH-1] = WhiteMarshall;
5603 initialPosition[5][BOARD_WIDTH-1] = WhiteAngel;
5604 initialPosition[1][1] = initialPosition[2][1] =
5605 initialPosition[6][BOARD_WIDTH-2] = initialPosition[5][BOARD_WIDTH-2] = 1;
5607 if (appData.debugMode) {
5608 fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
5610 if(shuffleOpenings) {
5611 SetUpShuffle(initialPosition, appData.defaultFrcPosition);
5612 startedFromSetupPosition = TRUE;
5614 if(startedFromPositionFile) {
5615 /* [HGM] loadPos: use PositionFile for every new game */
5616 CopyBoard(initialPosition, filePosition);
5617 for(i=0; i<nrCastlingRights; i++)
5618 initialRights[i] = filePosition[CASTLING][i];
5619 startedFromSetupPosition = TRUE;
5622 CopyBoard(boards[0], initialPosition);
5624 if(oldx != gameInfo.boardWidth ||
5625 oldy != gameInfo.boardHeight ||
5626 oldv != gameInfo.variant ||
5627 oldh != gameInfo.holdingsWidth
5629 InitDrawingSizes(-2 ,0);
5631 oldv = gameInfo.variant;
5633 DrawPosition(TRUE, boards[currentMove]);
5637 SendBoard(cps, moveNum)
5638 ChessProgramState *cps;
5641 char message[MSG_SIZ];
5643 if (cps->useSetboard) {
5644 char* fen = PositionToFEN(moveNum, cps->fenOverride);
5645 snprintf(message, MSG_SIZ,"setboard %s\n", fen);
5646 SendToProgram(message, cps);
5652 /* Kludge to set black to move, avoiding the troublesome and now
5653 * deprecated "black" command.
5655 if (!WhiteOnMove(moveNum)) // [HGM] but better a deprecated command than an illegal move...
5656 SendToProgram(boards[0][1][BOARD_LEFT] == WhitePawn ? "a2a3\n" : "black\n", cps);
5658 SendToProgram("edit\n", cps);
5659 SendToProgram("#\n", cps);
5660 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
5661 bp = &boards[moveNum][i][BOARD_LEFT];
5662 for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
5663 if ((int) *bp < (int) BlackPawn) {
5664 snprintf(message, MSG_SIZ, "%c%c%c\n", PieceToChar(*bp),
5666 if(message[0] == '+' || message[0] == '~') {
5667 snprintf(message, MSG_SIZ,"%c%c%c+\n",
5668 PieceToChar((ChessSquare)(DEMOTED *bp)),
5671 if(cps->alphaRank) { /* [HGM] shogi: translate coords */
5672 message[1] = BOARD_RGHT - 1 - j + '1';
5673 message[2] = BOARD_HEIGHT - 1 - i + 'a';
5675 SendToProgram(message, cps);
5680 SendToProgram("c\n", cps);
5681 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
5682 bp = &boards[moveNum][i][BOARD_LEFT];
5683 for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
5684 if (((int) *bp != (int) EmptySquare)
5685 && ((int) *bp >= (int) BlackPawn)) {
5686 snprintf(message,MSG_SIZ, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
5688 if(message[0] == '+' || message[0] == '~') {
5689 snprintf(message, MSG_SIZ,"%c%c%c+\n",
5690 PieceToChar((ChessSquare)(DEMOTED *bp)),
5693 if(cps->alphaRank) { /* [HGM] shogi: translate coords */
5694 message[1] = BOARD_RGHT - 1 - j + '1';
5695 message[2] = BOARD_HEIGHT - 1 - i + 'a';
5697 SendToProgram(message, cps);
5702 SendToProgram(".\n", cps);
5704 setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
5707 static int autoQueen; // [HGM] oneclick
5710 HasPromotionChoice(int fromX, int fromY, int toX, int toY, char *promoChoice)
5712 /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
5713 /* [HGM] add Shogi promotions */
5714 int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
5719 if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
5720 if(toX < BOARD_LEFT || toX >= BOARD_RGHT) return FALSE; // move into holdings
5722 if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
5723 !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
5726 piece = boards[currentMove][fromY][fromX];
5727 if(gameInfo.variant == VariantShogi) {
5728 promotionZoneSize = BOARD_HEIGHT/3;
5729 highestPromotingPiece = (int)WhiteFerz;
5730 } else if(gameInfo.variant == VariantMakruk) {
5731 promotionZoneSize = 3;
5734 // Treat Lance as Pawn when it is not representing Amazon
5735 if(gameInfo.variant != VariantSuper) {
5736 if(piece == WhiteLance) piece = WhitePawn; else
5737 if(piece == BlackLance) piece = BlackPawn;
5740 // next weed out all moves that do not touch the promotion zone at all
5741 if((int)piece >= BlackPawn) {
5742 if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
5744 highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
5746 if( toY < BOARD_HEIGHT - promotionZoneSize &&
5747 fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
5750 if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
5752 // weed out mandatory Shogi promotions
5753 if(gameInfo.variant == VariantShogi) {
5754 if(piece >= BlackPawn) {
5755 if(toY == 0 && piece == BlackPawn ||
5756 toY == 0 && piece == BlackQueen ||
5757 toY <= 1 && piece == BlackKnight) {
5762 if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
5763 toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
5764 toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
5771 // weed out obviously illegal Pawn moves
5772 if(appData.testLegality && (piece == WhitePawn || piece == BlackPawn) ) {
5773 if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
5774 if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
5775 if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
5776 if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
5777 // note we are not allowed to test for valid (non-)capture, due to premove
5780 // we either have a choice what to promote to, or (in Shogi) whether to promote
5781 if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk) {
5782 *promoChoice = PieceToChar(BlackFerz); // no choice
5785 // no sense asking what we must promote to if it is going to explode...
5786 if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) {
5787 *promoChoice = PieceToChar(BlackQueen); // Queen as good as any
5790 // give caller the default choice even if we will not make it
5791 if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway)
5792 *promoChoice = PieceToChar(BlackKing); // in Suicide Q is the last thing we want
5793 else if(gameInfo.variant == VariantSpartan)
5794 *promoChoice = ToLower(PieceToChar(toY ? WhiteQueen : BlackAngel));
5795 else if(gameInfo.variant == VariantShogi)
5797 else *promoChoice = ToLower(PieceToChar(toY ? WhiteQueen : BlackQueen));
5798 if(*promoChoice == '.') *promoChoice = ToLower(PieceToChar(piece)); // safety catch, to make sure promoChoice is a defined piece
5799 if(autoQueen) return FALSE; // predetermined
5801 // suppress promotion popup on illegal moves that are not premoves
5802 premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
5803 gameMode == IcsPlayingBlack && WhiteOnMove(currentMove);
5804 if(appData.testLegality && !premove) {
5805 moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5806 fromY, fromX, toY, toX, gameInfo.variant == VariantShogi ? '+' : NULLCHAR);
5807 if(moveType != WhitePromotion && moveType != BlackPromotion)
5815 InPalace(row, column)
5817 { /* [HGM] for Xiangqi */
5818 if( (row < 3 || row > BOARD_HEIGHT-4) &&
5819 column < (BOARD_WIDTH + 4)/2 &&
5820 column > (BOARD_WIDTH - 5)/2 ) return TRUE;
5825 PieceForSquare (x, y)
5829 if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
5832 return boards[currentMove][y][x];
5836 OKToStartUserMove(x, y)
5839 ChessSquare from_piece;
5842 if (matchMode) return FALSE;
5843 if (gameMode == EditPosition) return TRUE;
5845 if (x >= 0 && y >= 0)
5846 from_piece = boards[currentMove][y][x];
5848 from_piece = EmptySquare;
5850 if (from_piece == EmptySquare) return FALSE;
5852 white_piece = (int)from_piece >= (int)WhitePawn &&
5853 (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
5856 case PlayFromGameFile:
5858 case TwoMachinesPlay:
5866 case MachinePlaysWhite:
5867 case IcsPlayingBlack:
5868 if (appData.zippyPlay) return FALSE;
5870 DisplayMoveError(_("You are playing Black"));
5875 case MachinePlaysBlack:
5876 case IcsPlayingWhite:
5877 if (appData.zippyPlay) return FALSE;
5879 DisplayMoveError(_("You are playing White"));
5885 if (!white_piece && WhiteOnMove(currentMove)) {
5886 DisplayMoveError(_("It is White's turn"));
5889 if (white_piece && !WhiteOnMove(currentMove)) {
5890 DisplayMoveError(_("It is Black's turn"));
5893 if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
5894 /* Editing correspondence game history */
5895 /* Could disallow this or prompt for confirmation */
5900 case BeginningOfGame:
5901 if (appData.icsActive) return FALSE;
5902 if (!appData.noChessProgram) {
5904 DisplayMoveError(_("You are playing White"));
5911 if (!white_piece && WhiteOnMove(currentMove)) {
5912 DisplayMoveError(_("It is White's turn"));
5915 if (white_piece && !WhiteOnMove(currentMove)) {
5916 DisplayMoveError(_("It is Black's turn"));
5925 if (currentMove != forwardMostMove && gameMode != AnalyzeMode
5926 && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
5927 && gameMode != AnalyzeFile && gameMode != Training) {
5928 DisplayMoveError(_("Displayed position is not current"));
5935 OnlyMove(int *x, int *y, Boolean captures) {
5936 DisambiguateClosure cl;
5937 if (appData.zippyPlay) return FALSE;
5939 case MachinePlaysBlack:
5940 case IcsPlayingWhite:
5941 case BeginningOfGame:
5942 if(!WhiteOnMove(currentMove)) return FALSE;
5944 case MachinePlaysWhite:
5945 case IcsPlayingBlack:
5946 if(WhiteOnMove(currentMove)) return FALSE;
5953 cl.pieceIn = EmptySquare;
5958 cl.promoCharIn = NULLCHAR;
5959 Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
5960 if( cl.kind == NormalMove ||
5961 cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
5962 cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
5963 cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
5970 if(cl.kind != ImpossibleMove) return FALSE;
5971 cl.pieceIn = EmptySquare;
5976 cl.promoCharIn = NULLCHAR;
5977 Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
5978 if( cl.kind == NormalMove ||
5979 cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
5980 cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
5981 cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
5986 autoQueen = TRUE; // act as if autoQueen on when we click to-square
5992 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
5993 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
5994 int lastLoadGameUseList = FALSE;
5995 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
5996 ChessMove lastLoadGameStart = EndOfFile;
5999 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
6000 int fromX, fromY, toX, toY;
6004 ChessSquare pdown, pup;
6006 /* Check if the user is playing in turn. This is complicated because we
6007 let the user "pick up" a piece before it is his turn. So the piece he
6008 tried to pick up may have been captured by the time he puts it down!
6009 Therefore we use the color the user is supposed to be playing in this
6010 test, not the color of the piece that is currently on the starting
6011 square---except in EditGame mode, where the user is playing both
6012 sides; fortunately there the capture race can't happen. (It can
6013 now happen in IcsExamining mode, but that's just too bad. The user
6014 will get a somewhat confusing message in that case.)
6018 case PlayFromGameFile:
6020 case TwoMachinesPlay:
6024 /* We switched into a game mode where moves are not accepted,
6025 perhaps while the mouse button was down. */
6028 case MachinePlaysWhite:
6029 /* User is moving for Black */
6030 if (WhiteOnMove(currentMove)) {
6031 DisplayMoveError(_("It is White's turn"));
6036 case MachinePlaysBlack:
6037 /* User is moving for White */
6038 if (!WhiteOnMove(currentMove)) {
6039 DisplayMoveError(_("It is Black's turn"));
6046 case BeginningOfGame:
6049 if(fromY == DROP_RANK) break; // [HGM] drop moves (entered through move type-in) are automatically assigned to side-to-move
6050 if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
6051 (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
6052 /* User is moving for Black */
6053 if (WhiteOnMove(currentMove)) {
6054 DisplayMoveError(_("It is White's turn"));
6058 /* User is moving for White */
6059 if (!WhiteOnMove(currentMove)) {
6060 DisplayMoveError(_("It is Black's turn"));
6066 case IcsPlayingBlack:
6067 /* User is moving for Black */
6068 if (WhiteOnMove(currentMove)) {
6069 if (!appData.premove) {
6070 DisplayMoveError(_("It is White's turn"));
6071 } else if (toX >= 0 && toY >= 0) {
6074 premoveFromX = fromX;
6075 premoveFromY = fromY;
6076 premovePromoChar = promoChar;
6078 if (appData.debugMode)
6079 fprintf(debugFP, "Got premove: fromX %d,"
6080 "fromY %d, toX %d, toY %d\n",
6081 fromX, fromY, toX, toY);
6087 case IcsPlayingWhite:
6088 /* User is moving for White */
6089 if (!WhiteOnMove(currentMove)) {
6090 if (!appData.premove) {
6091 DisplayMoveError(_("It is Black's turn"));
6092 } else if (toX >= 0 && toY >= 0) {
6095 premoveFromX = fromX;
6096 premoveFromY = fromY;
6097 premovePromoChar = promoChar;
6099 if (appData.debugMode)
6100 fprintf(debugFP, "Got premove: fromX %d,"
6101 "fromY %d, toX %d, toY %d\n",
6102 fromX, fromY, toX, toY);
6112 /* EditPosition, empty square, or different color piece;
6113 click-click move is possible */
6114 if (toX == -2 || toY == -2) {
6115 boards[0][fromY][fromX] = EmptySquare;
6116 DrawPosition(FALSE, boards[currentMove]);
6118 } else if (toX >= 0 && toY >= 0) {
6119 boards[0][toY][toX] = boards[0][fromY][fromX];
6120 if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
6121 if(boards[0][fromY][0] != EmptySquare) {
6122 if(boards[0][fromY][1]) boards[0][fromY][1]--;
6123 if(boards[0][fromY][1] == 0) boards[0][fromY][0] = EmptySquare;
6126 if(fromX == BOARD_RGHT+1) {
6127 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
6128 if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
6129 if(boards[0][fromY][BOARD_WIDTH-2] == 0) boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
6132 boards[0][fromY][fromX] = EmptySquare;
6133 DrawPosition(FALSE, boards[currentMove]);
6139 if(toX < 0 || toY < 0) return;
6140 pdown = boards[currentMove][fromY][fromX];
6141 pup = boards[currentMove][toY][toX];
6143 /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
6144 if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ) {
6145 if( pup != EmptySquare ) return;
6146 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
6147 if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n",
6148 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
6149 // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
6150 if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
6151 fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
6152 while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++;
6156 /* [HGM] always test for legality, to get promotion info */
6157 moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6158 fromY, fromX, toY, toX, promoChar);
6159 /* [HGM] but possibly ignore an IllegalMove result */
6160 if (appData.testLegality) {
6161 if (moveType == IllegalMove || moveType == ImpossibleMove) {
6162 DisplayMoveError(_("Illegal move"));
6167 FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
6170 /* Common tail of UserMoveEvent and DropMenuEvent */
6172 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
6174 int fromX, fromY, toX, toY;
6175 /*char*/int promoChar;
6179 if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) && promoChar != NULLCHAR) {
6180 // [HGM] superchess: suppress promotions to non-available piece
6181 int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
6182 if(WhiteOnMove(currentMove)) {
6183 if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
6185 if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
6189 /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
6190 move type in caller when we know the move is a legal promotion */
6191 if(moveType == NormalMove && promoChar)
6192 moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
6194 /* [HGM] <popupFix> The following if has been moved here from
6195 UserMoveEvent(). Because it seemed to belong here (why not allow
6196 piece drops in training games?), and because it can only be
6197 performed after it is known to what we promote. */
6198 if (gameMode == Training) {
6199 /* compare the move played on the board to the next move in the
6200 * game. If they match, display the move and the opponent's response.
6201 * If they don't match, display an error message.
6205 CopyBoard(testBoard, boards[currentMove]);
6206 ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
6208 if (CompareBoards(testBoard, boards[currentMove+1])) {
6209 ForwardInner(currentMove+1);
6211 /* Autoplay the opponent's response.
6212 * if appData.animate was TRUE when Training mode was entered,
6213 * the response will be animated.
6215 saveAnimate = appData.animate;
6216 appData.animate = animateTraining;
6217 ForwardInner(currentMove+1);
6218 appData.animate = saveAnimate;
6220 /* check for the end of the game */
6221 if (currentMove >= forwardMostMove) {
6222 gameMode = PlayFromGameFile;
6224 SetTrainingModeOff();
6225 DisplayInformation(_("End of game"));
6228 DisplayError(_("Incorrect move"), 0);
6233 /* Ok, now we know that the move is good, so we can kill
6234 the previous line in Analysis Mode */
6235 if ((gameMode == AnalyzeMode || gameMode == EditGame)
6236 && currentMove < forwardMostMove) {
6237 if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
6238 else forwardMostMove = currentMove;
6241 /* If we need the chess program but it's dead, restart it */
6242 ResurrectChessProgram();
6244 /* A user move restarts a paused game*/
6248 thinkOutput[0] = NULLCHAR;
6250 MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
6252 if(Adjudicate(NULL)) { // [HGM] adjudicate: take care of automatic game end
6253 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6257 if (gameMode == BeginningOfGame) {
6258 if (appData.noChessProgram) {
6259 gameMode = EditGame;
6263 gameMode = MachinePlaysBlack;
6266 snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
6268 if (first.sendName) {
6269 snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
6270 SendToProgram(buf, &first);
6277 /* Relay move to ICS or chess engine */
6278 if (appData.icsActive) {
6279 if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
6280 gameMode == IcsExamining) {
6281 if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
6282 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
6284 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6286 // also send plain move, in case ICS does not understand atomic claims
6287 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6291 if (first.sendTime && (gameMode == BeginningOfGame ||
6292 gameMode == MachinePlaysWhite ||
6293 gameMode == MachinePlaysBlack)) {
6294 SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
6296 if (gameMode != EditGame && gameMode != PlayFromGameFile) {
6297 // [HGM] book: if program might be playing, let it use book
6298 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
6299 first.maybeThinking = TRUE;
6300 } else SendMoveToProgram(forwardMostMove-1, &first);
6301 if (currentMove == cmailOldMove + 1) {
6302 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
6306 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6310 if(appData.testLegality)
6311 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
6317 if (WhiteOnMove(currentMove)) {
6318 GameEnds(BlackWins, "Black mates", GE_PLAYER);
6320 GameEnds(WhiteWins, "White mates", GE_PLAYER);
6324 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
6329 case MachinePlaysBlack:
6330 case MachinePlaysWhite:
6331 /* disable certain menu options while machine is thinking */
6332 SetMachineThinkingEnables();
6339 userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
6341 if(bookHit) { // [HGM] book: simulate book reply
6342 static char bookMove[MSG_SIZ]; // a bit generous?
6344 programStats.nodes = programStats.depth = programStats.time =
6345 programStats.score = programStats.got_only_move = 0;
6346 sprintf(programStats.movelist, "%s (xbook)", bookHit);
6348 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
6349 strcat(bookMove, bookHit);
6350 HandleMachineMove(bookMove, &first);
6356 Mark(board, flags, kind, rf, ff, rt, ft, closure)
6363 typedef char Markers[BOARD_RANKS][BOARD_FILES];
6364 Markers *m = (Markers *) closure;
6365 if(rf == fromY && ff == fromX)
6366 (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
6367 || kind == WhiteCapturesEnPassant
6368 || kind == BlackCapturesEnPassant);
6369 else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
6373 MarkTargetSquares(int clear)
6376 if(!appData.markers || !appData.highlightDragging ||
6377 !appData.testLegality || gameMode == EditPosition) return;
6379 for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) marker[y][x] = 0;
6382 GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker);
6383 if(PosFlags(0) & F_MANDATORY_CAPTURE) {
6384 for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
6386 for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
6389 DrawPosition(TRUE, NULL);
6393 Explode(Board board, int fromX, int fromY, int toX, int toY)
6395 if(gameInfo.variant == VariantAtomic &&
6396 (board[toY][toX] != EmptySquare || // capture?
6397 toX != fromX && (board[fromY][fromX] == WhitePawn || // e.p. ?
6398 board[fromY][fromX] == BlackPawn )
6400 AnimateAtomicCapture(board, fromX, fromY, toX, toY);
6406 ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
6408 void LeftClick(ClickType clickType, int xPix, int yPix)
6411 Boolean saveAnimate;
6412 static int second = 0, promotionChoice = 0, dragging = 0;
6413 char promoChoice = NULLCHAR;
6415 if(appData.seekGraph && appData.icsActive && loggedOn &&
6416 (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
6417 SeekGraphClick(clickType, xPix, yPix, 0);
6421 if (clickType == Press) ErrorPopDown();
6422 MarkTargetSquares(1);
6424 x = EventToSquare(xPix, BOARD_WIDTH);
6425 y = EventToSquare(yPix, BOARD_HEIGHT);
6426 if (!flipView && y >= 0) {
6427 y = BOARD_HEIGHT - 1 - y;
6429 if (flipView && x >= 0) {
6430 x = BOARD_WIDTH - 1 - x;
6433 if(promoSweep != EmptySquare) {
6434 char promoChar = ToLower(PieceToChar(promoSweep));
6435 if(gameInfo.variant == VariantShogi) promoChar = promoSweep == boards[currentMove][fromY][fromX] ? '=' : '+';
6436 saveAnimate = appData.animate; appData.animate = FALSE;
6437 UserMoveEvent(fromX, fromY, toX, toY, promoChar);
6438 appData.animate = saveAnimate;
6439 promoSweep = EmptySquare;
6440 DrawPosition(FALSE, boards[currentMove]);
6445 if(promotionChoice) { // we are waiting for a click to indicate promotion piece
6446 if(clickType == Release) return; // ignore upclick of click-click destination
6447 promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
6448 if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
6449 if(gameInfo.holdingsWidth &&
6450 (WhiteOnMove(currentMove)
6451 ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y > 0
6452 : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT-1) ) {
6453 // click in right holdings, for determining promotion piece
6454 ChessSquare p = boards[currentMove][y][x];
6455 if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
6456 if(p != EmptySquare) {
6457 FinishMove(NormalMove, fromX, fromY, toX, toY, ToLower(PieceToChar(p)));
6462 DrawPosition(FALSE, boards[currentMove]);
6466 /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
6467 if(clickType == Press
6468 && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
6469 || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
6470 || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
6473 autoQueen = appData.alwaysPromoteToQueen;
6476 gatingPiece = EmptySquare;
6477 if (clickType != Press) {
6478 if(dragging) { // [HGM] from-square must have been reset due to game end since last press
6479 DragPieceEnd(xPix, yPix); dragging = 0;
6480 DrawPosition(FALSE, NULL);
6484 if(!appData.oneClick || !OnlyMove(&x, &y, FALSE)) {
6486 if (OKToStartUserMove(x, y)) {
6490 MarkTargetSquares(0);
6491 DragPieceBegin(xPix, yPix); dragging = 1;
6492 if (appData.highlightDragging) {
6493 SetHighlights(x, y, -1, -1);
6501 if (clickType == Press && gameMode != EditPosition) {
6506 // ignore off-board to clicks
6507 if(y < 0 || x < 0) return;
6509 /* Check if clicking again on the same color piece */
6510 fromP = boards[currentMove][fromY][fromX];
6511 toP = boards[currentMove][y][x];
6512 frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess;
6513 if ((WhitePawn <= fromP && fromP <= WhiteKing &&
6514 WhitePawn <= toP && toP <= WhiteKing &&
6515 !(fromP == WhiteKing && toP == WhiteRook && frc) &&
6516 !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
6517 (BlackPawn <= fromP && fromP <= BlackKing &&
6518 BlackPawn <= toP && toP <= BlackKing &&
6519 !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
6520 !(fromP == BlackKing && toP == BlackRook && frc))) {
6521 /* Clicked again on same color piece -- changed his mind */
6522 second = (x == fromX && y == fromY);
6523 if(!second || appData.oneClick && !OnlyMove(&x, &y, TRUE)) {
6524 if (appData.highlightDragging) {
6525 SetHighlights(x, y, -1, -1);
6529 if (OKToStartUserMove(x, y)) {
6530 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
6531 (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
6532 y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
6533 gatingPiece = boards[currentMove][fromY][fromX];
6534 else gatingPiece = EmptySquare;
6536 fromY = y; dragging = 1;
6537 MarkTargetSquares(0);
6538 DragPieceBegin(xPix, yPix);
6541 if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
6544 // ignore clicks on holdings
6545 if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
6548 if (clickType == Release && x == fromX && y == fromY) {
6549 DragPieceEnd(xPix, yPix); dragging = 0;
6550 if (appData.animateDragging) {
6551 /* Undo animation damage if any */
6552 DrawPosition(FALSE, NULL);
6555 /* Second up/down in same square; just abort move */
6558 gatingPiece = EmptySquare;
6561 ClearPremoveHighlights();
6563 /* First upclick in same square; start click-click mode */
6564 SetHighlights(x, y, -1, -1);
6569 /* we now have a different from- and (possibly off-board) to-square */
6570 /* Completed move */
6573 saveAnimate = appData.animate;
6574 if (clickType == Press) {
6575 /* Finish clickclick move */
6576 if (appData.animate || appData.highlightLastMove) {
6577 SetHighlights(fromX, fromY, toX, toY);
6582 /* Finish drag move */
6583 if (appData.highlightLastMove) {
6584 SetHighlights(fromX, fromY, toX, toY);
6588 DragPieceEnd(xPix, yPix); dragging = 0;
6589 /* Don't animate move and drag both */
6590 appData.animate = FALSE;
6593 // moves into holding are invalid for now (except in EditPosition, adapting to-square)
6594 if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
6595 ChessSquare piece = boards[currentMove][fromY][fromX];
6596 if(gameMode == EditPosition && piece != EmptySquare &&
6597 fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
6600 if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
6601 n = PieceToNumber(piece - (int)BlackPawn);
6602 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
6603 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
6604 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
6606 if(x == BOARD_RGHT+1 && piece < BlackPawn) {
6607 n = PieceToNumber(piece);
6608 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
6609 boards[currentMove][n][BOARD_WIDTH-1] = piece;
6610 boards[currentMove][n][BOARD_WIDTH-2]++;
6612 boards[currentMove][fromY][fromX] = EmptySquare;
6616 DrawPosition(TRUE, boards[currentMove]);
6620 // off-board moves should not be highlighted
6621 if(x < 0 || y < 0) ClearHighlights();
6623 if(gatingPiece != EmptySquare) promoChoice = ToLower(PieceToChar(gatingPiece));
6625 if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice)) {
6626 SetHighlights(fromX, fromY, toX, toY);
6627 if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
6628 // [HGM] super: promotion to captured piece selected from holdings
6629 ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
6630 promotionChoice = TRUE;
6631 // kludge follows to temporarily execute move on display, without promoting yet
6632 boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
6633 boards[currentMove][toY][toX] = p;
6634 DrawPosition(FALSE, boards[currentMove]);
6635 boards[currentMove][fromY][fromX] = p; // take back, but display stays
6636 boards[currentMove][toY][toX] = q;
6637 DisplayMessage("Click in holdings to choose piece", "");
6640 if(appData.sweepSelect && clickType == Press) {
6641 lastX = xPix; lastY = yPix;
6642 ChessSquare piece = boards[currentMove][fromY][fromX];
6643 promoSweep = CharToPiece((piece >= BlackPawn ? ToLower : ToUpper)(promoChoice));
6644 if(promoChoice == '+') promoSweep = PROMOTED piece;
6646 } else PromotionPopUp();
6648 int oldMove = currentMove;
6649 UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
6650 if (!appData.highlightLastMove || gotPremove) ClearHighlights();
6651 if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
6652 if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
6653 Explode(boards[currentMove-1], fromX, fromY, toX, toY))
6654 DrawPosition(TRUE, boards[currentMove]);
6657 appData.animate = saveAnimate;
6658 if (appData.animate || appData.animateDragging) {
6659 /* Undo animation damage if needed */
6660 DrawPosition(FALSE, NULL);
6664 int RightClick(ClickType action, int x, int y, int *fromX, int *fromY)
6665 { // front-end-free part taken out of PieceMenuPopup
6666 int whichMenu; int xSqr, ySqr;
6668 if(seekGraphUp) { // [HGM] seekgraph
6669 if(action == Press) SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
6670 if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
6674 if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
6675 && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
6676 if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
6677 if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
6678 if(action == Press) {
6679 originalFlip = flipView;
6680 flipView = !flipView; // temporarily flip board to see game from partners perspective
6681 DrawPosition(TRUE, partnerBoard);
6682 DisplayMessage(partnerStatus, "");
6684 } else if(action == Release) {
6685 flipView = originalFlip;
6686 DrawPosition(TRUE, boards[currentMove]);
6692 xSqr = EventToSquare(x, BOARD_WIDTH);
6693 ySqr = EventToSquare(y, BOARD_HEIGHT);
6694 if (action == Release) {
6695 if(pieceSweep != EmptySquare) {
6696 EditPositionMenuEvent(pieceSweep, toX, toY);
6697 pieceSweep = EmptySquare;
6698 } else UnLoadPV(); // [HGM] pv
6700 if (action != Press) return -2; // return code to be ignored
6703 if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;
\r
6705 if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;
\r
6706 if (xSqr < 0 || ySqr < 0) return -1;
6707 if(appData.pieceMenu) { whichMenu = 0; break; } // edit-position menu
6708 pieceSweep = shiftKey ? BlackPawn : WhitePawn; // [HGM] sweep: prepare selecting piece by mouse sweep
6709 toX = xSqr; toY = ySqr; lastX = x, lastY = y;
6710 if(flipView) toX = BOARD_WIDTH - 1 - toX; else toY = BOARD_HEIGHT - 1 - toY;
6714 if(!appData.icsEngineAnalyze) return -1;
6715 case IcsPlayingWhite:
6716 case IcsPlayingBlack:
6717 if(!appData.zippyPlay) goto noZip;
6720 case MachinePlaysWhite:
6721 case MachinePlaysBlack:
6722 case TwoMachinesPlay: // [HGM] pv: use for showing PV
6723 if (!appData.dropMenu) {
6725 return 2; // flag front-end to grab mouse events
6727 if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
6728 gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
6731 if (xSqr < 0 || ySqr < 0) return -1;
6732 if (!appData.dropMenu || appData.testLegality &&
6733 gameInfo.variant != VariantBughouse &&
6734 gameInfo.variant != VariantCrazyhouse) return -1;
6735 whichMenu = 1; // drop menu
6741 if (((*fromX = xSqr) < 0) ||
6742 ((*fromY = ySqr) < 0)) {
6743 *fromX = *fromY = -1;
6747 *fromX = BOARD_WIDTH - 1 - *fromX;
6749 *fromY = BOARD_HEIGHT - 1 - *fromY;
6754 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
6756 // char * hint = lastHint;
6757 FrontEndProgramStats stats;
6759 stats.which = cps == &first ? 0 : 1;
6760 stats.depth = cpstats->depth;
6761 stats.nodes = cpstats->nodes;
6762 stats.score = cpstats->score;
6763 stats.time = cpstats->time;
6764 stats.pv = cpstats->movelist;
6765 stats.hint = lastHint;
6766 stats.an_move_index = 0;
6767 stats.an_move_count = 0;
6769 if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
6770 stats.hint = cpstats->move_name;
6771 stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
6772 stats.an_move_count = cpstats->nr_moves;
6775 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
6777 SetProgramStats( &stats );
6781 Count(Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
6782 { // count all piece types
6784 *nB = *nW = *wStale = *bStale = *bishopColor = 0;
6785 for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
6786 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
6789 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
6790 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
6791 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
6792 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
6793 p == BlackBishop || p == BlackFerz || p == BlackAlfil )
6794 *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
6799 SufficientDefence(int pCnt[], int side, int nMine, int nHis)
6801 int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
6802 int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
6804 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
6805 if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
6806 if(myPawns == 2 && nMine == 3) // KPP
6807 return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
6808 if(myPawns == 1 && nMine == 2) // KP
6809 return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
6810 if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
6811 return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
6812 if(myPawns) return FALSE;
6813 if(pCnt[WhiteRook+side])
6814 return pCnt[BlackRook-side] ||
6815 pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
6816 pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
6817 pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
6818 if(pCnt[WhiteCannon+side]) {
6819 if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
6820 return majorDefense || pCnt[BlackAlfil-side] >= 2;
6822 if(pCnt[WhiteKnight+side])
6823 return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
6828 MatingPotential(int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
6830 VariantClass v = gameInfo.variant;
6832 if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
6833 if(v == VariantShatranj) return TRUE; // always winnable through baring
6834 if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
6835 if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
6837 if(v == VariantXiangqi) {
6838 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
6840 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
6841 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
6842 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
6843 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
6844 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
6845 if(stale) // we have at least one last-rank P plus perhaps C
6846 return majors // KPKX
6847 || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
6849 return pCnt[WhiteFerz+side] // KCAK
6850 || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
6851 || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
6852 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
6854 } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
6855 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
6857 if(nMine == 1) return FALSE; // bare King
6858 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
6859 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
6860 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
6861 // by now we have King + 1 piece (or multiple Bishops on the same color)
6862 if(pCnt[WhiteKnight+side])
6863 return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
6864 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
6865 || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
6867 return (pCnt[BlackKnight-side]); // KBKN, KFKN
6868 if(pCnt[WhiteAlfil+side])
6869 return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
6870 if(pCnt[WhiteWazir+side])
6871 return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
6878 Adjudicate(ChessProgramState *cps)
6879 { // [HGM] some adjudications useful with buggy engines
6880 // [HGM] adjudicate: made into separate routine, which now can be called after every move
6881 // In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
6882 // Actually ending the game is now based on the additional internal condition canAdjudicate.
6883 // Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
6884 int k, count = 0; static int bare = 1;
6885 ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
6886 Boolean canAdjudicate = !appData.icsActive;
6888 // most tests only when we understand the game, i.e. legality-checking on
6889 if( appData.testLegality )
6890 { /* [HGM] Some more adjudications for obstinate engines */
6891 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+1], i;
6892 static int moveCount = 6;
6894 char *reason = NULL;
6896 /* Count what is on board. */
6897 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
6899 /* Some material-based adjudications that have to be made before stalemate test */
6900 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
6901 // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
6902 boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
6903 if(canAdjudicate && appData.checkMates) {
6905 SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
6906 GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
6907 "Xboard adjudication: King destroyed", GE_XBOARD );
6912 /* Bare King in Shatranj (loses) or Losers (wins) */
6913 if( nrW == 1 || nrB == 1) {
6914 if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
6915 boards[forwardMostMove][EP_STATUS] = EP_WINS; // mark as win, so it becomes claimable
6916 if(canAdjudicate && appData.checkMates) {
6918 SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
6919 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
6920 "Xboard adjudication: Bare king", GE_XBOARD );
6924 if( gameInfo.variant == VariantShatranj && --bare < 0)
6926 boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
6927 if(canAdjudicate && appData.checkMates) {
6928 /* but only adjudicate if adjudication enabled */
6930 SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
6931 GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
6932 "Xboard adjudication: Bare king", GE_XBOARD );
6939 // don't wait for engine to announce game end if we can judge ourselves
6940 switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
6942 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
6943 int i, checkCnt = 0; // (should really be done by making nr of checks part of game state)
6944 for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
6945 if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
6948 reason = "Xboard adjudication: 3rd check";
6949 boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
6959 reason = "Xboard adjudication: Stalemate";
6960 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
6961 boards[forwardMostMove][EP_STATUS] = EP_STALEMATE; // default result for stalemate is draw
6962 if(gameInfo.variant == VariantLosers || gameInfo.variant == VariantGiveaway) // [HGM] losers:
6963 boards[forwardMostMove][EP_STATUS] = EP_WINS; // in these variants stalemated is always a win
6964 else if(gameInfo.variant == VariantSuicide) // in suicide it depends
6965 boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
6966 ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
6967 EP_CHECKMATE : EP_WINS);
6968 else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
6969 boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
6973 reason = "Xboard adjudication: Checkmate";
6974 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
6978 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
6980 result = GameIsDrawn; break;
6982 result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
6984 result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
6988 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
6990 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6991 GameEnds( result, reason, GE_XBOARD );
6995 /* Next absolutely insufficient mating material. */
6996 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
6997 !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
6998 { /* includes KBK, KNK, KK of KBKB with like Bishops */
7000 /* always flag draws, for judging claims */
7001 boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
7003 if(canAdjudicate && appData.materialDraws) {
7004 /* but only adjudicate them if adjudication enabled */
7005 if(engineOpponent) {
7006 SendToProgram("force\n", engineOpponent); // suppress reply
7007 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
7009 GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
7014 /* Then some trivial draws (only adjudicate, cannot be claimed) */
7015 if(gameInfo.variant == VariantXiangqi ?
7016 SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
7018 ( nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
7019 || nr[WhiteQueen] && nr[BlackQueen]==1 /* KQKQ */
7020 || nr[WhiteKnight]==2 || nr[BlackKnight]==2 /* KNNK */
7021 || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
7023 if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
7024 { /* if the first 3 moves do not show a tactical win, declare draw */
7025 if(engineOpponent) {
7026 SendToProgram("force\n", engineOpponent); // suppress reply
7027 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7029 GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
7032 } else moveCount = 6;
7034 if (appData.debugMode) { int i;
7035 fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
7036 forwardMostMove, backwardMostMove, boards[backwardMostMove][EP_STATUS],
7037 appData.drawRepeats);
7038 for( i=forwardMostMove; i>=backwardMostMove; i-- )
7039 fprintf(debugFP, "%d ep=%d\n", i, (signed char)boards[i][EP_STATUS]);
7043 // Repetition draws and 50-move rule can be applied independently of legality testing
7045 /* Check for rep-draws */
7047 for(k = forwardMostMove-2;
7048 k>=backwardMostMove && k>=forwardMostMove-100 &&
7049 (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
7050 (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE;
7053 if(CompareBoards(boards[k], boards[forwardMostMove])) {
7054 /* compare castling rights */
7055 if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
7056 (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
7057 rights++; /* King lost rights, while rook still had them */
7058 if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
7059 if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
7060 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
7061 rights++; /* but at least one rook lost them */
7063 if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
7064 (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
7066 if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
7067 if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
7068 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
7071 if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
7072 && appData.drawRepeats > 1) {
7073 /* adjudicate after user-specified nr of repeats */
7074 int result = GameIsDrawn;
7075 char *details = "XBoard adjudication: repetition draw";
7076 if(gameInfo.variant == VariantXiangqi && appData.testLegality) {
7077 // [HGM] xiangqi: check for forbidden perpetuals
7078 int m, ourPerpetual = 1, hisPerpetual = 1;
7079 for(m=forwardMostMove; m>k; m-=2) {
7080 if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
7081 ourPerpetual = 0; // the current mover did not always check
7082 if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
7083 hisPerpetual = 0; // the opponent did not always check
7085 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
7086 ourPerpetual, hisPerpetual);
7087 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
7088 result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7089 details = "Xboard adjudication: perpetual checking";
7091 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
7092 break; // (or we would have caught him before). Abort repetition-checking loop.
7094 // Now check for perpetual chases
7095 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
7096 hisPerpetual = PerpetualChase(k, forwardMostMove);
7097 ourPerpetual = PerpetualChase(k+1, forwardMostMove);
7098 if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
7099 result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7100 details = "Xboard adjudication: perpetual chasing";
7102 if(hisPerpetual && !ourPerpetual) // he is chasing us, but did not repeat yet
7103 break; // Abort repetition-checking loop.
7105 // if neither of us is checking or chasing all the time, or both are, it is draw
7107 if(engineOpponent) {
7108 SendToProgram("force\n", engineOpponent); // suppress reply
7109 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7111 GameEnds( result, details, GE_XBOARD );
7114 if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
7115 boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
7119 /* Now we test for 50-move draws. Determine ply count */
7120 count = forwardMostMove;
7121 /* look for last irreversble move */
7122 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
7124 /* if we hit starting position, add initial plies */
7125 if( count == backwardMostMove )
7126 count -= initialRulePlies;
7127 count = forwardMostMove - count;
7128 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
7129 // adjust reversible move counter for checks in Xiangqi
7130 int i = forwardMostMove - count, inCheck = 0, lastCheck;
7131 if(i < backwardMostMove) i = backwardMostMove;
7132 while(i <= forwardMostMove) {
7133 lastCheck = inCheck; // check evasion does not count
7134 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
7135 if(inCheck || lastCheck) count--; // check does not count
7140 boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
7141 /* this is used to judge if draw claims are legal */
7142 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
7143 if(engineOpponent) {
7144 SendToProgram("force\n", engineOpponent); // suppress reply
7145 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7147 GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
7151 /* if draw offer is pending, treat it as a draw claim
7152 * when draw condition present, to allow engines a way to
7153 * claim draws before making their move to avoid a race
7154 * condition occurring after their move
7156 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
7158 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
7159 p = "Draw claim: 50-move rule";
7160 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
7161 p = "Draw claim: 3-fold repetition";
7162 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
7163 p = "Draw claim: insufficient mating material";
7164 if( p != NULL && canAdjudicate) {
7165 if(engineOpponent) {
7166 SendToProgram("force\n", engineOpponent); // suppress reply
7167 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7169 GameEnds( GameIsDrawn, p, GE_XBOARD );
7174 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
7175 if(engineOpponent) {
7176 SendToProgram("force\n", engineOpponent); // suppress reply
7177 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7179 GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
7185 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
7186 { // [HGM] book: this routine intercepts moves to simulate book replies
7187 char *bookHit = NULL;
7189 //first determine if the incoming move brings opponent into his book
7190 if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
7191 bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
7192 if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
7193 if(bookHit != NULL && !cps->bookSuspend) {
7194 // make sure opponent is not going to reply after receiving move to book position
7195 SendToProgram("force\n", cps);
7196 cps->bookSuspend = TRUE; // flag indicating it has to be restarted
7198 if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
7199 // now arrange restart after book miss
7201 // after a book hit we never send 'go', and the code after the call to this routine
7202 // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
7204 snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), bookHit); // force book move into program supposed to play it
7205 SendToProgram(buf, cps);
7206 if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
7207 } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
7208 SendToProgram("go\n", cps);
7209 cps->bookSuspend = FALSE; // after a 'go' we are never suspended
7210 } else { // 'go' might be sent based on 'firstMove' after this routine returns
7211 if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
7212 SendToProgram("go\n", cps);
7213 cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
7215 return bookHit; // notify caller of hit, so it can take action to send move to opponent
7219 ChessProgramState *savedState;
7220 void DeferredBookMove(void)
7222 if(savedState->lastPing != savedState->lastPong)
7223 ScheduleDelayedEvent(DeferredBookMove, 10);
7225 HandleMachineMove(savedMessage, savedState);
7229 HandleMachineMove(message, cps)
7231 ChessProgramState *cps;
7233 char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
7234 char realname[MSG_SIZ];
7235 int fromX, fromY, toX, toY;
7244 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
7246 * Kludge to ignore BEL characters
7248 while (*message == '\007') message++;
7251 * [HGM] engine debug message: ignore lines starting with '#' character
7253 if(cps->debug && *message == '#') return;
7256 * Look for book output
7258 if (cps == &first && bookRequested) {
7259 if (message[0] == '\t' || message[0] == ' ') {
7260 /* Part of the book output is here; append it */
7261 strcat(bookOutput, message);
7262 strcat(bookOutput, " \n");
7264 } else if (bookOutput[0] != NULLCHAR) {
7265 /* All of book output has arrived; display it */
7266 char *p = bookOutput;
7267 while (*p != NULLCHAR) {
7268 if (*p == '\t') *p = ' ';
7271 DisplayInformation(bookOutput);
7272 bookRequested = FALSE;
7273 /* Fall through to parse the current output */
7278 * Look for machine move.
7280 if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
7281 (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
7283 /* This method is only useful on engines that support ping */
7284 if (cps->lastPing != cps->lastPong) {
7285 if (gameMode == BeginningOfGame) {
7286 /* Extra move from before last new; ignore */
7287 if (appData.debugMode) {
7288 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7291 if (appData.debugMode) {
7292 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7293 cps->which, gameMode);
7296 SendToProgram("undo\n", cps);
7302 case BeginningOfGame:
7303 /* Extra move from before last reset; ignore */
7304 if (appData.debugMode) {
7305 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7312 /* Extra move after we tried to stop. The mode test is
7313 not a reliable way of detecting this problem, but it's
7314 the best we can do on engines that don't support ping.
7316 if (appData.debugMode) {
7317 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7318 cps->which, gameMode);
7320 SendToProgram("undo\n", cps);
7323 case MachinePlaysWhite:
7324 case IcsPlayingWhite:
7325 machineWhite = TRUE;
7328 case MachinePlaysBlack:
7329 case IcsPlayingBlack:
7330 machineWhite = FALSE;
7333 case TwoMachinesPlay:
7334 machineWhite = (cps->twoMachinesColor[0] == 'w');
7337 if (WhiteOnMove(forwardMostMove) != machineWhite) {
7338 if (appData.debugMode) {
7340 "Ignoring move out of turn by %s, gameMode %d"
7341 ", forwardMost %d\n",
7342 cps->which, gameMode, forwardMostMove);
7347 if (appData.debugMode) { int f = forwardMostMove;
7348 fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
7349 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
7350 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
7352 if(cps->alphaRank) AlphaRank(machineMove, 4);
7353 if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
7354 &fromX, &fromY, &toX, &toY, &promoChar)) {
7355 /* Machine move could not be parsed; ignore it. */
7356 snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
7357 machineMove, _(cps->which));
7358 DisplayError(buf1, 0);
7359 snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
7360 machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
7361 if (gameMode == TwoMachinesPlay) {
7362 GameEnds(machineWhite ? BlackWins : WhiteWins,
7368 /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
7369 /* So we have to redo legality test with true e.p. status here, */
7370 /* to make sure an illegal e.p. capture does not slip through, */
7371 /* to cause a forfeit on a justified illegal-move complaint */
7372 /* of the opponent. */
7373 if( gameMode==TwoMachinesPlay && appData.testLegality ) {
7375 moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
7376 fromY, fromX, toY, toX, promoChar);
7377 if (appData.debugMode) {
7379 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
7380 boards[forwardMostMove][CASTLING][i], castlingRank[i]);
7381 fprintf(debugFP, "castling rights\n");
7383 if(moveType == IllegalMove) {
7384 snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
7385 machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
7386 GameEnds(machineWhite ? BlackWins : WhiteWins,
7389 } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
7390 /* [HGM] Kludge to handle engines that send FRC-style castling
7391 when they shouldn't (like TSCP-Gothic) */
7393 case WhiteASideCastleFR:
7394 case BlackASideCastleFR:
7396 currentMoveString[2]++;
7398 case WhiteHSideCastleFR:
7399 case BlackHSideCastleFR:
7401 currentMoveString[2]--;
7403 default: ; // nothing to do, but suppresses warning of pedantic compilers
7406 hintRequested = FALSE;
7407 lastHint[0] = NULLCHAR;
7408 bookRequested = FALSE;
7409 /* Program may be pondering now */
7410 cps->maybeThinking = TRUE;
7411 if (cps->sendTime == 2) cps->sendTime = 1;
7412 if (cps->offeredDraw) cps->offeredDraw--;
7414 /* [AS] Save move info*/
7415 pvInfoList[ forwardMostMove ].score = programStats.score;
7416 pvInfoList[ forwardMostMove ].depth = programStats.depth;
7417 pvInfoList[ forwardMostMove ].time = programStats.time; // [HGM] PGNtime: take time from engine stats
7419 MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
7421 /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
7422 if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
7425 while( count < adjudicateLossPlies ) {
7426 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
7429 score = -score; /* Flip score for winning side */
7432 if( score > adjudicateLossThreshold ) {
7439 if( count >= adjudicateLossPlies ) {
7440 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7442 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7443 "Xboard adjudication",
7450 if(Adjudicate(cps)) {
7451 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7452 return; // [HGM] adjudicate: for all automatic game ends
7456 if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
7458 if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
7459 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
7461 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7463 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7465 if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
7466 char buf[3*MSG_SIZ];
7468 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
7469 programStats.score / 100.,
7471 programStats.time / 100.,
7472 (unsigned int)programStats.nodes,
7473 (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
7474 programStats.movelist);
7476 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
7481 /* [AS] Clear stats for next move */
7482 ClearProgramStats();
7483 thinkOutput[0] = NULLCHAR;
7484 hiddenThinkOutputState = 0;
7487 if (gameMode == TwoMachinesPlay) {
7488 /* [HGM] relaying draw offers moved to after reception of move */
7489 /* and interpreting offer as claim if it brings draw condition */
7490 if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
7491 SendToProgram("draw\n", cps->other);
7493 if (cps->other->sendTime) {
7494 SendTimeRemaining(cps->other,
7495 cps->other->twoMachinesColor[0] == 'w');
7497 bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
7498 if (firstMove && !bookHit) {
7500 if (cps->other->useColors) {
7501 SendToProgram(cps->other->twoMachinesColor, cps->other);
7503 SendToProgram("go\n", cps->other);
7505 cps->other->maybeThinking = TRUE;
7508 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7510 if (!pausing && appData.ringBellAfterMoves) {
7515 * Reenable menu items that were disabled while
7516 * machine was thinking
7518 if (gameMode != TwoMachinesPlay)
7519 SetUserThinkingEnables();
7521 // [HGM] book: after book hit opponent has received move and is now in force mode
7522 // force the book reply into it, and then fake that it outputted this move by jumping
7523 // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
7525 static char bookMove[MSG_SIZ]; // a bit generous?
7527 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
7528 strcat(bookMove, bookHit);
7531 programStats.nodes = programStats.depth = programStats.time =
7532 programStats.score = programStats.got_only_move = 0;
7533 sprintf(programStats.movelist, "%s (xbook)", bookHit);
7535 if(cps->lastPing != cps->lastPong) {
7536 savedMessage = message; // args for deferred call
7538 ScheduleDelayedEvent(DeferredBookMove, 10);
7547 /* Set special modes for chess engines. Later something general
7548 * could be added here; for now there is just one kludge feature,
7549 * needed because Crafty 15.10 and earlier don't ignore SIGINT
7550 * when "xboard" is given as an interactive command.
7552 if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
7553 cps->useSigint = FALSE;
7554 cps->useSigterm = FALSE;
7556 if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
7557 ParseFeatures(message+8, cps);
7558 return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
7561 if (!appData.testLegality && !strncmp(message, "setup ", 6)) { // [HGM] allow first engine to define opening position
7562 int dummy, s=6; char buf[MSG_SIZ];
7563 if(appData.icsActive || forwardMostMove != 0 || cps != &first || startedFromSetupPosition) return;
7564 if(sscanf(message, "setup (%s", buf) == 1) s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTable(pieceToChar, buf);
7565 ParseFEN(boards[0], &dummy, message+s);
7566 DrawPosition(TRUE, boards[0]);
7567 startedFromSetupPosition = TRUE;
7570 /* [HGM] Allow engine to set up a position. Don't ask me why one would
7571 * want this, I was asked to put it in, and obliged.
7573 if (!strncmp(message, "setboard ", 9)) {
7574 Board initial_position;
7576 GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
7578 if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
7579 DisplayError(_("Bad FEN received from engine"), 0);
7583 CopyBoard(boards[0], initial_position);
7584 initialRulePlies = FENrulePlies;
7585 if(blackPlaysFirst) gameMode = MachinePlaysWhite;
7586 else gameMode = MachinePlaysBlack;
7587 DrawPosition(FALSE, boards[currentMove]);
7593 * Look for communication commands
7595 if (!strncmp(message, "telluser ", 9)) {
7596 if(message[9] == '\\' && message[10] == '\\')
7597 EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
7598 DisplayNote(message + 9);
7601 if (!strncmp(message, "tellusererror ", 14)) {
7603 if(message[14] == '\\' && message[15] == '\\')
7604 EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
7605 DisplayError(message + 14, 0);
7608 if (!strncmp(message, "tellopponent ", 13)) {
7609 if (appData.icsActive) {
7611 snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
7615 DisplayNote(message + 13);
7619 if (!strncmp(message, "tellothers ", 11)) {
7620 if (appData.icsActive) {
7622 snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
7628 if (!strncmp(message, "tellall ", 8)) {
7629 if (appData.icsActive) {
7631 snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
7635 DisplayNote(message + 8);
7639 if (strncmp(message, "warning", 7) == 0) {
7640 /* Undocumented feature, use tellusererror in new code */
7641 DisplayError(message, 0);
7644 if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
7645 safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
7646 strcat(realname, " query");
7647 AskQuestion(realname, buf2, buf1, cps->pr);
7650 /* Commands from the engine directly to ICS. We don't allow these to be
7651 * sent until we are logged on. Crafty kibitzes have been known to
7652 * interfere with the login process.
7655 if (!strncmp(message, "tellics ", 8)) {
7656 SendToICS(message + 8);
7660 if (!strncmp(message, "tellicsnoalias ", 15)) {
7661 SendToICS(ics_prefix);
7662 SendToICS(message + 15);
7666 /* The following are for backward compatibility only */
7667 if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
7668 !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
7669 SendToICS(ics_prefix);
7675 if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
7679 * If the move is illegal, cancel it and redraw the board.
7680 * Also deal with other error cases. Matching is rather loose
7681 * here to accommodate engines written before the spec.
7683 if (strncmp(message + 1, "llegal move", 11) == 0 ||
7684 strncmp(message, "Error", 5) == 0) {
7685 if (StrStr(message, "name") ||
7686 StrStr(message, "rating") || StrStr(message, "?") ||
7687 StrStr(message, "result") || StrStr(message, "board") ||
7688 StrStr(message, "bk") || StrStr(message, "computer") ||
7689 StrStr(message, "variant") || StrStr(message, "hint") ||
7690 StrStr(message, "random") || StrStr(message, "depth") ||
7691 StrStr(message, "accepted")) {
7694 if (StrStr(message, "protover")) {
7695 /* Program is responding to input, so it's apparently done
7696 initializing, and this error message indicates it is
7697 protocol version 1. So we don't need to wait any longer
7698 for it to initialize and send feature commands. */
7699 FeatureDone(cps, 1);
7700 cps->protocolVersion = 1;
7703 cps->maybeThinking = FALSE;
7705 if (StrStr(message, "draw")) {
7706 /* Program doesn't have "draw" command */
7707 cps->sendDrawOffers = 0;
7710 if (cps->sendTime != 1 &&
7711 (StrStr(message, "time") || StrStr(message, "otim"))) {
7712 /* Program apparently doesn't have "time" or "otim" command */
7716 if (StrStr(message, "analyze")) {
7717 cps->analysisSupport = FALSE;
7718 cps->analyzing = FALSE;
7720 snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
7721 DisplayError(buf2, 0);
7724 if (StrStr(message, "(no matching move)st")) {
7725 /* Special kludge for GNU Chess 4 only */
7726 cps->stKludge = TRUE;
7727 SendTimeControl(cps, movesPerSession, timeControl,
7728 timeIncrement, appData.searchDepth,
7732 if (StrStr(message, "(no matching move)sd")) {
7733 /* Special kludge for GNU Chess 4 only */
7734 cps->sdKludge = TRUE;
7735 SendTimeControl(cps, movesPerSession, timeControl,
7736 timeIncrement, appData.searchDepth,
7740 if (!StrStr(message, "llegal")) {
7743 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
7744 gameMode == IcsIdle) return;
7745 if (forwardMostMove <= backwardMostMove) return;
7746 if (pausing) PauseEvent();
7747 if(appData.forceIllegal) {
7748 // [HGM] illegal: machine refused move; force position after move into it
7749 SendToProgram("force\n", cps);
7750 if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
7751 // we have a real problem now, as SendBoard will use the a2a3 kludge
7752 // when black is to move, while there might be nothing on a2 or black
7753 // might already have the move. So send the board as if white has the move.
7754 // But first we must change the stm of the engine, as it refused the last move
7755 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
7756 if(WhiteOnMove(forwardMostMove)) {
7757 SendToProgram("a7a6\n", cps); // for the engine black still had the move
7758 SendBoard(cps, forwardMostMove); // kludgeless board
7760 SendToProgram("a2a3\n", cps); // for the engine white still had the move
7761 CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
7762 SendBoard(cps, forwardMostMove+1); // kludgeless board
7764 } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
7765 if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
7766 gameMode == TwoMachinesPlay)
7767 SendToProgram("go\n", cps);
7770 if (gameMode == PlayFromGameFile) {
7771 /* Stop reading this game file */
7772 gameMode = EditGame;
7775 /* [HGM] illegal-move claim should forfeit game when Xboard */
7776 /* only passes fully legal moves */
7777 if( appData.testLegality && gameMode == TwoMachinesPlay ) {
7778 GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
7779 "False illegal-move claim", GE_XBOARD );
7780 return; // do not take back move we tested as valid
7782 currentMove = forwardMostMove-1;
7783 DisplayMove(currentMove-1); /* before DisplayMoveError */
7784 SwitchClocks(forwardMostMove-1); // [HGM] race
7785 DisplayBothClocks();
7786 snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
7787 parseList[currentMove], _(cps->which));
7788 DisplayMoveError(buf1);
7789 DrawPosition(FALSE, boards[currentMove]);
7792 if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
7793 /* Program has a broken "time" command that
7794 outputs a string not ending in newline.
7800 * If chess program startup fails, exit with an error message.
7801 * Attempts to recover here are futile.
7803 if ((StrStr(message, "unknown host") != NULL)
7804 || (StrStr(message, "No remote directory") != NULL)
7805 || (StrStr(message, "not found") != NULL)
7806 || (StrStr(message, "No such file") != NULL)
7807 || (StrStr(message, "can't alloc") != NULL)
7808 || (StrStr(message, "Permission denied") != NULL)) {
7810 cps->maybeThinking = FALSE;
7811 snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
7812 _(cps->which), cps->program, cps->host, message);
7813 RemoveInputSource(cps->isr);
7814 DisplayFatalError(buf1, 0, 1);
7819 * Look for hint output
7821 if (sscanf(message, "Hint: %s", buf1) == 1) {
7822 if (cps == &first && hintRequested) {
7823 hintRequested = FALSE;
7824 if (ParseOneMove(buf1, forwardMostMove, &moveType,
7825 &fromX, &fromY, &toX, &toY, &promoChar)) {
7826 (void) CoordsToAlgebraic(boards[forwardMostMove],
7827 PosFlags(forwardMostMove),
7828 fromY, fromX, toY, toX, promoChar, buf1);
7829 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
7830 DisplayInformation(buf2);
7832 /* Hint move could not be parsed!? */
7833 snprintf(buf2, sizeof(buf2),
7834 _("Illegal hint move \"%s\"\nfrom %s chess program"),
7835 buf1, _(cps->which));
7836 DisplayError(buf2, 0);
7839 safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
7845 * Ignore other messages if game is not in progress
7847 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
7848 gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
7851 * look for win, lose, draw, or draw offer
7853 if (strncmp(message, "1-0", 3) == 0) {
7854 char *p, *q, *r = "";
7855 p = strchr(message, '{');
7863 GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
7865 } else if (strncmp(message, "0-1", 3) == 0) {
7866 char *p, *q, *r = "";
7867 p = strchr(message, '{');
7875 /* Kludge for Arasan 4.1 bug */
7876 if (strcmp(r, "Black resigns") == 0) {
7877 GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
7880 GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
7882 } else if (strncmp(message, "1/2", 3) == 0) {
7883 char *p, *q, *r = "";
7884 p = strchr(message, '{');
7893 GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
7896 } else if (strncmp(message, "White resign", 12) == 0) {
7897 GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
7899 } else if (strncmp(message, "Black resign", 12) == 0) {
7900 GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
7902 } else if (strncmp(message, "White matches", 13) == 0 ||
7903 strncmp(message, "Black matches", 13) == 0 ) {
7904 /* [HGM] ignore GNUShogi noises */
7906 } else if (strncmp(message, "White", 5) == 0 &&
7907 message[5] != '(' &&
7908 StrStr(message, "Black") == NULL) {
7909 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7911 } else if (strncmp(message, "Black", 5) == 0 &&
7912 message[5] != '(') {
7913 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7915 } else if (strcmp(message, "resign") == 0 ||
7916 strcmp(message, "computer resigns") == 0) {
7918 case MachinePlaysBlack:
7919 case IcsPlayingBlack:
7920 GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
7922 case MachinePlaysWhite:
7923 case IcsPlayingWhite:
7924 GameEnds(BlackWins, "White resigns", GE_ENGINE);
7926 case TwoMachinesPlay:
7927 if (cps->twoMachinesColor[0] == 'w')
7928 GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
7930 GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
7937 } else if (strncmp(message, "opponent mates", 14) == 0) {
7939 case MachinePlaysBlack:
7940 case IcsPlayingBlack:
7941 GameEnds(WhiteWins, "White mates", GE_ENGINE);
7943 case MachinePlaysWhite:
7944 case IcsPlayingWhite:
7945 GameEnds(BlackWins, "Black mates", GE_ENGINE);
7947 case TwoMachinesPlay:
7948 if (cps->twoMachinesColor[0] == 'w')
7949 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7951 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7958 } else if (strncmp(message, "computer mates", 14) == 0) {
7960 case MachinePlaysBlack:
7961 case IcsPlayingBlack:
7962 GameEnds(BlackWins, "Black mates", GE_ENGINE1);
7964 case MachinePlaysWhite:
7965 case IcsPlayingWhite:
7966 GameEnds(WhiteWins, "White mates", GE_ENGINE);
7968 case TwoMachinesPlay:
7969 if (cps->twoMachinesColor[0] == 'w')
7970 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7972 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7979 } else if (strncmp(message, "checkmate", 9) == 0) {
7980 if (WhiteOnMove(forwardMostMove)) {
7981 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7983 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7986 } else if (strstr(message, "Draw") != NULL ||
7987 strstr(message, "game is a draw") != NULL) {
7988 GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
7990 } else if (strstr(message, "offer") != NULL &&
7991 strstr(message, "draw") != NULL) {
7993 if (appData.zippyPlay && first.initDone) {
7994 /* Relay offer to ICS */
7995 SendToICS(ics_prefix);
7996 SendToICS("draw\n");
7999 cps->offeredDraw = 2; /* valid until this engine moves twice */
8000 if (gameMode == TwoMachinesPlay) {
8001 if (cps->other->offeredDraw) {
8002 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8003 /* [HGM] in two-machine mode we delay relaying draw offer */
8004 /* until after we also have move, to see if it is really claim */
8006 } else if (gameMode == MachinePlaysWhite ||
8007 gameMode == MachinePlaysBlack) {
8008 if (userOfferedDraw) {
8009 DisplayInformation(_("Machine accepts your draw offer"));
8010 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8012 DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
8019 * Look for thinking output
8021 if ( appData.showThinking // [HGM] thinking: test all options that cause this output
8022 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
8024 int plylev, mvleft, mvtot, curscore, time;
8025 char mvname[MOVE_LEN];
8029 int prefixHint = FALSE;
8030 mvname[0] = NULLCHAR;
8033 case MachinePlaysBlack:
8034 case IcsPlayingBlack:
8035 if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8037 case MachinePlaysWhite:
8038 case IcsPlayingWhite:
8039 if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8044 case IcsObserving: /* [DM] icsEngineAnalyze */
8045 if (!appData.icsEngineAnalyze) ignore = TRUE;
8047 case TwoMachinesPlay:
8048 if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
8058 ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
8060 if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8061 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
8063 if (plyext != ' ' && plyext != '\t') {
8067 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8068 if( cps->scoreIsAbsolute &&
8069 ( gameMode == MachinePlaysBlack ||
8070 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
8071 gameMode == IcsPlayingBlack || // [HGM] also add other situations where engine should report black POV
8072 (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
8073 !WhiteOnMove(currentMove)
8076 curscore = -curscore;
8080 tempStats.depth = plylev;
8081 tempStats.nodes = nodes;
8082 tempStats.time = time;
8083 tempStats.score = curscore;
8084 tempStats.got_only_move = 0;
8086 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
8089 if(cps->nps == 0) ticklen = 10*time; // use engine reported time
8090 else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
8091 if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
8092 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
8093 whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
8094 if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
8095 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
8096 blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
8099 /* Buffer overflow protection */
8100 if (buf1[0] != NULLCHAR) {
8101 if (strlen(buf1) >= sizeof(tempStats.movelist)
8102 && appData.debugMode) {
8104 "PV is too long; using the first %u bytes.\n",
8105 (unsigned) sizeof(tempStats.movelist) - 1);
8108 safeStrCpy( tempStats.movelist, buf1, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
8110 sprintf(tempStats.movelist, " no PV\n");
8113 if (tempStats.seen_stat) {
8114 tempStats.ok_to_send = 1;
8117 if (strchr(tempStats.movelist, '(') != NULL) {
8118 tempStats.line_is_book = 1;
8119 tempStats.nr_moves = 0;
8120 tempStats.moves_left = 0;
8122 tempStats.line_is_book = 0;
8125 if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
8126 programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
8128 SendProgramStatsToFrontend( cps, &tempStats );
8131 [AS] Protect the thinkOutput buffer from overflow... this
8132 is only useful if buf1 hasn't overflowed first!
8134 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%+.2f %s%s",
8136 (gameMode == TwoMachinesPlay ?
8137 ToUpper(cps->twoMachinesColor[0]) : ' '),
8138 ((double) curscore) / 100.0,
8139 prefixHint ? lastHint : "",
8140 prefixHint ? " " : "" );
8142 if( buf1[0] != NULLCHAR ) {
8143 unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
8145 if( strlen(buf1) > max_len ) {
8146 if( appData.debugMode) {
8147 fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
8149 buf1[max_len+1] = '\0';
8152 strcat( thinkOutput, buf1 );
8155 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
8156 || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8157 DisplayMove(currentMove - 1);
8161 } else if ((p=StrStr(message, "(only move)")) != NULL) {
8162 /* crafty (9.25+) says "(only move) <move>"
8163 * if there is only 1 legal move
8165 sscanf(p, "(only move) %s", buf1);
8166 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
8167 sprintf(programStats.movelist, "%s (only move)", buf1);
8168 programStats.depth = 1;
8169 programStats.nr_moves = 1;
8170 programStats.moves_left = 1;
8171 programStats.nodes = 1;
8172 programStats.time = 1;
8173 programStats.got_only_move = 1;
8175 /* Not really, but we also use this member to
8176 mean "line isn't going to change" (Crafty
8177 isn't searching, so stats won't change) */
8178 programStats.line_is_book = 1;
8180 SendProgramStatsToFrontend( cps, &programStats );
8182 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8183 gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8184 DisplayMove(currentMove - 1);
8187 } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
8188 &time, &nodes, &plylev, &mvleft,
8189 &mvtot, mvname) >= 5) {
8190 /* The stat01: line is from Crafty (9.29+) in response
8191 to the "." command */
8192 programStats.seen_stat = 1;
8193 cps->maybeThinking = TRUE;
8195 if (programStats.got_only_move || !appData.periodicUpdates)
8198 programStats.depth = plylev;
8199 programStats.time = time;
8200 programStats.nodes = nodes;
8201 programStats.moves_left = mvleft;
8202 programStats.nr_moves = mvtot;
8203 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
8204 programStats.ok_to_send = 1;
8205 programStats.movelist[0] = '\0';
8207 SendProgramStatsToFrontend( cps, &programStats );
8211 } else if (strncmp(message,"++",2) == 0) {
8212 /* Crafty 9.29+ outputs this */
8213 programStats.got_fail = 2;
8216 } else if (strncmp(message,"--",2) == 0) {
8217 /* Crafty 9.29+ outputs this */
8218 programStats.got_fail = 1;
8221 } else if (thinkOutput[0] != NULLCHAR &&
8222 strncmp(message, " ", 4) == 0) {
8223 unsigned message_len;
8226 while (*p && *p == ' ') p++;
8228 message_len = strlen( p );
8230 /* [AS] Avoid buffer overflow */
8231 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
8232 strcat(thinkOutput, " ");
8233 strcat(thinkOutput, p);
8236 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
8237 strcat(programStats.movelist, " ");
8238 strcat(programStats.movelist, p);
8241 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8242 gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8243 DisplayMove(currentMove - 1);
8251 if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8252 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
8254 ChessProgramStats cpstats;
8256 if (plyext != ' ' && plyext != '\t') {
8260 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8261 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
8262 curscore = -curscore;
8265 cpstats.depth = plylev;
8266 cpstats.nodes = nodes;
8267 cpstats.time = time;
8268 cpstats.score = curscore;
8269 cpstats.got_only_move = 0;
8270 cpstats.movelist[0] = '\0';
8272 if (buf1[0] != NULLCHAR) {
8273 safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
8276 cpstats.ok_to_send = 0;
8277 cpstats.line_is_book = 0;
8278 cpstats.nr_moves = 0;
8279 cpstats.moves_left = 0;
8281 SendProgramStatsToFrontend( cps, &cpstats );
8288 /* Parse a game score from the character string "game", and
8289 record it as the history of the current game. The game
8290 score is NOT assumed to start from the standard position.
8291 The display is not updated in any way.
8294 ParseGameHistory(game)
8298 int fromX, fromY, toX, toY, boardIndex;
8303 if (appData.debugMode)
8304 fprintf(debugFP, "Parsing game history: %s\n", game);
8306 if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
8307 gameInfo.site = StrSave(appData.icsHost);
8308 gameInfo.date = PGNDate();
8309 gameInfo.round = StrSave("-");
8311 /* Parse out names of players */
8312 while (*game == ' ') game++;
8314 while (*game != ' ') *p++ = *game++;
8316 gameInfo.white = StrSave(buf);
8317 while (*game == ' ') game++;
8319 while (*game != ' ' && *game != '\n') *p++ = *game++;
8321 gameInfo.black = StrSave(buf);
8324 boardIndex = blackPlaysFirst ? 1 : 0;
8327 yyboardindex = boardIndex;
8328 moveType = (ChessMove) Myylex();
8330 case IllegalMove: /* maybe suicide chess, etc. */
8331 if (appData.debugMode) {
8332 fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
8333 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8334 setbuf(debugFP, NULL);
8336 case WhitePromotion:
8337 case BlackPromotion:
8338 case WhiteNonPromotion:
8339 case BlackNonPromotion:
8341 case WhiteCapturesEnPassant:
8342 case BlackCapturesEnPassant:
8343 case WhiteKingSideCastle:
8344 case WhiteQueenSideCastle:
8345 case BlackKingSideCastle:
8346 case BlackQueenSideCastle:
8347 case WhiteKingSideCastleWild:
8348 case WhiteQueenSideCastleWild:
8349 case BlackKingSideCastleWild:
8350 case BlackQueenSideCastleWild:
8352 case WhiteHSideCastleFR:
8353 case WhiteASideCastleFR:
8354 case BlackHSideCastleFR:
8355 case BlackASideCastleFR:
8357 fromX = currentMoveString[0] - AAA;
8358 fromY = currentMoveString[1] - ONE;
8359 toX = currentMoveString[2] - AAA;
8360 toY = currentMoveString[3] - ONE;
8361 promoChar = currentMoveString[4];
8365 fromX = moveType == WhiteDrop ?
8366 (int) CharToPiece(ToUpper(currentMoveString[0])) :
8367 (int) CharToPiece(ToLower(currentMoveString[0]));
8369 toX = currentMoveString[2] - AAA;
8370 toY = currentMoveString[3] - ONE;
8371 promoChar = NULLCHAR;
8375 snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
8376 if (appData.debugMode) {
8377 fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
8378 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8379 setbuf(debugFP, NULL);
8381 DisplayError(buf, 0);
8383 case ImpossibleMove:
8385 snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
8386 if (appData.debugMode) {
8387 fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
8388 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8389 setbuf(debugFP, NULL);
8391 DisplayError(buf, 0);
8394 if (boardIndex < backwardMostMove) {
8395 /* Oops, gap. How did that happen? */
8396 DisplayError(_("Gap in move list"), 0);
8399 backwardMostMove = blackPlaysFirst ? 1 : 0;
8400 if (boardIndex > forwardMostMove) {
8401 forwardMostMove = boardIndex;
8405 if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
8406 strcat(parseList[boardIndex-1], " ");
8407 strcat(parseList[boardIndex-1], yy_text);
8419 case GameUnfinished:
8420 if (gameMode == IcsExamining) {
8421 if (boardIndex < backwardMostMove) {
8422 /* Oops, gap. How did that happen? */
8425 backwardMostMove = blackPlaysFirst ? 1 : 0;
8428 gameInfo.result = moveType;
8429 p = strchr(yy_text, '{');
8430 if (p == NULL) p = strchr(yy_text, '(');
8433 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
8435 q = strchr(p, *p == '{' ? '}' : ')');
8436 if (q != NULL) *q = NULLCHAR;
8439 while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
8440 gameInfo.resultDetails = StrSave(p);
8443 if (boardIndex >= forwardMostMove &&
8444 !(gameMode == IcsObserving && ics_gamenum == -1)) {
8445 backwardMostMove = blackPlaysFirst ? 1 : 0;
8448 (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
8449 fromY, fromX, toY, toX, promoChar,
8450 parseList[boardIndex]);
8451 CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
8452 /* currentMoveString is set as a side-effect of yylex */
8453 safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
8454 strcat(moveList[boardIndex], "\n");
8456 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
8457 switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
8463 if(gameInfo.variant != VariantShogi)
8464 strcat(parseList[boardIndex - 1], "+");
8468 strcat(parseList[boardIndex - 1], "#");
8475 /* Apply a move to the given board */
8477 ApplyMove(fromX, fromY, toX, toY, promoChar, board)
8478 int fromX, fromY, toX, toY;
8482 ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
8483 int promoRank = gameInfo.variant == VariantMakruk ? 3 : 1;
8485 /* [HGM] compute & store e.p. status and castling rights for new position */
8486 /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
8488 if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
8489 oldEP = (signed char)board[EP_STATUS];
8490 board[EP_STATUS] = EP_NONE;
8492 if( board[toY][toX] != EmptySquare )
8493 board[EP_STATUS] = EP_CAPTURE;
8495 if (fromY == DROP_RANK) {
8497 piece = board[toY][toX] = (ChessSquare) fromX;
8501 if( board[fromY][fromX] == WhiteLance || board[fromY][fromX] == BlackLance ) {
8502 if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi )
8503 board[EP_STATUS] = EP_PAWN_MOVE; // Lance is Pawn-like in most variants
8505 if( board[fromY][fromX] == WhitePawn ) {
8506 if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
8507 board[EP_STATUS] = EP_PAWN_MOVE;
8509 if(toX>BOARD_LEFT && board[toY][toX-1] == BlackPawn &&
8510 gameInfo.variant != VariantBerolina || toX < fromX)
8511 board[EP_STATUS] = toX | berolina;
8512 if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
8513 gameInfo.variant != VariantBerolina || toX > fromX)
8514 board[EP_STATUS] = toX;
8517 if( board[fromY][fromX] == BlackPawn ) {
8518 if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
8519 board[EP_STATUS] = EP_PAWN_MOVE;
8520 if( toY-fromY== -2) {
8521 if(toX>BOARD_LEFT && board[toY][toX-1] == WhitePawn &&
8522 gameInfo.variant != VariantBerolina || toX < fromX)
8523 board[EP_STATUS] = toX | berolina;
8524 if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
8525 gameInfo.variant != VariantBerolina || toX > fromX)
8526 board[EP_STATUS] = toX;
8530 for(i=0; i<nrCastlingRights; i++) {
8531 if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
8532 board[CASTLING][i] == toX && castlingRank[i] == toY
8533 ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
8536 if (fromX == toX && fromY == toY) return;
8538 piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
8539 king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
8540 if(gameInfo.variant == VariantKnightmate)
8541 king += (int) WhiteUnicorn - (int) WhiteKing;
8543 /* Code added by Tord: */
8544 /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
8545 if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
8546 board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
8547 board[fromY][fromX] = EmptySquare;
8548 board[toY][toX] = EmptySquare;
8549 if((toX > fromX) != (piece == WhiteRook)) {
8550 board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
8552 board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
8554 } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
8555 board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
8556 board[fromY][fromX] = EmptySquare;
8557 board[toY][toX] = EmptySquare;
8558 if((toX > fromX) != (piece == BlackRook)) {
8559 board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
8561 board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
8563 /* End of code added by Tord */
8565 } else if (board[fromY][fromX] == king
8566 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8567 && toY == fromY && toX > fromX+1) {
8568 board[fromY][fromX] = EmptySquare;
8569 board[toY][toX] = king;
8570 board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
8571 board[fromY][BOARD_RGHT-1] = EmptySquare;
8572 } else if (board[fromY][fromX] == king
8573 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8574 && toY == fromY && toX < fromX-1) {
8575 board[fromY][fromX] = EmptySquare;
8576 board[toY][toX] = king;
8577 board[toY][toX+1] = board[fromY][BOARD_LEFT];
8578 board[fromY][BOARD_LEFT] = EmptySquare;
8579 } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
8580 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
8581 && toY >= BOARD_HEIGHT-promoRank
8583 /* white pawn promotion */
8584 board[toY][toX] = CharToPiece(ToUpper(promoChar));
8585 if (board[toY][toX] == EmptySquare) {
8586 board[toY][toX] = WhiteQueen;
8588 if(gameInfo.variant==VariantBughouse ||
8589 gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
8590 board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
8591 board[fromY][fromX] = EmptySquare;
8592 } else if ((fromY == BOARD_HEIGHT-4)
8594 && gameInfo.variant != VariantXiangqi
8595 && gameInfo.variant != VariantBerolina
8596 && (board[fromY][fromX] == WhitePawn)
8597 && (board[toY][toX] == EmptySquare)) {
8598 board[fromY][fromX] = EmptySquare;
8599 board[toY][toX] = WhitePawn;
8600 captured = board[toY - 1][toX];
8601 board[toY - 1][toX] = EmptySquare;
8602 } else if ((fromY == BOARD_HEIGHT-4)
8604 && gameInfo.variant == VariantBerolina
8605 && (board[fromY][fromX] == WhitePawn)
8606 && (board[toY][toX] == EmptySquare)) {
8607 board[fromY][fromX] = EmptySquare;
8608 board[toY][toX] = WhitePawn;
8609 if(oldEP & EP_BEROLIN_A) {
8610 captured = board[fromY][fromX-1];
8611 board[fromY][fromX-1] = EmptySquare;
8612 }else{ captured = board[fromY][fromX+1];
8613 board[fromY][fromX+1] = EmptySquare;
8615 } else if (board[fromY][fromX] == king
8616 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8617 && toY == fromY && toX > fromX+1) {
8618 board[fromY][fromX] = EmptySquare;
8619 board[toY][toX] = king;
8620 board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
8621 board[fromY][BOARD_RGHT-1] = EmptySquare;
8622 } else if (board[fromY][fromX] == king
8623 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8624 && toY == fromY && toX < fromX-1) {
8625 board[fromY][fromX] = EmptySquare;
8626 board[toY][toX] = king;
8627 board[toY][toX+1] = board[fromY][BOARD_LEFT];
8628 board[fromY][BOARD_LEFT] = EmptySquare;
8629 } else if (fromY == 7 && fromX == 3
8630 && board[fromY][fromX] == BlackKing
8631 && toY == 7 && toX == 5) {
8632 board[fromY][fromX] = EmptySquare;
8633 board[toY][toX] = BlackKing;
8634 board[fromY][7] = EmptySquare;
8635 board[toY][4] = BlackRook;
8636 } else if (fromY == 7 && fromX == 3
8637 && board[fromY][fromX] == BlackKing
8638 && toY == 7 && toX == 1) {
8639 board[fromY][fromX] = EmptySquare;
8640 board[toY][toX] = BlackKing;
8641 board[fromY][0] = EmptySquare;
8642 board[toY][2] = BlackRook;
8643 } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
8644 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
8647 /* black pawn promotion */
8648 board[toY][toX] = CharToPiece(ToLower(promoChar));
8649 if (board[toY][toX] == EmptySquare) {
8650 board[toY][toX] = BlackQueen;
8652 if(gameInfo.variant==VariantBughouse ||
8653 gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
8654 board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
8655 board[fromY][fromX] = EmptySquare;
8656 } else if ((fromY == 3)
8658 && gameInfo.variant != VariantXiangqi
8659 && gameInfo.variant != VariantBerolina
8660 && (board[fromY][fromX] == BlackPawn)
8661 && (board[toY][toX] == EmptySquare)) {
8662 board[fromY][fromX] = EmptySquare;
8663 board[toY][toX] = BlackPawn;
8664 captured = board[toY + 1][toX];
8665 board[toY + 1][toX] = EmptySquare;
8666 } else if ((fromY == 3)
8668 && gameInfo.variant == VariantBerolina
8669 && (board[fromY][fromX] == BlackPawn)
8670 && (board[toY][toX] == EmptySquare)) {
8671 board[fromY][fromX] = EmptySquare;
8672 board[toY][toX] = BlackPawn;
8673 if(oldEP & EP_BEROLIN_A) {
8674 captured = board[fromY][fromX-1];
8675 board[fromY][fromX-1] = EmptySquare;
8676 }else{ captured = board[fromY][fromX+1];
8677 board[fromY][fromX+1] = EmptySquare;
8680 board[toY][toX] = board[fromY][fromX];
8681 board[fromY][fromX] = EmptySquare;
8685 if (gameInfo.holdingsWidth != 0) {
8687 /* !!A lot more code needs to be written to support holdings */
8688 /* [HGM] OK, so I have written it. Holdings are stored in the */
8689 /* penultimate board files, so they are automaticlly stored */
8690 /* in the game history. */
8691 if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
8692 && promoChar && piece != WhitePawn && piece != BlackPawn) {
8693 /* Delete from holdings, by decreasing count */
8694 /* and erasing image if necessary */
8695 p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
8696 if(p < (int) BlackPawn) { /* white drop */
8697 p -= (int)WhitePawn;
8698 p = PieceToNumber((ChessSquare)p);
8699 if(p >= gameInfo.holdingsSize) p = 0;
8700 if(--board[p][BOARD_WIDTH-2] <= 0)
8701 board[p][BOARD_WIDTH-1] = EmptySquare;
8702 if((int)board[p][BOARD_WIDTH-2] < 0)
8703 board[p][BOARD_WIDTH-2] = 0;
8704 } else { /* black drop */
8705 p -= (int)BlackPawn;
8706 p = PieceToNumber((ChessSquare)p);
8707 if(p >= gameInfo.holdingsSize) p = 0;
8708 if(--board[BOARD_HEIGHT-1-p][1] <= 0)
8709 board[BOARD_HEIGHT-1-p][0] = EmptySquare;
8710 if((int)board[BOARD_HEIGHT-1-p][1] < 0)
8711 board[BOARD_HEIGHT-1-p][1] = 0;
8714 if (captured != EmptySquare && gameInfo.holdingsSize > 0
8715 && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess ) {
8716 /* [HGM] holdings: Add to holdings, if holdings exist */
8717 if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
8718 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
8719 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
8722 if (p >= (int) BlackPawn) {
8723 p -= (int)BlackPawn;
8724 if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
8725 /* in Shogi restore piece to its original first */
8726 captured = (ChessSquare) (DEMOTED captured);
8729 p = PieceToNumber((ChessSquare)p);
8730 if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
8731 board[p][BOARD_WIDTH-2]++;
8732 board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
8734 p -= (int)WhitePawn;
8735 if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
8736 captured = (ChessSquare) (DEMOTED captured);
8739 p = PieceToNumber((ChessSquare)p);
8740 if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
8741 board[BOARD_HEIGHT-1-p][1]++;
8742 board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
8745 } else if (gameInfo.variant == VariantAtomic) {
8746 if (captured != EmptySquare) {
8748 for (y = toY-1; y <= toY+1; y++) {
8749 for (x = toX-1; x <= toX+1; x++) {
8750 if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
8751 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
8752 board[y][x] = EmptySquare;
8756 board[toY][toX] = EmptySquare;
8759 if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
8760 board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
8762 if(promoChar == '+') {
8763 /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite orinary Pawn promotion) */
8764 board[toY][toX] = (ChessSquare) (PROMOTED piece);
8765 } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
8766 board[toY][toX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
8768 if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
8769 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
8770 // [HGM] superchess: take promotion piece out of holdings
8771 int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
8772 if((int)piece < (int)BlackPawn) { // determine stm from piece color
8773 if(!--board[k][BOARD_WIDTH-2])
8774 board[k][BOARD_WIDTH-1] = EmptySquare;
8776 if(!--board[BOARD_HEIGHT-1-k][1])
8777 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
8783 /* Updates forwardMostMove */
8785 MakeMove(fromX, fromY, toX, toY, promoChar)
8786 int fromX, fromY, toX, toY;
8789 // forwardMostMove++; // [HGM] bare: moved downstream
8791 if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
8792 int timeLeft; static int lastLoadFlag=0; int king, piece;
8793 piece = boards[forwardMostMove][fromY][fromX];
8794 king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
8795 if(gameInfo.variant == VariantKnightmate)
8796 king += (int) WhiteUnicorn - (int) WhiteKing;
8797 if(forwardMostMove == 0) {
8799 fprintf(serverMoves, "%s;", second.tidy);
8800 fprintf(serverMoves, "%s;", first.tidy);
8801 if(!blackPlaysFirst)
8802 fprintf(serverMoves, "%s;", second.tidy);
8803 } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
8804 lastLoadFlag = loadFlag;
8806 fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
8807 // print castling suffix
8808 if( toY == fromY && piece == king ) {
8810 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
8812 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
8815 if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
8816 boards[forwardMostMove][fromY][fromX] == BlackPawn ) &&
8817 boards[forwardMostMove][toY][toX] == EmptySquare
8818 && fromX != toX && fromY != toY)
8819 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
8821 if(promoChar != NULLCHAR)
8822 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);
8824 fprintf(serverMoves, "/%d/%d",
8825 pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
8826 if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
8827 else timeLeft = blackTimeRemaining/1000;
8828 fprintf(serverMoves, "/%d", timeLeft);
8830 fflush(serverMoves);
8833 if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations
8834 DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
8838 UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
8839 if (commentList[forwardMostMove+1] != NULL) {
8840 free(commentList[forwardMostMove+1]);
8841 commentList[forwardMostMove+1] = NULL;
8843 CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
8844 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
8845 // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
8846 SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
8847 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
8848 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
8849 gameInfo.result = GameUnfinished;
8850 if (gameInfo.resultDetails != NULL) {
8851 free(gameInfo.resultDetails);
8852 gameInfo.resultDetails = NULL;
8854 CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
8855 moveList[forwardMostMove - 1]);
8856 (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
8857 PosFlags(forwardMostMove - 1),
8858 fromY, fromX, toY, toX, promoChar,
8859 parseList[forwardMostMove - 1]);
8860 switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
8866 if(gameInfo.variant != VariantShogi)
8867 strcat(parseList[forwardMostMove - 1], "+");
8871 strcat(parseList[forwardMostMove - 1], "#");
8874 if (appData.debugMode) {
8875 fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
8880 /* Updates currentMove if not pausing */
8882 ShowMove(fromX, fromY, toX, toY)
8884 int instant = (gameMode == PlayFromGameFile) ?
8885 (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
8886 if(appData.noGUI) return;
8887 if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
8889 if (forwardMostMove == currentMove + 1) {
8890 AnimateMove(boards[forwardMostMove - 1],
8891 fromX, fromY, toX, toY);
8893 if (appData.highlightLastMove) {
8894 SetHighlights(fromX, fromY, toX, toY);
8897 currentMove = forwardMostMove;
8900 if (instant) return;
8902 DisplayMove(currentMove - 1);
8903 DrawPosition(FALSE, boards[currentMove]);
8904 DisplayBothClocks();
8905 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
8908 void SendEgtPath(ChessProgramState *cps)
8909 { /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
8910 char buf[MSG_SIZ], name[MSG_SIZ], *p;
8912 if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
8915 char c, *q = name+1, *r, *s;
8917 name[0] = ','; // extract next format name from feature and copy with prefixed ','
8918 while(*p && *p != ',') *q++ = *p++;
8920 if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
8921 strcmp(name, ",nalimov:") == 0 ) {
8922 // take nalimov path from the menu-changeable option first, if it is defined
8923 snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
8924 SendToProgram(buf,cps); // send egtbpath command for nalimov
8926 if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
8927 (s = StrStr(appData.egtFormats, name)) != NULL) {
8928 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
8929 s = r = StrStr(s, ":") + 1; // beginning of path info
8930 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
8931 c = *r; *r = 0; // temporarily null-terminate path info
8932 *--q = 0; // strip of trailig ':' from name
8933 snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
8935 SendToProgram(buf,cps); // send egtbpath command for this format
8937 if(*p == ',') p++; // read away comma to position for next format name
8942 InitChessProgram(cps, setup)
8943 ChessProgramState *cps;
8944 int setup; /* [HGM] needed to setup FRC opening position */
8946 char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
8947 if (appData.noChessProgram) return;
8948 hintRequested = FALSE;
8949 bookRequested = FALSE;
8951 /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
8952 /* moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
8953 if(cps->memSize) { /* [HGM] memory */
8954 snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
8955 SendToProgram(buf, cps);
8957 SendEgtPath(cps); /* [HGM] EGT */
8958 if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
8959 snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
8960 SendToProgram(buf, cps);
8963 SendToProgram(cps->initString, cps);
8964 if (gameInfo.variant != VariantNormal &&
8965 gameInfo.variant != VariantLoadable
8966 /* [HGM] also send variant if board size non-standard */
8967 || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
8969 char *v = VariantName(gameInfo.variant);
8970 if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
8971 /* [HGM] in protocol 1 we have to assume all variants valid */
8972 snprintf(buf, MSG_SIZ, _("Variant %s not supported by %s"), v, cps->tidy);
8973 DisplayFatalError(buf, 0, 1);
8977 /* [HGM] make prefix for non-standard board size. Awkward testing... */
8978 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8979 if( gameInfo.variant == VariantXiangqi )
8980 overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
8981 if( gameInfo.variant == VariantShogi )
8982 overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
8983 if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
8984 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
8985 if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
8986 gameInfo.variant == VariantGothic || gameInfo.variant == VariantFalcon || gameInfo.variant == VariantJanus )
8987 overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8988 if( gameInfo.variant == VariantCourier )
8989 overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8990 if( gameInfo.variant == VariantSuper )
8991 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
8992 if( gameInfo.variant == VariantGreat )
8993 overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
8994 if( gameInfo.variant == VariantSChess )
8995 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 7;
8998 snprintf(b, MSG_SIZ, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
8999 gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
9000 /* [HGM] varsize: try first if this defiant size variant is specifically known */
9001 if(StrStr(cps->variants, b) == NULL) {
9002 // specific sized variant not known, check if general sizing allowed
9003 if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
9004 if(StrStr(cps->variants, "boardsize") == NULL) {
9005 snprintf(buf, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
9006 gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
9007 DisplayFatalError(buf, 0, 1);
9010 /* [HGM] here we really should compare with the maximum supported board size */
9013 } else snprintf(b, MSG_SIZ,"%s", VariantName(gameInfo.variant));
9014 snprintf(buf, MSG_SIZ, "variant %s\n", b);
9015 SendToProgram(buf, cps);
9017 currentlyInitializedVariant = gameInfo.variant;
9019 /* [HGM] send opening position in FRC to first engine */
9021 SendToProgram("force\n", cps);
9023 /* engine is now in force mode! Set flag to wake it up after first move. */
9024 setboardSpoiledMachineBlack = 1;
9028 snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
9029 SendToProgram(buf, cps);
9031 cps->maybeThinking = FALSE;
9032 cps->offeredDraw = 0;
9033 if (!appData.icsActive) {
9034 SendTimeControl(cps, movesPerSession, timeControl,
9035 timeIncrement, appData.searchDepth,
9038 if (appData.showThinking
9039 // [HGM] thinking: four options require thinking output to be sent
9040 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
9042 SendToProgram("post\n", cps);
9044 SendToProgram("hard\n", cps);
9045 if (!appData.ponderNextMove) {
9046 /* Warning: "easy" is a toggle in GNU Chess, so don't send
9047 it without being sure what state we are in first. "hard"
9048 is not a toggle, so that one is OK.
9050 SendToProgram("easy\n", cps);
9053 snprintf(buf, MSG_SIZ, "ping %d\n", ++cps->lastPing);
9054 SendToProgram(buf, cps);
9056 cps->initDone = TRUE;
9061 StartChessProgram(cps)
9062 ChessProgramState *cps;
9067 if (appData.noChessProgram) return;
9068 cps->initDone = FALSE;
9070 if (strcmp(cps->host, "localhost") == 0) {
9071 err = StartChildProcess(cps->program, cps->dir, &cps->pr);
9072 } else if (*appData.remoteShell == NULLCHAR) {
9073 err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
9075 if (*appData.remoteUser == NULLCHAR) {
9076 snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
9079 snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
9080 cps->host, appData.remoteUser, cps->program);
9082 err = StartChildProcess(buf, "", &cps->pr);
9086 snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
9087 DisplayFatalError(buf, err, 1);
9093 cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
9094 if (cps->protocolVersion > 1) {
9095 snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
9096 cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
9097 cps->comboCnt = 0; // and values of combo boxes
9098 SendToProgram(buf, cps);
9100 SendToProgram("xboard\n", cps);
9106 TwoMachinesEventIfReady P((void))
9108 if (first.lastPing != first.lastPong) {
9109 DisplayMessage("", _("Waiting for first chess program"));
9110 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9113 if (second.lastPing != second.lastPong) {
9114 DisplayMessage("", _("Waiting for second chess program"));
9115 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9123 NextMatchGame P((void))
9125 int index; /* [HGM] autoinc: step load index during match */
9127 if (*appData.loadGameFile != NULLCHAR) {
9128 index = appData.loadGameIndex;
9129 if(index < 0) { // [HGM] autoinc
9130 lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
9131 if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
9133 LoadGameFromFile(appData.loadGameFile,
9135 appData.loadGameFile, FALSE);
9136 } else if (*appData.loadPositionFile != NULLCHAR) {
9137 index = appData.loadPositionIndex;
9138 if(index < 0) { // [HGM] autoinc
9139 lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
9140 if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
9142 LoadPositionFromFile(appData.loadPositionFile,
9144 appData.loadPositionFile);
9146 TwoMachinesEventIfReady();
9149 void UserAdjudicationEvent( int result )
9151 ChessMove gameResult = GameIsDrawn;
9154 gameResult = WhiteWins;
9156 else if( result < 0 ) {
9157 gameResult = BlackWins;
9160 if( gameMode == TwoMachinesPlay ) {
9161 GameEnds( gameResult, "User adjudication", GE_XBOARD );
9166 // [HGM] save: calculate checksum of game to make games easily identifiable
9167 int StringCheckSum(char *s)
9170 if(s==NULL) return 0;
9171 while(*s) i = i*259 + *s++;
9178 for(i=backwardMostMove; i<forwardMostMove; i++) {
9179 sum += pvInfoList[i].depth;
9180 sum += StringCheckSum(parseList[i]);
9181 sum += StringCheckSum(commentList[i]);
9184 if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
9185 return sum + StringCheckSum(commentList[i]);
9186 } // end of save patch
9189 GameEnds(result, resultDetails, whosays)
9191 char *resultDetails;
9194 GameMode nextGameMode;
9196 char buf[MSG_SIZ], popupRequested = 0;
9198 if(endingGame) return; /* [HGM] crash: forbid recursion */
9200 if(twoBoards) { // [HGM] dual: switch back to one board
9201 twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
9202 DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
9204 if (appData.debugMode) {
9205 fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
9206 result, resultDetails ? resultDetails : "(null)", whosays);
9209 fromX = fromY = -1; // [HGM] abort any move the user is entering.
9211 if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
9212 /* If we are playing on ICS, the server decides when the
9213 game is over, but the engine can offer to draw, claim
9217 if (appData.zippyPlay && first.initDone) {
9218 if (result == GameIsDrawn) {
9219 /* In case draw still needs to be claimed */
9220 SendToICS(ics_prefix);
9221 SendToICS("draw\n");
9222 } else if (StrCaseStr(resultDetails, "resign")) {
9223 SendToICS(ics_prefix);
9224 SendToICS("resign\n");
9228 endingGame = 0; /* [HGM] crash */
9232 /* If we're loading the game from a file, stop */
9233 if (whosays == GE_FILE) {
9234 (void) StopLoadGameTimer();
9238 /* Cancel draw offers */
9239 first.offeredDraw = second.offeredDraw = 0;
9241 /* If this is an ICS game, only ICS can really say it's done;
9242 if not, anyone can. */
9243 isIcsGame = (gameMode == IcsPlayingWhite ||
9244 gameMode == IcsPlayingBlack ||
9245 gameMode == IcsObserving ||
9246 gameMode == IcsExamining);
9248 if (!isIcsGame || whosays == GE_ICS) {
9249 /* OK -- not an ICS game, or ICS said it was done */
9251 if (!isIcsGame && !appData.noChessProgram)
9252 SetUserThinkingEnables();
9254 /* [HGM] if a machine claims the game end we verify this claim */
9255 if(gameMode == TwoMachinesPlay && appData.testClaims) {
9256 if(appData.testLegality && whosays >= GE_ENGINE1 ) {
9258 ChessMove trueResult = (ChessMove) -1;
9260 claimer = whosays == GE_ENGINE1 ? /* color of claimer */
9261 first.twoMachinesColor[0] :
9262 second.twoMachinesColor[0] ;
9264 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
9265 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
9266 /* [HGM] verify: engine mate claims accepted if they were flagged */
9267 trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
9269 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
9270 /* [HGM] verify: engine mate claims accepted if they were flagged */
9271 trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
9273 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
9274 trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
9277 // now verify win claims, but not in drop games, as we don't understand those yet
9278 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
9279 || gameInfo.variant == VariantGreat) &&
9280 (result == WhiteWins && claimer == 'w' ||
9281 result == BlackWins && claimer == 'b' ) ) { // case to verify: engine claims own win
9282 if (appData.debugMode) {
9283 fprintf(debugFP, "result=%d sp=%d move=%d\n",
9284 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
9286 if(result != trueResult) {
9287 snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
9288 result = claimer == 'w' ? BlackWins : WhiteWins;
9289 resultDetails = buf;
9292 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
9293 && (forwardMostMove <= backwardMostMove ||
9294 (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
9295 (claimer=='b')==(forwardMostMove&1))
9297 /* [HGM] verify: draws that were not flagged are false claims */
9298 snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
9299 result = claimer == 'w' ? BlackWins : WhiteWins;
9300 resultDetails = buf;
9302 /* (Claiming a loss is accepted no questions asked!) */
9304 /* [HGM] bare: don't allow bare King to win */
9305 if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
9306 && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
9307 && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
9308 && result != GameIsDrawn)
9309 { int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
9310 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
9311 int p = (signed char)boards[forwardMostMove][i][j] - color;
9312 if(p >= 0 && p <= (int)WhiteKing) k++;
9314 if (appData.debugMode) {
9315 fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
9316 result, resultDetails ? resultDetails : "(null)", whosays, k, color);
9319 result = GameIsDrawn;
9320 snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
9321 resultDetails = buf;
9327 if(serverMoves != NULL && !loadFlag) { char c = '=';
9328 if(result==WhiteWins) c = '+';
9329 if(result==BlackWins) c = '-';
9330 if(resultDetails != NULL)
9331 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
9333 if (resultDetails != NULL) {
9334 gameInfo.result = result;
9335 gameInfo.resultDetails = StrSave(resultDetails);
9337 /* display last move only if game was not loaded from file */
9338 if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
9339 DisplayMove(currentMove - 1);
9341 if (forwardMostMove != 0) {
9342 if (gameMode != PlayFromGameFile && gameMode != EditGame
9343 && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
9345 if (*appData.saveGameFile != NULLCHAR) {
9346 SaveGameToFile(appData.saveGameFile, TRUE);
9347 } else if (appData.autoSaveGames) {
9350 if (*appData.savePositionFile != NULLCHAR) {
9351 SavePositionToFile(appData.savePositionFile);
9356 /* Tell program how game ended in case it is learning */
9357 /* [HGM] Moved this to after saving the PGN, just in case */
9358 /* engine died and we got here through time loss. In that */
9359 /* case we will get a fatal error writing the pipe, which */
9360 /* would otherwise lose us the PGN. */
9361 /* [HGM] crash: not needed anymore, but doesn't hurt; */
9362 /* output during GameEnds should never be fatal anymore */
9363 if (gameMode == MachinePlaysWhite ||
9364 gameMode == MachinePlaysBlack ||
9365 gameMode == TwoMachinesPlay ||
9366 gameMode == IcsPlayingWhite ||
9367 gameMode == IcsPlayingBlack ||
9368 gameMode == BeginningOfGame) {
9370 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
9372 if (first.pr != NoProc) {
9373 SendToProgram(buf, &first);
9375 if (second.pr != NoProc &&
9376 gameMode == TwoMachinesPlay) {
9377 SendToProgram(buf, &second);
9382 if (appData.icsActive) {
9383 if (appData.quietPlay &&
9384 (gameMode == IcsPlayingWhite ||
9385 gameMode == IcsPlayingBlack)) {
9386 SendToICS(ics_prefix);
9387 SendToICS("set shout 1\n");
9389 nextGameMode = IcsIdle;
9390 ics_user_moved = FALSE;
9391 /* clean up premove. It's ugly when the game has ended and the
9392 * premove highlights are still on the board.
9396 ClearPremoveHighlights();
9397 DrawPosition(FALSE, boards[currentMove]);
9399 if (whosays == GE_ICS) {
9402 if (gameMode == IcsPlayingWhite)
9404 else if(gameMode == IcsPlayingBlack)
9408 if (gameMode == IcsPlayingBlack)
9410 else if(gameMode == IcsPlayingWhite)
9417 PlayIcsUnfinishedSound();
9420 } else if (gameMode == EditGame ||
9421 gameMode == PlayFromGameFile ||
9422 gameMode == AnalyzeMode ||
9423 gameMode == AnalyzeFile) {
9424 nextGameMode = gameMode;
9426 nextGameMode = EndOfGame;
9431 nextGameMode = gameMode;
9434 if (appData.noChessProgram) {
9435 gameMode = nextGameMode;
9437 endingGame = 0; /* [HGM] crash */
9442 /* Put first chess program into idle state */
9443 if (first.pr != NoProc &&
9444 (gameMode == MachinePlaysWhite ||
9445 gameMode == MachinePlaysBlack ||
9446 gameMode == TwoMachinesPlay ||
9447 gameMode == IcsPlayingWhite ||
9448 gameMode == IcsPlayingBlack ||
9449 gameMode == BeginningOfGame)) {
9450 SendToProgram("force\n", &first);
9451 if (first.usePing) {
9453 snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
9454 SendToProgram(buf, &first);
9457 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
9458 /* Kill off first chess program */
9459 if (first.isr != NULL)
9460 RemoveInputSource(first.isr);
9463 if (first.pr != NoProc) {
9465 DoSleep( appData.delayBeforeQuit );
9466 SendToProgram("quit\n", &first);
9467 DoSleep( appData.delayAfterQuit );
9468 DestroyChildProcess(first.pr, first.useSigterm);
9473 /* Put second chess program into idle state */
9474 if (second.pr != NoProc &&
9475 gameMode == TwoMachinesPlay) {
9476 SendToProgram("force\n", &second);
9477 if (second.usePing) {
9479 snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
9480 SendToProgram(buf, &second);
9483 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
9484 /* Kill off second chess program */
9485 if (second.isr != NULL)
9486 RemoveInputSource(second.isr);
9489 if (second.pr != NoProc) {
9490 DoSleep( appData.delayBeforeQuit );
9491 SendToProgram("quit\n", &second);
9492 DoSleep( appData.delayAfterQuit );
9493 DestroyChildProcess(second.pr, second.useSigterm);
9498 if (matchMode && gameMode == TwoMachinesPlay) {
9501 if (first.twoMachinesColor[0] == 'w') {
9508 if (first.twoMachinesColor[0] == 'b') {
9517 if (matchGame < appData.matchGames) {
9519 if(appData.sameColorGames <= 1) { /* [HGM] alternate: suppress color swap */
9520 tmp = first.twoMachinesColor;
9521 first.twoMachinesColor = second.twoMachinesColor;
9522 second.twoMachinesColor = tmp;
9524 gameMode = nextGameMode;
9526 if(appData.matchPause>10000 || appData.matchPause<10)
9527 appData.matchPause = 10000; /* [HGM] make pause adjustable */
9528 ScheduleDelayedEvent(NextMatchGame, appData.matchPause);
9529 endingGame = 0; /* [HGM] crash */
9532 gameMode = nextGameMode;
9533 snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
9534 first.tidy, second.tidy,
9535 first.matchWins, second.matchWins,
9536 appData.matchGames - (first.matchWins + second.matchWins));
9537 popupRequested++; // [HGM] crash: postpone to after resetting endingGame
9538 if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
9539 first.twoMachinesColor = "black\n";
9540 second.twoMachinesColor = "white\n";
9542 first.twoMachinesColor = "white\n";
9543 second.twoMachinesColor = "black\n";
9547 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
9548 !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
9550 gameMode = nextGameMode;
9552 endingGame = 0; /* [HGM] crash */
9553 if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
9554 if(matchMode == TRUE) DisplayFatalError(buf, 0, 0); else {
9555 matchMode = FALSE; appData.matchGames = matchGame = 0;
9561 /* Assumes program was just initialized (initString sent).
9562 Leaves program in force mode. */
9564 FeedMovesToProgram(cps, upto)
9565 ChessProgramState *cps;
9570 if (appData.debugMode)
9571 fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
9572 startedFromSetupPosition ? "position and " : "",
9573 backwardMostMove, upto, cps->which);
9574 if(currentlyInitializedVariant != gameInfo.variant) {
9576 // [HGM] variantswitch: make engine aware of new variant
9577 if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
9578 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
9579 snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
9580 SendToProgram(buf, cps);
9581 currentlyInitializedVariant = gameInfo.variant;
9583 SendToProgram("force\n", cps);
9584 if (startedFromSetupPosition) {
9585 SendBoard(cps, backwardMostMove);
9586 if (appData.debugMode) {
9587 fprintf(debugFP, "feedMoves\n");
9590 for (i = backwardMostMove; i < upto; i++) {
9591 SendMoveToProgram(i, cps);
9597 ResurrectChessProgram()
9599 /* The chess program may have exited.
9600 If so, restart it and feed it all the moves made so far. */
9602 if (appData.noChessProgram || first.pr != NoProc) return;
9604 StartChessProgram(&first);
9605 InitChessProgram(&first, FALSE);
9606 FeedMovesToProgram(&first, currentMove);
9608 if (!first.sendTime) {
9609 /* can't tell gnuchess what its clock should read,
9610 so we bow to its notion. */
9612 timeRemaining[0][currentMove] = whiteTimeRemaining;
9613 timeRemaining[1][currentMove] = blackTimeRemaining;
9616 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
9617 appData.icsEngineAnalyze) && first.analysisSupport) {
9618 SendToProgram("analyze\n", &first);
9619 first.analyzing = TRUE;
9632 if (appData.debugMode) {
9633 fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
9634 redraw, init, gameMode);
9636 CleanupTail(); // [HGM] vari: delete any stored variations
9637 pausing = pauseExamInvalid = FALSE;
9638 startedFromSetupPosition = blackPlaysFirst = FALSE;
9640 whiteFlag = blackFlag = FALSE;
9641 userOfferedDraw = FALSE;
9642 hintRequested = bookRequested = FALSE;
9643 first.maybeThinking = FALSE;
9644 second.maybeThinking = FALSE;
9645 first.bookSuspend = FALSE; // [HGM] book
9646 second.bookSuspend = FALSE;
9647 thinkOutput[0] = NULLCHAR;
9648 lastHint[0] = NULLCHAR;
9649 ClearGameInfo(&gameInfo);
9650 gameInfo.variant = StringToVariant(appData.variant);
9651 ics_user_moved = ics_clock_paused = FALSE;
9652 ics_getting_history = H_FALSE;
9654 white_holding[0] = black_holding[0] = NULLCHAR;
9655 ClearProgramStats();
9656 opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
9660 flipView = appData.flipView;
9661 ClearPremoveHighlights();
9663 alarmSounded = FALSE;
9665 GameEnds(EndOfFile, NULL, GE_PLAYER);
9666 if(appData.serverMovesName != NULL) {
9667 /* [HGM] prepare to make moves file for broadcasting */
9668 clock_t t = clock();
9669 if(serverMoves != NULL) fclose(serverMoves);
9670 serverMoves = fopen(appData.serverMovesName, "r");
9671 if(serverMoves != NULL) {
9672 fclose(serverMoves);
9673 /* delay 15 sec before overwriting, so all clients can see end */
9674 while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
9676 serverMoves = fopen(appData.serverMovesName, "w");
9680 gameMode = BeginningOfGame;
9682 if(appData.icsActive) gameInfo.variant = VariantNormal;
9683 currentMove = forwardMostMove = backwardMostMove = 0;
9684 InitPosition(redraw);
9685 for (i = 0; i < MAX_MOVES; i++) {
9686 if (commentList[i] != NULL) {
9687 free(commentList[i]);
9688 commentList[i] = NULL;
9692 timeRemaining[0][0] = whiteTimeRemaining;
9693 timeRemaining[1][0] = blackTimeRemaining;
9694 if (first.pr == NULL) {
9695 StartChessProgram(&first);
9698 InitChessProgram(&first, startedFromSetupPosition);
9701 DisplayMessage("", "");
9702 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
9703 lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
9710 if (!AutoPlayOneMove())
9712 if (matchMode || appData.timeDelay == 0)
9714 if (appData.timeDelay < 0)
9716 StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
9725 int fromX, fromY, toX, toY;
9727 if (appData.debugMode) {
9728 fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
9731 if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
9734 if (gameMode == AnalyzeFile && currentMove > backwardMostMove) {
9735 pvInfoList[currentMove].depth = programStats.depth;
9736 pvInfoList[currentMove].score = programStats.score;
9737 pvInfoList[currentMove].time = 0;
9738 if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
9741 if (currentMove >= forwardMostMove) {
9742 if(gameMode == AnalyzeFile) { ExitAnalyzeMode(); SendToProgram("force\n", &first); }
9743 gameMode = EditGame;
9746 /* [AS] Clear current move marker at the end of a game */
9747 /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
9752 toX = moveList[currentMove][2] - AAA;
9753 toY = moveList[currentMove][3] - ONE;
9755 if (moveList[currentMove][1] == '@') {
9756 if (appData.highlightLastMove) {
9757 SetHighlights(-1, -1, toX, toY);
9760 fromX = moveList[currentMove][0] - AAA;
9761 fromY = moveList[currentMove][1] - ONE;
9763 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
9765 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
9767 if (appData.highlightLastMove) {
9768 SetHighlights(fromX, fromY, toX, toY);
9771 DisplayMove(currentMove);
9772 SendMoveToProgram(currentMove++, &first);
9773 DisplayBothClocks();
9774 DrawPosition(FALSE, boards[currentMove]);
9775 // [HGM] PV info: always display, routine tests if empty
9776 DisplayComment(currentMove - 1, commentList[currentMove]);
9782 LoadGameOneMove(readAhead)
9783 ChessMove readAhead;
9785 int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
9786 char promoChar = NULLCHAR;
9791 if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
9792 gameMode != AnalyzeMode && gameMode != Training) {
9797 yyboardindex = forwardMostMove;
9798 if (readAhead != EndOfFile) {
9799 moveType = readAhead;
9801 if (gameFileFP == NULL)
9803 moveType = (ChessMove) Myylex();
9809 if (appData.debugMode)
9810 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9813 /* append the comment but don't display it */
9814 AppendComment(currentMove, p, FALSE);
9817 case WhiteCapturesEnPassant:
9818 case BlackCapturesEnPassant:
9819 case WhitePromotion:
9820 case BlackPromotion:
9821 case WhiteNonPromotion:
9822 case BlackNonPromotion:
9824 case WhiteKingSideCastle:
9825 case WhiteQueenSideCastle:
9826 case BlackKingSideCastle:
9827 case BlackQueenSideCastle:
9828 case WhiteKingSideCastleWild:
9829 case WhiteQueenSideCastleWild:
9830 case BlackKingSideCastleWild:
9831 case BlackQueenSideCastleWild:
9833 case WhiteHSideCastleFR:
9834 case WhiteASideCastleFR:
9835 case BlackHSideCastleFR:
9836 case BlackASideCastleFR:
9838 if (appData.debugMode)
9839 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
9840 fromX = currentMoveString[0] - AAA;
9841 fromY = currentMoveString[1] - ONE;
9842 toX = currentMoveString[2] - AAA;
9843 toY = currentMoveString[3] - ONE;
9844 promoChar = currentMoveString[4];
9849 if (appData.debugMode)
9850 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
9851 fromX = moveType == WhiteDrop ?
9852 (int) CharToPiece(ToUpper(currentMoveString[0])) :
9853 (int) CharToPiece(ToLower(currentMoveString[0]));
9855 toX = currentMoveString[2] - AAA;
9856 toY = currentMoveString[3] - ONE;
9862 case GameUnfinished:
9863 if (appData.debugMode)
9864 fprintf(debugFP, "Parsed game end: %s\n", yy_text);
9865 p = strchr(yy_text, '{');
9866 if (p == NULL) p = strchr(yy_text, '(');
9869 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
9871 q = strchr(p, *p == '{' ? '}' : ')');
9872 if (q != NULL) *q = NULLCHAR;
9875 while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
9876 GameEnds(moveType, p, GE_FILE);
9878 if (cmailMsgLoaded) {
9880 flipView = WhiteOnMove(currentMove);
9881 if (moveType == GameUnfinished) flipView = !flipView;
9882 if (appData.debugMode)
9883 fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
9888 if (appData.debugMode)
9889 fprintf(debugFP, "Parser hit end of file\n");
9890 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9896 if (WhiteOnMove(currentMove)) {
9897 GameEnds(BlackWins, "Black mates", GE_FILE);
9899 GameEnds(WhiteWins, "White mates", GE_FILE);
9903 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
9910 if (lastLoadGameStart == GNUChessGame) {
9911 /* GNUChessGames have numbers, but they aren't move numbers */
9912 if (appData.debugMode)
9913 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
9914 yy_text, (int) moveType);
9915 return LoadGameOneMove(EndOfFile); /* tail recursion */
9917 /* else fall thru */
9922 /* Reached start of next game in file */
9923 if (appData.debugMode)
9924 fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
9925 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9931 if (WhiteOnMove(currentMove)) {
9932 GameEnds(BlackWins, "Black mates", GE_FILE);
9934 GameEnds(WhiteWins, "White mates", GE_FILE);
9938 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
9944 case PositionDiagram: /* should not happen; ignore */
9945 case ElapsedTime: /* ignore */
9946 case NAG: /* ignore */
9947 if (appData.debugMode)
9948 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
9949 yy_text, (int) moveType);
9950 return LoadGameOneMove(EndOfFile); /* tail recursion */
9953 if (appData.testLegality) {
9954 if (appData.debugMode)
9955 fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
9956 snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
9957 (forwardMostMove / 2) + 1,
9958 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9959 DisplayError(move, 0);
9962 if (appData.debugMode)
9963 fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
9964 yy_text, currentMoveString);
9965 fromX = currentMoveString[0] - AAA;
9966 fromY = currentMoveString[1] - ONE;
9967 toX = currentMoveString[2] - AAA;
9968 toY = currentMoveString[3] - ONE;
9969 promoChar = currentMoveString[4];
9974 if (appData.debugMode)
9975 fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
9976 snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
9977 (forwardMostMove / 2) + 1,
9978 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9979 DisplayError(move, 0);
9984 case ImpossibleMove:
9985 if (appData.debugMode)
9986 fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
9987 snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
9988 (forwardMostMove / 2) + 1,
9989 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9990 DisplayError(move, 0);
9996 if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
9997 DrawPosition(FALSE, boards[currentMove]);
9998 DisplayBothClocks();
9999 if (!appData.matchMode) // [HGM] PV info: routine tests if empty
10000 DisplayComment(currentMove - 1, commentList[currentMove]);
10002 (void) StopLoadGameTimer();
10004 cmailOldMove = forwardMostMove;
10007 /* currentMoveString is set as a side-effect of yylex */
10009 thinkOutput[0] = NULLCHAR;
10010 MakeMove(fromX, fromY, toX, toY, promoChar);
10011 currentMove = forwardMostMove;
10016 /* Load the nth game from the given file */
10018 LoadGameFromFile(filename, n, title, useList)
10022 /*Boolean*/ int useList;
10027 if (strcmp(filename, "-") == 0) {
10031 f = fopen(filename, "rb");
10033 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10034 DisplayError(buf, errno);
10038 if (fseek(f, 0, 0) == -1) {
10039 /* f is not seekable; probably a pipe */
10042 if (useList && n == 0) {
10043 int error = GameListBuild(f);
10045 DisplayError(_("Cannot build game list"), error);
10046 } else if (!ListEmpty(&gameList) &&
10047 ((ListGame *) gameList.tailPred)->number > 1) {
10048 GameListPopUp(f, title);
10055 return LoadGame(f, n, title, FALSE);
10060 MakeRegisteredMove()
10062 int fromX, fromY, toX, toY;
10064 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
10065 switch (cmailMoveType[lastLoadGameNumber - 1]) {
10068 if (appData.debugMode)
10069 fprintf(debugFP, "Restoring %s for game %d\n",
10070 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
10072 thinkOutput[0] = NULLCHAR;
10073 safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
10074 fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
10075 fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
10076 toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
10077 toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
10078 promoChar = cmailMove[lastLoadGameNumber - 1][4];
10079 MakeMove(fromX, fromY, toX, toY, promoChar);
10080 ShowMove(fromX, fromY, toX, toY);
10082 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
10089 if (WhiteOnMove(currentMove)) {
10090 GameEnds(BlackWins, "Black mates", GE_PLAYER);
10092 GameEnds(WhiteWins, "White mates", GE_PLAYER);
10097 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
10104 if (WhiteOnMove(currentMove)) {
10105 GameEnds(BlackWins, "White resigns", GE_PLAYER);
10107 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
10112 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
10123 /* Wrapper around LoadGame for use when a Cmail message is loaded */
10125 CmailLoadGame(f, gameNumber, title, useList)
10133 if (gameNumber > nCmailGames) {
10134 DisplayError(_("No more games in this message"), 0);
10137 if (f == lastLoadGameFP) {
10138 int offset = gameNumber - lastLoadGameNumber;
10140 cmailMsg[0] = NULLCHAR;
10141 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
10142 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
10143 nCmailMovesRegistered--;
10145 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
10146 if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
10147 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
10150 if (! RegisterMove()) return FALSE;
10154 retVal = LoadGame(f, gameNumber, title, useList);
10156 /* Make move registered during previous look at this game, if any */
10157 MakeRegisteredMove();
10159 if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
10160 commentList[currentMove]
10161 = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
10162 DisplayComment(currentMove - 1, commentList[currentMove]);
10168 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
10173 int gameNumber = lastLoadGameNumber + offset;
10174 if (lastLoadGameFP == NULL) {
10175 DisplayError(_("No game has been loaded yet"), 0);
10178 if (gameNumber <= 0) {
10179 DisplayError(_("Can't back up any further"), 0);
10182 if (cmailMsgLoaded) {
10183 return CmailLoadGame(lastLoadGameFP, gameNumber,
10184 lastLoadGameTitle, lastLoadGameUseList);
10186 return LoadGame(lastLoadGameFP, gameNumber,
10187 lastLoadGameTitle, lastLoadGameUseList);
10193 /* Load the nth game from open file f */
10195 LoadGame(f, gameNumber, title, useList)
10203 int gn = gameNumber;
10204 ListGame *lg = NULL;
10205 int numPGNTags = 0;
10207 GameMode oldGameMode;
10208 VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
10210 if (appData.debugMode)
10211 fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
10213 if (gameMode == Training )
10214 SetTrainingModeOff();
10216 oldGameMode = gameMode;
10217 if (gameMode != BeginningOfGame) {
10218 Reset(FALSE, TRUE);
10222 if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
10223 fclose(lastLoadGameFP);
10227 lg = (ListGame *) ListElem(&gameList, gameNumber-1);
10230 fseek(f, lg->offset, 0);
10231 GameListHighlight(gameNumber);
10235 DisplayError(_("Game number out of range"), 0);
10240 if (fseek(f, 0, 0) == -1) {
10241 if (f == lastLoadGameFP ?
10242 gameNumber == lastLoadGameNumber + 1 :
10246 DisplayError(_("Can't seek on game file"), 0);
10251 lastLoadGameFP = f;
10252 lastLoadGameNumber = gameNumber;
10253 safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
10254 lastLoadGameUseList = useList;
10258 if (lg && lg->gameInfo.white && lg->gameInfo.black) {
10259 snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
10260 lg->gameInfo.black);
10262 } else if (*title != NULLCHAR) {
10263 if (gameNumber > 1) {
10264 snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
10267 DisplayTitle(title);
10271 if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
10272 gameMode = PlayFromGameFile;
10276 currentMove = forwardMostMove = backwardMostMove = 0;
10277 CopyBoard(boards[0], initialPosition);
10281 * Skip the first gn-1 games in the file.
10282 * Also skip over anything that precedes an identifiable
10283 * start of game marker, to avoid being confused by
10284 * garbage at the start of the file. Currently
10285 * recognized start of game markers are the move number "1",
10286 * the pattern "gnuchess .* game", the pattern
10287 * "^[#;%] [^ ]* game file", and a PGN tag block.
10288 * A game that starts with one of the latter two patterns
10289 * will also have a move number 1, possibly
10290 * following a position diagram.
10291 * 5-4-02: Let's try being more lenient and allowing a game to
10292 * start with an unnumbered move. Does that break anything?
10294 cm = lastLoadGameStart = EndOfFile;
10296 yyboardindex = forwardMostMove;
10297 cm = (ChessMove) Myylex();
10300 if (cmailMsgLoaded) {
10301 nCmailGames = CMAIL_MAX_GAMES - gn;
10304 DisplayError(_("Game not found in file"), 0);
10311 lastLoadGameStart = cm;
10314 case MoveNumberOne:
10315 switch (lastLoadGameStart) {
10320 case MoveNumberOne:
10322 gn--; /* count this game */
10323 lastLoadGameStart = cm;
10332 switch (lastLoadGameStart) {
10335 case MoveNumberOne:
10337 gn--; /* count this game */
10338 lastLoadGameStart = cm;
10341 lastLoadGameStart = cm; /* game counted already */
10349 yyboardindex = forwardMostMove;
10350 cm = (ChessMove) Myylex();
10351 } while (cm == PGNTag || cm == Comment);
10358 if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
10359 if ( cmailResult[CMAIL_MAX_GAMES - gn - 1]
10360 != CMAIL_OLD_RESULT) {
10362 cmailResult[ CMAIL_MAX_GAMES
10363 - gn - 1] = CMAIL_OLD_RESULT;
10369 /* Only a NormalMove can be at the start of a game
10370 * without a position diagram. */
10371 if (lastLoadGameStart == EndOfFile ) {
10373 lastLoadGameStart = MoveNumberOne;
10382 if (appData.debugMode)
10383 fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
10385 if (cm == XBoardGame) {
10386 /* Skip any header junk before position diagram and/or move 1 */
10388 yyboardindex = forwardMostMove;
10389 cm = (ChessMove) Myylex();
10391 if (cm == EndOfFile ||
10392 cm == GNUChessGame || cm == XBoardGame) {
10393 /* Empty game; pretend end-of-file and handle later */
10398 if (cm == MoveNumberOne || cm == PositionDiagram ||
10399 cm == PGNTag || cm == Comment)
10402 } else if (cm == GNUChessGame) {
10403 if (gameInfo.event != NULL) {
10404 free(gameInfo.event);
10406 gameInfo.event = StrSave(yy_text);
10409 startedFromSetupPosition = FALSE;
10410 while (cm == PGNTag) {
10411 if (appData.debugMode)
10412 fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
10413 err = ParsePGNTag(yy_text, &gameInfo);
10414 if (!err) numPGNTags++;
10416 /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
10417 if(gameInfo.variant != oldVariant) {
10418 startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
10419 ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
10420 InitPosition(TRUE);
10421 oldVariant = gameInfo.variant;
10422 if (appData.debugMode)
10423 fprintf(debugFP, "New variant %d\n", (int) oldVariant);
10427 if (gameInfo.fen != NULL) {
10428 Board initial_position;
10429 startedFromSetupPosition = TRUE;
10430 if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
10432 DisplayError(_("Bad FEN position in file"), 0);
10435 CopyBoard(boards[0], initial_position);
10436 if (blackPlaysFirst) {
10437 currentMove = forwardMostMove = backwardMostMove = 1;
10438 CopyBoard(boards[1], initial_position);
10439 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
10440 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
10441 timeRemaining[0][1] = whiteTimeRemaining;
10442 timeRemaining[1][1] = blackTimeRemaining;
10443 if (commentList[0] != NULL) {
10444 commentList[1] = commentList[0];
10445 commentList[0] = NULL;
10448 currentMove = forwardMostMove = backwardMostMove = 0;
10450 /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
10452 initialRulePlies = FENrulePlies;
10453 for( i=0; i< nrCastlingRights; i++ )
10454 initialRights[i] = initial_position[CASTLING][i];
10456 yyboardindex = forwardMostMove;
10457 free(gameInfo.fen);
10458 gameInfo.fen = NULL;
10461 yyboardindex = forwardMostMove;
10462 cm = (ChessMove) Myylex();
10464 /* Handle comments interspersed among the tags */
10465 while (cm == Comment) {
10467 if (appData.debugMode)
10468 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10470 AppendComment(currentMove, p, FALSE);
10471 yyboardindex = forwardMostMove;
10472 cm = (ChessMove) Myylex();
10476 /* don't rely on existence of Event tag since if game was
10477 * pasted from clipboard the Event tag may not exist
10479 if (numPGNTags > 0){
10481 if (gameInfo.variant == VariantNormal) {
10482 VariantClass v = StringToVariant(gameInfo.event);
10483 // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
10484 if(v < VariantShogi) gameInfo.variant = v;
10487 if( appData.autoDisplayTags ) {
10488 tags = PGNTags(&gameInfo);
10489 TagsPopUp(tags, CmailMsg());
10494 /* Make something up, but don't display it now */
10499 if (cm == PositionDiagram) {
10502 Board initial_position;
10504 if (appData.debugMode)
10505 fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
10507 if (!startedFromSetupPosition) {
10509 for (i = BOARD_HEIGHT - 1; i >= 0; i--)
10510 for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
10521 initial_position[i][j++] = CharToPiece(*p);
10524 while (*p == ' ' || *p == '\t' ||
10525 *p == '\n' || *p == '\r') p++;
10527 if (strncmp(p, "black", strlen("black"))==0)
10528 blackPlaysFirst = TRUE;
10530 blackPlaysFirst = FALSE;
10531 startedFromSetupPosition = TRUE;
10533 CopyBoard(boards[0], initial_position);
10534 if (blackPlaysFirst) {
10535 currentMove = forwardMostMove = backwardMostMove = 1;
10536 CopyBoard(boards[1], initial_position);
10537 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
10538 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
10539 timeRemaining[0][1] = whiteTimeRemaining;
10540 timeRemaining[1][1] = blackTimeRemaining;
10541 if (commentList[0] != NULL) {
10542 commentList[1] = commentList[0];
10543 commentList[0] = NULL;
10546 currentMove = forwardMostMove = backwardMostMove = 0;
10549 yyboardindex = forwardMostMove;
10550 cm = (ChessMove) Myylex();
10553 if (first.pr == NoProc) {
10554 StartChessProgram(&first);
10556 InitChessProgram(&first, FALSE);
10557 SendToProgram("force\n", &first);
10558 if (startedFromSetupPosition) {
10559 SendBoard(&first, forwardMostMove);
10560 if (appData.debugMode) {
10561 fprintf(debugFP, "Load Game\n");
10563 DisplayBothClocks();
10566 /* [HGM] server: flag to write setup moves in broadcast file as one */
10567 loadFlag = appData.suppressLoadMoves;
10569 while (cm == Comment) {
10571 if (appData.debugMode)
10572 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10574 AppendComment(currentMove, p, FALSE);
10575 yyboardindex = forwardMostMove;
10576 cm = (ChessMove) Myylex();
10579 if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
10580 cm == WhiteWins || cm == BlackWins ||
10581 cm == GameIsDrawn || cm == GameUnfinished) {
10582 DisplayMessage("", _("No moves in game"));
10583 if (cmailMsgLoaded) {
10584 if (appData.debugMode)
10585 fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
10589 DrawPosition(FALSE, boards[currentMove]);
10590 DisplayBothClocks();
10591 gameMode = EditGame;
10598 // [HGM] PV info: routine tests if comment empty
10599 if (!matchMode && (pausing || appData.timeDelay != 0)) {
10600 DisplayComment(currentMove - 1, commentList[currentMove]);
10602 if (!matchMode && appData.timeDelay != 0)
10603 DrawPosition(FALSE, boards[currentMove]);
10605 if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
10606 programStats.ok_to_send = 1;
10609 /* if the first token after the PGN tags is a move
10610 * and not move number 1, retrieve it from the parser
10612 if (cm != MoveNumberOne)
10613 LoadGameOneMove(cm);
10615 /* load the remaining moves from the file */
10616 while (LoadGameOneMove(EndOfFile)) {
10617 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10618 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10621 /* rewind to the start of the game */
10622 currentMove = backwardMostMove;
10624 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10626 if (oldGameMode == AnalyzeFile ||
10627 oldGameMode == AnalyzeMode) {
10628 AnalyzeFileEvent();
10631 if (matchMode || appData.timeDelay == 0) {
10633 gameMode = EditGame;
10635 } else if (appData.timeDelay > 0) {
10636 AutoPlayGameLoop();
10639 if (appData.debugMode)
10640 fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
10642 loadFlag = 0; /* [HGM] true game starts */
10646 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
10648 ReloadPosition(offset)
10651 int positionNumber = lastLoadPositionNumber + offset;
10652 if (lastLoadPositionFP == NULL) {
10653 DisplayError(_("No position has been loaded yet"), 0);
10656 if (positionNumber <= 0) {
10657 DisplayError(_("Can't back up any further"), 0);
10660 return LoadPosition(lastLoadPositionFP, positionNumber,
10661 lastLoadPositionTitle);
10664 /* Load the nth position from the given file */
10666 LoadPositionFromFile(filename, n, title)
10674 if (strcmp(filename, "-") == 0) {
10675 return LoadPosition(stdin, n, "stdin");
10677 f = fopen(filename, "rb");
10679 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10680 DisplayError(buf, errno);
10683 return LoadPosition(f, n, title);
10688 /* Load the nth position from the given open file, and close it */
10690 LoadPosition(f, positionNumber, title)
10692 int positionNumber;
10695 char *p, line[MSG_SIZ];
10696 Board initial_position;
10697 int i, j, fenMode, pn;
10699 if (gameMode == Training )
10700 SetTrainingModeOff();
10702 if (gameMode != BeginningOfGame) {
10703 Reset(FALSE, TRUE);
10705 if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
10706 fclose(lastLoadPositionFP);
10708 if (positionNumber == 0) positionNumber = 1;
10709 lastLoadPositionFP = f;
10710 lastLoadPositionNumber = positionNumber;
10711 safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
10712 if (first.pr == NoProc) {
10713 StartChessProgram(&first);
10714 InitChessProgram(&first, FALSE);
10716 pn = positionNumber;
10717 if (positionNumber < 0) {
10718 /* Negative position number means to seek to that byte offset */
10719 if (fseek(f, -positionNumber, 0) == -1) {
10720 DisplayError(_("Can't seek on position file"), 0);
10725 if (fseek(f, 0, 0) == -1) {
10726 if (f == lastLoadPositionFP ?
10727 positionNumber == lastLoadPositionNumber + 1 :
10728 positionNumber == 1) {
10731 DisplayError(_("Can't seek on position file"), 0);
10736 /* See if this file is FEN or old-style xboard */
10737 if (fgets(line, MSG_SIZ, f) == NULL) {
10738 DisplayError(_("Position not found in file"), 0);
10741 // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
10742 fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
10745 if (fenMode || line[0] == '#') pn--;
10747 /* skip positions before number pn */
10748 if (fgets(line, MSG_SIZ, f) == NULL) {
10750 DisplayError(_("Position not found in file"), 0);
10753 if (fenMode || line[0] == '#') pn--;
10758 if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
10759 DisplayError(_("Bad FEN position in file"), 0);
10763 (void) fgets(line, MSG_SIZ, f);
10764 (void) fgets(line, MSG_SIZ, f);
10766 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
10767 (void) fgets(line, MSG_SIZ, f);
10768 for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
10771 initial_position[i][j++] = CharToPiece(*p);
10775 blackPlaysFirst = FALSE;
10777 (void) fgets(line, MSG_SIZ, f);
10778 if (strncmp(line, "black", strlen("black"))==0)
10779 blackPlaysFirst = TRUE;
10782 startedFromSetupPosition = TRUE;
10784 SendToProgram("force\n", &first);
10785 CopyBoard(boards[0], initial_position);
10786 if (blackPlaysFirst) {
10787 currentMove = forwardMostMove = backwardMostMove = 1;
10788 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
10789 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
10790 CopyBoard(boards[1], initial_position);
10791 DisplayMessage("", _("Black to play"));
10793 currentMove = forwardMostMove = backwardMostMove = 0;
10794 DisplayMessage("", _("White to play"));
10796 initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
10797 SendBoard(&first, forwardMostMove);
10798 if (appData.debugMode) {
10800 for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
10801 for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
10802 fprintf(debugFP, "Load Position\n");
10805 if (positionNumber > 1) {
10806 snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
10807 DisplayTitle(line);
10809 DisplayTitle(title);
10811 gameMode = EditGame;
10814 timeRemaining[0][1] = whiteTimeRemaining;
10815 timeRemaining[1][1] = blackTimeRemaining;
10816 DrawPosition(FALSE, boards[currentMove]);
10823 CopyPlayerNameIntoFileName(dest, src)
10826 while (*src != NULLCHAR && *src != ',') {
10831 *(*dest)++ = *src++;
10836 char *DefaultFileName(ext)
10839 static char def[MSG_SIZ];
10842 if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
10844 CopyPlayerNameIntoFileName(&p, gameInfo.white);
10846 CopyPlayerNameIntoFileName(&p, gameInfo.black);
10848 safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
10855 /* Save the current game to the given file */
10857 SaveGameToFile(filename, append)
10864 if (strcmp(filename, "-") == 0) {
10865 return SaveGame(stdout, 0, NULL);
10867 f = fopen(filename, append ? "a" : "w");
10869 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10870 DisplayError(buf, errno);
10873 return SaveGame(f, 0, NULL);
10882 static char buf[MSG_SIZ];
10885 p = strchr(str, ' ');
10886 if (p == NULL) return str;
10887 strncpy(buf, str, p - str);
10888 buf[p - str] = NULLCHAR;
10892 #define PGN_MAX_LINE 75
10894 #define PGN_SIDE_WHITE 0
10895 #define PGN_SIDE_BLACK 1
10898 static int FindFirstMoveOutOfBook( int side )
10902 if( backwardMostMove == 0 && ! startedFromSetupPosition) {
10903 int index = backwardMostMove;
10904 int has_book_hit = 0;
10906 if( (index % 2) != side ) {
10910 while( index < forwardMostMove ) {
10911 /* Check to see if engine is in book */
10912 int depth = pvInfoList[index].depth;
10913 int score = pvInfoList[index].score;
10919 else if( score == 0 && depth == 63 ) {
10920 in_book = 1; /* Zappa */
10922 else if( score == 2 && depth == 99 ) {
10923 in_book = 1; /* Abrok */
10926 has_book_hit += in_book;
10942 void GetOutOfBookInfo( char * buf )
10946 int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10948 oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
10949 oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
10953 if( oob[0] >= 0 || oob[1] >= 0 ) {
10954 for( i=0; i<2; i++ ) {
10958 if( i > 0 && oob[0] >= 0 ) {
10959 strcat( buf, " " );
10962 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
10963 sprintf( buf+strlen(buf), "%s%.2f",
10964 pvInfoList[idx].score >= 0 ? "+" : "",
10965 pvInfoList[idx].score / 100.0 );
10971 /* Save game in PGN style and close the file */
10976 int i, offset, linelen, newblock;
10980 int movelen, numlen, blank;
10981 char move_buffer[100]; /* [AS] Buffer for move+PV info */
10983 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10985 tm = time((time_t *) NULL);
10987 PrintPGNTags(f, &gameInfo);
10989 if (backwardMostMove > 0 || startedFromSetupPosition) {
10990 char *fen = PositionToFEN(backwardMostMove, NULL);
10991 fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
10992 fprintf(f, "\n{--------------\n");
10993 PrintPosition(f, backwardMostMove);
10994 fprintf(f, "--------------}\n");
10998 /* [AS] Out of book annotation */
10999 if( appData.saveOutOfBookInfo ) {
11002 GetOutOfBookInfo( buf );
11004 if( buf[0] != '\0' ) {
11005 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
11012 i = backwardMostMove;
11016 while (i < forwardMostMove) {
11017 /* Print comments preceding this move */
11018 if (commentList[i] != NULL) {
11019 if (linelen > 0) fprintf(f, "\n");
11020 fprintf(f, "%s", commentList[i]);
11025 /* Format move number */
11027 snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
11030 snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
11032 numtext[0] = NULLCHAR;
11034 numlen = strlen(numtext);
11037 /* Print move number */
11038 blank = linelen > 0 && numlen > 0;
11039 if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
11048 fprintf(f, "%s", numtext);
11052 safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
11053 movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
11056 blank = linelen > 0 && movelen > 0;
11057 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
11066 fprintf(f, "%s", move_buffer);
11067 linelen += movelen;
11069 /* [AS] Add PV info if present */
11070 if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
11071 /* [HGM] add time */
11072 char buf[MSG_SIZ]; int seconds;
11074 seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
11080 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
11083 seconds = (seconds + 4)/10; // round to full seconds
11085 snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
11087 snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
11090 snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
11091 pvInfoList[i].score >= 0 ? "+" : "",
11092 pvInfoList[i].score / 100.0,
11093 pvInfoList[i].depth,
11096 movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
11098 /* Print score/depth */
11099 blank = linelen > 0 && movelen > 0;
11100 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
11109 fprintf(f, "%s", move_buffer);
11110 linelen += movelen;
11116 /* Start a new line */
11117 if (linelen > 0) fprintf(f, "\n");
11119 /* Print comments after last move */
11120 if (commentList[i] != NULL) {
11121 fprintf(f, "%s\n", commentList[i]);
11125 if (gameInfo.resultDetails != NULL &&
11126 gameInfo.resultDetails[0] != NULLCHAR) {
11127 fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
11128 PGNResult(gameInfo.result));
11130 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
11134 lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
11138 /* Save game in old style and close the file */
11140 SaveGameOldStyle(f)
11146 tm = time((time_t *) NULL);
11148 fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
11151 if (backwardMostMove > 0 || startedFromSetupPosition) {
11152 fprintf(f, "\n[--------------\n");
11153 PrintPosition(f, backwardMostMove);
11154 fprintf(f, "--------------]\n");
11159 i = backwardMostMove;
11160 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
11162 while (i < forwardMostMove) {
11163 if (commentList[i] != NULL) {
11164 fprintf(f, "[%s]\n", commentList[i]);
11167 if ((i % 2) == 1) {
11168 fprintf(f, "%d. ... %s\n", (i - offset)/2 + 1, parseList[i]);
11171 fprintf(f, "%d. %s ", (i - offset)/2 + 1, parseList[i]);
11173 if (commentList[i] != NULL) {
11177 if (i >= forwardMostMove) {
11181 fprintf(f, "%s\n", parseList[i]);
11186 if (commentList[i] != NULL) {
11187 fprintf(f, "[%s]\n", commentList[i]);
11190 /* This isn't really the old style, but it's close enough */
11191 if (gameInfo.resultDetails != NULL &&
11192 gameInfo.resultDetails[0] != NULLCHAR) {
11193 fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
11194 gameInfo.resultDetails);
11196 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
11203 /* Save the current game to open file f and close the file */
11205 SaveGame(f, dummy, dummy2)
11210 if (gameMode == EditPosition) EditPositionDone(TRUE);
11211 lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
11212 if (appData.oldSaveStyle)
11213 return SaveGameOldStyle(f);
11215 return SaveGamePGN(f);
11218 /* Save the current position to the given file */
11220 SavePositionToFile(filename)
11226 if (strcmp(filename, "-") == 0) {
11227 return SavePosition(stdout, 0, NULL);
11229 f = fopen(filename, "a");
11231 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
11232 DisplayError(buf, errno);
11235 SavePosition(f, 0, NULL);
11241 /* Save the current position to the given open file and close the file */
11243 SavePosition(f, dummy, dummy2)
11251 if (gameMode == EditPosition) EditPositionDone(TRUE);
11252 if (appData.oldSaveStyle) {
11253 tm = time((time_t *) NULL);
11255 fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
11257 fprintf(f, "[--------------\n");
11258 PrintPosition(f, currentMove);
11259 fprintf(f, "--------------]\n");
11261 fen = PositionToFEN(currentMove, NULL);
11262 fprintf(f, "%s\n", fen);
11270 ReloadCmailMsgEvent(unregister)
11274 static char *inFilename = NULL;
11275 static char *outFilename;
11277 struct stat inbuf, outbuf;
11280 /* Any registered moves are unregistered if unregister is set, */
11281 /* i.e. invoked by the signal handler */
11283 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
11284 cmailMoveRegistered[i] = FALSE;
11285 if (cmailCommentList[i] != NULL) {
11286 free(cmailCommentList[i]);
11287 cmailCommentList[i] = NULL;
11290 nCmailMovesRegistered = 0;
11293 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
11294 cmailResult[i] = CMAIL_NOT_RESULT;
11298 if (inFilename == NULL) {
11299 /* Because the filenames are static they only get malloced once */
11300 /* and they never get freed */
11301 inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
11302 sprintf(inFilename, "%s.game.in", appData.cmailGameName);
11304 outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
11305 sprintf(outFilename, "%s.out", appData.cmailGameName);
11308 status = stat(outFilename, &outbuf);
11310 cmailMailedMove = FALSE;
11312 status = stat(inFilename, &inbuf);
11313 cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
11316 /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
11317 counts the games, notes how each one terminated, etc.
11319 It would be nice to remove this kludge and instead gather all
11320 the information while building the game list. (And to keep it
11321 in the game list nodes instead of having a bunch of fixed-size
11322 parallel arrays.) Note this will require getting each game's
11323 termination from the PGN tags, as the game list builder does
11324 not process the game moves. --mann
11326 cmailMsgLoaded = TRUE;
11327 LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
11329 /* Load first game in the file or popup game menu */
11330 LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
11332 #endif /* !WIN32 */
11340 char string[MSG_SIZ];
11342 if ( cmailMailedMove
11343 || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
11344 return TRUE; /* Allow free viewing */
11347 /* Unregister move to ensure that we don't leave RegisterMove */
11348 /* with the move registered when the conditions for registering no */
11350 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11351 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
11352 nCmailMovesRegistered --;
11354 if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
11356 free(cmailCommentList[lastLoadGameNumber - 1]);
11357 cmailCommentList[lastLoadGameNumber - 1] = NULL;
11361 if (cmailOldMove == -1) {
11362 DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
11366 if (currentMove > cmailOldMove + 1) {
11367 DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
11371 if (currentMove < cmailOldMove) {
11372 DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
11376 if (forwardMostMove > currentMove) {
11377 /* Silently truncate extra moves */
11381 if ( (currentMove == cmailOldMove + 1)
11382 || ( (currentMove == cmailOldMove)
11383 && ( (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
11384 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
11385 if (gameInfo.result != GameUnfinished) {
11386 cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
11389 if (commentList[currentMove] != NULL) {
11390 cmailCommentList[lastLoadGameNumber - 1]
11391 = StrSave(commentList[currentMove]);
11393 safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
11395 if (appData.debugMode)
11396 fprintf(debugFP, "Saving %s for game %d\n",
11397 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
11399 snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
11401 f = fopen(string, "w");
11402 if (appData.oldSaveStyle) {
11403 SaveGameOldStyle(f); /* also closes the file */
11405 snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
11406 f = fopen(string, "w");
11407 SavePosition(f, 0, NULL); /* also closes the file */
11409 fprintf(f, "{--------------\n");
11410 PrintPosition(f, currentMove);
11411 fprintf(f, "--------------}\n\n");
11413 SaveGame(f, 0, NULL); /* also closes the file*/
11416 cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
11417 nCmailMovesRegistered ++;
11418 } else if (nCmailGames == 1) {
11419 DisplayError(_("You have not made a move yet"), 0);
11430 static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
11431 FILE *commandOutput;
11432 char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
11433 int nBytes = 0; /* Suppress warnings on uninitialized variables */
11439 if (! cmailMsgLoaded) {
11440 DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
11444 if (nCmailGames == nCmailResults) {
11445 DisplayError(_("No unfinished games"), 0);
11449 #if CMAIL_PROHIBIT_REMAIL
11450 if (cmailMailedMove) {
11451 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);
11452 DisplayError(msg, 0);
11457 if (! (cmailMailedMove || RegisterMove())) return;
11459 if ( cmailMailedMove
11460 || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
11461 snprintf(string, MSG_SIZ, partCommandString,
11462 appData.debugMode ? " -v" : "", appData.cmailGameName);
11463 commandOutput = popen(string, "r");
11465 if (commandOutput == NULL) {
11466 DisplayError(_("Failed to invoke cmail"), 0);
11468 for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
11469 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
11471 if (nBuffers > 1) {
11472 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
11473 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
11474 nBytes = MSG_SIZ - 1;
11476 (void) memcpy(msg, buffer, nBytes);
11478 *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
11480 if(StrStr(msg, "Mailed cmail message to ") != NULL) {
11481 cmailMailedMove = TRUE; /* Prevent >1 moves */
11484 for (i = 0; i < nCmailGames; i ++) {
11485 if (cmailResult[i] == CMAIL_NOT_RESULT) {
11490 && ( (arcDir = (char *) getenv("CMAIL_ARCDIR"))
11492 snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
11494 appData.cmailGameName,
11496 LoadGameFromFile(buffer, 1, buffer, FALSE);
11497 cmailMsgLoaded = FALSE;
11501 DisplayInformation(msg);
11502 pclose(commandOutput);
11505 if ((*cmailMsg) != '\0') {
11506 DisplayInformation(cmailMsg);
11511 #endif /* !WIN32 */
11520 int prependComma = 0;
11522 char string[MSG_SIZ]; /* Space for game-list */
11525 if (!cmailMsgLoaded) return "";
11527 if (cmailMailedMove) {
11528 snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
11530 /* Create a list of games left */
11531 snprintf(string, MSG_SIZ, "[");
11532 for (i = 0; i < nCmailGames; i ++) {
11533 if (! ( cmailMoveRegistered[i]
11534 || (cmailResult[i] == CMAIL_OLD_RESULT))) {
11535 if (prependComma) {
11536 snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
11538 snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
11542 strcat(string, number);
11545 strcat(string, "]");
11547 if (nCmailMovesRegistered + nCmailResults == 0) {
11548 switch (nCmailGames) {
11550 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
11554 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
11558 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
11563 switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
11565 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
11570 if (nCmailResults == nCmailGames) {
11571 snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
11573 snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
11578 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
11590 if (gameMode == Training)
11591 SetTrainingModeOff();
11594 cmailMsgLoaded = FALSE;
11595 if (appData.icsActive) {
11596 SendToICS(ics_prefix);
11597 SendToICS("refresh\n");
11607 /* Give up on clean exit */
11611 /* Keep trying for clean exit */
11615 if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
11617 if (telnetISR != NULL) {
11618 RemoveInputSource(telnetISR);
11620 if (icsPR != NoProc) {
11621 DestroyChildProcess(icsPR, TRUE);
11624 /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
11625 GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
11627 /* [HGM] crash: the above GameEnds() is a dud if another one was running */
11628 /* make sure this other one finishes before killing it! */
11629 if(endingGame) { int count = 0;
11630 if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
11631 while(endingGame && count++ < 10) DoSleep(1);
11632 if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
11635 /* Kill off chess programs */
11636 if (first.pr != NoProc) {
11639 DoSleep( appData.delayBeforeQuit );
11640 SendToProgram("quit\n", &first);
11641 DoSleep( appData.delayAfterQuit );
11642 DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
11644 if (second.pr != NoProc) {
11645 DoSleep( appData.delayBeforeQuit );
11646 SendToProgram("quit\n", &second);
11647 DoSleep( appData.delayAfterQuit );
11648 DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
11650 if (first.isr != NULL) {
11651 RemoveInputSource(first.isr);
11653 if (second.isr != NULL) {
11654 RemoveInputSource(second.isr);
11657 ShutDownFrontEnd();
11664 if (appData.debugMode)
11665 fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
11669 if (gameMode == MachinePlaysWhite ||
11670 gameMode == MachinePlaysBlack) {
11673 DisplayBothClocks();
11675 if (gameMode == PlayFromGameFile) {
11676 if (appData.timeDelay >= 0)
11677 AutoPlayGameLoop();
11678 } else if (gameMode == IcsExamining && pauseExamInvalid) {
11679 Reset(FALSE, TRUE);
11680 SendToICS(ics_prefix);
11681 SendToICS("refresh\n");
11682 } else if (currentMove < forwardMostMove) {
11683 ForwardInner(forwardMostMove);
11685 pauseExamInvalid = FALSE;
11687 switch (gameMode) {
11691 pauseExamForwardMostMove = forwardMostMove;
11692 pauseExamInvalid = FALSE;
11695 case IcsPlayingWhite:
11696 case IcsPlayingBlack:
11700 case PlayFromGameFile:
11701 (void) StopLoadGameTimer();
11705 case BeginningOfGame:
11706 if (appData.icsActive) return;
11707 /* else fall through */
11708 case MachinePlaysWhite:
11709 case MachinePlaysBlack:
11710 case TwoMachinesPlay:
11711 if (forwardMostMove == 0)
11712 return; /* don't pause if no one has moved */
11713 if ((gameMode == MachinePlaysWhite &&
11714 !WhiteOnMove(forwardMostMove)) ||
11715 (gameMode == MachinePlaysBlack &&
11716 WhiteOnMove(forwardMostMove))) {
11729 char title[MSG_SIZ];
11731 if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
11732 safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
11734 snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
11735 WhiteOnMove(currentMove - 1) ? " " : ".. ",
11736 parseList[currentMove - 1]);
11739 EditCommentPopUp(currentMove, title, commentList[currentMove]);
11746 char *tags = PGNTags(&gameInfo);
11747 EditTagsPopUp(tags, NULL);
11754 if (appData.noChessProgram || gameMode == AnalyzeMode)
11757 if (gameMode != AnalyzeFile) {
11758 if (!appData.icsEngineAnalyze) {
11760 if (gameMode != EditGame) return;
11762 ResurrectChessProgram();
11763 SendToProgram("analyze\n", &first);
11764 first.analyzing = TRUE;
11765 /*first.maybeThinking = TRUE;*/
11766 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11767 EngineOutputPopUp();
11769 if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
11774 StartAnalysisClock();
11775 GetTimeMark(&lastNodeCountTime);
11782 if (appData.noChessProgram || gameMode == AnalyzeFile)
11785 if (gameMode != AnalyzeMode) {
11787 if (gameMode != EditGame) return;
11788 ResurrectChessProgram();
11789 SendToProgram("analyze\n", &first);
11790 first.analyzing = TRUE;
11791 /*first.maybeThinking = TRUE;*/
11792 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11793 EngineOutputPopUp();
11795 gameMode = AnalyzeFile;
11800 StartAnalysisClock();
11801 GetTimeMark(&lastNodeCountTime);
11806 MachineWhiteEvent()
11809 char *bookHit = NULL;
11811 if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
11815 if (gameMode == PlayFromGameFile ||
11816 gameMode == TwoMachinesPlay ||
11817 gameMode == Training ||
11818 gameMode == AnalyzeMode ||
11819 gameMode == EndOfGame)
11822 if (gameMode == EditPosition)
11823 EditPositionDone(TRUE);
11825 if (!WhiteOnMove(currentMove)) {
11826 DisplayError(_("It is not White's turn"), 0);
11830 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
11833 if (gameMode == EditGame || gameMode == AnalyzeMode ||
11834 gameMode == AnalyzeFile)
11837 ResurrectChessProgram(); /* in case it isn't running */
11838 if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
11839 gameMode = MachinePlaysWhite;
11842 gameMode = MachinePlaysWhite;
11846 snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
11848 if (first.sendName) {
11849 snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
11850 SendToProgram(buf, &first);
11852 if (first.sendTime) {
11853 if (first.useColors) {
11854 SendToProgram("black\n", &first); /*gnu kludge*/
11856 SendTimeRemaining(&first, TRUE);
11858 if (first.useColors) {
11859 SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
11861 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
11862 SetMachineThinkingEnables();
11863 first.maybeThinking = TRUE;
11867 if (appData.autoFlipView && !flipView) {
11868 flipView = !flipView;
11869 DrawPosition(FALSE, NULL);
11870 DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;
11873 if(bookHit) { // [HGM] book: simulate book reply
11874 static char bookMove[MSG_SIZ]; // a bit generous?
11876 programStats.nodes = programStats.depth = programStats.time =
11877 programStats.score = programStats.got_only_move = 0;
11878 sprintf(programStats.movelist, "%s (xbook)", bookHit);
11880 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
11881 strcat(bookMove, bookHit);
11882 HandleMachineMove(bookMove, &first);
11887 MachineBlackEvent()
11890 char *bookHit = NULL;
11892 if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
11896 if (gameMode == PlayFromGameFile ||
11897 gameMode == TwoMachinesPlay ||
11898 gameMode == Training ||
11899 gameMode == AnalyzeMode ||
11900 gameMode == EndOfGame)
11903 if (gameMode == EditPosition)
11904 EditPositionDone(TRUE);
11906 if (WhiteOnMove(currentMove)) {
11907 DisplayError(_("It is not Black's turn"), 0);
11911 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
11914 if (gameMode == EditGame || gameMode == AnalyzeMode ||
11915 gameMode == AnalyzeFile)
11918 ResurrectChessProgram(); /* in case it isn't running */
11919 gameMode = MachinePlaysBlack;
11923 snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
11925 if (first.sendName) {
11926 snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
11927 SendToProgram(buf, &first);
11929 if (first.sendTime) {
11930 if (first.useColors) {
11931 SendToProgram("white\n", &first); /*gnu kludge*/
11933 SendTimeRemaining(&first, FALSE);
11935 if (first.useColors) {
11936 SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
11938 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
11939 SetMachineThinkingEnables();
11940 first.maybeThinking = TRUE;
11943 if (appData.autoFlipView && flipView) {
11944 flipView = !flipView;
11945 DrawPosition(FALSE, NULL);
11946 DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;
11948 if(bookHit) { // [HGM] book: simulate book reply
11949 static char bookMove[MSG_SIZ]; // a bit generous?
11951 programStats.nodes = programStats.depth = programStats.time =
11952 programStats.score = programStats.got_only_move = 0;
11953 sprintf(programStats.movelist, "%s (xbook)", bookHit);
11955 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
11956 strcat(bookMove, bookHit);
11957 HandleMachineMove(bookMove, &first);
11963 DisplayTwoMachinesTitle()
11966 if (appData.matchGames > 0) {
11967 if (first.twoMachinesColor[0] == 'w') {
11968 snprintf(buf, MSG_SIZ, "%s vs. %s (%d-%d-%d)",
11969 gameInfo.white, gameInfo.black,
11970 first.matchWins, second.matchWins,
11971 matchGame - 1 - (first.matchWins + second.matchWins));
11973 snprintf(buf, MSG_SIZ, "%s vs. %s (%d-%d-%d)",
11974 gameInfo.white, gameInfo.black,
11975 second.matchWins, first.matchWins,
11976 matchGame - 1 - (first.matchWins + second.matchWins));
11979 snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
11985 SettingsMenuIfReady()
11987 if (second.lastPing != second.lastPong) {
11988 DisplayMessage("", _("Waiting for second chess program"));
11989 ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
11993 DisplayMessage("", "");
11994 SettingsPopUp(&second);
11998 WaitForSecond(DelayedEventCallback retry)
12000 if (second.pr == NULL) {
12001 StartChessProgram(&second);
12002 if (second.protocolVersion == 1) {
12005 /* kludge: allow timeout for initial "feature" command */
12007 DisplayMessage("", _("Starting second chess program"));
12008 ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
12016 TwoMachinesEvent P((void))
12020 ChessProgramState *onmove;
12021 char *bookHit = NULL;
12023 if (appData.noChessProgram) return;
12025 switch (gameMode) {
12026 case TwoMachinesPlay:
12028 case MachinePlaysWhite:
12029 case MachinePlaysBlack:
12030 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
12031 DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
12035 case BeginningOfGame:
12036 case PlayFromGameFile:
12039 if (gameMode != EditGame) return;
12042 EditPositionDone(TRUE);
12053 // forwardMostMove = currentMove;
12054 TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
12055 ResurrectChessProgram(); /* in case first program isn't running */
12057 if(WaitForSecond(TwoMachinesEventIfReady)) return;
12058 DisplayMessage("", "");
12059 InitChessProgram(&second, FALSE);
12060 SendToProgram("force\n", &second);
12061 if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
12062 ScheduleDelayedEvent(TwoMachinesEvent, 10);
12065 if (startedFromSetupPosition) {
12066 SendBoard(&second, backwardMostMove);
12067 if (appData.debugMode) {
12068 fprintf(debugFP, "Two Machines\n");
12071 for (i = backwardMostMove; i < forwardMostMove; i++) {
12072 SendMoveToProgram(i, &second);
12075 gameMode = TwoMachinesPlay;
12079 DisplayTwoMachinesTitle();
12081 if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
12087 SendToProgram(first.computerString, &first);
12088 if (first.sendName) {
12089 snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
12090 SendToProgram(buf, &first);
12092 SendToProgram(second.computerString, &second);
12093 if (second.sendName) {
12094 snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
12095 SendToProgram(buf, &second);
12099 if (!first.sendTime || !second.sendTime) {
12100 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12101 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12103 if (onmove->sendTime) {
12104 if (onmove->useColors) {
12105 SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
12107 SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
12109 if (onmove->useColors) {
12110 SendToProgram(onmove->twoMachinesColor, onmove);
12112 bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
12113 // SendToProgram("go\n", onmove);
12114 onmove->maybeThinking = TRUE;
12115 SetMachineThinkingEnables();
12119 if(bookHit) { // [HGM] book: simulate book reply
12120 static char bookMove[MSG_SIZ]; // a bit generous?
12122 programStats.nodes = programStats.depth = programStats.time =
12123 programStats.score = programStats.got_only_move = 0;
12124 sprintf(programStats.movelist, "%s (xbook)", bookHit);
12126 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
12127 strcat(bookMove, bookHit);
12128 savedMessage = bookMove; // args for deferred call
12129 savedState = onmove;
12130 ScheduleDelayedEvent(DeferredBookMove, 1);
12137 if (gameMode == Training) {
12138 SetTrainingModeOff();
12139 gameMode = PlayFromGameFile;
12140 DisplayMessage("", _("Training mode off"));
12142 gameMode = Training;
12143 animateTraining = appData.animate;
12145 /* make sure we are not already at the end of the game */
12146 if (currentMove < forwardMostMove) {
12147 SetTrainingModeOn();
12148 DisplayMessage("", _("Training mode on"));
12150 gameMode = PlayFromGameFile;
12151 DisplayError(_("Already at end of game"), 0);
12160 if (!appData.icsActive) return;
12161 switch (gameMode) {
12162 case IcsPlayingWhite:
12163 case IcsPlayingBlack:
12166 case BeginningOfGame:
12174 EditPositionDone(TRUE);
12187 gameMode = IcsIdle;
12198 switch (gameMode) {
12200 SetTrainingModeOff();
12202 case MachinePlaysWhite:
12203 case MachinePlaysBlack:
12204 case BeginningOfGame:
12205 SendToProgram("force\n", &first);
12206 SetUserThinkingEnables();
12208 case PlayFromGameFile:
12209 (void) StopLoadGameTimer();
12210 if (gameFileFP != NULL) {
12215 EditPositionDone(TRUE);
12220 SendToProgram("force\n", &first);
12222 case TwoMachinesPlay:
12223 GameEnds(EndOfFile, NULL, GE_PLAYER);
12224 ResurrectChessProgram();
12225 SetUserThinkingEnables();
12228 ResurrectChessProgram();
12230 case IcsPlayingBlack:
12231 case IcsPlayingWhite:
12232 DisplayError(_("Warning: You are still playing a game"), 0);
12235 DisplayError(_("Warning: You are still observing a game"), 0);
12238 DisplayError(_("Warning: You are still examining a game"), 0);
12249 first.offeredDraw = second.offeredDraw = 0;
12251 if (gameMode == PlayFromGameFile) {
12252 whiteTimeRemaining = timeRemaining[0][currentMove];
12253 blackTimeRemaining = timeRemaining[1][currentMove];
12257 if (gameMode == MachinePlaysWhite ||
12258 gameMode == MachinePlaysBlack ||
12259 gameMode == TwoMachinesPlay ||
12260 gameMode == EndOfGame) {
12261 i = forwardMostMove;
12262 while (i > currentMove) {
12263 SendToProgram("undo\n", &first);
12266 whiteTimeRemaining = timeRemaining[0][currentMove];
12267 blackTimeRemaining = timeRemaining[1][currentMove];
12268 DisplayBothClocks();
12269 if (whiteFlag || blackFlag) {
12270 whiteFlag = blackFlag = 0;
12275 gameMode = EditGame;
12282 EditPositionEvent()
12284 if (gameMode == EditPosition) {
12290 if (gameMode != EditGame) return;
12292 gameMode = EditPosition;
12295 if (currentMove > 0)
12296 CopyBoard(boards[0], boards[currentMove]);
12298 blackPlaysFirst = !WhiteOnMove(currentMove);
12300 currentMove = forwardMostMove = backwardMostMove = 0;
12301 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12308 /* [DM] icsEngineAnalyze - possible call from other functions */
12309 if (appData.icsEngineAnalyze) {
12310 appData.icsEngineAnalyze = FALSE;
12312 DisplayMessage("",_("Close ICS engine analyze..."));
12314 if (first.analysisSupport && first.analyzing) {
12315 SendToProgram("exit\n", &first);
12316 first.analyzing = FALSE;
12318 thinkOutput[0] = NULLCHAR;
12322 EditPositionDone(Boolean fakeRights)
12324 int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
12326 startedFromSetupPosition = TRUE;
12327 InitChessProgram(&first, FALSE);
12328 if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
12329 boards[0][EP_STATUS] = EP_NONE;
12330 boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
12331 if(boards[0][0][BOARD_WIDTH>>1] == king) {
12332 boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : NoRights;
12333 boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
12334 } else boards[0][CASTLING][2] = NoRights;
12335 if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
12336 boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : NoRights;
12337 boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
12338 } else boards[0][CASTLING][5] = NoRights;
12340 SendToProgram("force\n", &first);
12341 if (blackPlaysFirst) {
12342 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12343 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12344 currentMove = forwardMostMove = backwardMostMove = 1;
12345 CopyBoard(boards[1], boards[0]);
12347 currentMove = forwardMostMove = backwardMostMove = 0;
12349 SendBoard(&first, forwardMostMove);
12350 if (appData.debugMode) {
12351 fprintf(debugFP, "EditPosDone\n");
12354 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12355 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12356 gameMode = EditGame;
12358 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12359 ClearHighlights(); /* [AS] */
12362 /* Pause for `ms' milliseconds */
12363 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
12373 } while (SubtractTimeMarks(&m2, &m1) < ms);
12376 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
12378 SendMultiLineToICS(buf)
12381 char temp[MSG_SIZ+1], *p;
12388 strncpy(temp, buf, len);
12393 if (*p == '\n' || *p == '\r')
12398 strcat(temp, "\n");
12400 SendToPlayer(temp, strlen(temp));
12404 SetWhiteToPlayEvent()
12406 if (gameMode == EditPosition) {
12407 blackPlaysFirst = FALSE;
12408 DisplayBothClocks(); /* works because currentMove is 0 */
12409 } else if (gameMode == IcsExamining) {
12410 SendToICS(ics_prefix);
12411 SendToICS("tomove white\n");
12416 SetBlackToPlayEvent()
12418 if (gameMode == EditPosition) {
12419 blackPlaysFirst = TRUE;
12420 currentMove = 1; /* kludge */
12421 DisplayBothClocks();
12423 } else if (gameMode == IcsExamining) {
12424 SendToICS(ics_prefix);
12425 SendToICS("tomove black\n");
12430 EditPositionMenuEvent(selection, x, y)
12431 ChessSquare selection;
12435 ChessSquare piece = boards[0][y][x];
12437 if (gameMode != EditPosition && gameMode != IcsExamining) return;
12439 switch (selection) {
12441 if (gameMode == IcsExamining && ics_type == ICS_FICS) {
12442 SendToICS(ics_prefix);
12443 SendToICS("bsetup clear\n");
12444 } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
12445 SendToICS(ics_prefix);
12446 SendToICS("clearboard\n");
12448 for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
12449 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
12450 for (y = 0; y < BOARD_HEIGHT; y++) {
12451 if (gameMode == IcsExamining) {
12452 if (boards[currentMove][y][x] != EmptySquare) {
12453 snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
12458 boards[0][y][x] = p;
12463 if (gameMode == EditPosition) {
12464 DrawPosition(FALSE, boards[0]);
12469 SetWhiteToPlayEvent();
12473 SetBlackToPlayEvent();
12477 if (gameMode == IcsExamining) {
12478 if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
12479 snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
12482 if(x < BOARD_LEFT || x >= BOARD_RGHT) {
12483 if(x == BOARD_LEFT-2) {
12484 if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
12485 boards[0][y][1] = 0;
12487 if(x == BOARD_RGHT+1) {
12488 if(y >= gameInfo.holdingsSize) break;
12489 boards[0][y][BOARD_WIDTH-2] = 0;
12492 boards[0][y][x] = EmptySquare;
12493 DrawPosition(FALSE, boards[0]);
12498 if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
12499 piece >= (int)BlackPawn && piece < (int)BlackMan ) {
12500 selection = (ChessSquare) (PROMOTED piece);
12501 } else if(piece == EmptySquare) selection = WhiteSilver;
12502 else selection = (ChessSquare)((int)piece - 1);
12506 if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
12507 piece > (int)BlackMan && piece <= (int)BlackKing ) {
12508 selection = (ChessSquare) (DEMOTED piece);
12509 } else if(piece == EmptySquare) selection = BlackSilver;
12510 else selection = (ChessSquare)((int)piece + 1);
12515 if(gameInfo.variant == VariantShatranj ||
12516 gameInfo.variant == VariantXiangqi ||
12517 gameInfo.variant == VariantCourier ||
12518 gameInfo.variant == VariantMakruk )
12519 selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
12524 if(gameInfo.variant == VariantXiangqi)
12525 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
12526 if(gameInfo.variant == VariantKnightmate)
12527 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
12530 if (gameMode == IcsExamining) {
12531 if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
12532 snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
12533 PieceToChar(selection), AAA + x, ONE + y);
12536 if(x < BOARD_LEFT || x >= BOARD_RGHT) {
12538 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
12539 n = PieceToNumber(selection - BlackPawn);
12540 if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
12541 boards[0][BOARD_HEIGHT-1-n][0] = selection;
12542 boards[0][BOARD_HEIGHT-1-n][1]++;
12544 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
12545 n = PieceToNumber(selection);
12546 if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
12547 boards[0][n][BOARD_WIDTH-1] = selection;
12548 boards[0][n][BOARD_WIDTH-2]++;
12551 boards[0][y][x] = selection;
12552 DrawPosition(TRUE, boards[0]);
12560 DropMenuEvent(selection, x, y)
12561 ChessSquare selection;
12564 ChessMove moveType;
12566 switch (gameMode) {
12567 case IcsPlayingWhite:
12568 case MachinePlaysBlack:
12569 if (!WhiteOnMove(currentMove)) {
12570 DisplayMoveError(_("It is Black's turn"));
12573 moveType = WhiteDrop;
12575 case IcsPlayingBlack:
12576 case MachinePlaysWhite:
12577 if (WhiteOnMove(currentMove)) {
12578 DisplayMoveError(_("It is White's turn"));
12581 moveType = BlackDrop;
12584 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
12590 if (moveType == BlackDrop && selection < BlackPawn) {
12591 selection = (ChessSquare) ((int) selection
12592 + (int) BlackPawn - (int) WhitePawn);
12594 if (boards[currentMove][y][x] != EmptySquare) {
12595 DisplayMoveError(_("That square is occupied"));
12599 FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
12605 /* Accept a pending offer of any kind from opponent */
12607 if (appData.icsActive) {
12608 SendToICS(ics_prefix);
12609 SendToICS("accept\n");
12610 } else if (cmailMsgLoaded) {
12611 if (currentMove == cmailOldMove &&
12612 commentList[cmailOldMove] != NULL &&
12613 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12614 "Black offers a draw" : "White offers a draw")) {
12616 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
12617 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
12619 DisplayError(_("There is no pending offer on this move"), 0);
12620 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12623 /* Not used for offers from chess program */
12630 /* Decline a pending offer of any kind from opponent */
12632 if (appData.icsActive) {
12633 SendToICS(ics_prefix);
12634 SendToICS("decline\n");
12635 } else if (cmailMsgLoaded) {
12636 if (currentMove == cmailOldMove &&
12637 commentList[cmailOldMove] != NULL &&
12638 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12639 "Black offers a draw" : "White offers a draw")) {
12641 AppendComment(cmailOldMove, "Draw declined", TRUE);
12642 DisplayComment(cmailOldMove - 1, "Draw declined");
12645 DisplayError(_("There is no pending offer on this move"), 0);
12648 /* Not used for offers from chess program */
12655 /* Issue ICS rematch command */
12656 if (appData.icsActive) {
12657 SendToICS(ics_prefix);
12658 SendToICS("rematch\n");
12665 /* Call your opponent's flag (claim a win on time) */
12666 if (appData.icsActive) {
12667 SendToICS(ics_prefix);
12668 SendToICS("flag\n");
12670 switch (gameMode) {
12673 case MachinePlaysWhite:
12676 GameEnds(GameIsDrawn, "Both players ran out of time",
12679 GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
12681 DisplayError(_("Your opponent is not out of time"), 0);
12684 case MachinePlaysBlack:
12687 GameEnds(GameIsDrawn, "Both players ran out of time",
12690 GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
12692 DisplayError(_("Your opponent is not out of time"), 0);
12700 ClockClick(int which)
12701 { // [HGM] code moved to back-end from winboard.c
12702 if(which) { // black clock
12703 if (gameMode == EditPosition || gameMode == IcsExamining) {
12704 if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
12705 SetBlackToPlayEvent();
12706 } else if (gameMode == EditGame || shiftKey) {
12707 AdjustClock(which, -1);
12708 } else if (gameMode == IcsPlayingWhite ||
12709 gameMode == MachinePlaysBlack) {
12712 } else { // white clock
12713 if (gameMode == EditPosition || gameMode == IcsExamining) {
12714 if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
12715 SetWhiteToPlayEvent();
12716 } else if (gameMode == EditGame || shiftKey) {
12717 AdjustClock(which, -1);
12718 } else if (gameMode == IcsPlayingBlack ||
12719 gameMode == MachinePlaysWhite) {
12728 /* Offer draw or accept pending draw offer from opponent */
12730 if (appData.icsActive) {
12731 /* Note: tournament rules require draw offers to be
12732 made after you make your move but before you punch
12733 your clock. Currently ICS doesn't let you do that;
12734 instead, you immediately punch your clock after making
12735 a move, but you can offer a draw at any time. */
12737 SendToICS(ics_prefix);
12738 SendToICS("draw\n");
12739 userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
12740 } else if (cmailMsgLoaded) {
12741 if (currentMove == cmailOldMove &&
12742 commentList[cmailOldMove] != NULL &&
12743 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12744 "Black offers a draw" : "White offers a draw")) {
12745 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
12746 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
12747 } else if (currentMove == cmailOldMove + 1) {
12748 char *offer = WhiteOnMove(cmailOldMove) ?
12749 "White offers a draw" : "Black offers a draw";
12750 AppendComment(currentMove, offer, TRUE);
12751 DisplayComment(currentMove - 1, offer);
12752 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
12754 DisplayError(_("You must make your move before offering a draw"), 0);
12755 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12757 } else if (first.offeredDraw) {
12758 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
12760 if (first.sendDrawOffers) {
12761 SendToProgram("draw\n", &first);
12762 userOfferedDraw = TRUE;
12770 /* Offer Adjourn or accept pending Adjourn offer from opponent */
12772 if (appData.icsActive) {
12773 SendToICS(ics_prefix);
12774 SendToICS("adjourn\n");
12776 /* Currently GNU Chess doesn't offer or accept Adjourns */
12784 /* Offer Abort or accept pending Abort offer from opponent */
12786 if (appData.icsActive) {
12787 SendToICS(ics_prefix);
12788 SendToICS("abort\n");
12790 GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
12797 /* Resign. You can do this even if it's not your turn. */
12799 if (appData.icsActive) {
12800 SendToICS(ics_prefix);
12801 SendToICS("resign\n");
12803 switch (gameMode) {
12804 case MachinePlaysWhite:
12805 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12807 case MachinePlaysBlack:
12808 GameEnds(BlackWins, "White resigns", GE_PLAYER);
12811 if (cmailMsgLoaded) {
12813 if (WhiteOnMove(cmailOldMove)) {
12814 GameEnds(BlackWins, "White resigns", GE_PLAYER);
12816 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12818 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
12829 StopObservingEvent()
12831 /* Stop observing current games */
12832 SendToICS(ics_prefix);
12833 SendToICS("unobserve\n");
12837 StopExaminingEvent()
12839 /* Stop observing current game */
12840 SendToICS(ics_prefix);
12841 SendToICS("unexamine\n");
12845 ForwardInner(target)
12850 if (appData.debugMode)
12851 fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
12852 target, currentMove, forwardMostMove);
12854 if (gameMode == EditPosition)
12857 if (gameMode == PlayFromGameFile && !pausing)
12860 if (gameMode == IcsExamining && pausing)
12861 limit = pauseExamForwardMostMove;
12863 limit = forwardMostMove;
12865 if (target > limit) target = limit;
12867 if (target > 0 && moveList[target - 1][0]) {
12868 int fromX, fromY, toX, toY;
12869 toX = moveList[target - 1][2] - AAA;
12870 toY = moveList[target - 1][3] - ONE;
12871 if (moveList[target - 1][1] == '@') {
12872 if (appData.highlightLastMove) {
12873 SetHighlights(-1, -1, toX, toY);
12876 fromX = moveList[target - 1][0] - AAA;
12877 fromY = moveList[target - 1][1] - ONE;
12878 if (target == currentMove + 1) {
12879 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
12881 if (appData.highlightLastMove) {
12882 SetHighlights(fromX, fromY, toX, toY);
12886 if (gameMode == EditGame || gameMode == AnalyzeMode ||
12887 gameMode == Training || gameMode == PlayFromGameFile ||
12888 gameMode == AnalyzeFile) {
12889 while (currentMove < target) {
12890 SendMoveToProgram(currentMove++, &first);
12893 currentMove = target;
12896 if (gameMode == EditGame || gameMode == EndOfGame) {
12897 whiteTimeRemaining = timeRemaining[0][currentMove];
12898 blackTimeRemaining = timeRemaining[1][currentMove];
12900 DisplayBothClocks();
12901 DisplayMove(currentMove - 1);
12902 DrawPosition(FALSE, boards[currentMove]);
12903 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
12904 if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
12905 DisplayComment(currentMove - 1, commentList[currentMove]);
12913 if (gameMode == IcsExamining && !pausing) {
12914 SendToICS(ics_prefix);
12915 SendToICS("forward\n");
12917 ForwardInner(currentMove + 1);
12924 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12925 /* to optimze, we temporarily turn off analysis mode while we feed
12926 * the remaining moves to the engine. Otherwise we get analysis output
12929 if (first.analysisSupport) {
12930 SendToProgram("exit\nforce\n", &first);
12931 first.analyzing = FALSE;
12935 if (gameMode == IcsExamining && !pausing) {
12936 SendToICS(ics_prefix);
12937 SendToICS("forward 999999\n");
12939 ForwardInner(forwardMostMove);
12942 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12943 /* we have fed all the moves, so reactivate analysis mode */
12944 SendToProgram("analyze\n", &first);
12945 first.analyzing = TRUE;
12946 /*first.maybeThinking = TRUE;*/
12947 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12952 BackwardInner(target)
12955 int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
12957 if (appData.debugMode)
12958 fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
12959 target, currentMove, forwardMostMove);
12961 if (gameMode == EditPosition) return;
12962 if (currentMove <= backwardMostMove) {
12964 DrawPosition(full_redraw, boards[currentMove]);
12967 if (gameMode == PlayFromGameFile && !pausing)
12970 if (moveList[target][0]) {
12971 int fromX, fromY, toX, toY;
12972 toX = moveList[target][2] - AAA;
12973 toY = moveList[target][3] - ONE;
12974 if (moveList[target][1] == '@') {
12975 if (appData.highlightLastMove) {
12976 SetHighlights(-1, -1, toX, toY);
12979 fromX = moveList[target][0] - AAA;
12980 fromY = moveList[target][1] - ONE;
12981 if (target == currentMove - 1) {
12982 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
12984 if (appData.highlightLastMove) {
12985 SetHighlights(fromX, fromY, toX, toY);
12989 if (gameMode == EditGame || gameMode==AnalyzeMode ||
12990 gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
12991 while (currentMove > target) {
12992 SendToProgram("undo\n", &first);
12996 currentMove = target;
12999 if (gameMode == EditGame || gameMode == EndOfGame) {
13000 whiteTimeRemaining = timeRemaining[0][currentMove];
13001 blackTimeRemaining = timeRemaining[1][currentMove];
13003 DisplayBothClocks();
13004 DisplayMove(currentMove - 1);
13005 DrawPosition(full_redraw, boards[currentMove]);
13006 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
13007 // [HGM] PV info: routine tests if comment empty
13008 DisplayComment(currentMove - 1, commentList[currentMove]);
13014 if (gameMode == IcsExamining && !pausing) {
13015 SendToICS(ics_prefix);
13016 SendToICS("backward\n");
13018 BackwardInner(currentMove - 1);
13025 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13026 /* to optimize, we temporarily turn off analysis mode while we undo
13027 * all the moves. Otherwise we get analysis output after each undo.
13029 if (first.analysisSupport) {
13030 SendToProgram("exit\nforce\n", &first);
13031 first.analyzing = FALSE;
13035 if (gameMode == IcsExamining && !pausing) {
13036 SendToICS(ics_prefix);
13037 SendToICS("backward 999999\n");
13039 BackwardInner(backwardMostMove);
13042 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13043 /* we have fed all the moves, so reactivate analysis mode */
13044 SendToProgram("analyze\n", &first);
13045 first.analyzing = TRUE;
13046 /*first.maybeThinking = TRUE;*/
13047 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13054 if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
13055 if (to >= forwardMostMove) to = forwardMostMove;
13056 if (to <= backwardMostMove) to = backwardMostMove;
13057 if (to < currentMove) {
13065 RevertEvent(Boolean annotate)
13067 if(PopTail(annotate)) { // [HGM] vari: restore old game tail
13070 if (gameMode != IcsExamining) {
13071 DisplayError(_("You are not examining a game"), 0);
13075 DisplayError(_("You can't revert while pausing"), 0);
13078 SendToICS(ics_prefix);
13079 SendToICS("revert\n");
13085 switch (gameMode) {
13086 case MachinePlaysWhite:
13087 case MachinePlaysBlack:
13088 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
13089 DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
13092 if (forwardMostMove < 2) return;
13093 currentMove = forwardMostMove = forwardMostMove - 2;
13094 whiteTimeRemaining = timeRemaining[0][currentMove];
13095 blackTimeRemaining = timeRemaining[1][currentMove];
13096 DisplayBothClocks();
13097 DisplayMove(currentMove - 1);
13098 ClearHighlights();/*!! could figure this out*/
13099 DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
13100 SendToProgram("remove\n", &first);
13101 /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
13104 case BeginningOfGame:
13108 case IcsPlayingWhite:
13109 case IcsPlayingBlack:
13110 if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
13111 SendToICS(ics_prefix);
13112 SendToICS("takeback 2\n");
13114 SendToICS(ics_prefix);
13115 SendToICS("takeback 1\n");
13124 ChessProgramState *cps;
13126 switch (gameMode) {
13127 case MachinePlaysWhite:
13128 if (!WhiteOnMove(forwardMostMove)) {
13129 DisplayError(_("It is your turn"), 0);
13134 case MachinePlaysBlack:
13135 if (WhiteOnMove(forwardMostMove)) {
13136 DisplayError(_("It is your turn"), 0);
13141 case TwoMachinesPlay:
13142 if (WhiteOnMove(forwardMostMove) ==
13143 (first.twoMachinesColor[0] == 'w')) {
13149 case BeginningOfGame:
13153 SendToProgram("?\n", cps);
13157 TruncateGameEvent()
13160 if (gameMode != EditGame) return;
13167 CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
13168 if (forwardMostMove > currentMove) {
13169 if (gameInfo.resultDetails != NULL) {
13170 free(gameInfo.resultDetails);
13171 gameInfo.resultDetails = NULL;
13172 gameInfo.result = GameUnfinished;
13174 forwardMostMove = currentMove;
13175 HistorySet(parseList, backwardMostMove, forwardMostMove,
13183 if (appData.noChessProgram) return;
13184 switch (gameMode) {
13185 case MachinePlaysWhite:
13186 if (WhiteOnMove(forwardMostMove)) {
13187 DisplayError(_("Wait until your turn"), 0);
13191 case BeginningOfGame:
13192 case MachinePlaysBlack:
13193 if (!WhiteOnMove(forwardMostMove)) {
13194 DisplayError(_("Wait until your turn"), 0);
13199 DisplayError(_("No hint available"), 0);
13202 SendToProgram("hint\n", &first);
13203 hintRequested = TRUE;
13209 if (appData.noChessProgram) return;
13210 switch (gameMode) {
13211 case MachinePlaysWhite:
13212 if (WhiteOnMove(forwardMostMove)) {
13213 DisplayError(_("Wait until your turn"), 0);
13217 case BeginningOfGame:
13218 case MachinePlaysBlack:
13219 if (!WhiteOnMove(forwardMostMove)) {
13220 DisplayError(_("Wait until your turn"), 0);
13225 EditPositionDone(TRUE);
13227 case TwoMachinesPlay:
13232 SendToProgram("bk\n", &first);
13233 bookOutput[0] = NULLCHAR;
13234 bookRequested = TRUE;
13240 char *tags = PGNTags(&gameInfo);
13241 TagsPopUp(tags, CmailMsg());
13245 /* end button procedures */
13248 PrintPosition(fp, move)
13254 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13255 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
13256 char c = PieceToChar(boards[move][i][j]);
13257 fputc(c == 'x' ? '.' : c, fp);
13258 fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
13261 if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
13262 fprintf(fp, "white to play\n");
13264 fprintf(fp, "black to play\n");
13271 if (gameInfo.white != NULL) {
13272 fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
13278 /* Find last component of program's own name, using some heuristics */
13280 TidyProgramName(prog, host, buf)
13281 char *prog, *host, buf[MSG_SIZ];
13284 int local = (strcmp(host, "localhost") == 0);
13285 while (!local && (p = strchr(prog, ';')) != NULL) {
13287 while (*p == ' ') p++;
13290 if (*prog == '"' || *prog == '\'') {
13291 q = strchr(prog + 1, *prog);
13293 q = strchr(prog, ' ');
13295 if (q == NULL) q = prog + strlen(prog);
13297 while (p >= prog && *p != '/' && *p != '\\') p--;
13299 if(p == prog && *p == '"') p++;
13300 if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
13301 memcpy(buf, p, q - p);
13302 buf[q - p] = NULLCHAR;
13310 TimeControlTagValue()
13313 if (!appData.clockMode) {
13314 safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
13315 } else if (movesPerSession > 0) {
13316 snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
13317 } else if (timeIncrement == 0) {
13318 snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
13320 snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
13322 return StrSave(buf);
13328 /* This routine is used only for certain modes */
13329 VariantClass v = gameInfo.variant;
13330 ChessMove r = GameUnfinished;
13333 if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
13334 r = gameInfo.result;
13335 p = gameInfo.resultDetails;
13336 gameInfo.resultDetails = NULL;
13338 ClearGameInfo(&gameInfo);
13339 gameInfo.variant = v;
13341 switch (gameMode) {
13342 case MachinePlaysWhite:
13343 gameInfo.event = StrSave( appData.pgnEventHeader );
13344 gameInfo.site = StrSave(HostName());
13345 gameInfo.date = PGNDate();
13346 gameInfo.round = StrSave("-");
13347 gameInfo.white = StrSave(first.tidy);
13348 gameInfo.black = StrSave(UserName());
13349 gameInfo.timeControl = TimeControlTagValue();
13352 case MachinePlaysBlack:
13353 gameInfo.event = StrSave( appData.pgnEventHeader );
13354 gameInfo.site = StrSave(HostName());
13355 gameInfo.date = PGNDate();
13356 gameInfo.round = StrSave("-");
13357 gameInfo.white = StrSave(UserName());
13358 gameInfo.black = StrSave(first.tidy);
13359 gameInfo.timeControl = TimeControlTagValue();
13362 case TwoMachinesPlay:
13363 gameInfo.event = StrSave( appData.pgnEventHeader );
13364 gameInfo.site = StrSave(HostName());
13365 gameInfo.date = PGNDate();
13366 if (matchGame > 0) {
13368 snprintf(buf, MSG_SIZ, "%d", matchGame);
13369 gameInfo.round = StrSave(buf);
13371 gameInfo.round = StrSave("-");
13373 if (first.twoMachinesColor[0] == 'w') {
13374 gameInfo.white = StrSave(first.tidy);
13375 gameInfo.black = StrSave(second.tidy);
13377 gameInfo.white = StrSave(second.tidy);
13378 gameInfo.black = StrSave(first.tidy);
13380 gameInfo.timeControl = TimeControlTagValue();
13384 gameInfo.event = StrSave("Edited game");
13385 gameInfo.site = StrSave(HostName());
13386 gameInfo.date = PGNDate();
13387 gameInfo.round = StrSave("-");
13388 gameInfo.white = StrSave("-");
13389 gameInfo.black = StrSave("-");
13390 gameInfo.result = r;
13391 gameInfo.resultDetails = p;
13395 gameInfo.event = StrSave("Edited position");
13396 gameInfo.site = StrSave(HostName());
13397 gameInfo.date = PGNDate();
13398 gameInfo.round = StrSave("-");
13399 gameInfo.white = StrSave("-");
13400 gameInfo.black = StrSave("-");
13403 case IcsPlayingWhite:
13404 case IcsPlayingBlack:
13409 case PlayFromGameFile:
13410 gameInfo.event = StrSave("Game from non-PGN file");
13411 gameInfo.site = StrSave(HostName());
13412 gameInfo.date = PGNDate();
13413 gameInfo.round = StrSave("-");
13414 gameInfo.white = StrSave("?");
13415 gameInfo.black = StrSave("?");
13424 ReplaceComment(index, text)
13432 if(index && sscanf(text, "%f/%d", &score, &len) == 2 &&
13433 pvInfoList[index-1].depth == len &&
13434 fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
13435 (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
13436 while (*text == '\n') text++;
13437 len = strlen(text);
13438 while (len > 0 && text[len - 1] == '\n') len--;
13440 if (commentList[index] != NULL)
13441 free(commentList[index]);
13444 commentList[index] = NULL;
13447 if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
13448 *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
13449 *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
13450 commentList[index] = (char *) malloc(len + 2);
13451 strncpy(commentList[index], text, len);
13452 commentList[index][len] = '\n';
13453 commentList[index][len + 1] = NULLCHAR;
13455 // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
13457 commentList[index] = (char *) malloc(len + 7);
13458 safeStrCpy(commentList[index], "{\n", 3);
13459 safeStrCpy(commentList[index]+2, text, len+1);
13460 commentList[index][len+2] = NULLCHAR;
13461 while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
13462 strcat(commentList[index], "\n}\n");
13476 if (ch == '\r') continue;
13478 } while (ch != '\0');
13482 AppendComment(index, text, addBraces)
13485 Boolean addBraces; // [HGM] braces: tells if we should add {}
13490 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
13491 text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
13494 while (*text == '\n') text++;
13495 len = strlen(text);
13496 while (len > 0 && text[len - 1] == '\n') len--;
13498 if (len == 0) return;
13500 if (commentList[index] != NULL) {
13501 old = commentList[index];
13502 oldlen = strlen(old);
13503 while(commentList[index][oldlen-1] == '\n')
13504 commentList[index][--oldlen] = NULLCHAR;
13505 commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
13506 safeStrCpy(commentList[index], old, oldlen + len + 6);
13508 // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
13509 if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
13510 if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
13511 while (*text == '\n') { text++; len--; }
13512 commentList[index][--oldlen] = NULLCHAR;
13514 if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
13515 else strcat(commentList[index], "\n");
13516 strcat(commentList[index], text);
13517 if(addBraces) strcat(commentList[index], addBraces == 2 ? ")\n" : "\n}\n");
13518 else strcat(commentList[index], "\n");
13520 commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
13522 safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
13523 else commentList[index][0] = NULLCHAR;
13524 strcat(commentList[index], text);
13525 strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
13526 if(addBraces == TRUE) strcat(commentList[index], "}\n");
13530 static char * FindStr( char * text, char * sub_text )
13532 char * result = strstr( text, sub_text );
13534 if( result != NULL ) {
13535 result += strlen( sub_text );
13541 /* [AS] Try to extract PV info from PGN comment */
13542 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
13543 char *GetInfoFromComment( int index, char * text )
13545 char * sep = text, *p;
13547 if( text != NULL && index > 0 ) {
13550 int time = -1, sec = 0, deci;
13551 char * s_eval = FindStr( text, "[%eval " );
13552 char * s_emt = FindStr( text, "[%emt " );
13554 if( s_eval != NULL || s_emt != NULL ) {
13558 if( s_eval != NULL ) {
13559 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
13563 if( delim != ']' ) {
13568 if( s_emt != NULL ) {
13573 /* We expect something like: [+|-]nnn.nn/dd */
13576 if(*text != '{') return text; // [HGM] braces: must be normal comment
13578 sep = strchr( text, '/' );
13579 if( sep == NULL || sep < (text+4) ) {
13584 if(p[1] == '(') { // comment starts with PV
13585 p = strchr(p, ')'); // locate end of PV
13586 if(p == NULL || sep < p+5) return text;
13587 // at this point we have something like "{(.*) +0.23/6 ..."
13588 p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
13589 *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
13590 // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
13592 time = -1; sec = -1; deci = -1;
13593 if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
13594 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
13595 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
13596 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3 ) {
13600 if( score_lo < 0 || score_lo >= 100 ) {
13604 if(sec >= 0) time = 600*time + 10*sec; else
13605 if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
13607 score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
13609 /* [HGM] PV time: now locate end of PV info */
13610 while( *++sep >= '0' && *sep <= '9'); // strip depth
13612 while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
13614 while( *++sep >= '0' && *sep <= '9'); // strip seconds
13616 while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
13617 while(*sep == ' ') sep++;
13628 pvInfoList[index-1].depth = depth;
13629 pvInfoList[index-1].score = score;
13630 pvInfoList[index-1].time = 10*time; // centi-sec
13631 if(*sep == '}') *sep = 0; else *--sep = '{';
13632 if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
13638 SendToProgram(message, cps)
13640 ChessProgramState *cps;
13642 int count, outCount, error;
13645 if (cps->pr == NULL) return;
13648 if (appData.debugMode) {
13651 fprintf(debugFP, "%ld >%-6s: %s",
13652 SubtractTimeMarks(&now, &programStartTime),
13653 cps->which, message);
13656 count = strlen(message);
13657 outCount = OutputToProcess(cps->pr, message, count, &error);
13658 if (outCount < count && !exiting
13659 && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
13660 snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
13661 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
13662 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
13663 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
13664 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
13666 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
13668 gameInfo.resultDetails = StrSave(buf);
13670 if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
13675 ReceiveFromProgram(isr, closure, message, count, error)
13676 InputSourceRef isr;
13684 ChessProgramState *cps = (ChessProgramState *)closure;
13686 if (isr != cps->isr) return; /* Killed intentionally */
13689 snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
13690 _(cps->which), cps->program);
13691 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
13692 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
13693 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
13694 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
13696 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
13698 gameInfo.resultDetails = StrSave(buf);
13700 RemoveInputSource(cps->isr);
13701 if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
13703 snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
13704 _(cps->which), cps->program);
13705 RemoveInputSource(cps->isr);
13707 /* [AS] Program is misbehaving badly... kill it */
13708 if( count == -2 ) {
13709 DestroyChildProcess( cps->pr, 9 );
13713 if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
13718 if ((end_str = strchr(message, '\r')) != NULL)
13719 *end_str = NULLCHAR;
13720 if ((end_str = strchr(message, '\n')) != NULL)
13721 *end_str = NULLCHAR;
13723 if (appData.debugMode) {
13724 TimeMark now; int print = 1;
13725 char *quote = ""; char c; int i;
13727 if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
13728 char start = message[0];
13729 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
13730 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
13731 sscanf(message, "move %c", &c)!=1 && sscanf(message, "offer%c", &c)!=1 &&
13732 sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
13733 sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
13734 sscanf(message, "tell%c", &c)!=1 && sscanf(message, "0-1 %c", &c)!=1 &&
13735 sscanf(message, "1-0 %c", &c)!=1 && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
13736 sscanf(message, "setboard %c", &c)!=1 && sscanf(message, "setup %c", &c)!=1 &&
13737 sscanf(message, "hint: %c", &c)!=1 &&
13738 sscanf(message, "pong %c", &c)!=1 && start != '#') {
13739 quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
13740 print = (appData.engineComments >= 2);
13742 message[0] = start; // restore original message
13746 fprintf(debugFP, "%ld <%-6s: %s%s\n",
13747 SubtractTimeMarks(&now, &programStartTime), cps->which,
13753 /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
13754 if (appData.icsEngineAnalyze) {
13755 if (strstr(message, "whisper") != NULL ||
13756 strstr(message, "kibitz") != NULL ||
13757 strstr(message, "tellics") != NULL) return;
13760 HandleMachineMove(message, cps);
13765 SendTimeControl(cps, mps, tc, inc, sd, st)
13766 ChessProgramState *cps;
13767 int mps, inc, sd, st;
13773 if( timeControl_2 > 0 ) {
13774 if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
13775 tc = timeControl_2;
13778 tc /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
13779 inc /= cps->timeOdds;
13780 st /= cps->timeOdds;
13782 seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
13785 /* Set exact time per move, normally using st command */
13786 if (cps->stKludge) {
13787 /* GNU Chess 4 has no st command; uses level in a nonstandard way */
13789 if (seconds == 0) {
13790 snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
13792 snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
13795 snprintf(buf, MSG_SIZ, "st %d\n", st);
13798 /* Set conventional or incremental time control, using level command */
13799 if (seconds == 0) {
13800 /* Note old gnuchess bug -- minutes:seconds used to not work.
13801 Fixed in later versions, but still avoid :seconds
13802 when seconds is 0. */
13803 snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
13805 snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
13806 seconds, inc/1000.);
13809 SendToProgram(buf, cps);
13811 /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
13812 /* Orthogonally, limit search to given depth */
13814 if (cps->sdKludge) {
13815 snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
13817 snprintf(buf, MSG_SIZ, "sd %d\n", sd);
13819 SendToProgram(buf, cps);
13822 if(cps->nps >= 0) { /* [HGM] nps */
13823 if(cps->supportsNPS == FALSE)
13824 cps->nps = -1; // don't use if engine explicitly says not supported!
13826 snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
13827 SendToProgram(buf, cps);
13832 ChessProgramState *WhitePlayer()
13833 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
13835 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
13836 gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
13842 SendTimeRemaining(cps, machineWhite)
13843 ChessProgramState *cps;
13844 int /*boolean*/ machineWhite;
13846 char message[MSG_SIZ];
13849 /* Note: this routine must be called when the clocks are stopped
13850 or when they have *just* been set or switched; otherwise
13851 it will be off by the time since the current tick started.
13853 if (machineWhite) {
13854 time = whiteTimeRemaining / 10;
13855 otime = blackTimeRemaining / 10;
13857 time = blackTimeRemaining / 10;
13858 otime = whiteTimeRemaining / 10;
13860 /* [HGM] translate opponent's time by time-odds factor */
13861 otime = (otime * cps->other->timeOdds) / cps->timeOdds;
13862 if (appData.debugMode) {
13863 fprintf(debugFP, "time odds: %f %f \n", cps->timeOdds, cps->other->timeOdds);
13866 if (time <= 0) time = 1;
13867 if (otime <= 0) otime = 1;
13869 snprintf(message, MSG_SIZ, "time %ld\n", time);
13870 SendToProgram(message, cps);
13872 snprintf(message, MSG_SIZ, "otim %ld\n", otime);
13873 SendToProgram(message, cps);
13877 BoolFeature(p, name, loc, cps)
13881 ChessProgramState *cps;
13884 int len = strlen(name);
13887 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
13889 sscanf(*p, "%d", &val);
13891 while (**p && **p != ' ')
13893 snprintf(buf, MSG_SIZ, "accepted %s\n", name);
13894 SendToProgram(buf, cps);
13901 IntFeature(p, name, loc, cps)
13905 ChessProgramState *cps;
13908 int len = strlen(name);
13909 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
13911 sscanf(*p, "%d", loc);
13912 while (**p && **p != ' ') (*p)++;
13913 snprintf(buf, MSG_SIZ, "accepted %s\n", name);
13914 SendToProgram(buf, cps);
13921 StringFeature(p, name, loc, cps)
13925 ChessProgramState *cps;
13928 int len = strlen(name);
13929 if (strncmp((*p), name, len) == 0
13930 && (*p)[len] == '=' && (*p)[len+1] == '\"') {
13932 sscanf(*p, "%[^\"]", loc);
13933 while (**p && **p != '\"') (*p)++;
13934 if (**p == '\"') (*p)++;
13935 snprintf(buf, MSG_SIZ, "accepted %s\n", name);
13936 SendToProgram(buf, cps);
13943 ParseOption(Option *opt, ChessProgramState *cps)
13944 // [HGM] options: process the string that defines an engine option, and determine
13945 // name, type, default value, and allowed value range
13947 char *p, *q, buf[MSG_SIZ];
13948 int n, min = (-1)<<31, max = 1<<31, def;
13950 if(p = strstr(opt->name, " -spin ")) {
13951 if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
13952 if(max < min) max = min; // enforce consistency
13953 if(def < min) def = min;
13954 if(def > max) def = max;
13959 } else if((p = strstr(opt->name, " -slider "))) {
13960 // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
13961 if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
13962 if(max < min) max = min; // enforce consistency
13963 if(def < min) def = min;
13964 if(def > max) def = max;
13968 opt->type = Spin; // Slider;
13969 } else if((p = strstr(opt->name, " -string "))) {
13970 opt->textValue = p+9;
13971 opt->type = TextBox;
13972 } else if((p = strstr(opt->name, " -file "))) {
13973 // for now -file is a synonym for -string, to already provide compatibility with future polyglots
13974 opt->textValue = p+7;
13975 opt->type = FileName; // FileName;
13976 } else if((p = strstr(opt->name, " -path "))) {
13977 // for now -file is a synonym for -string, to already provide compatibility with future polyglots
13978 opt->textValue = p+7;
13979 opt->type = PathName; // PathName;
13980 } else if(p = strstr(opt->name, " -check ")) {
13981 if(sscanf(p, " -check %d", &def) < 1) return FALSE;
13982 opt->value = (def != 0);
13983 opt->type = CheckBox;
13984 } else if(p = strstr(opt->name, " -combo ")) {
13985 opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
13986 cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
13987 if(*q == '*') cps->comboList[cps->comboCnt-1]++;
13988 opt->value = n = 0;
13989 while(q = StrStr(q, " /// ")) {
13990 n++; *q = 0; // count choices, and null-terminate each of them
13992 if(*q == '*') { // remember default, which is marked with * prefix
13996 cps->comboList[cps->comboCnt++] = q;
13998 cps->comboList[cps->comboCnt++] = NULL;
14000 opt->type = ComboBox;
14001 } else if(p = strstr(opt->name, " -button")) {
14002 opt->type = Button;
14003 } else if(p = strstr(opt->name, " -save")) {
14004 opt->type = SaveButton;
14005 } else return FALSE;
14006 *p = 0; // terminate option name
14007 // now look if the command-line options define a setting for this engine option.
14008 if(cps->optionSettings && cps->optionSettings[0])
14009 p = strstr(cps->optionSettings, opt->name); else p = NULL;
14010 if(p && (p == cps->optionSettings || p[-1] == ',')) {
14011 snprintf(buf, MSG_SIZ, "option %s", p);
14012 if(p = strstr(buf, ",")) *p = 0;
14013 if(q = strchr(buf, '=')) switch(opt->type) {
14015 for(n=0; n<opt->max; n++)
14016 if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
14019 safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
14023 opt->value = atoi(q+1);
14028 SendToProgram(buf, cps);
14034 FeatureDone(cps, val)
14035 ChessProgramState* cps;
14038 DelayedEventCallback cb = GetDelayedEvent();
14039 if ((cb == InitBackEnd3 && cps == &first) ||
14040 (cb == SettingsMenuIfReady && cps == &second) ||
14041 (cb == TwoMachinesEventIfReady && cps == &second)) {
14042 CancelDelayedEvent();
14043 ScheduleDelayedEvent(cb, val ? 1 : 3600000);
14045 cps->initDone = val;
14048 /* Parse feature command from engine */
14050 ParseFeatures(args, cps)
14052 ChessProgramState *cps;
14060 while (*p == ' ') p++;
14061 if (*p == NULLCHAR) return;
14063 if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
14064 if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
14065 if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
14066 if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
14067 if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
14068 if (BoolFeature(&p, "reuse", &val, cps)) {
14069 /* Engine can disable reuse, but can't enable it if user said no */
14070 if (!val) cps->reuse = FALSE;
14073 if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
14074 if (StringFeature(&p, "myname", &cps->tidy, cps)) {
14075 if (gameMode == TwoMachinesPlay) {
14076 DisplayTwoMachinesTitle();
14082 if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
14083 if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
14084 if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
14085 if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
14086 if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
14087 if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
14088 if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
14089 if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
14090 if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
14091 if (IntFeature(&p, "done", &val, cps)) {
14092 FeatureDone(cps, val);
14095 /* Added by Tord: */
14096 if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
14097 if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
14098 /* End of additions by Tord */
14100 /* [HGM] added features: */
14101 if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
14102 if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
14103 if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
14104 if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
14105 if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
14106 if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
14107 if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
14108 if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
14109 snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
14110 SendToProgram(buf, cps);
14113 if(cps->nrOptions >= MAX_OPTIONS) {
14115 snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
14116 DisplayError(buf, 0);
14120 /* End of additions by HGM */
14122 /* unknown feature: complain and skip */
14124 while (*q && *q != '=') q++;
14125 snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
14126 SendToProgram(buf, cps);
14132 while (*p && *p != '\"') p++;
14133 if (*p == '\"') p++;
14135 while (*p && *p != ' ') p++;
14143 PeriodicUpdatesEvent(newState)
14146 if (newState == appData.periodicUpdates)
14149 appData.periodicUpdates=newState;
14151 /* Display type changes, so update it now */
14152 // DisplayAnalysis();
14154 /* Get the ball rolling again... */
14156 AnalysisPeriodicEvent(1);
14157 StartAnalysisClock();
14162 PonderNextMoveEvent(newState)
14165 if (newState == appData.ponderNextMove) return;
14166 if (gameMode == EditPosition) EditPositionDone(TRUE);
14168 SendToProgram("hard\n", &first);
14169 if (gameMode == TwoMachinesPlay) {
14170 SendToProgram("hard\n", &second);
14173 SendToProgram("easy\n", &first);
14174 thinkOutput[0] = NULLCHAR;
14175 if (gameMode == TwoMachinesPlay) {
14176 SendToProgram("easy\n", &second);
14179 appData.ponderNextMove = newState;
14183 NewSettingEvent(option, feature, command, value)
14185 int option, value, *feature;
14189 if (gameMode == EditPosition) EditPositionDone(TRUE);
14190 snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
14191 if(feature == NULL || *feature) SendToProgram(buf, &first);
14192 if (gameMode == TwoMachinesPlay) {
14193 if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
14198 ShowThinkingEvent()
14199 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
14201 static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
14202 int newState = appData.showThinking
14203 // [HGM] thinking: other features now need thinking output as well
14204 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
14206 if (oldState == newState) return;
14207 oldState = newState;
14208 if (gameMode == EditPosition) EditPositionDone(TRUE);
14210 SendToProgram("post\n", &first);
14211 if (gameMode == TwoMachinesPlay) {
14212 SendToProgram("post\n", &second);
14215 SendToProgram("nopost\n", &first);
14216 thinkOutput[0] = NULLCHAR;
14217 if (gameMode == TwoMachinesPlay) {
14218 SendToProgram("nopost\n", &second);
14221 // appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
14225 AskQuestionEvent(title, question, replyPrefix, which)
14226 char *title; char *question; char *replyPrefix; char *which;
14228 ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
14229 if (pr == NoProc) return;
14230 AskQuestion(title, question, replyPrefix, pr);
14234 DisplayMove(moveNumber)
14237 char message[MSG_SIZ];
14239 char cpThinkOutput[MSG_SIZ];
14241 if(appData.noGUI) return; // [HGM] fast: suppress display of moves
14243 if (moveNumber == forwardMostMove - 1 ||
14244 gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14246 safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
14248 if (strchr(cpThinkOutput, '\n')) {
14249 *strchr(cpThinkOutput, '\n') = NULLCHAR;
14252 *cpThinkOutput = NULLCHAR;
14255 /* [AS] Hide thinking from human user */
14256 if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
14257 *cpThinkOutput = NULLCHAR;
14258 if( thinkOutput[0] != NULLCHAR ) {
14261 for( i=0; i<=hiddenThinkOutputState; i++ ) {
14262 cpThinkOutput[i] = '.';
14264 cpThinkOutput[i] = NULLCHAR;
14265 hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
14269 if (moveNumber == forwardMostMove - 1 &&
14270 gameInfo.resultDetails != NULL) {
14271 if (gameInfo.resultDetails[0] == NULLCHAR) {
14272 snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
14274 snprintf(res, MSG_SIZ, " {%s} %s",
14275 T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
14281 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
14282 DisplayMessage(res, cpThinkOutput);
14284 snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
14285 WhiteOnMove(moveNumber) ? " " : ".. ",
14286 parseList[moveNumber], res);
14287 DisplayMessage(message, cpThinkOutput);
14292 DisplayComment(moveNumber, text)
14296 char title[MSG_SIZ];
14297 char buf[8000]; // comment can be long!
14300 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
14301 safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
14303 snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
14304 WhiteOnMove(moveNumber) ? " " : ".. ",
14305 parseList[moveNumber]);
14307 // [HGM] PV info: display PV info together with (or as) comment
14308 if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {
14309 if(text == NULL) text = "";
14310 score = pvInfoList[moveNumber].score;
14311 snprintf(buf,sizeof(buf)/sizeof(buf[0]), "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,
14312 depth, (pvInfoList[moveNumber].time+50)/100, text);
14315 if (text != NULL && (appData.autoDisplayComment || commentUp))
14316 CommentPopUp(title, text);
14319 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
14320 * might be busy thinking or pondering. It can be omitted if your
14321 * gnuchess is configured to stop thinking immediately on any user
14322 * input. However, that gnuchess feature depends on the FIONREAD
14323 * ioctl, which does not work properly on some flavors of Unix.
14327 ChessProgramState *cps;
14330 if (!cps->useSigint) return;
14331 if (appData.noChessProgram || (cps->pr == NoProc)) return;
14332 switch (gameMode) {
14333 case MachinePlaysWhite:
14334 case MachinePlaysBlack:
14335 case TwoMachinesPlay:
14336 case IcsPlayingWhite:
14337 case IcsPlayingBlack:
14340 /* Skip if we know it isn't thinking */
14341 if (!cps->maybeThinking) return;
14342 if (appData.debugMode)
14343 fprintf(debugFP, "Interrupting %s\n", cps->which);
14344 InterruptChildProcess(cps->pr);
14345 cps->maybeThinking = FALSE;
14350 #endif /*ATTENTION*/
14356 if (whiteTimeRemaining <= 0) {
14359 if (appData.icsActive) {
14360 if (appData.autoCallFlag &&
14361 gameMode == IcsPlayingBlack && !blackFlag) {
14362 SendToICS(ics_prefix);
14363 SendToICS("flag\n");
14367 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
14369 if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
14370 if (appData.autoCallFlag) {
14371 GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
14378 if (blackTimeRemaining <= 0) {
14381 if (appData.icsActive) {
14382 if (appData.autoCallFlag &&
14383 gameMode == IcsPlayingWhite && !whiteFlag) {
14384 SendToICS(ics_prefix);
14385 SendToICS("flag\n");
14389 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
14391 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
14392 if (appData.autoCallFlag) {
14393 GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
14406 if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
14407 gameMode == PlayFromGameFile || forwardMostMove == 0) return;
14410 * add time to clocks when time control is achieved ([HGM] now also used for increment)
14412 if ( !WhiteOnMove(forwardMostMove) ) {
14413 /* White made time control */
14414 lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
14415 whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
14416 /* [HGM] time odds: correct new time quota for time odds! */
14417 / WhitePlayer()->timeOdds;
14418 lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
14420 lastBlack -= blackTimeRemaining;
14421 /* Black made time control */
14422 blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
14423 / WhitePlayer()->other->timeOdds;
14424 lastWhite = whiteTimeRemaining;
14429 DisplayBothClocks()
14431 int wom = gameMode == EditPosition ?
14432 !blackPlaysFirst : WhiteOnMove(currentMove);
14433 DisplayWhiteClock(whiteTimeRemaining, wom);
14434 DisplayBlackClock(blackTimeRemaining, !wom);
14438 /* Timekeeping seems to be a portability nightmare. I think everyone
14439 has ftime(), but I'm really not sure, so I'm including some ifdefs
14440 to use other calls if you don't. Clocks will be less accurate if
14441 you have neither ftime nor gettimeofday.
14444 /* VS 2008 requires the #include outside of the function */
14445 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
14446 #include <sys/timeb.h>
14449 /* Get the current time as a TimeMark */
14454 #if HAVE_GETTIMEOFDAY
14456 struct timeval timeVal;
14457 struct timezone timeZone;
14459 gettimeofday(&timeVal, &timeZone);
14460 tm->sec = (long) timeVal.tv_sec;
14461 tm->ms = (int) (timeVal.tv_usec / 1000L);
14463 #else /*!HAVE_GETTIMEOFDAY*/
14466 // include <sys/timeb.h> / moved to just above start of function
14467 struct timeb timeB;
14470 tm->sec = (long) timeB.time;
14471 tm->ms = (int) timeB.millitm;
14473 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
14474 tm->sec = (long) time(NULL);
14480 /* Return the difference in milliseconds between two
14481 time marks. We assume the difference will fit in a long!
14484 SubtractTimeMarks(tm2, tm1)
14485 TimeMark *tm2, *tm1;
14487 return 1000L*(tm2->sec - tm1->sec) +
14488 (long) (tm2->ms - tm1->ms);
14493 * Code to manage the game clocks.
14495 * In tournament play, black starts the clock and then white makes a move.
14496 * We give the human user a slight advantage if he is playing white---the
14497 * clocks don't run until he makes his first move, so it takes zero time.
14498 * Also, we don't account for network lag, so we could get out of sync
14499 * with GNU Chess's clock -- but then, referees are always right.
14502 static TimeMark tickStartTM;
14503 static long intendedTickLength;
14506 NextTickLength(timeRemaining)
14507 long timeRemaining;
14509 long nominalTickLength, nextTickLength;
14511 if (timeRemaining > 0L && timeRemaining <= 10000L)
14512 nominalTickLength = 100L;
14514 nominalTickLength = 1000L;
14515 nextTickLength = timeRemaining % nominalTickLength;
14516 if (nextTickLength <= 0) nextTickLength += nominalTickLength;
14518 return nextTickLength;
14521 /* Adjust clock one minute up or down */
14523 AdjustClock(Boolean which, int dir)
14525 if(which) blackTimeRemaining += 60000*dir;
14526 else whiteTimeRemaining += 60000*dir;
14527 DisplayBothClocks();
14530 /* Stop clocks and reset to a fresh time control */
14534 (void) StopClockTimer();
14535 if (appData.icsActive) {
14536 whiteTimeRemaining = blackTimeRemaining = 0;
14537 } else if (searchTime) {
14538 whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
14539 blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
14540 } else { /* [HGM] correct new time quote for time odds */
14541 whiteTC = blackTC = fullTimeControlString;
14542 whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
14543 blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
14545 if (whiteFlag || blackFlag) {
14547 whiteFlag = blackFlag = FALSE;
14549 lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
14550 DisplayBothClocks();
14553 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
14555 /* Decrement running clock by amount of time that has passed */
14559 long timeRemaining;
14560 long lastTickLength, fudge;
14563 if (!appData.clockMode) return;
14564 if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
14568 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
14570 /* Fudge if we woke up a little too soon */
14571 fudge = intendedTickLength - lastTickLength;
14572 if (fudge < 0 || fudge > FUDGE) fudge = 0;
14574 if (WhiteOnMove(forwardMostMove)) {
14575 if(whiteNPS >= 0) lastTickLength = 0;
14576 timeRemaining = whiteTimeRemaining -= lastTickLength;
14577 if(timeRemaining < 0 && !appData.icsActive) {
14578 GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
14579 if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
14580 whiteStartMove = forwardMostMove; whiteTC = nextSession;
14581 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
14584 DisplayWhiteClock(whiteTimeRemaining - fudge,
14585 WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
14587 if(blackNPS >= 0) lastTickLength = 0;
14588 timeRemaining = blackTimeRemaining -= lastTickLength;
14589 if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
14590 GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
14592 blackStartMove = forwardMostMove;
14593 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
14596 DisplayBlackClock(blackTimeRemaining - fudge,
14597 !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
14599 if (CheckFlags()) return;
14602 intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
14603 StartClockTimer(intendedTickLength);
14605 /* if the time remaining has fallen below the alarm threshold, sound the
14606 * alarm. if the alarm has sounded and (due to a takeback or time control
14607 * with increment) the time remaining has increased to a level above the
14608 * threshold, reset the alarm so it can sound again.
14611 if (appData.icsActive && appData.icsAlarm) {
14613 /* make sure we are dealing with the user's clock */
14614 if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
14615 ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
14618 if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
14619 alarmSounded = FALSE;
14620 } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
14622 alarmSounded = TRUE;
14628 /* A player has just moved, so stop the previously running
14629 clock and (if in clock mode) start the other one.
14630 We redisplay both clocks in case we're in ICS mode, because
14631 ICS gives us an update to both clocks after every move.
14632 Note that this routine is called *after* forwardMostMove
14633 is updated, so the last fractional tick must be subtracted
14634 from the color that is *not* on move now.
14637 SwitchClocks(int newMoveNr)
14639 long lastTickLength;
14641 int flagged = FALSE;
14645 if (StopClockTimer() && appData.clockMode) {
14646 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
14647 if (!WhiteOnMove(forwardMostMove)) {
14648 if(blackNPS >= 0) lastTickLength = 0;
14649 blackTimeRemaining -= lastTickLength;
14650 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
14651 // if(pvInfoList[forwardMostMove].time == -1)
14652 pvInfoList[forwardMostMove].time = // use GUI time
14653 (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
14655 if(whiteNPS >= 0) lastTickLength = 0;
14656 whiteTimeRemaining -= lastTickLength;
14657 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
14658 // if(pvInfoList[forwardMostMove].time == -1)
14659 pvInfoList[forwardMostMove].time =
14660 (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
14662 flagged = CheckFlags();
14664 forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
14665 CheckTimeControl();
14667 if (flagged || !appData.clockMode) return;
14669 switch (gameMode) {
14670 case MachinePlaysBlack:
14671 case MachinePlaysWhite:
14672 case BeginningOfGame:
14673 if (pausing) return;
14677 case PlayFromGameFile:
14685 if (searchTime) { // [HGM] st: set clock of player that has to move to max time
14686 if(WhiteOnMove(forwardMostMove))
14687 whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
14688 else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
14692 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
14693 whiteTimeRemaining : blackTimeRemaining);
14694 StartClockTimer(intendedTickLength);
14698 /* Stop both clocks */
14702 long lastTickLength;
14705 if (!StopClockTimer()) return;
14706 if (!appData.clockMode) return;
14710 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
14711 if (WhiteOnMove(forwardMostMove)) {
14712 if(whiteNPS >= 0) lastTickLength = 0;
14713 whiteTimeRemaining -= lastTickLength;
14714 DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
14716 if(blackNPS >= 0) lastTickLength = 0;
14717 blackTimeRemaining -= lastTickLength;
14718 DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
14723 /* Start clock of player on move. Time may have been reset, so
14724 if clock is already running, stop and restart it. */
14728 (void) StopClockTimer(); /* in case it was running already */
14729 DisplayBothClocks();
14730 if (CheckFlags()) return;
14732 if (!appData.clockMode) return;
14733 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
14735 GetTimeMark(&tickStartTM);
14736 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
14737 whiteTimeRemaining : blackTimeRemaining);
14739 /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
14740 whiteNPS = blackNPS = -1;
14741 if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
14742 || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
14743 whiteNPS = first.nps;
14744 if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
14745 || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
14746 blackNPS = first.nps;
14747 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
14748 whiteNPS = second.nps;
14749 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
14750 blackNPS = second.nps;
14751 if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
14753 StartClockTimer(intendedTickLength);
14760 long second, minute, hour, day;
14762 static char buf[32];
14764 if (ms > 0 && ms <= 9900) {
14765 /* convert milliseconds to tenths, rounding up */
14766 double tenths = floor( ((double)(ms + 99L)) / 100.00 );
14768 snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
14772 /* convert milliseconds to seconds, rounding up */
14773 /* use floating point to avoid strangeness of integer division
14774 with negative dividends on many machines */
14775 second = (long) floor(((double) (ms + 999L)) / 1000.0);
14782 day = second / (60 * 60 * 24);
14783 second = second % (60 * 60 * 24);
14784 hour = second / (60 * 60);
14785 second = second % (60 * 60);
14786 minute = second / 60;
14787 second = second % 60;
14790 snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
14791 sign, day, hour, minute, second);
14793 snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
14795 snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
14802 * This is necessary because some C libraries aren't ANSI C compliant yet.
14805 StrStr(string, match)
14806 char *string, *match;
14810 length = strlen(match);
14812 for (i = strlen(string) - length; i >= 0; i--, string++)
14813 if (!strncmp(match, string, length))
14820 StrCaseStr(string, match)
14821 char *string, *match;
14825 length = strlen(match);
14827 for (i = strlen(string) - length; i >= 0; i--, string++) {
14828 for (j = 0; j < length; j++) {
14829 if (ToLower(match[j]) != ToLower(string[j]))
14832 if (j == length) return string;
14846 c1 = ToLower(*s1++);
14847 c2 = ToLower(*s2++);
14848 if (c1 > c2) return 1;
14849 if (c1 < c2) return -1;
14850 if (c1 == NULLCHAR) return 0;
14859 return isupper(c) ? tolower(c) : c;
14867 return islower(c) ? toupper(c) : c;
14869 #endif /* !_amigados */
14877 if ((ret = (char *) malloc(strlen(s) + 1)))
14879 safeStrCpy(ret, s, strlen(s)+1);
14885 StrSavePtr(s, savePtr)
14886 char *s, **savePtr;
14891 if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
14892 safeStrCpy(*savePtr, s, strlen(s)+1);
14904 clock = time((time_t *)NULL);
14905 tm = localtime(&clock);
14906 snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
14907 tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
14908 return StrSave(buf);
14913 PositionToFEN(move, overrideCastling)
14915 char *overrideCastling;
14917 int i, j, fromX, fromY, toX, toY;
14924 whiteToPlay = (gameMode == EditPosition) ?
14925 !blackPlaysFirst : (move % 2 == 0);
14928 /* Piece placement data */
14929 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
14931 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
14932 if (boards[move][i][j] == EmptySquare) {
14934 } else { ChessSquare piece = boards[move][i][j];
14935 if (emptycount > 0) {
14936 if(emptycount<10) /* [HGM] can be >= 10 */
14937 *p++ = '0' + emptycount;
14938 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
14941 if(PieceToChar(piece) == '+') {
14942 /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
14944 piece = (ChessSquare)(DEMOTED piece);
14946 *p++ = PieceToChar(piece);
14948 /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
14949 p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
14954 if (emptycount > 0) {
14955 if(emptycount<10) /* [HGM] can be >= 10 */
14956 *p++ = '0' + emptycount;
14957 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
14964 /* [HGM] print Crazyhouse or Shogi holdings */
14965 if( gameInfo.holdingsWidth ) {
14966 *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
14968 for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
14969 piece = boards[move][i][BOARD_WIDTH-1];
14970 if( piece != EmptySquare )
14971 for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
14972 *p++ = PieceToChar(piece);
14974 for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
14975 piece = boards[move][BOARD_HEIGHT-i-1][0];
14976 if( piece != EmptySquare )
14977 for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
14978 *p++ = PieceToChar(piece);
14981 if( q == p ) *p++ = '-';
14987 *p++ = whiteToPlay ? 'w' : 'b';
14990 if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
14991 while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
14993 if(nrCastlingRights) {
14995 if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
14996 /* [HGM] write directly from rights */
14997 if(boards[move][CASTLING][2] != NoRights &&
14998 boards[move][CASTLING][0] != NoRights )
14999 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
15000 if(boards[move][CASTLING][2] != NoRights &&
15001 boards[move][CASTLING][1] != NoRights )
15002 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
15003 if(boards[move][CASTLING][5] != NoRights &&
15004 boards[move][CASTLING][3] != NoRights )
15005 *p++ = boards[move][CASTLING][3] + AAA;
15006 if(boards[move][CASTLING][5] != NoRights &&
15007 boards[move][CASTLING][4] != NoRights )
15008 *p++ = boards[move][CASTLING][4] + AAA;
15011 /* [HGM] write true castling rights */
15012 if( nrCastlingRights == 6 ) {
15013 if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
15014 boards[move][CASTLING][2] != NoRights ) *p++ = 'K';
15015 if(boards[move][CASTLING][1] == BOARD_LEFT &&
15016 boards[move][CASTLING][2] != NoRights ) *p++ = 'Q';
15017 if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
15018 boards[move][CASTLING][5] != NoRights ) *p++ = 'k';
15019 if(boards[move][CASTLING][4] == BOARD_LEFT &&
15020 boards[move][CASTLING][5] != NoRights ) *p++ = 'q';
15023 if (q == p) *p++ = '-'; /* No castling rights */
15027 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
15028 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
15029 /* En passant target square */
15030 if (move > backwardMostMove) {
15031 fromX = moveList[move - 1][0] - AAA;
15032 fromY = moveList[move - 1][1] - ONE;
15033 toX = moveList[move - 1][2] - AAA;
15034 toY = moveList[move - 1][3] - ONE;
15035 if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
15036 toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
15037 boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
15039 /* 2-square pawn move just happened */
15041 *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
15045 } else if(move == backwardMostMove) {
15046 // [HGM] perhaps we should always do it like this, and forget the above?
15047 if((signed char)boards[move][EP_STATUS] >= 0) {
15048 *p++ = boards[move][EP_STATUS] + AAA;
15049 *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
15060 /* [HGM] find reversible plies */
15061 { int i = 0, j=move;
15063 if (appData.debugMode) { int k;
15064 fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
15065 for(k=backwardMostMove; k<=forwardMostMove; k++)
15066 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
15070 while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
15071 if( j == backwardMostMove ) i += initialRulePlies;
15072 sprintf(p, "%d ", i);
15073 p += i>=100 ? 4 : i >= 10 ? 3 : 2;
15075 /* Fullmove number */
15076 sprintf(p, "%d", (move / 2) + 1);
15078 return StrSave(buf);
15082 ParseFEN(board, blackPlaysFirst, fen)
15084 int *blackPlaysFirst;
15094 /* [HGM] by default clear Crazyhouse holdings, if present */
15095 if(gameInfo.holdingsWidth) {
15096 for(i=0; i<BOARD_HEIGHT; i++) {
15097 board[i][0] = EmptySquare; /* black holdings */
15098 board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
15099 board[i][1] = (ChessSquare) 0; /* black counts */
15100 board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
15104 /* Piece placement data */
15105 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
15108 if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
15109 if (*p == '/') p++;
15110 emptycount = gameInfo.boardWidth - j;
15111 while (emptycount--)
15112 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
15114 #if(BOARD_FILES >= 10)
15115 } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
15116 p++; emptycount=10;
15117 if (j + emptycount > gameInfo.boardWidth) return FALSE;
15118 while (emptycount--)
15119 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
15121 } else if (isdigit(*p)) {
15122 emptycount = *p++ - '0';
15123 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
15124 if (j + emptycount > gameInfo.boardWidth) return FALSE;
15125 while (emptycount--)
15126 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
15127 } else if (*p == '+' || isalpha(*p)) {
15128 if (j >= gameInfo.boardWidth) return FALSE;
15130 piece = CharToPiece(*++p);
15131 if(piece == EmptySquare) return FALSE; /* unknown piece */
15132 piece = (ChessSquare) (PROMOTED piece ); p++;
15133 if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
15134 } else piece = CharToPiece(*p++);
15136 if(piece==EmptySquare) return FALSE; /* unknown piece */
15137 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
15138 piece = (ChessSquare) (PROMOTED piece);
15139 if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
15142 board[i][(j++)+gameInfo.holdingsWidth] = piece;
15148 while (*p == '/' || *p == ' ') p++;
15150 /* [HGM] look for Crazyhouse holdings here */
15151 while(*p==' ') p++;
15152 if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
15154 if(*p == '-' ) p++; /* empty holdings */ else {
15155 if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
15156 /* if we would allow FEN reading to set board size, we would */
15157 /* have to add holdings and shift the board read so far here */
15158 while( (piece = CharToPiece(*p) ) != EmptySquare ) {
15160 if((int) piece >= (int) BlackPawn ) {
15161 i = (int)piece - (int)BlackPawn;
15162 i = PieceToNumber((ChessSquare)i);
15163 if( i >= gameInfo.holdingsSize ) return FALSE;
15164 board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
15165 board[BOARD_HEIGHT-1-i][1]++; /* black counts */
15167 i = (int)piece - (int)WhitePawn;
15168 i = PieceToNumber((ChessSquare)i);
15169 if( i >= gameInfo.holdingsSize ) return FALSE;
15170 board[i][BOARD_WIDTH-1] = piece; /* white holdings */
15171 board[i][BOARD_WIDTH-2]++; /* black holdings */
15178 while(*p == ' ') p++;
15182 if(appData.colorNickNames) {
15183 if( c == appData.colorNickNames[0] ) c = 'w'; else
15184 if( c == appData.colorNickNames[1] ) c = 'b';
15188 *blackPlaysFirst = FALSE;
15191 *blackPlaysFirst = TRUE;
15197 /* [HGM] We NO LONGER ignore the rest of the FEN notation */
15198 /* return the extra info in global variiables */
15200 /* set defaults in case FEN is incomplete */
15201 board[EP_STATUS] = EP_UNKNOWN;
15202 for(i=0; i<nrCastlingRights; i++ ) {
15203 board[CASTLING][i] =
15204 gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
15205 } /* assume possible unless obviously impossible */
15206 if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
15207 if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
15208 if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
15209 && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
15210 if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
15211 if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
15212 if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
15213 && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
15216 while(*p==' ') p++;
15217 if(nrCastlingRights) {
15218 if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
15219 /* castling indicator present, so default becomes no castlings */
15220 for(i=0; i<nrCastlingRights; i++ ) {
15221 board[CASTLING][i] = NoRights;
15224 while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
15225 (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
15226 ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
15227 ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth) ) {
15228 char c = *p++; int whiteKingFile=NoRights, blackKingFile=NoRights;
15230 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
15231 if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
15232 if(board[0 ][i] == WhiteKing) whiteKingFile = i;
15234 if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
15235 whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
15236 if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
15237 && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
15238 if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
15239 && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
15242 for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
15243 board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
15244 board[CASTLING][2] = whiteKingFile;
15247 for(i=BOARD_LEFT; i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
15248 board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
15249 board[CASTLING][2] = whiteKingFile;
15252 for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
15253 board[CASTLING][3] = i != blackKingFile ? i : NoRights;
15254 board[CASTLING][5] = blackKingFile;
15257 for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
15258 board[CASTLING][4] = i != blackKingFile ? i : NoRights;
15259 board[CASTLING][5] = blackKingFile;
15262 default: /* FRC castlings */
15263 if(c >= 'a') { /* black rights */
15264 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
15265 if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
15266 if(i == BOARD_RGHT) break;
15267 board[CASTLING][5] = i;
15269 if(board[BOARD_HEIGHT-1][c] < BlackPawn ||
15270 board[BOARD_HEIGHT-1][c] >= BlackKing ) break;
15272 board[CASTLING][3] = c;
15274 board[CASTLING][4] = c;
15275 } else { /* white rights */
15276 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
15277 if(board[0][i] == WhiteKing) break;
15278 if(i == BOARD_RGHT) break;
15279 board[CASTLING][2] = i;
15280 c -= AAA - 'a' + 'A';
15281 if(board[0][c] >= WhiteKing) break;
15283 board[CASTLING][0] = c;
15285 board[CASTLING][1] = c;
15289 for(i=0; i<nrCastlingRights; i++)
15290 if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
15291 if (appData.debugMode) {
15292 fprintf(debugFP, "FEN castling rights:");
15293 for(i=0; i<nrCastlingRights; i++)
15294 fprintf(debugFP, " %d", board[CASTLING][i]);
15295 fprintf(debugFP, "\n");
15298 while(*p==' ') p++;
15301 /* read e.p. field in games that know e.p. capture */
15302 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
15303 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
15305 p++; board[EP_STATUS] = EP_NONE;
15307 char c = *p++ - AAA;
15309 if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
15310 if(*p >= '0' && *p <='9') p++;
15311 board[EP_STATUS] = c;
15316 if(sscanf(p, "%d", &i) == 1) {
15317 FENrulePlies = i; /* 50-move ply counter */
15318 /* (The move number is still ignored) */
15325 EditPositionPasteFEN(char *fen)
15328 Board initial_position;
15330 if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
15331 DisplayError(_("Bad FEN position in clipboard"), 0);
15334 int savedBlackPlaysFirst = blackPlaysFirst;
15335 EditPositionEvent();
15336 blackPlaysFirst = savedBlackPlaysFirst;
15337 CopyBoard(boards[0], initial_position);
15338 initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
15339 EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
15340 DisplayBothClocks();
15341 DrawPosition(FALSE, boards[currentMove]);
15346 static char cseq[12] = "\\ ";
15348 Boolean set_cont_sequence(char *new_seq)
15353 // handle bad attempts to set the sequence
15355 return 0; // acceptable error - no debug
15357 len = strlen(new_seq);
15358 ret = (len > 0) && (len < sizeof(cseq));
15360 safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
15361 else if (appData.debugMode)
15362 fprintf(debugFP, "Invalid continuation sequence \"%s\" (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
15367 reformat a source message so words don't cross the width boundary. internal
15368 newlines are not removed. returns the wrapped size (no null character unless
15369 included in source message). If dest is NULL, only calculate the size required
15370 for the dest buffer. lp argument indicats line position upon entry, and it's
15371 passed back upon exit.
15373 int wrap(char *dest, char *src, int count, int width, int *lp)
15375 int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
15377 cseq_len = strlen(cseq);
15378 old_line = line = *lp;
15379 ansi = len = clen = 0;
15381 for (i=0; i < count; i++)
15383 if (src[i] == '\033')
15386 // if we hit the width, back up
15387 if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
15389 // store i & len in case the word is too long
15390 old_i = i, old_len = len;
15392 // find the end of the last word
15393 while (i && src[i] != ' ' && src[i] != '\n')
15399 // word too long? restore i & len before splitting it
15400 if ((old_i-i+clen) >= width)
15407 if (i && src[i-1] == ' ')
15410 if (src[i] != ' ' && src[i] != '\n')
15417 // now append the newline and continuation sequence
15422 strncpy(dest+len, cseq, cseq_len);
15430 dest[len] = src[i];
15434 if (src[i] == '\n')
15439 if (dest && appData.debugMode)
15441 fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
15442 count, width, line, len, *lp);
15443 show_bytes(debugFP, src, count);
15444 fprintf(debugFP, "\ndest: ");
15445 show_bytes(debugFP, dest, len);
15446 fprintf(debugFP, "\n");
15448 *lp = dest ? line : old_line;
15453 // [HGM] vari: routines for shelving variations
15456 PushTail(int firstMove, int lastMove)
15458 int i, j, nrMoves = lastMove - firstMove;
15460 if(appData.icsActive) { // only in local mode
15461 forwardMostMove = currentMove; // mimic old ICS behavior
15464 if(storedGames >= MAX_VARIATIONS-1) return;
15466 // push current tail of game on stack
15467 savedResult[storedGames] = gameInfo.result;
15468 savedDetails[storedGames] = gameInfo.resultDetails;
15469 gameInfo.resultDetails = NULL;
15470 savedFirst[storedGames] = firstMove;
15471 savedLast [storedGames] = lastMove;
15472 savedFramePtr[storedGames] = framePtr;
15473 framePtr -= nrMoves; // reserve space for the boards
15474 for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
15475 CopyBoard(boards[framePtr+i], boards[firstMove+i]);
15476 for(j=0; j<MOVE_LEN; j++)
15477 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
15478 for(j=0; j<2*MOVE_LEN; j++)
15479 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
15480 timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
15481 timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
15482 pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
15483 pvInfoList[firstMove+i-1].depth = 0;
15484 commentList[framePtr+i] = commentList[firstMove+i];
15485 commentList[firstMove+i] = NULL;
15489 forwardMostMove = firstMove; // truncate game so we can start variation
15490 if(storedGames == 1) GreyRevert(FALSE);
15494 PopTail(Boolean annotate)
15497 char buf[8000], moveBuf[20];
15499 if(appData.icsActive) return FALSE; // only in local mode
15500 if(!storedGames) return FALSE; // sanity
15501 CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
15504 ToNrEvent(savedFirst[storedGames]); // sets currentMove
15505 nrMoves = savedLast[storedGames] - currentMove;
15508 if(!WhiteOnMove(currentMove))
15509 snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
15510 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
15511 for(i=currentMove; i<forwardMostMove; i++) {
15513 snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
15514 else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
15515 strcat(buf, moveBuf);
15516 if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
15517 if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
15521 for(i=1; i<=nrMoves; i++) { // copy last variation back
15522 CopyBoard(boards[currentMove+i], boards[framePtr+i]);
15523 for(j=0; j<MOVE_LEN; j++)
15524 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
15525 for(j=0; j<2*MOVE_LEN; j++)
15526 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
15527 timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
15528 timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
15529 pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
15530 if(commentList[currentMove+i]) free(commentList[currentMove+i]);
15531 commentList[currentMove+i] = commentList[framePtr+i];
15532 commentList[framePtr+i] = NULL;
15534 if(annotate) AppendComment(currentMove+1, buf, FALSE);
15535 framePtr = savedFramePtr[storedGames];
15536 gameInfo.result = savedResult[storedGames];
15537 if(gameInfo.resultDetails != NULL) {
15538 free(gameInfo.resultDetails);
15540 gameInfo.resultDetails = savedDetails[storedGames];
15541 forwardMostMove = currentMove + nrMoves;
15542 if(storedGames == 0) GreyRevert(TRUE);
15548 { // remove all shelved variations
15550 for(i=0; i<storedGames; i++) {
15551 if(savedDetails[i])
15552 free(savedDetails[i]);
15553 savedDetails[i] = NULL;
15555 for(i=framePtr; i<MAX_MOVES; i++) {
15556 if(commentList[i]) free(commentList[i]);
15557 commentList[i] = NULL;
15559 framePtr = MAX_MOVES-1;
15564 LoadVariation(int index, char *text)
15565 { // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
15566 char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
15567 int level = 0, move;
15569 if(gameMode != EditGame && gameMode != AnalyzeMode) return;
15570 // first find outermost bracketing variation
15571 while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
15572 if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
15573 if(*p == '{') wait = '}'; else
15574 if(*p == '[') wait = ']'; else
15575 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
15576 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
15578 if(*p == wait) wait = NULLCHAR; // closing ]} found
15581 if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
15582 if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
15583 end[1] = NULLCHAR; // clip off comment beyond variation
15584 ToNrEvent(currentMove-1);
15585 PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
15586 // kludge: use ParsePV() to append variation to game
15587 move = currentMove;
15588 ParsePV(start, TRUE);
15589 forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
15590 ClearPremoveHighlights();
15592 ToNrEvent(currentMove+1);