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 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 */
271 /* States for ics_getting_history */
273 #define H_REQUESTED 1
274 #define H_GOT_REQ_HEADER 2
275 #define H_GOT_UNREQ_HEADER 3
276 #define H_GETTING_MOVES 4
277 #define H_GOT_UNWANTED_HEADER 5
279 /* whosays values for GameEnds */
288 /* Maximum number of games in a cmail message */
289 #define CMAIL_MAX_GAMES 20
291 /* Different types of move when calling RegisterMove */
293 #define CMAIL_RESIGN 1
295 #define CMAIL_ACCEPT 3
297 /* Different types of result to remember for each game */
298 #define CMAIL_NOT_RESULT 0
299 #define CMAIL_OLD_RESULT 1
300 #define CMAIL_NEW_RESULT 2
302 /* Telnet protocol constants */
313 safeStrCpy( char *dst, const char *src, size_t count )
316 assert( dst != NULL );
317 assert( src != NULL );
320 for(i=0; i<count; i++) if((dst[i] = src[i]) == NULLCHAR) break;
321 if( i == count && dst[count-1] != NULLCHAR)
323 dst[ count-1 ] = '\0'; // make sure incomplete copy still null-terminated
324 if(appData.debugMode)
325 fprintf(debugFP, "safeStrCpy: copying %s into %s didn't work, not enough space %d\n",src,dst,count);
331 /* Some compiler can't cast u64 to double
332 * This function do the job for us:
334 * We use the highest bit for cast, this only
335 * works if the highest bit is not
336 * in use (This should not happen)
338 * We used this for all compiler
341 u64ToDouble(u64 value)
344 u64 tmp = value & u64Const(0x7fffffffffffffff);
345 r = (double)(s64)tmp;
346 if (value & u64Const(0x8000000000000000))
347 r += 9.2233720368547758080e18; /* 2^63 */
351 /* Fake up flags for now, as we aren't keeping track of castling
352 availability yet. [HGM] Change of logic: the flag now only
353 indicates the type of castlings allowed by the rule of the game.
354 The actual rights themselves are maintained in the array
355 castlingRights, as part of the game history, and are not probed
361 int flags = F_ALL_CASTLE_OK;
362 if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
363 switch (gameInfo.variant) {
365 flags &= ~F_ALL_CASTLE_OK;
366 case VariantGiveaway: // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
367 flags |= F_IGNORE_CHECK;
369 flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
372 flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
374 case VariantKriegspiel:
375 flags |= F_KRIEGSPIEL_CAPTURE;
377 case VariantCapaRandom:
378 case VariantFischeRandom:
379 flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
380 case VariantNoCastle:
381 case VariantShatranj:
384 flags &= ~F_ALL_CASTLE_OK;
392 FILE *gameFileFP, *debugFP;
395 [AS] Note: sometimes, the sscanf() function is used to parse the input
396 into a fixed-size buffer. Because of this, we must be prepared to
397 receive strings as long as the size of the input buffer, which is currently
398 set to 4K for Windows and 8K for the rest.
399 So, we must either allocate sufficiently large buffers here, or
400 reduce the size of the input buffer in the input reading part.
403 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
404 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
405 char thinkOutput1[MSG_SIZ*10];
407 ChessProgramState first, second;
409 /* premove variables */
412 int premoveFromX = 0;
413 int premoveFromY = 0;
414 int premovePromoChar = 0;
416 Boolean alarmSounded;
417 /* end premove variables */
419 char *ics_prefix = "$";
420 int ics_type = ICS_GENERIC;
422 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
423 int pauseExamForwardMostMove = 0;
424 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
425 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
426 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
427 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
428 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
429 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
430 int whiteFlag = FALSE, blackFlag = FALSE;
431 int userOfferedDraw = FALSE;
432 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
433 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
434 int cmailMoveType[CMAIL_MAX_GAMES];
435 long ics_clock_paused = 0;
436 ProcRef icsPR = NoProc, cmailPR = NoProc;
437 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
438 GameMode gameMode = BeginningOfGame;
439 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
440 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
441 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
442 int hiddenThinkOutputState = 0; /* [AS] */
443 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
444 int adjudicateLossPlies = 6;
445 char white_holding[64], black_holding[64];
446 TimeMark lastNodeCountTime;
447 long lastNodeCount=0;
448 int shiftKey; // [HGM] set by mouse handler
450 int have_sent_ICS_logon = 0;
452 int suddenDeath, whiteStartMove, blackStartMove; /* [HGM] for implementation of 'any per time' sessions, as in first part of byoyomi TC */
453 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement, lastWhite, lastBlack;
454 long timeControl_2; /* [AS] Allow separate time controls */
455 char *fullTimeControlString = NULL, *nextSession, *whiteTC, *blackTC; /* [HGM] secondary TC: merge of MPS, TC and inc */
456 long timeRemaining[2][MAX_MOVES];
458 TimeMark programStartTime;
459 char ics_handle[MSG_SIZ];
460 int have_set_title = 0;
462 /* animateTraining preserves the state of appData.animate
463 * when Training mode is activated. This allows the
464 * response to be animated when appData.animate == TRUE and
465 * appData.animateDragging == TRUE.
467 Boolean animateTraining;
473 Board boards[MAX_MOVES];
474 /* [HGM] Following 7 needed for accurate legality tests: */
475 signed char castlingRank[BOARD_FILES]; // and corresponding ranks
476 signed char initialRights[BOARD_FILES];
477 int nrCastlingRights; // For TwoKings, or to implement castling-unknown status
478 int initialRulePlies, FENrulePlies;
479 FILE *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
482 int mute; // mute all sounds
484 // [HGM] vari: next 12 to save and restore variations
485 #define MAX_VARIATIONS 10
486 int framePtr = MAX_MOVES-1; // points to free stack entry
488 int savedFirst[MAX_VARIATIONS];
489 int savedLast[MAX_VARIATIONS];
490 int savedFramePtr[MAX_VARIATIONS];
491 char *savedDetails[MAX_VARIATIONS];
492 ChessMove savedResult[MAX_VARIATIONS];
494 void PushTail P((int firstMove, int lastMove));
495 Boolean PopTail P((Boolean annotate));
496 void CleanupTail P((void));
498 ChessSquare FIDEArray[2][BOARD_FILES] = {
499 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
500 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
501 { BlackRook, BlackKnight, BlackBishop, BlackQueen,
502 BlackKing, BlackBishop, BlackKnight, BlackRook }
505 ChessSquare twoKingsArray[2][BOARD_FILES] = {
506 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
507 WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
508 { BlackRook, BlackKnight, BlackBishop, BlackQueen,
509 BlackKing, BlackKing, BlackKnight, BlackRook }
512 ChessSquare KnightmateArray[2][BOARD_FILES] = {
513 { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
514 WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
515 { BlackRook, BlackMan, BlackBishop, BlackQueen,
516 BlackUnicorn, BlackBishop, BlackMan, BlackRook }
519 ChessSquare fairyArray[2][BOARD_FILES] = { /* [HGM] Queen side differs from King side */
520 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
521 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
522 { BlackCardinal, BlackAlfil, BlackMarshall, BlackAngel,
523 BlackKing, BlackMarshall, BlackAlfil, BlackCardinal }
526 ChessSquare ShatranjArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
527 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
528 WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
529 { BlackRook, BlackKnight, BlackAlfil, BlackKing,
530 BlackFerz, BlackAlfil, BlackKnight, BlackRook }
533 ChessSquare makrukArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
534 { WhiteRook, WhiteKnight, WhiteMan, WhiteKing,
535 WhiteFerz, WhiteMan, WhiteKnight, WhiteRook },
536 { BlackRook, BlackKnight, BlackMan, BlackFerz,
537 BlackKing, BlackMan, BlackKnight, BlackRook }
541 #if (BOARD_FILES>=10)
542 ChessSquare ShogiArray[2][BOARD_FILES] = {
543 { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
544 WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
545 { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
546 BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
549 ChessSquare XiangqiArray[2][BOARD_FILES] = {
550 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
551 WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
552 { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
553 BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
556 ChessSquare CapablancaArray[2][BOARD_FILES] = {
557 { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
558 WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
559 { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
560 BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
563 ChessSquare GreatArray[2][BOARD_FILES] = {
564 { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
565 WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
566 { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
567 BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
570 ChessSquare JanusArray[2][BOARD_FILES] = {
571 { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
572 WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
573 { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
574 BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
578 ChessSquare GothicArray[2][BOARD_FILES] = {
579 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
580 WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
581 { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
582 BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
585 #define GothicArray CapablancaArray
589 ChessSquare FalconArray[2][BOARD_FILES] = {
590 { WhiteRook, WhiteKnight, WhiteBishop, WhiteLance, WhiteQueen,
591 WhiteKing, WhiteLance, WhiteBishop, WhiteKnight, WhiteRook },
592 { BlackRook, BlackKnight, BlackBishop, BlackLance, BlackQueen,
593 BlackKing, BlackLance, BlackBishop, BlackKnight, BlackRook }
596 #define FalconArray CapablancaArray
599 #else // !(BOARD_FILES>=10)
600 #define XiangqiPosition FIDEArray
601 #define CapablancaArray FIDEArray
602 #define GothicArray FIDEArray
603 #define GreatArray FIDEArray
604 #endif // !(BOARD_FILES>=10)
606 #if (BOARD_FILES>=12)
607 ChessSquare CourierArray[2][BOARD_FILES] = {
608 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
609 WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
610 { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
611 BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
613 #else // !(BOARD_FILES>=12)
614 #define CourierArray CapablancaArray
615 #endif // !(BOARD_FILES>=12)
618 Board initialPosition;
621 /* Convert str to a rating. Checks for special cases of "----",
623 "++++", etc. Also strips ()'s */
625 string_to_rating(str)
628 while(*str && !isdigit(*str)) ++str;
630 return 0; /* One of the special "no rating" cases */
638 /* Init programStats */
639 programStats.movelist[0] = 0;
640 programStats.depth = 0;
641 programStats.nr_moves = 0;
642 programStats.moves_left = 0;
643 programStats.nodes = 0;
644 programStats.time = -1; // [HGM] PGNtime: make invalid to recognize engine output
645 programStats.score = 0;
646 programStats.got_only_move = 0;
647 programStats.got_fail = 0;
648 programStats.line_is_book = 0;
654 int matched, min, sec;
656 ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
657 startVariant = StringToVariant(appData.variant); // [HGM] nicks: remember original variant
659 GetTimeMark(&programStartTime);
660 srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
663 programStats.ok_to_send = 1;
664 programStats.seen_stat = 0;
667 * Initialize game list
673 * Internet chess server status
675 if (appData.icsActive) {
676 appData.matchMode = FALSE;
677 appData.matchGames = 0;
679 appData.noChessProgram = !appData.zippyPlay;
681 appData.zippyPlay = FALSE;
682 appData.zippyTalk = FALSE;
683 appData.noChessProgram = TRUE;
685 if (*appData.icsHelper != NULLCHAR) {
686 appData.useTelnet = TRUE;
687 appData.telnetProgram = appData.icsHelper;
690 appData.zippyTalk = appData.zippyPlay = FALSE;
693 /* [AS] Initialize pv info list [HGM] and game state */
697 for( i=0; i<=framePtr; i++ ) {
698 pvInfoList[i].depth = -1;
699 boards[i][EP_STATUS] = EP_NONE;
700 for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
705 * Parse timeControl resource
707 if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
708 appData.movesPerSession)) {
710 snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
711 DisplayFatalError(buf, 0, 2);
715 * Parse searchTime resource
717 if (*appData.searchTime != NULLCHAR) {
718 matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
720 searchTime = min * 60;
721 } else if (matched == 2) {
722 searchTime = min * 60 + sec;
725 snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
726 DisplayFatalError(buf, 0, 2);
730 /* [AS] Adjudication threshold */
731 adjudicateLossThreshold = appData.adjudicateLossThreshold;
733 first.which = _("first");
734 second.which = _("second");
735 first.maybeThinking = second.maybeThinking = FALSE;
736 first.pr = second.pr = NoProc;
737 first.isr = second.isr = NULL;
738 first.sendTime = second.sendTime = 2;
739 first.sendDrawOffers = 1;
740 if (appData.firstPlaysBlack) {
741 first.twoMachinesColor = "black\n";
742 second.twoMachinesColor = "white\n";
744 first.twoMachinesColor = "white\n";
745 second.twoMachinesColor = "black\n";
747 first.program = appData.firstChessProgram;
748 second.program = appData.secondChessProgram;
749 first.host = appData.firstHost;
750 second.host = appData.secondHost;
751 first.dir = appData.firstDirectory;
752 second.dir = appData.secondDirectory;
753 first.other = &second;
754 second.other = &first;
755 first.initString = appData.initString;
756 second.initString = appData.secondInitString;
757 first.computerString = appData.firstComputerString;
758 second.computerString = appData.secondComputerString;
759 first.useSigint = second.useSigint = TRUE;
760 first.useSigterm = second.useSigterm = TRUE;
761 first.reuse = appData.reuseFirst;
762 second.reuse = appData.reuseSecond;
763 first.nps = appData.firstNPS; // [HGM] nps: copy nodes per second
764 second.nps = appData.secondNPS;
765 first.useSetboard = second.useSetboard = FALSE;
766 first.useSAN = second.useSAN = FALSE;
767 first.usePing = second.usePing = FALSE;
768 first.lastPing = second.lastPing = 0;
769 first.lastPong = second.lastPong = 0;
770 first.usePlayother = second.usePlayother = FALSE;
771 first.useColors = second.useColors = TRUE;
772 first.useUsermove = second.useUsermove = FALSE;
773 first.sendICS = second.sendICS = FALSE;
774 first.sendName = second.sendName = appData.icsActive;
775 first.sdKludge = second.sdKludge = FALSE;
776 first.stKludge = second.stKludge = FALSE;
777 TidyProgramName(first.program, first.host, first.tidy);
778 TidyProgramName(second.program, second.host, second.tidy);
779 first.matchWins = second.matchWins = 0;
780 safeStrCpy(first.variants, appData.variant, sizeof(first.variants)/sizeof(first.variants[0]));
781 safeStrCpy(second.variants, appData.variant,sizeof(second.variants)/sizeof(second.variants[0]));
782 first.analysisSupport = second.analysisSupport = 2; /* detect */
783 first.analyzing = second.analyzing = FALSE;
784 first.initDone = second.initDone = FALSE;
786 /* New features added by Tord: */
787 first.useFEN960 = FALSE; second.useFEN960 = FALSE;
788 first.useOOCastle = TRUE; second.useOOCastle = TRUE;
789 /* End of new features added by Tord. */
790 first.fenOverride = appData.fenOverride1;
791 second.fenOverride = appData.fenOverride2;
793 /* [HGM] time odds: set factor for each machine */
794 first.timeOdds = appData.firstTimeOdds;
795 second.timeOdds = appData.secondTimeOdds;
797 if(appData.timeOddsMode) {
798 norm = first.timeOdds;
799 if(norm > second.timeOdds) norm = second.timeOdds;
801 first.timeOdds /= norm;
802 second.timeOdds /= norm;
805 /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
806 first.accumulateTC = appData.firstAccumulateTC;
807 second.accumulateTC = appData.secondAccumulateTC;
808 first.maxNrOfSessions = second.maxNrOfSessions = 1;
811 first.debug = second.debug = FALSE;
812 first.supportsNPS = second.supportsNPS = UNKNOWN;
815 first.optionSettings = appData.firstOptions;
816 second.optionSettings = appData.secondOptions;
818 first.scoreIsAbsolute = appData.firstScoreIsAbsolute; /* [AS] */
819 second.scoreIsAbsolute = appData.secondScoreIsAbsolute; /* [AS] */
820 first.isUCI = appData.firstIsUCI; /* [AS] */
821 second.isUCI = appData.secondIsUCI; /* [AS] */
822 first.hasOwnBookUCI = appData.firstHasOwnBookUCI; /* [AS] */
823 second.hasOwnBookUCI = appData.secondHasOwnBookUCI; /* [AS] */
825 if (appData.firstProtocolVersion > PROTOVER
826 || appData.firstProtocolVersion < 1)
831 len = snprintf(buf, MSG_SIZ, _("protocol version %d not supported"),
832 appData.firstProtocolVersion);
833 if( (len > MSG_SIZ) && appData.debugMode )
834 fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
836 DisplayFatalError(buf, 0, 2);
840 first.protocolVersion = appData.firstProtocolVersion;
843 if (appData.secondProtocolVersion > PROTOVER
844 || appData.secondProtocolVersion < 1)
849 len = snprintf(buf, MSG_SIZ, _("protocol version %d not supported"),
850 appData.secondProtocolVersion);
851 if( (len > MSG_SIZ) && appData.debugMode )
852 fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
854 DisplayFatalError(buf, 0, 2);
858 second.protocolVersion = appData.secondProtocolVersion;
861 if (appData.icsActive) {
862 appData.clockMode = TRUE; /* changes dynamically in ICS mode */
863 // } else if (*appData.searchTime != NULLCHAR || appData.noChessProgram) {
864 } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
865 appData.clockMode = FALSE;
866 first.sendTime = second.sendTime = 0;
870 /* Override some settings from environment variables, for backward
871 compatibility. Unfortunately it's not feasible to have the env
872 vars just set defaults, at least in xboard. Ugh.
874 if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
879 if (appData.noChessProgram) {
880 programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
881 sprintf(programVersion, "%s", PACKAGE_STRING);
883 /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
884 programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
885 sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
888 if (!appData.icsActive) {
892 /* Check for variants that are supported only in ICS mode,
893 or not at all. Some that are accepted here nevertheless
894 have bugs; see comments below.
896 VariantClass variant = StringToVariant(appData.variant);
898 case VariantBughouse: /* need four players and two boards */
899 case VariantKriegspiel: /* need to hide pieces and move details */
900 /* case VariantFischeRandom: (Fabien: moved below) */
901 len = snprintf(buf,MSG_SIZ, _("Variant %s supported only in ICS mode"), appData.variant);
902 if( (len > MSG_SIZ) && appData.debugMode )
903 fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
905 DisplayFatalError(buf, 0, 2);
909 case VariantLoadable:
919 len = snprintf(buf, MSG_SIZ, _("Unknown variant name %s"), appData.variant);
920 if( (len > MSG_SIZ) && appData.debugMode )
921 fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
923 DisplayFatalError(buf, 0, 2);
926 case VariantXiangqi: /* [HGM] repetition rules not implemented */
927 case VariantFairy: /* [HGM] TestLegality definitely off! */
928 case VariantGothic: /* [HGM] should work */
929 case VariantCapablanca: /* [HGM] should work */
930 case VariantCourier: /* [HGM] initial forced moves not implemented */
931 case VariantShogi: /* [HGM] could still mate with pawn drop */
932 case VariantKnightmate: /* [HGM] should work */
933 case VariantCylinder: /* [HGM] untested */
934 case VariantFalcon: /* [HGM] untested */
935 case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
936 offboard interposition not understood */
937 case VariantNormal: /* definitely works! */
938 case VariantWildCastle: /* pieces not automatically shuffled */
939 case VariantNoCastle: /* pieces not automatically shuffled */
940 case VariantFischeRandom: /* [HGM] works and shuffles pieces */
941 case VariantLosers: /* should work except for win condition,
942 and doesn't know captures are mandatory */
943 case VariantSuicide: /* should work except for win condition,
944 and doesn't know captures are mandatory */
945 case VariantGiveaway: /* should work except for win condition,
946 and doesn't know captures are mandatory */
947 case VariantTwoKings: /* should work */
948 case VariantAtomic: /* should work except for win condition */
949 case Variant3Check: /* should work except for win condition */
950 case VariantShatranj: /* should work except for all win conditions */
951 case VariantMakruk: /* should work except for daw countdown */
952 case VariantBerolina: /* might work if TestLegality is off */
953 case VariantCapaRandom: /* should work */
954 case VariantJanus: /* should work */
955 case VariantSuper: /* experimental */
956 case VariantGreat: /* experimental, requires legality testing to be off */
957 case VariantSChess: /* S-Chess, should work */
962 InitEngineUCI( installDir, &first ); // [HGM] moved here from winboard.c, to make available in xboard
963 InitEngineUCI( installDir, &second );
966 int NextIntegerFromString( char ** str, long * value )
971 while( *s == ' ' || *s == '\t' ) {
977 if( *s >= '0' && *s <= '9' ) {
978 while( *s >= '0' && *s <= '9' ) {
979 *value = *value * 10 + (*s - '0');
991 int NextTimeControlFromString( char ** str, long * value )
994 int result = NextIntegerFromString( str, &temp );
997 *value = temp * 60; /* Minutes */
1000 result = NextIntegerFromString( str, &temp );
1001 *value += temp; /* Seconds */
1008 int NextSessionFromString( char ** str, int *moves, long * tc, long *inc, int *incType)
1009 { /* [HGM] routine added to read '+moves/time' for secondary time control. */
1010 int result = -1, type = 0; long temp, temp2;
1012 if(**str != ':') return -1; // old params remain in force!
1014 if(**str == '*') type = *(*str)++, temp = 0; // sandclock TC
1015 if( NextIntegerFromString( str, &temp ) ) return -1;
1016 if(type) { *moves = 0; *tc = temp * 500; *inc = temp * 1000; *incType = '*'; return 0; }
1019 /* time only: incremental or sudden-death time control */
1020 if(**str == '+') { /* increment follows; read it */
1022 if(**str == '!') type = *(*str)++; // Bronstein TC
1023 if(result = NextIntegerFromString( str, &temp2)) return -1;
1024 *inc = temp2 * 1000;
1025 if(**str == '.') { // read fraction of increment
1026 char *start = ++(*str);
1027 if(result = NextIntegerFromString( str, &temp2)) return -1;
1029 while(start++ < *str) temp2 /= 10;
1033 *moves = 0; *tc = temp * 1000; *incType = type;
1037 (*str)++; /* classical time control */
1038 result = NextIntegerFromString( str, &temp2); // NOTE: already converted to seconds by ParseTimeControl()
1049 int GetTimeQuota(int movenr, int lastUsed, char *tcString)
1050 { /* [HGM] get time to add from the multi-session time-control string */
1051 int incType, moves=1; /* kludge to force reading of first session */
1052 long time, increment;
1055 if(!*s) return 0; // empty TC string means we ran out of the last sudden-death version
1056 if(appData.debugMode) fprintf(debugFP, "TC string = '%s'\n", tcString);
1058 if(moves) NextSessionFromString(&s, &moves, &time, &increment, &incType);
1059 nextSession = s; suddenDeath = moves == 0 && increment == 0;
1060 if(appData.debugMode) fprintf(debugFP, "mps=%d tc=%d inc=%d\n", moves, (int) time, (int) increment);
1061 if(movenr == -1) return time; /* last move before new session */
1062 if(incType == '*') increment = 0; else // for sandclock, time is added while not thinking
1063 if(incType == '!' && lastUsed < increment) increment = lastUsed;
1064 if(!moves) return increment; /* current session is incremental */
1065 if(movenr >= 0) movenr -= moves; /* we already finished this session */
1066 } while(movenr >= -1); /* try again for next session */
1068 return 0; // no new time quota on this move
1072 ParseTimeControl(tc, ti, mps)
1079 char buf[MSG_SIZ], buf2[MSG_SIZ], *mytc = tc;
1082 if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1083 if(!strchr(tc, '+') && !strchr(tc, '/') && sscanf(tc, "%d:%d", &min, &sec) >= 1)
1084 sprintf(mytc=buf2, "%d", 60*min+sec); // convert 'classical' min:sec tc string to seconds
1088 snprintf(buf, MSG_SIZ, ":%d/%s+%g", mps, mytc, ti);
1090 snprintf(buf, MSG_SIZ, ":%s+%g", mytc, ti);
1093 snprintf(buf, MSG_SIZ, ":%d/%s", mps, mytc);
1095 snprintf(buf, MSG_SIZ, ":%s", mytc);
1097 fullTimeControlString = StrSave(buf); // this should now be in PGN format
1099 if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1104 /* Parse second time control */
1107 if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1115 timeControl_2 = tc2 * 1000;
1125 timeControl = tc1 * 1000;
1128 timeIncrement = ti * 1000; /* convert to ms */
1129 movesPerSession = 0;
1132 movesPerSession = mps;
1140 if (appData.debugMode) {
1141 fprintf(debugFP, "%s\n", programVersion);
1144 set_cont_sequence(appData.wrapContSeq);
1145 if (appData.matchGames > 0) {
1146 appData.matchMode = TRUE;
1147 } else if (appData.matchMode) {
1148 appData.matchGames = 1;
1150 if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1151 appData.matchGames = appData.sameColorGames;
1152 if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1153 if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1154 if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1157 if (appData.noChessProgram || first.protocolVersion == 1) {
1160 /* kludge: allow timeout for initial "feature" commands */
1162 DisplayMessage("", _("Starting chess program"));
1163 ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1168 InitBackEnd3 P((void))
1170 GameMode initialMode;
1174 InitChessProgram(&first, startedFromSetupPosition);
1176 if(!appData.noChessProgram) { /* [HGM] tidy: redo program version to use name from myname feature */
1177 free(programVersion);
1178 programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1179 sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1182 if (appData.icsActive) {
1184 /* [DM] Make a console window if needed [HGM] merged ifs */
1190 if (*appData.icsCommPort != NULLCHAR)
1191 len = snprintf(buf, MSG_SIZ, _("Could not open comm port %s"),
1192 appData.icsCommPort);
1194 len = snprintf(buf, MSG_SIZ, _("Could not connect to host %s, port %s"),
1195 appData.icsHost, appData.icsPort);
1197 if( (len > MSG_SIZ) && appData.debugMode )
1198 fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1200 DisplayFatalError(buf, err, 1);
1205 AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1207 AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1208 if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1209 ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1210 } else if (appData.noChessProgram) {
1216 if (*appData.cmailGameName != NULLCHAR) {
1218 OpenLoopback(&cmailPR);
1220 AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1224 DisplayMessage("", "");
1225 if (StrCaseCmp(appData.initialMode, "") == 0) {
1226 initialMode = BeginningOfGame;
1227 } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1228 initialMode = TwoMachinesPlay;
1229 } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1230 initialMode = AnalyzeFile;
1231 } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1232 initialMode = AnalyzeMode;
1233 } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1234 initialMode = MachinePlaysWhite;
1235 } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1236 initialMode = MachinePlaysBlack;
1237 } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1238 initialMode = EditGame;
1239 } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1240 initialMode = EditPosition;
1241 } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1242 initialMode = Training;
1244 len = snprintf(buf, MSG_SIZ, _("Unknown initialMode %s"), appData.initialMode);
1245 if( (len > MSG_SIZ) && appData.debugMode )
1246 fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1248 DisplayFatalError(buf, 0, 2);
1252 if (appData.matchMode) {
1253 /* Set up machine vs. machine match */
1254 if (appData.noChessProgram) {
1255 DisplayFatalError(_("Can't have a match with no chess programs"),
1261 if (*appData.loadGameFile != NULLCHAR) {
1262 int index = appData.loadGameIndex; // [HGM] autoinc
1263 if(index<0) lastIndex = index = 1;
1264 if (!LoadGameFromFile(appData.loadGameFile,
1266 appData.loadGameFile, FALSE)) {
1267 DisplayFatalError(_("Bad game file"), 0, 1);
1270 } else if (*appData.loadPositionFile != NULLCHAR) {
1271 int index = appData.loadPositionIndex; // [HGM] autoinc
1272 if(index<0) lastIndex = index = 1;
1273 if (!LoadPositionFromFile(appData.loadPositionFile,
1275 appData.loadPositionFile)) {
1276 DisplayFatalError(_("Bad position file"), 0, 1);
1281 } else if (*appData.cmailGameName != NULLCHAR) {
1282 /* Set up cmail mode */
1283 ReloadCmailMsgEvent(TRUE);
1285 /* Set up other modes */
1286 if (initialMode == AnalyzeFile) {
1287 if (*appData.loadGameFile == NULLCHAR) {
1288 DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1292 if (*appData.loadGameFile != NULLCHAR) {
1293 (void) LoadGameFromFile(appData.loadGameFile,
1294 appData.loadGameIndex,
1295 appData.loadGameFile, TRUE);
1296 } else if (*appData.loadPositionFile != NULLCHAR) {
1297 (void) LoadPositionFromFile(appData.loadPositionFile,
1298 appData.loadPositionIndex,
1299 appData.loadPositionFile);
1300 /* [HGM] try to make self-starting even after FEN load */
1301 /* to allow automatic setup of fairy variants with wtm */
1302 if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1303 gameMode = BeginningOfGame;
1304 setboardSpoiledMachineBlack = 1;
1306 /* [HGM] loadPos: make that every new game uses the setup */
1307 /* from file as long as we do not switch variant */
1308 if(!blackPlaysFirst) {
1309 startedFromPositionFile = TRUE;
1310 CopyBoard(filePosition, boards[0]);
1313 if (initialMode == AnalyzeMode) {
1314 if (appData.noChessProgram) {
1315 DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1318 if (appData.icsActive) {
1319 DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1323 } else if (initialMode == AnalyzeFile) {
1324 appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1325 ShowThinkingEvent();
1327 AnalysisPeriodicEvent(1);
1328 } else if (initialMode == MachinePlaysWhite) {
1329 if (appData.noChessProgram) {
1330 DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1334 if (appData.icsActive) {
1335 DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1339 MachineWhiteEvent();
1340 } else if (initialMode == MachinePlaysBlack) {
1341 if (appData.noChessProgram) {
1342 DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1346 if (appData.icsActive) {
1347 DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1351 MachineBlackEvent();
1352 } else if (initialMode == TwoMachinesPlay) {
1353 if (appData.noChessProgram) {
1354 DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1358 if (appData.icsActive) {
1359 DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1364 } else if (initialMode == EditGame) {
1366 } else if (initialMode == EditPosition) {
1367 EditPositionEvent();
1368 } else if (initialMode == Training) {
1369 if (*appData.loadGameFile == NULLCHAR) {
1370 DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1379 * Establish will establish a contact to a remote host.port.
1380 * Sets icsPR to a ProcRef for a process (or pseudo-process)
1381 * used to talk to the host.
1382 * Returns 0 if okay, error code if not.
1389 if (*appData.icsCommPort != NULLCHAR) {
1390 /* Talk to the host through a serial comm port */
1391 return OpenCommPort(appData.icsCommPort, &icsPR);
1393 } else if (*appData.gateway != NULLCHAR) {
1394 if (*appData.remoteShell == NULLCHAR) {
1395 /* Use the rcmd protocol to run telnet program on a gateway host */
1396 snprintf(buf, sizeof(buf), "%s %s %s",
1397 appData.telnetProgram, appData.icsHost, appData.icsPort);
1398 return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1401 /* Use the rsh program to run telnet program on a gateway host */
1402 if (*appData.remoteUser == NULLCHAR) {
1403 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1404 appData.gateway, appData.telnetProgram,
1405 appData.icsHost, appData.icsPort);
1407 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1408 appData.remoteShell, appData.gateway,
1409 appData.remoteUser, appData.telnetProgram,
1410 appData.icsHost, appData.icsPort);
1412 return StartChildProcess(buf, "", &icsPR);
1415 } else if (appData.useTelnet) {
1416 return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1419 /* TCP socket interface differs somewhat between
1420 Unix and NT; handle details in the front end.
1422 return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1426 void EscapeExpand(char *p, char *q)
1427 { // [HGM] initstring: routine to shape up string arguments
1428 while(*p++ = *q++) if(p[-1] == '\\')
1430 case 'n': p[-1] = '\n'; break;
1431 case 'r': p[-1] = '\r'; break;
1432 case 't': p[-1] = '\t'; break;
1433 case '\\': p[-1] = '\\'; break;
1434 case 0: *p = 0; return;
1435 default: p[-1] = q[-1]; break;
1440 show_bytes(fp, buf, count)
1446 if (*buf < 040 || *(unsigned char *) buf > 0177) {
1447 fprintf(fp, "\\%03o", *buf & 0xff);
1456 /* Returns an errno value */
1458 OutputMaybeTelnet(pr, message, count, outError)
1464 char buf[8192], *p, *q, *buflim;
1465 int left, newcount, outcount;
1467 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1468 *appData.gateway != NULLCHAR) {
1469 if (appData.debugMode) {
1470 fprintf(debugFP, ">ICS: ");
1471 show_bytes(debugFP, message, count);
1472 fprintf(debugFP, "\n");
1474 return OutputToProcess(pr, message, count, outError);
1477 buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1484 if (appData.debugMode) {
1485 fprintf(debugFP, ">ICS: ");
1486 show_bytes(debugFP, buf, newcount);
1487 fprintf(debugFP, "\n");
1489 outcount = OutputToProcess(pr, buf, newcount, outError);
1490 if (outcount < newcount) return -1; /* to be sure */
1497 } else if (((unsigned char) *p) == TN_IAC) {
1498 *q++ = (char) TN_IAC;
1505 if (appData.debugMode) {
1506 fprintf(debugFP, ">ICS: ");
1507 show_bytes(debugFP, buf, newcount);
1508 fprintf(debugFP, "\n");
1510 outcount = OutputToProcess(pr, buf, newcount, outError);
1511 if (outcount < newcount) return -1; /* to be sure */
1516 read_from_player(isr, closure, message, count, error)
1523 int outError, outCount;
1524 static int gotEof = 0;
1526 /* Pass data read from player on to ICS */
1529 outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1530 if (outCount < count) {
1531 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1533 } else if (count < 0) {
1534 RemoveInputSource(isr);
1535 DisplayFatalError(_("Error reading from keyboard"), error, 1);
1536 } else if (gotEof++ > 0) {
1537 RemoveInputSource(isr);
1538 DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1544 { // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1545 if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1546 connectionAlive = FALSE; // only sticks if no response to 'date' command.
1547 SendToICS("date\n");
1548 if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1551 /* added routine for printf style output to ics */
1552 void ics_printf(char *format, ...)
1554 char buffer[MSG_SIZ];
1557 va_start(args, format);
1558 vsnprintf(buffer, sizeof(buffer), format, args);
1559 buffer[sizeof(buffer)-1] = '\0';
1568 int count, outCount, outError;
1570 if (icsPR == NULL) return;
1573 outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1574 if (outCount < count) {
1575 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1579 /* This is used for sending logon scripts to the ICS. Sending
1580 without a delay causes problems when using timestamp on ICC
1581 (at least on my machine). */
1583 SendToICSDelayed(s,msdelay)
1587 int count, outCount, outError;
1589 if (icsPR == NULL) return;
1592 if (appData.debugMode) {
1593 fprintf(debugFP, ">ICS: ");
1594 show_bytes(debugFP, s, count);
1595 fprintf(debugFP, "\n");
1597 outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1599 if (outCount < count) {
1600 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1605 /* Remove all highlighting escape sequences in s
1606 Also deletes any suffix starting with '('
1609 StripHighlightAndTitle(s)
1612 static char retbuf[MSG_SIZ];
1615 while (*s != NULLCHAR) {
1616 while (*s == '\033') {
1617 while (*s != NULLCHAR && !isalpha(*s)) s++;
1618 if (*s != NULLCHAR) s++;
1620 while (*s != NULLCHAR && *s != '\033') {
1621 if (*s == '(' || *s == '[') {
1632 /* Remove all highlighting escape sequences in s */
1637 static char retbuf[MSG_SIZ];
1640 while (*s != NULLCHAR) {
1641 while (*s == '\033') {
1642 while (*s != NULLCHAR && !isalpha(*s)) s++;
1643 if (*s != NULLCHAR) s++;
1645 while (*s != NULLCHAR && *s != '\033') {
1653 char *variantNames[] = VARIANT_NAMES;
1658 return variantNames[v];
1662 /* Identify a variant from the strings the chess servers use or the
1663 PGN Variant tag names we use. */
1670 VariantClass v = VariantNormal;
1671 int i, found = FALSE;
1677 /* [HGM] skip over optional board-size prefixes */
1678 if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1679 sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1680 while( *e++ != '_');
1683 if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
1687 for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
1688 if (StrCaseStr(e, variantNames[i])) {
1689 v = (VariantClass) i;
1696 if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
1697 || StrCaseStr(e, "wild/fr")
1698 || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
1699 v = VariantFischeRandom;
1700 } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
1701 (i = 1, p = StrCaseStr(e, "w"))) {
1703 while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
1710 case 0: /* FICS only, actually */
1712 /* Castling legal even if K starts on d-file */
1713 v = VariantWildCastle;
1718 /* Castling illegal even if K & R happen to start in
1719 normal positions. */
1720 v = VariantNoCastle;
1733 /* Castling legal iff K & R start in normal positions */
1739 /* Special wilds for position setup; unclear what to do here */
1740 v = VariantLoadable;
1743 /* Bizarre ICC game */
1744 v = VariantTwoKings;
1747 v = VariantKriegspiel;
1753 v = VariantFischeRandom;
1756 v = VariantCrazyhouse;
1759 v = VariantBughouse;
1765 /* Not quite the same as FICS suicide! */
1766 v = VariantGiveaway;
1772 v = VariantShatranj;
1775 /* Temporary names for future ICC types. The name *will* change in
1776 the next xboard/WinBoard release after ICC defines it. */
1814 v = VariantCapablanca;
1817 v = VariantKnightmate;
1823 v = VariantCylinder;
1829 v = VariantCapaRandom;
1832 v = VariantBerolina;
1844 /* Found "wild" or "w" in the string but no number;
1845 must assume it's normal chess. */
1849 len = snprintf(buf, MSG_SIZ, _("Unknown wild type %d"), wnum);
1850 if( (len > MSG_SIZ) && appData.debugMode )
1851 fprintf(debugFP, "StringToVariant: buffer truncated.\n");
1853 DisplayError(buf, 0);
1859 if (appData.debugMode) {
1860 fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
1861 e, wnum, VariantName(v));
1866 static int leftover_start = 0, leftover_len = 0;
1867 char star_match[STAR_MATCH_N][MSG_SIZ];
1869 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
1870 advance *index beyond it, and set leftover_start to the new value of
1871 *index; else return FALSE. If pattern contains the character '*', it
1872 matches any sequence of characters not containing '\r', '\n', or the
1873 character following the '*' (if any), and the matched sequence(s) are
1874 copied into star_match.
1877 looking_at(buf, index, pattern)
1882 char *bufp = &buf[*index], *patternp = pattern;
1884 char *matchp = star_match[0];
1887 if (*patternp == NULLCHAR) {
1888 *index = leftover_start = bufp - buf;
1892 if (*bufp == NULLCHAR) return FALSE;
1893 if (*patternp == '*') {
1894 if (*bufp == *(patternp + 1)) {
1896 matchp = star_match[++star_count];
1900 } else if (*bufp == '\n' || *bufp == '\r') {
1902 if (*patternp == NULLCHAR)
1907 *matchp++ = *bufp++;
1911 if (*patternp != *bufp) return FALSE;
1918 SendToPlayer(data, length)
1922 int error, outCount;
1923 outCount = OutputToProcess(NoProc, data, length, &error);
1924 if (outCount < length) {
1925 DisplayFatalError(_("Error writing to display"), error, 1);
1930 PackHolding(packed, holding)
1942 switch (runlength) {
1953 sprintf(q, "%d", runlength);
1965 /* Telnet protocol requests from the front end */
1967 TelnetRequest(ddww, option)
1968 unsigned char ddww, option;
1970 unsigned char msg[3];
1971 int outCount, outError;
1973 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
1975 if (appData.debugMode) {
1976 char buf1[8], buf2[8], *ddwwStr, *optionStr;
1992 snprintf(buf1,sizeof(buf1)/sizeof(buf1[0]), "%d", ddww);
2001 snprintf(buf2,sizeof(buf2)/sizeof(buf2[0]), "%d", option);
2004 fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
2009 outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
2011 DisplayFatalError(_("Error writing to ICS"), outError, 1);
2018 if (!appData.icsActive) return;
2019 TelnetRequest(TN_DO, TN_ECHO);
2025 if (!appData.icsActive) return;
2026 TelnetRequest(TN_DONT, TN_ECHO);
2030 CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece)
2032 /* put the holdings sent to us by the server on the board holdings area */
2033 int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2037 if(gameInfo.holdingsWidth < 2) return;
2038 if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2039 return; // prevent overwriting by pre-board holdings
2041 if( (int)lowestPiece >= BlackPawn ) {
2044 holdingsStartRow = BOARD_HEIGHT-1;
2047 holdingsColumn = BOARD_WIDTH-1;
2048 countsColumn = BOARD_WIDTH-2;
2049 holdingsStartRow = 0;
2053 for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
2054 board[i][holdingsColumn] = EmptySquare;
2055 board[i][countsColumn] = (ChessSquare) 0;
2057 while( (p=*holdings++) != NULLCHAR ) {
2058 piece = CharToPiece( ToUpper(p) );
2059 if(piece == EmptySquare) continue;
2060 /*j = (int) piece - (int) WhitePawn;*/
2061 j = PieceToNumber(piece);
2062 if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2063 if(j < 0) continue; /* should not happen */
2064 piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2065 board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2066 board[holdingsStartRow+j*direction][countsColumn]++;
2072 VariantSwitch(Board board, VariantClass newVariant)
2074 int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2075 static Board oldBoard;
2077 startedFromPositionFile = FALSE;
2078 if(gameInfo.variant == newVariant) return;
2080 /* [HGM] This routine is called each time an assignment is made to
2081 * gameInfo.variant during a game, to make sure the board sizes
2082 * are set to match the new variant. If that means adding or deleting
2083 * holdings, we shift the playing board accordingly
2084 * This kludge is needed because in ICS observe mode, we get boards
2085 * of an ongoing game without knowing the variant, and learn about the
2086 * latter only later. This can be because of the move list we requested,
2087 * in which case the game history is refilled from the beginning anyway,
2088 * but also when receiving holdings of a crazyhouse game. In the latter
2089 * case we want to add those holdings to the already received position.
2093 if (appData.debugMode) {
2094 fprintf(debugFP, "Switch board from %s to %s\n",
2095 VariantName(gameInfo.variant), VariantName(newVariant));
2096 setbuf(debugFP, NULL);
2098 shuffleOpenings = 0; /* [HGM] shuffle */
2099 gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2103 newWidth = 9; newHeight = 9;
2104 gameInfo.holdingsSize = 7;
2105 case VariantBughouse:
2106 case VariantCrazyhouse:
2107 newHoldingsWidth = 2; break;
2111 newHoldingsWidth = 2;
2112 gameInfo.holdingsSize = 8;
2115 case VariantCapablanca:
2116 case VariantCapaRandom:
2119 newHoldingsWidth = gameInfo.holdingsSize = 0;
2122 if(newWidth != gameInfo.boardWidth ||
2123 newHeight != gameInfo.boardHeight ||
2124 newHoldingsWidth != gameInfo.holdingsWidth ) {
2126 /* shift position to new playing area, if needed */
2127 if(newHoldingsWidth > gameInfo.holdingsWidth) {
2128 for(i=0; i<BOARD_HEIGHT; i++)
2129 for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2130 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2132 for(i=0; i<newHeight; i++) {
2133 board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2134 board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2136 } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2137 for(i=0; i<BOARD_HEIGHT; i++)
2138 for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2139 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2142 gameInfo.boardWidth = newWidth;
2143 gameInfo.boardHeight = newHeight;
2144 gameInfo.holdingsWidth = newHoldingsWidth;
2145 gameInfo.variant = newVariant;
2146 InitDrawingSizes(-2, 0);
2147 } else gameInfo.variant = newVariant;
2148 CopyBoard(oldBoard, board); // remember correctly formatted board
2149 InitPosition(FALSE); /* this sets up board[0], but also other stuff */
2150 DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2153 static int loggedOn = FALSE;
2155 /*-- Game start info cache: --*/
2157 char gs_kind[MSG_SIZ];
2158 static char player1Name[128] = "";
2159 static char player2Name[128] = "";
2160 static char cont_seq[] = "\n\\ ";
2161 static int player1Rating = -1;
2162 static int player2Rating = -1;
2163 /*----------------------------*/
2165 ColorClass curColor = ColorNormal;
2166 int suppressKibitz = 0;
2169 Boolean soughtPending = FALSE;
2170 Boolean seekGraphUp;
2171 #define MAX_SEEK_ADS 200
2173 char *seekAdList[MAX_SEEK_ADS];
2174 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2175 float tcList[MAX_SEEK_ADS];
2176 char colorList[MAX_SEEK_ADS];
2177 int nrOfSeekAds = 0;
2178 int minRating = 1010, maxRating = 2800;
2179 int hMargin = 10, vMargin = 20, h, w;
2180 extern int squareSize, lineGap;
2185 int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2186 xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2187 if(r < minRating+100 && r >=0 ) r = minRating+100;
2188 if(r > maxRating) r = maxRating;
2189 if(tc < 1.) tc = 1.;
2190 if(tc > 95.) tc = 95.;
2191 x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2192 y = ((double)r - minRating)/(maxRating - minRating)
2193 * (h-vMargin-squareSize/8-1) + vMargin;
2194 if(ratingList[i] < 0) y = vMargin + squareSize/4;
2195 if(strstr(seekAdList[i], " u ")) color = 1;
2196 if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2197 !strstr(seekAdList[i], "bullet") &&
2198 !strstr(seekAdList[i], "blitz") &&
2199 !strstr(seekAdList[i], "standard") ) color = 2;
2200 if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2201 DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2205 AddAd(char *handle, char *rating, int base, int inc, char rated, char *type, int nr, Boolean plot)
2207 char buf[MSG_SIZ], *ext = "";
2208 VariantClass v = StringToVariant(type);
2209 if(strstr(type, "wild")) {
2210 ext = type + 4; // append wild number
2211 if(v == VariantFischeRandom) type = "chess960"; else
2212 if(v == VariantLoadable) type = "setup"; else
2213 type = VariantName(v);
2215 snprintf(buf, MSG_SIZ, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2216 if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2217 if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2218 ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2219 sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2220 tcList[nrOfSeekAds] = base + (2./3.)*inc;
2221 seekNrList[nrOfSeekAds] = nr;
2222 zList[nrOfSeekAds] = 0;
2223 seekAdList[nrOfSeekAds++] = StrSave(buf);
2224 if(plot) PlotSeekAd(nrOfSeekAds-1);
2231 int x = xList[i], y = yList[i], d=squareSize/4, k;
2232 DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2233 if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2234 // now replot every dot that overlapped
2235 for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2236 int xx = xList[k], yy = yList[k];
2237 if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2238 DrawSeekDot(xx, yy, colorList[k]);
2243 RemoveSeekAd(int nr)
2246 for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2248 if(seekAdList[i]) free(seekAdList[i]);
2249 seekAdList[i] = seekAdList[--nrOfSeekAds];
2250 seekNrList[i] = seekNrList[nrOfSeekAds];
2251 ratingList[i] = ratingList[nrOfSeekAds];
2252 colorList[i] = colorList[nrOfSeekAds];
2253 tcList[i] = tcList[nrOfSeekAds];
2254 xList[i] = xList[nrOfSeekAds];
2255 yList[i] = yList[nrOfSeekAds];
2256 zList[i] = zList[nrOfSeekAds];
2257 seekAdList[nrOfSeekAds] = NULL;
2263 MatchSoughtLine(char *line)
2265 char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2266 int nr, base, inc, u=0; char dummy;
2268 if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2269 sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2271 (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2272 sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7) ) {
2273 // match: compact and save the line
2274 AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2284 if(!seekGraphUp) return FALSE;
2285 h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
2286 w = BOARD_WIDTH * (squareSize + lineGap) + lineGap;
2288 DrawSeekBackground(0, 0, w, h);
2289 DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2290 DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2291 for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2292 int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2294 DrawSeekAxis(hMargin+5*(i%500==0), yy, hMargin-5, yy); // rating ticks
2297 snprintf(buf, MSG_SIZ, "%d", i);
2298 DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2301 DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2302 for(i=1; i<100; i+=(i<10?1:5)) {
2303 int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2304 DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2305 if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2307 snprintf(buf, MSG_SIZ, "%d", i);
2308 DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2311 for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2315 int SeekGraphClick(ClickType click, int x, int y, int moving)
2317 static int lastDown = 0, displayed = 0, lastSecond;
2318 if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2319 if(click == Release || moving) return FALSE;
2321 soughtPending = TRUE;
2322 SendToICS(ics_prefix);
2323 SendToICS("sought\n"); // should this be "sought all"?
2324 } else { // issue challenge based on clicked ad
2325 int dist = 10000; int i, closest = 0, second = 0;
2326 for(i=0; i<nrOfSeekAds; i++) {
2327 int d = (x-xList[i])*(x-xList[i]) + (y-yList[i])*(y-yList[i]) + zList[i];
2328 if(d < dist) { dist = d; closest = i; }
2329 second += (d - zList[i] < 120); // count in-range ads
2330 if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2334 second = (second > 1);
2335 if(displayed != closest || second != lastSecond) {
2336 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2337 lastSecond = second; displayed = closest;
2339 if(click == Press) {
2340 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2343 } // on press 'hit', only show info
2344 if(moving == 2) return TRUE; // ignore right up-clicks on dot
2345 snprintf(buf, MSG_SIZ, "play %d\n", seekNrList[closest]);
2346 SendToICS(ics_prefix);
2348 return TRUE; // let incoming board of started game pop down the graph
2349 } else if(click == Release) { // release 'miss' is ignored
2350 zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2351 if(moving == 2) { // right up-click
2352 nrOfSeekAds = 0; // refresh graph
2353 soughtPending = TRUE;
2354 SendToICS(ics_prefix);
2355 SendToICS("sought\n"); // should this be "sought all"?
2358 } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2359 // press miss or release hit 'pop down' seek graph
2360 seekGraphUp = FALSE;
2361 DrawPosition(TRUE, NULL);
2367 read_from_ics(isr, closure, data, count, error)
2374 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2375 #define STARTED_NONE 0
2376 #define STARTED_MOVES 1
2377 #define STARTED_BOARD 2
2378 #define STARTED_OBSERVE 3
2379 #define STARTED_HOLDINGS 4
2380 #define STARTED_CHATTER 5
2381 #define STARTED_COMMENT 6
2382 #define STARTED_MOVES_NOHIDE 7
2384 static int started = STARTED_NONE;
2385 static char parse[20000];
2386 static int parse_pos = 0;
2387 static char buf[BUF_SIZE + 1];
2388 static int firstTime = TRUE, intfSet = FALSE;
2389 static ColorClass prevColor = ColorNormal;
2390 static int savingComment = FALSE;
2391 static int cmatch = 0; // continuation sequence match
2398 int backup; /* [DM] For zippy color lines */
2400 char talker[MSG_SIZ]; // [HGM] chat
2403 connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2405 if (appData.debugMode) {
2407 fprintf(debugFP, "<ICS: ");
2408 show_bytes(debugFP, data, count);
2409 fprintf(debugFP, "\n");
2413 if (appData.debugMode) { int f = forwardMostMove;
2414 fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2415 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2416 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2419 /* If last read ended with a partial line that we couldn't parse,
2420 prepend it to the new read and try again. */
2421 if (leftover_len > 0) {
2422 for (i=0; i<leftover_len; i++)
2423 buf[i] = buf[leftover_start + i];
2426 /* copy new characters into the buffer */
2427 bp = buf + leftover_len;
2428 buf_len=leftover_len;
2429 for (i=0; i<count; i++)
2432 if (data[i] == '\r')
2435 // join lines split by ICS?
2436 if (!appData.noJoin)
2439 Joining just consists of finding matches against the
2440 continuation sequence, and discarding that sequence
2441 if found instead of copying it. So, until a match
2442 fails, there's nothing to do since it might be the
2443 complete sequence, and thus, something we don't want
2446 if (data[i] == cont_seq[cmatch])
2449 if (cmatch == strlen(cont_seq))
2451 cmatch = 0; // complete match. just reset the counter
2454 it's possible for the ICS to not include the space
2455 at the end of the last word, making our [correct]
2456 join operation fuse two separate words. the server
2457 does this when the space occurs at the width setting.
2459 if (!buf_len || buf[buf_len-1] != ' ')
2470 match failed, so we have to copy what matched before
2471 falling through and copying this character. In reality,
2472 this will only ever be just the newline character, but
2473 it doesn't hurt to be precise.
2475 strncpy(bp, cont_seq, cmatch);
2487 buf[buf_len] = NULLCHAR;
2488 // next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2493 while (i < buf_len) {
2494 /* Deal with part of the TELNET option negotiation
2495 protocol. We refuse to do anything beyond the
2496 defaults, except that we allow the WILL ECHO option,
2497 which ICS uses to turn off password echoing when we are
2498 directly connected to it. We reject this option
2499 if localLineEditing mode is on (always on in xboard)
2500 and we are talking to port 23, which might be a real
2501 telnet server that will try to keep WILL ECHO on permanently.
2503 if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2504 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2505 unsigned char option;
2507 switch ((unsigned char) buf[++i]) {
2509 if (appData.debugMode)
2510 fprintf(debugFP, "\n<WILL ");
2511 switch (option = (unsigned char) buf[++i]) {
2513 if (appData.debugMode)
2514 fprintf(debugFP, "ECHO ");
2515 /* Reply only if this is a change, according
2516 to the protocol rules. */
2517 if (remoteEchoOption) break;
2518 if (appData.localLineEditing &&
2519 atoi(appData.icsPort) == TN_PORT) {
2520 TelnetRequest(TN_DONT, TN_ECHO);
2523 TelnetRequest(TN_DO, TN_ECHO);
2524 remoteEchoOption = TRUE;
2528 if (appData.debugMode)
2529 fprintf(debugFP, "%d ", option);
2530 /* Whatever this is, we don't want it. */
2531 TelnetRequest(TN_DONT, option);
2536 if (appData.debugMode)
2537 fprintf(debugFP, "\n<WONT ");
2538 switch (option = (unsigned char) buf[++i]) {
2540 if (appData.debugMode)
2541 fprintf(debugFP, "ECHO ");
2542 /* Reply only if this is a change, according
2543 to the protocol rules. */
2544 if (!remoteEchoOption) break;
2546 TelnetRequest(TN_DONT, TN_ECHO);
2547 remoteEchoOption = FALSE;
2550 if (appData.debugMode)
2551 fprintf(debugFP, "%d ", (unsigned char) option);
2552 /* Whatever this is, it must already be turned
2553 off, because we never agree to turn on
2554 anything non-default, so according to the
2555 protocol rules, we don't reply. */
2560 if (appData.debugMode)
2561 fprintf(debugFP, "\n<DO ");
2562 switch (option = (unsigned char) buf[++i]) {
2564 /* Whatever this is, we refuse to do it. */
2565 if (appData.debugMode)
2566 fprintf(debugFP, "%d ", option);
2567 TelnetRequest(TN_WONT, option);
2572 if (appData.debugMode)
2573 fprintf(debugFP, "\n<DONT ");
2574 switch (option = (unsigned char) buf[++i]) {
2576 if (appData.debugMode)
2577 fprintf(debugFP, "%d ", option);
2578 /* Whatever this is, we are already not doing
2579 it, because we never agree to do anything
2580 non-default, so according to the protocol
2581 rules, we don't reply. */
2586 if (appData.debugMode)
2587 fprintf(debugFP, "\n<IAC ");
2588 /* Doubled IAC; pass it through */
2592 if (appData.debugMode)
2593 fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2594 /* Drop all other telnet commands on the floor */
2597 if (oldi > next_out)
2598 SendToPlayer(&buf[next_out], oldi - next_out);
2604 /* OK, this at least will *usually* work */
2605 if (!loggedOn && looking_at(buf, &i, "ics%")) {
2609 if (loggedOn && !intfSet) {
2610 if (ics_type == ICS_ICC) {
2611 snprintf(str, MSG_SIZ,
2612 "/set-quietly interface %s\n/set-quietly style 12\n",
2614 if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2615 strcat(str, "/set-2 51 1\n/set seek 1\n");
2616 } else if (ics_type == ICS_CHESSNET) {
2617 snprintf(str, MSG_SIZ, "/style 12\n");
2619 safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
2620 strcat(str, programVersion);
2621 strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2622 if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2623 strcat(str, "$iset seekremove 1\n$set seek 1\n");
2625 strcat(str, "$iset nohighlight 1\n");
2627 strcat(str, "$iset lock 1\n$style 12\n");
2630 NotifyFrontendLogin();
2634 if (started == STARTED_COMMENT) {
2635 /* Accumulate characters in comment */
2636 parse[parse_pos++] = buf[i];
2637 if (buf[i] == '\n') {
2638 parse[parse_pos] = NULLCHAR;
2639 if(chattingPartner>=0) {
2641 snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
2642 OutputChatMessage(chattingPartner, mess);
2643 chattingPartner = -1;
2644 next_out = i+1; // [HGM] suppress printing in ICS window
2646 if(!suppressKibitz) // [HGM] kibitz
2647 AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
2648 else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2649 int nrDigit = 0, nrAlph = 0, j;
2650 if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2651 { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2652 parse[parse_pos] = NULLCHAR;
2653 // try to be smart: if it does not look like search info, it should go to
2654 // ICS interaction window after all, not to engine-output window.
2655 for(j=0; j<parse_pos; j++) { // count letters and digits
2656 nrDigit += (parse[j] >= '0' && parse[j] <= '9');
2657 nrAlph += (parse[j] >= 'a' && parse[j] <= 'z');
2658 nrAlph += (parse[j] >= 'A' && parse[j] <= 'Z');
2660 if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2661 int depth=0; float score;
2662 if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2663 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2664 pvInfoList[forwardMostMove-1].depth = depth;
2665 pvInfoList[forwardMostMove-1].score = 100*score;
2667 OutputKibitz(suppressKibitz, parse);
2670 snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
2671 SendToPlayer(tmp, strlen(tmp));
2673 next_out = i+1; // [HGM] suppress printing in ICS window
2675 started = STARTED_NONE;
2677 /* Don't match patterns against characters in comment */
2682 if (started == STARTED_CHATTER) {
2683 if (buf[i] != '\n') {
2684 /* Don't match patterns against characters in chatter */
2688 started = STARTED_NONE;
2689 if(suppressKibitz) next_out = i+1;
2692 /* Kludge to deal with rcmd protocol */
2693 if (firstTime && looking_at(buf, &i, "\001*")) {
2694 DisplayFatalError(&buf[1], 0, 1);
2700 if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2703 if (appData.debugMode)
2704 fprintf(debugFP, "ics_type %d\n", ics_type);
2707 if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
2708 ics_type = ICS_FICS;
2710 if (appData.debugMode)
2711 fprintf(debugFP, "ics_type %d\n", ics_type);
2714 if (!loggedOn && looking_at(buf, &i, "chess.net")) {
2715 ics_type = ICS_CHESSNET;
2717 if (appData.debugMode)
2718 fprintf(debugFP, "ics_type %d\n", ics_type);
2723 (looking_at(buf, &i, "\"*\" is *a registered name") ||
2724 looking_at(buf, &i, "Logging you in as \"*\"") ||
2725 looking_at(buf, &i, "will be \"*\""))) {
2726 safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
2730 if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
2732 snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
2733 DisplayIcsInteractionTitle(buf);
2734 have_set_title = TRUE;
2737 /* skip finger notes */
2738 if (started == STARTED_NONE &&
2739 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
2740 (buf[i] == '1' && buf[i+1] == '0')) &&
2741 buf[i+2] == ':' && buf[i+3] == ' ') {
2742 started = STARTED_CHATTER;
2748 // [HGM] seekgraph: recognize sought lines and end-of-sought message
2749 if(appData.seekGraph) {
2750 if(soughtPending && MatchSoughtLine(buf+i)) {
2751 i = strstr(buf+i, "rated") - buf;
2752 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2753 next_out = leftover_start = i;
2754 started = STARTED_CHATTER;
2755 suppressKibitz = TRUE;
2758 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
2759 && looking_at(buf, &i, "* ads displayed")) {
2760 soughtPending = FALSE;
2765 if(appData.autoRefresh) {
2766 if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
2767 int s = (ics_type == ICS_ICC); // ICC format differs
2769 AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
2770 star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
2771 looking_at(buf, &i, "*% "); // eat prompt
2772 if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
2773 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2774 next_out = i; // suppress
2777 if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
2778 char *p = star_match[0];
2780 if(seekGraphUp) RemoveSeekAd(atoi(p));
2781 while(*p && *p++ != ' '); // next
2783 looking_at(buf, &i, "*% "); // eat prompt
2784 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2791 /* skip formula vars */
2792 if (started == STARTED_NONE &&
2793 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
2794 started = STARTED_CHATTER;
2799 // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
2800 if (appData.autoKibitz && started == STARTED_NONE &&
2801 !appData.icsEngineAnalyze && // [HGM] [DM] ICS analyze
2802 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
2803 if((looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
2804 (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
2805 StrStr(star_match[0], gameInfo.black) == star_match[0] )) { // kibitz of self or opponent
2806 suppressKibitz = TRUE;
2807 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2809 if((StrStr(star_match[0], gameInfo.white) == star_match[0]
2810 && (gameMode == IcsPlayingWhite)) ||
2811 (StrStr(star_match[0], gameInfo.black) == star_match[0]
2812 && (gameMode == IcsPlayingBlack)) ) // opponent kibitz
2813 started = STARTED_CHATTER; // own kibitz we simply discard
2815 started = STARTED_COMMENT; // make sure it will be collected in parse[]
2816 parse_pos = 0; parse[0] = NULLCHAR;
2817 savingComment = TRUE;
2818 suppressKibitz = gameMode != IcsObserving ? 2 :
2819 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
2823 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
2824 looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
2825 && atoi(star_match[0])) {
2826 // suppress the acknowledgements of our own autoKibitz
2828 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2829 if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
2830 SendToPlayer(star_match[0], strlen(star_match[0]));
2831 if(looking_at(buf, &i, "*% ")) // eat prompt
2832 suppressKibitz = FALSE;
2836 } // [HGM] kibitz: end of patch
2838 // [HGM] chat: intercept tells by users for which we have an open chat window
2840 if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
2841 looking_at(buf, &i, "* whispers:") ||
2842 looking_at(buf, &i, "* kibitzes:") ||
2843 looking_at(buf, &i, "* shouts:") ||
2844 looking_at(buf, &i, "* c-shouts:") ||
2845 looking_at(buf, &i, "--> * ") ||
2846 looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
2847 looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
2848 looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
2849 looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
2851 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
2852 chattingPartner = -1;
2854 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
2855 for(p=0; p<MAX_CHAT; p++) {
2856 if(channel == atoi(chatPartner[p])) {
2857 talker[0] = '['; strcat(talker, "] ");
2858 Colorize(channel == 1 ? ColorChannel1 : ColorChannel, FALSE);
2859 chattingPartner = p; break;
2862 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
2863 for(p=0; p<MAX_CHAT; p++) {
2864 if(!strcmp("kibitzes", chatPartner[p])) {
2865 talker[0] = '['; strcat(talker, "] ");
2866 chattingPartner = p; break;
2869 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
2870 for(p=0; p<MAX_CHAT; p++) {
2871 if(!strcmp("whispers", chatPartner[p])) {
2872 talker[0] = '['; strcat(talker, "] ");
2873 chattingPartner = p; break;
2876 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
2877 if(buf[i-8] == '-' && buf[i-3] == 't')
2878 for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
2879 if(!strcmp("c-shouts", chatPartner[p])) {
2880 talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
2881 chattingPartner = p; break;
2884 if(chattingPartner < 0)
2885 for(p=0; p<MAX_CHAT; p++) {
2886 if(!strcmp("shouts", chatPartner[p])) {
2887 if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
2888 else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
2889 else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
2890 chattingPartner = p; break;
2894 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
2895 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
2896 talker[0] = 0; Colorize(ColorTell, FALSE);
2897 chattingPartner = p; break;
2899 if(chattingPartner<0) i = oldi; else {
2900 Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
2901 if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
2902 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2903 started = STARTED_COMMENT;
2904 parse_pos = 0; parse[0] = NULLCHAR;
2905 savingComment = 3 + chattingPartner; // counts as TRUE
2906 suppressKibitz = TRUE;
2909 } // [HGM] chat: end of patch
2911 if (appData.zippyTalk || appData.zippyPlay) {
2912 /* [DM] Backup address for color zippy lines */
2915 if (loggedOn == TRUE)
2916 if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
2917 (appData.zippyPlay && ZippyMatch(buf, &backup)));
2919 } // [DM] 'else { ' deleted
2921 /* Regular tells and says */
2922 (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
2923 looking_at(buf, &i, "* (your partner) tells you: ") ||
2924 looking_at(buf, &i, "* says: ") ||
2925 /* Don't color "message" or "messages" output */
2926 (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
2927 looking_at(buf, &i, "*. * at *:*: ") ||
2928 looking_at(buf, &i, "--* (*:*): ") ||
2929 /* Message notifications (same color as tells) */
2930 looking_at(buf, &i, "* has left a message ") ||
2931 looking_at(buf, &i, "* just sent you a message:\n") ||
2932 /* Whispers and kibitzes */
2933 (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
2934 looking_at(buf, &i, "* kibitzes: ") ||
2936 (tkind = 3, looking_at(buf, &i, "*(*: "))) {
2938 if (tkind == 1 && strchr(star_match[0], ':')) {
2939 /* Avoid "tells you:" spoofs in channels */
2942 if (star_match[0][0] == NULLCHAR ||
2943 strchr(star_match[0], ' ') ||
2944 (tkind == 3 && strchr(star_match[1], ' '))) {
2945 /* Reject bogus matches */
2948 if (appData.colorize) {
2949 if (oldi > next_out) {
2950 SendToPlayer(&buf[next_out], oldi - next_out);
2955 Colorize(ColorTell, FALSE);
2956 curColor = ColorTell;
2959 Colorize(ColorKibitz, FALSE);
2960 curColor = ColorKibitz;
2963 p = strrchr(star_match[1], '(');
2970 Colorize(ColorChannel1, FALSE);
2971 curColor = ColorChannel1;
2973 Colorize(ColorChannel, FALSE);
2974 curColor = ColorChannel;
2978 curColor = ColorNormal;
2982 if (started == STARTED_NONE && appData.autoComment &&
2983 (gameMode == IcsObserving ||
2984 gameMode == IcsPlayingWhite ||
2985 gameMode == IcsPlayingBlack)) {
2986 parse_pos = i - oldi;
2987 memcpy(parse, &buf[oldi], parse_pos);
2988 parse[parse_pos] = NULLCHAR;
2989 started = STARTED_COMMENT;
2990 savingComment = TRUE;
2992 started = STARTED_CHATTER;
2993 savingComment = FALSE;
3000 if (looking_at(buf, &i, "* s-shouts: ") ||
3001 looking_at(buf, &i, "* c-shouts: ")) {
3002 if (appData.colorize) {
3003 if (oldi > next_out) {
3004 SendToPlayer(&buf[next_out], oldi - next_out);
3007 Colorize(ColorSShout, FALSE);
3008 curColor = ColorSShout;
3011 started = STARTED_CHATTER;
3015 if (looking_at(buf, &i, "--->")) {
3020 if (looking_at(buf, &i, "* shouts: ") ||
3021 looking_at(buf, &i, "--> ")) {
3022 if (appData.colorize) {
3023 if (oldi > next_out) {
3024 SendToPlayer(&buf[next_out], oldi - next_out);
3027 Colorize(ColorShout, FALSE);
3028 curColor = ColorShout;
3031 started = STARTED_CHATTER;
3035 if (looking_at( buf, &i, "Challenge:")) {
3036 if (appData.colorize) {
3037 if (oldi > next_out) {
3038 SendToPlayer(&buf[next_out], oldi - next_out);
3041 Colorize(ColorChallenge, FALSE);
3042 curColor = ColorChallenge;
3048 if (looking_at(buf, &i, "* offers you") ||
3049 looking_at(buf, &i, "* offers to be") ||
3050 looking_at(buf, &i, "* would like to") ||
3051 looking_at(buf, &i, "* requests to") ||
3052 looking_at(buf, &i, "Your opponent offers") ||
3053 looking_at(buf, &i, "Your opponent requests")) {
3055 if (appData.colorize) {
3056 if (oldi > next_out) {
3057 SendToPlayer(&buf[next_out], oldi - next_out);
3060 Colorize(ColorRequest, FALSE);
3061 curColor = ColorRequest;
3066 if (looking_at(buf, &i, "* (*) seeking")) {
3067 if (appData.colorize) {
3068 if (oldi > next_out) {
3069 SendToPlayer(&buf[next_out], oldi - next_out);
3072 Colorize(ColorSeek, FALSE);
3073 curColor = ColorSeek;
3078 if (looking_at(buf, &i, "\\ ")) {
3079 if (prevColor != ColorNormal) {
3080 if (oldi > next_out) {
3081 SendToPlayer(&buf[next_out], oldi - next_out);
3084 Colorize(prevColor, TRUE);
3085 curColor = prevColor;
3087 if (savingComment) {
3088 parse_pos = i - oldi;
3089 memcpy(parse, &buf[oldi], parse_pos);
3090 parse[parse_pos] = NULLCHAR;
3091 started = STARTED_COMMENT;
3092 if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3093 chattingPartner = savingComment - 3; // kludge to remember the box
3095 started = STARTED_CHATTER;
3100 if (looking_at(buf, &i, "Black Strength :") ||
3101 looking_at(buf, &i, "<<< style 10 board >>>") ||
3102 looking_at(buf, &i, "<10>") ||
3103 looking_at(buf, &i, "#@#")) {
3104 /* Wrong board style */
3106 SendToICS(ics_prefix);
3107 SendToICS("set style 12\n");
3108 SendToICS(ics_prefix);
3109 SendToICS("refresh\n");
3113 if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
3115 have_sent_ICS_logon = 1;
3119 if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3120 (looking_at(buf, &i, "\n<12> ") ||
3121 looking_at(buf, &i, "<12> "))) {
3123 if (oldi > next_out) {
3124 SendToPlayer(&buf[next_out], oldi - next_out);
3127 started = STARTED_BOARD;
3132 if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3133 looking_at(buf, &i, "<b1> ")) {
3134 if (oldi > next_out) {
3135 SendToPlayer(&buf[next_out], oldi - next_out);
3138 started = STARTED_HOLDINGS;
3143 if (looking_at(buf, &i, "* *vs. * *--- *")) {
3145 /* Header for a move list -- first line */
3147 switch (ics_getting_history) {
3151 case BeginningOfGame:
3152 /* User typed "moves" or "oldmoves" while we
3153 were idle. Pretend we asked for these
3154 moves and soak them up so user can step
3155 through them and/or save them.
3158 gameMode = IcsObserving;
3161 ics_getting_history = H_GOT_UNREQ_HEADER;
3163 case EditGame: /*?*/
3164 case EditPosition: /*?*/
3165 /* Should above feature work in these modes too? */
3166 /* For now it doesn't */
3167 ics_getting_history = H_GOT_UNWANTED_HEADER;
3170 ics_getting_history = H_GOT_UNWANTED_HEADER;
3175 /* Is this the right one? */
3176 if (gameInfo.white && gameInfo.black &&
3177 strcmp(gameInfo.white, star_match[0]) == 0 &&
3178 strcmp(gameInfo.black, star_match[2]) == 0) {
3180 ics_getting_history = H_GOT_REQ_HEADER;
3183 case H_GOT_REQ_HEADER:
3184 case H_GOT_UNREQ_HEADER:
3185 case H_GOT_UNWANTED_HEADER:
3186 case H_GETTING_MOVES:
3187 /* Should not happen */
3188 DisplayError(_("Error gathering move list: two headers"), 0);
3189 ics_getting_history = H_FALSE;
3193 /* Save player ratings into gameInfo if needed */
3194 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3195 ics_getting_history == H_GOT_UNREQ_HEADER) &&
3196 (gameInfo.whiteRating == -1 ||
3197 gameInfo.blackRating == -1)) {
3199 gameInfo.whiteRating = string_to_rating(star_match[1]);
3200 gameInfo.blackRating = string_to_rating(star_match[3]);
3201 if (appData.debugMode)
3202 fprintf(debugFP, _("Ratings from header: W %d, B %d\n"),
3203 gameInfo.whiteRating, gameInfo.blackRating);
3208 if (looking_at(buf, &i,
3209 "* * match, initial time: * minute*, increment: * second")) {
3210 /* Header for a move list -- second line */
3211 /* Initial board will follow if this is a wild game */
3212 if (gameInfo.event != NULL) free(gameInfo.event);
3213 snprintf(str, MSG_SIZ, "ICS %s %s match", star_match[0], star_match[1]);
3214 gameInfo.event = StrSave(str);
3215 /* [HGM] we switched variant. Translate boards if needed. */
3216 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3220 if (looking_at(buf, &i, "Move ")) {
3221 /* Beginning of a move list */
3222 switch (ics_getting_history) {
3224 /* Normally should not happen */
3225 /* Maybe user hit reset while we were parsing */
3228 /* Happens if we are ignoring a move list that is not
3229 * the one we just requested. Common if the user
3230 * tries to observe two games without turning off
3233 case H_GETTING_MOVES:
3234 /* Should not happen */
3235 DisplayError(_("Error gathering move list: nested"), 0);
3236 ics_getting_history = H_FALSE;
3238 case H_GOT_REQ_HEADER:
3239 ics_getting_history = H_GETTING_MOVES;
3240 started = STARTED_MOVES;
3242 if (oldi > next_out) {
3243 SendToPlayer(&buf[next_out], oldi - next_out);
3246 case H_GOT_UNREQ_HEADER:
3247 ics_getting_history = H_GETTING_MOVES;
3248 started = STARTED_MOVES_NOHIDE;
3251 case H_GOT_UNWANTED_HEADER:
3252 ics_getting_history = H_FALSE;
3258 if (looking_at(buf, &i, "% ") ||
3259 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3260 && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3261 if(ics_type == ICS_ICC && soughtPending) { // [HGM] seekgraph: on ICC sought-list has no termination line
3262 soughtPending = FALSE;
3266 if(suppressKibitz) next_out = i;
3267 savingComment = FALSE;
3271 case STARTED_MOVES_NOHIDE:
3272 memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3273 parse[parse_pos + i - oldi] = NULLCHAR;
3274 ParseGameHistory(parse);
3276 if (appData.zippyPlay && first.initDone) {
3277 FeedMovesToProgram(&first, forwardMostMove);
3278 if (gameMode == IcsPlayingWhite) {
3279 if (WhiteOnMove(forwardMostMove)) {
3280 if (first.sendTime) {
3281 if (first.useColors) {
3282 SendToProgram("black\n", &first);
3284 SendTimeRemaining(&first, TRUE);
3286 if (first.useColors) {
3287 SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3289 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3290 first.maybeThinking = TRUE;
3292 if (first.usePlayother) {
3293 if (first.sendTime) {
3294 SendTimeRemaining(&first, TRUE);
3296 SendToProgram("playother\n", &first);
3302 } else if (gameMode == IcsPlayingBlack) {
3303 if (!WhiteOnMove(forwardMostMove)) {
3304 if (first.sendTime) {
3305 if (first.useColors) {
3306 SendToProgram("white\n", &first);
3308 SendTimeRemaining(&first, FALSE);
3310 if (first.useColors) {
3311 SendToProgram("black\n", &first);
3313 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3314 first.maybeThinking = TRUE;
3316 if (first.usePlayother) {
3317 if (first.sendTime) {
3318 SendTimeRemaining(&first, FALSE);
3320 SendToProgram("playother\n", &first);
3329 if (gameMode == IcsObserving && ics_gamenum == -1) {
3330 /* Moves came from oldmoves or moves command
3331 while we weren't doing anything else.
3333 currentMove = forwardMostMove;
3334 ClearHighlights();/*!!could figure this out*/
3335 flipView = appData.flipView;
3336 DrawPosition(TRUE, boards[currentMove]);
3337 DisplayBothClocks();
3338 snprintf(str, MSG_SIZ, "%s vs. %s",
3339 gameInfo.white, gameInfo.black);
3343 /* Moves were history of an active game */
3344 if (gameInfo.resultDetails != NULL) {
3345 free(gameInfo.resultDetails);
3346 gameInfo.resultDetails = NULL;
3349 HistorySet(parseList, backwardMostMove,
3350 forwardMostMove, currentMove-1);
3351 DisplayMove(currentMove - 1);
3352 if (started == STARTED_MOVES) next_out = i;
3353 started = STARTED_NONE;
3354 ics_getting_history = H_FALSE;
3357 case STARTED_OBSERVE:
3358 started = STARTED_NONE;
3359 SendToICS(ics_prefix);
3360 SendToICS("refresh\n");
3366 if(bookHit) { // [HGM] book: simulate book reply
3367 static char bookMove[MSG_SIZ]; // a bit generous?
3369 programStats.nodes = programStats.depth = programStats.time =
3370 programStats.score = programStats.got_only_move = 0;
3371 sprintf(programStats.movelist, "%s (xbook)", bookHit);
3373 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
3374 strcat(bookMove, bookHit);
3375 HandleMachineMove(bookMove, &first);
3380 if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3381 started == STARTED_HOLDINGS ||
3382 started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3383 /* Accumulate characters in move list or board */
3384 parse[parse_pos++] = buf[i];
3387 /* Start of game messages. Mostly we detect start of game
3388 when the first board image arrives. On some versions
3389 of the ICS, though, we need to do a "refresh" after starting
3390 to observe in order to get the current board right away. */
3391 if (looking_at(buf, &i, "Adding game * to observation list")) {
3392 started = STARTED_OBSERVE;
3396 /* Handle auto-observe */
3397 if (appData.autoObserve &&
3398 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3399 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3401 /* Choose the player that was highlighted, if any. */
3402 if (star_match[0][0] == '\033' ||
3403 star_match[1][0] != '\033') {
3404 player = star_match[0];
3406 player = star_match[2];
3408 snprintf(str, MSG_SIZ, "%sobserve %s\n",
3409 ics_prefix, StripHighlightAndTitle(player));
3412 /* Save ratings from notify string */
3413 safeStrCpy(player1Name, star_match[0], sizeof(player1Name)/sizeof(player1Name[0]));
3414 player1Rating = string_to_rating(star_match[1]);
3415 safeStrCpy(player2Name, star_match[2], sizeof(player2Name)/sizeof(player2Name[0]));
3416 player2Rating = string_to_rating(star_match[3]);
3418 if (appData.debugMode)
3420 "Ratings from 'Game notification:' %s %d, %s %d\n",
3421 player1Name, player1Rating,
3422 player2Name, player2Rating);
3427 /* Deal with automatic examine mode after a game,
3428 and with IcsObserving -> IcsExamining transition */
3429 if (looking_at(buf, &i, "Entering examine mode for game *") ||
3430 looking_at(buf, &i, "has made you an examiner of game *")) {
3432 int gamenum = atoi(star_match[0]);
3433 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3434 gamenum == ics_gamenum) {
3435 /* We were already playing or observing this game;
3436 no need to refetch history */
3437 gameMode = IcsExamining;
3439 pauseExamForwardMostMove = forwardMostMove;
3440 } else if (currentMove < forwardMostMove) {
3441 ForwardInner(forwardMostMove);
3444 /* I don't think this case really can happen */
3445 SendToICS(ics_prefix);
3446 SendToICS("refresh\n");
3451 /* Error messages */
3452 // if (ics_user_moved) {
3453 if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3454 if (looking_at(buf, &i, "Illegal move") ||
3455 looking_at(buf, &i, "Not a legal move") ||
3456 looking_at(buf, &i, "Your king is in check") ||
3457 looking_at(buf, &i, "It isn't your turn") ||
3458 looking_at(buf, &i, "It is not your move")) {
3460 if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3461 currentMove = forwardMostMove-1;
3462 DisplayMove(currentMove - 1); /* before DMError */
3463 DrawPosition(FALSE, boards[currentMove]);
3464 SwitchClocks(forwardMostMove-1); // [HGM] race
3465 DisplayBothClocks();
3467 DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3473 if (looking_at(buf, &i, "still have time") ||
3474 looking_at(buf, &i, "not out of time") ||
3475 looking_at(buf, &i, "either player is out of time") ||
3476 looking_at(buf, &i, "has timeseal; checking")) {
3477 /* We must have called his flag a little too soon */
3478 whiteFlag = blackFlag = FALSE;
3482 if (looking_at(buf, &i, "added * seconds to") ||
3483 looking_at(buf, &i, "seconds were added to")) {
3484 /* Update the clocks */
3485 SendToICS(ics_prefix);
3486 SendToICS("refresh\n");
3490 if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3491 ics_clock_paused = TRUE;
3496 if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3497 ics_clock_paused = FALSE;
3502 /* Grab player ratings from the Creating: message.
3503 Note we have to check for the special case when
3504 the ICS inserts things like [white] or [black]. */
3505 if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3506 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3508 0 player 1 name (not necessarily white)
3510 2 empty, white, or black (IGNORED)
3511 3 player 2 name (not necessarily black)
3514 The names/ratings are sorted out when the game
3515 actually starts (below).
3517 safeStrCpy(player1Name, StripHighlightAndTitle(star_match[0]), sizeof(player1Name)/sizeof(player1Name[0]));
3518 player1Rating = string_to_rating(star_match[1]);
3519 safeStrCpy(player2Name, StripHighlightAndTitle(star_match[3]), sizeof(player2Name)/sizeof(player2Name[0]));
3520 player2Rating = string_to_rating(star_match[4]);
3522 if (appData.debugMode)
3524 "Ratings from 'Creating:' %s %d, %s %d\n",
3525 player1Name, player1Rating,
3526 player2Name, player2Rating);
3531 /* Improved generic start/end-of-game messages */
3532 if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3533 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3534 /* If tkind == 0: */
3535 /* star_match[0] is the game number */
3536 /* [1] is the white player's name */
3537 /* [2] is the black player's name */
3538 /* For end-of-game: */
3539 /* [3] is the reason for the game end */
3540 /* [4] is a PGN end game-token, preceded by " " */
3541 /* For start-of-game: */
3542 /* [3] begins with "Creating" or "Continuing" */
3543 /* [4] is " *" or empty (don't care). */
3544 int gamenum = atoi(star_match[0]);
3545 char *whitename, *blackname, *why, *endtoken;
3546 ChessMove endtype = EndOfFile;
3549 whitename = star_match[1];
3550 blackname = star_match[2];
3551 why = star_match[3];
3552 endtoken = star_match[4];
3554 whitename = star_match[1];
3555 blackname = star_match[3];
3556 why = star_match[5];
3557 endtoken = star_match[6];
3560 /* Game start messages */
3561 if (strncmp(why, "Creating ", 9) == 0 ||
3562 strncmp(why, "Continuing ", 11) == 0) {
3563 gs_gamenum = gamenum;
3564 safeStrCpy(gs_kind, strchr(why, ' ') + 1,sizeof(gs_kind)/sizeof(gs_kind[0]));
3565 VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
3567 if (appData.zippyPlay) {
3568 ZippyGameStart(whitename, blackname);
3571 partnerBoardValid = FALSE; // [HGM] bughouse
3575 /* Game end messages */
3576 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3577 ics_gamenum != gamenum) {
3580 while (endtoken[0] == ' ') endtoken++;
3581 switch (endtoken[0]) {
3584 endtype = GameUnfinished;
3587 endtype = BlackWins;
3590 if (endtoken[1] == '/')
3591 endtype = GameIsDrawn;
3593 endtype = WhiteWins;
3596 GameEnds(endtype, why, GE_ICS);
3598 if (appData.zippyPlay && first.initDone) {
3599 ZippyGameEnd(endtype, why);
3600 if (first.pr == NULL) {
3601 /* Start the next process early so that we'll
3602 be ready for the next challenge */
3603 StartChessProgram(&first);
3605 /* Send "new" early, in case this command takes
3606 a long time to finish, so that we'll be ready
3607 for the next challenge. */
3608 gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3612 if(appData.bgObserve && partnerBoardValid) DrawPosition(TRUE, partnerBoard);
3616 if (looking_at(buf, &i, "Removing game * from observation") ||
3617 looking_at(buf, &i, "no longer observing game *") ||
3618 looking_at(buf, &i, "Game * (*) has no examiners")) {
3619 if (gameMode == IcsObserving &&
3620 atoi(star_match[0]) == ics_gamenum)
3622 /* icsEngineAnalyze */
3623 if (appData.icsEngineAnalyze) {
3630 ics_user_moved = FALSE;
3635 if (looking_at(buf, &i, "no longer examining game *")) {
3636 if (gameMode == IcsExamining &&
3637 atoi(star_match[0]) == ics_gamenum)
3641 ics_user_moved = FALSE;
3646 /* Advance leftover_start past any newlines we find,
3647 so only partial lines can get reparsed */
3648 if (looking_at(buf, &i, "\n")) {
3649 prevColor = curColor;
3650 if (curColor != ColorNormal) {
3651 if (oldi > next_out) {
3652 SendToPlayer(&buf[next_out], oldi - next_out);
3655 Colorize(ColorNormal, FALSE);
3656 curColor = ColorNormal;
3658 if (started == STARTED_BOARD) {
3659 started = STARTED_NONE;
3660 parse[parse_pos] = NULLCHAR;
3661 ParseBoard12(parse);
3664 /* Send premove here */
3665 if (appData.premove) {
3667 if (currentMove == 0 &&
3668 gameMode == IcsPlayingWhite &&
3669 appData.premoveWhite) {
3670 snprintf(str, MSG_SIZ, "%s\n", appData.premoveWhiteText);
3671 if (appData.debugMode)
3672 fprintf(debugFP, "Sending premove:\n");
3674 } else if (currentMove == 1 &&
3675 gameMode == IcsPlayingBlack &&
3676 appData.premoveBlack) {
3677 snprintf(str, MSG_SIZ, "%s\n", appData.premoveBlackText);
3678 if (appData.debugMode)
3679 fprintf(debugFP, "Sending premove:\n");
3681 } else if (gotPremove) {
3683 ClearPremoveHighlights();
3684 if (appData.debugMode)
3685 fprintf(debugFP, "Sending premove:\n");
3686 UserMoveEvent(premoveFromX, premoveFromY,
3687 premoveToX, premoveToY,
3692 /* Usually suppress following prompt */
3693 if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
3694 while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
3695 if (looking_at(buf, &i, "*% ")) {
3696 savingComment = FALSE;
3701 } else if (started == STARTED_HOLDINGS) {
3703 char new_piece[MSG_SIZ];
3704 started = STARTED_NONE;
3705 parse[parse_pos] = NULLCHAR;
3706 if (appData.debugMode)
3707 fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
3708 parse, currentMove);
3709 if (sscanf(parse, " game %d", &gamenum) == 1) {
3710 if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game
3711 if (gameInfo.variant == VariantNormal) {
3712 /* [HGM] We seem to switch variant during a game!
3713 * Presumably no holdings were displayed, so we have
3714 * to move the position two files to the right to
3715 * create room for them!
3717 VariantClass newVariant;
3718 switch(gameInfo.boardWidth) { // base guess on board width
3719 case 9: newVariant = VariantShogi; break;
3720 case 10: newVariant = VariantGreat; break;
3721 default: newVariant = VariantCrazyhouse; break;
3723 VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3724 /* Get a move list just to see the header, which
3725 will tell us whether this is really bug or zh */
3726 if (ics_getting_history == H_FALSE) {
3727 ics_getting_history = H_REQUESTED;
3728 snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
3732 new_piece[0] = NULLCHAR;
3733 sscanf(parse, "game %d white [%s black [%s <- %s",
3734 &gamenum, white_holding, black_holding,
3736 white_holding[strlen(white_holding)-1] = NULLCHAR;
3737 black_holding[strlen(black_holding)-1] = NULLCHAR;
3738 /* [HGM] copy holdings to board holdings area */
3739 CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
3740 CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
3741 boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
3743 if (appData.zippyPlay && first.initDone) {
3744 ZippyHoldings(white_holding, black_holding,
3748 if (tinyLayout || smallLayout) {
3749 char wh[16], bh[16];
3750 PackHolding(wh, white_holding);
3751 PackHolding(bh, black_holding);
3752 snprintf(str, MSG_SIZ,"[%s-%s] %s-%s", wh, bh,
3753 gameInfo.white, gameInfo.black);
3755 snprintf(str, MSG_SIZ, "%s [%s] vs. %s [%s]",
3756 gameInfo.white, white_holding,
3757 gameInfo.black, black_holding);
3759 if(!partnerUp) // [HGM] bughouse: when peeking at partner game we already know what he captured...
3760 DrawPosition(FALSE, boards[currentMove]);
3762 } else if(appData.bgObserve) { // [HGM] bughouse: holdings of other game => background
3763 sscanf(parse, "game %d white [%s black [%s <- %s",
3764 &gamenum, white_holding, black_holding,
3766 white_holding[strlen(white_holding)-1] = NULLCHAR;
3767 black_holding[strlen(black_holding)-1] = NULLCHAR;
3768 /* [HGM] copy holdings to partner-board holdings area */
3769 CopyHoldings(partnerBoard, white_holding, WhitePawn);
3770 CopyHoldings(partnerBoard, black_holding, BlackPawn);
3771 if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual: always draw
3772 if(partnerUp) DrawPosition(FALSE, partnerBoard);
3773 if(twoBoards) { partnerUp = 0; flipView = !flipView; }
3776 /* Suppress following prompt */
3777 if (looking_at(buf, &i, "*% ")) {
3778 if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
3779 savingComment = FALSE;
3787 i++; /* skip unparsed character and loop back */
3790 if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
3791 // started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
3792 // SendToPlayer(&buf[next_out], i - next_out);
3793 started != STARTED_HOLDINGS && leftover_start > next_out) {
3794 SendToPlayer(&buf[next_out], leftover_start - next_out);
3798 leftover_len = buf_len - leftover_start;
3799 /* if buffer ends with something we couldn't parse,
3800 reparse it after appending the next read */
3802 } else if (count == 0) {
3803 RemoveInputSource(isr);
3804 DisplayFatalError(_("Connection closed by ICS"), 0, 0);
3806 DisplayFatalError(_("Error reading from ICS"), error, 1);
3811 /* Board style 12 looks like this:
3813 <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
3815 * The "<12> " is stripped before it gets to this routine. The two
3816 * trailing 0's (flip state and clock ticking) are later addition, and
3817 * some chess servers may not have them, or may have only the first.
3818 * Additional trailing fields may be added in the future.
3821 #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"
3823 #define RELATION_OBSERVING_PLAYED 0
3824 #define RELATION_OBSERVING_STATIC -2 /* examined, oldmoves, or smoves */
3825 #define RELATION_PLAYING_MYMOVE 1
3826 #define RELATION_PLAYING_NOTMYMOVE -1
3827 #define RELATION_EXAMINING 2
3828 #define RELATION_ISOLATED_BOARD -3
3829 #define RELATION_STARTING_POSITION -4 /* FICS only */
3832 ParseBoard12(string)
3835 GameMode newGameMode;
3836 int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
3837 int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
3838 int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
3839 char to_play, board_chars[200];
3840 char move_str[MSG_SIZ], str[MSG_SIZ], elapsed_time[MSG_SIZ];
3841 char black[32], white[32];
3843 int prevMove = currentMove;
3846 int fromX, fromY, toX, toY;
3848 int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
3849 char *bookHit = NULL; // [HGM] book
3850 Boolean weird = FALSE, reqFlag = FALSE;
3852 fromX = fromY = toX = toY = -1;
3856 if (appData.debugMode)
3857 fprintf(debugFP, _("Parsing board: %s\n"), string);
3859 move_str[0] = NULLCHAR;
3860 elapsed_time[0] = NULLCHAR;
3861 { /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
3863 while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
3864 if(string[i] == ' ') { ranks++; files = 0; }
3866 if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
3869 for(j = 0; j <i; j++) board_chars[j] = string[j];
3870 board_chars[i] = '\0';
3873 n = sscanf(string, PATTERN, &to_play, &double_push,
3874 &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
3875 &gamenum, white, black, &relation, &basetime, &increment,
3876 &white_stren, &black_stren, &white_time, &black_time,
3877 &moveNum, str, elapsed_time, move_str, &ics_flip,
3881 snprintf(str, MSG_SIZ, _("Failed to parse board string:\n\"%s\""), string);
3882 DisplayError(str, 0);
3886 /* Convert the move number to internal form */
3887 moveNum = (moveNum - 1) * 2;
3888 if (to_play == 'B') moveNum++;
3889 if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
3890 DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
3896 case RELATION_OBSERVING_PLAYED:
3897 case RELATION_OBSERVING_STATIC:
3898 if (gamenum == -1) {
3899 /* Old ICC buglet */
3900 relation = RELATION_OBSERVING_STATIC;
3902 newGameMode = IcsObserving;
3904 case RELATION_PLAYING_MYMOVE:
3905 case RELATION_PLAYING_NOTMYMOVE:
3907 ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
3908 IcsPlayingWhite : IcsPlayingBlack;
3910 case RELATION_EXAMINING:
3911 newGameMode = IcsExamining;
3913 case RELATION_ISOLATED_BOARD:
3915 /* Just display this board. If user was doing something else,
3916 we will forget about it until the next board comes. */
3917 newGameMode = IcsIdle;
3919 case RELATION_STARTING_POSITION:
3920 newGameMode = gameMode;
3924 if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
3925 && newGameMode == IcsObserving && gamenum != ics_gamenum && appData.bgObserve) {
3926 // [HGM] bughouse: don't act on alien boards while we play. Just parse the board and save it */
3928 for (k = 0; k < ranks; k++) {
3929 for (j = 0; j < files; j++)
3930 board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
3931 if(gameInfo.holdingsWidth > 1) {
3932 board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
3933 board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
3936 CopyBoard(partnerBoard, board);
3937 if(toSqr = strchr(str, '/')) { // extract highlights from long move
3938 partnerBoard[EP_STATUS-3] = toSqr[1] - AAA; // kludge: hide highlighting info in board
3939 partnerBoard[EP_STATUS-4] = toSqr[2] - ONE;
3940 } else partnerBoard[EP_STATUS-4] = partnerBoard[EP_STATUS-3] = -1;
3941 if(toSqr = strchr(str, '-')) {
3942 partnerBoard[EP_STATUS-1] = toSqr[1] - AAA;
3943 partnerBoard[EP_STATUS-2] = toSqr[2] - ONE;
3944 } else partnerBoard[EP_STATUS-1] = partnerBoard[EP_STATUS-2] = -1;
3945 if(appData.dualBoard && !twoBoards) { twoBoards = 1; InitDrawingSizes(-2,0); }
3946 if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual
3947 if(partnerUp) DrawPosition(FALSE, partnerBoard);
3948 if(twoBoards) { partnerUp = 0; flipView = !flipView; } // [HGM] dual
3949 snprintf(partnerStatus, MSG_SIZ,"W: %d:%02d B: %d:%02d (%d-%d) %c", white_time/60000, (white_time%60000)/1000,
3950 (black_time/60000), (black_time%60000)/1000, white_stren, black_stren, to_play);
3951 DisplayMessage(partnerStatus, "");
3952 partnerBoardValid = TRUE;
3956 /* Modify behavior for initial board display on move listing
3959 switch (ics_getting_history) {
3963 case H_GOT_REQ_HEADER:
3964 case H_GOT_UNREQ_HEADER:
3965 /* This is the initial position of the current game */
3966 gamenum = ics_gamenum;
3967 moveNum = 0; /* old ICS bug workaround */
3968 if (to_play == 'B') {
3969 startedFromSetupPosition = TRUE;
3970 blackPlaysFirst = TRUE;
3972 if (forwardMostMove == 0) forwardMostMove = 1;
3973 if (backwardMostMove == 0) backwardMostMove = 1;
3974 if (currentMove == 0) currentMove = 1;
3976 newGameMode = gameMode;
3977 relation = RELATION_STARTING_POSITION; /* ICC needs this */
3979 case H_GOT_UNWANTED_HEADER:
3980 /* This is an initial board that we don't want */
3982 case H_GETTING_MOVES:
3983 /* Should not happen */
3984 DisplayError(_("Error gathering move list: extra board"), 0);
3985 ics_getting_history = H_FALSE;
3989 if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
3990 weird && (int)gameInfo.variant < (int)VariantShogi) {
3991 /* [HGM] We seem to have switched variant unexpectedly
3992 * Try to guess new variant from board size
3994 VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
3995 if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
3996 if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
3997 if(ranks == 8 && files == 12) newVariant = VariantCourier; else
3998 if(ranks == 9 && files == 9) newVariant = VariantShogi; else
3999 if(!weird) newVariant = VariantNormal;
4000 VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4001 /* Get a move list just to see the header, which
4002 will tell us whether this is really bug or zh */
4003 if (ics_getting_history == H_FALSE) {
4004 ics_getting_history = H_REQUESTED; reqFlag = TRUE;
4005 snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4010 /* Take action if this is the first board of a new game, or of a
4011 different game than is currently being displayed. */
4012 if (gamenum != ics_gamenum || newGameMode != gameMode ||
4013 relation == RELATION_ISOLATED_BOARD) {
4015 /* Forget the old game and get the history (if any) of the new one */
4016 if (gameMode != BeginningOfGame) {
4020 if (appData.autoRaiseBoard) BoardToTop();
4022 if (gamenum == -1) {
4023 newGameMode = IcsIdle;
4024 } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
4025 appData.getMoveList && !reqFlag) {
4026 /* Need to get game history */
4027 ics_getting_history = H_REQUESTED;
4028 snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4032 /* Initially flip the board to have black on the bottom if playing
4033 black or if the ICS flip flag is set, but let the user change
4034 it with the Flip View button. */
4035 flipView = appData.autoFlipView ?
4036 (newGameMode == IcsPlayingBlack) || ics_flip :
4039 /* Done with values from previous mode; copy in new ones */
4040 gameMode = newGameMode;
4042 ics_gamenum = gamenum;
4043 if (gamenum == gs_gamenum) {
4044 int klen = strlen(gs_kind);
4045 if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
4046 snprintf(str, MSG_SIZ, "ICS %s", gs_kind);
4047 gameInfo.event = StrSave(str);
4049 gameInfo.event = StrSave("ICS game");
4051 gameInfo.site = StrSave(appData.icsHost);
4052 gameInfo.date = PGNDate();
4053 gameInfo.round = StrSave("-");
4054 gameInfo.white = StrSave(white);
4055 gameInfo.black = StrSave(black);
4056 timeControl = basetime * 60 * 1000;
4058 timeIncrement = increment * 1000;
4059 movesPerSession = 0;
4060 gameInfo.timeControl = TimeControlTagValue();
4061 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
4062 if (appData.debugMode) {
4063 fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
4064 fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
4065 setbuf(debugFP, NULL);
4068 gameInfo.outOfBook = NULL;
4070 /* Do we have the ratings? */
4071 if (strcmp(player1Name, white) == 0 &&
4072 strcmp(player2Name, black) == 0) {
4073 if (appData.debugMode)
4074 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4075 player1Rating, player2Rating);
4076 gameInfo.whiteRating = player1Rating;
4077 gameInfo.blackRating = player2Rating;
4078 } else if (strcmp(player2Name, white) == 0 &&
4079 strcmp(player1Name, black) == 0) {
4080 if (appData.debugMode)
4081 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4082 player2Rating, player1Rating);
4083 gameInfo.whiteRating = player2Rating;
4084 gameInfo.blackRating = player1Rating;
4086 player1Name[0] = player2Name[0] = NULLCHAR;
4088 /* Silence shouts if requested */
4089 if (appData.quietPlay &&
4090 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
4091 SendToICS(ics_prefix);
4092 SendToICS("set shout 0\n");
4096 /* Deal with midgame name changes */
4098 if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
4099 if (gameInfo.white) free(gameInfo.white);
4100 gameInfo.white = StrSave(white);
4102 if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
4103 if (gameInfo.black) free(gameInfo.black);
4104 gameInfo.black = StrSave(black);
4108 /* Throw away game result if anything actually changes in examine mode */
4109 if (gameMode == IcsExamining && !newGame) {
4110 gameInfo.result = GameUnfinished;
4111 if (gameInfo.resultDetails != NULL) {
4112 free(gameInfo.resultDetails);
4113 gameInfo.resultDetails = NULL;
4117 /* In pausing && IcsExamining mode, we ignore boards coming
4118 in if they are in a different variation than we are. */
4119 if (pauseExamInvalid) return;
4120 if (pausing && gameMode == IcsExamining) {
4121 if (moveNum <= pauseExamForwardMostMove) {
4122 pauseExamInvalid = TRUE;
4123 forwardMostMove = pauseExamForwardMostMove;
4128 if (appData.debugMode) {
4129 fprintf(debugFP, "load %dx%d board\n", files, ranks);
4131 /* Parse the board */
4132 for (k = 0; k < ranks; k++) {
4133 for (j = 0; j < files; j++)
4134 board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4135 if(gameInfo.holdingsWidth > 1) {
4136 board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4137 board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4140 CopyBoard(boards[moveNum], board);
4141 boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
4143 startedFromSetupPosition =
4144 !CompareBoards(board, initialPosition);
4145 if(startedFromSetupPosition)
4146 initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
4149 /* [HGM] Set castling rights. Take the outermost Rooks,
4150 to make it also work for FRC opening positions. Note that board12
4151 is really defective for later FRC positions, as it has no way to
4152 indicate which Rook can castle if they are on the same side of King.
4153 For the initial position we grant rights to the outermost Rooks,
4154 and remember thos rights, and we then copy them on positions
4155 later in an FRC game. This means WB might not recognize castlings with
4156 Rooks that have moved back to their original position as illegal,
4157 but in ICS mode that is not its job anyway.
4159 if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
4160 { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
4162 for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4163 if(board[0][i] == WhiteRook) j = i;
4164 initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4165 for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4166 if(board[0][i] == WhiteRook) j = i;
4167 initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4168 for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4169 if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4170 initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4171 for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4172 if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4173 initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4175 if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
4176 for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4177 if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
4178 for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4179 if(board[BOARD_HEIGHT-1][k] == bKing)
4180 initialRights[5] = boards[moveNum][CASTLING][5] = k;
4181 if(gameInfo.variant == VariantTwoKings) {
4182 // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
4183 if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
4184 if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
4187 r = boards[moveNum][CASTLING][0] = initialRights[0];
4188 if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4189 r = boards[moveNum][CASTLING][1] = initialRights[1];
4190 if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4191 r = boards[moveNum][CASTLING][3] = initialRights[3];
4192 if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4193 r = boards[moveNum][CASTLING][4] = initialRights[4];
4194 if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4195 /* wildcastle kludge: always assume King has rights */
4196 r = boards[moveNum][CASTLING][2] = initialRights[2];
4197 r = boards[moveNum][CASTLING][5] = initialRights[5];
4199 /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4200 boards[moveNum][EP_STATUS] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
4203 if (ics_getting_history == H_GOT_REQ_HEADER ||
4204 ics_getting_history == H_GOT_UNREQ_HEADER) {
4205 /* This was an initial position from a move list, not
4206 the current position */
4210 /* Update currentMove and known move number limits */
4211 newMove = newGame || moveNum > forwardMostMove;
4214 forwardMostMove = backwardMostMove = currentMove = moveNum;
4215 if (gameMode == IcsExamining && moveNum == 0) {
4216 /* Workaround for ICS limitation: we are not told the wild
4217 type when starting to examine a game. But if we ask for
4218 the move list, the move list header will tell us */
4219 ics_getting_history = H_REQUESTED;
4220 snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4223 } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4224 || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4226 /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4227 /* [HGM] applied this also to an engine that is silently watching */
4228 if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4229 (gameMode == IcsObserving || gameMode == IcsExamining) &&
4230 gameInfo.variant == currentlyInitializedVariant) {
4231 takeback = forwardMostMove - moveNum;
4232 for (i = 0; i < takeback; i++) {
4233 if (appData.debugMode) fprintf(debugFP, "take back move\n");
4234 SendToProgram("undo\n", &first);
4239 forwardMostMove = moveNum;
4240 if (!pausing || currentMove > forwardMostMove)
4241 currentMove = forwardMostMove;
4243 /* New part of history that is not contiguous with old part */
4244 if (pausing && gameMode == IcsExamining) {
4245 pauseExamInvalid = TRUE;
4246 forwardMostMove = pauseExamForwardMostMove;
4249 if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4251 if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4252 // [HGM] when we will receive the move list we now request, it will be
4253 // fed to the engine from the first move on. So if the engine is not
4254 // in the initial position now, bring it there.
4255 InitChessProgram(&first, 0);
4258 ics_getting_history = H_REQUESTED;
4259 snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4262 forwardMostMove = backwardMostMove = currentMove = moveNum;
4265 /* Update the clocks */
4266 if (strchr(elapsed_time, '.')) {
4268 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4269 timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4271 /* Time is in seconds */
4272 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4273 timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4278 if (appData.zippyPlay && newGame &&
4279 gameMode != IcsObserving && gameMode != IcsIdle &&
4280 gameMode != IcsExamining)
4281 ZippyFirstBoard(moveNum, basetime, increment);
4284 /* Put the move on the move list, first converting
4285 to canonical algebraic form. */
4287 if (appData.debugMode) {
4288 if (appData.debugMode) { int f = forwardMostMove;
4289 fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4290 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4291 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4293 fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4294 fprintf(debugFP, "moveNum = %d\n", moveNum);
4295 fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4296 setbuf(debugFP, NULL);
4298 if (moveNum <= backwardMostMove) {
4299 /* We don't know what the board looked like before
4301 safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4302 strcat(parseList[moveNum - 1], " ");
4303 strcat(parseList[moveNum - 1], elapsed_time);
4304 moveList[moveNum - 1][0] = NULLCHAR;
4305 } else if (strcmp(move_str, "none") == 0) {
4306 // [HGM] long SAN: swapped order; test for 'none' before parsing move
4307 /* Again, we don't know what the board looked like;
4308 this is really the start of the game. */
4309 parseList[moveNum - 1][0] = NULLCHAR;
4310 moveList[moveNum - 1][0] = NULLCHAR;
4311 backwardMostMove = moveNum;
4312 startedFromSetupPosition = TRUE;
4313 fromX = fromY = toX = toY = -1;
4315 // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
4316 // So we parse the long-algebraic move string in stead of the SAN move
4317 int valid; char buf[MSG_SIZ], *prom;
4319 if(gameInfo.variant == VariantShogi && !strchr(move_str, '=') && !strchr(move_str, '@'))
4320 strcat(move_str, "="); // if ICS does not say 'promote' on non-drop, we defer.
4321 // str looks something like "Q/a1-a2"; kill the slash
4323 snprintf(buf, MSG_SIZ,"%c%s", str[0], str+2);
4324 else safeStrCpy(buf, str, sizeof(buf)/sizeof(buf[0])); // might be castling
4325 if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
4326 strcat(buf, prom); // long move lacks promo specification!
4327 if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4328 if(appData.debugMode)
4329 fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4330 safeStrCpy(move_str, buf, MSG_SIZ);
4332 valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4333 &fromX, &fromY, &toX, &toY, &promoChar)
4334 || ParseOneMove(buf, moveNum - 1, &moveType,
4335 &fromX, &fromY, &toX, &toY, &promoChar);
4336 // end of long SAN patch
4338 (void) CoordsToAlgebraic(boards[moveNum - 1],
4339 PosFlags(moveNum - 1),
4340 fromY, fromX, toY, toX, promoChar,
4341 parseList[moveNum-1]);
4342 switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4348 if(gameInfo.variant != VariantShogi)
4349 strcat(parseList[moveNum - 1], "+");
4352 case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4353 strcat(parseList[moveNum - 1], "#");
4356 strcat(parseList[moveNum - 1], " ");
4357 strcat(parseList[moveNum - 1], elapsed_time);
4358 /* currentMoveString is set as a side-effect of ParseOneMove */
4359 if(gameInfo.variant == VariantShogi && currentMoveString[4]) currentMoveString[4] = '^';
4360 safeStrCpy(moveList[moveNum - 1], currentMoveString, sizeof(moveList[moveNum - 1])/sizeof(moveList[moveNum - 1][0]));
4361 strcat(moveList[moveNum - 1], "\n");
4363 if(gameInfo.holdingsWidth && !appData.disguise) // inherit info that ICS does not give from previous board
4364 for(k=0; k<ranks; k++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
4365 ChessSquare old, new = boards[moveNum][k][j];
4366 if(fromY == DROP_RANK && k==toY && j==toX) continue; // dropped pieces always stand for themselves
4367 old = (k==toY && j==toX) ? boards[moveNum-1][fromY][fromX] : boards[moveNum-1][k][j]; // trace back mover
4368 if(old == new) continue;
4369 if(old == PROMOTED new) boards[moveNum][k][j] = old; // prevent promoted pieces to revert to primordial ones
4370 else if(new == WhiteWazir || new == BlackWazir) {
4371 if(old < WhiteCannon || old >= BlackPawn && old < BlackCannon)
4372 boards[moveNum][k][j] = PROMOTED old; // choose correct type of Gold in promotion
4373 else boards[moveNum][k][j] = old; // preserve type of Gold
4374 } else if((old == WhitePawn || old == BlackPawn) && new != EmptySquare) // Pawn promotions (but not e.p.capture!)
4375 boards[moveNum][k][j] = PROMOTED new; // use non-primordial representation of chosen piece
4378 /* Move from ICS was illegal!? Punt. */
4379 if (appData.debugMode) {
4380 fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4381 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4383 safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4384 strcat(parseList[moveNum - 1], " ");
4385 strcat(parseList[moveNum - 1], elapsed_time);
4386 moveList[moveNum - 1][0] = NULLCHAR;
4387 fromX = fromY = toX = toY = -1;
4390 if (appData.debugMode) {
4391 fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4392 setbuf(debugFP, NULL);
4396 /* Send move to chess program (BEFORE animating it). */
4397 if (appData.zippyPlay && !newGame && newMove &&
4398 (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4400 if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4401 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4402 if (moveList[moveNum - 1][0] == NULLCHAR) {
4403 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"),
4405 DisplayError(str, 0);
4407 if (first.sendTime) {
4408 SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4410 bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4411 if (firstMove && !bookHit) {
4413 if (first.useColors) {
4414 SendToProgram(gameMode == IcsPlayingWhite ?
4416 "black\ngo\n", &first);
4418 SendToProgram("go\n", &first);
4420 first.maybeThinking = TRUE;
4423 } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4424 if (moveList[moveNum - 1][0] == NULLCHAR) {
4425 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"), move_str);
4426 DisplayError(str, 0);
4428 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4429 SendMoveToProgram(moveNum - 1, &first);
4436 if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4437 /* If move comes from a remote source, animate it. If it
4438 isn't remote, it will have already been animated. */
4439 if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4440 AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4442 if (!pausing && appData.highlightLastMove) {
4443 SetHighlights(fromX, fromY, toX, toY);
4447 /* Start the clocks */
4448 whiteFlag = blackFlag = FALSE;
4449 appData.clockMode = !(basetime == 0 && increment == 0);
4451 ics_clock_paused = TRUE;
4453 } else if (ticking == 1) {
4454 ics_clock_paused = FALSE;
4456 if (gameMode == IcsIdle ||
4457 relation == RELATION_OBSERVING_STATIC ||
4458 relation == RELATION_EXAMINING ||
4460 DisplayBothClocks();
4464 /* Display opponents and material strengths */
4465 if (gameInfo.variant != VariantBughouse &&
4466 gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4467 if (tinyLayout || smallLayout) {
4468 if(gameInfo.variant == VariantNormal)
4469 snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d}",
4470 gameInfo.white, white_stren, gameInfo.black, black_stren,
4471 basetime, increment);
4473 snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d w%d}",
4474 gameInfo.white, white_stren, gameInfo.black, black_stren,
4475 basetime, increment, (int) gameInfo.variant);
4477 if(gameInfo.variant == VariantNormal)
4478 snprintf(str, MSG_SIZ, "%s (%d) vs. %s (%d) {%d %d}",
4479 gameInfo.white, white_stren, gameInfo.black, black_stren,
4480 basetime, increment);
4482 snprintf(str, MSG_SIZ, "%s (%d) vs. %s (%d) {%d %d %s}",
4483 gameInfo.white, white_stren, gameInfo.black, black_stren,
4484 basetime, increment, VariantName(gameInfo.variant));
4487 if (appData.debugMode) {
4488 fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
4493 /* Display the board */
4494 if (!pausing && !appData.noGUI) {
4496 if (appData.premove)
4498 ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
4499 ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
4500 ClearPremoveHighlights();
4502 j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
4503 if(partnerUp) { flipView = originalFlip; partnerUp = FALSE; j = TRUE; } // [HGM] bughouse: restore view
4504 DrawPosition(j, boards[currentMove]);
4506 DisplayMove(moveNum - 1);
4507 if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4508 !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4509 (gameMode == IcsPlayingBlack) && (WhiteOnMove(moveNum)) ) ) {
4510 if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4514 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4516 if(bookHit) { // [HGM] book: simulate book reply
4517 static char bookMove[MSG_SIZ]; // a bit generous?
4519 programStats.nodes = programStats.depth = programStats.time =
4520 programStats.score = programStats.got_only_move = 0;
4521 sprintf(programStats.movelist, "%s (xbook)", bookHit);
4523 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
4524 strcat(bookMove, bookHit);
4525 HandleMachineMove(bookMove, &first);
4534 if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4535 ics_getting_history = H_REQUESTED;
4536 snprintf(buf, MSG_SIZ, "%smoves %d\n", ics_prefix, ics_gamenum);
4542 AnalysisPeriodicEvent(force)
4545 if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4546 && !force) || !appData.periodicUpdates)
4549 /* Send . command to Crafty to collect stats */
4550 SendToProgram(".\n", &first);
4552 /* Don't send another until we get a response (this makes
4553 us stop sending to old Crafty's which don't understand
4554 the "." command (sending illegal cmds resets node count & time,
4555 which looks bad)) */
4556 programStats.ok_to_send = 0;
4559 void ics_update_width(new_width)
4562 ics_printf("set width %d\n", new_width);
4566 SendMoveToProgram(moveNum, cps)
4568 ChessProgramState *cps;
4572 if (cps->useUsermove) {
4573 SendToProgram("usermove ", cps);
4577 if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4578 int len = space - parseList[moveNum];
4579 memcpy(buf, parseList[moveNum], len);
4581 buf[len] = NULLCHAR;
4583 snprintf(buf, MSG_SIZ,"%s\n", parseList[moveNum]);
4585 SendToProgram(buf, cps);
4587 if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4588 AlphaRank(moveList[moveNum], 4);
4589 SendToProgram(moveList[moveNum], cps);
4590 AlphaRank(moveList[moveNum], 4); // and back
4592 /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
4593 * the engine. It would be nice to have a better way to identify castle
4595 if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
4596 && cps->useOOCastle) {
4597 int fromX = moveList[moveNum][0] - AAA;
4598 int fromY = moveList[moveNum][1] - ONE;
4599 int toX = moveList[moveNum][2] - AAA;
4600 int toY = moveList[moveNum][3] - ONE;
4601 if((boards[moveNum][fromY][fromX] == WhiteKing
4602 && boards[moveNum][toY][toX] == WhiteRook)
4603 || (boards[moveNum][fromY][fromX] == BlackKing
4604 && boards[moveNum][toY][toX] == BlackRook)) {
4605 if(toX > fromX) SendToProgram("O-O\n", cps);
4606 else SendToProgram("O-O-O\n", cps);
4608 else SendToProgram(moveList[moveNum], cps);
4610 else SendToProgram(moveList[moveNum], cps);
4611 /* End of additions by Tord */
4614 /* [HGM] setting up the opening has brought engine in force mode! */
4615 /* Send 'go' if we are in a mode where machine should play. */
4616 if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4617 (gameMode == TwoMachinesPlay ||
4619 gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite ||
4621 gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4622 SendToProgram("go\n", cps);
4623 if (appData.debugMode) {
4624 fprintf(debugFP, "(extra)\n");
4627 setboardSpoiledMachineBlack = 0;
4631 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar)
4633 int fromX, fromY, toX, toY;
4636 char user_move[MSG_SIZ];
4640 snprintf(user_move, MSG_SIZ, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4641 (int)moveType, fromX, fromY, toX, toY);
4642 DisplayError(user_move + strlen("say "), 0);
4644 case WhiteKingSideCastle:
4645 case BlackKingSideCastle:
4646 case WhiteQueenSideCastleWild:
4647 case BlackQueenSideCastleWild:
4649 case WhiteHSideCastleFR:
4650 case BlackHSideCastleFR:
4652 snprintf(user_move, MSG_SIZ, "o-o\n");
4654 case WhiteQueenSideCastle:
4655 case BlackQueenSideCastle:
4656 case WhiteKingSideCastleWild:
4657 case BlackKingSideCastleWild:
4659 case WhiteASideCastleFR:
4660 case BlackASideCastleFR:
4662 snprintf(user_move, MSG_SIZ, "o-o-o\n");
4664 case WhiteNonPromotion:
4665 case BlackNonPromotion:
4666 sprintf(user_move, "%c%c%c%c==\n", AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4668 case WhitePromotion:
4669 case BlackPromotion:
4670 if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
4671 snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
4672 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4673 PieceToChar(WhiteFerz));
4674 else if(gameInfo.variant == VariantGreat)
4675 snprintf(user_move, MSG_SIZ,"%c%c%c%c=%c\n",
4676 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4677 PieceToChar(WhiteMan));
4679 snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
4680 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4686 snprintf(user_move, MSG_SIZ, "%c@%c%c\n",
4687 ToUpper(PieceToChar((ChessSquare) fromX)),
4688 AAA + toX, ONE + toY);
4690 case IllegalMove: /* could be a variant we don't quite understand */
4691 if(fromY == DROP_RANK) goto drop; // We need 'IllegalDrop' move type?
4693 case WhiteCapturesEnPassant:
4694 case BlackCapturesEnPassant:
4695 snprintf(user_move, MSG_SIZ,"%c%c%c%c\n",
4696 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4699 SendToICS(user_move);
4700 if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
4701 ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
4706 { // [HGM] upload: send entire stored game to ICS as long-algebraic moves.
4707 int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
4708 static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
4709 if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
4710 DisplayError("You cannot do this while you are playing or observing", 0);
4713 if(gameMode != IcsExamining) { // is this ever not the case?
4714 char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
4716 if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
4717 snprintf(command,MSG_SIZ, "match %s", ics_handle);
4718 } else { // on FICS we must first go to general examine mode
4719 safeStrCpy(command, "examine\nbsetup", sizeof(command)/sizeof(command[0])); // and specify variant within it with bsetups
4721 if(gameInfo.variant != VariantNormal) {
4722 // try figure out wild number, as xboard names are not always valid on ICS
4723 for(i=1; i<=36; i++) {
4724 snprintf(buf, MSG_SIZ, "wild/%d", i);
4725 if(StringToVariant(buf) == gameInfo.variant) break;
4727 if(i<=36 && ics_type == ICS_ICC) snprintf(buf, MSG_SIZ,"%s w%d\n", command, i);
4728 else if(i == 22) snprintf(buf,MSG_SIZ, "%s fr\n", command);
4729 else snprintf(buf, MSG_SIZ,"%s %s\n", command, VariantName(gameInfo.variant));
4730 } else snprintf(buf, MSG_SIZ,"%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
4731 SendToICS(ics_prefix);
4733 if(startedFromSetupPosition || backwardMostMove != 0) {
4734 fen = PositionToFEN(backwardMostMove, NULL);
4735 if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
4736 snprintf(buf, MSG_SIZ,"loadfen %s\n", fen);
4738 } else { // FICS: everything has to set by separate bsetup commands
4739 p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board
4740 snprintf(buf, MSG_SIZ,"bsetup fen %s\n", fen);
4742 if(!WhiteOnMove(backwardMostMove)) {
4743 SendToICS("bsetup tomove black\n");
4745 i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL);
4746 snprintf(buf, MSG_SIZ,"bsetup wcastle %s\n", castlingStrings[i]);
4748 i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
4749 snprintf(buf, MSG_SIZ, "bsetup bcastle %s\n", castlingStrings[i]);
4751 i = boards[backwardMostMove][EP_STATUS];
4752 if(i >= 0) { // set e.p.
4753 snprintf(buf, MSG_SIZ,"bsetup eppos %c\n", i+AAA);
4759 if(bsetup || ics_type != ICS_ICC && gameInfo.variant != VariantNormal)
4760 SendToICS("bsetup done\n"); // switch to normal examining.
4762 for(i = backwardMostMove; i<last; i++) {
4764 snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s\n", parseList[i]);
4767 SendToICS(ics_prefix);
4768 SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
4772 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
4777 if (rf == DROP_RANK) {
4778 sprintf(move, "%c@%c%c\n",
4779 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
4781 if (promoChar == 'x' || promoChar == NULLCHAR) {
4782 sprintf(move, "%c%c%c%c\n",
4783 AAA + ff, ONE + rf, AAA + ft, ONE + rt);
4785 sprintf(move, "%c%c%c%c%c\n",
4786 AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
4792 ProcessICSInitScript(f)
4797 while (fgets(buf, MSG_SIZ, f)) {
4798 SendToICSDelayed(buf,(long)appData.msLoginDelay);
4805 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
4807 AlphaRank(char *move, int n)
4809 // char *p = move, c; int x, y;
4811 if (appData.debugMode) {
4812 fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
4816 move[2]>='0' && move[2]<='9' &&
4817 move[3]>='a' && move[3]<='x' ) {
4819 move[2] = BOARD_RGHT -1 - (move[2]-'1') + AAA;
4820 move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4822 if(move[0]>='0' && move[0]<='9' &&
4823 move[1]>='a' && move[1]<='x' &&
4824 move[2]>='0' && move[2]<='9' &&
4825 move[3]>='a' && move[3]<='x' ) {
4826 /* input move, Shogi -> normal */
4827 move[0] = BOARD_RGHT -1 - (move[0]-'1') + AAA;
4828 move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
4829 move[2] = BOARD_RGHT -1 - (move[2]-'1') + AAA;
4830 move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4833 move[3]>='0' && move[3]<='9' &&
4834 move[2]>='a' && move[2]<='x' ) {
4836 move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4837 move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4840 move[0]>='a' && move[0]<='x' &&
4841 move[3]>='0' && move[3]<='9' &&
4842 move[2]>='a' && move[2]<='x' ) {
4843 /* output move, normal -> Shogi */
4844 move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
4845 move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
4846 move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4847 move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4848 if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
4850 if (appData.debugMode) {
4851 fprintf(debugFP, " out = '%s'\n", move);
4855 char yy_textstr[8000];
4857 /* Parser for moves from gnuchess, ICS, or user typein box */
4859 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
4862 ChessMove *moveType;
4863 int *fromX, *fromY, *toX, *toY;
4866 *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
4868 switch (*moveType) {
4869 case WhitePromotion:
4870 case BlackPromotion:
4871 case WhiteNonPromotion:
4872 case BlackNonPromotion:
4874 case WhiteCapturesEnPassant:
4875 case BlackCapturesEnPassant:
4876 case WhiteKingSideCastle:
4877 case WhiteQueenSideCastle:
4878 case BlackKingSideCastle:
4879 case BlackQueenSideCastle:
4880 case WhiteKingSideCastleWild:
4881 case WhiteQueenSideCastleWild:
4882 case BlackKingSideCastleWild:
4883 case BlackQueenSideCastleWild:
4884 /* Code added by Tord: */
4885 case WhiteHSideCastleFR:
4886 case WhiteASideCastleFR:
4887 case BlackHSideCastleFR:
4888 case BlackASideCastleFR:
4889 /* End of code added by Tord */
4890 case IllegalMove: /* bug or odd chess variant */
4891 *fromX = currentMoveString[0] - AAA;
4892 *fromY = currentMoveString[1] - ONE;
4893 *toX = currentMoveString[2] - AAA;
4894 *toY = currentMoveString[3] - ONE;
4895 *promoChar = currentMoveString[4];
4896 if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
4897 *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
4898 if (appData.debugMode) {
4899 fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
4901 *fromX = *fromY = *toX = *toY = 0;
4904 if (appData.testLegality) {
4905 return (*moveType != IllegalMove);
4907 return !(*fromX == *toX && *fromY == *toY) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
4908 WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
4913 *fromX = *moveType == WhiteDrop ?
4914 (int) CharToPiece(ToUpper(currentMoveString[0])) :
4915 (int) CharToPiece(ToLower(currentMoveString[0]));
4917 *toX = currentMoveString[2] - AAA;
4918 *toY = currentMoveString[3] - ONE;
4919 *promoChar = NULLCHAR;
4923 case ImpossibleMove:
4933 if (appData.debugMode) {
4934 fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
4937 *fromX = *fromY = *toX = *toY = 0;
4938 *promoChar = NULLCHAR;
4945 ParsePV(char *pv, Boolean storeComments)
4946 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
4947 int fromX, fromY, toX, toY; char promoChar;
4952 endPV = forwardMostMove;
4954 while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
4955 if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
4956 valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
4957 if(appData.debugMode){
4958 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);
4960 if(!valid && nr == 0 &&
4961 ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
4962 nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
4963 // Hande case where played move is different from leading PV move
4964 CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
4965 CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
4966 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
4967 if(!CompareBoards(boards[endPV], boards[endPV+2])) {
4968 endPV += 2; // if position different, keep this
4969 moveList[endPV-1][0] = fromX + AAA;
4970 moveList[endPV-1][1] = fromY + ONE;
4971 moveList[endPV-1][2] = toX + AAA;
4972 moveList[endPV-1][3] = toY + ONE;
4973 parseList[endPV-1][0] = NULLCHAR;
4974 safeStrCpy(moveList[endPV-2], "_0_0", sizeof(moveList[endPV-2])/sizeof(moveList[endPV-2][0])); // suppress premove highlight on takeback move
4977 pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
4978 if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
4979 if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
4980 if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
4981 valid++; // allow comments in PV
4985 if(endPV+1 > framePtr) break; // no space, truncate
4988 CopyBoard(boards[endPV], boards[endPV-1]);
4989 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
4990 moveList[endPV-1][0] = fromX + AAA;
4991 moveList[endPV-1][1] = fromY + ONE;
4992 moveList[endPV-1][2] = toX + AAA;
4993 moveList[endPV-1][3] = toY + ONE;
4995 CoordsToAlgebraic(boards[endPV - 1],
4996 PosFlags(endPV - 1),
4997 fromY, fromX, toY, toX, promoChar,
4998 parseList[endPV - 1]);
5000 parseList[endPV-1][0] = NULLCHAR;
5002 currentMove = endPV;
5003 if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5004 SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5005 moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5006 DrawPosition(TRUE, boards[currentMove]);
5009 static int lastX, lastY;
5012 LoadMultiPV(int x, int y, char *buf, int index, int *start, int *end)
5017 if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
5018 lastX = x; lastY = y;
5019 while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
5021 while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
5022 if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
5024 do{ while(buf[index] && buf[index] != '\n') index++;
5025 } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
5027 ParsePV(buf+startPV, FALSE);
5028 *start = startPV; *end = index-1;
5033 LoadPV(int x, int y)
5034 { // called on right mouse click to load PV
5035 int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
5036 lastX = x; lastY = y;
5037 ParsePV(lastPV[which], FALSE); // load the PV of the thinking engine in the boards array.
5044 if(endPV < 0) return;
5046 currentMove = forwardMostMove;
5047 ClearPremoveHighlights();
5048 DrawPosition(TRUE, boards[currentMove]);
5052 MovePV(int x, int y, int h)
5053 { // step through PV based on mouse coordinates (called on mouse move)
5054 int margin = h>>3, step = 0;
5056 if(endPV < 0) return;
5057 // we must somehow check if right button is still down (might be released off board!)
5058 if(y < margin && (abs(x - lastX) > 6 || abs(y - lastY) > 6)) step = 1; else
5059 if(y > h - margin && (abs(x - lastX) > 6 || abs(y - lastY) > 6)) step = -1; else
5060 if( y > lastY + 6 ) step = -1; else if(y < lastY - 6) step = 1;
5062 lastX = x; lastY = y;
5063 if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
5064 currentMove += step;
5065 if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5066 SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5067 moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5068 DrawPosition(FALSE, boards[currentMove]);
5072 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
5073 // All positions will have equal probability, but the current method will not provide a unique
5074 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
5080 int piecesLeft[(int)BlackPawn];
5081 int seed, nrOfShuffles;
5083 void GetPositionNumber()
5084 { // sets global variable seed
5087 seed = appData.defaultFrcPosition;
5088 if(seed < 0) { // randomize based on time for negative FRC position numbers
5089 for(i=0; i<50; i++) seed += random();
5090 seed = random() ^ random() >> 8 ^ random() << 8;
5091 if(seed<0) seed = -seed;
5095 int put(Board board, int pieceType, int rank, int n, int shade)
5096 // put the piece on the (n-1)-th empty squares of the given shade
5100 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5101 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5102 board[rank][i] = (ChessSquare) pieceType;
5103 squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5105 piecesLeft[pieceType]--;
5113 void AddOnePiece(Board board, int pieceType, int rank, int shade)
5114 // calculate where the next piece goes, (any empty square), and put it there
5118 i = seed % squaresLeft[shade];
5119 nrOfShuffles *= squaresLeft[shade];
5120 seed /= squaresLeft[shade];
5121 put(board, pieceType, rank, i, shade);
5124 void AddTwoPieces(Board board, int pieceType, int rank)
5125 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5127 int i, n=squaresLeft[ANY], j=n-1, k;
5129 k = n*(n-1)/2; // nr of possibilities, not counting permutations
5130 i = seed % k; // pick one
5133 while(i >= j) i -= j--;
5134 j = n - 1 - j; i += j;
5135 put(board, pieceType, rank, j, ANY);
5136 put(board, pieceType, rank, i, ANY);
5139 void SetUpShuffle(Board board, int number)
5143 GetPositionNumber(); nrOfShuffles = 1;
5145 squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5146 squaresLeft[ANY] = BOARD_RGHT - BOARD_LEFT;
5147 squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5149 for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5151 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5152 p = (int) board[0][i];
5153 if(p < (int) BlackPawn) piecesLeft[p] ++;
5154 board[0][i] = EmptySquare;
5157 if(PosFlags(0) & F_ALL_CASTLE_OK) {
5158 // shuffles restricted to allow normal castling put KRR first
5159 if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5160 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5161 else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5162 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5163 if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5164 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5165 if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5166 put(board, WhiteRook, 0, 0, ANY);
5167 // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5170 if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5171 // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5172 for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5173 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5174 while(piecesLeft[p] >= 2) {
5175 AddOnePiece(board, p, 0, LITE);
5176 AddOnePiece(board, p, 0, DARK);
5178 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5181 for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5182 // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5183 // but we leave King and Rooks for last, to possibly obey FRC restriction
5184 if(p == (int)WhiteRook) continue;
5185 while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5186 if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY); // add the odd piece
5189 // now everything is placed, except perhaps King (Unicorn) and Rooks
5191 if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5192 // Last King gets castling rights
5193 while(piecesLeft[(int)WhiteUnicorn]) {
5194 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5195 initialRights[2] = initialRights[5] = board[CASTLING][2] = board[CASTLING][5] = i;
5198 while(piecesLeft[(int)WhiteKing]) {
5199 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5200 initialRights[2] = initialRights[5] = board[CASTLING][2] = board[CASTLING][5] = i;
5205 while(piecesLeft[(int)WhiteKing]) AddOnePiece(board, WhiteKing, 0, ANY);
5206 while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
5209 // Only Rooks can be left; simply place them all
5210 while(piecesLeft[(int)WhiteRook]) {
5211 i = put(board, WhiteRook, 0, 0, ANY);
5212 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
5215 initialRights[1] = initialRights[4] = board[CASTLING][1] = board[CASTLING][4] = i;
5217 initialRights[0] = initialRights[3] = board[CASTLING][0] = board[CASTLING][3] = i;
5220 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
5221 board[BOARD_HEIGHT-1][i] = (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
5224 if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
5227 int SetCharTable( char *table, const char * map )
5228 /* [HGM] moved here from winboard.c because of its general usefulness */
5229 /* Basically a safe strcpy that uses the last character as King */
5231 int result = FALSE; int NrPieces;
5233 if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
5234 && NrPieces >= 12 && !(NrPieces&1)) {
5235 int i; /* [HGM] Accept even length from 12 to 34 */
5237 for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
5238 for( i=0; i<NrPieces/2-1; i++ ) {
5240 table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
5242 table[(int) WhiteKing] = map[NrPieces/2-1];
5243 table[(int) BlackKing] = map[NrPieces-1];
5251 void Prelude(Board board)
5252 { // [HGM] superchess: random selection of exo-pieces
5253 int i, j, k; ChessSquare p;
5254 static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
5256 GetPositionNumber(); // use FRC position number
5258 if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
5259 SetCharTable(pieceToChar, appData.pieceToCharTable);
5260 for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
5261 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
5264 j = seed%4; seed /= 4;
5265 p = board[0][BOARD_LEFT+j]; board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5266 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
5267 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
5268 j = seed%3 + (seed%3 >= j); seed /= 3;
5269 p = board[0][BOARD_LEFT+j]; board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5270 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
5271 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
5272 j = seed%3; seed /= 3;
5273 p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5274 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
5275 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
5276 j = seed%2 + (seed%2 >= j); seed /= 2;
5277 p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5278 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
5279 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
5280 j = seed%4; seed /= 4; put(board, exoPieces[3], 0, j, ANY);
5281 j = seed%3; seed /= 3; put(board, exoPieces[2], 0, j, ANY);
5282 j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
5283 put(board, exoPieces[0], 0, 0, ANY);
5284 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
5288 InitPosition(redraw)
5291 ChessSquare (* pieces)[BOARD_FILES];
5292 int i, j, pawnRow, overrule,
5293 oldx = gameInfo.boardWidth,
5294 oldy = gameInfo.boardHeight,
5295 oldh = gameInfo.holdingsWidth,
5296 oldv = gameInfo.variant;
5298 if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
5300 /* [AS] Initialize pv info list [HGM] and game status */
5302 for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
5303 pvInfoList[i].depth = 0;
5304 boards[i][EP_STATUS] = EP_NONE;
5305 for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
5308 initialRulePlies = 0; /* 50-move counter start */
5310 castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
5311 castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
5315 /* [HGM] logic here is completely changed. In stead of full positions */
5316 /* the initialized data only consist of the two backranks. The switch */
5317 /* selects which one we will use, which is than copied to the Board */
5318 /* initialPosition, which for the rest is initialized by Pawns and */
5319 /* empty squares. This initial position is then copied to boards[0], */
5320 /* possibly after shuffling, so that it remains available. */
5322 gameInfo.holdingsWidth = 0; /* default board sizes */
5323 gameInfo.boardWidth = 8;
5324 gameInfo.boardHeight = 8;
5325 gameInfo.holdingsSize = 0;
5326 nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
5327 for(i=0; i<BOARD_FILES-2; i++)
5328 initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
5329 initialPosition[EP_STATUS] = EP_NONE;
5330 SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
5331 if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
5332 SetCharTable(pieceNickName, appData.pieceNickNames);
5333 else SetCharTable(pieceNickName, "............");
5336 switch (gameInfo.variant) {
5337 case VariantFischeRandom:
5338 shuffleOpenings = TRUE;
5341 case VariantShatranj:
5342 pieces = ShatranjArray;
5343 nrCastlingRights = 0;
5344 SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
5347 pieces = makrukArray;
5348 nrCastlingRights = 0;
5349 startedFromSetupPosition = TRUE;
5350 SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
5352 case VariantTwoKings:
5353 pieces = twoKingsArray;
5355 case VariantCapaRandom:
5356 shuffleOpenings = TRUE;
5357 case VariantCapablanca:
5358 pieces = CapablancaArray;
5359 gameInfo.boardWidth = 10;
5360 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5363 pieces = GothicArray;
5364 gameInfo.boardWidth = 10;
5365 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5368 SetCharTable(pieceToChar, "PNBRQ..HEKpnbrq..hek");
5369 gameInfo.holdingsSize = 7;
5372 pieces = JanusArray;
5373 gameInfo.boardWidth = 10;
5374 SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
5375 nrCastlingRights = 6;
5376 initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5377 initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5378 initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
5379 initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5380 initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5381 initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
5384 pieces = FalconArray;
5385 gameInfo.boardWidth = 10;
5386 SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
5388 case VariantXiangqi:
5389 pieces = XiangqiArray;
5390 gameInfo.boardWidth = 9;
5391 gameInfo.boardHeight = 10;
5392 nrCastlingRights = 0;
5393 SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
5396 pieces = ShogiArray;
5397 gameInfo.boardWidth = 9;
5398 gameInfo.boardHeight = 9;
5399 gameInfo.holdingsSize = 7;
5400 nrCastlingRights = 0;
5401 SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
5403 case VariantCourier:
5404 pieces = CourierArray;
5405 gameInfo.boardWidth = 12;
5406 nrCastlingRights = 0;
5407 SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
5409 case VariantKnightmate:
5410 pieces = KnightmateArray;
5411 SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
5414 pieces = fairyArray;
5415 SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
5418 pieces = GreatArray;
5419 gameInfo.boardWidth = 10;
5420 SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
5421 gameInfo.holdingsSize = 8;
5425 SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
5426 gameInfo.holdingsSize = 8;
5427 startedFromSetupPosition = TRUE;
5429 case VariantCrazyhouse:
5430 case VariantBughouse:
5432 SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
5433 gameInfo.holdingsSize = 5;
5435 case VariantWildCastle:
5437 /* !!?shuffle with kings guaranteed to be on d or e file */
5438 shuffleOpenings = 1;
5440 case VariantNoCastle:
5442 nrCastlingRights = 0;
5443 /* !!?unconstrained back-rank shuffle */
5444 shuffleOpenings = 1;
5449 if(appData.NrFiles >= 0) {
5450 if(gameInfo.boardWidth != appData.NrFiles) overrule++;
5451 gameInfo.boardWidth = appData.NrFiles;
5453 if(appData.NrRanks >= 0) {
5454 gameInfo.boardHeight = appData.NrRanks;
5456 if(appData.holdingsSize >= 0) {
5457 i = appData.holdingsSize;
5458 if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
5459 gameInfo.holdingsSize = i;
5461 if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
5462 if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
5463 DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
5465 pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
5466 if(pawnRow < 1) pawnRow = 1;
5467 if(gameInfo.variant == VariantMakruk) pawnRow = 2;
5469 /* User pieceToChar list overrules defaults */
5470 if(appData.pieceToCharTable != NULL)
5471 SetCharTable(pieceToChar, appData.pieceToCharTable);
5473 for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
5475 if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
5476 s = (ChessSquare) 0; /* account holding counts in guard band */
5477 for( i=0; i<BOARD_HEIGHT; i++ )
5478 initialPosition[i][j] = s;
5480 if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
5481 initialPosition[0][j] = pieces[0][j-gameInfo.holdingsWidth];
5482 initialPosition[pawnRow][j] = WhitePawn;
5483 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = BlackPawn;
5484 if(gameInfo.variant == VariantXiangqi) {
5486 initialPosition[pawnRow][j] =
5487 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
5488 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
5489 initialPosition[2][j] = WhiteCannon;
5490 initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
5494 initialPosition[BOARD_HEIGHT-1][j] = pieces[1][j-gameInfo.holdingsWidth];
5496 if( (gameInfo.variant == VariantShogi) && !overrule ) {
5499 initialPosition[1][j] = WhiteBishop;
5500 initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
5502 initialPosition[1][j] = WhiteRook;
5503 initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
5506 if( nrCastlingRights == -1) {
5507 /* [HGM] Build normal castling rights (must be done after board sizing!) */
5508 /* This sets default castling rights from none to normal corners */
5509 /* Variants with other castling rights must set them themselves above */
5510 nrCastlingRights = 6;
5512 initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5513 initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5514 initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
5515 initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5516 initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5517 initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
5520 if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
5521 if(gameInfo.variant == VariantGreat) { // promotion commoners
5522 initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
5523 initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
5524 initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
5525 initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
5527 if( gameInfo.variant == VariantSChess ) {
5528 initialPosition[1][0] = BlackMarshall;
5529 initialPosition[2][0] = BlackAngel;
5530 initialPosition[6][BOARD_WIDTH-1] = WhiteMarshall;
5531 initialPosition[5][BOARD_WIDTH-1] = WhiteAngel;
5532 initialPosition[1][1] = initialPosition[2][1] =
5533 initialPosition[6][BOARD_WIDTH-2] = initialPosition[5][BOARD_WIDTH-2] = 1;
5535 if (appData.debugMode) {
5536 fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
5538 if(shuffleOpenings) {
5539 SetUpShuffle(initialPosition, appData.defaultFrcPosition);
5540 startedFromSetupPosition = TRUE;
5542 if(startedFromPositionFile) {
5543 /* [HGM] loadPos: use PositionFile for every new game */
5544 CopyBoard(initialPosition, filePosition);
5545 for(i=0; i<nrCastlingRights; i++)
5546 initialRights[i] = filePosition[CASTLING][i];
5547 startedFromSetupPosition = TRUE;
5550 CopyBoard(boards[0], initialPosition);
5552 if(oldx != gameInfo.boardWidth ||
5553 oldy != gameInfo.boardHeight ||
5554 oldh != gameInfo.holdingsWidth
5556 || oldv == VariantGothic || // For licensing popups
5557 gameInfo.variant == VariantGothic
5560 || oldv == VariantFalcon ||
5561 gameInfo.variant == VariantFalcon
5564 InitDrawingSizes(-2 ,0);
5567 DrawPosition(TRUE, boards[currentMove]);
5571 SendBoard(cps, moveNum)
5572 ChessProgramState *cps;
5575 char message[MSG_SIZ];
5577 if (cps->useSetboard) {
5578 char* fen = PositionToFEN(moveNum, cps->fenOverride);
5579 snprintf(message, MSG_SIZ,"setboard %s\n", fen);
5580 SendToProgram(message, cps);
5586 /* Kludge to set black to move, avoiding the troublesome and now
5587 * deprecated "black" command.
5589 if (!WhiteOnMove(moveNum)) // [HGM] but better a deprecated command than an illegal move...
5590 SendToProgram(boards[0][1][BOARD_LEFT] == WhitePawn ? "a2a3\n" : "black\n", cps);
5592 SendToProgram("edit\n", cps);
5593 SendToProgram("#\n", cps);
5594 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
5595 bp = &boards[moveNum][i][BOARD_LEFT];
5596 for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
5597 if ((int) *bp < (int) BlackPawn) {
5598 snprintf(message, MSG_SIZ, "%c%c%c\n", PieceToChar(*bp),
5600 if(message[0] == '+' || message[0] == '~') {
5601 snprintf(message, MSG_SIZ,"%c%c%c+\n",
5602 PieceToChar((ChessSquare)(DEMOTED *bp)),
5605 if(cps->alphaRank) { /* [HGM] shogi: translate coords */
5606 message[1] = BOARD_RGHT - 1 - j + '1';
5607 message[2] = BOARD_HEIGHT - 1 - i + 'a';
5609 SendToProgram(message, cps);
5614 SendToProgram("c\n", cps);
5615 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
5616 bp = &boards[moveNum][i][BOARD_LEFT];
5617 for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
5618 if (((int) *bp != (int) EmptySquare)
5619 && ((int) *bp >= (int) BlackPawn)) {
5620 snprintf(message,MSG_SIZ, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
5622 if(message[0] == '+' || message[0] == '~') {
5623 snprintf(message, MSG_SIZ,"%c%c%c+\n",
5624 PieceToChar((ChessSquare)(DEMOTED *bp)),
5627 if(cps->alphaRank) { /* [HGM] shogi: translate coords */
5628 message[1] = BOARD_RGHT - 1 - j + '1';
5629 message[2] = BOARD_HEIGHT - 1 - i + 'a';
5631 SendToProgram(message, cps);
5636 SendToProgram(".\n", cps);
5638 setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
5641 static int autoQueen; // [HGM] oneclick
5644 HasPromotionChoice(int fromX, int fromY, int toX, int toY, char *promoChoice)
5646 /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
5647 /* [HGM] add Shogi promotions */
5648 int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
5653 if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
5654 if(toX < BOARD_LEFT || toX >= BOARD_RGHT) return FALSE; // move into holdings
5656 if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
5657 !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
5660 piece = boards[currentMove][fromY][fromX];
5661 if(gameInfo.variant == VariantShogi) {
5662 promotionZoneSize = BOARD_HEIGHT/3;
5663 highestPromotingPiece = (int)WhiteFerz;
5664 } else if(gameInfo.variant == VariantMakruk) {
5665 promotionZoneSize = 3;
5668 // next weed out all moves that do not touch the promotion zone at all
5669 if((int)piece >= BlackPawn) {
5670 if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
5672 highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
5674 if( toY < BOARD_HEIGHT - promotionZoneSize &&
5675 fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
5678 if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
5680 // weed out mandatory Shogi promotions
5681 if(gameInfo.variant == VariantShogi) {
5682 if(piece >= BlackPawn) {
5683 if(toY == 0 && piece == BlackPawn ||
5684 toY == 0 && piece == BlackQueen ||
5685 toY <= 1 && piece == BlackKnight) {
5690 if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
5691 toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
5692 toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
5699 // weed out obviously illegal Pawn moves
5700 if(appData.testLegality && (piece == WhitePawn || piece == BlackPawn) ) {
5701 if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
5702 if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
5703 if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
5704 if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
5705 // note we are not allowed to test for valid (non-)capture, due to premove
5708 // we either have a choice what to promote to, or (in Shogi) whether to promote
5709 if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk) {
5710 *promoChoice = PieceToChar(BlackFerz); // no choice
5713 // no sense asking what we must promote to if it is going to explode...
5714 if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) {
5715 *promoChoice = PieceToChar(BlackQueen); // Queen as good as any
5718 if(autoQueen) { // predetermined
5719 if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantLosers)
5720 *promoChoice = PieceToChar(BlackKing); // in Suicide Q is the last thing we want
5721 else *promoChoice = PieceToChar(BlackQueen);
5725 // suppress promotion popup on illegal moves that are not premoves
5726 premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
5727 gameMode == IcsPlayingBlack && WhiteOnMove(currentMove);
5728 if(appData.testLegality && !premove) {
5729 moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5730 fromY, fromX, toY, toX, gameInfo.variant == VariantShogi ? '+' : NULLCHAR);
5731 if(moveType != WhitePromotion && moveType != BlackPromotion)
5739 InPalace(row, column)
5741 { /* [HGM] for Xiangqi */
5742 if( (row < 3 || row > BOARD_HEIGHT-4) &&
5743 column < (BOARD_WIDTH + 4)/2 &&
5744 column > (BOARD_WIDTH - 5)/2 ) return TRUE;
5749 PieceForSquare (x, y)
5753 if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
5756 return boards[currentMove][y][x];
5760 OKToStartUserMove(x, y)
5763 ChessSquare from_piece;
5766 if (matchMode) return FALSE;
5767 if (gameMode == EditPosition) return TRUE;
5769 if (x >= 0 && y >= 0)
5770 from_piece = boards[currentMove][y][x];
5772 from_piece = EmptySquare;
5774 if (from_piece == EmptySquare) return FALSE;
5776 white_piece = (int)from_piece >= (int)WhitePawn &&
5777 (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
5780 case PlayFromGameFile:
5782 case TwoMachinesPlay:
5790 case MachinePlaysWhite:
5791 case IcsPlayingBlack:
5792 if (appData.zippyPlay) return FALSE;
5794 DisplayMoveError(_("You are playing Black"));
5799 case MachinePlaysBlack:
5800 case IcsPlayingWhite:
5801 if (appData.zippyPlay) return FALSE;
5803 DisplayMoveError(_("You are playing White"));
5809 if (!white_piece && WhiteOnMove(currentMove)) {
5810 DisplayMoveError(_("It is White's turn"));
5813 if (white_piece && !WhiteOnMove(currentMove)) {
5814 DisplayMoveError(_("It is Black's turn"));
5817 if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
5818 /* Editing correspondence game history */
5819 /* Could disallow this or prompt for confirmation */
5824 case BeginningOfGame:
5825 if (appData.icsActive) return FALSE;
5826 if (!appData.noChessProgram) {
5828 DisplayMoveError(_("You are playing White"));
5835 if (!white_piece && WhiteOnMove(currentMove)) {
5836 DisplayMoveError(_("It is White's turn"));
5839 if (white_piece && !WhiteOnMove(currentMove)) {
5840 DisplayMoveError(_("It is Black's turn"));
5849 if (currentMove != forwardMostMove && gameMode != AnalyzeMode
5850 && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
5851 && gameMode != AnalyzeFile && gameMode != Training) {
5852 DisplayMoveError(_("Displayed position is not current"));
5859 OnlyMove(int *x, int *y, Boolean captures) {
5860 DisambiguateClosure cl;
5861 if (appData.zippyPlay) return FALSE;
5863 case MachinePlaysBlack:
5864 case IcsPlayingWhite:
5865 case BeginningOfGame:
5866 if(!WhiteOnMove(currentMove)) return FALSE;
5868 case MachinePlaysWhite:
5869 case IcsPlayingBlack:
5870 if(WhiteOnMove(currentMove)) return FALSE;
5877 cl.pieceIn = EmptySquare;
5882 cl.promoCharIn = NULLCHAR;
5883 Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
5884 if( cl.kind == NormalMove ||
5885 cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
5886 cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
5887 cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
5894 if(cl.kind != ImpossibleMove) return FALSE;
5895 cl.pieceIn = EmptySquare;
5900 cl.promoCharIn = NULLCHAR;
5901 Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
5902 if( cl.kind == NormalMove ||
5903 cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
5904 cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
5905 cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
5910 autoQueen = TRUE; // act as if autoQueen on when we click to-square
5916 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
5917 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
5918 int lastLoadGameUseList = FALSE;
5919 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
5920 ChessMove lastLoadGameStart = EndOfFile;
5923 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
5924 int fromX, fromY, toX, toY;
5928 ChessSquare pdown, pup;
5930 /* Check if the user is playing in turn. This is complicated because we
5931 let the user "pick up" a piece before it is his turn. So the piece he
5932 tried to pick up may have been captured by the time he puts it down!
5933 Therefore we use the color the user is supposed to be playing in this
5934 test, not the color of the piece that is currently on the starting
5935 square---except in EditGame mode, where the user is playing both
5936 sides; fortunately there the capture race can't happen. (It can
5937 now happen in IcsExamining mode, but that's just too bad. The user
5938 will get a somewhat confusing message in that case.)
5942 case PlayFromGameFile:
5944 case TwoMachinesPlay:
5948 /* We switched into a game mode where moves are not accepted,
5949 perhaps while the mouse button was down. */
5952 case MachinePlaysWhite:
5953 /* User is moving for Black */
5954 if (WhiteOnMove(currentMove)) {
5955 DisplayMoveError(_("It is White's turn"));
5960 case MachinePlaysBlack:
5961 /* User is moving for White */
5962 if (!WhiteOnMove(currentMove)) {
5963 DisplayMoveError(_("It is Black's turn"));
5970 case BeginningOfGame:
5973 if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
5974 (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
5975 /* User is moving for Black */
5976 if (WhiteOnMove(currentMove)) {
5977 DisplayMoveError(_("It is White's turn"));
5981 /* User is moving for White */
5982 if (!WhiteOnMove(currentMove)) {
5983 DisplayMoveError(_("It is Black's turn"));
5989 case IcsPlayingBlack:
5990 /* User is moving for Black */
5991 if (WhiteOnMove(currentMove)) {
5992 if (!appData.premove) {
5993 DisplayMoveError(_("It is White's turn"));
5994 } else if (toX >= 0 && toY >= 0) {
5997 premoveFromX = fromX;
5998 premoveFromY = fromY;
5999 premovePromoChar = promoChar;
6001 if (appData.debugMode)
6002 fprintf(debugFP, "Got premove: fromX %d,"
6003 "fromY %d, toX %d, toY %d\n",
6004 fromX, fromY, toX, toY);
6010 case IcsPlayingWhite:
6011 /* User is moving for White */
6012 if (!WhiteOnMove(currentMove)) {
6013 if (!appData.premove) {
6014 DisplayMoveError(_("It is Black's turn"));
6015 } else if (toX >= 0 && toY >= 0) {
6018 premoveFromX = fromX;
6019 premoveFromY = fromY;
6020 premovePromoChar = promoChar;
6022 if (appData.debugMode)
6023 fprintf(debugFP, "Got premove: fromX %d,"
6024 "fromY %d, toX %d, toY %d\n",
6025 fromX, fromY, toX, toY);
6035 /* EditPosition, empty square, or different color piece;
6036 click-click move is possible */
6037 if (toX == -2 || toY == -2) {
6038 boards[0][fromY][fromX] = EmptySquare;
6039 DrawPosition(FALSE, boards[currentMove]);
6041 } else if (toX >= 0 && toY >= 0) {
6042 boards[0][toY][toX] = boards[0][fromY][fromX];
6043 if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
6044 if(boards[0][fromY][0] != EmptySquare) {
6045 if(boards[0][fromY][1]) boards[0][fromY][1]--;
6046 if(boards[0][fromY][1] == 0) boards[0][fromY][0] = EmptySquare;
6049 if(fromX == BOARD_RGHT+1) {
6050 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
6051 if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
6052 if(boards[0][fromY][BOARD_WIDTH-2] == 0) boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
6055 boards[0][fromY][fromX] = EmptySquare;
6056 DrawPosition(FALSE, boards[currentMove]);
6062 if(toX < 0 || toY < 0) return;
6063 pdown = boards[currentMove][fromY][fromX];
6064 pup = boards[currentMove][toY][toX];
6066 /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
6067 if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) {
6068 if( pup != EmptySquare ) return;
6069 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
6070 if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n",
6071 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
6072 // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
6073 if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
6074 fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
6075 while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++;
6079 /* [HGM] always test for legality, to get promotion info */
6080 moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6081 fromY, fromX, toY, toX, promoChar);
6082 /* [HGM] but possibly ignore an IllegalMove result */
6083 if (appData.testLegality) {
6084 if (moveType == IllegalMove || moveType == ImpossibleMove) {
6085 DisplayMoveError(_("Illegal move"));
6090 FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
6093 /* Common tail of UserMoveEvent and DropMenuEvent */
6095 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
6097 int fromX, fromY, toX, toY;
6098 /*char*/int promoChar;
6102 if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) && promoChar != NULLCHAR) {
6103 // [HGM] superchess: suppress promotions to non-available piece
6104 int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
6105 if(WhiteOnMove(currentMove)) {
6106 if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
6108 if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
6112 /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
6113 move type in caller when we know the move is a legal promotion */
6114 if(moveType == NormalMove && promoChar)
6115 moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
6117 /* [HGM] <popupFix> The following if has been moved here from
6118 UserMoveEvent(). Because it seemed to belong here (why not allow
6119 piece drops in training games?), and because it can only be
6120 performed after it is known to what we promote. */
6121 if (gameMode == Training) {
6122 /* compare the move played on the board to the next move in the
6123 * game. If they match, display the move and the opponent's response.
6124 * If they don't match, display an error message.
6128 CopyBoard(testBoard, boards[currentMove]);
6129 ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
6131 if (CompareBoards(testBoard, boards[currentMove+1])) {
6132 ForwardInner(currentMove+1);
6134 /* Autoplay the opponent's response.
6135 * if appData.animate was TRUE when Training mode was entered,
6136 * the response will be animated.
6138 saveAnimate = appData.animate;
6139 appData.animate = animateTraining;
6140 ForwardInner(currentMove+1);
6141 appData.animate = saveAnimate;
6143 /* check for the end of the game */
6144 if (currentMove >= forwardMostMove) {
6145 gameMode = PlayFromGameFile;
6147 SetTrainingModeOff();
6148 DisplayInformation(_("End of game"));
6151 DisplayError(_("Incorrect move"), 0);
6156 /* Ok, now we know that the move is good, so we can kill
6157 the previous line in Analysis Mode */
6158 if ((gameMode == AnalyzeMode || gameMode == EditGame)
6159 && currentMove < forwardMostMove) {
6160 if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
6161 else forwardMostMove = currentMove;
6164 /* If we need the chess program but it's dead, restart it */
6165 ResurrectChessProgram();
6167 /* A user move restarts a paused game*/
6171 thinkOutput[0] = NULLCHAR;
6173 MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
6175 if(Adjudicate(NULL)) return 1; // [HGM] adjudicate: take care of automtic game end
6177 if (gameMode == BeginningOfGame) {
6178 if (appData.noChessProgram) {
6179 gameMode = EditGame;
6183 gameMode = MachinePlaysBlack;
6186 snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
6188 if (first.sendName) {
6189 snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
6190 SendToProgram(buf, &first);
6197 /* Relay move to ICS or chess engine */
6198 if (appData.icsActive) {
6199 if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
6200 gameMode == IcsExamining) {
6201 if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
6202 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
6204 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6206 // also send plain move, in case ICS does not understand atomic claims
6207 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6211 if (first.sendTime && (gameMode == BeginningOfGame ||
6212 gameMode == MachinePlaysWhite ||
6213 gameMode == MachinePlaysBlack)) {
6214 SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
6216 if (gameMode != EditGame && gameMode != PlayFromGameFile) {
6217 // [HGM] book: if program might be playing, let it use book
6218 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
6219 first.maybeThinking = TRUE;
6220 } else SendMoveToProgram(forwardMostMove-1, &first);
6221 if (currentMove == cmailOldMove + 1) {
6222 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
6226 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6230 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
6236 if (WhiteOnMove(currentMove)) {
6237 GameEnds(BlackWins, "Black mates", GE_PLAYER);
6239 GameEnds(WhiteWins, "White mates", GE_PLAYER);
6243 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
6248 case MachinePlaysBlack:
6249 case MachinePlaysWhite:
6250 /* disable certain menu options while machine is thinking */
6251 SetMachineThinkingEnables();
6258 userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
6260 if(bookHit) { // [HGM] book: simulate book reply
6261 static char bookMove[MSG_SIZ]; // a bit generous?
6263 programStats.nodes = programStats.depth = programStats.time =
6264 programStats.score = programStats.got_only_move = 0;
6265 sprintf(programStats.movelist, "%s (xbook)", bookHit);
6267 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
6268 strcat(bookMove, bookHit);
6269 HandleMachineMove(bookMove, &first);
6275 Mark(board, flags, kind, rf, ff, rt, ft, closure)
6282 typedef char Markers[BOARD_RANKS][BOARD_FILES];
6283 Markers *m = (Markers *) closure;
6284 if(rf == fromY && ff == fromX)
6285 (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
6286 || kind == WhiteCapturesEnPassant
6287 || kind == BlackCapturesEnPassant);
6288 else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
6292 MarkTargetSquares(int clear)
6295 if(!appData.markers || !appData.highlightDragging ||
6296 !appData.testLegality || gameMode == EditPosition) return;
6298 for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) marker[y][x] = 0;
6301 GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker);
6302 if(PosFlags(0) & F_MANDATORY_CAPTURE) {
6303 for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
6305 for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
6308 DrawPosition(TRUE, NULL);
6312 Explode(Board board, int fromX, int fromY, int toX, int toY)
6314 if(gameInfo.variant == VariantAtomic &&
6315 (board[toY][toX] != EmptySquare || // capture?
6316 toX != fromX && (board[fromY][fromX] == WhitePawn || // e.p. ?
6317 board[fromY][fromX] == BlackPawn )
6319 AnimateAtomicCapture(board, fromX, fromY, toX, toY);
6325 ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
6327 void LeftClick(ClickType clickType, int xPix, int yPix)
6330 Boolean saveAnimate;
6331 static int second = 0, promotionChoice = 0, dragging = 0;
6332 char promoChoice = NULLCHAR;
6334 if(appData.seekGraph && appData.icsActive && loggedOn &&
6335 (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
6336 SeekGraphClick(clickType, xPix, yPix, 0);
6340 if (clickType == Press) ErrorPopDown();
6341 MarkTargetSquares(1);
6343 x = EventToSquare(xPix, BOARD_WIDTH);
6344 y = EventToSquare(yPix, BOARD_HEIGHT);
6345 if (!flipView && y >= 0) {
6346 y = BOARD_HEIGHT - 1 - y;
6348 if (flipView && x >= 0) {
6349 x = BOARD_WIDTH - 1 - x;
6352 if(promotionChoice) { // we are waiting for a click to indicate promotion piece
6353 if(clickType == Release) return; // ignore upclick of click-click destination
6354 promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
6355 if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
6356 if(gameInfo.holdingsWidth &&
6357 (WhiteOnMove(currentMove)
6358 ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y > 0
6359 : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT-1) ) {
6360 // click in right holdings, for determining promotion piece
6361 ChessSquare p = boards[currentMove][y][x];
6362 if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
6363 if(p != EmptySquare) {
6364 FinishMove(NormalMove, fromX, fromY, toX, toY, ToLower(PieceToChar(p)));
6369 DrawPosition(FALSE, boards[currentMove]);
6373 /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
6374 if(clickType == Press
6375 && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
6376 || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
6377 || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
6380 autoQueen = appData.alwaysPromoteToQueen;
6383 gatingPiece = EmptySquare;
6384 if (clickType != Press) {
6385 if(dragging) { // [HGM] from-square must have been reset due to game end since last press
6386 DragPieceEnd(xPix, yPix); dragging = 0;
6387 DrawPosition(FALSE, NULL);
6391 if(!appData.oneClick || !OnlyMove(&x, &y, FALSE)) {
6393 if (OKToStartUserMove(x, y)) {
6397 MarkTargetSquares(0);
6398 DragPieceBegin(xPix, yPix); dragging = 1;
6399 if (appData.highlightDragging) {
6400 SetHighlights(x, y, -1, -1);
6408 if (clickType == Press && gameMode != EditPosition) {
6413 // ignore off-board to clicks
6414 if(y < 0 || x < 0) return;
6416 /* Check if clicking again on the same color piece */
6417 fromP = boards[currentMove][fromY][fromX];
6418 toP = boards[currentMove][y][x];
6419 frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess;
6420 if ((WhitePawn <= fromP && fromP <= WhiteKing &&
6421 WhitePawn <= toP && toP <= WhiteKing &&
6422 !(fromP == WhiteKing && toP == WhiteRook && frc) &&
6423 !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
6424 (BlackPawn <= fromP && fromP <= BlackKing &&
6425 BlackPawn <= toP && toP <= BlackKing &&
6426 !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
6427 !(fromP == BlackKing && toP == BlackRook && frc))) {
6428 /* Clicked again on same color piece -- changed his mind */
6429 second = (x == fromX && y == fromY);
6430 if(!second || appData.oneClick && !OnlyMove(&x, &y, TRUE)) {
6431 if (appData.highlightDragging) {
6432 SetHighlights(x, y, -1, -1);
6436 if (OKToStartUserMove(x, y)) {
6437 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
6438 (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
6439 y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
6440 gatingPiece = boards[currentMove][fromY][fromX];
6441 else gatingPiece = EmptySquare;
6443 fromY = y; dragging = 1;
6444 MarkTargetSquares(0);
6445 DragPieceBegin(xPix, yPix);
6448 if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
6451 // ignore clicks on holdings
6452 if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
6455 if (clickType == Release && x == fromX && y == fromY) {
6456 DragPieceEnd(xPix, yPix); dragging = 0;
6457 if (appData.animateDragging) {
6458 /* Undo animation damage if any */
6459 DrawPosition(FALSE, NULL);
6462 /* Second up/down in same square; just abort move */
6465 gatingPiece = EmptySquare;
6468 ClearPremoveHighlights();
6470 /* First upclick in same square; start click-click mode */
6471 SetHighlights(x, y, -1, -1);
6476 /* we now have a different from- and (possibly off-board) to-square */
6477 /* Completed move */
6480 saveAnimate = appData.animate;
6481 if (clickType == Press) {
6482 /* Finish clickclick move */
6483 if (appData.animate || appData.highlightLastMove) {
6484 SetHighlights(fromX, fromY, toX, toY);
6489 /* Finish drag move */
6490 if (appData.highlightLastMove) {
6491 SetHighlights(fromX, fromY, toX, toY);
6495 DragPieceEnd(xPix, yPix); dragging = 0;
6496 /* Don't animate move and drag both */
6497 appData.animate = FALSE;
6500 // moves into holding are invalid for now (except in EditPosition, adapting to-square)
6501 if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
6502 ChessSquare piece = boards[currentMove][fromY][fromX];
6503 if(gameMode == EditPosition && piece != EmptySquare &&
6504 fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
6507 if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
6508 n = PieceToNumber(piece - (int)BlackPawn);
6509 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
6510 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
6511 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
6513 if(x == BOARD_RGHT+1 && piece < BlackPawn) {
6514 n = PieceToNumber(piece);
6515 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
6516 boards[currentMove][n][BOARD_WIDTH-1] = piece;
6517 boards[currentMove][n][BOARD_WIDTH-2]++;
6519 boards[currentMove][fromY][fromX] = EmptySquare;
6523 DrawPosition(TRUE, boards[currentMove]);
6527 // off-board moves should not be highlighted
6528 if(x < 0 || y < 0) ClearHighlights();
6530 if(gatingPiece != EmptySquare) promoChoice = ToLower(PieceToChar(gatingPiece));
6532 if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice)) {
6533 SetHighlights(fromX, fromY, toX, toY);
6534 if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
6535 // [HGM] super: promotion to captured piece selected from holdings
6536 ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
6537 promotionChoice = TRUE;
6538 // kludge follows to temporarily execute move on display, without promoting yet
6539 boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
6540 boards[currentMove][toY][toX] = p;
6541 DrawPosition(FALSE, boards[currentMove]);
6542 boards[currentMove][fromY][fromX] = p; // take back, but display stays
6543 boards[currentMove][toY][toX] = q;
6544 DisplayMessage("Click in holdings to choose piece", "");
6549 int oldMove = currentMove;
6550 UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
6551 if (!appData.highlightLastMove || gotPremove) ClearHighlights();
6552 if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
6553 if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
6554 Explode(boards[currentMove-1], fromX, fromY, toX, toY))
6555 DrawPosition(TRUE, boards[currentMove]);
6558 appData.animate = saveAnimate;
6559 if (appData.animate || appData.animateDragging) {
6560 /* Undo animation damage if needed */
6561 DrawPosition(FALSE, NULL);
6565 int RightClick(ClickType action, int x, int y, int *fromX, int *fromY)
6566 { // front-end-free part taken out of PieceMenuPopup
6567 int whichMenu; int xSqr, ySqr;
6569 if(seekGraphUp) { // [HGM] seekgraph
6570 if(action == Press) SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
6571 if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
6575 if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
6576 && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
6577 if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
6578 if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
6579 if(action == Press) {
6580 originalFlip = flipView;
6581 flipView = !flipView; // temporarily flip board to see game from partners perspective
6582 DrawPosition(TRUE, partnerBoard);
6583 DisplayMessage(partnerStatus, "");
6585 } else if(action == Release) {
6586 flipView = originalFlip;
6587 DrawPosition(TRUE, boards[currentMove]);
6593 xSqr = EventToSquare(x, BOARD_WIDTH);
6594 ySqr = EventToSquare(y, BOARD_HEIGHT);
6595 if (action == Release) UnLoadPV(); // [HGM] pv
6596 if (action != Press) return -2; // return code to be ignored
6599 if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;
\r
6601 if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;
\r
6602 if (xSqr < 0 || ySqr < 0) return -1;
\r
6603 whichMenu = 0; // edit-position menu
6606 if(!appData.icsEngineAnalyze) return -1;
6607 case IcsPlayingWhite:
6608 case IcsPlayingBlack:
6609 if(!appData.zippyPlay) goto noZip;
6612 case MachinePlaysWhite:
6613 case MachinePlaysBlack:
6614 case TwoMachinesPlay: // [HGM] pv: use for showing PV
6615 if (!appData.dropMenu) {
6617 return 2; // flag front-end to grab mouse events
6619 if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
6620 gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
6623 if (xSqr < 0 || ySqr < 0) return -1;
6624 if (!appData.dropMenu || appData.testLegality &&
6625 gameInfo.variant != VariantBughouse &&
6626 gameInfo.variant != VariantCrazyhouse) return -1;
6627 whichMenu = 1; // drop menu
6633 if (((*fromX = xSqr) < 0) ||
6634 ((*fromY = ySqr) < 0)) {
6635 *fromX = *fromY = -1;
6639 *fromX = BOARD_WIDTH - 1 - *fromX;
6641 *fromY = BOARD_HEIGHT - 1 - *fromY;
6646 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
6648 // char * hint = lastHint;
6649 FrontEndProgramStats stats;
6651 stats.which = cps == &first ? 0 : 1;
6652 stats.depth = cpstats->depth;
6653 stats.nodes = cpstats->nodes;
6654 stats.score = cpstats->score;
6655 stats.time = cpstats->time;
6656 stats.pv = cpstats->movelist;
6657 stats.hint = lastHint;
6658 stats.an_move_index = 0;
6659 stats.an_move_count = 0;
6661 if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
6662 stats.hint = cpstats->move_name;
6663 stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
6664 stats.an_move_count = cpstats->nr_moves;
6667 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
6669 SetProgramStats( &stats );
6673 Count(Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
6674 { // count all piece types
6676 *nB = *nW = *wStale = *bStale = *bishopColor = 0;
6677 for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
6678 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
6681 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
6682 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
6683 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
6684 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
6685 p == BlackBishop || p == BlackFerz || p == BlackAlfil )
6686 *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
6691 SufficientDefence(int pCnt[], int side, int nMine, int nHis)
6693 int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
6694 int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
6696 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
6697 if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
6698 if(myPawns == 2 && nMine == 3) // KPP
6699 return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
6700 if(myPawns == 1 && nMine == 2) // KP
6701 return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
6702 if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
6703 return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
6704 if(myPawns) return FALSE;
6705 if(pCnt[WhiteRook+side])
6706 return pCnt[BlackRook-side] ||
6707 pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
6708 pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
6709 pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
6710 if(pCnt[WhiteCannon+side]) {
6711 if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
6712 return majorDefense || pCnt[BlackAlfil-side] >= 2;
6714 if(pCnt[WhiteKnight+side])
6715 return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
6720 MatingPotential(int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
6722 VariantClass v = gameInfo.variant;
6724 if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
6725 if(v == VariantShatranj) return TRUE; // always winnable through baring
6726 if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
6727 if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
6729 if(v == VariantXiangqi) {
6730 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
6732 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
6733 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
6734 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
6735 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
6736 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
6737 if(stale) // we have at least one last-rank P plus perhaps C
6738 return majors // KPKX
6739 || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
6741 return pCnt[WhiteFerz+side] // KCAK
6742 || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
6743 || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
6744 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
6746 } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
6747 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
6749 if(nMine == 1) return FALSE; // bare King
6750 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
6751 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
6752 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
6753 // by now we have King + 1 piece (or multiple Bishops on the same color)
6754 if(pCnt[WhiteKnight+side])
6755 return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
6756 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
6757 || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
6759 return (pCnt[BlackKnight-side]); // KBKN, KFKN
6760 if(pCnt[WhiteAlfil+side])
6761 return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
6762 if(pCnt[WhiteWazir+side])
6763 return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
6770 Adjudicate(ChessProgramState *cps)
6771 { // [HGM] some adjudications useful with buggy engines
6772 // [HGM] adjudicate: made into separate routine, which now can be called after every move
6773 // In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
6774 // Actually ending the game is now based on the additional internal condition canAdjudicate.
6775 // Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
6776 int k, count = 0; static int bare = 1;
6777 ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
6778 Boolean canAdjudicate = !appData.icsActive;
6780 // most tests only when we understand the game, i.e. legality-checking on
6781 if( appData.testLegality )
6782 { /* [HGM] Some more adjudications for obstinate engines */
6783 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+1], i;
6784 static int moveCount = 6;
6786 char *reason = NULL;
6788 /* Count what is on board. */
6789 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
6791 /* Some material-based adjudications that have to be made before stalemate test */
6792 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
6793 // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
6794 boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
6795 if(canAdjudicate && appData.checkMates) {
6797 SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
6798 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6799 GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
6800 "Xboard adjudication: King destroyed", GE_XBOARD );
6805 /* Bare King in Shatranj (loses) or Losers (wins) */
6806 if( nrW == 1 || nrB == 1) {
6807 if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
6808 boards[forwardMostMove][EP_STATUS] = EP_WINS; // mark as win, so it becomes claimable
6809 if(canAdjudicate && appData.checkMates) {
6811 SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
6812 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6813 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
6814 "Xboard adjudication: Bare king", GE_XBOARD );
6818 if( gameInfo.variant == VariantShatranj && --bare < 0)
6820 boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
6821 if(canAdjudicate && appData.checkMates) {
6822 /* but only adjudicate if adjudication enabled */
6824 SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
6825 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6826 GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
6827 "Xboard adjudication: Bare king", GE_XBOARD );
6834 // don't wait for engine to announce game end if we can judge ourselves
6835 switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
6837 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
6838 int i, checkCnt = 0; // (should really be done by making nr of checks part of game state)
6839 for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
6840 if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
6843 reason = "Xboard adjudication: 3rd check";
6844 boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
6854 reason = "Xboard adjudication: Stalemate";
6855 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
6856 boards[forwardMostMove][EP_STATUS] = EP_STALEMATE; // default result for stalemate is draw
6857 if(gameInfo.variant == VariantLosers || gameInfo.variant == VariantGiveaway) // [HGM] losers:
6858 boards[forwardMostMove][EP_STATUS] = EP_WINS; // in these variants stalemated is always a win
6859 else if(gameInfo.variant == VariantSuicide) // in suicide it depends
6860 boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
6861 ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
6862 EP_CHECKMATE : EP_WINS);
6863 else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
6864 boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
6868 reason = "Xboard adjudication: Checkmate";
6869 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
6873 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
6875 result = GameIsDrawn; break;
6877 result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
6879 result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
6883 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
6885 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6886 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6887 GameEnds( result, reason, GE_XBOARD );
6891 /* Next absolutely insufficient mating material. */
6892 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
6893 !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
6894 { /* includes KBK, KNK, KK of KBKB with like Bishops */
6896 /* always flag draws, for judging claims */
6897 boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
6899 if(canAdjudicate && appData.materialDraws) {
6900 /* but only adjudicate them if adjudication enabled */
6901 if(engineOpponent) {
6902 SendToProgram("force\n", engineOpponent); // suppress reply
6903 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
6905 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6906 GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
6911 /* Then some trivial draws (only adjudicate, cannot be claimed) */
6912 if(gameInfo.variant == VariantXiangqi ?
6913 SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
6915 ( nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
6916 || nr[WhiteQueen] && nr[BlackQueen]==1 /* KQKQ */
6917 || nr[WhiteKnight]==2 || nr[BlackKnight]==2 /* KNNK */
6918 || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
6920 if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
6921 { /* if the first 3 moves do not show a tactical win, declare draw */
6922 if(engineOpponent) {
6923 SendToProgram("force\n", engineOpponent); // suppress reply
6924 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6926 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6927 GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
6930 } else moveCount = 6;
6932 if (appData.debugMode) { int i;
6933 fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
6934 forwardMostMove, backwardMostMove, boards[backwardMostMove][EP_STATUS],
6935 appData.drawRepeats);
6936 for( i=forwardMostMove; i>=backwardMostMove; i-- )
6937 fprintf(debugFP, "%d ep=%d\n", i, (signed char)boards[i][EP_STATUS]);
6941 // Repetition draws and 50-move rule can be applied independently of legality testing
6943 /* Check for rep-draws */
6945 for(k = forwardMostMove-2;
6946 k>=backwardMostMove && k>=forwardMostMove-100 &&
6947 (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
6948 (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE;
6951 if(CompareBoards(boards[k], boards[forwardMostMove])) {
6952 /* compare castling rights */
6953 if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
6954 (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
6955 rights++; /* King lost rights, while rook still had them */
6956 if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
6957 if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
6958 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
6959 rights++; /* but at least one rook lost them */
6961 if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
6962 (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
6964 if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
6965 if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
6966 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
6969 if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
6970 && appData.drawRepeats > 1) {
6971 /* adjudicate after user-specified nr of repeats */
6972 int result = GameIsDrawn;
6973 char *details = "XBoard adjudication: repetition draw";
6974 if(gameInfo.variant == VariantXiangqi && appData.testLegality) {
6975 // [HGM] xiangqi: check for forbidden perpetuals
6976 int m, ourPerpetual = 1, hisPerpetual = 1;
6977 for(m=forwardMostMove; m>k; m-=2) {
6978 if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
6979 ourPerpetual = 0; // the current mover did not always check
6980 if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
6981 hisPerpetual = 0; // the opponent did not always check
6983 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
6984 ourPerpetual, hisPerpetual);
6985 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
6986 result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
6987 details = "Xboard adjudication: perpetual checking";
6989 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
6990 break; // (or we would have caught him before). Abort repetition-checking loop.
6992 // Now check for perpetual chases
6993 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
6994 hisPerpetual = PerpetualChase(k, forwardMostMove);
6995 ourPerpetual = PerpetualChase(k+1, forwardMostMove);
6996 if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
6997 result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
6998 details = "Xboard adjudication: perpetual chasing";
7000 if(hisPerpetual && !ourPerpetual) // he is chasing us, but did not repeat yet
7001 break; // Abort repetition-checking loop.
7003 // if neither of us is checking or chasing all the time, or both are, it is draw
7005 if(engineOpponent) {
7006 SendToProgram("force\n", engineOpponent); // suppress reply
7007 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7009 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7010 GameEnds( result, details, GE_XBOARD );
7013 if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
7014 boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
7018 /* Now we test for 50-move draws. Determine ply count */
7019 count = forwardMostMove;
7020 /* look for last irreversble move */
7021 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
7023 /* if we hit starting position, add initial plies */
7024 if( count == backwardMostMove )
7025 count -= initialRulePlies;
7026 count = forwardMostMove - count;
7027 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
7028 // adjust reversible move counter for checks in Xiangqi
7029 int i = forwardMostMove - count, inCheck = 0, lastCheck;
7030 if(i < backwardMostMove) i = backwardMostMove;
7031 while(i <= forwardMostMove) {
7032 lastCheck = inCheck; // check evasion does not count
7033 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
7034 if(inCheck || lastCheck) count--; // check does not count
7039 boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
7040 /* this is used to judge if draw claims are legal */
7041 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
7042 if(engineOpponent) {
7043 SendToProgram("force\n", engineOpponent); // suppress reply
7044 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7046 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7047 GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
7051 /* if draw offer is pending, treat it as a draw claim
7052 * when draw condition present, to allow engines a way to
7053 * claim draws before making their move to avoid a race
7054 * condition occurring after their move
7056 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
7058 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
7059 p = "Draw claim: 50-move rule";
7060 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
7061 p = "Draw claim: 3-fold repetition";
7062 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
7063 p = "Draw claim: insufficient mating material";
7064 if( p != NULL && canAdjudicate) {
7065 if(engineOpponent) {
7066 SendToProgram("force\n", engineOpponent); // suppress reply
7067 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7069 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7070 GameEnds( GameIsDrawn, p, GE_XBOARD );
7075 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
7076 if(engineOpponent) {
7077 SendToProgram("force\n", engineOpponent); // suppress reply
7078 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7080 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7081 GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
7087 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
7088 { // [HGM] book: this routine intercepts moves to simulate book replies
7089 char *bookHit = NULL;
7091 //first determine if the incoming move brings opponent into his book
7092 if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
7093 bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
7094 if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
7095 if(bookHit != NULL && !cps->bookSuspend) {
7096 // make sure opponent is not going to reply after receiving move to book position
7097 SendToProgram("force\n", cps);
7098 cps->bookSuspend = TRUE; // flag indicating it has to be restarted
7100 if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
7101 // now arrange restart after book miss
7103 // after a book hit we never send 'go', and the code after the call to this routine
7104 // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
7106 snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), bookHit); // force book move into program supposed to play it
7107 SendToProgram(buf, cps);
7108 if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
7109 } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
7110 SendToProgram("go\n", cps);
7111 cps->bookSuspend = FALSE; // after a 'go' we are never suspended
7112 } else { // 'go' might be sent based on 'firstMove' after this routine returns
7113 if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
7114 SendToProgram("go\n", cps);
7115 cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
7117 return bookHit; // notify caller of hit, so it can take action to send move to opponent
7121 ChessProgramState *savedState;
7122 void DeferredBookMove(void)
7124 if(savedState->lastPing != savedState->lastPong)
7125 ScheduleDelayedEvent(DeferredBookMove, 10);
7127 HandleMachineMove(savedMessage, savedState);
7131 HandleMachineMove(message, cps)
7133 ChessProgramState *cps;
7135 char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
7136 char realname[MSG_SIZ];
7137 int fromX, fromY, toX, toY;
7146 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
7148 * Kludge to ignore BEL characters
7150 while (*message == '\007') message++;
7153 * [HGM] engine debug message: ignore lines starting with '#' character
7155 if(cps->debug && *message == '#') return;
7158 * Look for book output
7160 if (cps == &first && bookRequested) {
7161 if (message[0] == '\t' || message[0] == ' ') {
7162 /* Part of the book output is here; append it */
7163 strcat(bookOutput, message);
7164 strcat(bookOutput, " \n");
7166 } else if (bookOutput[0] != NULLCHAR) {
7167 /* All of book output has arrived; display it */
7168 char *p = bookOutput;
7169 while (*p != NULLCHAR) {
7170 if (*p == '\t') *p = ' ';
7173 DisplayInformation(bookOutput);
7174 bookRequested = FALSE;
7175 /* Fall through to parse the current output */
7180 * Look for machine move.
7182 if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
7183 (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
7185 /* This method is only useful on engines that support ping */
7186 if (cps->lastPing != cps->lastPong) {
7187 if (gameMode == BeginningOfGame) {
7188 /* Extra move from before last new; ignore */
7189 if (appData.debugMode) {
7190 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7193 if (appData.debugMode) {
7194 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7195 cps->which, gameMode);
7198 SendToProgram("undo\n", cps);
7204 case BeginningOfGame:
7205 /* Extra move from before last reset; ignore */
7206 if (appData.debugMode) {
7207 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7214 /* Extra move after we tried to stop. The mode test is
7215 not a reliable way of detecting this problem, but it's
7216 the best we can do on engines that don't support ping.
7218 if (appData.debugMode) {
7219 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7220 cps->which, gameMode);
7222 SendToProgram("undo\n", cps);
7225 case MachinePlaysWhite:
7226 case IcsPlayingWhite:
7227 machineWhite = TRUE;
7230 case MachinePlaysBlack:
7231 case IcsPlayingBlack:
7232 machineWhite = FALSE;
7235 case TwoMachinesPlay:
7236 machineWhite = (cps->twoMachinesColor[0] == 'w');
7239 if (WhiteOnMove(forwardMostMove) != machineWhite) {
7240 if (appData.debugMode) {
7242 "Ignoring move out of turn by %s, gameMode %d"
7243 ", forwardMost %d\n",
7244 cps->which, gameMode, forwardMostMove);
7249 if (appData.debugMode) { int f = forwardMostMove;
7250 fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
7251 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
7252 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
7254 if(cps->alphaRank) AlphaRank(machineMove, 4);
7255 if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
7256 &fromX, &fromY, &toX, &toY, &promoChar)) {
7257 /* Machine move could not be parsed; ignore it. */
7258 snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
7259 machineMove, cps->which);
7260 DisplayError(buf1, 0);
7261 snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
7262 machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
7263 if (gameMode == TwoMachinesPlay) {
7264 GameEnds(machineWhite ? BlackWins : WhiteWins,
7270 /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
7271 /* So we have to redo legality test with true e.p. status here, */
7272 /* to make sure an illegal e.p. capture does not slip through, */
7273 /* to cause a forfeit on a justified illegal-move complaint */
7274 /* of the opponent. */
7275 if( gameMode==TwoMachinesPlay && appData.testLegality ) {
7277 moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
7278 fromY, fromX, toY, toX, promoChar);
7279 if (appData.debugMode) {
7281 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
7282 boards[forwardMostMove][CASTLING][i], castlingRank[i]);
7283 fprintf(debugFP, "castling rights\n");
7285 if(moveType == IllegalMove) {
7286 snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
7287 machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
7288 GameEnds(machineWhite ? BlackWins : WhiteWins,
7291 } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
7292 /* [HGM] Kludge to handle engines that send FRC-style castling
7293 when they shouldn't (like TSCP-Gothic) */
7295 case WhiteASideCastleFR:
7296 case BlackASideCastleFR:
7298 currentMoveString[2]++;
7300 case WhiteHSideCastleFR:
7301 case BlackHSideCastleFR:
7303 currentMoveString[2]--;
7305 default: ; // nothing to do, but suppresses warning of pedantic compilers
7308 hintRequested = FALSE;
7309 lastHint[0] = NULLCHAR;
7310 bookRequested = FALSE;
7311 /* Program may be pondering now */
7312 cps->maybeThinking = TRUE;
7313 if (cps->sendTime == 2) cps->sendTime = 1;
7314 if (cps->offeredDraw) cps->offeredDraw--;
7316 /* currentMoveString is set as a side-effect of ParseOneMove */
7317 safeStrCpy(machineMove, currentMoveString, sizeof(machineMove)/sizeof(machineMove[0]));
7318 strcat(machineMove, "\n");
7319 safeStrCpy(moveList[forwardMostMove], machineMove, sizeof(moveList[forwardMostMove])/sizeof(moveList[forwardMostMove][0]));
7321 /* [AS] Save move info*/
7322 pvInfoList[ forwardMostMove ].score = programStats.score;
7323 pvInfoList[ forwardMostMove ].depth = programStats.depth;
7324 pvInfoList[ forwardMostMove ].time = programStats.time; // [HGM] PGNtime: take time from engine stats
7326 MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
7328 /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
7329 if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
7332 while( count < adjudicateLossPlies ) {
7333 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
7336 score = -score; /* Flip score for winning side */
7339 if( score > adjudicateLossThreshold ) {
7346 if( count >= adjudicateLossPlies ) {
7347 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7349 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7350 "Xboard adjudication",
7357 if(Adjudicate(cps)) return; // [HGM] adjudicate: for all automatic game ends
7360 if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
7362 if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
7363 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
7365 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7367 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7369 if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
7370 char buf[3*MSG_SIZ];
7372 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
7373 programStats.score / 100.,
7375 programStats.time / 100.,
7376 (unsigned int)programStats.nodes,
7377 (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
7378 programStats.movelist);
7380 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
7385 /* [AS] Clear stats for next move */
7386 ClearProgramStats();
7387 thinkOutput[0] = NULLCHAR;
7388 hiddenThinkOutputState = 0;
7391 if (gameMode == TwoMachinesPlay) {
7392 /* [HGM] relaying draw offers moved to after reception of move */
7393 /* and interpreting offer as claim if it brings draw condition */
7394 if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
7395 SendToProgram("draw\n", cps->other);
7397 if (cps->other->sendTime) {
7398 SendTimeRemaining(cps->other,
7399 cps->other->twoMachinesColor[0] == 'w');
7401 bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
7402 if (firstMove && !bookHit) {
7404 if (cps->other->useColors) {
7405 SendToProgram(cps->other->twoMachinesColor, cps->other);
7407 SendToProgram("go\n", cps->other);
7409 cps->other->maybeThinking = TRUE;
7412 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7414 if (!pausing && appData.ringBellAfterMoves) {
7419 * Reenable menu items that were disabled while
7420 * machine was thinking
7422 if (gameMode != TwoMachinesPlay)
7423 SetUserThinkingEnables();
7425 // [HGM] book: after book hit opponent has received move and is now in force mode
7426 // force the book reply into it, and then fake that it outputted this move by jumping
7427 // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
7429 static char bookMove[MSG_SIZ]; // a bit generous?
7431 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
7432 strcat(bookMove, bookHit);
7435 programStats.nodes = programStats.depth = programStats.time =
7436 programStats.score = programStats.got_only_move = 0;
7437 sprintf(programStats.movelist, "%s (xbook)", bookHit);
7439 if(cps->lastPing != cps->lastPong) {
7440 savedMessage = message; // args for deferred call
7442 ScheduleDelayedEvent(DeferredBookMove, 10);
7451 /* Set special modes for chess engines. Later something general
7452 * could be added here; for now there is just one kludge feature,
7453 * needed because Crafty 15.10 and earlier don't ignore SIGINT
7454 * when "xboard" is given as an interactive command.
7456 if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
7457 cps->useSigint = FALSE;
7458 cps->useSigterm = FALSE;
7460 if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
7461 ParseFeatures(message+8, cps);
7462 return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
7465 if (!appData.testLegality && !strncmp(message, "setup ", 6)) { // [HGM] allow first engine to define opening position
7466 int dummy, s=6; char buf[MSG_SIZ];
7467 if(appData.icsActive || forwardMostMove != 0 || cps != &first || startedFromSetupPosition) return;
7468 if(sscanf(message, "setup (%s", buf) == 1) s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTable(pieceToChar, buf);
7469 ParseFEN(boards[0], &dummy, message+s);
7470 DrawPosition(TRUE, boards[0]);
7471 startedFromSetupPosition = TRUE;
7474 /* [HGM] Allow engine to set up a position. Don't ask me why one would
7475 * want this, I was asked to put it in, and obliged.
7477 if (!strncmp(message, "setboard ", 9)) {
7478 Board initial_position;
7480 GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
7482 if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
7483 DisplayError(_("Bad FEN received from engine"), 0);
7487 CopyBoard(boards[0], initial_position);
7488 initialRulePlies = FENrulePlies;
7489 if(blackPlaysFirst) gameMode = MachinePlaysWhite;
7490 else gameMode = MachinePlaysBlack;
7491 DrawPosition(FALSE, boards[currentMove]);
7497 * Look for communication commands
7499 if (!strncmp(message, "telluser ", 9)) {
7500 if(message[9] == '\\' && message[10] == '\\')
7501 EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
7502 DisplayNote(message + 9);
7505 if (!strncmp(message, "tellusererror ", 14)) {
7507 if(message[14] == '\\' && message[15] == '\\')
7508 EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
7509 DisplayError(message + 14, 0);
7512 if (!strncmp(message, "tellopponent ", 13)) {
7513 if (appData.icsActive) {
7515 snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
7519 DisplayNote(message + 13);
7523 if (!strncmp(message, "tellothers ", 11)) {
7524 if (appData.icsActive) {
7526 snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
7532 if (!strncmp(message, "tellall ", 8)) {
7533 if (appData.icsActive) {
7535 snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
7539 DisplayNote(message + 8);
7543 if (strncmp(message, "warning", 7) == 0) {
7544 /* Undocumented feature, use tellusererror in new code */
7545 DisplayError(message, 0);
7548 if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
7549 safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
7550 strcat(realname, " query");
7551 AskQuestion(realname, buf2, buf1, cps->pr);
7554 /* Commands from the engine directly to ICS. We don't allow these to be
7555 * sent until we are logged on. Crafty kibitzes have been known to
7556 * interfere with the login process.
7559 if (!strncmp(message, "tellics ", 8)) {
7560 SendToICS(message + 8);
7564 if (!strncmp(message, "tellicsnoalias ", 15)) {
7565 SendToICS(ics_prefix);
7566 SendToICS(message + 15);
7570 /* The following are for backward compatibility only */
7571 if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
7572 !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
7573 SendToICS(ics_prefix);
7579 if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
7583 * If the move is illegal, cancel it and redraw the board.
7584 * Also deal with other error cases. Matching is rather loose
7585 * here to accommodate engines written before the spec.
7587 if (strncmp(message + 1, "llegal move", 11) == 0 ||
7588 strncmp(message, "Error", 5) == 0) {
7589 if (StrStr(message, "name") ||
7590 StrStr(message, "rating") || StrStr(message, "?") ||
7591 StrStr(message, "result") || StrStr(message, "board") ||
7592 StrStr(message, "bk") || StrStr(message, "computer") ||
7593 StrStr(message, "variant") || StrStr(message, "hint") ||
7594 StrStr(message, "random") || StrStr(message, "depth") ||
7595 StrStr(message, "accepted")) {
7598 if (StrStr(message, "protover")) {
7599 /* Program is responding to input, so it's apparently done
7600 initializing, and this error message indicates it is
7601 protocol version 1. So we don't need to wait any longer
7602 for it to initialize and send feature commands. */
7603 FeatureDone(cps, 1);
7604 cps->protocolVersion = 1;
7607 cps->maybeThinking = FALSE;
7609 if (StrStr(message, "draw")) {
7610 /* Program doesn't have "draw" command */
7611 cps->sendDrawOffers = 0;
7614 if (cps->sendTime != 1 &&
7615 (StrStr(message, "time") || StrStr(message, "otim"))) {
7616 /* Program apparently doesn't have "time" or "otim" command */
7620 if (StrStr(message, "analyze")) {
7621 cps->analysisSupport = FALSE;
7622 cps->analyzing = FALSE;
7624 snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
7625 DisplayError(buf2, 0);
7628 if (StrStr(message, "(no matching move)st")) {
7629 /* Special kludge for GNU Chess 4 only */
7630 cps->stKludge = TRUE;
7631 SendTimeControl(cps, movesPerSession, timeControl,
7632 timeIncrement, appData.searchDepth,
7636 if (StrStr(message, "(no matching move)sd")) {
7637 /* Special kludge for GNU Chess 4 only */
7638 cps->sdKludge = TRUE;
7639 SendTimeControl(cps, movesPerSession, timeControl,
7640 timeIncrement, appData.searchDepth,
7644 if (!StrStr(message, "llegal")) {
7647 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
7648 gameMode == IcsIdle) return;
7649 if (forwardMostMove <= backwardMostMove) return;
7650 if (pausing) PauseEvent();
7651 if(appData.forceIllegal) {
7652 // [HGM] illegal: machine refused move; force position after move into it
7653 SendToProgram("force\n", cps);
7654 if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
7655 // we have a real problem now, as SendBoard will use the a2a3 kludge
7656 // when black is to move, while there might be nothing on a2 or black
7657 // might already have the move. So send the board as if white has the move.
7658 // But first we must change the stm of the engine, as it refused the last move
7659 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
7660 if(WhiteOnMove(forwardMostMove)) {
7661 SendToProgram("a7a6\n", cps); // for the engine black still had the move
7662 SendBoard(cps, forwardMostMove); // kludgeless board
7664 SendToProgram("a2a3\n", cps); // for the engine white still had the move
7665 CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
7666 SendBoard(cps, forwardMostMove+1); // kludgeless board
7668 } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
7669 if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
7670 gameMode == TwoMachinesPlay)
7671 SendToProgram("go\n", cps);
7674 if (gameMode == PlayFromGameFile) {
7675 /* Stop reading this game file */
7676 gameMode = EditGame;
7679 currentMove = forwardMostMove-1;
7680 DisplayMove(currentMove-1); /* before DisplayMoveError */
7681 SwitchClocks(forwardMostMove-1); // [HGM] race
7682 DisplayBothClocks();
7683 snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
7684 parseList[currentMove], cps->which);
7685 DisplayMoveError(buf1);
7686 DrawPosition(FALSE, boards[currentMove]);
7688 /* [HGM] illegal-move claim should forfeit game when Xboard */
7689 /* only passes fully legal moves */
7690 if( appData.testLegality && gameMode == TwoMachinesPlay ) {
7691 GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
7692 "False illegal-move claim", GE_XBOARD );
7696 if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
7697 /* Program has a broken "time" command that
7698 outputs a string not ending in newline.
7704 * If chess program startup fails, exit with an error message.
7705 * Attempts to recover here are futile.
7707 if ((StrStr(message, "unknown host") != NULL)
7708 || (StrStr(message, "No remote directory") != NULL)
7709 || (StrStr(message, "not found") != NULL)
7710 || (StrStr(message, "No such file") != NULL)
7711 || (StrStr(message, "can't alloc") != NULL)
7712 || (StrStr(message, "Permission denied") != NULL)) {
7714 cps->maybeThinking = FALSE;
7715 snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
7716 cps->which, cps->program, cps->host, message);
7717 RemoveInputSource(cps->isr);
7718 DisplayFatalError(buf1, 0, 1);
7723 * Look for hint output
7725 if (sscanf(message, "Hint: %s", buf1) == 1) {
7726 if (cps == &first && hintRequested) {
7727 hintRequested = FALSE;
7728 if (ParseOneMove(buf1, forwardMostMove, &moveType,
7729 &fromX, &fromY, &toX, &toY, &promoChar)) {
7730 (void) CoordsToAlgebraic(boards[forwardMostMove],
7731 PosFlags(forwardMostMove),
7732 fromY, fromX, toY, toX, promoChar, buf1);
7733 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
7734 DisplayInformation(buf2);
7736 /* Hint move could not be parsed!? */
7737 snprintf(buf2, sizeof(buf2),
7738 _("Illegal hint move \"%s\"\nfrom %s chess program"),
7740 DisplayError(buf2, 0);
7743 safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
7749 * Ignore other messages if game is not in progress
7751 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
7752 gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
7755 * look for win, lose, draw, or draw offer
7757 if (strncmp(message, "1-0", 3) == 0) {
7758 char *p, *q, *r = "";
7759 p = strchr(message, '{');
7767 GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
7769 } else if (strncmp(message, "0-1", 3) == 0) {
7770 char *p, *q, *r = "";
7771 p = strchr(message, '{');
7779 /* Kludge for Arasan 4.1 bug */
7780 if (strcmp(r, "Black resigns") == 0) {
7781 GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
7784 GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
7786 } else if (strncmp(message, "1/2", 3) == 0) {
7787 char *p, *q, *r = "";
7788 p = strchr(message, '{');
7797 GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
7800 } else if (strncmp(message, "White resign", 12) == 0) {
7801 GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
7803 } else if (strncmp(message, "Black resign", 12) == 0) {
7804 GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
7806 } else if (strncmp(message, "White matches", 13) == 0 ||
7807 strncmp(message, "Black matches", 13) == 0 ) {
7808 /* [HGM] ignore GNUShogi noises */
7810 } else if (strncmp(message, "White", 5) == 0 &&
7811 message[5] != '(' &&
7812 StrStr(message, "Black") == NULL) {
7813 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7815 } else if (strncmp(message, "Black", 5) == 0 &&
7816 message[5] != '(') {
7817 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7819 } else if (strcmp(message, "resign") == 0 ||
7820 strcmp(message, "computer resigns") == 0) {
7822 case MachinePlaysBlack:
7823 case IcsPlayingBlack:
7824 GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
7826 case MachinePlaysWhite:
7827 case IcsPlayingWhite:
7828 GameEnds(BlackWins, "White resigns", GE_ENGINE);
7830 case TwoMachinesPlay:
7831 if (cps->twoMachinesColor[0] == 'w')
7832 GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
7834 GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
7841 } else if (strncmp(message, "opponent mates", 14) == 0) {
7843 case MachinePlaysBlack:
7844 case IcsPlayingBlack:
7845 GameEnds(WhiteWins, "White mates", GE_ENGINE);
7847 case MachinePlaysWhite:
7848 case IcsPlayingWhite:
7849 GameEnds(BlackWins, "Black mates", GE_ENGINE);
7851 case TwoMachinesPlay:
7852 if (cps->twoMachinesColor[0] == 'w')
7853 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7855 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7862 } else if (strncmp(message, "computer mates", 14) == 0) {
7864 case MachinePlaysBlack:
7865 case IcsPlayingBlack:
7866 GameEnds(BlackWins, "Black mates", GE_ENGINE1);
7868 case MachinePlaysWhite:
7869 case IcsPlayingWhite:
7870 GameEnds(WhiteWins, "White mates", GE_ENGINE);
7872 case TwoMachinesPlay:
7873 if (cps->twoMachinesColor[0] == 'w')
7874 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7876 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7883 } else if (strncmp(message, "checkmate", 9) == 0) {
7884 if (WhiteOnMove(forwardMostMove)) {
7885 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7887 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7890 } else if (strstr(message, "Draw") != NULL ||
7891 strstr(message, "game is a draw") != NULL) {
7892 GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
7894 } else if (strstr(message, "offer") != NULL &&
7895 strstr(message, "draw") != NULL) {
7897 if (appData.zippyPlay && first.initDone) {
7898 /* Relay offer to ICS */
7899 SendToICS(ics_prefix);
7900 SendToICS("draw\n");
7903 cps->offeredDraw = 2; /* valid until this engine moves twice */
7904 if (gameMode == TwoMachinesPlay) {
7905 if (cps->other->offeredDraw) {
7906 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
7907 /* [HGM] in two-machine mode we delay relaying draw offer */
7908 /* until after we also have move, to see if it is really claim */
7910 } else if (gameMode == MachinePlaysWhite ||
7911 gameMode == MachinePlaysBlack) {
7912 if (userOfferedDraw) {
7913 DisplayInformation(_("Machine accepts your draw offer"));
7914 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
7916 DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
7923 * Look for thinking output
7925 if ( appData.showThinking // [HGM] thinking: test all options that cause this output
7926 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
7928 int plylev, mvleft, mvtot, curscore, time;
7929 char mvname[MOVE_LEN];
7933 int prefixHint = FALSE;
7934 mvname[0] = NULLCHAR;
7937 case MachinePlaysBlack:
7938 case IcsPlayingBlack:
7939 if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
7941 case MachinePlaysWhite:
7942 case IcsPlayingWhite:
7943 if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
7948 case IcsObserving: /* [DM] icsEngineAnalyze */
7949 if (!appData.icsEngineAnalyze) ignore = TRUE;
7951 case TwoMachinesPlay:
7952 if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
7962 ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
7964 if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
7965 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
7967 if (plyext != ' ' && plyext != '\t') {
7971 /* [AS] Negate score if machine is playing black and reporting absolute scores */
7972 if( cps->scoreIsAbsolute &&
7973 ( gameMode == MachinePlaysBlack ||
7974 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
7975 gameMode == IcsPlayingBlack || // [HGM] also add other situations where engine should report black POV
7976 (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
7977 !WhiteOnMove(currentMove)
7980 curscore = -curscore;
7984 tempStats.depth = plylev;
7985 tempStats.nodes = nodes;
7986 tempStats.time = time;
7987 tempStats.score = curscore;
7988 tempStats.got_only_move = 0;
7990 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
7993 if(cps->nps == 0) ticklen = 10*time; // use engine reported time
7994 else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
7995 if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
7996 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
7997 whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
7998 if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
7999 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
8000 blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
8003 /* Buffer overflow protection */
8004 if (buf1[0] != NULLCHAR) {
8005 if (strlen(buf1) >= sizeof(tempStats.movelist)
8006 && appData.debugMode) {
8008 "PV is too long; using the first %u bytes.\n",
8009 (unsigned) sizeof(tempStats.movelist) - 1);
8012 safeStrCpy( tempStats.movelist, buf1, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
8014 sprintf(tempStats.movelist, " no PV\n");
8017 if (tempStats.seen_stat) {
8018 tempStats.ok_to_send = 1;
8021 if (strchr(tempStats.movelist, '(') != NULL) {
8022 tempStats.line_is_book = 1;
8023 tempStats.nr_moves = 0;
8024 tempStats.moves_left = 0;
8026 tempStats.line_is_book = 0;
8029 if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
8030 programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
8032 SendProgramStatsToFrontend( cps, &tempStats );
8035 [AS] Protect the thinkOutput buffer from overflow... this
8036 is only useful if buf1 hasn't overflowed first!
8038 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%+.2f %s%s",
8040 (gameMode == TwoMachinesPlay ?
8041 ToUpper(cps->twoMachinesColor[0]) : ' '),
8042 ((double) curscore) / 100.0,
8043 prefixHint ? lastHint : "",
8044 prefixHint ? " " : "" );
8046 if( buf1[0] != NULLCHAR ) {
8047 unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
8049 if( strlen(buf1) > max_len ) {
8050 if( appData.debugMode) {
8051 fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
8053 buf1[max_len+1] = '\0';
8056 strcat( thinkOutput, buf1 );
8059 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
8060 || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8061 DisplayMove(currentMove - 1);
8065 } else if ((p=StrStr(message, "(only move)")) != NULL) {
8066 /* crafty (9.25+) says "(only move) <move>"
8067 * if there is only 1 legal move
8069 sscanf(p, "(only move) %s", buf1);
8070 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
8071 sprintf(programStats.movelist, "%s (only move)", buf1);
8072 programStats.depth = 1;
8073 programStats.nr_moves = 1;
8074 programStats.moves_left = 1;
8075 programStats.nodes = 1;
8076 programStats.time = 1;
8077 programStats.got_only_move = 1;
8079 /* Not really, but we also use this member to
8080 mean "line isn't going to change" (Crafty
8081 isn't searching, so stats won't change) */
8082 programStats.line_is_book = 1;
8084 SendProgramStatsToFrontend( cps, &programStats );
8086 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8087 gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8088 DisplayMove(currentMove - 1);
8091 } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
8092 &time, &nodes, &plylev, &mvleft,
8093 &mvtot, mvname) >= 5) {
8094 /* The stat01: line is from Crafty (9.29+) in response
8095 to the "." command */
8096 programStats.seen_stat = 1;
8097 cps->maybeThinking = TRUE;
8099 if (programStats.got_only_move || !appData.periodicUpdates)
8102 programStats.depth = plylev;
8103 programStats.time = time;
8104 programStats.nodes = nodes;
8105 programStats.moves_left = mvleft;
8106 programStats.nr_moves = mvtot;
8107 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
8108 programStats.ok_to_send = 1;
8109 programStats.movelist[0] = '\0';
8111 SendProgramStatsToFrontend( cps, &programStats );
8115 } else if (strncmp(message,"++",2) == 0) {
8116 /* Crafty 9.29+ outputs this */
8117 programStats.got_fail = 2;
8120 } else if (strncmp(message,"--",2) == 0) {
8121 /* Crafty 9.29+ outputs this */
8122 programStats.got_fail = 1;
8125 } else if (thinkOutput[0] != NULLCHAR &&
8126 strncmp(message, " ", 4) == 0) {
8127 unsigned message_len;
8130 while (*p && *p == ' ') p++;
8132 message_len = strlen( p );
8134 /* [AS] Avoid buffer overflow */
8135 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
8136 strcat(thinkOutput, " ");
8137 strcat(thinkOutput, p);
8140 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
8141 strcat(programStats.movelist, " ");
8142 strcat(programStats.movelist, p);
8145 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8146 gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8147 DisplayMove(currentMove - 1);
8155 if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8156 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
8158 ChessProgramStats cpstats;
8160 if (plyext != ' ' && plyext != '\t') {
8164 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8165 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
8166 curscore = -curscore;
8169 cpstats.depth = plylev;
8170 cpstats.nodes = nodes;
8171 cpstats.time = time;
8172 cpstats.score = curscore;
8173 cpstats.got_only_move = 0;
8174 cpstats.movelist[0] = '\0';
8176 if (buf1[0] != NULLCHAR) {
8177 safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
8180 cpstats.ok_to_send = 0;
8181 cpstats.line_is_book = 0;
8182 cpstats.nr_moves = 0;
8183 cpstats.moves_left = 0;
8185 SendProgramStatsToFrontend( cps, &cpstats );
8192 /* Parse a game score from the character string "game", and
8193 record it as the history of the current game. The game
8194 score is NOT assumed to start from the standard position.
8195 The display is not updated in any way.
8198 ParseGameHistory(game)
8202 int fromX, fromY, toX, toY, boardIndex;
8207 if (appData.debugMode)
8208 fprintf(debugFP, "Parsing game history: %s\n", game);
8210 if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
8211 gameInfo.site = StrSave(appData.icsHost);
8212 gameInfo.date = PGNDate();
8213 gameInfo.round = StrSave("-");
8215 /* Parse out names of players */
8216 while (*game == ' ') game++;
8218 while (*game != ' ') *p++ = *game++;
8220 gameInfo.white = StrSave(buf);
8221 while (*game == ' ') game++;
8223 while (*game != ' ' && *game != '\n') *p++ = *game++;
8225 gameInfo.black = StrSave(buf);
8228 boardIndex = blackPlaysFirst ? 1 : 0;
8231 yyboardindex = boardIndex;
8232 moveType = (ChessMove) Myylex();
8234 case IllegalMove: /* maybe suicide chess, etc. */
8235 if (appData.debugMode) {
8236 fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
8237 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8238 setbuf(debugFP, NULL);
8240 case WhitePromotion:
8241 case BlackPromotion:
8242 case WhiteNonPromotion:
8243 case BlackNonPromotion:
8245 case WhiteCapturesEnPassant:
8246 case BlackCapturesEnPassant:
8247 case WhiteKingSideCastle:
8248 case WhiteQueenSideCastle:
8249 case BlackKingSideCastle:
8250 case BlackQueenSideCastle:
8251 case WhiteKingSideCastleWild:
8252 case WhiteQueenSideCastleWild:
8253 case BlackKingSideCastleWild:
8254 case BlackQueenSideCastleWild:
8256 case WhiteHSideCastleFR:
8257 case WhiteASideCastleFR:
8258 case BlackHSideCastleFR:
8259 case BlackASideCastleFR:
8261 fromX = currentMoveString[0] - AAA;
8262 fromY = currentMoveString[1] - ONE;
8263 toX = currentMoveString[2] - AAA;
8264 toY = currentMoveString[3] - ONE;
8265 promoChar = currentMoveString[4];
8269 fromX = moveType == WhiteDrop ?
8270 (int) CharToPiece(ToUpper(currentMoveString[0])) :
8271 (int) CharToPiece(ToLower(currentMoveString[0]));
8273 toX = currentMoveString[2] - AAA;
8274 toY = currentMoveString[3] - ONE;
8275 promoChar = NULLCHAR;
8279 snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
8280 if (appData.debugMode) {
8281 fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
8282 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8283 setbuf(debugFP, NULL);
8285 DisplayError(buf, 0);
8287 case ImpossibleMove:
8289 snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
8290 if (appData.debugMode) {
8291 fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
8292 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8293 setbuf(debugFP, NULL);
8295 DisplayError(buf, 0);
8298 if (boardIndex < backwardMostMove) {
8299 /* Oops, gap. How did that happen? */
8300 DisplayError(_("Gap in move list"), 0);
8303 backwardMostMove = blackPlaysFirst ? 1 : 0;
8304 if (boardIndex > forwardMostMove) {
8305 forwardMostMove = boardIndex;
8309 if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
8310 strcat(parseList[boardIndex-1], " ");
8311 strcat(parseList[boardIndex-1], yy_text);
8323 case GameUnfinished:
8324 if (gameMode == IcsExamining) {
8325 if (boardIndex < backwardMostMove) {
8326 /* Oops, gap. How did that happen? */
8329 backwardMostMove = blackPlaysFirst ? 1 : 0;
8332 gameInfo.result = moveType;
8333 p = strchr(yy_text, '{');
8334 if (p == NULL) p = strchr(yy_text, '(');
8337 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
8339 q = strchr(p, *p == '{' ? '}' : ')');
8340 if (q != NULL) *q = NULLCHAR;
8343 gameInfo.resultDetails = StrSave(p);
8346 if (boardIndex >= forwardMostMove &&
8347 !(gameMode == IcsObserving && ics_gamenum == -1)) {
8348 backwardMostMove = blackPlaysFirst ? 1 : 0;
8351 (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
8352 fromY, fromX, toY, toX, promoChar,
8353 parseList[boardIndex]);
8354 CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
8355 /* currentMoveString is set as a side-effect of yylex */
8356 safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
8357 strcat(moveList[boardIndex], "\n");
8359 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
8360 switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
8366 if(gameInfo.variant != VariantShogi)
8367 strcat(parseList[boardIndex - 1], "+");
8371 strcat(parseList[boardIndex - 1], "#");
8378 /* Apply a move to the given board */
8380 ApplyMove(fromX, fromY, toX, toY, promoChar, board)
8381 int fromX, fromY, toX, toY;
8385 ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
8386 int promoRank = gameInfo.variant == VariantMakruk ? 3 : 1;
8388 /* [HGM] compute & store e.p. status and castling rights for new position */
8389 /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
8391 if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
8392 oldEP = (signed char)board[EP_STATUS];
8393 board[EP_STATUS] = EP_NONE;
8395 if( board[toY][toX] != EmptySquare )
8396 board[EP_STATUS] = EP_CAPTURE;
8398 if (fromY == DROP_RANK) {
8400 piece = board[toY][toX] = (ChessSquare) fromX;
8404 if( board[fromY][fromX] == WhiteLance || board[fromY][fromX] == BlackLance ) {
8405 if( gameInfo.variant == VariantFairy ) board[EP_STATUS] = EP_PAWN_MOVE; // Lance in fairy is Pawn-like
8407 if( board[fromY][fromX] == WhitePawn ) {
8408 if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
8409 board[EP_STATUS] = EP_PAWN_MOVE;
8411 if(toX>BOARD_LEFT && board[toY][toX-1] == BlackPawn &&
8412 gameInfo.variant != VariantBerolina || toX < fromX)
8413 board[EP_STATUS] = toX | berolina;
8414 if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
8415 gameInfo.variant != VariantBerolina || toX > fromX)
8416 board[EP_STATUS] = toX;
8419 if( board[fromY][fromX] == BlackPawn ) {
8420 if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
8421 board[EP_STATUS] = EP_PAWN_MOVE;
8422 if( toY-fromY== -2) {
8423 if(toX>BOARD_LEFT && board[toY][toX-1] == WhitePawn &&
8424 gameInfo.variant != VariantBerolina || toX < fromX)
8425 board[EP_STATUS] = toX | berolina;
8426 if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
8427 gameInfo.variant != VariantBerolina || toX > fromX)
8428 board[EP_STATUS] = toX;
8432 for(i=0; i<nrCastlingRights; i++) {
8433 if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
8434 board[CASTLING][i] == toX && castlingRank[i] == toY
8435 ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
8438 if (fromX == toX && fromY == toY) return;
8440 piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
8441 king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
8442 if(gameInfo.variant == VariantKnightmate)
8443 king += (int) WhiteUnicorn - (int) WhiteKing;
8445 /* Code added by Tord: */
8446 /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
8447 if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
8448 board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
8449 board[fromY][fromX] = EmptySquare;
8450 board[toY][toX] = EmptySquare;
8451 if((toX > fromX) != (piece == WhiteRook)) {
8452 board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
8454 board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
8456 } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
8457 board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
8458 board[fromY][fromX] = EmptySquare;
8459 board[toY][toX] = EmptySquare;
8460 if((toX > fromX) != (piece == BlackRook)) {
8461 board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
8463 board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
8465 /* End of code added by Tord */
8467 } else if (board[fromY][fromX] == king
8468 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8469 && toY == fromY && toX > fromX+1) {
8470 board[fromY][fromX] = EmptySquare;
8471 board[toY][toX] = king;
8472 board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
8473 board[fromY][BOARD_RGHT-1] = EmptySquare;
8474 } else if (board[fromY][fromX] == king
8475 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8476 && toY == fromY && toX < fromX-1) {
8477 board[fromY][fromX] = EmptySquare;
8478 board[toY][toX] = king;
8479 board[toY][toX+1] = board[fromY][BOARD_LEFT];
8480 board[fromY][BOARD_LEFT] = EmptySquare;
8481 } else if (board[fromY][fromX] == WhitePawn
8482 && toY >= BOARD_HEIGHT-promoRank
8483 && gameInfo.variant != VariantXiangqi
8485 /* white pawn promotion */
8486 board[toY][toX] = CharToPiece(ToUpper(promoChar));
8487 if (board[toY][toX] == EmptySquare) {
8488 board[toY][toX] = WhiteQueen;
8490 if(gameInfo.variant==VariantBughouse ||
8491 gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
8492 board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
8493 board[fromY][fromX] = EmptySquare;
8494 } else if ((fromY == BOARD_HEIGHT-4)
8496 && gameInfo.variant != VariantXiangqi
8497 && gameInfo.variant != VariantBerolina
8498 && (board[fromY][fromX] == WhitePawn)
8499 && (board[toY][toX] == EmptySquare)) {
8500 board[fromY][fromX] = EmptySquare;
8501 board[toY][toX] = WhitePawn;
8502 captured = board[toY - 1][toX];
8503 board[toY - 1][toX] = EmptySquare;
8504 } else if ((fromY == BOARD_HEIGHT-4)
8506 && gameInfo.variant == VariantBerolina
8507 && (board[fromY][fromX] == WhitePawn)
8508 && (board[toY][toX] == EmptySquare)) {
8509 board[fromY][fromX] = EmptySquare;
8510 board[toY][toX] = WhitePawn;
8511 if(oldEP & EP_BEROLIN_A) {
8512 captured = board[fromY][fromX-1];
8513 board[fromY][fromX-1] = EmptySquare;
8514 }else{ captured = board[fromY][fromX+1];
8515 board[fromY][fromX+1] = EmptySquare;
8517 } else if (board[fromY][fromX] == king
8518 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8519 && toY == fromY && toX > fromX+1) {
8520 board[fromY][fromX] = EmptySquare;
8521 board[toY][toX] = king;
8522 board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
8523 board[fromY][BOARD_RGHT-1] = EmptySquare;
8524 } else if (board[fromY][fromX] == king
8525 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8526 && toY == fromY && toX < fromX-1) {
8527 board[fromY][fromX] = EmptySquare;
8528 board[toY][toX] = king;
8529 board[toY][toX+1] = board[fromY][BOARD_LEFT];
8530 board[fromY][BOARD_LEFT] = EmptySquare;
8531 } else if (fromY == 7 && fromX == 3
8532 && board[fromY][fromX] == BlackKing
8533 && toY == 7 && toX == 5) {
8534 board[fromY][fromX] = EmptySquare;
8535 board[toY][toX] = BlackKing;
8536 board[fromY][7] = EmptySquare;
8537 board[toY][4] = BlackRook;
8538 } else if (fromY == 7 && fromX == 3
8539 && board[fromY][fromX] == BlackKing
8540 && toY == 7 && toX == 1) {
8541 board[fromY][fromX] = EmptySquare;
8542 board[toY][toX] = BlackKing;
8543 board[fromY][0] = EmptySquare;
8544 board[toY][2] = BlackRook;
8545 } else if (board[fromY][fromX] == BlackPawn
8547 && gameInfo.variant != VariantXiangqi
8549 /* black pawn promotion */
8550 board[toY][toX] = CharToPiece(ToLower(promoChar));
8551 if (board[toY][toX] == EmptySquare) {
8552 board[toY][toX] = BlackQueen;
8554 if(gameInfo.variant==VariantBughouse ||
8555 gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
8556 board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
8557 board[fromY][fromX] = EmptySquare;
8558 } else if ((fromY == 3)
8560 && gameInfo.variant != VariantXiangqi
8561 && gameInfo.variant != VariantBerolina
8562 && (board[fromY][fromX] == BlackPawn)
8563 && (board[toY][toX] == EmptySquare)) {
8564 board[fromY][fromX] = EmptySquare;
8565 board[toY][toX] = BlackPawn;
8566 captured = board[toY + 1][toX];
8567 board[toY + 1][toX] = EmptySquare;
8568 } else if ((fromY == 3)
8570 && gameInfo.variant == VariantBerolina
8571 && (board[fromY][fromX] == BlackPawn)
8572 && (board[toY][toX] == EmptySquare)) {
8573 board[fromY][fromX] = EmptySquare;
8574 board[toY][toX] = BlackPawn;
8575 if(oldEP & EP_BEROLIN_A) {
8576 captured = board[fromY][fromX-1];
8577 board[fromY][fromX-1] = EmptySquare;
8578 }else{ captured = board[fromY][fromX+1];
8579 board[fromY][fromX+1] = EmptySquare;
8582 board[toY][toX] = board[fromY][fromX];
8583 board[fromY][fromX] = EmptySquare;
8587 if (gameInfo.holdingsWidth != 0) {
8589 /* !!A lot more code needs to be written to support holdings */
8590 /* [HGM] OK, so I have written it. Holdings are stored in the */
8591 /* penultimate board files, so they are automaticlly stored */
8592 /* in the game history. */
8593 if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
8594 && promoChar && piece != WhitePawn && piece != BlackPawn) {
8595 /* Delete from holdings, by decreasing count */
8596 /* and erasing image if necessary */
8597 p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
8598 if(p < (int) BlackPawn) { /* white drop */
8599 p -= (int)WhitePawn;
8600 p = PieceToNumber((ChessSquare)p);
8601 if(p >= gameInfo.holdingsSize) p = 0;
8602 if(--board[p][BOARD_WIDTH-2] <= 0)
8603 board[p][BOARD_WIDTH-1] = EmptySquare;
8604 if((int)board[p][BOARD_WIDTH-2] < 0)
8605 board[p][BOARD_WIDTH-2] = 0;
8606 } else { /* black drop */
8607 p -= (int)BlackPawn;
8608 p = PieceToNumber((ChessSquare)p);
8609 if(p >= gameInfo.holdingsSize) p = 0;
8610 if(--board[BOARD_HEIGHT-1-p][1] <= 0)
8611 board[BOARD_HEIGHT-1-p][0] = EmptySquare;
8612 if((int)board[BOARD_HEIGHT-1-p][1] < 0)
8613 board[BOARD_HEIGHT-1-p][1] = 0;
8616 if (captured != EmptySquare && gameInfo.holdingsSize > 0
8617 && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess ) {
8618 /* [HGM] holdings: Add to holdings, if holdings exist */
8619 if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
8620 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
8621 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
8624 if (p >= (int) BlackPawn) {
8625 p -= (int)BlackPawn;
8626 if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
8627 /* in Shogi restore piece to its original first */
8628 captured = (ChessSquare) (DEMOTED captured);
8631 p = PieceToNumber((ChessSquare)p);
8632 if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
8633 board[p][BOARD_WIDTH-2]++;
8634 board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
8636 p -= (int)WhitePawn;
8637 if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
8638 captured = (ChessSquare) (DEMOTED captured);
8641 p = PieceToNumber((ChessSquare)p);
8642 if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
8643 board[BOARD_HEIGHT-1-p][1]++;
8644 board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
8647 } else if (gameInfo.variant == VariantAtomic) {
8648 if (captured != EmptySquare) {
8650 for (y = toY-1; y <= toY+1; y++) {
8651 for (x = toX-1; x <= toX+1; x++) {
8652 if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
8653 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
8654 board[y][x] = EmptySquare;
8658 board[toY][toX] = EmptySquare;
8661 if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
8662 board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
8664 if(promoChar == '+') {
8665 /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite orinary Pawn promotion) */
8666 board[toY][toX] = (ChessSquare) (PROMOTED piece);
8667 } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
8668 board[toY][toX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
8670 if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
8671 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
8672 // [HGM] superchess: take promotion piece out of holdings
8673 int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
8674 if((int)piece < (int)BlackPawn) { // determine stm from piece color
8675 if(!--board[k][BOARD_WIDTH-2])
8676 board[k][BOARD_WIDTH-1] = EmptySquare;
8678 if(!--board[BOARD_HEIGHT-1-k][1])
8679 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
8685 /* Updates forwardMostMove */
8687 MakeMove(fromX, fromY, toX, toY, promoChar)
8688 int fromX, fromY, toX, toY;
8691 // forwardMostMove++; // [HGM] bare: moved downstream
8693 if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
8694 int timeLeft; static int lastLoadFlag=0; int king, piece;
8695 piece = boards[forwardMostMove][fromY][fromX];
8696 king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
8697 if(gameInfo.variant == VariantKnightmate)
8698 king += (int) WhiteUnicorn - (int) WhiteKing;
8699 if(forwardMostMove == 0) {
8701 fprintf(serverMoves, "%s;", second.tidy);
8702 fprintf(serverMoves, "%s;", first.tidy);
8703 if(!blackPlaysFirst)
8704 fprintf(serverMoves, "%s;", second.tidy);
8705 } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
8706 lastLoadFlag = loadFlag;
8708 fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
8709 // print castling suffix
8710 if( toY == fromY && piece == king ) {
8712 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
8714 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
8717 if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
8718 boards[forwardMostMove][fromY][fromX] == BlackPawn ) &&
8719 boards[forwardMostMove][toY][toX] == EmptySquare
8720 && fromX != toX && fromY != toY)
8721 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
8723 if(promoChar != NULLCHAR)
8724 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);
8726 fprintf(serverMoves, "/%d/%d",
8727 pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
8728 if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
8729 else timeLeft = blackTimeRemaining/1000;
8730 fprintf(serverMoves, "/%d", timeLeft);
8732 fflush(serverMoves);
8735 if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations
8736 DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
8740 UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
8741 if (commentList[forwardMostMove+1] != NULL) {
8742 free(commentList[forwardMostMove+1]);
8743 commentList[forwardMostMove+1] = NULL;
8745 CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
8746 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
8747 // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
8748 SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
8749 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
8750 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
8751 gameInfo.result = GameUnfinished;
8752 if (gameInfo.resultDetails != NULL) {
8753 free(gameInfo.resultDetails);
8754 gameInfo.resultDetails = NULL;
8756 CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
8757 moveList[forwardMostMove - 1]);
8758 (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
8759 PosFlags(forwardMostMove - 1),
8760 fromY, fromX, toY, toX, promoChar,
8761 parseList[forwardMostMove - 1]);
8762 switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
8768 if(gameInfo.variant != VariantShogi)
8769 strcat(parseList[forwardMostMove - 1], "+");
8773 strcat(parseList[forwardMostMove - 1], "#");
8776 if (appData.debugMode) {
8777 fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
8782 /* Updates currentMove if not pausing */
8784 ShowMove(fromX, fromY, toX, toY)
8786 int instant = (gameMode == PlayFromGameFile) ?
8787 (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
8788 if(appData.noGUI) return;
8789 if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
8791 if (forwardMostMove == currentMove + 1) {
8792 AnimateMove(boards[forwardMostMove - 1],
8793 fromX, fromY, toX, toY);
8795 if (appData.highlightLastMove) {
8796 SetHighlights(fromX, fromY, toX, toY);
8799 currentMove = forwardMostMove;
8802 if (instant) return;
8804 DisplayMove(currentMove - 1);
8805 DrawPosition(FALSE, boards[currentMove]);
8806 DisplayBothClocks();
8807 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
8810 void SendEgtPath(ChessProgramState *cps)
8811 { /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
8812 char buf[MSG_SIZ], name[MSG_SIZ], *p;
8814 if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
8817 char c, *q = name+1, *r, *s;
8819 name[0] = ','; // extract next format name from feature and copy with prefixed ','
8820 while(*p && *p != ',') *q++ = *p++;
8822 if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
8823 strcmp(name, ",nalimov:") == 0 ) {
8824 // take nalimov path from the menu-changeable option first, if it is defined
8825 snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
8826 SendToProgram(buf,cps); // send egtbpath command for nalimov
8828 if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
8829 (s = StrStr(appData.egtFormats, name)) != NULL) {
8830 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
8831 s = r = StrStr(s, ":") + 1; // beginning of path info
8832 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
8833 c = *r; *r = 0; // temporarily null-terminate path info
8834 *--q = 0; // strip of trailig ':' from name
8835 snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
8837 SendToProgram(buf,cps); // send egtbpath command for this format
8839 if(*p == ',') p++; // read away comma to position for next format name
8844 InitChessProgram(cps, setup)
8845 ChessProgramState *cps;
8846 int setup; /* [HGM] needed to setup FRC opening position */
8848 char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
8849 if (appData.noChessProgram) return;
8850 hintRequested = FALSE;
8851 bookRequested = FALSE;
8853 /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
8854 /* moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
8855 if(cps->memSize) { /* [HGM] memory */
8856 snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
8857 SendToProgram(buf, cps);
8859 SendEgtPath(cps); /* [HGM] EGT */
8860 if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
8861 snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
8862 SendToProgram(buf, cps);
8865 SendToProgram(cps->initString, cps);
8866 if (gameInfo.variant != VariantNormal &&
8867 gameInfo.variant != VariantLoadable
8868 /* [HGM] also send variant if board size non-standard */
8869 || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
8871 char *v = VariantName(gameInfo.variant);
8872 if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
8873 /* [HGM] in protocol 1 we have to assume all variants valid */
8874 snprintf(buf, MSG_SIZ, _("Variant %s not supported by %s"), v, cps->tidy);
8875 DisplayFatalError(buf, 0, 1);
8879 /* [HGM] make prefix for non-standard board size. Awkward testing... */
8880 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8881 if( gameInfo.variant == VariantXiangqi )
8882 overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
8883 if( gameInfo.variant == VariantShogi )
8884 overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
8885 if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
8886 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
8887 if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
8888 gameInfo.variant == VariantGothic || gameInfo.variant == VariantFalcon )
8889 overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8890 if( gameInfo.variant == VariantCourier )
8891 overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8892 if( gameInfo.variant == VariantSuper )
8893 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
8894 if( gameInfo.variant == VariantGreat )
8895 overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
8896 if( gameInfo.variant == VariantSChess )
8897 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 7;
8900 snprintf(b, MSG_SIZ, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
8901 gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
8902 /* [HGM] varsize: try first if this defiant size variant is specifically known */
8903 if(StrStr(cps->variants, b) == NULL) {
8904 // specific sized variant not known, check if general sizing allowed
8905 if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
8906 if(StrStr(cps->variants, "boardsize") == NULL) {
8907 snprintf(buf, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
8908 gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
8909 DisplayFatalError(buf, 0, 1);
8912 /* [HGM] here we really should compare with the maximum supported board size */
8915 } else snprintf(b, MSG_SIZ,"%s", VariantName(gameInfo.variant));
8916 snprintf(buf, MSG_SIZ, "variant %s\n", b);
8917 SendToProgram(buf, cps);
8919 currentlyInitializedVariant = gameInfo.variant;
8921 /* [HGM] send opening position in FRC to first engine */
8923 SendToProgram("force\n", cps);
8925 /* engine is now in force mode! Set flag to wake it up after first move. */
8926 setboardSpoiledMachineBlack = 1;
8930 snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
8931 SendToProgram(buf, cps);
8933 cps->maybeThinking = FALSE;
8934 cps->offeredDraw = 0;
8935 if (!appData.icsActive) {
8936 SendTimeControl(cps, movesPerSession, timeControl,
8937 timeIncrement, appData.searchDepth,
8940 if (appData.showThinking
8941 // [HGM] thinking: four options require thinking output to be sent
8942 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
8944 SendToProgram("post\n", cps);
8946 SendToProgram("hard\n", cps);
8947 if (!appData.ponderNextMove) {
8948 /* Warning: "easy" is a toggle in GNU Chess, so don't send
8949 it without being sure what state we are in first. "hard"
8950 is not a toggle, so that one is OK.
8952 SendToProgram("easy\n", cps);
8955 snprintf(buf, MSG_SIZ, "ping %d\n", ++cps->lastPing);
8956 SendToProgram(buf, cps);
8958 cps->initDone = TRUE;
8963 StartChessProgram(cps)
8964 ChessProgramState *cps;
8969 if (appData.noChessProgram) return;
8970 cps->initDone = FALSE;
8972 if (strcmp(cps->host, "localhost") == 0) {
8973 err = StartChildProcess(cps->program, cps->dir, &cps->pr);
8974 } else if (*appData.remoteShell == NULLCHAR) {
8975 err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
8977 if (*appData.remoteUser == NULLCHAR) {
8978 snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
8981 snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
8982 cps->host, appData.remoteUser, cps->program);
8984 err = StartChildProcess(buf, "", &cps->pr);
8988 snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
8989 DisplayFatalError(buf, err, 1);
8995 cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
8996 if (cps->protocolVersion > 1) {
8997 snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
8998 cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
8999 cps->comboCnt = 0; // and values of combo boxes
9000 SendToProgram(buf, cps);
9002 SendToProgram("xboard\n", cps);
9008 TwoMachinesEventIfReady P((void))
9010 if (first.lastPing != first.lastPong) {
9011 DisplayMessage("", _("Waiting for first chess program"));
9012 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9015 if (second.lastPing != second.lastPong) {
9016 DisplayMessage("", _("Waiting for second chess program"));
9017 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9025 NextMatchGame P((void))
9027 int index; /* [HGM] autoinc: step load index during match */
9029 if (*appData.loadGameFile != NULLCHAR) {
9030 index = appData.loadGameIndex;
9031 if(index < 0) { // [HGM] autoinc
9032 lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
9033 if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
9035 LoadGameFromFile(appData.loadGameFile,
9037 appData.loadGameFile, FALSE);
9038 } else if (*appData.loadPositionFile != NULLCHAR) {
9039 index = appData.loadPositionIndex;
9040 if(index < 0) { // [HGM] autoinc
9041 lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
9042 if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
9044 LoadPositionFromFile(appData.loadPositionFile,
9046 appData.loadPositionFile);
9048 TwoMachinesEventIfReady();
9051 void UserAdjudicationEvent( int result )
9053 ChessMove gameResult = GameIsDrawn;
9056 gameResult = WhiteWins;
9058 else if( result < 0 ) {
9059 gameResult = BlackWins;
9062 if( gameMode == TwoMachinesPlay ) {
9063 GameEnds( gameResult, "User adjudication", GE_XBOARD );
9068 // [HGM] save: calculate checksum of game to make games easily identifiable
9069 int StringCheckSum(char *s)
9072 if(s==NULL) return 0;
9073 while(*s) i = i*259 + *s++;
9080 for(i=backwardMostMove; i<forwardMostMove; i++) {
9081 sum += pvInfoList[i].depth;
9082 sum += StringCheckSum(parseList[i]);
9083 sum += StringCheckSum(commentList[i]);
9086 if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
9087 return sum + StringCheckSum(commentList[i]);
9088 } // end of save patch
9091 GameEnds(result, resultDetails, whosays)
9093 char *resultDetails;
9096 GameMode nextGameMode;
9098 char buf[MSG_SIZ], popupRequested = 0;
9100 if(endingGame) return; /* [HGM] crash: forbid recursion */
9102 if(twoBoards) { // [HGM] dual: switch back to one board
9103 twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
9104 DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
9106 if (appData.debugMode) {
9107 fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
9108 result, resultDetails ? resultDetails : "(null)", whosays);
9111 fromX = fromY = -1; // [HGM] abort any move the user is entering.
9113 if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
9114 /* If we are playing on ICS, the server decides when the
9115 game is over, but the engine can offer to draw, claim
9119 if (appData.zippyPlay && first.initDone) {
9120 if (result == GameIsDrawn) {
9121 /* In case draw still needs to be claimed */
9122 SendToICS(ics_prefix);
9123 SendToICS("draw\n");
9124 } else if (StrCaseStr(resultDetails, "resign")) {
9125 SendToICS(ics_prefix);
9126 SendToICS("resign\n");
9130 endingGame = 0; /* [HGM] crash */
9134 /* If we're loading the game from a file, stop */
9135 if (whosays == GE_FILE) {
9136 (void) StopLoadGameTimer();
9140 /* Cancel draw offers */
9141 first.offeredDraw = second.offeredDraw = 0;
9143 /* If this is an ICS game, only ICS can really say it's done;
9144 if not, anyone can. */
9145 isIcsGame = (gameMode == IcsPlayingWhite ||
9146 gameMode == IcsPlayingBlack ||
9147 gameMode == IcsObserving ||
9148 gameMode == IcsExamining);
9150 if (!isIcsGame || whosays == GE_ICS) {
9151 /* OK -- not an ICS game, or ICS said it was done */
9153 if (!isIcsGame && !appData.noChessProgram)
9154 SetUserThinkingEnables();
9156 /* [HGM] if a machine claims the game end we verify this claim */
9157 if(gameMode == TwoMachinesPlay && appData.testClaims) {
9158 if(appData.testLegality && whosays >= GE_ENGINE1 ) {
9160 ChessMove trueResult = (ChessMove) -1;
9162 claimer = whosays == GE_ENGINE1 ? /* color of claimer */
9163 first.twoMachinesColor[0] :
9164 second.twoMachinesColor[0] ;
9166 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
9167 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
9168 /* [HGM] verify: engine mate claims accepted if they were flagged */
9169 trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
9171 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
9172 /* [HGM] verify: engine mate claims accepted if they were flagged */
9173 trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
9175 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
9176 trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
9179 // now verify win claims, but not in drop games, as we don't understand those yet
9180 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
9181 || gameInfo.variant == VariantGreat) &&
9182 (result == WhiteWins && claimer == 'w' ||
9183 result == BlackWins && claimer == 'b' ) ) { // case to verify: engine claims own win
9184 if (appData.debugMode) {
9185 fprintf(debugFP, "result=%d sp=%d move=%d\n",
9186 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
9188 if(result != trueResult) {
9189 snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
9190 result = claimer == 'w' ? BlackWins : WhiteWins;
9191 resultDetails = buf;
9194 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
9195 && (forwardMostMove <= backwardMostMove ||
9196 (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
9197 (claimer=='b')==(forwardMostMove&1))
9199 /* [HGM] verify: draws that were not flagged are false claims */
9200 snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
9201 result = claimer == 'w' ? BlackWins : WhiteWins;
9202 resultDetails = buf;
9204 /* (Claiming a loss is accepted no questions asked!) */
9206 /* [HGM] bare: don't allow bare King to win */
9207 if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
9208 && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
9209 && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
9210 && result != GameIsDrawn)
9211 { int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
9212 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
9213 int p = (signed char)boards[forwardMostMove][i][j] - color;
9214 if(p >= 0 && p <= (int)WhiteKing) k++;
9216 if (appData.debugMode) {
9217 fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
9218 result, resultDetails ? resultDetails : "(null)", whosays, k, color);
9221 result = GameIsDrawn;
9222 snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
9223 resultDetails = buf;
9229 if(serverMoves != NULL && !loadFlag) { char c = '=';
9230 if(result==WhiteWins) c = '+';
9231 if(result==BlackWins) c = '-';
9232 if(resultDetails != NULL)
9233 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
9235 if (resultDetails != NULL) {
9236 gameInfo.result = result;
9237 gameInfo.resultDetails = StrSave(resultDetails);
9239 /* display last move only if game was not loaded from file */
9240 if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
9241 DisplayMove(currentMove - 1);
9243 if (forwardMostMove != 0) {
9244 if (gameMode != PlayFromGameFile && gameMode != EditGame
9245 && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
9247 if (*appData.saveGameFile != NULLCHAR) {
9248 SaveGameToFile(appData.saveGameFile, TRUE);
9249 } else if (appData.autoSaveGames) {
9252 if (*appData.savePositionFile != NULLCHAR) {
9253 SavePositionToFile(appData.savePositionFile);
9258 /* Tell program how game ended in case it is learning */
9259 /* [HGM] Moved this to after saving the PGN, just in case */
9260 /* engine died and we got here through time loss. In that */
9261 /* case we will get a fatal error writing the pipe, which */
9262 /* would otherwise lose us the PGN. */
9263 /* [HGM] crash: not needed anymore, but doesn't hurt; */
9264 /* output during GameEnds should never be fatal anymore */
9265 if (gameMode == MachinePlaysWhite ||
9266 gameMode == MachinePlaysBlack ||
9267 gameMode == TwoMachinesPlay ||
9268 gameMode == IcsPlayingWhite ||
9269 gameMode == IcsPlayingBlack ||
9270 gameMode == BeginningOfGame) {
9272 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
9274 if (first.pr != NoProc) {
9275 SendToProgram(buf, &first);
9277 if (second.pr != NoProc &&
9278 gameMode == TwoMachinesPlay) {
9279 SendToProgram(buf, &second);
9284 if (appData.icsActive) {
9285 if (appData.quietPlay &&
9286 (gameMode == IcsPlayingWhite ||
9287 gameMode == IcsPlayingBlack)) {
9288 SendToICS(ics_prefix);
9289 SendToICS("set shout 1\n");
9291 nextGameMode = IcsIdle;
9292 ics_user_moved = FALSE;
9293 /* clean up premove. It's ugly when the game has ended and the
9294 * premove highlights are still on the board.
9298 ClearPremoveHighlights();
9299 DrawPosition(FALSE, boards[currentMove]);
9301 if (whosays == GE_ICS) {
9304 if (gameMode == IcsPlayingWhite)
9306 else if(gameMode == IcsPlayingBlack)
9310 if (gameMode == IcsPlayingBlack)
9312 else if(gameMode == IcsPlayingWhite)
9319 PlayIcsUnfinishedSound();
9322 } else if (gameMode == EditGame ||
9323 gameMode == PlayFromGameFile ||
9324 gameMode == AnalyzeMode ||
9325 gameMode == AnalyzeFile) {
9326 nextGameMode = gameMode;
9328 nextGameMode = EndOfGame;
9333 nextGameMode = gameMode;
9336 if (appData.noChessProgram) {
9337 gameMode = nextGameMode;
9339 endingGame = 0; /* [HGM] crash */
9344 /* Put first chess program into idle state */
9345 if (first.pr != NoProc &&
9346 (gameMode == MachinePlaysWhite ||
9347 gameMode == MachinePlaysBlack ||
9348 gameMode == TwoMachinesPlay ||
9349 gameMode == IcsPlayingWhite ||
9350 gameMode == IcsPlayingBlack ||
9351 gameMode == BeginningOfGame)) {
9352 SendToProgram("force\n", &first);
9353 if (first.usePing) {
9355 snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
9356 SendToProgram(buf, &first);
9359 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
9360 /* Kill off first chess program */
9361 if (first.isr != NULL)
9362 RemoveInputSource(first.isr);
9365 if (first.pr != NoProc) {
9367 DoSleep( appData.delayBeforeQuit );
9368 SendToProgram("quit\n", &first);
9369 DoSleep( appData.delayAfterQuit );
9370 DestroyChildProcess(first.pr, first.useSigterm);
9375 /* Put second chess program into idle state */
9376 if (second.pr != NoProc &&
9377 gameMode == TwoMachinesPlay) {
9378 SendToProgram("force\n", &second);
9379 if (second.usePing) {
9381 snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
9382 SendToProgram(buf, &second);
9385 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
9386 /* Kill off second chess program */
9387 if (second.isr != NULL)
9388 RemoveInputSource(second.isr);
9391 if (second.pr != NoProc) {
9392 DoSleep( appData.delayBeforeQuit );
9393 SendToProgram("quit\n", &second);
9394 DoSleep( appData.delayAfterQuit );
9395 DestroyChildProcess(second.pr, second.useSigterm);
9400 if (matchMode && gameMode == TwoMachinesPlay) {
9403 if (first.twoMachinesColor[0] == 'w') {
9410 if (first.twoMachinesColor[0] == 'b') {
9419 if (matchGame < appData.matchGames) {
9421 if(appData.sameColorGames <= 1) { /* [HGM] alternate: suppress color swap */
9422 tmp = first.twoMachinesColor;
9423 first.twoMachinesColor = second.twoMachinesColor;
9424 second.twoMachinesColor = tmp;
9426 gameMode = nextGameMode;
9428 if(appData.matchPause>10000 || appData.matchPause<10)
9429 appData.matchPause = 10000; /* [HGM] make pause adjustable */
9430 ScheduleDelayedEvent(NextMatchGame, appData.matchPause);
9431 endingGame = 0; /* [HGM] crash */
9434 gameMode = nextGameMode;
9435 snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
9436 first.tidy, second.tidy,
9437 first.matchWins, second.matchWins,
9438 appData.matchGames - (first.matchWins + second.matchWins));
9439 popupRequested++; // [HGM] crash: postpone to after resetting endingGame
9442 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
9443 !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
9445 gameMode = nextGameMode;
9447 endingGame = 0; /* [HGM] crash */
9448 if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
9449 if(matchMode == TRUE) DisplayFatalError(buf, 0, 0); else {
9450 matchMode = FALSE; appData.matchGames = matchGame = 0;
9456 /* Assumes program was just initialized (initString sent).
9457 Leaves program in force mode. */
9459 FeedMovesToProgram(cps, upto)
9460 ChessProgramState *cps;
9465 if (appData.debugMode)
9466 fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
9467 startedFromSetupPosition ? "position and " : "",
9468 backwardMostMove, upto, cps->which);
9469 if(currentlyInitializedVariant != gameInfo.variant) {
9471 // [HGM] variantswitch: make engine aware of new variant
9472 if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
9473 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
9474 snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
9475 SendToProgram(buf, cps);
9476 currentlyInitializedVariant = gameInfo.variant;
9478 SendToProgram("force\n", cps);
9479 if (startedFromSetupPosition) {
9480 SendBoard(cps, backwardMostMove);
9481 if (appData.debugMode) {
9482 fprintf(debugFP, "feedMoves\n");
9485 for (i = backwardMostMove; i < upto; i++) {
9486 SendMoveToProgram(i, cps);
9492 ResurrectChessProgram()
9494 /* The chess program may have exited.
9495 If so, restart it and feed it all the moves made so far. */
9497 if (appData.noChessProgram || first.pr != NoProc) return;
9499 StartChessProgram(&first);
9500 InitChessProgram(&first, FALSE);
9501 FeedMovesToProgram(&first, currentMove);
9503 if (!first.sendTime) {
9504 /* can't tell gnuchess what its clock should read,
9505 so we bow to its notion. */
9507 timeRemaining[0][currentMove] = whiteTimeRemaining;
9508 timeRemaining[1][currentMove] = blackTimeRemaining;
9511 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
9512 appData.icsEngineAnalyze) && first.analysisSupport) {
9513 SendToProgram("analyze\n", &first);
9514 first.analyzing = TRUE;
9527 if (appData.debugMode) {
9528 fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
9529 redraw, init, gameMode);
9531 CleanupTail(); // [HGM] vari: delete any stored variations
9532 pausing = pauseExamInvalid = FALSE;
9533 startedFromSetupPosition = blackPlaysFirst = FALSE;
9535 whiteFlag = blackFlag = FALSE;
9536 userOfferedDraw = FALSE;
9537 hintRequested = bookRequested = FALSE;
9538 first.maybeThinking = FALSE;
9539 second.maybeThinking = FALSE;
9540 first.bookSuspend = FALSE; // [HGM] book
9541 second.bookSuspend = FALSE;
9542 thinkOutput[0] = NULLCHAR;
9543 lastHint[0] = NULLCHAR;
9544 ClearGameInfo(&gameInfo);
9545 gameInfo.variant = StringToVariant(appData.variant);
9546 ics_user_moved = ics_clock_paused = FALSE;
9547 ics_getting_history = H_FALSE;
9549 white_holding[0] = black_holding[0] = NULLCHAR;
9550 ClearProgramStats();
9551 opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
9555 flipView = appData.flipView;
9556 ClearPremoveHighlights();
9558 alarmSounded = FALSE;
9560 GameEnds(EndOfFile, NULL, GE_PLAYER);
9561 if(appData.serverMovesName != NULL) {
9562 /* [HGM] prepare to make moves file for broadcasting */
9563 clock_t t = clock();
9564 if(serverMoves != NULL) fclose(serverMoves);
9565 serverMoves = fopen(appData.serverMovesName, "r");
9566 if(serverMoves != NULL) {
9567 fclose(serverMoves);
9568 /* delay 15 sec before overwriting, so all clients can see end */
9569 while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
9571 serverMoves = fopen(appData.serverMovesName, "w");
9575 gameMode = BeginningOfGame;
9577 if(appData.icsActive) gameInfo.variant = VariantNormal;
9578 currentMove = forwardMostMove = backwardMostMove = 0;
9579 InitPosition(redraw);
9580 for (i = 0; i < MAX_MOVES; i++) {
9581 if (commentList[i] != NULL) {
9582 free(commentList[i]);
9583 commentList[i] = NULL;
9587 timeRemaining[0][0] = whiteTimeRemaining;
9588 timeRemaining[1][0] = blackTimeRemaining;
9589 if (first.pr == NULL) {
9590 StartChessProgram(&first);
9593 InitChessProgram(&first, startedFromSetupPosition);
9596 DisplayMessage("", "");
9597 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
9598 lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
9605 if (!AutoPlayOneMove())
9607 if (matchMode || appData.timeDelay == 0)
9609 if (appData.timeDelay < 0)
9611 StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
9620 int fromX, fromY, toX, toY;
9622 if (appData.debugMode) {
9623 fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
9626 if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
9629 if (gameMode == AnalyzeFile && currentMove > backwardMostMove) {
9630 pvInfoList[currentMove].depth = programStats.depth;
9631 pvInfoList[currentMove].score = programStats.score;
9632 pvInfoList[currentMove].time = 0;
9633 if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
9636 if (currentMove >= forwardMostMove) {
9637 if(gameMode == AnalyzeFile) { ExitAnalyzeMode(); SendToProgram("force\n", &first); }
9638 gameMode = EditGame;
9641 /* [AS] Clear current move marker at the end of a game */
9642 /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
9647 toX = moveList[currentMove][2] - AAA;
9648 toY = moveList[currentMove][3] - ONE;
9650 if (moveList[currentMove][1] == '@') {
9651 if (appData.highlightLastMove) {
9652 SetHighlights(-1, -1, toX, toY);
9655 fromX = moveList[currentMove][0] - AAA;
9656 fromY = moveList[currentMove][1] - ONE;
9658 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
9660 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
9662 if (appData.highlightLastMove) {
9663 SetHighlights(fromX, fromY, toX, toY);
9666 DisplayMove(currentMove);
9667 SendMoveToProgram(currentMove++, &first);
9668 DisplayBothClocks();
9669 DrawPosition(FALSE, boards[currentMove]);
9670 // [HGM] PV info: always display, routine tests if empty
9671 DisplayComment(currentMove - 1, commentList[currentMove]);
9677 LoadGameOneMove(readAhead)
9678 ChessMove readAhead;
9680 int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
9681 char promoChar = NULLCHAR;
9686 if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
9687 gameMode != AnalyzeMode && gameMode != Training) {
9692 yyboardindex = forwardMostMove;
9693 if (readAhead != EndOfFile) {
9694 moveType = readAhead;
9696 if (gameFileFP == NULL)
9698 moveType = (ChessMove) Myylex();
9704 if (appData.debugMode)
9705 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9708 /* append the comment but don't display it */
9709 AppendComment(currentMove, p, FALSE);
9712 case WhiteCapturesEnPassant:
9713 case BlackCapturesEnPassant:
9714 case WhitePromotion:
9715 case BlackPromotion:
9716 case WhiteNonPromotion:
9717 case BlackNonPromotion:
9719 case WhiteKingSideCastle:
9720 case WhiteQueenSideCastle:
9721 case BlackKingSideCastle:
9722 case BlackQueenSideCastle:
9723 case WhiteKingSideCastleWild:
9724 case WhiteQueenSideCastleWild:
9725 case BlackKingSideCastleWild:
9726 case BlackQueenSideCastleWild:
9728 case WhiteHSideCastleFR:
9729 case WhiteASideCastleFR:
9730 case BlackHSideCastleFR:
9731 case BlackASideCastleFR:
9733 if (appData.debugMode)
9734 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
9735 fromX = currentMoveString[0] - AAA;
9736 fromY = currentMoveString[1] - ONE;
9737 toX = currentMoveString[2] - AAA;
9738 toY = currentMoveString[3] - ONE;
9739 promoChar = currentMoveString[4];
9744 if (appData.debugMode)
9745 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
9746 fromX = moveType == WhiteDrop ?
9747 (int) CharToPiece(ToUpper(currentMoveString[0])) :
9748 (int) CharToPiece(ToLower(currentMoveString[0]));
9750 toX = currentMoveString[2] - AAA;
9751 toY = currentMoveString[3] - ONE;
9757 case GameUnfinished:
9758 if (appData.debugMode)
9759 fprintf(debugFP, "Parsed game end: %s\n", yy_text);
9760 p = strchr(yy_text, '{');
9761 if (p == NULL) p = strchr(yy_text, '(');
9764 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
9766 q = strchr(p, *p == '{' ? '}' : ')');
9767 if (q != NULL) *q = NULLCHAR;
9770 GameEnds(moveType, p, GE_FILE);
9772 if (cmailMsgLoaded) {
9774 flipView = WhiteOnMove(currentMove);
9775 if (moveType == GameUnfinished) flipView = !flipView;
9776 if (appData.debugMode)
9777 fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
9782 if (appData.debugMode)
9783 fprintf(debugFP, "Parser hit end of file\n");
9784 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9790 if (WhiteOnMove(currentMove)) {
9791 GameEnds(BlackWins, "Black mates", GE_FILE);
9793 GameEnds(WhiteWins, "White mates", GE_FILE);
9797 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
9804 if (lastLoadGameStart == GNUChessGame) {
9805 /* GNUChessGames have numbers, but they aren't move numbers */
9806 if (appData.debugMode)
9807 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
9808 yy_text, (int) moveType);
9809 return LoadGameOneMove(EndOfFile); /* tail recursion */
9811 /* else fall thru */
9816 /* Reached start of next game in file */
9817 if (appData.debugMode)
9818 fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
9819 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9825 if (WhiteOnMove(currentMove)) {
9826 GameEnds(BlackWins, "Black mates", GE_FILE);
9828 GameEnds(WhiteWins, "White mates", GE_FILE);
9832 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
9838 case PositionDiagram: /* should not happen; ignore */
9839 case ElapsedTime: /* ignore */
9840 case NAG: /* ignore */
9841 if (appData.debugMode)
9842 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
9843 yy_text, (int) moveType);
9844 return LoadGameOneMove(EndOfFile); /* tail recursion */
9847 if (appData.testLegality) {
9848 if (appData.debugMode)
9849 fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
9850 snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
9851 (forwardMostMove / 2) + 1,
9852 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9853 DisplayError(move, 0);
9856 if (appData.debugMode)
9857 fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
9858 yy_text, currentMoveString);
9859 fromX = currentMoveString[0] - AAA;
9860 fromY = currentMoveString[1] - ONE;
9861 toX = currentMoveString[2] - AAA;
9862 toY = currentMoveString[3] - ONE;
9863 promoChar = currentMoveString[4];
9868 if (appData.debugMode)
9869 fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
9870 snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
9871 (forwardMostMove / 2) + 1,
9872 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9873 DisplayError(move, 0);
9878 case ImpossibleMove:
9879 if (appData.debugMode)
9880 fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
9881 snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
9882 (forwardMostMove / 2) + 1,
9883 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9884 DisplayError(move, 0);
9890 if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
9891 DrawPosition(FALSE, boards[currentMove]);
9892 DisplayBothClocks();
9893 if (!appData.matchMode) // [HGM] PV info: routine tests if empty
9894 DisplayComment(currentMove - 1, commentList[currentMove]);
9896 (void) StopLoadGameTimer();
9898 cmailOldMove = forwardMostMove;
9901 /* currentMoveString is set as a side-effect of yylex */
9902 strcat(currentMoveString, "\n");
9903 safeStrCpy(moveList[forwardMostMove], currentMoveString, sizeof(moveList[forwardMostMove])/sizeof(moveList[forwardMostMove][0]));
9905 thinkOutput[0] = NULLCHAR;
9906 MakeMove(fromX, fromY, toX, toY, promoChar);
9907 currentMove = forwardMostMove;
9912 /* Load the nth game from the given file */
9914 LoadGameFromFile(filename, n, title, useList)
9918 /*Boolean*/ int useList;
9923 if (strcmp(filename, "-") == 0) {
9927 f = fopen(filename, "rb");
9929 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9930 DisplayError(buf, errno);
9934 if (fseek(f, 0, 0) == -1) {
9935 /* f is not seekable; probably a pipe */
9938 if (useList && n == 0) {
9939 int error = GameListBuild(f);
9941 DisplayError(_("Cannot build game list"), error);
9942 } else if (!ListEmpty(&gameList) &&
9943 ((ListGame *) gameList.tailPred)->number > 1) {
9944 GameListPopUp(f, title);
9951 return LoadGame(f, n, title, FALSE);
9956 MakeRegisteredMove()
9958 int fromX, fromY, toX, toY;
9960 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
9961 switch (cmailMoveType[lastLoadGameNumber - 1]) {
9964 if (appData.debugMode)
9965 fprintf(debugFP, "Restoring %s for game %d\n",
9966 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
9968 thinkOutput[0] = NULLCHAR;
9969 safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
9970 fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
9971 fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
9972 toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
9973 toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
9974 promoChar = cmailMove[lastLoadGameNumber - 1][4];
9975 MakeMove(fromX, fromY, toX, toY, promoChar);
9976 ShowMove(fromX, fromY, toX, toY);
9978 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9985 if (WhiteOnMove(currentMove)) {
9986 GameEnds(BlackWins, "Black mates", GE_PLAYER);
9988 GameEnds(WhiteWins, "White mates", GE_PLAYER);
9993 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
10000 if (WhiteOnMove(currentMove)) {
10001 GameEnds(BlackWins, "White resigns", GE_PLAYER);
10003 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
10008 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
10019 /* Wrapper around LoadGame for use when a Cmail message is loaded */
10021 CmailLoadGame(f, gameNumber, title, useList)
10029 if (gameNumber > nCmailGames) {
10030 DisplayError(_("No more games in this message"), 0);
10033 if (f == lastLoadGameFP) {
10034 int offset = gameNumber - lastLoadGameNumber;
10036 cmailMsg[0] = NULLCHAR;
10037 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
10038 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
10039 nCmailMovesRegistered--;
10041 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
10042 if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
10043 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
10046 if (! RegisterMove()) return FALSE;
10050 retVal = LoadGame(f, gameNumber, title, useList);
10052 /* Make move registered during previous look at this game, if any */
10053 MakeRegisteredMove();
10055 if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
10056 commentList[currentMove]
10057 = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
10058 DisplayComment(currentMove - 1, commentList[currentMove]);
10064 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
10069 int gameNumber = lastLoadGameNumber + offset;
10070 if (lastLoadGameFP == NULL) {
10071 DisplayError(_("No game has been loaded yet"), 0);
10074 if (gameNumber <= 0) {
10075 DisplayError(_("Can't back up any further"), 0);
10078 if (cmailMsgLoaded) {
10079 return CmailLoadGame(lastLoadGameFP, gameNumber,
10080 lastLoadGameTitle, lastLoadGameUseList);
10082 return LoadGame(lastLoadGameFP, gameNumber,
10083 lastLoadGameTitle, lastLoadGameUseList);
10089 /* Load the nth game from open file f */
10091 LoadGame(f, gameNumber, title, useList)
10099 int gn = gameNumber;
10100 ListGame *lg = NULL;
10101 int numPGNTags = 0;
10103 GameMode oldGameMode;
10104 VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
10106 if (appData.debugMode)
10107 fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
10109 if (gameMode == Training )
10110 SetTrainingModeOff();
10112 oldGameMode = gameMode;
10113 if (gameMode != BeginningOfGame) {
10114 Reset(FALSE, TRUE);
10118 if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
10119 fclose(lastLoadGameFP);
10123 lg = (ListGame *) ListElem(&gameList, gameNumber-1);
10126 fseek(f, lg->offset, 0);
10127 GameListHighlight(gameNumber);
10131 DisplayError(_("Game number out of range"), 0);
10136 if (fseek(f, 0, 0) == -1) {
10137 if (f == lastLoadGameFP ?
10138 gameNumber == lastLoadGameNumber + 1 :
10142 DisplayError(_("Can't seek on game file"), 0);
10147 lastLoadGameFP = f;
10148 lastLoadGameNumber = gameNumber;
10149 safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
10150 lastLoadGameUseList = useList;
10154 if (lg && lg->gameInfo.white && lg->gameInfo.black) {
10155 snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
10156 lg->gameInfo.black);
10158 } else if (*title != NULLCHAR) {
10159 if (gameNumber > 1) {
10160 snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
10163 DisplayTitle(title);
10167 if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
10168 gameMode = PlayFromGameFile;
10172 currentMove = forwardMostMove = backwardMostMove = 0;
10173 CopyBoard(boards[0], initialPosition);
10177 * Skip the first gn-1 games in the file.
10178 * Also skip over anything that precedes an identifiable
10179 * start of game marker, to avoid being confused by
10180 * garbage at the start of the file. Currently
10181 * recognized start of game markers are the move number "1",
10182 * the pattern "gnuchess .* game", the pattern
10183 * "^[#;%] [^ ]* game file", and a PGN tag block.
10184 * A game that starts with one of the latter two patterns
10185 * will also have a move number 1, possibly
10186 * following a position diagram.
10187 * 5-4-02: Let's try being more lenient and allowing a game to
10188 * start with an unnumbered move. Does that break anything?
10190 cm = lastLoadGameStart = EndOfFile;
10192 yyboardindex = forwardMostMove;
10193 cm = (ChessMove) Myylex();
10196 if (cmailMsgLoaded) {
10197 nCmailGames = CMAIL_MAX_GAMES - gn;
10200 DisplayError(_("Game not found in file"), 0);
10207 lastLoadGameStart = cm;
10210 case MoveNumberOne:
10211 switch (lastLoadGameStart) {
10216 case MoveNumberOne:
10218 gn--; /* count this game */
10219 lastLoadGameStart = cm;
10228 switch (lastLoadGameStart) {
10231 case MoveNumberOne:
10233 gn--; /* count this game */
10234 lastLoadGameStart = cm;
10237 lastLoadGameStart = cm; /* game counted already */
10245 yyboardindex = forwardMostMove;
10246 cm = (ChessMove) Myylex();
10247 } while (cm == PGNTag || cm == Comment);
10254 if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
10255 if ( cmailResult[CMAIL_MAX_GAMES - gn - 1]
10256 != CMAIL_OLD_RESULT) {
10258 cmailResult[ CMAIL_MAX_GAMES
10259 - gn - 1] = CMAIL_OLD_RESULT;
10265 /* Only a NormalMove can be at the start of a game
10266 * without a position diagram. */
10267 if (lastLoadGameStart == EndOfFile ) {
10269 lastLoadGameStart = MoveNumberOne;
10278 if (appData.debugMode)
10279 fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
10281 if (cm == XBoardGame) {
10282 /* Skip any header junk before position diagram and/or move 1 */
10284 yyboardindex = forwardMostMove;
10285 cm = (ChessMove) Myylex();
10287 if (cm == EndOfFile ||
10288 cm == GNUChessGame || cm == XBoardGame) {
10289 /* Empty game; pretend end-of-file and handle later */
10294 if (cm == MoveNumberOne || cm == PositionDiagram ||
10295 cm == PGNTag || cm == Comment)
10298 } else if (cm == GNUChessGame) {
10299 if (gameInfo.event != NULL) {
10300 free(gameInfo.event);
10302 gameInfo.event = StrSave(yy_text);
10305 startedFromSetupPosition = FALSE;
10306 while (cm == PGNTag) {
10307 if (appData.debugMode)
10308 fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
10309 err = ParsePGNTag(yy_text, &gameInfo);
10310 if (!err) numPGNTags++;
10312 /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
10313 if(gameInfo.variant != oldVariant) {
10314 startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
10315 ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
10316 InitPosition(TRUE);
10317 oldVariant = gameInfo.variant;
10318 if (appData.debugMode)
10319 fprintf(debugFP, "New variant %d\n", (int) oldVariant);
10323 if (gameInfo.fen != NULL) {
10324 Board initial_position;
10325 startedFromSetupPosition = TRUE;
10326 if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
10328 DisplayError(_("Bad FEN position in file"), 0);
10331 CopyBoard(boards[0], initial_position);
10332 if (blackPlaysFirst) {
10333 currentMove = forwardMostMove = backwardMostMove = 1;
10334 CopyBoard(boards[1], initial_position);
10335 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
10336 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
10337 timeRemaining[0][1] = whiteTimeRemaining;
10338 timeRemaining[1][1] = blackTimeRemaining;
10339 if (commentList[0] != NULL) {
10340 commentList[1] = commentList[0];
10341 commentList[0] = NULL;
10344 currentMove = forwardMostMove = backwardMostMove = 0;
10346 /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
10348 initialRulePlies = FENrulePlies;
10349 for( i=0; i< nrCastlingRights; i++ )
10350 initialRights[i] = initial_position[CASTLING][i];
10352 yyboardindex = forwardMostMove;
10353 free(gameInfo.fen);
10354 gameInfo.fen = NULL;
10357 yyboardindex = forwardMostMove;
10358 cm = (ChessMove) Myylex();
10360 /* Handle comments interspersed among the tags */
10361 while (cm == Comment) {
10363 if (appData.debugMode)
10364 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10366 AppendComment(currentMove, p, FALSE);
10367 yyboardindex = forwardMostMove;
10368 cm = (ChessMove) Myylex();
10372 /* don't rely on existence of Event tag since if game was
10373 * pasted from clipboard the Event tag may not exist
10375 if (numPGNTags > 0){
10377 if (gameInfo.variant == VariantNormal) {
10378 VariantClass v = StringToVariant(gameInfo.event);
10379 // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
10380 if(v < VariantShogi) gameInfo.variant = v;
10383 if( appData.autoDisplayTags ) {
10384 tags = PGNTags(&gameInfo);
10385 TagsPopUp(tags, CmailMsg());
10390 /* Make something up, but don't display it now */
10395 if (cm == PositionDiagram) {
10398 Board initial_position;
10400 if (appData.debugMode)
10401 fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
10403 if (!startedFromSetupPosition) {
10405 for (i = BOARD_HEIGHT - 1; i >= 0; i--)
10406 for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
10416 initial_position[i][j++] = CharToPiece(*p);
10419 while (*p == ' ' || *p == '\t' ||
10420 *p == '\n' || *p == '\r') p++;
10422 if (strncmp(p, "black", strlen("black"))==0)
10423 blackPlaysFirst = TRUE;
10425 blackPlaysFirst = FALSE;
10426 startedFromSetupPosition = TRUE;
10428 CopyBoard(boards[0], initial_position);
10429 if (blackPlaysFirst) {
10430 currentMove = forwardMostMove = backwardMostMove = 1;
10431 CopyBoard(boards[1], initial_position);
10432 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
10433 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
10434 timeRemaining[0][1] = whiteTimeRemaining;
10435 timeRemaining[1][1] = blackTimeRemaining;
10436 if (commentList[0] != NULL) {
10437 commentList[1] = commentList[0];
10438 commentList[0] = NULL;
10441 currentMove = forwardMostMove = backwardMostMove = 0;
10444 yyboardindex = forwardMostMove;
10445 cm = (ChessMove) Myylex();
10448 if (first.pr == NoProc) {
10449 StartChessProgram(&first);
10451 InitChessProgram(&first, FALSE);
10452 SendToProgram("force\n", &first);
10453 if (startedFromSetupPosition) {
10454 SendBoard(&first, forwardMostMove);
10455 if (appData.debugMode) {
10456 fprintf(debugFP, "Load Game\n");
10458 DisplayBothClocks();
10461 /* [HGM] server: flag to write setup moves in broadcast file as one */
10462 loadFlag = appData.suppressLoadMoves;
10464 while (cm == Comment) {
10466 if (appData.debugMode)
10467 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10469 AppendComment(currentMove, p, FALSE);
10470 yyboardindex = forwardMostMove;
10471 cm = (ChessMove) Myylex();
10474 if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
10475 cm == WhiteWins || cm == BlackWins ||
10476 cm == GameIsDrawn || cm == GameUnfinished) {
10477 DisplayMessage("", _("No moves in game"));
10478 if (cmailMsgLoaded) {
10479 if (appData.debugMode)
10480 fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
10484 DrawPosition(FALSE, boards[currentMove]);
10485 DisplayBothClocks();
10486 gameMode = EditGame;
10493 // [HGM] PV info: routine tests if comment empty
10494 if (!matchMode && (pausing || appData.timeDelay != 0)) {
10495 DisplayComment(currentMove - 1, commentList[currentMove]);
10497 if (!matchMode && appData.timeDelay != 0)
10498 DrawPosition(FALSE, boards[currentMove]);
10500 if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
10501 programStats.ok_to_send = 1;
10504 /* if the first token after the PGN tags is a move
10505 * and not move number 1, retrieve it from the parser
10507 if (cm != MoveNumberOne)
10508 LoadGameOneMove(cm);
10510 /* load the remaining moves from the file */
10511 while (LoadGameOneMove(EndOfFile)) {
10512 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10513 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10516 /* rewind to the start of the game */
10517 currentMove = backwardMostMove;
10519 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10521 if (oldGameMode == AnalyzeFile ||
10522 oldGameMode == AnalyzeMode) {
10523 AnalyzeFileEvent();
10526 if (matchMode || appData.timeDelay == 0) {
10528 gameMode = EditGame;
10530 } else if (appData.timeDelay > 0) {
10531 AutoPlayGameLoop();
10534 if (appData.debugMode)
10535 fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
10537 loadFlag = 0; /* [HGM] true game starts */
10541 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
10543 ReloadPosition(offset)
10546 int positionNumber = lastLoadPositionNumber + offset;
10547 if (lastLoadPositionFP == NULL) {
10548 DisplayError(_("No position has been loaded yet"), 0);
10551 if (positionNumber <= 0) {
10552 DisplayError(_("Can't back up any further"), 0);
10555 return LoadPosition(lastLoadPositionFP, positionNumber,
10556 lastLoadPositionTitle);
10559 /* Load the nth position from the given file */
10561 LoadPositionFromFile(filename, n, title)
10569 if (strcmp(filename, "-") == 0) {
10570 return LoadPosition(stdin, n, "stdin");
10572 f = fopen(filename, "rb");
10574 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10575 DisplayError(buf, errno);
10578 return LoadPosition(f, n, title);
10583 /* Load the nth position from the given open file, and close it */
10585 LoadPosition(f, positionNumber, title)
10587 int positionNumber;
10590 char *p, line[MSG_SIZ];
10591 Board initial_position;
10592 int i, j, fenMode, pn;
10594 if (gameMode == Training )
10595 SetTrainingModeOff();
10597 if (gameMode != BeginningOfGame) {
10598 Reset(FALSE, TRUE);
10600 if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
10601 fclose(lastLoadPositionFP);
10603 if (positionNumber == 0) positionNumber = 1;
10604 lastLoadPositionFP = f;
10605 lastLoadPositionNumber = positionNumber;
10606 safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
10607 if (first.pr == NoProc) {
10608 StartChessProgram(&first);
10609 InitChessProgram(&first, FALSE);
10611 pn = positionNumber;
10612 if (positionNumber < 0) {
10613 /* Negative position number means to seek to that byte offset */
10614 if (fseek(f, -positionNumber, 0) == -1) {
10615 DisplayError(_("Can't seek on position file"), 0);
10620 if (fseek(f, 0, 0) == -1) {
10621 if (f == lastLoadPositionFP ?
10622 positionNumber == lastLoadPositionNumber + 1 :
10623 positionNumber == 1) {
10626 DisplayError(_("Can't seek on position file"), 0);
10631 /* See if this file is FEN or old-style xboard */
10632 if (fgets(line, MSG_SIZ, f) == NULL) {
10633 DisplayError(_("Position not found in file"), 0);
10636 // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
10637 fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
10640 if (fenMode || line[0] == '#') pn--;
10642 /* skip positions before number pn */
10643 if (fgets(line, MSG_SIZ, f) == NULL) {
10645 DisplayError(_("Position not found in file"), 0);
10648 if (fenMode || line[0] == '#') pn--;
10653 if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
10654 DisplayError(_("Bad FEN position in file"), 0);
10658 (void) fgets(line, MSG_SIZ, f);
10659 (void) fgets(line, MSG_SIZ, f);
10661 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
10662 (void) fgets(line, MSG_SIZ, f);
10663 for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
10666 initial_position[i][j++] = CharToPiece(*p);
10670 blackPlaysFirst = FALSE;
10672 (void) fgets(line, MSG_SIZ, f);
10673 if (strncmp(line, "black", strlen("black"))==0)
10674 blackPlaysFirst = TRUE;
10677 startedFromSetupPosition = TRUE;
10679 SendToProgram("force\n", &first);
10680 CopyBoard(boards[0], initial_position);
10681 if (blackPlaysFirst) {
10682 currentMove = forwardMostMove = backwardMostMove = 1;
10683 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
10684 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
10685 CopyBoard(boards[1], initial_position);
10686 DisplayMessage("", _("Black to play"));
10688 currentMove = forwardMostMove = backwardMostMove = 0;
10689 DisplayMessage("", _("White to play"));
10691 initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
10692 SendBoard(&first, forwardMostMove);
10693 if (appData.debugMode) {
10695 for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
10696 for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
10697 fprintf(debugFP, "Load Position\n");
10700 if (positionNumber > 1) {
10701 snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
10702 DisplayTitle(line);
10704 DisplayTitle(title);
10706 gameMode = EditGame;
10709 timeRemaining[0][1] = whiteTimeRemaining;
10710 timeRemaining[1][1] = blackTimeRemaining;
10711 DrawPosition(FALSE, boards[currentMove]);
10718 CopyPlayerNameIntoFileName(dest, src)
10721 while (*src != NULLCHAR && *src != ',') {
10726 *(*dest)++ = *src++;
10731 char *DefaultFileName(ext)
10734 static char def[MSG_SIZ];
10737 if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
10739 CopyPlayerNameIntoFileName(&p, gameInfo.white);
10741 CopyPlayerNameIntoFileName(&p, gameInfo.black);
10743 safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
10750 /* Save the current game to the given file */
10752 SaveGameToFile(filename, append)
10759 if (strcmp(filename, "-") == 0) {
10760 return SaveGame(stdout, 0, NULL);
10762 f = fopen(filename, append ? "a" : "w");
10764 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10765 DisplayError(buf, errno);
10768 return SaveGame(f, 0, NULL);
10777 static char buf[MSG_SIZ];
10780 p = strchr(str, ' ');
10781 if (p == NULL) return str;
10782 strncpy(buf, str, p - str);
10783 buf[p - str] = NULLCHAR;
10787 #define PGN_MAX_LINE 75
10789 #define PGN_SIDE_WHITE 0
10790 #define PGN_SIDE_BLACK 1
10793 static int FindFirstMoveOutOfBook( int side )
10797 if( backwardMostMove == 0 && ! startedFromSetupPosition) {
10798 int index = backwardMostMove;
10799 int has_book_hit = 0;
10801 if( (index % 2) != side ) {
10805 while( index < forwardMostMove ) {
10806 /* Check to see if engine is in book */
10807 int depth = pvInfoList[index].depth;
10808 int score = pvInfoList[index].score;
10814 else if( score == 0 && depth == 63 ) {
10815 in_book = 1; /* Zappa */
10817 else if( score == 2 && depth == 99 ) {
10818 in_book = 1; /* Abrok */
10821 has_book_hit += in_book;
10837 void GetOutOfBookInfo( char * buf )
10841 int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10843 oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
10844 oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
10848 if( oob[0] >= 0 || oob[1] >= 0 ) {
10849 for( i=0; i<2; i++ ) {
10853 if( i > 0 && oob[0] >= 0 ) {
10854 strcat( buf, " " );
10857 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
10858 sprintf( buf+strlen(buf), "%s%.2f",
10859 pvInfoList[idx].score >= 0 ? "+" : "",
10860 pvInfoList[idx].score / 100.0 );
10866 /* Save game in PGN style and close the file */
10871 int i, offset, linelen, newblock;
10875 int movelen, numlen, blank;
10876 char move_buffer[100]; /* [AS] Buffer for move+PV info */
10878 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10880 tm = time((time_t *) NULL);
10882 PrintPGNTags(f, &gameInfo);
10884 if (backwardMostMove > 0 || startedFromSetupPosition) {
10885 char *fen = PositionToFEN(backwardMostMove, NULL);
10886 fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
10887 fprintf(f, "\n{--------------\n");
10888 PrintPosition(f, backwardMostMove);
10889 fprintf(f, "--------------}\n");
10893 /* [AS] Out of book annotation */
10894 if( appData.saveOutOfBookInfo ) {
10897 GetOutOfBookInfo( buf );
10899 if( buf[0] != '\0' ) {
10900 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
10907 i = backwardMostMove;
10911 while (i < forwardMostMove) {
10912 /* Print comments preceding this move */
10913 if (commentList[i] != NULL) {
10914 if (linelen > 0) fprintf(f, "\n");
10915 fprintf(f, "%s", commentList[i]);
10920 /* Format move number */
10922 snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
10925 snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
10927 numtext[0] = NULLCHAR;
10929 numlen = strlen(numtext);
10932 /* Print move number */
10933 blank = linelen > 0 && numlen > 0;
10934 if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
10943 fprintf(f, "%s", numtext);
10947 safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
10948 movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
10951 blank = linelen > 0 && movelen > 0;
10952 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
10961 fprintf(f, "%s", move_buffer);
10962 linelen += movelen;
10964 /* [AS] Add PV info if present */
10965 if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
10966 /* [HGM] add time */
10967 char buf[MSG_SIZ]; int seconds;
10969 seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
10975 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
10978 seconds = (seconds + 4)/10; // round to full seconds
10980 snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
10982 snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
10985 snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
10986 pvInfoList[i].score >= 0 ? "+" : "",
10987 pvInfoList[i].score / 100.0,
10988 pvInfoList[i].depth,
10991 movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
10993 /* Print score/depth */
10994 blank = linelen > 0 && movelen > 0;
10995 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
11004 fprintf(f, "%s", move_buffer);
11005 linelen += movelen;
11011 /* Start a new line */
11012 if (linelen > 0) fprintf(f, "\n");
11014 /* Print comments after last move */
11015 if (commentList[i] != NULL) {
11016 fprintf(f, "%s\n", commentList[i]);
11020 if (gameInfo.resultDetails != NULL &&
11021 gameInfo.resultDetails[0] != NULLCHAR) {
11022 fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
11023 PGNResult(gameInfo.result));
11025 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
11029 lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
11033 /* Save game in old style and close the file */
11035 SaveGameOldStyle(f)
11041 tm = time((time_t *) NULL);
11043 fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
11046 if (backwardMostMove > 0 || startedFromSetupPosition) {
11047 fprintf(f, "\n[--------------\n");
11048 PrintPosition(f, backwardMostMove);
11049 fprintf(f, "--------------]\n");
11054 i = backwardMostMove;
11055 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
11057 while (i < forwardMostMove) {
11058 if (commentList[i] != NULL) {
11059 fprintf(f, "[%s]\n", commentList[i]);
11062 if ((i % 2) == 1) {
11063 fprintf(f, "%d. ... %s\n", (i - offset)/2 + 1, parseList[i]);
11066 fprintf(f, "%d. %s ", (i - offset)/2 + 1, parseList[i]);
11068 if (commentList[i] != NULL) {
11072 if (i >= forwardMostMove) {
11076 fprintf(f, "%s\n", parseList[i]);
11081 if (commentList[i] != NULL) {
11082 fprintf(f, "[%s]\n", commentList[i]);
11085 /* This isn't really the old style, but it's close enough */
11086 if (gameInfo.resultDetails != NULL &&
11087 gameInfo.resultDetails[0] != NULLCHAR) {
11088 fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
11089 gameInfo.resultDetails);
11091 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
11098 /* Save the current game to open file f and close the file */
11100 SaveGame(f, dummy, dummy2)
11105 if (gameMode == EditPosition) EditPositionDone(TRUE);
11106 lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
11107 if (appData.oldSaveStyle)
11108 return SaveGameOldStyle(f);
11110 return SaveGamePGN(f);
11113 /* Save the current position to the given file */
11115 SavePositionToFile(filename)
11121 if (strcmp(filename, "-") == 0) {
11122 return SavePosition(stdout, 0, NULL);
11124 f = fopen(filename, "a");
11126 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
11127 DisplayError(buf, errno);
11130 SavePosition(f, 0, NULL);
11136 /* Save the current position to the given open file and close the file */
11138 SavePosition(f, dummy, dummy2)
11146 if (gameMode == EditPosition) EditPositionDone(TRUE);
11147 if (appData.oldSaveStyle) {
11148 tm = time((time_t *) NULL);
11150 fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
11152 fprintf(f, "[--------------\n");
11153 PrintPosition(f, currentMove);
11154 fprintf(f, "--------------]\n");
11156 fen = PositionToFEN(currentMove, NULL);
11157 fprintf(f, "%s\n", fen);
11165 ReloadCmailMsgEvent(unregister)
11169 static char *inFilename = NULL;
11170 static char *outFilename;
11172 struct stat inbuf, outbuf;
11175 /* Any registered moves are unregistered if unregister is set, */
11176 /* i.e. invoked by the signal handler */
11178 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
11179 cmailMoveRegistered[i] = FALSE;
11180 if (cmailCommentList[i] != NULL) {
11181 free(cmailCommentList[i]);
11182 cmailCommentList[i] = NULL;
11185 nCmailMovesRegistered = 0;
11188 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
11189 cmailResult[i] = CMAIL_NOT_RESULT;
11193 if (inFilename == NULL) {
11194 /* Because the filenames are static they only get malloced once */
11195 /* and they never get freed */
11196 inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
11197 sprintf(inFilename, "%s.game.in", appData.cmailGameName);
11199 outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
11200 sprintf(outFilename, "%s.out", appData.cmailGameName);
11203 status = stat(outFilename, &outbuf);
11205 cmailMailedMove = FALSE;
11207 status = stat(inFilename, &inbuf);
11208 cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
11211 /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
11212 counts the games, notes how each one terminated, etc.
11214 It would be nice to remove this kludge and instead gather all
11215 the information while building the game list. (And to keep it
11216 in the game list nodes instead of having a bunch of fixed-size
11217 parallel arrays.) Note this will require getting each game's
11218 termination from the PGN tags, as the game list builder does
11219 not process the game moves. --mann
11221 cmailMsgLoaded = TRUE;
11222 LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
11224 /* Load first game in the file or popup game menu */
11225 LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
11227 #endif /* !WIN32 */
11235 char string[MSG_SIZ];
11237 if ( cmailMailedMove
11238 || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
11239 return TRUE; /* Allow free viewing */
11242 /* Unregister move to ensure that we don't leave RegisterMove */
11243 /* with the move registered when the conditions for registering no */
11245 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11246 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
11247 nCmailMovesRegistered --;
11249 if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
11251 free(cmailCommentList[lastLoadGameNumber - 1]);
11252 cmailCommentList[lastLoadGameNumber - 1] = NULL;
11256 if (cmailOldMove == -1) {
11257 DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
11261 if (currentMove > cmailOldMove + 1) {
11262 DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
11266 if (currentMove < cmailOldMove) {
11267 DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
11271 if (forwardMostMove > currentMove) {
11272 /* Silently truncate extra moves */
11276 if ( (currentMove == cmailOldMove + 1)
11277 || ( (currentMove == cmailOldMove)
11278 && ( (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
11279 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
11280 if (gameInfo.result != GameUnfinished) {
11281 cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
11284 if (commentList[currentMove] != NULL) {
11285 cmailCommentList[lastLoadGameNumber - 1]
11286 = StrSave(commentList[currentMove]);
11288 safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
11290 if (appData.debugMode)
11291 fprintf(debugFP, "Saving %s for game %d\n",
11292 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
11294 snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
11296 f = fopen(string, "w");
11297 if (appData.oldSaveStyle) {
11298 SaveGameOldStyle(f); /* also closes the file */
11300 snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
11301 f = fopen(string, "w");
11302 SavePosition(f, 0, NULL); /* also closes the file */
11304 fprintf(f, "{--------------\n");
11305 PrintPosition(f, currentMove);
11306 fprintf(f, "--------------}\n\n");
11308 SaveGame(f, 0, NULL); /* also closes the file*/
11311 cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
11312 nCmailMovesRegistered ++;
11313 } else if (nCmailGames == 1) {
11314 DisplayError(_("You have not made a move yet"), 0);
11325 static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
11326 FILE *commandOutput;
11327 char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
11328 int nBytes = 0; /* Suppress warnings on uninitialized variables */
11334 if (! cmailMsgLoaded) {
11335 DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
11339 if (nCmailGames == nCmailResults) {
11340 DisplayError(_("No unfinished games"), 0);
11344 #if CMAIL_PROHIBIT_REMAIL
11345 if (cmailMailedMove) {
11346 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);
11347 DisplayError(msg, 0);
11352 if (! (cmailMailedMove || RegisterMove())) return;
11354 if ( cmailMailedMove
11355 || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
11356 snprintf(string, MSG_SIZ, partCommandString,
11357 appData.debugMode ? " -v" : "", appData.cmailGameName);
11358 commandOutput = popen(string, "r");
11360 if (commandOutput == NULL) {
11361 DisplayError(_("Failed to invoke cmail"), 0);
11363 for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
11364 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
11366 if (nBuffers > 1) {
11367 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
11368 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
11369 nBytes = MSG_SIZ - 1;
11371 (void) memcpy(msg, buffer, nBytes);
11373 *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
11375 if(StrStr(msg, "Mailed cmail message to ") != NULL) {
11376 cmailMailedMove = TRUE; /* Prevent >1 moves */
11379 for (i = 0; i < nCmailGames; i ++) {
11380 if (cmailResult[i] == CMAIL_NOT_RESULT) {
11385 && ( (arcDir = (char *) getenv("CMAIL_ARCDIR"))
11387 snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
11389 appData.cmailGameName,
11391 LoadGameFromFile(buffer, 1, buffer, FALSE);
11392 cmailMsgLoaded = FALSE;
11396 DisplayInformation(msg);
11397 pclose(commandOutput);
11400 if ((*cmailMsg) != '\0') {
11401 DisplayInformation(cmailMsg);
11406 #endif /* !WIN32 */
11415 int prependComma = 0;
11417 char string[MSG_SIZ]; /* Space for game-list */
11420 if (!cmailMsgLoaded) return "";
11422 if (cmailMailedMove) {
11423 snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
11425 /* Create a list of games left */
11426 snprintf(string, MSG_SIZ, "[");
11427 for (i = 0; i < nCmailGames; i ++) {
11428 if (! ( cmailMoveRegistered[i]
11429 || (cmailResult[i] == CMAIL_OLD_RESULT))) {
11430 if (prependComma) {
11431 snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
11433 snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
11437 strcat(string, number);
11440 strcat(string, "]");
11442 if (nCmailMovesRegistered + nCmailResults == 0) {
11443 switch (nCmailGames) {
11445 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
11449 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
11453 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
11458 switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
11460 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
11465 if (nCmailResults == nCmailGames) {
11466 snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
11468 snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
11473 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
11485 if (gameMode == Training)
11486 SetTrainingModeOff();
11489 cmailMsgLoaded = FALSE;
11490 if (appData.icsActive) {
11491 SendToICS(ics_prefix);
11492 SendToICS("refresh\n");
11502 /* Give up on clean exit */
11506 /* Keep trying for clean exit */
11510 if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
11512 if (telnetISR != NULL) {
11513 RemoveInputSource(telnetISR);
11515 if (icsPR != NoProc) {
11516 DestroyChildProcess(icsPR, TRUE);
11519 /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
11520 GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
11522 /* [HGM] crash: the above GameEnds() is a dud if another one was running */
11523 /* make sure this other one finishes before killing it! */
11524 if(endingGame) { int count = 0;
11525 if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
11526 while(endingGame && count++ < 10) DoSleep(1);
11527 if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
11530 /* Kill off chess programs */
11531 if (first.pr != NoProc) {
11534 DoSleep( appData.delayBeforeQuit );
11535 SendToProgram("quit\n", &first);
11536 DoSleep( appData.delayAfterQuit );
11537 DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
11539 if (second.pr != NoProc) {
11540 DoSleep( appData.delayBeforeQuit );
11541 SendToProgram("quit\n", &second);
11542 DoSleep( appData.delayAfterQuit );
11543 DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
11545 if (first.isr != NULL) {
11546 RemoveInputSource(first.isr);
11548 if (second.isr != NULL) {
11549 RemoveInputSource(second.isr);
11552 ShutDownFrontEnd();
11559 if (appData.debugMode)
11560 fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
11564 if (gameMode == MachinePlaysWhite ||
11565 gameMode == MachinePlaysBlack) {
11568 DisplayBothClocks();
11570 if (gameMode == PlayFromGameFile) {
11571 if (appData.timeDelay >= 0)
11572 AutoPlayGameLoop();
11573 } else if (gameMode == IcsExamining && pauseExamInvalid) {
11574 Reset(FALSE, TRUE);
11575 SendToICS(ics_prefix);
11576 SendToICS("refresh\n");
11577 } else if (currentMove < forwardMostMove) {
11578 ForwardInner(forwardMostMove);
11580 pauseExamInvalid = FALSE;
11582 switch (gameMode) {
11586 pauseExamForwardMostMove = forwardMostMove;
11587 pauseExamInvalid = FALSE;
11590 case IcsPlayingWhite:
11591 case IcsPlayingBlack:
11595 case PlayFromGameFile:
11596 (void) StopLoadGameTimer();
11600 case BeginningOfGame:
11601 if (appData.icsActive) return;
11602 /* else fall through */
11603 case MachinePlaysWhite:
11604 case MachinePlaysBlack:
11605 case TwoMachinesPlay:
11606 if (forwardMostMove == 0)
11607 return; /* don't pause if no one has moved */
11608 if ((gameMode == MachinePlaysWhite &&
11609 !WhiteOnMove(forwardMostMove)) ||
11610 (gameMode == MachinePlaysBlack &&
11611 WhiteOnMove(forwardMostMove))) {
11624 char title[MSG_SIZ];
11626 if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
11627 safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
11629 snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
11630 WhiteOnMove(currentMove - 1) ? " " : ".. ",
11631 parseList[currentMove - 1]);
11634 EditCommentPopUp(currentMove, title, commentList[currentMove]);
11641 char *tags = PGNTags(&gameInfo);
11642 EditTagsPopUp(tags);
11649 if (appData.noChessProgram || gameMode == AnalyzeMode)
11652 if (gameMode != AnalyzeFile) {
11653 if (!appData.icsEngineAnalyze) {
11655 if (gameMode != EditGame) return;
11657 ResurrectChessProgram();
11658 SendToProgram("analyze\n", &first);
11659 first.analyzing = TRUE;
11660 /*first.maybeThinking = TRUE;*/
11661 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11662 EngineOutputPopUp();
11664 if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
11669 StartAnalysisClock();
11670 GetTimeMark(&lastNodeCountTime);
11677 if (appData.noChessProgram || gameMode == AnalyzeFile)
11680 if (gameMode != AnalyzeMode) {
11682 if (gameMode != EditGame) return;
11683 ResurrectChessProgram();
11684 SendToProgram("analyze\n", &first);
11685 first.analyzing = TRUE;
11686 /*first.maybeThinking = TRUE;*/
11687 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11688 EngineOutputPopUp();
11690 gameMode = AnalyzeFile;
11695 StartAnalysisClock();
11696 GetTimeMark(&lastNodeCountTime);
11701 MachineWhiteEvent()
11704 char *bookHit = NULL;
11706 if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
11710 if (gameMode == PlayFromGameFile ||
11711 gameMode == TwoMachinesPlay ||
11712 gameMode == Training ||
11713 gameMode == AnalyzeMode ||
11714 gameMode == EndOfGame)
11717 if (gameMode == EditPosition)
11718 EditPositionDone(TRUE);
11720 if (!WhiteOnMove(currentMove)) {
11721 DisplayError(_("It is not White's turn"), 0);
11725 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
11728 if (gameMode == EditGame || gameMode == AnalyzeMode ||
11729 gameMode == AnalyzeFile)
11732 ResurrectChessProgram(); /* in case it isn't running */
11733 if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
11734 gameMode = MachinePlaysWhite;
11737 gameMode = MachinePlaysWhite;
11741 snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
11743 if (first.sendName) {
11744 snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
11745 SendToProgram(buf, &first);
11747 if (first.sendTime) {
11748 if (first.useColors) {
11749 SendToProgram("black\n", &first); /*gnu kludge*/
11751 SendTimeRemaining(&first, TRUE);
11753 if (first.useColors) {
11754 SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
11756 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
11757 SetMachineThinkingEnables();
11758 first.maybeThinking = TRUE;
11762 if (appData.autoFlipView && !flipView) {
11763 flipView = !flipView;
11764 DrawPosition(FALSE, NULL);
11765 DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;
11768 if(bookHit) { // [HGM] book: simulate book reply
11769 static char bookMove[MSG_SIZ]; // a bit generous?
11771 programStats.nodes = programStats.depth = programStats.time =
11772 programStats.score = programStats.got_only_move = 0;
11773 sprintf(programStats.movelist, "%s (xbook)", bookHit);
11775 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
11776 strcat(bookMove, bookHit);
11777 HandleMachineMove(bookMove, &first);
11782 MachineBlackEvent()
11785 char *bookHit = NULL;
11787 if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
11791 if (gameMode == PlayFromGameFile ||
11792 gameMode == TwoMachinesPlay ||
11793 gameMode == Training ||
11794 gameMode == AnalyzeMode ||
11795 gameMode == EndOfGame)
11798 if (gameMode == EditPosition)
11799 EditPositionDone(TRUE);
11801 if (WhiteOnMove(currentMove)) {
11802 DisplayError(_("It is not Black's turn"), 0);
11806 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
11809 if (gameMode == EditGame || gameMode == AnalyzeMode ||
11810 gameMode == AnalyzeFile)
11813 ResurrectChessProgram(); /* in case it isn't running */
11814 gameMode = MachinePlaysBlack;
11818 snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
11820 if (first.sendName) {
11821 snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
11822 SendToProgram(buf, &first);
11824 if (first.sendTime) {
11825 if (first.useColors) {
11826 SendToProgram("white\n", &first); /*gnu kludge*/
11828 SendTimeRemaining(&first, FALSE);
11830 if (first.useColors) {
11831 SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
11833 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
11834 SetMachineThinkingEnables();
11835 first.maybeThinking = TRUE;
11838 if (appData.autoFlipView && flipView) {
11839 flipView = !flipView;
11840 DrawPosition(FALSE, NULL);
11841 DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;
11843 if(bookHit) { // [HGM] book: simulate book reply
11844 static char bookMove[MSG_SIZ]; // a bit generous?
11846 programStats.nodes = programStats.depth = programStats.time =
11847 programStats.score = programStats.got_only_move = 0;
11848 sprintf(programStats.movelist, "%s (xbook)", bookHit);
11850 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
11851 strcat(bookMove, bookHit);
11852 HandleMachineMove(bookMove, &first);
11858 DisplayTwoMachinesTitle()
11861 if (appData.matchGames > 0) {
11862 if (first.twoMachinesColor[0] == 'w') {
11863 snprintf(buf, MSG_SIZ, "%s vs. %s (%d-%d-%d)",
11864 gameInfo.white, gameInfo.black,
11865 first.matchWins, second.matchWins,
11866 matchGame - 1 - (first.matchWins + second.matchWins));
11868 snprintf(buf, MSG_SIZ, "%s vs. %s (%d-%d-%d)",
11869 gameInfo.white, gameInfo.black,
11870 second.matchWins, first.matchWins,
11871 matchGame - 1 - (first.matchWins + second.matchWins));
11874 snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
11880 SettingsMenuIfReady()
11882 if (second.lastPing != second.lastPong) {
11883 DisplayMessage("", _("Waiting for second chess program"));
11884 ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
11888 DisplayMessage("", "");
11889 SettingsPopUp(&second);
11893 WaitForSecond(DelayedEventCallback retry)
11895 if (second.pr == NULL) {
11896 StartChessProgram(&second);
11897 if (second.protocolVersion == 1) {
11900 /* kludge: allow timeout for initial "feature" command */
11902 DisplayMessage("", _("Starting second chess program"));
11903 ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
11911 TwoMachinesEvent P((void))
11915 ChessProgramState *onmove;
11916 char *bookHit = NULL;
11918 if (appData.noChessProgram) return;
11920 switch (gameMode) {
11921 case TwoMachinesPlay:
11923 case MachinePlaysWhite:
11924 case MachinePlaysBlack:
11925 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
11926 DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
11930 case BeginningOfGame:
11931 case PlayFromGameFile:
11934 if (gameMode != EditGame) return;
11937 EditPositionDone(TRUE);
11948 // forwardMostMove = currentMove;
11949 TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
11950 ResurrectChessProgram(); /* in case first program isn't running */
11952 if(WaitForSecond(TwoMachinesEventIfReady)) return;
11953 DisplayMessage("", "");
11954 InitChessProgram(&second, FALSE);
11955 SendToProgram("force\n", &second);
11956 if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
11957 ScheduleDelayedEvent(TwoMachinesEvent, 10);
11960 if (startedFromSetupPosition) {
11961 SendBoard(&second, backwardMostMove);
11962 if (appData.debugMode) {
11963 fprintf(debugFP, "Two Machines\n");
11966 for (i = backwardMostMove; i < forwardMostMove; i++) {
11967 SendMoveToProgram(i, &second);
11970 gameMode = TwoMachinesPlay;
11974 DisplayTwoMachinesTitle();
11976 if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
11982 SendToProgram(first.computerString, &first);
11983 if (first.sendName) {
11984 snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
11985 SendToProgram(buf, &first);
11987 SendToProgram(second.computerString, &second);
11988 if (second.sendName) {
11989 snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
11990 SendToProgram(buf, &second);
11994 if (!first.sendTime || !second.sendTime) {
11995 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11996 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11998 if (onmove->sendTime) {
11999 if (onmove->useColors) {
12000 SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
12002 SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
12004 if (onmove->useColors) {
12005 SendToProgram(onmove->twoMachinesColor, onmove);
12007 bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
12008 // SendToProgram("go\n", onmove);
12009 onmove->maybeThinking = TRUE;
12010 SetMachineThinkingEnables();
12014 if(bookHit) { // [HGM] book: simulate book reply
12015 static char bookMove[MSG_SIZ]; // a bit generous?
12017 programStats.nodes = programStats.depth = programStats.time =
12018 programStats.score = programStats.got_only_move = 0;
12019 sprintf(programStats.movelist, "%s (xbook)", bookHit);
12021 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
12022 strcat(bookMove, bookHit);
12023 savedMessage = bookMove; // args for deferred call
12024 savedState = onmove;
12025 ScheduleDelayedEvent(DeferredBookMove, 1);
12032 if (gameMode == Training) {
12033 SetTrainingModeOff();
12034 gameMode = PlayFromGameFile;
12035 DisplayMessage("", _("Training mode off"));
12037 gameMode = Training;
12038 animateTraining = appData.animate;
12040 /* make sure we are not already at the end of the game */
12041 if (currentMove < forwardMostMove) {
12042 SetTrainingModeOn();
12043 DisplayMessage("", _("Training mode on"));
12045 gameMode = PlayFromGameFile;
12046 DisplayError(_("Already at end of game"), 0);
12055 if (!appData.icsActive) return;
12056 switch (gameMode) {
12057 case IcsPlayingWhite:
12058 case IcsPlayingBlack:
12061 case BeginningOfGame:
12069 EditPositionDone(TRUE);
12082 gameMode = IcsIdle;
12093 switch (gameMode) {
12095 SetTrainingModeOff();
12097 case MachinePlaysWhite:
12098 case MachinePlaysBlack:
12099 case BeginningOfGame:
12100 SendToProgram("force\n", &first);
12101 SetUserThinkingEnables();
12103 case PlayFromGameFile:
12104 (void) StopLoadGameTimer();
12105 if (gameFileFP != NULL) {
12110 EditPositionDone(TRUE);
12115 SendToProgram("force\n", &first);
12117 case TwoMachinesPlay:
12118 GameEnds(EndOfFile, NULL, GE_PLAYER);
12119 ResurrectChessProgram();
12120 SetUserThinkingEnables();
12123 ResurrectChessProgram();
12125 case IcsPlayingBlack:
12126 case IcsPlayingWhite:
12127 DisplayError(_("Warning: You are still playing a game"), 0);
12130 DisplayError(_("Warning: You are still observing a game"), 0);
12133 DisplayError(_("Warning: You are still examining a game"), 0);
12144 first.offeredDraw = second.offeredDraw = 0;
12146 if (gameMode == PlayFromGameFile) {
12147 whiteTimeRemaining = timeRemaining[0][currentMove];
12148 blackTimeRemaining = timeRemaining[1][currentMove];
12152 if (gameMode == MachinePlaysWhite ||
12153 gameMode == MachinePlaysBlack ||
12154 gameMode == TwoMachinesPlay ||
12155 gameMode == EndOfGame) {
12156 i = forwardMostMove;
12157 while (i > currentMove) {
12158 SendToProgram("undo\n", &first);
12161 whiteTimeRemaining = timeRemaining[0][currentMove];
12162 blackTimeRemaining = timeRemaining[1][currentMove];
12163 DisplayBothClocks();
12164 if (whiteFlag || blackFlag) {
12165 whiteFlag = blackFlag = 0;
12170 gameMode = EditGame;
12177 EditPositionEvent()
12179 if (gameMode == EditPosition) {
12185 if (gameMode != EditGame) return;
12187 gameMode = EditPosition;
12190 if (currentMove > 0)
12191 CopyBoard(boards[0], boards[currentMove]);
12193 blackPlaysFirst = !WhiteOnMove(currentMove);
12195 currentMove = forwardMostMove = backwardMostMove = 0;
12196 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12203 /* [DM] icsEngineAnalyze - possible call from other functions */
12204 if (appData.icsEngineAnalyze) {
12205 appData.icsEngineAnalyze = FALSE;
12207 DisplayMessage("",_("Close ICS engine analyze..."));
12209 if (first.analysisSupport && first.analyzing) {
12210 SendToProgram("exit\n", &first);
12211 first.analyzing = FALSE;
12213 thinkOutput[0] = NULLCHAR;
12217 EditPositionDone(Boolean fakeRights)
12219 int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
12221 startedFromSetupPosition = TRUE;
12222 InitChessProgram(&first, FALSE);
12223 if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
12224 boards[0][EP_STATUS] = EP_NONE;
12225 boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
12226 if(boards[0][0][BOARD_WIDTH>>1] == king) {
12227 boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : NoRights;
12228 boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
12229 } else boards[0][CASTLING][2] = NoRights;
12230 if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
12231 boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : NoRights;
12232 boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
12233 } else boards[0][CASTLING][5] = NoRights;
12235 SendToProgram("force\n", &first);
12236 if (blackPlaysFirst) {
12237 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12238 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12239 currentMove = forwardMostMove = backwardMostMove = 1;
12240 CopyBoard(boards[1], boards[0]);
12242 currentMove = forwardMostMove = backwardMostMove = 0;
12244 SendBoard(&first, forwardMostMove);
12245 if (appData.debugMode) {
12246 fprintf(debugFP, "EditPosDone\n");
12249 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12250 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12251 gameMode = EditGame;
12253 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12254 ClearHighlights(); /* [AS] */
12257 /* Pause for `ms' milliseconds */
12258 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
12268 } while (SubtractTimeMarks(&m2, &m1) < ms);
12271 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
12273 SendMultiLineToICS(buf)
12276 char temp[MSG_SIZ+1], *p;
12283 strncpy(temp, buf, len);
12288 if (*p == '\n' || *p == '\r')
12293 strcat(temp, "\n");
12295 SendToPlayer(temp, strlen(temp));
12299 SetWhiteToPlayEvent()
12301 if (gameMode == EditPosition) {
12302 blackPlaysFirst = FALSE;
12303 DisplayBothClocks(); /* works because currentMove is 0 */
12304 } else if (gameMode == IcsExamining) {
12305 SendToICS(ics_prefix);
12306 SendToICS("tomove white\n");
12311 SetBlackToPlayEvent()
12313 if (gameMode == EditPosition) {
12314 blackPlaysFirst = TRUE;
12315 currentMove = 1; /* kludge */
12316 DisplayBothClocks();
12318 } else if (gameMode == IcsExamining) {
12319 SendToICS(ics_prefix);
12320 SendToICS("tomove black\n");
12325 EditPositionMenuEvent(selection, x, y)
12326 ChessSquare selection;
12330 ChessSquare piece = boards[0][y][x];
12332 if (gameMode != EditPosition && gameMode != IcsExamining) return;
12334 switch (selection) {
12336 if (gameMode == IcsExamining && ics_type == ICS_FICS) {
12337 SendToICS(ics_prefix);
12338 SendToICS("bsetup clear\n");
12339 } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
12340 SendToICS(ics_prefix);
12341 SendToICS("clearboard\n");
12343 for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
12344 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
12345 for (y = 0; y < BOARD_HEIGHT; y++) {
12346 if (gameMode == IcsExamining) {
12347 if (boards[currentMove][y][x] != EmptySquare) {
12348 snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
12353 boards[0][y][x] = p;
12358 if (gameMode == EditPosition) {
12359 DrawPosition(FALSE, boards[0]);
12364 SetWhiteToPlayEvent();
12368 SetBlackToPlayEvent();
12372 if (gameMode == IcsExamining) {
12373 if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
12374 snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
12377 if(x < BOARD_LEFT || x >= BOARD_RGHT) {
12378 if(x == BOARD_LEFT-2) {
12379 if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
12380 boards[0][y][1] = 0;
12382 if(x == BOARD_RGHT+1) {
12383 if(y >= gameInfo.holdingsSize) break;
12384 boards[0][y][BOARD_WIDTH-2] = 0;
12387 boards[0][y][x] = EmptySquare;
12388 DrawPosition(FALSE, boards[0]);
12393 if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
12394 piece >= (int)BlackPawn && piece < (int)BlackMan ) {
12395 selection = (ChessSquare) (PROMOTED piece);
12396 } else if(piece == EmptySquare) selection = WhiteSilver;
12397 else selection = (ChessSquare)((int)piece - 1);
12401 if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
12402 piece > (int)BlackMan && piece <= (int)BlackKing ) {
12403 selection = (ChessSquare) (DEMOTED piece);
12404 } else if(piece == EmptySquare) selection = BlackSilver;
12405 else selection = (ChessSquare)((int)piece + 1);
12410 if(gameInfo.variant == VariantShatranj ||
12411 gameInfo.variant == VariantXiangqi ||
12412 gameInfo.variant == VariantCourier ||
12413 gameInfo.variant == VariantMakruk )
12414 selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
12419 if(gameInfo.variant == VariantXiangqi)
12420 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
12421 if(gameInfo.variant == VariantKnightmate)
12422 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
12425 if (gameMode == IcsExamining) {
12426 if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
12427 snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
12428 PieceToChar(selection), AAA + x, ONE + y);
12431 if(x < BOARD_LEFT || x >= BOARD_RGHT) {
12433 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
12434 n = PieceToNumber(selection - BlackPawn);
12435 if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
12436 boards[0][BOARD_HEIGHT-1-n][0] = selection;
12437 boards[0][BOARD_HEIGHT-1-n][1]++;
12439 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
12440 n = PieceToNumber(selection);
12441 if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
12442 boards[0][n][BOARD_WIDTH-1] = selection;
12443 boards[0][n][BOARD_WIDTH-2]++;
12446 boards[0][y][x] = selection;
12447 DrawPosition(TRUE, boards[0]);
12455 DropMenuEvent(selection, x, y)
12456 ChessSquare selection;
12459 ChessMove moveType;
12461 switch (gameMode) {
12462 case IcsPlayingWhite:
12463 case MachinePlaysBlack:
12464 if (!WhiteOnMove(currentMove)) {
12465 DisplayMoveError(_("It is Black's turn"));
12468 moveType = WhiteDrop;
12470 case IcsPlayingBlack:
12471 case MachinePlaysWhite:
12472 if (WhiteOnMove(currentMove)) {
12473 DisplayMoveError(_("It is White's turn"));
12476 moveType = BlackDrop;
12479 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
12485 if (moveType == BlackDrop && selection < BlackPawn) {
12486 selection = (ChessSquare) ((int) selection
12487 + (int) BlackPawn - (int) WhitePawn);
12489 if (boards[currentMove][y][x] != EmptySquare) {
12490 DisplayMoveError(_("That square is occupied"));
12494 FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
12500 /* Accept a pending offer of any kind from opponent */
12502 if (appData.icsActive) {
12503 SendToICS(ics_prefix);
12504 SendToICS("accept\n");
12505 } else if (cmailMsgLoaded) {
12506 if (currentMove == cmailOldMove &&
12507 commentList[cmailOldMove] != NULL &&
12508 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12509 "Black offers a draw" : "White offers a draw")) {
12511 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
12512 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
12514 DisplayError(_("There is no pending offer on this move"), 0);
12515 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12518 /* Not used for offers from chess program */
12525 /* Decline a pending offer of any kind from opponent */
12527 if (appData.icsActive) {
12528 SendToICS(ics_prefix);
12529 SendToICS("decline\n");
12530 } else if (cmailMsgLoaded) {
12531 if (currentMove == cmailOldMove &&
12532 commentList[cmailOldMove] != NULL &&
12533 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12534 "Black offers a draw" : "White offers a draw")) {
12536 AppendComment(cmailOldMove, "Draw declined", TRUE);
12537 DisplayComment(cmailOldMove - 1, "Draw declined");
12540 DisplayError(_("There is no pending offer on this move"), 0);
12543 /* Not used for offers from chess program */
12550 /* Issue ICS rematch command */
12551 if (appData.icsActive) {
12552 SendToICS(ics_prefix);
12553 SendToICS("rematch\n");
12560 /* Call your opponent's flag (claim a win on time) */
12561 if (appData.icsActive) {
12562 SendToICS(ics_prefix);
12563 SendToICS("flag\n");
12565 switch (gameMode) {
12568 case MachinePlaysWhite:
12571 GameEnds(GameIsDrawn, "Both players ran out of time",
12574 GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
12576 DisplayError(_("Your opponent is not out of time"), 0);
12579 case MachinePlaysBlack:
12582 GameEnds(GameIsDrawn, "Both players ran out of time",
12585 GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
12587 DisplayError(_("Your opponent is not out of time"), 0);
12597 /* Offer draw or accept pending draw offer from opponent */
12599 if (appData.icsActive) {
12600 /* Note: tournament rules require draw offers to be
12601 made after you make your move but before you punch
12602 your clock. Currently ICS doesn't let you do that;
12603 instead, you immediately punch your clock after making
12604 a move, but you can offer a draw at any time. */
12606 SendToICS(ics_prefix);
12607 SendToICS("draw\n");
12608 userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
12609 } else if (cmailMsgLoaded) {
12610 if (currentMove == cmailOldMove &&
12611 commentList[cmailOldMove] != NULL &&
12612 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12613 "Black offers a draw" : "White offers a draw")) {
12614 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
12615 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
12616 } else if (currentMove == cmailOldMove + 1) {
12617 char *offer = WhiteOnMove(cmailOldMove) ?
12618 "White offers a draw" : "Black offers a draw";
12619 AppendComment(currentMove, offer, TRUE);
12620 DisplayComment(currentMove - 1, offer);
12621 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
12623 DisplayError(_("You must make your move before offering a draw"), 0);
12624 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12626 } else if (first.offeredDraw) {
12627 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
12629 if (first.sendDrawOffers) {
12630 SendToProgram("draw\n", &first);
12631 userOfferedDraw = TRUE;
12639 /* Offer Adjourn or accept pending Adjourn offer from opponent */
12641 if (appData.icsActive) {
12642 SendToICS(ics_prefix);
12643 SendToICS("adjourn\n");
12645 /* Currently GNU Chess doesn't offer or accept Adjourns */
12653 /* Offer Abort or accept pending Abort offer from opponent */
12655 if (appData.icsActive) {
12656 SendToICS(ics_prefix);
12657 SendToICS("abort\n");
12659 GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
12666 /* Resign. You can do this even if it's not your turn. */
12668 if (appData.icsActive) {
12669 SendToICS(ics_prefix);
12670 SendToICS("resign\n");
12672 switch (gameMode) {
12673 case MachinePlaysWhite:
12674 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12676 case MachinePlaysBlack:
12677 GameEnds(BlackWins, "White resigns", GE_PLAYER);
12680 if (cmailMsgLoaded) {
12682 if (WhiteOnMove(cmailOldMove)) {
12683 GameEnds(BlackWins, "White resigns", GE_PLAYER);
12685 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12687 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
12698 StopObservingEvent()
12700 /* Stop observing current games */
12701 SendToICS(ics_prefix);
12702 SendToICS("unobserve\n");
12706 StopExaminingEvent()
12708 /* Stop observing current game */
12709 SendToICS(ics_prefix);
12710 SendToICS("unexamine\n");
12714 ForwardInner(target)
12719 if (appData.debugMode)
12720 fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
12721 target, currentMove, forwardMostMove);
12723 if (gameMode == EditPosition)
12726 if (gameMode == PlayFromGameFile && !pausing)
12729 if (gameMode == IcsExamining && pausing)
12730 limit = pauseExamForwardMostMove;
12732 limit = forwardMostMove;
12734 if (target > limit) target = limit;
12736 if (target > 0 && moveList[target - 1][0]) {
12737 int fromX, fromY, toX, toY;
12738 toX = moveList[target - 1][2] - AAA;
12739 toY = moveList[target - 1][3] - ONE;
12740 if (moveList[target - 1][1] == '@') {
12741 if (appData.highlightLastMove) {
12742 SetHighlights(-1, -1, toX, toY);
12745 fromX = moveList[target - 1][0] - AAA;
12746 fromY = moveList[target - 1][1] - ONE;
12747 if (target == currentMove + 1) {
12748 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
12750 if (appData.highlightLastMove) {
12751 SetHighlights(fromX, fromY, toX, toY);
12755 if (gameMode == EditGame || gameMode == AnalyzeMode ||
12756 gameMode == Training || gameMode == PlayFromGameFile ||
12757 gameMode == AnalyzeFile) {
12758 while (currentMove < target) {
12759 SendMoveToProgram(currentMove++, &first);
12762 currentMove = target;
12765 if (gameMode == EditGame || gameMode == EndOfGame) {
12766 whiteTimeRemaining = timeRemaining[0][currentMove];
12767 blackTimeRemaining = timeRemaining[1][currentMove];
12769 DisplayBothClocks();
12770 DisplayMove(currentMove - 1);
12771 DrawPosition(FALSE, boards[currentMove]);
12772 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
12773 if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
12774 DisplayComment(currentMove - 1, commentList[currentMove]);
12782 if (gameMode == IcsExamining && !pausing) {
12783 SendToICS(ics_prefix);
12784 SendToICS("forward\n");
12786 ForwardInner(currentMove + 1);
12793 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12794 /* to optimze, we temporarily turn off analysis mode while we feed
12795 * the remaining moves to the engine. Otherwise we get analysis output
12798 if (first.analysisSupport) {
12799 SendToProgram("exit\nforce\n", &first);
12800 first.analyzing = FALSE;
12804 if (gameMode == IcsExamining && !pausing) {
12805 SendToICS(ics_prefix);
12806 SendToICS("forward 999999\n");
12808 ForwardInner(forwardMostMove);
12811 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12812 /* we have fed all the moves, so reactivate analysis mode */
12813 SendToProgram("analyze\n", &first);
12814 first.analyzing = TRUE;
12815 /*first.maybeThinking = TRUE;*/
12816 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12821 BackwardInner(target)
12824 int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
12826 if (appData.debugMode)
12827 fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
12828 target, currentMove, forwardMostMove);
12830 if (gameMode == EditPosition) return;
12831 if (currentMove <= backwardMostMove) {
12833 DrawPosition(full_redraw, boards[currentMove]);
12836 if (gameMode == PlayFromGameFile && !pausing)
12839 if (moveList[target][0]) {
12840 int fromX, fromY, toX, toY;
12841 toX = moveList[target][2] - AAA;
12842 toY = moveList[target][3] - ONE;
12843 if (moveList[target][1] == '@') {
12844 if (appData.highlightLastMove) {
12845 SetHighlights(-1, -1, toX, toY);
12848 fromX = moveList[target][0] - AAA;
12849 fromY = moveList[target][1] - ONE;
12850 if (target == currentMove - 1) {
12851 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
12853 if (appData.highlightLastMove) {
12854 SetHighlights(fromX, fromY, toX, toY);
12858 if (gameMode == EditGame || gameMode==AnalyzeMode ||
12859 gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
12860 while (currentMove > target) {
12861 SendToProgram("undo\n", &first);
12865 currentMove = target;
12868 if (gameMode == EditGame || gameMode == EndOfGame) {
12869 whiteTimeRemaining = timeRemaining[0][currentMove];
12870 blackTimeRemaining = timeRemaining[1][currentMove];
12872 DisplayBothClocks();
12873 DisplayMove(currentMove - 1);
12874 DrawPosition(full_redraw, boards[currentMove]);
12875 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
12876 // [HGM] PV info: routine tests if comment empty
12877 DisplayComment(currentMove - 1, commentList[currentMove]);
12883 if (gameMode == IcsExamining && !pausing) {
12884 SendToICS(ics_prefix);
12885 SendToICS("backward\n");
12887 BackwardInner(currentMove - 1);
12894 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12895 /* to optimize, we temporarily turn off analysis mode while we undo
12896 * all the moves. Otherwise we get analysis output after each undo.
12898 if (first.analysisSupport) {
12899 SendToProgram("exit\nforce\n", &first);
12900 first.analyzing = FALSE;
12904 if (gameMode == IcsExamining && !pausing) {
12905 SendToICS(ics_prefix);
12906 SendToICS("backward 999999\n");
12908 BackwardInner(backwardMostMove);
12911 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12912 /* we have fed all the moves, so reactivate analysis mode */
12913 SendToProgram("analyze\n", &first);
12914 first.analyzing = TRUE;
12915 /*first.maybeThinking = TRUE;*/
12916 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12923 if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
12924 if (to >= forwardMostMove) to = forwardMostMove;
12925 if (to <= backwardMostMove) to = backwardMostMove;
12926 if (to < currentMove) {
12934 RevertEvent(Boolean annotate)
12936 if(PopTail(annotate)) { // [HGM] vari: restore old game tail
12939 if (gameMode != IcsExamining) {
12940 DisplayError(_("You are not examining a game"), 0);
12944 DisplayError(_("You can't revert while pausing"), 0);
12947 SendToICS(ics_prefix);
12948 SendToICS("revert\n");
12954 switch (gameMode) {
12955 case MachinePlaysWhite:
12956 case MachinePlaysBlack:
12957 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
12958 DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
12961 if (forwardMostMove < 2) return;
12962 currentMove = forwardMostMove = forwardMostMove - 2;
12963 whiteTimeRemaining = timeRemaining[0][currentMove];
12964 blackTimeRemaining = timeRemaining[1][currentMove];
12965 DisplayBothClocks();
12966 DisplayMove(currentMove - 1);
12967 ClearHighlights();/*!! could figure this out*/
12968 DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
12969 SendToProgram("remove\n", &first);
12970 /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
12973 case BeginningOfGame:
12977 case IcsPlayingWhite:
12978 case IcsPlayingBlack:
12979 if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
12980 SendToICS(ics_prefix);
12981 SendToICS("takeback 2\n");
12983 SendToICS(ics_prefix);
12984 SendToICS("takeback 1\n");
12993 ChessProgramState *cps;
12995 switch (gameMode) {
12996 case MachinePlaysWhite:
12997 if (!WhiteOnMove(forwardMostMove)) {
12998 DisplayError(_("It is your turn"), 0);
13003 case MachinePlaysBlack:
13004 if (WhiteOnMove(forwardMostMove)) {
13005 DisplayError(_("It is your turn"), 0);
13010 case TwoMachinesPlay:
13011 if (WhiteOnMove(forwardMostMove) ==
13012 (first.twoMachinesColor[0] == 'w')) {
13018 case BeginningOfGame:
13022 SendToProgram("?\n", cps);
13026 TruncateGameEvent()
13029 if (gameMode != EditGame) return;
13036 CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
13037 if (forwardMostMove > currentMove) {
13038 if (gameInfo.resultDetails != NULL) {
13039 free(gameInfo.resultDetails);
13040 gameInfo.resultDetails = NULL;
13041 gameInfo.result = GameUnfinished;
13043 forwardMostMove = currentMove;
13044 HistorySet(parseList, backwardMostMove, forwardMostMove,
13052 if (appData.noChessProgram) return;
13053 switch (gameMode) {
13054 case MachinePlaysWhite:
13055 if (WhiteOnMove(forwardMostMove)) {
13056 DisplayError(_("Wait until your turn"), 0);
13060 case BeginningOfGame:
13061 case MachinePlaysBlack:
13062 if (!WhiteOnMove(forwardMostMove)) {
13063 DisplayError(_("Wait until your turn"), 0);
13068 DisplayError(_("No hint available"), 0);
13071 SendToProgram("hint\n", &first);
13072 hintRequested = TRUE;
13078 if (appData.noChessProgram) return;
13079 switch (gameMode) {
13080 case MachinePlaysWhite:
13081 if (WhiteOnMove(forwardMostMove)) {
13082 DisplayError(_("Wait until your turn"), 0);
13086 case BeginningOfGame:
13087 case MachinePlaysBlack:
13088 if (!WhiteOnMove(forwardMostMove)) {
13089 DisplayError(_("Wait until your turn"), 0);
13094 EditPositionDone(TRUE);
13096 case TwoMachinesPlay:
13101 SendToProgram("bk\n", &first);
13102 bookOutput[0] = NULLCHAR;
13103 bookRequested = TRUE;
13109 char *tags = PGNTags(&gameInfo);
13110 TagsPopUp(tags, CmailMsg());
13114 /* end button procedures */
13117 PrintPosition(fp, move)
13123 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13124 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
13125 char c = PieceToChar(boards[move][i][j]);
13126 fputc(c == 'x' ? '.' : c, fp);
13127 fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
13130 if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
13131 fprintf(fp, "white to play\n");
13133 fprintf(fp, "black to play\n");
13140 if (gameInfo.white != NULL) {
13141 fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
13147 /* Find last component of program's own name, using some heuristics */
13149 TidyProgramName(prog, host, buf)
13150 char *prog, *host, buf[MSG_SIZ];
13153 int local = (strcmp(host, "localhost") == 0);
13154 while (!local && (p = strchr(prog, ';')) != NULL) {
13156 while (*p == ' ') p++;
13159 if (*prog == '"' || *prog == '\'') {
13160 q = strchr(prog + 1, *prog);
13162 q = strchr(prog, ' ');
13164 if (q == NULL) q = prog + strlen(prog);
13166 while (p >= prog && *p != '/' && *p != '\\') p--;
13168 if(p == prog && *p == '"') p++;
13169 if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
13170 memcpy(buf, p, q - p);
13171 buf[q - p] = NULLCHAR;
13179 TimeControlTagValue()
13182 if (!appData.clockMode) {
13183 safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
13184 } else if (movesPerSession > 0) {
13185 snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
13186 } else if (timeIncrement == 0) {
13187 snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
13189 snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
13191 return StrSave(buf);
13197 /* This routine is used only for certain modes */
13198 VariantClass v = gameInfo.variant;
13199 ChessMove r = GameUnfinished;
13202 if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
13203 r = gameInfo.result;
13204 p = gameInfo.resultDetails;
13205 gameInfo.resultDetails = NULL;
13207 ClearGameInfo(&gameInfo);
13208 gameInfo.variant = v;
13210 switch (gameMode) {
13211 case MachinePlaysWhite:
13212 gameInfo.event = StrSave( appData.pgnEventHeader );
13213 gameInfo.site = StrSave(HostName());
13214 gameInfo.date = PGNDate();
13215 gameInfo.round = StrSave("-");
13216 gameInfo.white = StrSave(first.tidy);
13217 gameInfo.black = StrSave(UserName());
13218 gameInfo.timeControl = TimeControlTagValue();
13221 case MachinePlaysBlack:
13222 gameInfo.event = StrSave( appData.pgnEventHeader );
13223 gameInfo.site = StrSave(HostName());
13224 gameInfo.date = PGNDate();
13225 gameInfo.round = StrSave("-");
13226 gameInfo.white = StrSave(UserName());
13227 gameInfo.black = StrSave(first.tidy);
13228 gameInfo.timeControl = TimeControlTagValue();
13231 case TwoMachinesPlay:
13232 gameInfo.event = StrSave( appData.pgnEventHeader );
13233 gameInfo.site = StrSave(HostName());
13234 gameInfo.date = PGNDate();
13235 if (matchGame > 0) {
13237 snprintf(buf, MSG_SIZ, "%d", matchGame);
13238 gameInfo.round = StrSave(buf);
13240 gameInfo.round = StrSave("-");
13242 if (first.twoMachinesColor[0] == 'w') {
13243 gameInfo.white = StrSave(first.tidy);
13244 gameInfo.black = StrSave(second.tidy);
13246 gameInfo.white = StrSave(second.tidy);
13247 gameInfo.black = StrSave(first.tidy);
13249 gameInfo.timeControl = TimeControlTagValue();
13253 gameInfo.event = StrSave("Edited game");
13254 gameInfo.site = StrSave(HostName());
13255 gameInfo.date = PGNDate();
13256 gameInfo.round = StrSave("-");
13257 gameInfo.white = StrSave("-");
13258 gameInfo.black = StrSave("-");
13259 gameInfo.result = r;
13260 gameInfo.resultDetails = p;
13264 gameInfo.event = StrSave("Edited position");
13265 gameInfo.site = StrSave(HostName());
13266 gameInfo.date = PGNDate();
13267 gameInfo.round = StrSave("-");
13268 gameInfo.white = StrSave("-");
13269 gameInfo.black = StrSave("-");
13272 case IcsPlayingWhite:
13273 case IcsPlayingBlack:
13278 case PlayFromGameFile:
13279 gameInfo.event = StrSave("Game from non-PGN file");
13280 gameInfo.site = StrSave(HostName());
13281 gameInfo.date = PGNDate();
13282 gameInfo.round = StrSave("-");
13283 gameInfo.white = StrSave("?");
13284 gameInfo.black = StrSave("?");
13293 ReplaceComment(index, text)
13301 if(index && sscanf(text, "%f/%d", &score, &len) == 2 &&
13302 pvInfoList[index-1].depth == len &&
13303 pvInfoList[index-1].score == (int) (score*100 + 0.5) &&
13304 (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
13305 while (*text == '\n') text++;
13306 len = strlen(text);
13307 while (len > 0 && text[len - 1] == '\n') len--;
13309 if (commentList[index] != NULL)
13310 free(commentList[index]);
13313 commentList[index] = NULL;
13316 if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
13317 *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
13318 *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
13319 commentList[index] = (char *) malloc(len + 2);
13320 strncpy(commentList[index], text, len);
13321 commentList[index][len] = '\n';
13322 commentList[index][len + 1] = NULLCHAR;
13324 // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
13326 commentList[index] = (char *) malloc(len + 7);
13327 safeStrCpy(commentList[index], "{\n", 3);
13328 safeStrCpy(commentList[index]+2, text, len+1);
13329 commentList[index][len+2] = NULLCHAR;
13330 while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
13331 strcat(commentList[index], "\n}\n");
13345 if (ch == '\r') continue;
13347 } while (ch != '\0');
13351 AppendComment(index, text, addBraces)
13354 Boolean addBraces; // [HGM] braces: tells if we should add {}
13359 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
13360 text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
13363 while (*text == '\n') text++;
13364 len = strlen(text);
13365 while (len > 0 && text[len - 1] == '\n') len--;
13367 if (len == 0) return;
13369 if (commentList[index] != NULL) {
13370 old = commentList[index];
13371 oldlen = strlen(old);
13372 while(commentList[index][oldlen-1] == '\n')
13373 commentList[index][--oldlen] = NULLCHAR;
13374 commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
13375 safeStrCpy(commentList[index], old, oldlen + len + 6);
13377 // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
13378 if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
13379 if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
13380 while (*text == '\n') { text++; len--; }
13381 commentList[index][--oldlen] = NULLCHAR;
13383 if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
13384 else strcat(commentList[index], "\n");
13385 strcat(commentList[index], text);
13386 if(addBraces) strcat(commentList[index], addBraces == 2 ? ")\n" : "\n}\n");
13387 else strcat(commentList[index], "\n");
13389 commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
13391 safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
13392 else commentList[index][0] = NULLCHAR;
13393 strcat(commentList[index], text);
13394 strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
13395 if(addBraces == TRUE) strcat(commentList[index], "}\n");
13399 static char * FindStr( char * text, char * sub_text )
13401 char * result = strstr( text, sub_text );
13403 if( result != NULL ) {
13404 result += strlen( sub_text );
13410 /* [AS] Try to extract PV info from PGN comment */
13411 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
13412 char *GetInfoFromComment( int index, char * text )
13416 if( text != NULL && index > 0 ) {
13419 int time = -1, sec = 0, deci;
13420 char * s_eval = FindStr( text, "[%eval " );
13421 char * s_emt = FindStr( text, "[%emt " );
13423 if( s_eval != NULL || s_emt != NULL ) {
13427 if( s_eval != NULL ) {
13428 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
13432 if( delim != ']' ) {
13437 if( s_emt != NULL ) {
13442 /* We expect something like: [+|-]nnn.nn/dd */
13445 if(*text != '{') return text; // [HGM] braces: must be normal comment
13447 sep = strchr( text, '/' );
13448 if( sep == NULL || sep < (text+4) ) {
13452 time = -1; sec = -1; deci = -1;
13453 if( sscanf( text+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
13454 sscanf( text+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
13455 sscanf( text+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
13456 sscanf( text+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3 ) {
13460 if( score_lo < 0 || score_lo >= 100 ) {
13464 if(sec >= 0) time = 600*time + 10*sec; else
13465 if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
13467 score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
13469 /* [HGM] PV time: now locate end of PV info */
13470 while( *++sep >= '0' && *sep <= '9'); // strip depth
13472 while( *++sep >= '0' && *sep <= '9'); // strip time
13474 while( *++sep >= '0' && *sep <= '9'); // strip seconds
13476 while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
13477 while(*sep == ' ') sep++;
13488 pvInfoList[index-1].depth = depth;
13489 pvInfoList[index-1].score = score;
13490 pvInfoList[index-1].time = 10*time; // centi-sec
13491 if(*sep == '}') *sep = 0; else *--sep = '{';
13497 SendToProgram(message, cps)
13499 ChessProgramState *cps;
13501 int count, outCount, error;
13504 if (cps->pr == NULL) return;
13507 if (appData.debugMode) {
13510 fprintf(debugFP, "%ld >%-6s: %s",
13511 SubtractTimeMarks(&now, &programStartTime),
13512 cps->which, message);
13515 count = strlen(message);
13516 outCount = OutputToProcess(cps->pr, message, count, &error);
13517 if (outCount < count && !exiting
13518 && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
13519 snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), cps->which);
13520 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
13521 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
13522 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
13523 snprintf(buf, MSG_SIZ, "%s program exits in draw position (%s)", cps->which, cps->program);
13525 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
13527 gameInfo.resultDetails = StrSave(buf);
13529 if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
13534 ReceiveFromProgram(isr, closure, message, count, error)
13535 InputSourceRef isr;
13543 ChessProgramState *cps = (ChessProgramState *)closure;
13545 if (isr != cps->isr) return; /* Killed intentionally */
13548 snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
13549 cps->which, cps->program);
13550 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
13551 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
13552 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
13553 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), cps->which, cps->program);
13555 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
13557 gameInfo.resultDetails = StrSave(buf);
13559 RemoveInputSource(cps->isr);
13560 if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
13562 snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
13563 cps->which, cps->program);
13564 RemoveInputSource(cps->isr);
13566 /* [AS] Program is misbehaving badly... kill it */
13567 if( count == -2 ) {
13568 DestroyChildProcess( cps->pr, 9 );
13572 if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
13577 if ((end_str = strchr(message, '\r')) != NULL)
13578 *end_str = NULLCHAR;
13579 if ((end_str = strchr(message, '\n')) != NULL)
13580 *end_str = NULLCHAR;
13582 if (appData.debugMode) {
13583 TimeMark now; int print = 1;
13584 char *quote = ""; char c; int i;
13586 if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
13587 char start = message[0];
13588 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
13589 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
13590 sscanf(message, "move %c", &c)!=1 && sscanf(message, "offer%c", &c)!=1 &&
13591 sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
13592 sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
13593 sscanf(message, "tell%c", &c)!=1 && sscanf(message, "0-1 %c", &c)!=1 &&
13594 sscanf(message, "1-0 %c", &c)!=1 && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
13595 sscanf(message, "setboard %c", &c)!=1 && sscanf(message, "setup %c", &c)!=1 &&
13596 sscanf(message, "hint: %c", &c)!=1 &&
13597 sscanf(message, "pong %c", &c)!=1 && start != '#') {
13598 quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
13599 print = (appData.engineComments >= 2);
13601 message[0] = start; // restore original message
13605 fprintf(debugFP, "%ld <%-6s: %s%s\n",
13606 SubtractTimeMarks(&now, &programStartTime), cps->which,
13612 /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
13613 if (appData.icsEngineAnalyze) {
13614 if (strstr(message, "whisper") != NULL ||
13615 strstr(message, "kibitz") != NULL ||
13616 strstr(message, "tellics") != NULL) return;
13619 HandleMachineMove(message, cps);
13624 SendTimeControl(cps, mps, tc, inc, sd, st)
13625 ChessProgramState *cps;
13626 int mps, inc, sd, st;
13632 if( timeControl_2 > 0 ) {
13633 if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
13634 tc = timeControl_2;
13637 tc /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
13638 inc /= cps->timeOdds;
13639 st /= cps->timeOdds;
13641 seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
13644 /* Set exact time per move, normally using st command */
13645 if (cps->stKludge) {
13646 /* GNU Chess 4 has no st command; uses level in a nonstandard way */
13648 if (seconds == 0) {
13649 snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
13651 snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
13654 snprintf(buf, MSG_SIZ, "st %d\n", st);
13657 /* Set conventional or incremental time control, using level command */
13658 if (seconds == 0) {
13659 /* Note old gnuchess bug -- minutes:seconds used to not work.
13660 Fixed in later versions, but still avoid :seconds
13661 when seconds is 0. */
13662 snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
13664 snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
13665 seconds, inc/1000.);
13668 SendToProgram(buf, cps);
13670 /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
13671 /* Orthogonally, limit search to given depth */
13673 if (cps->sdKludge) {
13674 snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
13676 snprintf(buf, MSG_SIZ, "sd %d\n", sd);
13678 SendToProgram(buf, cps);
13681 if(cps->nps > 0) { /* [HGM] nps */
13682 if(cps->supportsNPS == FALSE)
13683 cps->nps = -1; // don't use if engine explicitly says not supported!
13685 snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
13686 SendToProgram(buf, cps);
13691 ChessProgramState *WhitePlayer()
13692 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
13694 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
13695 gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
13701 SendTimeRemaining(cps, machineWhite)
13702 ChessProgramState *cps;
13703 int /*boolean*/ machineWhite;
13705 char message[MSG_SIZ];
13708 /* Note: this routine must be called when the clocks are stopped
13709 or when they have *just* been set or switched; otherwise
13710 it will be off by the time since the current tick started.
13712 if (machineWhite) {
13713 time = whiteTimeRemaining / 10;
13714 otime = blackTimeRemaining / 10;
13716 time = blackTimeRemaining / 10;
13717 otime = whiteTimeRemaining / 10;
13719 /* [HGM] translate opponent's time by time-odds factor */
13720 otime = (otime * cps->other->timeOdds) / cps->timeOdds;
13721 if (appData.debugMode) {
13722 fprintf(debugFP, "time odds: %f %f \n", cps->timeOdds, cps->other->timeOdds);
13725 if (time <= 0) time = 1;
13726 if (otime <= 0) otime = 1;
13728 snprintf(message, MSG_SIZ, "time %ld\n", time);
13729 SendToProgram(message, cps);
13731 snprintf(message, MSG_SIZ, "otim %ld\n", otime);
13732 SendToProgram(message, cps);
13736 BoolFeature(p, name, loc, cps)
13740 ChessProgramState *cps;
13743 int len = strlen(name);
13746 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
13748 sscanf(*p, "%d", &val);
13750 while (**p && **p != ' ')
13752 snprintf(buf, MSG_SIZ, "accepted %s\n", name);
13753 SendToProgram(buf, cps);
13760 IntFeature(p, name, loc, cps)
13764 ChessProgramState *cps;
13767 int len = strlen(name);
13768 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
13770 sscanf(*p, "%d", loc);
13771 while (**p && **p != ' ') (*p)++;
13772 snprintf(buf, MSG_SIZ, "accepted %s\n", name);
13773 SendToProgram(buf, cps);
13780 StringFeature(p, name, loc, cps)
13784 ChessProgramState *cps;
13787 int len = strlen(name);
13788 if (strncmp((*p), name, len) == 0
13789 && (*p)[len] == '=' && (*p)[len+1] == '\"') {
13791 sscanf(*p, "%[^\"]", loc);
13792 while (**p && **p != '\"') (*p)++;
13793 if (**p == '\"') (*p)++;
13794 snprintf(buf, MSG_SIZ, "accepted %s\n", name);
13795 SendToProgram(buf, cps);
13802 ParseOption(Option *opt, ChessProgramState *cps)
13803 // [HGM] options: process the string that defines an engine option, and determine
13804 // name, type, default value, and allowed value range
13806 char *p, *q, buf[MSG_SIZ];
13807 int n, min = (-1)<<31, max = 1<<31, def;
13809 if(p = strstr(opt->name, " -spin ")) {
13810 if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
13811 if(max < min) max = min; // enforce consistency
13812 if(def < min) def = min;
13813 if(def > max) def = max;
13818 } else if((p = strstr(opt->name, " -slider "))) {
13819 // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
13820 if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
13821 if(max < min) max = min; // enforce consistency
13822 if(def < min) def = min;
13823 if(def > max) def = max;
13827 opt->type = Spin; // Slider;
13828 } else if((p = strstr(opt->name, " -string "))) {
13829 opt->textValue = p+9;
13830 opt->type = TextBox;
13831 } else if((p = strstr(opt->name, " -file "))) {
13832 // for now -file is a synonym for -string, to already provide compatibility with future polyglots
13833 opt->textValue = p+7;
13834 opt->type = TextBox; // FileName;
13835 } else if((p = strstr(opt->name, " -path "))) {
13836 // for now -file is a synonym for -string, to already provide compatibility with future polyglots
13837 opt->textValue = p+7;
13838 opt->type = TextBox; // PathName;
13839 } else if(p = strstr(opt->name, " -check ")) {
13840 if(sscanf(p, " -check %d", &def) < 1) return FALSE;
13841 opt->value = (def != 0);
13842 opt->type = CheckBox;
13843 } else if(p = strstr(opt->name, " -combo ")) {
13844 opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
13845 cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
13846 if(*q == '*') cps->comboList[cps->comboCnt-1]++;
13847 opt->value = n = 0;
13848 while(q = StrStr(q, " /// ")) {
13849 n++; *q = 0; // count choices, and null-terminate each of them
13851 if(*q == '*') { // remember default, which is marked with * prefix
13855 cps->comboList[cps->comboCnt++] = q;
13857 cps->comboList[cps->comboCnt++] = NULL;
13859 opt->type = ComboBox;
13860 } else if(p = strstr(opt->name, " -button")) {
13861 opt->type = Button;
13862 } else if(p = strstr(opt->name, " -save")) {
13863 opt->type = SaveButton;
13864 } else return FALSE;
13865 *p = 0; // terminate option name
13866 // now look if the command-line options define a setting for this engine option.
13867 if(cps->optionSettings && cps->optionSettings[0])
13868 p = strstr(cps->optionSettings, opt->name); else p = NULL;
13869 if(p && (p == cps->optionSettings || p[-1] == ',')) {
13870 snprintf(buf, MSG_SIZ, "option %s", p);
13871 if(p = strstr(buf, ",")) *p = 0;
13872 if(q = strchr(buf, '=')) switch(opt->type) {
13874 for(n=0; n<opt->max; n++)
13875 if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
13878 safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
13882 opt->value = atoi(q+1);
13887 SendToProgram(buf, cps);
13893 FeatureDone(cps, val)
13894 ChessProgramState* cps;
13897 DelayedEventCallback cb = GetDelayedEvent();
13898 if ((cb == InitBackEnd3 && cps == &first) ||
13899 (cb == SettingsMenuIfReady && cps == &second) ||
13900 (cb == TwoMachinesEventIfReady && cps == &second)) {
13901 CancelDelayedEvent();
13902 ScheduleDelayedEvent(cb, val ? 1 : 3600000);
13904 cps->initDone = val;
13907 /* Parse feature command from engine */
13909 ParseFeatures(args, cps)
13911 ChessProgramState *cps;
13919 while (*p == ' ') p++;
13920 if (*p == NULLCHAR) return;
13922 if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
13923 if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
13924 if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
13925 if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
13926 if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
13927 if (BoolFeature(&p, "reuse", &val, cps)) {
13928 /* Engine can disable reuse, but can't enable it if user said no */
13929 if (!val) cps->reuse = FALSE;
13932 if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
13933 if (StringFeature(&p, "myname", &cps->tidy, cps)) {
13934 if (gameMode == TwoMachinesPlay) {
13935 DisplayTwoMachinesTitle();
13941 if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
13942 if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
13943 if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
13944 if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
13945 if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
13946 if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
13947 if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
13948 if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
13949 if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
13950 if (IntFeature(&p, "done", &val, cps)) {
13951 FeatureDone(cps, val);
13954 /* Added by Tord: */
13955 if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
13956 if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
13957 /* End of additions by Tord */
13959 /* [HGM] added features: */
13960 if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
13961 if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
13962 if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
13963 if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
13964 if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
13965 if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
13966 if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
13967 if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
13968 snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
13969 SendToProgram(buf, cps);
13972 if(cps->nrOptions >= MAX_OPTIONS) {
13974 snprintf(buf, MSG_SIZ, "%s engine has too many options\n", cps->which);
13975 DisplayError(buf, 0);
13979 /* End of additions by HGM */
13981 /* unknown feature: complain and skip */
13983 while (*q && *q != '=') q++;
13984 snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
13985 SendToProgram(buf, cps);
13991 while (*p && *p != '\"') p++;
13992 if (*p == '\"') p++;
13994 while (*p && *p != ' ') p++;
14002 PeriodicUpdatesEvent(newState)
14005 if (newState == appData.periodicUpdates)
14008 appData.periodicUpdates=newState;
14010 /* Display type changes, so update it now */
14011 // DisplayAnalysis();
14013 /* Get the ball rolling again... */
14015 AnalysisPeriodicEvent(1);
14016 StartAnalysisClock();
14021 PonderNextMoveEvent(newState)
14024 if (newState == appData.ponderNextMove) return;
14025 if (gameMode == EditPosition) EditPositionDone(TRUE);
14027 SendToProgram("hard\n", &first);
14028 if (gameMode == TwoMachinesPlay) {
14029 SendToProgram("hard\n", &second);
14032 SendToProgram("easy\n", &first);
14033 thinkOutput[0] = NULLCHAR;
14034 if (gameMode == TwoMachinesPlay) {
14035 SendToProgram("easy\n", &second);
14038 appData.ponderNextMove = newState;
14042 NewSettingEvent(option, feature, command, value)
14044 int option, value, *feature;
14048 if (gameMode == EditPosition) EditPositionDone(TRUE);
14049 snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
14050 if(feature == NULL || *feature) SendToProgram(buf, &first);
14051 if (gameMode == TwoMachinesPlay) {
14052 if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
14057 ShowThinkingEvent()
14058 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
14060 static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
14061 int newState = appData.showThinking
14062 // [HGM] thinking: other features now need thinking output as well
14063 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
14065 if (oldState == newState) return;
14066 oldState = newState;
14067 if (gameMode == EditPosition) EditPositionDone(TRUE);
14069 SendToProgram("post\n", &first);
14070 if (gameMode == TwoMachinesPlay) {
14071 SendToProgram("post\n", &second);
14074 SendToProgram("nopost\n", &first);
14075 thinkOutput[0] = NULLCHAR;
14076 if (gameMode == TwoMachinesPlay) {
14077 SendToProgram("nopost\n", &second);
14080 // appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
14084 AskQuestionEvent(title, question, replyPrefix, which)
14085 char *title; char *question; char *replyPrefix; char *which;
14087 ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
14088 if (pr == NoProc) return;
14089 AskQuestion(title, question, replyPrefix, pr);
14093 DisplayMove(moveNumber)
14096 char message[MSG_SIZ];
14098 char cpThinkOutput[MSG_SIZ];
14100 if(appData.noGUI) return; // [HGM] fast: suppress display of moves
14102 if (moveNumber == forwardMostMove - 1 ||
14103 gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14105 safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
14107 if (strchr(cpThinkOutput, '\n')) {
14108 *strchr(cpThinkOutput, '\n') = NULLCHAR;
14111 *cpThinkOutput = NULLCHAR;
14114 /* [AS] Hide thinking from human user */
14115 if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
14116 *cpThinkOutput = NULLCHAR;
14117 if( thinkOutput[0] != NULLCHAR ) {
14120 for( i=0; i<=hiddenThinkOutputState; i++ ) {
14121 cpThinkOutput[i] = '.';
14123 cpThinkOutput[i] = NULLCHAR;
14124 hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
14128 if (moveNumber == forwardMostMove - 1 &&
14129 gameInfo.resultDetails != NULL) {
14130 if (gameInfo.resultDetails[0] == NULLCHAR) {
14131 snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
14133 snprintf(res, MSG_SIZ, " {%s} %s",
14134 T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
14140 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
14141 DisplayMessage(res, cpThinkOutput);
14143 snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
14144 WhiteOnMove(moveNumber) ? " " : ".. ",
14145 parseList[moveNumber], res);
14146 DisplayMessage(message, cpThinkOutput);
14151 DisplayComment(moveNumber, text)
14155 char title[MSG_SIZ];
14156 char buf[8000]; // comment can be long!
14159 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
14160 safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
14162 snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
14163 WhiteOnMove(moveNumber) ? " " : ".. ",
14164 parseList[moveNumber]);
14166 // [HGM] PV info: display PV info together with (or as) comment
14167 if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {
14168 if(text == NULL) text = "";
14169 score = pvInfoList[moveNumber].score;
14170 snprintf(buf,sizeof(buf)/sizeof(buf[0]), "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,
14171 depth, (pvInfoList[moveNumber].time+50)/100, text);
14174 if (text != NULL && (appData.autoDisplayComment || commentUp))
14175 CommentPopUp(title, text);
14178 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
14179 * might be busy thinking or pondering. It can be omitted if your
14180 * gnuchess is configured to stop thinking immediately on any user
14181 * input. However, that gnuchess feature depends on the FIONREAD
14182 * ioctl, which does not work properly on some flavors of Unix.
14186 ChessProgramState *cps;
14189 if (!cps->useSigint) return;
14190 if (appData.noChessProgram || (cps->pr == NoProc)) return;
14191 switch (gameMode) {
14192 case MachinePlaysWhite:
14193 case MachinePlaysBlack:
14194 case TwoMachinesPlay:
14195 case IcsPlayingWhite:
14196 case IcsPlayingBlack:
14199 /* Skip if we know it isn't thinking */
14200 if (!cps->maybeThinking) return;
14201 if (appData.debugMode)
14202 fprintf(debugFP, "Interrupting %s\n", cps->which);
14203 InterruptChildProcess(cps->pr);
14204 cps->maybeThinking = FALSE;
14209 #endif /*ATTENTION*/
14215 if (whiteTimeRemaining <= 0) {
14218 if (appData.icsActive) {
14219 if (appData.autoCallFlag &&
14220 gameMode == IcsPlayingBlack && !blackFlag) {
14221 SendToICS(ics_prefix);
14222 SendToICS("flag\n");
14226 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
14228 if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
14229 if (appData.autoCallFlag) {
14230 GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
14237 if (blackTimeRemaining <= 0) {
14240 if (appData.icsActive) {
14241 if (appData.autoCallFlag &&
14242 gameMode == IcsPlayingWhite && !whiteFlag) {
14243 SendToICS(ics_prefix);
14244 SendToICS("flag\n");
14248 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
14250 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
14251 if (appData.autoCallFlag) {
14252 GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
14265 if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
14266 gameMode == PlayFromGameFile || forwardMostMove == 0) return;
14269 * add time to clocks when time control is achieved ([HGM] now also used for increment)
14271 if ( !WhiteOnMove(forwardMostMove) ) {
14272 /* White made time control */
14273 lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
14274 whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
14275 /* [HGM] time odds: correct new time quota for time odds! */
14276 / WhitePlayer()->timeOdds;
14277 lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
14279 lastBlack -= blackTimeRemaining;
14280 /* Black made time control */
14281 blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
14282 / WhitePlayer()->other->timeOdds;
14283 lastWhite = whiteTimeRemaining;
14288 DisplayBothClocks()
14290 int wom = gameMode == EditPosition ?
14291 !blackPlaysFirst : WhiteOnMove(currentMove);
14292 DisplayWhiteClock(whiteTimeRemaining, wom);
14293 DisplayBlackClock(blackTimeRemaining, !wom);
14297 /* Timekeeping seems to be a portability nightmare. I think everyone
14298 has ftime(), but I'm really not sure, so I'm including some ifdefs
14299 to use other calls if you don't. Clocks will be less accurate if
14300 you have neither ftime nor gettimeofday.
14303 /* VS 2008 requires the #include outside of the function */
14304 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
14305 #include <sys/timeb.h>
14308 /* Get the current time as a TimeMark */
14313 #if HAVE_GETTIMEOFDAY
14315 struct timeval timeVal;
14316 struct timezone timeZone;
14318 gettimeofday(&timeVal, &timeZone);
14319 tm->sec = (long) timeVal.tv_sec;
14320 tm->ms = (int) (timeVal.tv_usec / 1000L);
14322 #else /*!HAVE_GETTIMEOFDAY*/
14325 // include <sys/timeb.h> / moved to just above start of function
14326 struct timeb timeB;
14329 tm->sec = (long) timeB.time;
14330 tm->ms = (int) timeB.millitm;
14332 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
14333 tm->sec = (long) time(NULL);
14339 /* Return the difference in milliseconds between two
14340 time marks. We assume the difference will fit in a long!
14343 SubtractTimeMarks(tm2, tm1)
14344 TimeMark *tm2, *tm1;
14346 return 1000L*(tm2->sec - tm1->sec) +
14347 (long) (tm2->ms - tm1->ms);
14352 * Code to manage the game clocks.
14354 * In tournament play, black starts the clock and then white makes a move.
14355 * We give the human user a slight advantage if he is playing white---the
14356 * clocks don't run until he makes his first move, so it takes zero time.
14357 * Also, we don't account for network lag, so we could get out of sync
14358 * with GNU Chess's clock -- but then, referees are always right.
14361 static TimeMark tickStartTM;
14362 static long intendedTickLength;
14365 NextTickLength(timeRemaining)
14366 long timeRemaining;
14368 long nominalTickLength, nextTickLength;
14370 if (timeRemaining > 0L && timeRemaining <= 10000L)
14371 nominalTickLength = 100L;
14373 nominalTickLength = 1000L;
14374 nextTickLength = timeRemaining % nominalTickLength;
14375 if (nextTickLength <= 0) nextTickLength += nominalTickLength;
14377 return nextTickLength;
14380 /* Adjust clock one minute up or down */
14382 AdjustClock(Boolean which, int dir)
14384 if(which) blackTimeRemaining += 60000*dir;
14385 else whiteTimeRemaining += 60000*dir;
14386 DisplayBothClocks();
14389 /* Stop clocks and reset to a fresh time control */
14393 (void) StopClockTimer();
14394 if (appData.icsActive) {
14395 whiteTimeRemaining = blackTimeRemaining = 0;
14396 } else if (searchTime) {
14397 whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
14398 blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
14399 } else { /* [HGM] correct new time quote for time odds */
14400 whiteTC = blackTC = fullTimeControlString;
14401 whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
14402 blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
14404 if (whiteFlag || blackFlag) {
14406 whiteFlag = blackFlag = FALSE;
14408 lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
14409 DisplayBothClocks();
14412 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
14414 /* Decrement running clock by amount of time that has passed */
14418 long timeRemaining;
14419 long lastTickLength, fudge;
14422 if (!appData.clockMode) return;
14423 if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
14427 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
14429 /* Fudge if we woke up a little too soon */
14430 fudge = intendedTickLength - lastTickLength;
14431 if (fudge < 0 || fudge > FUDGE) fudge = 0;
14433 if (WhiteOnMove(forwardMostMove)) {
14434 if(whiteNPS >= 0) lastTickLength = 0;
14435 timeRemaining = whiteTimeRemaining -= lastTickLength;
14436 if(timeRemaining < 0 && !appData.icsActive) {
14437 GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
14438 if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
14439 whiteStartMove = forwardMostMove; whiteTC = nextSession;
14440 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
14443 DisplayWhiteClock(whiteTimeRemaining - fudge,
14444 WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
14446 if(blackNPS >= 0) lastTickLength = 0;
14447 timeRemaining = blackTimeRemaining -= lastTickLength;
14448 if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
14449 GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
14451 blackStartMove = forwardMostMove;
14452 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
14455 DisplayBlackClock(blackTimeRemaining - fudge,
14456 !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
14458 if (CheckFlags()) return;
14461 intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
14462 StartClockTimer(intendedTickLength);
14464 /* if the time remaining has fallen below the alarm threshold, sound the
14465 * alarm. if the alarm has sounded and (due to a takeback or time control
14466 * with increment) the time remaining has increased to a level above the
14467 * threshold, reset the alarm so it can sound again.
14470 if (appData.icsActive && appData.icsAlarm) {
14472 /* make sure we are dealing with the user's clock */
14473 if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
14474 ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
14477 if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
14478 alarmSounded = FALSE;
14479 } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
14481 alarmSounded = TRUE;
14487 /* A player has just moved, so stop the previously running
14488 clock and (if in clock mode) start the other one.
14489 We redisplay both clocks in case we're in ICS mode, because
14490 ICS gives us an update to both clocks after every move.
14491 Note that this routine is called *after* forwardMostMove
14492 is updated, so the last fractional tick must be subtracted
14493 from the color that is *not* on move now.
14496 SwitchClocks(int newMoveNr)
14498 long lastTickLength;
14500 int flagged = FALSE;
14504 if (StopClockTimer() && appData.clockMode) {
14505 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
14506 if (!WhiteOnMove(forwardMostMove)) {
14507 if(blackNPS >= 0) lastTickLength = 0;
14508 blackTimeRemaining -= lastTickLength;
14509 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
14510 // if(pvInfoList[forwardMostMove-1].time == -1)
14511 pvInfoList[forwardMostMove-1].time = // use GUI time
14512 (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
14514 if(whiteNPS >= 0) lastTickLength = 0;
14515 whiteTimeRemaining -= lastTickLength;
14516 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
14517 // if(pvInfoList[forwardMostMove-1].time == -1)
14518 pvInfoList[forwardMostMove-1].time =
14519 (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
14521 flagged = CheckFlags();
14523 forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
14524 CheckTimeControl();
14526 if (flagged || !appData.clockMode) return;
14528 switch (gameMode) {
14529 case MachinePlaysBlack:
14530 case MachinePlaysWhite:
14531 case BeginningOfGame:
14532 if (pausing) return;
14536 case PlayFromGameFile:
14544 if (searchTime) { // [HGM] st: set clock of player that has to move to max time
14545 if(WhiteOnMove(forwardMostMove))
14546 whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
14547 else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
14551 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
14552 whiteTimeRemaining : blackTimeRemaining);
14553 StartClockTimer(intendedTickLength);
14557 /* Stop both clocks */
14561 long lastTickLength;
14564 if (!StopClockTimer()) return;
14565 if (!appData.clockMode) return;
14569 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
14570 if (WhiteOnMove(forwardMostMove)) {
14571 if(whiteNPS >= 0) lastTickLength = 0;
14572 whiteTimeRemaining -= lastTickLength;
14573 DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
14575 if(blackNPS >= 0) lastTickLength = 0;
14576 blackTimeRemaining -= lastTickLength;
14577 DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
14582 /* Start clock of player on move. Time may have been reset, so
14583 if clock is already running, stop and restart it. */
14587 (void) StopClockTimer(); /* in case it was running already */
14588 DisplayBothClocks();
14589 if (CheckFlags()) return;
14591 if (!appData.clockMode) return;
14592 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
14594 GetTimeMark(&tickStartTM);
14595 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
14596 whiteTimeRemaining : blackTimeRemaining);
14598 /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
14599 whiteNPS = blackNPS = -1;
14600 if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
14601 || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
14602 whiteNPS = first.nps;
14603 if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
14604 || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
14605 blackNPS = first.nps;
14606 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
14607 whiteNPS = second.nps;
14608 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
14609 blackNPS = second.nps;
14610 if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
14612 StartClockTimer(intendedTickLength);
14619 long second, minute, hour, day;
14621 static char buf[32];
14623 if (ms > 0 && ms <= 9900) {
14624 /* convert milliseconds to tenths, rounding up */
14625 double tenths = floor( ((double)(ms + 99L)) / 100.00 );
14627 snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
14631 /* convert milliseconds to seconds, rounding up */
14632 /* use floating point to avoid strangeness of integer division
14633 with negative dividends on many machines */
14634 second = (long) floor(((double) (ms + 999L)) / 1000.0);
14641 day = second / (60 * 60 * 24);
14642 second = second % (60 * 60 * 24);
14643 hour = second / (60 * 60);
14644 second = second % (60 * 60);
14645 minute = second / 60;
14646 second = second % 60;
14649 snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
14650 sign, day, hour, minute, second);
14652 snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
14654 snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
14661 * This is necessary because some C libraries aren't ANSI C compliant yet.
14664 StrStr(string, match)
14665 char *string, *match;
14669 length = strlen(match);
14671 for (i = strlen(string) - length; i >= 0; i--, string++)
14672 if (!strncmp(match, string, length))
14679 StrCaseStr(string, match)
14680 char *string, *match;
14684 length = strlen(match);
14686 for (i = strlen(string) - length; i >= 0; i--, string++) {
14687 for (j = 0; j < length; j++) {
14688 if (ToLower(match[j]) != ToLower(string[j]))
14691 if (j == length) return string;
14705 c1 = ToLower(*s1++);
14706 c2 = ToLower(*s2++);
14707 if (c1 > c2) return 1;
14708 if (c1 < c2) return -1;
14709 if (c1 == NULLCHAR) return 0;
14718 return isupper(c) ? tolower(c) : c;
14726 return islower(c) ? toupper(c) : c;
14728 #endif /* !_amigados */
14736 if ((ret = (char *) malloc(strlen(s) + 1)))
14738 safeStrCpy(ret, s, strlen(s)+1);
14744 StrSavePtr(s, savePtr)
14745 char *s, **savePtr;
14750 if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
14751 safeStrCpy(*savePtr, s, strlen(s)+1);
14763 clock = time((time_t *)NULL);
14764 tm = localtime(&clock);
14765 snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
14766 tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
14767 return StrSave(buf);
14772 PositionToFEN(move, overrideCastling)
14774 char *overrideCastling;
14776 int i, j, fromX, fromY, toX, toY;
14783 whiteToPlay = (gameMode == EditPosition) ?
14784 !blackPlaysFirst : (move % 2 == 0);
14787 /* Piece placement data */
14788 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
14790 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
14791 if (boards[move][i][j] == EmptySquare) {
14793 } else { ChessSquare piece = boards[move][i][j];
14794 if (emptycount > 0) {
14795 if(emptycount<10) /* [HGM] can be >= 10 */
14796 *p++ = '0' + emptycount;
14797 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
14800 if(PieceToChar(piece) == '+') {
14801 /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
14803 piece = (ChessSquare)(DEMOTED piece);
14805 *p++ = PieceToChar(piece);
14807 /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
14808 p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
14813 if (emptycount > 0) {
14814 if(emptycount<10) /* [HGM] can be >= 10 */
14815 *p++ = '0' + emptycount;
14816 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
14823 /* [HGM] print Crazyhouse or Shogi holdings */
14824 if( gameInfo.holdingsWidth ) {
14825 *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
14827 for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
14828 piece = boards[move][i][BOARD_WIDTH-1];
14829 if( piece != EmptySquare )
14830 for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
14831 *p++ = PieceToChar(piece);
14833 for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
14834 piece = boards[move][BOARD_HEIGHT-i-1][0];
14835 if( piece != EmptySquare )
14836 for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
14837 *p++ = PieceToChar(piece);
14840 if( q == p ) *p++ = '-';
14846 *p++ = whiteToPlay ? 'w' : 'b';
14849 if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
14850 while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
14852 if(nrCastlingRights) {
14854 if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
14855 /* [HGM] write directly from rights */
14856 if(boards[move][CASTLING][2] != NoRights &&
14857 boards[move][CASTLING][0] != NoRights )
14858 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
14859 if(boards[move][CASTLING][2] != NoRights &&
14860 boards[move][CASTLING][1] != NoRights )
14861 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
14862 if(boards[move][CASTLING][5] != NoRights &&
14863 boards[move][CASTLING][3] != NoRights )
14864 *p++ = boards[move][CASTLING][3] + AAA;
14865 if(boards[move][CASTLING][5] != NoRights &&
14866 boards[move][CASTLING][4] != NoRights )
14867 *p++ = boards[move][CASTLING][4] + AAA;
14870 /* [HGM] write true castling rights */
14871 if( nrCastlingRights == 6 ) {
14872 if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
14873 boards[move][CASTLING][2] != NoRights ) *p++ = 'K';
14874 if(boards[move][CASTLING][1] == BOARD_LEFT &&
14875 boards[move][CASTLING][2] != NoRights ) *p++ = 'Q';
14876 if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
14877 boards[move][CASTLING][5] != NoRights ) *p++ = 'k';
14878 if(boards[move][CASTLING][4] == BOARD_LEFT &&
14879 boards[move][CASTLING][5] != NoRights ) *p++ = 'q';
14882 if (q == p) *p++ = '-'; /* No castling rights */
14886 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
14887 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
14888 /* En passant target square */
14889 if (move > backwardMostMove) {
14890 fromX = moveList[move - 1][0] - AAA;
14891 fromY = moveList[move - 1][1] - ONE;
14892 toX = moveList[move - 1][2] - AAA;
14893 toY = moveList[move - 1][3] - ONE;
14894 if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
14895 toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
14896 boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
14898 /* 2-square pawn move just happened */
14900 *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
14904 } else if(move == backwardMostMove) {
14905 // [HGM] perhaps we should always do it like this, and forget the above?
14906 if((signed char)boards[move][EP_STATUS] >= 0) {
14907 *p++ = boards[move][EP_STATUS] + AAA;
14908 *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
14919 /* [HGM] find reversible plies */
14920 { int i = 0, j=move;
14922 if (appData.debugMode) { int k;
14923 fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
14924 for(k=backwardMostMove; k<=forwardMostMove; k++)
14925 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
14929 while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
14930 if( j == backwardMostMove ) i += initialRulePlies;
14931 sprintf(p, "%d ", i);
14932 p += i>=100 ? 4 : i >= 10 ? 3 : 2;
14934 /* Fullmove number */
14935 sprintf(p, "%d", (move / 2) + 1);
14937 return StrSave(buf);
14941 ParseFEN(board, blackPlaysFirst, fen)
14943 int *blackPlaysFirst;
14953 /* [HGM] by default clear Crazyhouse holdings, if present */
14954 if(gameInfo.holdingsWidth) {
14955 for(i=0; i<BOARD_HEIGHT; i++) {
14956 board[i][0] = EmptySquare; /* black holdings */
14957 board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
14958 board[i][1] = (ChessSquare) 0; /* black counts */
14959 board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
14963 /* Piece placement data */
14964 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
14967 if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
14968 if (*p == '/') p++;
14969 emptycount = gameInfo.boardWidth - j;
14970 while (emptycount--)
14971 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
14973 #if(BOARD_FILES >= 10)
14974 } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
14975 p++; emptycount=10;
14976 if (j + emptycount > gameInfo.boardWidth) return FALSE;
14977 while (emptycount--)
14978 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
14980 } else if (isdigit(*p)) {
14981 emptycount = *p++ - '0';
14982 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
14983 if (j + emptycount > gameInfo.boardWidth) return FALSE;
14984 while (emptycount--)
14985 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
14986 } else if (*p == '+' || isalpha(*p)) {
14987 if (j >= gameInfo.boardWidth) return FALSE;
14989 piece = CharToPiece(*++p);
14990 if(piece == EmptySquare) return FALSE; /* unknown piece */
14991 piece = (ChessSquare) (PROMOTED piece ); p++;
14992 if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
14993 } else piece = CharToPiece(*p++);
14995 if(piece==EmptySquare) return FALSE; /* unknown piece */
14996 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
14997 piece = (ChessSquare) (PROMOTED piece);
14998 if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
15001 board[i][(j++)+gameInfo.holdingsWidth] = piece;
15007 while (*p == '/' || *p == ' ') p++;
15009 /* [HGM] look for Crazyhouse holdings here */
15010 while(*p==' ') p++;
15011 if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
15013 if(*p == '-' ) p++; /* empty holdings */ else {
15014 if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
15015 /* if we would allow FEN reading to set board size, we would */
15016 /* have to add holdings and shift the board read so far here */
15017 while( (piece = CharToPiece(*p) ) != EmptySquare ) {
15019 if((int) piece >= (int) BlackPawn ) {
15020 i = (int)piece - (int)BlackPawn;
15021 i = PieceToNumber((ChessSquare)i);
15022 if( i >= gameInfo.holdingsSize ) return FALSE;
15023 board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
15024 board[BOARD_HEIGHT-1-i][1]++; /* black counts */
15026 i = (int)piece - (int)WhitePawn;
15027 i = PieceToNumber((ChessSquare)i);
15028 if( i >= gameInfo.holdingsSize ) return FALSE;
15029 board[i][BOARD_WIDTH-1] = piece; /* white holdings */
15030 board[i][BOARD_WIDTH-2]++; /* black holdings */
15037 while(*p == ' ') p++;
15041 if(appData.colorNickNames) {
15042 if( c == appData.colorNickNames[0] ) c = 'w'; else
15043 if( c == appData.colorNickNames[1] ) c = 'b';
15047 *blackPlaysFirst = FALSE;
15050 *blackPlaysFirst = TRUE;
15056 /* [HGM] We NO LONGER ignore the rest of the FEN notation */
15057 /* return the extra info in global variiables */
15059 /* set defaults in case FEN is incomplete */
15060 board[EP_STATUS] = EP_UNKNOWN;
15061 for(i=0; i<nrCastlingRights; i++ ) {
15062 board[CASTLING][i] =
15063 gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
15064 } /* assume possible unless obviously impossible */
15065 if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
15066 if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
15067 if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
15068 && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
15069 if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
15070 if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
15071 if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
15072 && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
15075 while(*p==' ') p++;
15076 if(nrCastlingRights) {
15077 if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
15078 /* castling indicator present, so default becomes no castlings */
15079 for(i=0; i<nrCastlingRights; i++ ) {
15080 board[CASTLING][i] = NoRights;
15083 while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
15084 (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
15085 ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
15086 ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth) ) {
15087 char c = *p++; int whiteKingFile=NoRights, blackKingFile=NoRights;
15089 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
15090 if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
15091 if(board[0 ][i] == WhiteKing) whiteKingFile = i;
15093 if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
15094 whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
15095 if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
15096 && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
15097 if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
15098 && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
15101 for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
15102 board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
15103 board[CASTLING][2] = whiteKingFile;
15106 for(i=BOARD_LEFT; i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
15107 board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
15108 board[CASTLING][2] = whiteKingFile;
15111 for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
15112 board[CASTLING][3] = i != blackKingFile ? i : NoRights;
15113 board[CASTLING][5] = blackKingFile;
15116 for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
15117 board[CASTLING][4] = i != blackKingFile ? i : NoRights;
15118 board[CASTLING][5] = blackKingFile;
15121 default: /* FRC castlings */
15122 if(c >= 'a') { /* black rights */
15123 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
15124 if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
15125 if(i == BOARD_RGHT) break;
15126 board[CASTLING][5] = i;
15128 if(board[BOARD_HEIGHT-1][c] < BlackPawn ||
15129 board[BOARD_HEIGHT-1][c] >= BlackKing ) break;
15131 board[CASTLING][3] = c;
15133 board[CASTLING][4] = c;
15134 } else { /* white rights */
15135 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
15136 if(board[0][i] == WhiteKing) break;
15137 if(i == BOARD_RGHT) break;
15138 board[CASTLING][2] = i;
15139 c -= AAA - 'a' + 'A';
15140 if(board[0][c] >= WhiteKing) break;
15142 board[CASTLING][0] = c;
15144 board[CASTLING][1] = c;
15148 for(i=0; i<nrCastlingRights; i++)
15149 if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
15150 if (appData.debugMode) {
15151 fprintf(debugFP, "FEN castling rights:");
15152 for(i=0; i<nrCastlingRights; i++)
15153 fprintf(debugFP, " %d", board[CASTLING][i]);
15154 fprintf(debugFP, "\n");
15157 while(*p==' ') p++;
15160 /* read e.p. field in games that know e.p. capture */
15161 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
15162 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
15164 p++; board[EP_STATUS] = EP_NONE;
15166 char c = *p++ - AAA;
15168 if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
15169 if(*p >= '0' && *p <='9') p++;
15170 board[EP_STATUS] = c;
15175 if(sscanf(p, "%d", &i) == 1) {
15176 FENrulePlies = i; /* 50-move ply counter */
15177 /* (The move number is still ignored) */
15184 EditPositionPasteFEN(char *fen)
15187 Board initial_position;
15189 if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
15190 DisplayError(_("Bad FEN position in clipboard"), 0);
15193 int savedBlackPlaysFirst = blackPlaysFirst;
15194 EditPositionEvent();
15195 blackPlaysFirst = savedBlackPlaysFirst;
15196 CopyBoard(boards[0], initial_position);
15197 initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
15198 EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
15199 DisplayBothClocks();
15200 DrawPosition(FALSE, boards[currentMove]);
15205 static char cseq[12] = "\\ ";
15207 Boolean set_cont_sequence(char *new_seq)
15212 // handle bad attempts to set the sequence
15214 return 0; // acceptable error - no debug
15216 len = strlen(new_seq);
15217 ret = (len > 0) && (len < sizeof(cseq));
15219 safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
15220 else if (appData.debugMode)
15221 fprintf(debugFP, "Invalid continuation sequence \"%s\" (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
15226 reformat a source message so words don't cross the width boundary. internal
15227 newlines are not removed. returns the wrapped size (no null character unless
15228 included in source message). If dest is NULL, only calculate the size required
15229 for the dest buffer. lp argument indicats line position upon entry, and it's
15230 passed back upon exit.
15232 int wrap(char *dest, char *src, int count, int width, int *lp)
15234 int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
15236 cseq_len = strlen(cseq);
15237 old_line = line = *lp;
15238 ansi = len = clen = 0;
15240 for (i=0; i < count; i++)
15242 if (src[i] == '\033')
15245 // if we hit the width, back up
15246 if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
15248 // store i & len in case the word is too long
15249 old_i = i, old_len = len;
15251 // find the end of the last word
15252 while (i && src[i] != ' ' && src[i] != '\n')
15258 // word too long? restore i & len before splitting it
15259 if ((old_i-i+clen) >= width)
15266 if (i && src[i-1] == ' ')
15269 if (src[i] != ' ' && src[i] != '\n')
15276 // now append the newline and continuation sequence
15281 strncpy(dest+len, cseq, cseq_len);
15289 dest[len] = src[i];
15293 if (src[i] == '\n')
15298 if (dest && appData.debugMode)
15300 fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
15301 count, width, line, len, *lp);
15302 show_bytes(debugFP, src, count);
15303 fprintf(debugFP, "\ndest: ");
15304 show_bytes(debugFP, dest, len);
15305 fprintf(debugFP, "\n");
15307 *lp = dest ? line : old_line;
15312 // [HGM] vari: routines for shelving variations
15315 PushTail(int firstMove, int lastMove)
15317 int i, j, nrMoves = lastMove - firstMove;
15319 if(appData.icsActive) { // only in local mode
15320 forwardMostMove = currentMove; // mimic old ICS behavior
15323 if(storedGames >= MAX_VARIATIONS-1) return;
15325 // push current tail of game on stack
15326 savedResult[storedGames] = gameInfo.result;
15327 savedDetails[storedGames] = gameInfo.resultDetails;
15328 gameInfo.resultDetails = NULL;
15329 savedFirst[storedGames] = firstMove;
15330 savedLast [storedGames] = lastMove;
15331 savedFramePtr[storedGames] = framePtr;
15332 framePtr -= nrMoves; // reserve space for the boards
15333 for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
15334 CopyBoard(boards[framePtr+i], boards[firstMove+i]);
15335 for(j=0; j<MOVE_LEN; j++)
15336 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
15337 for(j=0; j<2*MOVE_LEN; j++)
15338 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
15339 timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
15340 timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
15341 pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
15342 pvInfoList[firstMove+i-1].depth = 0;
15343 commentList[framePtr+i] = commentList[firstMove+i];
15344 commentList[firstMove+i] = NULL;
15348 forwardMostMove = firstMove; // truncate game so we can start variation
15349 if(storedGames == 1) GreyRevert(FALSE);
15353 PopTail(Boolean annotate)
15356 char buf[8000], moveBuf[20];
15358 if(appData.icsActive) return FALSE; // only in local mode
15359 if(!storedGames) return FALSE; // sanity
15360 CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
15363 ToNrEvent(savedFirst[storedGames]); // sets currentMove
15364 nrMoves = savedLast[storedGames] - currentMove;
15367 if(!WhiteOnMove(currentMove))
15368 snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
15369 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
15370 for(i=currentMove; i<forwardMostMove; i++) {
15372 snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
15373 else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
15374 strcat(buf, moveBuf);
15375 if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
15376 if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
15380 for(i=1; i<=nrMoves; i++) { // copy last variation back
15381 CopyBoard(boards[currentMove+i], boards[framePtr+i]);
15382 for(j=0; j<MOVE_LEN; j++)
15383 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
15384 for(j=0; j<2*MOVE_LEN; j++)
15385 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
15386 timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
15387 timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
15388 pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
15389 if(commentList[currentMove+i]) free(commentList[currentMove+i]);
15390 commentList[currentMove+i] = commentList[framePtr+i];
15391 commentList[framePtr+i] = NULL;
15393 if(annotate) AppendComment(currentMove+1, buf, FALSE);
15394 framePtr = savedFramePtr[storedGames];
15395 gameInfo.result = savedResult[storedGames];
15396 if(gameInfo.resultDetails != NULL) {
15397 free(gameInfo.resultDetails);
15399 gameInfo.resultDetails = savedDetails[storedGames];
15400 forwardMostMove = currentMove + nrMoves;
15401 if(storedGames == 0) GreyRevert(TRUE);
15407 { // remove all shelved variations
15409 for(i=0; i<storedGames; i++) {
15410 if(savedDetails[i])
15411 free(savedDetails[i]);
15412 savedDetails[i] = NULL;
15414 for(i=framePtr; i<MAX_MOVES; i++) {
15415 if(commentList[i]) free(commentList[i]);
15416 commentList[i] = NULL;
15418 framePtr = MAX_MOVES-1;
15423 LoadVariation(int index, char *text)
15424 { // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
15425 char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
15426 int level = 0, move;
15428 if(gameMode != EditGame && gameMode != AnalyzeMode) return;
15429 // first find outermost bracketing variation
15430 while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
15431 if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
15432 if(*p == '{') wait = '}'; else
15433 if(*p == '[') wait = ']'; else
15434 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
15435 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
15437 if(*p == wait) wait = NULLCHAR; // closing ]} found
15440 if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
15441 if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
15442 end[1] = NULLCHAR; // clip off comment beyond variation
15443 ToNrEvent(currentMove-1);
15444 PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
15445 // kludge: use ParsePV() to append variation to game
15446 move = currentMove;
15447 ParsePV(start, TRUE);
15448 forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
15449 ClearPremoveHighlights();
15451 ToNrEvent(currentMove+1);