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 || gameMode == AnalyzeFile)
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)
9629 if (currentMove >= forwardMostMove) {
9630 gameMode = EditGame;
9633 /* [AS] Clear current move marker at the end of a game */
9634 /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
9639 toX = moveList[currentMove][2] - AAA;
9640 toY = moveList[currentMove][3] - ONE;
9642 if (moveList[currentMove][1] == '@') {
9643 if (appData.highlightLastMove) {
9644 SetHighlights(-1, -1, toX, toY);
9647 fromX = moveList[currentMove][0] - AAA;
9648 fromY = moveList[currentMove][1] - ONE;
9650 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
9652 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
9654 if (appData.highlightLastMove) {
9655 SetHighlights(fromX, fromY, toX, toY);
9658 DisplayMove(currentMove);
9659 SendMoveToProgram(currentMove++, &first);
9660 DisplayBothClocks();
9661 DrawPosition(FALSE, boards[currentMove]);
9662 // [HGM] PV info: always display, routine tests if empty
9663 DisplayComment(currentMove - 1, commentList[currentMove]);
9669 LoadGameOneMove(readAhead)
9670 ChessMove readAhead;
9672 int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
9673 char promoChar = NULLCHAR;
9678 if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
9679 gameMode != AnalyzeMode && gameMode != Training) {
9684 yyboardindex = forwardMostMove;
9685 if (readAhead != EndOfFile) {
9686 moveType = readAhead;
9688 if (gameFileFP == NULL)
9690 moveType = (ChessMove) Myylex();
9696 if (appData.debugMode)
9697 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9700 /* append the comment but don't display it */
9701 AppendComment(currentMove, p, FALSE);
9704 case WhiteCapturesEnPassant:
9705 case BlackCapturesEnPassant:
9706 case WhitePromotion:
9707 case BlackPromotion:
9708 case WhiteNonPromotion:
9709 case BlackNonPromotion:
9711 case WhiteKingSideCastle:
9712 case WhiteQueenSideCastle:
9713 case BlackKingSideCastle:
9714 case BlackQueenSideCastle:
9715 case WhiteKingSideCastleWild:
9716 case WhiteQueenSideCastleWild:
9717 case BlackKingSideCastleWild:
9718 case BlackQueenSideCastleWild:
9720 case WhiteHSideCastleFR:
9721 case WhiteASideCastleFR:
9722 case BlackHSideCastleFR:
9723 case BlackASideCastleFR:
9725 if (appData.debugMode)
9726 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
9727 fromX = currentMoveString[0] - AAA;
9728 fromY = currentMoveString[1] - ONE;
9729 toX = currentMoveString[2] - AAA;
9730 toY = currentMoveString[3] - ONE;
9731 promoChar = currentMoveString[4];
9736 if (appData.debugMode)
9737 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
9738 fromX = moveType == WhiteDrop ?
9739 (int) CharToPiece(ToUpper(currentMoveString[0])) :
9740 (int) CharToPiece(ToLower(currentMoveString[0]));
9742 toX = currentMoveString[2] - AAA;
9743 toY = currentMoveString[3] - ONE;
9749 case GameUnfinished:
9750 if (appData.debugMode)
9751 fprintf(debugFP, "Parsed game end: %s\n", yy_text);
9752 p = strchr(yy_text, '{');
9753 if (p == NULL) p = strchr(yy_text, '(');
9756 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
9758 q = strchr(p, *p == '{' ? '}' : ')');
9759 if (q != NULL) *q = NULLCHAR;
9762 GameEnds(moveType, p, GE_FILE);
9764 if (cmailMsgLoaded) {
9766 flipView = WhiteOnMove(currentMove);
9767 if (moveType == GameUnfinished) flipView = !flipView;
9768 if (appData.debugMode)
9769 fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
9774 if (appData.debugMode)
9775 fprintf(debugFP, "Parser hit end of file\n");
9776 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9782 if (WhiteOnMove(currentMove)) {
9783 GameEnds(BlackWins, "Black mates", GE_FILE);
9785 GameEnds(WhiteWins, "White mates", GE_FILE);
9789 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
9796 if (lastLoadGameStart == GNUChessGame) {
9797 /* GNUChessGames have numbers, but they aren't move numbers */
9798 if (appData.debugMode)
9799 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
9800 yy_text, (int) moveType);
9801 return LoadGameOneMove(EndOfFile); /* tail recursion */
9803 /* else fall thru */
9808 /* Reached start of next game in file */
9809 if (appData.debugMode)
9810 fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
9811 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9817 if (WhiteOnMove(currentMove)) {
9818 GameEnds(BlackWins, "Black mates", GE_FILE);
9820 GameEnds(WhiteWins, "White mates", GE_FILE);
9824 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
9830 case PositionDiagram: /* should not happen; ignore */
9831 case ElapsedTime: /* ignore */
9832 case NAG: /* ignore */
9833 if (appData.debugMode)
9834 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
9835 yy_text, (int) moveType);
9836 return LoadGameOneMove(EndOfFile); /* tail recursion */
9839 if (appData.testLegality) {
9840 if (appData.debugMode)
9841 fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
9842 snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
9843 (forwardMostMove / 2) + 1,
9844 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9845 DisplayError(move, 0);
9848 if (appData.debugMode)
9849 fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
9850 yy_text, currentMoveString);
9851 fromX = currentMoveString[0] - AAA;
9852 fromY = currentMoveString[1] - ONE;
9853 toX = currentMoveString[2] - AAA;
9854 toY = currentMoveString[3] - ONE;
9855 promoChar = currentMoveString[4];
9860 if (appData.debugMode)
9861 fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
9862 snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
9863 (forwardMostMove / 2) + 1,
9864 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9865 DisplayError(move, 0);
9870 case ImpossibleMove:
9871 if (appData.debugMode)
9872 fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
9873 snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
9874 (forwardMostMove / 2) + 1,
9875 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9876 DisplayError(move, 0);
9882 if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
9883 DrawPosition(FALSE, boards[currentMove]);
9884 DisplayBothClocks();
9885 if (!appData.matchMode) // [HGM] PV info: routine tests if empty
9886 DisplayComment(currentMove - 1, commentList[currentMove]);
9888 (void) StopLoadGameTimer();
9890 cmailOldMove = forwardMostMove;
9893 /* currentMoveString is set as a side-effect of yylex */
9894 strcat(currentMoveString, "\n");
9895 safeStrCpy(moveList[forwardMostMove], currentMoveString, sizeof(moveList[forwardMostMove])/sizeof(moveList[forwardMostMove][0]));
9897 thinkOutput[0] = NULLCHAR;
9898 MakeMove(fromX, fromY, toX, toY, promoChar);
9899 currentMove = forwardMostMove;
9904 /* Load the nth game from the given file */
9906 LoadGameFromFile(filename, n, title, useList)
9910 /*Boolean*/ int useList;
9915 if (strcmp(filename, "-") == 0) {
9919 f = fopen(filename, "rb");
9921 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9922 DisplayError(buf, errno);
9926 if (fseek(f, 0, 0) == -1) {
9927 /* f is not seekable; probably a pipe */
9930 if (useList && n == 0) {
9931 int error = GameListBuild(f);
9933 DisplayError(_("Cannot build game list"), error);
9934 } else if (!ListEmpty(&gameList) &&
9935 ((ListGame *) gameList.tailPred)->number > 1) {
9936 GameListPopUp(f, title);
9943 return LoadGame(f, n, title, FALSE);
9948 MakeRegisteredMove()
9950 int fromX, fromY, toX, toY;
9952 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
9953 switch (cmailMoveType[lastLoadGameNumber - 1]) {
9956 if (appData.debugMode)
9957 fprintf(debugFP, "Restoring %s for game %d\n",
9958 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
9960 thinkOutput[0] = NULLCHAR;
9961 safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
9962 fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
9963 fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
9964 toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
9965 toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
9966 promoChar = cmailMove[lastLoadGameNumber - 1][4];
9967 MakeMove(fromX, fromY, toX, toY, promoChar);
9968 ShowMove(fromX, fromY, toX, toY);
9970 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9977 if (WhiteOnMove(currentMove)) {
9978 GameEnds(BlackWins, "Black mates", GE_PLAYER);
9980 GameEnds(WhiteWins, "White mates", GE_PLAYER);
9985 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
9992 if (WhiteOnMove(currentMove)) {
9993 GameEnds(BlackWins, "White resigns", GE_PLAYER);
9995 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
10000 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
10011 /* Wrapper around LoadGame for use when a Cmail message is loaded */
10013 CmailLoadGame(f, gameNumber, title, useList)
10021 if (gameNumber > nCmailGames) {
10022 DisplayError(_("No more games in this message"), 0);
10025 if (f == lastLoadGameFP) {
10026 int offset = gameNumber - lastLoadGameNumber;
10028 cmailMsg[0] = NULLCHAR;
10029 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
10030 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
10031 nCmailMovesRegistered--;
10033 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
10034 if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
10035 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
10038 if (! RegisterMove()) return FALSE;
10042 retVal = LoadGame(f, gameNumber, title, useList);
10044 /* Make move registered during previous look at this game, if any */
10045 MakeRegisteredMove();
10047 if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
10048 commentList[currentMove]
10049 = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
10050 DisplayComment(currentMove - 1, commentList[currentMove]);
10056 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
10061 int gameNumber = lastLoadGameNumber + offset;
10062 if (lastLoadGameFP == NULL) {
10063 DisplayError(_("No game has been loaded yet"), 0);
10066 if (gameNumber <= 0) {
10067 DisplayError(_("Can't back up any further"), 0);
10070 if (cmailMsgLoaded) {
10071 return CmailLoadGame(lastLoadGameFP, gameNumber,
10072 lastLoadGameTitle, lastLoadGameUseList);
10074 return LoadGame(lastLoadGameFP, gameNumber,
10075 lastLoadGameTitle, lastLoadGameUseList);
10081 /* Load the nth game from open file f */
10083 LoadGame(f, gameNumber, title, useList)
10091 int gn = gameNumber;
10092 ListGame *lg = NULL;
10093 int numPGNTags = 0;
10095 GameMode oldGameMode;
10096 VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
10098 if (appData.debugMode)
10099 fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
10101 if (gameMode == Training )
10102 SetTrainingModeOff();
10104 oldGameMode = gameMode;
10105 if (gameMode != BeginningOfGame) {
10106 Reset(FALSE, TRUE);
10110 if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
10111 fclose(lastLoadGameFP);
10115 lg = (ListGame *) ListElem(&gameList, gameNumber-1);
10118 fseek(f, lg->offset, 0);
10119 GameListHighlight(gameNumber);
10123 DisplayError(_("Game number out of range"), 0);
10128 if (fseek(f, 0, 0) == -1) {
10129 if (f == lastLoadGameFP ?
10130 gameNumber == lastLoadGameNumber + 1 :
10134 DisplayError(_("Can't seek on game file"), 0);
10139 lastLoadGameFP = f;
10140 lastLoadGameNumber = gameNumber;
10141 safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
10142 lastLoadGameUseList = useList;
10146 if (lg && lg->gameInfo.white && lg->gameInfo.black) {
10147 snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
10148 lg->gameInfo.black);
10150 } else if (*title != NULLCHAR) {
10151 if (gameNumber > 1) {
10152 snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
10155 DisplayTitle(title);
10159 if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
10160 gameMode = PlayFromGameFile;
10164 currentMove = forwardMostMove = backwardMostMove = 0;
10165 CopyBoard(boards[0], initialPosition);
10169 * Skip the first gn-1 games in the file.
10170 * Also skip over anything that precedes an identifiable
10171 * start of game marker, to avoid being confused by
10172 * garbage at the start of the file. Currently
10173 * recognized start of game markers are the move number "1",
10174 * the pattern "gnuchess .* game", the pattern
10175 * "^[#;%] [^ ]* game file", and a PGN tag block.
10176 * A game that starts with one of the latter two patterns
10177 * will also have a move number 1, possibly
10178 * following a position diagram.
10179 * 5-4-02: Let's try being more lenient and allowing a game to
10180 * start with an unnumbered move. Does that break anything?
10182 cm = lastLoadGameStart = EndOfFile;
10184 yyboardindex = forwardMostMove;
10185 cm = (ChessMove) Myylex();
10188 if (cmailMsgLoaded) {
10189 nCmailGames = CMAIL_MAX_GAMES - gn;
10192 DisplayError(_("Game not found in file"), 0);
10199 lastLoadGameStart = cm;
10202 case MoveNumberOne:
10203 switch (lastLoadGameStart) {
10208 case MoveNumberOne:
10210 gn--; /* count this game */
10211 lastLoadGameStart = cm;
10220 switch (lastLoadGameStart) {
10223 case MoveNumberOne:
10225 gn--; /* count this game */
10226 lastLoadGameStart = cm;
10229 lastLoadGameStart = cm; /* game counted already */
10237 yyboardindex = forwardMostMove;
10238 cm = (ChessMove) Myylex();
10239 } while (cm == PGNTag || cm == Comment);
10246 if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
10247 if ( cmailResult[CMAIL_MAX_GAMES - gn - 1]
10248 != CMAIL_OLD_RESULT) {
10250 cmailResult[ CMAIL_MAX_GAMES
10251 - gn - 1] = CMAIL_OLD_RESULT;
10257 /* Only a NormalMove can be at the start of a game
10258 * without a position diagram. */
10259 if (lastLoadGameStart == EndOfFile ) {
10261 lastLoadGameStart = MoveNumberOne;
10270 if (appData.debugMode)
10271 fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
10273 if (cm == XBoardGame) {
10274 /* Skip any header junk before position diagram and/or move 1 */
10276 yyboardindex = forwardMostMove;
10277 cm = (ChessMove) Myylex();
10279 if (cm == EndOfFile ||
10280 cm == GNUChessGame || cm == XBoardGame) {
10281 /* Empty game; pretend end-of-file and handle later */
10286 if (cm == MoveNumberOne || cm == PositionDiagram ||
10287 cm == PGNTag || cm == Comment)
10290 } else if (cm == GNUChessGame) {
10291 if (gameInfo.event != NULL) {
10292 free(gameInfo.event);
10294 gameInfo.event = StrSave(yy_text);
10297 startedFromSetupPosition = FALSE;
10298 while (cm == PGNTag) {
10299 if (appData.debugMode)
10300 fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
10301 err = ParsePGNTag(yy_text, &gameInfo);
10302 if (!err) numPGNTags++;
10304 /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
10305 if(gameInfo.variant != oldVariant) {
10306 startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
10307 ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
10308 InitPosition(TRUE);
10309 oldVariant = gameInfo.variant;
10310 if (appData.debugMode)
10311 fprintf(debugFP, "New variant %d\n", (int) oldVariant);
10315 if (gameInfo.fen != NULL) {
10316 Board initial_position;
10317 startedFromSetupPosition = TRUE;
10318 if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
10320 DisplayError(_("Bad FEN position in file"), 0);
10323 CopyBoard(boards[0], initial_position);
10324 if (blackPlaysFirst) {
10325 currentMove = forwardMostMove = backwardMostMove = 1;
10326 CopyBoard(boards[1], initial_position);
10327 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
10328 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
10329 timeRemaining[0][1] = whiteTimeRemaining;
10330 timeRemaining[1][1] = blackTimeRemaining;
10331 if (commentList[0] != NULL) {
10332 commentList[1] = commentList[0];
10333 commentList[0] = NULL;
10336 currentMove = forwardMostMove = backwardMostMove = 0;
10338 /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
10340 initialRulePlies = FENrulePlies;
10341 for( i=0; i< nrCastlingRights; i++ )
10342 initialRights[i] = initial_position[CASTLING][i];
10344 yyboardindex = forwardMostMove;
10345 free(gameInfo.fen);
10346 gameInfo.fen = NULL;
10349 yyboardindex = forwardMostMove;
10350 cm = (ChessMove) Myylex();
10352 /* Handle comments interspersed among the tags */
10353 while (cm == Comment) {
10355 if (appData.debugMode)
10356 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10358 AppendComment(currentMove, p, FALSE);
10359 yyboardindex = forwardMostMove;
10360 cm = (ChessMove) Myylex();
10364 /* don't rely on existence of Event tag since if game was
10365 * pasted from clipboard the Event tag may not exist
10367 if (numPGNTags > 0){
10369 if (gameInfo.variant == VariantNormal) {
10370 VariantClass v = StringToVariant(gameInfo.event);
10371 // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
10372 if(v < VariantShogi) gameInfo.variant = v;
10375 if( appData.autoDisplayTags ) {
10376 tags = PGNTags(&gameInfo);
10377 TagsPopUp(tags, CmailMsg());
10382 /* Make something up, but don't display it now */
10387 if (cm == PositionDiagram) {
10390 Board initial_position;
10392 if (appData.debugMode)
10393 fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
10395 if (!startedFromSetupPosition) {
10397 for (i = BOARD_HEIGHT - 1; i >= 0; i--)
10398 for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
10408 initial_position[i][j++] = CharToPiece(*p);
10411 while (*p == ' ' || *p == '\t' ||
10412 *p == '\n' || *p == '\r') p++;
10414 if (strncmp(p, "black", strlen("black"))==0)
10415 blackPlaysFirst = TRUE;
10417 blackPlaysFirst = FALSE;
10418 startedFromSetupPosition = TRUE;
10420 CopyBoard(boards[0], initial_position);
10421 if (blackPlaysFirst) {
10422 currentMove = forwardMostMove = backwardMostMove = 1;
10423 CopyBoard(boards[1], initial_position);
10424 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
10425 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
10426 timeRemaining[0][1] = whiteTimeRemaining;
10427 timeRemaining[1][1] = blackTimeRemaining;
10428 if (commentList[0] != NULL) {
10429 commentList[1] = commentList[0];
10430 commentList[0] = NULL;
10433 currentMove = forwardMostMove = backwardMostMove = 0;
10436 yyboardindex = forwardMostMove;
10437 cm = (ChessMove) Myylex();
10440 if (first.pr == NoProc) {
10441 StartChessProgram(&first);
10443 InitChessProgram(&first, FALSE);
10444 SendToProgram("force\n", &first);
10445 if (startedFromSetupPosition) {
10446 SendBoard(&first, forwardMostMove);
10447 if (appData.debugMode) {
10448 fprintf(debugFP, "Load Game\n");
10450 DisplayBothClocks();
10453 /* [HGM] server: flag to write setup moves in broadcast file as one */
10454 loadFlag = appData.suppressLoadMoves;
10456 while (cm == Comment) {
10458 if (appData.debugMode)
10459 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10461 AppendComment(currentMove, p, FALSE);
10462 yyboardindex = forwardMostMove;
10463 cm = (ChessMove) Myylex();
10466 if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
10467 cm == WhiteWins || cm == BlackWins ||
10468 cm == GameIsDrawn || cm == GameUnfinished) {
10469 DisplayMessage("", _("No moves in game"));
10470 if (cmailMsgLoaded) {
10471 if (appData.debugMode)
10472 fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
10476 DrawPosition(FALSE, boards[currentMove]);
10477 DisplayBothClocks();
10478 gameMode = EditGame;
10485 // [HGM] PV info: routine tests if comment empty
10486 if (!matchMode && (pausing || appData.timeDelay != 0)) {
10487 DisplayComment(currentMove - 1, commentList[currentMove]);
10489 if (!matchMode && appData.timeDelay != 0)
10490 DrawPosition(FALSE, boards[currentMove]);
10492 if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
10493 programStats.ok_to_send = 1;
10496 /* if the first token after the PGN tags is a move
10497 * and not move number 1, retrieve it from the parser
10499 if (cm != MoveNumberOne)
10500 LoadGameOneMove(cm);
10502 /* load the remaining moves from the file */
10503 while (LoadGameOneMove(EndOfFile)) {
10504 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10505 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10508 /* rewind to the start of the game */
10509 currentMove = backwardMostMove;
10511 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10513 if (oldGameMode == AnalyzeFile ||
10514 oldGameMode == AnalyzeMode) {
10515 AnalyzeFileEvent();
10518 if (matchMode || appData.timeDelay == 0) {
10520 gameMode = EditGame;
10522 } else if (appData.timeDelay > 0) {
10523 AutoPlayGameLoop();
10526 if (appData.debugMode)
10527 fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
10529 loadFlag = 0; /* [HGM] true game starts */
10533 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
10535 ReloadPosition(offset)
10538 int positionNumber = lastLoadPositionNumber + offset;
10539 if (lastLoadPositionFP == NULL) {
10540 DisplayError(_("No position has been loaded yet"), 0);
10543 if (positionNumber <= 0) {
10544 DisplayError(_("Can't back up any further"), 0);
10547 return LoadPosition(lastLoadPositionFP, positionNumber,
10548 lastLoadPositionTitle);
10551 /* Load the nth position from the given file */
10553 LoadPositionFromFile(filename, n, title)
10561 if (strcmp(filename, "-") == 0) {
10562 return LoadPosition(stdin, n, "stdin");
10564 f = fopen(filename, "rb");
10566 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10567 DisplayError(buf, errno);
10570 return LoadPosition(f, n, title);
10575 /* Load the nth position from the given open file, and close it */
10577 LoadPosition(f, positionNumber, title)
10579 int positionNumber;
10582 char *p, line[MSG_SIZ];
10583 Board initial_position;
10584 int i, j, fenMode, pn;
10586 if (gameMode == Training )
10587 SetTrainingModeOff();
10589 if (gameMode != BeginningOfGame) {
10590 Reset(FALSE, TRUE);
10592 if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
10593 fclose(lastLoadPositionFP);
10595 if (positionNumber == 0) positionNumber = 1;
10596 lastLoadPositionFP = f;
10597 lastLoadPositionNumber = positionNumber;
10598 safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
10599 if (first.pr == NoProc) {
10600 StartChessProgram(&first);
10601 InitChessProgram(&first, FALSE);
10603 pn = positionNumber;
10604 if (positionNumber < 0) {
10605 /* Negative position number means to seek to that byte offset */
10606 if (fseek(f, -positionNumber, 0) == -1) {
10607 DisplayError(_("Can't seek on position file"), 0);
10612 if (fseek(f, 0, 0) == -1) {
10613 if (f == lastLoadPositionFP ?
10614 positionNumber == lastLoadPositionNumber + 1 :
10615 positionNumber == 1) {
10618 DisplayError(_("Can't seek on position file"), 0);
10623 /* See if this file is FEN or old-style xboard */
10624 if (fgets(line, MSG_SIZ, f) == NULL) {
10625 DisplayError(_("Position not found in file"), 0);
10628 // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
10629 fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
10632 if (fenMode || line[0] == '#') pn--;
10634 /* skip positions before number pn */
10635 if (fgets(line, MSG_SIZ, f) == NULL) {
10637 DisplayError(_("Position not found in file"), 0);
10640 if (fenMode || line[0] == '#') pn--;
10645 if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
10646 DisplayError(_("Bad FEN position in file"), 0);
10650 (void) fgets(line, MSG_SIZ, f);
10651 (void) fgets(line, MSG_SIZ, f);
10653 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
10654 (void) fgets(line, MSG_SIZ, f);
10655 for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
10658 initial_position[i][j++] = CharToPiece(*p);
10662 blackPlaysFirst = FALSE;
10664 (void) fgets(line, MSG_SIZ, f);
10665 if (strncmp(line, "black", strlen("black"))==0)
10666 blackPlaysFirst = TRUE;
10669 startedFromSetupPosition = TRUE;
10671 SendToProgram("force\n", &first);
10672 CopyBoard(boards[0], initial_position);
10673 if (blackPlaysFirst) {
10674 currentMove = forwardMostMove = backwardMostMove = 1;
10675 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
10676 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
10677 CopyBoard(boards[1], initial_position);
10678 DisplayMessage("", _("Black to play"));
10680 currentMove = forwardMostMove = backwardMostMove = 0;
10681 DisplayMessage("", _("White to play"));
10683 initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
10684 SendBoard(&first, forwardMostMove);
10685 if (appData.debugMode) {
10687 for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
10688 for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
10689 fprintf(debugFP, "Load Position\n");
10692 if (positionNumber > 1) {
10693 snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
10694 DisplayTitle(line);
10696 DisplayTitle(title);
10698 gameMode = EditGame;
10701 timeRemaining[0][1] = whiteTimeRemaining;
10702 timeRemaining[1][1] = blackTimeRemaining;
10703 DrawPosition(FALSE, boards[currentMove]);
10710 CopyPlayerNameIntoFileName(dest, src)
10713 while (*src != NULLCHAR && *src != ',') {
10718 *(*dest)++ = *src++;
10723 char *DefaultFileName(ext)
10726 static char def[MSG_SIZ];
10729 if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
10731 CopyPlayerNameIntoFileName(&p, gameInfo.white);
10733 CopyPlayerNameIntoFileName(&p, gameInfo.black);
10735 safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
10742 /* Save the current game to the given file */
10744 SaveGameToFile(filename, append)
10751 if (strcmp(filename, "-") == 0) {
10752 return SaveGame(stdout, 0, NULL);
10754 f = fopen(filename, append ? "a" : "w");
10756 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10757 DisplayError(buf, errno);
10760 return SaveGame(f, 0, NULL);
10769 static char buf[MSG_SIZ];
10772 p = strchr(str, ' ');
10773 if (p == NULL) return str;
10774 strncpy(buf, str, p - str);
10775 buf[p - str] = NULLCHAR;
10779 #define PGN_MAX_LINE 75
10781 #define PGN_SIDE_WHITE 0
10782 #define PGN_SIDE_BLACK 1
10785 static int FindFirstMoveOutOfBook( int side )
10789 if( backwardMostMove == 0 && ! startedFromSetupPosition) {
10790 int index = backwardMostMove;
10791 int has_book_hit = 0;
10793 if( (index % 2) != side ) {
10797 while( index < forwardMostMove ) {
10798 /* Check to see if engine is in book */
10799 int depth = pvInfoList[index].depth;
10800 int score = pvInfoList[index].score;
10806 else if( score == 0 && depth == 63 ) {
10807 in_book = 1; /* Zappa */
10809 else if( score == 2 && depth == 99 ) {
10810 in_book = 1; /* Abrok */
10813 has_book_hit += in_book;
10829 void GetOutOfBookInfo( char * buf )
10833 int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10835 oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
10836 oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
10840 if( oob[0] >= 0 || oob[1] >= 0 ) {
10841 for( i=0; i<2; i++ ) {
10845 if( i > 0 && oob[0] >= 0 ) {
10846 strcat( buf, " " );
10849 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
10850 sprintf( buf+strlen(buf), "%s%.2f",
10851 pvInfoList[idx].score >= 0 ? "+" : "",
10852 pvInfoList[idx].score / 100.0 );
10858 /* Save game in PGN style and close the file */
10863 int i, offset, linelen, newblock;
10867 int movelen, numlen, blank;
10868 char move_buffer[100]; /* [AS] Buffer for move+PV info */
10870 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10872 tm = time((time_t *) NULL);
10874 PrintPGNTags(f, &gameInfo);
10876 if (backwardMostMove > 0 || startedFromSetupPosition) {
10877 char *fen = PositionToFEN(backwardMostMove, NULL);
10878 fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
10879 fprintf(f, "\n{--------------\n");
10880 PrintPosition(f, backwardMostMove);
10881 fprintf(f, "--------------}\n");
10885 /* [AS] Out of book annotation */
10886 if( appData.saveOutOfBookInfo ) {
10889 GetOutOfBookInfo( buf );
10891 if( buf[0] != '\0' ) {
10892 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
10899 i = backwardMostMove;
10903 while (i < forwardMostMove) {
10904 /* Print comments preceding this move */
10905 if (commentList[i] != NULL) {
10906 if (linelen > 0) fprintf(f, "\n");
10907 fprintf(f, "%s", commentList[i]);
10912 /* Format move number */
10914 snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
10917 snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
10919 numtext[0] = NULLCHAR;
10921 numlen = strlen(numtext);
10924 /* Print move number */
10925 blank = linelen > 0 && numlen > 0;
10926 if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
10935 fprintf(f, "%s", numtext);
10939 safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
10940 movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
10943 blank = linelen > 0 && movelen > 0;
10944 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
10953 fprintf(f, "%s", move_buffer);
10954 linelen += movelen;
10956 /* [AS] Add PV info if present */
10957 if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
10958 /* [HGM] add time */
10959 char buf[MSG_SIZ]; int seconds;
10961 seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
10967 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
10970 seconds = (seconds + 4)/10; // round to full seconds
10972 snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
10974 snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
10977 snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
10978 pvInfoList[i].score >= 0 ? "+" : "",
10979 pvInfoList[i].score / 100.0,
10980 pvInfoList[i].depth,
10983 movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
10985 /* Print score/depth */
10986 blank = linelen > 0 && movelen > 0;
10987 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
10996 fprintf(f, "%s", move_buffer);
10997 linelen += movelen;
11003 /* Start a new line */
11004 if (linelen > 0) fprintf(f, "\n");
11006 /* Print comments after last move */
11007 if (commentList[i] != NULL) {
11008 fprintf(f, "%s\n", commentList[i]);
11012 if (gameInfo.resultDetails != NULL &&
11013 gameInfo.resultDetails[0] != NULLCHAR) {
11014 fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
11015 PGNResult(gameInfo.result));
11017 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
11021 lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
11025 /* Save game in old style and close the file */
11027 SaveGameOldStyle(f)
11033 tm = time((time_t *) NULL);
11035 fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
11038 if (backwardMostMove > 0 || startedFromSetupPosition) {
11039 fprintf(f, "\n[--------------\n");
11040 PrintPosition(f, backwardMostMove);
11041 fprintf(f, "--------------]\n");
11046 i = backwardMostMove;
11047 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
11049 while (i < forwardMostMove) {
11050 if (commentList[i] != NULL) {
11051 fprintf(f, "[%s]\n", commentList[i]);
11054 if ((i % 2) == 1) {
11055 fprintf(f, "%d. ... %s\n", (i - offset)/2 + 1, parseList[i]);
11058 fprintf(f, "%d. %s ", (i - offset)/2 + 1, parseList[i]);
11060 if (commentList[i] != NULL) {
11064 if (i >= forwardMostMove) {
11068 fprintf(f, "%s\n", parseList[i]);
11073 if (commentList[i] != NULL) {
11074 fprintf(f, "[%s]\n", commentList[i]);
11077 /* This isn't really the old style, but it's close enough */
11078 if (gameInfo.resultDetails != NULL &&
11079 gameInfo.resultDetails[0] != NULLCHAR) {
11080 fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
11081 gameInfo.resultDetails);
11083 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
11090 /* Save the current game to open file f and close the file */
11092 SaveGame(f, dummy, dummy2)
11097 if (gameMode == EditPosition) EditPositionDone(TRUE);
11098 lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
11099 if (appData.oldSaveStyle)
11100 return SaveGameOldStyle(f);
11102 return SaveGamePGN(f);
11105 /* Save the current position to the given file */
11107 SavePositionToFile(filename)
11113 if (strcmp(filename, "-") == 0) {
11114 return SavePosition(stdout, 0, NULL);
11116 f = fopen(filename, "a");
11118 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
11119 DisplayError(buf, errno);
11122 SavePosition(f, 0, NULL);
11128 /* Save the current position to the given open file and close the file */
11130 SavePosition(f, dummy, dummy2)
11138 if (gameMode == EditPosition) EditPositionDone(TRUE);
11139 if (appData.oldSaveStyle) {
11140 tm = time((time_t *) NULL);
11142 fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
11144 fprintf(f, "[--------------\n");
11145 PrintPosition(f, currentMove);
11146 fprintf(f, "--------------]\n");
11148 fen = PositionToFEN(currentMove, NULL);
11149 fprintf(f, "%s\n", fen);
11157 ReloadCmailMsgEvent(unregister)
11161 static char *inFilename = NULL;
11162 static char *outFilename;
11164 struct stat inbuf, outbuf;
11167 /* Any registered moves are unregistered if unregister is set, */
11168 /* i.e. invoked by the signal handler */
11170 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
11171 cmailMoveRegistered[i] = FALSE;
11172 if (cmailCommentList[i] != NULL) {
11173 free(cmailCommentList[i]);
11174 cmailCommentList[i] = NULL;
11177 nCmailMovesRegistered = 0;
11180 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
11181 cmailResult[i] = CMAIL_NOT_RESULT;
11185 if (inFilename == NULL) {
11186 /* Because the filenames are static they only get malloced once */
11187 /* and they never get freed */
11188 inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
11189 sprintf(inFilename, "%s.game.in", appData.cmailGameName);
11191 outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
11192 sprintf(outFilename, "%s.out", appData.cmailGameName);
11195 status = stat(outFilename, &outbuf);
11197 cmailMailedMove = FALSE;
11199 status = stat(inFilename, &inbuf);
11200 cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
11203 /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
11204 counts the games, notes how each one terminated, etc.
11206 It would be nice to remove this kludge and instead gather all
11207 the information while building the game list. (And to keep it
11208 in the game list nodes instead of having a bunch of fixed-size
11209 parallel arrays.) Note this will require getting each game's
11210 termination from the PGN tags, as the game list builder does
11211 not process the game moves. --mann
11213 cmailMsgLoaded = TRUE;
11214 LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
11216 /* Load first game in the file or popup game menu */
11217 LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
11219 #endif /* !WIN32 */
11227 char string[MSG_SIZ];
11229 if ( cmailMailedMove
11230 || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
11231 return TRUE; /* Allow free viewing */
11234 /* Unregister move to ensure that we don't leave RegisterMove */
11235 /* with the move registered when the conditions for registering no */
11237 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11238 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
11239 nCmailMovesRegistered --;
11241 if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
11243 free(cmailCommentList[lastLoadGameNumber - 1]);
11244 cmailCommentList[lastLoadGameNumber - 1] = NULL;
11248 if (cmailOldMove == -1) {
11249 DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
11253 if (currentMove > cmailOldMove + 1) {
11254 DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
11258 if (currentMove < cmailOldMove) {
11259 DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
11263 if (forwardMostMove > currentMove) {
11264 /* Silently truncate extra moves */
11268 if ( (currentMove == cmailOldMove + 1)
11269 || ( (currentMove == cmailOldMove)
11270 && ( (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
11271 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
11272 if (gameInfo.result != GameUnfinished) {
11273 cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
11276 if (commentList[currentMove] != NULL) {
11277 cmailCommentList[lastLoadGameNumber - 1]
11278 = StrSave(commentList[currentMove]);
11280 safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
11282 if (appData.debugMode)
11283 fprintf(debugFP, "Saving %s for game %d\n",
11284 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
11286 snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
11288 f = fopen(string, "w");
11289 if (appData.oldSaveStyle) {
11290 SaveGameOldStyle(f); /* also closes the file */
11292 snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
11293 f = fopen(string, "w");
11294 SavePosition(f, 0, NULL); /* also closes the file */
11296 fprintf(f, "{--------------\n");
11297 PrintPosition(f, currentMove);
11298 fprintf(f, "--------------}\n\n");
11300 SaveGame(f, 0, NULL); /* also closes the file*/
11303 cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
11304 nCmailMovesRegistered ++;
11305 } else if (nCmailGames == 1) {
11306 DisplayError(_("You have not made a move yet"), 0);
11317 static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
11318 FILE *commandOutput;
11319 char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
11320 int nBytes = 0; /* Suppress warnings on uninitialized variables */
11326 if (! cmailMsgLoaded) {
11327 DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
11331 if (nCmailGames == nCmailResults) {
11332 DisplayError(_("No unfinished games"), 0);
11336 #if CMAIL_PROHIBIT_REMAIL
11337 if (cmailMailedMove) {
11338 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);
11339 DisplayError(msg, 0);
11344 if (! (cmailMailedMove || RegisterMove())) return;
11346 if ( cmailMailedMove
11347 || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
11348 snprintf(string, MSG_SIZ, partCommandString,
11349 appData.debugMode ? " -v" : "", appData.cmailGameName);
11350 commandOutput = popen(string, "r");
11352 if (commandOutput == NULL) {
11353 DisplayError(_("Failed to invoke cmail"), 0);
11355 for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
11356 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
11358 if (nBuffers > 1) {
11359 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
11360 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
11361 nBytes = MSG_SIZ - 1;
11363 (void) memcpy(msg, buffer, nBytes);
11365 *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
11367 if(StrStr(msg, "Mailed cmail message to ") != NULL) {
11368 cmailMailedMove = TRUE; /* Prevent >1 moves */
11371 for (i = 0; i < nCmailGames; i ++) {
11372 if (cmailResult[i] == CMAIL_NOT_RESULT) {
11377 && ( (arcDir = (char *) getenv("CMAIL_ARCDIR"))
11379 snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
11381 appData.cmailGameName,
11383 LoadGameFromFile(buffer, 1, buffer, FALSE);
11384 cmailMsgLoaded = FALSE;
11388 DisplayInformation(msg);
11389 pclose(commandOutput);
11392 if ((*cmailMsg) != '\0') {
11393 DisplayInformation(cmailMsg);
11398 #endif /* !WIN32 */
11407 int prependComma = 0;
11409 char string[MSG_SIZ]; /* Space for game-list */
11412 if (!cmailMsgLoaded) return "";
11414 if (cmailMailedMove) {
11415 snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
11417 /* Create a list of games left */
11418 snprintf(string, MSG_SIZ, "[");
11419 for (i = 0; i < nCmailGames; i ++) {
11420 if (! ( cmailMoveRegistered[i]
11421 || (cmailResult[i] == CMAIL_OLD_RESULT))) {
11422 if (prependComma) {
11423 snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
11425 snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
11429 strcat(string, number);
11432 strcat(string, "]");
11434 if (nCmailMovesRegistered + nCmailResults == 0) {
11435 switch (nCmailGames) {
11437 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
11441 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
11445 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
11450 switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
11452 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
11457 if (nCmailResults == nCmailGames) {
11458 snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
11460 snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
11465 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
11477 if (gameMode == Training)
11478 SetTrainingModeOff();
11481 cmailMsgLoaded = FALSE;
11482 if (appData.icsActive) {
11483 SendToICS(ics_prefix);
11484 SendToICS("refresh\n");
11494 /* Give up on clean exit */
11498 /* Keep trying for clean exit */
11502 if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
11504 if (telnetISR != NULL) {
11505 RemoveInputSource(telnetISR);
11507 if (icsPR != NoProc) {
11508 DestroyChildProcess(icsPR, TRUE);
11511 /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
11512 GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
11514 /* [HGM] crash: the above GameEnds() is a dud if another one was running */
11515 /* make sure this other one finishes before killing it! */
11516 if(endingGame) { int count = 0;
11517 if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
11518 while(endingGame && count++ < 10) DoSleep(1);
11519 if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
11522 /* Kill off chess programs */
11523 if (first.pr != NoProc) {
11526 DoSleep( appData.delayBeforeQuit );
11527 SendToProgram("quit\n", &first);
11528 DoSleep( appData.delayAfterQuit );
11529 DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
11531 if (second.pr != NoProc) {
11532 DoSleep( appData.delayBeforeQuit );
11533 SendToProgram("quit\n", &second);
11534 DoSleep( appData.delayAfterQuit );
11535 DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
11537 if (first.isr != NULL) {
11538 RemoveInputSource(first.isr);
11540 if (second.isr != NULL) {
11541 RemoveInputSource(second.isr);
11544 ShutDownFrontEnd();
11551 if (appData.debugMode)
11552 fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
11556 if (gameMode == MachinePlaysWhite ||
11557 gameMode == MachinePlaysBlack) {
11560 DisplayBothClocks();
11562 if (gameMode == PlayFromGameFile) {
11563 if (appData.timeDelay >= 0)
11564 AutoPlayGameLoop();
11565 } else if (gameMode == IcsExamining && pauseExamInvalid) {
11566 Reset(FALSE, TRUE);
11567 SendToICS(ics_prefix);
11568 SendToICS("refresh\n");
11569 } else if (currentMove < forwardMostMove) {
11570 ForwardInner(forwardMostMove);
11572 pauseExamInvalid = FALSE;
11574 switch (gameMode) {
11578 pauseExamForwardMostMove = forwardMostMove;
11579 pauseExamInvalid = FALSE;
11582 case IcsPlayingWhite:
11583 case IcsPlayingBlack:
11587 case PlayFromGameFile:
11588 (void) StopLoadGameTimer();
11592 case BeginningOfGame:
11593 if (appData.icsActive) return;
11594 /* else fall through */
11595 case MachinePlaysWhite:
11596 case MachinePlaysBlack:
11597 case TwoMachinesPlay:
11598 if (forwardMostMove == 0)
11599 return; /* don't pause if no one has moved */
11600 if ((gameMode == MachinePlaysWhite &&
11601 !WhiteOnMove(forwardMostMove)) ||
11602 (gameMode == MachinePlaysBlack &&
11603 WhiteOnMove(forwardMostMove))) {
11616 char title[MSG_SIZ];
11618 if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
11619 safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
11621 snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
11622 WhiteOnMove(currentMove - 1) ? " " : ".. ",
11623 parseList[currentMove - 1]);
11626 EditCommentPopUp(currentMove, title, commentList[currentMove]);
11633 char *tags = PGNTags(&gameInfo);
11634 EditTagsPopUp(tags);
11641 if (appData.noChessProgram || gameMode == AnalyzeMode)
11644 if (gameMode != AnalyzeFile) {
11645 if (!appData.icsEngineAnalyze) {
11647 if (gameMode != EditGame) return;
11649 ResurrectChessProgram();
11650 SendToProgram("analyze\n", &first);
11651 first.analyzing = TRUE;
11652 /*first.maybeThinking = TRUE;*/
11653 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11654 EngineOutputPopUp();
11656 if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
11661 StartAnalysisClock();
11662 GetTimeMark(&lastNodeCountTime);
11669 if (appData.noChessProgram || gameMode == AnalyzeFile)
11672 if (gameMode != AnalyzeMode) {
11674 if (gameMode != EditGame) return;
11675 ResurrectChessProgram();
11676 SendToProgram("analyze\n", &first);
11677 first.analyzing = TRUE;
11678 /*first.maybeThinking = TRUE;*/
11679 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11680 EngineOutputPopUp();
11682 gameMode = AnalyzeFile;
11687 StartAnalysisClock();
11688 GetTimeMark(&lastNodeCountTime);
11693 MachineWhiteEvent()
11696 char *bookHit = NULL;
11698 if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
11702 if (gameMode == PlayFromGameFile ||
11703 gameMode == TwoMachinesPlay ||
11704 gameMode == Training ||
11705 gameMode == AnalyzeMode ||
11706 gameMode == EndOfGame)
11709 if (gameMode == EditPosition)
11710 EditPositionDone(TRUE);
11712 if (!WhiteOnMove(currentMove)) {
11713 DisplayError(_("It is not White's turn"), 0);
11717 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
11720 if (gameMode == EditGame || gameMode == AnalyzeMode ||
11721 gameMode == AnalyzeFile)
11724 ResurrectChessProgram(); /* in case it isn't running */
11725 if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
11726 gameMode = MachinePlaysWhite;
11729 gameMode = MachinePlaysWhite;
11733 snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
11735 if (first.sendName) {
11736 snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
11737 SendToProgram(buf, &first);
11739 if (first.sendTime) {
11740 if (first.useColors) {
11741 SendToProgram("black\n", &first); /*gnu kludge*/
11743 SendTimeRemaining(&first, TRUE);
11745 if (first.useColors) {
11746 SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
11748 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
11749 SetMachineThinkingEnables();
11750 first.maybeThinking = TRUE;
11754 if (appData.autoFlipView && !flipView) {
11755 flipView = !flipView;
11756 DrawPosition(FALSE, NULL);
11757 DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;
11760 if(bookHit) { // [HGM] book: simulate book reply
11761 static char bookMove[MSG_SIZ]; // a bit generous?
11763 programStats.nodes = programStats.depth = programStats.time =
11764 programStats.score = programStats.got_only_move = 0;
11765 sprintf(programStats.movelist, "%s (xbook)", bookHit);
11767 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
11768 strcat(bookMove, bookHit);
11769 HandleMachineMove(bookMove, &first);
11774 MachineBlackEvent()
11777 char *bookHit = NULL;
11779 if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
11783 if (gameMode == PlayFromGameFile ||
11784 gameMode == TwoMachinesPlay ||
11785 gameMode == Training ||
11786 gameMode == AnalyzeMode ||
11787 gameMode == EndOfGame)
11790 if (gameMode == EditPosition)
11791 EditPositionDone(TRUE);
11793 if (WhiteOnMove(currentMove)) {
11794 DisplayError(_("It is not Black's turn"), 0);
11798 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
11801 if (gameMode == EditGame || gameMode == AnalyzeMode ||
11802 gameMode == AnalyzeFile)
11805 ResurrectChessProgram(); /* in case it isn't running */
11806 gameMode = MachinePlaysBlack;
11810 snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
11812 if (first.sendName) {
11813 snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
11814 SendToProgram(buf, &first);
11816 if (first.sendTime) {
11817 if (first.useColors) {
11818 SendToProgram("white\n", &first); /*gnu kludge*/
11820 SendTimeRemaining(&first, FALSE);
11822 if (first.useColors) {
11823 SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
11825 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
11826 SetMachineThinkingEnables();
11827 first.maybeThinking = TRUE;
11830 if (appData.autoFlipView && flipView) {
11831 flipView = !flipView;
11832 DrawPosition(FALSE, NULL);
11833 DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;
11835 if(bookHit) { // [HGM] book: simulate book reply
11836 static char bookMove[MSG_SIZ]; // a bit generous?
11838 programStats.nodes = programStats.depth = programStats.time =
11839 programStats.score = programStats.got_only_move = 0;
11840 sprintf(programStats.movelist, "%s (xbook)", bookHit);
11842 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
11843 strcat(bookMove, bookHit);
11844 HandleMachineMove(bookMove, &first);
11850 DisplayTwoMachinesTitle()
11853 if (appData.matchGames > 0) {
11854 if (first.twoMachinesColor[0] == 'w') {
11855 snprintf(buf, MSG_SIZ, "%s vs. %s (%d-%d-%d)",
11856 gameInfo.white, gameInfo.black,
11857 first.matchWins, second.matchWins,
11858 matchGame - 1 - (first.matchWins + second.matchWins));
11860 snprintf(buf, MSG_SIZ, "%s vs. %s (%d-%d-%d)",
11861 gameInfo.white, gameInfo.black,
11862 second.matchWins, first.matchWins,
11863 matchGame - 1 - (first.matchWins + second.matchWins));
11866 snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
11872 SettingsMenuIfReady()
11874 if (second.lastPing != second.lastPong) {
11875 DisplayMessage("", _("Waiting for second chess program"));
11876 ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
11880 DisplayMessage("", "");
11881 SettingsPopUp(&second);
11885 WaitForSecond(DelayedEventCallback retry)
11887 if (second.pr == NULL) {
11888 StartChessProgram(&second);
11889 if (second.protocolVersion == 1) {
11892 /* kludge: allow timeout for initial "feature" command */
11894 DisplayMessage("", _("Starting second chess program"));
11895 ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
11903 TwoMachinesEvent P((void))
11907 ChessProgramState *onmove;
11908 char *bookHit = NULL;
11910 if (appData.noChessProgram) return;
11912 switch (gameMode) {
11913 case TwoMachinesPlay:
11915 case MachinePlaysWhite:
11916 case MachinePlaysBlack:
11917 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
11918 DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
11922 case BeginningOfGame:
11923 case PlayFromGameFile:
11926 if (gameMode != EditGame) return;
11929 EditPositionDone(TRUE);
11940 // forwardMostMove = currentMove;
11941 TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
11942 ResurrectChessProgram(); /* in case first program isn't running */
11944 if(WaitForSecond(TwoMachinesEventIfReady)) return;
11945 DisplayMessage("", "");
11946 InitChessProgram(&second, FALSE);
11947 SendToProgram("force\n", &second);
11948 if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
11949 ScheduleDelayedEvent(TwoMachinesEvent, 10);
11952 if (startedFromSetupPosition) {
11953 SendBoard(&second, backwardMostMove);
11954 if (appData.debugMode) {
11955 fprintf(debugFP, "Two Machines\n");
11958 for (i = backwardMostMove; i < forwardMostMove; i++) {
11959 SendMoveToProgram(i, &second);
11962 gameMode = TwoMachinesPlay;
11966 DisplayTwoMachinesTitle();
11968 if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
11974 SendToProgram(first.computerString, &first);
11975 if (first.sendName) {
11976 snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
11977 SendToProgram(buf, &first);
11979 SendToProgram(second.computerString, &second);
11980 if (second.sendName) {
11981 snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
11982 SendToProgram(buf, &second);
11986 if (!first.sendTime || !second.sendTime) {
11987 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11988 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11990 if (onmove->sendTime) {
11991 if (onmove->useColors) {
11992 SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
11994 SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
11996 if (onmove->useColors) {
11997 SendToProgram(onmove->twoMachinesColor, onmove);
11999 bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
12000 // SendToProgram("go\n", onmove);
12001 onmove->maybeThinking = TRUE;
12002 SetMachineThinkingEnables();
12006 if(bookHit) { // [HGM] book: simulate book reply
12007 static char bookMove[MSG_SIZ]; // a bit generous?
12009 programStats.nodes = programStats.depth = programStats.time =
12010 programStats.score = programStats.got_only_move = 0;
12011 sprintf(programStats.movelist, "%s (xbook)", bookHit);
12013 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
12014 strcat(bookMove, bookHit);
12015 savedMessage = bookMove; // args for deferred call
12016 savedState = onmove;
12017 ScheduleDelayedEvent(DeferredBookMove, 1);
12024 if (gameMode == Training) {
12025 SetTrainingModeOff();
12026 gameMode = PlayFromGameFile;
12027 DisplayMessage("", _("Training mode off"));
12029 gameMode = Training;
12030 animateTraining = appData.animate;
12032 /* make sure we are not already at the end of the game */
12033 if (currentMove < forwardMostMove) {
12034 SetTrainingModeOn();
12035 DisplayMessage("", _("Training mode on"));
12037 gameMode = PlayFromGameFile;
12038 DisplayError(_("Already at end of game"), 0);
12047 if (!appData.icsActive) return;
12048 switch (gameMode) {
12049 case IcsPlayingWhite:
12050 case IcsPlayingBlack:
12053 case BeginningOfGame:
12061 EditPositionDone(TRUE);
12074 gameMode = IcsIdle;
12085 switch (gameMode) {
12087 SetTrainingModeOff();
12089 case MachinePlaysWhite:
12090 case MachinePlaysBlack:
12091 case BeginningOfGame:
12092 SendToProgram("force\n", &first);
12093 SetUserThinkingEnables();
12095 case PlayFromGameFile:
12096 (void) StopLoadGameTimer();
12097 if (gameFileFP != NULL) {
12102 EditPositionDone(TRUE);
12107 SendToProgram("force\n", &first);
12109 case TwoMachinesPlay:
12110 GameEnds(EndOfFile, NULL, GE_PLAYER);
12111 ResurrectChessProgram();
12112 SetUserThinkingEnables();
12115 ResurrectChessProgram();
12117 case IcsPlayingBlack:
12118 case IcsPlayingWhite:
12119 DisplayError(_("Warning: You are still playing a game"), 0);
12122 DisplayError(_("Warning: You are still observing a game"), 0);
12125 DisplayError(_("Warning: You are still examining a game"), 0);
12136 first.offeredDraw = second.offeredDraw = 0;
12138 if (gameMode == PlayFromGameFile) {
12139 whiteTimeRemaining = timeRemaining[0][currentMove];
12140 blackTimeRemaining = timeRemaining[1][currentMove];
12144 if (gameMode == MachinePlaysWhite ||
12145 gameMode == MachinePlaysBlack ||
12146 gameMode == TwoMachinesPlay ||
12147 gameMode == EndOfGame) {
12148 i = forwardMostMove;
12149 while (i > currentMove) {
12150 SendToProgram("undo\n", &first);
12153 whiteTimeRemaining = timeRemaining[0][currentMove];
12154 blackTimeRemaining = timeRemaining[1][currentMove];
12155 DisplayBothClocks();
12156 if (whiteFlag || blackFlag) {
12157 whiteFlag = blackFlag = 0;
12162 gameMode = EditGame;
12169 EditPositionEvent()
12171 if (gameMode == EditPosition) {
12177 if (gameMode != EditGame) return;
12179 gameMode = EditPosition;
12182 if (currentMove > 0)
12183 CopyBoard(boards[0], boards[currentMove]);
12185 blackPlaysFirst = !WhiteOnMove(currentMove);
12187 currentMove = forwardMostMove = backwardMostMove = 0;
12188 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12195 /* [DM] icsEngineAnalyze - possible call from other functions */
12196 if (appData.icsEngineAnalyze) {
12197 appData.icsEngineAnalyze = FALSE;
12199 DisplayMessage("",_("Close ICS engine analyze..."));
12201 if (first.analysisSupport && first.analyzing) {
12202 SendToProgram("exit\n", &first);
12203 first.analyzing = FALSE;
12205 thinkOutput[0] = NULLCHAR;
12209 EditPositionDone(Boolean fakeRights)
12211 int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
12213 startedFromSetupPosition = TRUE;
12214 InitChessProgram(&first, FALSE);
12215 if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
12216 boards[0][EP_STATUS] = EP_NONE;
12217 boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
12218 if(boards[0][0][BOARD_WIDTH>>1] == king) {
12219 boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : NoRights;
12220 boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
12221 } else boards[0][CASTLING][2] = NoRights;
12222 if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
12223 boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : NoRights;
12224 boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
12225 } else boards[0][CASTLING][5] = NoRights;
12227 SendToProgram("force\n", &first);
12228 if (blackPlaysFirst) {
12229 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12230 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12231 currentMove = forwardMostMove = backwardMostMove = 1;
12232 CopyBoard(boards[1], boards[0]);
12234 currentMove = forwardMostMove = backwardMostMove = 0;
12236 SendBoard(&first, forwardMostMove);
12237 if (appData.debugMode) {
12238 fprintf(debugFP, "EditPosDone\n");
12241 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12242 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12243 gameMode = EditGame;
12245 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12246 ClearHighlights(); /* [AS] */
12249 /* Pause for `ms' milliseconds */
12250 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
12260 } while (SubtractTimeMarks(&m2, &m1) < ms);
12263 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
12265 SendMultiLineToICS(buf)
12268 char temp[MSG_SIZ+1], *p;
12275 strncpy(temp, buf, len);
12280 if (*p == '\n' || *p == '\r')
12285 strcat(temp, "\n");
12287 SendToPlayer(temp, strlen(temp));
12291 SetWhiteToPlayEvent()
12293 if (gameMode == EditPosition) {
12294 blackPlaysFirst = FALSE;
12295 DisplayBothClocks(); /* works because currentMove is 0 */
12296 } else if (gameMode == IcsExamining) {
12297 SendToICS(ics_prefix);
12298 SendToICS("tomove white\n");
12303 SetBlackToPlayEvent()
12305 if (gameMode == EditPosition) {
12306 blackPlaysFirst = TRUE;
12307 currentMove = 1; /* kludge */
12308 DisplayBothClocks();
12310 } else if (gameMode == IcsExamining) {
12311 SendToICS(ics_prefix);
12312 SendToICS("tomove black\n");
12317 EditPositionMenuEvent(selection, x, y)
12318 ChessSquare selection;
12322 ChessSquare piece = boards[0][y][x];
12324 if (gameMode != EditPosition && gameMode != IcsExamining) return;
12326 switch (selection) {
12328 if (gameMode == IcsExamining && ics_type == ICS_FICS) {
12329 SendToICS(ics_prefix);
12330 SendToICS("bsetup clear\n");
12331 } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
12332 SendToICS(ics_prefix);
12333 SendToICS("clearboard\n");
12335 for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
12336 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
12337 for (y = 0; y < BOARD_HEIGHT; y++) {
12338 if (gameMode == IcsExamining) {
12339 if (boards[currentMove][y][x] != EmptySquare) {
12340 snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
12345 boards[0][y][x] = p;
12350 if (gameMode == EditPosition) {
12351 DrawPosition(FALSE, boards[0]);
12356 SetWhiteToPlayEvent();
12360 SetBlackToPlayEvent();
12364 if (gameMode == IcsExamining) {
12365 if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
12366 snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
12369 if(x < BOARD_LEFT || x >= BOARD_RGHT) {
12370 if(x == BOARD_LEFT-2) {
12371 if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
12372 boards[0][y][1] = 0;
12374 if(x == BOARD_RGHT+1) {
12375 if(y >= gameInfo.holdingsSize) break;
12376 boards[0][y][BOARD_WIDTH-2] = 0;
12379 boards[0][y][x] = EmptySquare;
12380 DrawPosition(FALSE, boards[0]);
12385 if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
12386 piece >= (int)BlackPawn && piece < (int)BlackMan ) {
12387 selection = (ChessSquare) (PROMOTED piece);
12388 } else if(piece == EmptySquare) selection = WhiteSilver;
12389 else selection = (ChessSquare)((int)piece - 1);
12393 if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
12394 piece > (int)BlackMan && piece <= (int)BlackKing ) {
12395 selection = (ChessSquare) (DEMOTED piece);
12396 } else if(piece == EmptySquare) selection = BlackSilver;
12397 else selection = (ChessSquare)((int)piece + 1);
12402 if(gameInfo.variant == VariantShatranj ||
12403 gameInfo.variant == VariantXiangqi ||
12404 gameInfo.variant == VariantCourier ||
12405 gameInfo.variant == VariantMakruk )
12406 selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
12411 if(gameInfo.variant == VariantXiangqi)
12412 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
12413 if(gameInfo.variant == VariantKnightmate)
12414 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
12417 if (gameMode == IcsExamining) {
12418 if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
12419 snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
12420 PieceToChar(selection), AAA + x, ONE + y);
12423 if(x < BOARD_LEFT || x >= BOARD_RGHT) {
12425 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
12426 n = PieceToNumber(selection - BlackPawn);
12427 if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
12428 boards[0][BOARD_HEIGHT-1-n][0] = selection;
12429 boards[0][BOARD_HEIGHT-1-n][1]++;
12431 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
12432 n = PieceToNumber(selection);
12433 if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
12434 boards[0][n][BOARD_WIDTH-1] = selection;
12435 boards[0][n][BOARD_WIDTH-2]++;
12438 boards[0][y][x] = selection;
12439 DrawPosition(TRUE, boards[0]);
12447 DropMenuEvent(selection, x, y)
12448 ChessSquare selection;
12451 ChessMove moveType;
12453 switch (gameMode) {
12454 case IcsPlayingWhite:
12455 case MachinePlaysBlack:
12456 if (!WhiteOnMove(currentMove)) {
12457 DisplayMoveError(_("It is Black's turn"));
12460 moveType = WhiteDrop;
12462 case IcsPlayingBlack:
12463 case MachinePlaysWhite:
12464 if (WhiteOnMove(currentMove)) {
12465 DisplayMoveError(_("It is White's turn"));
12468 moveType = BlackDrop;
12471 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
12477 if (moveType == BlackDrop && selection < BlackPawn) {
12478 selection = (ChessSquare) ((int) selection
12479 + (int) BlackPawn - (int) WhitePawn);
12481 if (boards[currentMove][y][x] != EmptySquare) {
12482 DisplayMoveError(_("That square is occupied"));
12486 FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
12492 /* Accept a pending offer of any kind from opponent */
12494 if (appData.icsActive) {
12495 SendToICS(ics_prefix);
12496 SendToICS("accept\n");
12497 } else if (cmailMsgLoaded) {
12498 if (currentMove == cmailOldMove &&
12499 commentList[cmailOldMove] != NULL &&
12500 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12501 "Black offers a draw" : "White offers a draw")) {
12503 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
12504 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
12506 DisplayError(_("There is no pending offer on this move"), 0);
12507 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12510 /* Not used for offers from chess program */
12517 /* Decline a pending offer of any kind from opponent */
12519 if (appData.icsActive) {
12520 SendToICS(ics_prefix);
12521 SendToICS("decline\n");
12522 } else if (cmailMsgLoaded) {
12523 if (currentMove == cmailOldMove &&
12524 commentList[cmailOldMove] != NULL &&
12525 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12526 "Black offers a draw" : "White offers a draw")) {
12528 AppendComment(cmailOldMove, "Draw declined", TRUE);
12529 DisplayComment(cmailOldMove - 1, "Draw declined");
12532 DisplayError(_("There is no pending offer on this move"), 0);
12535 /* Not used for offers from chess program */
12542 /* Issue ICS rematch command */
12543 if (appData.icsActive) {
12544 SendToICS(ics_prefix);
12545 SendToICS("rematch\n");
12552 /* Call your opponent's flag (claim a win on time) */
12553 if (appData.icsActive) {
12554 SendToICS(ics_prefix);
12555 SendToICS("flag\n");
12557 switch (gameMode) {
12560 case MachinePlaysWhite:
12563 GameEnds(GameIsDrawn, "Both players ran out of time",
12566 GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
12568 DisplayError(_("Your opponent is not out of time"), 0);
12571 case MachinePlaysBlack:
12574 GameEnds(GameIsDrawn, "Both players ran out of time",
12577 GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
12579 DisplayError(_("Your opponent is not out of time"), 0);
12589 /* Offer draw or accept pending draw offer from opponent */
12591 if (appData.icsActive) {
12592 /* Note: tournament rules require draw offers to be
12593 made after you make your move but before you punch
12594 your clock. Currently ICS doesn't let you do that;
12595 instead, you immediately punch your clock after making
12596 a move, but you can offer a draw at any time. */
12598 SendToICS(ics_prefix);
12599 SendToICS("draw\n");
12600 userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
12601 } else if (cmailMsgLoaded) {
12602 if (currentMove == cmailOldMove &&
12603 commentList[cmailOldMove] != NULL &&
12604 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12605 "Black offers a draw" : "White offers a draw")) {
12606 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
12607 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
12608 } else if (currentMove == cmailOldMove + 1) {
12609 char *offer = WhiteOnMove(cmailOldMove) ?
12610 "White offers a draw" : "Black offers a draw";
12611 AppendComment(currentMove, offer, TRUE);
12612 DisplayComment(currentMove - 1, offer);
12613 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
12615 DisplayError(_("You must make your move before offering a draw"), 0);
12616 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12618 } else if (first.offeredDraw) {
12619 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
12621 if (first.sendDrawOffers) {
12622 SendToProgram("draw\n", &first);
12623 userOfferedDraw = TRUE;
12631 /* Offer Adjourn or accept pending Adjourn offer from opponent */
12633 if (appData.icsActive) {
12634 SendToICS(ics_prefix);
12635 SendToICS("adjourn\n");
12637 /* Currently GNU Chess doesn't offer or accept Adjourns */
12645 /* Offer Abort or accept pending Abort offer from opponent */
12647 if (appData.icsActive) {
12648 SendToICS(ics_prefix);
12649 SendToICS("abort\n");
12651 GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
12658 /* Resign. You can do this even if it's not your turn. */
12660 if (appData.icsActive) {
12661 SendToICS(ics_prefix);
12662 SendToICS("resign\n");
12664 switch (gameMode) {
12665 case MachinePlaysWhite:
12666 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12668 case MachinePlaysBlack:
12669 GameEnds(BlackWins, "White resigns", GE_PLAYER);
12672 if (cmailMsgLoaded) {
12674 if (WhiteOnMove(cmailOldMove)) {
12675 GameEnds(BlackWins, "White resigns", GE_PLAYER);
12677 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12679 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
12690 StopObservingEvent()
12692 /* Stop observing current games */
12693 SendToICS(ics_prefix);
12694 SendToICS("unobserve\n");
12698 StopExaminingEvent()
12700 /* Stop observing current game */
12701 SendToICS(ics_prefix);
12702 SendToICS("unexamine\n");
12706 ForwardInner(target)
12711 if (appData.debugMode)
12712 fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
12713 target, currentMove, forwardMostMove);
12715 if (gameMode == EditPosition)
12718 if (gameMode == PlayFromGameFile && !pausing)
12721 if (gameMode == IcsExamining && pausing)
12722 limit = pauseExamForwardMostMove;
12724 limit = forwardMostMove;
12726 if (target > limit) target = limit;
12728 if (target > 0 && moveList[target - 1][0]) {
12729 int fromX, fromY, toX, toY;
12730 toX = moveList[target - 1][2] - AAA;
12731 toY = moveList[target - 1][3] - ONE;
12732 if (moveList[target - 1][1] == '@') {
12733 if (appData.highlightLastMove) {
12734 SetHighlights(-1, -1, toX, toY);
12737 fromX = moveList[target - 1][0] - AAA;
12738 fromY = moveList[target - 1][1] - ONE;
12739 if (target == currentMove + 1) {
12740 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
12742 if (appData.highlightLastMove) {
12743 SetHighlights(fromX, fromY, toX, toY);
12747 if (gameMode == EditGame || gameMode == AnalyzeMode ||
12748 gameMode == Training || gameMode == PlayFromGameFile ||
12749 gameMode == AnalyzeFile) {
12750 while (currentMove < target) {
12751 SendMoveToProgram(currentMove++, &first);
12754 currentMove = target;
12757 if (gameMode == EditGame || gameMode == EndOfGame) {
12758 whiteTimeRemaining = timeRemaining[0][currentMove];
12759 blackTimeRemaining = timeRemaining[1][currentMove];
12761 DisplayBothClocks();
12762 DisplayMove(currentMove - 1);
12763 DrawPosition(FALSE, boards[currentMove]);
12764 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
12765 if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
12766 DisplayComment(currentMove - 1, commentList[currentMove]);
12774 if (gameMode == IcsExamining && !pausing) {
12775 SendToICS(ics_prefix);
12776 SendToICS("forward\n");
12778 ForwardInner(currentMove + 1);
12785 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12786 /* to optimze, we temporarily turn off analysis mode while we feed
12787 * the remaining moves to the engine. Otherwise we get analysis output
12790 if (first.analysisSupport) {
12791 SendToProgram("exit\nforce\n", &first);
12792 first.analyzing = FALSE;
12796 if (gameMode == IcsExamining && !pausing) {
12797 SendToICS(ics_prefix);
12798 SendToICS("forward 999999\n");
12800 ForwardInner(forwardMostMove);
12803 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12804 /* we have fed all the moves, so reactivate analysis mode */
12805 SendToProgram("analyze\n", &first);
12806 first.analyzing = TRUE;
12807 /*first.maybeThinking = TRUE;*/
12808 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12813 BackwardInner(target)
12816 int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
12818 if (appData.debugMode)
12819 fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
12820 target, currentMove, forwardMostMove);
12822 if (gameMode == EditPosition) return;
12823 if (currentMove <= backwardMostMove) {
12825 DrawPosition(full_redraw, boards[currentMove]);
12828 if (gameMode == PlayFromGameFile && !pausing)
12831 if (moveList[target][0]) {
12832 int fromX, fromY, toX, toY;
12833 toX = moveList[target][2] - AAA;
12834 toY = moveList[target][3] - ONE;
12835 if (moveList[target][1] == '@') {
12836 if (appData.highlightLastMove) {
12837 SetHighlights(-1, -1, toX, toY);
12840 fromX = moveList[target][0] - AAA;
12841 fromY = moveList[target][1] - ONE;
12842 if (target == currentMove - 1) {
12843 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
12845 if (appData.highlightLastMove) {
12846 SetHighlights(fromX, fromY, toX, toY);
12850 if (gameMode == EditGame || gameMode==AnalyzeMode ||
12851 gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
12852 while (currentMove > target) {
12853 SendToProgram("undo\n", &first);
12857 currentMove = target;
12860 if (gameMode == EditGame || gameMode == EndOfGame) {
12861 whiteTimeRemaining = timeRemaining[0][currentMove];
12862 blackTimeRemaining = timeRemaining[1][currentMove];
12864 DisplayBothClocks();
12865 DisplayMove(currentMove - 1);
12866 DrawPosition(full_redraw, boards[currentMove]);
12867 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
12868 // [HGM] PV info: routine tests if comment empty
12869 DisplayComment(currentMove - 1, commentList[currentMove]);
12875 if (gameMode == IcsExamining && !pausing) {
12876 SendToICS(ics_prefix);
12877 SendToICS("backward\n");
12879 BackwardInner(currentMove - 1);
12886 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12887 /* to optimize, we temporarily turn off analysis mode while we undo
12888 * all the moves. Otherwise we get analysis output after each undo.
12890 if (first.analysisSupport) {
12891 SendToProgram("exit\nforce\n", &first);
12892 first.analyzing = FALSE;
12896 if (gameMode == IcsExamining && !pausing) {
12897 SendToICS(ics_prefix);
12898 SendToICS("backward 999999\n");
12900 BackwardInner(backwardMostMove);
12903 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12904 /* we have fed all the moves, so reactivate analysis mode */
12905 SendToProgram("analyze\n", &first);
12906 first.analyzing = TRUE;
12907 /*first.maybeThinking = TRUE;*/
12908 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12915 if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
12916 if (to >= forwardMostMove) to = forwardMostMove;
12917 if (to <= backwardMostMove) to = backwardMostMove;
12918 if (to < currentMove) {
12926 RevertEvent(Boolean annotate)
12928 if(PopTail(annotate)) { // [HGM] vari: restore old game tail
12931 if (gameMode != IcsExamining) {
12932 DisplayError(_("You are not examining a game"), 0);
12936 DisplayError(_("You can't revert while pausing"), 0);
12939 SendToICS(ics_prefix);
12940 SendToICS("revert\n");
12946 switch (gameMode) {
12947 case MachinePlaysWhite:
12948 case MachinePlaysBlack:
12949 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
12950 DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
12953 if (forwardMostMove < 2) return;
12954 currentMove = forwardMostMove = forwardMostMove - 2;
12955 whiteTimeRemaining = timeRemaining[0][currentMove];
12956 blackTimeRemaining = timeRemaining[1][currentMove];
12957 DisplayBothClocks();
12958 DisplayMove(currentMove - 1);
12959 ClearHighlights();/*!! could figure this out*/
12960 DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
12961 SendToProgram("remove\n", &first);
12962 /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
12965 case BeginningOfGame:
12969 case IcsPlayingWhite:
12970 case IcsPlayingBlack:
12971 if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
12972 SendToICS(ics_prefix);
12973 SendToICS("takeback 2\n");
12975 SendToICS(ics_prefix);
12976 SendToICS("takeback 1\n");
12985 ChessProgramState *cps;
12987 switch (gameMode) {
12988 case MachinePlaysWhite:
12989 if (!WhiteOnMove(forwardMostMove)) {
12990 DisplayError(_("It is your turn"), 0);
12995 case MachinePlaysBlack:
12996 if (WhiteOnMove(forwardMostMove)) {
12997 DisplayError(_("It is your turn"), 0);
13002 case TwoMachinesPlay:
13003 if (WhiteOnMove(forwardMostMove) ==
13004 (first.twoMachinesColor[0] == 'w')) {
13010 case BeginningOfGame:
13014 SendToProgram("?\n", cps);
13018 TruncateGameEvent()
13021 if (gameMode != EditGame) return;
13028 CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
13029 if (forwardMostMove > currentMove) {
13030 if (gameInfo.resultDetails != NULL) {
13031 free(gameInfo.resultDetails);
13032 gameInfo.resultDetails = NULL;
13033 gameInfo.result = GameUnfinished;
13035 forwardMostMove = currentMove;
13036 HistorySet(parseList, backwardMostMove, forwardMostMove,
13044 if (appData.noChessProgram) return;
13045 switch (gameMode) {
13046 case MachinePlaysWhite:
13047 if (WhiteOnMove(forwardMostMove)) {
13048 DisplayError(_("Wait until your turn"), 0);
13052 case BeginningOfGame:
13053 case MachinePlaysBlack:
13054 if (!WhiteOnMove(forwardMostMove)) {
13055 DisplayError(_("Wait until your turn"), 0);
13060 DisplayError(_("No hint available"), 0);
13063 SendToProgram("hint\n", &first);
13064 hintRequested = TRUE;
13070 if (appData.noChessProgram) return;
13071 switch (gameMode) {
13072 case MachinePlaysWhite:
13073 if (WhiteOnMove(forwardMostMove)) {
13074 DisplayError(_("Wait until your turn"), 0);
13078 case BeginningOfGame:
13079 case MachinePlaysBlack:
13080 if (!WhiteOnMove(forwardMostMove)) {
13081 DisplayError(_("Wait until your turn"), 0);
13086 EditPositionDone(TRUE);
13088 case TwoMachinesPlay:
13093 SendToProgram("bk\n", &first);
13094 bookOutput[0] = NULLCHAR;
13095 bookRequested = TRUE;
13101 char *tags = PGNTags(&gameInfo);
13102 TagsPopUp(tags, CmailMsg());
13106 /* end button procedures */
13109 PrintPosition(fp, move)
13115 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13116 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
13117 char c = PieceToChar(boards[move][i][j]);
13118 fputc(c == 'x' ? '.' : c, fp);
13119 fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
13122 if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
13123 fprintf(fp, "white to play\n");
13125 fprintf(fp, "black to play\n");
13132 if (gameInfo.white != NULL) {
13133 fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
13139 /* Find last component of program's own name, using some heuristics */
13141 TidyProgramName(prog, host, buf)
13142 char *prog, *host, buf[MSG_SIZ];
13145 int local = (strcmp(host, "localhost") == 0);
13146 while (!local && (p = strchr(prog, ';')) != NULL) {
13148 while (*p == ' ') p++;
13151 if (*prog == '"' || *prog == '\'') {
13152 q = strchr(prog + 1, *prog);
13154 q = strchr(prog, ' ');
13156 if (q == NULL) q = prog + strlen(prog);
13158 while (p >= prog && *p != '/' && *p != '\\') p--;
13160 if(p == prog && *p == '"') p++;
13161 if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
13162 memcpy(buf, p, q - p);
13163 buf[q - p] = NULLCHAR;
13171 TimeControlTagValue()
13174 if (!appData.clockMode) {
13175 safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
13176 } else if (movesPerSession > 0) {
13177 snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
13178 } else if (timeIncrement == 0) {
13179 snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
13181 snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
13183 return StrSave(buf);
13189 /* This routine is used only for certain modes */
13190 VariantClass v = gameInfo.variant;
13191 ChessMove r = GameUnfinished;
13194 if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
13195 r = gameInfo.result;
13196 p = gameInfo.resultDetails;
13197 gameInfo.resultDetails = NULL;
13199 ClearGameInfo(&gameInfo);
13200 gameInfo.variant = v;
13202 switch (gameMode) {
13203 case MachinePlaysWhite:
13204 gameInfo.event = StrSave( appData.pgnEventHeader );
13205 gameInfo.site = StrSave(HostName());
13206 gameInfo.date = PGNDate();
13207 gameInfo.round = StrSave("-");
13208 gameInfo.white = StrSave(first.tidy);
13209 gameInfo.black = StrSave(UserName());
13210 gameInfo.timeControl = TimeControlTagValue();
13213 case MachinePlaysBlack:
13214 gameInfo.event = StrSave( appData.pgnEventHeader );
13215 gameInfo.site = StrSave(HostName());
13216 gameInfo.date = PGNDate();
13217 gameInfo.round = StrSave("-");
13218 gameInfo.white = StrSave(UserName());
13219 gameInfo.black = StrSave(first.tidy);
13220 gameInfo.timeControl = TimeControlTagValue();
13223 case TwoMachinesPlay:
13224 gameInfo.event = StrSave( appData.pgnEventHeader );
13225 gameInfo.site = StrSave(HostName());
13226 gameInfo.date = PGNDate();
13227 if (matchGame > 0) {
13229 snprintf(buf, MSG_SIZ, "%d", matchGame);
13230 gameInfo.round = StrSave(buf);
13232 gameInfo.round = StrSave("-");
13234 if (first.twoMachinesColor[0] == 'w') {
13235 gameInfo.white = StrSave(first.tidy);
13236 gameInfo.black = StrSave(second.tidy);
13238 gameInfo.white = StrSave(second.tidy);
13239 gameInfo.black = StrSave(first.tidy);
13241 gameInfo.timeControl = TimeControlTagValue();
13245 gameInfo.event = StrSave("Edited game");
13246 gameInfo.site = StrSave(HostName());
13247 gameInfo.date = PGNDate();
13248 gameInfo.round = StrSave("-");
13249 gameInfo.white = StrSave("-");
13250 gameInfo.black = StrSave("-");
13251 gameInfo.result = r;
13252 gameInfo.resultDetails = p;
13256 gameInfo.event = StrSave("Edited position");
13257 gameInfo.site = StrSave(HostName());
13258 gameInfo.date = PGNDate();
13259 gameInfo.round = StrSave("-");
13260 gameInfo.white = StrSave("-");
13261 gameInfo.black = StrSave("-");
13264 case IcsPlayingWhite:
13265 case IcsPlayingBlack:
13270 case PlayFromGameFile:
13271 gameInfo.event = StrSave("Game from non-PGN file");
13272 gameInfo.site = StrSave(HostName());
13273 gameInfo.date = PGNDate();
13274 gameInfo.round = StrSave("-");
13275 gameInfo.white = StrSave("?");
13276 gameInfo.black = StrSave("?");
13285 ReplaceComment(index, text)
13293 if(index && sscanf(text, "%f/%d", &score, &len) == 2 &&
13294 pvInfoList[index-1].depth == len &&
13295 pvInfoList[index-1].score == (int) (score*100 + 0.5) &&
13296 (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
13297 while (*text == '\n') text++;
13298 len = strlen(text);
13299 while (len > 0 && text[len - 1] == '\n') len--;
13301 if (commentList[index] != NULL)
13302 free(commentList[index]);
13305 commentList[index] = NULL;
13308 if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
13309 *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
13310 *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
13311 commentList[index] = (char *) malloc(len + 2);
13312 strncpy(commentList[index], text, len);
13313 commentList[index][len] = '\n';
13314 commentList[index][len + 1] = NULLCHAR;
13316 // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
13318 commentList[index] = (char *) malloc(len + 7);
13319 safeStrCpy(commentList[index], "{\n", 3);
13320 safeStrCpy(commentList[index]+2, text, len+1);
13321 commentList[index][len+2] = NULLCHAR;
13322 while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
13323 strcat(commentList[index], "\n}\n");
13337 if (ch == '\r') continue;
13339 } while (ch != '\0');
13343 AppendComment(index, text, addBraces)
13346 Boolean addBraces; // [HGM] braces: tells if we should add {}
13351 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
13352 text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
13355 while (*text == '\n') text++;
13356 len = strlen(text);
13357 while (len > 0 && text[len - 1] == '\n') len--;
13359 if (len == 0) return;
13361 if (commentList[index] != NULL) {
13362 old = commentList[index];
13363 oldlen = strlen(old);
13364 while(commentList[index][oldlen-1] == '\n')
13365 commentList[index][--oldlen] = NULLCHAR;
13366 commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
13367 safeStrCpy(commentList[index], old, oldlen + len + 6);
13369 // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
13370 if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces)) {
13371 if(addBraces) addBraces = FALSE; else { text++; len--; }
13372 while (*text == '\n') { text++; len--; }
13373 commentList[index][--oldlen] = NULLCHAR;
13375 if(addBraces) strcat(commentList[index], "\n{\n");
13376 else strcat(commentList[index], "\n");
13377 strcat(commentList[index], text);
13378 if(addBraces) strcat(commentList[index], "\n}\n");
13379 else strcat(commentList[index], "\n");
13381 commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
13383 safeStrCpy(commentList[index], "{\n", 3);
13384 else commentList[index][0] = NULLCHAR;
13385 strcat(commentList[index], text);
13386 strcat(commentList[index], "\n");
13387 if(addBraces) strcat(commentList[index], "}\n");
13391 static char * FindStr( char * text, char * sub_text )
13393 char * result = strstr( text, sub_text );
13395 if( result != NULL ) {
13396 result += strlen( sub_text );
13402 /* [AS] Try to extract PV info from PGN comment */
13403 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
13404 char *GetInfoFromComment( int index, char * text )
13408 if( text != NULL && index > 0 ) {
13411 int time = -1, sec = 0, deci;
13412 char * s_eval = FindStr( text, "[%eval " );
13413 char * s_emt = FindStr( text, "[%emt " );
13415 if( s_eval != NULL || s_emt != NULL ) {
13419 if( s_eval != NULL ) {
13420 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
13424 if( delim != ']' ) {
13429 if( s_emt != NULL ) {
13434 /* We expect something like: [+|-]nnn.nn/dd */
13437 if(*text != '{') return text; // [HGM] braces: must be normal comment
13439 sep = strchr( text, '/' );
13440 if( sep == NULL || sep < (text+4) ) {
13444 time = -1; sec = -1; deci = -1;
13445 if( sscanf( text+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
13446 sscanf( text+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
13447 sscanf( text+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
13448 sscanf( text+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3 ) {
13452 if( score_lo < 0 || score_lo >= 100 ) {
13456 if(sec >= 0) time = 600*time + 10*sec; else
13457 if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
13459 score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
13461 /* [HGM] PV time: now locate end of PV info */
13462 while( *++sep >= '0' && *sep <= '9'); // strip depth
13464 while( *++sep >= '0' && *sep <= '9'); // strip time
13466 while( *++sep >= '0' && *sep <= '9'); // strip seconds
13468 while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
13469 while(*sep == ' ') sep++;
13480 pvInfoList[index-1].depth = depth;
13481 pvInfoList[index-1].score = score;
13482 pvInfoList[index-1].time = 10*time; // centi-sec
13483 if(*sep == '}') *sep = 0; else *--sep = '{';
13489 SendToProgram(message, cps)
13491 ChessProgramState *cps;
13493 int count, outCount, error;
13496 if (cps->pr == NULL) return;
13499 if (appData.debugMode) {
13502 fprintf(debugFP, "%ld >%-6s: %s",
13503 SubtractTimeMarks(&now, &programStartTime),
13504 cps->which, message);
13507 count = strlen(message);
13508 outCount = OutputToProcess(cps->pr, message, count, &error);
13509 if (outCount < count && !exiting
13510 && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
13511 snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), cps->which);
13512 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
13513 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
13514 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
13515 snprintf(buf, MSG_SIZ, "%s program exits in draw position (%s)", cps->which, cps->program);
13517 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
13519 gameInfo.resultDetails = StrSave(buf);
13521 if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
13526 ReceiveFromProgram(isr, closure, message, count, error)
13527 InputSourceRef isr;
13535 ChessProgramState *cps = (ChessProgramState *)closure;
13537 if (isr != cps->isr) return; /* Killed intentionally */
13540 snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
13541 cps->which, cps->program);
13542 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
13543 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
13544 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
13545 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), cps->which, cps->program);
13547 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
13549 gameInfo.resultDetails = StrSave(buf);
13551 RemoveInputSource(cps->isr);
13552 if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
13554 snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
13555 cps->which, cps->program);
13556 RemoveInputSource(cps->isr);
13558 /* [AS] Program is misbehaving badly... kill it */
13559 if( count == -2 ) {
13560 DestroyChildProcess( cps->pr, 9 );
13564 if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
13569 if ((end_str = strchr(message, '\r')) != NULL)
13570 *end_str = NULLCHAR;
13571 if ((end_str = strchr(message, '\n')) != NULL)
13572 *end_str = NULLCHAR;
13574 if (appData.debugMode) {
13575 TimeMark now; int print = 1;
13576 char *quote = ""; char c; int i;
13578 if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
13579 char start = message[0];
13580 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
13581 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
13582 sscanf(message, "move %c", &c)!=1 && sscanf(message, "offer%c", &c)!=1 &&
13583 sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
13584 sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
13585 sscanf(message, "tell%c", &c)!=1 && sscanf(message, "0-1 %c", &c)!=1 &&
13586 sscanf(message, "1-0 %c", &c)!=1 && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
13587 sscanf(message, "setboard %c", &c)!=1 && sscanf(message, "setup %c", &c)!=1 &&
13588 sscanf(message, "hint: %c", &c)!=1 &&
13589 sscanf(message, "pong %c", &c)!=1 && start != '#') {
13590 quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
13591 print = (appData.engineComments >= 2);
13593 message[0] = start; // restore original message
13597 fprintf(debugFP, "%ld <%-6s: %s%s\n",
13598 SubtractTimeMarks(&now, &programStartTime), cps->which,
13604 /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
13605 if (appData.icsEngineAnalyze) {
13606 if (strstr(message, "whisper") != NULL ||
13607 strstr(message, "kibitz") != NULL ||
13608 strstr(message, "tellics") != NULL) return;
13611 HandleMachineMove(message, cps);
13616 SendTimeControl(cps, mps, tc, inc, sd, st)
13617 ChessProgramState *cps;
13618 int mps, inc, sd, st;
13624 if( timeControl_2 > 0 ) {
13625 if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
13626 tc = timeControl_2;
13629 tc /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
13630 inc /= cps->timeOdds;
13631 st /= cps->timeOdds;
13633 seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
13636 /* Set exact time per move, normally using st command */
13637 if (cps->stKludge) {
13638 /* GNU Chess 4 has no st command; uses level in a nonstandard way */
13640 if (seconds == 0) {
13641 snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
13643 snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
13646 snprintf(buf, MSG_SIZ, "st %d\n", st);
13649 /* Set conventional or incremental time control, using level command */
13650 if (seconds == 0) {
13651 /* Note old gnuchess bug -- minutes:seconds used to not work.
13652 Fixed in later versions, but still avoid :seconds
13653 when seconds is 0. */
13654 snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
13656 snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
13657 seconds, inc/1000.);
13660 SendToProgram(buf, cps);
13662 /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
13663 /* Orthogonally, limit search to given depth */
13665 if (cps->sdKludge) {
13666 snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
13668 snprintf(buf, MSG_SIZ, "sd %d\n", sd);
13670 SendToProgram(buf, cps);
13673 if(cps->nps > 0) { /* [HGM] nps */
13674 if(cps->supportsNPS == FALSE)
13675 cps->nps = -1; // don't use if engine explicitly says not supported!
13677 snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
13678 SendToProgram(buf, cps);
13683 ChessProgramState *WhitePlayer()
13684 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
13686 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
13687 gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
13693 SendTimeRemaining(cps, machineWhite)
13694 ChessProgramState *cps;
13695 int /*boolean*/ machineWhite;
13697 char message[MSG_SIZ];
13700 /* Note: this routine must be called when the clocks are stopped
13701 or when they have *just* been set or switched; otherwise
13702 it will be off by the time since the current tick started.
13704 if (machineWhite) {
13705 time = whiteTimeRemaining / 10;
13706 otime = blackTimeRemaining / 10;
13708 time = blackTimeRemaining / 10;
13709 otime = whiteTimeRemaining / 10;
13711 /* [HGM] translate opponent's time by time-odds factor */
13712 otime = (otime * cps->other->timeOdds) / cps->timeOdds;
13713 if (appData.debugMode) {
13714 fprintf(debugFP, "time odds: %f %f \n", cps->timeOdds, cps->other->timeOdds);
13717 if (time <= 0) time = 1;
13718 if (otime <= 0) otime = 1;
13720 snprintf(message, MSG_SIZ, "time %ld\n", time);
13721 SendToProgram(message, cps);
13723 snprintf(message, MSG_SIZ, "otim %ld\n", otime);
13724 SendToProgram(message, cps);
13728 BoolFeature(p, name, loc, cps)
13732 ChessProgramState *cps;
13735 int len = strlen(name);
13738 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
13740 sscanf(*p, "%d", &val);
13742 while (**p && **p != ' ')
13744 snprintf(buf, MSG_SIZ, "accepted %s\n", name);
13745 SendToProgram(buf, cps);
13752 IntFeature(p, name, loc, cps)
13756 ChessProgramState *cps;
13759 int len = strlen(name);
13760 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
13762 sscanf(*p, "%d", loc);
13763 while (**p && **p != ' ') (*p)++;
13764 snprintf(buf, MSG_SIZ, "accepted %s\n", name);
13765 SendToProgram(buf, cps);
13772 StringFeature(p, name, loc, cps)
13776 ChessProgramState *cps;
13779 int len = strlen(name);
13780 if (strncmp((*p), name, len) == 0
13781 && (*p)[len] == '=' && (*p)[len+1] == '\"') {
13783 sscanf(*p, "%[^\"]", loc);
13784 while (**p && **p != '\"') (*p)++;
13785 if (**p == '\"') (*p)++;
13786 snprintf(buf, MSG_SIZ, "accepted %s\n", name);
13787 SendToProgram(buf, cps);
13794 ParseOption(Option *opt, ChessProgramState *cps)
13795 // [HGM] options: process the string that defines an engine option, and determine
13796 // name, type, default value, and allowed value range
13798 char *p, *q, buf[MSG_SIZ];
13799 int n, min = (-1)<<31, max = 1<<31, def;
13801 if(p = strstr(opt->name, " -spin ")) {
13802 if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
13803 if(max < min) max = min; // enforce consistency
13804 if(def < min) def = min;
13805 if(def > max) def = max;
13810 } else if((p = strstr(opt->name, " -slider "))) {
13811 // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
13812 if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
13813 if(max < min) max = min; // enforce consistency
13814 if(def < min) def = min;
13815 if(def > max) def = max;
13819 opt->type = Spin; // Slider;
13820 } else if((p = strstr(opt->name, " -string "))) {
13821 opt->textValue = p+9;
13822 opt->type = TextBox;
13823 } else if((p = strstr(opt->name, " -file "))) {
13824 // for now -file is a synonym for -string, to already provide compatibility with future polyglots
13825 opt->textValue = p+7;
13826 opt->type = TextBox; // FileName;
13827 } else if((p = strstr(opt->name, " -path "))) {
13828 // for now -file is a synonym for -string, to already provide compatibility with future polyglots
13829 opt->textValue = p+7;
13830 opt->type = TextBox; // PathName;
13831 } else if(p = strstr(opt->name, " -check ")) {
13832 if(sscanf(p, " -check %d", &def) < 1) return FALSE;
13833 opt->value = (def != 0);
13834 opt->type = CheckBox;
13835 } else if(p = strstr(opt->name, " -combo ")) {
13836 opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
13837 cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
13838 if(*q == '*') cps->comboList[cps->comboCnt-1]++;
13839 opt->value = n = 0;
13840 while(q = StrStr(q, " /// ")) {
13841 n++; *q = 0; // count choices, and null-terminate each of them
13843 if(*q == '*') { // remember default, which is marked with * prefix
13847 cps->comboList[cps->comboCnt++] = q;
13849 cps->comboList[cps->comboCnt++] = NULL;
13851 opt->type = ComboBox;
13852 } else if(p = strstr(opt->name, " -button")) {
13853 opt->type = Button;
13854 } else if(p = strstr(opt->name, " -save")) {
13855 opt->type = SaveButton;
13856 } else return FALSE;
13857 *p = 0; // terminate option name
13858 // now look if the command-line options define a setting for this engine option.
13859 if(cps->optionSettings && cps->optionSettings[0])
13860 p = strstr(cps->optionSettings, opt->name); else p = NULL;
13861 if(p && (p == cps->optionSettings || p[-1] == ',')) {
13862 snprintf(buf, MSG_SIZ, "option %s", p);
13863 if(p = strstr(buf, ",")) *p = 0;
13864 if(q = strchr(buf, '=')) switch(opt->type) {
13866 for(n=0; n<opt->max; n++)
13867 if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
13870 safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
13874 opt->value = atoi(q+1);
13879 SendToProgram(buf, cps);
13885 FeatureDone(cps, val)
13886 ChessProgramState* cps;
13889 DelayedEventCallback cb = GetDelayedEvent();
13890 if ((cb == InitBackEnd3 && cps == &first) ||
13891 (cb == SettingsMenuIfReady && cps == &second) ||
13892 (cb == TwoMachinesEventIfReady && cps == &second)) {
13893 CancelDelayedEvent();
13894 ScheduleDelayedEvent(cb, val ? 1 : 3600000);
13896 cps->initDone = val;
13899 /* Parse feature command from engine */
13901 ParseFeatures(args, cps)
13903 ChessProgramState *cps;
13911 while (*p == ' ') p++;
13912 if (*p == NULLCHAR) return;
13914 if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
13915 if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
13916 if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
13917 if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
13918 if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
13919 if (BoolFeature(&p, "reuse", &val, cps)) {
13920 /* Engine can disable reuse, but can't enable it if user said no */
13921 if (!val) cps->reuse = FALSE;
13924 if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
13925 if (StringFeature(&p, "myname", &cps->tidy, cps)) {
13926 if (gameMode == TwoMachinesPlay) {
13927 DisplayTwoMachinesTitle();
13933 if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
13934 if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
13935 if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
13936 if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
13937 if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
13938 if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
13939 if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
13940 if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
13941 if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
13942 if (IntFeature(&p, "done", &val, cps)) {
13943 FeatureDone(cps, val);
13946 /* Added by Tord: */
13947 if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
13948 if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
13949 /* End of additions by Tord */
13951 /* [HGM] added features: */
13952 if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
13953 if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
13954 if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
13955 if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
13956 if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
13957 if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
13958 if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
13959 if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
13960 snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
13961 SendToProgram(buf, cps);
13964 if(cps->nrOptions >= MAX_OPTIONS) {
13966 snprintf(buf, MSG_SIZ, "%s engine has too many options\n", cps->which);
13967 DisplayError(buf, 0);
13971 /* End of additions by HGM */
13973 /* unknown feature: complain and skip */
13975 while (*q && *q != '=') q++;
13976 snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
13977 SendToProgram(buf, cps);
13983 while (*p && *p != '\"') p++;
13984 if (*p == '\"') p++;
13986 while (*p && *p != ' ') p++;
13994 PeriodicUpdatesEvent(newState)
13997 if (newState == appData.periodicUpdates)
14000 appData.periodicUpdates=newState;
14002 /* Display type changes, so update it now */
14003 // DisplayAnalysis();
14005 /* Get the ball rolling again... */
14007 AnalysisPeriodicEvent(1);
14008 StartAnalysisClock();
14013 PonderNextMoveEvent(newState)
14016 if (newState == appData.ponderNextMove) return;
14017 if (gameMode == EditPosition) EditPositionDone(TRUE);
14019 SendToProgram("hard\n", &first);
14020 if (gameMode == TwoMachinesPlay) {
14021 SendToProgram("hard\n", &second);
14024 SendToProgram("easy\n", &first);
14025 thinkOutput[0] = NULLCHAR;
14026 if (gameMode == TwoMachinesPlay) {
14027 SendToProgram("easy\n", &second);
14030 appData.ponderNextMove = newState;
14034 NewSettingEvent(option, feature, command, value)
14036 int option, value, *feature;
14040 if (gameMode == EditPosition) EditPositionDone(TRUE);
14041 snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
14042 if(feature == NULL || *feature) SendToProgram(buf, &first);
14043 if (gameMode == TwoMachinesPlay) {
14044 if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
14049 ShowThinkingEvent()
14050 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
14052 static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
14053 int newState = appData.showThinking
14054 // [HGM] thinking: other features now need thinking output as well
14055 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
14057 if (oldState == newState) return;
14058 oldState = newState;
14059 if (gameMode == EditPosition) EditPositionDone(TRUE);
14061 SendToProgram("post\n", &first);
14062 if (gameMode == TwoMachinesPlay) {
14063 SendToProgram("post\n", &second);
14066 SendToProgram("nopost\n", &first);
14067 thinkOutput[0] = NULLCHAR;
14068 if (gameMode == TwoMachinesPlay) {
14069 SendToProgram("nopost\n", &second);
14072 // appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
14076 AskQuestionEvent(title, question, replyPrefix, which)
14077 char *title; char *question; char *replyPrefix; char *which;
14079 ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
14080 if (pr == NoProc) return;
14081 AskQuestion(title, question, replyPrefix, pr);
14085 DisplayMove(moveNumber)
14088 char message[MSG_SIZ];
14090 char cpThinkOutput[MSG_SIZ];
14092 if(appData.noGUI) return; // [HGM] fast: suppress display of moves
14094 if (moveNumber == forwardMostMove - 1 ||
14095 gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14097 safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
14099 if (strchr(cpThinkOutput, '\n')) {
14100 *strchr(cpThinkOutput, '\n') = NULLCHAR;
14103 *cpThinkOutput = NULLCHAR;
14106 /* [AS] Hide thinking from human user */
14107 if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
14108 *cpThinkOutput = NULLCHAR;
14109 if( thinkOutput[0] != NULLCHAR ) {
14112 for( i=0; i<=hiddenThinkOutputState; i++ ) {
14113 cpThinkOutput[i] = '.';
14115 cpThinkOutput[i] = NULLCHAR;
14116 hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
14120 if (moveNumber == forwardMostMove - 1 &&
14121 gameInfo.resultDetails != NULL) {
14122 if (gameInfo.resultDetails[0] == NULLCHAR) {
14123 snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
14125 snprintf(res, MSG_SIZ, " {%s} %s",
14126 T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
14132 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
14133 DisplayMessage(res, cpThinkOutput);
14135 snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
14136 WhiteOnMove(moveNumber) ? " " : ".. ",
14137 parseList[moveNumber], res);
14138 DisplayMessage(message, cpThinkOutput);
14143 DisplayComment(moveNumber, text)
14147 char title[MSG_SIZ];
14148 char buf[8000]; // comment can be long!
14151 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
14152 safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
14154 snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
14155 WhiteOnMove(moveNumber) ? " " : ".. ",
14156 parseList[moveNumber]);
14158 // [HGM] PV info: display PV info together with (or as) comment
14159 if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {
14160 if(text == NULL) text = "";
14161 score = pvInfoList[moveNumber].score;
14162 snprintf(buf,sizeof(buf)/sizeof(buf[0]), "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,
14163 depth, (pvInfoList[moveNumber].time+50)/100, text);
14166 if (text != NULL && (appData.autoDisplayComment || commentUp))
14167 CommentPopUp(title, text);
14170 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
14171 * might be busy thinking or pondering. It can be omitted if your
14172 * gnuchess is configured to stop thinking immediately on any user
14173 * input. However, that gnuchess feature depends on the FIONREAD
14174 * ioctl, which does not work properly on some flavors of Unix.
14178 ChessProgramState *cps;
14181 if (!cps->useSigint) return;
14182 if (appData.noChessProgram || (cps->pr == NoProc)) return;
14183 switch (gameMode) {
14184 case MachinePlaysWhite:
14185 case MachinePlaysBlack:
14186 case TwoMachinesPlay:
14187 case IcsPlayingWhite:
14188 case IcsPlayingBlack:
14191 /* Skip if we know it isn't thinking */
14192 if (!cps->maybeThinking) return;
14193 if (appData.debugMode)
14194 fprintf(debugFP, "Interrupting %s\n", cps->which);
14195 InterruptChildProcess(cps->pr);
14196 cps->maybeThinking = FALSE;
14201 #endif /*ATTENTION*/
14207 if (whiteTimeRemaining <= 0) {
14210 if (appData.icsActive) {
14211 if (appData.autoCallFlag &&
14212 gameMode == IcsPlayingBlack && !blackFlag) {
14213 SendToICS(ics_prefix);
14214 SendToICS("flag\n");
14218 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
14220 if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
14221 if (appData.autoCallFlag) {
14222 GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
14229 if (blackTimeRemaining <= 0) {
14232 if (appData.icsActive) {
14233 if (appData.autoCallFlag &&
14234 gameMode == IcsPlayingWhite && !whiteFlag) {
14235 SendToICS(ics_prefix);
14236 SendToICS("flag\n");
14240 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
14242 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
14243 if (appData.autoCallFlag) {
14244 GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
14257 if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
14258 gameMode == PlayFromGameFile || forwardMostMove == 0) return;
14261 * add time to clocks when time control is achieved ([HGM] now also used for increment)
14263 if ( !WhiteOnMove(forwardMostMove) ) {
14264 /* White made time control */
14265 lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
14266 whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
14267 /* [HGM] time odds: correct new time quota for time odds! */
14268 / WhitePlayer()->timeOdds;
14269 lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
14271 lastBlack -= blackTimeRemaining;
14272 /* Black made time control */
14273 blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
14274 / WhitePlayer()->other->timeOdds;
14275 lastWhite = whiteTimeRemaining;
14280 DisplayBothClocks()
14282 int wom = gameMode == EditPosition ?
14283 !blackPlaysFirst : WhiteOnMove(currentMove);
14284 DisplayWhiteClock(whiteTimeRemaining, wom);
14285 DisplayBlackClock(blackTimeRemaining, !wom);
14289 /* Timekeeping seems to be a portability nightmare. I think everyone
14290 has ftime(), but I'm really not sure, so I'm including some ifdefs
14291 to use other calls if you don't. Clocks will be less accurate if
14292 you have neither ftime nor gettimeofday.
14295 /* VS 2008 requires the #include outside of the function */
14296 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
14297 #include <sys/timeb.h>
14300 /* Get the current time as a TimeMark */
14305 #if HAVE_GETTIMEOFDAY
14307 struct timeval timeVal;
14308 struct timezone timeZone;
14310 gettimeofday(&timeVal, &timeZone);
14311 tm->sec = (long) timeVal.tv_sec;
14312 tm->ms = (int) (timeVal.tv_usec / 1000L);
14314 #else /*!HAVE_GETTIMEOFDAY*/
14317 // include <sys/timeb.h> / moved to just above start of function
14318 struct timeb timeB;
14321 tm->sec = (long) timeB.time;
14322 tm->ms = (int) timeB.millitm;
14324 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
14325 tm->sec = (long) time(NULL);
14331 /* Return the difference in milliseconds between two
14332 time marks. We assume the difference will fit in a long!
14335 SubtractTimeMarks(tm2, tm1)
14336 TimeMark *tm2, *tm1;
14338 return 1000L*(tm2->sec - tm1->sec) +
14339 (long) (tm2->ms - tm1->ms);
14344 * Code to manage the game clocks.
14346 * In tournament play, black starts the clock and then white makes a move.
14347 * We give the human user a slight advantage if he is playing white---the
14348 * clocks don't run until he makes his first move, so it takes zero time.
14349 * Also, we don't account for network lag, so we could get out of sync
14350 * with GNU Chess's clock -- but then, referees are always right.
14353 static TimeMark tickStartTM;
14354 static long intendedTickLength;
14357 NextTickLength(timeRemaining)
14358 long timeRemaining;
14360 long nominalTickLength, nextTickLength;
14362 if (timeRemaining > 0L && timeRemaining <= 10000L)
14363 nominalTickLength = 100L;
14365 nominalTickLength = 1000L;
14366 nextTickLength = timeRemaining % nominalTickLength;
14367 if (nextTickLength <= 0) nextTickLength += nominalTickLength;
14369 return nextTickLength;
14372 /* Adjust clock one minute up or down */
14374 AdjustClock(Boolean which, int dir)
14376 if(which) blackTimeRemaining += 60000*dir;
14377 else whiteTimeRemaining += 60000*dir;
14378 DisplayBothClocks();
14381 /* Stop clocks and reset to a fresh time control */
14385 (void) StopClockTimer();
14386 if (appData.icsActive) {
14387 whiteTimeRemaining = blackTimeRemaining = 0;
14388 } else if (searchTime) {
14389 whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
14390 blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
14391 } else { /* [HGM] correct new time quote for time odds */
14392 whiteTC = blackTC = fullTimeControlString;
14393 whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
14394 blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
14396 if (whiteFlag || blackFlag) {
14398 whiteFlag = blackFlag = FALSE;
14400 lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
14401 DisplayBothClocks();
14404 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
14406 /* Decrement running clock by amount of time that has passed */
14410 long timeRemaining;
14411 long lastTickLength, fudge;
14414 if (!appData.clockMode) return;
14415 if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
14419 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
14421 /* Fudge if we woke up a little too soon */
14422 fudge = intendedTickLength - lastTickLength;
14423 if (fudge < 0 || fudge > FUDGE) fudge = 0;
14425 if (WhiteOnMove(forwardMostMove)) {
14426 if(whiteNPS >= 0) lastTickLength = 0;
14427 timeRemaining = whiteTimeRemaining -= lastTickLength;
14428 if(timeRemaining < 0 && !appData.icsActive) {
14429 GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
14430 if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
14431 whiteStartMove = forwardMostMove; whiteTC = nextSession;
14432 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
14435 DisplayWhiteClock(whiteTimeRemaining - fudge,
14436 WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
14438 if(blackNPS >= 0) lastTickLength = 0;
14439 timeRemaining = blackTimeRemaining -= lastTickLength;
14440 if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
14441 GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
14443 blackStartMove = forwardMostMove;
14444 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
14447 DisplayBlackClock(blackTimeRemaining - fudge,
14448 !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
14450 if (CheckFlags()) return;
14453 intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
14454 StartClockTimer(intendedTickLength);
14456 /* if the time remaining has fallen below the alarm threshold, sound the
14457 * alarm. if the alarm has sounded and (due to a takeback or time control
14458 * with increment) the time remaining has increased to a level above the
14459 * threshold, reset the alarm so it can sound again.
14462 if (appData.icsActive && appData.icsAlarm) {
14464 /* make sure we are dealing with the user's clock */
14465 if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
14466 ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
14469 if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
14470 alarmSounded = FALSE;
14471 } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
14473 alarmSounded = TRUE;
14479 /* A player has just moved, so stop the previously running
14480 clock and (if in clock mode) start the other one.
14481 We redisplay both clocks in case we're in ICS mode, because
14482 ICS gives us an update to both clocks after every move.
14483 Note that this routine is called *after* forwardMostMove
14484 is updated, so the last fractional tick must be subtracted
14485 from the color that is *not* on move now.
14488 SwitchClocks(int newMoveNr)
14490 long lastTickLength;
14492 int flagged = FALSE;
14496 if (StopClockTimer() && appData.clockMode) {
14497 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
14498 if (!WhiteOnMove(forwardMostMove)) {
14499 if(blackNPS >= 0) lastTickLength = 0;
14500 blackTimeRemaining -= lastTickLength;
14501 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
14502 // if(pvInfoList[forwardMostMove-1].time == -1)
14503 pvInfoList[forwardMostMove-1].time = // use GUI time
14504 (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
14506 if(whiteNPS >= 0) lastTickLength = 0;
14507 whiteTimeRemaining -= lastTickLength;
14508 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
14509 // if(pvInfoList[forwardMostMove-1].time == -1)
14510 pvInfoList[forwardMostMove-1].time =
14511 (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
14513 flagged = CheckFlags();
14515 forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
14516 CheckTimeControl();
14518 if (flagged || !appData.clockMode) return;
14520 switch (gameMode) {
14521 case MachinePlaysBlack:
14522 case MachinePlaysWhite:
14523 case BeginningOfGame:
14524 if (pausing) return;
14528 case PlayFromGameFile:
14536 if (searchTime) { // [HGM] st: set clock of player that has to move to max time
14537 if(WhiteOnMove(forwardMostMove))
14538 whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
14539 else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
14543 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
14544 whiteTimeRemaining : blackTimeRemaining);
14545 StartClockTimer(intendedTickLength);
14549 /* Stop both clocks */
14553 long lastTickLength;
14556 if (!StopClockTimer()) return;
14557 if (!appData.clockMode) return;
14561 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
14562 if (WhiteOnMove(forwardMostMove)) {
14563 if(whiteNPS >= 0) lastTickLength = 0;
14564 whiteTimeRemaining -= lastTickLength;
14565 DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
14567 if(blackNPS >= 0) lastTickLength = 0;
14568 blackTimeRemaining -= lastTickLength;
14569 DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
14574 /* Start clock of player on move. Time may have been reset, so
14575 if clock is already running, stop and restart it. */
14579 (void) StopClockTimer(); /* in case it was running already */
14580 DisplayBothClocks();
14581 if (CheckFlags()) return;
14583 if (!appData.clockMode) return;
14584 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
14586 GetTimeMark(&tickStartTM);
14587 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
14588 whiteTimeRemaining : blackTimeRemaining);
14590 /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
14591 whiteNPS = blackNPS = -1;
14592 if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
14593 || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
14594 whiteNPS = first.nps;
14595 if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
14596 || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
14597 blackNPS = first.nps;
14598 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
14599 whiteNPS = second.nps;
14600 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
14601 blackNPS = second.nps;
14602 if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
14604 StartClockTimer(intendedTickLength);
14611 long second, minute, hour, day;
14613 static char buf[32];
14615 if (ms > 0 && ms <= 9900) {
14616 /* convert milliseconds to tenths, rounding up */
14617 double tenths = floor( ((double)(ms + 99L)) / 100.00 );
14619 snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
14623 /* convert milliseconds to seconds, rounding up */
14624 /* use floating point to avoid strangeness of integer division
14625 with negative dividends on many machines */
14626 second = (long) floor(((double) (ms + 999L)) / 1000.0);
14633 day = second / (60 * 60 * 24);
14634 second = second % (60 * 60 * 24);
14635 hour = second / (60 * 60);
14636 second = second % (60 * 60);
14637 minute = second / 60;
14638 second = second % 60;
14641 snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
14642 sign, day, hour, minute, second);
14644 snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
14646 snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
14653 * This is necessary because some C libraries aren't ANSI C compliant yet.
14656 StrStr(string, match)
14657 char *string, *match;
14661 length = strlen(match);
14663 for (i = strlen(string) - length; i >= 0; i--, string++)
14664 if (!strncmp(match, string, length))
14671 StrCaseStr(string, match)
14672 char *string, *match;
14676 length = strlen(match);
14678 for (i = strlen(string) - length; i >= 0; i--, string++) {
14679 for (j = 0; j < length; j++) {
14680 if (ToLower(match[j]) != ToLower(string[j]))
14683 if (j == length) return string;
14697 c1 = ToLower(*s1++);
14698 c2 = ToLower(*s2++);
14699 if (c1 > c2) return 1;
14700 if (c1 < c2) return -1;
14701 if (c1 == NULLCHAR) return 0;
14710 return isupper(c) ? tolower(c) : c;
14718 return islower(c) ? toupper(c) : c;
14720 #endif /* !_amigados */
14728 if ((ret = (char *) malloc(strlen(s) + 1)))
14730 safeStrCpy(ret, s, strlen(s)+1);
14736 StrSavePtr(s, savePtr)
14737 char *s, **savePtr;
14742 if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
14743 safeStrCpy(*savePtr, s, strlen(s)+1);
14755 clock = time((time_t *)NULL);
14756 tm = localtime(&clock);
14757 snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
14758 tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
14759 return StrSave(buf);
14764 PositionToFEN(move, overrideCastling)
14766 char *overrideCastling;
14768 int i, j, fromX, fromY, toX, toY;
14775 whiteToPlay = (gameMode == EditPosition) ?
14776 !blackPlaysFirst : (move % 2 == 0);
14779 /* Piece placement data */
14780 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
14782 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
14783 if (boards[move][i][j] == EmptySquare) {
14785 } else { ChessSquare piece = boards[move][i][j];
14786 if (emptycount > 0) {
14787 if(emptycount<10) /* [HGM] can be >= 10 */
14788 *p++ = '0' + emptycount;
14789 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
14792 if(PieceToChar(piece) == '+') {
14793 /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
14795 piece = (ChessSquare)(DEMOTED piece);
14797 *p++ = PieceToChar(piece);
14799 /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
14800 p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
14805 if (emptycount > 0) {
14806 if(emptycount<10) /* [HGM] can be >= 10 */
14807 *p++ = '0' + emptycount;
14808 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
14815 /* [HGM] print Crazyhouse or Shogi holdings */
14816 if( gameInfo.holdingsWidth ) {
14817 *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
14819 for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
14820 piece = boards[move][i][BOARD_WIDTH-1];
14821 if( piece != EmptySquare )
14822 for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
14823 *p++ = PieceToChar(piece);
14825 for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
14826 piece = boards[move][BOARD_HEIGHT-i-1][0];
14827 if( piece != EmptySquare )
14828 for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
14829 *p++ = PieceToChar(piece);
14832 if( q == p ) *p++ = '-';
14838 *p++ = whiteToPlay ? 'w' : 'b';
14841 if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
14842 while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
14844 if(nrCastlingRights) {
14846 if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
14847 /* [HGM] write directly from rights */
14848 if(boards[move][CASTLING][2] != NoRights &&
14849 boards[move][CASTLING][0] != NoRights )
14850 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
14851 if(boards[move][CASTLING][2] != NoRights &&
14852 boards[move][CASTLING][1] != NoRights )
14853 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
14854 if(boards[move][CASTLING][5] != NoRights &&
14855 boards[move][CASTLING][3] != NoRights )
14856 *p++ = boards[move][CASTLING][3] + AAA;
14857 if(boards[move][CASTLING][5] != NoRights &&
14858 boards[move][CASTLING][4] != NoRights )
14859 *p++ = boards[move][CASTLING][4] + AAA;
14862 /* [HGM] write true castling rights */
14863 if( nrCastlingRights == 6 ) {
14864 if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
14865 boards[move][CASTLING][2] != NoRights ) *p++ = 'K';
14866 if(boards[move][CASTLING][1] == BOARD_LEFT &&
14867 boards[move][CASTLING][2] != NoRights ) *p++ = 'Q';
14868 if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
14869 boards[move][CASTLING][5] != NoRights ) *p++ = 'k';
14870 if(boards[move][CASTLING][4] == BOARD_LEFT &&
14871 boards[move][CASTLING][5] != NoRights ) *p++ = 'q';
14874 if (q == p) *p++ = '-'; /* No castling rights */
14878 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
14879 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
14880 /* En passant target square */
14881 if (move > backwardMostMove) {
14882 fromX = moveList[move - 1][0] - AAA;
14883 fromY = moveList[move - 1][1] - ONE;
14884 toX = moveList[move - 1][2] - AAA;
14885 toY = moveList[move - 1][3] - ONE;
14886 if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
14887 toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
14888 boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
14890 /* 2-square pawn move just happened */
14892 *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
14896 } else if(move == backwardMostMove) {
14897 // [HGM] perhaps we should always do it like this, and forget the above?
14898 if((signed char)boards[move][EP_STATUS] >= 0) {
14899 *p++ = boards[move][EP_STATUS] + AAA;
14900 *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
14911 /* [HGM] find reversible plies */
14912 { int i = 0, j=move;
14914 if (appData.debugMode) { int k;
14915 fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
14916 for(k=backwardMostMove; k<=forwardMostMove; k++)
14917 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
14921 while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
14922 if( j == backwardMostMove ) i += initialRulePlies;
14923 sprintf(p, "%d ", i);
14924 p += i>=100 ? 4 : i >= 10 ? 3 : 2;
14926 /* Fullmove number */
14927 sprintf(p, "%d", (move / 2) + 1);
14929 return StrSave(buf);
14933 ParseFEN(board, blackPlaysFirst, fen)
14935 int *blackPlaysFirst;
14945 /* [HGM] by default clear Crazyhouse holdings, if present */
14946 if(gameInfo.holdingsWidth) {
14947 for(i=0; i<BOARD_HEIGHT; i++) {
14948 board[i][0] = EmptySquare; /* black holdings */
14949 board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
14950 board[i][1] = (ChessSquare) 0; /* black counts */
14951 board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
14955 /* Piece placement data */
14956 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
14959 if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
14960 if (*p == '/') p++;
14961 emptycount = gameInfo.boardWidth - j;
14962 while (emptycount--)
14963 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
14965 #if(BOARD_FILES >= 10)
14966 } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
14967 p++; emptycount=10;
14968 if (j + emptycount > gameInfo.boardWidth) return FALSE;
14969 while (emptycount--)
14970 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
14972 } else if (isdigit(*p)) {
14973 emptycount = *p++ - '0';
14974 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
14975 if (j + emptycount > gameInfo.boardWidth) return FALSE;
14976 while (emptycount--)
14977 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
14978 } else if (*p == '+' || isalpha(*p)) {
14979 if (j >= gameInfo.boardWidth) return FALSE;
14981 piece = CharToPiece(*++p);
14982 if(piece == EmptySquare) return FALSE; /* unknown piece */
14983 piece = (ChessSquare) (PROMOTED piece ); p++;
14984 if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
14985 } else piece = CharToPiece(*p++);
14987 if(piece==EmptySquare) return FALSE; /* unknown piece */
14988 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
14989 piece = (ChessSquare) (PROMOTED piece);
14990 if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
14993 board[i][(j++)+gameInfo.holdingsWidth] = piece;
14999 while (*p == '/' || *p == ' ') p++;
15001 /* [HGM] look for Crazyhouse holdings here */
15002 while(*p==' ') p++;
15003 if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
15005 if(*p == '-' ) p++; /* empty holdings */ else {
15006 if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
15007 /* if we would allow FEN reading to set board size, we would */
15008 /* have to add holdings and shift the board read so far here */
15009 while( (piece = CharToPiece(*p) ) != EmptySquare ) {
15011 if((int) piece >= (int) BlackPawn ) {
15012 i = (int)piece - (int)BlackPawn;
15013 i = PieceToNumber((ChessSquare)i);
15014 if( i >= gameInfo.holdingsSize ) return FALSE;
15015 board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
15016 board[BOARD_HEIGHT-1-i][1]++; /* black counts */
15018 i = (int)piece - (int)WhitePawn;
15019 i = PieceToNumber((ChessSquare)i);
15020 if( i >= gameInfo.holdingsSize ) return FALSE;
15021 board[i][BOARD_WIDTH-1] = piece; /* white holdings */
15022 board[i][BOARD_WIDTH-2]++; /* black holdings */
15029 while(*p == ' ') p++;
15033 if(appData.colorNickNames) {
15034 if( c == appData.colorNickNames[0] ) c = 'w'; else
15035 if( c == appData.colorNickNames[1] ) c = 'b';
15039 *blackPlaysFirst = FALSE;
15042 *blackPlaysFirst = TRUE;
15048 /* [HGM] We NO LONGER ignore the rest of the FEN notation */
15049 /* return the extra info in global variiables */
15051 /* set defaults in case FEN is incomplete */
15052 board[EP_STATUS] = EP_UNKNOWN;
15053 for(i=0; i<nrCastlingRights; i++ ) {
15054 board[CASTLING][i] =
15055 gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
15056 } /* assume possible unless obviously impossible */
15057 if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
15058 if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
15059 if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
15060 && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
15061 if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
15062 if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
15063 if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
15064 && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
15067 while(*p==' ') p++;
15068 if(nrCastlingRights) {
15069 if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
15070 /* castling indicator present, so default becomes no castlings */
15071 for(i=0; i<nrCastlingRights; i++ ) {
15072 board[CASTLING][i] = NoRights;
15075 while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
15076 (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
15077 ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
15078 ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth) ) {
15079 char c = *p++; int whiteKingFile=NoRights, blackKingFile=NoRights;
15081 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
15082 if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
15083 if(board[0 ][i] == WhiteKing) whiteKingFile = i;
15085 if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
15086 whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
15087 if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
15088 && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
15089 if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
15090 && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
15093 for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
15094 board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
15095 board[CASTLING][2] = whiteKingFile;
15098 for(i=BOARD_LEFT; i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
15099 board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
15100 board[CASTLING][2] = whiteKingFile;
15103 for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
15104 board[CASTLING][3] = i != blackKingFile ? i : NoRights;
15105 board[CASTLING][5] = blackKingFile;
15108 for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
15109 board[CASTLING][4] = i != blackKingFile ? i : NoRights;
15110 board[CASTLING][5] = blackKingFile;
15113 default: /* FRC castlings */
15114 if(c >= 'a') { /* black rights */
15115 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
15116 if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
15117 if(i == BOARD_RGHT) break;
15118 board[CASTLING][5] = i;
15120 if(board[BOARD_HEIGHT-1][c] < BlackPawn ||
15121 board[BOARD_HEIGHT-1][c] >= BlackKing ) break;
15123 board[CASTLING][3] = c;
15125 board[CASTLING][4] = c;
15126 } else { /* white rights */
15127 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
15128 if(board[0][i] == WhiteKing) break;
15129 if(i == BOARD_RGHT) break;
15130 board[CASTLING][2] = i;
15131 c -= AAA - 'a' + 'A';
15132 if(board[0][c] >= WhiteKing) break;
15134 board[CASTLING][0] = c;
15136 board[CASTLING][1] = c;
15140 for(i=0; i<nrCastlingRights; i++)
15141 if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
15142 if (appData.debugMode) {
15143 fprintf(debugFP, "FEN castling rights:");
15144 for(i=0; i<nrCastlingRights; i++)
15145 fprintf(debugFP, " %d", board[CASTLING][i]);
15146 fprintf(debugFP, "\n");
15149 while(*p==' ') p++;
15152 /* read e.p. field in games that know e.p. capture */
15153 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
15154 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
15156 p++; board[EP_STATUS] = EP_NONE;
15158 char c = *p++ - AAA;
15160 if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
15161 if(*p >= '0' && *p <='9') p++;
15162 board[EP_STATUS] = c;
15167 if(sscanf(p, "%d", &i) == 1) {
15168 FENrulePlies = i; /* 50-move ply counter */
15169 /* (The move number is still ignored) */
15176 EditPositionPasteFEN(char *fen)
15179 Board initial_position;
15181 if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
15182 DisplayError(_("Bad FEN position in clipboard"), 0);
15185 int savedBlackPlaysFirst = blackPlaysFirst;
15186 EditPositionEvent();
15187 blackPlaysFirst = savedBlackPlaysFirst;
15188 CopyBoard(boards[0], initial_position);
15189 initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
15190 EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
15191 DisplayBothClocks();
15192 DrawPosition(FALSE, boards[currentMove]);
15197 static char cseq[12] = "\\ ";
15199 Boolean set_cont_sequence(char *new_seq)
15204 // handle bad attempts to set the sequence
15206 return 0; // acceptable error - no debug
15208 len = strlen(new_seq);
15209 ret = (len > 0) && (len < sizeof(cseq));
15211 safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
15212 else if (appData.debugMode)
15213 fprintf(debugFP, "Invalid continuation sequence \"%s\" (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
15218 reformat a source message so words don't cross the width boundary. internal
15219 newlines are not removed. returns the wrapped size (no null character unless
15220 included in source message). If dest is NULL, only calculate the size required
15221 for the dest buffer. lp argument indicats line position upon entry, and it's
15222 passed back upon exit.
15224 int wrap(char *dest, char *src, int count, int width, int *lp)
15226 int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
15228 cseq_len = strlen(cseq);
15229 old_line = line = *lp;
15230 ansi = len = clen = 0;
15232 for (i=0; i < count; i++)
15234 if (src[i] == '\033')
15237 // if we hit the width, back up
15238 if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
15240 // store i & len in case the word is too long
15241 old_i = i, old_len = len;
15243 // find the end of the last word
15244 while (i && src[i] != ' ' && src[i] != '\n')
15250 // word too long? restore i & len before splitting it
15251 if ((old_i-i+clen) >= width)
15258 if (i && src[i-1] == ' ')
15261 if (src[i] != ' ' && src[i] != '\n')
15268 // now append the newline and continuation sequence
15273 strncpy(dest+len, cseq, cseq_len);
15281 dest[len] = src[i];
15285 if (src[i] == '\n')
15290 if (dest && appData.debugMode)
15292 fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
15293 count, width, line, len, *lp);
15294 show_bytes(debugFP, src, count);
15295 fprintf(debugFP, "\ndest: ");
15296 show_bytes(debugFP, dest, len);
15297 fprintf(debugFP, "\n");
15299 *lp = dest ? line : old_line;
15304 // [HGM] vari: routines for shelving variations
15307 PushTail(int firstMove, int lastMove)
15309 int i, j, nrMoves = lastMove - firstMove;
15311 if(appData.icsActive) { // only in local mode
15312 forwardMostMove = currentMove; // mimic old ICS behavior
15315 if(storedGames >= MAX_VARIATIONS-1) return;
15317 // push current tail of game on stack
15318 savedResult[storedGames] = gameInfo.result;
15319 savedDetails[storedGames] = gameInfo.resultDetails;
15320 gameInfo.resultDetails = NULL;
15321 savedFirst[storedGames] = firstMove;
15322 savedLast [storedGames] = lastMove;
15323 savedFramePtr[storedGames] = framePtr;
15324 framePtr -= nrMoves; // reserve space for the boards
15325 for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
15326 CopyBoard(boards[framePtr+i], boards[firstMove+i]);
15327 for(j=0; j<MOVE_LEN; j++)
15328 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
15329 for(j=0; j<2*MOVE_LEN; j++)
15330 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
15331 timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
15332 timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
15333 pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
15334 pvInfoList[firstMove+i-1].depth = 0;
15335 commentList[framePtr+i] = commentList[firstMove+i];
15336 commentList[firstMove+i] = NULL;
15340 forwardMostMove = firstMove; // truncate game so we can start variation
15341 if(storedGames == 1) GreyRevert(FALSE);
15345 PopTail(Boolean annotate)
15348 char buf[8000], moveBuf[20];
15350 if(appData.icsActive) return FALSE; // only in local mode
15351 if(!storedGames) return FALSE; // sanity
15352 CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
15355 ToNrEvent(savedFirst[storedGames]); // sets currentMove
15356 nrMoves = savedLast[storedGames] - currentMove;
15359 if(!WhiteOnMove(currentMove))
15360 snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
15361 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
15362 for(i=currentMove; i<forwardMostMove; i++) {
15364 snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
15365 else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
15366 strcat(buf, moveBuf);
15367 if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
15368 if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
15372 for(i=1; i<=nrMoves; i++) { // copy last variation back
15373 CopyBoard(boards[currentMove+i], boards[framePtr+i]);
15374 for(j=0; j<MOVE_LEN; j++)
15375 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
15376 for(j=0; j<2*MOVE_LEN; j++)
15377 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
15378 timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
15379 timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
15380 pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
15381 if(commentList[currentMove+i]) free(commentList[currentMove+i]);
15382 commentList[currentMove+i] = commentList[framePtr+i];
15383 commentList[framePtr+i] = NULL;
15385 if(annotate) AppendComment(currentMove+1, buf, FALSE);
15386 framePtr = savedFramePtr[storedGames];
15387 gameInfo.result = savedResult[storedGames];
15388 if(gameInfo.resultDetails != NULL) {
15389 free(gameInfo.resultDetails);
15391 gameInfo.resultDetails = savedDetails[storedGames];
15392 forwardMostMove = currentMove + nrMoves;
15393 if(storedGames == 0) GreyRevert(TRUE);
15399 { // remove all shelved variations
15401 for(i=0; i<storedGames; i++) {
15402 if(savedDetails[i])
15403 free(savedDetails[i]);
15404 savedDetails[i] = NULL;
15406 for(i=framePtr; i<MAX_MOVES; i++) {
15407 if(commentList[i]) free(commentList[i]);
15408 commentList[i] = NULL;
15410 framePtr = MAX_MOVES-1;
15415 LoadVariation(int index, char *text)
15416 { // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
15417 char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
15418 int level = 0, move;
15420 if(gameMode != EditGame && gameMode != AnalyzeMode) return;
15421 // first find outermost bracketing variation
15422 while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
15423 if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
15424 if(*p == '{') wait = '}'; else
15425 if(*p == '[') wait = ']'; else
15426 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
15427 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
15429 if(*p == wait) wait = NULLCHAR; // closing ]} found
15432 if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
15433 if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
15434 end[1] = NULLCHAR; // clip off comment beyond variation
15435 ToNrEvent(currentMove-1);
15436 PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
15437 // kludge: use ParsePV() to append variation to game
15438 move = currentMove;
15439 ParsePV(start, TRUE);
15440 forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
15441 ClearPremoveHighlights();
15443 ToNrEvent(currentMove+1);