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 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((void));
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 char endingGame = 0; /* [HGM] crash: flag to prevent recursion of GameEnds() */
246 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS */
247 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
248 int lastIndex = 0; /* [HGM] autoinc: last game/position used in match mode */
249 Boolean connectionAlive;/* [HGM] alive: ICS connection status from probing */
250 int opponentKibitzes;
251 int lastSavedGame; /* [HGM] save: ID of game */
252 char chatPartner[MAX_CHAT][MSG_SIZ]; /* [HGM] chat: list of chatting partners */
253 extern int chatCount;
255 char marker[BOARD_RANKS][BOARD_FILES]; /* [HGM] marks for target squares */
257 /* States for ics_getting_history */
259 #define H_REQUESTED 1
260 #define H_GOT_REQ_HEADER 2
261 #define H_GOT_UNREQ_HEADER 3
262 #define H_GETTING_MOVES 4
263 #define H_GOT_UNWANTED_HEADER 5
265 /* whosays values for GameEnds */
274 /* Maximum number of games in a cmail message */
275 #define CMAIL_MAX_GAMES 20
277 /* Different types of move when calling RegisterMove */
279 #define CMAIL_RESIGN 1
281 #define CMAIL_ACCEPT 3
283 /* Different types of result to remember for each game */
284 #define CMAIL_NOT_RESULT 0
285 #define CMAIL_OLD_RESULT 1
286 #define CMAIL_NEW_RESULT 2
288 /* Telnet protocol constants */
299 static char * safeStrCpy( char * dst, const char * src, size_t count )
301 assert( dst != NULL );
302 assert( src != NULL );
305 strncpy( dst, src, count );
306 dst[ count-1 ] = '\0';
310 /* Some compiler can't cast u64 to double
311 * This function do the job for us:
313 * We use the highest bit for cast, this only
314 * works if the highest bit is not
315 * in use (This should not happen)
317 * We used this for all compiler
320 u64ToDouble(u64 value)
323 u64 tmp = value & u64Const(0x7fffffffffffffff);
324 r = (double)(s64)tmp;
325 if (value & u64Const(0x8000000000000000))
326 r += 9.2233720368547758080e18; /* 2^63 */
330 /* Fake up flags for now, as we aren't keeping track of castling
331 availability yet. [HGM] Change of logic: the flag now only
332 indicates the type of castlings allowed by the rule of the game.
333 The actual rights themselves are maintained in the array
334 castlingRights, as part of the game history, and are not probed
340 int flags = F_ALL_CASTLE_OK;
341 if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
342 switch (gameInfo.variant) {
344 flags &= ~F_ALL_CASTLE_OK;
345 case VariantGiveaway: // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
346 flags |= F_IGNORE_CHECK;
348 flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
351 flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
353 case VariantKriegspiel:
354 flags |= F_KRIEGSPIEL_CAPTURE;
356 case VariantCapaRandom:
357 case VariantFischeRandom:
358 flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
359 case VariantNoCastle:
360 case VariantShatranj:
363 flags &= ~F_ALL_CASTLE_OK;
371 FILE *gameFileFP, *debugFP;
374 [AS] Note: sometimes, the sscanf() function is used to parse the input
375 into a fixed-size buffer. Because of this, we must be prepared to
376 receive strings as long as the size of the input buffer, which is currently
377 set to 4K for Windows and 8K for the rest.
378 So, we must either allocate sufficiently large buffers here, or
379 reduce the size of the input buffer in the input reading part.
382 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
383 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
384 char thinkOutput1[MSG_SIZ*10];
386 ChessProgramState first, second;
388 /* premove variables */
391 int premoveFromX = 0;
392 int premoveFromY = 0;
393 int premovePromoChar = 0;
395 Boolean alarmSounded;
396 /* end premove variables */
398 char *ics_prefix = "$";
399 int ics_type = ICS_GENERIC;
401 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
402 int pauseExamForwardMostMove = 0;
403 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
404 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
405 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
406 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
407 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
408 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
409 int whiteFlag = FALSE, blackFlag = FALSE;
410 int userOfferedDraw = FALSE;
411 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
412 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
413 int cmailMoveType[CMAIL_MAX_GAMES];
414 long ics_clock_paused = 0;
415 ProcRef icsPR = NoProc, cmailPR = NoProc;
416 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
417 GameMode gameMode = BeginningOfGame;
418 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
419 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
420 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
421 int hiddenThinkOutputState = 0; /* [AS] */
422 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
423 int adjudicateLossPlies = 6;
424 char white_holding[64], black_holding[64];
425 TimeMark lastNodeCountTime;
426 long lastNodeCount=0;
427 int have_sent_ICS_logon = 0;
429 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement;
430 long timeControl_2; /* [AS] Allow separate time controls */
431 char *fullTimeControlString = NULL; /* [HGM] secondary TC: merge of MPS, TC and inc */
432 long timeRemaining[2][MAX_MOVES];
434 TimeMark programStartTime;
435 char ics_handle[MSG_SIZ];
436 int have_set_title = 0;
438 /* animateTraining preserves the state of appData.animate
439 * when Training mode is activated. This allows the
440 * response to be animated when appData.animate == TRUE and
441 * appData.animateDragging == TRUE.
443 Boolean animateTraining;
449 Board boards[MAX_MOVES];
450 /* [HGM] Following 7 needed for accurate legality tests: */
451 signed char castlingRank[BOARD_FILES]; // and corresponding ranks
452 signed char initialRights[BOARD_FILES];
453 int nrCastlingRights; // For TwoKings, or to implement castling-unknown status
454 int initialRulePlies, FENrulePlies;
455 FILE *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
458 int mute; // mute all sounds
460 // [HGM] vari: next 12 to save and restore variations
461 #define MAX_VARIATIONS 10
462 int framePtr = MAX_MOVES-1; // points to free stack entry
464 int savedFirst[MAX_VARIATIONS];
465 int savedLast[MAX_VARIATIONS];
466 int savedFramePtr[MAX_VARIATIONS];
467 char *savedDetails[MAX_VARIATIONS];
468 ChessMove savedResult[MAX_VARIATIONS];
470 void PushTail P((int firstMove, int lastMove));
471 Boolean PopTail P((Boolean annotate));
472 void CleanupTail P((void));
474 ChessSquare FIDEArray[2][BOARD_FILES] = {
475 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
476 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
477 { BlackRook, BlackKnight, BlackBishop, BlackQueen,
478 BlackKing, BlackBishop, BlackKnight, BlackRook }
481 ChessSquare twoKingsArray[2][BOARD_FILES] = {
482 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
483 WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
484 { BlackRook, BlackKnight, BlackBishop, BlackQueen,
485 BlackKing, BlackKing, BlackKnight, BlackRook }
488 ChessSquare KnightmateArray[2][BOARD_FILES] = {
489 { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
490 WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
491 { BlackRook, BlackMan, BlackBishop, BlackQueen,
492 BlackUnicorn, BlackBishop, BlackMan, BlackRook }
495 ChessSquare fairyArray[2][BOARD_FILES] = { /* [HGM] Queen side differs from King side */
496 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
497 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
498 { BlackLance, BlackAlfil, BlackMarshall, BlackAngel,
499 BlackKing, BlackMarshall, BlackAlfil, BlackLance }
502 ChessSquare ShatranjArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
503 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
504 WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
505 { BlackRook, BlackKnight, BlackAlfil, BlackKing,
506 BlackFerz, BlackAlfil, BlackKnight, BlackRook }
509 ChessSquare makrukArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
510 { WhiteRook, WhiteKnight, WhiteMan, WhiteKing,
511 WhiteFerz, WhiteMan, WhiteKnight, WhiteRook },
512 { BlackRook, BlackKnight, BlackMan, BlackFerz,
513 BlackKing, BlackMan, BlackKnight, BlackRook }
517 #if (BOARD_FILES>=10)
518 ChessSquare ShogiArray[2][BOARD_FILES] = {
519 { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
520 WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
521 { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
522 BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
525 ChessSquare XiangqiArray[2][BOARD_FILES] = {
526 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
527 WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
528 { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
529 BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
532 ChessSquare CapablancaArray[2][BOARD_FILES] = {
533 { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
534 WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
535 { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
536 BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
539 ChessSquare GreatArray[2][BOARD_FILES] = {
540 { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
541 WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
542 { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
543 BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
546 ChessSquare JanusArray[2][BOARD_FILES] = {
547 { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
548 WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
549 { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
550 BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
554 ChessSquare GothicArray[2][BOARD_FILES] = {
555 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
556 WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
557 { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
558 BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
561 #define GothicArray CapablancaArray
565 ChessSquare FalconArray[2][BOARD_FILES] = {
566 { WhiteRook, WhiteKnight, WhiteBishop, WhiteLance, WhiteQueen,
567 WhiteKing, WhiteLance, WhiteBishop, WhiteKnight, WhiteRook },
568 { BlackRook, BlackKnight, BlackBishop, BlackLance, BlackQueen,
569 BlackKing, BlackLance, BlackBishop, BlackKnight, BlackRook }
572 #define FalconArray CapablancaArray
575 #else // !(BOARD_FILES>=10)
576 #define XiangqiPosition FIDEArray
577 #define CapablancaArray FIDEArray
578 #define GothicArray FIDEArray
579 #define GreatArray FIDEArray
580 #endif // !(BOARD_FILES>=10)
582 #if (BOARD_FILES>=12)
583 ChessSquare CourierArray[2][BOARD_FILES] = {
584 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
585 WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
586 { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
587 BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
589 #else // !(BOARD_FILES>=12)
590 #define CourierArray CapablancaArray
591 #endif // !(BOARD_FILES>=12)
594 Board initialPosition;
597 /* Convert str to a rating. Checks for special cases of "----",
599 "++++", etc. Also strips ()'s */
601 string_to_rating(str)
604 while(*str && !isdigit(*str)) ++str;
606 return 0; /* One of the special "no rating" cases */
614 /* Init programStats */
615 programStats.movelist[0] = 0;
616 programStats.depth = 0;
617 programStats.nr_moves = 0;
618 programStats.moves_left = 0;
619 programStats.nodes = 0;
620 programStats.time = -1; // [HGM] PGNtime: make invalid to recognize engine output
621 programStats.score = 0;
622 programStats.got_only_move = 0;
623 programStats.got_fail = 0;
624 programStats.line_is_book = 0;
630 int matched, min, sec;
632 ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
634 GetTimeMark(&programStartTime);
635 srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
638 programStats.ok_to_send = 1;
639 programStats.seen_stat = 0;
642 * Initialize game list
648 * Internet chess server status
650 if (appData.icsActive) {
651 appData.matchMode = FALSE;
652 appData.matchGames = 0;
654 appData.noChessProgram = !appData.zippyPlay;
656 appData.zippyPlay = FALSE;
657 appData.zippyTalk = FALSE;
658 appData.noChessProgram = TRUE;
660 if (*appData.icsHelper != NULLCHAR) {
661 appData.useTelnet = TRUE;
662 appData.telnetProgram = appData.icsHelper;
665 appData.zippyTalk = appData.zippyPlay = FALSE;
668 /* [AS] Initialize pv info list [HGM] and game state */
672 for( i=0; i<=framePtr; i++ ) {
673 pvInfoList[i].depth = -1;
674 boards[i][EP_STATUS] = EP_NONE;
675 for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
680 * Parse timeControl resource
682 if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
683 appData.movesPerSession)) {
685 snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
686 DisplayFatalError(buf, 0, 2);
690 * Parse searchTime resource
692 if (*appData.searchTime != NULLCHAR) {
693 matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
695 searchTime = min * 60;
696 } else if (matched == 2) {
697 searchTime = min * 60 + sec;
700 snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
701 DisplayFatalError(buf, 0, 2);
705 /* [AS] Adjudication threshold */
706 adjudicateLossThreshold = appData.adjudicateLossThreshold;
708 first.which = "first";
709 second.which = "second";
710 first.maybeThinking = second.maybeThinking = FALSE;
711 first.pr = second.pr = NoProc;
712 first.isr = second.isr = NULL;
713 first.sendTime = second.sendTime = 2;
714 first.sendDrawOffers = 1;
715 if (appData.firstPlaysBlack) {
716 first.twoMachinesColor = "black\n";
717 second.twoMachinesColor = "white\n";
719 first.twoMachinesColor = "white\n";
720 second.twoMachinesColor = "black\n";
722 first.program = appData.firstChessProgram;
723 second.program = appData.secondChessProgram;
724 first.host = appData.firstHost;
725 second.host = appData.secondHost;
726 first.dir = appData.firstDirectory;
727 second.dir = appData.secondDirectory;
728 first.other = &second;
729 second.other = &first;
730 first.initString = appData.initString;
731 second.initString = appData.secondInitString;
732 first.computerString = appData.firstComputerString;
733 second.computerString = appData.secondComputerString;
734 first.useSigint = second.useSigint = TRUE;
735 first.useSigterm = second.useSigterm = TRUE;
736 first.reuse = appData.reuseFirst;
737 second.reuse = appData.reuseSecond;
738 first.nps = appData.firstNPS; // [HGM] nps: copy nodes per second
739 second.nps = appData.secondNPS;
740 first.useSetboard = second.useSetboard = FALSE;
741 first.useSAN = second.useSAN = FALSE;
742 first.usePing = second.usePing = FALSE;
743 first.lastPing = second.lastPing = 0;
744 first.lastPong = second.lastPong = 0;
745 first.usePlayother = second.usePlayother = FALSE;
746 first.useColors = second.useColors = TRUE;
747 first.useUsermove = second.useUsermove = FALSE;
748 first.sendICS = second.sendICS = FALSE;
749 first.sendName = second.sendName = appData.icsActive;
750 first.sdKludge = second.sdKludge = FALSE;
751 first.stKludge = second.stKludge = FALSE;
752 TidyProgramName(first.program, first.host, first.tidy);
753 TidyProgramName(second.program, second.host, second.tidy);
754 first.matchWins = second.matchWins = 0;
755 strcpy(first.variants, appData.variant);
756 strcpy(second.variants, appData.variant);
757 first.analysisSupport = second.analysisSupport = 2; /* detect */
758 first.analyzing = second.analyzing = FALSE;
759 first.initDone = second.initDone = FALSE;
761 /* New features added by Tord: */
762 first.useFEN960 = FALSE; second.useFEN960 = FALSE;
763 first.useOOCastle = TRUE; second.useOOCastle = TRUE;
764 /* End of new features added by Tord. */
765 first.fenOverride = appData.fenOverride1;
766 second.fenOverride = appData.fenOverride2;
768 /* [HGM] time odds: set factor for each machine */
769 first.timeOdds = appData.firstTimeOdds;
770 second.timeOdds = appData.secondTimeOdds;
772 if(appData.timeOddsMode) {
773 norm = first.timeOdds;
774 if(norm > second.timeOdds) norm = second.timeOdds;
776 first.timeOdds /= norm;
777 second.timeOdds /= norm;
780 /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
781 first.accumulateTC = appData.firstAccumulateTC;
782 second.accumulateTC = appData.secondAccumulateTC;
783 first.maxNrOfSessions = second.maxNrOfSessions = 1;
786 first.debug = second.debug = FALSE;
787 first.supportsNPS = second.supportsNPS = UNKNOWN;
790 first.optionSettings = appData.firstOptions;
791 second.optionSettings = appData.secondOptions;
793 first.scoreIsAbsolute = appData.firstScoreIsAbsolute; /* [AS] */
794 second.scoreIsAbsolute = appData.secondScoreIsAbsolute; /* [AS] */
795 first.isUCI = appData.firstIsUCI; /* [AS] */
796 second.isUCI = appData.secondIsUCI; /* [AS] */
797 first.hasOwnBookUCI = appData.firstHasOwnBookUCI; /* [AS] */
798 second.hasOwnBookUCI = appData.secondHasOwnBookUCI; /* [AS] */
800 if (appData.firstProtocolVersion > PROTOVER ||
801 appData.firstProtocolVersion < 1) {
803 sprintf(buf, _("protocol version %d not supported"),
804 appData.firstProtocolVersion);
805 DisplayFatalError(buf, 0, 2);
807 first.protocolVersion = appData.firstProtocolVersion;
810 if (appData.secondProtocolVersion > PROTOVER ||
811 appData.secondProtocolVersion < 1) {
813 sprintf(buf, _("protocol version %d not supported"),
814 appData.secondProtocolVersion);
815 DisplayFatalError(buf, 0, 2);
817 second.protocolVersion = appData.secondProtocolVersion;
820 if (appData.icsActive) {
821 appData.clockMode = TRUE; /* changes dynamically in ICS mode */
822 // } else if (*appData.searchTime != NULLCHAR || appData.noChessProgram) {
823 } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
824 appData.clockMode = FALSE;
825 first.sendTime = second.sendTime = 0;
829 /* Override some settings from environment variables, for backward
830 compatibility. Unfortunately it's not feasible to have the env
831 vars just set defaults, at least in xboard. Ugh.
833 if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
838 if (appData.noChessProgram) {
839 programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
840 sprintf(programVersion, "%s", PACKAGE_STRING);
842 /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
843 programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
844 sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
847 if (!appData.icsActive) {
849 /* Check for variants that are supported only in ICS mode,
850 or not at all. Some that are accepted here nevertheless
851 have bugs; see comments below.
853 VariantClass variant = StringToVariant(appData.variant);
855 case VariantBughouse: /* need four players and two boards */
856 case VariantKriegspiel: /* need to hide pieces and move details */
857 /* case VariantFischeRandom: (Fabien: moved below) */
858 sprintf(buf, _("Variant %s supported only in ICS mode"), appData.variant);
859 DisplayFatalError(buf, 0, 2);
863 case VariantLoadable:
873 sprintf(buf, _("Unknown variant name %s"), appData.variant);
874 DisplayFatalError(buf, 0, 2);
877 case VariantXiangqi: /* [HGM] repetition rules not implemented */
878 case VariantFairy: /* [HGM] TestLegality definitely off! */
879 case VariantGothic: /* [HGM] should work */
880 case VariantCapablanca: /* [HGM] should work */
881 case VariantCourier: /* [HGM] initial forced moves not implemented */
882 case VariantShogi: /* [HGM] drops not tested for legality */
883 case VariantKnightmate: /* [HGM] should work */
884 case VariantCylinder: /* [HGM] untested */
885 case VariantFalcon: /* [HGM] untested */
886 case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
887 offboard interposition not understood */
888 case VariantNormal: /* definitely works! */
889 case VariantWildCastle: /* pieces not automatically shuffled */
890 case VariantNoCastle: /* pieces not automatically shuffled */
891 case VariantFischeRandom: /* [HGM] works and shuffles pieces */
892 case VariantLosers: /* should work except for win condition,
893 and doesn't know captures are mandatory */
894 case VariantSuicide: /* should work except for win condition,
895 and doesn't know captures are mandatory */
896 case VariantGiveaway: /* should work except for win condition,
897 and doesn't know captures are mandatory */
898 case VariantTwoKings: /* should work */
899 case VariantAtomic: /* should work except for win condition */
900 case Variant3Check: /* should work except for win condition */
901 case VariantShatranj: /* should work except for all win conditions */
902 case VariantMakruk: /* should work except for daw countdown */
903 case VariantBerolina: /* might work if TestLegality is off */
904 case VariantCapaRandom: /* should work */
905 case VariantJanus: /* should work */
906 case VariantSuper: /* experimental */
907 case VariantGreat: /* experimental, requires legality testing to be off */
912 InitEngineUCI( installDir, &first ); // [HGM] moved here from winboard.c, to make available in xboard
913 InitEngineUCI( installDir, &second );
916 int NextIntegerFromString( char ** str, long * value )
921 while( *s == ' ' || *s == '\t' ) {
927 if( *s >= '0' && *s <= '9' ) {
928 while( *s >= '0' && *s <= '9' ) {
929 *value = *value * 10 + (*s - '0');
941 int NextTimeControlFromString( char ** str, long * value )
944 int result = NextIntegerFromString( str, &temp );
947 *value = temp * 60; /* Minutes */
950 result = NextIntegerFromString( str, &temp );
951 *value += temp; /* Seconds */
958 int NextSessionFromString( char ** str, int *moves, long * tc, long *inc)
959 { /* [HGM] routine added to read '+moves/time' for secondary time control */
960 int result = -1; long temp, temp2;
962 if(**str != '+') return -1; // old params remain in force!
964 if( NextTimeControlFromString( str, &temp ) ) return -1;
967 /* time only: incremental or sudden-death time control */
968 if(**str == '+') { /* increment follows; read it */
970 if(result = NextIntegerFromString( str, &temp2)) return -1;
973 *moves = 0; *tc = temp * 1000;
975 } else if(temp % 60 != 0) return -1; /* moves was given as min:sec */
977 (*str)++; /* classical time control */
978 result = NextTimeControlFromString( str, &temp2);
987 int GetTimeQuota(int movenr)
988 { /* [HGM] get time to add from the multi-session time-control string */
989 int moves=1; /* kludge to force reading of first session */
990 long time, increment;
991 char *s = fullTimeControlString;
993 if(appData.debugMode) fprintf(debugFP, "TC string = '%s'\n", fullTimeControlString);
995 if(moves) NextSessionFromString(&s, &moves, &time, &increment);
996 if(appData.debugMode) fprintf(debugFP, "mps=%d tc=%d inc=%d\n", moves, (int) time, (int) increment);
997 if(movenr == -1) return time; /* last move before new session */
998 if(!moves) return increment; /* current session is incremental */
999 if(movenr >= 0) movenr -= moves; /* we already finished this session */
1000 } while(movenr >= -1); /* try again for next session */
1002 return 0; // no new time quota on this move
1006 ParseTimeControl(tc, ti, mps)
1015 if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1018 sprintf(buf, "+%d/%s+%d", mps, tc, ti);
1019 else sprintf(buf, "+%s+%d", tc, ti);
1022 sprintf(buf, "+%d/%s", mps, tc);
1023 else sprintf(buf, "+%s", tc);
1025 fullTimeControlString = StrSave(buf);
1027 if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1032 /* Parse second time control */
1035 if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1043 timeControl_2 = tc2 * 1000;
1053 timeControl = tc1 * 1000;
1056 timeIncrement = ti * 1000; /* convert to ms */
1057 movesPerSession = 0;
1060 movesPerSession = mps;
1068 if (appData.debugMode) {
1069 fprintf(debugFP, "%s\n", programVersion);
1072 set_cont_sequence(appData.wrapContSeq);
1073 if (appData.matchGames > 0) {
1074 appData.matchMode = TRUE;
1075 } else if (appData.matchMode) {
1076 appData.matchGames = 1;
1078 if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1079 appData.matchGames = appData.sameColorGames;
1080 if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1081 if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1082 if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1085 if (appData.noChessProgram || first.protocolVersion == 1) {
1088 /* kludge: allow timeout for initial "feature" commands */
1090 DisplayMessage("", _("Starting chess program"));
1091 ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1096 InitBackEnd3 P((void))
1098 GameMode initialMode;
1102 InitChessProgram(&first, startedFromSetupPosition);
1105 if (appData.icsActive)
1110 if (*appData.icsCommPort != NULLCHAR)
1112 sprintf(buf, _("Could not open comm port %s"),
1113 appData.icsCommPort);
1117 snprintf(buf, sizeof(buf), _("Could not connect to host %s, port %s"),
1118 appData.icsHost, appData.icsPort);
1120 DisplayFatalError(buf, err, 1);
1126 AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1128 AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1129 if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1130 ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1132 else if (appData.noChessProgram)
1141 if (*appData.cmailGameName != NULLCHAR)
1144 OpenLoopback(&cmailPR);
1146 AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1150 DisplayMessage("", "");
1151 if (StrCaseCmp(appData.initialMode, "") == 0)
1153 initialMode = BeginningOfGame;
1155 else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0)
1157 initialMode = TwoMachinesPlay;
1159 else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0)
1161 initialMode = AnalyzeFile;
1163 else if (StrCaseCmp(appData.initialMode, "Analysis") == 0)
1165 initialMode = AnalyzeMode;
1167 else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0)
1169 initialMode = MachinePlaysWhite;
1171 else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0)
1173 initialMode = MachinePlaysBlack;
1175 else if (StrCaseCmp(appData.initialMode, "EditGame") == 0)
1177 initialMode = EditGame;
1179 else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0)
1181 initialMode = EditPosition;
1183 else if (StrCaseCmp(appData.initialMode, "Training") == 0)
1185 initialMode = Training;
1189 sprintf(buf, _("Unknown initialMode %s"), appData.initialMode);
1190 DisplayFatalError(buf, 0, 2);
1194 if (appData.matchMode)
1196 /* Set up machine vs. machine match */
1197 if (appData.noChessProgram)
1199 DisplayFatalError(_("Can't have a match with no chess programs"),
1205 if (*appData.loadGameFile != NULLCHAR)
1207 int index = appData.loadGameIndex; // [HGM] autoinc
1208 if(index<0) lastIndex = index = 1;
1209 if (!LoadGameFromFile(appData.loadGameFile,
1211 appData.loadGameFile, FALSE))
1213 DisplayFatalError(_("Bad game file"), 0, 1);
1217 else if (*appData.loadPositionFile != NULLCHAR)
1219 int index = appData.loadPositionIndex; // [HGM] autoinc
1220 if(index<0) lastIndex = index = 1;
1221 if (!LoadPositionFromFile(appData.loadPositionFile,
1223 appData.loadPositionFile))
1225 DisplayFatalError(_("Bad position file"), 0, 1);
1231 else if (*appData.cmailGameName != NULLCHAR)
1233 /* Set up cmail mode */
1234 ReloadCmailMsgEvent(TRUE);
1238 /* Set up other modes */
1239 if (initialMode == AnalyzeFile)
1241 if (*appData.loadGameFile == NULLCHAR)
1243 DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1247 if (*appData.loadGameFile != NULLCHAR)
1249 (void) LoadGameFromFile(appData.loadGameFile,
1250 appData.loadGameIndex,
1251 appData.loadGameFile, TRUE);
1253 else if (*appData.loadPositionFile != NULLCHAR)
1255 (void) LoadPositionFromFile(appData.loadPositionFile,
1256 appData.loadPositionIndex,
1257 appData.loadPositionFile);
1258 /* [HGM] try to make self-starting even after FEN load */
1259 /* to allow automatic setup of fairy variants with wtm */
1260 if(initialMode == BeginningOfGame && !blackPlaysFirst)
1262 gameMode = BeginningOfGame;
1263 setboardSpoiledMachineBlack = 1;
1265 /* [HGM] loadPos: make that every new game uses the setup */
1266 /* from file as long as we do not switch variant */
1267 if(!blackPlaysFirst)
1269 startedFromPositionFile = TRUE;
1270 CopyBoard(filePosition, boards[0]);
1273 if (initialMode == AnalyzeMode)
1275 if (appData.noChessProgram)
1277 DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1280 if (appData.icsActive)
1282 DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1287 else if (initialMode == AnalyzeFile)
1289 appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1290 ShowThinkingEvent();
1292 AnalysisPeriodicEvent(1);
1294 else if (initialMode == MachinePlaysWhite)
1296 if (appData.noChessProgram)
1298 DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1302 if (appData.icsActive)
1304 DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1308 MachineWhiteEvent();
1310 else if (initialMode == MachinePlaysBlack)
1312 if (appData.noChessProgram)
1314 DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1318 if (appData.icsActive)
1320 DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1324 MachineBlackEvent();
1326 else if (initialMode == TwoMachinesPlay)
1328 if (appData.noChessProgram)
1330 DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1334 if (appData.icsActive)
1336 DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1342 else if (initialMode == EditGame)
1346 else if (initialMode == EditPosition)
1348 EditPositionEvent();
1350 else if (initialMode == Training)
1352 if (*appData.loadGameFile == NULLCHAR)
1354 DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1365 * Establish will establish a contact to a remote host.port.
1366 * Sets icsPR to a ProcRef for a process (or pseudo-process)
1367 * used to talk to the host.
1368 * Returns 0 if okay, error code if not.
1375 if (*appData.icsCommPort != NULLCHAR) {
1376 /* Talk to the host through a serial comm port */
1377 return OpenCommPort(appData.icsCommPort, &icsPR);
1379 } else if (*appData.gateway != NULLCHAR) {
1380 if (*appData.remoteShell == NULLCHAR) {
1381 /* Use the rcmd protocol to run telnet program on a gateway host */
1382 snprintf(buf, sizeof(buf), "%s %s %s",
1383 appData.telnetProgram, appData.icsHost, appData.icsPort);
1384 return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1387 /* Use the rsh program to run telnet program on a gateway host */
1388 if (*appData.remoteUser == NULLCHAR) {
1389 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1390 appData.gateway, appData.telnetProgram,
1391 appData.icsHost, appData.icsPort);
1393 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1394 appData.remoteShell, appData.gateway,
1395 appData.remoteUser, appData.telnetProgram,
1396 appData.icsHost, appData.icsPort);
1398 return StartChildProcess(buf, "", &icsPR);
1401 } else if (appData.useTelnet) {
1402 return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1405 /* TCP socket interface differs somewhat between
1406 Unix and NT; handle details in the front end.
1408 return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1413 show_bytes(fp, buf, count)
1419 if (*buf < 040 || *(unsigned char *) buf > 0177) {
1420 fprintf(fp, "\\%03o", *buf & 0xff);
1429 /* Returns an errno value */
1431 OutputMaybeTelnet(pr, message, count, outError)
1437 char buf[8192], *p, *q, *buflim;
1438 int left, newcount, outcount;
1440 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1441 *appData.gateway != NULLCHAR) {
1442 if (appData.debugMode) {
1443 fprintf(debugFP, ">ICS: ");
1444 show_bytes(debugFP, message, count);
1445 fprintf(debugFP, "\n");
1447 return OutputToProcess(pr, message, count, outError);
1450 buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1457 if (appData.debugMode) {
1458 fprintf(debugFP, ">ICS: ");
1459 show_bytes(debugFP, buf, newcount);
1460 fprintf(debugFP, "\n");
1462 outcount = OutputToProcess(pr, buf, newcount, outError);
1463 if (outcount < newcount) return -1; /* to be sure */
1470 } else if (((unsigned char) *p) == TN_IAC) {
1471 *q++ = (char) TN_IAC;
1478 if (appData.debugMode) {
1479 fprintf(debugFP, ">ICS: ");
1480 show_bytes(debugFP, buf, newcount);
1481 fprintf(debugFP, "\n");
1483 outcount = OutputToProcess(pr, buf, newcount, outError);
1484 if (outcount < newcount) return -1; /* to be sure */
1489 read_from_player(isr, closure, message, count, error)
1496 int outError, outCount;
1497 static int gotEof = 0;
1499 /* Pass data read from player on to ICS */
1502 outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1503 if (outCount < count) {
1504 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1506 } else if (count < 0) {
1507 RemoveInputSource(isr);
1508 DisplayFatalError(_("Error reading from keyboard"), error, 1);
1509 } else if (gotEof++ > 0) {
1510 RemoveInputSource(isr);
1511 DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1517 { // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1518 if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1519 connectionAlive = FALSE; // only sticks if no response to 'date' command.
1520 SendToICS("date\n");
1521 if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1524 /* added routine for printf style output to ics */
1525 void ics_printf(char *format, ...)
1527 char buffer[MSG_SIZ];
1530 va_start(args, format);
1531 vsnprintf(buffer, sizeof(buffer), format, args);
1532 buffer[sizeof(buffer)-1] = '\0';
1541 int count, outCount, outError;
1543 if (icsPR == NULL) return;
1546 outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1547 if (outCount < count) {
1548 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1552 /* This is used for sending logon scripts to the ICS. Sending
1553 without a delay causes problems when using timestamp on ICC
1554 (at least on my machine). */
1556 SendToICSDelayed(s,msdelay)
1560 int count, outCount, outError;
1562 if (icsPR == NULL) return;
1565 if (appData.debugMode) {
1566 fprintf(debugFP, ">ICS: ");
1567 show_bytes(debugFP, s, count);
1568 fprintf(debugFP, "\n");
1570 outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1572 if (outCount < count) {
1573 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1578 /* Remove all highlighting escape sequences in s
1579 Also deletes any suffix starting with '('
1582 StripHighlightAndTitle(s)
1585 static char retbuf[MSG_SIZ];
1588 while (*s != NULLCHAR) {
1589 while (*s == '\033') {
1590 while (*s != NULLCHAR && !isalpha(*s)) s++;
1591 if (*s != NULLCHAR) s++;
1593 while (*s != NULLCHAR && *s != '\033') {
1594 if (*s == '(' || *s == '[') {
1605 /* Remove all highlighting escape sequences in s */
1610 static char retbuf[MSG_SIZ];
1613 while (*s != NULLCHAR) {
1614 while (*s == '\033') {
1615 while (*s != NULLCHAR && !isalpha(*s)) s++;
1616 if (*s != NULLCHAR) s++;
1618 while (*s != NULLCHAR && *s != '\033') {
1626 char *variantNames[] = VARIANT_NAMES;
1631 return variantNames[v];
1635 /* Identify a variant from the strings the chess servers use or the
1636 PGN Variant tag names we use. */
1643 VariantClass v = VariantNormal;
1644 int i, found = FALSE;
1649 /* [HGM] skip over optional board-size prefixes */
1650 if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1651 sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1652 while( *e++ != '_');
1655 if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
1659 for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
1660 if (StrCaseStr(e, variantNames[i])) {
1661 v = (VariantClass) i;
1668 if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
1669 || StrCaseStr(e, "wild/fr")
1670 || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
1671 v = VariantFischeRandom;
1672 } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
1673 (i = 1, p = StrCaseStr(e, "w"))) {
1675 while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
1682 case 0: /* FICS only, actually */
1684 /* Castling legal even if K starts on d-file */
1685 v = VariantWildCastle;
1690 /* Castling illegal even if K & R happen to start in
1691 normal positions. */
1692 v = VariantNoCastle;
1705 /* Castling legal iff K & R start in normal positions */
1711 /* Special wilds for position setup; unclear what to do here */
1712 v = VariantLoadable;
1715 /* Bizarre ICC game */
1716 v = VariantTwoKings;
1719 v = VariantKriegspiel;
1725 v = VariantFischeRandom;
1728 v = VariantCrazyhouse;
1731 v = VariantBughouse;
1737 /* Not quite the same as FICS suicide! */
1738 v = VariantGiveaway;
1744 v = VariantShatranj;
1747 /* Temporary names for future ICC types. The name *will* change in
1748 the next xboard/WinBoard release after ICC defines it. */
1786 v = VariantCapablanca;
1789 v = VariantKnightmate;
1795 v = VariantCylinder;
1801 v = VariantCapaRandom;
1804 v = VariantBerolina;
1816 /* Found "wild" or "w" in the string but no number;
1817 must assume it's normal chess. */
1821 sprintf(buf, _("Unknown wild type %d"), wnum);
1822 DisplayError(buf, 0);
1828 if (appData.debugMode) {
1829 fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
1830 e, wnum, VariantName(v));
1835 static int leftover_start = 0, leftover_len = 0;
1836 char star_match[STAR_MATCH_N][MSG_SIZ];
1838 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
1839 advance *index beyond it, and set leftover_start to the new value of
1840 *index; else return FALSE. If pattern contains the character '*', it
1841 matches any sequence of characters not containing '\r', '\n', or the
1842 character following the '*' (if any), and the matched sequence(s) are
1843 copied into star_match.
1846 looking_at(buf, index, pattern)
1851 char *bufp = &buf[*index], *patternp = pattern;
1853 char *matchp = star_match[0];
1856 if (*patternp == NULLCHAR) {
1857 *index = leftover_start = bufp - buf;
1861 if (*bufp == NULLCHAR) return FALSE;
1862 if (*patternp == '*') {
1863 if (*bufp == *(patternp + 1)) {
1865 matchp = star_match[++star_count];
1869 } else if (*bufp == '\n' || *bufp == '\r') {
1871 if (*patternp == NULLCHAR)
1876 *matchp++ = *bufp++;
1880 if (*patternp != *bufp) return FALSE;
1887 SendToPlayer(data, length)
1891 int error, outCount;
1892 outCount = OutputToProcess(NoProc, data, length, &error);
1893 if (outCount < length) {
1894 DisplayFatalError(_("Error writing to display"), error, 1);
1899 PackHolding(packed, holding)
1911 switch (runlength) {
1922 sprintf(q, "%d", runlength);
1934 /* Telnet protocol requests from the front end */
1936 TelnetRequest(ddww, option)
1937 unsigned char ddww, option;
1939 unsigned char msg[3];
1940 int outCount, outError;
1942 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
1944 if (appData.debugMode) {
1945 char buf1[8], buf2[8], *ddwwStr, *optionStr;
1961 sprintf(buf1, "%d", ddww);
1970 sprintf(buf2, "%d", option);
1973 fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
1978 outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
1980 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1987 if (!appData.icsActive) return;
1988 TelnetRequest(TN_DO, TN_ECHO);
1994 if (!appData.icsActive) return;
1995 TelnetRequest(TN_DONT, TN_ECHO);
1999 CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece)
2001 /* put the holdings sent to us by the server on the board holdings area */
2002 int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2006 if(gameInfo.holdingsWidth < 2) return;
2007 if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2008 return; // prevent overwriting by pre-board holdings
2010 if( (int)lowestPiece >= BlackPawn ) {
2013 holdingsStartRow = BOARD_HEIGHT-1;
2016 holdingsColumn = BOARD_WIDTH-1;
2017 countsColumn = BOARD_WIDTH-2;
2018 holdingsStartRow = 0;
2022 for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
2023 board[i][holdingsColumn] = EmptySquare;
2024 board[i][countsColumn] = (ChessSquare) 0;
2026 while( (p=*holdings++) != NULLCHAR ) {
2027 piece = CharToPiece( ToUpper(p) );
2028 if(piece == EmptySquare) continue;
2029 /*j = (int) piece - (int) WhitePawn;*/
2030 j = PieceToNumber(piece);
2031 if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2032 if(j < 0) continue; /* should not happen */
2033 piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2034 board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2035 board[holdingsStartRow+j*direction][countsColumn]++;
2041 VariantSwitch(Board board, VariantClass newVariant)
2043 int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2046 startedFromPositionFile = FALSE;
2047 if(gameInfo.variant == newVariant) return;
2049 /* [HGM] This routine is called each time an assignment is made to
2050 * gameInfo.variant during a game, to make sure the board sizes
2051 * are set to match the new variant. If that means adding or deleting
2052 * holdings, we shift the playing board accordingly
2053 * This kludge is needed because in ICS observe mode, we get boards
2054 * of an ongoing game without knowing the variant, and learn about the
2055 * latter only later. This can be because of the move list we requested,
2056 * in which case the game history is refilled from the beginning anyway,
2057 * but also when receiving holdings of a crazyhouse game. In the latter
2058 * case we want to add those holdings to the already received position.
2061 if (appData.debugMode) {
2062 fprintf(debugFP, "Switch board from %s to %s\n",
2063 VariantName(gameInfo.variant), VariantName(newVariant));
2064 setbuf(debugFP, NULL);
2066 shuffleOpenings = 0; /* [HGM] shuffle */
2067 gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2071 newWidth = 9; newHeight = 9;
2072 gameInfo.holdingsSize = 7;
2073 case VariantBughouse:
2074 case VariantCrazyhouse:
2075 newHoldingsWidth = 2; break;
2079 newHoldingsWidth = 2;
2080 gameInfo.holdingsSize = 8;
2083 case VariantCapablanca:
2084 case VariantCapaRandom:
2087 newHoldingsWidth = gameInfo.holdingsSize = 0;
2090 if(newWidth != gameInfo.boardWidth ||
2091 newHeight != gameInfo.boardHeight ||
2092 newHoldingsWidth != gameInfo.holdingsWidth ) {
2094 /* shift position to new playing area, if needed */
2095 if(newHoldingsWidth > gameInfo.holdingsWidth) {
2096 for(i=0; i<BOARD_HEIGHT; i++)
2097 for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2098 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2100 for(i=0; i<newHeight; i++) {
2101 board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2102 board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2104 } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2105 for(i=0; i<BOARD_HEIGHT; i++)
2106 for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2107 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2110 gameInfo.boardWidth = newWidth;
2111 gameInfo.boardHeight = newHeight;
2112 gameInfo.holdingsWidth = newHoldingsWidth;
2113 gameInfo.variant = newVariant;
2114 InitDrawingSizes(-2, 0);
2115 } else gameInfo.variant = newVariant;
2116 CopyBoard(oldBoard, board); // remember correctly formatted board
2117 InitPosition(FALSE); /* this sets up board[0], but also other stuff */
2118 DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2121 static int loggedOn = FALSE;
2123 /*-- Game start info cache: --*/
2125 char gs_kind[MSG_SIZ];
2126 static char player1Name[128] = "";
2127 static char player2Name[128] = "";
2128 static char cont_seq[] = "\n\\ ";
2129 static int player1Rating = -1;
2130 static int player2Rating = -1;
2131 /*----------------------------*/
2133 ColorClass curColor = ColorNormal;
2134 int suppressKibitz = 0;
2137 Boolean soughtPending = FALSE;
2138 Boolean seekGraphUp;
2139 #define MAX_SEEK_ADS 200
2141 char *seekAdList[MAX_SEEK_ADS];
2142 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2143 float tcList[MAX_SEEK_ADS];
2144 char colorList[MAX_SEEK_ADS];
2145 int nrOfSeekAds = 0;
2146 int minRating = 1010, maxRating = 2800;
2147 int hMargin = 10, vMargin = 20, h, w;
2148 extern int squareSize, lineGap;
2153 int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2154 xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2155 if(r < minRating+100 && r >=0 ) r = minRating+100;
2156 if(r > maxRating) r = maxRating;
2157 if(tc < 1.) tc = 1.;
2158 if(tc > 95.) tc = 95.;
2159 x = (w-hMargin)* log(tc)/log(100.) + hMargin;
2160 y = ((double)r - minRating)/(maxRating - minRating)
2161 * (h-vMargin-squareSize/8-1) + vMargin;
2162 if(ratingList[i] < 0) y = vMargin + squareSize/4;
2163 if(strstr(seekAdList[i], " u ")) color = 1;
2164 if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2165 !strstr(seekAdList[i], "bullet") &&
2166 !strstr(seekAdList[i], "blitz") &&
2167 !strstr(seekAdList[i], "standard") ) color = 2;
2168 if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2169 DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2173 AddAd(char *handle, char *rating, int base, int inc, char rated, char *type, int nr, Boolean plot)
2175 char buf[MSG_SIZ], *ext = "";
2176 VariantClass v = StringToVariant(type);
2177 if(strstr(type, "wild")) {
2178 ext = type + 4; // append wild number
2179 if(v == VariantFischeRandom) type = "chess960"; else
2180 if(v == VariantLoadable) type = "setup"; else
2181 type = VariantName(v);
2183 sprintf(buf, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2184 if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2185 if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2186 ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2187 sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2188 tcList[nrOfSeekAds] = base + (2./3.)*inc;
2189 seekNrList[nrOfSeekAds] = nr;
2190 zList[nrOfSeekAds] = 0;
2191 seekAdList[nrOfSeekAds++] = StrSave(buf);
2192 if(plot) PlotSeekAd(nrOfSeekAds-1);
2199 int x = xList[i], y = yList[i], d=squareSize/4, k;
2200 DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2201 if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2202 // now replot every dot that overlapped
2203 for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2204 int xx = xList[k], yy = yList[k];
2205 if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2206 DrawSeekDot(xx, yy, colorList[k]);
2211 RemoveSeekAd(int nr)
2214 for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2216 if(seekAdList[i]) free(seekAdList[i]);
2217 seekAdList[i] = seekAdList[--nrOfSeekAds];
2218 seekNrList[i] = seekNrList[nrOfSeekAds];
2219 ratingList[i] = ratingList[nrOfSeekAds];
2220 colorList[i] = colorList[nrOfSeekAds];
2221 tcList[i] = tcList[nrOfSeekAds];
2222 xList[i] = xList[nrOfSeekAds];
2223 yList[i] = yList[nrOfSeekAds];
2224 zList[i] = zList[nrOfSeekAds];
2225 seekAdList[nrOfSeekAds] = NULL;
2231 MatchSoughtLine(char *line)
2233 char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2234 int nr, base, inc, u=0; char dummy;
2236 if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2237 sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2239 (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2240 sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7) ) {
2241 // match: compact and save the line
2242 AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2251 if(!seekGraphUp) return FALSE;
2253 h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
2254 w = BOARD_WIDTH * (squareSize + lineGap) + lineGap;
2256 DrawSeekBackground(0, 0, w, h);
2257 DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2258 DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2259 for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2260 int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2262 DrawSeekAxis(hMargin+5*(i%500==0), yy, hMargin-5, yy); // rating ticks
2265 sprintf(buf, "%d", i);
2266 DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2269 DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2270 for(i=1; i<100; i+=(i<10?1:5)) {
2271 int xx = (w-hMargin)* log((double)i)/log(100.) + hMargin;
2272 DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2273 if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2275 sprintf(buf, "%d", i);
2276 DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2279 for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2283 int SeekGraphClick(ClickType click, int x, int y, int moving)
2285 static int lastDown = 0, displayed = 0, lastSecond;
2286 if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2287 if(click == Release || moving) return FALSE;
2289 soughtPending = TRUE;
2290 SendToICS(ics_prefix);
2291 SendToICS("sought\n"); // should this be "sought all"?
2292 } else { // issue challenge based on clicked ad
2293 int dist = 10000; int i, closest = 0, second = 0;
2294 for(i=0; i<nrOfSeekAds; i++) {
2295 int d = (x-xList[i])*(x-xList[i]) + (y-yList[i])*(y-yList[i]) + zList[i];
2296 if(d < dist) { dist = d; closest = i; }
2297 second += (d - zList[i] < 120); // count in-range ads
2298 if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2302 second = (second > 1);
2303 if(displayed != closest || second != lastSecond) {
2304 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2305 lastSecond = second; displayed = closest;
2307 sprintf(buf, "play %d\n", seekNrList[closest]);
2308 if(click == Press) {
2309 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2312 } // on press 'hit', only show info
2313 if(moving == 2) return TRUE; // ignore right up-clicks on dot
2314 SendToICS(ics_prefix);
2315 SendToICS(buf); // should this be "sought all"?
2316 } else if(click == Release) { // release 'miss' is ignored
2317 zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2318 if(moving == 2) { // right up-click
2319 nrOfSeekAds = 0; // refresh graph
2320 soughtPending = TRUE;
2321 SendToICS(ics_prefix);
2322 SendToICS("sought\n"); // should this be "sought all"?
2325 } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2326 // press miss or release hit 'pop down' seek graph
2327 seekGraphUp = FALSE;
2328 DrawPosition(TRUE, NULL);
2334 read_from_ics(isr, closure, data, count, error)
2341 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2342 #define STARTED_NONE 0
2343 #define STARTED_MOVES 1
2344 #define STARTED_BOARD 2
2345 #define STARTED_OBSERVE 3
2346 #define STARTED_HOLDINGS 4
2347 #define STARTED_CHATTER 5
2348 #define STARTED_COMMENT 6
2349 #define STARTED_MOVES_NOHIDE 7
2351 static int started = STARTED_NONE;
2352 static char parse[20000];
2353 static int parse_pos = 0;
2354 static char buf[BUF_SIZE + 1];
2355 static int firstTime = TRUE, intfSet = FALSE;
2356 static ColorClass prevColor = ColorNormal;
2357 static int savingComment = FALSE;
2358 static int cmatch = 0; // continuation sequence match
2365 int backup; /* [DM] For zippy color lines */
2367 char talker[MSG_SIZ]; // [HGM] chat
2370 connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2372 if (appData.debugMode) {
2374 fprintf(debugFP, "<ICS: ");
2375 show_bytes(debugFP, data, count);
2376 fprintf(debugFP, "\n");
2380 if (appData.debugMode) { int f = forwardMostMove;
2381 fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2382 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2383 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2386 /* If last read ended with a partial line that we couldn't parse,
2387 prepend it to the new read and try again. */
2388 if (leftover_len > 0) {
2389 for (i=0; i<leftover_len; i++)
2390 buf[i] = buf[leftover_start + i];
2393 /* copy new characters into the buffer */
2394 bp = buf + leftover_len;
2395 buf_len=leftover_len;
2396 for (i=0; i<count; i++)
2399 if (data[i] == '\r')
2402 // join lines split by ICS?
2403 if (!appData.noJoin)
2406 Joining just consists of finding matches against the
2407 continuation sequence, and discarding that sequence
2408 if found instead of copying it. So, until a match
2409 fails, there's nothing to do since it might be the
2410 complete sequence, and thus, something we don't want
2413 if (data[i] == cont_seq[cmatch])
2416 if (cmatch == strlen(cont_seq))
2418 cmatch = 0; // complete match. just reset the counter
2421 it's possible for the ICS to not include the space
2422 at the end of the last word, making our [correct]
2423 join operation fuse two separate words. the server
2424 does this when the space occurs at the width setting.
2426 if (!buf_len || buf[buf_len-1] != ' ')
2437 match failed, so we have to copy what matched before
2438 falling through and copying this character. In reality,
2439 this will only ever be just the newline character, but
2440 it doesn't hurt to be precise.
2442 strncpy(bp, cont_seq, cmatch);
2454 buf[buf_len] = NULLCHAR;
2455 // next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2460 while (i < buf_len) {
2461 /* Deal with part of the TELNET option negotiation
2462 protocol. We refuse to do anything beyond the
2463 defaults, except that we allow the WILL ECHO option,
2464 which ICS uses to turn off password echoing when we are
2465 directly connected to it. We reject this option
2466 if localLineEditing mode is on (always on in xboard)
2467 and we are talking to port 23, which might be a real
2468 telnet server that will try to keep WILL ECHO on permanently.
2470 if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2471 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2472 unsigned char option;
2474 switch ((unsigned char) buf[++i]) {
2476 if (appData.debugMode)
2477 fprintf(debugFP, "\n<WILL ");
2478 switch (option = (unsigned char) buf[++i]) {
2480 if (appData.debugMode)
2481 fprintf(debugFP, "ECHO ");
2482 /* Reply only if this is a change, according
2483 to the protocol rules. */
2484 if (remoteEchoOption) break;
2485 if (appData.localLineEditing &&
2486 atoi(appData.icsPort) == TN_PORT) {
2487 TelnetRequest(TN_DONT, TN_ECHO);
2490 TelnetRequest(TN_DO, TN_ECHO);
2491 remoteEchoOption = TRUE;
2495 if (appData.debugMode)
2496 fprintf(debugFP, "%d ", option);
2497 /* Whatever this is, we don't want it. */
2498 TelnetRequest(TN_DONT, option);
2503 if (appData.debugMode)
2504 fprintf(debugFP, "\n<WONT ");
2505 switch (option = (unsigned char) buf[++i]) {
2507 if (appData.debugMode)
2508 fprintf(debugFP, "ECHO ");
2509 /* Reply only if this is a change, according
2510 to the protocol rules. */
2511 if (!remoteEchoOption) break;
2513 TelnetRequest(TN_DONT, TN_ECHO);
2514 remoteEchoOption = FALSE;
2517 if (appData.debugMode)
2518 fprintf(debugFP, "%d ", (unsigned char) option);
2519 /* Whatever this is, it must already be turned
2520 off, because we never agree to turn on
2521 anything non-default, so according to the
2522 protocol rules, we don't reply. */
2527 if (appData.debugMode)
2528 fprintf(debugFP, "\n<DO ");
2529 switch (option = (unsigned char) buf[++i]) {
2531 /* Whatever this is, we refuse to do it. */
2532 if (appData.debugMode)
2533 fprintf(debugFP, "%d ", option);
2534 TelnetRequest(TN_WONT, option);
2539 if (appData.debugMode)
2540 fprintf(debugFP, "\n<DONT ");
2541 switch (option = (unsigned char) buf[++i]) {
2543 if (appData.debugMode)
2544 fprintf(debugFP, "%d ", option);
2545 /* Whatever this is, we are already not doing
2546 it, because we never agree to do anything
2547 non-default, so according to the protocol
2548 rules, we don't reply. */
2553 if (appData.debugMode)
2554 fprintf(debugFP, "\n<IAC ");
2555 /* Doubled IAC; pass it through */
2559 if (appData.debugMode)
2560 fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2561 /* Drop all other telnet commands on the floor */
2564 if (oldi > next_out)
2565 SendToPlayer(&buf[next_out], oldi - next_out);
2571 /* OK, this at least will *usually* work */
2572 if (!loggedOn && looking_at(buf, &i, "ics%")) {
2576 if (loggedOn && !intfSet) {
2577 if (ics_type == ICS_ICC) {
2579 "/set-quietly interface %s\n/set-quietly style 12\n",
2581 if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2582 strcat(str, "/set-2 51 1\n/set seek 1\n");
2583 } else if (ics_type == ICS_CHESSNET) {
2584 sprintf(str, "/style 12\n");
2586 strcpy(str, "alias $ @\n$set interface ");
2587 strcat(str, programVersion);
2588 strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2589 if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2590 strcat(str, "$iset seekremove 1\n$set seek 1\n");
2592 strcat(str, "$iset nohighlight 1\n");
2594 strcat(str, "$iset lock 1\n$style 12\n");
2597 NotifyFrontendLogin();
2601 if (started == STARTED_COMMENT) {
2602 /* Accumulate characters in comment */
2603 parse[parse_pos++] = buf[i];
2604 if (buf[i] == '\n') {
2605 parse[parse_pos] = NULLCHAR;
2606 if(chattingPartner>=0) {
2608 sprintf(mess, "%s%s", talker, parse);
2609 OutputChatMessage(chattingPartner, mess);
2610 chattingPartner = -1;
2612 if(!suppressKibitz) // [HGM] kibitz
2613 AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
2614 else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2615 int nrDigit = 0, nrAlph = 0, j;
2616 if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2617 { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2618 parse[parse_pos] = NULLCHAR;
2619 // try to be smart: if it does not look like search info, it should go to
2620 // ICS interaction window after all, not to engine-output window.
2621 for(j=0; j<parse_pos; j++) { // count letters and digits
2622 nrDigit += (parse[j] >= '0' && parse[j] <= '9');
2623 nrAlph += (parse[j] >= 'a' && parse[j] <= 'z');
2624 nrAlph += (parse[j] >= 'A' && parse[j] <= 'Z');
2626 if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2627 int depth=0; float score;
2628 if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2629 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2630 pvInfoList[forwardMostMove-1].depth = depth;
2631 pvInfoList[forwardMostMove-1].score = 100*score;
2633 OutputKibitz(suppressKibitz, parse);
2634 next_out = i+1; // [HGM] suppress printing in ICS window
2637 sprintf(tmp, _("your opponent kibitzes: %s"), parse);
2638 SendToPlayer(tmp, strlen(tmp));
2641 started = STARTED_NONE;
2643 /* Don't match patterns against characters in comment */
2648 if (started == STARTED_CHATTER) {
2649 if (buf[i] != '\n') {
2650 /* Don't match patterns against characters in chatter */
2654 started = STARTED_NONE;
2657 /* Kludge to deal with rcmd protocol */
2658 if (firstTime && looking_at(buf, &i, "\001*")) {
2659 DisplayFatalError(&buf[1], 0, 1);
2665 if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2668 if (appData.debugMode)
2669 fprintf(debugFP, "ics_type %d\n", ics_type);
2672 if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
2673 ics_type = ICS_FICS;
2675 if (appData.debugMode)
2676 fprintf(debugFP, "ics_type %d\n", ics_type);
2679 if (!loggedOn && looking_at(buf, &i, "chess.net")) {
2680 ics_type = ICS_CHESSNET;
2682 if (appData.debugMode)
2683 fprintf(debugFP, "ics_type %d\n", ics_type);
2688 (looking_at(buf, &i, "\"*\" is *a registered name") ||
2689 looking_at(buf, &i, "Logging you in as \"*\"") ||
2690 looking_at(buf, &i, "will be \"*\""))) {
2691 strcpy(ics_handle, star_match[0]);
2695 if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
2697 snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
2698 DisplayIcsInteractionTitle(buf);
2699 have_set_title = TRUE;
2702 /* skip finger notes */
2703 if (started == STARTED_NONE &&
2704 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
2705 (buf[i] == '1' && buf[i+1] == '0')) &&
2706 buf[i+2] == ':' && buf[i+3] == ' ') {
2707 started = STARTED_CHATTER;
2712 // [HGM] seekgraph: recognize sought lines and end-of-sought message
2713 if(appData.seekGraph) {
2714 if(soughtPending && MatchSoughtLine(buf+i)) {
2715 i = strstr(buf+i, "rated") - buf;
2716 next_out = leftover_start = i;
2717 started = STARTED_CHATTER;
2718 suppressKibitz = TRUE;
2721 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
2722 && looking_at(buf, &i, "* ads displayed")) {
2723 soughtPending = FALSE;
2728 if(appData.autoRefresh) {
2729 if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
2730 int s = (ics_type == ICS_ICC); // ICC format differs
2732 AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
2733 star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
2734 looking_at(buf, &i, "*% "); // eat prompt
2735 next_out = i; // suppress
2738 if(looking_at(buf, &i, "Ads removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
2739 char *p = star_match[0];
2741 if(seekGraphUp) RemoveSeekAd(atoi(p));
2742 while(*p && *p++ != ' '); // next
2744 looking_at(buf, &i, "*% "); // eat prompt
2751 /* skip formula vars */
2752 if (started == STARTED_NONE &&
2753 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
2754 started = STARTED_CHATTER;
2760 // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
2761 if (appData.autoKibitz && started == STARTED_NONE &&
2762 !appData.icsEngineAnalyze && // [HGM] [DM] ICS analyze
2763 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
2764 if(looking_at(buf, &i, "* kibitzes: ") &&
2765 (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
2766 StrStr(star_match[0], gameInfo.black) == star_match[0] )) { // kibitz of self or opponent
2767 suppressKibitz = TRUE;
2768 if((StrStr(star_match[0], gameInfo.white) == star_match[0]
2769 && (gameMode == IcsPlayingWhite)) ||
2770 (StrStr(star_match[0], gameInfo.black) == star_match[0]
2771 && (gameMode == IcsPlayingBlack)) ) // opponent kibitz
2772 started = STARTED_CHATTER; // own kibitz we simply discard
2774 started = STARTED_COMMENT; // make sure it will be collected in parse[]
2775 parse_pos = 0; parse[0] = NULLCHAR;
2776 savingComment = TRUE;
2777 suppressKibitz = gameMode != IcsObserving ? 2 :
2778 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
2782 if(looking_at(buf, &i, "kibitzed to *\n") && atoi(star_match[0])) {
2783 // suppress the acknowledgements of our own autoKibitz
2785 if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
2786 SendToPlayer(star_match[0], strlen(star_match[0]));
2787 looking_at(buf, &i, "*% "); // eat prompt
2790 } // [HGM] kibitz: end of patch
2792 //if(appData.debugMode) fprintf(debugFP, "hunt for tell, buf = %s\n", buf+i);
2794 // [HGM] chat: intercept tells by users for which we have an open chat window
2796 if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
2797 looking_at(buf, &i, "* whispers:") ||
2798 looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
2799 looking_at(buf, &i, "*(*)(*):") && sscanf(star_match[2], "%d", &channel) == 1 )) {
2801 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
2802 chattingPartner = -1;
2804 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
2805 for(p=0; p<MAX_CHAT; p++) {
2806 if(channel == atoi(chatPartner[p])) {
2807 talker[0] = '['; strcat(talker, "] ");
2808 chattingPartner = p; break;
2811 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
2812 for(p=0; p<MAX_CHAT; p++) {
2813 if(!strcmp("WHISPER", chatPartner[p])) {
2814 talker[0] = '['; strcat(talker, "] ");
2815 chattingPartner = p; break;
2818 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
2819 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
2821 chattingPartner = p; break;
2823 if(chattingPartner<0) i = oldi; else {
2824 started = STARTED_COMMENT;
2825 parse_pos = 0; parse[0] = NULLCHAR;
2826 savingComment = 3 + chattingPartner; // counts as TRUE
2827 suppressKibitz = TRUE;
2829 } // [HGM] chat: end of patch
2831 if (appData.zippyTalk || appData.zippyPlay) {
2832 /* [DM] Backup address for color zippy lines */
2836 if (loggedOn == TRUE)
2837 if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
2838 (appData.zippyPlay && ZippyMatch(buf, &backup)));
2840 if (ZippyControl(buf, &i) ||
2841 ZippyConverse(buf, &i) ||
2842 (appData.zippyPlay && ZippyMatch(buf, &i))) {
2844 if (!appData.colorize) continue;
2848 } // [DM] 'else { ' deleted
2850 /* Regular tells and says */
2851 (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
2852 looking_at(buf, &i, "* (your partner) tells you: ") ||
2853 looking_at(buf, &i, "* says: ") ||
2854 /* Don't color "message" or "messages" output */
2855 (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
2856 looking_at(buf, &i, "*. * at *:*: ") ||
2857 looking_at(buf, &i, "--* (*:*): ") ||
2858 /* Message notifications (same color as tells) */
2859 looking_at(buf, &i, "* has left a message ") ||
2860 looking_at(buf, &i, "* just sent you a message:\n") ||
2861 /* Whispers and kibitzes */
2862 (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
2863 looking_at(buf, &i, "* kibitzes: ") ||
2865 (tkind = 3, looking_at(buf, &i, "*(*: "))) {
2867 if (tkind == 1 && strchr(star_match[0], ':')) {
2868 /* Avoid "tells you:" spoofs in channels */
2871 if (star_match[0][0] == NULLCHAR ||
2872 strchr(star_match[0], ' ') ||
2873 (tkind == 3 && strchr(star_match[1], ' '))) {
2874 /* Reject bogus matches */
2877 if (appData.colorize) {
2878 if (oldi > next_out) {
2879 SendToPlayer(&buf[next_out], oldi - next_out);
2884 Colorize(ColorTell, FALSE);
2885 curColor = ColorTell;
2888 Colorize(ColorKibitz, FALSE);
2889 curColor = ColorKibitz;
2892 p = strrchr(star_match[1], '(');
2899 Colorize(ColorChannel1, FALSE);
2900 curColor = ColorChannel1;
2902 Colorize(ColorChannel, FALSE);
2903 curColor = ColorChannel;
2907 curColor = ColorNormal;
2911 if (started == STARTED_NONE && appData.autoComment &&
2912 (gameMode == IcsObserving ||
2913 gameMode == IcsPlayingWhite ||
2914 gameMode == IcsPlayingBlack)) {
2915 parse_pos = i - oldi;
2916 memcpy(parse, &buf[oldi], parse_pos);
2917 parse[parse_pos] = NULLCHAR;
2918 started = STARTED_COMMENT;
2919 savingComment = TRUE;
2921 started = STARTED_CHATTER;
2922 savingComment = FALSE;
2929 if (looking_at(buf, &i, "* s-shouts: ") ||
2930 looking_at(buf, &i, "* c-shouts: ")) {
2931 if (appData.colorize) {
2932 if (oldi > next_out) {
2933 SendToPlayer(&buf[next_out], oldi - next_out);
2936 Colorize(ColorSShout, FALSE);
2937 curColor = ColorSShout;
2940 started = STARTED_CHATTER;
2944 if (looking_at(buf, &i, "--->")) {
2949 if (looking_at(buf, &i, "* shouts: ") ||
2950 looking_at(buf, &i, "--> ")) {
2951 if (appData.colorize) {
2952 if (oldi > next_out) {
2953 SendToPlayer(&buf[next_out], oldi - next_out);
2956 Colorize(ColorShout, FALSE);
2957 curColor = ColorShout;
2960 started = STARTED_CHATTER;
2964 if (looking_at( buf, &i, "Challenge:")) {
2965 if (appData.colorize) {
2966 if (oldi > next_out) {
2967 SendToPlayer(&buf[next_out], oldi - next_out);
2970 Colorize(ColorChallenge, FALSE);
2971 curColor = ColorChallenge;
2977 if (looking_at(buf, &i, "* offers you") ||
2978 looking_at(buf, &i, "* offers to be") ||
2979 looking_at(buf, &i, "* would like to") ||
2980 looking_at(buf, &i, "* requests to") ||
2981 looking_at(buf, &i, "Your opponent offers") ||
2982 looking_at(buf, &i, "Your opponent requests")) {
2984 if (appData.colorize) {
2985 if (oldi > next_out) {
2986 SendToPlayer(&buf[next_out], oldi - next_out);
2989 Colorize(ColorRequest, FALSE);
2990 curColor = ColorRequest;
2995 if (looking_at(buf, &i, "* (*) seeking")) {
2996 if (appData.colorize) {
2997 if (oldi > next_out) {
2998 SendToPlayer(&buf[next_out], oldi - next_out);
3001 Colorize(ColorSeek, FALSE);
3002 curColor = ColorSeek;
3007 if (looking_at(buf, &i, "\\ ")) {
3008 if (prevColor != ColorNormal) {
3009 if (oldi > next_out) {
3010 SendToPlayer(&buf[next_out], oldi - next_out);
3013 Colorize(prevColor, TRUE);
3014 curColor = prevColor;
3016 if (savingComment) {
3017 parse_pos = i - oldi;
3018 memcpy(parse, &buf[oldi], parse_pos);
3019 parse[parse_pos] = NULLCHAR;
3020 started = STARTED_COMMENT;
3021 if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3022 chattingPartner = savingComment - 3; // kludge to remember the box
3024 started = STARTED_CHATTER;
3029 if (looking_at(buf, &i, "Black Strength :") ||
3030 looking_at(buf, &i, "<<< style 10 board >>>") ||
3031 looking_at(buf, &i, "<10>") ||
3032 looking_at(buf, &i, "#@#")) {
3033 /* Wrong board style */
3035 SendToICS(ics_prefix);
3036 SendToICS("set style 12\n");
3037 SendToICS(ics_prefix);
3038 SendToICS("refresh\n");
3042 if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
3044 have_sent_ICS_logon = 1;
3048 if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3049 (looking_at(buf, &i, "\n<12> ") ||
3050 looking_at(buf, &i, "<12> "))) {
3052 if (oldi > next_out) {
3053 SendToPlayer(&buf[next_out], oldi - next_out);
3056 started = STARTED_BOARD;
3061 if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3062 looking_at(buf, &i, "<b1> ")) {
3063 if (oldi > next_out) {
3064 SendToPlayer(&buf[next_out], oldi - next_out);
3067 started = STARTED_HOLDINGS;
3072 if (looking_at(buf, &i, "* *vs. * *--- *")) {
3074 /* Header for a move list -- first line */
3076 switch (ics_getting_history) {
3080 case BeginningOfGame:
3081 /* User typed "moves" or "oldmoves" while we
3082 were idle. Pretend we asked for these
3083 moves and soak them up so user can step
3084 through them and/or save them.
3087 gameMode = IcsObserving;
3090 ics_getting_history = H_GOT_UNREQ_HEADER;
3092 case EditGame: /*?*/
3093 case EditPosition: /*?*/
3094 /* Should above feature work in these modes too? */
3095 /* For now it doesn't */
3096 ics_getting_history = H_GOT_UNWANTED_HEADER;
3099 ics_getting_history = H_GOT_UNWANTED_HEADER;
3104 /* Is this the right one? */
3105 if (gameInfo.white && gameInfo.black &&
3106 strcmp(gameInfo.white, star_match[0]) == 0 &&
3107 strcmp(gameInfo.black, star_match[2]) == 0) {
3109 ics_getting_history = H_GOT_REQ_HEADER;
3112 case H_GOT_REQ_HEADER:
3113 case H_GOT_UNREQ_HEADER:
3114 case H_GOT_UNWANTED_HEADER:
3115 case H_GETTING_MOVES:
3116 /* Should not happen */
3117 DisplayError(_("Error gathering move list: two headers"), 0);
3118 ics_getting_history = H_FALSE;
3122 /* Save player ratings into gameInfo if needed */
3123 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3124 ics_getting_history == H_GOT_UNREQ_HEADER) &&
3125 (gameInfo.whiteRating == -1 ||
3126 gameInfo.blackRating == -1)) {
3128 gameInfo.whiteRating = string_to_rating(star_match[1]);
3129 gameInfo.blackRating = string_to_rating(star_match[3]);
3130 if (appData.debugMode)
3131 fprintf(debugFP, _("Ratings from header: W %d, B %d\n"),
3132 gameInfo.whiteRating, gameInfo.blackRating);
3137 if (looking_at(buf, &i,
3138 "* * match, initial time: * minute*, increment: * second")) {
3139 /* Header for a move list -- second line */
3140 /* Initial board will follow if this is a wild game */
3141 if (gameInfo.event != NULL) free(gameInfo.event);
3142 sprintf(str, "ICS %s %s match", star_match[0], star_match[1]);
3143 gameInfo.event = StrSave(str);
3144 /* [HGM] we switched variant. Translate boards if needed. */
3145 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3149 if (looking_at(buf, &i, "Move ")) {
3150 /* Beginning of a move list */
3151 switch (ics_getting_history) {
3153 /* Normally should not happen */
3154 /* Maybe user hit reset while we were parsing */
3157 /* Happens if we are ignoring a move list that is not
3158 * the one we just requested. Common if the user
3159 * tries to observe two games without turning off
3162 case H_GETTING_MOVES:
3163 /* Should not happen */
3164 DisplayError(_("Error gathering move list: nested"), 0);
3165 ics_getting_history = H_FALSE;
3167 case H_GOT_REQ_HEADER:
3168 ics_getting_history = H_GETTING_MOVES;
3169 started = STARTED_MOVES;
3171 if (oldi > next_out) {
3172 SendToPlayer(&buf[next_out], oldi - next_out);
3175 case H_GOT_UNREQ_HEADER:
3176 ics_getting_history = H_GETTING_MOVES;
3177 started = STARTED_MOVES_NOHIDE;
3180 case H_GOT_UNWANTED_HEADER:
3181 ics_getting_history = H_FALSE;
3187 if (looking_at(buf, &i, "% ") ||
3188 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3189 && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3190 if(ics_type == ICS_ICC && soughtPending) { // [HGM] seekgraph: on ICC sought-list has no termination line
3191 soughtPending = FALSE;
3195 if(suppressKibitz) next_out = i;
3196 savingComment = FALSE;
3200 case STARTED_MOVES_NOHIDE:
3201 memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3202 parse[parse_pos + i - oldi] = NULLCHAR;
3203 ParseGameHistory(parse);
3205 if (appData.zippyPlay && first.initDone) {
3206 FeedMovesToProgram(&first, forwardMostMove);
3207 if (gameMode == IcsPlayingWhite) {
3208 if (WhiteOnMove(forwardMostMove)) {
3209 if (first.sendTime) {
3210 if (first.useColors) {
3211 SendToProgram("black\n", &first);
3213 SendTimeRemaining(&first, TRUE);
3215 if (first.useColors) {
3216 SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3218 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3219 first.maybeThinking = TRUE;
3221 if (first.usePlayother) {
3222 if (first.sendTime) {
3223 SendTimeRemaining(&first, TRUE);
3225 SendToProgram("playother\n", &first);
3231 } else if (gameMode == IcsPlayingBlack) {
3232 if (!WhiteOnMove(forwardMostMove)) {
3233 if (first.sendTime) {
3234 if (first.useColors) {
3235 SendToProgram("white\n", &first);
3237 SendTimeRemaining(&first, FALSE);
3239 if (first.useColors) {
3240 SendToProgram("black\n", &first);
3242 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3243 first.maybeThinking = TRUE;
3245 if (first.usePlayother) {
3246 if (first.sendTime) {
3247 SendTimeRemaining(&first, FALSE);
3249 SendToProgram("playother\n", &first);
3258 if (gameMode == IcsObserving && ics_gamenum == -1) {
3259 /* Moves came from oldmoves or moves command
3260 while we weren't doing anything else.
3262 currentMove = forwardMostMove;
3263 ClearHighlights();/*!!could figure this out*/
3264 flipView = appData.flipView;
3265 DrawPosition(TRUE, boards[currentMove]);
3266 DisplayBothClocks();
3267 sprintf(str, "%s vs. %s",
3268 gameInfo.white, gameInfo.black);
3272 /* Moves were history of an active game */
3273 if (gameInfo.resultDetails != NULL) {
3274 free(gameInfo.resultDetails);
3275 gameInfo.resultDetails = NULL;
3278 HistorySet(parseList, backwardMostMove,
3279 forwardMostMove, currentMove-1);
3280 DisplayMove(currentMove - 1);
3281 if (started == STARTED_MOVES) next_out = i;
3282 started = STARTED_NONE;
3283 ics_getting_history = H_FALSE;
3286 case STARTED_OBSERVE:
3287 started = STARTED_NONE;
3288 SendToICS(ics_prefix);
3289 SendToICS("refresh\n");
3295 if(bookHit) { // [HGM] book: simulate book reply
3296 static char bookMove[MSG_SIZ]; // a bit generous?
3298 programStats.nodes = programStats.depth = programStats.time =
3299 programStats.score = programStats.got_only_move = 0;
3300 sprintf(programStats.movelist, "%s (xbook)", bookHit);
3302 strcpy(bookMove, "move ");
3303 strcat(bookMove, bookHit);
3304 HandleMachineMove(bookMove, &first);
3309 if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3310 started == STARTED_HOLDINGS ||
3311 started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3312 /* Accumulate characters in move list or board */
3313 parse[parse_pos++] = buf[i];
3316 /* Start of game messages. Mostly we detect start of game
3317 when the first board image arrives. On some versions
3318 of the ICS, though, we need to do a "refresh" after starting
3319 to observe in order to get the current board right away. */
3320 if (looking_at(buf, &i, "Adding game * to observation list")) {
3321 started = STARTED_OBSERVE;
3325 /* Handle auto-observe */
3326 if (appData.autoObserve &&
3327 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3328 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3330 /* Choose the player that was highlighted, if any. */
3331 if (star_match[0][0] == '\033' ||
3332 star_match[1][0] != '\033') {
3333 player = star_match[0];
3335 player = star_match[2];
3337 sprintf(str, "%sobserve %s\n",
3338 ics_prefix, StripHighlightAndTitle(player));
3341 /* Save ratings from notify string */
3342 strcpy(player1Name, star_match[0]);
3343 player1Rating = string_to_rating(star_match[1]);
3344 strcpy(player2Name, star_match[2]);
3345 player2Rating = string_to_rating(star_match[3]);
3347 if (appData.debugMode)
3349 "Ratings from 'Game notification:' %s %d, %s %d\n",
3350 player1Name, player1Rating,
3351 player2Name, player2Rating);
3356 /* Deal with automatic examine mode after a game,
3357 and with IcsObserving -> IcsExamining transition */
3358 if (looking_at(buf, &i, "Entering examine mode for game *") ||
3359 looking_at(buf, &i, "has made you an examiner of game *")) {
3361 int gamenum = atoi(star_match[0]);
3362 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3363 gamenum == ics_gamenum) {
3364 /* We were already playing or observing this game;
3365 no need to refetch history */
3366 gameMode = IcsExamining;
3368 pauseExamForwardMostMove = forwardMostMove;
3369 } else if (currentMove < forwardMostMove) {
3370 ForwardInner(forwardMostMove);
3373 /* I don't think this case really can happen */
3374 SendToICS(ics_prefix);
3375 SendToICS("refresh\n");
3380 /* Error messages */
3381 // if (ics_user_moved) {
3382 if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3383 if (looking_at(buf, &i, "Illegal move") ||
3384 looking_at(buf, &i, "Not a legal move") ||
3385 looking_at(buf, &i, "Your king is in check") ||
3386 looking_at(buf, &i, "It isn't your turn") ||
3387 looking_at(buf, &i, "It is not your move")) {
3389 if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3390 currentMove = --forwardMostMove;
3391 DisplayMove(currentMove - 1); /* before DMError */
3392 DrawPosition(FALSE, boards[currentMove]);
3394 DisplayBothClocks();
3396 DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3402 if (looking_at(buf, &i, "still have time") ||
3403 looking_at(buf, &i, "not out of time") ||
3404 looking_at(buf, &i, "either player is out of time") ||
3405 looking_at(buf, &i, "has timeseal; checking")) {
3406 /* We must have called his flag a little too soon */
3407 whiteFlag = blackFlag = FALSE;
3411 if (looking_at(buf, &i, "added * seconds to") ||
3412 looking_at(buf, &i, "seconds were added to")) {
3413 /* Update the clocks */
3414 SendToICS(ics_prefix);
3415 SendToICS("refresh\n");
3419 if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3420 ics_clock_paused = TRUE;
3425 if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3426 ics_clock_paused = FALSE;
3431 /* Grab player ratings from the Creating: message.
3432 Note we have to check for the special case when
3433 the ICS inserts things like [white] or [black]. */
3434 if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3435 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3437 0 player 1 name (not necessarily white)
3439 2 empty, white, or black (IGNORED)
3440 3 player 2 name (not necessarily black)
3443 The names/ratings are sorted out when the game
3444 actually starts (below).
3446 strcpy(player1Name, StripHighlightAndTitle(star_match[0]));
3447 player1Rating = string_to_rating(star_match[1]);
3448 strcpy(player2Name, StripHighlightAndTitle(star_match[3]));
3449 player2Rating = string_to_rating(star_match[4]);
3451 if (appData.debugMode)
3453 "Ratings from 'Creating:' %s %d, %s %d\n",
3454 player1Name, player1Rating,
3455 player2Name, player2Rating);
3460 /* Improved generic start/end-of-game messages */
3461 if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3462 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3463 /* If tkind == 0: */
3464 /* star_match[0] is the game number */
3465 /* [1] is the white player's name */
3466 /* [2] is the black player's name */
3467 /* For end-of-game: */
3468 /* [3] is the reason for the game end */
3469 /* [4] is a PGN end game-token, preceded by " " */
3470 /* For start-of-game: */
3471 /* [3] begins with "Creating" or "Continuing" */
3472 /* [4] is " *" or empty (don't care). */
3473 int gamenum = atoi(star_match[0]);
3474 char *whitename, *blackname, *why, *endtoken;
3475 ChessMove endtype = (ChessMove) 0;
3478 whitename = star_match[1];
3479 blackname = star_match[2];
3480 why = star_match[3];
3481 endtoken = star_match[4];
3483 whitename = star_match[1];
3484 blackname = star_match[3];
3485 why = star_match[5];
3486 endtoken = star_match[6];
3489 /* Game start messages */
3490 if (strncmp(why, "Creating ", 9) == 0 ||
3491 strncmp(why, "Continuing ", 11) == 0) {
3492 gs_gamenum = gamenum;
3493 strcpy(gs_kind, strchr(why, ' ') + 1);
3494 VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
3496 if (appData.zippyPlay) {
3497 ZippyGameStart(whitename, blackname);
3503 /* Game end messages */
3504 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3505 ics_gamenum != gamenum) {
3508 while (endtoken[0] == ' ') endtoken++;
3509 switch (endtoken[0]) {
3512 endtype = GameUnfinished;
3515 endtype = BlackWins;
3518 if (endtoken[1] == '/')
3519 endtype = GameIsDrawn;
3521 endtype = WhiteWins;
3524 GameEnds(endtype, why, GE_ICS);
3526 if (appData.zippyPlay && first.initDone) {
3527 ZippyGameEnd(endtype, why);
3528 if (first.pr == NULL) {
3529 /* Start the next process early so that we'll
3530 be ready for the next challenge */
3531 StartChessProgram(&first);
3533 /* Send "new" early, in case this command takes
3534 a long time to finish, so that we'll be ready
3535 for the next challenge. */
3536 gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3543 if (looking_at(buf, &i, "Removing game * from observation") ||
3544 looking_at(buf, &i, "no longer observing game *") ||
3545 looking_at(buf, &i, "Game * (*) has no examiners")) {
3546 if (gameMode == IcsObserving &&
3547 atoi(star_match[0]) == ics_gamenum)
3549 /* icsEngineAnalyze */
3550 if (appData.icsEngineAnalyze) {
3557 ics_user_moved = FALSE;
3562 if (looking_at(buf, &i, "no longer examining game *")) {
3563 if (gameMode == IcsExamining &&
3564 atoi(star_match[0]) == ics_gamenum)
3568 ics_user_moved = FALSE;
3573 /* Advance leftover_start past any newlines we find,
3574 so only partial lines can get reparsed */
3575 if (looking_at(buf, &i, "\n")) {
3576 prevColor = curColor;
3577 if (curColor != ColorNormal) {
3578 if (oldi > next_out) {
3579 SendToPlayer(&buf[next_out], oldi - next_out);
3582 Colorize(ColorNormal, FALSE);
3583 curColor = ColorNormal;
3585 if (started == STARTED_BOARD) {
3586 started = STARTED_NONE;
3587 parse[parse_pos] = NULLCHAR;
3588 ParseBoard12(parse);
3591 /* Send premove here */
3592 if (appData.premove) {
3594 if (currentMove == 0 &&
3595 gameMode == IcsPlayingWhite &&
3596 appData.premoveWhite) {
3597 sprintf(str, "%s\n", appData.premoveWhiteText);
3598 if (appData.debugMode)
3599 fprintf(debugFP, "Sending premove:\n");
3601 } else if (currentMove == 1 &&
3602 gameMode == IcsPlayingBlack &&
3603 appData.premoveBlack) {
3604 sprintf(str, "%s\n", appData.premoveBlackText);
3605 if (appData.debugMode)
3606 fprintf(debugFP, "Sending premove:\n");
3608 } else if (gotPremove) {
3610 ClearPremoveHighlights();
3611 if (appData.debugMode)
3612 fprintf(debugFP, "Sending premove:\n");
3613 UserMoveEvent(premoveFromX, premoveFromY,
3614 premoveToX, premoveToY,
3619 /* Usually suppress following prompt */
3620 if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
3621 while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
3622 if (looking_at(buf, &i, "*% ")) {
3623 savingComment = FALSE;
3628 } else if (started == STARTED_HOLDINGS) {
3630 char new_piece[MSG_SIZ];
3631 started = STARTED_NONE;
3632 parse[parse_pos] = NULLCHAR;
3633 if (appData.debugMode)
3634 fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
3635 parse, currentMove);
3636 if (sscanf(parse, " game %d", &gamenum) == 1 &&
3637 gamenum == ics_gamenum) {
3638 if (gameInfo.variant == VariantNormal) {
3639 /* [HGM] We seem to switch variant during a game!
3640 * Presumably no holdings were displayed, so we have
3641 * to move the position two files to the right to
3642 * create room for them!
3644 VariantClass newVariant;
3645 switch(gameInfo.boardWidth) { // base guess on board width
3646 case 9: newVariant = VariantShogi; break;
3647 case 10: newVariant = VariantGreat; break;
3648 default: newVariant = VariantCrazyhouse; break;
3650 VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3651 /* Get a move list just to see the header, which
3652 will tell us whether this is really bug or zh */
3653 if (ics_getting_history == H_FALSE) {
3654 ics_getting_history = H_REQUESTED;
3655 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3659 new_piece[0] = NULLCHAR;
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 board holdings area */
3666 CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
3667 CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
3668 boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
3670 if (appData.zippyPlay && first.initDone) {
3671 ZippyHoldings(white_holding, black_holding,
3675 if (tinyLayout || smallLayout) {
3676 char wh[16], bh[16];
3677 PackHolding(wh, white_holding);
3678 PackHolding(bh, black_holding);
3679 sprintf(str, "[%s-%s] %s-%s", wh, bh,
3680 gameInfo.white, gameInfo.black);
3682 sprintf(str, "%s [%s] vs. %s [%s]",
3683 gameInfo.white, white_holding,
3684 gameInfo.black, black_holding);
3687 DrawPosition(FALSE, boards[currentMove]);
3690 /* Suppress following prompt */
3691 if (looking_at(buf, &i, "*% ")) {
3692 if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
3693 savingComment = FALSE;
3701 i++; /* skip unparsed character and loop back */
3704 if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
3705 // started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
3706 // SendToPlayer(&buf[next_out], i - next_out);
3707 started != STARTED_HOLDINGS && leftover_start > next_out) {
3708 SendToPlayer(&buf[next_out], leftover_start - next_out);
3712 leftover_len = buf_len - leftover_start;
3713 /* if buffer ends with something we couldn't parse,
3714 reparse it after appending the next read */
3716 } else if (count == 0) {
3717 RemoveInputSource(isr);
3718 DisplayFatalError(_("Connection closed by ICS"), 0, 0);
3720 DisplayFatalError(_("Error reading from ICS"), error, 1);
3725 /* Board style 12 looks like this:
3727 <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
3729 * The "<12> " is stripped before it gets to this routine. The two
3730 * trailing 0's (flip state and clock ticking) are later addition, and
3731 * some chess servers may not have them, or may have only the first.
3732 * Additional trailing fields may be added in the future.
3735 #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"
3737 #define RELATION_OBSERVING_PLAYED 0
3738 #define RELATION_OBSERVING_STATIC -2 /* examined, oldmoves, or smoves */
3739 #define RELATION_PLAYING_MYMOVE 1
3740 #define RELATION_PLAYING_NOTMYMOVE -1
3741 #define RELATION_EXAMINING 2
3742 #define RELATION_ISOLATED_BOARD -3
3743 #define RELATION_STARTING_POSITION -4 /* FICS only */
3746 ParseBoard12(string)
3749 GameMode newGameMode;
3750 int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
3751 int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
3752 int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
3753 char to_play, board_chars[200];
3754 char move_str[500], str[500], elapsed_time[500];
3755 char black[32], white[32];
3757 int prevMove = currentMove;
3760 int fromX, fromY, toX, toY;
3762 int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
3763 char *bookHit = NULL; // [HGM] book
3764 Boolean weird = FALSE, reqFlag = FALSE;
3766 fromX = fromY = toX = toY = -1;
3770 if (appData.debugMode)
3771 fprintf(debugFP, _("Parsing board: %s\n"), string);
3773 move_str[0] = NULLCHAR;
3774 elapsed_time[0] = NULLCHAR;
3775 { /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
3777 while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
3778 if(string[i] == ' ') { ranks++; files = 0; }
3780 if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
3783 for(j = 0; j <i; j++) board_chars[j] = string[j];
3784 board_chars[i] = '\0';
3787 n = sscanf(string, PATTERN, &to_play, &double_push,
3788 &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
3789 &gamenum, white, black, &relation, &basetime, &increment,
3790 &white_stren, &black_stren, &white_time, &black_time,
3791 &moveNum, str, elapsed_time, move_str, &ics_flip,
3795 snprintf(str, sizeof(str), _("Failed to parse board string:\n\"%s\""), string);
3796 DisplayError(str, 0);
3800 /* Convert the move number to internal form */
3801 moveNum = (moveNum - 1) * 2;
3802 if (to_play == 'B') moveNum++;
3803 if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
3804 DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
3810 case RELATION_OBSERVING_PLAYED:
3811 case RELATION_OBSERVING_STATIC:
3812 if (gamenum == -1) {
3813 /* Old ICC buglet */
3814 relation = RELATION_OBSERVING_STATIC;
3816 newGameMode = IcsObserving;
3818 case RELATION_PLAYING_MYMOVE:
3819 case RELATION_PLAYING_NOTMYMOVE:
3821 ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
3822 IcsPlayingWhite : IcsPlayingBlack;
3824 case RELATION_EXAMINING:
3825 newGameMode = IcsExamining;
3827 case RELATION_ISOLATED_BOARD:
3829 /* Just display this board. If user was doing something else,
3830 we will forget about it until the next board comes. */
3831 newGameMode = IcsIdle;
3833 case RELATION_STARTING_POSITION:
3834 newGameMode = gameMode;
3838 /* Modify behavior for initial board display on move listing
3841 switch (ics_getting_history) {
3845 case H_GOT_REQ_HEADER:
3846 case H_GOT_UNREQ_HEADER:
3847 /* This is the initial position of the current game */
3848 gamenum = ics_gamenum;
3849 moveNum = 0; /* old ICS bug workaround */
3850 if (to_play == 'B') {
3851 startedFromSetupPosition = TRUE;
3852 blackPlaysFirst = TRUE;
3854 if (forwardMostMove == 0) forwardMostMove = 1;
3855 if (backwardMostMove == 0) backwardMostMove = 1;
3856 if (currentMove == 0) currentMove = 1;
3858 newGameMode = gameMode;
3859 relation = RELATION_STARTING_POSITION; /* ICC needs this */
3861 case H_GOT_UNWANTED_HEADER:
3862 /* This is an initial board that we don't want */
3864 case H_GETTING_MOVES:
3865 /* Should not happen */
3866 DisplayError(_("Error gathering move list: extra board"), 0);
3867 ics_getting_history = H_FALSE;
3871 if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
3872 weird && (int)gameInfo.variant <= (int)VariantShogi) {
3873 /* [HGM] We seem to have switched variant unexpectedly
3874 * Try to guess new variant from board size
3876 VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
3877 if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
3878 if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
3879 if(ranks == 8 && files == 12) newVariant = VariantCourier; else
3880 if(ranks == 9 && files == 9) newVariant = VariantShogi; else
3881 if(!weird) newVariant = VariantNormal;
3882 VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3883 /* Get a move list just to see the header, which
3884 will tell us whether this is really bug or zh */
3885 if (ics_getting_history == H_FALSE) {
3886 ics_getting_history = H_REQUESTED; reqFlag = TRUE;
3887 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3892 /* Take action if this is the first board of a new game, or of a
3893 different game than is currently being displayed. */
3894 if (gamenum != ics_gamenum || newGameMode != gameMode ||
3895 relation == RELATION_ISOLATED_BOARD) {
3897 /* Forget the old game and get the history (if any) of the new one */
3898 if (gameMode != BeginningOfGame) {
3902 if (appData.autoRaiseBoard) BoardToTop();
3904 if (gamenum == -1) {
3905 newGameMode = IcsIdle;
3906 } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
3907 appData.getMoveList && !reqFlag) {
3908 /* Need to get game history */
3909 ics_getting_history = H_REQUESTED;
3910 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3914 /* Initially flip the board to have black on the bottom if playing
3915 black or if the ICS flip flag is set, but let the user change
3916 it with the Flip View button. */
3917 flipView = appData.autoFlipView ?
3918 (newGameMode == IcsPlayingBlack) || ics_flip :
3921 /* Done with values from previous mode; copy in new ones */
3922 gameMode = newGameMode;
3924 ics_gamenum = gamenum;
3925 if (gamenum == gs_gamenum) {
3926 int klen = strlen(gs_kind);
3927 if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
3928 sprintf(str, "ICS %s", gs_kind);
3929 gameInfo.event = StrSave(str);
3931 gameInfo.event = StrSave("ICS game");
3933 gameInfo.site = StrSave(appData.icsHost);
3934 gameInfo.date = PGNDate();
3935 gameInfo.round = StrSave("-");
3936 gameInfo.white = StrSave(white);
3937 gameInfo.black = StrSave(black);
3938 timeControl = basetime * 60 * 1000;
3940 timeIncrement = increment * 1000;
3941 movesPerSession = 0;
3942 gameInfo.timeControl = TimeControlTagValue();
3943 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
3944 if (appData.debugMode) {
3945 fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
3946 fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
3947 setbuf(debugFP, NULL);
3950 gameInfo.outOfBook = NULL;
3952 /* Do we have the ratings? */
3953 if (strcmp(player1Name, white) == 0 &&
3954 strcmp(player2Name, black) == 0) {
3955 if (appData.debugMode)
3956 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3957 player1Rating, player2Rating);
3958 gameInfo.whiteRating = player1Rating;
3959 gameInfo.blackRating = player2Rating;
3960 } else if (strcmp(player2Name, white) == 0 &&
3961 strcmp(player1Name, black) == 0) {
3962 if (appData.debugMode)
3963 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3964 player2Rating, player1Rating);
3965 gameInfo.whiteRating = player2Rating;
3966 gameInfo.blackRating = player1Rating;
3968 player1Name[0] = player2Name[0] = NULLCHAR;
3970 /* Silence shouts if requested */
3971 if (appData.quietPlay &&
3972 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
3973 SendToICS(ics_prefix);
3974 SendToICS("set shout 0\n");
3978 /* Deal with midgame name changes */
3980 if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
3981 if (gameInfo.white) free(gameInfo.white);
3982 gameInfo.white = StrSave(white);
3984 if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
3985 if (gameInfo.black) free(gameInfo.black);
3986 gameInfo.black = StrSave(black);
3990 /* Throw away game result if anything actually changes in examine mode */
3991 if (gameMode == IcsExamining && !newGame) {
3992 gameInfo.result = GameUnfinished;
3993 if (gameInfo.resultDetails != NULL) {
3994 free(gameInfo.resultDetails);
3995 gameInfo.resultDetails = NULL;
3999 /* In pausing && IcsExamining mode, we ignore boards coming
4000 in if they are in a different variation than we are. */
4001 if (pauseExamInvalid) return;
4002 if (pausing && gameMode == IcsExamining) {
4003 if (moveNum <= pauseExamForwardMostMove) {
4004 pauseExamInvalid = TRUE;
4005 forwardMostMove = pauseExamForwardMostMove;
4010 if (appData.debugMode) {
4011 fprintf(debugFP, "load %dx%d board\n", files, ranks);
4013 /* Parse the board */
4014 for (k = 0; k < ranks; k++) {
4015 for (j = 0; j < files; j++)
4016 board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4017 if(gameInfo.holdingsWidth > 1) {
4018 board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4019 board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4022 CopyBoard(boards[moveNum], board);
4023 boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
4025 startedFromSetupPosition =
4026 !CompareBoards(board, initialPosition);
4027 if(startedFromSetupPosition)
4028 initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
4031 /* [HGM] Set castling rights. Take the outermost Rooks,
4032 to make it also work for FRC opening positions. Note that board12
4033 is really defective for later FRC positions, as it has no way to
4034 indicate which Rook can castle if they are on the same side of King.
4035 For the initial position we grant rights to the outermost Rooks,
4036 and remember thos rights, and we then copy them on positions
4037 later in an FRC game. This means WB might not recognize castlings with
4038 Rooks that have moved back to their original position as illegal,
4039 but in ICS mode that is not its job anyway.
4041 if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
4042 { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
4044 for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4045 if(board[0][i] == WhiteRook) j = i;
4046 initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4047 for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4048 if(board[0][i] == WhiteRook) j = i;
4049 initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4050 for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4051 if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4052 initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4053 for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4054 if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4055 initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4057 if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
4058 for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4059 if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
4060 for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4061 if(board[BOARD_HEIGHT-1][k] == bKing)
4062 initialRights[5] = boards[moveNum][CASTLING][5] = k;
4063 if(gameInfo.variant == VariantTwoKings) {
4064 // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
4065 if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
4066 if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
4069 r = boards[moveNum][CASTLING][0] = initialRights[0];
4070 if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4071 r = boards[moveNum][CASTLING][1] = initialRights[1];
4072 if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4073 r = boards[moveNum][CASTLING][3] = initialRights[3];
4074 if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4075 r = boards[moveNum][CASTLING][4] = initialRights[4];
4076 if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4077 /* wildcastle kludge: always assume King has rights */
4078 r = boards[moveNum][CASTLING][2] = initialRights[2];
4079 r = boards[moveNum][CASTLING][5] = initialRights[5];
4081 /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4082 boards[moveNum][EP_STATUS] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
4085 if (ics_getting_history == H_GOT_REQ_HEADER ||
4086 ics_getting_history == H_GOT_UNREQ_HEADER) {
4087 /* This was an initial position from a move list, not
4088 the current position */
4092 /* Update currentMove and known move number limits */
4093 newMove = newGame || moveNum > forwardMostMove;
4096 forwardMostMove = backwardMostMove = currentMove = moveNum;
4097 if (gameMode == IcsExamining && moveNum == 0) {
4098 /* Workaround for ICS limitation: we are not told the wild
4099 type when starting to examine a game. But if we ask for
4100 the move list, the move list header will tell us */
4101 ics_getting_history = H_REQUESTED;
4102 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
4105 } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4106 || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4108 /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4109 /* [HGM] applied this also to an engine that is silently watching */
4110 if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4111 (gameMode == IcsObserving || gameMode == IcsExamining) &&
4112 gameInfo.variant == currentlyInitializedVariant) {
4113 takeback = forwardMostMove - moveNum;
4114 for (i = 0; i < takeback; i++) {
4115 if (appData.debugMode) fprintf(debugFP, "take back move\n");
4116 SendToProgram("undo\n", &first);
4121 forwardMostMove = moveNum;
4122 if (!pausing || currentMove > forwardMostMove)
4123 currentMove = forwardMostMove;
4125 /* New part of history that is not contiguous with old part */
4126 if (pausing && gameMode == IcsExamining) {
4127 pauseExamInvalid = TRUE;
4128 forwardMostMove = pauseExamForwardMostMove;
4131 if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4133 if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4134 // [HGM] when we will receive the move list we now request, it will be
4135 // fed to the engine from the first move on. So if the engine is not
4136 // in the initial position now, bring it there.
4137 InitChessProgram(&first, 0);
4140 ics_getting_history = H_REQUESTED;
4141 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
4144 forwardMostMove = backwardMostMove = currentMove = moveNum;
4147 /* Update the clocks */
4148 if (strchr(elapsed_time, '.')) {
4150 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4151 timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4153 /* Time is in seconds */
4154 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4155 timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4160 if (appData.zippyPlay && newGame &&
4161 gameMode != IcsObserving && gameMode != IcsIdle &&
4162 gameMode != IcsExamining)
4163 ZippyFirstBoard(moveNum, basetime, increment);
4166 /* Put the move on the move list, first converting
4167 to canonical algebraic form. */
4169 if (appData.debugMode) {
4170 if (appData.debugMode) { int f = forwardMostMove;
4171 fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4172 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4173 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4175 fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4176 fprintf(debugFP, "moveNum = %d\n", moveNum);
4177 fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4178 setbuf(debugFP, NULL);
4180 if (moveNum <= backwardMostMove) {
4181 /* We don't know what the board looked like before
4183 strcpy(parseList[moveNum - 1], move_str);
4184 strcat(parseList[moveNum - 1], " ");
4185 strcat(parseList[moveNum - 1], elapsed_time);
4186 moveList[moveNum - 1][0] = NULLCHAR;
4187 } else if (strcmp(move_str, "none") == 0) {
4188 // [HGM] long SAN: swapped order; test for 'none' before parsing move
4189 /* Again, we don't know what the board looked like;
4190 this is really the start of the game. */
4191 parseList[moveNum - 1][0] = NULLCHAR;
4192 moveList[moveNum - 1][0] = NULLCHAR;
4193 backwardMostMove = moveNum;
4194 startedFromSetupPosition = TRUE;
4195 fromX = fromY = toX = toY = -1;
4197 // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
4198 // So we parse the long-algebraic move string in stead of the SAN move
4199 int valid; char buf[MSG_SIZ], *prom;
4201 // str looks something like "Q/a1-a2"; kill the slash
4203 sprintf(buf, "%c%s", str[0], str+2);
4204 else strcpy(buf, str); // might be castling
4205 if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
4206 strcat(buf, prom); // long move lacks promo specification!
4207 if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4208 if(appData.debugMode)
4209 fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4210 strcpy(move_str, buf);
4212 valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4213 &fromX, &fromY, &toX, &toY, &promoChar)
4214 || ParseOneMove(buf, moveNum - 1, &moveType,
4215 &fromX, &fromY, &toX, &toY, &promoChar);
4216 // end of long SAN patch
4218 (void) CoordsToAlgebraic(boards[moveNum - 1],
4219 PosFlags(moveNum - 1),
4220 fromY, fromX, toY, toX, promoChar,
4221 parseList[moveNum-1]);
4222 switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4228 if(gameInfo.variant != VariantShogi)
4229 strcat(parseList[moveNum - 1], "+");
4232 case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4233 strcat(parseList[moveNum - 1], "#");
4236 strcat(parseList[moveNum - 1], " ");
4237 strcat(parseList[moveNum - 1], elapsed_time);
4238 /* currentMoveString is set as a side-effect of ParseOneMove */
4239 strcpy(moveList[moveNum - 1], currentMoveString);
4240 strcat(moveList[moveNum - 1], "\n");
4242 /* Move from ICS was illegal!? Punt. */
4243 if (appData.debugMode) {
4244 fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4245 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4247 strcpy(parseList[moveNum - 1], move_str);
4248 strcat(parseList[moveNum - 1], " ");
4249 strcat(parseList[moveNum - 1], elapsed_time);
4250 moveList[moveNum - 1][0] = NULLCHAR;
4251 fromX = fromY = toX = toY = -1;
4254 if (appData.debugMode) {
4255 fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4256 setbuf(debugFP, NULL);
4260 /* Send move to chess program (BEFORE animating it). */
4261 if (appData.zippyPlay && !newGame && newMove &&
4262 (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4264 if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4265 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4266 if (moveList[moveNum - 1][0] == NULLCHAR) {
4267 sprintf(str, _("Couldn't parse move \"%s\" from ICS"),
4269 DisplayError(str, 0);
4271 if (first.sendTime) {
4272 SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4274 bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4275 if (firstMove && !bookHit) {
4277 if (first.useColors) {
4278 SendToProgram(gameMode == IcsPlayingWhite ?
4280 "black\ngo\n", &first);
4282 SendToProgram("go\n", &first);
4284 first.maybeThinking = TRUE;
4287 } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4288 if (moveList[moveNum - 1][0] == NULLCHAR) {
4289 sprintf(str, _("Couldn't parse move \"%s\" from ICS"), move_str);
4290 DisplayError(str, 0);
4292 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4293 SendMoveToProgram(moveNum - 1, &first);
4300 if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4301 /* If move comes from a remote source, animate it. If it
4302 isn't remote, it will have already been animated. */
4303 if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4304 AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4306 if (!pausing && appData.highlightLastMove) {
4307 SetHighlights(fromX, fromY, toX, toY);
4311 /* Start the clocks */
4312 whiteFlag = blackFlag = FALSE;
4313 appData.clockMode = !(basetime == 0 && increment == 0);
4315 ics_clock_paused = TRUE;
4317 } else if (ticking == 1) {
4318 ics_clock_paused = FALSE;
4320 if (gameMode == IcsIdle ||
4321 relation == RELATION_OBSERVING_STATIC ||
4322 relation == RELATION_EXAMINING ||
4324 DisplayBothClocks();
4328 /* Display opponents and material strengths */
4329 if (gameInfo.variant != VariantBughouse &&
4330 gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4331 if (tinyLayout || smallLayout) {
4332 if(gameInfo.variant == VariantNormal)
4333 sprintf(str, "%s(%d) %s(%d) {%d %d}",
4334 gameInfo.white, white_stren, gameInfo.black, black_stren,
4335 basetime, increment);
4337 sprintf(str, "%s(%d) %s(%d) {%d %d w%d}",
4338 gameInfo.white, white_stren, gameInfo.black, black_stren,
4339 basetime, increment, (int) gameInfo.variant);
4341 if(gameInfo.variant == VariantNormal)
4342 sprintf(str, "%s (%d) vs. %s (%d) {%d %d}",
4343 gameInfo.white, white_stren, gameInfo.black, black_stren,
4344 basetime, increment);
4346 sprintf(str, "%s (%d) vs. %s (%d) {%d %d %s}",
4347 gameInfo.white, white_stren, gameInfo.black, black_stren,
4348 basetime, increment, VariantName(gameInfo.variant));
4351 if (appData.debugMode) {
4352 fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
4357 /* Display the board */
4358 if (!pausing && !appData.noGUI) {
4359 if (appData.premove)
4361 ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
4362 ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
4363 ClearPremoveHighlights();
4365 j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
4366 DrawPosition(j, boards[currentMove]);
4368 DisplayMove(moveNum - 1);
4369 if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4370 !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4371 (gameMode == IcsPlayingBlack) && (WhiteOnMove(moveNum)) ) ) {
4372 if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4376 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4378 if(bookHit) { // [HGM] book: simulate book reply
4379 static char bookMove[MSG_SIZ]; // a bit generous?
4381 programStats.nodes = programStats.depth = programStats.time =
4382 programStats.score = programStats.got_only_move = 0;
4383 sprintf(programStats.movelist, "%s (xbook)", bookHit);
4385 strcpy(bookMove, "move ");
4386 strcat(bookMove, bookHit);
4387 HandleMachineMove(bookMove, &first);
4396 if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4397 ics_getting_history = H_REQUESTED;
4398 sprintf(buf, "%smoves %d\n", ics_prefix, ics_gamenum);
4404 AnalysisPeriodicEvent(force)
4407 if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4408 && !force) || !appData.periodicUpdates)
4411 /* Send . command to Crafty to collect stats */
4412 SendToProgram(".\n", &first);
4414 /* Don't send another until we get a response (this makes
4415 us stop sending to old Crafty's which don't understand
4416 the "." command (sending illegal cmds resets node count & time,
4417 which looks bad)) */
4418 programStats.ok_to_send = 0;
4421 void ics_update_width(new_width)
4424 ics_printf("set width %d\n", new_width);
4428 SendMoveToProgram(moveNum, cps)
4430 ChessProgramState *cps;
4434 if (cps->useUsermove) {
4435 SendToProgram("usermove ", cps);
4439 if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4440 int len = space - parseList[moveNum];
4441 memcpy(buf, parseList[moveNum], len);
4443 buf[len] = NULLCHAR;
4445 sprintf(buf, "%s\n", parseList[moveNum]);
4447 SendToProgram(buf, cps);
4449 if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4450 AlphaRank(moveList[moveNum], 4);
4451 SendToProgram(moveList[moveNum], cps);
4452 AlphaRank(moveList[moveNum], 4); // and back
4454 /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
4455 * the engine. It would be nice to have a better way to identify castle
4457 if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
4458 && cps->useOOCastle) {
4459 int fromX = moveList[moveNum][0] - AAA;
4460 int fromY = moveList[moveNum][1] - ONE;
4461 int toX = moveList[moveNum][2] - AAA;
4462 int toY = moveList[moveNum][3] - ONE;
4463 if((boards[moveNum][fromY][fromX] == WhiteKing
4464 && boards[moveNum][toY][toX] == WhiteRook)
4465 || (boards[moveNum][fromY][fromX] == BlackKing
4466 && boards[moveNum][toY][toX] == BlackRook)) {
4467 if(toX > fromX) SendToProgram("O-O\n", cps);
4468 else SendToProgram("O-O-O\n", cps);
4470 else SendToProgram(moveList[moveNum], cps);
4472 else SendToProgram(moveList[moveNum], cps);
4473 /* End of additions by Tord */
4476 /* [HGM] setting up the opening has brought engine in force mode! */
4477 /* Send 'go' if we are in a mode where machine should play. */
4478 if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4479 (gameMode == TwoMachinesPlay ||
4481 gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite ||
4483 gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4484 SendToProgram("go\n", cps);
4485 if (appData.debugMode) {
4486 fprintf(debugFP, "(extra)\n");
4489 setboardSpoiledMachineBlack = 0;
4493 SendMoveToICS(moveType, fromX, fromY, toX, toY)
4495 int fromX, fromY, toX, toY;
4497 char user_move[MSG_SIZ];
4501 sprintf(user_move, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4502 (int)moveType, fromX, fromY, toX, toY);
4503 DisplayError(user_move + strlen("say "), 0);
4505 case WhiteKingSideCastle:
4506 case BlackKingSideCastle:
4507 case WhiteQueenSideCastleWild:
4508 case BlackQueenSideCastleWild:
4510 case WhiteHSideCastleFR:
4511 case BlackHSideCastleFR:
4513 sprintf(user_move, "o-o\n");
4515 case WhiteQueenSideCastle:
4516 case BlackQueenSideCastle:
4517 case WhiteKingSideCastleWild:
4518 case BlackKingSideCastleWild:
4520 case WhiteASideCastleFR:
4521 case BlackASideCastleFR:
4523 sprintf(user_move, "o-o-o\n");
4525 case WhitePromotionQueen:
4526 case BlackPromotionQueen:
4527 case WhitePromotionRook:
4528 case BlackPromotionRook:
4529 case WhitePromotionBishop:
4530 case BlackPromotionBishop:
4531 case WhitePromotionKnight:
4532 case BlackPromotionKnight:
4533 case WhitePromotionKing:
4534 case BlackPromotionKing:
4535 case WhitePromotionChancellor:
4536 case BlackPromotionChancellor:
4537 case WhitePromotionArchbishop:
4538 case BlackPromotionArchbishop:
4539 if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
4540 sprintf(user_move, "%c%c%c%c=%c\n",
4541 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4542 PieceToChar(WhiteFerz));
4543 else if(gameInfo.variant == VariantGreat)
4544 sprintf(user_move, "%c%c%c%c=%c\n",
4545 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4546 PieceToChar(WhiteMan));
4548 sprintf(user_move, "%c%c%c%c=%c\n",
4549 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4550 PieceToChar(PromoPiece(moveType)));
4554 sprintf(user_move, "%c@%c%c\n",
4555 ToUpper(PieceToChar((ChessSquare) fromX)),
4556 AAA + toX, ONE + toY);
4559 case WhiteCapturesEnPassant:
4560 case BlackCapturesEnPassant:
4561 case IllegalMove: /* could be a variant we don't quite understand */
4562 sprintf(user_move, "%c%c%c%c\n",
4563 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4566 SendToICS(user_move);
4567 if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
4568 ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
4572 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
4577 if (rf == DROP_RANK) {
4578 sprintf(move, "%c@%c%c\n",
4579 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
4581 if (promoChar == 'x' || promoChar == NULLCHAR) {
4582 sprintf(move, "%c%c%c%c\n",
4583 AAA + ff, ONE + rf, AAA + ft, ONE + rt);
4585 sprintf(move, "%c%c%c%c%c\n",
4586 AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
4592 ProcessICSInitScript(f)
4597 while (fgets(buf, MSG_SIZ, f)) {
4598 SendToICSDelayed(buf,(long)appData.msLoginDelay);
4605 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
4607 AlphaRank(char *move, int n)
4609 // char *p = move, c; int x, y;
4611 if (appData.debugMode) {
4612 fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
4616 move[2]>='0' && move[2]<='9' &&
4617 move[3]>='a' && move[3]<='x' ) {
4619 move[2] = BOARD_RGHT -1 - (move[2]-'1') + AAA;
4620 move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4622 if(move[0]>='0' && move[0]<='9' &&
4623 move[1]>='a' && move[1]<='x' &&
4624 move[2]>='0' && move[2]<='9' &&
4625 move[3]>='a' && move[3]<='x' ) {
4626 /* input move, Shogi -> normal */
4627 move[0] = BOARD_RGHT -1 - (move[0]-'1') + AAA;
4628 move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
4629 move[2] = BOARD_RGHT -1 - (move[2]-'1') + AAA;
4630 move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4633 move[3]>='0' && move[3]<='9' &&
4634 move[2]>='a' && move[2]<='x' ) {
4636 move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4637 move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4640 move[0]>='a' && move[0]<='x' &&
4641 move[3]>='0' && move[3]<='9' &&
4642 move[2]>='a' && move[2]<='x' ) {
4643 /* output move, normal -> Shogi */
4644 move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
4645 move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
4646 move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4647 move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4648 if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
4650 if (appData.debugMode) {
4651 fprintf(debugFP, " out = '%s'\n", move);
4655 /* Parser for moves from gnuchess, ICS, or user typein box */
4657 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
4660 ChessMove *moveType;
4661 int *fromX, *fromY, *toX, *toY;
4664 if (appData.debugMode) {
4665 fprintf(debugFP, "move to parse: %s\n", move);
4667 *moveType = yylexstr(moveNum, move);
4669 switch (*moveType) {
4670 case WhitePromotionChancellor:
4671 case BlackPromotionChancellor:
4672 case WhitePromotionArchbishop:
4673 case BlackPromotionArchbishop:
4674 case WhitePromotionQueen:
4675 case BlackPromotionQueen:
4676 case WhitePromotionRook:
4677 case BlackPromotionRook:
4678 case WhitePromotionBishop:
4679 case BlackPromotionBishop:
4680 case WhitePromotionKnight:
4681 case BlackPromotionKnight:
4682 case WhitePromotionKing:
4683 case BlackPromotionKing:
4685 case WhiteCapturesEnPassant:
4686 case BlackCapturesEnPassant:
4687 case WhiteKingSideCastle:
4688 case WhiteQueenSideCastle:
4689 case BlackKingSideCastle:
4690 case BlackQueenSideCastle:
4691 case WhiteKingSideCastleWild:
4692 case WhiteQueenSideCastleWild:
4693 case BlackKingSideCastleWild:
4694 case BlackQueenSideCastleWild:
4695 /* Code added by Tord: */
4696 case WhiteHSideCastleFR:
4697 case WhiteASideCastleFR:
4698 case BlackHSideCastleFR:
4699 case BlackASideCastleFR:
4700 /* End of code added by Tord */
4701 case IllegalMove: /* bug or odd chess variant */
4702 *fromX = currentMoveString[0] - AAA;
4703 *fromY = currentMoveString[1] - ONE;
4704 *toX = currentMoveString[2] - AAA;
4705 *toY = currentMoveString[3] - ONE;
4706 *promoChar = currentMoveString[4];
4707 if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
4708 *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
4709 if (appData.debugMode) {
4710 fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
4712 *fromX = *fromY = *toX = *toY = 0;
4715 if (appData.testLegality) {
4716 return (*moveType != IllegalMove);
4718 return !(*fromX == *toX && *fromY == *toY) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
4719 WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
4724 *fromX = *moveType == WhiteDrop ?
4725 (int) CharToPiece(ToUpper(currentMoveString[0])) :
4726 (int) CharToPiece(ToLower(currentMoveString[0]));
4728 *toX = currentMoveString[2] - AAA;
4729 *toY = currentMoveString[3] - ONE;
4730 *promoChar = NULLCHAR;
4734 case ImpossibleMove:
4735 case (ChessMove) 0: /* end of file */
4744 if (appData.debugMode) {
4745 fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
4748 *fromX = *fromY = *toX = *toY = 0;
4749 *promoChar = NULLCHAR;
4757 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
4758 int fromX, fromY, toX, toY; char promoChar;
4763 endPV = forwardMostMove;
4765 while(*pv == ' ') pv++;
4766 if(*pv == '(') pv++; // first (ponder) move can be in parentheses
4767 valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
4768 if(appData.debugMode){
4769 fprintf(debugFP,"parsePV: %d %c%c%c%c '%s'\n", valid, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, pv);
4771 if(!valid && nr == 0 &&
4772 ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
4773 nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
4775 while(*pv && *pv++ != ' '); // skip what we parsed; assume space separators
4776 if(moveType == Comment) { valid++; continue; } // allow comments in PV
4778 if(endPV+1 > framePtr) break; // no space, truncate
4781 CopyBoard(boards[endPV], boards[endPV-1]);
4782 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
4783 moveList[endPV-1][0] = fromX + AAA;
4784 moveList[endPV-1][1] = fromY + ONE;
4785 moveList[endPV-1][2] = toX + AAA;
4786 moveList[endPV-1][3] = toY + ONE;
4787 parseList[endPV-1][0] = NULLCHAR;
4789 currentMove = endPV;
4790 if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
4791 SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
4792 moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
4793 DrawPosition(TRUE, boards[currentMove]);
4796 static int lastX, lastY;
4799 LoadMultiPV(int x, int y, char *buf, int index, int *start, int *end)
4803 if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
4804 lastX = x; lastY = y;
4805 while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
4807 while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
4809 while(buf[index] && buf[index] != '\n') index++;
4811 ParsePV(buf+startPV);
4812 *start = startPV; *end = index-1;
4817 LoadPV(int x, int y)
4818 { // called on right mouse click to load PV
4819 int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
4820 lastX = x; lastY = y;
4821 ParsePV(lastPV[which]); // load the PV of the thinking engine in the boards array.
4828 if(endPV < 0) return;
4830 currentMove = forwardMostMove;
4831 ClearPremoveHighlights();
4832 DrawPosition(TRUE, boards[currentMove]);
4836 MovePV(int x, int y, int h)
4837 { // step through PV based on mouse coordinates (called on mouse move)
4838 int margin = h>>3, step = 0;
4840 if(endPV < 0) return;
4841 // we must somehow check if right button is still down (might be released off board!)
4842 if(y < margin && (abs(x - lastX) > 6 || abs(y - lastY) > 6)) step = 1; else
4843 if(y > h - margin && (abs(x - lastX) > 6 || abs(y - lastY) > 6)) step = -1; else
4844 if( y > lastY + 6 ) step = -1; else if(y < lastY - 6) step = 1;
4846 lastX = x; lastY = y;
4847 if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
4848 currentMove += step;
4849 if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
4850 SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
4851 moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
4852 DrawPosition(FALSE, boards[currentMove]);
4856 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
4857 // All positions will have equal probability, but the current method will not provide a unique
4858 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
4864 int piecesLeft[(int)BlackPawn];
4865 int seed, nrOfShuffles;
4867 void GetPositionNumber()
4868 { // sets global variable seed
4871 seed = appData.defaultFrcPosition;
4872 if(seed < 0) { // randomize based on time for negative FRC position numbers
4873 for(i=0; i<50; i++) seed += random();
4874 seed = random() ^ random() >> 8 ^ random() << 8;
4875 if(seed<0) seed = -seed;
4879 int put(Board board, int pieceType, int rank, int n, int shade)
4880 // put the piece on the (n-1)-th empty squares of the given shade
4884 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
4885 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
4886 board[rank][i] = (ChessSquare) pieceType;
4887 squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
4889 piecesLeft[pieceType]--;
4897 void AddOnePiece(Board board, int pieceType, int rank, int shade)
4898 // calculate where the next piece goes, (any empty square), and put it there
4902 i = seed % squaresLeft[shade];
4903 nrOfShuffles *= squaresLeft[shade];
4904 seed /= squaresLeft[shade];
4905 put(board, pieceType, rank, i, shade);
4908 void AddTwoPieces(Board board, int pieceType, int rank)
4909 // calculate where the next 2 identical pieces go, (any empty square), and put it there
4911 int i, n=squaresLeft[ANY], j=n-1, k;
4913 k = n*(n-1)/2; // nr of possibilities, not counting permutations
4914 i = seed % k; // pick one
4917 while(i >= j) i -= j--;
4918 j = n - 1 - j; i += j;
4919 put(board, pieceType, rank, j, ANY);
4920 put(board, pieceType, rank, i, ANY);
4923 void SetUpShuffle(Board board, int number)
4927 GetPositionNumber(); nrOfShuffles = 1;
4929 squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
4930 squaresLeft[ANY] = BOARD_RGHT - BOARD_LEFT;
4931 squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
4933 for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
4935 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
4936 p = (int) board[0][i];
4937 if(p < (int) BlackPawn) piecesLeft[p] ++;
4938 board[0][i] = EmptySquare;
4941 if(PosFlags(0) & F_ALL_CASTLE_OK) {
4942 // shuffles restricted to allow normal castling put KRR first
4943 if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
4944 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
4945 else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
4946 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
4947 if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
4948 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
4949 if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
4950 put(board, WhiteRook, 0, 0, ANY);
4951 // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
4954 if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
4955 // only for even boards make effort to put pairs of colorbound pieces on opposite colors
4956 for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
4957 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
4958 while(piecesLeft[p] >= 2) {
4959 AddOnePiece(board, p, 0, LITE);
4960 AddOnePiece(board, p, 0, DARK);
4962 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
4965 for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
4966 // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
4967 // but we leave King and Rooks for last, to possibly obey FRC restriction
4968 if(p == (int)WhiteRook) continue;
4969 while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
4970 if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY); // add the odd piece
4973 // now everything is placed, except perhaps King (Unicorn) and Rooks
4975 if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
4976 // Last King gets castling rights
4977 while(piecesLeft[(int)WhiteUnicorn]) {
4978 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
4979 initialRights[2] = initialRights[5] = board[CASTLING][2] = board[CASTLING][5] = i;
4982 while(piecesLeft[(int)WhiteKing]) {
4983 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
4984 initialRights[2] = initialRights[5] = board[CASTLING][2] = board[CASTLING][5] = i;
4989 while(piecesLeft[(int)WhiteKing]) AddOnePiece(board, WhiteKing, 0, ANY);
4990 while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
4993 // Only Rooks can be left; simply place them all
4994 while(piecesLeft[(int)WhiteRook]) {
4995 i = put(board, WhiteRook, 0, 0, ANY);
4996 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
4999 initialRights[1] = initialRights[4] = board[CASTLING][1] = board[CASTLING][4] = i;
5001 initialRights[0] = initialRights[3] = board[CASTLING][0] = board[CASTLING][3] = i;
5004 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
5005 board[BOARD_HEIGHT-1][i] = (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
5008 if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
5011 int SetCharTable( char *table, const char * map )
5012 /* [HGM] moved here from winboard.c because of its general usefulness */
5013 /* Basically a safe strcpy that uses the last character as King */
5015 int result = FALSE; int NrPieces;
5017 if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
5018 && NrPieces >= 12 && !(NrPieces&1)) {
5019 int i; /* [HGM] Accept even length from 12 to 34 */
5021 for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
5022 for( i=0; i<NrPieces/2-1; i++ ) {
5024 table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
5026 table[(int) WhiteKing] = map[NrPieces/2-1];
5027 table[(int) BlackKing] = map[NrPieces-1];
5035 void Prelude(Board board)
5036 { // [HGM] superchess: random selection of exo-pieces
5037 int i, j, k; ChessSquare p;
5038 static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
5040 GetPositionNumber(); // use FRC position number
5042 if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
5043 SetCharTable(pieceToChar, appData.pieceToCharTable);
5044 for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
5045 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
5048 j = seed%4; seed /= 4;
5049 p = board[0][BOARD_LEFT+j]; board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5050 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
5051 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
5052 j = seed%3 + (seed%3 >= j); seed /= 3;
5053 p = board[0][BOARD_LEFT+j]; board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5054 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
5055 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
5056 j = seed%3; seed /= 3;
5057 p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5058 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
5059 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
5060 j = seed%2 + (seed%2 >= j); seed /= 2;
5061 p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5062 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
5063 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
5064 j = seed%4; seed /= 4; put(board, exoPieces[3], 0, j, ANY);
5065 j = seed%3; seed /= 3; put(board, exoPieces[2], 0, j, ANY);
5066 j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
5067 put(board, exoPieces[0], 0, 0, ANY);
5068 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
5072 InitPosition(redraw)
5075 ChessSquare (* pieces)[BOARD_FILES];
5076 int i, j, pawnRow, overrule,
5077 oldx = gameInfo.boardWidth,
5078 oldy = gameInfo.boardHeight,
5079 oldh = gameInfo.holdingsWidth,
5080 oldv = gameInfo.variant;
5082 if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
5084 /* [AS] Initialize pv info list [HGM] and game status */
5086 for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
5087 pvInfoList[i].depth = 0;
5088 boards[i][EP_STATUS] = EP_NONE;
5089 for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
5092 initialRulePlies = 0; /* 50-move counter start */
5094 castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
5095 castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
5099 /* [HGM] logic here is completely changed. In stead of full positions */
5100 /* the initialized data only consist of the two backranks. The switch */
5101 /* selects which one we will use, which is than copied to the Board */
5102 /* initialPosition, which for the rest is initialized by Pawns and */
5103 /* empty squares. This initial position is then copied to boards[0], */
5104 /* possibly after shuffling, so that it remains available. */
5106 gameInfo.holdingsWidth = 0; /* default board sizes */
5107 gameInfo.boardWidth = 8;
5108 gameInfo.boardHeight = 8;
5109 gameInfo.holdingsSize = 0;
5110 nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
5111 for(i=0; i<BOARD_FILES-2; i++)
5112 initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
5113 initialPosition[EP_STATUS] = EP_NONE;
5114 SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
5116 switch (gameInfo.variant) {
5117 case VariantFischeRandom:
5118 shuffleOpenings = TRUE;
5122 case VariantShatranj:
5123 pieces = ShatranjArray;
5124 nrCastlingRights = 0;
5125 SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
5128 pieces = makrukArray;
5129 nrCastlingRights = 0;
5130 startedFromSetupPosition = TRUE;
5131 SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
5133 case VariantTwoKings:
5134 pieces = twoKingsArray;
5136 case VariantCapaRandom:
5137 shuffleOpenings = TRUE;
5138 case VariantCapablanca:
5139 pieces = CapablancaArray;
5140 gameInfo.boardWidth = 10;
5141 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5144 pieces = GothicArray;
5145 gameInfo.boardWidth = 10;
5146 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5149 pieces = JanusArray;
5150 gameInfo.boardWidth = 10;
5151 SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
5152 nrCastlingRights = 6;
5153 initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5154 initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5155 initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
5156 initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5157 initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5158 initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
5161 pieces = FalconArray;
5162 gameInfo.boardWidth = 10;
5163 SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
5165 case VariantXiangqi:
5166 pieces = XiangqiArray;
5167 gameInfo.boardWidth = 9;
5168 gameInfo.boardHeight = 10;
5169 nrCastlingRights = 0;
5170 SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
5173 pieces = ShogiArray;
5174 gameInfo.boardWidth = 9;
5175 gameInfo.boardHeight = 9;
5176 gameInfo.holdingsSize = 7;
5177 nrCastlingRights = 0;
5178 SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
5180 case VariantCourier:
5181 pieces = CourierArray;
5182 gameInfo.boardWidth = 12;
5183 nrCastlingRights = 0;
5184 SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
5186 case VariantKnightmate:
5187 pieces = KnightmateArray;
5188 SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
5191 pieces = fairyArray;
5192 SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
5195 pieces = GreatArray;
5196 gameInfo.boardWidth = 10;
5197 SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
5198 gameInfo.holdingsSize = 8;
5202 SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
5203 gameInfo.holdingsSize = 8;
5204 startedFromSetupPosition = TRUE;
5206 case VariantCrazyhouse:
5207 case VariantBughouse:
5209 SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
5210 gameInfo.holdingsSize = 5;
5212 case VariantWildCastle:
5214 /* !!?shuffle with kings guaranteed to be on d or e file */
5215 shuffleOpenings = 1;
5217 case VariantNoCastle:
5219 nrCastlingRights = 0;
5220 /* !!?unconstrained back-rank shuffle */
5221 shuffleOpenings = 1;
5226 if(appData.NrFiles >= 0) {
5227 if(gameInfo.boardWidth != appData.NrFiles) overrule++;
5228 gameInfo.boardWidth = appData.NrFiles;
5230 if(appData.NrRanks >= 0) {
5231 gameInfo.boardHeight = appData.NrRanks;
5233 if(appData.holdingsSize >= 0) {
5234 i = appData.holdingsSize;
5235 if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
5236 gameInfo.holdingsSize = i;
5238 if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
5239 if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
5240 DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
5242 pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
5243 if(pawnRow < 1) pawnRow = 1;
5244 if(gameInfo.variant == VariantMakruk) pawnRow = 2;
5246 /* User pieceToChar list overrules defaults */
5247 if(appData.pieceToCharTable != NULL)
5248 SetCharTable(pieceToChar, appData.pieceToCharTable);
5250 for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
5252 if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
5253 s = (ChessSquare) 0; /* account holding counts in guard band */
5254 for( i=0; i<BOARD_HEIGHT; i++ )
5255 initialPosition[i][j] = s;
5257 if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
5258 initialPosition[0][j] = pieces[0][j-gameInfo.holdingsWidth];
5259 initialPosition[pawnRow][j] = WhitePawn;
5260 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = BlackPawn;
5261 if(gameInfo.variant == VariantXiangqi) {
5263 initialPosition[pawnRow][j] =
5264 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
5265 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
5266 initialPosition[2][j] = WhiteCannon;
5267 initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
5271 initialPosition[BOARD_HEIGHT-1][j] = pieces[1][j-gameInfo.holdingsWidth];
5273 if( (gameInfo.variant == VariantShogi) && !overrule ) {
5276 initialPosition[1][j] = WhiteBishop;
5277 initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
5279 initialPosition[1][j] = WhiteRook;
5280 initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
5283 if( nrCastlingRights == -1) {
5284 /* [HGM] Build normal castling rights (must be done after board sizing!) */
5285 /* This sets default castling rights from none to normal corners */
5286 /* Variants with other castling rights must set them themselves above */
5287 nrCastlingRights = 6;
5288 initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5289 initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5290 initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
5291 initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5292 initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5293 initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
5296 if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
5297 if(gameInfo.variant == VariantGreat) { // promotion commoners
5298 initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
5299 initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
5300 initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
5301 initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
5303 if (appData.debugMode) {
5304 fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
5306 if(shuffleOpenings) {
5307 SetUpShuffle(initialPosition, appData.defaultFrcPosition);
5308 startedFromSetupPosition = TRUE;
5310 if(startedFromPositionFile) {
5311 /* [HGM] loadPos: use PositionFile for every new game */
5312 CopyBoard(initialPosition, filePosition);
5313 for(i=0; i<nrCastlingRights; i++)
5314 initialRights[i] = filePosition[CASTLING][i];
5315 startedFromSetupPosition = TRUE;
5318 CopyBoard(boards[0], initialPosition);
5319 if(oldx != gameInfo.boardWidth ||
5320 oldy != gameInfo.boardHeight ||
5321 oldh != gameInfo.holdingsWidth
5323 || oldv == VariantGothic || // For licensing popups
5324 gameInfo.variant == VariantGothic
5327 || oldv == VariantFalcon ||
5328 gameInfo.variant == VariantFalcon
5332 InitDrawingSizes(-2 ,0);
5336 DrawPosition(TRUE, boards[currentMove]);
5341 SendBoard(cps, moveNum)
5342 ChessProgramState *cps;
5345 char message[MSG_SIZ];
5347 if (cps->useSetboard) {
5348 char* fen = PositionToFEN(moveNum, cps->fenOverride);
5349 sprintf(message, "setboard %s\n", fen);
5350 SendToProgram(message, cps);
5356 /* Kludge to set black to move, avoiding the troublesome and now
5357 * deprecated "black" command.
5359 if (!WhiteOnMove(moveNum)) SendToProgram("a2a3\n", cps);
5361 SendToProgram("edit\n", cps);
5362 SendToProgram("#\n", cps);
5363 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
5364 bp = &boards[moveNum][i][BOARD_LEFT];
5365 for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
5366 if ((int) *bp < (int) BlackPawn) {
5367 sprintf(message, "%c%c%c\n", PieceToChar(*bp),
5369 if(message[0] == '+' || message[0] == '~') {
5370 sprintf(message, "%c%c%c+\n",
5371 PieceToChar((ChessSquare)(DEMOTED *bp)),
5374 if(cps->alphaRank) { /* [HGM] shogi: translate coords */
5375 message[1] = BOARD_RGHT - 1 - j + '1';
5376 message[2] = BOARD_HEIGHT - 1 - i + 'a';
5378 SendToProgram(message, cps);
5383 SendToProgram("c\n", cps);
5384 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
5385 bp = &boards[moveNum][i][BOARD_LEFT];
5386 for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
5387 if (((int) *bp != (int) EmptySquare)
5388 && ((int) *bp >= (int) BlackPawn)) {
5389 sprintf(message, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
5391 if(message[0] == '+' || message[0] == '~') {
5392 sprintf(message, "%c%c%c+\n",
5393 PieceToChar((ChessSquare)(DEMOTED *bp)),
5396 if(cps->alphaRank) { /* [HGM] shogi: translate coords */
5397 message[1] = BOARD_RGHT - 1 - j + '1';
5398 message[2] = BOARD_HEIGHT - 1 - i + 'a';
5400 SendToProgram(message, cps);
5405 SendToProgram(".\n", cps);
5407 setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
5411 HasPromotionChoice(int fromX, int fromY, int toX, int toY, char *promoChoice)
5413 /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
5414 /* [HGM] add Shogi promotions */
5415 int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
5420 if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
5421 if(toX < BOARD_LEFT || toX >= BOARD_RGHT) return FALSE; // move into holdings
5423 if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
5424 !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
5427 piece = boards[currentMove][fromY][fromX];
5428 if(gameInfo.variant == VariantShogi) {
5429 promotionZoneSize = 3;
5430 highestPromotingPiece = (int)WhiteFerz;
5431 } else if(gameInfo.variant == VariantMakruk) {
5432 promotionZoneSize = 3;
5435 // next weed out all moves that do not touch the promotion zone at all
5436 if((int)piece >= BlackPawn) {
5437 if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
5439 highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
5441 if( toY < BOARD_HEIGHT - promotionZoneSize &&
5442 fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
5445 if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
5447 // weed out mandatory Shogi promotions
5448 if(gameInfo.variant == VariantShogi) {
5449 if(piece >= BlackPawn) {
5450 if(toY == 0 && piece == BlackPawn ||
5451 toY == 0 && piece == BlackQueen ||
5452 toY <= 1 && piece == BlackKnight) {
5457 if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
5458 toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
5459 toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
5466 // weed out obviously illegal Pawn moves
5467 if(appData.testLegality && (piece == WhitePawn || piece == BlackPawn) ) {
5468 if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
5469 if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
5470 if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
5471 if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
5472 // note we are not allowed to test for valid (non-)capture, due to premove
5475 // we either have a choice what to promote to, or (in Shogi) whether to promote
5476 if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk) {
5477 *promoChoice = PieceToChar(BlackFerz); // no choice
5480 if(appData.alwaysPromoteToQueen) { // predetermined
5481 if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantLosers)
5482 *promoChoice = PieceToChar(BlackKing); // in Suicide Q is the last thing we want
5483 else *promoChoice = PieceToChar(BlackQueen);
5487 // suppress promotion popup on illegal moves that are not premoves
5488 premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
5489 gameMode == IcsPlayingBlack && WhiteOnMove(currentMove);
5490 if(appData.testLegality && !premove) {
5491 moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5492 fromY, fromX, toY, toX, NULLCHAR);
5493 if(moveType != WhitePromotionQueen && moveType != BlackPromotionQueen &&
5494 moveType != WhitePromotionKnight && moveType != BlackPromotionKnight)
5502 InPalace(row, column)
5504 { /* [HGM] for Xiangqi */
5505 if( (row < 3 || row > BOARD_HEIGHT-4) &&
5506 column < (BOARD_WIDTH + 4)/2 &&
5507 column > (BOARD_WIDTH - 5)/2 ) return TRUE;
5512 PieceForSquare (x, y)
5516 if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
5519 return boards[currentMove][y][x];
5523 OKToStartUserMove(x, y)
5526 ChessSquare from_piece;
5529 if (matchMode) return FALSE;
5530 if (gameMode == EditPosition) return TRUE;
5532 if (x >= 0 && y >= 0)
5533 from_piece = boards[currentMove][y][x];
5535 from_piece = EmptySquare;
5537 if (from_piece == EmptySquare) return FALSE;
5539 white_piece = (int)from_piece >= (int)WhitePawn &&
5540 (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
5543 case PlayFromGameFile:
5545 case TwoMachinesPlay:
5553 case MachinePlaysWhite:
5554 case IcsPlayingBlack:
5555 if (appData.zippyPlay) return FALSE;
5557 DisplayMoveError(_("You are playing Black"));
5562 case MachinePlaysBlack:
5563 case IcsPlayingWhite:
5564 if (appData.zippyPlay) return FALSE;
5566 DisplayMoveError(_("You are playing White"));
5572 if (!white_piece && WhiteOnMove(currentMove)) {
5573 DisplayMoveError(_("It is White's turn"));
5576 if (white_piece && !WhiteOnMove(currentMove)) {
5577 DisplayMoveError(_("It is Black's turn"));
5580 if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
5581 /* Editing correspondence game history */
5582 /* Could disallow this or prompt for confirmation */
5587 case BeginningOfGame:
5588 if (appData.icsActive) return FALSE;
5589 if (!appData.noChessProgram) {
5591 DisplayMoveError(_("You are playing White"));
5598 if (!white_piece && WhiteOnMove(currentMove)) {
5599 DisplayMoveError(_("It is White's turn"));
5602 if (white_piece && !WhiteOnMove(currentMove)) {
5603 DisplayMoveError(_("It is Black's turn"));
5612 if (currentMove != forwardMostMove && gameMode != AnalyzeMode
5613 && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
5614 && gameMode != AnalyzeFile && gameMode != Training) {
5615 DisplayMoveError(_("Displayed position is not current"));
5622 OnlyMove(int *x, int *y) {
5623 DisambiguateClosure cl;
5624 if (appData.zippyPlay) return FALSE;
5626 case MachinePlaysBlack:
5627 case IcsPlayingWhite:
5628 case BeginningOfGame:
5629 if(!WhiteOnMove(currentMove)) return FALSE;
5631 case MachinePlaysWhite:
5632 case IcsPlayingBlack:
5633 if(WhiteOnMove(currentMove)) return FALSE;
5638 cl.pieceIn = EmptySquare;
5643 cl.promoCharIn = NULLCHAR;
5644 Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
5645 if(cl.kind == NormalMove) {
5652 if(cl.kind != ImpossibleMove) return FALSE;
5653 cl.pieceIn = EmptySquare;
5658 cl.promoCharIn = NULLCHAR;
5659 Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
5660 if(cl.kind == NormalMove) {
5670 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
5671 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
5672 int lastLoadGameUseList = FALSE;
5673 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
5674 ChessMove lastLoadGameStart = (ChessMove) 0;
5677 UserMoveTest(fromX, fromY, toX, toY, promoChar, captureOwn)
5678 int fromX, fromY, toX, toY;
5683 ChessSquare pdown, pup;
5685 /* Check if the user is playing in turn. This is complicated because we
5686 let the user "pick up" a piece before it is his turn. So the piece he
5687 tried to pick up may have been captured by the time he puts it down!
5688 Therefore we use the color the user is supposed to be playing in this
5689 test, not the color of the piece that is currently on the starting
5690 square---except in EditGame mode, where the user is playing both
5691 sides; fortunately there the capture race can't happen. (It can
5692 now happen in IcsExamining mode, but that's just too bad. The user
5693 will get a somewhat confusing message in that case.)
5697 case PlayFromGameFile:
5699 case TwoMachinesPlay:
5703 /* We switched into a game mode where moves are not accepted,
5704 perhaps while the mouse button was down. */
5705 return ImpossibleMove;
5707 case MachinePlaysWhite:
5708 /* User is moving for Black */
5709 if (WhiteOnMove(currentMove)) {
5710 DisplayMoveError(_("It is White's turn"));
5711 return ImpossibleMove;
5715 case MachinePlaysBlack:
5716 /* User is moving for White */
5717 if (!WhiteOnMove(currentMove)) {
5718 DisplayMoveError(_("It is Black's turn"));
5719 return ImpossibleMove;
5725 case BeginningOfGame:
5728 if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
5729 (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
5730 /* User is moving for Black */
5731 if (WhiteOnMove(currentMove)) {
5732 DisplayMoveError(_("It is White's turn"));
5733 return ImpossibleMove;
5736 /* User is moving for White */
5737 if (!WhiteOnMove(currentMove)) {
5738 DisplayMoveError(_("It is Black's turn"));
5739 return ImpossibleMove;
5744 case IcsPlayingBlack:
5745 /* User is moving for Black */
5746 if (WhiteOnMove(currentMove)) {
5747 if (!appData.premove) {
5748 DisplayMoveError(_("It is White's turn"));
5749 } else if (toX >= 0 && toY >= 0) {
5752 premoveFromX = fromX;
5753 premoveFromY = fromY;
5754 premovePromoChar = promoChar;
5756 if (appData.debugMode)
5757 fprintf(debugFP, "Got premove: fromX %d,"
5758 "fromY %d, toX %d, toY %d\n",
5759 fromX, fromY, toX, toY);
5761 return ImpossibleMove;
5765 case IcsPlayingWhite:
5766 /* User is moving for White */
5767 if (!WhiteOnMove(currentMove)) {
5768 if (!appData.premove) {
5769 DisplayMoveError(_("It is Black's turn"));
5770 } else if (toX >= 0 && toY >= 0) {
5773 premoveFromX = fromX;
5774 premoveFromY = fromY;
5775 premovePromoChar = promoChar;
5777 if (appData.debugMode)
5778 fprintf(debugFP, "Got premove: fromX %d,"
5779 "fromY %d, toX %d, toY %d\n",
5780 fromX, fromY, toX, toY);
5782 return ImpossibleMove;
5790 /* EditPosition, empty square, or different color piece;
5791 click-click move is possible */
5792 if (toX == -2 || toY == -2) {
5793 boards[0][fromY][fromX] = EmptySquare;
5794 return AmbiguousMove;
5795 } else if (toX >= 0 && toY >= 0) {
5796 boards[0][toY][toX] = boards[0][fromY][fromX];
5797 if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
5798 if(boards[0][fromY][0] != EmptySquare) {
5799 if(boards[0][fromY][1]) boards[0][fromY][1]--;
5800 if(boards[0][fromY][1] == 0) boards[0][fromY][0] = EmptySquare;
5803 if(fromX == BOARD_RGHT+1) {
5804 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
5805 if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
5806 if(boards[0][fromY][BOARD_WIDTH-2] == 0) boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
5809 boards[0][fromY][fromX] = EmptySquare;
5810 return AmbiguousMove;
5812 return ImpossibleMove;
5815 if(toX < 0 || toY < 0) return ImpossibleMove;
5816 pdown = boards[currentMove][fromY][fromX];
5817 pup = boards[currentMove][toY][toX];
5819 /* [HGM] If move started in holdings, it means a drop */
5820 if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) {
5821 if( pup != EmptySquare ) return ImpossibleMove;
5822 if(appData.testLegality) {
5823 /* it would be more logical if LegalityTest() also figured out
5824 * which drops are legal. For now we forbid pawns on back rank.
5825 * Shogi is on its own here...
5827 if( (pdown == WhitePawn || pdown == BlackPawn) &&
5828 (toY == 0 || toY == BOARD_HEIGHT -1 ) )
5829 return(ImpossibleMove); /* no pawn drops on 1st/8th */
5831 return WhiteDrop; /* Not needed to specify white or black yet */
5835 /* [HGM] always test for legality, to get promotion info */
5836 moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5837 fromY, fromX, toY, toX, promoChar);
5838 /* [HGM] but possibly ignore an IllegalMove result */
5839 if (appData.testLegality) {
5840 if (moveType == IllegalMove || moveType == ImpossibleMove) {
5841 DisplayMoveError(_("Illegal move"));
5842 return ImpossibleMove;
5847 /* [HGM] <popupFix> in stead of calling FinishMove directly, this
5848 function is made into one that returns an OK move type if FinishMove
5849 should be called. This to give the calling driver routine the
5850 opportunity to finish the userMove input with a promotion popup,
5851 without bothering the user with this for invalid or illegal moves */
5853 /* FinishMove(moveType, fromX, fromY, toX, toY, promoChar); */
5856 /* Common tail of UserMoveEvent and DropMenuEvent */
5858 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
5860 int fromX, fromY, toX, toY;
5861 /*char*/int promoChar;
5865 if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) && promoChar != NULLCHAR)
5867 // [HGM] superchess: suppress promotions to non-available piece
5868 int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
5869 if(WhiteOnMove(currentMove))
5871 if(!boards[currentMove][k][BOARD_WIDTH-2])
5876 if(!boards[currentMove][BOARD_HEIGHT-1-k][1])
5881 /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
5882 move type in caller when we know the move is a legal promotion */
5883 if(moveType == NormalMove && promoChar)
5884 moveType = PromoCharToMoveType(WhiteOnMove(currentMove), promoChar);
5886 /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
5887 move type in caller when we know the move is a legal promotion */
5888 if(moveType == NormalMove && promoChar)
5889 moveType = PromoCharToMoveType(WhiteOnMove(currentMove), promoChar);
5891 /* [HGM] convert drag-and-drop piece drops to standard form */
5892 if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK )
5894 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
5895 if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n",
5896 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
5897 // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
5898 if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
5899 fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
5900 while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++;
5904 /* [HGM] <popupFix> The following if has been moved here from
5905 UserMoveEvent(). Because it seemed to belong here (why not allow
5906 piece drops in training games?), and because it can only be
5907 performed after it is known to what we promote. */
5908 if (gameMode == Training)
5910 /* compare the move played on the board to the next move in the
5911 * game. If they match, display the move and the opponent's response.
5912 * If they don't match, display an error message.
5916 CopyBoard(testBoard, boards[currentMove]);
5917 ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
5919 if (CompareBoards(testBoard, boards[currentMove+1]))
5921 ForwardInner(currentMove+1);
5923 /* Autoplay the opponent's response.
5924 * if appData.animate was TRUE when Training mode was entered,
5925 * the response will be animated.
5927 saveAnimate = appData.animate;
5928 appData.animate = animateTraining;
5929 ForwardInner(currentMove+1);
5930 appData.animate = saveAnimate;
5932 /* check for the end of the game */
5933 if (currentMove >= forwardMostMove)
5935 gameMode = PlayFromGameFile;
5937 SetTrainingModeOff();
5938 DisplayInformation(_("End of game"));
5943 DisplayError(_("Incorrect move"), 0);
5948 /* Ok, now we know that the move is good, so we can kill
5949 the previous line in Analysis Mode */
5950 if ((gameMode == AnalyzeMode || gameMode == EditGame)
5951 && currentMove < forwardMostMove) {
5952 PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
5955 /* If we need the chess program but it's dead, restart it */
5956 ResurrectChessProgram();
5958 /* A user move restarts a paused game*/
5962 thinkOutput[0] = NULLCHAR;
5964 MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
5967 if(Adjudicate(NULL)) return 1; // [HGM] adjudicate: take care of automtic game end
5969 if (gameMode == BeginningOfGame)
5971 if (appData.noChessProgram)
5973 gameMode = EditGame;
5979 gameMode = MachinePlaysBlack;
5982 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
5986 sprintf(buf, "name %s\n", gameInfo.white);
5987 SendToProgram(buf, &first);
5995 /* Relay move to ICS or chess engine */
5997 if (appData.icsActive) {
5998 if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
5999 gameMode == IcsExamining) {
6000 if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
6001 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
6003 SendMoveToICS(moveType, fromX, fromY, toX, toY);
6005 // also send plain move, in case ICS does not understand atomic claims
6006 SendMoveToICS(moveType, fromX, fromY, toX, toY);
6010 if (first.sendTime && (gameMode == BeginningOfGame ||
6011 gameMode == MachinePlaysWhite ||
6012 gameMode == MachinePlaysBlack)) {
6013 SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
6015 if (gameMode != EditGame && gameMode != PlayFromGameFile) {
6016 // [HGM] book: if program might be playing, let it use book
6017 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
6018 first.maybeThinking = TRUE;
6019 } else SendMoveToProgram(forwardMostMove-1, &first);
6020 if (currentMove == cmailOldMove + 1) {
6021 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
6025 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6030 switch (MateTest(boards[currentMove], PosFlags(currentMove)) )
6037 if (WhiteOnMove(currentMove)) {
6038 GameEnds(BlackWins, "Black mates", GE_PLAYER);
6040 GameEnds(WhiteWins, "White mates", GE_PLAYER);
6044 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
6049 case MachinePlaysBlack:
6050 case MachinePlaysWhite:
6051 /* disable certain menu options while machine is thinking */
6052 SetMachineThinkingEnables();
6058 userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
6061 { // [HGM] book: simulate book reply
6062 static char bookMove[MSG_SIZ]; // a bit generous?
6065 programStats.nodes = programStats.depth = programStats.time =
6066 programStats.score = programStats.got_only_move = 0;
6067 sprintf(programStats.movelist, "%s (xbook)", bookHit);
6069 strcpy(bookMove, "move ");
6070 strcat(bookMove, bookHit);
6071 HandleMachineMove(bookMove, &first);
6078 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
6079 int fromX, fromY, toX, toY;
6082 /* [HGM] This routine was added to allow calling of its two logical
6083 parts from other modules in the old way. Before, UserMoveEvent()
6084 automatically called FinishMove() if the move was OK, and returned
6085 otherwise. I separated the two, in order to make it possible to
6086 slip a promotion popup in between. But that it always needs two
6087 calls, to the first part, (now called UserMoveTest() ), and to
6088 FinishMove if the first part succeeded. Calls that do not need
6089 to do anything in between, can call this routine the old way.
6091 ChessMove moveType = UserMoveTest(fromX, fromY, toX, toY, promoChar, FALSE);
6092 if(appData.debugMode) fprintf(debugFP, "moveType 4 = %d, promochar = %x\n", moveType, promoChar);
6093 if(moveType == AmbiguousMove)
6094 DrawPosition(FALSE, boards[currentMove]);
6095 else if(moveType != ImpossibleMove && moveType != Comment)
6096 FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
6100 PromoDialog(int h, int w, Board board, Boolean clearBoard, char *title, int x, int y)
6101 { // dummy routine to mimic with pseudo-popup what front-end should do:
6102 // display a popup with h x w mini-board, and divert any mouse clicks
6103 // on it to the back-end routines RightClick and LeftClick, just
6104 // like the mouse event hadler of the board widget does now.
6105 // (Note it would have to off-set x if holdings are displayed!)
6106 DisplayMessage("Click on your piece of choice", "");
6107 DrawPosition(TRUE, board);
6110 int hTab[(int)EmptySquare/2+1] = { 1,1,1,1,1,1,2,1,2,3,2,3,3,3,2,3,4,3,3,4,4,3,4 };
6111 int wTab[(int)EmptySquare/2+1] = { 1,1,2,3,4,5,3,7,4,3,5,4,4,5,7,5,4,6,6,5,5,7,6 };
6113 int promotionChoice = 0;
6116 PiecePopUp(int x, int y)
6118 int i, j, h, w, nWhite=0, nBlack=0;
6119 ChessSquare list[EmptySquare];
6120 for(i=0; i<EmptySquare/2; i++) {
6121 if(PieceToChar(i) != '.') list[nWhite++] = i;
6122 if(PieceToChar(i+EmptySquare/2) != '.') list[EmptySquare - ++nBlack] = i + EmptySquare/2;
6124 CopyBoard(promoBoard, boards[currentMove]);
6125 for(i=0; i<BOARD_HEIGHT; i++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++) promoBoard[i][j] = EmptySquare;
6126 j = nWhite + nBlack + 1;
6127 h = sqrt((j+1)/2 + 1.); w = (j+h-1)/h;
6128 if(w>BOARD_RGHT-BOARD_LEFT) { w = BOARD_RGHT - BOARD_LEFT; h = (j+w-1)/w; }
6129 for(i=0; i<nWhite; i++) promoBoard[i/w][BOARD_LEFT+i%w] = list[nWhite-1-i];
6130 if(h==2 && nWhite == nBlack)
6131 for(i=0; i<nWhite; i++) promoBoard[1][BOARD_LEFT+i%w] = list[EmptySquare-nBlack+i];
6133 for(i=0; i<nBlack; i++) promoBoard[h-1-i/w][BOARD_LEFT+w-1-i%w] = list[EmptySquare-nBlack+i];
6134 promotionChoice = 3;
6136 PromoDialog(h, w, promoBoard, TRUE, _("Select piece:"), x, y);
6140 PromoPopUp(ChessSquare piece)
6141 { // determine the layout of the piece-choice dialog
6143 ChessSquare list[EmptySquare];
6145 for(i=0; i<BOARD_HEIGHT; i++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++) promoBoard[i][j] = EmptySquare;
6146 if(gameInfo.variant == VariantShogi) {
6147 // non-Pawn promotes; must be shogi
6148 h = 1; w = 1; promoBoard[0][BOARD_LEFT+0] = piece;
6149 if(PieceToChar(PROMOTED piece) != '.') {
6150 // promoted version is enabled
6151 w = 2; promoBoard[0][BOARD_LEFT+1] = PROMOTED piece;
6154 // Pawn, promotes to any enabled other piece
6156 for(i=1; i<EmptySquare/2; i++) {
6157 if(PieceToChar(piece+i) != '.'
6158 && PieceToChar(piece + i) != '~' // suppress bughouse true pieces
6160 list[w++] = piece+i; nr++;
6163 if(appData.testLegality && gameInfo.variant != VariantSuicide
6164 && gameInfo.variant != VariantGiveaway) nr--,w--; // remove King
6165 h = hTab[nr]; w = wTab[nr]; // factorize with nice ratio
6166 for(i=0; i < nr; i++) promoBoard[i/w][BOARD_LEFT+i%w] = list[i]; // layout
6168 promotionChoice = 2; // wait for click on board
6169 PromoDialog(h, w, promoBoard, FALSE, _("Promote to:"), -1, -1);
6173 Mark(board, flags, kind, rf, ff, rt, ft, closure)
6180 typedef char Markers[BOARD_RANKS][BOARD_FILES];
6181 Markers *m = (Markers *) closure;
6182 if(rf == fromY && ff == fromX)
6183 (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
6184 || kind == WhiteCapturesEnPassant
6185 || kind == BlackCapturesEnPassant);
6186 else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
6190 MarkTargetSquares(int clear)
6193 if(!appData.markers || !appData.highlightDragging ||
6194 !appData.testLegality || gameMode == EditPosition) return;
6196 for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) marker[y][x] = 0;
6199 GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker);
6200 if(PosFlags(0) & F_MANDATORY_CAPTURE) {
6201 for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
6203 for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
6206 DrawPosition(TRUE, NULL);
6209 void LeftClick(ClickType clickType, int xPix, int yPix)
6212 Boolean saveAnimate;
6213 static int second = 0;
6214 char promoChoice = NULLCHAR;
6216 if(appData.seekGraph && appData.icsActive && loggedOn &&
6217 (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
6218 SeekGraphClick(clickType, xPix, yPix, 0);
6222 if (clickType == Press) ErrorPopDown();
6223 MarkTargetSquares(1);
6225 x = EventToSquare(xPix, BOARD_WIDTH);
6226 y = EventToSquare(yPix, BOARD_HEIGHT);
6227 if (!flipView && y >= 0) {
6228 y = BOARD_HEIGHT - 1 - y;
6230 if (flipView && x >= 0) {
6231 x = BOARD_WIDTH - 1 - x;
6234 if(promotionChoice) { // we are waiting for a click to indicate promotion piece
6235 ChessSquare p = EmptySquare; Boolean inHoldings;
6236 if(promotionChoice == 3) {
6237 if(clickType == Press) EditPositionMenuEvent(promoBoard[y][x], fromX, fromY);
6238 else if(clickType == Release) promotionChoice = 0;
6242 if(clickType == Release) return; // ignore upclick of click-click destination
6243 if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
6244 inHoldings = gameInfo.holdingsWidth &&
6245 (WhiteOnMove(currentMove)
6246 ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y > 0
6247 : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT-1);
6248 // click in right holdings, for determining promotion piece
6249 if(promotionChoice == 1 && inHoldings || promotionChoice == 2 && x >= BOARD_LEFT && x < BOARD_RGHT) {
6250 p = promoBoard[y][x];
6251 if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
6252 if(p != EmptySquare) {
6253 char promoChar = PieceToChar(p);
6254 if(gameInfo.variant == VariantShogi && promoChar != '+') promoChar = '=';
6255 FinishMove(NormalMove, fromX, fromY, toX, toY, ToLower(promoChar));
6257 promotionChoice = 0;
6261 promotionChoice = 0; // only one chance: if click not OK it is interpreted as cancel
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) )
6274 if(!appData.oneClick || !OnlyMove(&x, &y)) {
6275 if (clickType == Press) {
6277 if (OKToStartUserMove(x, y)) {
6281 MarkTargetSquares(0);
6282 DragPieceBegin(xPix, yPix);
6283 if (appData.highlightDragging) {
6284 SetHighlights(x, y, -1, -1);
6293 if (clickType == Press && gameMode != EditPosition) {
6298 // ignore off-board to clicks
6299 if(y < 0 || x < 0) return;
6301 /* Check if clicking again on the same color piece */
6302 fromP = boards[currentMove][fromY][fromX];
6303 toP = boards[currentMove][y][x];
6304 frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom;
6305 if ((WhitePawn <= fromP && fromP <= WhiteKing &&
6306 WhitePawn <= toP && toP <= WhiteKing &&
6307 !(fromP == WhiteKing && toP == WhiteRook && frc) &&
6308 !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
6309 (BlackPawn <= fromP && fromP <= BlackKing &&
6310 BlackPawn <= toP && toP <= BlackKing &&
6311 !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
6312 !(fromP == BlackKing && toP == BlackRook && frc))) {
6313 /* Clicked again on same color piece -- changed his mind */
6314 second = (x == fromX && y == fromY);
6315 if (appData.highlightDragging) {
6316 SetHighlights(x, y, -1, -1);
6320 if (OKToStartUserMove(x, y)) {
6323 MarkTargetSquares(0);
6324 DragPieceBegin(xPix, yPix);
6328 // ignore clicks on holdings
6329 if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
6332 if (clickType == Release && x == fromX && y == fromY) {
6333 DragPieceEnd(xPix, yPix);
6334 if (appData.animateDragging) {
6335 /* Undo animation damage if any */
6336 DrawPosition(FALSE, NULL);
6339 /* Second up/down in same square; just abort move */
6344 ClearPremoveHighlights();
6346 /* First upclick in same square; start click-click mode */
6347 SetHighlights(x, y, -1, -1);
6352 /* we now have a different from- and (possibly off-board) to-square */
6353 /* Completed move */
6356 saveAnimate = appData.animate;
6357 if (clickType == Press) {
6358 /* Finish clickclick move */
6359 if (appData.animate || appData.highlightLastMove) {
6360 SetHighlights(fromX, fromY, toX, toY);
6365 /* Finish drag move */
6366 if (appData.highlightLastMove) {
6367 SetHighlights(fromX, fromY, toX, toY);
6371 DragPieceEnd(xPix, yPix);
6372 /* Don't animate move and drag both */
6373 appData.animate = FALSE;
6376 // moves into holding are invalid for now (except in EditPosition, adapting to-square)
6377 if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
6378 ChessSquare piece = boards[currentMove][fromY][fromX];
6379 if(gameMode == EditPosition && piece != EmptySquare &&
6380 fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
6383 if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
6384 n = PieceToNumber(piece - (int)BlackPawn);
6385 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
6386 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
6387 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
6389 if(x == BOARD_RGHT+1 && piece < BlackPawn) {
6390 n = PieceToNumber(piece);
6391 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
6392 boards[currentMove][n][BOARD_WIDTH-1] = piece;
6393 boards[currentMove][n][BOARD_WIDTH-2]++;
6395 boards[currentMove][fromY][fromX] = EmptySquare;
6399 DrawPosition(TRUE, boards[currentMove]);
6403 // off-board moves should not be highlighted
6404 if(x < 0 || x < 0) ClearHighlights();
6406 if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice)) {
6407 SetHighlights(fromX, fromY, toX, toY);
6408 if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
6409 // [HGM] super: promotion to captured piece selected from holdings
6410 ChessSquare p = boards[currentMove][fromY][fromX];
6411 promotionChoice = 1;
6412 CopyBoard(promoBoard, boards[currentMove]);
6413 // kludge follows to temporarily execute move on display, without promoting yet
6414 promoBoard[fromY][fromX] = EmptySquare; // move Pawn to 8th rank
6415 promoBoard[toY][toX] = p;
6416 DrawPosition(FALSE, promoBoard);
6417 DisplayMessage("Click in holdings to choose piece", "");
6420 CopyBoard(promoBoard, boards[currentMove]);
6421 PromoPopUp(boards[currentMove][fromY][fromX]);
6423 UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
6424 if (!appData.highlightLastMove || gotPremove) ClearHighlights();
6425 if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
6428 appData.animate = saveAnimate;
6429 if (appData.animate || appData.animateDragging) {
6430 /* Undo animation damage if needed */
6431 DrawPosition(FALSE, NULL);
6435 int RightClick(ClickType action, int x, int y, int *xx, int *yy)
6436 { // front-end-free part taken out of PieceMenuPopup
6437 int whichMenu; int xSqr, ySqr;
6439 if(seekGraphUp) { // [HGM] seekgraph
6440 if(action == Press) SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
6441 if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
6445 xSqr = EventToSquare(x, BOARD_WIDTH);
6446 ySqr = EventToSquare(y, BOARD_HEIGHT);
6448 xSqr = BOARD_WIDTH - 1 - xSqr;
6450 ySqr = BOARD_HEIGHT - 1 - ySqr;
6451 if(promotionChoice == 3 && action == Release
6452 && promoBoard[ySqr][xSqr] != EmptySquare && (xSqr != fromX || ySqr != fromY) // not needed if separate window
6454 EditPositionMenuEvent(promoBoard[ySqr][xSqr], fromX, fromY);
6456 promotionChoice = 0;
6459 if (action == Release) UnLoadPV(); // [HGM] pv
6460 if (action != Press) return -2; // return code to be ignored
6463 if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;
6465 if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;
6466 if (xSqr < 0 || ySqr < 0) return -1;
6467 whichMenu = 0; // edit-position menu
6470 if(!appData.icsEngineAnalyze) return -1;
6471 case IcsPlayingWhite:
6472 case IcsPlayingBlack:
6473 if(!appData.zippyPlay) goto noZip;
6476 case MachinePlaysWhite:
6477 case MachinePlaysBlack:
6478 case TwoMachinesPlay: // [HGM] pv: use for showing PV
6479 if (!appData.dropMenu) {
6481 return 2; // flag front-end to grab mouse events
6483 if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
6484 gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
6487 if (xSqr < 0 || ySqr < 0) return -1;
6488 if (!appData.dropMenu || appData.testLegality &&
6489 gameInfo.variant != VariantBughouse &&
6490 gameInfo.variant != VariantCrazyhouse) return -1;
6491 whichMenu = 1; // drop menu
6497 if (((*xx = xSqr) < 0) ||
6498 ((*yy = ySqr) < 0)) {
6503 fromX = *xx; fromY = *yy;
6504 if(whichMenu == 0) { PiecePopUp(x, y); return -1; } // suppress EditPosition menu
6509 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
6511 // char * hint = lastHint;
6512 FrontEndProgramStats stats;
6514 stats.which = cps == &first ? 0 : 1;
6515 stats.depth = cpstats->depth;
6516 stats.nodes = cpstats->nodes;
6517 stats.score = cpstats->score;
6518 stats.time = cpstats->time;
6519 stats.pv = cpstats->movelist;
6520 stats.hint = lastHint;
6521 stats.an_move_index = 0;
6522 stats.an_move_count = 0;
6524 if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
6525 stats.hint = cpstats->move_name;
6526 stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
6527 stats.an_move_count = cpstats->nr_moves;
6530 if(stats.pv && stats.pv[0]) strcpy(lastPV[stats.which], stats.pv); // [HGM] pv: remember last PV of each
6532 SetProgramStats( &stats );
6536 Adjudicate(ChessProgramState *cps)
6537 { // [HGM] some adjudications useful with buggy engines
6538 // [HGM] adjudicate: made into separate routine, which now can be called after every move
6539 // In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
6540 // Actually ending the game is now based on the additional internal condition canAdjudicate.
6541 // Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
6542 int k, count = 0; static int bare = 1;
6543 ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
6544 Boolean canAdjudicate = !appData.icsActive;
6546 // most tests only when we understand the game, i.e. legality-checking on, and (for the time being) no piece drops
6547 if(gameInfo.holdingsSize == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
6548 if( appData.testLegality )
6549 { /* [HGM] Some more adjudications for obstinate engines */
6550 int NrWN=0, NrBN=0, NrWB=0, NrBB=0, NrWR=0, NrBR=0,
6551 NrWQ=0, NrBQ=0, NrW=0, NrK=0, bishopsColor = 0,
6552 NrPieces=0, NrPawns=0, PawnAdvance=0, i, j;
6553 static int moveCount = 6;
6555 char *reason = NULL;
6558 /* Count what is on board. */
6559 for(i=0; i<BOARD_HEIGHT; i++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
6560 { ChessSquare p = boards[forwardMostMove][i][j];
6564 { /* count B,N,R and other of each side */
6567 NrK++; break; // [HGM] atomic: count Kings
6571 case WhiteFerz: // [HGM] shatranj: kludge to mke it work in shatranj
6572 bishopsColor |= 1 << ((i^j)&1);
6577 case BlackFerz: // [HGM] shatranj: kludge to mke it work in shatranj
6578 bishopsColor |= 1 << ((i^j)&1);
6593 PawnAdvance += m; NrPawns++;
6595 NrPieces += (p != EmptySquare);
6596 NrW += ((int)p < (int)BlackPawn);
6597 if(gameInfo.variant == VariantXiangqi &&
6598 (p == WhiteFerz || p == WhiteAlfil || p == BlackFerz || p == BlackAlfil)) {
6599 NrPieces--; // [HGM] XQ: do not count purely defensive pieces
6600 NrW -= ((int)p < (int)BlackPawn);
6604 /* Some material-based adjudications that have to be made before stalemate test */
6605 if(gameInfo.variant == VariantAtomic && NrK < 2) {
6606 // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
6607 boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
6608 if(canAdjudicate && appData.checkMates) {
6610 SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
6611 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6612 GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
6613 "Xboard adjudication: King destroyed", GE_XBOARD );
6618 /* Bare King in Shatranj (loses) or Losers (wins) */
6619 if( NrW == 1 || NrPieces - NrW == 1) {
6620 if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
6621 boards[forwardMostMove][EP_STATUS] = EP_WINS; // mark as win, so it becomes claimable
6622 if(canAdjudicate && appData.checkMates) {
6624 SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
6625 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6626 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
6627 "Xboard adjudication: Bare king", GE_XBOARD );
6631 if( gameInfo.variant == VariantShatranj && --bare < 0)
6633 boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
6634 if(canAdjudicate && appData.checkMates) {
6635 /* but only adjudicate if adjudication enabled */
6637 SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
6638 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6639 GameEnds( NrW > 1 ? WhiteWins : NrPieces - NrW > 1 ? BlackWins : GameIsDrawn,
6640 "Xboard adjudication: Bare king", GE_XBOARD );
6647 // don't wait for engine to announce game end if we can judge ourselves
6648 switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
6650 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
6651 int i, checkCnt = 0; // (should really be done by making nr of checks part of game state)
6652 for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
6653 if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
6656 reason = "Xboard adjudication: 3rd check";
6657 boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
6667 reason = "Xboard adjudication: Stalemate";
6668 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
6669 boards[forwardMostMove][EP_STATUS] = EP_STALEMATE; // default result for stalemate is draw
6670 if(gameInfo.variant == VariantLosers || gameInfo.variant == VariantGiveaway) // [HGM] losers:
6671 boards[forwardMostMove][EP_STATUS] = EP_WINS; // in these variants stalemated is always a win
6672 else if(gameInfo.variant == VariantSuicide) // in suicide it depends
6673 boards[forwardMostMove][EP_STATUS] = NrW == NrPieces-NrW ? EP_STALEMATE :
6674 ((NrW < NrPieces-NrW) != WhiteOnMove(forwardMostMove) ?
6675 EP_CHECKMATE : EP_WINS);
6676 else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
6677 boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
6681 reason = "Xboard adjudication: Checkmate";
6682 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
6686 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
6688 result = GameIsDrawn; break;
6690 result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
6692 result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
6694 result = (ChessMove) 0;
6696 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
6698 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6699 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6700 GameEnds( result, reason, GE_XBOARD );
6704 /* Next absolutely insufficient mating material. */
6705 if( NrPieces == 2 || gameInfo.variant != VariantXiangqi &&
6706 gameInfo.variant != VariantShatranj && // [HGM] baring will remain possible
6707 (NrPieces == 3 && NrWN+NrBN+NrWB+NrBB == 1 ||
6708 NrPieces == NrBB+NrWB+2 && bishopsColor != 3)) // [HGM] all Bishops (Ferz!) same color
6709 { /* KBK, KNK, KK of KBKB with like Bishops */
6711 /* always flag draws, for judging claims */
6712 boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
6714 if(canAdjudicate && appData.materialDraws) {
6715 /* but only adjudicate them if adjudication enabled */
6716 if(engineOpponent) {
6717 SendToProgram("force\n", engineOpponent); // suppress reply
6718 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
6720 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6721 GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
6726 /* Then some trivial draws (only adjudicate, cannot be claimed) */
6728 ( NrWR == 1 && NrBR == 1 /* KRKR */
6729 || NrWQ==1 && NrBQ==1 /* KQKQ */
6730 || NrWN==2 || NrBN==2 /* KNNK */
6731 || NrWN+NrWB == 1 && NrBN+NrBB == 1 /* KBKN, KBKB, KNKN */
6733 if(canAdjudicate && --moveCount < 0 && appData.trivialDraws)
6734 { /* if the first 3 moves do not show a tactical win, declare draw */
6735 if(engineOpponent) {
6736 SendToProgram("force\n", engineOpponent); // suppress reply
6737 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6739 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6740 GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
6743 } else moveCount = 6;
6747 if (appData.debugMode) { int i;
6748 fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
6749 forwardMostMove, backwardMostMove, boards[backwardMostMove][EP_STATUS],
6750 appData.drawRepeats);
6751 for( i=forwardMostMove; i>=backwardMostMove; i-- )
6752 fprintf(debugFP, "%d ep=%d\n", i, (signed char)boards[i][EP_STATUS]);
6756 // Repetition draws and 50-move rule can be applied independently of legality testing
6758 /* Check for rep-draws */
6760 for(k = forwardMostMove-2;
6761 k>=backwardMostMove && k>=forwardMostMove-100 &&
6762 (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
6763 (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE;
6766 if(CompareBoards(boards[k], boards[forwardMostMove])) {
6767 /* compare castling rights */
6768 if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
6769 (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
6770 rights++; /* King lost rights, while rook still had them */
6771 if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
6772 if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
6773 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
6774 rights++; /* but at least one rook lost them */
6776 if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
6777 (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
6779 if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
6780 if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
6781 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
6784 if( canAdjudicate && rights == 0 && ++count > appData.drawRepeats-2
6785 && appData.drawRepeats > 1) {
6786 /* adjudicate after user-specified nr of repeats */
6787 if(engineOpponent) {
6788 SendToProgram("force\n", engineOpponent); // suppress reply
6789 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6791 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6792 if(gameInfo.variant == VariantXiangqi && appData.testLegality) {
6793 // [HGM] xiangqi: check for forbidden perpetuals
6794 int m, ourPerpetual = 1, hisPerpetual = 1;
6795 for(m=forwardMostMove; m>k; m-=2) {
6796 if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
6797 ourPerpetual = 0; // the current mover did not always check
6798 if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
6799 hisPerpetual = 0; // the opponent did not always check
6801 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
6802 ourPerpetual, hisPerpetual);
6803 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
6804 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
6805 "Xboard adjudication: perpetual checking", GE_XBOARD );
6808 if(hisPerpetual && !ourPerpetual) // he is checking us, but did not repeat yet
6809 break; // (or we would have caught him before). Abort repetition-checking loop.
6810 // Now check for perpetual chases
6811 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
6812 hisPerpetual = PerpetualChase(k, forwardMostMove);
6813 ourPerpetual = PerpetualChase(k+1, forwardMostMove);
6814 if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
6815 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
6816 "Xboard adjudication: perpetual chasing", GE_XBOARD );
6819 if(hisPerpetual && !ourPerpetual) // he is chasing us, but did not repeat yet
6820 break; // Abort repetition-checking loop.
6822 // if neither of us is checking or chasing all the time, or both are, it is draw
6824 GameEnds( GameIsDrawn, "Xboard adjudication: repetition draw", GE_XBOARD );
6827 if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
6828 boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
6832 /* Now we test for 50-move draws. Determine ply count */
6833 count = forwardMostMove;
6834 /* look for last irreversble move */
6835 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
6837 /* if we hit starting position, add initial plies */
6838 if( count == backwardMostMove )
6839 count -= initialRulePlies;
6840 count = forwardMostMove - count;
6842 boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
6843 /* this is used to judge if draw claims are legal */
6844 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
6845 if(engineOpponent) {
6846 SendToProgram("force\n", engineOpponent); // suppress reply
6847 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6849 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6850 GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
6854 /* if draw offer is pending, treat it as a draw claim
6855 * when draw condition present, to allow engines a way to
6856 * claim draws before making their move to avoid a race
6857 * condition occurring after their move
6859 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
6861 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
6862 p = "Draw claim: 50-move rule";
6863 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
6864 p = "Draw claim: 3-fold repetition";
6865 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
6866 p = "Draw claim: insufficient mating material";
6867 if( p != NULL && canAdjudicate) {
6868 if(engineOpponent) {
6869 SendToProgram("force\n", engineOpponent); // suppress reply
6870 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6872 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6873 GameEnds( GameIsDrawn, p, GE_XBOARD );
6878 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
6879 if(engineOpponent) {
6880 SendToProgram("force\n", engineOpponent); // suppress reply
6881 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6883 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6884 GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
6890 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
6891 { // [HGM] book: this routine intercepts moves to simulate book replies
6892 char *bookHit = NULL;
6894 //first determine if the incoming move brings opponent into his book
6895 if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
6896 bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
6897 if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
6898 if(bookHit != NULL && !cps->bookSuspend) {
6899 // make sure opponent is not going to reply after receiving move to book position
6900 SendToProgram("force\n", cps);
6901 cps->bookSuspend = TRUE; // flag indicating it has to be restarted
6903 if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
6904 // now arrange restart after book miss
6906 // after a book hit we never send 'go', and the code after the call to this routine
6907 // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
6909 if (cps->useUsermove) sprintf(buf, "usermove "); // sorry, no SAN yet :(
6910 sprintf(buf, "%s\n", bookHit); // force book move into program supposed to play it
6911 SendToProgram(buf, cps);
6912 if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
6913 } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
6914 SendToProgram("go\n", cps);
6915 cps->bookSuspend = FALSE; // after a 'go' we are never suspended
6916 } else { // 'go' might be sent based on 'firstMove' after this routine returns
6917 if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
6918 SendToProgram("go\n", cps);
6919 cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
6921 return bookHit; // notify caller of hit, so it can take action to send move to opponent
6925 ChessProgramState *savedState;
6926 void DeferredBookMove(void)
6928 if(savedState->lastPing != savedState->lastPong)
6929 ScheduleDelayedEvent(DeferredBookMove, 10);
6931 HandleMachineMove(savedMessage, savedState);
6935 HandleMachineMove(message, cps)
6937 ChessProgramState *cps;
6939 char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
6940 char realname[MSG_SIZ];
6941 int fromX, fromY, toX, toY;
6950 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
6952 * Kludge to ignore BEL characters
6954 while (*message == '\007') message++;
6957 * [HGM] engine debug message: ignore lines starting with '#' character
6959 if(cps->debug && *message == '#') return;
6962 * Look for book output
6964 if (cps == &first && bookRequested) {
6965 if (message[0] == '\t' || message[0] == ' ') {
6966 /* Part of the book output is here; append it */
6967 strcat(bookOutput, message);
6968 strcat(bookOutput, " \n");
6970 } else if (bookOutput[0] != NULLCHAR) {
6971 /* All of book output has arrived; display it */
6972 char *p = bookOutput;
6973 while (*p != NULLCHAR) {
6974 if (*p == '\t') *p = ' ';
6977 DisplayInformation(bookOutput);
6978 bookRequested = FALSE;
6979 /* Fall through to parse the current output */
6984 * Look for machine move.
6986 if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
6987 (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
6989 /* This method is only useful on engines that support ping */
6990 if (cps->lastPing != cps->lastPong) {
6991 if (gameMode == BeginningOfGame) {
6992 /* Extra move from before last new; ignore */
6993 if (appData.debugMode) {
6994 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
6997 if (appData.debugMode) {
6998 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
6999 cps->which, gameMode);
7002 SendToProgram("undo\n", cps);
7008 case BeginningOfGame:
7009 /* Extra move from before last reset; ignore */
7010 if (appData.debugMode) {
7011 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7018 /* Extra move after we tried to stop. The mode test is
7019 not a reliable way of detecting this problem, but it's
7020 the best we can do on engines that don't support ping.
7022 if (appData.debugMode) {
7023 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7024 cps->which, gameMode);
7026 SendToProgram("undo\n", cps);
7029 case MachinePlaysWhite:
7030 case IcsPlayingWhite:
7031 machineWhite = TRUE;
7034 case MachinePlaysBlack:
7035 case IcsPlayingBlack:
7036 machineWhite = FALSE;
7039 case TwoMachinesPlay:
7040 machineWhite = (cps->twoMachinesColor[0] == 'w');
7043 if (WhiteOnMove(forwardMostMove) != machineWhite) {
7044 if (appData.debugMode) {
7046 "Ignoring move out of turn by %s, gameMode %d"
7047 ", forwardMost %d\n",
7048 cps->which, gameMode, forwardMostMove);
7053 if (appData.debugMode) { int f = forwardMostMove;
7054 fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
7055 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
7056 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
7058 if(cps->alphaRank) AlphaRank(machineMove, 4);
7059 if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
7060 &fromX, &fromY, &toX, &toY, &promoChar)) {
7061 /* Machine move could not be parsed; ignore it. */
7062 sprintf(buf1, _("Illegal move \"%s\" from %s machine"),
7063 machineMove, cps->which);
7064 DisplayError(buf1, 0);
7065 sprintf(buf1, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
7066 machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
7067 if (gameMode == TwoMachinesPlay) {
7068 GameEnds(machineWhite ? BlackWins : WhiteWins,
7074 /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
7075 /* So we have to redo legality test with true e.p. status here, */
7076 /* to make sure an illegal e.p. capture does not slip through, */
7077 /* to cause a forfeit on a justified illegal-move complaint */
7078 /* of the opponent. */
7079 if( gameMode==TwoMachinesPlay && appData.testLegality
7080 && fromY != DROP_RANK /* [HGM] temporary; should still add legality test for drops */
7083 moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
7084 fromY, fromX, toY, toX, promoChar);
7085 if (appData.debugMode) {
7087 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
7088 boards[forwardMostMove][CASTLING][i], castlingRank[i]);
7089 fprintf(debugFP, "castling rights\n");
7091 if(moveType == IllegalMove) {
7092 sprintf(buf1, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
7093 machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
7094 GameEnds(machineWhite ? BlackWins : WhiteWins,
7097 } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
7098 /* [HGM] Kludge to handle engines that send FRC-style castling
7099 when they shouldn't (like TSCP-Gothic) */
7101 case WhiteASideCastleFR:
7102 case BlackASideCastleFR:
7104 currentMoveString[2]++;
7106 case WhiteHSideCastleFR:
7107 case BlackHSideCastleFR:
7109 currentMoveString[2]--;
7111 default: ; // nothing to do, but suppresses warning of pedantic compilers
7114 hintRequested = FALSE;
7115 lastHint[0] = NULLCHAR;
7116 bookRequested = FALSE;
7117 /* Program may be pondering now */
7118 cps->maybeThinking = TRUE;
7119 if (cps->sendTime == 2) cps->sendTime = 1;
7120 if (cps->offeredDraw) cps->offeredDraw--;
7122 /* currentMoveString is set as a side-effect of ParseOneMove */
7123 strcpy(machineMove, currentMoveString);
7124 strcat(machineMove, "\n");
7125 strcpy(moveList[forwardMostMove], machineMove);
7127 MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
7129 /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
7130 if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
7133 while( count < adjudicateLossPlies ) {
7134 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
7137 score = -score; /* Flip score for winning side */
7140 if( score > adjudicateLossThreshold ) {
7147 if( count >= adjudicateLossPlies ) {
7148 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7150 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7151 "Xboard adjudication",
7158 if(Adjudicate(cps)) return; // [HGM] adjudicate: for all automatic game ends
7161 if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
7163 if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
7164 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
7166 SendMoveToICS(moveType, fromX, fromY, toX, toY);
7168 SendMoveToICS(moveType, fromX, fromY, toX, toY);
7170 if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
7171 char buf[3*MSG_SIZ];
7173 sprintf(buf, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
7174 programStats.score / 100.,
7176 programStats.time / 100.,
7177 (unsigned int)programStats.nodes,
7178 (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
7179 programStats.movelist);
7181 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
7186 /* [AS] Save move info and clear stats for next move */
7187 pvInfoList[ forwardMostMove-1 ].score = programStats.score;
7188 pvInfoList[ forwardMostMove-1 ].depth = programStats.depth;
7189 pvInfoList[ forwardMostMove-1 ].time = programStats.time; // [HGM] PGNtime: take time from engine stats
7190 ClearProgramStats();
7191 thinkOutput[0] = NULLCHAR;
7192 hiddenThinkOutputState = 0;
7195 if (gameMode == TwoMachinesPlay) {
7196 /* [HGM] relaying draw offers moved to after reception of move */
7197 /* and interpreting offer as claim if it brings draw condition */
7198 if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
7199 SendToProgram("draw\n", cps->other);
7201 if (cps->other->sendTime) {
7202 SendTimeRemaining(cps->other,
7203 cps->other->twoMachinesColor[0] == 'w');
7205 bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
7206 if (firstMove && !bookHit) {
7208 if (cps->other->useColors) {
7209 SendToProgram(cps->other->twoMachinesColor, cps->other);
7211 SendToProgram("go\n", cps->other);
7213 cps->other->maybeThinking = TRUE;
7216 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7218 if (!pausing && appData.ringBellAfterMoves) {
7223 * Reenable menu items that were disabled while
7224 * machine was thinking
7226 if (gameMode != TwoMachinesPlay)
7227 SetUserThinkingEnables();
7229 // [HGM] book: after book hit opponent has received move and is now in force mode
7230 // force the book reply into it, and then fake that it outputted this move by jumping
7231 // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
7233 static char bookMove[MSG_SIZ]; // a bit generous?
7235 strcpy(bookMove, "move ");
7236 strcat(bookMove, bookHit);
7239 programStats.nodes = programStats.depth = programStats.time =
7240 programStats.score = programStats.got_only_move = 0;
7241 sprintf(programStats.movelist, "%s (xbook)", bookHit);
7243 if(cps->lastPing != cps->lastPong) {
7244 savedMessage = message; // args for deferred call
7246 ScheduleDelayedEvent(DeferredBookMove, 10);
7255 /* Set special modes for chess engines. Later something general
7256 * could be added here; for now there is just one kludge feature,
7257 * needed because Crafty 15.10 and earlier don't ignore SIGINT
7258 * when "xboard" is given as an interactive command.
7260 if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
7261 cps->useSigint = FALSE;
7262 cps->useSigterm = FALSE;
7264 if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
7265 ParseFeatures(message+8, cps);
7266 return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
7269 /* [HGM] Allow engine to set up a position. Don't ask me why one would
7270 * want this, I was asked to put it in, and obliged.
7272 if (!strncmp(message, "setboard ", 9)) {
7273 Board initial_position;
7275 GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
7277 if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
7278 DisplayError(_("Bad FEN received from engine"), 0);
7282 CopyBoard(boards[0], initial_position);
7283 initialRulePlies = FENrulePlies;
7284 if(blackPlaysFirst) gameMode = MachinePlaysWhite;
7285 else gameMode = MachinePlaysBlack;
7286 DrawPosition(FALSE, boards[currentMove]);
7292 * Look for communication commands
7294 if (!strncmp(message, "telluser ", 9)) {
7295 DisplayNote(message + 9);
7298 if (!strncmp(message, "tellusererror ", 14)) {
7300 DisplayError(message + 14, 0);
7303 if (!strncmp(message, "tellopponent ", 13)) {
7304 if (appData.icsActive) {
7306 snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
7310 DisplayNote(message + 13);
7314 if (!strncmp(message, "tellothers ", 11)) {
7315 if (appData.icsActive) {
7317 snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
7323 if (!strncmp(message, "tellall ", 8)) {
7324 if (appData.icsActive) {
7326 snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
7330 DisplayNote(message + 8);
7334 if (strncmp(message, "warning", 7) == 0) {
7335 /* Undocumented feature, use tellusererror in new code */
7336 DisplayError(message, 0);
7339 if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
7340 strcpy(realname, cps->tidy);
7341 strcat(realname, " query");
7342 AskQuestion(realname, buf2, buf1, cps->pr);
7345 /* Commands from the engine directly to ICS. We don't allow these to be
7346 * sent until we are logged on. Crafty kibitzes have been known to
7347 * interfere with the login process.
7350 if (!strncmp(message, "tellics ", 8)) {
7351 SendToICS(message + 8);
7355 if (!strncmp(message, "tellicsnoalias ", 15)) {
7356 SendToICS(ics_prefix);
7357 SendToICS(message + 15);
7361 /* The following are for backward compatibility only */
7362 if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
7363 !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
7364 SendToICS(ics_prefix);
7370 if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
7374 * If the move is illegal, cancel it and redraw the board.
7375 * Also deal with other error cases. Matching is rather loose
7376 * here to accommodate engines written before the spec.
7378 if (strncmp(message + 1, "llegal move", 11) == 0 ||
7379 strncmp(message, "Error", 5) == 0) {
7380 if (StrStr(message, "name") ||
7381 StrStr(message, "rating") || StrStr(message, "?") ||
7382 StrStr(message, "result") || StrStr(message, "board") ||
7383 StrStr(message, "bk") || StrStr(message, "computer") ||
7384 StrStr(message, "variant") || StrStr(message, "hint") ||
7385 StrStr(message, "random") || StrStr(message, "depth") ||
7386 StrStr(message, "accepted")) {
7389 if (StrStr(message, "protover")) {
7390 /* Program is responding to input, so it's apparently done
7391 initializing, and this error message indicates it is
7392 protocol version 1. So we don't need to wait any longer
7393 for it to initialize and send feature commands. */
7394 FeatureDone(cps, 1);
7395 cps->protocolVersion = 1;
7398 cps->maybeThinking = FALSE;
7400 if (StrStr(message, "draw")) {
7401 /* Program doesn't have "draw" command */
7402 cps->sendDrawOffers = 0;
7405 if (cps->sendTime != 1 &&
7406 (StrStr(message, "time") || StrStr(message, "otim"))) {
7407 /* Program apparently doesn't have "time" or "otim" command */
7411 if (StrStr(message, "analyze")) {
7412 cps->analysisSupport = FALSE;
7413 cps->analyzing = FALSE;
7415 sprintf(buf2, _("%s does not support analysis"), cps->tidy);
7416 DisplayError(buf2, 0);
7419 if (StrStr(message, "(no matching move)st")) {
7420 /* Special kludge for GNU Chess 4 only */
7421 cps->stKludge = TRUE;
7422 SendTimeControl(cps, movesPerSession, timeControl,
7423 timeIncrement, appData.searchDepth,
7427 if (StrStr(message, "(no matching move)sd")) {
7428 /* Special kludge for GNU Chess 4 only */
7429 cps->sdKludge = TRUE;
7430 SendTimeControl(cps, movesPerSession, timeControl,
7431 timeIncrement, appData.searchDepth,
7435 if (!StrStr(message, "llegal")) {
7438 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
7439 gameMode == IcsIdle) return;
7440 if (forwardMostMove <= backwardMostMove) return;
7441 if (pausing) PauseEvent();
7442 if(appData.forceIllegal) {
7443 // [HGM] illegal: machine refused move; force position after move into it
7444 SendToProgram("force\n", cps);
7445 if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
7446 // we have a real problem now, as SendBoard will use the a2a3 kludge
7447 // when black is to move, while there might be nothing on a2 or black
7448 // might already have the move. So send the board as if white has the move.
7449 // But first we must change the stm of the engine, as it refused the last move
7450 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
7451 if(WhiteOnMove(forwardMostMove)) {
7452 SendToProgram("a7a6\n", cps); // for the engine black still had the move
7453 SendBoard(cps, forwardMostMove); // kludgeless board
7455 SendToProgram("a2a3\n", cps); // for the engine white still had the move
7456 CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
7457 SendBoard(cps, forwardMostMove+1); // kludgeless board
7459 } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
7460 if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
7461 gameMode == TwoMachinesPlay)
7462 SendToProgram("go\n", cps);
7465 if (gameMode == PlayFromGameFile) {
7466 /* Stop reading this game file */
7467 gameMode = EditGame;
7470 currentMove = --forwardMostMove;
7471 DisplayMove(currentMove-1); /* before DisplayMoveError */
7473 DisplayBothClocks();
7474 sprintf(buf1, _("Illegal move \"%s\" (rejected by %s chess program)"),
7475 parseList[currentMove], cps->which);
7476 DisplayMoveError(buf1);
7477 DrawPosition(FALSE, boards[currentMove]);
7479 /* [HGM] illegal-move claim should forfeit game when Xboard */
7480 /* only passes fully legal moves */
7481 if( appData.testLegality && gameMode == TwoMachinesPlay ) {
7482 GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
7483 "False illegal-move claim", GE_XBOARD );
7487 if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
7488 /* Program has a broken "time" command that
7489 outputs a string not ending in newline.
7495 * If chess program startup fails, exit with an error message.
7496 * Attempts to recover here are futile.
7498 if ((StrStr(message, "unknown host") != NULL)
7499 || (StrStr(message, "No remote directory") != NULL)
7500 || (StrStr(message, "not found") != NULL)
7501 || (StrStr(message, "No such file") != NULL)
7502 || (StrStr(message, "can't alloc") != NULL)
7503 || (StrStr(message, "Permission denied") != NULL)) {
7505 cps->maybeThinking = FALSE;
7506 snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
7507 cps->which, cps->program, cps->host, message);
7508 RemoveInputSource(cps->isr);
7509 DisplayFatalError(buf1, 0, 1);
7514 * Look for hint output
7516 if (sscanf(message, "Hint: %s", buf1) == 1) {
7517 if (cps == &first && hintRequested) {
7518 hintRequested = FALSE;
7519 if (ParseOneMove(buf1, forwardMostMove, &moveType,
7520 &fromX, &fromY, &toX, &toY, &promoChar)) {
7521 (void) CoordsToAlgebraic(boards[forwardMostMove],
7522 PosFlags(forwardMostMove),
7523 fromY, fromX, toY, toX, promoChar, buf1);
7524 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
7525 DisplayInformation(buf2);
7527 /* Hint move could not be parsed!? */
7528 snprintf(buf2, sizeof(buf2),
7529 _("Illegal hint move \"%s\"\nfrom %s chess program"),
7531 DisplayError(buf2, 0);
7534 strcpy(lastHint, buf1);
7540 * Ignore other messages if game is not in progress
7542 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
7543 gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
7546 * look for win, lose, draw, or draw offer
7548 if (strncmp(message, "1-0", 3) == 0) {
7549 char *p, *q, *r = "";
7550 p = strchr(message, '{');
7558 GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
7560 } else if (strncmp(message, "0-1", 3) == 0) {
7561 char *p, *q, *r = "";
7562 p = strchr(message, '{');
7570 /* Kludge for Arasan 4.1 bug */
7571 if (strcmp(r, "Black resigns") == 0) {
7572 GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
7575 GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
7577 } else if (strncmp(message, "1/2", 3) == 0) {
7578 char *p, *q, *r = "";
7579 p = strchr(message, '{');
7588 GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
7591 } else if (strncmp(message, "White resign", 12) == 0) {
7592 GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
7594 } else if (strncmp(message, "Black resign", 12) == 0) {
7595 GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
7597 } else if (strncmp(message, "White matches", 13) == 0 ||
7598 strncmp(message, "Black matches", 13) == 0 ) {
7599 /* [HGM] ignore GNUShogi noises */
7601 } else if (strncmp(message, "White", 5) == 0 &&
7602 message[5] != '(' &&
7603 StrStr(message, "Black") == NULL) {
7604 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7606 } else if (strncmp(message, "Black", 5) == 0 &&
7607 message[5] != '(') {
7608 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7610 } else if (strcmp(message, "resign") == 0 ||
7611 strcmp(message, "computer resigns") == 0) {
7613 case MachinePlaysBlack:
7614 case IcsPlayingBlack:
7615 GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
7617 case MachinePlaysWhite:
7618 case IcsPlayingWhite:
7619 GameEnds(BlackWins, "White resigns", GE_ENGINE);
7621 case TwoMachinesPlay:
7622 if (cps->twoMachinesColor[0] == 'w')
7623 GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
7625 GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
7632 } else if (strncmp(message, "opponent mates", 14) == 0) {
7634 case MachinePlaysBlack:
7635 case IcsPlayingBlack:
7636 GameEnds(WhiteWins, "White mates", GE_ENGINE);
7638 case MachinePlaysWhite:
7639 case IcsPlayingWhite:
7640 GameEnds(BlackWins, "Black mates", GE_ENGINE);
7642 case TwoMachinesPlay:
7643 if (cps->twoMachinesColor[0] == 'w')
7644 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7646 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7653 } else if (strncmp(message, "computer mates", 14) == 0) {
7655 case MachinePlaysBlack:
7656 case IcsPlayingBlack:
7657 GameEnds(BlackWins, "Black mates", GE_ENGINE1);
7659 case MachinePlaysWhite:
7660 case IcsPlayingWhite:
7661 GameEnds(WhiteWins, "White mates", GE_ENGINE);
7663 case TwoMachinesPlay:
7664 if (cps->twoMachinesColor[0] == 'w')
7665 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7667 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7674 } else if (strncmp(message, "checkmate", 9) == 0) {
7675 if (WhiteOnMove(forwardMostMove)) {
7676 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7678 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7681 } else if (strstr(message, "Draw") != NULL ||
7682 strstr(message, "game is a draw") != NULL) {
7683 GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
7685 } else if (strstr(message, "offer") != NULL &&
7686 strstr(message, "draw") != NULL) {
7688 if (appData.zippyPlay && first.initDone) {
7689 /* Relay offer to ICS */
7690 SendToICS(ics_prefix);
7691 SendToICS("draw\n");
7694 cps->offeredDraw = 2; /* valid until this engine moves twice */
7695 if (gameMode == TwoMachinesPlay) {
7696 if (cps->other->offeredDraw) {
7697 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
7698 /* [HGM] in two-machine mode we delay relaying draw offer */
7699 /* until after we also have move, to see if it is really claim */
7701 } else if (gameMode == MachinePlaysWhite ||
7702 gameMode == MachinePlaysBlack) {
7703 if (userOfferedDraw) {
7704 DisplayInformation(_("Machine accepts your draw offer"));
7705 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
7707 DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
7714 * Look for thinking output
7716 if ( appData.showThinking // [HGM] thinking: test all options that cause this output
7717 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
7719 int plylev, mvleft, mvtot, curscore, time;
7720 char mvname[MOVE_LEN];
7724 int prefixHint = FALSE;
7725 mvname[0] = NULLCHAR;
7728 case MachinePlaysBlack:
7729 case IcsPlayingBlack:
7730 if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
7732 case MachinePlaysWhite:
7733 case IcsPlayingWhite:
7734 if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
7739 case IcsObserving: /* [DM] icsEngineAnalyze */
7740 if (!appData.icsEngineAnalyze) ignore = TRUE;
7742 case TwoMachinesPlay:
7743 if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
7754 if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
7755 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
7757 if (plyext != ' ' && plyext != '\t') {
7761 /* [AS] Negate score if machine is playing black and reporting absolute scores */
7762 if( cps->scoreIsAbsolute &&
7763 ( gameMode == MachinePlaysBlack ||
7764 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
7765 gameMode == IcsPlayingBlack || // [HGM] also add other situations where engine should report black POV
7766 (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
7767 !WhiteOnMove(currentMove)
7770 curscore = -curscore;
7774 programStats.depth = plylev;
7775 programStats.nodes = nodes;
7776 programStats.time = time;
7777 programStats.score = curscore;
7778 programStats.got_only_move = 0;
7780 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
7783 if(cps->nps == 0) ticklen = 10*time; // use engine reported time
7784 else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
7785 if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
7786 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
7787 whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
7788 if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
7789 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
7790 blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
7793 /* Buffer overflow protection */
7794 if (buf1[0] != NULLCHAR) {
7795 if (strlen(buf1) >= sizeof(programStats.movelist)
7796 && appData.debugMode) {
7798 "PV is too long; using the first %u bytes.\n",
7799 (unsigned) sizeof(programStats.movelist) - 1);
7802 safeStrCpy( programStats.movelist, buf1, sizeof(programStats.movelist) );
7804 sprintf(programStats.movelist, " no PV\n");
7807 if (programStats.seen_stat) {
7808 programStats.ok_to_send = 1;
7811 if (strchr(programStats.movelist, '(') != NULL) {
7812 programStats.line_is_book = 1;
7813 programStats.nr_moves = 0;
7814 programStats.moves_left = 0;
7816 programStats.line_is_book = 0;
7819 SendProgramStatsToFrontend( cps, &programStats );
7822 [AS] Protect the thinkOutput buffer from overflow... this
7823 is only useful if buf1 hasn't overflowed first!
7825 sprintf(thinkOutput, "[%d]%c%+.2f %s%s",
7827 (gameMode == TwoMachinesPlay ?
7828 ToUpper(cps->twoMachinesColor[0]) : ' '),
7829 ((double) curscore) / 100.0,
7830 prefixHint ? lastHint : "",
7831 prefixHint ? " " : "" );
7833 if( buf1[0] != NULLCHAR ) {
7834 unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
7836 if( strlen(buf1) > max_len ) {
7837 if( appData.debugMode) {
7838 fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
7840 buf1[max_len+1] = '\0';
7843 strcat( thinkOutput, buf1 );
7846 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
7847 || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7848 DisplayMove(currentMove - 1);
7852 } else if ((p=StrStr(message, "(only move)")) != NULL) {
7853 /* crafty (9.25+) says "(only move) <move>"
7854 * if there is only 1 legal move
7856 sscanf(p, "(only move) %s", buf1);
7857 sprintf(thinkOutput, "%s (only move)", buf1);
7858 sprintf(programStats.movelist, "%s (only move)", buf1);
7859 programStats.depth = 1;
7860 programStats.nr_moves = 1;
7861 programStats.moves_left = 1;
7862 programStats.nodes = 1;
7863 programStats.time = 1;
7864 programStats.got_only_move = 1;
7866 /* Not really, but we also use this member to
7867 mean "line isn't going to change" (Crafty
7868 isn't searching, so stats won't change) */
7869 programStats.line_is_book = 1;
7871 SendProgramStatsToFrontend( cps, &programStats );
7873 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
7874 gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7875 DisplayMove(currentMove - 1);
7878 } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
7879 &time, &nodes, &plylev, &mvleft,
7880 &mvtot, mvname) >= 5) {
7881 /* The stat01: line is from Crafty (9.29+) in response
7882 to the "." command */
7883 programStats.seen_stat = 1;
7884 cps->maybeThinking = TRUE;
7886 if (programStats.got_only_move || !appData.periodicUpdates)
7889 programStats.depth = plylev;
7890 programStats.time = time;
7891 programStats.nodes = nodes;
7892 programStats.moves_left = mvleft;
7893 programStats.nr_moves = mvtot;
7894 strcpy(programStats.move_name, mvname);
7895 programStats.ok_to_send = 1;
7896 programStats.movelist[0] = '\0';
7898 SendProgramStatsToFrontend( cps, &programStats );
7902 } else if (strncmp(message,"++",2) == 0) {
7903 /* Crafty 9.29+ outputs this */
7904 programStats.got_fail = 2;
7907 } else if (strncmp(message,"--",2) == 0) {
7908 /* Crafty 9.29+ outputs this */
7909 programStats.got_fail = 1;
7912 } else if (thinkOutput[0] != NULLCHAR &&
7913 strncmp(message, " ", 4) == 0) {
7914 unsigned message_len;
7917 while (*p && *p == ' ') p++;
7919 message_len = strlen( p );
7921 /* [AS] Avoid buffer overflow */
7922 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
7923 strcat(thinkOutput, " ");
7924 strcat(thinkOutput, p);
7927 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
7928 strcat(programStats.movelist, " ");
7929 strcat(programStats.movelist, p);
7932 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
7933 gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7934 DisplayMove(currentMove - 1);
7942 if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
7943 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
7945 ChessProgramStats cpstats;
7947 if (plyext != ' ' && plyext != '\t') {
7951 /* [AS] Negate score if machine is playing black and reporting absolute scores */
7952 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
7953 curscore = -curscore;
7956 cpstats.depth = plylev;
7957 cpstats.nodes = nodes;
7958 cpstats.time = time;
7959 cpstats.score = curscore;
7960 cpstats.got_only_move = 0;
7961 cpstats.movelist[0] = '\0';
7963 if (buf1[0] != NULLCHAR) {
7964 safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist) );
7967 cpstats.ok_to_send = 0;
7968 cpstats.line_is_book = 0;
7969 cpstats.nr_moves = 0;
7970 cpstats.moves_left = 0;
7972 SendProgramStatsToFrontend( cps, &cpstats );
7979 /* Parse a game score from the character string "game", and
7980 record it as the history of the current game. The game
7981 score is NOT assumed to start from the standard position.
7982 The display is not updated in any way.
7985 ParseGameHistory(game)
7989 int fromX, fromY, toX, toY, boardIndex;
7994 if (appData.debugMode)
7995 fprintf(debugFP, "Parsing game history: %s\n", game);
7997 if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
7998 gameInfo.site = StrSave(appData.icsHost);
7999 gameInfo.date = PGNDate();
8000 gameInfo.round = StrSave("-");
8002 /* Parse out names of players */
8003 while (*game == ' ') game++;
8005 while (*game != ' ') *p++ = *game++;
8007 gameInfo.white = StrSave(buf);
8008 while (*game == ' ') game++;
8010 while (*game != ' ' && *game != '\n') *p++ = *game++;
8012 gameInfo.black = StrSave(buf);
8015 boardIndex = blackPlaysFirst ? 1 : 0;
8018 yyboardindex = boardIndex;
8019 moveType = (ChessMove) yylex();
8021 case IllegalMove: /* maybe suicide chess, etc. */
8022 if (appData.debugMode) {
8023 fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
8024 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8025 setbuf(debugFP, NULL);
8027 case WhitePromotionChancellor:
8028 case BlackPromotionChancellor:
8029 case WhitePromotionArchbishop:
8030 case BlackPromotionArchbishop:
8031 case WhitePromotionQueen:
8032 case BlackPromotionQueen:
8033 case WhitePromotionRook:
8034 case BlackPromotionRook:
8035 case WhitePromotionBishop:
8036 case BlackPromotionBishop:
8037 case WhitePromotionKnight:
8038 case BlackPromotionKnight:
8039 case WhitePromotionKing:
8040 case BlackPromotionKing:
8042 case WhiteCapturesEnPassant:
8043 case BlackCapturesEnPassant:
8044 case WhiteKingSideCastle:
8045 case WhiteQueenSideCastle:
8046 case BlackKingSideCastle:
8047 case BlackQueenSideCastle:
8048 case WhiteKingSideCastleWild:
8049 case WhiteQueenSideCastleWild:
8050 case BlackKingSideCastleWild:
8051 case BlackQueenSideCastleWild:
8053 case WhiteHSideCastleFR:
8054 case WhiteASideCastleFR:
8055 case BlackHSideCastleFR:
8056 case BlackASideCastleFR:
8058 fromX = currentMoveString[0] - AAA;
8059 fromY = currentMoveString[1] - ONE;
8060 toX = currentMoveString[2] - AAA;
8061 toY = currentMoveString[3] - ONE;
8062 promoChar = currentMoveString[4];
8066 fromX = moveType == WhiteDrop ?
8067 (int) CharToPiece(ToUpper(currentMoveString[0])) :
8068 (int) CharToPiece(ToLower(currentMoveString[0]));
8070 toX = currentMoveString[2] - AAA;
8071 toY = currentMoveString[3] - ONE;
8072 promoChar = NULLCHAR;
8076 sprintf(buf, _("Ambiguous move in ICS output: \"%s\""), yy_text);
8077 if (appData.debugMode) {
8078 fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
8079 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8080 setbuf(debugFP, NULL);
8082 DisplayError(buf, 0);
8084 case ImpossibleMove:
8086 sprintf(buf, _("Illegal move in ICS output: \"%s\""), yy_text);
8087 if (appData.debugMode) {
8088 fprintf(debugFP, "Impossible 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 (ChessMove) 0: /* end of file */
8095 if (boardIndex < backwardMostMove) {
8096 /* Oops, gap. How did that happen? */
8097 DisplayError(_("Gap in move list"), 0);
8100 backwardMostMove = blackPlaysFirst ? 1 : 0;
8101 if (boardIndex > forwardMostMove) {
8102 forwardMostMove = boardIndex;
8106 if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
8107 strcat(parseList[boardIndex-1], " ");
8108 strcat(parseList[boardIndex-1], yy_text);
8120 case GameUnfinished:
8121 if (gameMode == IcsExamining) {
8122 if (boardIndex < backwardMostMove) {
8123 /* Oops, gap. How did that happen? */
8126 backwardMostMove = blackPlaysFirst ? 1 : 0;
8129 gameInfo.result = moveType;
8130 p = strchr(yy_text, '{');
8131 if (p == NULL) p = strchr(yy_text, '(');
8134 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
8136 q = strchr(p, *p == '{' ? '}' : ')');
8137 if (q != NULL) *q = NULLCHAR;
8140 gameInfo.resultDetails = StrSave(p);
8143 if (boardIndex >= forwardMostMove &&
8144 !(gameMode == IcsObserving && ics_gamenum == -1)) {
8145 backwardMostMove = blackPlaysFirst ? 1 : 0;
8148 (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
8149 fromY, fromX, toY, toX, promoChar,
8150 parseList[boardIndex]);
8151 CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
8152 /* currentMoveString is set as a side-effect of yylex */
8153 strcpy(moveList[boardIndex], currentMoveString);
8154 strcat(moveList[boardIndex], "\n");
8156 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
8157 switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
8163 if(gameInfo.variant != VariantShogi)
8164 strcat(parseList[boardIndex - 1], "+");
8168 strcat(parseList[boardIndex - 1], "#");
8175 /* Apply a move to the given board */
8177 ApplyMove(fromX, fromY, toX, toY, promoChar, board)
8178 int fromX, fromY, toX, toY;
8182 ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
8183 int promoRank = gameInfo.variant == VariantMakruk ? 3 : 1;
8185 /* [HGM] compute & store e.p. status and castling rights for new position */
8186 /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
8189 if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
8190 oldEP = (signed char)board[EP_STATUS];
8191 board[EP_STATUS] = EP_NONE;
8193 if( board[toY][toX] != EmptySquare )
8194 board[EP_STATUS] = EP_CAPTURE;
8196 if( board[fromY][fromX] == WhitePawn ) {
8197 if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
8198 board[EP_STATUS] = EP_PAWN_MOVE;
8200 if(toX>BOARD_LEFT && board[toY][toX-1] == BlackPawn &&
8201 gameInfo.variant != VariantBerolina || toX < fromX)
8202 board[EP_STATUS] = toX | berolina;
8203 if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
8204 gameInfo.variant != VariantBerolina || toX > fromX)
8205 board[EP_STATUS] = toX;
8208 if( board[fromY][fromX] == BlackPawn ) {
8209 if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
8210 board[EP_STATUS] = EP_PAWN_MOVE;
8211 if( toY-fromY== -2) {
8212 if(toX>BOARD_LEFT && board[toY][toX-1] == WhitePawn &&
8213 gameInfo.variant != VariantBerolina || toX < fromX)
8214 board[EP_STATUS] = toX | berolina;
8215 if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
8216 gameInfo.variant != VariantBerolina || toX > fromX)
8217 board[EP_STATUS] = toX;
8221 for(i=0; i<nrCastlingRights; i++) {
8222 if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
8223 board[CASTLING][i] == toX && castlingRank[i] == toY
8224 ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
8229 /* [HGM] In Shatranj and Courier all promotions are to Ferz */
8230 if((gameInfo.variant==VariantShatranj || gameInfo.variant==VariantCourier || gameInfo.variant == VariantMakruk)
8231 && promoChar != 0) promoChar = PieceToChar(WhiteFerz);
8233 if (fromX == toX && fromY == toY) return;
8235 if (fromY == DROP_RANK) {
8237 piece = board[toY][toX] = (ChessSquare) fromX;
8239 piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
8240 king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
8241 if(gameInfo.variant == VariantKnightmate)
8242 king += (int) WhiteUnicorn - (int) WhiteKing;
8244 /* Code added by Tord: */
8245 /* FRC castling assumed when king captures friendly rook. */
8246 if (board[fromY][fromX] == WhiteKing &&
8247 board[toY][toX] == WhiteRook) {
8248 board[fromY][fromX] = EmptySquare;
8249 board[toY][toX] = EmptySquare;
8251 board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
8253 board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
8255 } else if (board[fromY][fromX] == BlackKing &&
8256 board[toY][toX] == BlackRook) {
8257 board[fromY][fromX] = EmptySquare;
8258 board[toY][toX] = EmptySquare;
8260 board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
8262 board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
8264 /* End of code added by Tord */
8266 } else if (board[fromY][fromX] == king
8267 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8268 && toY == fromY && toX > fromX+1) {
8269 board[fromY][fromX] = EmptySquare;
8270 board[toY][toX] = king;
8271 board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
8272 board[fromY][BOARD_RGHT-1] = EmptySquare;
8273 } else if (board[fromY][fromX] == king
8274 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8275 && toY == fromY && toX < fromX-1) {
8276 board[fromY][fromX] = EmptySquare;
8277 board[toY][toX] = king;
8278 board[toY][toX+1] = board[fromY][BOARD_LEFT];
8279 board[fromY][BOARD_LEFT] = EmptySquare;
8280 } else if (board[fromY][fromX] == WhitePawn
8281 && toY >= BOARD_HEIGHT-promoRank
8282 && gameInfo.variant != VariantXiangqi
8284 /* white pawn promotion */
8285 board[toY][toX] = CharToPiece(ToUpper(promoChar));
8286 if (board[toY][toX] == EmptySquare) {
8287 board[toY][toX] = WhiteQueen;
8289 if(gameInfo.variant==VariantBughouse ||
8290 gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
8291 board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
8292 board[fromY][fromX] = EmptySquare;
8293 } else if ((fromY == BOARD_HEIGHT-4)
8295 && gameInfo.variant != VariantXiangqi
8296 && gameInfo.variant != VariantBerolina
8297 && (board[fromY][fromX] == WhitePawn)
8298 && (board[toY][toX] == EmptySquare)) {
8299 board[fromY][fromX] = EmptySquare;
8300 board[toY][toX] = WhitePawn;
8301 captured = board[toY - 1][toX];
8302 board[toY - 1][toX] = EmptySquare;
8303 } else if ((fromY == BOARD_HEIGHT-4)
8305 && gameInfo.variant == VariantBerolina
8306 && (board[fromY][fromX] == WhitePawn)
8307 && (board[toY][toX] == EmptySquare)) {
8308 board[fromY][fromX] = EmptySquare;
8309 board[toY][toX] = WhitePawn;
8310 if(oldEP & EP_BEROLIN_A) {
8311 captured = board[fromY][fromX-1];
8312 board[fromY][fromX-1] = EmptySquare;
8313 }else{ captured = board[fromY][fromX+1];
8314 board[fromY][fromX+1] = EmptySquare;
8316 } else if (board[fromY][fromX] == king
8317 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8318 && toY == fromY && toX > fromX+1) {
8319 board[fromY][fromX] = EmptySquare;
8320 board[toY][toX] = king;
8321 board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
8322 board[fromY][BOARD_RGHT-1] = EmptySquare;
8323 } else if (board[fromY][fromX] == king
8324 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8325 && toY == fromY && toX < fromX-1) {
8326 board[fromY][fromX] = EmptySquare;
8327 board[toY][toX] = king;
8328 board[toY][toX+1] = board[fromY][BOARD_LEFT];
8329 board[fromY][BOARD_LEFT] = EmptySquare;
8330 } else if (fromY == 7 && fromX == 3
8331 && board[fromY][fromX] == BlackKing
8332 && toY == 7 && toX == 5) {
8333 board[fromY][fromX] = EmptySquare;
8334 board[toY][toX] = BlackKing;
8335 board[fromY][7] = EmptySquare;
8336 board[toY][4] = BlackRook;
8337 } else if (fromY == 7 && fromX == 3
8338 && board[fromY][fromX] == BlackKing
8339 && toY == 7 && toX == 1) {
8340 board[fromY][fromX] = EmptySquare;
8341 board[toY][toX] = BlackKing;
8342 board[fromY][0] = EmptySquare;
8343 board[toY][2] = BlackRook;
8344 } else if (board[fromY][fromX] == BlackPawn
8346 && gameInfo.variant != VariantXiangqi
8348 /* black pawn promotion */
8349 board[toY][toX] = CharToPiece(ToLower(promoChar));
8350 if (board[toY][toX] == EmptySquare) {
8351 board[toY][toX] = BlackQueen;
8353 if(gameInfo.variant==VariantBughouse ||
8354 gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
8355 board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
8356 board[fromY][fromX] = EmptySquare;
8357 } else if ((fromY == 3)
8359 && gameInfo.variant != VariantXiangqi
8360 && gameInfo.variant != VariantBerolina
8361 && (board[fromY][fromX] == BlackPawn)
8362 && (board[toY][toX] == EmptySquare)) {
8363 board[fromY][fromX] = EmptySquare;
8364 board[toY][toX] = BlackPawn;
8365 captured = board[toY + 1][toX];
8366 board[toY + 1][toX] = EmptySquare;
8367 } else if ((fromY == 3)
8369 && gameInfo.variant == VariantBerolina
8370 && (board[fromY][fromX] == BlackPawn)
8371 && (board[toY][toX] == EmptySquare)) {
8372 board[fromY][fromX] = EmptySquare;
8373 board[toY][toX] = BlackPawn;
8374 if(oldEP & EP_BEROLIN_A) {
8375 captured = board[fromY][fromX-1];
8376 board[fromY][fromX-1] = EmptySquare;
8377 }else{ captured = board[fromY][fromX+1];
8378 board[fromY][fromX+1] = EmptySquare;
8381 board[toY][toX] = board[fromY][fromX];
8382 board[fromY][fromX] = EmptySquare;
8385 /* [HGM] now we promote for Shogi, if needed */
8386 if(gameInfo.variant == VariantShogi && promoChar == 'q')
8387 board[toY][toX] = (ChessSquare) (PROMOTED piece);
8390 if (gameInfo.holdingsWidth != 0) {
8392 /* !!A lot more code needs to be written to support holdings */
8393 /* [HGM] OK, so I have written it. Holdings are stored in the */
8394 /* penultimate board files, so they are automaticlly stored */
8395 /* in the game history. */
8396 if (fromY == DROP_RANK) {
8397 /* Delete from holdings, by decreasing count */
8398 /* and erasing image if necessary */
8400 if(p < (int) BlackPawn) { /* white drop */
8401 p -= (int)WhitePawn;
8402 p = PieceToNumber((ChessSquare)p);
8403 if(p >= gameInfo.holdingsSize) p = 0;
8404 if(--board[p][BOARD_WIDTH-2] <= 0)
8405 board[p][BOARD_WIDTH-1] = EmptySquare;
8406 if((int)board[p][BOARD_WIDTH-2] < 0)
8407 board[p][BOARD_WIDTH-2] = 0;
8408 } else { /* black drop */
8409 p -= (int)BlackPawn;
8410 p = PieceToNumber((ChessSquare)p);
8411 if(p >= gameInfo.holdingsSize) p = 0;
8412 if(--board[BOARD_HEIGHT-1-p][1] <= 0)
8413 board[BOARD_HEIGHT-1-p][0] = EmptySquare;
8414 if((int)board[BOARD_HEIGHT-1-p][1] < 0)
8415 board[BOARD_HEIGHT-1-p][1] = 0;
8418 if (captured != EmptySquare && gameInfo.holdingsSize > 0
8419 && gameInfo.variant != VariantBughouse ) {
8420 /* [HGM] holdings: Add to holdings, if holdings exist */
8421 if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
8422 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
8423 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
8426 if (p >= (int) BlackPawn) {
8427 p -= (int)BlackPawn;
8428 if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
8429 /* in Shogi restore piece to its original first */
8430 captured = (ChessSquare) (DEMOTED captured);
8433 p = PieceToNumber((ChessSquare)p);
8434 if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
8435 board[p][BOARD_WIDTH-2]++;
8436 board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
8438 p -= (int)WhitePawn;
8439 if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
8440 captured = (ChessSquare) (DEMOTED captured);
8443 p = PieceToNumber((ChessSquare)p);
8444 if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
8445 board[BOARD_HEIGHT-1-p][1]++;
8446 board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
8449 } else if (gameInfo.variant == VariantAtomic) {
8450 if (captured != EmptySquare) {
8452 for (y = toY-1; y <= toY+1; y++) {
8453 for (x = toX-1; x <= toX+1; x++) {
8454 if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
8455 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
8456 board[y][x] = EmptySquare;
8460 board[toY][toX] = EmptySquare;
8463 if(gameInfo.variant == VariantShogi && promoChar != NULLCHAR && promoChar != '=') {
8464 /* [HGM] Shogi promotions */
8465 board[toY][toX] = (ChessSquare) (PROMOTED piece);
8468 if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
8469 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
8470 // [HGM] superchess: take promotion piece out of holdings
8471 int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
8472 if((int)piece < (int)BlackPawn) { // determine stm from piece color
8473 if(!--board[k][BOARD_WIDTH-2])
8474 board[k][BOARD_WIDTH-1] = EmptySquare;
8476 if(!--board[BOARD_HEIGHT-1-k][1])
8477 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
8483 /* Updates forwardMostMove */
8485 MakeMove(fromX, fromY, toX, toY, promoChar)
8486 int fromX, fromY, toX, toY;
8489 // forwardMostMove++; // [HGM] bare: moved downstream
8491 if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
8492 int timeLeft; static int lastLoadFlag=0; int king, piece;
8493 piece = boards[forwardMostMove][fromY][fromX];
8494 king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
8495 if(gameInfo.variant == VariantKnightmate)
8496 king += (int) WhiteUnicorn - (int) WhiteKing;
8497 if(forwardMostMove == 0) {
8499 fprintf(serverMoves, "%s;", second.tidy);
8500 fprintf(serverMoves, "%s;", first.tidy);
8501 if(!blackPlaysFirst)
8502 fprintf(serverMoves, "%s;", second.tidy);
8503 } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
8504 lastLoadFlag = loadFlag;
8506 fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
8507 // print castling suffix
8508 if( toY == fromY && piece == king ) {
8510 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
8512 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
8515 if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
8516 boards[forwardMostMove][fromY][fromX] == BlackPawn ) &&
8517 boards[forwardMostMove][toY][toX] == EmptySquare
8519 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
8521 if(promoChar != NULLCHAR)
8522 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);
8524 fprintf(serverMoves, "/%d/%d",
8525 pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
8526 if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
8527 else timeLeft = blackTimeRemaining/1000;
8528 fprintf(serverMoves, "/%d", timeLeft);
8530 fflush(serverMoves);
8533 if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations
8534 DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
8538 UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
8539 if (commentList[forwardMostMove+1] != NULL) {
8540 free(commentList[forwardMostMove+1]);
8541 commentList[forwardMostMove+1] = NULL;
8543 CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
8544 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
8545 forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
8546 SwitchClocks(); // uses forwardMostMove, so must be done after incrementing it !
8547 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
8548 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
8549 gameInfo.result = GameUnfinished;
8550 if (gameInfo.resultDetails != NULL) {
8551 free(gameInfo.resultDetails);
8552 gameInfo.resultDetails = NULL;
8554 CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
8555 moveList[forwardMostMove - 1]);
8556 (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
8557 PosFlags(forwardMostMove - 1),
8558 fromY, fromX, toY, toX, promoChar,
8559 parseList[forwardMostMove - 1]);
8560 switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
8566 if(gameInfo.variant != VariantShogi)
8567 strcat(parseList[forwardMostMove - 1], "+");
8571 strcat(parseList[forwardMostMove - 1], "#");
8574 if (appData.debugMode) {
8575 fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
8580 /* Updates currentMove if not pausing */
8582 ShowMove(fromX, fromY, toX, toY)
8584 int instant = (gameMode == PlayFromGameFile) ?
8585 (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
8587 if(appData.noGUI) return;
8589 if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile)
8593 if (forwardMostMove == currentMove + 1)
8596 // AnimateMove(boards[forwardMostMove - 1],
8597 // fromX, fromY, toX, toY);
8599 if (appData.highlightLastMove)
8601 SetHighlights(fromX, fromY, toX, toY);
8604 currentMove = forwardMostMove;
8607 if (instant) return;
8609 DisplayMove(currentMove - 1);
8610 DrawPosition(FALSE, boards[currentMove]);
8611 DisplayBothClocks();
8612 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
8617 void SendEgtPath(ChessProgramState *cps)
8618 { /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
8619 char buf[MSG_SIZ], name[MSG_SIZ], *p;
8621 if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
8624 char c, *q = name+1, *r, *s;
8626 name[0] = ','; // extract next format name from feature and copy with prefixed ','
8627 while(*p && *p != ',') *q++ = *p++;
8629 if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
8630 strcmp(name, ",nalimov:") == 0 ) {
8631 // take nalimov path from the menu-changeable option first, if it is defined
8632 sprintf(buf, "egtpath nalimov %s\n", appData.defaultPathEGTB);
8633 SendToProgram(buf,cps); // send egtbpath command for nalimov
8635 if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
8636 (s = StrStr(appData.egtFormats, name)) != NULL) {
8637 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
8638 s = r = StrStr(s, ":") + 1; // beginning of path info
8639 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
8640 c = *r; *r = 0; // temporarily null-terminate path info
8641 *--q = 0; // strip of trailig ':' from name
8642 sprintf(buf, "egtpath %s %s\n", name+1, s);
8644 SendToProgram(buf,cps); // send egtbpath command for this format
8646 if(*p == ',') p++; // read away comma to position for next format name
8651 InitChessProgram(cps, setup)
8652 ChessProgramState *cps;
8653 int setup; /* [HGM] needed to setup FRC opening position */
8655 char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
8656 if (appData.noChessProgram) return;
8657 hintRequested = FALSE;
8658 bookRequested = FALSE;
8660 /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
8661 /* moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
8662 if(cps->memSize) { /* [HGM] memory */
8663 sprintf(buf, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
8664 SendToProgram(buf, cps);
8666 SendEgtPath(cps); /* [HGM] EGT */
8667 if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
8668 sprintf(buf, "cores %d\n", appData.smpCores);
8669 SendToProgram(buf, cps);
8672 SendToProgram(cps->initString, cps);
8673 if (gameInfo.variant != VariantNormal &&
8674 gameInfo.variant != VariantLoadable
8675 /* [HGM] also send variant if board size non-standard */
8676 || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
8678 char *v = VariantName(gameInfo.variant);
8679 if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
8680 /* [HGM] in protocol 1 we have to assume all variants valid */
8681 sprintf(buf, _("Variant %s not supported by %s"), v, cps->tidy);
8682 DisplayFatalError(buf, 0, 1);
8686 /* [HGM] make prefix for non-standard board size. Awkward testing... */
8687 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8688 if( gameInfo.variant == VariantXiangqi )
8689 overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
8690 if( gameInfo.variant == VariantShogi )
8691 overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
8692 if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
8693 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
8694 if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
8695 gameInfo.variant == VariantGothic || gameInfo.variant == VariantFalcon )
8696 overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8697 if( gameInfo.variant == VariantCourier )
8698 overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8699 if( gameInfo.variant == VariantSuper )
8700 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
8701 if( gameInfo.variant == VariantGreat )
8702 overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
8705 sprintf(b, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
8706 gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
8707 /* [HGM] varsize: try first if this defiant size variant is specifically known */
8708 if(StrStr(cps->variants, b) == NULL) {
8709 // specific sized variant not known, check if general sizing allowed
8710 if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
8711 if(StrStr(cps->variants, "boardsize") == NULL) {
8712 sprintf(buf, "Board size %dx%d+%d not supported by %s",
8713 gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
8714 DisplayFatalError(buf, 0, 1);
8717 /* [HGM] here we really should compare with the maximum supported board size */
8720 } else sprintf(b, "%s", VariantName(gameInfo.variant));
8721 sprintf(buf, "variant %s\n", b);
8722 SendToProgram(buf, cps);
8724 currentlyInitializedVariant = gameInfo.variant;
8726 /* [HGM] send opening position in FRC to first engine */
8728 SendToProgram("force\n", cps);
8730 /* engine is now in force mode! Set flag to wake it up after first move. */
8731 setboardSpoiledMachineBlack = 1;
8735 snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
8736 SendToProgram(buf, cps);
8738 cps->maybeThinking = FALSE;
8739 cps->offeredDraw = 0;
8740 if (!appData.icsActive) {
8741 SendTimeControl(cps, movesPerSession, timeControl,
8742 timeIncrement, appData.searchDepth,
8745 if (appData.showThinking
8746 // [HGM] thinking: four options require thinking output to be sent
8747 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
8749 SendToProgram("post\n", cps);
8751 SendToProgram("hard\n", cps);
8752 if (!appData.ponderNextMove) {
8753 /* Warning: "easy" is a toggle in GNU Chess, so don't send
8754 it without being sure what state we are in first. "hard"
8755 is not a toggle, so that one is OK.
8757 SendToProgram("easy\n", cps);
8760 sprintf(buf, "ping %d\n", ++cps->lastPing);
8761 SendToProgram(buf, cps);
8763 cps->initDone = TRUE;
8768 StartChessProgram(cps)
8769 ChessProgramState *cps;
8774 if (appData.noChessProgram) return;
8775 cps->initDone = FALSE;
8777 if (strcmp(cps->host, "localhost") == 0) {
8778 err = StartChildProcess(cps->program, cps->dir, &cps->pr);
8779 } else if (*appData.remoteShell == NULLCHAR) {
8780 err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
8782 if (*appData.remoteUser == NULLCHAR) {
8783 snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
8786 snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
8787 cps->host, appData.remoteUser, cps->program);
8789 err = StartChildProcess(buf, "", &cps->pr);
8793 sprintf(buf, _("Startup failure on '%s'"), cps->program);
8794 DisplayFatalError(buf, err, 1);
8800 cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
8801 if (cps->protocolVersion > 1) {
8802 sprintf(buf, "xboard\nprotover %d\n", cps->protocolVersion);
8803 cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
8804 cps->comboCnt = 0; // and values of combo boxes
8805 SendToProgram(buf, cps);
8807 SendToProgram("xboard\n", cps);
8813 TwoMachinesEventIfReady P((void))
8815 if (first.lastPing != first.lastPong) {
8816 DisplayMessage("", _("Waiting for first chess program"));
8817 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
8820 if (second.lastPing != second.lastPong) {
8821 DisplayMessage("", _("Waiting for second chess program"));
8822 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
8830 NextMatchGame P((void))
8832 int index; /* [HGM] autoinc: step load index during match */
8834 if (*appData.loadGameFile != NULLCHAR) {
8835 index = appData.loadGameIndex;
8836 if(index < 0) { // [HGM] autoinc
8837 lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
8838 if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
8840 LoadGameFromFile(appData.loadGameFile,
8842 appData.loadGameFile, FALSE);
8843 } else if (*appData.loadPositionFile != NULLCHAR) {
8844 index = appData.loadPositionIndex;
8845 if(index < 0) { // [HGM] autoinc
8846 lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
8847 if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
8849 LoadPositionFromFile(appData.loadPositionFile,
8851 appData.loadPositionFile);
8853 TwoMachinesEventIfReady();
8856 void UserAdjudicationEvent( int result )
8858 ChessMove gameResult = GameIsDrawn;
8861 gameResult = WhiteWins;
8863 else if( result < 0 ) {
8864 gameResult = BlackWins;
8867 if( gameMode == TwoMachinesPlay ) {
8868 GameEnds( gameResult, "User adjudication", GE_XBOARD );
8873 // [HGM] save: calculate checksum of game to make games easily identifiable
8874 int StringCheckSum(char *s)
8877 if(s==NULL) return 0;
8878 while(*s) i = i*259 + *s++;
8885 for(i=backwardMostMove; i<forwardMostMove; i++) {
8886 sum += pvInfoList[i].depth;
8887 sum += StringCheckSum(parseList[i]);
8888 sum += StringCheckSum(commentList[i]);
8891 if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
8892 return sum + StringCheckSum(commentList[i]);
8893 } // end of save patch
8896 GameEnds(result, resultDetails, whosays)
8898 char *resultDetails;
8901 GameMode nextGameMode;
8905 if(endingGame) return; /* [HGM] crash: forbid recursion */
8908 if (appData.debugMode) {
8909 fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
8910 result, resultDetails ? resultDetails : "(null)", whosays);
8913 if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
8914 /* If we are playing on ICS, the server decides when the
8915 game is over, but the engine can offer to draw, claim
8919 if (appData.zippyPlay && first.initDone) {
8920 if (result == GameIsDrawn) {
8921 /* In case draw still needs to be claimed */
8922 SendToICS(ics_prefix);
8923 SendToICS("draw\n");
8924 } else if (StrCaseStr(resultDetails, "resign")) {
8925 SendToICS(ics_prefix);
8926 SendToICS("resign\n");
8930 endingGame = 0; /* [HGM] crash */
8934 /* If we're loading the game from a file, stop */
8935 if (whosays == GE_FILE) {
8936 (void) StopLoadGameTimer();
8940 /* Cancel draw offers */
8941 first.offeredDraw = second.offeredDraw = 0;
8943 /* If this is an ICS game, only ICS can really say it's done;
8944 if not, anyone can. */
8945 isIcsGame = (gameMode == IcsPlayingWhite ||
8946 gameMode == IcsPlayingBlack ||
8947 gameMode == IcsObserving ||
8948 gameMode == IcsExamining);
8950 if (!isIcsGame || whosays == GE_ICS) {
8951 /* OK -- not an ICS game, or ICS said it was done */
8953 if (!isIcsGame && !appData.noChessProgram)
8954 SetUserThinkingEnables();
8956 /* [HGM] if a machine claims the game end we verify this claim */
8957 if(gameMode == TwoMachinesPlay && appData.testClaims) {
8958 if(appData.testLegality && whosays >= GE_ENGINE1 ) {
8960 ChessMove trueResult = (ChessMove) -1;
8962 claimer = whosays == GE_ENGINE1 ? /* color of claimer */
8963 first.twoMachinesColor[0] :
8964 second.twoMachinesColor[0] ;
8966 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
8967 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
8968 /* [HGM] verify: engine mate claims accepted if they were flagged */
8969 trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
8971 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
8972 /* [HGM] verify: engine mate claims accepted if they were flagged */
8973 trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8975 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
8976 trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
8979 // now verify win claims, but not in drop games, as we don't understand those yet
8980 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
8981 || gameInfo.variant == VariantGreat) &&
8982 (result == WhiteWins && claimer == 'w' ||
8983 result == BlackWins && claimer == 'b' ) ) { // case to verify: engine claims own win
8984 if (appData.debugMode) {
8985 fprintf(debugFP, "result=%d sp=%d move=%d\n",
8986 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
8988 if(result != trueResult) {
8989 sprintf(buf, "False win claim: '%s'", resultDetails);
8990 result = claimer == 'w' ? BlackWins : WhiteWins;
8991 resultDetails = buf;
8994 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
8995 && (forwardMostMove <= backwardMostMove ||
8996 (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
8997 (claimer=='b')==(forwardMostMove&1))
8999 /* [HGM] verify: draws that were not flagged are false claims */
9000 sprintf(buf, "False draw claim: '%s'", resultDetails);
9001 result = claimer == 'w' ? BlackWins : WhiteWins;
9002 resultDetails = buf;
9004 /* (Claiming a loss is accepted no questions asked!) */
9007 /* [HGM] bare: don't allow bare King to win */
9008 if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
9009 && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
9010 && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
9011 && result != GameIsDrawn)
9012 { int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
9013 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
9014 int p = (signed char)boards[forwardMostMove][i][j] - color;
9015 if(p >= 0 && p <= (int)WhiteKing) k++;
9017 if (appData.debugMode) {
9018 fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
9019 result, resultDetails ? resultDetails : "(null)", whosays, k, color);
9022 result = GameIsDrawn;
9023 sprintf(buf, "%s but bare king", resultDetails);
9024 resultDetails = buf;
9029 if(serverMoves != NULL && !loadFlag) { char c = '=';
9030 if(result==WhiteWins) c = '+';
9031 if(result==BlackWins) c = '-';
9032 if(resultDetails != NULL)
9033 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
9035 if (resultDetails != NULL) {
9036 gameInfo.result = result;
9037 gameInfo.resultDetails = StrSave(resultDetails);
9039 /* display last move only if game was not loaded from file */
9040 if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
9041 DisplayMove(currentMove - 1);
9043 if (forwardMostMove != 0) {
9044 if (gameMode != PlayFromGameFile && gameMode != EditGame
9045 && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
9047 if (*appData.saveGameFile != NULLCHAR) {
9048 SaveGameToFile(appData.saveGameFile, TRUE);
9049 } else if (appData.autoSaveGames) {
9052 if (*appData.savePositionFile != NULLCHAR) {
9053 SavePositionToFile(appData.savePositionFile);
9058 /* Tell program how game ended in case it is learning */
9059 /* [HGM] Moved this to after saving the PGN, just in case */
9060 /* engine died and we got here through time loss. In that */
9061 /* case we will get a fatal error writing the pipe, which */
9062 /* would otherwise lose us the PGN. */
9063 /* [HGM] crash: not needed anymore, but doesn't hurt; */
9064 /* output during GameEnds should never be fatal anymore */
9065 if (gameMode == MachinePlaysWhite ||
9066 gameMode == MachinePlaysBlack ||
9067 gameMode == TwoMachinesPlay ||
9068 gameMode == IcsPlayingWhite ||
9069 gameMode == IcsPlayingBlack ||
9070 gameMode == BeginningOfGame) {
9072 sprintf(buf, "result %s {%s}\n", PGNResult(result),
9074 if (first.pr != NoProc) {
9075 SendToProgram(buf, &first);
9077 if (second.pr != NoProc &&
9078 gameMode == TwoMachinesPlay) {
9079 SendToProgram(buf, &second);
9084 if (appData.icsActive) {
9085 if (appData.quietPlay &&
9086 (gameMode == IcsPlayingWhite ||
9087 gameMode == IcsPlayingBlack)) {
9088 SendToICS(ics_prefix);
9089 SendToICS("set shout 1\n");
9091 nextGameMode = IcsIdle;
9092 ics_user_moved = FALSE;
9093 /* clean up premove. It's ugly when the game has ended and the
9094 * premove highlights are still on the board.
9098 ClearPremoveHighlights();
9099 DrawPosition(FALSE, boards[currentMove]);
9101 if (whosays == GE_ICS) {
9104 if (gameMode == IcsPlayingWhite)
9106 else if(gameMode == IcsPlayingBlack)
9110 if (gameMode == IcsPlayingBlack)
9112 else if(gameMode == IcsPlayingWhite)
9119 PlayIcsUnfinishedSound();
9122 } else if (gameMode == EditGame ||
9123 gameMode == PlayFromGameFile ||
9124 gameMode == AnalyzeMode ||
9125 gameMode == AnalyzeFile) {
9126 nextGameMode = gameMode;
9128 nextGameMode = EndOfGame;
9133 nextGameMode = gameMode;
9136 if (appData.noChessProgram) {
9137 gameMode = nextGameMode;
9139 endingGame = 0; /* [HGM] crash */
9144 /* Put first chess program into idle state */
9145 if (first.pr != NoProc &&
9146 (gameMode == MachinePlaysWhite ||
9147 gameMode == MachinePlaysBlack ||
9148 gameMode == TwoMachinesPlay ||
9149 gameMode == IcsPlayingWhite ||
9150 gameMode == IcsPlayingBlack ||
9151 gameMode == BeginningOfGame)) {
9152 SendToProgram("force\n", &first);
9153 if (first.usePing) {
9155 sprintf(buf, "ping %d\n", ++first.lastPing);
9156 SendToProgram(buf, &first);
9159 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
9160 /* Kill off first chess program */
9161 if (first.isr != NULL)
9162 RemoveInputSource(first.isr);
9165 if (first.pr != NoProc) {
9167 DoSleep( appData.delayBeforeQuit );
9168 SendToProgram("quit\n", &first);
9169 DoSleep( appData.delayAfterQuit );
9170 DestroyChildProcess(first.pr, first.useSigterm);
9175 /* Put second chess program into idle state */
9176 if (second.pr != NoProc &&
9177 gameMode == TwoMachinesPlay) {
9178 SendToProgram("force\n", &second);
9179 if (second.usePing) {
9181 sprintf(buf, "ping %d\n", ++second.lastPing);
9182 SendToProgram(buf, &second);
9185 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
9186 /* Kill off second chess program */
9187 if (second.isr != NULL)
9188 RemoveInputSource(second.isr);
9191 if (second.pr != NoProc) {
9192 DoSleep( appData.delayBeforeQuit );
9193 SendToProgram("quit\n", &second);
9194 DoSleep( appData.delayAfterQuit );
9195 DestroyChildProcess(second.pr, second.useSigterm);
9200 if (matchMode && gameMode == TwoMachinesPlay) {
9203 if (first.twoMachinesColor[0] == 'w') {
9210 if (first.twoMachinesColor[0] == 'b') {
9219 if (matchGame < appData.matchGames) {
9221 if(appData.sameColorGames <= 1) { /* [HGM] alternate: suppress color swap */
9222 tmp = first.twoMachinesColor;
9223 first.twoMachinesColor = second.twoMachinesColor;
9224 second.twoMachinesColor = tmp;
9226 gameMode = nextGameMode;
9228 if(appData.matchPause>10000 || appData.matchPause<10)
9229 appData.matchPause = 10000; /* [HGM] make pause adjustable */
9230 ScheduleDelayedEvent(NextMatchGame, appData.matchPause);
9231 endingGame = 0; /* [HGM] crash */
9235 gameMode = nextGameMode;
9236 sprintf(buf, _("Match %s vs. %s: final score %d-%d-%d"),
9237 first.tidy, second.tidy,
9238 first.matchWins, second.matchWins,
9239 appData.matchGames - (first.matchWins + second.matchWins));
9240 DisplayFatalError(buf, 0, 0);
9243 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
9244 !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
9246 gameMode = nextGameMode;
9248 endingGame = 0; /* [HGM] crash */
9251 /* Assumes program was just initialized (initString sent).
9252 Leaves program in force mode. */
9254 FeedMovesToProgram(cps, upto)
9255 ChessProgramState *cps;
9260 if (appData.debugMode)
9261 fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
9262 startedFromSetupPosition ? "position and " : "",
9263 backwardMostMove, upto, cps->which);
9264 if(currentlyInitializedVariant != gameInfo.variant) { char buf[MSG_SIZ];
9265 // [HGM] variantswitch: make engine aware of new variant
9266 if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
9267 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
9268 sprintf(buf, "variant %s\n", VariantName(gameInfo.variant));
9269 SendToProgram(buf, cps);
9270 currentlyInitializedVariant = gameInfo.variant;
9272 SendToProgram("force\n", cps);
9273 if (startedFromSetupPosition) {
9274 SendBoard(cps, backwardMostMove);
9275 if (appData.debugMode) {
9276 fprintf(debugFP, "feedMoves\n");
9279 for (i = backwardMostMove; i < upto; i++) {
9280 SendMoveToProgram(i, cps);
9286 ResurrectChessProgram()
9288 /* The chess program may have exited.
9289 If so, restart it and feed it all the moves made so far. */
9291 if (appData.noChessProgram || first.pr != NoProc) return;
9293 StartChessProgram(&first);
9294 InitChessProgram(&first, FALSE);
9295 FeedMovesToProgram(&first, currentMove);
9297 if (!first.sendTime) {
9298 /* can't tell gnuchess what its clock should read,
9299 so we bow to its notion. */
9301 timeRemaining[0][currentMove] = whiteTimeRemaining;
9302 timeRemaining[1][currentMove] = blackTimeRemaining;
9305 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
9306 appData.icsEngineAnalyze) && first.analysisSupport) {
9307 SendToProgram("analyze\n", &first);
9308 first.analyzing = TRUE;
9321 if (appData.debugMode) {
9322 fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
9323 redraw, init, gameMode);
9325 CleanupTail(); // [HGM] vari: delete any stored variations
9326 pausing = pauseExamInvalid = FALSE;
9327 startedFromSetupPosition = blackPlaysFirst = FALSE;
9329 whiteFlag = blackFlag = FALSE;
9330 userOfferedDraw = FALSE;
9331 hintRequested = bookRequested = FALSE;
9332 first.maybeThinking = FALSE;
9333 second.maybeThinking = FALSE;
9334 first.bookSuspend = FALSE; // [HGM] book
9335 second.bookSuspend = FALSE;
9336 thinkOutput[0] = NULLCHAR;
9337 lastHint[0] = NULLCHAR;
9338 ClearGameInfo(&gameInfo);
9339 gameInfo.variant = StringToVariant(appData.variant);
9340 ics_user_moved = ics_clock_paused = FALSE;
9341 ics_getting_history = H_FALSE;
9343 white_holding[0] = black_holding[0] = NULLCHAR;
9344 ClearProgramStats();
9345 opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
9349 flipView = appData.flipView;
9350 ClearPremoveHighlights();
9352 alarmSounded = FALSE;
9354 GameEnds((ChessMove) 0, NULL, GE_PLAYER);
9355 if(appData.serverMovesName != NULL) {
9356 /* [HGM] prepare to make moves file for broadcasting */
9357 clock_t t = clock();
9358 if(serverMoves != NULL) fclose(serverMoves);
9359 serverMoves = fopen(appData.serverMovesName, "r");
9360 if(serverMoves != NULL) {
9361 fclose(serverMoves);
9362 /* delay 15 sec before overwriting, so all clients can see end */
9363 while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
9365 serverMoves = fopen(appData.serverMovesName, "w");
9369 gameMode = BeginningOfGame;
9372 if(appData.icsActive) gameInfo.variant = VariantNormal;
9373 currentMove = forwardMostMove = backwardMostMove = 0;
9374 InitPosition(redraw);
9375 for (i = 0; i < MAX_MOVES; i++) {
9376 if (commentList[i] != NULL) {
9377 free(commentList[i]);
9378 commentList[i] = NULL;
9383 timeRemaining[0][0] = whiteTimeRemaining;
9384 timeRemaining[1][0] = blackTimeRemaining;
9385 if (first.pr == NULL) {
9386 StartChessProgram(&first);
9389 InitChessProgram(&first, startedFromSetupPosition);
9393 DisplayMessage("", "");
9394 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
9395 lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
9403 if (!AutoPlayOneMove())
9405 if (matchMode || appData.timeDelay == 0)
9407 if (appData.timeDelay < 0 || gameMode == AnalyzeFile)
9409 StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
9418 int fromX, fromY, toX, toY;
9420 if (appData.debugMode) {
9421 fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
9424 if (gameMode != PlayFromGameFile)
9427 if (currentMove >= forwardMostMove) {
9428 gameMode = EditGame;
9431 /* [AS] Clear current move marker at the end of a game */
9432 /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
9437 toX = moveList[currentMove][2] - AAA;
9438 toY = moveList[currentMove][3] - ONE;
9440 if (moveList[currentMove][1] == '@') {
9441 if (appData.highlightLastMove) {
9442 SetHighlights(-1, -1, toX, toY);
9445 fromX = moveList[currentMove][0] - AAA;
9446 fromY = moveList[currentMove][1] - ONE;
9448 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
9450 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
9452 if (appData.highlightLastMove) {
9453 SetHighlights(fromX, fromY, toX, toY);
9456 DisplayMove(currentMove);
9457 SendMoveToProgram(currentMove++, &first);
9458 DisplayBothClocks();
9459 DrawPosition(FALSE, boards[currentMove]);
9460 // [HGM] PV info: always display, routine tests if empty
9461 DisplayComment(currentMove - 1, commentList[currentMove]);
9467 LoadGameOneMove(readAhead)
9468 ChessMove readAhead;
9470 int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
9471 char promoChar = NULLCHAR;
9476 if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
9477 gameMode != AnalyzeMode && gameMode != Training) {
9482 yyboardindex = forwardMostMove;
9483 if (readAhead != (ChessMove)0) {
9484 moveType = readAhead;
9486 if (gameFileFP == NULL)
9488 moveType = (ChessMove) yylex();
9494 if (appData.debugMode)
9495 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9498 /* append the comment but don't display it */
9499 AppendComment(currentMove, p, FALSE);
9502 case WhiteCapturesEnPassant:
9503 case BlackCapturesEnPassant:
9504 case WhitePromotionChancellor:
9505 case BlackPromotionChancellor:
9506 case WhitePromotionArchbishop:
9507 case BlackPromotionArchbishop:
9508 case WhitePromotionCentaur:
9509 case BlackPromotionCentaur:
9510 case WhitePromotionQueen:
9511 case BlackPromotionQueen:
9512 case WhitePromotionRook:
9513 case BlackPromotionRook:
9514 case WhitePromotionBishop:
9515 case BlackPromotionBishop:
9516 case WhitePromotionKnight:
9517 case BlackPromotionKnight:
9518 case WhitePromotionKing:
9519 case BlackPromotionKing:
9521 case WhiteKingSideCastle:
9522 case WhiteQueenSideCastle:
9523 case BlackKingSideCastle:
9524 case BlackQueenSideCastle:
9525 case WhiteKingSideCastleWild:
9526 case WhiteQueenSideCastleWild:
9527 case BlackKingSideCastleWild:
9528 case BlackQueenSideCastleWild:
9530 case WhiteHSideCastleFR:
9531 case WhiteASideCastleFR:
9532 case BlackHSideCastleFR:
9533 case BlackASideCastleFR:
9535 if (appData.debugMode)
9536 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
9537 fromX = currentMoveString[0] - AAA;
9538 fromY = currentMoveString[1] - ONE;
9539 toX = currentMoveString[2] - AAA;
9540 toY = currentMoveString[3] - ONE;
9541 promoChar = currentMoveString[4];
9546 if (appData.debugMode)
9547 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
9548 fromX = moveType == WhiteDrop ?
9549 (int) CharToPiece(ToUpper(currentMoveString[0])) :
9550 (int) CharToPiece(ToLower(currentMoveString[0]));
9552 toX = currentMoveString[2] - AAA;
9553 toY = currentMoveString[3] - ONE;
9559 case GameUnfinished:
9560 if (appData.debugMode)
9561 fprintf(debugFP, "Parsed game end: %s\n", yy_text);
9562 p = strchr(yy_text, '{');
9563 if (p == NULL) p = strchr(yy_text, '(');
9566 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
9568 q = strchr(p, *p == '{' ? '}' : ')');
9569 if (q != NULL) *q = NULLCHAR;
9572 GameEnds(moveType, p, GE_FILE);
9574 if (cmailMsgLoaded) {
9576 flipView = WhiteOnMove(currentMove);
9577 if (moveType == GameUnfinished) flipView = !flipView;
9578 if (appData.debugMode)
9579 fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
9583 case (ChessMove) 0: /* end of file */
9584 if (appData.debugMode)
9585 fprintf(debugFP, "Parser hit end of file\n");
9586 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9592 if (WhiteOnMove(currentMove)) {
9593 GameEnds(BlackWins, "Black mates", GE_FILE);
9595 GameEnds(WhiteWins, "White mates", GE_FILE);
9599 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
9606 if (lastLoadGameStart == GNUChessGame) {
9607 /* GNUChessGames have numbers, but they aren't move numbers */
9608 if (appData.debugMode)
9609 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
9610 yy_text, (int) moveType);
9611 return LoadGameOneMove((ChessMove)0); /* tail recursion */
9613 /* else fall thru */
9618 /* Reached start of next game in file */
9619 if (appData.debugMode)
9620 fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
9621 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9627 if (WhiteOnMove(currentMove)) {
9628 GameEnds(BlackWins, "Black mates", GE_FILE);
9630 GameEnds(WhiteWins, "White mates", GE_FILE);
9634 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
9640 case PositionDiagram: /* should not happen; ignore */
9641 case ElapsedTime: /* ignore */
9642 case NAG: /* ignore */
9643 if (appData.debugMode)
9644 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
9645 yy_text, (int) moveType);
9646 return LoadGameOneMove((ChessMove)0); /* tail recursion */
9649 if (appData.testLegality) {
9650 if (appData.debugMode)
9651 fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
9652 sprintf(move, _("Illegal move: %d.%s%s"),
9653 (forwardMostMove / 2) + 1,
9654 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9655 DisplayError(move, 0);
9658 if (appData.debugMode)
9659 fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
9660 yy_text, currentMoveString);
9661 fromX = currentMoveString[0] - AAA;
9662 fromY = currentMoveString[1] - ONE;
9663 toX = currentMoveString[2] - AAA;
9664 toY = currentMoveString[3] - ONE;
9665 promoChar = currentMoveString[4];
9670 if (appData.debugMode)
9671 fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
9672 sprintf(move, _("Ambiguous move: %d.%s%s"),
9673 (forwardMostMove / 2) + 1,
9674 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9675 DisplayError(move, 0);
9680 case ImpossibleMove:
9681 if (appData.debugMode)
9682 fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
9683 sprintf(move, _("Illegal move: %d.%s%s"),
9684 (forwardMostMove / 2) + 1,
9685 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9686 DisplayError(move, 0);
9692 if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
9693 DrawPosition(FALSE, boards[currentMove]);
9694 DisplayBothClocks();
9695 if (!appData.matchMode) // [HGM] PV info: routine tests if empty
9696 DisplayComment(currentMove - 1, commentList[currentMove]);
9698 (void) StopLoadGameTimer();
9700 cmailOldMove = forwardMostMove;
9703 /* currentMoveString is set as a side-effect of yylex */
9704 strcat(currentMoveString, "\n");
9705 strcpy(moveList[forwardMostMove], currentMoveString);
9707 thinkOutput[0] = NULLCHAR;
9708 MakeMove(fromX, fromY, toX, toY, promoChar);
9709 currentMove = forwardMostMove;
9714 /* Load the nth game from the given file */
9716 LoadGameFromFile(filename, n, title, useList)
9720 /*Boolean*/ int useList;
9725 if (strcmp(filename, "-") == 0) {
9729 f = fopen(filename, "rb");
9731 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9732 DisplayError(buf, errno);
9736 if (fseek(f, 0, 0) == -1) {
9737 /* f is not seekable; probably a pipe */
9740 if (useList && n == 0) {
9741 int error = GameListBuild(f);
9743 DisplayError(_("Cannot build game list"), error);
9744 } else if (!ListEmpty(&gameList) &&
9745 ((ListGame *) gameList.tailPred)->number > 1) {
9746 // TODO convert to GTK
9747 // GameListPopUp(f, title);
9754 return LoadGame(f, n, title, FALSE);
9759 MakeRegisteredMove()
9761 int fromX, fromY, toX, toY;
9763 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
9764 switch (cmailMoveType[lastLoadGameNumber - 1]) {
9767 if (appData.debugMode)
9768 fprintf(debugFP, "Restoring %s for game %d\n",
9769 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
9771 thinkOutput[0] = NULLCHAR;
9772 strcpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1]);
9773 fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
9774 fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
9775 toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
9776 toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
9777 promoChar = cmailMove[lastLoadGameNumber - 1][4];
9778 MakeMove(fromX, fromY, toX, toY, promoChar);
9779 ShowMove(fromX, fromY, toX, toY);
9780 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9787 if (WhiteOnMove(currentMove)) {
9788 GameEnds(BlackWins, "Black mates", GE_PLAYER);
9790 GameEnds(WhiteWins, "White mates", GE_PLAYER);
9795 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
9802 if (WhiteOnMove(currentMove)) {
9803 GameEnds(BlackWins, "White resigns", GE_PLAYER);
9805 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
9810 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
9821 /* Wrapper around LoadGame for use when a Cmail message is loaded */
9823 CmailLoadGame(f, gameNumber, title, useList)
9831 if (gameNumber > nCmailGames) {
9832 DisplayError(_("No more games in this message"), 0);
9835 if (f == lastLoadGameFP) {
9836 int offset = gameNumber - lastLoadGameNumber;
9838 cmailMsg[0] = NULLCHAR;
9839 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
9840 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
9841 nCmailMovesRegistered--;
9843 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
9844 if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
9845 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
9848 if (! RegisterMove()) return FALSE;
9852 retVal = LoadGame(f, gameNumber, title, useList);
9854 /* Make move registered during previous look at this game, if any */
9855 MakeRegisteredMove();
9857 if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
9858 commentList[currentMove]
9859 = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
9860 DisplayComment(currentMove - 1, commentList[currentMove]);
9866 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
9871 int gameNumber = lastLoadGameNumber + offset;
9872 if (lastLoadGameFP == NULL) {
9873 DisplayError(_("No game has been loaded yet"), 0);
9876 if (gameNumber <= 0) {
9877 DisplayError(_("Can't back up any further"), 0);
9880 if (cmailMsgLoaded) {
9881 return CmailLoadGame(lastLoadGameFP, gameNumber,
9882 lastLoadGameTitle, lastLoadGameUseList);
9884 return LoadGame(lastLoadGameFP, gameNumber,
9885 lastLoadGameTitle, lastLoadGameUseList);
9891 /* Load the nth game from open file f */
9893 LoadGame(f, gameNumber, title, useList)
9901 int gn = gameNumber;
9902 ListGame *lg = NULL;
9905 GameMode oldGameMode;
9906 VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
9908 if (appData.debugMode)
9909 fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
9911 if (gameMode == Training )
9912 SetTrainingModeOff();
9914 oldGameMode = gameMode;
9915 if (gameMode != BeginningOfGame)
9921 if (lastLoadGameFP != NULL && lastLoadGameFP != f)
9923 fclose(lastLoadGameFP);
9928 lg = (ListGame *) ListElem(&gameList, gameNumber-1);
9932 fseek(f, lg->offset, 0);
9933 GameListHighlight(gameNumber);
9938 DisplayError(_("Game number out of range"), 0);
9945 if (fseek(f, 0, 0) == -1)
9947 if (f == lastLoadGameFP ?
9948 gameNumber == lastLoadGameNumber + 1 :
9955 DisplayError(_("Can't seek on game file"), 0);
9962 lastLoadGameNumber = gameNumber;
9963 strcpy(lastLoadGameTitle, title);
9964 lastLoadGameUseList = useList;
9968 if (lg && lg->gameInfo.white && lg->gameInfo.black)
9970 snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
9971 lg->gameInfo.black);
9974 else if (*title != NULLCHAR)
9978 sprintf(buf, "%s %d", title, gameNumber);
9983 DisplayTitle(title);
9987 if (gameMode != AnalyzeFile && gameMode != AnalyzeMode)
9989 gameMode = PlayFromGameFile;
9993 currentMove = forwardMostMove = backwardMostMove = 0;
9994 CopyBoard(boards[0], initialPosition);
9998 * Skip the first gn-1 games in the file.
9999 * Also skip over anything that precedes an identifiable
10000 * start of game marker, to avoid being confused by
10001 * garbage at the start of the file. Currently
10002 * recognized start of game markers are the move number "1",
10003 * the pattern "gnuchess .* game", the pattern
10004 * "^[#;%] [^ ]* game file", and a PGN tag block.
10005 * A game that starts with one of the latter two patterns
10006 * will also have a move number 1, possibly
10007 * following a position diagram.
10008 * 5-4-02: Let's try being more lenient and allowing a game to
10009 * start with an unnumbered move. Does that break anything?
10011 cm = lastLoadGameStart = (ChessMove) 0;
10013 yyboardindex = forwardMostMove;
10014 cm = (ChessMove) yylex();
10016 case (ChessMove) 0:
10017 if (cmailMsgLoaded) {
10018 nCmailGames = CMAIL_MAX_GAMES - gn;
10021 DisplayError(_("Game not found in file"), 0);
10028 lastLoadGameStart = cm;
10031 case MoveNumberOne:
10032 switch (lastLoadGameStart) {
10037 case MoveNumberOne:
10038 case (ChessMove) 0:
10039 gn--; /* count this game */
10040 lastLoadGameStart = cm;
10049 switch (lastLoadGameStart) {
10052 case MoveNumberOne:
10053 case (ChessMove) 0:
10054 gn--; /* count this game */
10055 lastLoadGameStart = cm;
10058 lastLoadGameStart = cm; /* game counted already */
10066 yyboardindex = forwardMostMove;
10067 cm = (ChessMove) yylex();
10068 } while (cm == PGNTag || cm == Comment);
10075 if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
10076 if ( cmailResult[CMAIL_MAX_GAMES - gn - 1]
10077 != CMAIL_OLD_RESULT) {
10079 cmailResult[ CMAIL_MAX_GAMES
10080 - gn - 1] = CMAIL_OLD_RESULT;
10086 /* Only a NormalMove can be at the start of a game
10087 * without a position diagram. */
10088 if (lastLoadGameStart == (ChessMove) 0) {
10090 lastLoadGameStart = MoveNumberOne;
10099 if (appData.debugMode)
10100 fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
10102 if (cm == XBoardGame) {
10103 /* Skip any header junk before position diagram and/or move 1 */
10105 yyboardindex = forwardMostMove;
10106 cm = (ChessMove) yylex();
10108 if (cm == (ChessMove) 0 ||
10109 cm == GNUChessGame || cm == XBoardGame) {
10110 /* Empty game; pretend end-of-file and handle later */
10111 cm = (ChessMove) 0;
10115 if (cm == MoveNumberOne || cm == PositionDiagram ||
10116 cm == PGNTag || cm == Comment)
10119 } else if (cm == GNUChessGame) {
10120 if (gameInfo.event != NULL) {
10121 free(gameInfo.event);
10123 gameInfo.event = StrSave(yy_text);
10126 startedFromSetupPosition = FALSE;
10127 while (cm == PGNTag) {
10128 if (appData.debugMode)
10129 fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
10130 err = ParsePGNTag(yy_text, &gameInfo);
10131 if (!err) numPGNTags++;
10133 /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
10134 if(gameInfo.variant != oldVariant) {
10135 startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
10136 InitPosition(TRUE);
10137 oldVariant = gameInfo.variant;
10138 if (appData.debugMode)
10139 fprintf(debugFP, "New variant %d\n", (int) oldVariant);
10143 if (gameInfo.fen != NULL) {
10144 Board initial_position;
10145 startedFromSetupPosition = TRUE;
10146 if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
10148 DisplayError(_("Bad FEN position in file"), 0);
10151 CopyBoard(boards[0], initial_position);
10152 if (blackPlaysFirst) {
10153 currentMove = forwardMostMove = backwardMostMove = 1;
10154 CopyBoard(boards[1], initial_position);
10155 strcpy(moveList[0], "");
10156 strcpy(parseList[0], "");
10157 timeRemaining[0][1] = whiteTimeRemaining;
10158 timeRemaining[1][1] = blackTimeRemaining;
10159 if (commentList[0] != NULL) {
10160 commentList[1] = commentList[0];
10161 commentList[0] = NULL;
10164 currentMove = forwardMostMove = backwardMostMove = 0;
10166 /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
10168 initialRulePlies = FENrulePlies;
10169 for( i=0; i< nrCastlingRights; i++ )
10170 initialRights[i] = initial_position[CASTLING][i];
10172 yyboardindex = forwardMostMove;
10173 free(gameInfo.fen);
10174 gameInfo.fen = NULL;
10177 yyboardindex = forwardMostMove;
10178 cm = (ChessMove) yylex();
10180 /* Handle comments interspersed among the tags */
10181 while (cm == Comment) {
10183 if (appData.debugMode)
10184 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10186 AppendComment(currentMove, p, FALSE);
10187 yyboardindex = forwardMostMove;
10188 cm = (ChessMove) yylex();
10192 /* don't rely on existence of Event tag since if game was
10193 * pasted from clipboard the Event tag may not exist
10195 if (numPGNTags > 0){
10197 if (gameInfo.variant == VariantNormal) {
10198 gameInfo.variant = StringToVariant(gameInfo.event);
10201 if( appData.autoDisplayTags ) {
10202 tags = PGNTags(&gameInfo);
10203 TagsPopUp(tags, CmailMsg());
10208 /* Make something up, but don't display it now */
10213 if (cm == PositionDiagram) {
10216 Board initial_position;
10218 if (appData.debugMode)
10219 fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
10221 if (!startedFromSetupPosition) {
10223 for (i = BOARD_HEIGHT - 1; i >= 0; i--)
10224 for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
10234 initial_position[i][j++] = CharToPiece(*p);
10237 while (*p == ' ' || *p == '\t' ||
10238 *p == '\n' || *p == '\r') p++;
10240 if (strncmp(p, "black", strlen("black"))==0)
10241 blackPlaysFirst = TRUE;
10243 blackPlaysFirst = FALSE;
10244 startedFromSetupPosition = TRUE;
10246 CopyBoard(boards[0], initial_position);
10247 if (blackPlaysFirst) {
10248 currentMove = forwardMostMove = backwardMostMove = 1;
10249 CopyBoard(boards[1], initial_position);
10250 strcpy(moveList[0], "");
10251 strcpy(parseList[0], "");
10252 timeRemaining[0][1] = whiteTimeRemaining;
10253 timeRemaining[1][1] = blackTimeRemaining;
10254 if (commentList[0] != NULL) {
10255 commentList[1] = commentList[0];
10256 commentList[0] = NULL;
10259 currentMove = forwardMostMove = backwardMostMove = 0;
10262 yyboardindex = forwardMostMove;
10263 cm = (ChessMove) yylex();
10266 if (first.pr == NoProc) {
10267 StartChessProgram(&first);
10269 InitChessProgram(&first, FALSE);
10270 SendToProgram("force\n", &first);
10271 if (startedFromSetupPosition) {
10272 SendBoard(&first, forwardMostMove);
10273 if (appData.debugMode) {
10274 fprintf(debugFP, "Load Game\n");
10276 DisplayBothClocks();
10279 /* [HGM] server: flag to write setup moves in broadcast file as one */
10280 loadFlag = appData.suppressLoadMoves;
10282 while (cm == Comment) {
10284 if (appData.debugMode)
10285 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10287 AppendComment(currentMove, p, FALSE);
10288 yyboardindex = forwardMostMove;
10289 cm = (ChessMove) yylex();
10292 if ((cm == (ChessMove) 0 && lastLoadGameStart != (ChessMove) 0) ||
10293 cm == WhiteWins || cm == BlackWins ||
10294 cm == GameIsDrawn || cm == GameUnfinished) {
10295 DisplayMessage("", _("No moves in game"));
10296 if (cmailMsgLoaded) {
10297 if (appData.debugMode)
10298 fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
10302 DrawPosition(FALSE, boards[currentMove]);
10303 DisplayBothClocks();
10304 gameMode = EditGame;
10311 // [HGM] PV info: routine tests if comment empty
10312 if (!matchMode && (pausing || appData.timeDelay != 0)) {
10313 DisplayComment(currentMove - 1, commentList[currentMove]);
10315 if (!matchMode && appData.timeDelay != 0)
10316 DrawPosition(FALSE, boards[currentMove]);
10318 if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
10319 programStats.ok_to_send = 1;
10322 /* if the first token after the PGN tags is a move
10323 * and not move number 1, retrieve it from the parser
10325 if (cm != MoveNumberOne)
10326 LoadGameOneMove(cm);
10328 /* load the remaining moves from the file */
10329 while (LoadGameOneMove((ChessMove)0)) {
10330 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10331 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10334 /* rewind to the start of the game */
10335 currentMove = backwardMostMove;
10337 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10339 if (oldGameMode == AnalyzeFile ||
10340 oldGameMode == AnalyzeMode) {
10341 AnalyzeFileEvent();
10344 if (matchMode || appData.timeDelay == 0) {
10346 gameMode = EditGame;
10348 } else if (appData.timeDelay > 0) {
10349 AutoPlayGameLoop();
10352 if (appData.debugMode)
10353 fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
10355 loadFlag = 0; /* [HGM] true game starts */
10359 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
10361 ReloadPosition(offset)
10364 int positionNumber = lastLoadPositionNumber + offset;
10365 if (lastLoadPositionFP == NULL) {
10366 DisplayError(_("No position has been loaded yet"), 0);
10369 if (positionNumber <= 0) {
10370 DisplayError(_("Can't back up any further"), 0);
10373 return LoadPosition(lastLoadPositionFP, positionNumber,
10374 lastLoadPositionTitle);
10377 /* Load the nth position from the given file */
10379 LoadPositionFromFile(filename, n, title)
10387 if (strcmp(filename, "-") == 0) {
10388 return LoadPosition(stdin, n, "stdin");
10390 f = fopen(filename, "rb");
10392 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10393 DisplayError(buf, errno);
10396 return LoadPosition(f, n, title);
10401 /* Load the nth position from the given open file, and close it */
10403 LoadPosition(f, positionNumber, title)
10405 int positionNumber;
10408 char *p, line[MSG_SIZ];
10409 Board initial_position;
10410 int i, j, fenMode, pn;
10412 if (gameMode == Training )
10413 SetTrainingModeOff();
10415 if (gameMode != BeginningOfGame) {
10416 Reset(FALSE, TRUE);
10418 if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
10419 fclose(lastLoadPositionFP);
10421 if (positionNumber == 0) positionNumber = 1;
10422 lastLoadPositionFP = f;
10423 lastLoadPositionNumber = positionNumber;
10424 strcpy(lastLoadPositionTitle, title);
10425 if (first.pr == NoProc) {
10426 StartChessProgram(&first);
10427 InitChessProgram(&first, FALSE);
10429 pn = positionNumber;
10430 if (positionNumber < 0) {
10431 /* Negative position number means to seek to that byte offset */
10432 if (fseek(f, -positionNumber, 0) == -1) {
10433 DisplayError(_("Can't seek on position file"), 0);
10438 if (fseek(f, 0, 0) == -1) {
10439 if (f == lastLoadPositionFP ?
10440 positionNumber == lastLoadPositionNumber + 1 :
10441 positionNumber == 1) {
10444 DisplayError(_("Can't seek on position file"), 0);
10449 /* See if this file is FEN or old-style xboard */
10450 if (fgets(line, MSG_SIZ, f) == NULL) {
10451 DisplayError(_("Position not found in file"), 0);
10454 // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
10455 fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
10458 if (fenMode || line[0] == '#') pn--;
10460 /* skip positions before number pn */
10461 if (fgets(line, MSG_SIZ, f) == NULL) {
10463 DisplayError(_("Position not found in file"), 0);
10466 if (fenMode || line[0] == '#') pn--;
10471 if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
10472 DisplayError(_("Bad FEN position in file"), 0);
10476 (void) fgets(line, MSG_SIZ, f);
10477 (void) fgets(line, MSG_SIZ, f);
10479 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
10480 (void) fgets(line, MSG_SIZ, f);
10481 for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
10484 initial_position[i][j++] = CharToPiece(*p);
10488 blackPlaysFirst = FALSE;
10490 (void) fgets(line, MSG_SIZ, f);
10491 if (strncmp(line, "black", strlen("black"))==0)
10492 blackPlaysFirst = TRUE;
10495 startedFromSetupPosition = TRUE;
10497 SendToProgram("force\n", &first);
10498 CopyBoard(boards[0], initial_position);
10499 if (blackPlaysFirst) {
10500 currentMove = forwardMostMove = backwardMostMove = 1;
10501 strcpy(moveList[0], "");
10502 strcpy(parseList[0], "");
10503 CopyBoard(boards[1], initial_position);
10504 DisplayMessage("", _("Black to play"));
10506 currentMove = forwardMostMove = backwardMostMove = 0;
10507 DisplayMessage("", _("White to play"));
10509 initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
10510 SendBoard(&first, forwardMostMove);
10511 if (appData.debugMode) {
10513 for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
10514 for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
10515 fprintf(debugFP, "Load Position\n");
10518 if (positionNumber > 1) {
10519 sprintf(line, "%s %d", title, positionNumber);
10520 DisplayTitle(line);
10522 DisplayTitle(title);
10524 gameMode = EditGame;
10527 timeRemaining[0][1] = whiteTimeRemaining;
10528 timeRemaining[1][1] = blackTimeRemaining;
10529 DrawPosition(FALSE, boards[currentMove]);
10536 CopyPlayerNameIntoFileName(dest, src)
10539 while (*src != NULLCHAR && *src != ',') {
10544 *(*dest)++ = *src++;
10549 char *DefaultFileName(ext)
10552 static char def[MSG_SIZ];
10555 if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
10557 CopyPlayerNameIntoFileName(&p, gameInfo.white);
10559 CopyPlayerNameIntoFileName(&p, gameInfo.black);
10568 /* Save the current game to the given file */
10570 SaveGameToFile(filename, append)
10577 if (strcmp(filename, "-") == 0) {
10578 return SaveGame(stdout, 0, NULL);
10580 f = fopen(filename, append ? "a" : "w");
10582 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10583 DisplayError(buf, errno);
10586 return SaveGame(f, 0, NULL);
10595 static char buf[MSG_SIZ];
10598 p = strchr(str, ' ');
10599 if (p == NULL) return str;
10600 strncpy(buf, str, p - str);
10601 buf[p - str] = NULLCHAR;
10605 #define PGN_MAX_LINE 75
10607 #define PGN_SIDE_WHITE 0
10608 #define PGN_SIDE_BLACK 1
10611 static int FindFirstMoveOutOfBook( int side )
10615 if( backwardMostMove == 0 && ! startedFromSetupPosition) {
10616 int index = backwardMostMove;
10617 int has_book_hit = 0;
10619 if( (index % 2) != side ) {
10623 while( index < forwardMostMove ) {
10624 /* Check to see if engine is in book */
10625 int depth = pvInfoList[index].depth;
10626 int score = pvInfoList[index].score;
10632 else if( score == 0 && depth == 63 ) {
10633 in_book = 1; /* Zappa */
10635 else if( score == 2 && depth == 99 ) {
10636 in_book = 1; /* Abrok */
10639 has_book_hit += in_book;
10655 void GetOutOfBookInfo( char * buf )
10659 int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10661 oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
10662 oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
10666 if( oob[0] >= 0 || oob[1] >= 0 ) {
10667 for( i=0; i<2; i++ ) {
10671 if( i > 0 && oob[0] >= 0 ) {
10672 strcat( buf, " " );
10675 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
10676 sprintf( buf+strlen(buf), "%s%.2f",
10677 pvInfoList[idx].score >= 0 ? "+" : "",
10678 pvInfoList[idx].score / 100.0 );
10684 /* Save game in PGN style and close the file */
10689 int i, offset, linelen, newblock;
10693 int movelen, numlen, blank;
10694 char move_buffer[100]; /* [AS] Buffer for move+PV info */
10696 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10698 tm = time((time_t *) NULL);
10700 PrintPGNTags(f, &gameInfo);
10702 if (backwardMostMove > 0 || startedFromSetupPosition) {
10703 char *fen = PositionToFEN(backwardMostMove, NULL);
10704 fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
10705 fprintf(f, "\n{--------------\n");
10706 PrintPosition(f, backwardMostMove);
10707 fprintf(f, "--------------}\n");
10711 /* [AS] Out of book annotation */
10712 if( appData.saveOutOfBookInfo ) {
10715 GetOutOfBookInfo( buf );
10717 if( buf[0] != '\0' ) {
10718 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
10725 i = backwardMostMove;
10729 while (i < forwardMostMove) {
10730 /* Print comments preceding this move */
10731 if (commentList[i] != NULL) {
10732 if (linelen > 0) fprintf(f, "\n");
10733 fprintf(f, "%s", commentList[i]);
10738 /* Format move number */
10739 if ((i % 2) == 0) {
10740 sprintf(numtext, "%d.", (i - offset)/2 + 1);
10743 sprintf(numtext, "%d...", (i - offset)/2 + 1);
10745 numtext[0] = NULLCHAR;
10748 numlen = strlen(numtext);
10751 /* Print move number */
10752 blank = linelen > 0 && numlen > 0;
10753 if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
10762 fprintf(f, "%s", numtext);
10766 strcpy(move_buffer, SavePart(parseList[i])); // [HGM] pgn: print move via buffer, so it can be edited
10767 movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
10770 blank = linelen > 0 && movelen > 0;
10771 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
10780 fprintf(f, "%s", move_buffer);
10781 linelen += movelen;
10783 /* [AS] Add PV info if present */
10784 if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
10785 /* [HGM] add time */
10786 char buf[MSG_SIZ]; int seconds;
10788 seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
10790 if( seconds <= 0) buf[0] = 0; else
10791 if( seconds < 30 ) sprintf(buf, " %3.1f%c", seconds/10., 0); else {
10792 seconds = (seconds + 4)/10; // round to full seconds
10793 if( seconds < 60 ) sprintf(buf, " %d%c", seconds, 0); else
10794 sprintf(buf, " %d:%02d%c", seconds/60, seconds%60, 0);
10797 sprintf( move_buffer, "{%s%.2f/%d%s}",
10798 pvInfoList[i].score >= 0 ? "+" : "",
10799 pvInfoList[i].score / 100.0,
10800 pvInfoList[i].depth,
10803 movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
10805 /* Print score/depth */
10806 blank = linelen > 0 && movelen > 0;
10807 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
10816 fprintf(f, "%s", move_buffer);
10817 linelen += movelen;
10823 /* Start a new line */
10824 if (linelen > 0) fprintf(f, "\n");
10826 /* Print comments after last move */
10827 if (commentList[i] != NULL) {
10828 fprintf(f, "%s\n", commentList[i]);
10832 if (gameInfo.resultDetails != NULL &&
10833 gameInfo.resultDetails[0] != NULLCHAR) {
10834 fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
10835 PGNResult(gameInfo.result));
10837 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
10841 lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
10845 /* Save game in old style and close the file */
10847 SaveGameOldStyle(f)
10853 tm = time((time_t *) NULL);
10855 fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
10858 if (backwardMostMove > 0 || startedFromSetupPosition) {
10859 fprintf(f, "\n[--------------\n");
10860 PrintPosition(f, backwardMostMove);
10861 fprintf(f, "--------------]\n");
10866 i = backwardMostMove;
10867 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10869 while (i < forwardMostMove) {
10870 if (commentList[i] != NULL) {
10871 fprintf(f, "[%s]\n", commentList[i]);
10874 if ((i % 2) == 1) {
10875 fprintf(f, "%d. ... %s\n", (i - offset)/2 + 1, parseList[i]);
10878 fprintf(f, "%d. %s ", (i - offset)/2 + 1, parseList[i]);
10880 if (commentList[i] != NULL) {
10884 if (i >= forwardMostMove) {
10888 fprintf(f, "%s\n", parseList[i]);
10893 if (commentList[i] != NULL) {
10894 fprintf(f, "[%s]\n", commentList[i]);
10897 /* This isn't really the old style, but it's close enough */
10898 if (gameInfo.resultDetails != NULL &&
10899 gameInfo.resultDetails[0] != NULLCHAR) {
10900 fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
10901 gameInfo.resultDetails);
10903 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
10910 /* Save the current game to open file f and close the file */
10912 SaveGame(f, dummy, dummy2)
10917 if (gameMode == EditPosition) EditPositionDone(TRUE);
10918 lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
10919 if (appData.oldSaveStyle)
10920 return SaveGameOldStyle(f);
10922 return SaveGamePGN(f);
10925 /* Save the current position to the given file */
10927 SavePositionToFile(filename)
10933 if (strcmp(filename, "-") == 0) {
10934 return SavePosition(stdout, 0, NULL);
10936 f = fopen(filename, "a");
10938 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10939 DisplayError(buf, errno);
10942 SavePosition(f, 0, NULL);
10948 /* Save the current position to the given open file and close the file */
10950 SavePosition(f, dummy, dummy2)
10957 if (gameMode == EditPosition) EditPositionDone(TRUE);
10958 if (appData.oldSaveStyle) {
10959 tm = time((time_t *) NULL);
10961 fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
10963 fprintf(f, "[--------------\n");
10964 PrintPosition(f, currentMove);
10965 fprintf(f, "--------------]\n");
10967 fen = PositionToFEN(currentMove, NULL);
10968 fprintf(f, "%s\n", fen);
10976 ReloadCmailMsgEvent(unregister)
10980 static char *inFilename = NULL;
10981 static char *outFilename;
10983 struct stat inbuf, outbuf;
10986 /* Any registered moves are unregistered if unregister is set, */
10987 /* i.e. invoked by the signal handler */
10989 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
10990 cmailMoveRegistered[i] = FALSE;
10991 if (cmailCommentList[i] != NULL) {
10992 free(cmailCommentList[i]);
10993 cmailCommentList[i] = NULL;
10996 nCmailMovesRegistered = 0;
10999 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
11000 cmailResult[i] = CMAIL_NOT_RESULT;
11004 if (inFilename == NULL) {
11005 /* Because the filenames are static they only get malloced once */
11006 /* and they never get freed */
11007 inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
11008 sprintf(inFilename, "%s.game.in", appData.cmailGameName);
11010 outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
11011 sprintf(outFilename, "%s.out", appData.cmailGameName);
11014 status = stat(outFilename, &outbuf);
11016 cmailMailedMove = FALSE;
11018 status = stat(inFilename, &inbuf);
11019 cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
11022 /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
11023 counts the games, notes how each one terminated, etc.
11025 It would be nice to remove this kludge and instead gather all
11026 the information while building the game list. (And to keep it
11027 in the game list nodes instead of having a bunch of fixed-size
11028 parallel arrays.) Note this will require getting each game's
11029 termination from the PGN tags, as the game list builder does
11030 not process the game moves. --mann
11032 cmailMsgLoaded = TRUE;
11033 LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
11035 /* Load first game in the file or popup game menu */
11036 LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
11038 #endif /* !WIN32 */
11046 char string[MSG_SIZ];
11048 if ( cmailMailedMove
11049 || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
11050 return TRUE; /* Allow free viewing */
11053 /* Unregister move to ensure that we don't leave RegisterMove */
11054 /* with the move registered when the conditions for registering no */
11056 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11057 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
11058 nCmailMovesRegistered --;
11060 if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
11062 free(cmailCommentList[lastLoadGameNumber - 1]);
11063 cmailCommentList[lastLoadGameNumber - 1] = NULL;
11067 if (cmailOldMove == -1) {
11068 DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
11072 if (currentMove > cmailOldMove + 1) {
11073 DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
11077 if (currentMove < cmailOldMove) {
11078 DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
11082 if (forwardMostMove > currentMove) {
11083 /* Silently truncate extra moves */
11087 if ( (currentMove == cmailOldMove + 1)
11088 || ( (currentMove == cmailOldMove)
11089 && ( (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
11090 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
11091 if (gameInfo.result != GameUnfinished) {
11092 cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
11095 if (commentList[currentMove] != NULL) {
11096 cmailCommentList[lastLoadGameNumber - 1]
11097 = StrSave(commentList[currentMove]);
11099 strcpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1]);
11101 if (appData.debugMode)
11102 fprintf(debugFP, "Saving %s for game %d\n",
11103 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
11106 "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
11108 f = fopen(string, "w");
11109 if (appData.oldSaveStyle) {
11110 SaveGameOldStyle(f); /* also closes the file */
11112 sprintf(string, "%s.pos.out", appData.cmailGameName);
11113 f = fopen(string, "w");
11114 SavePosition(f, 0, NULL); /* also closes the file */
11116 fprintf(f, "{--------------\n");
11117 PrintPosition(f, currentMove);
11118 fprintf(f, "--------------}\n\n");
11120 SaveGame(f, 0, NULL); /* also closes the file*/
11123 cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
11124 nCmailMovesRegistered ++;
11125 } else if (nCmailGames == 1) {
11126 DisplayError(_("You have not made a move yet"), 0);
11137 static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
11138 FILE *commandOutput;
11139 char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
11140 int nBytes = 0; /* Suppress warnings on uninitialized variables */
11146 if (! cmailMsgLoaded) {
11147 DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
11151 if (nCmailGames == nCmailResults) {
11152 DisplayError(_("No unfinished games"), 0);
11156 #if CMAIL_PROHIBIT_REMAIL
11157 if (cmailMailedMove) {
11158 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);
11159 DisplayError(msg, 0);
11164 if (! (cmailMailedMove || RegisterMove())) return;
11166 if ( cmailMailedMove
11167 || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
11168 sprintf(string, partCommandString,
11169 appData.debugMode ? " -v" : "", appData.cmailGameName);
11170 commandOutput = popen(string, "r");
11172 if (commandOutput == NULL) {
11173 DisplayError(_("Failed to invoke cmail"), 0);
11175 for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
11176 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
11178 if (nBuffers > 1) {
11179 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
11180 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
11181 nBytes = MSG_SIZ - 1;
11183 (void) memcpy(msg, buffer, nBytes);
11185 *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
11187 if(StrStr(msg, "Mailed cmail message to ") != NULL) {
11188 cmailMailedMove = TRUE; /* Prevent >1 moves */
11191 for (i = 0; i < nCmailGames; i ++) {
11192 if (cmailResult[i] == CMAIL_NOT_RESULT) {
11197 && ( (arcDir = (char *) getenv("CMAIL_ARCDIR"))
11199 sprintf(buffer, "%s/%s.%s.archive",
11201 appData.cmailGameName,
11203 LoadGameFromFile(buffer, 1, buffer, FALSE);
11204 cmailMsgLoaded = FALSE;
11208 DisplayInformation(msg);
11209 pclose(commandOutput);
11212 if ((*cmailMsg) != '\0') {
11213 DisplayInformation(cmailMsg);
11218 #endif /* !WIN32 */
11227 int prependComma = 0;
11229 char string[MSG_SIZ]; /* Space for game-list */
11232 if (!cmailMsgLoaded) return "";
11234 if (cmailMailedMove) {
11235 sprintf(cmailMsg, _("Waiting for reply from opponent\n"));
11237 /* Create a list of games left */
11238 sprintf(string, "[");
11239 for (i = 0; i < nCmailGames; i ++) {
11240 if (! ( cmailMoveRegistered[i]
11241 || (cmailResult[i] == CMAIL_OLD_RESULT))) {
11242 if (prependComma) {
11243 sprintf(number, ",%d", i + 1);
11245 sprintf(number, "%d", i + 1);
11249 strcat(string, number);
11252 strcat(string, "]");
11254 if (nCmailMovesRegistered + nCmailResults == 0) {
11255 switch (nCmailGames) {
11258 _("Still need to make move for game\n"));
11263 _("Still need to make moves for both games\n"));
11268 _("Still need to make moves for all %d games\n"),
11273 switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
11276 _("Still need to make a move for game %s\n"),
11281 if (nCmailResults == nCmailGames) {
11282 sprintf(cmailMsg, _("No unfinished games\n"));
11284 sprintf(cmailMsg, _("Ready to send mail\n"));
11290 _("Still need to make moves for games %s\n"),
11302 if (gameMode == Training)
11303 SetTrainingModeOff();
11306 cmailMsgLoaded = FALSE;
11307 if (appData.icsActive) {
11308 SendToICS(ics_prefix);
11309 SendToICS("refresh\n");
11319 /* Give up on clean exit */
11323 /* Keep trying for clean exit */
11327 if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
11329 if (telnetISR != NULL) {
11330 RemoveInputSource(telnetISR);
11332 if (icsPR != NoProc) {
11333 DestroyChildProcess(icsPR, TRUE);
11336 /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
11337 GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
11339 /* [HGM] crash: the above GameEnds() is a dud if another one was running */
11340 /* make sure this other one finishes before killing it! */
11341 if(endingGame) { int count = 0;
11342 if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
11343 while(endingGame && count++ < 10) DoSleep(1);
11344 if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
11347 /* Kill off chess programs */
11348 if (first.pr != NoProc) {
11351 DoSleep( appData.delayBeforeQuit );
11352 SendToProgram("quit\n", &first);
11353 DoSleep( appData.delayAfterQuit );
11354 DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
11356 if (second.pr != NoProc) {
11357 DoSleep( appData.delayBeforeQuit );
11358 SendToProgram("quit\n", &second);
11359 DoSleep( appData.delayAfterQuit );
11360 DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
11362 if (first.isr != NULL) {
11363 RemoveInputSource(first.isr);
11365 if (second.isr != NULL) {
11366 RemoveInputSource(second.isr);
11369 ShutDownFrontEnd();
11376 if (appData.debugMode)
11377 fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
11381 if (gameMode == MachinePlaysWhite ||
11382 gameMode == MachinePlaysBlack) {
11385 DisplayBothClocks();
11387 if (gameMode == PlayFromGameFile) {
11388 if (appData.timeDelay >= 0)
11389 AutoPlayGameLoop();
11390 } else if (gameMode == IcsExamining && pauseExamInvalid) {
11391 Reset(FALSE, TRUE);
11392 SendToICS(ics_prefix);
11393 SendToICS("refresh\n");
11394 } else if (currentMove < forwardMostMove) {
11395 ForwardInner(forwardMostMove);
11397 pauseExamInvalid = FALSE;
11399 switch (gameMode) {
11403 pauseExamForwardMostMove = forwardMostMove;
11404 pauseExamInvalid = FALSE;
11407 case IcsPlayingWhite:
11408 case IcsPlayingBlack:
11412 case PlayFromGameFile:
11413 (void) StopLoadGameTimer();
11417 case BeginningOfGame:
11418 if (appData.icsActive) return;
11419 /* else fall through */
11420 case MachinePlaysWhite:
11421 case MachinePlaysBlack:
11422 case TwoMachinesPlay:
11423 if (forwardMostMove == 0)
11424 return; /* don't pause if no one has moved */
11425 if ((gameMode == MachinePlaysWhite &&
11426 !WhiteOnMove(forwardMostMove)) ||
11427 (gameMode == MachinePlaysBlack &&
11428 WhiteOnMove(forwardMostMove))) {
11441 char title[MSG_SIZ];
11443 if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
11444 strcpy(title, _("Edit comment"));
11446 sprintf(title, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
11447 WhiteOnMove(currentMove - 1) ? " " : ".. ",
11448 parseList[currentMove - 1]);
11451 EditCommentPopUp(currentMove, title, commentList[currentMove]);
11458 char *tags = PGNTags(&gameInfo);
11459 EditTagsPopUp(tags);
11466 if (appData.noChessProgram || gameMode == AnalyzeMode)
11469 if (gameMode != AnalyzeFile) {
11470 if (!appData.icsEngineAnalyze) {
11472 if (gameMode != EditGame) return;
11474 ResurrectChessProgram();
11475 SendToProgram("analyze\n", &first);
11476 first.analyzing = TRUE;
11477 /*first.maybeThinking = TRUE;*/
11478 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11479 EngineOutputPopUp();
11481 if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
11486 StartAnalysisClock();
11487 GetTimeMark(&lastNodeCountTime);
11494 if (appData.noChessProgram || gameMode == AnalyzeFile)
11497 if (gameMode != AnalyzeMode) {
11499 if (gameMode != EditGame) return;
11500 ResurrectChessProgram();
11501 SendToProgram("analyze\n", &first);
11502 first.analyzing = TRUE;
11503 /*first.maybeThinking = TRUE;*/
11504 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11505 EngineOutputPopUp();
11507 gameMode = AnalyzeFile;
11512 StartAnalysisClock();
11513 GetTimeMark(&lastNodeCountTime);
11518 MachineWhiteEvent()
11521 char *bookHit = NULL;
11523 if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
11527 if (gameMode == PlayFromGameFile ||
11528 gameMode == TwoMachinesPlay ||
11529 gameMode == Training ||
11530 gameMode == AnalyzeMode ||
11531 gameMode == EndOfGame)
11534 if (gameMode == EditPosition)
11535 EditPositionDone(TRUE);
11537 if (!WhiteOnMove(currentMove)) {
11538 DisplayError(_("It is not White's turn"), 0);
11542 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
11545 if (gameMode == EditGame || gameMode == AnalyzeMode ||
11546 gameMode == AnalyzeFile)
11549 ResurrectChessProgram(); /* in case it isn't running */
11550 if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
11551 gameMode = MachinePlaysWhite;
11554 gameMode = MachinePlaysWhite;
11558 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
11560 if (first.sendName) {
11561 sprintf(buf, "name %s\n", gameInfo.black);
11562 SendToProgram(buf, &first);
11564 if (first.sendTime) {
11565 if (first.useColors) {
11566 SendToProgram("black\n", &first); /*gnu kludge*/
11568 SendTimeRemaining(&first, TRUE);
11570 if (first.useColors) {
11571 SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
11573 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
11574 SetMachineThinkingEnables();
11575 first.maybeThinking = TRUE;
11579 if (appData.autoFlipView && !flipView) {
11580 flipView = !flipView;
11581 DrawPosition(FALSE, NULL);
11582 DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;
11585 if(bookHit) { // [HGM] book: simulate book reply
11586 static char bookMove[MSG_SIZ]; // a bit generous?
11588 programStats.nodes = programStats.depth = programStats.time =
11589 programStats.score = programStats.got_only_move = 0;
11590 sprintf(programStats.movelist, "%s (xbook)", bookHit);
11592 strcpy(bookMove, "move ");
11593 strcat(bookMove, bookHit);
11594 HandleMachineMove(bookMove, &first);
11599 MachineBlackEvent()
11602 char *bookHit = NULL;
11604 if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
11608 if (gameMode == PlayFromGameFile
11609 || gameMode == TwoMachinesPlay
11610 || gameMode == Training
11611 || gameMode == AnalyzeMode
11612 || gameMode == EndOfGame)
11615 if (gameMode == EditPosition)
11616 EditPositionDone(TRUE);
11618 if (WhiteOnMove(currentMove))
11620 DisplayError(_("It is not Black's turn"), 0);
11624 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
11627 if (gameMode == EditGame || gameMode == AnalyzeMode
11628 || gameMode == AnalyzeFile)
11631 ResurrectChessProgram(); /* in case it isn't running */
11632 gameMode = MachinePlaysBlack;
11636 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
11638 if (first.sendName)
11640 sprintf(buf, "name %s\n", gameInfo.white);
11641 SendToProgram(buf, &first);
11643 if (first.sendTime)
11645 if (first.useColors)
11647 SendToProgram("white\n", &first); /*gnu kludge*/
11649 SendTimeRemaining(&first, FALSE);
11651 if (first.useColors)
11653 SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
11655 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
11656 SetMachineThinkingEnables();
11657 first.maybeThinking = TRUE;
11660 if (appData.autoFlipView && flipView)
11662 flipView = !flipView;
11663 DrawPosition(FALSE, NULL);
11664 DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;
11667 { // [HGM] book: simulate book reply
11668 static char bookMove[MSG_SIZ]; // a bit generous?
11670 programStats.nodes = programStats.depth = programStats.time
11671 = programStats.score = programStats.got_only_move = 0;
11672 sprintf(programStats.movelist, "%s (xbook)", bookHit);
11674 strcpy(bookMove, "move ");
11675 strcat(bookMove, bookHit);
11676 HandleMachineMove(bookMove, &first);
11683 DisplayTwoMachinesTitle()
11686 if (appData.matchGames > 0) {
11687 if (first.twoMachinesColor[0] == 'w') {
11688 sprintf(buf, "%s vs. %s (%d-%d-%d)",
11689 gameInfo.white, gameInfo.black,
11690 first.matchWins, second.matchWins,
11691 matchGame - 1 - (first.matchWins + second.matchWins));
11693 sprintf(buf, "%s vs. %s (%d-%d-%d)",
11694 gameInfo.white, gameInfo.black,
11695 second.matchWins, first.matchWins,
11696 matchGame - 1 - (first.matchWins + second.matchWins));
11699 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
11705 TwoMachinesEvent P((void))
11709 ChessProgramState *onmove;
11710 char *bookHit = NULL;
11712 if (appData.noChessProgram) return;
11714 switch (gameMode) {
11715 case TwoMachinesPlay:
11717 case MachinePlaysWhite:
11718 case MachinePlaysBlack:
11719 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
11720 DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
11724 case BeginningOfGame:
11725 case PlayFromGameFile:
11728 if (gameMode != EditGame) return;
11731 EditPositionDone(TRUE);
11742 // forwardMostMove = currentMove;
11743 TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
11744 ResurrectChessProgram(); /* in case first program isn't running */
11746 if (second.pr == NULL) {
11747 StartChessProgram(&second);
11748 if (second.protocolVersion == 1) {
11749 TwoMachinesEventIfReady();
11751 /* kludge: allow timeout for initial "feature" command */
11753 DisplayMessage("", _("Starting second chess program"));
11754 ScheduleDelayedEvent(TwoMachinesEventIfReady, FEATURE_TIMEOUT);
11758 DisplayMessage("", "");
11759 InitChessProgram(&second, FALSE);
11760 SendToProgram("force\n", &second);
11761 if (startedFromSetupPosition) {
11762 SendBoard(&second, backwardMostMove);
11763 if (appData.debugMode) {
11764 fprintf(debugFP, "Two Machines\n");
11767 for (i = backwardMostMove; i < forwardMostMove; i++) {
11768 SendMoveToProgram(i, &second);
11771 gameMode = TwoMachinesPlay;
11775 DisplayTwoMachinesTitle();
11777 if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
11783 SendToProgram(first.computerString, &first);
11784 if (first.sendName) {
11785 sprintf(buf, "name %s\n", second.tidy);
11786 SendToProgram(buf, &first);
11788 SendToProgram(second.computerString, &second);
11789 if (second.sendName) {
11790 sprintf(buf, "name %s\n", first.tidy);
11791 SendToProgram(buf, &second);
11795 if (!first.sendTime || !second.sendTime) {
11796 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11797 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11799 if (onmove->sendTime) {
11800 if (onmove->useColors) {
11801 SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
11803 SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
11805 if (onmove->useColors) {
11806 SendToProgram(onmove->twoMachinesColor, onmove);
11808 bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
11809 // SendToProgram("go\n", onmove);
11810 onmove->maybeThinking = TRUE;
11811 SetMachineThinkingEnables();
11815 if(bookHit) { // [HGM] book: simulate book reply
11816 static char bookMove[MSG_SIZ]; // a bit generous?
11818 programStats.nodes = programStats.depth = programStats.time =
11819 programStats.score = programStats.got_only_move = 0;
11820 sprintf(programStats.movelist, "%s (xbook)", bookHit);
11822 strcpy(bookMove, "move ");
11823 strcat(bookMove, bookHit);
11824 savedMessage = bookMove; // args for deferred call
11825 savedState = onmove;
11826 ScheduleDelayedEvent(DeferredBookMove, 1);
11833 if (gameMode == Training) {
11834 SetTrainingModeOff();
11835 gameMode = PlayFromGameFile;
11836 DisplayMessage("", _("Training mode off"));
11838 gameMode = Training;
11839 animateTraining = appData.animate;
11841 /* make sure we are not already at the end of the game */
11842 if (currentMove < forwardMostMove) {
11843 SetTrainingModeOn();
11844 DisplayMessage("", _("Training mode on"));
11846 gameMode = PlayFromGameFile;
11847 DisplayError(_("Already at end of game"), 0);
11856 if (!appData.icsActive) return;
11857 switch (gameMode) {
11858 case IcsPlayingWhite:
11859 case IcsPlayingBlack:
11862 case BeginningOfGame:
11870 EditPositionDone(TRUE);
11883 gameMode = IcsIdle;
11894 switch (gameMode) {
11896 SetTrainingModeOff();
11898 case MachinePlaysWhite:
11899 case MachinePlaysBlack:
11900 case BeginningOfGame:
11901 SendToProgram("force\n", &first);
11902 SetUserThinkingEnables();
11904 case PlayFromGameFile:
11905 (void) StopLoadGameTimer();
11906 if (gameFileFP != NULL) {
11911 EditPositionDone(TRUE);
11916 SendToProgram("force\n", &first);
11918 case TwoMachinesPlay:
11919 GameEnds((ChessMove) 0, NULL, GE_PLAYER);
11920 ResurrectChessProgram();
11921 SetUserThinkingEnables();
11924 ResurrectChessProgram();
11926 case IcsPlayingBlack:
11927 case IcsPlayingWhite:
11928 DisplayError(_("Warning: You are still playing a game"), 0);
11931 DisplayError(_("Warning: You are still observing a game"), 0);
11934 DisplayError(_("Warning: You are still examining a game"), 0);
11945 first.offeredDraw = second.offeredDraw = 0;
11947 if (gameMode == PlayFromGameFile) {
11948 whiteTimeRemaining = timeRemaining[0][currentMove];
11949 blackTimeRemaining = timeRemaining[1][currentMove];
11953 if (gameMode == MachinePlaysWhite ||
11954 gameMode == MachinePlaysBlack ||
11955 gameMode == TwoMachinesPlay ||
11956 gameMode == EndOfGame) {
11957 i = forwardMostMove;
11958 while (i > currentMove) {
11959 SendToProgram("undo\n", &first);
11962 whiteTimeRemaining = timeRemaining[0][currentMove];
11963 blackTimeRemaining = timeRemaining[1][currentMove];
11964 DisplayBothClocks();
11965 if (whiteFlag || blackFlag) {
11966 whiteFlag = blackFlag = 0;
11971 gameMode = EditGame;
11978 EditPositionEvent()
11980 if (gameMode == EditPosition) {
11986 if (gameMode != EditGame) return;
11988 gameMode = EditPosition;
11991 if (currentMove > 0)
11992 CopyBoard(boards[0], boards[currentMove]);
11994 blackPlaysFirst = !WhiteOnMove(currentMove);
11996 currentMove = forwardMostMove = backwardMostMove = 0;
11997 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12004 /* [DM] icsEngineAnalyze - possible call from other functions */
12005 if (appData.icsEngineAnalyze) {
12006 appData.icsEngineAnalyze = FALSE;
12008 DisplayMessage("",_("Close ICS engine analyze..."));
12010 if (first.analysisSupport && first.analyzing) {
12011 SendToProgram("exit\n", &first);
12012 first.analyzing = FALSE;
12014 thinkOutput[0] = NULLCHAR;
12018 EditPositionDone(Boolean fakeRights)
12020 int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
12022 startedFromSetupPosition = TRUE;
12023 InitChessProgram(&first, FALSE);
12024 if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
12025 boards[0][EP_STATUS] = EP_NONE;
12026 boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
12027 if(boards[0][0][BOARD_WIDTH>>1] == king) {
12028 boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : NoRights;
12029 boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
12030 } else boards[0][CASTLING][2] = NoRights;
12031 if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
12032 boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : NoRights;
12033 boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
12034 } else boards[0][CASTLING][5] = NoRights;
12036 SendToProgram("force\n", &first);
12037 if (blackPlaysFirst) {
12038 strcpy(moveList[0], "");
12039 strcpy(parseList[0], "");
12040 currentMove = forwardMostMove = backwardMostMove = 1;
12041 CopyBoard(boards[1], boards[0]);
12043 currentMove = forwardMostMove = backwardMostMove = 0;
12045 SendBoard(&first, forwardMostMove);
12046 if (appData.debugMode) {
12047 fprintf(debugFP, "EditPosDone\n");
12050 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12051 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12052 gameMode = EditGame;
12054 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12055 ClearHighlights(); /* [AS] */
12058 /* Pause for `ms' milliseconds */
12059 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
12069 } while (SubtractTimeMarks(&m2, &m1) < ms);
12072 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
12074 SendMultiLineToICS(buf)
12077 char temp[MSG_SIZ+1], *p;
12084 strncpy(temp, buf, len);
12089 if (*p == '\n' || *p == '\r')
12094 strcat(temp, "\n");
12096 SendToPlayer(temp, strlen(temp));
12100 SetWhiteToPlayEvent()
12102 if (gameMode == EditPosition) {
12103 blackPlaysFirst = FALSE;
12104 DisplayBothClocks(); /* works because currentMove is 0 */
12105 } else if (gameMode == IcsExamining) {
12106 SendToICS(ics_prefix);
12107 SendToICS("tomove white\n");
12112 SetBlackToPlayEvent()
12114 if (gameMode == EditPosition) {
12115 blackPlaysFirst = TRUE;
12116 currentMove = 1; /* kludge */
12117 DisplayBothClocks();
12119 } else if (gameMode == IcsExamining) {
12120 SendToICS(ics_prefix);
12121 SendToICS("tomove black\n");
12126 EditPositionMenuEvent(selection, x, y)
12127 ChessSquare selection;
12131 ChessSquare piece = boards[0][y][x];
12133 if (gameMode != EditPosition && gameMode != IcsExamining) return;
12135 switch (selection) {
12137 if (gameMode == IcsExamining && ics_type == ICS_FICS) {
12138 SendToICS(ics_prefix);
12139 SendToICS("bsetup clear\n");
12140 } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
12141 SendToICS(ics_prefix);
12142 SendToICS("clearboard\n");
12144 for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
12145 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
12146 for (y = 0; y < BOARD_HEIGHT; y++) {
12147 if (gameMode == IcsExamining) {
12148 if (boards[currentMove][y][x] != EmptySquare) {
12149 sprintf(buf, "%sx@%c%c\n", ics_prefix,
12154 boards[0][y][x] = p;
12159 if (gameMode == EditPosition) {
12160 DrawPosition(FALSE, boards[0]);
12165 SetWhiteToPlayEvent();
12169 SetBlackToPlayEvent();
12173 if (gameMode == IcsExamining) {
12174 if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
12175 sprintf(buf, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
12178 if(x < BOARD_LEFT || x >= BOARD_RGHT) {
12179 if(x == BOARD_LEFT-2) {
12180 if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
12181 boards[0][y][1] = 0;
12183 if(x == BOARD_RGHT+1) {
12184 if(y >= gameInfo.holdingsSize) break;
12185 boards[0][y][BOARD_WIDTH-2] = 0;
12188 boards[0][y][x] = EmptySquare;
12189 DrawPosition(FALSE, boards[0]);
12194 if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
12195 piece >= (int)BlackPawn && piece < (int)BlackMan ) {
12196 selection = (ChessSquare) (PROMOTED piece);
12197 } else if(piece == EmptySquare) selection = WhiteSilver;
12198 else selection = (ChessSquare)((int)piece - 1);
12202 if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
12203 piece > (int)BlackMan && piece <= (int)BlackKing ) {
12204 selection = (ChessSquare) (DEMOTED piece);
12205 } else if(piece == EmptySquare) selection = BlackSilver;
12206 else selection = (ChessSquare)((int)piece + 1);
12211 if(gameInfo.variant == VariantShatranj ||
12212 gameInfo.variant == VariantXiangqi ||
12213 gameInfo.variant == VariantCourier ||
12214 gameInfo.variant == VariantMakruk )
12215 selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
12220 if(gameInfo.variant == VariantXiangqi)
12221 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
12222 if(gameInfo.variant == VariantKnightmate)
12223 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
12226 if (gameMode == IcsExamining) {
12227 if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
12228 sprintf(buf, "%s%c@%c%c\n", ics_prefix,
12229 PieceToChar(selection), AAA + x, ONE + y);
12232 if(x < BOARD_LEFT || x >= BOARD_RGHT) {
12234 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
12235 n = PieceToNumber(selection - BlackPawn);
12236 if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
12237 boards[0][BOARD_HEIGHT-1-n][0] = selection;
12238 boards[0][BOARD_HEIGHT-1-n][1]++;
12240 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
12241 n = PieceToNumber(selection);
12242 if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
12243 boards[0][n][BOARD_WIDTH-1] = selection;
12244 boards[0][n][BOARD_WIDTH-2]++;
12247 boards[0][y][x] = selection;
12248 DrawPosition(TRUE, boards[0]);
12256 DropMenuEvent(selection, x, y)
12257 ChessSquare selection;
12260 ChessMove moveType;
12262 switch (gameMode) {
12263 case IcsPlayingWhite:
12264 case MachinePlaysBlack:
12265 if (!WhiteOnMove(currentMove)) {
12266 DisplayMoveError(_("It is Black's turn"));
12269 moveType = WhiteDrop;
12271 case IcsPlayingBlack:
12272 case MachinePlaysWhite:
12273 if (WhiteOnMove(currentMove)) {
12274 DisplayMoveError(_("It is White's turn"));
12277 moveType = BlackDrop;
12280 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
12286 if (moveType == BlackDrop && selection < BlackPawn) {
12287 selection = (ChessSquare) ((int) selection
12288 + (int) BlackPawn - (int) WhitePawn);
12290 if (boards[currentMove][y][x] != EmptySquare) {
12291 DisplayMoveError(_("That square is occupied"));
12295 FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
12301 /* Accept a pending offer of any kind from opponent */
12303 if (appData.icsActive) {
12304 SendToICS(ics_prefix);
12305 SendToICS("accept\n");
12306 } else if (cmailMsgLoaded) {
12307 if (currentMove == cmailOldMove &&
12308 commentList[cmailOldMove] != NULL &&
12309 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12310 "Black offers a draw" : "White offers a draw")) {
12312 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
12313 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
12315 DisplayError(_("There is no pending offer on this move"), 0);
12316 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12319 /* Not used for offers from chess program */
12326 /* Decline a pending offer of any kind from opponent */
12328 if (appData.icsActive) {
12329 SendToICS(ics_prefix);
12330 SendToICS("decline\n");
12331 } else if (cmailMsgLoaded) {
12332 if (currentMove == cmailOldMove &&
12333 commentList[cmailOldMove] != NULL &&
12334 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12335 "Black offers a draw" : "White offers a draw")) {
12337 AppendComment(cmailOldMove, "Draw declined", TRUE);
12338 DisplayComment(cmailOldMove - 1, "Draw declined");
12341 DisplayError(_("There is no pending offer on this move"), 0);
12344 /* Not used for offers from chess program */
12351 /* Issue ICS rematch command */
12352 if (appData.icsActive) {
12353 SendToICS(ics_prefix);
12354 SendToICS("rematch\n");
12361 /* Call your opponent's flag (claim a win on time) */
12362 if (appData.icsActive) {
12363 SendToICS(ics_prefix);
12364 SendToICS("flag\n");
12366 switch (gameMode) {
12369 case MachinePlaysWhite:
12372 GameEnds(GameIsDrawn, "Both players ran out of time",
12375 GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
12377 DisplayError(_("Your opponent is not out of time"), 0);
12380 case MachinePlaysBlack:
12383 GameEnds(GameIsDrawn, "Both players ran out of time",
12386 GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
12388 DisplayError(_("Your opponent is not out of time"), 0);
12398 /* Offer draw or accept pending draw offer from opponent */
12400 if (appData.icsActive) {
12401 /* Note: tournament rules require draw offers to be
12402 made after you make your move but before you punch
12403 your clock. Currently ICS doesn't let you do that;
12404 instead, you immediately punch your clock after making
12405 a move, but you can offer a draw at any time. */
12407 SendToICS(ics_prefix);
12408 SendToICS("draw\n");
12409 userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
12410 } else if (cmailMsgLoaded) {
12411 if (currentMove == cmailOldMove &&
12412 commentList[cmailOldMove] != NULL &&
12413 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12414 "Black offers a draw" : "White offers a draw")) {
12415 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
12416 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
12417 } else if (currentMove == cmailOldMove + 1) {
12418 char *offer = WhiteOnMove(cmailOldMove) ?
12419 "White offers a draw" : "Black offers a draw";
12420 AppendComment(currentMove, offer, TRUE);
12421 DisplayComment(currentMove - 1, offer);
12422 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
12424 DisplayError(_("You must make your move before offering a draw"), 0);
12425 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12427 } else if (first.offeredDraw) {
12428 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
12430 if (first.sendDrawOffers) {
12431 SendToProgram("draw\n", &first);
12432 userOfferedDraw = TRUE;
12440 /* Offer Adjourn or accept pending Adjourn offer from opponent */
12442 if (appData.icsActive) {
12443 SendToICS(ics_prefix);
12444 SendToICS("adjourn\n");
12446 /* Currently GNU Chess doesn't offer or accept Adjourns */
12454 /* Offer Abort or accept pending Abort offer from opponent */
12456 if (appData.icsActive) {
12457 SendToICS(ics_prefix);
12458 SendToICS("abort\n");
12460 GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
12467 /* Resign. You can do this even if it's not your turn. */
12469 if (appData.icsActive) {
12470 SendToICS(ics_prefix);
12471 SendToICS("resign\n");
12473 switch (gameMode) {
12474 case MachinePlaysWhite:
12475 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12477 case MachinePlaysBlack:
12478 GameEnds(BlackWins, "White resigns", GE_PLAYER);
12481 if (cmailMsgLoaded) {
12483 if (WhiteOnMove(cmailOldMove)) {
12484 GameEnds(BlackWins, "White resigns", GE_PLAYER);
12486 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12488 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
12499 StopObservingEvent()
12501 /* Stop observing current games */
12502 SendToICS(ics_prefix);
12503 SendToICS("unobserve\n");
12507 StopExaminingEvent()
12509 /* Stop observing current game */
12510 SendToICS(ics_prefix);
12511 SendToICS("unexamine\n");
12515 ForwardInner(target)
12520 if (appData.debugMode)
12521 fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
12522 target, currentMove, forwardMostMove);
12524 if (gameMode == EditPosition)
12527 if (gameMode == PlayFromGameFile && !pausing)
12530 if (gameMode == IcsExamining && pausing)
12531 limit = pauseExamForwardMostMove;
12533 limit = forwardMostMove;
12535 if (target > limit) target = limit;
12537 if (target > 0 && moveList[target - 1][0]) {
12538 int fromX, fromY, toX, toY;
12539 toX = moveList[target - 1][2] - AAA;
12540 toY = moveList[target - 1][3] - ONE;
12541 if (moveList[target - 1][1] == '@') {
12542 if (appData.highlightLastMove) {
12543 SetHighlights(-1, -1, toX, toY);
12546 fromX = moveList[target - 1][0] - AAA;
12547 fromY = moveList[target - 1][1] - ONE;
12548 if (target == currentMove + 1) {
12549 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
12551 if (appData.highlightLastMove) {
12552 SetHighlights(fromX, fromY, toX, toY);
12556 if (gameMode == EditGame || gameMode == AnalyzeMode ||
12557 gameMode == Training || gameMode == PlayFromGameFile ||
12558 gameMode == AnalyzeFile) {
12559 while (currentMove < target) {
12560 SendMoveToProgram(currentMove++, &first);
12563 currentMove = target;
12566 if (gameMode == EditGame || gameMode == EndOfGame) {
12567 whiteTimeRemaining = timeRemaining[0][currentMove];
12568 blackTimeRemaining = timeRemaining[1][currentMove];
12570 DisplayBothClocks();
12571 DisplayMove(currentMove - 1);
12572 DrawPosition(FALSE, boards[currentMove]);
12573 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
12574 if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
12575 DisplayComment(currentMove - 1, commentList[currentMove]);
12583 if (gameMode == IcsExamining && !pausing) {
12584 SendToICS(ics_prefix);
12585 SendToICS("forward\n");
12587 ForwardInner(currentMove + 1);
12594 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12595 /* to optimze, we temporarily turn off analysis mode while we feed
12596 * the remaining moves to the engine. Otherwise we get analysis output
12599 if (first.analysisSupport) {
12600 SendToProgram("exit\nforce\n", &first);
12601 first.analyzing = FALSE;
12605 if (gameMode == IcsExamining && !pausing) {
12606 SendToICS(ics_prefix);
12607 SendToICS("forward 999999\n");
12609 ForwardInner(forwardMostMove);
12612 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12613 /* we have fed all the moves, so reactivate analysis mode */
12614 SendToProgram("analyze\n", &first);
12615 first.analyzing = TRUE;
12616 /*first.maybeThinking = TRUE;*/
12617 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12622 BackwardInner(target)
12625 int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
12627 if (appData.debugMode)
12628 fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
12629 target, currentMove, forwardMostMove);
12631 if (gameMode == EditPosition) return;
12632 if (currentMove <= backwardMostMove) {
12634 DrawPosition(full_redraw, boards[currentMove]);
12637 if (gameMode == PlayFromGameFile && !pausing)
12640 if (moveList[target][0]) {
12641 int fromX, fromY, toX, toY;
12642 toX = moveList[target][2] - AAA;
12643 toY = moveList[target][3] - ONE;
12644 if (moveList[target][1] == '@') {
12645 if (appData.highlightLastMove) {
12646 SetHighlights(-1, -1, toX, toY);
12649 fromX = moveList[target][0] - AAA;
12650 fromY = moveList[target][1] - ONE;
12651 if (target == currentMove - 1) {
12652 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
12654 if (appData.highlightLastMove) {
12655 SetHighlights(fromX, fromY, toX, toY);
12659 if (gameMode == EditGame || gameMode==AnalyzeMode ||
12660 gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
12661 while (currentMove > target) {
12662 SendToProgram("undo\n", &first);
12666 currentMove = target;
12669 if (gameMode == EditGame || gameMode == EndOfGame) {
12670 whiteTimeRemaining = timeRemaining[0][currentMove];
12671 blackTimeRemaining = timeRemaining[1][currentMove];
12673 DisplayBothClocks();
12674 DisplayMove(currentMove - 1);
12675 DrawPosition(full_redraw, boards[currentMove]);
12676 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
12677 // [HGM] PV info: routine tests if comment empty
12678 DisplayComment(currentMove - 1, commentList[currentMove]);
12684 if (gameMode == IcsExamining && !pausing) {
12685 SendToICS(ics_prefix);
12686 SendToICS("backward\n");
12688 BackwardInner(currentMove - 1);
12695 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12696 /* to optimize, we temporarily turn off analysis mode while we undo
12697 * all the moves. Otherwise we get analysis output after each undo.
12699 if (first.analysisSupport) {
12700 SendToProgram("exit\nforce\n", &first);
12701 first.analyzing = FALSE;
12705 if (gameMode == IcsExamining && !pausing) {
12706 SendToICS(ics_prefix);
12707 SendToICS("backward 999999\n");
12709 BackwardInner(backwardMostMove);
12712 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12713 /* we have fed all the moves, so reactivate analysis mode */
12714 SendToProgram("analyze\n", &first);
12715 first.analyzing = TRUE;
12716 /*first.maybeThinking = TRUE;*/
12717 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12724 if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
12725 if (to >= forwardMostMove) to = forwardMostMove;
12726 if (to <= backwardMostMove) to = backwardMostMove;
12727 if (to < currentMove) {
12737 if(PopTail(TRUE)) { // [HGM] vari: restore old game tail
12740 if (gameMode != IcsExamining) {
12741 DisplayError(_("You are not examining a game"), 0);
12745 DisplayError(_("You can't revert while pausing"), 0);
12748 SendToICS(ics_prefix);
12749 SendToICS("revert\n");
12755 switch (gameMode) {
12756 case MachinePlaysWhite:
12757 case MachinePlaysBlack:
12758 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
12759 DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
12762 if (forwardMostMove < 2) return;
12763 currentMove = forwardMostMove = forwardMostMove - 2;
12764 whiteTimeRemaining = timeRemaining[0][currentMove];
12765 blackTimeRemaining = timeRemaining[1][currentMove];
12766 DisplayBothClocks();
12767 DisplayMove(currentMove - 1);
12768 ClearHighlights();/*!! could figure this out*/
12769 DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
12770 SendToProgram("remove\n", &first);
12771 /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
12774 case BeginningOfGame:
12778 case IcsPlayingWhite:
12779 case IcsPlayingBlack:
12780 if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
12781 SendToICS(ics_prefix);
12782 SendToICS("takeback 2\n");
12784 SendToICS(ics_prefix);
12785 SendToICS("takeback 1\n");
12794 ChessProgramState *cps;
12796 switch (gameMode) {
12797 case MachinePlaysWhite:
12798 if (!WhiteOnMove(forwardMostMove)) {
12799 DisplayError(_("It is your turn"), 0);
12804 case MachinePlaysBlack:
12805 if (WhiteOnMove(forwardMostMove)) {
12806 DisplayError(_("It is your turn"), 0);
12811 case TwoMachinesPlay:
12812 if (WhiteOnMove(forwardMostMove) ==
12813 (first.twoMachinesColor[0] == 'w')) {
12819 case BeginningOfGame:
12823 SendToProgram("?\n", cps);
12827 TruncateGameEvent()
12830 if (gameMode != EditGame) return;
12837 CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
12838 if (forwardMostMove > currentMove) {
12839 if (gameInfo.resultDetails != NULL) {
12840 free(gameInfo.resultDetails);
12841 gameInfo.resultDetails = NULL;
12842 gameInfo.result = GameUnfinished;
12844 forwardMostMove = currentMove;
12845 HistorySet(parseList, backwardMostMove, forwardMostMove,
12853 if (appData.noChessProgram) return;
12854 switch (gameMode) {
12855 case MachinePlaysWhite:
12856 if (WhiteOnMove(forwardMostMove)) {
12857 DisplayError(_("Wait until your turn"), 0);
12861 case BeginningOfGame:
12862 case MachinePlaysBlack:
12863 if (!WhiteOnMove(forwardMostMove)) {
12864 DisplayError(_("Wait until your turn"), 0);
12869 DisplayError(_("No hint available"), 0);
12872 SendToProgram("hint\n", &first);
12873 hintRequested = TRUE;
12879 if (appData.noChessProgram) return;
12880 switch (gameMode) {
12881 case MachinePlaysWhite:
12882 if (WhiteOnMove(forwardMostMove)) {
12883 DisplayError(_("Wait until your turn"), 0);
12887 case BeginningOfGame:
12888 case MachinePlaysBlack:
12889 if (!WhiteOnMove(forwardMostMove)) {
12890 DisplayError(_("Wait until your turn"), 0);
12895 EditPositionDone(TRUE);
12897 case TwoMachinesPlay:
12902 SendToProgram("bk\n", &first);
12903 bookOutput[0] = NULLCHAR;
12904 bookRequested = TRUE;
12910 char *tags = PGNTags(&gameInfo);
12911 TagsPopUp(tags, CmailMsg());
12915 /* end button procedures */
12918 PrintPosition(fp, move)
12924 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
12925 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
12926 char c = PieceToChar(boards[move][i][j]);
12927 fputc(c == 'x' ? '.' : c, fp);
12928 fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
12931 if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
12932 fprintf(fp, "white to play\n");
12934 fprintf(fp, "black to play\n");
12941 if (gameInfo.white != NULL) {
12942 fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
12948 /* Find last component of program's own name, using some heuristics */
12950 TidyProgramName(prog, host, buf)
12951 char *prog, *host, buf[MSG_SIZ];
12954 int local = (strcmp(host, "localhost") == 0);
12955 while (!local && (p = strchr(prog, ';')) != NULL) {
12957 while (*p == ' ') p++;
12960 if (*prog == '"' || *prog == '\'') {
12961 q = strchr(prog + 1, *prog);
12963 q = strchr(prog, ' ');
12965 if (q == NULL) q = prog + strlen(prog);
12967 while (p >= prog && *p != '/' && *p != '\\') p--;
12969 if(p == prog && *p == '"') p++;
12970 if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
12971 memcpy(buf, p, q - p);
12972 buf[q - p] = NULLCHAR;
12980 TimeControlTagValue()
12983 if (!appData.clockMode) {
12985 } else if (movesPerSession > 0) {
12986 sprintf(buf, "%d/%ld", movesPerSession, timeControl/1000);
12987 } else if (timeIncrement == 0) {
12988 sprintf(buf, "%ld", timeControl/1000);
12990 sprintf(buf, "%ld+%ld", timeControl/1000, timeIncrement/1000);
12992 return StrSave(buf);
12998 /* This routine is used only for certain modes */
12999 VariantClass v = gameInfo.variant;
13000 ChessMove r = GameUnfinished;
13003 if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
13004 r = gameInfo.result;
13005 p = gameInfo.resultDetails;
13006 gameInfo.resultDetails = NULL;
13008 ClearGameInfo(&gameInfo);
13009 gameInfo.variant = v;
13011 switch (gameMode) {
13012 case MachinePlaysWhite:
13013 gameInfo.event = StrSave( appData.pgnEventHeader );
13014 gameInfo.site = StrSave(HostName());
13015 gameInfo.date = PGNDate();
13016 gameInfo.round = StrSave("-");
13017 gameInfo.white = StrSave(first.tidy);
13018 gameInfo.black = StrSave(UserName());
13019 gameInfo.timeControl = TimeControlTagValue();
13022 case MachinePlaysBlack:
13023 gameInfo.event = StrSave( appData.pgnEventHeader );
13024 gameInfo.site = StrSave(HostName());
13025 gameInfo.date = PGNDate();
13026 gameInfo.round = StrSave("-");
13027 gameInfo.white = StrSave(UserName());
13028 gameInfo.black = StrSave(first.tidy);
13029 gameInfo.timeControl = TimeControlTagValue();
13032 case TwoMachinesPlay:
13033 gameInfo.event = StrSave( appData.pgnEventHeader );
13034 gameInfo.site = StrSave(HostName());
13035 gameInfo.date = PGNDate();
13036 if (matchGame > 0) {
13038 sprintf(buf, "%d", matchGame);
13039 gameInfo.round = StrSave(buf);
13041 gameInfo.round = StrSave("-");
13043 if (first.twoMachinesColor[0] == 'w') {
13044 gameInfo.white = StrSave(first.tidy);
13045 gameInfo.black = StrSave(second.tidy);
13047 gameInfo.white = StrSave(second.tidy);
13048 gameInfo.black = StrSave(first.tidy);
13050 gameInfo.timeControl = TimeControlTagValue();
13054 gameInfo.event = StrSave("Edited game");
13055 gameInfo.site = StrSave(HostName());
13056 gameInfo.date = PGNDate();
13057 gameInfo.round = StrSave("-");
13058 gameInfo.white = StrSave("-");
13059 gameInfo.black = StrSave("-");
13060 gameInfo.result = r;
13061 gameInfo.resultDetails = p;
13065 gameInfo.event = StrSave("Edited position");
13066 gameInfo.site = StrSave(HostName());
13067 gameInfo.date = PGNDate();
13068 gameInfo.round = StrSave("-");
13069 gameInfo.white = StrSave("-");
13070 gameInfo.black = StrSave("-");
13073 case IcsPlayingWhite:
13074 case IcsPlayingBlack:
13079 case PlayFromGameFile:
13080 gameInfo.event = StrSave("Game from non-PGN file");
13081 gameInfo.site = StrSave(HostName());
13082 gameInfo.date = PGNDate();
13083 gameInfo.round = StrSave("-");
13084 gameInfo.white = StrSave("?");
13085 gameInfo.black = StrSave("?");
13094 ReplaceComment(index, text)
13100 while (*text == '\n') text++;
13101 len = strlen(text);
13102 while (len > 0 && text[len - 1] == '\n') len--;
13104 if (commentList[index] != NULL)
13105 free(commentList[index]);
13108 commentList[index] = NULL;
13111 if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
13112 *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
13113 *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
13114 commentList[index] = (char *) malloc(len + 2);
13115 strncpy(commentList[index], text, len);
13116 commentList[index][len] = '\n';
13117 commentList[index][len + 1] = NULLCHAR;
13119 // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
13121 commentList[index] = (char *) malloc(len + 6);
13122 strcpy(commentList[index], "{\n");
13123 strncpy(commentList[index]+2, text, len);
13124 commentList[index][len+2] = NULLCHAR;
13125 while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
13126 strcat(commentList[index], "\n}\n");
13140 if (ch == '\r') continue;
13142 } while (ch != '\0');
13146 AppendComment(index, text, addBraces)
13149 Boolean addBraces; // [HGM] braces: tells if we should add {}
13154 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
13155 text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
13158 while (*text == '\n') text++;
13159 len = strlen(text);
13160 while (len > 0 && text[len - 1] == '\n') len--;
13162 if (len == 0) return;
13164 if (commentList[index] != NULL) {
13165 old = commentList[index];
13166 oldlen = strlen(old);
13167 while(commentList[index][oldlen-1] == '\n')
13168 commentList[index][--oldlen] = NULLCHAR;
13169 commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
13170 strcpy(commentList[index], old);
13172 // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
13173 if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces)) {
13174 if(addBraces) addBraces = FALSE; else { text++; len--; }
13175 while (*text == '\n') { text++; len--; }
13176 commentList[index][--oldlen] = NULLCHAR;
13178 if(addBraces) strcat(commentList[index], "\n{\n");
13179 else strcat(commentList[index], "\n");
13180 strcat(commentList[index], text);
13181 if(addBraces) strcat(commentList[index], "\n}\n");
13182 else strcat(commentList[index], "\n");
13184 commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
13186 strcpy(commentList[index], "{\n");
13187 else commentList[index][0] = NULLCHAR;
13188 strcat(commentList[index], text);
13189 strcat(commentList[index], "\n");
13190 if(addBraces) strcat(commentList[index], "}\n");
13194 static char * FindStr( char * text, char * sub_text )
13196 char * result = strstr( text, sub_text );
13198 if( result != NULL ) {
13199 result += strlen( sub_text );
13205 /* [AS] Try to extract PV info from PGN comment */
13206 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
13207 char *GetInfoFromComment( int index, char * text )
13211 if( text != NULL && index > 0 ) {
13214 int time = -1, sec = 0, deci;
13215 char * s_eval = FindStr( text, "[%eval " );
13216 char * s_emt = FindStr( text, "[%emt " );
13218 if( s_eval != NULL || s_emt != NULL ) {
13222 if( s_eval != NULL ) {
13223 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
13227 if( delim != ']' ) {
13232 if( s_emt != NULL ) {
13237 /* We expect something like: [+|-]nnn.nn/dd */
13240 if(*text != '{') return text; // [HGM] braces: must be normal comment
13242 sep = strchr( text, '/' );
13243 if( sep == NULL || sep < (text+4) ) {
13247 time = -1; sec = -1; deci = -1;
13248 if( sscanf( text+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
13249 sscanf( text+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
13250 sscanf( text+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
13251 sscanf( text+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3 ) {
13255 if( score_lo < 0 || score_lo >= 100 ) {
13259 if(sec >= 0) time = 600*time + 10*sec; else
13260 if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
13262 score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
13264 /* [HGM] PV time: now locate end of PV info */
13265 while( *++sep >= '0' && *sep <= '9'); // strip depth
13267 while( *++sep >= '0' && *sep <= '9'); // strip time
13269 while( *++sep >= '0' && *sep <= '9'); // strip seconds
13271 while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
13272 while(*sep == ' ') sep++;
13283 pvInfoList[index-1].depth = depth;
13284 pvInfoList[index-1].score = score;
13285 pvInfoList[index-1].time = 10*time; // centi-sec
13286 if(*sep == '}') *sep = 0; else *--sep = '{';
13292 SendToProgram(message, cps)
13294 ChessProgramState *cps;
13296 int count, outCount, error;
13299 if (cps->pr == NULL) return;
13302 if (appData.debugMode) {
13305 fprintf(debugFP, "%ld >%-6s: %s",
13306 SubtractTimeMarks(&now, &programStartTime),
13307 cps->which, message);
13310 count = strlen(message);
13311 outCount = OutputToProcess(cps->pr, message, count, &error);
13312 if (outCount < count && !exiting
13313 && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
13314 sprintf(buf, _("Error writing to %s chess program"), cps->which);
13315 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
13316 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
13317 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
13318 sprintf(buf, "%s program exits in draw position (%s)", cps->which, cps->program);
13320 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
13322 gameInfo.resultDetails = StrSave(buf);
13324 if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
13329 ReceiveFromProgram(isr, closure, message, count, error)
13330 InputSourceRef isr;
13338 ChessProgramState *cps = (ChessProgramState *)closure;
13340 if (isr != cps->isr) return; /* Killed intentionally */
13344 _("Error: %s chess program (%s) exited unexpectedly"),
13345 cps->which, cps->program);
13346 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
13347 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
13348 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
13349 sprintf(buf, _("%s program exits in draw position (%s)"), cps->which, cps->program);
13351 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
13353 gameInfo.resultDetails = StrSave(buf);
13355 RemoveInputSource(cps->isr);
13356 if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
13359 _("Error reading from %s chess program (%s)"),
13360 cps->which, cps->program);
13361 RemoveInputSource(cps->isr);
13363 /* [AS] Program is misbehaving badly... kill it */
13364 if( count == -2 ) {
13365 DestroyChildProcess( cps->pr, 9 );
13369 if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
13374 if ((end_str = strchr(message, '\r')) != NULL)
13375 *end_str = NULLCHAR;
13376 if ((end_str = strchr(message, '\n')) != NULL)
13377 *end_str = NULLCHAR;
13379 if (appData.debugMode) {
13380 TimeMark now; int print = 1;
13381 char *quote = ""; char c; int i;
13383 if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
13384 char start = message[0];
13385 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
13386 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
13387 sscanf(message, "move %c", &c)!=1 && sscanf(message, "offer%c", &c)!=1 &&
13388 sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
13389 sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
13390 sscanf(message, "tell%c", &c)!=1 && sscanf(message, "0-1 %c", &c)!=1 &&
13391 sscanf(message, "1-0 %c", &c)!=1 && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
13392 sscanf(message, "pong %c", &c)!=1 && start != '#')
13393 { quote = "# "; print = (appData.engineComments == 2); }
13394 message[0] = start; // restore original message
13398 fprintf(debugFP, "%ld <%-6s: %s%s\n",
13399 SubtractTimeMarks(&now, &programStartTime), cps->which,
13405 /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
13406 if (appData.icsEngineAnalyze) {
13407 if (strstr(message, "whisper") != NULL ||
13408 strstr(message, "kibitz") != NULL ||
13409 strstr(message, "tellics") != NULL) return;
13412 HandleMachineMove(message, cps);
13417 SendTimeControl(cps, mps, tc, inc, sd, st)
13418 ChessProgramState *cps;
13419 int mps, inc, sd, st;
13425 if( timeControl_2 > 0 ) {
13426 if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
13427 tc = timeControl_2;
13430 tc /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
13431 inc /= cps->timeOdds;
13432 st /= cps->timeOdds;
13434 seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
13437 /* Set exact time per move, normally using st command */
13438 if (cps->stKludge) {
13439 /* GNU Chess 4 has no st command; uses level in a nonstandard way */
13441 if (seconds == 0) {
13442 sprintf(buf, "level 1 %d\n", st/60);
13444 sprintf(buf, "level 1 %d:%02d\n", st/60, seconds);
13447 sprintf(buf, "st %d\n", st);
13450 /* Set conventional or incremental time control, using level command */
13451 if (seconds == 0) {
13452 /* Note old gnuchess bug -- minutes:seconds used to not work.
13453 Fixed in later versions, but still avoid :seconds
13454 when seconds is 0. */
13455 sprintf(buf, "level %d %ld %d\n", mps, tc/60000, inc/1000);
13457 sprintf(buf, "level %d %ld:%02d %d\n", mps, tc/60000,
13458 seconds, inc/1000);
13461 SendToProgram(buf, cps);
13463 /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
13464 /* Orthogonally, limit search to given depth */
13466 if (cps->sdKludge) {
13467 sprintf(buf, "depth\n%d\n", sd);
13469 sprintf(buf, "sd %d\n", sd);
13471 SendToProgram(buf, cps);
13474 if(cps->nps > 0) { /* [HGM] nps */
13475 if(cps->supportsNPS == FALSE) cps->nps = -1; // don't use if engine explicitly says not supported!
13477 sprintf(buf, "nps %d\n", cps->nps);
13478 SendToProgram(buf, cps);
13483 ChessProgramState *WhitePlayer()
13484 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
13486 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
13487 gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
13493 SendTimeRemaining(cps, machineWhite)
13494 ChessProgramState *cps;
13495 int /*boolean*/ machineWhite;
13497 char message[MSG_SIZ];
13500 /* Note: this routine must be called when the clocks are stopped
13501 or when they have *just* been set or switched; otherwise
13502 it will be off by the time since the current tick started.
13504 if (machineWhite) {
13505 time = whiteTimeRemaining / 10;
13506 otime = blackTimeRemaining / 10;
13508 time = blackTimeRemaining / 10;
13509 otime = whiteTimeRemaining / 10;
13511 /* [HGM] translate opponent's time by time-odds factor */
13512 otime = (otime * cps->other->timeOdds) / cps->timeOdds;
13513 if (appData.debugMode) {
13514 fprintf(debugFP, "time odds: %f %f \n", cps->timeOdds, cps->other->timeOdds);
13517 if (time <= 0) time = 1;
13518 if (otime <= 0) otime = 1;
13520 sprintf(message, "time %ld\n", time);
13521 SendToProgram(message, cps);
13523 sprintf(message, "otim %ld\n", otime);
13524 SendToProgram(message, cps);
13528 BoolFeature(p, name, loc, cps)
13532 ChessProgramState *cps;
13535 int len = strlen(name);
13537 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
13539 sscanf(*p, "%d", &val);
13541 while (**p && **p != ' ') (*p)++;
13542 sprintf(buf, "accepted %s\n", name);
13543 SendToProgram(buf, cps);
13550 IntFeature(p, name, loc, cps)
13554 ChessProgramState *cps;
13557 int len = strlen(name);
13558 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
13560 sscanf(*p, "%d", loc);
13561 while (**p && **p != ' ') (*p)++;
13562 sprintf(buf, "accepted %s\n", name);
13563 SendToProgram(buf, cps);
13570 StringFeature(p, name, loc, cps)
13574 ChessProgramState *cps;
13577 int len = strlen(name);
13578 if (strncmp((*p), name, len) == 0
13579 && (*p)[len] == '=' && (*p)[len+1] == '\"') {
13581 sscanf(*p, "%[^\"]", loc);
13582 while (**p && **p != '\"') (*p)++;
13583 if (**p == '\"') (*p)++;
13584 sprintf(buf, "accepted %s\n", name);
13585 SendToProgram(buf, cps);
13592 ParseOption(Option *opt, ChessProgramState *cps)
13593 // [HGM] options: process the string that defines an engine option, and determine
13594 // name, type, default value, and allowed value range
13596 char *p, *q, buf[MSG_SIZ];
13597 int n, min = (-1)<<31, max = 1<<31, def;
13599 if(p = strstr(opt->name, " -spin ")) {
13600 if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
13601 if(max < min) max = min; // enforce consistency
13602 if(def < min) def = min;
13603 if(def > max) def = max;
13608 } else if((p = strstr(opt->name, " -slider "))) {
13609 // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
13610 if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
13611 if(max < min) max = min; // enforce consistency
13612 if(def < min) def = min;
13613 if(def > max) def = max;
13617 opt->type = Spin; // Slider;
13618 } else if((p = strstr(opt->name, " -string "))) {
13619 opt->textValue = p+9;
13620 opt->type = TextBox;
13621 } else if((p = strstr(opt->name, " -file "))) {
13622 // for now -file is a synonym for -string, to already provide compatibility with future polyglots
13623 opt->textValue = p+7;
13624 opt->type = TextBox; // FileName;
13625 } else if((p = strstr(opt->name, " -path "))) {
13626 // for now -file is a synonym for -string, to already provide compatibility with future polyglots
13627 opt->textValue = p+7;
13628 opt->type = TextBox; // PathName;
13629 } else if(p = strstr(opt->name, " -check ")) {
13630 if(sscanf(p, " -check %d", &def) < 1) return FALSE;
13631 opt->value = (def != 0);
13632 opt->type = CheckBox;
13633 } else if(p = strstr(opt->name, " -combo ")) {
13634 opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
13635 cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
13636 if(*q == '*') cps->comboList[cps->comboCnt-1]++;
13637 opt->value = n = 0;
13638 while(q = StrStr(q, " /// ")) {
13639 n++; *q = 0; // count choices, and null-terminate each of them
13641 if(*q == '*') { // remember default, which is marked with * prefix
13645 cps->comboList[cps->comboCnt++] = q;
13647 cps->comboList[cps->comboCnt++] = NULL;
13649 opt->type = ComboBox;
13650 } else if(p = strstr(opt->name, " -button")) {
13651 opt->type = Button;
13652 } else if(p = strstr(opt->name, " -save")) {
13653 opt->type = SaveButton;
13654 } else return FALSE;
13655 *p = 0; // terminate option name
13656 // now look if the command-line options define a setting for this engine option.
13657 if(cps->optionSettings && cps->optionSettings[0])
13658 p = strstr(cps->optionSettings, opt->name); else p = NULL;
13659 if(p && (p == cps->optionSettings || p[-1] == ',')) {
13660 sprintf(buf, "option %s", p);
13661 if(p = strstr(buf, ",")) *p = 0;
13663 SendToProgram(buf, cps);
13669 FeatureDone(cps, val)
13670 ChessProgramState* cps;
13673 DelayedEventCallback cb = GetDelayedEvent();
13674 if ((cb == InitBackEnd3 && cps == &first) ||
13675 (cb == TwoMachinesEventIfReady && cps == &second)) {
13676 CancelDelayedEvent();
13677 ScheduleDelayedEvent(cb, val ? 1 : 3600000);
13679 cps->initDone = val;
13682 /* Parse feature command from engine */
13684 ParseFeatures(args, cps)
13686 ChessProgramState *cps;
13694 while (*p == ' ') p++;
13695 if (*p == NULLCHAR) return;
13697 if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
13698 if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
13699 if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
13700 if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
13701 if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
13702 if (BoolFeature(&p, "reuse", &val, cps)) {
13703 /* Engine can disable reuse, but can't enable it if user said no */
13704 if (!val) cps->reuse = FALSE;
13707 if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
13708 if (StringFeature(&p, "myname", &cps->tidy, cps)) {
13709 if (gameMode == TwoMachinesPlay) {
13710 DisplayTwoMachinesTitle();
13716 if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
13717 if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
13718 if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
13719 if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
13720 if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
13721 if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
13722 if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
13723 if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
13724 if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
13725 if (IntFeature(&p, "done", &val, cps)) {
13726 FeatureDone(cps, val);
13729 /* Added by Tord: */
13730 if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
13731 if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
13732 /* End of additions by Tord */
13734 /* [HGM] added features: */
13735 if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
13736 if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
13737 if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
13738 if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
13739 if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
13740 if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
13741 if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
13742 if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
13743 sprintf(buf, "rejected option %s\n", cps->option[--cps->nrOptions].name);
13744 SendToProgram(buf, cps);
13747 if(cps->nrOptions >= MAX_OPTIONS) {
13749 sprintf(buf, "%s engine has too many options\n", cps->which);
13750 DisplayError(buf, 0);
13754 if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
13755 /* End of additions by HGM */
13757 /* unknown feature: complain and skip */
13759 while (*q && *q != '=') q++;
13760 sprintf(buf, "rejected %.*s\n", (int)(q-p), p);
13761 SendToProgram(buf, cps);
13767 while (*p && *p != '\"') p++;
13768 if (*p == '\"') p++;
13770 while (*p && *p != ' ') p++;
13778 PeriodicUpdatesEvent(newState)
13781 if (newState == appData.periodicUpdates)
13784 appData.periodicUpdates=newState;
13786 /* Display type changes, so update it now */
13787 // DisplayAnalysis();
13789 /* Get the ball rolling again... */
13791 AnalysisPeriodicEvent(1);
13792 StartAnalysisClock();
13797 PonderNextMoveEvent(newState)
13800 if (newState == appData.ponderNextMove) return;
13801 if (gameMode == EditPosition) EditPositionDone(TRUE);
13803 SendToProgram("hard\n", &first);
13804 if (gameMode == TwoMachinesPlay) {
13805 SendToProgram("hard\n", &second);
13808 SendToProgram("easy\n", &first);
13809 thinkOutput[0] = NULLCHAR;
13810 if (gameMode == TwoMachinesPlay) {
13811 SendToProgram("easy\n", &second);
13814 appData.ponderNextMove = newState;
13818 NewSettingEvent(option, command, value)
13824 if (gameMode == EditPosition) EditPositionDone(TRUE);
13825 sprintf(buf, "%s%s %d\n", (option ? "option ": ""), command, value);
13826 SendToProgram(buf, &first);
13827 if (gameMode == TwoMachinesPlay) {
13828 SendToProgram(buf, &second);
13833 ShowThinkingEvent()
13834 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
13836 static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
13837 int newState = appData.showThinking
13838 // [HGM] thinking: other features now need thinking output as well
13839 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
13841 if (oldState == newState) return;
13842 oldState = newState;
13843 if (gameMode == EditPosition) EditPositionDone(TRUE);
13845 SendToProgram("post\n", &first);
13846 if (gameMode == TwoMachinesPlay) {
13847 SendToProgram("post\n", &second);
13850 SendToProgram("nopost\n", &first);
13851 thinkOutput[0] = NULLCHAR;
13852 if (gameMode == TwoMachinesPlay) {
13853 SendToProgram("nopost\n", &second);
13856 // appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
13860 AskQuestionEvent(title, question, replyPrefix, which)
13861 char *title; char *question; char *replyPrefix; char *which;
13863 ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
13864 if (pr == NoProc) return;
13865 AskQuestion(title, question, replyPrefix, pr);
13869 DisplayMove(moveNumber)
13872 char message[MSG_SIZ];
13874 char cpThinkOutput[MSG_SIZ];
13876 if(appData.noGUI) return; // [HGM] fast: suppress display of moves
13878 if (moveNumber == forwardMostMove - 1 ||
13879 gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13881 safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput));
13883 if (strchr(cpThinkOutput, '\n')) {
13884 *strchr(cpThinkOutput, '\n') = NULLCHAR;
13887 *cpThinkOutput = NULLCHAR;
13890 /* [AS] Hide thinking from human user */
13891 if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
13892 *cpThinkOutput = NULLCHAR;
13893 if( thinkOutput[0] != NULLCHAR ) {
13896 for( i=0; i<=hiddenThinkOutputState; i++ ) {
13897 cpThinkOutput[i] = '.';
13899 cpThinkOutput[i] = NULLCHAR;
13900 hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
13904 if (moveNumber == forwardMostMove - 1 &&
13905 gameInfo.resultDetails != NULL) {
13906 if (gameInfo.resultDetails[0] == NULLCHAR) {
13907 sprintf(res, " %s", PGNResult(gameInfo.result));
13909 sprintf(res, " {%s} %s",
13910 gameInfo.resultDetails, PGNResult(gameInfo.result));
13916 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
13917 DisplayMessage(res, cpThinkOutput);
13919 sprintf(message, "%d.%s%s%s", moveNumber / 2 + 1,
13920 WhiteOnMove(moveNumber) ? " " : ".. ",
13921 parseList[moveNumber], res);
13922 DisplayMessage(message, cpThinkOutput);
13927 DisplayComment(moveNumber, text)
13931 char title[MSG_SIZ];
13932 char buf[8000]; // comment can be long!
13934 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
13935 strcpy(title, "Comment");
13937 sprintf(title, "Comment on %d.%s%s", moveNumber / 2 + 1,
13938 WhiteOnMove(moveNumber) ? " " : ".. ",
13939 parseList[moveNumber]);
13941 // [HGM] PV info: display PV info together with (or as) comment
13942 if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {
13943 if(text == NULL) text = "";
13944 score = pvInfoList[moveNumber].score;
13945 sprintf(buf, "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,
13946 depth, (pvInfoList[moveNumber].time+50)/100, text);
13949 if (text != NULL && (appData.autoDisplayComment || commentUp))
13950 CommentPopUp(title, text);
13953 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
13954 * might be busy thinking or pondering. It can be omitted if your
13955 * gnuchess is configured to stop thinking immediately on any user
13956 * input. However, that gnuchess feature depends on the FIONREAD
13957 * ioctl, which does not work properly on some flavors of Unix.
13961 ChessProgramState *cps;
13964 if (!cps->useSigint) return;
13965 if (appData.noChessProgram || (cps->pr == NoProc)) return;
13966 switch (gameMode) {
13967 case MachinePlaysWhite:
13968 case MachinePlaysBlack:
13969 case TwoMachinesPlay:
13970 case IcsPlayingWhite:
13971 case IcsPlayingBlack:
13974 /* Skip if we know it isn't thinking */
13975 if (!cps->maybeThinking) return;
13976 if (appData.debugMode)
13977 fprintf(debugFP, "Interrupting %s\n", cps->which);
13978 InterruptChildProcess(cps->pr);
13979 cps->maybeThinking = FALSE;
13984 #endif /*ATTENTION*/
13990 if (whiteTimeRemaining <= 0) {
13993 if (appData.icsActive) {
13994 if (appData.autoCallFlag &&
13995 gameMode == IcsPlayingBlack && !blackFlag) {
13996 SendToICS(ics_prefix);
13997 SendToICS("flag\n");
14001 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
14003 if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
14004 if (appData.autoCallFlag) {
14005 GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
14012 if (blackTimeRemaining <= 0) {
14015 if (appData.icsActive) {
14016 if (appData.autoCallFlag &&
14017 gameMode == IcsPlayingWhite && !whiteFlag) {
14018 SendToICS(ics_prefix);
14019 SendToICS("flag\n");
14023 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
14025 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
14026 if (appData.autoCallFlag) {
14027 GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
14040 if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
14041 gameMode == PlayFromGameFile || forwardMostMove == 0) return;
14044 * add time to clocks when time control is achieved ([HGM] now also used for increment)
14046 if ( !WhiteOnMove(forwardMostMove) )
14047 /* White made time control */
14048 whiteTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
14049 /* [HGM] time odds: correct new time quota for time odds! */
14050 / WhitePlayer()->timeOdds;
14052 /* Black made time control */
14053 blackTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
14054 / WhitePlayer()->other->timeOdds;
14058 DisplayBothClocks()
14060 int wom = gameMode == EditPosition ?
14061 !blackPlaysFirst : WhiteOnMove(currentMove);
14062 DisplayWhiteClock(whiteTimeRemaining, wom);
14063 DisplayBlackClock(blackTimeRemaining, !wom);
14067 /* Timekeeping seems to be a portability nightmare. I think everyone
14068 has ftime(), but I'm really not sure, so I'm including some ifdefs
14069 to use other calls if you don't. Clocks will be less accurate if
14070 you have neither ftime nor gettimeofday.
14073 /* VS 2008 requires the #include outside of the function */
14074 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
14075 #include <sys/timeb.h>
14078 /* Get the current time as a TimeMark */
14083 #if HAVE_GETTIMEOFDAY
14085 struct timeval timeVal;
14086 struct timezone timeZone;
14088 gettimeofday(&timeVal, &timeZone);
14089 tm->sec = (long) timeVal.tv_sec;
14090 tm->ms = (int) (timeVal.tv_usec / 1000L);
14092 #else /*!HAVE_GETTIMEOFDAY*/
14095 // include <sys/timeb.h> / moved to just above start of function
14096 struct timeb timeB;
14099 tm->sec = (long) timeB.time;
14100 tm->ms = (int) timeB.millitm;
14102 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
14103 tm->sec = (long) time(NULL);
14109 /* Return the difference in milliseconds between two
14110 time marks. We assume the difference will fit in a long!
14113 SubtractTimeMarks(tm2, tm1)
14114 TimeMark *tm2, *tm1;
14116 return 1000L*(tm2->sec - tm1->sec) +
14117 (long) (tm2->ms - tm1->ms);
14122 * Code to manage the game clocks.
14124 * In tournament play, black starts the clock and then white makes a move.
14125 * We give the human user a slight advantage if he is playing white---the
14126 * clocks don't run until he makes his first move, so it takes zero time.
14127 * Also, we don't account for network lag, so we could get out of sync
14128 * with GNU Chess's clock -- but then, referees are always right.
14131 static TimeMark tickStartTM;
14132 static long intendedTickLength;
14135 NextTickLength(timeRemaining)
14136 long timeRemaining;
14138 long nominalTickLength, nextTickLength;
14140 if (timeRemaining > 0L && timeRemaining <= 10000L)
14141 nominalTickLength = 100L;
14143 nominalTickLength = 1000L;
14144 nextTickLength = timeRemaining % nominalTickLength;
14145 if (nextTickLength <= 0) nextTickLength += nominalTickLength;
14147 return nextTickLength;
14150 /* Adjust clock one minute up or down */
14152 AdjustClock(Boolean which, int dir)
14154 if(which) blackTimeRemaining += 60000*dir;
14155 else whiteTimeRemaining += 60000*dir;
14156 DisplayBothClocks();
14159 /* Stop clocks and reset to a fresh time control */
14163 (void) StopClockTimer();
14164 if (appData.icsActive) {
14165 whiteTimeRemaining = blackTimeRemaining = 0;
14166 } else if (searchTime) {
14167 whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
14168 blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
14169 } else { /* [HGM] correct new time quote for time odds */
14170 whiteTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->timeOdds;
14171 blackTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->other->timeOdds;
14173 if (whiteFlag || blackFlag) {
14175 whiteFlag = blackFlag = FALSE;
14177 DisplayBothClocks();
14180 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
14182 /* Decrement running clock by amount of time that has passed */
14186 long timeRemaining;
14187 long lastTickLength, fudge;
14190 if (!appData.clockMode) return;
14191 if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
14195 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
14197 /* Fudge if we woke up a little too soon */
14198 fudge = intendedTickLength - lastTickLength;
14199 if (fudge < 0 || fudge > FUDGE) fudge = 0;
14201 if (WhiteOnMove(forwardMostMove)) {
14202 if(whiteNPS >= 0) lastTickLength = 0;
14203 timeRemaining = whiteTimeRemaining -= lastTickLength;
14204 DisplayWhiteClock(whiteTimeRemaining - fudge,
14205 WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
14207 if(blackNPS >= 0) lastTickLength = 0;
14208 timeRemaining = blackTimeRemaining -= lastTickLength;
14209 DisplayBlackClock(blackTimeRemaining - fudge,
14210 !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
14213 if (CheckFlags()) return;
14216 intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
14217 StartClockTimer(intendedTickLength);
14219 /* if the time remaining has fallen below the alarm threshold, sound the
14220 * alarm. if the alarm has sounded and (due to a takeback or time control
14221 * with increment) the time remaining has increased to a level above the
14222 * threshold, reset the alarm so it can sound again.
14225 if (appData.icsActive && appData.icsAlarm) {
14227 /* make sure we are dealing with the user's clock */
14228 if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
14229 ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
14232 if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
14233 alarmSounded = FALSE;
14234 } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
14236 alarmSounded = TRUE;
14242 /* A player has just moved, so stop the previously running
14243 clock and (if in clock mode) start the other one.
14244 We redisplay both clocks in case we're in ICS mode, because
14245 ICS gives us an update to both clocks after every move.
14246 Note that this routine is called *after* forwardMostMove
14247 is updated, so the last fractional tick must be subtracted
14248 from the color that is *not* on move now.
14253 long lastTickLength;
14255 int flagged = FALSE;
14259 if (StopClockTimer() && appData.clockMode) {
14260 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
14261 if (WhiteOnMove(forwardMostMove)) {
14262 if(blackNPS >= 0) lastTickLength = 0;
14263 blackTimeRemaining -= lastTickLength;
14264 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
14265 // if(pvInfoList[forwardMostMove-1].time == -1)
14266 pvInfoList[forwardMostMove-1].time = // use GUI time
14267 (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
14269 if(whiteNPS >= 0) lastTickLength = 0;
14270 whiteTimeRemaining -= lastTickLength;
14271 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
14272 // if(pvInfoList[forwardMostMove-1].time == -1)
14273 pvInfoList[forwardMostMove-1].time =
14274 (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
14276 flagged = CheckFlags();
14278 CheckTimeControl();
14280 if (flagged || !appData.clockMode) return;
14282 switch (gameMode) {
14283 case MachinePlaysBlack:
14284 case MachinePlaysWhite:
14285 case BeginningOfGame:
14286 if (pausing) return;
14290 case PlayFromGameFile:
14298 if (searchTime) { // [HGM] st: set clock of player that has to move to max time
14299 if(WhiteOnMove(forwardMostMove))
14300 whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
14301 else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
14305 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
14306 whiteTimeRemaining : blackTimeRemaining);
14307 StartClockTimer(intendedTickLength);
14311 /* Stop both clocks */
14315 long lastTickLength;
14318 if (!StopClockTimer()) return;
14319 if (!appData.clockMode) return;
14323 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
14324 if (WhiteOnMove(forwardMostMove)) {
14325 if(whiteNPS >= 0) lastTickLength = 0;
14326 whiteTimeRemaining -= lastTickLength;
14327 DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
14329 if(blackNPS >= 0) lastTickLength = 0;
14330 blackTimeRemaining -= lastTickLength;
14331 DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
14336 /* Start clock of player on move. Time may have been reset, so
14337 if clock is already running, stop and restart it. */
14341 (void) StopClockTimer(); /* in case it was running already */
14342 DisplayBothClocks();
14343 if (CheckFlags()) return;
14345 if (!appData.clockMode) return;
14346 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
14348 GetTimeMark(&tickStartTM);
14349 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
14350 whiteTimeRemaining : blackTimeRemaining);
14352 /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
14353 whiteNPS = blackNPS = -1;
14354 if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
14355 || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
14356 whiteNPS = first.nps;
14357 if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
14358 || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
14359 blackNPS = first.nps;
14360 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
14361 whiteNPS = second.nps;
14362 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
14363 blackNPS = second.nps;
14364 if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
14366 StartClockTimer(intendedTickLength);
14373 long second, minute, hour, day;
14375 static char buf[32];
14377 if (ms > 0 && ms <= 9900) {
14378 /* convert milliseconds to tenths, rounding up */
14379 double tenths = floor( ((double)(ms + 99L)) / 100.00 );
14381 sprintf(buf, " %03.1f ", tenths/10.0);
14385 /* convert milliseconds to seconds, rounding up */
14386 /* use floating point to avoid strangeness of integer division
14387 with negative dividends on many machines */
14388 second = (long) floor(((double) (ms + 999L)) / 1000.0);
14395 day = second / (60 * 60 * 24);
14396 second = second % (60 * 60 * 24);
14397 hour = second / (60 * 60);
14398 second = second % (60 * 60);
14399 minute = second / 60;
14400 second = second % 60;
14403 sprintf(buf, " %s%ld:%02ld:%02ld:%02ld ",
14404 sign, day, hour, minute, second);
14406 sprintf(buf, " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
14408 sprintf(buf, " %s%2ld:%02ld ", sign, minute, second);
14415 * This is necessary because some C libraries aren't ANSI C compliant yet.
14418 StrStr(string, match)
14419 char *string, *match;
14423 length = strlen(match);
14425 for (i = strlen(string) - length; i >= 0; i--, string++)
14426 if (!strncmp(match, string, length))
14433 StrCaseStr(string, match)
14434 char *string, *match;
14438 length = strlen(match);
14440 for (i = strlen(string) - length; i >= 0; i--, string++) {
14441 for (j = 0; j < length; j++) {
14442 if (ToLower(match[j]) != ToLower(string[j]))
14445 if (j == length) return string;
14459 c1 = ToLower(*s1++);
14460 c2 = ToLower(*s2++);
14461 if (c1 > c2) return 1;
14462 if (c1 < c2) return -1;
14463 if (c1 == NULLCHAR) return 0;
14472 return isupper(c) ? tolower(c) : c;
14480 return islower(c) ? toupper(c) : c;
14482 #endif /* !_amigados */
14490 if ((ret = (char *) malloc(strlen(s) + 1))) {
14497 StrSavePtr(s, savePtr)
14498 char *s, **savePtr;
14503 if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
14504 strcpy(*savePtr, s);
14516 clock = time((time_t *)NULL);
14517 tm = localtime(&clock);
14518 sprintf(buf, "%04d.%02d.%02d",
14519 tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
14520 return StrSave(buf);
14525 PositionToFEN(move, overrideCastling)
14527 char *overrideCastling;
14529 int i, j, fromX, fromY, toX, toY;
14536 whiteToPlay = (gameMode == EditPosition) ?
14537 !blackPlaysFirst : (move % 2 == 0);
14540 /* Piece placement data */
14541 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
14543 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
14544 if (boards[move][i][j] == EmptySquare) {
14546 } else { ChessSquare piece = boards[move][i][j];
14547 if (emptycount > 0) {
14548 if(emptycount<10) /* [HGM] can be >= 10 */
14549 *p++ = '0' + emptycount;
14550 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
14553 if(PieceToChar(piece) == '+') {
14554 /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
14556 piece = (ChessSquare)(DEMOTED piece);
14558 *p++ = PieceToChar(piece);
14560 /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
14561 p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
14566 if (emptycount > 0) {
14567 if(emptycount<10) /* [HGM] can be >= 10 */
14568 *p++ = '0' + emptycount;
14569 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
14576 /* [HGM] print Crazyhouse or Shogi holdings */
14577 if( gameInfo.holdingsWidth ) {
14578 *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
14580 for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
14581 piece = boards[move][i][BOARD_WIDTH-1];
14582 if( piece != EmptySquare )
14583 for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
14584 *p++ = PieceToChar(piece);
14586 for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
14587 piece = boards[move][BOARD_HEIGHT-i-1][0];
14588 if( piece != EmptySquare )
14589 for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
14590 *p++ = PieceToChar(piece);
14593 if( q == p ) *p++ = '-';
14599 *p++ = whiteToPlay ? 'w' : 'b';
14602 if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
14603 while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
14605 if(nrCastlingRights) {
14607 if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
14608 /* [HGM] write directly from rights */
14609 if(boards[move][CASTLING][2] != NoRights &&
14610 boards[move][CASTLING][0] != NoRights )
14611 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
14612 if(boards[move][CASTLING][2] != NoRights &&
14613 boards[move][CASTLING][1] != NoRights )
14614 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
14615 if(boards[move][CASTLING][5] != NoRights &&
14616 boards[move][CASTLING][3] != NoRights )
14617 *p++ = boards[move][CASTLING][3] + AAA;
14618 if(boards[move][CASTLING][5] != NoRights &&
14619 boards[move][CASTLING][4] != NoRights )
14620 *p++ = boards[move][CASTLING][4] + AAA;
14623 /* [HGM] write true castling rights */
14624 if( nrCastlingRights == 6 ) {
14625 if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
14626 boards[move][CASTLING][2] != NoRights ) *p++ = 'K';
14627 if(boards[move][CASTLING][1] == BOARD_LEFT &&
14628 boards[move][CASTLING][2] != NoRights ) *p++ = 'Q';
14629 if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
14630 boards[move][CASTLING][5] != NoRights ) *p++ = 'k';
14631 if(boards[move][CASTLING][4] == BOARD_LEFT &&
14632 boards[move][CASTLING][5] != NoRights ) *p++ = 'q';
14635 if (q == p) *p++ = '-'; /* No castling rights */
14639 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
14640 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
14641 /* En passant target square */
14642 if (move > backwardMostMove) {
14643 fromX = moveList[move - 1][0] - AAA;
14644 fromY = moveList[move - 1][1] - ONE;
14645 toX = moveList[move - 1][2] - AAA;
14646 toY = moveList[move - 1][3] - ONE;
14647 if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
14648 toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
14649 boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
14651 /* 2-square pawn move just happened */
14653 *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
14657 } else if(move == backwardMostMove) {
14658 // [HGM] perhaps we should always do it like this, and forget the above?
14659 if((signed char)boards[move][EP_STATUS] >= 0) {
14660 *p++ = boards[move][EP_STATUS] + AAA;
14661 *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
14672 /* [HGM] find reversible plies */
14673 { int i = 0, j=move;
14675 if (appData.debugMode) { int k;
14676 fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
14677 for(k=backwardMostMove; k<=forwardMostMove; k++)
14678 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
14682 while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
14683 if( j == backwardMostMove ) i += initialRulePlies;
14684 sprintf(p, "%d ", i);
14685 p += i>=100 ? 4 : i >= 10 ? 3 : 2;
14687 /* Fullmove number */
14688 sprintf(p, "%d", (move / 2) + 1);
14690 return StrSave(buf);
14694 ParseFEN(board, blackPlaysFirst, fen)
14696 int *blackPlaysFirst;
14706 /* [HGM] by default clear Crazyhouse holdings, if present */
14707 if(gameInfo.holdingsWidth) {
14708 for(i=0; i<BOARD_HEIGHT; i++) {
14709 board[i][0] = EmptySquare; /* black holdings */
14710 board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
14711 board[i][1] = (ChessSquare) 0; /* black counts */
14712 board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
14716 /* Piece placement data */
14717 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
14720 if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
14721 if (*p == '/') p++;
14722 emptycount = gameInfo.boardWidth - j;
14723 while (emptycount--)
14724 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
14726 #if(BOARD_FILES >= 10)
14727 } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
14728 p++; emptycount=10;
14729 if (j + emptycount > gameInfo.boardWidth) return FALSE;
14730 while (emptycount--)
14731 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
14733 } else if (isdigit(*p)) {
14734 emptycount = *p++ - '0';
14735 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
14736 if (j + emptycount > gameInfo.boardWidth) return FALSE;
14737 while (emptycount--)
14738 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
14739 } else if (*p == '+' || isalpha(*p)) {
14740 if (j >= gameInfo.boardWidth) return FALSE;
14742 piece = CharToPiece(*++p);
14743 if(piece == EmptySquare) return FALSE; /* unknown piece */
14744 piece = (ChessSquare) (PROMOTED piece ); p++;
14745 if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
14746 } else piece = CharToPiece(*p++);
14748 if(piece==EmptySquare) return FALSE; /* unknown piece */
14749 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
14750 piece = (ChessSquare) (PROMOTED piece);
14751 if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
14754 board[i][(j++)+gameInfo.holdingsWidth] = piece;
14760 while (*p == '/' || *p == ' ') p++;
14762 /* [HGM] look for Crazyhouse holdings here */
14763 while(*p==' ') p++;
14764 if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
14766 if(*p == '-' ) *p++; /* empty holdings */ else {
14767 if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
14768 /* if we would allow FEN reading to set board size, we would */
14769 /* have to add holdings and shift the board read so far here */
14770 while( (piece = CharToPiece(*p) ) != EmptySquare ) {
14772 if((int) piece >= (int) BlackPawn ) {
14773 i = (int)piece - (int)BlackPawn;
14774 i = PieceToNumber((ChessSquare)i);
14775 if( i >= gameInfo.holdingsSize ) return FALSE;
14776 board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
14777 board[BOARD_HEIGHT-1-i][1]++; /* black counts */
14779 i = (int)piece - (int)WhitePawn;
14780 i = PieceToNumber((ChessSquare)i);
14781 if( i >= gameInfo.holdingsSize ) return FALSE;
14782 board[i][BOARD_WIDTH-1] = piece; /* white holdings */
14783 board[i][BOARD_WIDTH-2]++; /* black holdings */
14787 if(*p == ']') *p++;
14790 while(*p == ' ') p++;
14795 *blackPlaysFirst = FALSE;
14798 *blackPlaysFirst = TRUE;
14804 /* [HGM] We NO LONGER ignore the rest of the FEN notation */
14805 /* return the extra info in global variiables */
14807 /* set defaults in case FEN is incomplete */
14808 board[EP_STATUS] = EP_UNKNOWN;
14809 for(i=0; i<nrCastlingRights; i++ ) {
14810 board[CASTLING][i] =
14811 gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
14812 } /* assume possible unless obviously impossible */
14813 if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
14814 if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
14815 if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
14816 && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
14817 if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
14818 if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
14819 if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
14820 && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
14823 while(*p==' ') p++;
14824 if(nrCastlingRights) {
14825 if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
14826 /* castling indicator present, so default becomes no castlings */
14827 for(i=0; i<nrCastlingRights; i++ ) {
14828 board[CASTLING][i] = NoRights;
14831 while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
14832 (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
14833 ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
14834 ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth) ) {
14835 char c = *p++; int whiteKingFile=NoRights, blackKingFile=NoRights;
14837 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
14838 if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
14839 if(board[0 ][i] == WhiteKing) whiteKingFile = i;
14841 if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
14842 whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
14843 if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
14844 && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
14845 if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
14846 && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
14849 for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
14850 board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
14851 board[CASTLING][2] = whiteKingFile;
14854 for(i=BOARD_LEFT; i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
14855 board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
14856 board[CASTLING][2] = whiteKingFile;
14859 for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
14860 board[CASTLING][3] = i != blackKingFile ? i : NoRights;
14861 board[CASTLING][5] = blackKingFile;
14864 for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
14865 board[CASTLING][4] = i != blackKingFile ? i : NoRights;
14866 board[CASTLING][5] = blackKingFile;
14869 default: /* FRC castlings */
14870 if(c >= 'a') { /* black rights */
14871 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
14872 if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
14873 if(i == BOARD_RGHT) break;
14874 board[CASTLING][5] = i;
14876 if(board[BOARD_HEIGHT-1][c] < BlackPawn ||
14877 board[BOARD_HEIGHT-1][c] >= BlackKing ) break;
14879 board[CASTLING][3] = c;
14881 board[CASTLING][4] = c;
14882 } else { /* white rights */
14883 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
14884 if(board[0][i] == WhiteKing) break;
14885 if(i == BOARD_RGHT) break;
14886 board[CASTLING][2] = i;
14887 c -= AAA - 'a' + 'A';
14888 if(board[0][c] >= WhiteKing) break;
14890 board[CASTLING][0] = c;
14892 board[CASTLING][1] = c;
14896 for(i=0; i<nrCastlingRights; i++)
14897 if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
14898 if (appData.debugMode) {
14899 fprintf(debugFP, "FEN castling rights:");
14900 for(i=0; i<nrCastlingRights; i++)
14901 fprintf(debugFP, " %d", board[CASTLING][i]);
14902 fprintf(debugFP, "\n");
14905 while(*p==' ') p++;
14908 /* read e.p. field in games that know e.p. capture */
14909 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
14910 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
14912 p++; board[EP_STATUS] = EP_NONE;
14914 char c = *p++ - AAA;
14916 if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
14917 if(*p >= '0' && *p <='9') *p++;
14918 board[EP_STATUS] = c;
14923 if(sscanf(p, "%d", &i) == 1) {
14924 FENrulePlies = i; /* 50-move ply counter */
14925 /* (The move number is still ignored) */
14932 EditPositionPasteFEN(char *fen)
14935 Board initial_position;
14937 if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
14938 DisplayError(_("Bad FEN position in clipboard"), 0);
14941 int savedBlackPlaysFirst = blackPlaysFirst;
14942 EditPositionEvent();
14943 blackPlaysFirst = savedBlackPlaysFirst;
14944 CopyBoard(boards[0], initial_position);
14945 initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
14946 EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
14947 DisplayBothClocks();
14948 DrawPosition(FALSE, boards[currentMove]);
14953 static char cseq[12] = "\\ ";
14955 Boolean set_cont_sequence(char *new_seq)
14960 // handle bad attempts to set the sequence
14962 return 0; // acceptable error - no debug
14964 len = strlen(new_seq);
14965 ret = (len > 0) && (len < sizeof(cseq));
14967 strcpy(cseq, new_seq);
14968 else if (appData.debugMode)
14969 fprintf(debugFP, "Invalid continuation sequence \"%s\" (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
14974 reformat a source message so words don't cross the width boundary. internal
14975 newlines are not removed. returns the wrapped size (no null character unless
14976 included in source message). If dest is NULL, only calculate the size required
14977 for the dest buffer. lp argument indicats line position upon entry, and it's
14978 passed back upon exit.
14980 int wrap(char *dest, char *src, int count, int width, int *lp)
14982 int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
14984 cseq_len = strlen(cseq);
14985 old_line = line = *lp;
14986 ansi = len = clen = 0;
14988 for (i=0; i < count; i++)
14990 if (src[i] == '\033')
14993 // if we hit the width, back up
14994 if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
14996 // store i & len in case the word is too long
14997 old_i = i, old_len = len;
14999 // find the end of the last word
15000 while (i && src[i] != ' ' && src[i] != '\n')
15006 // word too long? restore i & len before splitting it
15007 if ((old_i-i+clen) >= width)
15014 if (i && src[i-1] == ' ')
15017 if (src[i] != ' ' && src[i] != '\n')
15024 // now append the newline and continuation sequence
15029 strncpy(dest+len, cseq, cseq_len);
15037 dest[len] = src[i];
15041 if (src[i] == '\n')
15046 if (dest && appData.debugMode)
15048 fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
15049 count, width, line, len, *lp);
15050 show_bytes(debugFP, src, count);
15051 fprintf(debugFP, "\ndest: ");
15052 show_bytes(debugFP, dest, len);
15053 fprintf(debugFP, "\n");
15055 *lp = dest ? line : old_line;
15060 // [HGM] vari: routines for shelving variations
15063 PushTail(int firstMove, int lastMove)
15065 int i, j, nrMoves = lastMove - firstMove;
15067 if(appData.icsActive) { // only in local mode
15068 forwardMostMove = currentMove; // mimic old ICS behavior
15071 if(storedGames >= MAX_VARIATIONS-1) return;
15073 // push current tail of game on stack
15074 savedResult[storedGames] = gameInfo.result;
15075 savedDetails[storedGames] = gameInfo.resultDetails;
15076 gameInfo.resultDetails = NULL;
15077 savedFirst[storedGames] = firstMove;
15078 savedLast [storedGames] = lastMove;
15079 savedFramePtr[storedGames] = framePtr;
15080 framePtr -= nrMoves; // reserve space for the boards
15081 for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
15082 CopyBoard(boards[framePtr+i], boards[firstMove+i]);
15083 for(j=0; j<MOVE_LEN; j++)
15084 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
15085 for(j=0; j<2*MOVE_LEN; j++)
15086 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
15087 timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
15088 timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
15089 pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
15090 pvInfoList[firstMove+i-1].depth = 0;
15091 commentList[framePtr+i] = commentList[firstMove+i];
15092 commentList[firstMove+i] = NULL;
15096 forwardMostMove = currentMove; // truncte game so we can start variation
15097 if(storedGames == 1) GreyRevert(FALSE);
15101 PopTail(Boolean annotate)
15104 char buf[8000], moveBuf[20];
15106 if(appData.icsActive) return FALSE; // only in local mode
15107 if(!storedGames) return FALSE; // sanity
15110 ToNrEvent(savedFirst[storedGames]); // sets currentMove
15111 nrMoves = savedLast[storedGames] - currentMove;
15114 if(!WhiteOnMove(currentMove)) sprintf(buf, "(%d...", currentMove+2>>1);
15115 else strcpy(buf, "(");
15116 for(i=currentMove; i<forwardMostMove; i++) {
15118 sprintf(moveBuf, " %d. %s", i+2>>1, SavePart(parseList[i]));
15119 else sprintf(moveBuf, " %s", SavePart(parseList[i]));
15120 strcat(buf, moveBuf);
15121 if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
15125 for(i=1; i<nrMoves; i++) { // copy last variation back
15126 CopyBoard(boards[currentMove+i], boards[framePtr+i]);
15127 for(j=0; j<MOVE_LEN; j++)
15128 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
15129 for(j=0; j<2*MOVE_LEN; j++)
15130 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
15131 timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
15132 timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
15133 pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
15134 if(commentList[currentMove+i]) free(commentList[currentMove+i]);
15135 commentList[currentMove+i] = commentList[framePtr+i];
15136 commentList[framePtr+i] = NULL;
15138 if(annotate) AppendComment(currentMove+1, buf, FALSE);
15139 framePtr = savedFramePtr[storedGames];
15140 gameInfo.result = savedResult[storedGames];
15141 if(gameInfo.resultDetails != NULL) {
15142 free(gameInfo.resultDetails);
15144 gameInfo.resultDetails = savedDetails[storedGames];
15145 forwardMostMove = currentMove + nrMoves;
15146 if(storedGames == 0) GreyRevert(TRUE);
15152 { // remove all shelved variations
15154 for(i=0; i<storedGames; i++) {
15155 if(savedDetails[i])
15156 free(savedDetails[i]);
15157 savedDetails[i] = NULL;
15159 for(i=framePtr; i<MAX_MOVES; i++) {
15160 if(commentList[i]) free(commentList[i]);
15161 commentList[i] = NULL;
15163 framePtr = MAX_MOVES-1;