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)
140 /* A point in time */
142 long sec; /* Assuming this is >= 32 bits */
143 int ms; /* Assuming this is >= 16 bits */
146 int establish P((void));
147 void read_from_player P((InputSourceRef isr, VOIDSTAR closure,
148 char *buf, int count, int error));
149 void read_from_ics P((InputSourceRef isr, VOIDSTAR closure,
150 char *buf, int count, int error));
151 void ics_printf P((char *format, ...));
152 void SendToICS P((char *s));
153 void SendToICSDelayed P((char *s, long msdelay));
154 void SendMoveToICS P((ChessMove moveType, int fromX, int fromY,
156 void HandleMachineMove P((char *message, ChessProgramState *cps));
157 int AutoPlayOneMove P((void));
158 int LoadGameOneMove P((ChessMove readAhead));
159 int LoadGameFromFile P((char *filename, int n, char *title, int useList));
160 int LoadPositionFromFile P((char *filename, int n, char *title));
161 int SavePositionToFile P((char *filename));
162 void ApplyMove P((int fromX, int fromY, int toX, int toY, int promoChar,
164 void MakeMove P((int fromX, int fromY, int toX, int toY, int promoChar));
165 void ShowMove P((int fromX, int fromY, int toX, int toY));
166 int FinishMove P((ChessMove moveType, int fromX, int fromY, int toX, int toY,
167 /*char*/int promoChar));
168 void BackwardInner P((int target));
169 void ForwardInner P((int target));
170 int Adjudicate P((ChessProgramState *cps));
171 void GameEnds P((ChessMove result, char *resultDetails, int whosays));
172 void EditPositionDone P((Boolean fakeRights));
173 void PrintOpponents P((FILE *fp));
174 void PrintPosition P((FILE *fp, int move));
175 void StartChessProgram P((ChessProgramState *cps));
176 void SendToProgram P((char *message, ChessProgramState *cps));
177 void SendMoveToProgram P((int moveNum, ChessProgramState *cps));
178 void ReceiveFromProgram P((InputSourceRef isr, VOIDSTAR closure,
179 char *buf, int count, int error));
180 void SendTimeControl P((ChessProgramState *cps,
181 int mps, long tc, int inc, int sd, int st));
182 char *TimeControlTagValue P((void));
183 void Attention P((ChessProgramState *cps));
184 void FeedMovesToProgram P((ChessProgramState *cps, int upto));
185 void ResurrectChessProgram P((void));
186 void DisplayComment P((int moveNumber, char *text));
187 void DisplayMove P((int moveNumber));
189 void ParseGameHistory P((char *game));
190 void ParseBoard12 P((char *string));
191 void KeepAlive P((void));
192 void StartClocks P((void));
193 void SwitchClocks P((int nr));
194 void StopClocks P((void));
195 void ResetClocks P((void));
196 char *PGNDate P((void));
197 void SetGameInfo P((void));
198 Boolean ParseFEN P((Board board, int *blackPlaysFirst, char *fen));
199 int RegisterMove P((void));
200 void MakeRegisteredMove P((void));
201 void TruncateGame P((void));
202 int looking_at P((char *, int *, char *));
203 void CopyPlayerNameIntoFileName P((char **, char *));
204 char *SavePart P((char *));
205 int SaveGameOldStyle P((FILE *));
206 int SaveGamePGN P((FILE *));
207 void GetTimeMark P((TimeMark *));
208 long SubtractTimeMarks P((TimeMark *, TimeMark *));
209 int CheckFlags P((void));
210 long NextTickLength P((long));
211 void CheckTimeControl P((void));
212 void show_bytes P((FILE *, char *, int));
213 int string_to_rating P((char *str));
214 void ParseFeatures P((char* args, ChessProgramState *cps));
215 void InitBackEnd3 P((void));
216 void FeatureDone P((ChessProgramState* cps, int val));
217 void InitChessProgram P((ChessProgramState *cps, int setup));
218 void OutputKibitz(int window, char *text);
219 int PerpetualChase(int first, int last);
220 int EngineOutputIsUp();
221 void InitDrawingSizes(int x, int y);
224 extern void ConsoleCreate();
227 ChessProgramState *WhitePlayer();
228 void InsertIntoMemo P((int which, char *text)); // [HGM] kibitz: in engineo.c
229 int VerifyDisplayMode P(());
231 char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment
232 void InitEngineUCI( const char * iniDir, ChessProgramState * cps ); // [HGM] moved here from winboard.c
233 char *ProbeBook P((int moveNr, char *book)); // [HGM] book: returns a book move
234 char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book
235 void ics_update_width P((int new_width));
236 extern char installDir[MSG_SIZ];
238 extern int tinyLayout, smallLayout;
239 ChessProgramStats programStats;
240 char lastPV[2][2*MSG_SIZ]; /* [HGM] pv: last PV in thinking output of each engine */
242 static int exiting = 0; /* [HGM] moved to top */
243 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
244 int startedFromPositionFile = FALSE; Board filePosition; /* [HGM] loadPos */
245 Board partnerBoard; /* [HGM] bughouse: for peeking at partner game */
246 Boolean partnerBoardValid = 0;
247 char partnerStatus[MSG_SIZ];
249 Boolean originalFlip;
250 Boolean twoBoards = 0;
251 char endingGame = 0; /* [HGM] crash: flag to prevent recursion of GameEnds() */
252 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS */
253 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
254 int lastIndex = 0; /* [HGM] autoinc: last game/position used in match mode */
255 Boolean connectionAlive;/* [HGM] alive: ICS connection status from probing */
256 int opponentKibitzes;
257 int lastSavedGame; /* [HGM] save: ID of game */
258 char chatPartner[MAX_CHAT][MSG_SIZ]; /* [HGM] chat: list of chatting partners */
259 extern int chatCount;
261 char marker[BOARD_RANKS][BOARD_FILES]; /* [HGM] marks for target squares */
263 /* States for ics_getting_history */
265 #define H_REQUESTED 1
266 #define H_GOT_REQ_HEADER 2
267 #define H_GOT_UNREQ_HEADER 3
268 #define H_GETTING_MOVES 4
269 #define H_GOT_UNWANTED_HEADER 5
271 /* whosays values for GameEnds */
280 /* Maximum number of games in a cmail message */
281 #define CMAIL_MAX_GAMES 20
283 /* Different types of move when calling RegisterMove */
285 #define CMAIL_RESIGN 1
287 #define CMAIL_ACCEPT 3
289 /* Different types of result to remember for each game */
290 #define CMAIL_NOT_RESULT 0
291 #define CMAIL_OLD_RESULT 1
292 #define CMAIL_NEW_RESULT 2
294 /* Telnet protocol constants */
305 static char * safeStrCpy( char * dst, const char * src, size_t count )
307 assert( dst != NULL );
308 assert( src != NULL );
311 strncpy( dst, src, count );
312 dst[ count-1 ] = '\0';
316 /* Some compiler can't cast u64 to double
317 * This function do the job for us:
319 * We use the highest bit for cast, this only
320 * works if the highest bit is not
321 * in use (This should not happen)
323 * We used this for all compiler
326 u64ToDouble(u64 value)
329 u64 tmp = value & u64Const(0x7fffffffffffffff);
330 r = (double)(s64)tmp;
331 if (value & u64Const(0x8000000000000000))
332 r += 9.2233720368547758080e18; /* 2^63 */
336 /* Fake up flags for now, as we aren't keeping track of castling
337 availability yet. [HGM] Change of logic: the flag now only
338 indicates the type of castlings allowed by the rule of the game.
339 The actual rights themselves are maintained in the array
340 castlingRights, as part of the game history, and are not probed
346 int flags = F_ALL_CASTLE_OK;
347 if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
348 switch (gameInfo.variant) {
350 flags &= ~F_ALL_CASTLE_OK;
351 case VariantGiveaway: // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
352 flags |= F_IGNORE_CHECK;
354 flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
357 flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
359 case VariantKriegspiel:
360 flags |= F_KRIEGSPIEL_CAPTURE;
362 case VariantCapaRandom:
363 case VariantFischeRandom:
364 flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
365 case VariantNoCastle:
366 case VariantShatranj:
369 flags &= ~F_ALL_CASTLE_OK;
377 FILE *gameFileFP, *debugFP;
380 [AS] Note: sometimes, the sscanf() function is used to parse the input
381 into a fixed-size buffer. Because of this, we must be prepared to
382 receive strings as long as the size of the input buffer, which is currently
383 set to 4K for Windows and 8K for the rest.
384 So, we must either allocate sufficiently large buffers here, or
385 reduce the size of the input buffer in the input reading part.
388 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
389 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
390 char thinkOutput1[MSG_SIZ*10];
392 ChessProgramState first, second;
394 /* premove variables */
397 int premoveFromX = 0;
398 int premoveFromY = 0;
399 int premovePromoChar = 0;
401 Boolean alarmSounded;
402 /* end premove variables */
404 char *ics_prefix = "$";
405 int ics_type = ICS_GENERIC;
407 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
408 int pauseExamForwardMostMove = 0;
409 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
410 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
411 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
412 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
413 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
414 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
415 int whiteFlag = FALSE, blackFlag = FALSE;
416 int userOfferedDraw = FALSE;
417 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
418 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
419 int cmailMoveType[CMAIL_MAX_GAMES];
420 long ics_clock_paused = 0;
421 ProcRef icsPR = NoProc, cmailPR = NoProc;
422 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
423 GameMode gameMode = BeginningOfGame;
424 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
425 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
426 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
427 int hiddenThinkOutputState = 0; /* [AS] */
428 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
429 int adjudicateLossPlies = 6;
430 char white_holding[64], black_holding[64];
431 TimeMark lastNodeCountTime;
432 long lastNodeCount=0;
433 int have_sent_ICS_logon = 0;
435 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement;
436 long timeControl_2; /* [AS] Allow separate time controls */
437 char *fullTimeControlString = NULL; /* [HGM] secondary TC: merge of MPS, TC and inc */
438 long timeRemaining[2][MAX_MOVES];
440 TimeMark programStartTime;
441 char ics_handle[MSG_SIZ];
442 int have_set_title = 0;
444 /* animateTraining preserves the state of appData.animate
445 * when Training mode is activated. This allows the
446 * response to be animated when appData.animate == TRUE and
447 * appData.animateDragging == TRUE.
449 Boolean animateTraining;
455 Board boards[MAX_MOVES];
456 /* [HGM] Following 7 needed for accurate legality tests: */
457 signed char castlingRank[BOARD_FILES]; // and corresponding ranks
458 signed char initialRights[BOARD_FILES];
459 int nrCastlingRights; // For TwoKings, or to implement castling-unknown status
460 int initialRulePlies, FENrulePlies;
461 FILE *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
464 int mute; // mute all sounds
466 // [HGM] vari: next 12 to save and restore variations
467 #define MAX_VARIATIONS 10
468 int framePtr = MAX_MOVES-1; // points to free stack entry
470 int savedFirst[MAX_VARIATIONS];
471 int savedLast[MAX_VARIATIONS];
472 int savedFramePtr[MAX_VARIATIONS];
473 char *savedDetails[MAX_VARIATIONS];
474 ChessMove savedResult[MAX_VARIATIONS];
476 void PushTail P((int firstMove, int lastMove));
477 Boolean PopTail P((Boolean annotate));
478 void CleanupTail P((void));
480 ChessSquare FIDEArray[2][BOARD_FILES] = {
481 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
482 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
483 { BlackRook, BlackKnight, BlackBishop, BlackQueen,
484 BlackKing, BlackBishop, BlackKnight, BlackRook }
487 ChessSquare twoKingsArray[2][BOARD_FILES] = {
488 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
489 WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
490 { BlackRook, BlackKnight, BlackBishop, BlackQueen,
491 BlackKing, BlackKing, BlackKnight, BlackRook }
494 ChessSquare KnightmateArray[2][BOARD_FILES] = {
495 { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
496 WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
497 { BlackRook, BlackMan, BlackBishop, BlackQueen,
498 BlackUnicorn, BlackBishop, BlackMan, BlackRook }
501 ChessSquare fairyArray[2][BOARD_FILES] = { /* [HGM] Queen side differs from King side */
502 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
503 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
504 { BlackLance, BlackAlfil, BlackMarshall, BlackAngel,
505 BlackKing, BlackMarshall, BlackAlfil, BlackLance }
508 ChessSquare ShatranjArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
509 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
510 WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
511 { BlackRook, BlackKnight, BlackAlfil, BlackKing,
512 BlackFerz, BlackAlfil, BlackKnight, BlackRook }
515 ChessSquare makrukArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
516 { WhiteRook, WhiteKnight, WhiteMan, WhiteKing,
517 WhiteFerz, WhiteMan, WhiteKnight, WhiteRook },
518 { BlackRook, BlackKnight, BlackMan, BlackFerz,
519 BlackKing, BlackMan, BlackKnight, BlackRook }
523 #if (BOARD_FILES>=10)
524 ChessSquare ShogiArray[2][BOARD_FILES] = {
525 { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
526 WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
527 { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
528 BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
531 ChessSquare XiangqiArray[2][BOARD_FILES] = {
532 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
533 WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
534 { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
535 BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
538 ChessSquare CapablancaArray[2][BOARD_FILES] = {
539 { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
540 WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
541 { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
542 BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
545 ChessSquare GreatArray[2][BOARD_FILES] = {
546 { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
547 WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
548 { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
549 BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
552 ChessSquare JanusArray[2][BOARD_FILES] = {
553 { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
554 WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
555 { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
556 BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
560 ChessSquare GothicArray[2][BOARD_FILES] = {
561 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
562 WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
563 { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
564 BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
567 #define GothicArray CapablancaArray
571 ChessSquare FalconArray[2][BOARD_FILES] = {
572 { WhiteRook, WhiteKnight, WhiteBishop, WhiteLance, WhiteQueen,
573 WhiteKing, WhiteLance, WhiteBishop, WhiteKnight, WhiteRook },
574 { BlackRook, BlackKnight, BlackBishop, BlackLance, BlackQueen,
575 BlackKing, BlackLance, BlackBishop, BlackKnight, BlackRook }
578 #define FalconArray CapablancaArray
581 #else // !(BOARD_FILES>=10)
582 #define XiangqiPosition FIDEArray
583 #define CapablancaArray FIDEArray
584 #define GothicArray FIDEArray
585 #define GreatArray FIDEArray
586 #endif // !(BOARD_FILES>=10)
588 #if (BOARD_FILES>=12)
589 ChessSquare CourierArray[2][BOARD_FILES] = {
590 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
591 WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
592 { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
593 BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
595 #else // !(BOARD_FILES>=12)
596 #define CourierArray CapablancaArray
597 #endif // !(BOARD_FILES>=12)
600 Board initialPosition;
603 /* Convert str to a rating. Checks for special cases of "----",
605 "++++", etc. Also strips ()'s */
607 string_to_rating(str)
610 while(*str && !isdigit(*str)) ++str;
612 return 0; /* One of the special "no rating" cases */
620 /* Init programStats */
621 programStats.movelist[0] = 0;
622 programStats.depth = 0;
623 programStats.nr_moves = 0;
624 programStats.moves_left = 0;
625 programStats.nodes = 0;
626 programStats.time = -1; // [HGM] PGNtime: make invalid to recognize engine output
627 programStats.score = 0;
628 programStats.got_only_move = 0;
629 programStats.got_fail = 0;
630 programStats.line_is_book = 0;
636 int matched, min, sec;
638 ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
640 GetTimeMark(&programStartTime);
641 srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
644 programStats.ok_to_send = 1;
645 programStats.seen_stat = 0;
648 * Initialize game list
654 * Internet chess server status
656 if (appData.icsActive) {
657 appData.matchMode = FALSE;
658 appData.matchGames = 0;
660 appData.noChessProgram = !appData.zippyPlay;
662 appData.zippyPlay = FALSE;
663 appData.zippyTalk = FALSE;
664 appData.noChessProgram = TRUE;
666 if (*appData.icsHelper != NULLCHAR) {
667 appData.useTelnet = TRUE;
668 appData.telnetProgram = appData.icsHelper;
671 appData.zippyTalk = appData.zippyPlay = FALSE;
674 /* [AS] Initialize pv info list [HGM] and game state */
678 for( i=0; i<=framePtr; i++ ) {
679 pvInfoList[i].depth = -1;
680 boards[i][EP_STATUS] = EP_NONE;
681 for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
686 * Parse timeControl resource
688 if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
689 appData.movesPerSession)) {
691 snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
692 DisplayFatalError(buf, 0, 2);
696 * Parse searchTime resource
698 if (*appData.searchTime != NULLCHAR) {
699 matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
701 searchTime = min * 60;
702 } else if (matched == 2) {
703 searchTime = min * 60 + sec;
706 snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
707 DisplayFatalError(buf, 0, 2);
711 /* [AS] Adjudication threshold */
712 adjudicateLossThreshold = appData.adjudicateLossThreshold;
714 first.which = "first";
715 second.which = "second";
716 first.maybeThinking = second.maybeThinking = FALSE;
717 first.pr = second.pr = NoProc;
718 first.isr = second.isr = NULL;
719 first.sendTime = second.sendTime = 2;
720 first.sendDrawOffers = 1;
721 if (appData.firstPlaysBlack) {
722 first.twoMachinesColor = "black\n";
723 second.twoMachinesColor = "white\n";
725 first.twoMachinesColor = "white\n";
726 second.twoMachinesColor = "black\n";
728 first.program = appData.firstChessProgram;
729 second.program = appData.secondChessProgram;
730 first.host = appData.firstHost;
731 second.host = appData.secondHost;
732 first.dir = appData.firstDirectory;
733 second.dir = appData.secondDirectory;
734 first.other = &second;
735 second.other = &first;
736 first.initString = appData.initString;
737 second.initString = appData.secondInitString;
738 first.computerString = appData.firstComputerString;
739 second.computerString = appData.secondComputerString;
740 first.useSigint = second.useSigint = TRUE;
741 first.useSigterm = second.useSigterm = TRUE;
742 first.reuse = appData.reuseFirst;
743 second.reuse = appData.reuseSecond;
744 first.nps = appData.firstNPS; // [HGM] nps: copy nodes per second
745 second.nps = appData.secondNPS;
746 first.useSetboard = second.useSetboard = FALSE;
747 first.useSAN = second.useSAN = FALSE;
748 first.usePing = second.usePing = FALSE;
749 first.lastPing = second.lastPing = 0;
750 first.lastPong = second.lastPong = 0;
751 first.usePlayother = second.usePlayother = FALSE;
752 first.useColors = second.useColors = TRUE;
753 first.useUsermove = second.useUsermove = FALSE;
754 first.sendICS = second.sendICS = FALSE;
755 first.sendName = second.sendName = appData.icsActive;
756 first.sdKludge = second.sdKludge = FALSE;
757 first.stKludge = second.stKludge = FALSE;
758 TidyProgramName(first.program, first.host, first.tidy);
759 TidyProgramName(second.program, second.host, second.tidy);
760 first.matchWins = second.matchWins = 0;
761 strcpy(first.variants, appData.variant);
762 strcpy(second.variants, appData.variant);
763 first.analysisSupport = second.analysisSupport = 2; /* detect */
764 first.analyzing = second.analyzing = FALSE;
765 first.initDone = second.initDone = FALSE;
767 /* New features added by Tord: */
768 first.useFEN960 = FALSE; second.useFEN960 = FALSE;
769 first.useOOCastle = TRUE; second.useOOCastle = TRUE;
770 /* End of new features added by Tord. */
771 first.fenOverride = appData.fenOverride1;
772 second.fenOverride = appData.fenOverride2;
774 /* [HGM] time odds: set factor for each machine */
775 first.timeOdds = appData.firstTimeOdds;
776 second.timeOdds = appData.secondTimeOdds;
778 if(appData.timeOddsMode) {
779 norm = first.timeOdds;
780 if(norm > second.timeOdds) norm = second.timeOdds;
782 first.timeOdds /= norm;
783 second.timeOdds /= norm;
786 /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
787 first.accumulateTC = appData.firstAccumulateTC;
788 second.accumulateTC = appData.secondAccumulateTC;
789 first.maxNrOfSessions = second.maxNrOfSessions = 1;
792 first.debug = second.debug = FALSE;
793 first.supportsNPS = second.supportsNPS = UNKNOWN;
796 first.optionSettings = appData.firstOptions;
797 second.optionSettings = appData.secondOptions;
799 first.scoreIsAbsolute = appData.firstScoreIsAbsolute; /* [AS] */
800 second.scoreIsAbsolute = appData.secondScoreIsAbsolute; /* [AS] */
801 first.isUCI = appData.firstIsUCI; /* [AS] */
802 second.isUCI = appData.secondIsUCI; /* [AS] */
803 first.hasOwnBookUCI = appData.firstHasOwnBookUCI; /* [AS] */
804 second.hasOwnBookUCI = appData.secondHasOwnBookUCI; /* [AS] */
806 if (appData.firstProtocolVersion > PROTOVER ||
807 appData.firstProtocolVersion < 1) {
809 sprintf(buf, _("protocol version %d not supported"),
810 appData.firstProtocolVersion);
811 DisplayFatalError(buf, 0, 2);
813 first.protocolVersion = appData.firstProtocolVersion;
816 if (appData.secondProtocolVersion > PROTOVER ||
817 appData.secondProtocolVersion < 1) {
819 sprintf(buf, _("protocol version %d not supported"),
820 appData.secondProtocolVersion);
821 DisplayFatalError(buf, 0, 2);
823 second.protocolVersion = appData.secondProtocolVersion;
826 if (appData.icsActive) {
827 appData.clockMode = TRUE; /* changes dynamically in ICS mode */
828 // } else if (*appData.searchTime != NULLCHAR || appData.noChessProgram) {
829 } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
830 appData.clockMode = FALSE;
831 first.sendTime = second.sendTime = 0;
835 /* Override some settings from environment variables, for backward
836 compatibility. Unfortunately it's not feasible to have the env
837 vars just set defaults, at least in xboard. Ugh.
839 if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
844 if (appData.noChessProgram) {
845 programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
846 sprintf(programVersion, "%s", PACKAGE_STRING);
848 /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
849 programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
850 sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
853 if (!appData.icsActive) {
855 /* Check for variants that are supported only in ICS mode,
856 or not at all. Some that are accepted here nevertheless
857 have bugs; see comments below.
859 VariantClass variant = StringToVariant(appData.variant);
861 case VariantBughouse: /* need four players and two boards */
862 case VariantKriegspiel: /* need to hide pieces and move details */
863 /* case VariantFischeRandom: (Fabien: moved below) */
864 sprintf(buf, _("Variant %s supported only in ICS mode"), appData.variant);
865 DisplayFatalError(buf, 0, 2);
869 case VariantLoadable:
879 sprintf(buf, _("Unknown variant name %s"), appData.variant);
880 DisplayFatalError(buf, 0, 2);
883 case VariantXiangqi: /* [HGM] repetition rules not implemented */
884 case VariantFairy: /* [HGM] TestLegality definitely off! */
885 case VariantGothic: /* [HGM] should work */
886 case VariantCapablanca: /* [HGM] should work */
887 case VariantCourier: /* [HGM] initial forced moves not implemented */
888 case VariantShogi: /* [HGM] drops not tested for legality */
889 case VariantKnightmate: /* [HGM] should work */
890 case VariantCylinder: /* [HGM] untested */
891 case VariantFalcon: /* [HGM] untested */
892 case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
893 offboard interposition not understood */
894 case VariantNormal: /* definitely works! */
895 case VariantWildCastle: /* pieces not automatically shuffled */
896 case VariantNoCastle: /* pieces not automatically shuffled */
897 case VariantFischeRandom: /* [HGM] works and shuffles pieces */
898 case VariantLosers: /* should work except for win condition,
899 and doesn't know captures are mandatory */
900 case VariantSuicide: /* should work except for win condition,
901 and doesn't know captures are mandatory */
902 case VariantGiveaway: /* should work except for win condition,
903 and doesn't know captures are mandatory */
904 case VariantTwoKings: /* should work */
905 case VariantAtomic: /* should work except for win condition */
906 case Variant3Check: /* should work except for win condition */
907 case VariantShatranj: /* should work except for all win conditions */
908 case VariantMakruk: /* should work except for daw countdown */
909 case VariantBerolina: /* might work if TestLegality is off */
910 case VariantCapaRandom: /* should work */
911 case VariantJanus: /* should work */
912 case VariantSuper: /* experimental */
913 case VariantGreat: /* experimental, requires legality testing to be off */
918 InitEngineUCI( installDir, &first ); // [HGM] moved here from winboard.c, to make available in xboard
919 InitEngineUCI( installDir, &second );
922 int NextIntegerFromString( char ** str, long * value )
927 while( *s == ' ' || *s == '\t' ) {
933 if( *s >= '0' && *s <= '9' ) {
934 while( *s >= '0' && *s <= '9' ) {
935 *value = *value * 10 + (*s - '0');
947 int NextTimeControlFromString( char ** str, long * value )
950 int result = NextIntegerFromString( str, &temp );
953 *value = temp * 60; /* Minutes */
956 result = NextIntegerFromString( str, &temp );
957 *value += temp; /* Seconds */
964 int NextSessionFromString( char ** str, int *moves, long * tc, long *inc)
965 { /* [HGM] routine added to read '+moves/time' for secondary time control */
966 int result = -1; long temp, temp2;
968 if(**str != '+') return -1; // old params remain in force!
970 if( NextTimeControlFromString( str, &temp ) ) return -1;
973 /* time only: incremental or sudden-death time control */
974 if(**str == '+') { /* increment follows; read it */
976 if(result = NextIntegerFromString( str, &temp2)) return -1;
979 *moves = 0; *tc = temp * 1000;
981 } else if(temp % 60 != 0) return -1; /* moves was given as min:sec */
983 (*str)++; /* classical time control */
984 result = NextTimeControlFromString( str, &temp2);
993 int GetTimeQuota(int movenr)
994 { /* [HGM] get time to add from the multi-session time-control string */
995 int moves=1; /* kludge to force reading of first session */
996 long time, increment;
997 char *s = fullTimeControlString;
999 if(appData.debugMode) fprintf(debugFP, "TC string = '%s'\n", fullTimeControlString);
1001 if(moves) NextSessionFromString(&s, &moves, &time, &increment);
1002 if(appData.debugMode) fprintf(debugFP, "mps=%d tc=%d inc=%d\n", moves, (int) time, (int) increment);
1003 if(movenr == -1) return time; /* last move before new session */
1004 if(!moves) return increment; /* current session is incremental */
1005 if(movenr >= 0) movenr -= moves; /* we already finished this session */
1006 } while(movenr >= -1); /* try again for next session */
1008 return 0; // no new time quota on this move
1012 ParseTimeControl(tc, ti, mps)
1021 if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1024 sprintf(buf, "+%d/%s+%d", mps, tc, ti);
1025 else sprintf(buf, "+%s+%d", tc, ti);
1028 sprintf(buf, "+%d/%s", mps, tc);
1029 else sprintf(buf, "+%s", tc);
1031 fullTimeControlString = StrSave(buf);
1033 if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1038 /* Parse second time control */
1041 if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1049 timeControl_2 = tc2 * 1000;
1059 timeControl = tc1 * 1000;
1062 timeIncrement = ti * 1000; /* convert to ms */
1063 movesPerSession = 0;
1066 movesPerSession = mps;
1074 if (appData.debugMode) {
1075 fprintf(debugFP, "%s\n", programVersion);
1078 set_cont_sequence(appData.wrapContSeq);
1079 if (appData.matchGames > 0) {
1080 appData.matchMode = TRUE;
1081 } else if (appData.matchMode) {
1082 appData.matchGames = 1;
1084 if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1085 appData.matchGames = appData.sameColorGames;
1086 if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1087 if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1088 if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1091 if (appData.noChessProgram || first.protocolVersion == 1) {
1094 /* kludge: allow timeout for initial "feature" commands */
1096 DisplayMessage("", _("Starting chess program"));
1097 ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1102 InitBackEnd3 P((void))
1104 GameMode initialMode;
1108 InitChessProgram(&first, startedFromSetupPosition);
1111 if (appData.icsActive) {
1113 /* [DM] Make a console window if needed [HGM] merged ifs */
1118 if (*appData.icsCommPort != NULLCHAR) {
1119 sprintf(buf, _("Could not open comm port %s"),
1120 appData.icsCommPort);
1122 snprintf(buf, sizeof(buf), _("Could not connect to host %s, port %s"),
1123 appData.icsHost, appData.icsPort);
1125 DisplayFatalError(buf, err, 1);
1130 AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1132 AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1133 if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1134 ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1135 } else if (appData.noChessProgram) {
1141 if (*appData.cmailGameName != NULLCHAR) {
1143 OpenLoopback(&cmailPR);
1145 AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1149 DisplayMessage("", "");
1150 if (StrCaseCmp(appData.initialMode, "") == 0) {
1151 initialMode = BeginningOfGame;
1152 } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1153 initialMode = TwoMachinesPlay;
1154 } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1155 initialMode = AnalyzeFile;
1156 } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1157 initialMode = AnalyzeMode;
1158 } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1159 initialMode = MachinePlaysWhite;
1160 } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1161 initialMode = MachinePlaysBlack;
1162 } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1163 initialMode = EditGame;
1164 } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1165 initialMode = EditPosition;
1166 } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1167 initialMode = Training;
1169 sprintf(buf, _("Unknown initialMode %s"), appData.initialMode);
1170 DisplayFatalError(buf, 0, 2);
1174 if (appData.matchMode) {
1175 /* Set up machine vs. machine match */
1176 if (appData.noChessProgram) {
1177 DisplayFatalError(_("Can't have a match with no chess programs"),
1183 if (*appData.loadGameFile != NULLCHAR) {
1184 int index = appData.loadGameIndex; // [HGM] autoinc
1185 if(index<0) lastIndex = index = 1;
1186 if (!LoadGameFromFile(appData.loadGameFile,
1188 appData.loadGameFile, FALSE)) {
1189 DisplayFatalError(_("Bad game file"), 0, 1);
1192 } else if (*appData.loadPositionFile != NULLCHAR) {
1193 int index = appData.loadPositionIndex; // [HGM] autoinc
1194 if(index<0) lastIndex = index = 1;
1195 if (!LoadPositionFromFile(appData.loadPositionFile,
1197 appData.loadPositionFile)) {
1198 DisplayFatalError(_("Bad position file"), 0, 1);
1203 } else if (*appData.cmailGameName != NULLCHAR) {
1204 /* Set up cmail mode */
1205 ReloadCmailMsgEvent(TRUE);
1207 /* Set up other modes */
1208 if (initialMode == AnalyzeFile) {
1209 if (*appData.loadGameFile == NULLCHAR) {
1210 DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1214 if (*appData.loadGameFile != NULLCHAR) {
1215 (void) LoadGameFromFile(appData.loadGameFile,
1216 appData.loadGameIndex,
1217 appData.loadGameFile, TRUE);
1218 } else if (*appData.loadPositionFile != NULLCHAR) {
1219 (void) LoadPositionFromFile(appData.loadPositionFile,
1220 appData.loadPositionIndex,
1221 appData.loadPositionFile);
1222 /* [HGM] try to make self-starting even after FEN load */
1223 /* to allow automatic setup of fairy variants with wtm */
1224 if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1225 gameMode = BeginningOfGame;
1226 setboardSpoiledMachineBlack = 1;
1228 /* [HGM] loadPos: make that every new game uses the setup */
1229 /* from file as long as we do not switch variant */
1230 if(!blackPlaysFirst) {
1231 startedFromPositionFile = TRUE;
1232 CopyBoard(filePosition, boards[0]);
1235 if (initialMode == AnalyzeMode) {
1236 if (appData.noChessProgram) {
1237 DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1240 if (appData.icsActive) {
1241 DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1245 } else if (initialMode == AnalyzeFile) {
1246 appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1247 ShowThinkingEvent();
1249 AnalysisPeriodicEvent(1);
1250 } else if (initialMode == MachinePlaysWhite) {
1251 if (appData.noChessProgram) {
1252 DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1256 if (appData.icsActive) {
1257 DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1261 MachineWhiteEvent();
1262 } else if (initialMode == MachinePlaysBlack) {
1263 if (appData.noChessProgram) {
1264 DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1268 if (appData.icsActive) {
1269 DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1273 MachineBlackEvent();
1274 } else if (initialMode == TwoMachinesPlay) {
1275 if (appData.noChessProgram) {
1276 DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1280 if (appData.icsActive) {
1281 DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1286 } else if (initialMode == EditGame) {
1288 } else if (initialMode == EditPosition) {
1289 EditPositionEvent();
1290 } else if (initialMode == Training) {
1291 if (*appData.loadGameFile == NULLCHAR) {
1292 DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1301 * Establish will establish a contact to a remote host.port.
1302 * Sets icsPR to a ProcRef for a process (or pseudo-process)
1303 * used to talk to the host.
1304 * Returns 0 if okay, error code if not.
1311 if (*appData.icsCommPort != NULLCHAR) {
1312 /* Talk to the host through a serial comm port */
1313 return OpenCommPort(appData.icsCommPort, &icsPR);
1315 } else if (*appData.gateway != NULLCHAR) {
1316 if (*appData.remoteShell == NULLCHAR) {
1317 /* Use the rcmd protocol to run telnet program on a gateway host */
1318 snprintf(buf, sizeof(buf), "%s %s %s",
1319 appData.telnetProgram, appData.icsHost, appData.icsPort);
1320 return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1323 /* Use the rsh program to run telnet program on a gateway host */
1324 if (*appData.remoteUser == NULLCHAR) {
1325 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1326 appData.gateway, appData.telnetProgram,
1327 appData.icsHost, appData.icsPort);
1329 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1330 appData.remoteShell, appData.gateway,
1331 appData.remoteUser, appData.telnetProgram,
1332 appData.icsHost, appData.icsPort);
1334 return StartChildProcess(buf, "", &icsPR);
1337 } else if (appData.useTelnet) {
1338 return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1341 /* TCP socket interface differs somewhat between
1342 Unix and NT; handle details in the front end.
1344 return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1349 show_bytes(fp, buf, count)
1355 if (*buf < 040 || *(unsigned char *) buf > 0177) {
1356 fprintf(fp, "\\%03o", *buf & 0xff);
1365 /* Returns an errno value */
1367 OutputMaybeTelnet(pr, message, count, outError)
1373 char buf[8192], *p, *q, *buflim;
1374 int left, newcount, outcount;
1376 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1377 *appData.gateway != NULLCHAR) {
1378 if (appData.debugMode) {
1379 fprintf(debugFP, ">ICS: ");
1380 show_bytes(debugFP, message, count);
1381 fprintf(debugFP, "\n");
1383 return OutputToProcess(pr, message, count, outError);
1386 buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1393 if (appData.debugMode) {
1394 fprintf(debugFP, ">ICS: ");
1395 show_bytes(debugFP, buf, newcount);
1396 fprintf(debugFP, "\n");
1398 outcount = OutputToProcess(pr, buf, newcount, outError);
1399 if (outcount < newcount) return -1; /* to be sure */
1406 } else if (((unsigned char) *p) == TN_IAC) {
1407 *q++ = (char) TN_IAC;
1414 if (appData.debugMode) {
1415 fprintf(debugFP, ">ICS: ");
1416 show_bytes(debugFP, buf, newcount);
1417 fprintf(debugFP, "\n");
1419 outcount = OutputToProcess(pr, buf, newcount, outError);
1420 if (outcount < newcount) return -1; /* to be sure */
1425 read_from_player(isr, closure, message, count, error)
1432 int outError, outCount;
1433 static int gotEof = 0;
1435 /* Pass data read from player on to ICS */
1438 outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1439 if (outCount < count) {
1440 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1442 } else if (count < 0) {
1443 RemoveInputSource(isr);
1444 DisplayFatalError(_("Error reading from keyboard"), error, 1);
1445 } else if (gotEof++ > 0) {
1446 RemoveInputSource(isr);
1447 DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1453 { // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1454 if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1455 connectionAlive = FALSE; // only sticks if no response to 'date' command.
1456 SendToICS("date\n");
1457 if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1460 /* added routine for printf style output to ics */
1461 void ics_printf(char *format, ...)
1463 char buffer[MSG_SIZ];
1466 va_start(args, format);
1467 vsnprintf(buffer, sizeof(buffer), format, args);
1468 buffer[sizeof(buffer)-1] = '\0';
1477 int count, outCount, outError;
1479 if (icsPR == NULL) return;
1482 outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1483 if (outCount < count) {
1484 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1488 /* This is used for sending logon scripts to the ICS. Sending
1489 without a delay causes problems when using timestamp on ICC
1490 (at least on my machine). */
1492 SendToICSDelayed(s,msdelay)
1496 int count, outCount, outError;
1498 if (icsPR == NULL) return;
1501 if (appData.debugMode) {
1502 fprintf(debugFP, ">ICS: ");
1503 show_bytes(debugFP, s, count);
1504 fprintf(debugFP, "\n");
1506 outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1508 if (outCount < count) {
1509 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1514 /* Remove all highlighting escape sequences in s
1515 Also deletes any suffix starting with '('
1518 StripHighlightAndTitle(s)
1521 static char retbuf[MSG_SIZ];
1524 while (*s != NULLCHAR) {
1525 while (*s == '\033') {
1526 while (*s != NULLCHAR && !isalpha(*s)) s++;
1527 if (*s != NULLCHAR) s++;
1529 while (*s != NULLCHAR && *s != '\033') {
1530 if (*s == '(' || *s == '[') {
1541 /* Remove all highlighting escape sequences in s */
1546 static char retbuf[MSG_SIZ];
1549 while (*s != NULLCHAR) {
1550 while (*s == '\033') {
1551 while (*s != NULLCHAR && !isalpha(*s)) s++;
1552 if (*s != NULLCHAR) s++;
1554 while (*s != NULLCHAR && *s != '\033') {
1562 char *variantNames[] = VARIANT_NAMES;
1567 return variantNames[v];
1571 /* Identify a variant from the strings the chess servers use or the
1572 PGN Variant tag names we use. */
1579 VariantClass v = VariantNormal;
1580 int i, found = FALSE;
1585 /* [HGM] skip over optional board-size prefixes */
1586 if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1587 sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1588 while( *e++ != '_');
1591 if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
1595 for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
1596 if (StrCaseStr(e, variantNames[i])) {
1597 v = (VariantClass) i;
1604 if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
1605 || StrCaseStr(e, "wild/fr")
1606 || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
1607 v = VariantFischeRandom;
1608 } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
1609 (i = 1, p = StrCaseStr(e, "w"))) {
1611 while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
1618 case 0: /* FICS only, actually */
1620 /* Castling legal even if K starts on d-file */
1621 v = VariantWildCastle;
1626 /* Castling illegal even if K & R happen to start in
1627 normal positions. */
1628 v = VariantNoCastle;
1641 /* Castling legal iff K & R start in normal positions */
1647 /* Special wilds for position setup; unclear what to do here */
1648 v = VariantLoadable;
1651 /* Bizarre ICC game */
1652 v = VariantTwoKings;
1655 v = VariantKriegspiel;
1661 v = VariantFischeRandom;
1664 v = VariantCrazyhouse;
1667 v = VariantBughouse;
1673 /* Not quite the same as FICS suicide! */
1674 v = VariantGiveaway;
1680 v = VariantShatranj;
1683 /* Temporary names for future ICC types. The name *will* change in
1684 the next xboard/WinBoard release after ICC defines it. */
1722 v = VariantCapablanca;
1725 v = VariantKnightmate;
1731 v = VariantCylinder;
1737 v = VariantCapaRandom;
1740 v = VariantBerolina;
1752 /* Found "wild" or "w" in the string but no number;
1753 must assume it's normal chess. */
1757 sprintf(buf, _("Unknown wild type %d"), wnum);
1758 DisplayError(buf, 0);
1764 if (appData.debugMode) {
1765 fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
1766 e, wnum, VariantName(v));
1771 static int leftover_start = 0, leftover_len = 0;
1772 char star_match[STAR_MATCH_N][MSG_SIZ];
1774 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
1775 advance *index beyond it, and set leftover_start to the new value of
1776 *index; else return FALSE. If pattern contains the character '*', it
1777 matches any sequence of characters not containing '\r', '\n', or the
1778 character following the '*' (if any), and the matched sequence(s) are
1779 copied into star_match.
1782 looking_at(buf, index, pattern)
1787 char *bufp = &buf[*index], *patternp = pattern;
1789 char *matchp = star_match[0];
1792 if (*patternp == NULLCHAR) {
1793 *index = leftover_start = bufp - buf;
1797 if (*bufp == NULLCHAR) return FALSE;
1798 if (*patternp == '*') {
1799 if (*bufp == *(patternp + 1)) {
1801 matchp = star_match[++star_count];
1805 } else if (*bufp == '\n' || *bufp == '\r') {
1807 if (*patternp == NULLCHAR)
1812 *matchp++ = *bufp++;
1816 if (*patternp != *bufp) return FALSE;
1823 SendToPlayer(data, length)
1827 int error, outCount;
1828 outCount = OutputToProcess(NoProc, data, length, &error);
1829 if (outCount < length) {
1830 DisplayFatalError(_("Error writing to display"), error, 1);
1835 PackHolding(packed, holding)
1847 switch (runlength) {
1858 sprintf(q, "%d", runlength);
1870 /* Telnet protocol requests from the front end */
1872 TelnetRequest(ddww, option)
1873 unsigned char ddww, option;
1875 unsigned char msg[3];
1876 int outCount, outError;
1878 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
1880 if (appData.debugMode) {
1881 char buf1[8], buf2[8], *ddwwStr, *optionStr;
1897 sprintf(buf1, "%d", ddww);
1906 sprintf(buf2, "%d", option);
1909 fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
1914 outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
1916 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1923 if (!appData.icsActive) return;
1924 TelnetRequest(TN_DO, TN_ECHO);
1930 if (!appData.icsActive) return;
1931 TelnetRequest(TN_DONT, TN_ECHO);
1935 CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece)
1937 /* put the holdings sent to us by the server on the board holdings area */
1938 int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
1942 if(gameInfo.holdingsWidth < 2) return;
1943 if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
1944 return; // prevent overwriting by pre-board holdings
1946 if( (int)lowestPiece >= BlackPawn ) {
1949 holdingsStartRow = BOARD_HEIGHT-1;
1952 holdingsColumn = BOARD_WIDTH-1;
1953 countsColumn = BOARD_WIDTH-2;
1954 holdingsStartRow = 0;
1958 for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
1959 board[i][holdingsColumn] = EmptySquare;
1960 board[i][countsColumn] = (ChessSquare) 0;
1962 while( (p=*holdings++) != NULLCHAR ) {
1963 piece = CharToPiece( ToUpper(p) );
1964 if(piece == EmptySquare) continue;
1965 /*j = (int) piece - (int) WhitePawn;*/
1966 j = PieceToNumber(piece);
1967 if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
1968 if(j < 0) continue; /* should not happen */
1969 piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
1970 board[holdingsStartRow+j*direction][holdingsColumn] = piece;
1971 board[holdingsStartRow+j*direction][countsColumn]++;
1977 VariantSwitch(Board board, VariantClass newVariant)
1979 int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
1982 startedFromPositionFile = FALSE;
1983 if(gameInfo.variant == newVariant) return;
1985 /* [HGM] This routine is called each time an assignment is made to
1986 * gameInfo.variant during a game, to make sure the board sizes
1987 * are set to match the new variant. If that means adding or deleting
1988 * holdings, we shift the playing board accordingly
1989 * This kludge is needed because in ICS observe mode, we get boards
1990 * of an ongoing game without knowing the variant, and learn about the
1991 * latter only later. This can be because of the move list we requested,
1992 * in which case the game history is refilled from the beginning anyway,
1993 * but also when receiving holdings of a crazyhouse game. In the latter
1994 * case we want to add those holdings to the already received position.
1998 if (appData.debugMode) {
1999 fprintf(debugFP, "Switch board from %s to %s\n",
2000 VariantName(gameInfo.variant), VariantName(newVariant));
2001 setbuf(debugFP, NULL);
2003 shuffleOpenings = 0; /* [HGM] shuffle */
2004 gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2008 newWidth = 9; newHeight = 9;
2009 gameInfo.holdingsSize = 7;
2010 case VariantBughouse:
2011 case VariantCrazyhouse:
2012 newHoldingsWidth = 2; break;
2016 newHoldingsWidth = 2;
2017 gameInfo.holdingsSize = 8;
2020 case VariantCapablanca:
2021 case VariantCapaRandom:
2024 newHoldingsWidth = gameInfo.holdingsSize = 0;
2027 if(newWidth != gameInfo.boardWidth ||
2028 newHeight != gameInfo.boardHeight ||
2029 newHoldingsWidth != gameInfo.holdingsWidth ) {
2031 /* shift position to new playing area, if needed */
2032 if(newHoldingsWidth > gameInfo.holdingsWidth) {
2033 for(i=0; i<BOARD_HEIGHT; i++)
2034 for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2035 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2037 for(i=0; i<newHeight; i++) {
2038 board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2039 board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2041 } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2042 for(i=0; i<BOARD_HEIGHT; i++)
2043 for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2044 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2047 gameInfo.boardWidth = newWidth;
2048 gameInfo.boardHeight = newHeight;
2049 gameInfo.holdingsWidth = newHoldingsWidth;
2050 gameInfo.variant = newVariant;
2051 InitDrawingSizes(-2, 0);
2052 } else gameInfo.variant = newVariant;
2053 CopyBoard(oldBoard, board); // remember correctly formatted board
2054 InitPosition(FALSE); /* this sets up board[0], but also other stuff */
2055 DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2058 static int loggedOn = FALSE;
2060 /*-- Game start info cache: --*/
2062 char gs_kind[MSG_SIZ];
2063 static char player1Name[128] = "";
2064 static char player2Name[128] = "";
2065 static char cont_seq[] = "\n\\ ";
2066 static int player1Rating = -1;
2067 static int player2Rating = -1;
2068 /*----------------------------*/
2070 ColorClass curColor = ColorNormal;
2071 int suppressKibitz = 0;
2074 Boolean soughtPending = FALSE;
2075 Boolean seekGraphUp;
2076 #define MAX_SEEK_ADS 200
2078 char *seekAdList[MAX_SEEK_ADS];
2079 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2080 float tcList[MAX_SEEK_ADS];
2081 char colorList[MAX_SEEK_ADS];
2082 int nrOfSeekAds = 0;
2083 int minRating = 1010, maxRating = 2800;
2084 int hMargin = 10, vMargin = 20, h, w;
2085 extern int squareSize, lineGap;
2090 int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2091 xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2092 if(r < minRating+100 && r >=0 ) r = minRating+100;
2093 if(r > maxRating) r = maxRating;
2094 if(tc < 1.) tc = 1.;
2095 if(tc > 95.) tc = 95.;
2096 x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2097 y = ((double)r - minRating)/(maxRating - minRating)
2098 * (h-vMargin-squareSize/8-1) + vMargin;
2099 if(ratingList[i] < 0) y = vMargin + squareSize/4;
2100 if(strstr(seekAdList[i], " u ")) color = 1;
2101 if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2102 !strstr(seekAdList[i], "bullet") &&
2103 !strstr(seekAdList[i], "blitz") &&
2104 !strstr(seekAdList[i], "standard") ) color = 2;
2105 if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2106 DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2110 AddAd(char *handle, char *rating, int base, int inc, char rated, char *type, int nr, Boolean plot)
2112 char buf[MSG_SIZ], *ext = "";
2113 VariantClass v = StringToVariant(type);
2114 if(strstr(type, "wild")) {
2115 ext = type + 4; // append wild number
2116 if(v == VariantFischeRandom) type = "chess960"; else
2117 if(v == VariantLoadable) type = "setup"; else
2118 type = VariantName(v);
2120 sprintf(buf, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2121 if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2122 if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2123 ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2124 sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2125 tcList[nrOfSeekAds] = base + (2./3.)*inc;
2126 seekNrList[nrOfSeekAds] = nr;
2127 zList[nrOfSeekAds] = 0;
2128 seekAdList[nrOfSeekAds++] = StrSave(buf);
2129 if(plot) PlotSeekAd(nrOfSeekAds-1);
2136 int x = xList[i], y = yList[i], d=squareSize/4, k;
2137 DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2138 if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2139 // now replot every dot that overlapped
2140 for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2141 int xx = xList[k], yy = yList[k];
2142 if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2143 DrawSeekDot(xx, yy, colorList[k]);
2148 RemoveSeekAd(int nr)
2151 for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2153 if(seekAdList[i]) free(seekAdList[i]);
2154 seekAdList[i] = seekAdList[--nrOfSeekAds];
2155 seekNrList[i] = seekNrList[nrOfSeekAds];
2156 ratingList[i] = ratingList[nrOfSeekAds];
2157 colorList[i] = colorList[nrOfSeekAds];
2158 tcList[i] = tcList[nrOfSeekAds];
2159 xList[i] = xList[nrOfSeekAds];
2160 yList[i] = yList[nrOfSeekAds];
2161 zList[i] = zList[nrOfSeekAds];
2162 seekAdList[nrOfSeekAds] = NULL;
2168 MatchSoughtLine(char *line)
2170 char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2171 int nr, base, inc, u=0; char dummy;
2173 if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2174 sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2176 (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2177 sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7) ) {
2178 // match: compact and save the line
2179 AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2188 if(!seekGraphUp) return FALSE;
2190 h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
2191 w = BOARD_WIDTH * (squareSize + lineGap) + lineGap;
2193 DrawSeekBackground(0, 0, w, h);
2194 DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2195 DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2196 for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2197 int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2199 DrawSeekAxis(hMargin+5*(i%500==0), yy, hMargin-5, yy); // rating ticks
2202 sprintf(buf, "%d", i);
2203 DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2206 DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2207 for(i=1; i<100; i+=(i<10?1:5)) {
2208 int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2209 DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2210 if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2212 sprintf(buf, "%d", i);
2213 DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2216 for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2220 int SeekGraphClick(ClickType click, int x, int y, int moving)
2222 static int lastDown = 0, displayed = 0, lastSecond;
2223 if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2224 if(click == Release || moving) return FALSE;
2226 soughtPending = TRUE;
2227 SendToICS(ics_prefix);
2228 SendToICS("sought\n"); // should this be "sought all"?
2229 } else { // issue challenge based on clicked ad
2230 int dist = 10000; int i, closest = 0, second = 0;
2231 for(i=0; i<nrOfSeekAds; i++) {
2232 int d = (x-xList[i])*(x-xList[i]) + (y-yList[i])*(y-yList[i]) + zList[i];
2233 if(d < dist) { dist = d; closest = i; }
2234 second += (d - zList[i] < 120); // count in-range ads
2235 if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2239 second = (second > 1);
2240 if(displayed != closest || second != lastSecond) {
2241 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2242 lastSecond = second; displayed = closest;
2244 if(click == Press) {
2245 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2248 } // on press 'hit', only show info
2249 if(moving == 2) return TRUE; // ignore right up-clicks on dot
2250 sprintf(buf, "play %d\n", seekNrList[closest]);
2251 SendToICS(ics_prefix);
2253 return TRUE; // let incoming board of started game pop down the graph
2254 } else if(click == Release) { // release 'miss' is ignored
2255 zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2256 if(moving == 2) { // right up-click
2257 nrOfSeekAds = 0; // refresh graph
2258 soughtPending = TRUE;
2259 SendToICS(ics_prefix);
2260 SendToICS("sought\n"); // should this be "sought all"?
2263 } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2264 // press miss or release hit 'pop down' seek graph
2265 seekGraphUp = FALSE;
2266 DrawPosition(TRUE, NULL);
2272 read_from_ics(isr, closure, data, count, error)
2279 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2280 #define STARTED_NONE 0
2281 #define STARTED_MOVES 1
2282 #define STARTED_BOARD 2
2283 #define STARTED_OBSERVE 3
2284 #define STARTED_HOLDINGS 4
2285 #define STARTED_CHATTER 5
2286 #define STARTED_COMMENT 6
2287 #define STARTED_MOVES_NOHIDE 7
2289 static int started = STARTED_NONE;
2290 static char parse[20000];
2291 static int parse_pos = 0;
2292 static char buf[BUF_SIZE + 1];
2293 static int firstTime = TRUE, intfSet = FALSE;
2294 static ColorClass prevColor = ColorNormal;
2295 static int savingComment = FALSE;
2296 static int cmatch = 0; // continuation sequence match
2303 int backup; /* [DM] For zippy color lines */
2305 char talker[MSG_SIZ]; // [HGM] chat
2308 connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2310 if (appData.debugMode) {
2312 fprintf(debugFP, "<ICS: ");
2313 show_bytes(debugFP, data, count);
2314 fprintf(debugFP, "\n");
2318 if (appData.debugMode) { int f = forwardMostMove;
2319 fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2320 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2321 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2324 /* If last read ended with a partial line that we couldn't parse,
2325 prepend it to the new read and try again. */
2326 if (leftover_len > 0) {
2327 for (i=0; i<leftover_len; i++)
2328 buf[i] = buf[leftover_start + i];
2331 /* copy new characters into the buffer */
2332 bp = buf + leftover_len;
2333 buf_len=leftover_len;
2334 for (i=0; i<count; i++)
2337 if (data[i] == '\r')
2340 // join lines split by ICS?
2341 if (!appData.noJoin)
2344 Joining just consists of finding matches against the
2345 continuation sequence, and discarding that sequence
2346 if found instead of copying it. So, until a match
2347 fails, there's nothing to do since it might be the
2348 complete sequence, and thus, something we don't want
2351 if (data[i] == cont_seq[cmatch])
2354 if (cmatch == strlen(cont_seq))
2356 cmatch = 0; // complete match. just reset the counter
2359 it's possible for the ICS to not include the space
2360 at the end of the last word, making our [correct]
2361 join operation fuse two separate words. the server
2362 does this when the space occurs at the width setting.
2364 if (!buf_len || buf[buf_len-1] != ' ')
2375 match failed, so we have to copy what matched before
2376 falling through and copying this character. In reality,
2377 this will only ever be just the newline character, but
2378 it doesn't hurt to be precise.
2380 strncpy(bp, cont_seq, cmatch);
2392 buf[buf_len] = NULLCHAR;
2393 // next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2398 while (i < buf_len) {
2399 /* Deal with part of the TELNET option negotiation
2400 protocol. We refuse to do anything beyond the
2401 defaults, except that we allow the WILL ECHO option,
2402 which ICS uses to turn off password echoing when we are
2403 directly connected to it. We reject this option
2404 if localLineEditing mode is on (always on in xboard)
2405 and we are talking to port 23, which might be a real
2406 telnet server that will try to keep WILL ECHO on permanently.
2408 if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2409 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2410 unsigned char option;
2412 switch ((unsigned char) buf[++i]) {
2414 if (appData.debugMode)
2415 fprintf(debugFP, "\n<WILL ");
2416 switch (option = (unsigned char) buf[++i]) {
2418 if (appData.debugMode)
2419 fprintf(debugFP, "ECHO ");
2420 /* Reply only if this is a change, according
2421 to the protocol rules. */
2422 if (remoteEchoOption) break;
2423 if (appData.localLineEditing &&
2424 atoi(appData.icsPort) == TN_PORT) {
2425 TelnetRequest(TN_DONT, TN_ECHO);
2428 TelnetRequest(TN_DO, TN_ECHO);
2429 remoteEchoOption = TRUE;
2433 if (appData.debugMode)
2434 fprintf(debugFP, "%d ", option);
2435 /* Whatever this is, we don't want it. */
2436 TelnetRequest(TN_DONT, option);
2441 if (appData.debugMode)
2442 fprintf(debugFP, "\n<WONT ");
2443 switch (option = (unsigned char) buf[++i]) {
2445 if (appData.debugMode)
2446 fprintf(debugFP, "ECHO ");
2447 /* Reply only if this is a change, according
2448 to the protocol rules. */
2449 if (!remoteEchoOption) break;
2451 TelnetRequest(TN_DONT, TN_ECHO);
2452 remoteEchoOption = FALSE;
2455 if (appData.debugMode)
2456 fprintf(debugFP, "%d ", (unsigned char) option);
2457 /* Whatever this is, it must already be turned
2458 off, because we never agree to turn on
2459 anything non-default, so according to the
2460 protocol rules, we don't reply. */
2465 if (appData.debugMode)
2466 fprintf(debugFP, "\n<DO ");
2467 switch (option = (unsigned char) buf[++i]) {
2469 /* Whatever this is, we refuse to do it. */
2470 if (appData.debugMode)
2471 fprintf(debugFP, "%d ", option);
2472 TelnetRequest(TN_WONT, option);
2477 if (appData.debugMode)
2478 fprintf(debugFP, "\n<DONT ");
2479 switch (option = (unsigned char) buf[++i]) {
2481 if (appData.debugMode)
2482 fprintf(debugFP, "%d ", option);
2483 /* Whatever this is, we are already not doing
2484 it, because we never agree to do anything
2485 non-default, so according to the protocol
2486 rules, we don't reply. */
2491 if (appData.debugMode)
2492 fprintf(debugFP, "\n<IAC ");
2493 /* Doubled IAC; pass it through */
2497 if (appData.debugMode)
2498 fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2499 /* Drop all other telnet commands on the floor */
2502 if (oldi > next_out)
2503 SendToPlayer(&buf[next_out], oldi - next_out);
2509 /* OK, this at least will *usually* work */
2510 if (!loggedOn && looking_at(buf, &i, "ics%")) {
2514 if (loggedOn && !intfSet) {
2515 if (ics_type == ICS_ICC) {
2517 "/set-quietly interface %s\n/set-quietly style 12\n",
2519 if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2520 strcat(str, "/set-2 51 1\n/set seek 1\n");
2521 } else if (ics_type == ICS_CHESSNET) {
2522 sprintf(str, "/style 12\n");
2524 strcpy(str, "alias $ @\n$set interface ");
2525 strcat(str, programVersion);
2526 strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2527 if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2528 strcat(str, "$iset seekremove 1\n$set seek 1\n");
2530 strcat(str, "$iset nohighlight 1\n");
2532 strcat(str, "$iset lock 1\n$style 12\n");
2535 NotifyFrontendLogin();
2539 if (started == STARTED_COMMENT) {
2540 /* Accumulate characters in comment */
2541 parse[parse_pos++] = buf[i];
2542 if (buf[i] == '\n') {
2543 parse[parse_pos] = NULLCHAR;
2544 if(chattingPartner>=0) {
2546 sprintf(mess, "%s%s", talker, parse);
2547 OutputChatMessage(chattingPartner, mess);
2548 chattingPartner = -1;
2549 next_out = i+1; // [HGM] suppress printing in ICS window
2551 if(!suppressKibitz) // [HGM] kibitz
2552 AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
2553 else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2554 int nrDigit = 0, nrAlph = 0, j;
2555 if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2556 { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2557 parse[parse_pos] = NULLCHAR;
2558 // try to be smart: if it does not look like search info, it should go to
2559 // ICS interaction window after all, not to engine-output window.
2560 for(j=0; j<parse_pos; j++) { // count letters and digits
2561 nrDigit += (parse[j] >= '0' && parse[j] <= '9');
2562 nrAlph += (parse[j] >= 'a' && parse[j] <= 'z');
2563 nrAlph += (parse[j] >= 'A' && parse[j] <= 'Z');
2565 if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2566 int depth=0; float score;
2567 if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2568 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2569 pvInfoList[forwardMostMove-1].depth = depth;
2570 pvInfoList[forwardMostMove-1].score = 100*score;
2572 OutputKibitz(suppressKibitz, parse);
2575 sprintf(tmp, _("your opponent kibitzes: %s"), parse);
2576 SendToPlayer(tmp, strlen(tmp));
2578 next_out = i+1; // [HGM] suppress printing in ICS window
2580 started = STARTED_NONE;
2582 /* Don't match patterns against characters in comment */
2587 if (started == STARTED_CHATTER) {
2588 if (buf[i] != '\n') {
2589 /* Don't match patterns against characters in chatter */
2593 started = STARTED_NONE;
2594 if(suppressKibitz) next_out = i+1;
2597 /* Kludge to deal with rcmd protocol */
2598 if (firstTime && looking_at(buf, &i, "\001*")) {
2599 DisplayFatalError(&buf[1], 0, 1);
2605 if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2608 if (appData.debugMode)
2609 fprintf(debugFP, "ics_type %d\n", ics_type);
2612 if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
2613 ics_type = ICS_FICS;
2615 if (appData.debugMode)
2616 fprintf(debugFP, "ics_type %d\n", ics_type);
2619 if (!loggedOn && looking_at(buf, &i, "chess.net")) {
2620 ics_type = ICS_CHESSNET;
2622 if (appData.debugMode)
2623 fprintf(debugFP, "ics_type %d\n", ics_type);
2628 (looking_at(buf, &i, "\"*\" is *a registered name") ||
2629 looking_at(buf, &i, "Logging you in as \"*\"") ||
2630 looking_at(buf, &i, "will be \"*\""))) {
2631 strcpy(ics_handle, star_match[0]);
2635 if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
2637 snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
2638 DisplayIcsInteractionTitle(buf);
2639 have_set_title = TRUE;
2642 /* skip finger notes */
2643 if (started == STARTED_NONE &&
2644 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
2645 (buf[i] == '1' && buf[i+1] == '0')) &&
2646 buf[i+2] == ':' && buf[i+3] == ' ') {
2647 started = STARTED_CHATTER;
2653 // [HGM] seekgraph: recognize sought lines and end-of-sought message
2654 if(appData.seekGraph) {
2655 if(soughtPending && MatchSoughtLine(buf+i)) {
2656 i = strstr(buf+i, "rated") - buf;
2657 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2658 next_out = leftover_start = i;
2659 started = STARTED_CHATTER;
2660 suppressKibitz = TRUE;
2663 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
2664 && looking_at(buf, &i, "* ads displayed")) {
2665 soughtPending = FALSE;
2670 if(appData.autoRefresh) {
2671 if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
2672 int s = (ics_type == ICS_ICC); // ICC format differs
2674 AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
2675 star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
2676 looking_at(buf, &i, "*% "); // eat prompt
2677 if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
2678 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2679 next_out = i; // suppress
2682 if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
2683 char *p = star_match[0];
2685 if(seekGraphUp) RemoveSeekAd(atoi(p));
2686 while(*p && *p++ != ' '); // next
2688 looking_at(buf, &i, "*% "); // eat prompt
2689 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2696 /* skip formula vars */
2697 if (started == STARTED_NONE &&
2698 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
2699 started = STARTED_CHATTER;
2704 // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
2705 if (appData.autoKibitz && started == STARTED_NONE &&
2706 !appData.icsEngineAnalyze && // [HGM] [DM] ICS analyze
2707 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
2708 if(looking_at(buf, &i, "* kibitzes: ") &&
2709 (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
2710 StrStr(star_match[0], gameInfo.black) == star_match[0] )) { // kibitz of self or opponent
2711 suppressKibitz = TRUE;
2712 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2714 if((StrStr(star_match[0], gameInfo.white) == star_match[0]
2715 && (gameMode == IcsPlayingWhite)) ||
2716 (StrStr(star_match[0], gameInfo.black) == star_match[0]
2717 && (gameMode == IcsPlayingBlack)) ) // opponent kibitz
2718 started = STARTED_CHATTER; // own kibitz we simply discard
2720 started = STARTED_COMMENT; // make sure it will be collected in parse[]
2721 parse_pos = 0; parse[0] = NULLCHAR;
2722 savingComment = TRUE;
2723 suppressKibitz = gameMode != IcsObserving ? 2 :
2724 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
2728 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
2729 looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
2730 && atoi(star_match[0])) {
2731 // suppress the acknowledgements of our own autoKibitz
2733 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2734 if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
2735 SendToPlayer(star_match[0], strlen(star_match[0]));
2736 if(looking_at(buf, &i, "*% ")) // eat prompt
2737 suppressKibitz = FALSE;
2741 } // [HGM] kibitz: end of patch
2743 // [HGM] chat: intercept tells by users for which we have an open chat window
2745 if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
2746 looking_at(buf, &i, "* whispers:") ||
2747 looking_at(buf, &i, "* shouts:") ||
2748 looking_at(buf, &i, "* c-shouts:") ||
2749 looking_at(buf, &i, "--> * ") ||
2750 looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
2751 looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
2752 looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
2753 looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
2755 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
2756 chattingPartner = -1;
2758 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
2759 for(p=0; p<MAX_CHAT; p++) {
2760 if(channel == atoi(chatPartner[p])) {
2761 talker[0] = '['; strcat(talker, "] ");
2762 Colorize(channel == 1 ? ColorChannel1 : ColorChannel, FALSE);
2763 chattingPartner = p; break;
2766 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
2767 for(p=0; p<MAX_CHAT; p++) {
2768 if(!strcmp("whispers", chatPartner[p])) {
2769 talker[0] = '['; strcat(talker, "] ");
2770 chattingPartner = p; break;
2773 if(buf[i-3] == 't' || buf[oldi+2] == '>') // shout, c-shout or it; look if there is a 'shouts' chatbox
2774 for(p=0; p<MAX_CHAT; p++) {
2775 if(!strcmp("shouts", chatPartner[p])) {
2776 if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
2777 else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
2778 else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
2779 chattingPartner = p; break;
2782 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
2783 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
2784 talker[0] = 0; Colorize(ColorTell, FALSE);
2785 chattingPartner = p; break;
2787 if(chattingPartner<0) i = oldi; else {
2788 Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
2789 if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
2790 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2791 started = STARTED_COMMENT;
2792 parse_pos = 0; parse[0] = NULLCHAR;
2793 savingComment = 3 + chattingPartner; // counts as TRUE
2794 suppressKibitz = TRUE;
2797 } // [HGM] chat: end of patch
2799 if (appData.zippyTalk || appData.zippyPlay) {
2800 /* [DM] Backup address for color zippy lines */
2804 if (loggedOn == TRUE)
2805 if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
2806 (appData.zippyPlay && ZippyMatch(buf, &backup)));
2808 if (ZippyControl(buf, &i) ||
2809 ZippyConverse(buf, &i) ||
2810 (appData.zippyPlay && ZippyMatch(buf, &i))) {
2812 if (!appData.colorize) continue;
2816 } // [DM] 'else { ' deleted
2818 /* Regular tells and says */
2819 (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
2820 looking_at(buf, &i, "* (your partner) tells you: ") ||
2821 looking_at(buf, &i, "* says: ") ||
2822 /* Don't color "message" or "messages" output */
2823 (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
2824 looking_at(buf, &i, "*. * at *:*: ") ||
2825 looking_at(buf, &i, "--* (*:*): ") ||
2826 /* Message notifications (same color as tells) */
2827 looking_at(buf, &i, "* has left a message ") ||
2828 looking_at(buf, &i, "* just sent you a message:\n") ||
2829 /* Whispers and kibitzes */
2830 (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
2831 looking_at(buf, &i, "* kibitzes: ") ||
2833 (tkind = 3, looking_at(buf, &i, "*(*: "))) {
2835 if (tkind == 1 && strchr(star_match[0], ':')) {
2836 /* Avoid "tells you:" spoofs in channels */
2839 if (star_match[0][0] == NULLCHAR ||
2840 strchr(star_match[0], ' ') ||
2841 (tkind == 3 && strchr(star_match[1], ' '))) {
2842 /* Reject bogus matches */
2845 if (appData.colorize) {
2846 if (oldi > next_out) {
2847 SendToPlayer(&buf[next_out], oldi - next_out);
2852 Colorize(ColorTell, FALSE);
2853 curColor = ColorTell;
2856 Colorize(ColorKibitz, FALSE);
2857 curColor = ColorKibitz;
2860 p = strrchr(star_match[1], '(');
2867 Colorize(ColorChannel1, FALSE);
2868 curColor = ColorChannel1;
2870 Colorize(ColorChannel, FALSE);
2871 curColor = ColorChannel;
2875 curColor = ColorNormal;
2879 if (started == STARTED_NONE && appData.autoComment &&
2880 (gameMode == IcsObserving ||
2881 gameMode == IcsPlayingWhite ||
2882 gameMode == IcsPlayingBlack)) {
2883 parse_pos = i - oldi;
2884 memcpy(parse, &buf[oldi], parse_pos);
2885 parse[parse_pos] = NULLCHAR;
2886 started = STARTED_COMMENT;
2887 savingComment = TRUE;
2889 started = STARTED_CHATTER;
2890 savingComment = FALSE;
2897 if (looking_at(buf, &i, "* s-shouts: ") ||
2898 looking_at(buf, &i, "* c-shouts: ")) {
2899 if (appData.colorize) {
2900 if (oldi > next_out) {
2901 SendToPlayer(&buf[next_out], oldi - next_out);
2904 Colorize(ColorSShout, FALSE);
2905 curColor = ColorSShout;
2908 started = STARTED_CHATTER;
2912 if (looking_at(buf, &i, "--->")) {
2917 if (looking_at(buf, &i, "* shouts: ") ||
2918 looking_at(buf, &i, "--> ")) {
2919 if (appData.colorize) {
2920 if (oldi > next_out) {
2921 SendToPlayer(&buf[next_out], oldi - next_out);
2924 Colorize(ColorShout, FALSE);
2925 curColor = ColorShout;
2928 started = STARTED_CHATTER;
2932 if (looking_at( buf, &i, "Challenge:")) {
2933 if (appData.colorize) {
2934 if (oldi > next_out) {
2935 SendToPlayer(&buf[next_out], oldi - next_out);
2938 Colorize(ColorChallenge, FALSE);
2939 curColor = ColorChallenge;
2945 if (looking_at(buf, &i, "* offers you") ||
2946 looking_at(buf, &i, "* offers to be") ||
2947 looking_at(buf, &i, "* would like to") ||
2948 looking_at(buf, &i, "* requests to") ||
2949 looking_at(buf, &i, "Your opponent offers") ||
2950 looking_at(buf, &i, "Your opponent requests")) {
2952 if (appData.colorize) {
2953 if (oldi > next_out) {
2954 SendToPlayer(&buf[next_out], oldi - next_out);
2957 Colorize(ColorRequest, FALSE);
2958 curColor = ColorRequest;
2963 if (looking_at(buf, &i, "* (*) seeking")) {
2964 if (appData.colorize) {
2965 if (oldi > next_out) {
2966 SendToPlayer(&buf[next_out], oldi - next_out);
2969 Colorize(ColorSeek, FALSE);
2970 curColor = ColorSeek;
2975 if (looking_at(buf, &i, "\\ ")) {
2976 if (prevColor != ColorNormal) {
2977 if (oldi > next_out) {
2978 SendToPlayer(&buf[next_out], oldi - next_out);
2981 Colorize(prevColor, TRUE);
2982 curColor = prevColor;
2984 if (savingComment) {
2985 parse_pos = i - oldi;
2986 memcpy(parse, &buf[oldi], parse_pos);
2987 parse[parse_pos] = NULLCHAR;
2988 started = STARTED_COMMENT;
2989 if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
2990 chattingPartner = savingComment - 3; // kludge to remember the box
2992 started = STARTED_CHATTER;
2997 if (looking_at(buf, &i, "Black Strength :") ||
2998 looking_at(buf, &i, "<<< style 10 board >>>") ||
2999 looking_at(buf, &i, "<10>") ||
3000 looking_at(buf, &i, "#@#")) {
3001 /* Wrong board style */
3003 SendToICS(ics_prefix);
3004 SendToICS("set style 12\n");
3005 SendToICS(ics_prefix);
3006 SendToICS("refresh\n");
3010 if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
3012 have_sent_ICS_logon = 1;
3016 if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3017 (looking_at(buf, &i, "\n<12> ") ||
3018 looking_at(buf, &i, "<12> "))) {
3020 if (oldi > next_out) {
3021 SendToPlayer(&buf[next_out], oldi - next_out);
3024 started = STARTED_BOARD;
3029 if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3030 looking_at(buf, &i, "<b1> ")) {
3031 if (oldi > next_out) {
3032 SendToPlayer(&buf[next_out], oldi - next_out);
3035 started = STARTED_HOLDINGS;
3040 if (looking_at(buf, &i, "* *vs. * *--- *")) {
3042 /* Header for a move list -- first line */
3044 switch (ics_getting_history) {
3048 case BeginningOfGame:
3049 /* User typed "moves" or "oldmoves" while we
3050 were idle. Pretend we asked for these
3051 moves and soak them up so user can step
3052 through them and/or save them.
3055 gameMode = IcsObserving;
3058 ics_getting_history = H_GOT_UNREQ_HEADER;
3060 case EditGame: /*?*/
3061 case EditPosition: /*?*/
3062 /* Should above feature work in these modes too? */
3063 /* For now it doesn't */
3064 ics_getting_history = H_GOT_UNWANTED_HEADER;
3067 ics_getting_history = H_GOT_UNWANTED_HEADER;
3072 /* Is this the right one? */
3073 if (gameInfo.white && gameInfo.black &&
3074 strcmp(gameInfo.white, star_match[0]) == 0 &&
3075 strcmp(gameInfo.black, star_match[2]) == 0) {
3077 ics_getting_history = H_GOT_REQ_HEADER;
3080 case H_GOT_REQ_HEADER:
3081 case H_GOT_UNREQ_HEADER:
3082 case H_GOT_UNWANTED_HEADER:
3083 case H_GETTING_MOVES:
3084 /* Should not happen */
3085 DisplayError(_("Error gathering move list: two headers"), 0);
3086 ics_getting_history = H_FALSE;
3090 /* Save player ratings into gameInfo if needed */
3091 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3092 ics_getting_history == H_GOT_UNREQ_HEADER) &&
3093 (gameInfo.whiteRating == -1 ||
3094 gameInfo.blackRating == -1)) {
3096 gameInfo.whiteRating = string_to_rating(star_match[1]);
3097 gameInfo.blackRating = string_to_rating(star_match[3]);
3098 if (appData.debugMode)
3099 fprintf(debugFP, _("Ratings from header: W %d, B %d\n"),
3100 gameInfo.whiteRating, gameInfo.blackRating);
3105 if (looking_at(buf, &i,
3106 "* * match, initial time: * minute*, increment: * second")) {
3107 /* Header for a move list -- second line */
3108 /* Initial board will follow if this is a wild game */
3109 if (gameInfo.event != NULL) free(gameInfo.event);
3110 sprintf(str, "ICS %s %s match", star_match[0], star_match[1]);
3111 gameInfo.event = StrSave(str);
3112 /* [HGM] we switched variant. Translate boards if needed. */
3113 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3117 if (looking_at(buf, &i, "Move ")) {
3118 /* Beginning of a move list */
3119 switch (ics_getting_history) {
3121 /* Normally should not happen */
3122 /* Maybe user hit reset while we were parsing */
3125 /* Happens if we are ignoring a move list that is not
3126 * the one we just requested. Common if the user
3127 * tries to observe two games without turning off
3130 case H_GETTING_MOVES:
3131 /* Should not happen */
3132 DisplayError(_("Error gathering move list: nested"), 0);
3133 ics_getting_history = H_FALSE;
3135 case H_GOT_REQ_HEADER:
3136 ics_getting_history = H_GETTING_MOVES;
3137 started = STARTED_MOVES;
3139 if (oldi > next_out) {
3140 SendToPlayer(&buf[next_out], oldi - next_out);
3143 case H_GOT_UNREQ_HEADER:
3144 ics_getting_history = H_GETTING_MOVES;
3145 started = STARTED_MOVES_NOHIDE;
3148 case H_GOT_UNWANTED_HEADER:
3149 ics_getting_history = H_FALSE;
3155 if (looking_at(buf, &i, "% ") ||
3156 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3157 && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3158 if(ics_type == ICS_ICC && soughtPending) { // [HGM] seekgraph: on ICC sought-list has no termination line
3159 soughtPending = FALSE;
3163 if(suppressKibitz) next_out = i;
3164 savingComment = FALSE;
3168 case STARTED_MOVES_NOHIDE:
3169 memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3170 parse[parse_pos + i - oldi] = NULLCHAR;
3171 ParseGameHistory(parse);
3173 if (appData.zippyPlay && first.initDone) {
3174 FeedMovesToProgram(&first, forwardMostMove);
3175 if (gameMode == IcsPlayingWhite) {
3176 if (WhiteOnMove(forwardMostMove)) {
3177 if (first.sendTime) {
3178 if (first.useColors) {
3179 SendToProgram("black\n", &first);
3181 SendTimeRemaining(&first, TRUE);
3183 if (first.useColors) {
3184 SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3186 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3187 first.maybeThinking = TRUE;
3189 if (first.usePlayother) {
3190 if (first.sendTime) {
3191 SendTimeRemaining(&first, TRUE);
3193 SendToProgram("playother\n", &first);
3199 } else if (gameMode == IcsPlayingBlack) {
3200 if (!WhiteOnMove(forwardMostMove)) {
3201 if (first.sendTime) {
3202 if (first.useColors) {
3203 SendToProgram("white\n", &first);
3205 SendTimeRemaining(&first, FALSE);
3207 if (first.useColors) {
3208 SendToProgram("black\n", &first);
3210 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3211 first.maybeThinking = TRUE;
3213 if (first.usePlayother) {
3214 if (first.sendTime) {
3215 SendTimeRemaining(&first, FALSE);
3217 SendToProgram("playother\n", &first);
3226 if (gameMode == IcsObserving && ics_gamenum == -1) {
3227 /* Moves came from oldmoves or moves command
3228 while we weren't doing anything else.
3230 currentMove = forwardMostMove;
3231 ClearHighlights();/*!!could figure this out*/
3232 flipView = appData.flipView;
3233 DrawPosition(TRUE, boards[currentMove]);
3234 DisplayBothClocks();
3235 sprintf(str, "%s vs. %s",
3236 gameInfo.white, gameInfo.black);
3240 /* Moves were history of an active game */
3241 if (gameInfo.resultDetails != NULL) {
3242 free(gameInfo.resultDetails);
3243 gameInfo.resultDetails = NULL;
3246 HistorySet(parseList, backwardMostMove,
3247 forwardMostMove, currentMove-1);
3248 DisplayMove(currentMove - 1);
3249 if (started == STARTED_MOVES) next_out = i;
3250 started = STARTED_NONE;
3251 ics_getting_history = H_FALSE;
3254 case STARTED_OBSERVE:
3255 started = STARTED_NONE;
3256 SendToICS(ics_prefix);
3257 SendToICS("refresh\n");
3263 if(bookHit) { // [HGM] book: simulate book reply
3264 static char bookMove[MSG_SIZ]; // a bit generous?
3266 programStats.nodes = programStats.depth = programStats.time =
3267 programStats.score = programStats.got_only_move = 0;
3268 sprintf(programStats.movelist, "%s (xbook)", bookHit);
3270 strcpy(bookMove, "move ");
3271 strcat(bookMove, bookHit);
3272 HandleMachineMove(bookMove, &first);
3277 if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3278 started == STARTED_HOLDINGS ||
3279 started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3280 /* Accumulate characters in move list or board */
3281 parse[parse_pos++] = buf[i];
3284 /* Start of game messages. Mostly we detect start of game
3285 when the first board image arrives. On some versions
3286 of the ICS, though, we need to do a "refresh" after starting
3287 to observe in order to get the current board right away. */
3288 if (looking_at(buf, &i, "Adding game * to observation list")) {
3289 started = STARTED_OBSERVE;
3293 /* Handle auto-observe */
3294 if (appData.autoObserve &&
3295 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3296 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3298 /* Choose the player that was highlighted, if any. */
3299 if (star_match[0][0] == '\033' ||
3300 star_match[1][0] != '\033') {
3301 player = star_match[0];
3303 player = star_match[2];
3305 sprintf(str, "%sobserve %s\n",
3306 ics_prefix, StripHighlightAndTitle(player));
3309 /* Save ratings from notify string */
3310 strcpy(player1Name, star_match[0]);
3311 player1Rating = string_to_rating(star_match[1]);
3312 strcpy(player2Name, star_match[2]);
3313 player2Rating = string_to_rating(star_match[3]);
3315 if (appData.debugMode)
3317 "Ratings from 'Game notification:' %s %d, %s %d\n",
3318 player1Name, player1Rating,
3319 player2Name, player2Rating);
3324 /* Deal with automatic examine mode after a game,
3325 and with IcsObserving -> IcsExamining transition */
3326 if (looking_at(buf, &i, "Entering examine mode for game *") ||
3327 looking_at(buf, &i, "has made you an examiner of game *")) {
3329 int gamenum = atoi(star_match[0]);
3330 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3331 gamenum == ics_gamenum) {
3332 /* We were already playing or observing this game;
3333 no need to refetch history */
3334 gameMode = IcsExamining;
3336 pauseExamForwardMostMove = forwardMostMove;
3337 } else if (currentMove < forwardMostMove) {
3338 ForwardInner(forwardMostMove);
3341 /* I don't think this case really can happen */
3342 SendToICS(ics_prefix);
3343 SendToICS("refresh\n");
3348 /* Error messages */
3349 // if (ics_user_moved) {
3350 if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3351 if (looking_at(buf, &i, "Illegal move") ||
3352 looking_at(buf, &i, "Not a legal move") ||
3353 looking_at(buf, &i, "Your king is in check") ||
3354 looking_at(buf, &i, "It isn't your turn") ||
3355 looking_at(buf, &i, "It is not your move")) {
3357 if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3358 currentMove = forwardMostMove-1;
3359 DisplayMove(currentMove - 1); /* before DMError */
3360 DrawPosition(FALSE, boards[currentMove]);
3361 SwitchClocks(forwardMostMove-1); // [HGM] race
3362 DisplayBothClocks();
3364 DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3370 if (looking_at(buf, &i, "still have time") ||
3371 looking_at(buf, &i, "not out of time") ||
3372 looking_at(buf, &i, "either player is out of time") ||
3373 looking_at(buf, &i, "has timeseal; checking")) {
3374 /* We must have called his flag a little too soon */
3375 whiteFlag = blackFlag = FALSE;
3379 if (looking_at(buf, &i, "added * seconds to") ||
3380 looking_at(buf, &i, "seconds were added to")) {
3381 /* Update the clocks */
3382 SendToICS(ics_prefix);
3383 SendToICS("refresh\n");
3387 if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3388 ics_clock_paused = TRUE;
3393 if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3394 ics_clock_paused = FALSE;
3399 /* Grab player ratings from the Creating: message.
3400 Note we have to check for the special case when
3401 the ICS inserts things like [white] or [black]. */
3402 if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3403 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3405 0 player 1 name (not necessarily white)
3407 2 empty, white, or black (IGNORED)
3408 3 player 2 name (not necessarily black)
3411 The names/ratings are sorted out when the game
3412 actually starts (below).
3414 strcpy(player1Name, StripHighlightAndTitle(star_match[0]));
3415 player1Rating = string_to_rating(star_match[1]);
3416 strcpy(player2Name, StripHighlightAndTitle(star_match[3]));
3417 player2Rating = string_to_rating(star_match[4]);
3419 if (appData.debugMode)
3421 "Ratings from 'Creating:' %s %d, %s %d\n",
3422 player1Name, player1Rating,
3423 player2Name, player2Rating);
3428 /* Improved generic start/end-of-game messages */
3429 if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3430 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3431 /* If tkind == 0: */
3432 /* star_match[0] is the game number */
3433 /* [1] is the white player's name */
3434 /* [2] is the black player's name */
3435 /* For end-of-game: */
3436 /* [3] is the reason for the game end */
3437 /* [4] is a PGN end game-token, preceded by " " */
3438 /* For start-of-game: */
3439 /* [3] begins with "Creating" or "Continuing" */
3440 /* [4] is " *" or empty (don't care). */
3441 int gamenum = atoi(star_match[0]);
3442 char *whitename, *blackname, *why, *endtoken;
3443 ChessMove endtype = (ChessMove) 0;
3446 whitename = star_match[1];
3447 blackname = star_match[2];
3448 why = star_match[3];
3449 endtoken = star_match[4];
3451 whitename = star_match[1];
3452 blackname = star_match[3];
3453 why = star_match[5];
3454 endtoken = star_match[6];
3457 /* Game start messages */
3458 if (strncmp(why, "Creating ", 9) == 0 ||
3459 strncmp(why, "Continuing ", 11) == 0) {
3460 gs_gamenum = gamenum;
3461 strcpy(gs_kind, strchr(why, ' ') + 1);
3462 VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
3464 if (appData.zippyPlay) {
3465 ZippyGameStart(whitename, blackname);
3468 partnerBoardValid = FALSE; // [HGM] bughouse
3472 /* Game end messages */
3473 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3474 ics_gamenum != gamenum) {
3477 while (endtoken[0] == ' ') endtoken++;
3478 switch (endtoken[0]) {
3481 endtype = GameUnfinished;
3484 endtype = BlackWins;
3487 if (endtoken[1] == '/')
3488 endtype = GameIsDrawn;
3490 endtype = WhiteWins;
3493 GameEnds(endtype, why, GE_ICS);
3495 if (appData.zippyPlay && first.initDone) {
3496 ZippyGameEnd(endtype, why);
3497 if (first.pr == NULL) {
3498 /* Start the next process early so that we'll
3499 be ready for the next challenge */
3500 StartChessProgram(&first);
3502 /* Send "new" early, in case this command takes
3503 a long time to finish, so that we'll be ready
3504 for the next challenge. */
3505 gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3509 if(appData.bgObserve && partnerBoardValid) DrawPosition(TRUE, partnerBoard);
3513 if (looking_at(buf, &i, "Removing game * from observation") ||
3514 looking_at(buf, &i, "no longer observing game *") ||
3515 looking_at(buf, &i, "Game * (*) has no examiners")) {
3516 if (gameMode == IcsObserving &&
3517 atoi(star_match[0]) == ics_gamenum)
3519 /* icsEngineAnalyze */
3520 if (appData.icsEngineAnalyze) {
3527 ics_user_moved = FALSE;
3532 if (looking_at(buf, &i, "no longer examining game *")) {
3533 if (gameMode == IcsExamining &&
3534 atoi(star_match[0]) == ics_gamenum)
3538 ics_user_moved = FALSE;
3543 /* Advance leftover_start past any newlines we find,
3544 so only partial lines can get reparsed */
3545 if (looking_at(buf, &i, "\n")) {
3546 prevColor = curColor;
3547 if (curColor != ColorNormal) {
3548 if (oldi > next_out) {
3549 SendToPlayer(&buf[next_out], oldi - next_out);
3552 Colorize(ColorNormal, FALSE);
3553 curColor = ColorNormal;
3555 if (started == STARTED_BOARD) {
3556 started = STARTED_NONE;
3557 parse[parse_pos] = NULLCHAR;
3558 ParseBoard12(parse);
3561 /* Send premove here */
3562 if (appData.premove) {
3564 if (currentMove == 0 &&
3565 gameMode == IcsPlayingWhite &&
3566 appData.premoveWhite) {
3567 sprintf(str, "%s\n", appData.premoveWhiteText);
3568 if (appData.debugMode)
3569 fprintf(debugFP, "Sending premove:\n");
3571 } else if (currentMove == 1 &&
3572 gameMode == IcsPlayingBlack &&
3573 appData.premoveBlack) {
3574 sprintf(str, "%s\n", appData.premoveBlackText);
3575 if (appData.debugMode)
3576 fprintf(debugFP, "Sending premove:\n");
3578 } else if (gotPremove) {
3580 ClearPremoveHighlights();
3581 if (appData.debugMode)
3582 fprintf(debugFP, "Sending premove:\n");
3583 UserMoveEvent(premoveFromX, premoveFromY,
3584 premoveToX, premoveToY,
3589 /* Usually suppress following prompt */
3590 if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
3591 while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
3592 if (looking_at(buf, &i, "*% ")) {
3593 savingComment = FALSE;
3598 } else if (started == STARTED_HOLDINGS) {
3600 char new_piece[MSG_SIZ];
3601 started = STARTED_NONE;
3602 parse[parse_pos] = NULLCHAR;
3603 if (appData.debugMode)
3604 fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
3605 parse, currentMove);
3606 if (sscanf(parse, " game %d", &gamenum) == 1) {
3607 if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game
3608 if (gameInfo.variant == VariantNormal) {
3609 /* [HGM] We seem to switch variant during a game!
3610 * Presumably no holdings were displayed, so we have
3611 * to move the position two files to the right to
3612 * create room for them!
3614 VariantClass newVariant;
3615 switch(gameInfo.boardWidth) { // base guess on board width
3616 case 9: newVariant = VariantShogi; break;
3617 case 10: newVariant = VariantGreat; break;
3618 default: newVariant = VariantCrazyhouse; break;
3620 VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3621 /* Get a move list just to see the header, which
3622 will tell us whether this is really bug or zh */
3623 if (ics_getting_history == H_FALSE) {
3624 ics_getting_history = H_REQUESTED;
3625 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3629 new_piece[0] = NULLCHAR;
3630 sscanf(parse, "game %d white [%s black [%s <- %s",
3631 &gamenum, white_holding, black_holding,
3633 white_holding[strlen(white_holding)-1] = NULLCHAR;
3634 black_holding[strlen(black_holding)-1] = NULLCHAR;
3635 /* [HGM] copy holdings to board holdings area */
3636 CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
3637 CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
3638 boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
3640 if (appData.zippyPlay && first.initDone) {
3641 ZippyHoldings(white_holding, black_holding,
3645 if (tinyLayout || smallLayout) {
3646 char wh[16], bh[16];
3647 PackHolding(wh, white_holding);
3648 PackHolding(bh, black_holding);
3649 sprintf(str, "[%s-%s] %s-%s", wh, bh,
3650 gameInfo.white, gameInfo.black);
3652 sprintf(str, "%s [%s] vs. %s [%s]",
3653 gameInfo.white, white_holding,
3654 gameInfo.black, black_holding);
3656 if(!partnerUp) // [HGM] bughouse: when peeking at partner game we already know what he captured...
3657 DrawPosition(FALSE, boards[currentMove]);
3659 } else if(appData.bgObserve) { // [HGM] bughouse: holdings of other game => background
3660 sscanf(parse, "game %d white [%s black [%s <- %s",
3661 &gamenum, white_holding, black_holding,
3663 white_holding[strlen(white_holding)-1] = NULLCHAR;
3664 black_holding[strlen(black_holding)-1] = NULLCHAR;
3665 /* [HGM] copy holdings to partner-board holdings area */
3666 CopyHoldings(partnerBoard, white_holding, WhitePawn);
3667 CopyHoldings(partnerBoard, black_holding, BlackPawn);
3668 if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual: always draw
3669 if(partnerUp) DrawPosition(FALSE, partnerBoard);
3670 if(twoBoards) { partnerUp = 0; flipView = !flipView; }
3673 /* Suppress following prompt */
3674 if (looking_at(buf, &i, "*% ")) {
3675 if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
3676 savingComment = FALSE;
3684 i++; /* skip unparsed character and loop back */
3687 if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
3688 // started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
3689 // SendToPlayer(&buf[next_out], i - next_out);
3690 started != STARTED_HOLDINGS && leftover_start > next_out) {
3691 SendToPlayer(&buf[next_out], leftover_start - next_out);
3695 leftover_len = buf_len - leftover_start;
3696 /* if buffer ends with something we couldn't parse,
3697 reparse it after appending the next read */
3699 } else if (count == 0) {
3700 RemoveInputSource(isr);
3701 DisplayFatalError(_("Connection closed by ICS"), 0, 0);
3703 DisplayFatalError(_("Error reading from ICS"), error, 1);
3708 /* Board style 12 looks like this:
3710 <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
3712 * The "<12> " is stripped before it gets to this routine. The two
3713 * trailing 0's (flip state and clock ticking) are later addition, and
3714 * some chess servers may not have them, or may have only the first.
3715 * Additional trailing fields may be added in the future.
3718 #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"
3720 #define RELATION_OBSERVING_PLAYED 0
3721 #define RELATION_OBSERVING_STATIC -2 /* examined, oldmoves, or smoves */
3722 #define RELATION_PLAYING_MYMOVE 1
3723 #define RELATION_PLAYING_NOTMYMOVE -1
3724 #define RELATION_EXAMINING 2
3725 #define RELATION_ISOLATED_BOARD -3
3726 #define RELATION_STARTING_POSITION -4 /* FICS only */
3729 ParseBoard12(string)
3732 GameMode newGameMode;
3733 int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
3734 int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
3735 int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
3736 char to_play, board_chars[200];
3737 char move_str[500], str[500], elapsed_time[500];
3738 char black[32], white[32];
3740 int prevMove = currentMove;
3743 int fromX, fromY, toX, toY;
3745 int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
3746 char *bookHit = NULL; // [HGM] book
3747 Boolean weird = FALSE, reqFlag = FALSE, repaint = FALSE;
3749 fromX = fromY = toX = toY = -1;
3753 if (appData.debugMode)
3754 fprintf(debugFP, _("Parsing board: %s\n"), string);
3756 move_str[0] = NULLCHAR;
3757 elapsed_time[0] = NULLCHAR;
3758 { /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
3760 while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
3761 if(string[i] == ' ') { ranks++; files = 0; }
3763 if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
3766 for(j = 0; j <i; j++) board_chars[j] = string[j];
3767 board_chars[i] = '\0';
3770 n = sscanf(string, PATTERN, &to_play, &double_push,
3771 &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
3772 &gamenum, white, black, &relation, &basetime, &increment,
3773 &white_stren, &black_stren, &white_time, &black_time,
3774 &moveNum, str, elapsed_time, move_str, &ics_flip,
3778 snprintf(str, sizeof(str), _("Failed to parse board string:\n\"%s\""), string);
3779 DisplayError(str, 0);
3783 /* Convert the move number to internal form */
3784 moveNum = (moveNum - 1) * 2;
3785 if (to_play == 'B') moveNum++;
3786 if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
3787 DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
3793 case RELATION_OBSERVING_PLAYED:
3794 case RELATION_OBSERVING_STATIC:
3795 if (gamenum == -1) {
3796 /* Old ICC buglet */
3797 relation = RELATION_OBSERVING_STATIC;
3799 newGameMode = IcsObserving;
3801 case RELATION_PLAYING_MYMOVE:
3802 case RELATION_PLAYING_NOTMYMOVE:
3804 ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
3805 IcsPlayingWhite : IcsPlayingBlack;
3807 case RELATION_EXAMINING:
3808 newGameMode = IcsExamining;
3810 case RELATION_ISOLATED_BOARD:
3812 /* Just display this board. If user was doing something else,
3813 we will forget about it until the next board comes. */
3814 newGameMode = IcsIdle;
3816 case RELATION_STARTING_POSITION:
3817 newGameMode = gameMode;
3821 if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
3822 && newGameMode == IcsObserving && appData.bgObserve) {
3823 // [HGM] bughouse: don't act on alien boards while we play. Just parse the board and save it */
3824 for (k = 0; k < ranks; k++) {
3825 for (j = 0; j < files; j++)
3826 board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
3827 if(gameInfo.holdingsWidth > 1) {
3828 board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
3829 board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
3832 CopyBoard(partnerBoard, board);
3833 if(appData.dualBoard && !twoBoards) { twoBoards = repaint = 1; InitDrawingSizes(-2,0); }
3834 if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual
3835 if(partnerUp) DrawPosition(repaint, partnerBoard);
3836 if(twoBoards) { partnerUp = 0; flipView = !flipView; } // [HGM] dual
3837 sprintf(partnerStatus, "W: %d:%02d B: %d:%02d (%d-%d) %c", white_time/60000, (white_time%60000)/1000,
3838 (black_time/60000), (black_time%60000)/1000, white_stren, black_stren, to_play);
3839 DisplayMessage(partnerStatus, "");
3840 partnerBoardValid = TRUE;
3844 /* Modify behavior for initial board display on move listing
3847 switch (ics_getting_history) {
3851 case H_GOT_REQ_HEADER:
3852 case H_GOT_UNREQ_HEADER:
3853 /* This is the initial position of the current game */
3854 gamenum = ics_gamenum;
3855 moveNum = 0; /* old ICS bug workaround */
3856 if (to_play == 'B') {
3857 startedFromSetupPosition = TRUE;
3858 blackPlaysFirst = TRUE;
3860 if (forwardMostMove == 0) forwardMostMove = 1;
3861 if (backwardMostMove == 0) backwardMostMove = 1;
3862 if (currentMove == 0) currentMove = 1;
3864 newGameMode = gameMode;
3865 relation = RELATION_STARTING_POSITION; /* ICC needs this */
3867 case H_GOT_UNWANTED_HEADER:
3868 /* This is an initial board that we don't want */
3870 case H_GETTING_MOVES:
3871 /* Should not happen */
3872 DisplayError(_("Error gathering move list: extra board"), 0);
3873 ics_getting_history = H_FALSE;
3877 if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
3878 weird && (int)gameInfo.variant <= (int)VariantShogi) {
3879 /* [HGM] We seem to have switched variant unexpectedly
3880 * Try to guess new variant from board size
3882 VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
3883 if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
3884 if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
3885 if(ranks == 8 && files == 12) newVariant = VariantCourier; else
3886 if(ranks == 9 && files == 9) newVariant = VariantShogi; else
3887 if(!weird) newVariant = VariantNormal;
3888 VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3889 /* Get a move list just to see the header, which
3890 will tell us whether this is really bug or zh */
3891 if (ics_getting_history == H_FALSE) {
3892 ics_getting_history = H_REQUESTED; reqFlag = TRUE;
3893 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3898 /* Take action if this is the first board of a new game, or of a
3899 different game than is currently being displayed. */
3900 if (gamenum != ics_gamenum || newGameMode != gameMode ||
3901 relation == RELATION_ISOLATED_BOARD) {
3903 /* Forget the old game and get the history (if any) of the new one */
3904 if (gameMode != BeginningOfGame) {
3908 if (appData.autoRaiseBoard) BoardToTop();
3910 if (gamenum == -1) {
3911 newGameMode = IcsIdle;
3912 } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
3913 appData.getMoveList && !reqFlag) {
3914 /* Need to get game history */
3915 ics_getting_history = H_REQUESTED;
3916 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3920 /* Initially flip the board to have black on the bottom if playing
3921 black or if the ICS flip flag is set, but let the user change
3922 it with the Flip View button. */
3923 flipView = appData.autoFlipView ?
3924 (newGameMode == IcsPlayingBlack) || ics_flip :
3927 /* Done with values from previous mode; copy in new ones */
3928 gameMode = newGameMode;
3930 ics_gamenum = gamenum;
3931 if (gamenum == gs_gamenum) {
3932 int klen = strlen(gs_kind);
3933 if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
3934 sprintf(str, "ICS %s", gs_kind);
3935 gameInfo.event = StrSave(str);
3937 gameInfo.event = StrSave("ICS game");
3939 gameInfo.site = StrSave(appData.icsHost);
3940 gameInfo.date = PGNDate();
3941 gameInfo.round = StrSave("-");
3942 gameInfo.white = StrSave(white);
3943 gameInfo.black = StrSave(black);
3944 timeControl = basetime * 60 * 1000;
3946 timeIncrement = increment * 1000;
3947 movesPerSession = 0;
3948 gameInfo.timeControl = TimeControlTagValue();
3949 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
3950 if (appData.debugMode) {
3951 fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
3952 fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
3953 setbuf(debugFP, NULL);
3956 gameInfo.outOfBook = NULL;
3958 /* Do we have the ratings? */
3959 if (strcmp(player1Name, white) == 0 &&
3960 strcmp(player2Name, black) == 0) {
3961 if (appData.debugMode)
3962 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3963 player1Rating, player2Rating);
3964 gameInfo.whiteRating = player1Rating;
3965 gameInfo.blackRating = player2Rating;
3966 } else if (strcmp(player2Name, white) == 0 &&
3967 strcmp(player1Name, black) == 0) {
3968 if (appData.debugMode)
3969 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3970 player2Rating, player1Rating);
3971 gameInfo.whiteRating = player2Rating;
3972 gameInfo.blackRating = player1Rating;
3974 player1Name[0] = player2Name[0] = NULLCHAR;
3976 /* Silence shouts if requested */
3977 if (appData.quietPlay &&
3978 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
3979 SendToICS(ics_prefix);
3980 SendToICS("set shout 0\n");
3984 /* Deal with midgame name changes */
3986 if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
3987 if (gameInfo.white) free(gameInfo.white);
3988 gameInfo.white = StrSave(white);
3990 if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
3991 if (gameInfo.black) free(gameInfo.black);
3992 gameInfo.black = StrSave(black);
3996 /* Throw away game result if anything actually changes in examine mode */
3997 if (gameMode == IcsExamining && !newGame) {
3998 gameInfo.result = GameUnfinished;
3999 if (gameInfo.resultDetails != NULL) {
4000 free(gameInfo.resultDetails);
4001 gameInfo.resultDetails = NULL;
4005 /* In pausing && IcsExamining mode, we ignore boards coming
4006 in if they are in a different variation than we are. */
4007 if (pauseExamInvalid) return;
4008 if (pausing && gameMode == IcsExamining) {
4009 if (moveNum <= pauseExamForwardMostMove) {
4010 pauseExamInvalid = TRUE;
4011 forwardMostMove = pauseExamForwardMostMove;
4016 if (appData.debugMode) {
4017 fprintf(debugFP, "load %dx%d board\n", files, ranks);
4019 /* Parse the board */
4020 for (k = 0; k < ranks; k++) {
4021 for (j = 0; j < files; j++)
4022 board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4023 if(gameInfo.holdingsWidth > 1) {
4024 board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4025 board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4028 CopyBoard(boards[moveNum], board);
4029 boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
4031 startedFromSetupPosition =
4032 !CompareBoards(board, initialPosition);
4033 if(startedFromSetupPosition)
4034 initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
4037 /* [HGM] Set castling rights. Take the outermost Rooks,
4038 to make it also work for FRC opening positions. Note that board12
4039 is really defective for later FRC positions, as it has no way to
4040 indicate which Rook can castle if they are on the same side of King.
4041 For the initial position we grant rights to the outermost Rooks,
4042 and remember thos rights, and we then copy them on positions
4043 later in an FRC game. This means WB might not recognize castlings with
4044 Rooks that have moved back to their original position as illegal,
4045 but in ICS mode that is not its job anyway.
4047 if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
4048 { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
4050 for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4051 if(board[0][i] == WhiteRook) j = i;
4052 initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4053 for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4054 if(board[0][i] == WhiteRook) j = i;
4055 initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4056 for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4057 if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4058 initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4059 for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4060 if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4061 initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4063 if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
4064 for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4065 if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
4066 for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4067 if(board[BOARD_HEIGHT-1][k] == bKing)
4068 initialRights[5] = boards[moveNum][CASTLING][5] = k;
4069 if(gameInfo.variant == VariantTwoKings) {
4070 // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
4071 if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
4072 if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
4075 r = boards[moveNum][CASTLING][0] = initialRights[0];
4076 if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4077 r = boards[moveNum][CASTLING][1] = initialRights[1];
4078 if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4079 r = boards[moveNum][CASTLING][3] = initialRights[3];
4080 if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4081 r = boards[moveNum][CASTLING][4] = initialRights[4];
4082 if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4083 /* wildcastle kludge: always assume King has rights */
4084 r = boards[moveNum][CASTLING][2] = initialRights[2];
4085 r = boards[moveNum][CASTLING][5] = initialRights[5];
4087 /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4088 boards[moveNum][EP_STATUS] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
4091 if (ics_getting_history == H_GOT_REQ_HEADER ||
4092 ics_getting_history == H_GOT_UNREQ_HEADER) {
4093 /* This was an initial position from a move list, not
4094 the current position */
4098 /* Update currentMove and known move number limits */
4099 newMove = newGame || moveNum > forwardMostMove;
4102 forwardMostMove = backwardMostMove = currentMove = moveNum;
4103 if (gameMode == IcsExamining && moveNum == 0) {
4104 /* Workaround for ICS limitation: we are not told the wild
4105 type when starting to examine a game. But if we ask for
4106 the move list, the move list header will tell us */
4107 ics_getting_history = H_REQUESTED;
4108 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
4111 } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4112 || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4114 /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4115 /* [HGM] applied this also to an engine that is silently watching */
4116 if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4117 (gameMode == IcsObserving || gameMode == IcsExamining) &&
4118 gameInfo.variant == currentlyInitializedVariant) {
4119 takeback = forwardMostMove - moveNum;
4120 for (i = 0; i < takeback; i++) {
4121 if (appData.debugMode) fprintf(debugFP, "take back move\n");
4122 SendToProgram("undo\n", &first);
4127 forwardMostMove = moveNum;
4128 if (!pausing || currentMove > forwardMostMove)
4129 currentMove = forwardMostMove;
4131 /* New part of history that is not contiguous with old part */
4132 if (pausing && gameMode == IcsExamining) {
4133 pauseExamInvalid = TRUE;
4134 forwardMostMove = pauseExamForwardMostMove;
4137 if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4139 if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4140 // [HGM] when we will receive the move list we now request, it will be
4141 // fed to the engine from the first move on. So if the engine is not
4142 // in the initial position now, bring it there.
4143 InitChessProgram(&first, 0);
4146 ics_getting_history = H_REQUESTED;
4147 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
4150 forwardMostMove = backwardMostMove = currentMove = moveNum;
4153 /* Update the clocks */
4154 if (strchr(elapsed_time, '.')) {
4156 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4157 timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4159 /* Time is in seconds */
4160 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4161 timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4166 if (appData.zippyPlay && newGame &&
4167 gameMode != IcsObserving && gameMode != IcsIdle &&
4168 gameMode != IcsExamining)
4169 ZippyFirstBoard(moveNum, basetime, increment);
4172 /* Put the move on the move list, first converting
4173 to canonical algebraic form. */
4175 if (appData.debugMode) {
4176 if (appData.debugMode) { int f = forwardMostMove;
4177 fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4178 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4179 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4181 fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4182 fprintf(debugFP, "moveNum = %d\n", moveNum);
4183 fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4184 setbuf(debugFP, NULL);
4186 if (moveNum <= backwardMostMove) {
4187 /* We don't know what the board looked like before
4189 strcpy(parseList[moveNum - 1], move_str);
4190 strcat(parseList[moveNum - 1], " ");
4191 strcat(parseList[moveNum - 1], elapsed_time);
4192 moveList[moveNum - 1][0] = NULLCHAR;
4193 } else if (strcmp(move_str, "none") == 0) {
4194 // [HGM] long SAN: swapped order; test for 'none' before parsing move
4195 /* Again, we don't know what the board looked like;
4196 this is really the start of the game. */
4197 parseList[moveNum - 1][0] = NULLCHAR;
4198 moveList[moveNum - 1][0] = NULLCHAR;
4199 backwardMostMove = moveNum;
4200 startedFromSetupPosition = TRUE;
4201 fromX = fromY = toX = toY = -1;
4203 // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
4204 // So we parse the long-algebraic move string in stead of the SAN move
4205 int valid; char buf[MSG_SIZ], *prom;
4207 // str looks something like "Q/a1-a2"; kill the slash
4209 sprintf(buf, "%c%s", str[0], str+2);
4210 else strcpy(buf, str); // might be castling
4211 if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
4212 strcat(buf, prom); // long move lacks promo specification!
4213 if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4214 if(appData.debugMode)
4215 fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4216 strcpy(move_str, buf);
4218 valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4219 &fromX, &fromY, &toX, &toY, &promoChar)
4220 || ParseOneMove(buf, moveNum - 1, &moveType,
4221 &fromX, &fromY, &toX, &toY, &promoChar);
4222 // end of long SAN patch
4224 (void) CoordsToAlgebraic(boards[moveNum - 1],
4225 PosFlags(moveNum - 1),
4226 fromY, fromX, toY, toX, promoChar,
4227 parseList[moveNum-1]);
4228 switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4234 if(gameInfo.variant != VariantShogi)
4235 strcat(parseList[moveNum - 1], "+");
4238 case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4239 strcat(parseList[moveNum - 1], "#");
4242 strcat(parseList[moveNum - 1], " ");
4243 strcat(parseList[moveNum - 1], elapsed_time);
4244 /* currentMoveString is set as a side-effect of ParseOneMove */
4245 strcpy(moveList[moveNum - 1], currentMoveString);
4246 strcat(moveList[moveNum - 1], "\n");
4248 /* Move from ICS was illegal!? Punt. */
4249 if (appData.debugMode) {
4250 fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4251 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4253 strcpy(parseList[moveNum - 1], move_str);
4254 strcat(parseList[moveNum - 1], " ");
4255 strcat(parseList[moveNum - 1], elapsed_time);
4256 moveList[moveNum - 1][0] = NULLCHAR;
4257 fromX = fromY = toX = toY = -1;
4260 if (appData.debugMode) {
4261 fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4262 setbuf(debugFP, NULL);
4266 /* Send move to chess program (BEFORE animating it). */
4267 if (appData.zippyPlay && !newGame && newMove &&
4268 (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4270 if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4271 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4272 if (moveList[moveNum - 1][0] == NULLCHAR) {
4273 sprintf(str, _("Couldn't parse move \"%s\" from ICS"),
4275 DisplayError(str, 0);
4277 if (first.sendTime) {
4278 SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4280 bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4281 if (firstMove && !bookHit) {
4283 if (first.useColors) {
4284 SendToProgram(gameMode == IcsPlayingWhite ?
4286 "black\ngo\n", &first);
4288 SendToProgram("go\n", &first);
4290 first.maybeThinking = TRUE;
4293 } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4294 if (moveList[moveNum - 1][0] == NULLCHAR) {
4295 sprintf(str, _("Couldn't parse move \"%s\" from ICS"), move_str);
4296 DisplayError(str, 0);
4298 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4299 SendMoveToProgram(moveNum - 1, &first);
4306 if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4307 /* If move comes from a remote source, animate it. If it
4308 isn't remote, it will have already been animated. */
4309 if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4310 AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4312 if (!pausing && appData.highlightLastMove) {
4313 SetHighlights(fromX, fromY, toX, toY);
4317 /* Start the clocks */
4318 whiteFlag = blackFlag = FALSE;
4319 appData.clockMode = !(basetime == 0 && increment == 0);
4321 ics_clock_paused = TRUE;
4323 } else if (ticking == 1) {
4324 ics_clock_paused = FALSE;
4326 if (gameMode == IcsIdle ||
4327 relation == RELATION_OBSERVING_STATIC ||
4328 relation == RELATION_EXAMINING ||
4330 DisplayBothClocks();
4334 /* Display opponents and material strengths */
4335 if (gameInfo.variant != VariantBughouse &&
4336 gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4337 if (tinyLayout || smallLayout) {
4338 if(gameInfo.variant == VariantNormal)
4339 sprintf(str, "%s(%d) %s(%d) {%d %d}",
4340 gameInfo.white, white_stren, gameInfo.black, black_stren,
4341 basetime, increment);
4343 sprintf(str, "%s(%d) %s(%d) {%d %d w%d}",
4344 gameInfo.white, white_stren, gameInfo.black, black_stren,
4345 basetime, increment, (int) gameInfo.variant);
4347 if(gameInfo.variant == VariantNormal)
4348 sprintf(str, "%s (%d) vs. %s (%d) {%d %d}",
4349 gameInfo.white, white_stren, gameInfo.black, black_stren,
4350 basetime, increment);
4352 sprintf(str, "%s (%d) vs. %s (%d) {%d %d %s}",
4353 gameInfo.white, white_stren, gameInfo.black, black_stren,
4354 basetime, increment, VariantName(gameInfo.variant));
4357 if (appData.debugMode) {
4358 fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
4363 /* Display the board */
4364 if (!pausing && !appData.noGUI) {
4366 if (appData.premove)
4368 ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
4369 ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
4370 ClearPremoveHighlights();
4372 j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
4373 if(partnerUp) { flipView = originalFlip; partnerUp = FALSE; j = TRUE; } // [HGM] bughouse: restore view
4374 DrawPosition(j, boards[currentMove]);
4376 DisplayMove(moveNum - 1);
4377 if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4378 !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4379 (gameMode == IcsPlayingBlack) && (WhiteOnMove(moveNum)) ) ) {
4380 if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4384 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4386 if(bookHit) { // [HGM] book: simulate book reply
4387 static char bookMove[MSG_SIZ]; // a bit generous?
4389 programStats.nodes = programStats.depth = programStats.time =
4390 programStats.score = programStats.got_only_move = 0;
4391 sprintf(programStats.movelist, "%s (xbook)", bookHit);
4393 strcpy(bookMove, "move ");
4394 strcat(bookMove, bookHit);
4395 HandleMachineMove(bookMove, &first);
4404 if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4405 ics_getting_history = H_REQUESTED;
4406 sprintf(buf, "%smoves %d\n", ics_prefix, ics_gamenum);
4412 AnalysisPeriodicEvent(force)
4415 if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4416 && !force) || !appData.periodicUpdates)
4419 /* Send . command to Crafty to collect stats */
4420 SendToProgram(".\n", &first);
4422 /* Don't send another until we get a response (this makes
4423 us stop sending to old Crafty's which don't understand
4424 the "." command (sending illegal cmds resets node count & time,
4425 which looks bad)) */
4426 programStats.ok_to_send = 0;
4429 void ics_update_width(new_width)
4432 ics_printf("set width %d\n", new_width);
4436 SendMoveToProgram(moveNum, cps)
4438 ChessProgramState *cps;
4442 if (cps->useUsermove) {
4443 SendToProgram("usermove ", cps);
4447 if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4448 int len = space - parseList[moveNum];
4449 memcpy(buf, parseList[moveNum], len);
4451 buf[len] = NULLCHAR;
4453 sprintf(buf, "%s\n", parseList[moveNum]);
4455 SendToProgram(buf, cps);
4457 if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4458 AlphaRank(moveList[moveNum], 4);
4459 SendToProgram(moveList[moveNum], cps);
4460 AlphaRank(moveList[moveNum], 4); // and back
4462 /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
4463 * the engine. It would be nice to have a better way to identify castle
4465 if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
4466 && cps->useOOCastle) {
4467 int fromX = moveList[moveNum][0] - AAA;
4468 int fromY = moveList[moveNum][1] - ONE;
4469 int toX = moveList[moveNum][2] - AAA;
4470 int toY = moveList[moveNum][3] - ONE;
4471 if((boards[moveNum][fromY][fromX] == WhiteKing
4472 && boards[moveNum][toY][toX] == WhiteRook)
4473 || (boards[moveNum][fromY][fromX] == BlackKing
4474 && boards[moveNum][toY][toX] == BlackRook)) {
4475 if(toX > fromX) SendToProgram("O-O\n", cps);
4476 else SendToProgram("O-O-O\n", cps);
4478 else SendToProgram(moveList[moveNum], cps);
4480 else SendToProgram(moveList[moveNum], cps);
4481 /* End of additions by Tord */
4484 /* [HGM] setting up the opening has brought engine in force mode! */
4485 /* Send 'go' if we are in a mode where machine should play. */
4486 if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4487 (gameMode == TwoMachinesPlay ||
4489 gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite ||
4491 gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4492 SendToProgram("go\n", cps);
4493 if (appData.debugMode) {
4494 fprintf(debugFP, "(extra)\n");
4497 setboardSpoiledMachineBlack = 0;
4501 SendMoveToICS(moveType, fromX, fromY, toX, toY)
4503 int fromX, fromY, toX, toY;
4505 char user_move[MSG_SIZ];
4509 sprintf(user_move, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4510 (int)moveType, fromX, fromY, toX, toY);
4511 DisplayError(user_move + strlen("say "), 0);
4513 case WhiteKingSideCastle:
4514 case BlackKingSideCastle:
4515 case WhiteQueenSideCastleWild:
4516 case BlackQueenSideCastleWild:
4518 case WhiteHSideCastleFR:
4519 case BlackHSideCastleFR:
4521 sprintf(user_move, "o-o\n");
4523 case WhiteQueenSideCastle:
4524 case BlackQueenSideCastle:
4525 case WhiteKingSideCastleWild:
4526 case BlackKingSideCastleWild:
4528 case WhiteASideCastleFR:
4529 case BlackASideCastleFR:
4531 sprintf(user_move, "o-o-o\n");
4533 case WhitePromotionQueen:
4534 case BlackPromotionQueen:
4535 case WhitePromotionRook:
4536 case BlackPromotionRook:
4537 case WhitePromotionBishop:
4538 case BlackPromotionBishop:
4539 case WhitePromotionKnight:
4540 case BlackPromotionKnight:
4541 case WhitePromotionKing:
4542 case BlackPromotionKing:
4543 case WhitePromotionChancellor:
4544 case BlackPromotionChancellor:
4545 case WhitePromotionArchbishop:
4546 case BlackPromotionArchbishop:
4547 if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
4548 sprintf(user_move, "%c%c%c%c=%c\n",
4549 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4550 PieceToChar(WhiteFerz));
4551 else if(gameInfo.variant == VariantGreat)
4552 sprintf(user_move, "%c%c%c%c=%c\n",
4553 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4554 PieceToChar(WhiteMan));
4556 sprintf(user_move, "%c%c%c%c=%c\n",
4557 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4558 PieceToChar(PromoPiece(moveType)));
4562 sprintf(user_move, "%c@%c%c\n",
4563 ToUpper(PieceToChar((ChessSquare) fromX)),
4564 AAA + toX, ONE + toY);
4567 case WhiteCapturesEnPassant:
4568 case BlackCapturesEnPassant:
4569 case IllegalMove: /* could be a variant we don't quite understand */
4570 sprintf(user_move, "%c%c%c%c\n",
4571 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4574 SendToICS(user_move);
4575 if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
4576 ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
4581 { // [HGM] upload: send entire stored game to ICS as long-algebraic moves.
4582 int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
4583 static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
4584 if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
4585 DisplayError("You cannot do this while you are playing or observing", 0);
4588 if(gameMode != IcsExamining) { // is this ever not the case?
4589 char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
4591 if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
4592 sprintf(command, "match %s", ics_handle);
4593 } else { // on FICS we must first go to general examine mode
4594 strcpy(command, "examine\nbsetup"); // and specify variant within it with bsetups
4596 if(gameInfo.variant != VariantNormal) {
4597 // try figure out wild number, as xboard names are not always valid on ICS
4598 for(i=1; i<=36; i++) {
4599 sprintf(buf, "wild/%d", i);
4600 if(StringToVariant(buf) == gameInfo.variant) break;
4602 if(i<=36 && ics_type == ICS_ICC) sprintf(buf, "%s w%d\n", command, i);
4603 else if(i == 22) sprintf(buf, "%s fr\n", command);
4604 else sprintf(buf, "%s %s\n", command, VariantName(gameInfo.variant));
4605 } else sprintf(buf, "%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
4606 SendToICS(ics_prefix);
4608 if(startedFromSetupPosition || backwardMostMove != 0) {
4609 fen = PositionToFEN(backwardMostMove, NULL);
4610 if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
4611 sprintf(buf, "loadfen %s\n", fen);
4613 } else { // FICS: everything has to set by separate bsetup commands
4614 p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board
4615 sprintf(buf, "bsetup fen %s\n", fen);
4617 if(!WhiteOnMove(backwardMostMove)) {
4618 SendToICS("bsetup tomove black\n");
4620 i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL);
4621 sprintf(buf, "bsetup wcastle %s\n", castlingStrings[i]);
4623 i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
4624 sprintf(buf, "bsetup bcastle %s\n", castlingStrings[i]);
4626 i = boards[backwardMostMove][EP_STATUS];
4627 if(i >= 0) { // set e.p.
4628 sprintf(buf, "bsetup eppos %c\n", i+AAA);
4634 if(bsetup || ics_type != ICS_ICC && gameInfo.variant != VariantNormal)
4635 SendToICS("bsetup done\n"); // switch to normal examining.
4637 for(i = backwardMostMove; i<last; i++) {
4639 sprintf(buf, "%s\n", parseList[i]);
4642 SendToICS(ics_prefix);
4643 SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
4647 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
4652 if (rf == DROP_RANK) {
4653 sprintf(move, "%c@%c%c\n",
4654 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
4656 if (promoChar == 'x' || promoChar == NULLCHAR) {
4657 sprintf(move, "%c%c%c%c\n",
4658 AAA + ff, ONE + rf, AAA + ft, ONE + rt);
4660 sprintf(move, "%c%c%c%c%c\n",
4661 AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
4667 ProcessICSInitScript(f)
4672 while (fgets(buf, MSG_SIZ, f)) {
4673 SendToICSDelayed(buf,(long)appData.msLoginDelay);
4680 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
4682 AlphaRank(char *move, int n)
4684 // char *p = move, c; int x, y;
4686 if (appData.debugMode) {
4687 fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
4691 move[2]>='0' && move[2]<='9' &&
4692 move[3]>='a' && move[3]<='x' ) {
4694 move[2] = BOARD_RGHT -1 - (move[2]-'1') + AAA;
4695 move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4697 if(move[0]>='0' && move[0]<='9' &&
4698 move[1]>='a' && move[1]<='x' &&
4699 move[2]>='0' && move[2]<='9' &&
4700 move[3]>='a' && move[3]<='x' ) {
4701 /* input move, Shogi -> normal */
4702 move[0] = BOARD_RGHT -1 - (move[0]-'1') + AAA;
4703 move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
4704 move[2] = BOARD_RGHT -1 - (move[2]-'1') + AAA;
4705 move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4708 move[3]>='0' && move[3]<='9' &&
4709 move[2]>='a' && move[2]<='x' ) {
4711 move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4712 move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4715 move[0]>='a' && move[0]<='x' &&
4716 move[3]>='0' && move[3]<='9' &&
4717 move[2]>='a' && move[2]<='x' ) {
4718 /* output move, normal -> Shogi */
4719 move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
4720 move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
4721 move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4722 move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4723 if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
4725 if (appData.debugMode) {
4726 fprintf(debugFP, " out = '%s'\n", move);
4730 char yy_textstr[8000];
4732 /* Parser for moves from gnuchess, ICS, or user typein box */
4734 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
4737 ChessMove *moveType;
4738 int *fromX, *fromY, *toX, *toY;
4741 if (appData.debugMode) {
4742 fprintf(debugFP, "move to parse: %s\n", move);
4744 *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
4746 switch (*moveType) {
4747 case WhitePromotionChancellor:
4748 case BlackPromotionChancellor:
4749 case WhitePromotionArchbishop:
4750 case BlackPromotionArchbishop:
4751 case WhitePromotionQueen:
4752 case BlackPromotionQueen:
4753 case WhitePromotionRook:
4754 case BlackPromotionRook:
4755 case WhitePromotionBishop:
4756 case BlackPromotionBishop:
4757 case WhitePromotionKnight:
4758 case BlackPromotionKnight:
4759 case WhitePromotionKing:
4760 case BlackPromotionKing:
4762 case WhiteCapturesEnPassant:
4763 case BlackCapturesEnPassant:
4764 case WhiteKingSideCastle:
4765 case WhiteQueenSideCastle:
4766 case BlackKingSideCastle:
4767 case BlackQueenSideCastle:
4768 case WhiteKingSideCastleWild:
4769 case WhiteQueenSideCastleWild:
4770 case BlackKingSideCastleWild:
4771 case BlackQueenSideCastleWild:
4772 /* Code added by Tord: */
4773 case WhiteHSideCastleFR:
4774 case WhiteASideCastleFR:
4775 case BlackHSideCastleFR:
4776 case BlackASideCastleFR:
4777 /* End of code added by Tord */
4778 case IllegalMove: /* bug or odd chess variant */
4779 *fromX = currentMoveString[0] - AAA;
4780 *fromY = currentMoveString[1] - ONE;
4781 *toX = currentMoveString[2] - AAA;
4782 *toY = currentMoveString[3] - ONE;
4783 *promoChar = currentMoveString[4];
4784 if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
4785 *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
4786 if (appData.debugMode) {
4787 fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
4789 *fromX = *fromY = *toX = *toY = 0;
4792 if (appData.testLegality) {
4793 return (*moveType != IllegalMove);
4795 return !(*fromX == *toX && *fromY == *toY) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
4796 WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
4801 *fromX = *moveType == WhiteDrop ?
4802 (int) CharToPiece(ToUpper(currentMoveString[0])) :
4803 (int) CharToPiece(ToLower(currentMoveString[0]));
4805 *toX = currentMoveString[2] - AAA;
4806 *toY = currentMoveString[3] - ONE;
4807 *promoChar = NULLCHAR;
4811 case ImpossibleMove:
4812 case (ChessMove) 0: /* end of file */
4821 if (appData.debugMode) {
4822 fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
4825 *fromX = *fromY = *toX = *toY = 0;
4826 *promoChar = NULLCHAR;
4833 ParsePV(char *pv, Boolean storeComments)
4834 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
4835 int fromX, fromY, toX, toY; char promoChar;
4840 endPV = forwardMostMove;
4842 while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
4843 if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
4844 valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
4845 if(appData.debugMode){
4846 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);
4848 if(!valid && nr == 0 &&
4849 ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
4850 nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
4851 // Hande case where played move is different from leading PV move
4852 CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
4853 CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
4854 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
4855 if(!CompareBoards(boards[endPV], boards[endPV+2])) {
4856 endPV += 2; // if position different, keep this
4857 moveList[endPV-1][0] = fromX + AAA;
4858 moveList[endPV-1][1] = fromY + ONE;
4859 moveList[endPV-1][2] = toX + AAA;
4860 moveList[endPV-1][3] = toY + ONE;
4861 parseList[endPV-1][0] = NULLCHAR;
4862 strcpy(moveList[endPV-2], "_0_0"); // suppress premove highlight on takeback move
4865 pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
4866 if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
4867 if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
4868 if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
4869 valid++; // allow comments in PV
4873 if(endPV+1 > framePtr) break; // no space, truncate
4876 CopyBoard(boards[endPV], boards[endPV-1]);
4877 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
4878 moveList[endPV-1][0] = fromX + AAA;
4879 moveList[endPV-1][1] = fromY + ONE;
4880 moveList[endPV-1][2] = toX + AAA;
4881 moveList[endPV-1][3] = toY + ONE;
4883 CoordsToAlgebraic(boards[endPV - 1],
4884 PosFlags(endPV - 1),
4885 fromY, fromX, toY, toX, promoChar,
4886 parseList[endPV - 1]);
4888 parseList[endPV-1][0] = NULLCHAR;
4890 currentMove = endPV;
4891 if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
4892 SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
4893 moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
4894 DrawPosition(TRUE, boards[currentMove]);
4897 static int lastX, lastY;
4900 LoadMultiPV(int x, int y, char *buf, int index, int *start, int *end)
4905 if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
4906 lastX = x; lastY = y;
4907 while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
4909 while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
4910 if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
4912 do{ while(buf[index] && buf[index] != '\n') index++;
4913 } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
4915 ParsePV(buf+startPV, FALSE);
4916 *start = startPV; *end = index-1;
4921 LoadPV(int x, int y)
4922 { // called on right mouse click to load PV
4923 int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
4924 lastX = x; lastY = y;
4925 ParsePV(lastPV[which], FALSE); // load the PV of the thinking engine in the boards array.
4932 if(endPV < 0) return;
4934 currentMove = forwardMostMove;
4935 ClearPremoveHighlights();
4936 DrawPosition(TRUE, boards[currentMove]);
4940 MovePV(int x, int y, int h)
4941 { // step through PV based on mouse coordinates (called on mouse move)
4942 int margin = h>>3, step = 0;
4944 if(endPV < 0) return;
4945 // we must somehow check if right button is still down (might be released off board!)
4946 if(y < margin && (abs(x - lastX) > 6 || abs(y - lastY) > 6)) step = 1; else
4947 if(y > h - margin && (abs(x - lastX) > 6 || abs(y - lastY) > 6)) step = -1; else
4948 if( y > lastY + 6 ) step = -1; else if(y < lastY - 6) step = 1;
4950 lastX = x; lastY = y;
4951 if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
4952 currentMove += step;
4953 if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
4954 SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
4955 moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
4956 DrawPosition(FALSE, boards[currentMove]);
4960 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
4961 // All positions will have equal probability, but the current method will not provide a unique
4962 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
4968 int piecesLeft[(int)BlackPawn];
4969 int seed, nrOfShuffles;
4971 void GetPositionNumber()
4972 { // sets global variable seed
4975 seed = appData.defaultFrcPosition;
4976 if(seed < 0) { // randomize based on time for negative FRC position numbers
4977 for(i=0; i<50; i++) seed += random();
4978 seed = random() ^ random() >> 8 ^ random() << 8;
4979 if(seed<0) seed = -seed;
4983 int put(Board board, int pieceType, int rank, int n, int shade)
4984 // put the piece on the (n-1)-th empty squares of the given shade
4988 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
4989 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
4990 board[rank][i] = (ChessSquare) pieceType;
4991 squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
4993 piecesLeft[pieceType]--;
5001 void AddOnePiece(Board board, int pieceType, int rank, int shade)
5002 // calculate where the next piece goes, (any empty square), and put it there
5006 i = seed % squaresLeft[shade];
5007 nrOfShuffles *= squaresLeft[shade];
5008 seed /= squaresLeft[shade];
5009 put(board, pieceType, rank, i, shade);
5012 void AddTwoPieces(Board board, int pieceType, int rank)
5013 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5015 int i, n=squaresLeft[ANY], j=n-1, k;
5017 k = n*(n-1)/2; // nr of possibilities, not counting permutations
5018 i = seed % k; // pick one
5021 while(i >= j) i -= j--;
5022 j = n - 1 - j; i += j;
5023 put(board, pieceType, rank, j, ANY);
5024 put(board, pieceType, rank, i, ANY);
5027 void SetUpShuffle(Board board, int number)
5031 GetPositionNumber(); nrOfShuffles = 1;
5033 squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5034 squaresLeft[ANY] = BOARD_RGHT - BOARD_LEFT;
5035 squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5037 for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5039 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5040 p = (int) board[0][i];
5041 if(p < (int) BlackPawn) piecesLeft[p] ++;
5042 board[0][i] = EmptySquare;
5045 if(PosFlags(0) & F_ALL_CASTLE_OK) {
5046 // shuffles restricted to allow normal castling put KRR first
5047 if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5048 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5049 else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5050 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5051 if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5052 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5053 if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5054 put(board, WhiteRook, 0, 0, ANY);
5055 // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5058 if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5059 // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5060 for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5061 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5062 while(piecesLeft[p] >= 2) {
5063 AddOnePiece(board, p, 0, LITE);
5064 AddOnePiece(board, p, 0, DARK);
5066 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5069 for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5070 // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5071 // but we leave King and Rooks for last, to possibly obey FRC restriction
5072 if(p == (int)WhiteRook) continue;
5073 while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5074 if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY); // add the odd piece
5077 // now everything is placed, except perhaps King (Unicorn) and Rooks
5079 if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5080 // Last King gets castling rights
5081 while(piecesLeft[(int)WhiteUnicorn]) {
5082 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5083 initialRights[2] = initialRights[5] = board[CASTLING][2] = board[CASTLING][5] = i;
5086 while(piecesLeft[(int)WhiteKing]) {
5087 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5088 initialRights[2] = initialRights[5] = board[CASTLING][2] = board[CASTLING][5] = i;
5093 while(piecesLeft[(int)WhiteKing]) AddOnePiece(board, WhiteKing, 0, ANY);
5094 while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
5097 // Only Rooks can be left; simply place them all
5098 while(piecesLeft[(int)WhiteRook]) {
5099 i = put(board, WhiteRook, 0, 0, ANY);
5100 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
5103 initialRights[1] = initialRights[4] = board[CASTLING][1] = board[CASTLING][4] = i;
5105 initialRights[0] = initialRights[3] = board[CASTLING][0] = board[CASTLING][3] = i;
5108 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
5109 board[BOARD_HEIGHT-1][i] = (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
5112 if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
5115 int SetCharTable( char *table, const char * map )
5116 /* [HGM] moved here from winboard.c because of its general usefulness */
5117 /* Basically a safe strcpy that uses the last character as King */
5119 int result = FALSE; int NrPieces;
5121 if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
5122 && NrPieces >= 12 && !(NrPieces&1)) {
5123 int i; /* [HGM] Accept even length from 12 to 34 */
5125 for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
5126 for( i=0; i<NrPieces/2-1; i++ ) {
5128 table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
5130 table[(int) WhiteKing] = map[NrPieces/2-1];
5131 table[(int) BlackKing] = map[NrPieces-1];
5139 void Prelude(Board board)
5140 { // [HGM] superchess: random selection of exo-pieces
5141 int i, j, k; ChessSquare p;
5142 static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
5144 GetPositionNumber(); // use FRC position number
5146 if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
5147 SetCharTable(pieceToChar, appData.pieceToCharTable);
5148 for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
5149 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
5152 j = seed%4; seed /= 4;
5153 p = board[0][BOARD_LEFT+j]; board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5154 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
5155 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
5156 j = seed%3 + (seed%3 >= j); seed /= 3;
5157 p = board[0][BOARD_LEFT+j]; board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5158 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
5159 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
5160 j = seed%3; seed /= 3;
5161 p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5162 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
5163 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
5164 j = seed%2 + (seed%2 >= j); seed /= 2;
5165 p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5166 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
5167 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
5168 j = seed%4; seed /= 4; put(board, exoPieces[3], 0, j, ANY);
5169 j = seed%3; seed /= 3; put(board, exoPieces[2], 0, j, ANY);
5170 j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
5171 put(board, exoPieces[0], 0, 0, ANY);
5172 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
5176 InitPosition(redraw)
5179 ChessSquare (* pieces)[BOARD_FILES];
5180 int i, j, pawnRow, overrule,
5181 oldx = gameInfo.boardWidth,
5182 oldy = gameInfo.boardHeight,
5183 oldh = gameInfo.holdingsWidth,
5184 oldv = gameInfo.variant;
5186 if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
5188 /* [AS] Initialize pv info list [HGM] and game status */
5190 for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
5191 pvInfoList[i].depth = 0;
5192 boards[i][EP_STATUS] = EP_NONE;
5193 for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
5196 initialRulePlies = 0; /* 50-move counter start */
5198 castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
5199 castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
5203 /* [HGM] logic here is completely changed. In stead of full positions */
5204 /* the initialized data only consist of the two backranks. The switch */
5205 /* selects which one we will use, which is than copied to the Board */
5206 /* initialPosition, which for the rest is initialized by Pawns and */
5207 /* empty squares. This initial position is then copied to boards[0], */
5208 /* possibly after shuffling, so that it remains available. */
5210 gameInfo.holdingsWidth = 0; /* default board sizes */
5211 gameInfo.boardWidth = 8;
5212 gameInfo.boardHeight = 8;
5213 gameInfo.holdingsSize = 0;
5214 nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
5215 for(i=0; i<BOARD_FILES-2; i++)
5216 initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
5217 initialPosition[EP_STATUS] = EP_NONE;
5218 SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
5220 switch (gameInfo.variant) {
5221 case VariantFischeRandom:
5222 shuffleOpenings = TRUE;
5226 case VariantShatranj:
5227 pieces = ShatranjArray;
5228 nrCastlingRights = 0;
5229 SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
5232 pieces = makrukArray;
5233 nrCastlingRights = 0;
5234 startedFromSetupPosition = TRUE;
5235 SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
5237 case VariantTwoKings:
5238 pieces = twoKingsArray;
5240 case VariantCapaRandom:
5241 shuffleOpenings = TRUE;
5242 case VariantCapablanca:
5243 pieces = CapablancaArray;
5244 gameInfo.boardWidth = 10;
5245 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5248 pieces = GothicArray;
5249 gameInfo.boardWidth = 10;
5250 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5253 pieces = JanusArray;
5254 gameInfo.boardWidth = 10;
5255 SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
5256 nrCastlingRights = 6;
5257 initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5258 initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5259 initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
5260 initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5261 initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5262 initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
5265 pieces = FalconArray;
5266 gameInfo.boardWidth = 10;
5267 SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
5269 case VariantXiangqi:
5270 pieces = XiangqiArray;
5271 gameInfo.boardWidth = 9;
5272 gameInfo.boardHeight = 10;
5273 nrCastlingRights = 0;
5274 SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
5277 pieces = ShogiArray;
5278 gameInfo.boardWidth = 9;
5279 gameInfo.boardHeight = 9;
5280 gameInfo.holdingsSize = 7;
5281 nrCastlingRights = 0;
5282 SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
5284 case VariantCourier:
5285 pieces = CourierArray;
5286 gameInfo.boardWidth = 12;
5287 nrCastlingRights = 0;
5288 SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
5290 case VariantKnightmate:
5291 pieces = KnightmateArray;
5292 SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
5295 pieces = fairyArray;
5296 SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
5299 pieces = GreatArray;
5300 gameInfo.boardWidth = 10;
5301 SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
5302 gameInfo.holdingsSize = 8;
5306 SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
5307 gameInfo.holdingsSize = 8;
5308 startedFromSetupPosition = TRUE;
5310 case VariantCrazyhouse:
5311 case VariantBughouse:
5313 SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
5314 gameInfo.holdingsSize = 5;
5316 case VariantWildCastle:
5318 /* !!?shuffle with kings guaranteed to be on d or e file */
5319 shuffleOpenings = 1;
5321 case VariantNoCastle:
5323 nrCastlingRights = 0;
5324 /* !!?unconstrained back-rank shuffle */
5325 shuffleOpenings = 1;
5330 if(appData.NrFiles >= 0) {
5331 if(gameInfo.boardWidth != appData.NrFiles) overrule++;
5332 gameInfo.boardWidth = appData.NrFiles;
5334 if(appData.NrRanks >= 0) {
5335 gameInfo.boardHeight = appData.NrRanks;
5337 if(appData.holdingsSize >= 0) {
5338 i = appData.holdingsSize;
5339 if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
5340 gameInfo.holdingsSize = i;
5342 if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
5343 if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
5344 DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
5346 pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
5347 if(pawnRow < 1) pawnRow = 1;
5348 if(gameInfo.variant == VariantMakruk) pawnRow = 2;
5350 /* User pieceToChar list overrules defaults */
5351 if(appData.pieceToCharTable != NULL)
5352 SetCharTable(pieceToChar, appData.pieceToCharTable);
5354 for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
5356 if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
5357 s = (ChessSquare) 0; /* account holding counts in guard band */
5358 for( i=0; i<BOARD_HEIGHT; i++ )
5359 initialPosition[i][j] = s;
5361 if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
5362 initialPosition[0][j] = pieces[0][j-gameInfo.holdingsWidth];
5363 initialPosition[pawnRow][j] = WhitePawn;
5364 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = BlackPawn;
5365 if(gameInfo.variant == VariantXiangqi) {
5367 initialPosition[pawnRow][j] =
5368 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
5369 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
5370 initialPosition[2][j] = WhiteCannon;
5371 initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
5375 initialPosition[BOARD_HEIGHT-1][j] = pieces[1][j-gameInfo.holdingsWidth];
5377 if( (gameInfo.variant == VariantShogi) && !overrule ) {
5380 initialPosition[1][j] = WhiteBishop;
5381 initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
5383 initialPosition[1][j] = WhiteRook;
5384 initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
5387 if( nrCastlingRights == -1) {
5388 /* [HGM] Build normal castling rights (must be done after board sizing!) */
5389 /* This sets default castling rights from none to normal corners */
5390 /* Variants with other castling rights must set them themselves above */
5391 nrCastlingRights = 6;
5393 initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5394 initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5395 initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
5396 initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5397 initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5398 initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
5401 if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
5402 if(gameInfo.variant == VariantGreat) { // promotion commoners
5403 initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
5404 initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
5405 initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
5406 initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
5408 if (appData.debugMode) {
5409 fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
5411 if(shuffleOpenings) {
5412 SetUpShuffle(initialPosition, appData.defaultFrcPosition);
5413 startedFromSetupPosition = TRUE;
5415 if(startedFromPositionFile) {
5416 /* [HGM] loadPos: use PositionFile for every new game */
5417 CopyBoard(initialPosition, filePosition);
5418 for(i=0; i<nrCastlingRights; i++)
5419 initialRights[i] = filePosition[CASTLING][i];
5420 startedFromSetupPosition = TRUE;
5423 CopyBoard(boards[0], initialPosition);
5425 if(oldx != gameInfo.boardWidth ||
5426 oldy != gameInfo.boardHeight ||
5427 oldh != gameInfo.holdingsWidth
5429 || oldv == VariantGothic || // For licensing popups
5430 gameInfo.variant == VariantGothic
5433 || oldv == VariantFalcon ||
5434 gameInfo.variant == VariantFalcon
5437 InitDrawingSizes(-2 ,0);
5440 DrawPosition(TRUE, boards[currentMove]);
5444 SendBoard(cps, moveNum)
5445 ChessProgramState *cps;
5448 char message[MSG_SIZ];
5450 if (cps->useSetboard) {
5451 char* fen = PositionToFEN(moveNum, cps->fenOverride);
5452 sprintf(message, "setboard %s\n", fen);
5453 SendToProgram(message, cps);
5459 /* Kludge to set black to move, avoiding the troublesome and now
5460 * deprecated "black" command.
5462 if (!WhiteOnMove(moveNum)) SendToProgram("a2a3\n", cps);
5464 SendToProgram("edit\n", cps);
5465 SendToProgram("#\n", cps);
5466 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
5467 bp = &boards[moveNum][i][BOARD_LEFT];
5468 for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
5469 if ((int) *bp < (int) BlackPawn) {
5470 sprintf(message, "%c%c%c\n", PieceToChar(*bp),
5472 if(message[0] == '+' || message[0] == '~') {
5473 sprintf(message, "%c%c%c+\n",
5474 PieceToChar((ChessSquare)(DEMOTED *bp)),
5477 if(cps->alphaRank) { /* [HGM] shogi: translate coords */
5478 message[1] = BOARD_RGHT - 1 - j + '1';
5479 message[2] = BOARD_HEIGHT - 1 - i + 'a';
5481 SendToProgram(message, cps);
5486 SendToProgram("c\n", cps);
5487 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
5488 bp = &boards[moveNum][i][BOARD_LEFT];
5489 for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
5490 if (((int) *bp != (int) EmptySquare)
5491 && ((int) *bp >= (int) BlackPawn)) {
5492 sprintf(message, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
5494 if(message[0] == '+' || message[0] == '~') {
5495 sprintf(message, "%c%c%c+\n",
5496 PieceToChar((ChessSquare)(DEMOTED *bp)),
5499 if(cps->alphaRank) { /* [HGM] shogi: translate coords */
5500 message[1] = BOARD_RGHT - 1 - j + '1';
5501 message[2] = BOARD_HEIGHT - 1 - i + 'a';
5503 SendToProgram(message, cps);
5508 SendToProgram(".\n", cps);
5510 setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
5513 static int autoQueen; // [HGM] oneclick
5516 HasPromotionChoice(int fromX, int fromY, int toX, int toY, char *promoChoice)
5518 /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
5519 /* [HGM] add Shogi promotions */
5520 int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
5525 if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
5526 if(toX < BOARD_LEFT || toX >= BOARD_RGHT) return FALSE; // move into holdings
5528 if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
5529 !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
5532 piece = boards[currentMove][fromY][fromX];
5533 if(gameInfo.variant == VariantShogi) {
5534 promotionZoneSize = 3;
5535 highestPromotingPiece = (int)WhiteFerz;
5536 } else if(gameInfo.variant == VariantMakruk) {
5537 promotionZoneSize = 3;
5540 // next weed out all moves that do not touch the promotion zone at all
5541 if((int)piece >= BlackPawn) {
5542 if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
5544 highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
5546 if( toY < BOARD_HEIGHT - promotionZoneSize &&
5547 fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
5550 if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
5552 // weed out mandatory Shogi promotions
5553 if(gameInfo.variant == VariantShogi) {
5554 if(piece >= BlackPawn) {
5555 if(toY == 0 && piece == BlackPawn ||
5556 toY == 0 && piece == BlackQueen ||
5557 toY <= 1 && piece == BlackKnight) {
5562 if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
5563 toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
5564 toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
5571 // weed out obviously illegal Pawn moves
5572 if(appData.testLegality && (piece == WhitePawn || piece == BlackPawn) ) {
5573 if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
5574 if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
5575 if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
5576 if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
5577 // note we are not allowed to test for valid (non-)capture, due to premove
5580 // we either have a choice what to promote to, or (in Shogi) whether to promote
5581 if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk) {
5582 *promoChoice = PieceToChar(BlackFerz); // no choice
5585 if(autoQueen) { // predetermined
5586 if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantLosers)
5587 *promoChoice = PieceToChar(BlackKing); // in Suicide Q is the last thing we want
5588 else *promoChoice = PieceToChar(BlackQueen);
5592 // suppress promotion popup on illegal moves that are not premoves
5593 premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
5594 gameMode == IcsPlayingBlack && WhiteOnMove(currentMove);
5595 if(appData.testLegality && !premove) {
5596 moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5597 fromY, fromX, toY, toX, NULLCHAR);
5598 if(moveType != WhitePromotionQueen && moveType != BlackPromotionQueen &&
5599 moveType != WhitePromotionKnight && moveType != BlackPromotionKnight)
5607 InPalace(row, column)
5609 { /* [HGM] for Xiangqi */
5610 if( (row < 3 || row > BOARD_HEIGHT-4) &&
5611 column < (BOARD_WIDTH + 4)/2 &&
5612 column > (BOARD_WIDTH - 5)/2 ) return TRUE;
5617 PieceForSquare (x, y)
5621 if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
5624 return boards[currentMove][y][x];
5628 OKToStartUserMove(x, y)
5631 ChessSquare from_piece;
5634 if (matchMode) return FALSE;
5635 if (gameMode == EditPosition) return TRUE;
5637 if (x >= 0 && y >= 0)
5638 from_piece = boards[currentMove][y][x];
5640 from_piece = EmptySquare;
5642 if (from_piece == EmptySquare) return FALSE;
5644 white_piece = (int)from_piece >= (int)WhitePawn &&
5645 (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
5648 case PlayFromGameFile:
5650 case TwoMachinesPlay:
5658 case MachinePlaysWhite:
5659 case IcsPlayingBlack:
5660 if (appData.zippyPlay) return FALSE;
5662 DisplayMoveError(_("You are playing Black"));
5667 case MachinePlaysBlack:
5668 case IcsPlayingWhite:
5669 if (appData.zippyPlay) return FALSE;
5671 DisplayMoveError(_("You are playing White"));
5677 if (!white_piece && WhiteOnMove(currentMove)) {
5678 DisplayMoveError(_("It is White's turn"));
5681 if (white_piece && !WhiteOnMove(currentMove)) {
5682 DisplayMoveError(_("It is Black's turn"));
5685 if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
5686 /* Editing correspondence game history */
5687 /* Could disallow this or prompt for confirmation */
5692 case BeginningOfGame:
5693 if (appData.icsActive) return FALSE;
5694 if (!appData.noChessProgram) {
5696 DisplayMoveError(_("You are playing White"));
5703 if (!white_piece && WhiteOnMove(currentMove)) {
5704 DisplayMoveError(_("It is White's turn"));
5707 if (white_piece && !WhiteOnMove(currentMove)) {
5708 DisplayMoveError(_("It is Black's turn"));
5717 if (currentMove != forwardMostMove && gameMode != AnalyzeMode
5718 && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
5719 && gameMode != AnalyzeFile && gameMode != Training) {
5720 DisplayMoveError(_("Displayed position is not current"));
5727 OnlyMove(int *x, int *y, Boolean captures) {
5728 DisambiguateClosure cl;
5729 if (appData.zippyPlay) return FALSE;
5731 case MachinePlaysBlack:
5732 case IcsPlayingWhite:
5733 case BeginningOfGame:
5734 if(!WhiteOnMove(currentMove)) return FALSE;
5736 case MachinePlaysWhite:
5737 case IcsPlayingBlack:
5738 if(WhiteOnMove(currentMove)) return FALSE;
5743 cl.pieceIn = EmptySquare;
5748 cl.promoCharIn = NULLCHAR;
5749 Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
5750 if( cl.kind == NormalMove ||
5751 cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
5752 cl.kind == WhitePromotionQueen || cl.kind == BlackPromotionQueen ||
5753 cl.kind == WhitePromotionKnight || cl.kind == BlackPromotionKnight ||
5754 cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
5761 if(cl.kind != ImpossibleMove) return FALSE;
5762 cl.pieceIn = EmptySquare;
5767 cl.promoCharIn = NULLCHAR;
5768 Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
5769 if( cl.kind == NormalMove ||
5770 cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
5771 cl.kind == WhitePromotionQueen || cl.kind == BlackPromotionQueen ||
5772 cl.kind == WhitePromotionKnight || cl.kind == BlackPromotionKnight ||
5773 cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
5778 autoQueen = TRUE; // act as if autoQueen on when we click to-square
5784 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
5785 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
5786 int lastLoadGameUseList = FALSE;
5787 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
5788 ChessMove lastLoadGameStart = (ChessMove) 0;
5791 UserMoveTest(fromX, fromY, toX, toY, promoChar, captureOwn)
5792 int fromX, fromY, toX, toY;
5797 ChessSquare pdown, pup;
5799 /* Check if the user is playing in turn. This is complicated because we
5800 let the user "pick up" a piece before it is his turn. So the piece he
5801 tried to pick up may have been captured by the time he puts it down!
5802 Therefore we use the color the user is supposed to be playing in this
5803 test, not the color of the piece that is currently on the starting
5804 square---except in EditGame mode, where the user is playing both
5805 sides; fortunately there the capture race can't happen. (It can
5806 now happen in IcsExamining mode, but that's just too bad. The user
5807 will get a somewhat confusing message in that case.)
5811 case PlayFromGameFile:
5813 case TwoMachinesPlay:
5817 /* We switched into a game mode where moves are not accepted,
5818 perhaps while the mouse button was down. */
5819 return ImpossibleMove;
5821 case MachinePlaysWhite:
5822 /* User is moving for Black */
5823 if (WhiteOnMove(currentMove)) {
5824 DisplayMoveError(_("It is White's turn"));
5825 return ImpossibleMove;
5829 case MachinePlaysBlack:
5830 /* User is moving for White */
5831 if (!WhiteOnMove(currentMove)) {
5832 DisplayMoveError(_("It is Black's turn"));
5833 return ImpossibleMove;
5839 case BeginningOfGame:
5842 if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
5843 (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
5844 /* User is moving for Black */
5845 if (WhiteOnMove(currentMove)) {
5846 DisplayMoveError(_("It is White's turn"));
5847 return ImpossibleMove;
5850 /* User is moving for White */
5851 if (!WhiteOnMove(currentMove)) {
5852 DisplayMoveError(_("It is Black's turn"));
5853 return ImpossibleMove;
5858 case IcsPlayingBlack:
5859 /* User is moving for Black */
5860 if (WhiteOnMove(currentMove)) {
5861 if (!appData.premove) {
5862 DisplayMoveError(_("It is White's turn"));
5863 } else if (toX >= 0 && toY >= 0) {
5866 premoveFromX = fromX;
5867 premoveFromY = fromY;
5868 premovePromoChar = promoChar;
5870 if (appData.debugMode)
5871 fprintf(debugFP, "Got premove: fromX %d,"
5872 "fromY %d, toX %d, toY %d\n",
5873 fromX, fromY, toX, toY);
5875 return ImpossibleMove;
5879 case IcsPlayingWhite:
5880 /* User is moving for White */
5881 if (!WhiteOnMove(currentMove)) {
5882 if (!appData.premove) {
5883 DisplayMoveError(_("It is Black's turn"));
5884 } else if (toX >= 0 && toY >= 0) {
5887 premoveFromX = fromX;
5888 premoveFromY = fromY;
5889 premovePromoChar = promoChar;
5891 if (appData.debugMode)
5892 fprintf(debugFP, "Got premove: fromX %d,"
5893 "fromY %d, toX %d, toY %d\n",
5894 fromX, fromY, toX, toY);
5896 return ImpossibleMove;
5904 /* EditPosition, empty square, or different color piece;
5905 click-click move is possible */
5906 if (toX == -2 || toY == -2) {
5907 boards[0][fromY][fromX] = EmptySquare;
5908 return AmbiguousMove;
5909 } else if (toX >= 0 && toY >= 0) {
5910 boards[0][toY][toX] = boards[0][fromY][fromX];
5911 if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
5912 if(boards[0][fromY][0] != EmptySquare) {
5913 if(boards[0][fromY][1]) boards[0][fromY][1]--;
5914 if(boards[0][fromY][1] == 0) boards[0][fromY][0] = EmptySquare;
5917 if(fromX == BOARD_RGHT+1) {
5918 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
5919 if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
5920 if(boards[0][fromY][BOARD_WIDTH-2] == 0) boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
5923 boards[0][fromY][fromX] = EmptySquare;
5924 return AmbiguousMove;
5926 return ImpossibleMove;
5929 if(toX < 0 || toY < 0) return ImpossibleMove;
5930 pdown = boards[currentMove][fromY][fromX];
5931 pup = boards[currentMove][toY][toX];
5933 /* [HGM] If move started in holdings, it means a drop */
5934 if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) {
5935 if( pup != EmptySquare ) return ImpossibleMove;
5936 if(appData.testLegality) {
5937 /* it would be more logical if LegalityTest() also figured out
5938 * which drops are legal. For now we forbid pawns on back rank.
5939 * Shogi is on its own here...
5941 if( (pdown == WhitePawn || pdown == BlackPawn) &&
5942 (toY == 0 || toY == BOARD_HEIGHT -1 ) )
5943 return(ImpossibleMove); /* no pawn drops on 1st/8th */
5945 return WhiteDrop; /* Not needed to specify white or black yet */
5948 /* [HGM] always test for legality, to get promotion info */
5949 moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5950 fromY, fromX, toY, toX, promoChar);
5951 /* [HGM] but possibly ignore an IllegalMove result */
5952 if (appData.testLegality) {
5953 if (moveType == IllegalMove || moveType == ImpossibleMove) {
5954 DisplayMoveError(_("Illegal move"));
5955 return ImpossibleMove;
5960 /* [HGM] <popupFix> in stead of calling FinishMove directly, this
5961 function is made into one that returns an OK move type if FinishMove
5962 should be called. This to give the calling driver routine the
5963 opportunity to finish the userMove input with a promotion popup,
5964 without bothering the user with this for invalid or illegal moves */
5966 /* FinishMove(moveType, fromX, fromY, toX, toY, promoChar); */
5969 /* Common tail of UserMoveEvent and DropMenuEvent */
5971 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
5973 int fromX, fromY, toX, toY;
5974 /*char*/int promoChar;
5978 if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) && promoChar != NULLCHAR) {
5979 // [HGM] superchess: suppress promotions to non-available piece
5980 int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
5981 if(WhiteOnMove(currentMove)) {
5982 if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
5984 if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
5988 /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
5989 move type in caller when we know the move is a legal promotion */
5990 if(moveType == NormalMove && promoChar)
5991 moveType = PromoCharToMoveType(WhiteOnMove(currentMove), promoChar);
5993 /* [HGM] convert drag-and-drop piece drops to standard form */
5994 if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ){
5995 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
5996 if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n",
5997 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
5998 // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
5999 if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
6000 fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
6001 while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++;
6005 /* [HGM] <popupFix> The following if has been moved here from
6006 UserMoveEvent(). Because it seemed to belong here (why not allow
6007 piece drops in training games?), and because it can only be
6008 performed after it is known to what we promote. */
6009 if (gameMode == Training) {
6010 /* compare the move played on the board to the next move in the
6011 * game. If they match, display the move and the opponent's response.
6012 * If they don't match, display an error message.
6016 CopyBoard(testBoard, boards[currentMove]);
6017 ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
6019 if (CompareBoards(testBoard, boards[currentMove+1])) {
6020 ForwardInner(currentMove+1);
6022 /* Autoplay the opponent's response.
6023 * if appData.animate was TRUE when Training mode was entered,
6024 * the response will be animated.
6026 saveAnimate = appData.animate;
6027 appData.animate = animateTraining;
6028 ForwardInner(currentMove+1);
6029 appData.animate = saveAnimate;
6031 /* check for the end of the game */
6032 if (currentMove >= forwardMostMove) {
6033 gameMode = PlayFromGameFile;
6035 SetTrainingModeOff();
6036 DisplayInformation(_("End of game"));
6039 DisplayError(_("Incorrect move"), 0);
6044 /* Ok, now we know that the move is good, so we can kill
6045 the previous line in Analysis Mode */
6046 if ((gameMode == AnalyzeMode || gameMode == EditGame)
6047 && currentMove < forwardMostMove) {
6048 PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
6051 /* If we need the chess program but it's dead, restart it */
6052 ResurrectChessProgram();
6054 /* A user move restarts a paused game*/
6058 thinkOutput[0] = NULLCHAR;
6060 MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
6062 if(Adjudicate(NULL)) return 1; // [HGM] adjudicate: take care of automtic game end
6064 if (gameMode == BeginningOfGame) {
6065 if (appData.noChessProgram) {
6066 gameMode = EditGame;
6070 gameMode = MachinePlaysBlack;
6073 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
6075 if (first.sendName) {
6076 sprintf(buf, "name %s\n", gameInfo.white);
6077 SendToProgram(buf, &first);
6084 /* Relay move to ICS or chess engine */
6085 if (appData.icsActive) {
6086 if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
6087 gameMode == IcsExamining) {
6088 if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
6089 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
6091 SendMoveToICS(moveType, fromX, fromY, toX, toY);
6093 // also send plain move, in case ICS does not understand atomic claims
6094 SendMoveToICS(moveType, fromX, fromY, toX, toY);
6098 if (first.sendTime && (gameMode == BeginningOfGame ||
6099 gameMode == MachinePlaysWhite ||
6100 gameMode == MachinePlaysBlack)) {
6101 SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
6103 if (gameMode != EditGame && gameMode != PlayFromGameFile) {
6104 // [HGM] book: if program might be playing, let it use book
6105 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
6106 first.maybeThinking = TRUE;
6107 } else SendMoveToProgram(forwardMostMove-1, &first);
6108 if (currentMove == cmailOldMove + 1) {
6109 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
6113 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6117 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
6123 if (WhiteOnMove(currentMove)) {
6124 GameEnds(BlackWins, "Black mates", GE_PLAYER);
6126 GameEnds(WhiteWins, "White mates", GE_PLAYER);
6130 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
6135 case MachinePlaysBlack:
6136 case MachinePlaysWhite:
6137 /* disable certain menu options while machine is thinking */
6138 SetMachineThinkingEnables();
6145 userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
6147 if(bookHit) { // [HGM] book: simulate book reply
6148 static char bookMove[MSG_SIZ]; // a bit generous?
6150 programStats.nodes = programStats.depth = programStats.time =
6151 programStats.score = programStats.got_only_move = 0;
6152 sprintf(programStats.movelist, "%s (xbook)", bookHit);
6154 strcpy(bookMove, "move ");
6155 strcat(bookMove, bookHit);
6156 HandleMachineMove(bookMove, &first);
6162 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
6163 int fromX, fromY, toX, toY;
6166 /* [HGM] This routine was added to allow calling of its two logical
6167 parts from other modules in the old way. Before, UserMoveEvent()
6168 automatically called FinishMove() if the move was OK, and returned
6169 otherwise. I separated the two, in order to make it possible to
6170 slip a promotion popup in between. But that it always needs two
6171 calls, to the first part, (now called UserMoveTest() ), and to
6172 FinishMove if the first part succeeded. Calls that do not need
6173 to do anything in between, can call this routine the old way.
6175 ChessMove moveType = UserMoveTest(fromX, fromY, toX, toY, promoChar, FALSE);
6176 if(appData.debugMode) fprintf(debugFP, "moveType 4 = %d, promochar = %x\n", moveType, promoChar);
6177 if(moveType == AmbiguousMove)
6178 DrawPosition(FALSE, boards[currentMove]);
6179 else if(moveType != ImpossibleMove && moveType != Comment)
6180 FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
6184 Mark(board, flags, kind, rf, ff, rt, ft, closure)
6191 typedef char Markers[BOARD_RANKS][BOARD_FILES];
6192 Markers *m = (Markers *) closure;
6193 if(rf == fromY && ff == fromX)
6194 (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
6195 || kind == WhiteCapturesEnPassant
6196 || kind == BlackCapturesEnPassant);
6197 else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
6201 MarkTargetSquares(int clear)
6204 if(!appData.markers || !appData.highlightDragging ||
6205 !appData.testLegality || gameMode == EditPosition) return;
6207 for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) marker[y][x] = 0;
6210 GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker);
6211 if(PosFlags(0) & F_MANDATORY_CAPTURE) {
6212 for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
6214 for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
6217 DrawPosition(TRUE, NULL);
6220 void LeftClick(ClickType clickType, int xPix, int yPix)
6223 Boolean saveAnimate;
6224 static int second = 0, promotionChoice = 0;
6225 char promoChoice = NULLCHAR;
6227 if(appData.seekGraph && appData.icsActive && loggedOn &&
6228 (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
6229 SeekGraphClick(clickType, xPix, yPix, 0);
6233 if (clickType == Press) ErrorPopDown();
6234 MarkTargetSquares(1);
6236 x = EventToSquare(xPix, BOARD_WIDTH);
6237 y = EventToSquare(yPix, BOARD_HEIGHT);
6238 if (!flipView && y >= 0) {
6239 y = BOARD_HEIGHT - 1 - y;
6241 if (flipView && x >= 0) {
6242 x = BOARD_WIDTH - 1 - x;
6245 if(promotionChoice) { // we are waiting for a click to indicate promotion piece
6246 if(clickType == Release) return; // ignore upclick of click-click destination
6247 promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
6248 if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
6249 if(gameInfo.holdingsWidth &&
6250 (WhiteOnMove(currentMove)
6251 ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y > 0
6252 : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT-1) ) {
6253 // click in right holdings, for determining promotion piece
6254 ChessSquare p = boards[currentMove][y][x];
6255 if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
6256 if(p != EmptySquare) {
6257 FinishMove(NormalMove, fromX, fromY, toX, toY, ToLower(PieceToChar(p)));
6262 DrawPosition(FALSE, boards[currentMove]);
6266 /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
6267 if(clickType == Press
6268 && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
6269 || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
6270 || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
6273 autoQueen = appData.alwaysPromoteToQueen;
6276 if(!appData.oneClick || !OnlyMove(&x, &y, FALSE)) {
6277 if (clickType == Press) {
6279 if (OKToStartUserMove(x, y)) {
6283 MarkTargetSquares(0);
6284 DragPieceBegin(xPix, yPix);
6285 if (appData.highlightDragging) {
6286 SetHighlights(x, y, -1, -1);
6295 if (clickType == Press && gameMode != EditPosition) {
6300 // ignore off-board to clicks
6301 if(y < 0 || x < 0) return;
6303 /* Check if clicking again on the same color piece */
6304 fromP = boards[currentMove][fromY][fromX];
6305 toP = boards[currentMove][y][x];
6306 frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom;
6307 if ((WhitePawn <= fromP && fromP <= WhiteKing &&
6308 WhitePawn <= toP && toP <= WhiteKing &&
6309 !(fromP == WhiteKing && toP == WhiteRook && frc) &&
6310 !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
6311 (BlackPawn <= fromP && fromP <= BlackKing &&
6312 BlackPawn <= toP && toP <= BlackKing &&
6313 !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
6314 !(fromP == BlackKing && toP == BlackRook && frc))) {
6315 /* Clicked again on same color piece -- changed his mind */
6316 second = (x == fromX && y == fromY);
6317 if(!second || !OnlyMove(&x, &y, TRUE)) {
6318 if (appData.highlightDragging) {
6319 SetHighlights(x, y, -1, -1);
6323 if (OKToStartUserMove(x, y)) {
6326 MarkTargetSquares(0);
6327 DragPieceBegin(xPix, yPix);
6332 // ignore clicks on holdings
6333 if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
6336 if (clickType == Release && x == fromX && y == fromY) {
6337 DragPieceEnd(xPix, yPix);
6338 if (appData.animateDragging) {
6339 /* Undo animation damage if any */
6340 DrawPosition(FALSE, NULL);
6343 /* Second up/down in same square; just abort move */
6348 ClearPremoveHighlights();
6350 /* First upclick in same square; start click-click mode */
6351 SetHighlights(x, y, -1, -1);
6356 /* we now have a different from- and (possibly off-board) to-square */
6357 /* Completed move */
6360 saveAnimate = appData.animate;
6361 if (clickType == Press) {
6362 /* Finish clickclick move */
6363 if (appData.animate || appData.highlightLastMove) {
6364 SetHighlights(fromX, fromY, toX, toY);
6369 /* Finish drag move */
6370 if (appData.highlightLastMove) {
6371 SetHighlights(fromX, fromY, toX, toY);
6375 DragPieceEnd(xPix, yPix);
6376 /* Don't animate move and drag both */
6377 appData.animate = FALSE;
6380 // moves into holding are invalid for now (except in EditPosition, adapting to-square)
6381 if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
6382 ChessSquare piece = boards[currentMove][fromY][fromX];
6383 if(gameMode == EditPosition && piece != EmptySquare &&
6384 fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
6387 if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
6388 n = PieceToNumber(piece - (int)BlackPawn);
6389 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
6390 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
6391 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
6393 if(x == BOARD_RGHT+1 && piece < BlackPawn) {
6394 n = PieceToNumber(piece);
6395 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
6396 boards[currentMove][n][BOARD_WIDTH-1] = piece;
6397 boards[currentMove][n][BOARD_WIDTH-2]++;
6399 boards[currentMove][fromY][fromX] = EmptySquare;
6403 DrawPosition(TRUE, boards[currentMove]);
6407 // off-board moves should not be highlighted
6408 if(x < 0 || x < 0) ClearHighlights();
6410 if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice)) {
6411 SetHighlights(fromX, fromY, toX, toY);
6412 if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
6413 // [HGM] super: promotion to captured piece selected from holdings
6414 ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
6415 promotionChoice = TRUE;
6416 // kludge follows to temporarily execute move on display, without promoting yet
6417 boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
6418 boards[currentMove][toY][toX] = p;
6419 DrawPosition(FALSE, boards[currentMove]);
6420 boards[currentMove][fromY][fromX] = p; // take back, but display stays
6421 boards[currentMove][toY][toX] = q;
6422 DisplayMessage("Click in holdings to choose piece", "");
6427 UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
6428 if (!appData.highlightLastMove || gotPremove) ClearHighlights();
6429 if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
6432 appData.animate = saveAnimate;
6433 if (appData.animate || appData.animateDragging) {
6434 /* Undo animation damage if needed */
6435 DrawPosition(FALSE, NULL);
6439 int RightClick(ClickType action, int x, int y, int *fromX, int *fromY)
6440 { // front-end-free part taken out of PieceMenuPopup
6441 int whichMenu; int xSqr, ySqr;
6443 if(seekGraphUp) { // [HGM] seekgraph
6444 if(action == Press) SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
6445 if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
6449 if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
6450 && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
6451 if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
6452 if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
6453 if(action == Press) {
6454 originalFlip = flipView;
6455 flipView = !flipView; // temporarily flip board to see game from partners perspective
6456 DrawPosition(TRUE, partnerBoard);
6457 DisplayMessage(partnerStatus, "");
6459 } else if(action == Release) {
6460 flipView = originalFlip;
6461 DrawPosition(TRUE, boards[currentMove]);
6467 xSqr = EventToSquare(x, BOARD_WIDTH);
6468 ySqr = EventToSquare(y, BOARD_HEIGHT);
6469 if (action == Release) UnLoadPV(); // [HGM] pv
6470 if (action != Press) return -2; // return code to be ignored
6473 if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;
\r
6475 if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;
\r
6476 if (xSqr < 0 || ySqr < 0) return -1;
\r
6477 whichMenu = 0; // edit-position menu
6480 if(!appData.icsEngineAnalyze) return -1;
6481 case IcsPlayingWhite:
6482 case IcsPlayingBlack:
6483 if(!appData.zippyPlay) goto noZip;
6486 case MachinePlaysWhite:
6487 case MachinePlaysBlack:
6488 case TwoMachinesPlay: // [HGM] pv: use for showing PV
6489 if (!appData.dropMenu) {
6491 return 2; // flag front-end to grab mouse events
6493 if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
6494 gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
6497 if (xSqr < 0 || ySqr < 0) return -1;
6498 if (!appData.dropMenu || appData.testLegality &&
6499 gameInfo.variant != VariantBughouse &&
6500 gameInfo.variant != VariantCrazyhouse) return -1;
6501 whichMenu = 1; // drop menu
6507 if (((*fromX = xSqr) < 0) ||
6508 ((*fromY = ySqr) < 0)) {
6509 *fromX = *fromY = -1;
6513 *fromX = BOARD_WIDTH - 1 - *fromX;
6515 *fromY = BOARD_HEIGHT - 1 - *fromY;
6520 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
6522 // char * hint = lastHint;
6523 FrontEndProgramStats stats;
6525 stats.which = cps == &first ? 0 : 1;
6526 stats.depth = cpstats->depth;
6527 stats.nodes = cpstats->nodes;
6528 stats.score = cpstats->score;
6529 stats.time = cpstats->time;
6530 stats.pv = cpstats->movelist;
6531 stats.hint = lastHint;
6532 stats.an_move_index = 0;
6533 stats.an_move_count = 0;
6535 if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
6536 stats.hint = cpstats->move_name;
6537 stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
6538 stats.an_move_count = cpstats->nr_moves;
6541 if(stats.pv && stats.pv[0]) strcpy(lastPV[stats.which], stats.pv); // [HGM] pv: remember last PV of each
6543 SetProgramStats( &stats );
6547 Adjudicate(ChessProgramState *cps)
6548 { // [HGM] some adjudications useful with buggy engines
6549 // [HGM] adjudicate: made into separate routine, which now can be called after every move
6550 // In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
6551 // Actually ending the game is now based on the additional internal condition canAdjudicate.
6552 // Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
6553 int k, count = 0; static int bare = 1;
6554 ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
6555 Boolean canAdjudicate = !appData.icsActive;
6557 // most tests only when we understand the game, i.e. legality-checking on, and (for the time being) no piece drops
6558 if(gameInfo.holdingsSize == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
6559 if( appData.testLegality )
6560 { /* [HGM] Some more adjudications for obstinate engines */
6561 int NrWN=0, NrBN=0, NrWB=0, NrBB=0, NrWR=0, NrBR=0,
6562 NrWQ=0, NrBQ=0, NrW=0, NrK=0, bishopsColor = 0,
6563 NrPieces=0, NrPawns=0, PawnAdvance=0, i, j;
6564 static int moveCount = 6;
6566 char *reason = NULL;
6568 /* Count what is on board. */
6569 for(i=0; i<BOARD_HEIGHT; i++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
6570 { ChessSquare p = boards[forwardMostMove][i][j];
6574 { /* count B,N,R and other of each side */
6577 NrK++; break; // [HGM] atomic: count Kings
6581 case WhiteFerz: // [HGM] shatranj: kludge to mke it work in shatranj
6582 bishopsColor |= 1 << ((i^j)&1);
6587 case BlackFerz: // [HGM] shatranj: kludge to mke it work in shatranj
6588 bishopsColor |= 1 << ((i^j)&1);
6603 PawnAdvance += m; NrPawns++;
6605 NrPieces += (p != EmptySquare);
6606 NrW += ((int)p < (int)BlackPawn);
6607 if(gameInfo.variant == VariantXiangqi &&
6608 (p == WhiteFerz || p == WhiteAlfil || p == BlackFerz || p == BlackAlfil)) {
6609 NrPieces--; // [HGM] XQ: do not count purely defensive pieces
6610 NrW -= ((int)p < (int)BlackPawn);
6614 /* Some material-based adjudications that have to be made before stalemate test */
6615 if(gameInfo.variant == VariantAtomic && NrK < 2) {
6616 // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
6617 boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
6618 if(canAdjudicate && appData.checkMates) {
6620 SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
6621 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6622 GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
6623 "Xboard adjudication: King destroyed", GE_XBOARD );
6628 /* Bare King in Shatranj (loses) or Losers (wins) */
6629 if( NrW == 1 || NrPieces - NrW == 1) {
6630 if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
6631 boards[forwardMostMove][EP_STATUS] = EP_WINS; // mark as win, so it becomes claimable
6632 if(canAdjudicate && appData.checkMates) {
6634 SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
6635 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6636 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
6637 "Xboard adjudication: Bare king", GE_XBOARD );
6641 if( gameInfo.variant == VariantShatranj && --bare < 0)
6643 boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
6644 if(canAdjudicate && appData.checkMates) {
6645 /* but only adjudicate if adjudication enabled */
6647 SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
6648 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6649 GameEnds( NrW > 1 ? WhiteWins : NrPieces - NrW > 1 ? BlackWins : GameIsDrawn,
6650 "Xboard adjudication: Bare king", GE_XBOARD );
6657 // don't wait for engine to announce game end if we can judge ourselves
6658 switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
6660 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
6661 int i, checkCnt = 0; // (should really be done by making nr of checks part of game state)
6662 for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
6663 if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
6666 reason = "Xboard adjudication: 3rd check";
6667 boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
6677 reason = "Xboard adjudication: Stalemate";
6678 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
6679 boards[forwardMostMove][EP_STATUS] = EP_STALEMATE; // default result for stalemate is draw
6680 if(gameInfo.variant == VariantLosers || gameInfo.variant == VariantGiveaway) // [HGM] losers:
6681 boards[forwardMostMove][EP_STATUS] = EP_WINS; // in these variants stalemated is always a win
6682 else if(gameInfo.variant == VariantSuicide) // in suicide it depends
6683 boards[forwardMostMove][EP_STATUS] = NrW == NrPieces-NrW ? EP_STALEMATE :
6684 ((NrW < NrPieces-NrW) != WhiteOnMove(forwardMostMove) ?
6685 EP_CHECKMATE : EP_WINS);
6686 else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
6687 boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
6691 reason = "Xboard adjudication: Checkmate";
6692 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
6696 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
6698 result = GameIsDrawn; break;
6700 result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
6702 result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
6704 result = (ChessMove) 0;
6706 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
6708 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6709 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6710 GameEnds( result, reason, GE_XBOARD );
6714 /* Next absolutely insufficient mating material. */
6715 if( NrPieces == 2 || gameInfo.variant != VariantXiangqi &&
6716 gameInfo.variant != VariantShatranj && // [HGM] baring will remain possible
6717 (NrPieces == 3 && NrWN+NrBN+NrWB+NrBB == 1 ||
6718 NrPieces == NrBB+NrWB+2 && bishopsColor != 3)) // [HGM] all Bishops (Ferz!) same color
6719 { /* KBK, KNK, KK of KBKB with like Bishops */
6721 /* always flag draws, for judging claims */
6722 boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
6724 if(canAdjudicate && appData.materialDraws) {
6725 /* but only adjudicate them if adjudication enabled */
6726 if(engineOpponent) {
6727 SendToProgram("force\n", engineOpponent); // suppress reply
6728 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
6730 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6731 GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
6736 /* Then some trivial draws (only adjudicate, cannot be claimed) */
6738 ( NrWR == 1 && NrBR == 1 /* KRKR */
6739 || NrWQ==1 && NrBQ==1 /* KQKQ */
6740 || NrWN==2 || NrBN==2 /* KNNK */
6741 || NrWN+NrWB == 1 && NrBN+NrBB == 1 /* KBKN, KBKB, KNKN */
6743 if(canAdjudicate && --moveCount < 0 && appData.trivialDraws)
6744 { /* if the first 3 moves do not show a tactical win, declare draw */
6745 if(engineOpponent) {
6746 SendToProgram("force\n", engineOpponent); // suppress reply
6747 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6749 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6750 GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
6753 } else moveCount = 6;
6757 if (appData.debugMode) { int i;
6758 fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
6759 forwardMostMove, backwardMostMove, boards[backwardMostMove][EP_STATUS],
6760 appData.drawRepeats);
6761 for( i=forwardMostMove; i>=backwardMostMove; i-- )
6762 fprintf(debugFP, "%d ep=%d\n", i, (signed char)boards[i][EP_STATUS]);
6766 // Repetition draws and 50-move rule can be applied independently of legality testing
6768 /* Check for rep-draws */
6770 for(k = forwardMostMove-2;
6771 k>=backwardMostMove && k>=forwardMostMove-100 &&
6772 (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
6773 (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE;
6776 if(CompareBoards(boards[k], boards[forwardMostMove])) {
6777 /* compare castling rights */
6778 if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
6779 (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
6780 rights++; /* King lost rights, while rook still had them */
6781 if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
6782 if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
6783 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
6784 rights++; /* but at least one rook lost them */
6786 if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
6787 (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
6789 if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
6790 if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
6791 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
6794 if( canAdjudicate && rights == 0 && ++count > appData.drawRepeats-2
6795 && appData.drawRepeats > 1) {
6796 /* adjudicate after user-specified nr of repeats */
6797 if(engineOpponent) {
6798 SendToProgram("force\n", engineOpponent); // suppress reply
6799 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6801 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6802 if(gameInfo.variant == VariantXiangqi && appData.testLegality) {
6803 // [HGM] xiangqi: check for forbidden perpetuals
6804 int m, ourPerpetual = 1, hisPerpetual = 1;
6805 for(m=forwardMostMove; m>k; m-=2) {
6806 if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
6807 ourPerpetual = 0; // the current mover did not always check
6808 if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
6809 hisPerpetual = 0; // the opponent did not always check
6811 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
6812 ourPerpetual, hisPerpetual);
6813 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
6814 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
6815 "Xboard adjudication: perpetual checking", GE_XBOARD );
6818 if(hisPerpetual && !ourPerpetual) // he is checking us, but did not repeat yet
6819 break; // (or we would have caught him before). Abort repetition-checking loop.
6820 // Now check for perpetual chases
6821 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
6822 hisPerpetual = PerpetualChase(k, forwardMostMove);
6823 ourPerpetual = PerpetualChase(k+1, forwardMostMove);
6824 if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
6825 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
6826 "Xboard adjudication: perpetual chasing", GE_XBOARD );
6829 if(hisPerpetual && !ourPerpetual) // he is chasing us, but did not repeat yet
6830 break; // Abort repetition-checking loop.
6832 // if neither of us is checking or chasing all the time, or both are, it is draw
6834 GameEnds( GameIsDrawn, "Xboard adjudication: repetition draw", GE_XBOARD );
6837 if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
6838 boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
6842 /* Now we test for 50-move draws. Determine ply count */
6843 count = forwardMostMove;
6844 /* look for last irreversble move */
6845 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
6847 /* if we hit starting position, add initial plies */
6848 if( count == backwardMostMove )
6849 count -= initialRulePlies;
6850 count = forwardMostMove - count;
6852 boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
6853 /* this is used to judge if draw claims are legal */
6854 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
6855 if(engineOpponent) {
6856 SendToProgram("force\n", engineOpponent); // suppress reply
6857 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6859 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6860 GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
6864 /* if draw offer is pending, treat it as a draw claim
6865 * when draw condition present, to allow engines a way to
6866 * claim draws before making their move to avoid a race
6867 * condition occurring after their move
6869 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
6871 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
6872 p = "Draw claim: 50-move rule";
6873 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
6874 p = "Draw claim: 3-fold repetition";
6875 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
6876 p = "Draw claim: insufficient mating material";
6877 if( p != NULL && canAdjudicate) {
6878 if(engineOpponent) {
6879 SendToProgram("force\n", engineOpponent); // suppress reply
6880 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6882 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6883 GameEnds( GameIsDrawn, p, GE_XBOARD );
6888 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
6889 if(engineOpponent) {
6890 SendToProgram("force\n", engineOpponent); // suppress reply
6891 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6893 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6894 GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
6900 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
6901 { // [HGM] book: this routine intercepts moves to simulate book replies
6902 char *bookHit = NULL;
6904 //first determine if the incoming move brings opponent into his book
6905 if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
6906 bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
6907 if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
6908 if(bookHit != NULL && !cps->bookSuspend) {
6909 // make sure opponent is not going to reply after receiving move to book position
6910 SendToProgram("force\n", cps);
6911 cps->bookSuspend = TRUE; // flag indicating it has to be restarted
6913 if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
6914 // now arrange restart after book miss
6916 // after a book hit we never send 'go', and the code after the call to this routine
6917 // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
6919 if (cps->useUsermove) sprintf(buf, "usermove "); // sorry, no SAN yet :(
6920 sprintf(buf, "%s\n", bookHit); // force book move into program supposed to play it
6921 SendToProgram(buf, cps);
6922 if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
6923 } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
6924 SendToProgram("go\n", cps);
6925 cps->bookSuspend = FALSE; // after a 'go' we are never suspended
6926 } else { // 'go' might be sent based on 'firstMove' after this routine returns
6927 if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
6928 SendToProgram("go\n", cps);
6929 cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
6931 return bookHit; // notify caller of hit, so it can take action to send move to opponent
6935 ChessProgramState *savedState;
6936 void DeferredBookMove(void)
6938 if(savedState->lastPing != savedState->lastPong)
6939 ScheduleDelayedEvent(DeferredBookMove, 10);
6941 HandleMachineMove(savedMessage, savedState);
6945 HandleMachineMove(message, cps)
6947 ChessProgramState *cps;
6949 char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
6950 char realname[MSG_SIZ];
6951 int fromX, fromY, toX, toY;
6960 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
6962 * Kludge to ignore BEL characters
6964 while (*message == '\007') message++;
6967 * [HGM] engine debug message: ignore lines starting with '#' character
6969 if(cps->debug && *message == '#') return;
6972 * Look for book output
6974 if (cps == &first && bookRequested) {
6975 if (message[0] == '\t' || message[0] == ' ') {
6976 /* Part of the book output is here; append it */
6977 strcat(bookOutput, message);
6978 strcat(bookOutput, " \n");
6980 } else if (bookOutput[0] != NULLCHAR) {
6981 /* All of book output has arrived; display it */
6982 char *p = bookOutput;
6983 while (*p != NULLCHAR) {
6984 if (*p == '\t') *p = ' ';
6987 DisplayInformation(bookOutput);
6988 bookRequested = FALSE;
6989 /* Fall through to parse the current output */
6994 * Look for machine move.
6996 if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
6997 (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
6999 /* This method is only useful on engines that support ping */
7000 if (cps->lastPing != cps->lastPong) {
7001 if (gameMode == BeginningOfGame) {
7002 /* Extra move from before last new; ignore */
7003 if (appData.debugMode) {
7004 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7007 if (appData.debugMode) {
7008 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7009 cps->which, gameMode);
7012 SendToProgram("undo\n", cps);
7018 case BeginningOfGame:
7019 /* Extra move from before last reset; ignore */
7020 if (appData.debugMode) {
7021 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7028 /* Extra move after we tried to stop. The mode test is
7029 not a reliable way of detecting this problem, but it's
7030 the best we can do on engines that don't support ping.
7032 if (appData.debugMode) {
7033 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7034 cps->which, gameMode);
7036 SendToProgram("undo\n", cps);
7039 case MachinePlaysWhite:
7040 case IcsPlayingWhite:
7041 machineWhite = TRUE;
7044 case MachinePlaysBlack:
7045 case IcsPlayingBlack:
7046 machineWhite = FALSE;
7049 case TwoMachinesPlay:
7050 machineWhite = (cps->twoMachinesColor[0] == 'w');
7053 if (WhiteOnMove(forwardMostMove) != machineWhite) {
7054 if (appData.debugMode) {
7056 "Ignoring move out of turn by %s, gameMode %d"
7057 ", forwardMost %d\n",
7058 cps->which, gameMode, forwardMostMove);
7063 if (appData.debugMode) { int f = forwardMostMove;
7064 fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
7065 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
7066 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
7068 if(cps->alphaRank) AlphaRank(machineMove, 4);
7069 if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
7070 &fromX, &fromY, &toX, &toY, &promoChar)) {
7071 /* Machine move could not be parsed; ignore it. */
7072 sprintf(buf1, _("Illegal move \"%s\" from %s machine"),
7073 machineMove, cps->which);
7074 DisplayError(buf1, 0);
7075 sprintf(buf1, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
7076 machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
7077 if (gameMode == TwoMachinesPlay) {
7078 GameEnds(machineWhite ? BlackWins : WhiteWins,
7084 /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
7085 /* So we have to redo legality test with true e.p. status here, */
7086 /* to make sure an illegal e.p. capture does not slip through, */
7087 /* to cause a forfeit on a justified illegal-move complaint */
7088 /* of the opponent. */
7089 if( gameMode==TwoMachinesPlay && appData.testLegality
7090 && fromY != DROP_RANK /* [HGM] temporary; should still add legality test for drops */
7093 moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
7094 fromY, fromX, toY, toX, promoChar);
7095 if (appData.debugMode) {
7097 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
7098 boards[forwardMostMove][CASTLING][i], castlingRank[i]);
7099 fprintf(debugFP, "castling rights\n");
7101 if(moveType == IllegalMove) {
7102 sprintf(buf1, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
7103 machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
7104 GameEnds(machineWhite ? BlackWins : WhiteWins,
7107 } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
7108 /* [HGM] Kludge to handle engines that send FRC-style castling
7109 when they shouldn't (like TSCP-Gothic) */
7111 case WhiteASideCastleFR:
7112 case BlackASideCastleFR:
7114 currentMoveString[2]++;
7116 case WhiteHSideCastleFR:
7117 case BlackHSideCastleFR:
7119 currentMoveString[2]--;
7121 default: ; // nothing to do, but suppresses warning of pedantic compilers
7124 hintRequested = FALSE;
7125 lastHint[0] = NULLCHAR;
7126 bookRequested = FALSE;
7127 /* Program may be pondering now */
7128 cps->maybeThinking = TRUE;
7129 if (cps->sendTime == 2) cps->sendTime = 1;
7130 if (cps->offeredDraw) cps->offeredDraw--;
7132 /* currentMoveString is set as a side-effect of ParseOneMove */
7133 strcpy(machineMove, currentMoveString);
7134 strcat(machineMove, "\n");
7135 strcpy(moveList[forwardMostMove], machineMove);
7137 MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
7139 /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
7140 if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
7143 while( count < adjudicateLossPlies ) {
7144 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
7147 score = -score; /* Flip score for winning side */
7150 if( score > adjudicateLossThreshold ) {
7157 if( count >= adjudicateLossPlies ) {
7158 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7160 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7161 "Xboard adjudication",
7168 if(Adjudicate(cps)) return; // [HGM] adjudicate: for all automatic game ends
7171 if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
7173 if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
7174 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
7176 SendMoveToICS(moveType, fromX, fromY, toX, toY);
7178 SendMoveToICS(moveType, fromX, fromY, toX, toY);
7180 if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
7181 char buf[3*MSG_SIZ];
7183 sprintf(buf, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
7184 programStats.score / 100.,
7186 programStats.time / 100.,
7187 (unsigned int)programStats.nodes,
7188 (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
7189 programStats.movelist);
7191 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
7196 /* [AS] Save move info and clear stats for next move */
7197 pvInfoList[ forwardMostMove-1 ].score = programStats.score;
7198 pvInfoList[ forwardMostMove-1 ].depth = programStats.depth;
7199 pvInfoList[ forwardMostMove-1 ].time = programStats.time; // [HGM] PGNtime: take time from engine stats
7200 ClearProgramStats();
7201 thinkOutput[0] = NULLCHAR;
7202 hiddenThinkOutputState = 0;
7205 if (gameMode == TwoMachinesPlay) {
7206 /* [HGM] relaying draw offers moved to after reception of move */
7207 /* and interpreting offer as claim if it brings draw condition */
7208 if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
7209 SendToProgram("draw\n", cps->other);
7211 if (cps->other->sendTime) {
7212 SendTimeRemaining(cps->other,
7213 cps->other->twoMachinesColor[0] == 'w');
7215 bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
7216 if (firstMove && !bookHit) {
7218 if (cps->other->useColors) {
7219 SendToProgram(cps->other->twoMachinesColor, cps->other);
7221 SendToProgram("go\n", cps->other);
7223 cps->other->maybeThinking = TRUE;
7226 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7228 if (!pausing && appData.ringBellAfterMoves) {
7233 * Reenable menu items that were disabled while
7234 * machine was thinking
7236 if (gameMode != TwoMachinesPlay)
7237 SetUserThinkingEnables();
7239 // [HGM] book: after book hit opponent has received move and is now in force mode
7240 // force the book reply into it, and then fake that it outputted this move by jumping
7241 // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
7243 static char bookMove[MSG_SIZ]; // a bit generous?
7245 strcpy(bookMove, "move ");
7246 strcat(bookMove, bookHit);
7249 programStats.nodes = programStats.depth = programStats.time =
7250 programStats.score = programStats.got_only_move = 0;
7251 sprintf(programStats.movelist, "%s (xbook)", bookHit);
7253 if(cps->lastPing != cps->lastPong) {
7254 savedMessage = message; // args for deferred call
7256 ScheduleDelayedEvent(DeferredBookMove, 10);
7265 /* Set special modes for chess engines. Later something general
7266 * could be added here; for now there is just one kludge feature,
7267 * needed because Crafty 15.10 and earlier don't ignore SIGINT
7268 * when "xboard" is given as an interactive command.
7270 if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
7271 cps->useSigint = FALSE;
7272 cps->useSigterm = FALSE;
7274 if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
7275 ParseFeatures(message+8, cps);
7276 return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
7279 /* [HGM] Allow engine to set up a position. Don't ask me why one would
7280 * want this, I was asked to put it in, and obliged.
7282 if (!strncmp(message, "setboard ", 9)) {
7283 Board initial_position;
7285 GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
7287 if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
7288 DisplayError(_("Bad FEN received from engine"), 0);
7292 CopyBoard(boards[0], initial_position);
7293 initialRulePlies = FENrulePlies;
7294 if(blackPlaysFirst) gameMode = MachinePlaysWhite;
7295 else gameMode = MachinePlaysBlack;
7296 DrawPosition(FALSE, boards[currentMove]);
7302 * Look for communication commands
7304 if (!strncmp(message, "telluser ", 9)) {
7305 DisplayNote(message + 9);
7308 if (!strncmp(message, "tellusererror ", 14)) {
7310 DisplayError(message + 14, 0);
7313 if (!strncmp(message, "tellopponent ", 13)) {
7314 if (appData.icsActive) {
7316 snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
7320 DisplayNote(message + 13);
7324 if (!strncmp(message, "tellothers ", 11)) {
7325 if (appData.icsActive) {
7327 snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
7333 if (!strncmp(message, "tellall ", 8)) {
7334 if (appData.icsActive) {
7336 snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
7340 DisplayNote(message + 8);
7344 if (strncmp(message, "warning", 7) == 0) {
7345 /* Undocumented feature, use tellusererror in new code */
7346 DisplayError(message, 0);
7349 if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
7350 strcpy(realname, cps->tidy);
7351 strcat(realname, " query");
7352 AskQuestion(realname, buf2, buf1, cps->pr);
7355 /* Commands from the engine directly to ICS. We don't allow these to be
7356 * sent until we are logged on. Crafty kibitzes have been known to
7357 * interfere with the login process.
7360 if (!strncmp(message, "tellics ", 8)) {
7361 SendToICS(message + 8);
7365 if (!strncmp(message, "tellicsnoalias ", 15)) {
7366 SendToICS(ics_prefix);
7367 SendToICS(message + 15);
7371 /* The following are for backward compatibility only */
7372 if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
7373 !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
7374 SendToICS(ics_prefix);
7380 if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
7384 * If the move is illegal, cancel it and redraw the board.
7385 * Also deal with other error cases. Matching is rather loose
7386 * here to accommodate engines written before the spec.
7388 if (strncmp(message + 1, "llegal move", 11) == 0 ||
7389 strncmp(message, "Error", 5) == 0) {
7390 if (StrStr(message, "name") ||
7391 StrStr(message, "rating") || StrStr(message, "?") ||
7392 StrStr(message, "result") || StrStr(message, "board") ||
7393 StrStr(message, "bk") || StrStr(message, "computer") ||
7394 StrStr(message, "variant") || StrStr(message, "hint") ||
7395 StrStr(message, "random") || StrStr(message, "depth") ||
7396 StrStr(message, "accepted")) {
7399 if (StrStr(message, "protover")) {
7400 /* Program is responding to input, so it's apparently done
7401 initializing, and this error message indicates it is
7402 protocol version 1. So we don't need to wait any longer
7403 for it to initialize and send feature commands. */
7404 FeatureDone(cps, 1);
7405 cps->protocolVersion = 1;
7408 cps->maybeThinking = FALSE;
7410 if (StrStr(message, "draw")) {
7411 /* Program doesn't have "draw" command */
7412 cps->sendDrawOffers = 0;
7415 if (cps->sendTime != 1 &&
7416 (StrStr(message, "time") || StrStr(message, "otim"))) {
7417 /* Program apparently doesn't have "time" or "otim" command */
7421 if (StrStr(message, "analyze")) {
7422 cps->analysisSupport = FALSE;
7423 cps->analyzing = FALSE;
7425 sprintf(buf2, _("%s does not support analysis"), cps->tidy);
7426 DisplayError(buf2, 0);
7429 if (StrStr(message, "(no matching move)st")) {
7430 /* Special kludge for GNU Chess 4 only */
7431 cps->stKludge = TRUE;
7432 SendTimeControl(cps, movesPerSession, timeControl,
7433 timeIncrement, appData.searchDepth,
7437 if (StrStr(message, "(no matching move)sd")) {
7438 /* Special kludge for GNU Chess 4 only */
7439 cps->sdKludge = TRUE;
7440 SendTimeControl(cps, movesPerSession, timeControl,
7441 timeIncrement, appData.searchDepth,
7445 if (!StrStr(message, "llegal")) {
7448 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
7449 gameMode == IcsIdle) return;
7450 if (forwardMostMove <= backwardMostMove) return;
7451 if (pausing) PauseEvent();
7452 if(appData.forceIllegal) {
7453 // [HGM] illegal: machine refused move; force position after move into it
7454 SendToProgram("force\n", cps);
7455 if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
7456 // we have a real problem now, as SendBoard will use the a2a3 kludge
7457 // when black is to move, while there might be nothing on a2 or black
7458 // might already have the move. So send the board as if white has the move.
7459 // But first we must change the stm of the engine, as it refused the last move
7460 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
7461 if(WhiteOnMove(forwardMostMove)) {
7462 SendToProgram("a7a6\n", cps); // for the engine black still had the move
7463 SendBoard(cps, forwardMostMove); // kludgeless board
7465 SendToProgram("a2a3\n", cps); // for the engine white still had the move
7466 CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
7467 SendBoard(cps, forwardMostMove+1); // kludgeless board
7469 } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
7470 if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
7471 gameMode == TwoMachinesPlay)
7472 SendToProgram("go\n", cps);
7475 if (gameMode == PlayFromGameFile) {
7476 /* Stop reading this game file */
7477 gameMode = EditGame;
7480 currentMove = forwardMostMove-1;
7481 DisplayMove(currentMove-1); /* before DisplayMoveError */
7482 SwitchClocks(forwardMostMove-1); // [HGM] race
7483 DisplayBothClocks();
7484 sprintf(buf1, _("Illegal move \"%s\" (rejected by %s chess program)"),
7485 parseList[currentMove], cps->which);
7486 DisplayMoveError(buf1);
7487 DrawPosition(FALSE, boards[currentMove]);
7489 /* [HGM] illegal-move claim should forfeit game when Xboard */
7490 /* only passes fully legal moves */
7491 if( appData.testLegality && gameMode == TwoMachinesPlay ) {
7492 GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
7493 "False illegal-move claim", GE_XBOARD );
7497 if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
7498 /* Program has a broken "time" command that
7499 outputs a string not ending in newline.
7505 * If chess program startup fails, exit with an error message.
7506 * Attempts to recover here are futile.
7508 if ((StrStr(message, "unknown host") != NULL)
7509 || (StrStr(message, "No remote directory") != NULL)
7510 || (StrStr(message, "not found") != NULL)
7511 || (StrStr(message, "No such file") != NULL)
7512 || (StrStr(message, "can't alloc") != NULL)
7513 || (StrStr(message, "Permission denied") != NULL)) {
7515 cps->maybeThinking = FALSE;
7516 snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
7517 cps->which, cps->program, cps->host, message);
7518 RemoveInputSource(cps->isr);
7519 DisplayFatalError(buf1, 0, 1);
7524 * Look for hint output
7526 if (sscanf(message, "Hint: %s", buf1) == 1) {
7527 if (cps == &first && hintRequested) {
7528 hintRequested = FALSE;
7529 if (ParseOneMove(buf1, forwardMostMove, &moveType,
7530 &fromX, &fromY, &toX, &toY, &promoChar)) {
7531 (void) CoordsToAlgebraic(boards[forwardMostMove],
7532 PosFlags(forwardMostMove),
7533 fromY, fromX, toY, toX, promoChar, buf1);
7534 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
7535 DisplayInformation(buf2);
7537 /* Hint move could not be parsed!? */
7538 snprintf(buf2, sizeof(buf2),
7539 _("Illegal hint move \"%s\"\nfrom %s chess program"),
7541 DisplayError(buf2, 0);
7544 strcpy(lastHint, buf1);
7550 * Ignore other messages if game is not in progress
7552 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
7553 gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
7556 * look for win, lose, draw, or draw offer
7558 if (strncmp(message, "1-0", 3) == 0) {
7559 char *p, *q, *r = "";
7560 p = strchr(message, '{');
7568 GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
7570 } else if (strncmp(message, "0-1", 3) == 0) {
7571 char *p, *q, *r = "";
7572 p = strchr(message, '{');
7580 /* Kludge for Arasan 4.1 bug */
7581 if (strcmp(r, "Black resigns") == 0) {
7582 GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
7585 GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
7587 } else if (strncmp(message, "1/2", 3) == 0) {
7588 char *p, *q, *r = "";
7589 p = strchr(message, '{');
7598 GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
7601 } else if (strncmp(message, "White resign", 12) == 0) {
7602 GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
7604 } else if (strncmp(message, "Black resign", 12) == 0) {
7605 GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
7607 } else if (strncmp(message, "White matches", 13) == 0 ||
7608 strncmp(message, "Black matches", 13) == 0 ) {
7609 /* [HGM] ignore GNUShogi noises */
7611 } else if (strncmp(message, "White", 5) == 0 &&
7612 message[5] != '(' &&
7613 StrStr(message, "Black") == NULL) {
7614 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7616 } else if (strncmp(message, "Black", 5) == 0 &&
7617 message[5] != '(') {
7618 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7620 } else if (strcmp(message, "resign") == 0 ||
7621 strcmp(message, "computer resigns") == 0) {
7623 case MachinePlaysBlack:
7624 case IcsPlayingBlack:
7625 GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
7627 case MachinePlaysWhite:
7628 case IcsPlayingWhite:
7629 GameEnds(BlackWins, "White resigns", GE_ENGINE);
7631 case TwoMachinesPlay:
7632 if (cps->twoMachinesColor[0] == 'w')
7633 GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
7635 GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
7642 } else if (strncmp(message, "opponent mates", 14) == 0) {
7644 case MachinePlaysBlack:
7645 case IcsPlayingBlack:
7646 GameEnds(WhiteWins, "White mates", GE_ENGINE);
7648 case MachinePlaysWhite:
7649 case IcsPlayingWhite:
7650 GameEnds(BlackWins, "Black mates", GE_ENGINE);
7652 case TwoMachinesPlay:
7653 if (cps->twoMachinesColor[0] == 'w')
7654 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7656 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7663 } else if (strncmp(message, "computer mates", 14) == 0) {
7665 case MachinePlaysBlack:
7666 case IcsPlayingBlack:
7667 GameEnds(BlackWins, "Black mates", GE_ENGINE1);
7669 case MachinePlaysWhite:
7670 case IcsPlayingWhite:
7671 GameEnds(WhiteWins, "White mates", GE_ENGINE);
7673 case TwoMachinesPlay:
7674 if (cps->twoMachinesColor[0] == 'w')
7675 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7677 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7684 } else if (strncmp(message, "checkmate", 9) == 0) {
7685 if (WhiteOnMove(forwardMostMove)) {
7686 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7688 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7691 } else if (strstr(message, "Draw") != NULL ||
7692 strstr(message, "game is a draw") != NULL) {
7693 GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
7695 } else if (strstr(message, "offer") != NULL &&
7696 strstr(message, "draw") != NULL) {
7698 if (appData.zippyPlay && first.initDone) {
7699 /* Relay offer to ICS */
7700 SendToICS(ics_prefix);
7701 SendToICS("draw\n");
7704 cps->offeredDraw = 2; /* valid until this engine moves twice */
7705 if (gameMode == TwoMachinesPlay) {
7706 if (cps->other->offeredDraw) {
7707 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
7708 /* [HGM] in two-machine mode we delay relaying draw offer */
7709 /* until after we also have move, to see if it is really claim */
7711 } else if (gameMode == MachinePlaysWhite ||
7712 gameMode == MachinePlaysBlack) {
7713 if (userOfferedDraw) {
7714 DisplayInformation(_("Machine accepts your draw offer"));
7715 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
7717 DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
7724 * Look for thinking output
7726 if ( appData.showThinking // [HGM] thinking: test all options that cause this output
7727 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
7729 int plylev, mvleft, mvtot, curscore, time;
7730 char mvname[MOVE_LEN];
7734 int prefixHint = FALSE;
7735 mvname[0] = NULLCHAR;
7738 case MachinePlaysBlack:
7739 case IcsPlayingBlack:
7740 if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
7742 case MachinePlaysWhite:
7743 case IcsPlayingWhite:
7744 if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
7749 case IcsObserving: /* [DM] icsEngineAnalyze */
7750 if (!appData.icsEngineAnalyze) ignore = TRUE;
7752 case TwoMachinesPlay:
7753 if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
7764 if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
7765 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
7767 if (plyext != ' ' && plyext != '\t') {
7771 /* [AS] Negate score if machine is playing black and reporting absolute scores */
7772 if( cps->scoreIsAbsolute &&
7773 ( gameMode == MachinePlaysBlack ||
7774 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
7775 gameMode == IcsPlayingBlack || // [HGM] also add other situations where engine should report black POV
7776 (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
7777 !WhiteOnMove(currentMove)
7780 curscore = -curscore;
7784 programStats.depth = plylev;
7785 programStats.nodes = nodes;
7786 programStats.time = time;
7787 programStats.score = curscore;
7788 programStats.got_only_move = 0;
7790 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
7793 if(cps->nps == 0) ticklen = 10*time; // use engine reported time
7794 else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
7795 if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
7796 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
7797 whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
7798 if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
7799 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
7800 blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
7803 /* Buffer overflow protection */
7804 if (buf1[0] != NULLCHAR) {
7805 if (strlen(buf1) >= sizeof(programStats.movelist)
7806 && appData.debugMode) {
7808 "PV is too long; using the first %u bytes.\n",
7809 (unsigned) sizeof(programStats.movelist) - 1);
7812 safeStrCpy( programStats.movelist, buf1, sizeof(programStats.movelist) );
7814 sprintf(programStats.movelist, " no PV\n");
7817 if (programStats.seen_stat) {
7818 programStats.ok_to_send = 1;
7821 if (strchr(programStats.movelist, '(') != NULL) {
7822 programStats.line_is_book = 1;
7823 programStats.nr_moves = 0;
7824 programStats.moves_left = 0;
7826 programStats.line_is_book = 0;
7829 SendProgramStatsToFrontend( cps, &programStats );
7832 [AS] Protect the thinkOutput buffer from overflow... this
7833 is only useful if buf1 hasn't overflowed first!
7835 sprintf(thinkOutput, "[%d]%c%+.2f %s%s",
7837 (gameMode == TwoMachinesPlay ?
7838 ToUpper(cps->twoMachinesColor[0]) : ' '),
7839 ((double) curscore) / 100.0,
7840 prefixHint ? lastHint : "",
7841 prefixHint ? " " : "" );
7843 if( buf1[0] != NULLCHAR ) {
7844 unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
7846 if( strlen(buf1) > max_len ) {
7847 if( appData.debugMode) {
7848 fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
7850 buf1[max_len+1] = '\0';
7853 strcat( thinkOutput, buf1 );
7856 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
7857 || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7858 DisplayMove(currentMove - 1);
7862 } else if ((p=StrStr(message, "(only move)")) != NULL) {
7863 /* crafty (9.25+) says "(only move) <move>"
7864 * if there is only 1 legal move
7866 sscanf(p, "(only move) %s", buf1);
7867 sprintf(thinkOutput, "%s (only move)", buf1);
7868 sprintf(programStats.movelist, "%s (only move)", buf1);
7869 programStats.depth = 1;
7870 programStats.nr_moves = 1;
7871 programStats.moves_left = 1;
7872 programStats.nodes = 1;
7873 programStats.time = 1;
7874 programStats.got_only_move = 1;
7876 /* Not really, but we also use this member to
7877 mean "line isn't going to change" (Crafty
7878 isn't searching, so stats won't change) */
7879 programStats.line_is_book = 1;
7881 SendProgramStatsToFrontend( cps, &programStats );
7883 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
7884 gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7885 DisplayMove(currentMove - 1);
7888 } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
7889 &time, &nodes, &plylev, &mvleft,
7890 &mvtot, mvname) >= 5) {
7891 /* The stat01: line is from Crafty (9.29+) in response
7892 to the "." command */
7893 programStats.seen_stat = 1;
7894 cps->maybeThinking = TRUE;
7896 if (programStats.got_only_move || !appData.periodicUpdates)
7899 programStats.depth = plylev;
7900 programStats.time = time;
7901 programStats.nodes = nodes;
7902 programStats.moves_left = mvleft;
7903 programStats.nr_moves = mvtot;
7904 strcpy(programStats.move_name, mvname);
7905 programStats.ok_to_send = 1;
7906 programStats.movelist[0] = '\0';
7908 SendProgramStatsToFrontend( cps, &programStats );
7912 } else if (strncmp(message,"++",2) == 0) {
7913 /* Crafty 9.29+ outputs this */
7914 programStats.got_fail = 2;
7917 } else if (strncmp(message,"--",2) == 0) {
7918 /* Crafty 9.29+ outputs this */
7919 programStats.got_fail = 1;
7922 } else if (thinkOutput[0] != NULLCHAR &&
7923 strncmp(message, " ", 4) == 0) {
7924 unsigned message_len;
7927 while (*p && *p == ' ') p++;
7929 message_len = strlen( p );
7931 /* [AS] Avoid buffer overflow */
7932 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
7933 strcat(thinkOutput, " ");
7934 strcat(thinkOutput, p);
7937 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
7938 strcat(programStats.movelist, " ");
7939 strcat(programStats.movelist, p);
7942 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
7943 gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7944 DisplayMove(currentMove - 1);
7952 if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
7953 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
7955 ChessProgramStats cpstats;
7957 if (plyext != ' ' && plyext != '\t') {
7961 /* [AS] Negate score if machine is playing black and reporting absolute scores */
7962 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
7963 curscore = -curscore;
7966 cpstats.depth = plylev;
7967 cpstats.nodes = nodes;
7968 cpstats.time = time;
7969 cpstats.score = curscore;
7970 cpstats.got_only_move = 0;
7971 cpstats.movelist[0] = '\0';
7973 if (buf1[0] != NULLCHAR) {
7974 safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist) );
7977 cpstats.ok_to_send = 0;
7978 cpstats.line_is_book = 0;
7979 cpstats.nr_moves = 0;
7980 cpstats.moves_left = 0;
7982 SendProgramStatsToFrontend( cps, &cpstats );
7989 /* Parse a game score from the character string "game", and
7990 record it as the history of the current game. The game
7991 score is NOT assumed to start from the standard position.
7992 The display is not updated in any way.
7995 ParseGameHistory(game)
7999 int fromX, fromY, toX, toY, boardIndex;
8004 if (appData.debugMode)
8005 fprintf(debugFP, "Parsing game history: %s\n", game);
8007 if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
8008 gameInfo.site = StrSave(appData.icsHost);
8009 gameInfo.date = PGNDate();
8010 gameInfo.round = StrSave("-");
8012 /* Parse out names of players */
8013 while (*game == ' ') game++;
8015 while (*game != ' ') *p++ = *game++;
8017 gameInfo.white = StrSave(buf);
8018 while (*game == ' ') game++;
8020 while (*game != ' ' && *game != '\n') *p++ = *game++;
8022 gameInfo.black = StrSave(buf);
8025 boardIndex = blackPlaysFirst ? 1 : 0;
8028 yyboardindex = boardIndex;
8029 moveType = (ChessMove) yylex();
8031 case IllegalMove: /* maybe suicide chess, etc. */
8032 if (appData.debugMode) {
8033 fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
8034 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8035 setbuf(debugFP, NULL);
8037 case WhitePromotionChancellor:
8038 case BlackPromotionChancellor:
8039 case WhitePromotionArchbishop:
8040 case BlackPromotionArchbishop:
8041 case WhitePromotionQueen:
8042 case BlackPromotionQueen:
8043 case WhitePromotionRook:
8044 case BlackPromotionRook:
8045 case WhitePromotionBishop:
8046 case BlackPromotionBishop:
8047 case WhitePromotionKnight:
8048 case BlackPromotionKnight:
8049 case WhitePromotionKing:
8050 case BlackPromotionKing:
8052 case WhiteCapturesEnPassant:
8053 case BlackCapturesEnPassant:
8054 case WhiteKingSideCastle:
8055 case WhiteQueenSideCastle:
8056 case BlackKingSideCastle:
8057 case BlackQueenSideCastle:
8058 case WhiteKingSideCastleWild:
8059 case WhiteQueenSideCastleWild:
8060 case BlackKingSideCastleWild:
8061 case BlackQueenSideCastleWild:
8063 case WhiteHSideCastleFR:
8064 case WhiteASideCastleFR:
8065 case BlackHSideCastleFR:
8066 case BlackASideCastleFR:
8068 fromX = currentMoveString[0] - AAA;
8069 fromY = currentMoveString[1] - ONE;
8070 toX = currentMoveString[2] - AAA;
8071 toY = currentMoveString[3] - ONE;
8072 promoChar = currentMoveString[4];
8076 fromX = moveType == WhiteDrop ?
8077 (int) CharToPiece(ToUpper(currentMoveString[0])) :
8078 (int) CharToPiece(ToLower(currentMoveString[0]));
8080 toX = currentMoveString[2] - AAA;
8081 toY = currentMoveString[3] - ONE;
8082 promoChar = NULLCHAR;
8086 sprintf(buf, _("Ambiguous move in ICS output: \"%s\""), yy_text);
8087 if (appData.debugMode) {
8088 fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
8089 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8090 setbuf(debugFP, NULL);
8092 DisplayError(buf, 0);
8094 case ImpossibleMove:
8096 sprintf(buf, _("Illegal move in ICS output: \"%s\""), yy_text);
8097 if (appData.debugMode) {
8098 fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
8099 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8100 setbuf(debugFP, NULL);
8102 DisplayError(buf, 0);
8104 case (ChessMove) 0: /* end of file */
8105 if (boardIndex < backwardMostMove) {
8106 /* Oops, gap. How did that happen? */
8107 DisplayError(_("Gap in move list"), 0);
8110 backwardMostMove = blackPlaysFirst ? 1 : 0;
8111 if (boardIndex > forwardMostMove) {
8112 forwardMostMove = boardIndex;
8116 if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
8117 strcat(parseList[boardIndex-1], " ");
8118 strcat(parseList[boardIndex-1], yy_text);
8130 case GameUnfinished:
8131 if (gameMode == IcsExamining) {
8132 if (boardIndex < backwardMostMove) {
8133 /* Oops, gap. How did that happen? */
8136 backwardMostMove = blackPlaysFirst ? 1 : 0;
8139 gameInfo.result = moveType;
8140 p = strchr(yy_text, '{');
8141 if (p == NULL) p = strchr(yy_text, '(');
8144 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
8146 q = strchr(p, *p == '{' ? '}' : ')');
8147 if (q != NULL) *q = NULLCHAR;
8150 gameInfo.resultDetails = StrSave(p);
8153 if (boardIndex >= forwardMostMove &&
8154 !(gameMode == IcsObserving && ics_gamenum == -1)) {
8155 backwardMostMove = blackPlaysFirst ? 1 : 0;
8158 (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
8159 fromY, fromX, toY, toX, promoChar,
8160 parseList[boardIndex]);
8161 CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
8162 /* currentMoveString is set as a side-effect of yylex */
8163 strcpy(moveList[boardIndex], currentMoveString);
8164 strcat(moveList[boardIndex], "\n");
8166 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
8167 switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
8173 if(gameInfo.variant != VariantShogi)
8174 strcat(parseList[boardIndex - 1], "+");
8178 strcat(parseList[boardIndex - 1], "#");
8185 /* Apply a move to the given board */
8187 ApplyMove(fromX, fromY, toX, toY, promoChar, board)
8188 int fromX, fromY, toX, toY;
8192 ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
8193 int promoRank = gameInfo.variant == VariantMakruk ? 3 : 1;
8195 /* [HGM] compute & store e.p. status and castling rights for new position */
8196 /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
8199 if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
8200 oldEP = (signed char)board[EP_STATUS];
8201 board[EP_STATUS] = EP_NONE;
8203 if( board[toY][toX] != EmptySquare )
8204 board[EP_STATUS] = EP_CAPTURE;
8206 if( board[fromY][fromX] == WhitePawn ) {
8207 if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
8208 board[EP_STATUS] = EP_PAWN_MOVE;
8210 if(toX>BOARD_LEFT && board[toY][toX-1] == BlackPawn &&
8211 gameInfo.variant != VariantBerolina || toX < fromX)
8212 board[EP_STATUS] = toX | berolina;
8213 if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
8214 gameInfo.variant != VariantBerolina || toX > fromX)
8215 board[EP_STATUS] = toX;
8218 if( board[fromY][fromX] == BlackPawn ) {
8219 if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
8220 board[EP_STATUS] = EP_PAWN_MOVE;
8221 if( toY-fromY== -2) {
8222 if(toX>BOARD_LEFT && board[toY][toX-1] == WhitePawn &&
8223 gameInfo.variant != VariantBerolina || toX < fromX)
8224 board[EP_STATUS] = toX | berolina;
8225 if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
8226 gameInfo.variant != VariantBerolina || toX > fromX)
8227 board[EP_STATUS] = toX;
8231 for(i=0; i<nrCastlingRights; i++) {
8232 if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
8233 board[CASTLING][i] == toX && castlingRank[i] == toY
8234 ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
8239 /* [HGM] In Shatranj and Courier all promotions are to Ferz */
8240 if((gameInfo.variant==VariantShatranj || gameInfo.variant==VariantCourier || gameInfo.variant == VariantMakruk)
8241 && promoChar != 0) promoChar = PieceToChar(WhiteFerz);
8243 if (fromX == toX && fromY == toY) return;
8245 if (fromY == DROP_RANK) {
8247 piece = board[toY][toX] = (ChessSquare) fromX;
8249 piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
8250 king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
8251 if(gameInfo.variant == VariantKnightmate)
8252 king += (int) WhiteUnicorn - (int) WhiteKing;
8254 /* Code added by Tord: */
8255 /* FRC castling assumed when king captures friendly rook. */
8256 if (board[fromY][fromX] == WhiteKing &&
8257 board[toY][toX] == WhiteRook) {
8258 board[fromY][fromX] = EmptySquare;
8259 board[toY][toX] = EmptySquare;
8261 board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
8263 board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
8265 } else if (board[fromY][fromX] == BlackKing &&
8266 board[toY][toX] == BlackRook) {
8267 board[fromY][fromX] = EmptySquare;
8268 board[toY][toX] = EmptySquare;
8270 board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
8272 board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
8274 /* End of code added by Tord */
8276 } else if (board[fromY][fromX] == king
8277 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8278 && toY == fromY && toX > fromX+1) {
8279 board[fromY][fromX] = EmptySquare;
8280 board[toY][toX] = king;
8281 board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
8282 board[fromY][BOARD_RGHT-1] = EmptySquare;
8283 } else if (board[fromY][fromX] == king
8284 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8285 && toY == fromY && toX < fromX-1) {
8286 board[fromY][fromX] = EmptySquare;
8287 board[toY][toX] = king;
8288 board[toY][toX+1] = board[fromY][BOARD_LEFT];
8289 board[fromY][BOARD_LEFT] = EmptySquare;
8290 } else if (board[fromY][fromX] == WhitePawn
8291 && toY >= BOARD_HEIGHT-promoRank
8292 && gameInfo.variant != VariantXiangqi
8294 /* white pawn promotion */
8295 board[toY][toX] = CharToPiece(ToUpper(promoChar));
8296 if (board[toY][toX] == EmptySquare) {
8297 board[toY][toX] = WhiteQueen;
8299 if(gameInfo.variant==VariantBughouse ||
8300 gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
8301 board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
8302 board[fromY][fromX] = EmptySquare;
8303 } else if ((fromY == BOARD_HEIGHT-4)
8305 && gameInfo.variant != VariantXiangqi
8306 && gameInfo.variant != VariantBerolina
8307 && (board[fromY][fromX] == WhitePawn)
8308 && (board[toY][toX] == EmptySquare)) {
8309 board[fromY][fromX] = EmptySquare;
8310 board[toY][toX] = WhitePawn;
8311 captured = board[toY - 1][toX];
8312 board[toY - 1][toX] = EmptySquare;
8313 } else if ((fromY == BOARD_HEIGHT-4)
8315 && gameInfo.variant == VariantBerolina
8316 && (board[fromY][fromX] == WhitePawn)
8317 && (board[toY][toX] == EmptySquare)) {
8318 board[fromY][fromX] = EmptySquare;
8319 board[toY][toX] = WhitePawn;
8320 if(oldEP & EP_BEROLIN_A) {
8321 captured = board[fromY][fromX-1];
8322 board[fromY][fromX-1] = EmptySquare;
8323 }else{ captured = board[fromY][fromX+1];
8324 board[fromY][fromX+1] = EmptySquare;
8326 } else if (board[fromY][fromX] == king
8327 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8328 && toY == fromY && toX > fromX+1) {
8329 board[fromY][fromX] = EmptySquare;
8330 board[toY][toX] = king;
8331 board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
8332 board[fromY][BOARD_RGHT-1] = EmptySquare;
8333 } else if (board[fromY][fromX] == king
8334 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8335 && toY == fromY && toX < fromX-1) {
8336 board[fromY][fromX] = EmptySquare;
8337 board[toY][toX] = king;
8338 board[toY][toX+1] = board[fromY][BOARD_LEFT];
8339 board[fromY][BOARD_LEFT] = EmptySquare;
8340 } else if (fromY == 7 && fromX == 3
8341 && board[fromY][fromX] == BlackKing
8342 && toY == 7 && toX == 5) {
8343 board[fromY][fromX] = EmptySquare;
8344 board[toY][toX] = BlackKing;
8345 board[fromY][7] = EmptySquare;
8346 board[toY][4] = BlackRook;
8347 } else if (fromY == 7 && fromX == 3
8348 && board[fromY][fromX] == BlackKing
8349 && toY == 7 && toX == 1) {
8350 board[fromY][fromX] = EmptySquare;
8351 board[toY][toX] = BlackKing;
8352 board[fromY][0] = EmptySquare;
8353 board[toY][2] = BlackRook;
8354 } else if (board[fromY][fromX] == BlackPawn
8356 && gameInfo.variant != VariantXiangqi
8358 /* black pawn promotion */
8359 board[toY][toX] = CharToPiece(ToLower(promoChar));
8360 if (board[toY][toX] == EmptySquare) {
8361 board[toY][toX] = BlackQueen;
8363 if(gameInfo.variant==VariantBughouse ||
8364 gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
8365 board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
8366 board[fromY][fromX] = EmptySquare;
8367 } else if ((fromY == 3)
8369 && gameInfo.variant != VariantXiangqi
8370 && gameInfo.variant != VariantBerolina
8371 && (board[fromY][fromX] == BlackPawn)
8372 && (board[toY][toX] == EmptySquare)) {
8373 board[fromY][fromX] = EmptySquare;
8374 board[toY][toX] = BlackPawn;
8375 captured = board[toY + 1][toX];
8376 board[toY + 1][toX] = EmptySquare;
8377 } else if ((fromY == 3)
8379 && gameInfo.variant == VariantBerolina
8380 && (board[fromY][fromX] == BlackPawn)
8381 && (board[toY][toX] == EmptySquare)) {
8382 board[fromY][fromX] = EmptySquare;
8383 board[toY][toX] = BlackPawn;
8384 if(oldEP & EP_BEROLIN_A) {
8385 captured = board[fromY][fromX-1];
8386 board[fromY][fromX-1] = EmptySquare;
8387 }else{ captured = board[fromY][fromX+1];
8388 board[fromY][fromX+1] = EmptySquare;
8391 board[toY][toX] = board[fromY][fromX];
8392 board[fromY][fromX] = EmptySquare;
8395 /* [HGM] now we promote for Shogi, if needed */
8396 if(gameInfo.variant == VariantShogi && promoChar == 'q')
8397 board[toY][toX] = (ChessSquare) (PROMOTED piece);
8400 if (gameInfo.holdingsWidth != 0) {
8402 /* !!A lot more code needs to be written to support holdings */
8403 /* [HGM] OK, so I have written it. Holdings are stored in the */
8404 /* penultimate board files, so they are automaticlly stored */
8405 /* in the game history. */
8406 if (fromY == DROP_RANK) {
8407 /* Delete from holdings, by decreasing count */
8408 /* and erasing image if necessary */
8410 if(p < (int) BlackPawn) { /* white drop */
8411 p -= (int)WhitePawn;
8412 p = PieceToNumber((ChessSquare)p);
8413 if(p >= gameInfo.holdingsSize) p = 0;
8414 if(--board[p][BOARD_WIDTH-2] <= 0)
8415 board[p][BOARD_WIDTH-1] = EmptySquare;
8416 if((int)board[p][BOARD_WIDTH-2] < 0)
8417 board[p][BOARD_WIDTH-2] = 0;
8418 } else { /* black drop */
8419 p -= (int)BlackPawn;
8420 p = PieceToNumber((ChessSquare)p);
8421 if(p >= gameInfo.holdingsSize) p = 0;
8422 if(--board[BOARD_HEIGHT-1-p][1] <= 0)
8423 board[BOARD_HEIGHT-1-p][0] = EmptySquare;
8424 if((int)board[BOARD_HEIGHT-1-p][1] < 0)
8425 board[BOARD_HEIGHT-1-p][1] = 0;
8428 if (captured != EmptySquare && gameInfo.holdingsSize > 0
8429 && gameInfo.variant != VariantBughouse ) {
8430 /* [HGM] holdings: Add to holdings, if holdings exist */
8431 if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
8432 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
8433 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
8436 if (p >= (int) BlackPawn) {
8437 p -= (int)BlackPawn;
8438 if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
8439 /* in Shogi restore piece to its original first */
8440 captured = (ChessSquare) (DEMOTED captured);
8443 p = PieceToNumber((ChessSquare)p);
8444 if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
8445 board[p][BOARD_WIDTH-2]++;
8446 board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
8448 p -= (int)WhitePawn;
8449 if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
8450 captured = (ChessSquare) (DEMOTED captured);
8453 p = PieceToNumber((ChessSquare)p);
8454 if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
8455 board[BOARD_HEIGHT-1-p][1]++;
8456 board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
8459 } else if (gameInfo.variant == VariantAtomic) {
8460 if (captured != EmptySquare) {
8462 for (y = toY-1; y <= toY+1; y++) {
8463 for (x = toX-1; x <= toX+1; x++) {
8464 if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
8465 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
8466 board[y][x] = EmptySquare;
8470 board[toY][toX] = EmptySquare;
8473 if(gameInfo.variant == VariantShogi && promoChar != NULLCHAR && promoChar != '=') {
8474 /* [HGM] Shogi promotions */
8475 board[toY][toX] = (ChessSquare) (PROMOTED piece);
8478 if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
8479 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
8480 // [HGM] superchess: take promotion piece out of holdings
8481 int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
8482 if((int)piece < (int)BlackPawn) { // determine stm from piece color
8483 if(!--board[k][BOARD_WIDTH-2])
8484 board[k][BOARD_WIDTH-1] = EmptySquare;
8486 if(!--board[BOARD_HEIGHT-1-k][1])
8487 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
8493 /* Updates forwardMostMove */
8495 MakeMove(fromX, fromY, toX, toY, promoChar)
8496 int fromX, fromY, toX, toY;
8499 // forwardMostMove++; // [HGM] bare: moved downstream
8501 if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
8502 int timeLeft; static int lastLoadFlag=0; int king, piece;
8503 piece = boards[forwardMostMove][fromY][fromX];
8504 king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
8505 if(gameInfo.variant == VariantKnightmate)
8506 king += (int) WhiteUnicorn - (int) WhiteKing;
8507 if(forwardMostMove == 0) {
8509 fprintf(serverMoves, "%s;", second.tidy);
8510 fprintf(serverMoves, "%s;", first.tidy);
8511 if(!blackPlaysFirst)
8512 fprintf(serverMoves, "%s;", second.tidy);
8513 } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
8514 lastLoadFlag = loadFlag;
8516 fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
8517 // print castling suffix
8518 if( toY == fromY && piece == king ) {
8520 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
8522 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
8525 if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
8526 boards[forwardMostMove][fromY][fromX] == BlackPawn ) &&
8527 boards[forwardMostMove][toY][toX] == EmptySquare
8529 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
8531 if(promoChar != NULLCHAR)
8532 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);
8534 fprintf(serverMoves, "/%d/%d",
8535 pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
8536 if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
8537 else timeLeft = blackTimeRemaining/1000;
8538 fprintf(serverMoves, "/%d", timeLeft);
8540 fflush(serverMoves);
8543 if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations
8544 DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
8548 UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
8549 if (commentList[forwardMostMove+1] != NULL) {
8550 free(commentList[forwardMostMove+1]);
8551 commentList[forwardMostMove+1] = NULL;
8553 CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
8554 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
8555 // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
8556 SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
8557 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
8558 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
8559 gameInfo.result = GameUnfinished;
8560 if (gameInfo.resultDetails != NULL) {
8561 free(gameInfo.resultDetails);
8562 gameInfo.resultDetails = NULL;
8564 CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
8565 moveList[forwardMostMove - 1]);
8566 (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
8567 PosFlags(forwardMostMove - 1),
8568 fromY, fromX, toY, toX, promoChar,
8569 parseList[forwardMostMove - 1]);
8570 switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
8576 if(gameInfo.variant != VariantShogi)
8577 strcat(parseList[forwardMostMove - 1], "+");
8581 strcat(parseList[forwardMostMove - 1], "#");
8584 if (appData.debugMode) {
8585 fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
8590 /* Updates currentMove if not pausing */
8592 ShowMove(fromX, fromY, toX, toY)
8594 int instant = (gameMode == PlayFromGameFile) ?
8595 (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
8596 if(appData.noGUI) return;
8597 if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
8599 if (forwardMostMove == currentMove + 1) {
8600 AnimateMove(boards[forwardMostMove - 1],
8601 fromX, fromY, toX, toY);
8603 if (appData.highlightLastMove) {
8604 SetHighlights(fromX, fromY, toX, toY);
8607 currentMove = forwardMostMove;
8610 if (instant) return;
8612 DisplayMove(currentMove - 1);
8613 DrawPosition(FALSE, boards[currentMove]);
8614 DisplayBothClocks();
8615 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
8618 void SendEgtPath(ChessProgramState *cps)
8619 { /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
8620 char buf[MSG_SIZ], name[MSG_SIZ], *p;
8622 if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
8625 char c, *q = name+1, *r, *s;
8627 name[0] = ','; // extract next format name from feature and copy with prefixed ','
8628 while(*p && *p != ',') *q++ = *p++;
8630 if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
8631 strcmp(name, ",nalimov:") == 0 ) {
8632 // take nalimov path from the menu-changeable option first, if it is defined
8633 sprintf(buf, "egtpath nalimov %s\n", appData.defaultPathEGTB);
8634 SendToProgram(buf,cps); // send egtbpath command for nalimov
8636 if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
8637 (s = StrStr(appData.egtFormats, name)) != NULL) {
8638 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
8639 s = r = StrStr(s, ":") + 1; // beginning of path info
8640 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
8641 c = *r; *r = 0; // temporarily null-terminate path info
8642 *--q = 0; // strip of trailig ':' from name
8643 sprintf(buf, "egtpath %s %s\n", name+1, s);
8645 SendToProgram(buf,cps); // send egtbpath command for this format
8647 if(*p == ',') p++; // read away comma to position for next format name
8652 InitChessProgram(cps, setup)
8653 ChessProgramState *cps;
8654 int setup; /* [HGM] needed to setup FRC opening position */
8656 char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
8657 if (appData.noChessProgram) return;
8658 hintRequested = FALSE;
8659 bookRequested = FALSE;
8661 /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
8662 /* moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
8663 if(cps->memSize) { /* [HGM] memory */
8664 sprintf(buf, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
8665 SendToProgram(buf, cps);
8667 SendEgtPath(cps); /* [HGM] EGT */
8668 if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
8669 sprintf(buf, "cores %d\n", appData.smpCores);
8670 SendToProgram(buf, cps);
8673 SendToProgram(cps->initString, cps);
8674 if (gameInfo.variant != VariantNormal &&
8675 gameInfo.variant != VariantLoadable
8676 /* [HGM] also send variant if board size non-standard */
8677 || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
8679 char *v = VariantName(gameInfo.variant);
8680 if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
8681 /* [HGM] in protocol 1 we have to assume all variants valid */
8682 sprintf(buf, _("Variant %s not supported by %s"), v, cps->tidy);
8683 DisplayFatalError(buf, 0, 1);
8687 /* [HGM] make prefix for non-standard board size. Awkward testing... */
8688 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8689 if( gameInfo.variant == VariantXiangqi )
8690 overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
8691 if( gameInfo.variant == VariantShogi )
8692 overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
8693 if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
8694 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
8695 if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
8696 gameInfo.variant == VariantGothic || gameInfo.variant == VariantFalcon )
8697 overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8698 if( gameInfo.variant == VariantCourier )
8699 overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8700 if( gameInfo.variant == VariantSuper )
8701 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
8702 if( gameInfo.variant == VariantGreat )
8703 overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
8706 sprintf(b, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
8707 gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
8708 /* [HGM] varsize: try first if this defiant size variant is specifically known */
8709 if(StrStr(cps->variants, b) == NULL) {
8710 // specific sized variant not known, check if general sizing allowed
8711 if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
8712 if(StrStr(cps->variants, "boardsize") == NULL) {
8713 sprintf(buf, "Board size %dx%d+%d not supported by %s",
8714 gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
8715 DisplayFatalError(buf, 0, 1);
8718 /* [HGM] here we really should compare with the maximum supported board size */
8721 } else sprintf(b, "%s", VariantName(gameInfo.variant));
8722 sprintf(buf, "variant %s\n", b);
8723 SendToProgram(buf, cps);
8725 currentlyInitializedVariant = gameInfo.variant;
8727 /* [HGM] send opening position in FRC to first engine */
8729 SendToProgram("force\n", cps);
8731 /* engine is now in force mode! Set flag to wake it up after first move. */
8732 setboardSpoiledMachineBlack = 1;
8736 snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
8737 SendToProgram(buf, cps);
8739 cps->maybeThinking = FALSE;
8740 cps->offeredDraw = 0;
8741 if (!appData.icsActive) {
8742 SendTimeControl(cps, movesPerSession, timeControl,
8743 timeIncrement, appData.searchDepth,
8746 if (appData.showThinking
8747 // [HGM] thinking: four options require thinking output to be sent
8748 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
8750 SendToProgram("post\n", cps);
8752 SendToProgram("hard\n", cps);
8753 if (!appData.ponderNextMove) {
8754 /* Warning: "easy" is a toggle in GNU Chess, so don't send
8755 it without being sure what state we are in first. "hard"
8756 is not a toggle, so that one is OK.
8758 SendToProgram("easy\n", cps);
8761 sprintf(buf, "ping %d\n", ++cps->lastPing);
8762 SendToProgram(buf, cps);
8764 cps->initDone = TRUE;
8769 StartChessProgram(cps)
8770 ChessProgramState *cps;
8775 if (appData.noChessProgram) return;
8776 cps->initDone = FALSE;
8778 if (strcmp(cps->host, "localhost") == 0) {
8779 err = StartChildProcess(cps->program, cps->dir, &cps->pr);
8780 } else if (*appData.remoteShell == NULLCHAR) {
8781 err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
8783 if (*appData.remoteUser == NULLCHAR) {
8784 snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
8787 snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
8788 cps->host, appData.remoteUser, cps->program);
8790 err = StartChildProcess(buf, "", &cps->pr);
8794 sprintf(buf, _("Startup failure on '%s'"), cps->program);
8795 DisplayFatalError(buf, err, 1);
8801 cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
8802 if (cps->protocolVersion > 1) {
8803 sprintf(buf, "xboard\nprotover %d\n", cps->protocolVersion);
8804 cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
8805 cps->comboCnt = 0; // and values of combo boxes
8806 SendToProgram(buf, cps);
8808 SendToProgram("xboard\n", cps);
8814 TwoMachinesEventIfReady P((void))
8816 if (first.lastPing != first.lastPong) {
8817 DisplayMessage("", _("Waiting for first chess program"));
8818 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
8821 if (second.lastPing != second.lastPong) {
8822 DisplayMessage("", _("Waiting for second chess program"));
8823 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
8831 NextMatchGame P((void))
8833 int index; /* [HGM] autoinc: step load index during match */
8835 if (*appData.loadGameFile != NULLCHAR) {
8836 index = appData.loadGameIndex;
8837 if(index < 0) { // [HGM] autoinc
8838 lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
8839 if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
8841 LoadGameFromFile(appData.loadGameFile,
8843 appData.loadGameFile, FALSE);
8844 } else if (*appData.loadPositionFile != NULLCHAR) {
8845 index = appData.loadPositionIndex;
8846 if(index < 0) { // [HGM] autoinc
8847 lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
8848 if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
8850 LoadPositionFromFile(appData.loadPositionFile,
8852 appData.loadPositionFile);
8854 TwoMachinesEventIfReady();
8857 void UserAdjudicationEvent( int result )
8859 ChessMove gameResult = GameIsDrawn;
8862 gameResult = WhiteWins;
8864 else if( result < 0 ) {
8865 gameResult = BlackWins;
8868 if( gameMode == TwoMachinesPlay ) {
8869 GameEnds( gameResult, "User adjudication", GE_XBOARD );
8874 // [HGM] save: calculate checksum of game to make games easily identifiable
8875 int StringCheckSum(char *s)
8878 if(s==NULL) return 0;
8879 while(*s) i = i*259 + *s++;
8886 for(i=backwardMostMove; i<forwardMostMove; i++) {
8887 sum += pvInfoList[i].depth;
8888 sum += StringCheckSum(parseList[i]);
8889 sum += StringCheckSum(commentList[i]);
8892 if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
8893 return sum + StringCheckSum(commentList[i]);
8894 } // end of save patch
8897 GameEnds(result, resultDetails, whosays)
8899 char *resultDetails;
8902 GameMode nextGameMode;
8906 if(endingGame) return; /* [HGM] crash: forbid recursion */
8908 if(twoBoards) { // [HGM] dual: switch back to one board
8909 twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
8910 DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
8912 if (appData.debugMode) {
8913 fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
8914 result, resultDetails ? resultDetails : "(null)", whosays);
8917 fromX = fromY = -1; // [HGM] abort any move the user is entering.
8919 if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
8920 /* If we are playing on ICS, the server decides when the
8921 game is over, but the engine can offer to draw, claim
8925 if (appData.zippyPlay && first.initDone) {
8926 if (result == GameIsDrawn) {
8927 /* In case draw still needs to be claimed */
8928 SendToICS(ics_prefix);
8929 SendToICS("draw\n");
8930 } else if (StrCaseStr(resultDetails, "resign")) {
8931 SendToICS(ics_prefix);
8932 SendToICS("resign\n");
8936 endingGame = 0; /* [HGM] crash */
8940 /* If we're loading the game from a file, stop */
8941 if (whosays == GE_FILE) {
8942 (void) StopLoadGameTimer();
8946 /* Cancel draw offers */
8947 first.offeredDraw = second.offeredDraw = 0;
8949 /* If this is an ICS game, only ICS can really say it's done;
8950 if not, anyone can. */
8951 isIcsGame = (gameMode == IcsPlayingWhite ||
8952 gameMode == IcsPlayingBlack ||
8953 gameMode == IcsObserving ||
8954 gameMode == IcsExamining);
8956 if (!isIcsGame || whosays == GE_ICS) {
8957 /* OK -- not an ICS game, or ICS said it was done */
8959 if (!isIcsGame && !appData.noChessProgram)
8960 SetUserThinkingEnables();
8962 /* [HGM] if a machine claims the game end we verify this claim */
8963 if(gameMode == TwoMachinesPlay && appData.testClaims) {
8964 if(appData.testLegality && whosays >= GE_ENGINE1 ) {
8966 ChessMove trueResult = (ChessMove) -1;
8968 claimer = whosays == GE_ENGINE1 ? /* color of claimer */
8969 first.twoMachinesColor[0] :
8970 second.twoMachinesColor[0] ;
8972 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
8973 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
8974 /* [HGM] verify: engine mate claims accepted if they were flagged */
8975 trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
8977 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
8978 /* [HGM] verify: engine mate claims accepted if they were flagged */
8979 trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8981 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
8982 trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
8985 // now verify win claims, but not in drop games, as we don't understand those yet
8986 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
8987 || gameInfo.variant == VariantGreat) &&
8988 (result == WhiteWins && claimer == 'w' ||
8989 result == BlackWins && claimer == 'b' ) ) { // case to verify: engine claims own win
8990 if (appData.debugMode) {
8991 fprintf(debugFP, "result=%d sp=%d move=%d\n",
8992 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
8994 if(result != trueResult) {
8995 sprintf(buf, "False win claim: '%s'", resultDetails);
8996 result = claimer == 'w' ? BlackWins : WhiteWins;
8997 resultDetails = buf;
9000 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
9001 && (forwardMostMove <= backwardMostMove ||
9002 (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
9003 (claimer=='b')==(forwardMostMove&1))
9005 /* [HGM] verify: draws that were not flagged are false claims */
9006 sprintf(buf, "False draw claim: '%s'", resultDetails);
9007 result = claimer == 'w' ? BlackWins : WhiteWins;
9008 resultDetails = buf;
9010 /* (Claiming a loss is accepted no questions asked!) */
9012 /* [HGM] bare: don't allow bare King to win */
9013 if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
9014 && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
9015 && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
9016 && result != GameIsDrawn)
9017 { int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
9018 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
9019 int p = (signed char)boards[forwardMostMove][i][j] - color;
9020 if(p >= 0 && p <= (int)WhiteKing) k++;
9022 if (appData.debugMode) {
9023 fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
9024 result, resultDetails ? resultDetails : "(null)", whosays, k, color);
9027 result = GameIsDrawn;
9028 sprintf(buf, "%s but bare king", resultDetails);
9029 resultDetails = buf;
9035 if(serverMoves != NULL && !loadFlag) { char c = '=';
9036 if(result==WhiteWins) c = '+';
9037 if(result==BlackWins) c = '-';
9038 if(resultDetails != NULL)
9039 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
9041 if (resultDetails != NULL) {
9042 gameInfo.result = result;
9043 gameInfo.resultDetails = StrSave(resultDetails);
9045 /* display last move only if game was not loaded from file */
9046 if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
9047 DisplayMove(currentMove - 1);
9049 if (forwardMostMove != 0) {
9050 if (gameMode != PlayFromGameFile && gameMode != EditGame
9051 && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
9053 if (*appData.saveGameFile != NULLCHAR) {
9054 SaveGameToFile(appData.saveGameFile, TRUE);
9055 } else if (appData.autoSaveGames) {
9058 if (*appData.savePositionFile != NULLCHAR) {
9059 SavePositionToFile(appData.savePositionFile);
9064 /* Tell program how game ended in case it is learning */
9065 /* [HGM] Moved this to after saving the PGN, just in case */
9066 /* engine died and we got here through time loss. In that */
9067 /* case we will get a fatal error writing the pipe, which */
9068 /* would otherwise lose us the PGN. */
9069 /* [HGM] crash: not needed anymore, but doesn't hurt; */
9070 /* output during GameEnds should never be fatal anymore */
9071 if (gameMode == MachinePlaysWhite ||
9072 gameMode == MachinePlaysBlack ||
9073 gameMode == TwoMachinesPlay ||
9074 gameMode == IcsPlayingWhite ||
9075 gameMode == IcsPlayingBlack ||
9076 gameMode == BeginningOfGame) {
9078 sprintf(buf, "result %s {%s}\n", PGNResult(result),
9080 if (first.pr != NoProc) {
9081 SendToProgram(buf, &first);
9083 if (second.pr != NoProc &&
9084 gameMode == TwoMachinesPlay) {
9085 SendToProgram(buf, &second);
9090 if (appData.icsActive) {
9091 if (appData.quietPlay &&
9092 (gameMode == IcsPlayingWhite ||
9093 gameMode == IcsPlayingBlack)) {
9094 SendToICS(ics_prefix);
9095 SendToICS("set shout 1\n");
9097 nextGameMode = IcsIdle;
9098 ics_user_moved = FALSE;
9099 /* clean up premove. It's ugly when the game has ended and the
9100 * premove highlights are still on the board.
9104 ClearPremoveHighlights();
9105 DrawPosition(FALSE, boards[currentMove]);
9107 if (whosays == GE_ICS) {
9110 if (gameMode == IcsPlayingWhite)
9112 else if(gameMode == IcsPlayingBlack)
9116 if (gameMode == IcsPlayingBlack)
9118 else if(gameMode == IcsPlayingWhite)
9125 PlayIcsUnfinishedSound();
9128 } else if (gameMode == EditGame ||
9129 gameMode == PlayFromGameFile ||
9130 gameMode == AnalyzeMode ||
9131 gameMode == AnalyzeFile) {
9132 nextGameMode = gameMode;
9134 nextGameMode = EndOfGame;
9139 nextGameMode = gameMode;
9142 if (appData.noChessProgram) {
9143 gameMode = nextGameMode;
9145 endingGame = 0; /* [HGM] crash */
9150 /* Put first chess program into idle state */
9151 if (first.pr != NoProc &&
9152 (gameMode == MachinePlaysWhite ||
9153 gameMode == MachinePlaysBlack ||
9154 gameMode == TwoMachinesPlay ||
9155 gameMode == IcsPlayingWhite ||
9156 gameMode == IcsPlayingBlack ||
9157 gameMode == BeginningOfGame)) {
9158 SendToProgram("force\n", &first);
9159 if (first.usePing) {
9161 sprintf(buf, "ping %d\n", ++first.lastPing);
9162 SendToProgram(buf, &first);
9165 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
9166 /* Kill off first chess program */
9167 if (first.isr != NULL)
9168 RemoveInputSource(first.isr);
9171 if (first.pr != NoProc) {
9173 DoSleep( appData.delayBeforeQuit );
9174 SendToProgram("quit\n", &first);
9175 DoSleep( appData.delayAfterQuit );
9176 DestroyChildProcess(first.pr, first.useSigterm);
9181 /* Put second chess program into idle state */
9182 if (second.pr != NoProc &&
9183 gameMode == TwoMachinesPlay) {
9184 SendToProgram("force\n", &second);
9185 if (second.usePing) {
9187 sprintf(buf, "ping %d\n", ++second.lastPing);
9188 SendToProgram(buf, &second);
9191 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
9192 /* Kill off second chess program */
9193 if (second.isr != NULL)
9194 RemoveInputSource(second.isr);
9197 if (second.pr != NoProc) {
9198 DoSleep( appData.delayBeforeQuit );
9199 SendToProgram("quit\n", &second);
9200 DoSleep( appData.delayAfterQuit );
9201 DestroyChildProcess(second.pr, second.useSigterm);
9206 if (matchMode && gameMode == TwoMachinesPlay) {
9209 if (first.twoMachinesColor[0] == 'w') {
9216 if (first.twoMachinesColor[0] == 'b') {
9225 if (matchGame < appData.matchGames) {
9227 if(appData.sameColorGames <= 1) { /* [HGM] alternate: suppress color swap */
9228 tmp = first.twoMachinesColor;
9229 first.twoMachinesColor = second.twoMachinesColor;
9230 second.twoMachinesColor = tmp;
9232 gameMode = nextGameMode;
9234 if(appData.matchPause>10000 || appData.matchPause<10)
9235 appData.matchPause = 10000; /* [HGM] make pause adjustable */
9236 ScheduleDelayedEvent(NextMatchGame, appData.matchPause);
9237 endingGame = 0; /* [HGM] crash */
9241 gameMode = nextGameMode;
9242 sprintf(buf, _("Match %s vs. %s: final score %d-%d-%d"),
9243 first.tidy, second.tidy,
9244 first.matchWins, second.matchWins,
9245 appData.matchGames - (first.matchWins + second.matchWins));
9246 DisplayFatalError(buf, 0, 0);
9249 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
9250 !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
9252 gameMode = nextGameMode;
9254 endingGame = 0; /* [HGM] crash */
9257 /* Assumes program was just initialized (initString sent).
9258 Leaves program in force mode. */
9260 FeedMovesToProgram(cps, upto)
9261 ChessProgramState *cps;
9266 if (appData.debugMode)
9267 fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
9268 startedFromSetupPosition ? "position and " : "",
9269 backwardMostMove, upto, cps->which);
9270 if(currentlyInitializedVariant != gameInfo.variant) { char buf[MSG_SIZ];
9271 // [HGM] variantswitch: make engine aware of new variant
9272 if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
9273 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
9274 sprintf(buf, "variant %s\n", VariantName(gameInfo.variant));
9275 SendToProgram(buf, cps);
9276 currentlyInitializedVariant = gameInfo.variant;
9278 SendToProgram("force\n", cps);
9279 if (startedFromSetupPosition) {
9280 SendBoard(cps, backwardMostMove);
9281 if (appData.debugMode) {
9282 fprintf(debugFP, "feedMoves\n");
9285 for (i = backwardMostMove; i < upto; i++) {
9286 SendMoveToProgram(i, cps);
9292 ResurrectChessProgram()
9294 /* The chess program may have exited.
9295 If so, restart it and feed it all the moves made so far. */
9297 if (appData.noChessProgram || first.pr != NoProc) return;
9299 StartChessProgram(&first);
9300 InitChessProgram(&first, FALSE);
9301 FeedMovesToProgram(&first, currentMove);
9303 if (!first.sendTime) {
9304 /* can't tell gnuchess what its clock should read,
9305 so we bow to its notion. */
9307 timeRemaining[0][currentMove] = whiteTimeRemaining;
9308 timeRemaining[1][currentMove] = blackTimeRemaining;
9311 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
9312 appData.icsEngineAnalyze) && first.analysisSupport) {
9313 SendToProgram("analyze\n", &first);
9314 first.analyzing = TRUE;
9327 if (appData.debugMode) {
9328 fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
9329 redraw, init, gameMode);
9331 CleanupTail(); // [HGM] vari: delete any stored variations
9332 pausing = pauseExamInvalid = FALSE;
9333 startedFromSetupPosition = blackPlaysFirst = FALSE;
9335 whiteFlag = blackFlag = FALSE;
9336 userOfferedDraw = FALSE;
9337 hintRequested = bookRequested = FALSE;
9338 first.maybeThinking = FALSE;
9339 second.maybeThinking = FALSE;
9340 first.bookSuspend = FALSE; // [HGM] book
9341 second.bookSuspend = FALSE;
9342 thinkOutput[0] = NULLCHAR;
9343 lastHint[0] = NULLCHAR;
9344 ClearGameInfo(&gameInfo);
9345 gameInfo.variant = StringToVariant(appData.variant);
9346 ics_user_moved = ics_clock_paused = FALSE;
9347 ics_getting_history = H_FALSE;
9349 white_holding[0] = black_holding[0] = NULLCHAR;
9350 ClearProgramStats();
9351 opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
9355 flipView = appData.flipView;
9356 ClearPremoveHighlights();
9358 alarmSounded = FALSE;
9360 GameEnds((ChessMove) 0, NULL, GE_PLAYER);
9361 if(appData.serverMovesName != NULL) {
9362 /* [HGM] prepare to make moves file for broadcasting */
9363 clock_t t = clock();
9364 if(serverMoves != NULL) fclose(serverMoves);
9365 serverMoves = fopen(appData.serverMovesName, "r");
9366 if(serverMoves != NULL) {
9367 fclose(serverMoves);
9368 /* delay 15 sec before overwriting, so all clients can see end */
9369 while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
9371 serverMoves = fopen(appData.serverMovesName, "w");
9375 gameMode = BeginningOfGame;
9377 if(appData.icsActive) gameInfo.variant = VariantNormal;
9378 currentMove = forwardMostMove = backwardMostMove = 0;
9379 InitPosition(redraw);
9380 for (i = 0; i < MAX_MOVES; i++) {
9381 if (commentList[i] != NULL) {
9382 free(commentList[i]);
9383 commentList[i] = NULL;
9387 timeRemaining[0][0] = whiteTimeRemaining;
9388 timeRemaining[1][0] = blackTimeRemaining;
9389 if (first.pr == NULL) {
9390 StartChessProgram(&first);
9393 InitChessProgram(&first, startedFromSetupPosition);
9396 DisplayMessage("", "");
9397 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
9398 lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
9405 if (!AutoPlayOneMove())
9407 if (matchMode || appData.timeDelay == 0)
9409 if (appData.timeDelay < 0 || gameMode == AnalyzeFile)
9411 StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
9420 int fromX, fromY, toX, toY;
9422 if (appData.debugMode) {
9423 fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
9426 if (gameMode != PlayFromGameFile)
9429 if (currentMove >= forwardMostMove) {
9430 gameMode = EditGame;
9433 /* [AS] Clear current move marker at the end of a game */
9434 /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
9439 toX = moveList[currentMove][2] - AAA;
9440 toY = moveList[currentMove][3] - ONE;
9442 if (moveList[currentMove][1] == '@') {
9443 if (appData.highlightLastMove) {
9444 SetHighlights(-1, -1, toX, toY);
9447 fromX = moveList[currentMove][0] - AAA;
9448 fromY = moveList[currentMove][1] - ONE;
9450 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
9452 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
9454 if (appData.highlightLastMove) {
9455 SetHighlights(fromX, fromY, toX, toY);
9458 DisplayMove(currentMove);
9459 SendMoveToProgram(currentMove++, &first);
9460 DisplayBothClocks();
9461 DrawPosition(FALSE, boards[currentMove]);
9462 // [HGM] PV info: always display, routine tests if empty
9463 DisplayComment(currentMove - 1, commentList[currentMove]);
9469 LoadGameOneMove(readAhead)
9470 ChessMove readAhead;
9472 int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
9473 char promoChar = NULLCHAR;
9478 if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
9479 gameMode != AnalyzeMode && gameMode != Training) {
9484 yyboardindex = forwardMostMove;
9485 if (readAhead != (ChessMove)0) {
9486 moveType = readAhead;
9488 if (gameFileFP == NULL)
9490 moveType = (ChessMove) yylex();
9496 if (appData.debugMode)
9497 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9500 /* append the comment but don't display it */
9501 AppendComment(currentMove, p, FALSE);
9504 case WhiteCapturesEnPassant:
9505 case BlackCapturesEnPassant:
9506 case WhitePromotionChancellor:
9507 case BlackPromotionChancellor:
9508 case WhitePromotionArchbishop:
9509 case BlackPromotionArchbishop:
9510 case WhitePromotionCentaur:
9511 case BlackPromotionCentaur:
9512 case WhitePromotionQueen:
9513 case BlackPromotionQueen:
9514 case WhitePromotionRook:
9515 case BlackPromotionRook:
9516 case WhitePromotionBishop:
9517 case BlackPromotionBishop:
9518 case WhitePromotionKnight:
9519 case BlackPromotionKnight:
9520 case WhitePromotionKing:
9521 case BlackPromotionKing:
9523 case WhiteKingSideCastle:
9524 case WhiteQueenSideCastle:
9525 case BlackKingSideCastle:
9526 case BlackQueenSideCastle:
9527 case WhiteKingSideCastleWild:
9528 case WhiteQueenSideCastleWild:
9529 case BlackKingSideCastleWild:
9530 case BlackQueenSideCastleWild:
9532 case WhiteHSideCastleFR:
9533 case WhiteASideCastleFR:
9534 case BlackHSideCastleFR:
9535 case BlackASideCastleFR:
9537 if (appData.debugMode)
9538 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
9539 fromX = currentMoveString[0] - AAA;
9540 fromY = currentMoveString[1] - ONE;
9541 toX = currentMoveString[2] - AAA;
9542 toY = currentMoveString[3] - ONE;
9543 promoChar = currentMoveString[4];
9548 if (appData.debugMode)
9549 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
9550 fromX = moveType == WhiteDrop ?
9551 (int) CharToPiece(ToUpper(currentMoveString[0])) :
9552 (int) CharToPiece(ToLower(currentMoveString[0]));
9554 toX = currentMoveString[2] - AAA;
9555 toY = currentMoveString[3] - ONE;
9561 case GameUnfinished:
9562 if (appData.debugMode)
9563 fprintf(debugFP, "Parsed game end: %s\n", yy_text);
9564 p = strchr(yy_text, '{');
9565 if (p == NULL) p = strchr(yy_text, '(');
9568 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
9570 q = strchr(p, *p == '{' ? '}' : ')');
9571 if (q != NULL) *q = NULLCHAR;
9574 GameEnds(moveType, p, GE_FILE);
9576 if (cmailMsgLoaded) {
9578 flipView = WhiteOnMove(currentMove);
9579 if (moveType == GameUnfinished) flipView = !flipView;
9580 if (appData.debugMode)
9581 fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
9585 case (ChessMove) 0: /* end of file */
9586 if (appData.debugMode)
9587 fprintf(debugFP, "Parser hit end of file\n");
9588 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9594 if (WhiteOnMove(currentMove)) {
9595 GameEnds(BlackWins, "Black mates", GE_FILE);
9597 GameEnds(WhiteWins, "White mates", GE_FILE);
9601 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
9608 if (lastLoadGameStart == GNUChessGame) {
9609 /* GNUChessGames have numbers, but they aren't move numbers */
9610 if (appData.debugMode)
9611 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
9612 yy_text, (int) moveType);
9613 return LoadGameOneMove((ChessMove)0); /* tail recursion */
9615 /* else fall thru */
9620 /* Reached start of next game in file */
9621 if (appData.debugMode)
9622 fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
9623 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9629 if (WhiteOnMove(currentMove)) {
9630 GameEnds(BlackWins, "Black mates", GE_FILE);
9632 GameEnds(WhiteWins, "White mates", GE_FILE);
9636 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
9642 case PositionDiagram: /* should not happen; ignore */
9643 case ElapsedTime: /* ignore */
9644 case NAG: /* ignore */
9645 if (appData.debugMode)
9646 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
9647 yy_text, (int) moveType);
9648 return LoadGameOneMove((ChessMove)0); /* tail recursion */
9651 if (appData.testLegality) {
9652 if (appData.debugMode)
9653 fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
9654 sprintf(move, _("Illegal move: %d.%s%s"),
9655 (forwardMostMove / 2) + 1,
9656 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9657 DisplayError(move, 0);
9660 if (appData.debugMode)
9661 fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
9662 yy_text, currentMoveString);
9663 fromX = currentMoveString[0] - AAA;
9664 fromY = currentMoveString[1] - ONE;
9665 toX = currentMoveString[2] - AAA;
9666 toY = currentMoveString[3] - ONE;
9667 promoChar = currentMoveString[4];
9672 if (appData.debugMode)
9673 fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
9674 sprintf(move, _("Ambiguous move: %d.%s%s"),
9675 (forwardMostMove / 2) + 1,
9676 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9677 DisplayError(move, 0);
9682 case ImpossibleMove:
9683 if (appData.debugMode)
9684 fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
9685 sprintf(move, _("Illegal move: %d.%s%s"),
9686 (forwardMostMove / 2) + 1,
9687 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9688 DisplayError(move, 0);
9694 if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
9695 DrawPosition(FALSE, boards[currentMove]);
9696 DisplayBothClocks();
9697 if (!appData.matchMode) // [HGM] PV info: routine tests if empty
9698 DisplayComment(currentMove - 1, commentList[currentMove]);
9700 (void) StopLoadGameTimer();
9702 cmailOldMove = forwardMostMove;
9705 /* currentMoveString is set as a side-effect of yylex */
9706 strcat(currentMoveString, "\n");
9707 strcpy(moveList[forwardMostMove], currentMoveString);
9709 thinkOutput[0] = NULLCHAR;
9710 MakeMove(fromX, fromY, toX, toY, promoChar);
9711 currentMove = forwardMostMove;
9716 /* Load the nth game from the given file */
9718 LoadGameFromFile(filename, n, title, useList)
9722 /*Boolean*/ int useList;
9727 if (strcmp(filename, "-") == 0) {
9731 f = fopen(filename, "rb");
9733 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9734 DisplayError(buf, errno);
9738 if (fseek(f, 0, 0) == -1) {
9739 /* f is not seekable; probably a pipe */
9742 if (useList && n == 0) {
9743 int error = GameListBuild(f);
9745 DisplayError(_("Cannot build game list"), error);
9746 } else if (!ListEmpty(&gameList) &&
9747 ((ListGame *) gameList.tailPred)->number > 1) {
9748 GameListPopUp(f, title);
9755 return LoadGame(f, n, title, FALSE);
9760 MakeRegisteredMove()
9762 int fromX, fromY, toX, toY;
9764 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
9765 switch (cmailMoveType[lastLoadGameNumber - 1]) {
9768 if (appData.debugMode)
9769 fprintf(debugFP, "Restoring %s for game %d\n",
9770 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
9772 thinkOutput[0] = NULLCHAR;
9773 strcpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1]);
9774 fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
9775 fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
9776 toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
9777 toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
9778 promoChar = cmailMove[lastLoadGameNumber - 1][4];
9779 MakeMove(fromX, fromY, toX, toY, promoChar);
9780 ShowMove(fromX, fromY, toX, toY);
9782 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9789 if (WhiteOnMove(currentMove)) {
9790 GameEnds(BlackWins, "Black mates", GE_PLAYER);
9792 GameEnds(WhiteWins, "White mates", GE_PLAYER);
9797 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
9804 if (WhiteOnMove(currentMove)) {
9805 GameEnds(BlackWins, "White resigns", GE_PLAYER);
9807 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
9812 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
9823 /* Wrapper around LoadGame for use when a Cmail message is loaded */
9825 CmailLoadGame(f, gameNumber, title, useList)
9833 if (gameNumber > nCmailGames) {
9834 DisplayError(_("No more games in this message"), 0);
9837 if (f == lastLoadGameFP) {
9838 int offset = gameNumber - lastLoadGameNumber;
9840 cmailMsg[0] = NULLCHAR;
9841 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
9842 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
9843 nCmailMovesRegistered--;
9845 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
9846 if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
9847 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
9850 if (! RegisterMove()) return FALSE;
9854 retVal = LoadGame(f, gameNumber, title, useList);
9856 /* Make move registered during previous look at this game, if any */
9857 MakeRegisteredMove();
9859 if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
9860 commentList[currentMove]
9861 = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
9862 DisplayComment(currentMove - 1, commentList[currentMove]);
9868 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
9873 int gameNumber = lastLoadGameNumber + offset;
9874 if (lastLoadGameFP == NULL) {
9875 DisplayError(_("No game has been loaded yet"), 0);
9878 if (gameNumber <= 0) {
9879 DisplayError(_("Can't back up any further"), 0);
9882 if (cmailMsgLoaded) {
9883 return CmailLoadGame(lastLoadGameFP, gameNumber,
9884 lastLoadGameTitle, lastLoadGameUseList);
9886 return LoadGame(lastLoadGameFP, gameNumber,
9887 lastLoadGameTitle, lastLoadGameUseList);
9893 /* Load the nth game from open file f */
9895 LoadGame(f, gameNumber, title, useList)
9903 int gn = gameNumber;
9904 ListGame *lg = NULL;
9907 GameMode oldGameMode;
9908 VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
9910 if (appData.debugMode)
9911 fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
9913 if (gameMode == Training )
9914 SetTrainingModeOff();
9916 oldGameMode = gameMode;
9917 if (gameMode != BeginningOfGame) {
9922 if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
9923 fclose(lastLoadGameFP);
9927 lg = (ListGame *) ListElem(&gameList, gameNumber-1);
9930 fseek(f, lg->offset, 0);
9931 GameListHighlight(gameNumber);
9935 DisplayError(_("Game number out of range"), 0);
9940 if (fseek(f, 0, 0) == -1) {
9941 if (f == lastLoadGameFP ?
9942 gameNumber == lastLoadGameNumber + 1 :
9946 DisplayError(_("Can't seek on game file"), 0);
9952 lastLoadGameNumber = gameNumber;
9953 strcpy(lastLoadGameTitle, title);
9954 lastLoadGameUseList = useList;
9958 if (lg && lg->gameInfo.white && lg->gameInfo.black) {
9959 snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
9960 lg->gameInfo.black);
9962 } else if (*title != NULLCHAR) {
9963 if (gameNumber > 1) {
9964 sprintf(buf, "%s %d", title, gameNumber);
9967 DisplayTitle(title);
9971 if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
9972 gameMode = PlayFromGameFile;
9976 currentMove = forwardMostMove = backwardMostMove = 0;
9977 CopyBoard(boards[0], initialPosition);
9981 * Skip the first gn-1 games in the file.
9982 * Also skip over anything that precedes an identifiable
9983 * start of game marker, to avoid being confused by
9984 * garbage at the start of the file. Currently
9985 * recognized start of game markers are the move number "1",
9986 * the pattern "gnuchess .* game", the pattern
9987 * "^[#;%] [^ ]* game file", and a PGN tag block.
9988 * A game that starts with one of the latter two patterns
9989 * will also have a move number 1, possibly
9990 * following a position diagram.
9991 * 5-4-02: Let's try being more lenient and allowing a game to
9992 * start with an unnumbered move. Does that break anything?
9994 cm = lastLoadGameStart = (ChessMove) 0;
9996 yyboardindex = forwardMostMove;
9997 cm = (ChessMove) yylex();
10000 if (cmailMsgLoaded) {
10001 nCmailGames = CMAIL_MAX_GAMES - gn;
10004 DisplayError(_("Game not found in file"), 0);
10011 lastLoadGameStart = cm;
10014 case MoveNumberOne:
10015 switch (lastLoadGameStart) {
10020 case MoveNumberOne:
10021 case (ChessMove) 0:
10022 gn--; /* count this game */
10023 lastLoadGameStart = cm;
10032 switch (lastLoadGameStart) {
10035 case MoveNumberOne:
10036 case (ChessMove) 0:
10037 gn--; /* count this game */
10038 lastLoadGameStart = cm;
10041 lastLoadGameStart = cm; /* game counted already */
10049 yyboardindex = forwardMostMove;
10050 cm = (ChessMove) yylex();
10051 } while (cm == PGNTag || cm == Comment);
10058 if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
10059 if ( cmailResult[CMAIL_MAX_GAMES - gn - 1]
10060 != CMAIL_OLD_RESULT) {
10062 cmailResult[ CMAIL_MAX_GAMES
10063 - gn - 1] = CMAIL_OLD_RESULT;
10069 /* Only a NormalMove can be at the start of a game
10070 * without a position diagram. */
10071 if (lastLoadGameStart == (ChessMove) 0) {
10073 lastLoadGameStart = MoveNumberOne;
10082 if (appData.debugMode)
10083 fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
10085 if (cm == XBoardGame) {
10086 /* Skip any header junk before position diagram and/or move 1 */
10088 yyboardindex = forwardMostMove;
10089 cm = (ChessMove) yylex();
10091 if (cm == (ChessMove) 0 ||
10092 cm == GNUChessGame || cm == XBoardGame) {
10093 /* Empty game; pretend end-of-file and handle later */
10094 cm = (ChessMove) 0;
10098 if (cm == MoveNumberOne || cm == PositionDiagram ||
10099 cm == PGNTag || cm == Comment)
10102 } else if (cm == GNUChessGame) {
10103 if (gameInfo.event != NULL) {
10104 free(gameInfo.event);
10106 gameInfo.event = StrSave(yy_text);
10109 startedFromSetupPosition = FALSE;
10110 while (cm == PGNTag) {
10111 if (appData.debugMode)
10112 fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
10113 err = ParsePGNTag(yy_text, &gameInfo);
10114 if (!err) numPGNTags++;
10116 /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
10117 if(gameInfo.variant != oldVariant) {
10118 startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
10119 InitPosition(TRUE);
10120 oldVariant = gameInfo.variant;
10121 if (appData.debugMode)
10122 fprintf(debugFP, "New variant %d\n", (int) oldVariant);
10126 if (gameInfo.fen != NULL) {
10127 Board initial_position;
10128 startedFromSetupPosition = TRUE;
10129 if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
10131 DisplayError(_("Bad FEN position in file"), 0);
10134 CopyBoard(boards[0], initial_position);
10135 if (blackPlaysFirst) {
10136 currentMove = forwardMostMove = backwardMostMove = 1;
10137 CopyBoard(boards[1], initial_position);
10138 strcpy(moveList[0], "");
10139 strcpy(parseList[0], "");
10140 timeRemaining[0][1] = whiteTimeRemaining;
10141 timeRemaining[1][1] = blackTimeRemaining;
10142 if (commentList[0] != NULL) {
10143 commentList[1] = commentList[0];
10144 commentList[0] = NULL;
10147 currentMove = forwardMostMove = backwardMostMove = 0;
10149 /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
10151 initialRulePlies = FENrulePlies;
10152 for( i=0; i< nrCastlingRights; i++ )
10153 initialRights[i] = initial_position[CASTLING][i];
10155 yyboardindex = forwardMostMove;
10156 free(gameInfo.fen);
10157 gameInfo.fen = NULL;
10160 yyboardindex = forwardMostMove;
10161 cm = (ChessMove) yylex();
10163 /* Handle comments interspersed among the tags */
10164 while (cm == Comment) {
10166 if (appData.debugMode)
10167 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10169 AppendComment(currentMove, p, FALSE);
10170 yyboardindex = forwardMostMove;
10171 cm = (ChessMove) yylex();
10175 /* don't rely on existence of Event tag since if game was
10176 * pasted from clipboard the Event tag may not exist
10178 if (numPGNTags > 0){
10180 if (gameInfo.variant == VariantNormal) {
10181 VariantClass v = StringToVariant(gameInfo.event);
10182 // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
10183 if(v < VariantShogi) gameInfo.variant = v;
10186 if( appData.autoDisplayTags ) {
10187 tags = PGNTags(&gameInfo);
10188 TagsPopUp(tags, CmailMsg());
10193 /* Make something up, but don't display it now */
10198 if (cm == PositionDiagram) {
10201 Board initial_position;
10203 if (appData.debugMode)
10204 fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
10206 if (!startedFromSetupPosition) {
10208 for (i = BOARD_HEIGHT - 1; i >= 0; i--)
10209 for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
10219 initial_position[i][j++] = CharToPiece(*p);
10222 while (*p == ' ' || *p == '\t' ||
10223 *p == '\n' || *p == '\r') p++;
10225 if (strncmp(p, "black", strlen("black"))==0)
10226 blackPlaysFirst = TRUE;
10228 blackPlaysFirst = FALSE;
10229 startedFromSetupPosition = TRUE;
10231 CopyBoard(boards[0], initial_position);
10232 if (blackPlaysFirst) {
10233 currentMove = forwardMostMove = backwardMostMove = 1;
10234 CopyBoard(boards[1], initial_position);
10235 strcpy(moveList[0], "");
10236 strcpy(parseList[0], "");
10237 timeRemaining[0][1] = whiteTimeRemaining;
10238 timeRemaining[1][1] = blackTimeRemaining;
10239 if (commentList[0] != NULL) {
10240 commentList[1] = commentList[0];
10241 commentList[0] = NULL;
10244 currentMove = forwardMostMove = backwardMostMove = 0;
10247 yyboardindex = forwardMostMove;
10248 cm = (ChessMove) yylex();
10251 if (first.pr == NoProc) {
10252 StartChessProgram(&first);
10254 InitChessProgram(&first, FALSE);
10255 SendToProgram("force\n", &first);
10256 if (startedFromSetupPosition) {
10257 SendBoard(&first, forwardMostMove);
10258 if (appData.debugMode) {
10259 fprintf(debugFP, "Load Game\n");
10261 DisplayBothClocks();
10264 /* [HGM] server: flag to write setup moves in broadcast file as one */
10265 loadFlag = appData.suppressLoadMoves;
10267 while (cm == Comment) {
10269 if (appData.debugMode)
10270 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10272 AppendComment(currentMove, p, FALSE);
10273 yyboardindex = forwardMostMove;
10274 cm = (ChessMove) yylex();
10277 if ((cm == (ChessMove) 0 && lastLoadGameStart != (ChessMove) 0) ||
10278 cm == WhiteWins || cm == BlackWins ||
10279 cm == GameIsDrawn || cm == GameUnfinished) {
10280 DisplayMessage("", _("No moves in game"));
10281 if (cmailMsgLoaded) {
10282 if (appData.debugMode)
10283 fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
10287 DrawPosition(FALSE, boards[currentMove]);
10288 DisplayBothClocks();
10289 gameMode = EditGame;
10296 // [HGM] PV info: routine tests if comment empty
10297 if (!matchMode && (pausing || appData.timeDelay != 0)) {
10298 DisplayComment(currentMove - 1, commentList[currentMove]);
10300 if (!matchMode && appData.timeDelay != 0)
10301 DrawPosition(FALSE, boards[currentMove]);
10303 if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
10304 programStats.ok_to_send = 1;
10307 /* if the first token after the PGN tags is a move
10308 * and not move number 1, retrieve it from the parser
10310 if (cm != MoveNumberOne)
10311 LoadGameOneMove(cm);
10313 /* load the remaining moves from the file */
10314 while (LoadGameOneMove((ChessMove)0)) {
10315 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10316 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10319 /* rewind to the start of the game */
10320 currentMove = backwardMostMove;
10322 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10324 if (oldGameMode == AnalyzeFile ||
10325 oldGameMode == AnalyzeMode) {
10326 AnalyzeFileEvent();
10329 if (matchMode || appData.timeDelay == 0) {
10331 gameMode = EditGame;
10333 } else if (appData.timeDelay > 0) {
10334 AutoPlayGameLoop();
10337 if (appData.debugMode)
10338 fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
10340 loadFlag = 0; /* [HGM] true game starts */
10344 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
10346 ReloadPosition(offset)
10349 int positionNumber = lastLoadPositionNumber + offset;
10350 if (lastLoadPositionFP == NULL) {
10351 DisplayError(_("No position has been loaded yet"), 0);
10354 if (positionNumber <= 0) {
10355 DisplayError(_("Can't back up any further"), 0);
10358 return LoadPosition(lastLoadPositionFP, positionNumber,
10359 lastLoadPositionTitle);
10362 /* Load the nth position from the given file */
10364 LoadPositionFromFile(filename, n, title)
10372 if (strcmp(filename, "-") == 0) {
10373 return LoadPosition(stdin, n, "stdin");
10375 f = fopen(filename, "rb");
10377 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10378 DisplayError(buf, errno);
10381 return LoadPosition(f, n, title);
10386 /* Load the nth position from the given open file, and close it */
10388 LoadPosition(f, positionNumber, title)
10390 int positionNumber;
10393 char *p, line[MSG_SIZ];
10394 Board initial_position;
10395 int i, j, fenMode, pn;
10397 if (gameMode == Training )
10398 SetTrainingModeOff();
10400 if (gameMode != BeginningOfGame) {
10401 Reset(FALSE, TRUE);
10403 if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
10404 fclose(lastLoadPositionFP);
10406 if (positionNumber == 0) positionNumber = 1;
10407 lastLoadPositionFP = f;
10408 lastLoadPositionNumber = positionNumber;
10409 strcpy(lastLoadPositionTitle, title);
10410 if (first.pr == NoProc) {
10411 StartChessProgram(&first);
10412 InitChessProgram(&first, FALSE);
10414 pn = positionNumber;
10415 if (positionNumber < 0) {
10416 /* Negative position number means to seek to that byte offset */
10417 if (fseek(f, -positionNumber, 0) == -1) {
10418 DisplayError(_("Can't seek on position file"), 0);
10423 if (fseek(f, 0, 0) == -1) {
10424 if (f == lastLoadPositionFP ?
10425 positionNumber == lastLoadPositionNumber + 1 :
10426 positionNumber == 1) {
10429 DisplayError(_("Can't seek on position file"), 0);
10434 /* See if this file is FEN or old-style xboard */
10435 if (fgets(line, MSG_SIZ, f) == NULL) {
10436 DisplayError(_("Position not found in file"), 0);
10439 // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
10440 fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
10443 if (fenMode || line[0] == '#') pn--;
10445 /* skip positions before number pn */
10446 if (fgets(line, MSG_SIZ, f) == NULL) {
10448 DisplayError(_("Position not found in file"), 0);
10451 if (fenMode || line[0] == '#') pn--;
10456 if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
10457 DisplayError(_("Bad FEN position in file"), 0);
10461 (void) fgets(line, MSG_SIZ, f);
10462 (void) fgets(line, MSG_SIZ, f);
10464 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
10465 (void) fgets(line, MSG_SIZ, f);
10466 for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
10469 initial_position[i][j++] = CharToPiece(*p);
10473 blackPlaysFirst = FALSE;
10475 (void) fgets(line, MSG_SIZ, f);
10476 if (strncmp(line, "black", strlen("black"))==0)
10477 blackPlaysFirst = TRUE;
10480 startedFromSetupPosition = TRUE;
10482 SendToProgram("force\n", &first);
10483 CopyBoard(boards[0], initial_position);
10484 if (blackPlaysFirst) {
10485 currentMove = forwardMostMove = backwardMostMove = 1;
10486 strcpy(moveList[0], "");
10487 strcpy(parseList[0], "");
10488 CopyBoard(boards[1], initial_position);
10489 DisplayMessage("", _("Black to play"));
10491 currentMove = forwardMostMove = backwardMostMove = 0;
10492 DisplayMessage("", _("White to play"));
10494 initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
10495 SendBoard(&first, forwardMostMove);
10496 if (appData.debugMode) {
10498 for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
10499 for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
10500 fprintf(debugFP, "Load Position\n");
10503 if (positionNumber > 1) {
10504 sprintf(line, "%s %d", title, positionNumber);
10505 DisplayTitle(line);
10507 DisplayTitle(title);
10509 gameMode = EditGame;
10512 timeRemaining[0][1] = whiteTimeRemaining;
10513 timeRemaining[1][1] = blackTimeRemaining;
10514 DrawPosition(FALSE, boards[currentMove]);
10521 CopyPlayerNameIntoFileName(dest, src)
10524 while (*src != NULLCHAR && *src != ',') {
10529 *(*dest)++ = *src++;
10534 char *DefaultFileName(ext)
10537 static char def[MSG_SIZ];
10540 if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
10542 CopyPlayerNameIntoFileName(&p, gameInfo.white);
10544 CopyPlayerNameIntoFileName(&p, gameInfo.black);
10553 /* Save the current game to the given file */
10555 SaveGameToFile(filename, append)
10562 if (strcmp(filename, "-") == 0) {
10563 return SaveGame(stdout, 0, NULL);
10565 f = fopen(filename, append ? "a" : "w");
10567 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10568 DisplayError(buf, errno);
10571 return SaveGame(f, 0, NULL);
10580 static char buf[MSG_SIZ];
10583 p = strchr(str, ' ');
10584 if (p == NULL) return str;
10585 strncpy(buf, str, p - str);
10586 buf[p - str] = NULLCHAR;
10590 #define PGN_MAX_LINE 75
10592 #define PGN_SIDE_WHITE 0
10593 #define PGN_SIDE_BLACK 1
10596 static int FindFirstMoveOutOfBook( int side )
10600 if( backwardMostMove == 0 && ! startedFromSetupPosition) {
10601 int index = backwardMostMove;
10602 int has_book_hit = 0;
10604 if( (index % 2) != side ) {
10608 while( index < forwardMostMove ) {
10609 /* Check to see if engine is in book */
10610 int depth = pvInfoList[index].depth;
10611 int score = pvInfoList[index].score;
10617 else if( score == 0 && depth == 63 ) {
10618 in_book = 1; /* Zappa */
10620 else if( score == 2 && depth == 99 ) {
10621 in_book = 1; /* Abrok */
10624 has_book_hit += in_book;
10640 void GetOutOfBookInfo( char * buf )
10644 int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10646 oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
10647 oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
10651 if( oob[0] >= 0 || oob[1] >= 0 ) {
10652 for( i=0; i<2; i++ ) {
10656 if( i > 0 && oob[0] >= 0 ) {
10657 strcat( buf, " " );
10660 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
10661 sprintf( buf+strlen(buf), "%s%.2f",
10662 pvInfoList[idx].score >= 0 ? "+" : "",
10663 pvInfoList[idx].score / 100.0 );
10669 /* Save game in PGN style and close the file */
10674 int i, offset, linelen, newblock;
10678 int movelen, numlen, blank;
10679 char move_buffer[100]; /* [AS] Buffer for move+PV info */
10681 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10683 tm = time((time_t *) NULL);
10685 PrintPGNTags(f, &gameInfo);
10687 if (backwardMostMove > 0 || startedFromSetupPosition) {
10688 char *fen = PositionToFEN(backwardMostMove, NULL);
10689 fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
10690 fprintf(f, "\n{--------------\n");
10691 PrintPosition(f, backwardMostMove);
10692 fprintf(f, "--------------}\n");
10696 /* [AS] Out of book annotation */
10697 if( appData.saveOutOfBookInfo ) {
10700 GetOutOfBookInfo( buf );
10702 if( buf[0] != '\0' ) {
10703 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
10710 i = backwardMostMove;
10714 while (i < forwardMostMove) {
10715 /* Print comments preceding this move */
10716 if (commentList[i] != NULL) {
10717 if (linelen > 0) fprintf(f, "\n");
10718 fprintf(f, "%s", commentList[i]);
10723 /* Format move number */
10724 if ((i % 2) == 0) {
10725 sprintf(numtext, "%d.", (i - offset)/2 + 1);
10728 sprintf(numtext, "%d...", (i - offset)/2 + 1);
10730 numtext[0] = NULLCHAR;
10733 numlen = strlen(numtext);
10736 /* Print move number */
10737 blank = linelen > 0 && numlen > 0;
10738 if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
10747 fprintf(f, "%s", numtext);
10751 strcpy(move_buffer, SavePart(parseList[i])); // [HGM] pgn: print move via buffer, so it can be edited
10752 movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
10755 blank = linelen > 0 && movelen > 0;
10756 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
10765 fprintf(f, "%s", move_buffer);
10766 linelen += movelen;
10768 /* [AS] Add PV info if present */
10769 if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
10770 /* [HGM] add time */
10771 char buf[MSG_SIZ]; int seconds;
10773 seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
10775 if( seconds <= 0) buf[0] = 0; else
10776 if( seconds < 30 ) sprintf(buf, " %3.1f%c", seconds/10., 0); else {
10777 seconds = (seconds + 4)/10; // round to full seconds
10778 if( seconds < 60 ) sprintf(buf, " %d%c", seconds, 0); else
10779 sprintf(buf, " %d:%02d%c", seconds/60, seconds%60, 0);
10782 sprintf( move_buffer, "{%s%.2f/%d%s}",
10783 pvInfoList[i].score >= 0 ? "+" : "",
10784 pvInfoList[i].score / 100.0,
10785 pvInfoList[i].depth,
10788 movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
10790 /* Print score/depth */
10791 blank = linelen > 0 && movelen > 0;
10792 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
10801 fprintf(f, "%s", move_buffer);
10802 linelen += movelen;
10808 /* Start a new line */
10809 if (linelen > 0) fprintf(f, "\n");
10811 /* Print comments after last move */
10812 if (commentList[i] != NULL) {
10813 fprintf(f, "%s\n", commentList[i]);
10817 if (gameInfo.resultDetails != NULL &&
10818 gameInfo.resultDetails[0] != NULLCHAR) {
10819 fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
10820 PGNResult(gameInfo.result));
10822 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
10826 lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
10830 /* Save game in old style and close the file */
10832 SaveGameOldStyle(f)
10838 tm = time((time_t *) NULL);
10840 fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
10843 if (backwardMostMove > 0 || startedFromSetupPosition) {
10844 fprintf(f, "\n[--------------\n");
10845 PrintPosition(f, backwardMostMove);
10846 fprintf(f, "--------------]\n");
10851 i = backwardMostMove;
10852 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10854 while (i < forwardMostMove) {
10855 if (commentList[i] != NULL) {
10856 fprintf(f, "[%s]\n", commentList[i]);
10859 if ((i % 2) == 1) {
10860 fprintf(f, "%d. ... %s\n", (i - offset)/2 + 1, parseList[i]);
10863 fprintf(f, "%d. %s ", (i - offset)/2 + 1, parseList[i]);
10865 if (commentList[i] != NULL) {
10869 if (i >= forwardMostMove) {
10873 fprintf(f, "%s\n", parseList[i]);
10878 if (commentList[i] != NULL) {
10879 fprintf(f, "[%s]\n", commentList[i]);
10882 /* This isn't really the old style, but it's close enough */
10883 if (gameInfo.resultDetails != NULL &&
10884 gameInfo.resultDetails[0] != NULLCHAR) {
10885 fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
10886 gameInfo.resultDetails);
10888 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
10895 /* Save the current game to open file f and close the file */
10897 SaveGame(f, dummy, dummy2)
10902 if (gameMode == EditPosition) EditPositionDone(TRUE);
10903 lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
10904 if (appData.oldSaveStyle)
10905 return SaveGameOldStyle(f);
10907 return SaveGamePGN(f);
10910 /* Save the current position to the given file */
10912 SavePositionToFile(filename)
10918 if (strcmp(filename, "-") == 0) {
10919 return SavePosition(stdout, 0, NULL);
10921 f = fopen(filename, "a");
10923 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10924 DisplayError(buf, errno);
10927 SavePosition(f, 0, NULL);
10933 /* Save the current position to the given open file and close the file */
10935 SavePosition(f, dummy, dummy2)
10943 if (gameMode == EditPosition) EditPositionDone(TRUE);
10944 if (appData.oldSaveStyle) {
10945 tm = time((time_t *) NULL);
10947 fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
10949 fprintf(f, "[--------------\n");
10950 PrintPosition(f, currentMove);
10951 fprintf(f, "--------------]\n");
10953 fen = PositionToFEN(currentMove, NULL);
10954 fprintf(f, "%s\n", fen);
10962 ReloadCmailMsgEvent(unregister)
10966 static char *inFilename = NULL;
10967 static char *outFilename;
10969 struct stat inbuf, outbuf;
10972 /* Any registered moves are unregistered if unregister is set, */
10973 /* i.e. invoked by the signal handler */
10975 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
10976 cmailMoveRegistered[i] = FALSE;
10977 if (cmailCommentList[i] != NULL) {
10978 free(cmailCommentList[i]);
10979 cmailCommentList[i] = NULL;
10982 nCmailMovesRegistered = 0;
10985 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
10986 cmailResult[i] = CMAIL_NOT_RESULT;
10990 if (inFilename == NULL) {
10991 /* Because the filenames are static they only get malloced once */
10992 /* and they never get freed */
10993 inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
10994 sprintf(inFilename, "%s.game.in", appData.cmailGameName);
10996 outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
10997 sprintf(outFilename, "%s.out", appData.cmailGameName);
11000 status = stat(outFilename, &outbuf);
11002 cmailMailedMove = FALSE;
11004 status = stat(inFilename, &inbuf);
11005 cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
11008 /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
11009 counts the games, notes how each one terminated, etc.
11011 It would be nice to remove this kludge and instead gather all
11012 the information while building the game list. (And to keep it
11013 in the game list nodes instead of having a bunch of fixed-size
11014 parallel arrays.) Note this will require getting each game's
11015 termination from the PGN tags, as the game list builder does
11016 not process the game moves. --mann
11018 cmailMsgLoaded = TRUE;
11019 LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
11021 /* Load first game in the file or popup game menu */
11022 LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
11024 #endif /* !WIN32 */
11032 char string[MSG_SIZ];
11034 if ( cmailMailedMove
11035 || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
11036 return TRUE; /* Allow free viewing */
11039 /* Unregister move to ensure that we don't leave RegisterMove */
11040 /* with the move registered when the conditions for registering no */
11042 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11043 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
11044 nCmailMovesRegistered --;
11046 if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
11048 free(cmailCommentList[lastLoadGameNumber - 1]);
11049 cmailCommentList[lastLoadGameNumber - 1] = NULL;
11053 if (cmailOldMove == -1) {
11054 DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
11058 if (currentMove > cmailOldMove + 1) {
11059 DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
11063 if (currentMove < cmailOldMove) {
11064 DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
11068 if (forwardMostMove > currentMove) {
11069 /* Silently truncate extra moves */
11073 if ( (currentMove == cmailOldMove + 1)
11074 || ( (currentMove == cmailOldMove)
11075 && ( (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
11076 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
11077 if (gameInfo.result != GameUnfinished) {
11078 cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
11081 if (commentList[currentMove] != NULL) {
11082 cmailCommentList[lastLoadGameNumber - 1]
11083 = StrSave(commentList[currentMove]);
11085 strcpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1]);
11087 if (appData.debugMode)
11088 fprintf(debugFP, "Saving %s for game %d\n",
11089 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
11092 "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
11094 f = fopen(string, "w");
11095 if (appData.oldSaveStyle) {
11096 SaveGameOldStyle(f); /* also closes the file */
11098 sprintf(string, "%s.pos.out", appData.cmailGameName);
11099 f = fopen(string, "w");
11100 SavePosition(f, 0, NULL); /* also closes the file */
11102 fprintf(f, "{--------------\n");
11103 PrintPosition(f, currentMove);
11104 fprintf(f, "--------------}\n\n");
11106 SaveGame(f, 0, NULL); /* also closes the file*/
11109 cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
11110 nCmailMovesRegistered ++;
11111 } else if (nCmailGames == 1) {
11112 DisplayError(_("You have not made a move yet"), 0);
11123 static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
11124 FILE *commandOutput;
11125 char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
11126 int nBytes = 0; /* Suppress warnings on uninitialized variables */
11132 if (! cmailMsgLoaded) {
11133 DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
11137 if (nCmailGames == nCmailResults) {
11138 DisplayError(_("No unfinished games"), 0);
11142 #if CMAIL_PROHIBIT_REMAIL
11143 if (cmailMailedMove) {
11144 sprintf(msg, _("You have already mailed a move.\nWait until a move arrives from your opponent.\nTo resend the same move, type\n\"cmail -remail -game %s\"\non the command line."), appData.cmailGameName);
11145 DisplayError(msg, 0);
11150 if (! (cmailMailedMove || RegisterMove())) return;
11152 if ( cmailMailedMove
11153 || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
11154 sprintf(string, partCommandString,
11155 appData.debugMode ? " -v" : "", appData.cmailGameName);
11156 commandOutput = popen(string, "r");
11158 if (commandOutput == NULL) {
11159 DisplayError(_("Failed to invoke cmail"), 0);
11161 for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
11162 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
11164 if (nBuffers > 1) {
11165 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
11166 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
11167 nBytes = MSG_SIZ - 1;
11169 (void) memcpy(msg, buffer, nBytes);
11171 *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
11173 if(StrStr(msg, "Mailed cmail message to ") != NULL) {
11174 cmailMailedMove = TRUE; /* Prevent >1 moves */
11177 for (i = 0; i < nCmailGames; i ++) {
11178 if (cmailResult[i] == CMAIL_NOT_RESULT) {
11183 && ( (arcDir = (char *) getenv("CMAIL_ARCDIR"))
11185 sprintf(buffer, "%s/%s.%s.archive",
11187 appData.cmailGameName,
11189 LoadGameFromFile(buffer, 1, buffer, FALSE);
11190 cmailMsgLoaded = FALSE;
11194 DisplayInformation(msg);
11195 pclose(commandOutput);
11198 if ((*cmailMsg) != '\0') {
11199 DisplayInformation(cmailMsg);
11204 #endif /* !WIN32 */
11213 int prependComma = 0;
11215 char string[MSG_SIZ]; /* Space for game-list */
11218 if (!cmailMsgLoaded) return "";
11220 if (cmailMailedMove) {
11221 sprintf(cmailMsg, _("Waiting for reply from opponent\n"));
11223 /* Create a list of games left */
11224 sprintf(string, "[");
11225 for (i = 0; i < nCmailGames; i ++) {
11226 if (! ( cmailMoveRegistered[i]
11227 || (cmailResult[i] == CMAIL_OLD_RESULT))) {
11228 if (prependComma) {
11229 sprintf(number, ",%d", i + 1);
11231 sprintf(number, "%d", i + 1);
11235 strcat(string, number);
11238 strcat(string, "]");
11240 if (nCmailMovesRegistered + nCmailResults == 0) {
11241 switch (nCmailGames) {
11244 _("Still need to make move for game\n"));
11249 _("Still need to make moves for both games\n"));
11254 _("Still need to make moves for all %d games\n"),
11259 switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
11262 _("Still need to make a move for game %s\n"),
11267 if (nCmailResults == nCmailGames) {
11268 sprintf(cmailMsg, _("No unfinished games\n"));
11270 sprintf(cmailMsg, _("Ready to send mail\n"));
11276 _("Still need to make moves for games %s\n"),
11288 if (gameMode == Training)
11289 SetTrainingModeOff();
11292 cmailMsgLoaded = FALSE;
11293 if (appData.icsActive) {
11294 SendToICS(ics_prefix);
11295 SendToICS("refresh\n");
11305 /* Give up on clean exit */
11309 /* Keep trying for clean exit */
11313 if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
11315 if (telnetISR != NULL) {
11316 RemoveInputSource(telnetISR);
11318 if (icsPR != NoProc) {
11319 DestroyChildProcess(icsPR, TRUE);
11322 /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
11323 GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
11325 /* [HGM] crash: the above GameEnds() is a dud if another one was running */
11326 /* make sure this other one finishes before killing it! */
11327 if(endingGame) { int count = 0;
11328 if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
11329 while(endingGame && count++ < 10) DoSleep(1);
11330 if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
11333 /* Kill off chess programs */
11334 if (first.pr != NoProc) {
11337 DoSleep( appData.delayBeforeQuit );
11338 SendToProgram("quit\n", &first);
11339 DoSleep( appData.delayAfterQuit );
11340 DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
11342 if (second.pr != NoProc) {
11343 DoSleep( appData.delayBeforeQuit );
11344 SendToProgram("quit\n", &second);
11345 DoSleep( appData.delayAfterQuit );
11346 DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
11348 if (first.isr != NULL) {
11349 RemoveInputSource(first.isr);
11351 if (second.isr != NULL) {
11352 RemoveInputSource(second.isr);
11355 ShutDownFrontEnd();
11362 if (appData.debugMode)
11363 fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
11367 if (gameMode == MachinePlaysWhite ||
11368 gameMode == MachinePlaysBlack) {
11371 DisplayBothClocks();
11373 if (gameMode == PlayFromGameFile) {
11374 if (appData.timeDelay >= 0)
11375 AutoPlayGameLoop();
11376 } else if (gameMode == IcsExamining && pauseExamInvalid) {
11377 Reset(FALSE, TRUE);
11378 SendToICS(ics_prefix);
11379 SendToICS("refresh\n");
11380 } else if (currentMove < forwardMostMove) {
11381 ForwardInner(forwardMostMove);
11383 pauseExamInvalid = FALSE;
11385 switch (gameMode) {
11389 pauseExamForwardMostMove = forwardMostMove;
11390 pauseExamInvalid = FALSE;
11393 case IcsPlayingWhite:
11394 case IcsPlayingBlack:
11398 case PlayFromGameFile:
11399 (void) StopLoadGameTimer();
11403 case BeginningOfGame:
11404 if (appData.icsActive) return;
11405 /* else fall through */
11406 case MachinePlaysWhite:
11407 case MachinePlaysBlack:
11408 case TwoMachinesPlay:
11409 if (forwardMostMove == 0)
11410 return; /* don't pause if no one has moved */
11411 if ((gameMode == MachinePlaysWhite &&
11412 !WhiteOnMove(forwardMostMove)) ||
11413 (gameMode == MachinePlaysBlack &&
11414 WhiteOnMove(forwardMostMove))) {
11427 char title[MSG_SIZ];
11429 if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
11430 strcpy(title, _("Edit comment"));
11432 sprintf(title, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
11433 WhiteOnMove(currentMove - 1) ? " " : ".. ",
11434 parseList[currentMove - 1]);
11437 EditCommentPopUp(currentMove, title, commentList[currentMove]);
11444 char *tags = PGNTags(&gameInfo);
11445 EditTagsPopUp(tags);
11452 if (appData.noChessProgram || gameMode == AnalyzeMode)
11455 if (gameMode != AnalyzeFile) {
11456 if (!appData.icsEngineAnalyze) {
11458 if (gameMode != EditGame) return;
11460 ResurrectChessProgram();
11461 SendToProgram("analyze\n", &first);
11462 first.analyzing = TRUE;
11463 /*first.maybeThinking = TRUE;*/
11464 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11465 EngineOutputPopUp();
11467 if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
11472 StartAnalysisClock();
11473 GetTimeMark(&lastNodeCountTime);
11480 if (appData.noChessProgram || gameMode == AnalyzeFile)
11483 if (gameMode != AnalyzeMode) {
11485 if (gameMode != EditGame) return;
11486 ResurrectChessProgram();
11487 SendToProgram("analyze\n", &first);
11488 first.analyzing = TRUE;
11489 /*first.maybeThinking = TRUE;*/
11490 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11491 EngineOutputPopUp();
11493 gameMode = AnalyzeFile;
11498 StartAnalysisClock();
11499 GetTimeMark(&lastNodeCountTime);
11504 MachineWhiteEvent()
11507 char *bookHit = NULL;
11509 if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
11513 if (gameMode == PlayFromGameFile ||
11514 gameMode == TwoMachinesPlay ||
11515 gameMode == Training ||
11516 gameMode == AnalyzeMode ||
11517 gameMode == EndOfGame)
11520 if (gameMode == EditPosition)
11521 EditPositionDone(TRUE);
11523 if (!WhiteOnMove(currentMove)) {
11524 DisplayError(_("It is not White's turn"), 0);
11528 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
11531 if (gameMode == EditGame || gameMode == AnalyzeMode ||
11532 gameMode == AnalyzeFile)
11535 ResurrectChessProgram(); /* in case it isn't running */
11536 if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
11537 gameMode = MachinePlaysWhite;
11540 gameMode = MachinePlaysWhite;
11544 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
11546 if (first.sendName) {
11547 sprintf(buf, "name %s\n", gameInfo.black);
11548 SendToProgram(buf, &first);
11550 if (first.sendTime) {
11551 if (first.useColors) {
11552 SendToProgram("black\n", &first); /*gnu kludge*/
11554 SendTimeRemaining(&first, TRUE);
11556 if (first.useColors) {
11557 SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
11559 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
11560 SetMachineThinkingEnables();
11561 first.maybeThinking = TRUE;
11565 if (appData.autoFlipView && !flipView) {
11566 flipView = !flipView;
11567 DrawPosition(FALSE, NULL);
11568 DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;
11571 if(bookHit) { // [HGM] book: simulate book reply
11572 static char bookMove[MSG_SIZ]; // a bit generous?
11574 programStats.nodes = programStats.depth = programStats.time =
11575 programStats.score = programStats.got_only_move = 0;
11576 sprintf(programStats.movelist, "%s (xbook)", bookHit);
11578 strcpy(bookMove, "move ");
11579 strcat(bookMove, bookHit);
11580 HandleMachineMove(bookMove, &first);
11585 MachineBlackEvent()
11588 char *bookHit = NULL;
11590 if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
11594 if (gameMode == PlayFromGameFile ||
11595 gameMode == TwoMachinesPlay ||
11596 gameMode == Training ||
11597 gameMode == AnalyzeMode ||
11598 gameMode == EndOfGame)
11601 if (gameMode == EditPosition)
11602 EditPositionDone(TRUE);
11604 if (WhiteOnMove(currentMove)) {
11605 DisplayError(_("It is not Black's turn"), 0);
11609 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
11612 if (gameMode == EditGame || gameMode == AnalyzeMode ||
11613 gameMode == AnalyzeFile)
11616 ResurrectChessProgram(); /* in case it isn't running */
11617 gameMode = MachinePlaysBlack;
11621 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
11623 if (first.sendName) {
11624 sprintf(buf, "name %s\n", gameInfo.white);
11625 SendToProgram(buf, &first);
11627 if (first.sendTime) {
11628 if (first.useColors) {
11629 SendToProgram("white\n", &first); /*gnu kludge*/
11631 SendTimeRemaining(&first, FALSE);
11633 if (first.useColors) {
11634 SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
11636 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
11637 SetMachineThinkingEnables();
11638 first.maybeThinking = TRUE;
11641 if (appData.autoFlipView && flipView) {
11642 flipView = !flipView;
11643 DrawPosition(FALSE, NULL);
11644 DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;
11646 if(bookHit) { // [HGM] book: simulate book reply
11647 static char bookMove[MSG_SIZ]; // a bit generous?
11649 programStats.nodes = programStats.depth = programStats.time =
11650 programStats.score = programStats.got_only_move = 0;
11651 sprintf(programStats.movelist, "%s (xbook)", bookHit);
11653 strcpy(bookMove, "move ");
11654 strcat(bookMove, bookHit);
11655 HandleMachineMove(bookMove, &first);
11661 DisplayTwoMachinesTitle()
11664 if (appData.matchGames > 0) {
11665 if (first.twoMachinesColor[0] == 'w') {
11666 sprintf(buf, "%s vs. %s (%d-%d-%d)",
11667 gameInfo.white, gameInfo.black,
11668 first.matchWins, second.matchWins,
11669 matchGame - 1 - (first.matchWins + second.matchWins));
11671 sprintf(buf, "%s vs. %s (%d-%d-%d)",
11672 gameInfo.white, gameInfo.black,
11673 second.matchWins, first.matchWins,
11674 matchGame - 1 - (first.matchWins + second.matchWins));
11677 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
11683 TwoMachinesEvent P((void))
11687 ChessProgramState *onmove;
11688 char *bookHit = NULL;
11690 if (appData.noChessProgram) return;
11692 switch (gameMode) {
11693 case TwoMachinesPlay:
11695 case MachinePlaysWhite:
11696 case MachinePlaysBlack:
11697 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
11698 DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
11702 case BeginningOfGame:
11703 case PlayFromGameFile:
11706 if (gameMode != EditGame) return;
11709 EditPositionDone(TRUE);
11720 // forwardMostMove = currentMove;
11721 TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
11722 ResurrectChessProgram(); /* in case first program isn't running */
11724 if (second.pr == NULL) {
11725 StartChessProgram(&second);
11726 if (second.protocolVersion == 1) {
11727 TwoMachinesEventIfReady();
11729 /* kludge: allow timeout for initial "feature" command */
11731 DisplayMessage("", _("Starting second chess program"));
11732 ScheduleDelayedEvent(TwoMachinesEventIfReady, FEATURE_TIMEOUT);
11736 DisplayMessage("", "");
11737 InitChessProgram(&second, FALSE);
11738 SendToProgram("force\n", &second);
11739 if (startedFromSetupPosition) {
11740 SendBoard(&second, backwardMostMove);
11741 if (appData.debugMode) {
11742 fprintf(debugFP, "Two Machines\n");
11745 for (i = backwardMostMove; i < forwardMostMove; i++) {
11746 SendMoveToProgram(i, &second);
11749 gameMode = TwoMachinesPlay;
11753 DisplayTwoMachinesTitle();
11755 if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
11761 SendToProgram(first.computerString, &first);
11762 if (first.sendName) {
11763 sprintf(buf, "name %s\n", second.tidy);
11764 SendToProgram(buf, &first);
11766 SendToProgram(second.computerString, &second);
11767 if (second.sendName) {
11768 sprintf(buf, "name %s\n", first.tidy);
11769 SendToProgram(buf, &second);
11773 if (!first.sendTime || !second.sendTime) {
11774 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11775 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11777 if (onmove->sendTime) {
11778 if (onmove->useColors) {
11779 SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
11781 SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
11783 if (onmove->useColors) {
11784 SendToProgram(onmove->twoMachinesColor, onmove);
11786 bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
11787 // SendToProgram("go\n", onmove);
11788 onmove->maybeThinking = TRUE;
11789 SetMachineThinkingEnables();
11793 if(bookHit) { // [HGM] book: simulate book reply
11794 static char bookMove[MSG_SIZ]; // a bit generous?
11796 programStats.nodes = programStats.depth = programStats.time =
11797 programStats.score = programStats.got_only_move = 0;
11798 sprintf(programStats.movelist, "%s (xbook)", bookHit);
11800 strcpy(bookMove, "move ");
11801 strcat(bookMove, bookHit);
11802 savedMessage = bookMove; // args for deferred call
11803 savedState = onmove;
11804 ScheduleDelayedEvent(DeferredBookMove, 1);
11811 if (gameMode == Training) {
11812 SetTrainingModeOff();
11813 gameMode = PlayFromGameFile;
11814 DisplayMessage("", _("Training mode off"));
11816 gameMode = Training;
11817 animateTraining = appData.animate;
11819 /* make sure we are not already at the end of the game */
11820 if (currentMove < forwardMostMove) {
11821 SetTrainingModeOn();
11822 DisplayMessage("", _("Training mode on"));
11824 gameMode = PlayFromGameFile;
11825 DisplayError(_("Already at end of game"), 0);
11834 if (!appData.icsActive) return;
11835 switch (gameMode) {
11836 case IcsPlayingWhite:
11837 case IcsPlayingBlack:
11840 case BeginningOfGame:
11848 EditPositionDone(TRUE);
11861 gameMode = IcsIdle;
11872 switch (gameMode) {
11874 SetTrainingModeOff();
11876 case MachinePlaysWhite:
11877 case MachinePlaysBlack:
11878 case BeginningOfGame:
11879 SendToProgram("force\n", &first);
11880 SetUserThinkingEnables();
11882 case PlayFromGameFile:
11883 (void) StopLoadGameTimer();
11884 if (gameFileFP != NULL) {
11889 EditPositionDone(TRUE);
11894 SendToProgram("force\n", &first);
11896 case TwoMachinesPlay:
11897 GameEnds((ChessMove) 0, NULL, GE_PLAYER);
11898 ResurrectChessProgram();
11899 SetUserThinkingEnables();
11902 ResurrectChessProgram();
11904 case IcsPlayingBlack:
11905 case IcsPlayingWhite:
11906 DisplayError(_("Warning: You are still playing a game"), 0);
11909 DisplayError(_("Warning: You are still observing a game"), 0);
11912 DisplayError(_("Warning: You are still examining a game"), 0);
11923 first.offeredDraw = second.offeredDraw = 0;
11925 if (gameMode == PlayFromGameFile) {
11926 whiteTimeRemaining = timeRemaining[0][currentMove];
11927 blackTimeRemaining = timeRemaining[1][currentMove];
11931 if (gameMode == MachinePlaysWhite ||
11932 gameMode == MachinePlaysBlack ||
11933 gameMode == TwoMachinesPlay ||
11934 gameMode == EndOfGame) {
11935 i = forwardMostMove;
11936 while (i > currentMove) {
11937 SendToProgram("undo\n", &first);
11940 whiteTimeRemaining = timeRemaining[0][currentMove];
11941 blackTimeRemaining = timeRemaining[1][currentMove];
11942 DisplayBothClocks();
11943 if (whiteFlag || blackFlag) {
11944 whiteFlag = blackFlag = 0;
11949 gameMode = EditGame;
11956 EditPositionEvent()
11958 if (gameMode == EditPosition) {
11964 if (gameMode != EditGame) return;
11966 gameMode = EditPosition;
11969 if (currentMove > 0)
11970 CopyBoard(boards[0], boards[currentMove]);
11972 blackPlaysFirst = !WhiteOnMove(currentMove);
11974 currentMove = forwardMostMove = backwardMostMove = 0;
11975 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11982 /* [DM] icsEngineAnalyze - possible call from other functions */
11983 if (appData.icsEngineAnalyze) {
11984 appData.icsEngineAnalyze = FALSE;
11986 DisplayMessage("",_("Close ICS engine analyze..."));
11988 if (first.analysisSupport && first.analyzing) {
11989 SendToProgram("exit\n", &first);
11990 first.analyzing = FALSE;
11992 thinkOutput[0] = NULLCHAR;
11996 EditPositionDone(Boolean fakeRights)
11998 int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
12000 startedFromSetupPosition = TRUE;
12001 InitChessProgram(&first, FALSE);
12002 if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
12003 boards[0][EP_STATUS] = EP_NONE;
12004 boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
12005 if(boards[0][0][BOARD_WIDTH>>1] == king) {
12006 boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : NoRights;
12007 boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
12008 } else boards[0][CASTLING][2] = NoRights;
12009 if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
12010 boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : NoRights;
12011 boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
12012 } else boards[0][CASTLING][5] = NoRights;
12014 SendToProgram("force\n", &first);
12015 if (blackPlaysFirst) {
12016 strcpy(moveList[0], "");
12017 strcpy(parseList[0], "");
12018 currentMove = forwardMostMove = backwardMostMove = 1;
12019 CopyBoard(boards[1], boards[0]);
12021 currentMove = forwardMostMove = backwardMostMove = 0;
12023 SendBoard(&first, forwardMostMove);
12024 if (appData.debugMode) {
12025 fprintf(debugFP, "EditPosDone\n");
12028 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12029 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12030 gameMode = EditGame;
12032 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12033 ClearHighlights(); /* [AS] */
12036 /* Pause for `ms' milliseconds */
12037 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
12047 } while (SubtractTimeMarks(&m2, &m1) < ms);
12050 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
12052 SendMultiLineToICS(buf)
12055 char temp[MSG_SIZ+1], *p;
12062 strncpy(temp, buf, len);
12067 if (*p == '\n' || *p == '\r')
12072 strcat(temp, "\n");
12074 SendToPlayer(temp, strlen(temp));
12078 SetWhiteToPlayEvent()
12080 if (gameMode == EditPosition) {
12081 blackPlaysFirst = FALSE;
12082 DisplayBothClocks(); /* works because currentMove is 0 */
12083 } else if (gameMode == IcsExamining) {
12084 SendToICS(ics_prefix);
12085 SendToICS("tomove white\n");
12090 SetBlackToPlayEvent()
12092 if (gameMode == EditPosition) {
12093 blackPlaysFirst = TRUE;
12094 currentMove = 1; /* kludge */
12095 DisplayBothClocks();
12097 } else if (gameMode == IcsExamining) {
12098 SendToICS(ics_prefix);
12099 SendToICS("tomove black\n");
12104 EditPositionMenuEvent(selection, x, y)
12105 ChessSquare selection;
12109 ChessSquare piece = boards[0][y][x];
12111 if (gameMode != EditPosition && gameMode != IcsExamining) return;
12113 switch (selection) {
12115 if (gameMode == IcsExamining && ics_type == ICS_FICS) {
12116 SendToICS(ics_prefix);
12117 SendToICS("bsetup clear\n");
12118 } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
12119 SendToICS(ics_prefix);
12120 SendToICS("clearboard\n");
12122 for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
12123 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
12124 for (y = 0; y < BOARD_HEIGHT; y++) {
12125 if (gameMode == IcsExamining) {
12126 if (boards[currentMove][y][x] != EmptySquare) {
12127 sprintf(buf, "%sx@%c%c\n", ics_prefix,
12132 boards[0][y][x] = p;
12137 if (gameMode == EditPosition) {
12138 DrawPosition(FALSE, boards[0]);
12143 SetWhiteToPlayEvent();
12147 SetBlackToPlayEvent();
12151 if (gameMode == IcsExamining) {
12152 if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
12153 sprintf(buf, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
12156 if(x < BOARD_LEFT || x >= BOARD_RGHT) {
12157 if(x == BOARD_LEFT-2) {
12158 if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
12159 boards[0][y][1] = 0;
12161 if(x == BOARD_RGHT+1) {
12162 if(y >= gameInfo.holdingsSize) break;
12163 boards[0][y][BOARD_WIDTH-2] = 0;
12166 boards[0][y][x] = EmptySquare;
12167 DrawPosition(FALSE, boards[0]);
12172 if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
12173 piece >= (int)BlackPawn && piece < (int)BlackMan ) {
12174 selection = (ChessSquare) (PROMOTED piece);
12175 } else if(piece == EmptySquare) selection = WhiteSilver;
12176 else selection = (ChessSquare)((int)piece - 1);
12180 if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
12181 piece > (int)BlackMan && piece <= (int)BlackKing ) {
12182 selection = (ChessSquare) (DEMOTED piece);
12183 } else if(piece == EmptySquare) selection = BlackSilver;
12184 else selection = (ChessSquare)((int)piece + 1);
12189 if(gameInfo.variant == VariantShatranj ||
12190 gameInfo.variant == VariantXiangqi ||
12191 gameInfo.variant == VariantCourier ||
12192 gameInfo.variant == VariantMakruk )
12193 selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
12198 if(gameInfo.variant == VariantXiangqi)
12199 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
12200 if(gameInfo.variant == VariantKnightmate)
12201 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
12204 if (gameMode == IcsExamining) {
12205 if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
12206 sprintf(buf, "%s%c@%c%c\n", ics_prefix,
12207 PieceToChar(selection), AAA + x, ONE + y);
12210 if(x < BOARD_LEFT || x >= BOARD_RGHT) {
12212 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
12213 n = PieceToNumber(selection - BlackPawn);
12214 if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
12215 boards[0][BOARD_HEIGHT-1-n][0] = selection;
12216 boards[0][BOARD_HEIGHT-1-n][1]++;
12218 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
12219 n = PieceToNumber(selection);
12220 if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
12221 boards[0][n][BOARD_WIDTH-1] = selection;
12222 boards[0][n][BOARD_WIDTH-2]++;
12225 boards[0][y][x] = selection;
12226 DrawPosition(TRUE, boards[0]);
12234 DropMenuEvent(selection, x, y)
12235 ChessSquare selection;
12238 ChessMove moveType;
12240 switch (gameMode) {
12241 case IcsPlayingWhite:
12242 case MachinePlaysBlack:
12243 if (!WhiteOnMove(currentMove)) {
12244 DisplayMoveError(_("It is Black's turn"));
12247 moveType = WhiteDrop;
12249 case IcsPlayingBlack:
12250 case MachinePlaysWhite:
12251 if (WhiteOnMove(currentMove)) {
12252 DisplayMoveError(_("It is White's turn"));
12255 moveType = BlackDrop;
12258 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
12264 if (moveType == BlackDrop && selection < BlackPawn) {
12265 selection = (ChessSquare) ((int) selection
12266 + (int) BlackPawn - (int) WhitePawn);
12268 if (boards[currentMove][y][x] != EmptySquare) {
12269 DisplayMoveError(_("That square is occupied"));
12273 FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
12279 /* Accept a pending offer of any kind from opponent */
12281 if (appData.icsActive) {
12282 SendToICS(ics_prefix);
12283 SendToICS("accept\n");
12284 } else if (cmailMsgLoaded) {
12285 if (currentMove == cmailOldMove &&
12286 commentList[cmailOldMove] != NULL &&
12287 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12288 "Black offers a draw" : "White offers a draw")) {
12290 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
12291 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
12293 DisplayError(_("There is no pending offer on this move"), 0);
12294 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12297 /* Not used for offers from chess program */
12304 /* Decline a pending offer of any kind from opponent */
12306 if (appData.icsActive) {
12307 SendToICS(ics_prefix);
12308 SendToICS("decline\n");
12309 } else if (cmailMsgLoaded) {
12310 if (currentMove == cmailOldMove &&
12311 commentList[cmailOldMove] != NULL &&
12312 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12313 "Black offers a draw" : "White offers a draw")) {
12315 AppendComment(cmailOldMove, "Draw declined", TRUE);
12316 DisplayComment(cmailOldMove - 1, "Draw declined");
12319 DisplayError(_("There is no pending offer on this move"), 0);
12322 /* Not used for offers from chess program */
12329 /* Issue ICS rematch command */
12330 if (appData.icsActive) {
12331 SendToICS(ics_prefix);
12332 SendToICS("rematch\n");
12339 /* Call your opponent's flag (claim a win on time) */
12340 if (appData.icsActive) {
12341 SendToICS(ics_prefix);
12342 SendToICS("flag\n");
12344 switch (gameMode) {
12347 case MachinePlaysWhite:
12350 GameEnds(GameIsDrawn, "Both players ran out of time",
12353 GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
12355 DisplayError(_("Your opponent is not out of time"), 0);
12358 case MachinePlaysBlack:
12361 GameEnds(GameIsDrawn, "Both players ran out of time",
12364 GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
12366 DisplayError(_("Your opponent is not out of time"), 0);
12376 /* Offer draw or accept pending draw offer from opponent */
12378 if (appData.icsActive) {
12379 /* Note: tournament rules require draw offers to be
12380 made after you make your move but before you punch
12381 your clock. Currently ICS doesn't let you do that;
12382 instead, you immediately punch your clock after making
12383 a move, but you can offer a draw at any time. */
12385 SendToICS(ics_prefix);
12386 SendToICS("draw\n");
12387 userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
12388 } else if (cmailMsgLoaded) {
12389 if (currentMove == cmailOldMove &&
12390 commentList[cmailOldMove] != NULL &&
12391 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12392 "Black offers a draw" : "White offers a draw")) {
12393 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
12394 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
12395 } else if (currentMove == cmailOldMove + 1) {
12396 char *offer = WhiteOnMove(cmailOldMove) ?
12397 "White offers a draw" : "Black offers a draw";
12398 AppendComment(currentMove, offer, TRUE);
12399 DisplayComment(currentMove - 1, offer);
12400 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
12402 DisplayError(_("You must make your move before offering a draw"), 0);
12403 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12405 } else if (first.offeredDraw) {
12406 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
12408 if (first.sendDrawOffers) {
12409 SendToProgram("draw\n", &first);
12410 userOfferedDraw = TRUE;
12418 /* Offer Adjourn or accept pending Adjourn offer from opponent */
12420 if (appData.icsActive) {
12421 SendToICS(ics_prefix);
12422 SendToICS("adjourn\n");
12424 /* Currently GNU Chess doesn't offer or accept Adjourns */
12432 /* Offer Abort or accept pending Abort offer from opponent */
12434 if (appData.icsActive) {
12435 SendToICS(ics_prefix);
12436 SendToICS("abort\n");
12438 GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
12445 /* Resign. You can do this even if it's not your turn. */
12447 if (appData.icsActive) {
12448 SendToICS(ics_prefix);
12449 SendToICS("resign\n");
12451 switch (gameMode) {
12452 case MachinePlaysWhite:
12453 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12455 case MachinePlaysBlack:
12456 GameEnds(BlackWins, "White resigns", GE_PLAYER);
12459 if (cmailMsgLoaded) {
12461 if (WhiteOnMove(cmailOldMove)) {
12462 GameEnds(BlackWins, "White resigns", GE_PLAYER);
12464 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12466 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
12477 StopObservingEvent()
12479 /* Stop observing current games */
12480 SendToICS(ics_prefix);
12481 SendToICS("unobserve\n");
12485 StopExaminingEvent()
12487 /* Stop observing current game */
12488 SendToICS(ics_prefix);
12489 SendToICS("unexamine\n");
12493 ForwardInner(target)
12498 if (appData.debugMode)
12499 fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
12500 target, currentMove, forwardMostMove);
12502 if (gameMode == EditPosition)
12505 if (gameMode == PlayFromGameFile && !pausing)
12508 if (gameMode == IcsExamining && pausing)
12509 limit = pauseExamForwardMostMove;
12511 limit = forwardMostMove;
12513 if (target > limit) target = limit;
12515 if (target > 0 && moveList[target - 1][0]) {
12516 int fromX, fromY, toX, toY;
12517 toX = moveList[target - 1][2] - AAA;
12518 toY = moveList[target - 1][3] - ONE;
12519 if (moveList[target - 1][1] == '@') {
12520 if (appData.highlightLastMove) {
12521 SetHighlights(-1, -1, toX, toY);
12524 fromX = moveList[target - 1][0] - AAA;
12525 fromY = moveList[target - 1][1] - ONE;
12526 if (target == currentMove + 1) {
12527 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
12529 if (appData.highlightLastMove) {
12530 SetHighlights(fromX, fromY, toX, toY);
12534 if (gameMode == EditGame || gameMode == AnalyzeMode ||
12535 gameMode == Training || gameMode == PlayFromGameFile ||
12536 gameMode == AnalyzeFile) {
12537 while (currentMove < target) {
12538 SendMoveToProgram(currentMove++, &first);
12541 currentMove = target;
12544 if (gameMode == EditGame || gameMode == EndOfGame) {
12545 whiteTimeRemaining = timeRemaining[0][currentMove];
12546 blackTimeRemaining = timeRemaining[1][currentMove];
12548 DisplayBothClocks();
12549 DisplayMove(currentMove - 1);
12550 DrawPosition(FALSE, boards[currentMove]);
12551 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
12552 if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
12553 DisplayComment(currentMove - 1, commentList[currentMove]);
12561 if (gameMode == IcsExamining && !pausing) {
12562 SendToICS(ics_prefix);
12563 SendToICS("forward\n");
12565 ForwardInner(currentMove + 1);
12572 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12573 /* to optimze, we temporarily turn off analysis mode while we feed
12574 * the remaining moves to the engine. Otherwise we get analysis output
12577 if (first.analysisSupport) {
12578 SendToProgram("exit\nforce\n", &first);
12579 first.analyzing = FALSE;
12583 if (gameMode == IcsExamining && !pausing) {
12584 SendToICS(ics_prefix);
12585 SendToICS("forward 999999\n");
12587 ForwardInner(forwardMostMove);
12590 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12591 /* we have fed all the moves, so reactivate analysis mode */
12592 SendToProgram("analyze\n", &first);
12593 first.analyzing = TRUE;
12594 /*first.maybeThinking = TRUE;*/
12595 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12600 BackwardInner(target)
12603 int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
12605 if (appData.debugMode)
12606 fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
12607 target, currentMove, forwardMostMove);
12609 if (gameMode == EditPosition) return;
12610 if (currentMove <= backwardMostMove) {
12612 DrawPosition(full_redraw, boards[currentMove]);
12615 if (gameMode == PlayFromGameFile && !pausing)
12618 if (moveList[target][0]) {
12619 int fromX, fromY, toX, toY;
12620 toX = moveList[target][2] - AAA;
12621 toY = moveList[target][3] - ONE;
12622 if (moveList[target][1] == '@') {
12623 if (appData.highlightLastMove) {
12624 SetHighlights(-1, -1, toX, toY);
12627 fromX = moveList[target][0] - AAA;
12628 fromY = moveList[target][1] - ONE;
12629 if (target == currentMove - 1) {
12630 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
12632 if (appData.highlightLastMove) {
12633 SetHighlights(fromX, fromY, toX, toY);
12637 if (gameMode == EditGame || gameMode==AnalyzeMode ||
12638 gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
12639 while (currentMove > target) {
12640 SendToProgram("undo\n", &first);
12644 currentMove = target;
12647 if (gameMode == EditGame || gameMode == EndOfGame) {
12648 whiteTimeRemaining = timeRemaining[0][currentMove];
12649 blackTimeRemaining = timeRemaining[1][currentMove];
12651 DisplayBothClocks();
12652 DisplayMove(currentMove - 1);
12653 DrawPosition(full_redraw, boards[currentMove]);
12654 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
12655 // [HGM] PV info: routine tests if comment empty
12656 DisplayComment(currentMove - 1, commentList[currentMove]);
12662 if (gameMode == IcsExamining && !pausing) {
12663 SendToICS(ics_prefix);
12664 SendToICS("backward\n");
12666 BackwardInner(currentMove - 1);
12673 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12674 /* to optimize, we temporarily turn off analysis mode while we undo
12675 * all the moves. Otherwise we get analysis output after each undo.
12677 if (first.analysisSupport) {
12678 SendToProgram("exit\nforce\n", &first);
12679 first.analyzing = FALSE;
12683 if (gameMode == IcsExamining && !pausing) {
12684 SendToICS(ics_prefix);
12685 SendToICS("backward 999999\n");
12687 BackwardInner(backwardMostMove);
12690 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12691 /* we have fed all the moves, so reactivate analysis mode */
12692 SendToProgram("analyze\n", &first);
12693 first.analyzing = TRUE;
12694 /*first.maybeThinking = TRUE;*/
12695 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12702 if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
12703 if (to >= forwardMostMove) to = forwardMostMove;
12704 if (to <= backwardMostMove) to = backwardMostMove;
12705 if (to < currentMove) {
12713 RevertEvent(Boolean annotate)
12715 if(PopTail(annotate)) { // [HGM] vari: restore old game tail
12718 if (gameMode != IcsExamining) {
12719 DisplayError(_("You are not examining a game"), 0);
12723 DisplayError(_("You can't revert while pausing"), 0);
12726 SendToICS(ics_prefix);
12727 SendToICS("revert\n");
12733 switch (gameMode) {
12734 case MachinePlaysWhite:
12735 case MachinePlaysBlack:
12736 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
12737 DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
12740 if (forwardMostMove < 2) return;
12741 currentMove = forwardMostMove = forwardMostMove - 2;
12742 whiteTimeRemaining = timeRemaining[0][currentMove];
12743 blackTimeRemaining = timeRemaining[1][currentMove];
12744 DisplayBothClocks();
12745 DisplayMove(currentMove - 1);
12746 ClearHighlights();/*!! could figure this out*/
12747 DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
12748 SendToProgram("remove\n", &first);
12749 /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
12752 case BeginningOfGame:
12756 case IcsPlayingWhite:
12757 case IcsPlayingBlack:
12758 if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
12759 SendToICS(ics_prefix);
12760 SendToICS("takeback 2\n");
12762 SendToICS(ics_prefix);
12763 SendToICS("takeback 1\n");
12772 ChessProgramState *cps;
12774 switch (gameMode) {
12775 case MachinePlaysWhite:
12776 if (!WhiteOnMove(forwardMostMove)) {
12777 DisplayError(_("It is your turn"), 0);
12782 case MachinePlaysBlack:
12783 if (WhiteOnMove(forwardMostMove)) {
12784 DisplayError(_("It is your turn"), 0);
12789 case TwoMachinesPlay:
12790 if (WhiteOnMove(forwardMostMove) ==
12791 (first.twoMachinesColor[0] == 'w')) {
12797 case BeginningOfGame:
12801 SendToProgram("?\n", cps);
12805 TruncateGameEvent()
12808 if (gameMode != EditGame) return;
12815 CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
12816 if (forwardMostMove > currentMove) {
12817 if (gameInfo.resultDetails != NULL) {
12818 free(gameInfo.resultDetails);
12819 gameInfo.resultDetails = NULL;
12820 gameInfo.result = GameUnfinished;
12822 forwardMostMove = currentMove;
12823 HistorySet(parseList, backwardMostMove, forwardMostMove,
12831 if (appData.noChessProgram) return;
12832 switch (gameMode) {
12833 case MachinePlaysWhite:
12834 if (WhiteOnMove(forwardMostMove)) {
12835 DisplayError(_("Wait until your turn"), 0);
12839 case BeginningOfGame:
12840 case MachinePlaysBlack:
12841 if (!WhiteOnMove(forwardMostMove)) {
12842 DisplayError(_("Wait until your turn"), 0);
12847 DisplayError(_("No hint available"), 0);
12850 SendToProgram("hint\n", &first);
12851 hintRequested = TRUE;
12857 if (appData.noChessProgram) return;
12858 switch (gameMode) {
12859 case MachinePlaysWhite:
12860 if (WhiteOnMove(forwardMostMove)) {
12861 DisplayError(_("Wait until your turn"), 0);
12865 case BeginningOfGame:
12866 case MachinePlaysBlack:
12867 if (!WhiteOnMove(forwardMostMove)) {
12868 DisplayError(_("Wait until your turn"), 0);
12873 EditPositionDone(TRUE);
12875 case TwoMachinesPlay:
12880 SendToProgram("bk\n", &first);
12881 bookOutput[0] = NULLCHAR;
12882 bookRequested = TRUE;
12888 char *tags = PGNTags(&gameInfo);
12889 TagsPopUp(tags, CmailMsg());
12893 /* end button procedures */
12896 PrintPosition(fp, move)
12902 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
12903 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
12904 char c = PieceToChar(boards[move][i][j]);
12905 fputc(c == 'x' ? '.' : c, fp);
12906 fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
12909 if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
12910 fprintf(fp, "white to play\n");
12912 fprintf(fp, "black to play\n");
12919 if (gameInfo.white != NULL) {
12920 fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
12926 /* Find last component of program's own name, using some heuristics */
12928 TidyProgramName(prog, host, buf)
12929 char *prog, *host, buf[MSG_SIZ];
12932 int local = (strcmp(host, "localhost") == 0);
12933 while (!local && (p = strchr(prog, ';')) != NULL) {
12935 while (*p == ' ') p++;
12938 if (*prog == '"' || *prog == '\'') {
12939 q = strchr(prog + 1, *prog);
12941 q = strchr(prog, ' ');
12943 if (q == NULL) q = prog + strlen(prog);
12945 while (p >= prog && *p != '/' && *p != '\\') p--;
12947 if(p == prog && *p == '"') p++;
12948 if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
12949 memcpy(buf, p, q - p);
12950 buf[q - p] = NULLCHAR;
12958 TimeControlTagValue()
12961 if (!appData.clockMode) {
12963 } else if (movesPerSession > 0) {
12964 sprintf(buf, "%d/%ld", movesPerSession, timeControl/1000);
12965 } else if (timeIncrement == 0) {
12966 sprintf(buf, "%ld", timeControl/1000);
12968 sprintf(buf, "%ld+%ld", timeControl/1000, timeIncrement/1000);
12970 return StrSave(buf);
12976 /* This routine is used only for certain modes */
12977 VariantClass v = gameInfo.variant;
12978 ChessMove r = GameUnfinished;
12981 if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
12982 r = gameInfo.result;
12983 p = gameInfo.resultDetails;
12984 gameInfo.resultDetails = NULL;
12986 ClearGameInfo(&gameInfo);
12987 gameInfo.variant = v;
12989 switch (gameMode) {
12990 case MachinePlaysWhite:
12991 gameInfo.event = StrSave( appData.pgnEventHeader );
12992 gameInfo.site = StrSave(HostName());
12993 gameInfo.date = PGNDate();
12994 gameInfo.round = StrSave("-");
12995 gameInfo.white = StrSave(first.tidy);
12996 gameInfo.black = StrSave(UserName());
12997 gameInfo.timeControl = TimeControlTagValue();
13000 case MachinePlaysBlack:
13001 gameInfo.event = StrSave( appData.pgnEventHeader );
13002 gameInfo.site = StrSave(HostName());
13003 gameInfo.date = PGNDate();
13004 gameInfo.round = StrSave("-");
13005 gameInfo.white = StrSave(UserName());
13006 gameInfo.black = StrSave(first.tidy);
13007 gameInfo.timeControl = TimeControlTagValue();
13010 case TwoMachinesPlay:
13011 gameInfo.event = StrSave( appData.pgnEventHeader );
13012 gameInfo.site = StrSave(HostName());
13013 gameInfo.date = PGNDate();
13014 if (matchGame > 0) {
13016 sprintf(buf, "%d", matchGame);
13017 gameInfo.round = StrSave(buf);
13019 gameInfo.round = StrSave("-");
13021 if (first.twoMachinesColor[0] == 'w') {
13022 gameInfo.white = StrSave(first.tidy);
13023 gameInfo.black = StrSave(second.tidy);
13025 gameInfo.white = StrSave(second.tidy);
13026 gameInfo.black = StrSave(first.tidy);
13028 gameInfo.timeControl = TimeControlTagValue();
13032 gameInfo.event = StrSave("Edited game");
13033 gameInfo.site = StrSave(HostName());
13034 gameInfo.date = PGNDate();
13035 gameInfo.round = StrSave("-");
13036 gameInfo.white = StrSave("-");
13037 gameInfo.black = StrSave("-");
13038 gameInfo.result = r;
13039 gameInfo.resultDetails = p;
13043 gameInfo.event = StrSave("Edited position");
13044 gameInfo.site = StrSave(HostName());
13045 gameInfo.date = PGNDate();
13046 gameInfo.round = StrSave("-");
13047 gameInfo.white = StrSave("-");
13048 gameInfo.black = StrSave("-");
13051 case IcsPlayingWhite:
13052 case IcsPlayingBlack:
13057 case PlayFromGameFile:
13058 gameInfo.event = StrSave("Game from non-PGN file");
13059 gameInfo.site = StrSave(HostName());
13060 gameInfo.date = PGNDate();
13061 gameInfo.round = StrSave("-");
13062 gameInfo.white = StrSave("?");
13063 gameInfo.black = StrSave("?");
13072 ReplaceComment(index, text)
13078 while (*text == '\n') text++;
13079 len = strlen(text);
13080 while (len > 0 && text[len - 1] == '\n') len--;
13082 if (commentList[index] != NULL)
13083 free(commentList[index]);
13086 commentList[index] = NULL;
13089 if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
13090 *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
13091 *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
13092 commentList[index] = (char *) malloc(len + 2);
13093 strncpy(commentList[index], text, len);
13094 commentList[index][len] = '\n';
13095 commentList[index][len + 1] = NULLCHAR;
13097 // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
13099 commentList[index] = (char *) malloc(len + 6);
13100 strcpy(commentList[index], "{\n");
13101 strncpy(commentList[index]+2, text, len);
13102 commentList[index][len+2] = NULLCHAR;
13103 while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
13104 strcat(commentList[index], "\n}\n");
13118 if (ch == '\r') continue;
13120 } while (ch != '\0');
13124 AppendComment(index, text, addBraces)
13127 Boolean addBraces; // [HGM] braces: tells if we should add {}
13132 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
13133 text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
13136 while (*text == '\n') text++;
13137 len = strlen(text);
13138 while (len > 0 && text[len - 1] == '\n') len--;
13140 if (len == 0) return;
13142 if (commentList[index] != NULL) {
13143 old = commentList[index];
13144 oldlen = strlen(old);
13145 while(commentList[index][oldlen-1] == '\n')
13146 commentList[index][--oldlen] = NULLCHAR;
13147 commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
13148 strcpy(commentList[index], old);
13150 // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
13151 if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces)) {
13152 if(addBraces) addBraces = FALSE; else { text++; len--; }
13153 while (*text == '\n') { text++; len--; }
13154 commentList[index][--oldlen] = NULLCHAR;
13156 if(addBraces) strcat(commentList[index], "\n{\n");
13157 else strcat(commentList[index], "\n");
13158 strcat(commentList[index], text);
13159 if(addBraces) strcat(commentList[index], "\n}\n");
13160 else strcat(commentList[index], "\n");
13162 commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
13164 strcpy(commentList[index], "{\n");
13165 else commentList[index][0] = NULLCHAR;
13166 strcat(commentList[index], text);
13167 strcat(commentList[index], "\n");
13168 if(addBraces) strcat(commentList[index], "}\n");
13172 static char * FindStr( char * text, char * sub_text )
13174 char * result = strstr( text, sub_text );
13176 if( result != NULL ) {
13177 result += strlen( sub_text );
13183 /* [AS] Try to extract PV info from PGN comment */
13184 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
13185 char *GetInfoFromComment( int index, char * text )
13189 if( text != NULL && index > 0 ) {
13192 int time = -1, sec = 0, deci;
13193 char * s_eval = FindStr( text, "[%eval " );
13194 char * s_emt = FindStr( text, "[%emt " );
13196 if( s_eval != NULL || s_emt != NULL ) {
13200 if( s_eval != NULL ) {
13201 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
13205 if( delim != ']' ) {
13210 if( s_emt != NULL ) {
13215 /* We expect something like: [+|-]nnn.nn/dd */
13218 if(*text != '{') return text; // [HGM] braces: must be normal comment
13220 sep = strchr( text, '/' );
13221 if( sep == NULL || sep < (text+4) ) {
13225 time = -1; sec = -1; deci = -1;
13226 if( sscanf( text+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
13227 sscanf( text+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
13228 sscanf( text+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
13229 sscanf( text+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3 ) {
13233 if( score_lo < 0 || score_lo >= 100 ) {
13237 if(sec >= 0) time = 600*time + 10*sec; else
13238 if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
13240 score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
13242 /* [HGM] PV time: now locate end of PV info */
13243 while( *++sep >= '0' && *sep <= '9'); // strip depth
13245 while( *++sep >= '0' && *sep <= '9'); // strip time
13247 while( *++sep >= '0' && *sep <= '9'); // strip seconds
13249 while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
13250 while(*sep == ' ') sep++;
13261 pvInfoList[index-1].depth = depth;
13262 pvInfoList[index-1].score = score;
13263 pvInfoList[index-1].time = 10*time; // centi-sec
13264 if(*sep == '}') *sep = 0; else *--sep = '{';
13270 SendToProgram(message, cps)
13272 ChessProgramState *cps;
13274 int count, outCount, error;
13277 if (cps->pr == NULL) return;
13280 if (appData.debugMode) {
13283 fprintf(debugFP, "%ld >%-6s: %s",
13284 SubtractTimeMarks(&now, &programStartTime),
13285 cps->which, message);
13288 count = strlen(message);
13289 outCount = OutputToProcess(cps->pr, message, count, &error);
13290 if (outCount < count && !exiting
13291 && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
13292 sprintf(buf, _("Error writing to %s chess program"), cps->which);
13293 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
13294 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
13295 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
13296 sprintf(buf, "%s program exits in draw position (%s)", cps->which, cps->program);
13298 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
13300 gameInfo.resultDetails = StrSave(buf);
13302 if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
13307 ReceiveFromProgram(isr, closure, message, count, error)
13308 InputSourceRef isr;
13316 ChessProgramState *cps = (ChessProgramState *)closure;
13318 if (isr != cps->isr) return; /* Killed intentionally */
13322 _("Error: %s chess program (%s) exited unexpectedly"),
13323 cps->which, cps->program);
13324 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
13325 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
13326 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
13327 sprintf(buf, _("%s program exits in draw position (%s)"), cps->which, cps->program);
13329 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
13331 gameInfo.resultDetails = StrSave(buf);
13333 RemoveInputSource(cps->isr);
13334 if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
13337 _("Error reading from %s chess program (%s)"),
13338 cps->which, cps->program);
13339 RemoveInputSource(cps->isr);
13341 /* [AS] Program is misbehaving badly... kill it */
13342 if( count == -2 ) {
13343 DestroyChildProcess( cps->pr, 9 );
13347 if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
13352 if ((end_str = strchr(message, '\r')) != NULL)
13353 *end_str = NULLCHAR;
13354 if ((end_str = strchr(message, '\n')) != NULL)
13355 *end_str = NULLCHAR;
13357 if (appData.debugMode) {
13358 TimeMark now; int print = 1;
13359 char *quote = ""; char c; int i;
13361 if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
13362 char start = message[0];
13363 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
13364 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
13365 sscanf(message, "move %c", &c)!=1 && sscanf(message, "offer%c", &c)!=1 &&
13366 sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
13367 sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
13368 sscanf(message, "tell%c", &c)!=1 && sscanf(message, "0-1 %c", &c)!=1 &&
13369 sscanf(message, "1-0 %c", &c)!=1 && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
13370 sscanf(message, "pong %c", &c)!=1 && start != '#') {
13371 quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
13372 print = (appData.engineComments >= 2);
13374 message[0] = start; // restore original message
13378 fprintf(debugFP, "%ld <%-6s: %s%s\n",
13379 SubtractTimeMarks(&now, &programStartTime), cps->which,
13385 /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
13386 if (appData.icsEngineAnalyze) {
13387 if (strstr(message, "whisper") != NULL ||
13388 strstr(message, "kibitz") != NULL ||
13389 strstr(message, "tellics") != NULL) return;
13392 HandleMachineMove(message, cps);
13397 SendTimeControl(cps, mps, tc, inc, sd, st)
13398 ChessProgramState *cps;
13399 int mps, inc, sd, st;
13405 if( timeControl_2 > 0 ) {
13406 if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
13407 tc = timeControl_2;
13410 tc /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
13411 inc /= cps->timeOdds;
13412 st /= cps->timeOdds;
13414 seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
13417 /* Set exact time per move, normally using st command */
13418 if (cps->stKludge) {
13419 /* GNU Chess 4 has no st command; uses level in a nonstandard way */
13421 if (seconds == 0) {
13422 sprintf(buf, "level 1 %d\n", st/60);
13424 sprintf(buf, "level 1 %d:%02d\n", st/60, seconds);
13427 sprintf(buf, "st %d\n", st);
13430 /* Set conventional or incremental time control, using level command */
13431 if (seconds == 0) {
13432 /* Note old gnuchess bug -- minutes:seconds used to not work.
13433 Fixed in later versions, but still avoid :seconds
13434 when seconds is 0. */
13435 sprintf(buf, "level %d %ld %d\n", mps, tc/60000, inc/1000);
13437 sprintf(buf, "level %d %ld:%02d %d\n", mps, tc/60000,
13438 seconds, inc/1000);
13441 SendToProgram(buf, cps);
13443 /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
13444 /* Orthogonally, limit search to given depth */
13446 if (cps->sdKludge) {
13447 sprintf(buf, "depth\n%d\n", sd);
13449 sprintf(buf, "sd %d\n", sd);
13451 SendToProgram(buf, cps);
13454 if(cps->nps > 0) { /* [HGM] nps */
13455 if(cps->supportsNPS == FALSE) cps->nps = -1; // don't use if engine explicitly says not supported!
13457 sprintf(buf, "nps %d\n", cps->nps);
13458 SendToProgram(buf, cps);
13463 ChessProgramState *WhitePlayer()
13464 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
13466 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
13467 gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
13473 SendTimeRemaining(cps, machineWhite)
13474 ChessProgramState *cps;
13475 int /*boolean*/ machineWhite;
13477 char message[MSG_SIZ];
13480 /* Note: this routine must be called when the clocks are stopped
13481 or when they have *just* been set or switched; otherwise
13482 it will be off by the time since the current tick started.
13484 if (machineWhite) {
13485 time = whiteTimeRemaining / 10;
13486 otime = blackTimeRemaining / 10;
13488 time = blackTimeRemaining / 10;
13489 otime = whiteTimeRemaining / 10;
13491 /* [HGM] translate opponent's time by time-odds factor */
13492 otime = (otime * cps->other->timeOdds) / cps->timeOdds;
13493 if (appData.debugMode) {
13494 fprintf(debugFP, "time odds: %f %f \n", cps->timeOdds, cps->other->timeOdds);
13497 if (time <= 0) time = 1;
13498 if (otime <= 0) otime = 1;
13500 sprintf(message, "time %ld\n", time);
13501 SendToProgram(message, cps);
13503 sprintf(message, "otim %ld\n", otime);
13504 SendToProgram(message, cps);
13508 BoolFeature(p, name, loc, cps)
13512 ChessProgramState *cps;
13515 int len = strlen(name);
13517 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
13519 sscanf(*p, "%d", &val);
13521 while (**p && **p != ' ') (*p)++;
13522 sprintf(buf, "accepted %s\n", name);
13523 SendToProgram(buf, cps);
13530 IntFeature(p, name, loc, cps)
13534 ChessProgramState *cps;
13537 int len = strlen(name);
13538 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
13540 sscanf(*p, "%d", loc);
13541 while (**p && **p != ' ') (*p)++;
13542 sprintf(buf, "accepted %s\n", name);
13543 SendToProgram(buf, cps);
13550 StringFeature(p, name, loc, cps)
13554 ChessProgramState *cps;
13557 int len = strlen(name);
13558 if (strncmp((*p), name, len) == 0
13559 && (*p)[len] == '=' && (*p)[len+1] == '\"') {
13561 sscanf(*p, "%[^\"]", loc);
13562 while (**p && **p != '\"') (*p)++;
13563 if (**p == '\"') (*p)++;
13564 sprintf(buf, "accepted %s\n", name);
13565 SendToProgram(buf, cps);
13572 ParseOption(Option *opt, ChessProgramState *cps)
13573 // [HGM] options: process the string that defines an engine option, and determine
13574 // name, type, default value, and allowed value range
13576 char *p, *q, buf[MSG_SIZ];
13577 int n, min = (-1)<<31, max = 1<<31, def;
13579 if(p = strstr(opt->name, " -spin ")) {
13580 if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
13581 if(max < min) max = min; // enforce consistency
13582 if(def < min) def = min;
13583 if(def > max) def = max;
13588 } else if((p = strstr(opt->name, " -slider "))) {
13589 // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
13590 if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
13591 if(max < min) max = min; // enforce consistency
13592 if(def < min) def = min;
13593 if(def > max) def = max;
13597 opt->type = Spin; // Slider;
13598 } else if((p = strstr(opt->name, " -string "))) {
13599 opt->textValue = p+9;
13600 opt->type = TextBox;
13601 } else if((p = strstr(opt->name, " -file "))) {
13602 // for now -file is a synonym for -string, to already provide compatibility with future polyglots
13603 opt->textValue = p+7;
13604 opt->type = TextBox; // FileName;
13605 } else if((p = strstr(opt->name, " -path "))) {
13606 // for now -file is a synonym for -string, to already provide compatibility with future polyglots
13607 opt->textValue = p+7;
13608 opt->type = TextBox; // PathName;
13609 } else if(p = strstr(opt->name, " -check ")) {
13610 if(sscanf(p, " -check %d", &def) < 1) return FALSE;
13611 opt->value = (def != 0);
13612 opt->type = CheckBox;
13613 } else if(p = strstr(opt->name, " -combo ")) {
13614 opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
13615 cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
13616 if(*q == '*') cps->comboList[cps->comboCnt-1]++;
13617 opt->value = n = 0;
13618 while(q = StrStr(q, " /// ")) {
13619 n++; *q = 0; // count choices, and null-terminate each of them
13621 if(*q == '*') { // remember default, which is marked with * prefix
13625 cps->comboList[cps->comboCnt++] = q;
13627 cps->comboList[cps->comboCnt++] = NULL;
13629 opt->type = ComboBox;
13630 } else if(p = strstr(opt->name, " -button")) {
13631 opt->type = Button;
13632 } else if(p = strstr(opt->name, " -save")) {
13633 opt->type = SaveButton;
13634 } else return FALSE;
13635 *p = 0; // terminate option name
13636 // now look if the command-line options define a setting for this engine option.
13637 if(cps->optionSettings && cps->optionSettings[0])
13638 p = strstr(cps->optionSettings, opt->name); else p = NULL;
13639 if(p && (p == cps->optionSettings || p[-1] == ',')) {
13640 sprintf(buf, "option %s", p);
13641 if(p = strstr(buf, ",")) *p = 0;
13643 SendToProgram(buf, cps);
13649 FeatureDone(cps, val)
13650 ChessProgramState* cps;
13653 DelayedEventCallback cb = GetDelayedEvent();
13654 if ((cb == InitBackEnd3 && cps == &first) ||
13655 (cb == TwoMachinesEventIfReady && cps == &second)) {
13656 CancelDelayedEvent();
13657 ScheduleDelayedEvent(cb, val ? 1 : 3600000);
13659 cps->initDone = val;
13662 /* Parse feature command from engine */
13664 ParseFeatures(args, cps)
13666 ChessProgramState *cps;
13674 while (*p == ' ') p++;
13675 if (*p == NULLCHAR) return;
13677 if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
13678 if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
13679 if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
13680 if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
13681 if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
13682 if (BoolFeature(&p, "reuse", &val, cps)) {
13683 /* Engine can disable reuse, but can't enable it if user said no */
13684 if (!val) cps->reuse = FALSE;
13687 if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
13688 if (StringFeature(&p, "myname", &cps->tidy, cps)) {
13689 if (gameMode == TwoMachinesPlay) {
13690 DisplayTwoMachinesTitle();
13696 if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
13697 if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
13698 if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
13699 if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
13700 if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
13701 if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
13702 if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
13703 if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
13704 if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
13705 if (IntFeature(&p, "done", &val, cps)) {
13706 FeatureDone(cps, val);
13709 /* Added by Tord: */
13710 if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
13711 if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
13712 /* End of additions by Tord */
13714 /* [HGM] added features: */
13715 if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
13716 if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
13717 if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
13718 if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
13719 if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
13720 if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
13721 if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
13722 if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
13723 sprintf(buf, "rejected option %s\n", cps->option[--cps->nrOptions].name);
13724 SendToProgram(buf, cps);
13727 if(cps->nrOptions >= MAX_OPTIONS) {
13729 sprintf(buf, "%s engine has too many options\n", cps->which);
13730 DisplayError(buf, 0);
13734 if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
13735 /* End of additions by HGM */
13737 /* unknown feature: complain and skip */
13739 while (*q && *q != '=') q++;
13740 sprintf(buf, "rejected %.*s\n", (int)(q-p), p);
13741 SendToProgram(buf, cps);
13747 while (*p && *p != '\"') p++;
13748 if (*p == '\"') p++;
13750 while (*p && *p != ' ') p++;
13758 PeriodicUpdatesEvent(newState)
13761 if (newState == appData.periodicUpdates)
13764 appData.periodicUpdates=newState;
13766 /* Display type changes, so update it now */
13767 // DisplayAnalysis();
13769 /* Get the ball rolling again... */
13771 AnalysisPeriodicEvent(1);
13772 StartAnalysisClock();
13777 PonderNextMoveEvent(newState)
13780 if (newState == appData.ponderNextMove) return;
13781 if (gameMode == EditPosition) EditPositionDone(TRUE);
13783 SendToProgram("hard\n", &first);
13784 if (gameMode == TwoMachinesPlay) {
13785 SendToProgram("hard\n", &second);
13788 SendToProgram("easy\n", &first);
13789 thinkOutput[0] = NULLCHAR;
13790 if (gameMode == TwoMachinesPlay) {
13791 SendToProgram("easy\n", &second);
13794 appData.ponderNextMove = newState;
13798 NewSettingEvent(option, command, value)
13804 if (gameMode == EditPosition) EditPositionDone(TRUE);
13805 sprintf(buf, "%s%s %d\n", (option ? "option ": ""), command, value);
13806 SendToProgram(buf, &first);
13807 if (gameMode == TwoMachinesPlay) {
13808 SendToProgram(buf, &second);
13813 ShowThinkingEvent()
13814 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
13816 static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
13817 int newState = appData.showThinking
13818 // [HGM] thinking: other features now need thinking output as well
13819 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
13821 if (oldState == newState) return;
13822 oldState = newState;
13823 if (gameMode == EditPosition) EditPositionDone(TRUE);
13825 SendToProgram("post\n", &first);
13826 if (gameMode == TwoMachinesPlay) {
13827 SendToProgram("post\n", &second);
13830 SendToProgram("nopost\n", &first);
13831 thinkOutput[0] = NULLCHAR;
13832 if (gameMode == TwoMachinesPlay) {
13833 SendToProgram("nopost\n", &second);
13836 // appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
13840 AskQuestionEvent(title, question, replyPrefix, which)
13841 char *title; char *question; char *replyPrefix; char *which;
13843 ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
13844 if (pr == NoProc) return;
13845 AskQuestion(title, question, replyPrefix, pr);
13849 DisplayMove(moveNumber)
13852 char message[MSG_SIZ];
13854 char cpThinkOutput[MSG_SIZ];
13856 if(appData.noGUI) return; // [HGM] fast: suppress display of moves
13858 if (moveNumber == forwardMostMove - 1 ||
13859 gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13861 safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput));
13863 if (strchr(cpThinkOutput, '\n')) {
13864 *strchr(cpThinkOutput, '\n') = NULLCHAR;
13867 *cpThinkOutput = NULLCHAR;
13870 /* [AS] Hide thinking from human user */
13871 if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
13872 *cpThinkOutput = NULLCHAR;
13873 if( thinkOutput[0] != NULLCHAR ) {
13876 for( i=0; i<=hiddenThinkOutputState; i++ ) {
13877 cpThinkOutput[i] = '.';
13879 cpThinkOutput[i] = NULLCHAR;
13880 hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
13884 if (moveNumber == forwardMostMove - 1 &&
13885 gameInfo.resultDetails != NULL) {
13886 if (gameInfo.resultDetails[0] == NULLCHAR) {
13887 sprintf(res, " %s", PGNResult(gameInfo.result));
13889 sprintf(res, " {%s} %s",
13890 gameInfo.resultDetails, PGNResult(gameInfo.result));
13896 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
13897 DisplayMessage(res, cpThinkOutput);
13899 sprintf(message, "%d.%s%s%s", moveNumber / 2 + 1,
13900 WhiteOnMove(moveNumber) ? " " : ".. ",
13901 parseList[moveNumber], res);
13902 DisplayMessage(message, cpThinkOutput);
13907 DisplayComment(moveNumber, text)
13911 char title[MSG_SIZ];
13912 char buf[8000]; // comment can be long!
13915 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
13916 strcpy(title, "Comment");
13918 sprintf(title, "Comment on %d.%s%s", moveNumber / 2 + 1,
13919 WhiteOnMove(moveNumber) ? " " : ".. ",
13920 parseList[moveNumber]);
13922 // [HGM] PV info: display PV info together with (or as) comment
13923 if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {
13924 if(text == NULL) text = "";
13925 score = pvInfoList[moveNumber].score;
13926 sprintf(buf, "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,
13927 depth, (pvInfoList[moveNumber].time+50)/100, text);
13930 if (text != NULL && (appData.autoDisplayComment || commentUp))
13931 CommentPopUp(title, text);
13934 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
13935 * might be busy thinking or pondering. It can be omitted if your
13936 * gnuchess is configured to stop thinking immediately on any user
13937 * input. However, that gnuchess feature depends on the FIONREAD
13938 * ioctl, which does not work properly on some flavors of Unix.
13942 ChessProgramState *cps;
13945 if (!cps->useSigint) return;
13946 if (appData.noChessProgram || (cps->pr == NoProc)) return;
13947 switch (gameMode) {
13948 case MachinePlaysWhite:
13949 case MachinePlaysBlack:
13950 case TwoMachinesPlay:
13951 case IcsPlayingWhite:
13952 case IcsPlayingBlack:
13955 /* Skip if we know it isn't thinking */
13956 if (!cps->maybeThinking) return;
13957 if (appData.debugMode)
13958 fprintf(debugFP, "Interrupting %s\n", cps->which);
13959 InterruptChildProcess(cps->pr);
13960 cps->maybeThinking = FALSE;
13965 #endif /*ATTENTION*/
13971 if (whiteTimeRemaining <= 0) {
13974 if (appData.icsActive) {
13975 if (appData.autoCallFlag &&
13976 gameMode == IcsPlayingBlack && !blackFlag) {
13977 SendToICS(ics_prefix);
13978 SendToICS("flag\n");
13982 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
13984 if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
13985 if (appData.autoCallFlag) {
13986 GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
13993 if (blackTimeRemaining <= 0) {
13996 if (appData.icsActive) {
13997 if (appData.autoCallFlag &&
13998 gameMode == IcsPlayingWhite && !whiteFlag) {
13999 SendToICS(ics_prefix);
14000 SendToICS("flag\n");
14004 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
14006 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
14007 if (appData.autoCallFlag) {
14008 GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
14021 if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
14022 gameMode == PlayFromGameFile || forwardMostMove == 0) return;
14025 * add time to clocks when time control is achieved ([HGM] now also used for increment)
14027 if ( !WhiteOnMove(forwardMostMove) )
14028 /* White made time control */
14029 whiteTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
14030 /* [HGM] time odds: correct new time quota for time odds! */
14031 / WhitePlayer()->timeOdds;
14033 /* Black made time control */
14034 blackTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
14035 / WhitePlayer()->other->timeOdds;
14039 DisplayBothClocks()
14041 int wom = gameMode == EditPosition ?
14042 !blackPlaysFirst : WhiteOnMove(currentMove);
14043 DisplayWhiteClock(whiteTimeRemaining, wom);
14044 DisplayBlackClock(blackTimeRemaining, !wom);
14048 /* Timekeeping seems to be a portability nightmare. I think everyone
14049 has ftime(), but I'm really not sure, so I'm including some ifdefs
14050 to use other calls if you don't. Clocks will be less accurate if
14051 you have neither ftime nor gettimeofday.
14054 /* VS 2008 requires the #include outside of the function */
14055 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
14056 #include <sys/timeb.h>
14059 /* Get the current time as a TimeMark */
14064 #if HAVE_GETTIMEOFDAY
14066 struct timeval timeVal;
14067 struct timezone timeZone;
14069 gettimeofday(&timeVal, &timeZone);
14070 tm->sec = (long) timeVal.tv_sec;
14071 tm->ms = (int) (timeVal.tv_usec / 1000L);
14073 #else /*!HAVE_GETTIMEOFDAY*/
14076 // include <sys/timeb.h> / moved to just above start of function
14077 struct timeb timeB;
14080 tm->sec = (long) timeB.time;
14081 tm->ms = (int) timeB.millitm;
14083 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
14084 tm->sec = (long) time(NULL);
14090 /* Return the difference in milliseconds between two
14091 time marks. We assume the difference will fit in a long!
14094 SubtractTimeMarks(tm2, tm1)
14095 TimeMark *tm2, *tm1;
14097 return 1000L*(tm2->sec - tm1->sec) +
14098 (long) (tm2->ms - tm1->ms);
14103 * Code to manage the game clocks.
14105 * In tournament play, black starts the clock and then white makes a move.
14106 * We give the human user a slight advantage if he is playing white---the
14107 * clocks don't run until he makes his first move, so it takes zero time.
14108 * Also, we don't account for network lag, so we could get out of sync
14109 * with GNU Chess's clock -- but then, referees are always right.
14112 static TimeMark tickStartTM;
14113 static long intendedTickLength;
14116 NextTickLength(timeRemaining)
14117 long timeRemaining;
14119 long nominalTickLength, nextTickLength;
14121 if (timeRemaining > 0L && timeRemaining <= 10000L)
14122 nominalTickLength = 100L;
14124 nominalTickLength = 1000L;
14125 nextTickLength = timeRemaining % nominalTickLength;
14126 if (nextTickLength <= 0) nextTickLength += nominalTickLength;
14128 return nextTickLength;
14131 /* Adjust clock one minute up or down */
14133 AdjustClock(Boolean which, int dir)
14135 if(which) blackTimeRemaining += 60000*dir;
14136 else whiteTimeRemaining += 60000*dir;
14137 DisplayBothClocks();
14140 /* Stop clocks and reset to a fresh time control */
14144 (void) StopClockTimer();
14145 if (appData.icsActive) {
14146 whiteTimeRemaining = blackTimeRemaining = 0;
14147 } else if (searchTime) {
14148 whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
14149 blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
14150 } else { /* [HGM] correct new time quote for time odds */
14151 whiteTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->timeOdds;
14152 blackTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->other->timeOdds;
14154 if (whiteFlag || blackFlag) {
14156 whiteFlag = blackFlag = FALSE;
14158 DisplayBothClocks();
14161 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
14163 /* Decrement running clock by amount of time that has passed */
14167 long timeRemaining;
14168 long lastTickLength, fudge;
14171 if (!appData.clockMode) return;
14172 if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
14176 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
14178 /* Fudge if we woke up a little too soon */
14179 fudge = intendedTickLength - lastTickLength;
14180 if (fudge < 0 || fudge > FUDGE) fudge = 0;
14182 if (WhiteOnMove(forwardMostMove)) {
14183 if(whiteNPS >= 0) lastTickLength = 0;
14184 timeRemaining = whiteTimeRemaining -= lastTickLength;
14185 DisplayWhiteClock(whiteTimeRemaining - fudge,
14186 WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
14188 if(blackNPS >= 0) lastTickLength = 0;
14189 timeRemaining = blackTimeRemaining -= lastTickLength;
14190 DisplayBlackClock(blackTimeRemaining - fudge,
14191 !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
14194 if (CheckFlags()) return;
14197 intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
14198 StartClockTimer(intendedTickLength);
14200 /* if the time remaining has fallen below the alarm threshold, sound the
14201 * alarm. if the alarm has sounded and (due to a takeback or time control
14202 * with increment) the time remaining has increased to a level above the
14203 * threshold, reset the alarm so it can sound again.
14206 if (appData.icsActive && appData.icsAlarm) {
14208 /* make sure we are dealing with the user's clock */
14209 if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
14210 ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
14213 if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
14214 alarmSounded = FALSE;
14215 } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
14217 alarmSounded = TRUE;
14223 /* A player has just moved, so stop the previously running
14224 clock and (if in clock mode) start the other one.
14225 We redisplay both clocks in case we're in ICS mode, because
14226 ICS gives us an update to both clocks after every move.
14227 Note that this routine is called *after* forwardMostMove
14228 is updated, so the last fractional tick must be subtracted
14229 from the color that is *not* on move now.
14232 SwitchClocks(int newMoveNr)
14234 long lastTickLength;
14236 int flagged = FALSE;
14240 if (StopClockTimer() && appData.clockMode) {
14241 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
14242 if (!WhiteOnMove(forwardMostMove)) {
14243 if(blackNPS >= 0) lastTickLength = 0;
14244 blackTimeRemaining -= lastTickLength;
14245 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
14246 // if(pvInfoList[forwardMostMove-1].time == -1)
14247 pvInfoList[forwardMostMove-1].time = // use GUI time
14248 (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
14250 if(whiteNPS >= 0) lastTickLength = 0;
14251 whiteTimeRemaining -= lastTickLength;
14252 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
14253 // if(pvInfoList[forwardMostMove-1].time == -1)
14254 pvInfoList[forwardMostMove-1].time =
14255 (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
14257 flagged = CheckFlags();
14259 forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
14260 CheckTimeControl();
14262 if (flagged || !appData.clockMode) return;
14264 switch (gameMode) {
14265 case MachinePlaysBlack:
14266 case MachinePlaysWhite:
14267 case BeginningOfGame:
14268 if (pausing) return;
14272 case PlayFromGameFile:
14280 if (searchTime) { // [HGM] st: set clock of player that has to move to max time
14281 if(WhiteOnMove(forwardMostMove))
14282 whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
14283 else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
14287 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
14288 whiteTimeRemaining : blackTimeRemaining);
14289 StartClockTimer(intendedTickLength);
14293 /* Stop both clocks */
14297 long lastTickLength;
14300 if (!StopClockTimer()) return;
14301 if (!appData.clockMode) return;
14305 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
14306 if (WhiteOnMove(forwardMostMove)) {
14307 if(whiteNPS >= 0) lastTickLength = 0;
14308 whiteTimeRemaining -= lastTickLength;
14309 DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
14311 if(blackNPS >= 0) lastTickLength = 0;
14312 blackTimeRemaining -= lastTickLength;
14313 DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
14318 /* Start clock of player on move. Time may have been reset, so
14319 if clock is already running, stop and restart it. */
14323 (void) StopClockTimer(); /* in case it was running already */
14324 DisplayBothClocks();
14325 if (CheckFlags()) return;
14327 if (!appData.clockMode) return;
14328 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
14330 GetTimeMark(&tickStartTM);
14331 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
14332 whiteTimeRemaining : blackTimeRemaining);
14334 /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
14335 whiteNPS = blackNPS = -1;
14336 if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
14337 || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
14338 whiteNPS = first.nps;
14339 if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
14340 || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
14341 blackNPS = first.nps;
14342 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
14343 whiteNPS = second.nps;
14344 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
14345 blackNPS = second.nps;
14346 if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
14348 StartClockTimer(intendedTickLength);
14355 long second, minute, hour, day;
14357 static char buf[32];
14359 if (ms > 0 && ms <= 9900) {
14360 /* convert milliseconds to tenths, rounding up */
14361 double tenths = floor( ((double)(ms + 99L)) / 100.00 );
14363 sprintf(buf, " %03.1f ", tenths/10.0);
14367 /* convert milliseconds to seconds, rounding up */
14368 /* use floating point to avoid strangeness of integer division
14369 with negative dividends on many machines */
14370 second = (long) floor(((double) (ms + 999L)) / 1000.0);
14377 day = second / (60 * 60 * 24);
14378 second = second % (60 * 60 * 24);
14379 hour = second / (60 * 60);
14380 second = second % (60 * 60);
14381 minute = second / 60;
14382 second = second % 60;
14385 sprintf(buf, " %s%ld:%02ld:%02ld:%02ld ",
14386 sign, day, hour, minute, second);
14388 sprintf(buf, " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
14390 sprintf(buf, " %s%2ld:%02ld ", sign, minute, second);
14397 * This is necessary because some C libraries aren't ANSI C compliant yet.
14400 StrStr(string, match)
14401 char *string, *match;
14405 length = strlen(match);
14407 for (i = strlen(string) - length; i >= 0; i--, string++)
14408 if (!strncmp(match, string, length))
14415 StrCaseStr(string, match)
14416 char *string, *match;
14420 length = strlen(match);
14422 for (i = strlen(string) - length; i >= 0; i--, string++) {
14423 for (j = 0; j < length; j++) {
14424 if (ToLower(match[j]) != ToLower(string[j]))
14427 if (j == length) return string;
14441 c1 = ToLower(*s1++);
14442 c2 = ToLower(*s2++);
14443 if (c1 > c2) return 1;
14444 if (c1 < c2) return -1;
14445 if (c1 == NULLCHAR) return 0;
14454 return isupper(c) ? tolower(c) : c;
14462 return islower(c) ? toupper(c) : c;
14464 #endif /* !_amigados */
14472 if ((ret = (char *) malloc(strlen(s) + 1))) {
14479 StrSavePtr(s, savePtr)
14480 char *s, **savePtr;
14485 if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
14486 strcpy(*savePtr, s);
14498 clock = time((time_t *)NULL);
14499 tm = localtime(&clock);
14500 sprintf(buf, "%04d.%02d.%02d",
14501 tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
14502 return StrSave(buf);
14507 PositionToFEN(move, overrideCastling)
14509 char *overrideCastling;
14511 int i, j, fromX, fromY, toX, toY;
14518 whiteToPlay = (gameMode == EditPosition) ?
14519 !blackPlaysFirst : (move % 2 == 0);
14522 /* Piece placement data */
14523 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
14525 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
14526 if (boards[move][i][j] == EmptySquare) {
14528 } else { ChessSquare piece = boards[move][i][j];
14529 if (emptycount > 0) {
14530 if(emptycount<10) /* [HGM] can be >= 10 */
14531 *p++ = '0' + emptycount;
14532 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
14535 if(PieceToChar(piece) == '+') {
14536 /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
14538 piece = (ChessSquare)(DEMOTED piece);
14540 *p++ = PieceToChar(piece);
14542 /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
14543 p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
14548 if (emptycount > 0) {
14549 if(emptycount<10) /* [HGM] can be >= 10 */
14550 *p++ = '0' + emptycount;
14551 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
14558 /* [HGM] print Crazyhouse or Shogi holdings */
14559 if( gameInfo.holdingsWidth ) {
14560 *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
14562 for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
14563 piece = boards[move][i][BOARD_WIDTH-1];
14564 if( piece != EmptySquare )
14565 for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
14566 *p++ = PieceToChar(piece);
14568 for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
14569 piece = boards[move][BOARD_HEIGHT-i-1][0];
14570 if( piece != EmptySquare )
14571 for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
14572 *p++ = PieceToChar(piece);
14575 if( q == p ) *p++ = '-';
14581 *p++ = whiteToPlay ? 'w' : 'b';
14584 if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
14585 while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
14587 if(nrCastlingRights) {
14589 if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
14590 /* [HGM] write directly from rights */
14591 if(boards[move][CASTLING][2] != NoRights &&
14592 boards[move][CASTLING][0] != NoRights )
14593 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
14594 if(boards[move][CASTLING][2] != NoRights &&
14595 boards[move][CASTLING][1] != NoRights )
14596 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
14597 if(boards[move][CASTLING][5] != NoRights &&
14598 boards[move][CASTLING][3] != NoRights )
14599 *p++ = boards[move][CASTLING][3] + AAA;
14600 if(boards[move][CASTLING][5] != NoRights &&
14601 boards[move][CASTLING][4] != NoRights )
14602 *p++ = boards[move][CASTLING][4] + AAA;
14605 /* [HGM] write true castling rights */
14606 if( nrCastlingRights == 6 ) {
14607 if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
14608 boards[move][CASTLING][2] != NoRights ) *p++ = 'K';
14609 if(boards[move][CASTLING][1] == BOARD_LEFT &&
14610 boards[move][CASTLING][2] != NoRights ) *p++ = 'Q';
14611 if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
14612 boards[move][CASTLING][5] != NoRights ) *p++ = 'k';
14613 if(boards[move][CASTLING][4] == BOARD_LEFT &&
14614 boards[move][CASTLING][5] != NoRights ) *p++ = 'q';
14617 if (q == p) *p++ = '-'; /* No castling rights */
14621 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
14622 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
14623 /* En passant target square */
14624 if (move > backwardMostMove) {
14625 fromX = moveList[move - 1][0] - AAA;
14626 fromY = moveList[move - 1][1] - ONE;
14627 toX = moveList[move - 1][2] - AAA;
14628 toY = moveList[move - 1][3] - ONE;
14629 if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
14630 toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
14631 boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
14633 /* 2-square pawn move just happened */
14635 *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
14639 } else if(move == backwardMostMove) {
14640 // [HGM] perhaps we should always do it like this, and forget the above?
14641 if((signed char)boards[move][EP_STATUS] >= 0) {
14642 *p++ = boards[move][EP_STATUS] + AAA;
14643 *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
14654 /* [HGM] find reversible plies */
14655 { int i = 0, j=move;
14657 if (appData.debugMode) { int k;
14658 fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
14659 for(k=backwardMostMove; k<=forwardMostMove; k++)
14660 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
14664 while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
14665 if( j == backwardMostMove ) i += initialRulePlies;
14666 sprintf(p, "%d ", i);
14667 p += i>=100 ? 4 : i >= 10 ? 3 : 2;
14669 /* Fullmove number */
14670 sprintf(p, "%d", (move / 2) + 1);
14672 return StrSave(buf);
14676 ParseFEN(board, blackPlaysFirst, fen)
14678 int *blackPlaysFirst;
14688 /* [HGM] by default clear Crazyhouse holdings, if present */
14689 if(gameInfo.holdingsWidth) {
14690 for(i=0; i<BOARD_HEIGHT; i++) {
14691 board[i][0] = EmptySquare; /* black holdings */
14692 board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
14693 board[i][1] = (ChessSquare) 0; /* black counts */
14694 board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
14698 /* Piece placement data */
14699 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
14702 if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
14703 if (*p == '/') p++;
14704 emptycount = gameInfo.boardWidth - j;
14705 while (emptycount--)
14706 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
14708 #if(BOARD_FILES >= 10)
14709 } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
14710 p++; emptycount=10;
14711 if (j + emptycount > gameInfo.boardWidth) return FALSE;
14712 while (emptycount--)
14713 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
14715 } else if (isdigit(*p)) {
14716 emptycount = *p++ - '0';
14717 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
14718 if (j + emptycount > gameInfo.boardWidth) return FALSE;
14719 while (emptycount--)
14720 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
14721 } else if (*p == '+' || isalpha(*p)) {
14722 if (j >= gameInfo.boardWidth) return FALSE;
14724 piece = CharToPiece(*++p);
14725 if(piece == EmptySquare) return FALSE; /* unknown piece */
14726 piece = (ChessSquare) (PROMOTED piece ); p++;
14727 if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
14728 } else piece = CharToPiece(*p++);
14730 if(piece==EmptySquare) return FALSE; /* unknown piece */
14731 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
14732 piece = (ChessSquare) (PROMOTED piece);
14733 if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
14736 board[i][(j++)+gameInfo.holdingsWidth] = piece;
14742 while (*p == '/' || *p == ' ') p++;
14744 /* [HGM] look for Crazyhouse holdings here */
14745 while(*p==' ') p++;
14746 if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
14748 if(*p == '-' ) *p++; /* empty holdings */ else {
14749 if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
14750 /* if we would allow FEN reading to set board size, we would */
14751 /* have to add holdings and shift the board read so far here */
14752 while( (piece = CharToPiece(*p) ) != EmptySquare ) {
14754 if((int) piece >= (int) BlackPawn ) {
14755 i = (int)piece - (int)BlackPawn;
14756 i = PieceToNumber((ChessSquare)i);
14757 if( i >= gameInfo.holdingsSize ) return FALSE;
14758 board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
14759 board[BOARD_HEIGHT-1-i][1]++; /* black counts */
14761 i = (int)piece - (int)WhitePawn;
14762 i = PieceToNumber((ChessSquare)i);
14763 if( i >= gameInfo.holdingsSize ) return FALSE;
14764 board[i][BOARD_WIDTH-1] = piece; /* white holdings */
14765 board[i][BOARD_WIDTH-2]++; /* black holdings */
14769 if(*p == ']') *p++;
14772 while(*p == ' ') p++;
14777 *blackPlaysFirst = FALSE;
14780 *blackPlaysFirst = TRUE;
14786 /* [HGM] We NO LONGER ignore the rest of the FEN notation */
14787 /* return the extra info in global variiables */
14789 /* set defaults in case FEN is incomplete */
14790 board[EP_STATUS] = EP_UNKNOWN;
14791 for(i=0; i<nrCastlingRights; i++ ) {
14792 board[CASTLING][i] =
14793 gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
14794 } /* assume possible unless obviously impossible */
14795 if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
14796 if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
14797 if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
14798 && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
14799 if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
14800 if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
14801 if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
14802 && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
14805 while(*p==' ') p++;
14806 if(nrCastlingRights) {
14807 if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
14808 /* castling indicator present, so default becomes no castlings */
14809 for(i=0; i<nrCastlingRights; i++ ) {
14810 board[CASTLING][i] = NoRights;
14813 while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
14814 (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
14815 ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
14816 ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth) ) {
14817 char c = *p++; int whiteKingFile=NoRights, blackKingFile=NoRights;
14819 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
14820 if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
14821 if(board[0 ][i] == WhiteKing) whiteKingFile = i;
14823 if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
14824 whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
14825 if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
14826 && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
14827 if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
14828 && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
14831 for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
14832 board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
14833 board[CASTLING][2] = whiteKingFile;
14836 for(i=BOARD_LEFT; i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
14837 board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
14838 board[CASTLING][2] = whiteKingFile;
14841 for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
14842 board[CASTLING][3] = i != blackKingFile ? i : NoRights;
14843 board[CASTLING][5] = blackKingFile;
14846 for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
14847 board[CASTLING][4] = i != blackKingFile ? i : NoRights;
14848 board[CASTLING][5] = blackKingFile;
14851 default: /* FRC castlings */
14852 if(c >= 'a') { /* black rights */
14853 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
14854 if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
14855 if(i == BOARD_RGHT) break;
14856 board[CASTLING][5] = i;
14858 if(board[BOARD_HEIGHT-1][c] < BlackPawn ||
14859 board[BOARD_HEIGHT-1][c] >= BlackKing ) break;
14861 board[CASTLING][3] = c;
14863 board[CASTLING][4] = c;
14864 } else { /* white rights */
14865 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
14866 if(board[0][i] == WhiteKing) break;
14867 if(i == BOARD_RGHT) break;
14868 board[CASTLING][2] = i;
14869 c -= AAA - 'a' + 'A';
14870 if(board[0][c] >= WhiteKing) break;
14872 board[CASTLING][0] = c;
14874 board[CASTLING][1] = c;
14878 for(i=0; i<nrCastlingRights; i++)
14879 if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
14880 if (appData.debugMode) {
14881 fprintf(debugFP, "FEN castling rights:");
14882 for(i=0; i<nrCastlingRights; i++)
14883 fprintf(debugFP, " %d", board[CASTLING][i]);
14884 fprintf(debugFP, "\n");
14887 while(*p==' ') p++;
14890 /* read e.p. field in games that know e.p. capture */
14891 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
14892 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
14894 p++; board[EP_STATUS] = EP_NONE;
14896 char c = *p++ - AAA;
14898 if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
14899 if(*p >= '0' && *p <='9') *p++;
14900 board[EP_STATUS] = c;
14905 if(sscanf(p, "%d", &i) == 1) {
14906 FENrulePlies = i; /* 50-move ply counter */
14907 /* (The move number is still ignored) */
14914 EditPositionPasteFEN(char *fen)
14917 Board initial_position;
14919 if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
14920 DisplayError(_("Bad FEN position in clipboard"), 0);
14923 int savedBlackPlaysFirst = blackPlaysFirst;
14924 EditPositionEvent();
14925 blackPlaysFirst = savedBlackPlaysFirst;
14926 CopyBoard(boards[0], initial_position);
14927 initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
14928 EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
14929 DisplayBothClocks();
14930 DrawPosition(FALSE, boards[currentMove]);
14935 static char cseq[12] = "\\ ";
14937 Boolean set_cont_sequence(char *new_seq)
14942 // handle bad attempts to set the sequence
14944 return 0; // acceptable error - no debug
14946 len = strlen(new_seq);
14947 ret = (len > 0) && (len < sizeof(cseq));
14949 strcpy(cseq, new_seq);
14950 else if (appData.debugMode)
14951 fprintf(debugFP, "Invalid continuation sequence \"%s\" (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
14956 reformat a source message so words don't cross the width boundary. internal
14957 newlines are not removed. returns the wrapped size (no null character unless
14958 included in source message). If dest is NULL, only calculate the size required
14959 for the dest buffer. lp argument indicats line position upon entry, and it's
14960 passed back upon exit.
14962 int wrap(char *dest, char *src, int count, int width, int *lp)
14964 int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
14966 cseq_len = strlen(cseq);
14967 old_line = line = *lp;
14968 ansi = len = clen = 0;
14970 for (i=0; i < count; i++)
14972 if (src[i] == '\033')
14975 // if we hit the width, back up
14976 if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
14978 // store i & len in case the word is too long
14979 old_i = i, old_len = len;
14981 // find the end of the last word
14982 while (i && src[i] != ' ' && src[i] != '\n')
14988 // word too long? restore i & len before splitting it
14989 if ((old_i-i+clen) >= width)
14996 if (i && src[i-1] == ' ')
14999 if (src[i] != ' ' && src[i] != '\n')
15006 // now append the newline and continuation sequence
15011 strncpy(dest+len, cseq, cseq_len);
15019 dest[len] = src[i];
15023 if (src[i] == '\n')
15028 if (dest && appData.debugMode)
15030 fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
15031 count, width, line, len, *lp);
15032 show_bytes(debugFP, src, count);
15033 fprintf(debugFP, "\ndest: ");
15034 show_bytes(debugFP, dest, len);
15035 fprintf(debugFP, "\n");
15037 *lp = dest ? line : old_line;
15042 // [HGM] vari: routines for shelving variations
15045 PushTail(int firstMove, int lastMove)
15047 int i, j, nrMoves = lastMove - firstMove;
15049 if(appData.icsActive) { // only in local mode
15050 forwardMostMove = currentMove; // mimic old ICS behavior
15053 if(storedGames >= MAX_VARIATIONS-1) return;
15055 // push current tail of game on stack
15056 savedResult[storedGames] = gameInfo.result;
15057 savedDetails[storedGames] = gameInfo.resultDetails;
15058 gameInfo.resultDetails = NULL;
15059 savedFirst[storedGames] = firstMove;
15060 savedLast [storedGames] = lastMove;
15061 savedFramePtr[storedGames] = framePtr;
15062 framePtr -= nrMoves; // reserve space for the boards
15063 for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
15064 CopyBoard(boards[framePtr+i], boards[firstMove+i]);
15065 for(j=0; j<MOVE_LEN; j++)
15066 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
15067 for(j=0; j<2*MOVE_LEN; j++)
15068 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
15069 timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
15070 timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
15071 pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
15072 pvInfoList[firstMove+i-1].depth = 0;
15073 commentList[framePtr+i] = commentList[firstMove+i];
15074 commentList[firstMove+i] = NULL;
15078 forwardMostMove = firstMove; // truncate game so we can start variation
15079 if(storedGames == 1) GreyRevert(FALSE);
15083 PopTail(Boolean annotate)
15086 char buf[8000], moveBuf[20];
15088 if(appData.icsActive) return FALSE; // only in local mode
15089 if(!storedGames) return FALSE; // sanity
15090 CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
15093 ToNrEvent(savedFirst[storedGames]); // sets currentMove
15094 nrMoves = savedLast[storedGames] - currentMove;
15097 if(!WhiteOnMove(currentMove)) sprintf(buf, "(%d...", currentMove+2>>1);
15098 else strcpy(buf, "(");
15099 for(i=currentMove; i<forwardMostMove; i++) {
15101 sprintf(moveBuf, " %d. %s", i+2>>1, SavePart(parseList[i]));
15102 else sprintf(moveBuf, " %s", SavePart(parseList[i]));
15103 strcat(buf, moveBuf);
15104 if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
15105 if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
15109 for(i=1; i<=nrMoves; i++) { // copy last variation back
15110 CopyBoard(boards[currentMove+i], boards[framePtr+i]);
15111 for(j=0; j<MOVE_LEN; j++)
15112 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
15113 for(j=0; j<2*MOVE_LEN; j++)
15114 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
15115 timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
15116 timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
15117 pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
15118 if(commentList[currentMove+i]) free(commentList[currentMove+i]);
15119 commentList[currentMove+i] = commentList[framePtr+i];
15120 commentList[framePtr+i] = NULL;
15122 if(annotate) AppendComment(currentMove+1, buf, FALSE);
15123 framePtr = savedFramePtr[storedGames];
15124 gameInfo.result = savedResult[storedGames];
15125 if(gameInfo.resultDetails != NULL) {
15126 free(gameInfo.resultDetails);
15128 gameInfo.resultDetails = savedDetails[storedGames];
15129 forwardMostMove = currentMove + nrMoves;
15130 if(storedGames == 0) GreyRevert(TRUE);
15136 { // remove all shelved variations
15138 for(i=0; i<storedGames; i++) {
15139 if(savedDetails[i])
15140 free(savedDetails[i]);
15141 savedDetails[i] = NULL;
15143 for(i=framePtr; i<MAX_MOVES; i++) {
15144 if(commentList[i]) free(commentList[i]);
15145 commentList[i] = NULL;
15147 framePtr = MAX_MOVES-1;
15152 LoadVariation(int index, char *text)
15153 { // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
15154 char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
15155 int level = 0, move;
15157 if(gameMode != EditGame && gameMode != AnalyzeMode) return;
15158 // first find outermost bracketing variation
15159 while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
15160 if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
15161 if(*p == '{') wait = '}'; else
15162 if(*p == '[') wait = ']'; else
15163 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
15164 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
15166 if(*p == wait) wait = NULLCHAR; // closing ]} found
15169 if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
15170 if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
15171 end[1] = NULLCHAR; // clip off comment beyond variation
15172 ToNrEvent(currentMove-1);
15173 PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
15174 // kludge: use ParsePV() to append variation to game
15175 move = currentMove;
15176 ParsePV(start, TRUE);
15177 forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
15178 ClearPremoveHighlights();
15180 ToNrEvent(currentMove+1);