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)
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;
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);
6140 PromoPopUp(ChessSquare piece)
6141 { // determine the layout of the piece-choice dialog
6143 ChessSquare list[EmptySquare];
6145 if(gameInfo.variant == VariantShogi) {
6146 // non-Pawn promotes; must be shogi
6147 h = 1; w = 1; list[0] = piece;
6148 if(PieceToChar(PROMOTED piece) != '.') {
6149 // promoted version is enabled
6150 w = 2; list[1] = PROMOTED piece;
6153 // Pawn, promotes to any enabled other piece
6155 for(i=1; i<EmptySquare/2; i++) {
6156 if(PieceToChar(piece+i) != '.'
6157 && PieceToChar(PROMOTED piece + i) != '~' // suppress bughouse true pieces
6159 list[w++] = piece+i; nr++;
6162 if(appData.testLegality && gameInfo.variant != VariantSuicide) nr--,w--; // remove King
6163 h = hTab[nr]; w = wTab[nr]; // factorize with nice ratio
6164 for(i=0; i<BOARD_HEIGHT; i++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++) promoBoard[i][j] = EmptySquare;
6165 for(i=0; i < nr; i++) promoBoard[BOARD_LEFT+i/w][i%w] = list[i]; // layout
6167 promotionChoice = 2; // wait for click on board
6168 PromoDialog(h, w, promoBoard, FALSE);
6172 Mark(board, flags, kind, rf, ff, rt, ft, closure)
6179 typedef char Markers[BOARD_RANKS][BOARD_FILES];
6180 Markers *m = (Markers *) closure;
6181 if(rf == fromY && ff == fromX)
6182 (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
6183 || kind == WhiteCapturesEnPassant
6184 || kind == BlackCapturesEnPassant);
6185 else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
6189 MarkTargetSquares(int clear)
6192 if(!appData.markers || !appData.highlightDragging ||
6193 !appData.testLegality || gameMode == EditPosition) return;
6195 for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) marker[y][x] = 0;
6198 GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker);
6199 if(PosFlags(0) & F_MANDATORY_CAPTURE) {
6200 for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
6202 for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
6205 DrawPosition(TRUE, NULL);
6208 void LeftClick(ClickType clickType, int xPix, int yPix)
6211 Boolean saveAnimate;
6212 static int second = 0;
6213 char promoChoice = NULLCHAR;
6215 if(appData.seekGraph && appData.icsActive && loggedOn &&
6216 (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
6217 SeekGraphClick(clickType, xPix, yPix, 0);
6221 if (clickType == Press) ErrorPopDown();
6222 MarkTargetSquares(1);
6224 x = EventToSquare(xPix, BOARD_WIDTH);
6225 y = EventToSquare(yPix, BOARD_HEIGHT);
6226 if (!flipView && y >= 0) {
6227 y = BOARD_HEIGHT - 1 - y;
6229 if (flipView && x >= 0) {
6230 x = BOARD_WIDTH - 1 - x;
6233 if(promotionChoice) { // we are waiting for a click to indicate promotion piece
6234 ChessSquare p = EmptySquare; Boolean inHoldings;
6235 if(promotionChoice == 3) {
6236 if(clickType == Press) EditPositionMenuEvent(promoBoard[y][x], fromX, fromY);
6237 else if(clickType == Release) promotionChoice = 0;
6241 if(clickType == Release) return; // ignore upclick of click-click destination
6242 if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
6243 inHoldings = gameInfo.holdingsWidth &&
6244 (WhiteOnMove(currentMove)
6245 ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y > 0
6246 : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT-1);
6247 // click in right holdings, for determining promotion piece
6248 if(promotionChoice == 1 && inHoldings || promotionChoice == 2 && x >= BOARD_LEFT && x < BOARD_RGHT) {
6249 p = promoBoard[y][x];
6250 if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
6251 if(p != EmptySquare) {
6252 FinishMove(NormalMove, fromX, fromY, toX, toY, ToLower(PieceToChar(p)));
6254 promotionChoice = 0;
6258 promotionChoice = 0; // only one chance: if click not OK it is interpreted as cancel
6259 DrawPosition(FALSE, boards[currentMove]);
6263 /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
6264 if(clickType == Press
6265 && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
6266 || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
6267 || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
6271 if(!appData.oneClick || !OnlyMove(&x, &y)) {
6272 if (clickType == Press) {
6274 if (OKToStartUserMove(x, y)) {
6278 MarkTargetSquares(0);
6279 DragPieceBegin(xPix, yPix);
6280 if (appData.highlightDragging) {
6281 SetHighlights(x, y, -1, -1);
6290 if (clickType == Press && gameMode != EditPosition) {
6295 // ignore off-board to clicks
6296 if(y < 0 || x < 0) return;
6298 /* Check if clicking again on the same color piece */
6299 fromP = boards[currentMove][fromY][fromX];
6300 toP = boards[currentMove][y][x];
6301 frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom;
6302 if ((WhitePawn <= fromP && fromP <= WhiteKing &&
6303 WhitePawn <= toP && toP <= WhiteKing &&
6304 !(fromP == WhiteKing && toP == WhiteRook && frc) &&
6305 !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
6306 (BlackPawn <= fromP && fromP <= BlackKing &&
6307 BlackPawn <= toP && toP <= BlackKing &&
6308 !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
6309 !(fromP == BlackKing && toP == BlackRook && frc))) {
6310 /* Clicked again on same color piece -- changed his mind */
6311 second = (x == fromX && y == fromY);
6312 if (appData.highlightDragging) {
6313 SetHighlights(x, y, -1, -1);
6317 if (OKToStartUserMove(x, y)) {
6320 MarkTargetSquares(0);
6321 DragPieceBegin(xPix, yPix);
6325 // ignore clicks on holdings
6326 if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
6329 if (clickType == Release && x == fromX && y == fromY) {
6330 DragPieceEnd(xPix, yPix);
6331 if (appData.animateDragging) {
6332 /* Undo animation damage if any */
6333 DrawPosition(FALSE, NULL);
6336 /* Second up/down in same square; just abort move */
6341 ClearPremoveHighlights();
6343 /* First upclick in same square; start click-click mode */
6344 SetHighlights(x, y, -1, -1);
6349 /* we now have a different from- and (possibly off-board) to-square */
6350 /* Completed move */
6353 saveAnimate = appData.animate;
6354 if (clickType == Press) {
6355 /* Finish clickclick move */
6356 if (appData.animate || appData.highlightLastMove) {
6357 SetHighlights(fromX, fromY, toX, toY);
6362 /* Finish drag move */
6363 if (appData.highlightLastMove) {
6364 SetHighlights(fromX, fromY, toX, toY);
6368 DragPieceEnd(xPix, yPix);
6369 /* Don't animate move and drag both */
6370 appData.animate = FALSE;
6373 // moves into holding are invalid for now (except in EditPosition, adapting to-square)
6374 if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
6375 ChessSquare piece = boards[currentMove][fromY][fromX];
6376 if(gameMode == EditPosition && piece != EmptySquare &&
6377 fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
6380 if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
6381 n = PieceToNumber(piece - (int)BlackPawn);
6382 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
6383 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
6384 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
6386 if(x == BOARD_RGHT+1 && piece < BlackPawn) {
6387 n = PieceToNumber(piece);
6388 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
6389 boards[currentMove][n][BOARD_WIDTH-1] = piece;
6390 boards[currentMove][n][BOARD_WIDTH-2]++;
6392 boards[currentMove][fromY][fromX] = EmptySquare;
6396 DrawPosition(TRUE, boards[currentMove]);
6400 // off-board moves should not be highlighted
6401 if(x < 0 || x < 0) ClearHighlights();
6403 if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice)) {
6404 SetHighlights(fromX, fromY, toX, toY);
6405 if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
6406 // [HGM] super: promotion to captured piece selected from holdings
6407 ChessSquare p = boards[currentMove][fromY][fromX];
6408 promotionChoice = 1;
6409 CopyBoard(promoBoard, boards[currentMove]);
6410 // kludge follows to temporarily execute move on display, without promoting yet
6411 promoBoard[fromY][fromX] = EmptySquare; // move Pawn to 8th rank
6412 promoBoard[toY][toX] = p;
6413 DrawPosition(FALSE, promoBoard);
6414 DisplayMessage("Click in holdings to choose piece", "");
6417 CopyBoard(promoBoard, boards[currentMove]);
6418 PromoPopUp(boards[currentMove][fromY][fromX]);
6420 UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
6421 if (!appData.highlightLastMove || gotPremove) ClearHighlights();
6422 if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
6425 appData.animate = saveAnimate;
6426 if (appData.animate || appData.animateDragging) {
6427 /* Undo animation damage if needed */
6428 DrawPosition(FALSE, NULL);
6432 int RightClick(ClickType action, int x, int y, int *xx, int *yy)
6433 { // front-end-free part taken out of PieceMenuPopup
6434 int whichMenu; int xSqr, ySqr;
6436 if(seekGraphUp) { // [HGM] seekgraph
6437 if(action == Press) SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
6438 if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
6442 xSqr = EventToSquare(x, BOARD_WIDTH);
6443 ySqr = EventToSquare(y, BOARD_HEIGHT);
6445 xSqr = BOARD_WIDTH - 1 - xSqr;
6447 ySqr = BOARD_HEIGHT - 1 - ySqr;
6448 if(promotionChoice == 3 && action == Release
6449 && promoBoard[ySqr][xSqr] != EmptySquare && (xSqr != fromX || ySqr != fromY) // not needed if separate window
6451 EditPositionMenuEvent(promoBoard[ySqr][xSqr], fromX, fromY);
6453 promotionChoice = 0;
6456 if (action == Release) UnLoadPV(); // [HGM] pv
6457 if (action != Press) return -2; // return code to be ignored
6460 if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;
6462 if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;
6463 if (xSqr < 0 || ySqr < 0) return -1;
6464 whichMenu = 0; // edit-position menu
6467 if(!appData.icsEngineAnalyze) return -1;
6468 case IcsPlayingWhite:
6469 case IcsPlayingBlack:
6470 if(!appData.zippyPlay) goto noZip;
6473 case MachinePlaysWhite:
6474 case MachinePlaysBlack:
6475 case TwoMachinesPlay: // [HGM] pv: use for showing PV
6476 if (!appData.dropMenu) {
6478 return 2; // flag front-end to grab mouse events
6480 if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
6481 gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
6484 if (xSqr < 0 || ySqr < 0) return -1;
6485 if (!appData.dropMenu || appData.testLegality &&
6486 gameInfo.variant != VariantBughouse &&
6487 gameInfo.variant != VariantCrazyhouse) return -1;
6488 whichMenu = 1; // drop menu
6494 if (((*xx = xSqr) < 0) ||
6495 ((*yy = ySqr) < 0)) {
6500 fromX = *xx; fromY = *yy;
6501 if(whichMenu == 0) { PiecePopUp(); return -1; } // suppress EditPosition menu
6506 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
6508 // char * hint = lastHint;
6509 FrontEndProgramStats stats;
6511 stats.which = cps == &first ? 0 : 1;
6512 stats.depth = cpstats->depth;
6513 stats.nodes = cpstats->nodes;
6514 stats.score = cpstats->score;
6515 stats.time = cpstats->time;
6516 stats.pv = cpstats->movelist;
6517 stats.hint = lastHint;
6518 stats.an_move_index = 0;
6519 stats.an_move_count = 0;
6521 if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
6522 stats.hint = cpstats->move_name;
6523 stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
6524 stats.an_move_count = cpstats->nr_moves;
6527 if(stats.pv && stats.pv[0]) strcpy(lastPV[stats.which], stats.pv); // [HGM] pv: remember last PV of each
6529 SetProgramStats( &stats );
6533 Adjudicate(ChessProgramState *cps)
6534 { // [HGM] some adjudications useful with buggy engines
6535 // [HGM] adjudicate: made into separate routine, which now can be called after every move
6536 // In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
6537 // Actually ending the game is now based on the additional internal condition canAdjudicate.
6538 // Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
6539 int k, count = 0; static int bare = 1;
6540 ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
6541 Boolean canAdjudicate = !appData.icsActive;
6543 // most tests only when we understand the game, i.e. legality-checking on, and (for the time being) no piece drops
6544 if(gameInfo.holdingsSize == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
6545 if( appData.testLegality )
6546 { /* [HGM] Some more adjudications for obstinate engines */
6547 int NrWN=0, NrBN=0, NrWB=0, NrBB=0, NrWR=0, NrBR=0,
6548 NrWQ=0, NrBQ=0, NrW=0, NrK=0, bishopsColor = 0,
6549 NrPieces=0, NrPawns=0, PawnAdvance=0, i, j;
6550 static int moveCount = 6;
6552 char *reason = NULL;
6555 /* Count what is on board. */
6556 for(i=0; i<BOARD_HEIGHT; i++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
6557 { ChessSquare p = boards[forwardMostMove][i][j];
6561 { /* count B,N,R and other of each side */
6564 NrK++; break; // [HGM] atomic: count Kings
6568 case WhiteFerz: // [HGM] shatranj: kludge to mke it work in shatranj
6569 bishopsColor |= 1 << ((i^j)&1);
6574 case BlackFerz: // [HGM] shatranj: kludge to mke it work in shatranj
6575 bishopsColor |= 1 << ((i^j)&1);
6590 PawnAdvance += m; NrPawns++;
6592 NrPieces += (p != EmptySquare);
6593 NrW += ((int)p < (int)BlackPawn);
6594 if(gameInfo.variant == VariantXiangqi &&
6595 (p == WhiteFerz || p == WhiteAlfil || p == BlackFerz || p == BlackAlfil)) {
6596 NrPieces--; // [HGM] XQ: do not count purely defensive pieces
6597 NrW -= ((int)p < (int)BlackPawn);
6601 /* Some material-based adjudications that have to be made before stalemate test */
6602 if(gameInfo.variant == VariantAtomic && NrK < 2) {
6603 // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
6604 boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
6605 if(canAdjudicate && appData.checkMates) {
6607 SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
6608 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6609 GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
6610 "Xboard adjudication: King destroyed", GE_XBOARD );
6615 /* Bare King in Shatranj (loses) or Losers (wins) */
6616 if( NrW == 1 || NrPieces - NrW == 1) {
6617 if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
6618 boards[forwardMostMove][EP_STATUS] = EP_WINS; // mark as win, so it becomes claimable
6619 if(canAdjudicate && appData.checkMates) {
6621 SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
6622 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6623 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
6624 "Xboard adjudication: Bare king", GE_XBOARD );
6628 if( gameInfo.variant == VariantShatranj && --bare < 0)
6630 boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
6631 if(canAdjudicate && appData.checkMates) {
6632 /* but only adjudicate if adjudication enabled */
6634 SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
6635 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6636 GameEnds( NrW > 1 ? WhiteWins : NrPieces - NrW > 1 ? BlackWins : GameIsDrawn,
6637 "Xboard adjudication: Bare king", GE_XBOARD );
6644 // don't wait for engine to announce game end if we can judge ourselves
6645 switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
6647 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
6648 int i, checkCnt = 0; // (should really be done by making nr of checks part of game state)
6649 for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
6650 if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
6653 reason = "Xboard adjudication: 3rd check";
6654 boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
6664 reason = "Xboard adjudication: Stalemate";
6665 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
6666 boards[forwardMostMove][EP_STATUS] = EP_STALEMATE; // default result for stalemate is draw
6667 if(gameInfo.variant == VariantLosers || gameInfo.variant == VariantGiveaway) // [HGM] losers:
6668 boards[forwardMostMove][EP_STATUS] = EP_WINS; // in these variants stalemated is always a win
6669 else if(gameInfo.variant == VariantSuicide) // in suicide it depends
6670 boards[forwardMostMove][EP_STATUS] = NrW == NrPieces-NrW ? EP_STALEMATE :
6671 ((NrW < NrPieces-NrW) != WhiteOnMove(forwardMostMove) ?
6672 EP_CHECKMATE : EP_WINS);
6673 else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
6674 boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
6678 reason = "Xboard adjudication: Checkmate";
6679 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
6683 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
6685 result = GameIsDrawn; break;
6687 result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
6689 result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
6691 result = (ChessMove) 0;
6693 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
6695 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6696 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6697 GameEnds( result, reason, GE_XBOARD );
6701 /* Next absolutely insufficient mating material. */
6702 if( NrPieces == 2 || gameInfo.variant != VariantXiangqi &&
6703 gameInfo.variant != VariantShatranj && // [HGM] baring will remain possible
6704 (NrPieces == 3 && NrWN+NrBN+NrWB+NrBB == 1 ||
6705 NrPieces == NrBB+NrWB+2 && bishopsColor != 3)) // [HGM] all Bishops (Ferz!) same color
6706 { /* KBK, KNK, KK of KBKB with like Bishops */
6708 /* always flag draws, for judging claims */
6709 boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
6711 if(canAdjudicate && appData.materialDraws) {
6712 /* but only adjudicate them if adjudication enabled */
6713 if(engineOpponent) {
6714 SendToProgram("force\n", engineOpponent); // suppress reply
6715 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
6717 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6718 GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
6723 /* Then some trivial draws (only adjudicate, cannot be claimed) */
6725 ( NrWR == 1 && NrBR == 1 /* KRKR */
6726 || NrWQ==1 && NrBQ==1 /* KQKQ */
6727 || NrWN==2 || NrBN==2 /* KNNK */
6728 || NrWN+NrWB == 1 && NrBN+NrBB == 1 /* KBKN, KBKB, KNKN */
6730 if(canAdjudicate && --moveCount < 0 && appData.trivialDraws)
6731 { /* if the first 3 moves do not show a tactical win, declare draw */
6732 if(engineOpponent) {
6733 SendToProgram("force\n", engineOpponent); // suppress reply
6734 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6736 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6737 GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
6740 } else moveCount = 6;
6744 if (appData.debugMode) { int i;
6745 fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
6746 forwardMostMove, backwardMostMove, boards[backwardMostMove][EP_STATUS],
6747 appData.drawRepeats);
6748 for( i=forwardMostMove; i>=backwardMostMove; i-- )
6749 fprintf(debugFP, "%d ep=%d\n", i, (signed char)boards[i][EP_STATUS]);
6753 // Repetition draws and 50-move rule can be applied independently of legality testing
6755 /* Check for rep-draws */
6757 for(k = forwardMostMove-2;
6758 k>=backwardMostMove && k>=forwardMostMove-100 &&
6759 (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
6760 (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE;
6763 if(CompareBoards(boards[k], boards[forwardMostMove])) {
6764 /* compare castling rights */
6765 if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
6766 (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
6767 rights++; /* King lost rights, while rook still had them */
6768 if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
6769 if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
6770 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
6771 rights++; /* but at least one rook lost them */
6773 if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
6774 (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
6776 if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
6777 if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
6778 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
6781 if( canAdjudicate && rights == 0 && ++count > appData.drawRepeats-2
6782 && appData.drawRepeats > 1) {
6783 /* adjudicate after user-specified nr of repeats */
6784 if(engineOpponent) {
6785 SendToProgram("force\n", engineOpponent); // suppress reply
6786 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6788 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6789 if(gameInfo.variant == VariantXiangqi && appData.testLegality) {
6790 // [HGM] xiangqi: check for forbidden perpetuals
6791 int m, ourPerpetual = 1, hisPerpetual = 1;
6792 for(m=forwardMostMove; m>k; m-=2) {
6793 if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
6794 ourPerpetual = 0; // the current mover did not always check
6795 if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
6796 hisPerpetual = 0; // the opponent did not always check
6798 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
6799 ourPerpetual, hisPerpetual);
6800 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
6801 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
6802 "Xboard adjudication: perpetual checking", GE_XBOARD );
6805 if(hisPerpetual && !ourPerpetual) // he is checking us, but did not repeat yet
6806 break; // (or we would have caught him before). Abort repetition-checking loop.
6807 // Now check for perpetual chases
6808 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
6809 hisPerpetual = PerpetualChase(k, forwardMostMove);
6810 ourPerpetual = PerpetualChase(k+1, forwardMostMove);
6811 if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
6812 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
6813 "Xboard adjudication: perpetual chasing", GE_XBOARD );
6816 if(hisPerpetual && !ourPerpetual) // he is chasing us, but did not repeat yet
6817 break; // Abort repetition-checking loop.
6819 // if neither of us is checking or chasing all the time, or both are, it is draw
6821 GameEnds( GameIsDrawn, "Xboard adjudication: repetition draw", GE_XBOARD );
6824 if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
6825 boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
6829 /* Now we test for 50-move draws. Determine ply count */
6830 count = forwardMostMove;
6831 /* look for last irreversble move */
6832 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
6834 /* if we hit starting position, add initial plies */
6835 if( count == backwardMostMove )
6836 count -= initialRulePlies;
6837 count = forwardMostMove - count;
6839 boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
6840 /* this is used to judge if draw claims are legal */
6841 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
6842 if(engineOpponent) {
6843 SendToProgram("force\n", engineOpponent); // suppress reply
6844 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6846 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6847 GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
6851 /* if draw offer is pending, treat it as a draw claim
6852 * when draw condition present, to allow engines a way to
6853 * claim draws before making their move to avoid a race
6854 * condition occurring after their move
6856 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
6858 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
6859 p = "Draw claim: 50-move rule";
6860 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
6861 p = "Draw claim: 3-fold repetition";
6862 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
6863 p = "Draw claim: insufficient mating material";
6864 if( p != NULL && canAdjudicate) {
6865 if(engineOpponent) {
6866 SendToProgram("force\n", engineOpponent); // suppress reply
6867 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6869 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6870 GameEnds( GameIsDrawn, p, GE_XBOARD );
6875 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
6876 if(engineOpponent) {
6877 SendToProgram("force\n", engineOpponent); // suppress reply
6878 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6880 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6881 GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
6887 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
6888 { // [HGM] book: this routine intercepts moves to simulate book replies
6889 char *bookHit = NULL;
6891 //first determine if the incoming move brings opponent into his book
6892 if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
6893 bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
6894 if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
6895 if(bookHit != NULL && !cps->bookSuspend) {
6896 // make sure opponent is not going to reply after receiving move to book position
6897 SendToProgram("force\n", cps);
6898 cps->bookSuspend = TRUE; // flag indicating it has to be restarted
6900 if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
6901 // now arrange restart after book miss
6903 // after a book hit we never send 'go', and the code after the call to this routine
6904 // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
6906 if (cps->useUsermove) sprintf(buf, "usermove "); // sorry, no SAN yet :(
6907 sprintf(buf, "%s\n", bookHit); // force book move into program supposed to play it
6908 SendToProgram(buf, cps);
6909 if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
6910 } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
6911 SendToProgram("go\n", cps);
6912 cps->bookSuspend = FALSE; // after a 'go' we are never suspended
6913 } else { // 'go' might be sent based on 'firstMove' after this routine returns
6914 if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
6915 SendToProgram("go\n", cps);
6916 cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
6918 return bookHit; // notify caller of hit, so it can take action to send move to opponent
6922 ChessProgramState *savedState;
6923 void DeferredBookMove(void)
6925 if(savedState->lastPing != savedState->lastPong)
6926 ScheduleDelayedEvent(DeferredBookMove, 10);
6928 HandleMachineMove(savedMessage, savedState);
6932 HandleMachineMove(message, cps)
6934 ChessProgramState *cps;
6936 char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
6937 char realname[MSG_SIZ];
6938 int fromX, fromY, toX, toY;
6947 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
6949 * Kludge to ignore BEL characters
6951 while (*message == '\007') message++;
6954 * [HGM] engine debug message: ignore lines starting with '#' character
6956 if(cps->debug && *message == '#') return;
6959 * Look for book output
6961 if (cps == &first && bookRequested) {
6962 if (message[0] == '\t' || message[0] == ' ') {
6963 /* Part of the book output is here; append it */
6964 strcat(bookOutput, message);
6965 strcat(bookOutput, " \n");
6967 } else if (bookOutput[0] != NULLCHAR) {
6968 /* All of book output has arrived; display it */
6969 char *p = bookOutput;
6970 while (*p != NULLCHAR) {
6971 if (*p == '\t') *p = ' ';
6974 DisplayInformation(bookOutput);
6975 bookRequested = FALSE;
6976 /* Fall through to parse the current output */
6981 * Look for machine move.
6983 if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
6984 (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
6986 /* This method is only useful on engines that support ping */
6987 if (cps->lastPing != cps->lastPong) {
6988 if (gameMode == BeginningOfGame) {
6989 /* Extra move from before last new; ignore */
6990 if (appData.debugMode) {
6991 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
6994 if (appData.debugMode) {
6995 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
6996 cps->which, gameMode);
6999 SendToProgram("undo\n", cps);
7005 case BeginningOfGame:
7006 /* Extra move from before last reset; ignore */
7007 if (appData.debugMode) {
7008 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7015 /* Extra move after we tried to stop. The mode test is
7016 not a reliable way of detecting this problem, but it's
7017 the best we can do on engines that don't support ping.
7019 if (appData.debugMode) {
7020 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7021 cps->which, gameMode);
7023 SendToProgram("undo\n", cps);
7026 case MachinePlaysWhite:
7027 case IcsPlayingWhite:
7028 machineWhite = TRUE;
7031 case MachinePlaysBlack:
7032 case IcsPlayingBlack:
7033 machineWhite = FALSE;
7036 case TwoMachinesPlay:
7037 machineWhite = (cps->twoMachinesColor[0] == 'w');
7040 if (WhiteOnMove(forwardMostMove) != machineWhite) {
7041 if (appData.debugMode) {
7043 "Ignoring move out of turn by %s, gameMode %d"
7044 ", forwardMost %d\n",
7045 cps->which, gameMode, forwardMostMove);
7050 if (appData.debugMode) { int f = forwardMostMove;
7051 fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
7052 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
7053 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
7055 if(cps->alphaRank) AlphaRank(machineMove, 4);
7056 if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
7057 &fromX, &fromY, &toX, &toY, &promoChar)) {
7058 /* Machine move could not be parsed; ignore it. */
7059 sprintf(buf1, _("Illegal move \"%s\" from %s machine"),
7060 machineMove, cps->which);
7061 DisplayError(buf1, 0);
7062 sprintf(buf1, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
7063 machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
7064 if (gameMode == TwoMachinesPlay) {
7065 GameEnds(machineWhite ? BlackWins : WhiteWins,
7071 /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
7072 /* So we have to redo legality test with true e.p. status here, */
7073 /* to make sure an illegal e.p. capture does not slip through, */
7074 /* to cause a forfeit on a justified illegal-move complaint */
7075 /* of the opponent. */
7076 if( gameMode==TwoMachinesPlay && appData.testLegality
7077 && fromY != DROP_RANK /* [HGM] temporary; should still add legality test for drops */
7080 moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
7081 fromY, fromX, toY, toX, promoChar);
7082 if (appData.debugMode) {
7084 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
7085 boards[forwardMostMove][CASTLING][i], castlingRank[i]);
7086 fprintf(debugFP, "castling rights\n");
7088 if(moveType == IllegalMove) {
7089 sprintf(buf1, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
7090 machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
7091 GameEnds(machineWhite ? BlackWins : WhiteWins,
7094 } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
7095 /* [HGM] Kludge to handle engines that send FRC-style castling
7096 when they shouldn't (like TSCP-Gothic) */
7098 case WhiteASideCastleFR:
7099 case BlackASideCastleFR:
7101 currentMoveString[2]++;
7103 case WhiteHSideCastleFR:
7104 case BlackHSideCastleFR:
7106 currentMoveString[2]--;
7108 default: ; // nothing to do, but suppresses warning of pedantic compilers
7111 hintRequested = FALSE;
7112 lastHint[0] = NULLCHAR;
7113 bookRequested = FALSE;
7114 /* Program may be pondering now */
7115 cps->maybeThinking = TRUE;
7116 if (cps->sendTime == 2) cps->sendTime = 1;
7117 if (cps->offeredDraw) cps->offeredDraw--;
7119 /* currentMoveString is set as a side-effect of ParseOneMove */
7120 strcpy(machineMove, currentMoveString);
7121 strcat(machineMove, "\n");
7122 strcpy(moveList[forwardMostMove], machineMove);
7124 MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
7126 /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
7127 if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
7130 while( count < adjudicateLossPlies ) {
7131 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
7134 score = -score; /* Flip score for winning side */
7137 if( score > adjudicateLossThreshold ) {
7144 if( count >= adjudicateLossPlies ) {
7145 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7147 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7148 "Xboard adjudication",
7155 if(Adjudicate(cps)) return; // [HGM] adjudicate: for all automatic game ends
7158 if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
7160 if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
7161 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
7163 SendMoveToICS(moveType, fromX, fromY, toX, toY);
7165 SendMoveToICS(moveType, fromX, fromY, toX, toY);
7167 if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
7168 char buf[3*MSG_SIZ];
7170 sprintf(buf, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
7171 programStats.score / 100.,
7173 programStats.time / 100.,
7174 (unsigned int)programStats.nodes,
7175 (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
7176 programStats.movelist);
7178 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
7183 /* [AS] Save move info and clear stats for next move */
7184 pvInfoList[ forwardMostMove-1 ].score = programStats.score;
7185 pvInfoList[ forwardMostMove-1 ].depth = programStats.depth;
7186 pvInfoList[ forwardMostMove-1 ].time = programStats.time; // [HGM] PGNtime: take time from engine stats
7187 ClearProgramStats();
7188 thinkOutput[0] = NULLCHAR;
7189 hiddenThinkOutputState = 0;
7192 if (gameMode == TwoMachinesPlay) {
7193 /* [HGM] relaying draw offers moved to after reception of move */
7194 /* and interpreting offer as claim if it brings draw condition */
7195 if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
7196 SendToProgram("draw\n", cps->other);
7198 if (cps->other->sendTime) {
7199 SendTimeRemaining(cps->other,
7200 cps->other->twoMachinesColor[0] == 'w');
7202 bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
7203 if (firstMove && !bookHit) {
7205 if (cps->other->useColors) {
7206 SendToProgram(cps->other->twoMachinesColor, cps->other);
7208 SendToProgram("go\n", cps->other);
7210 cps->other->maybeThinking = TRUE;
7213 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7215 if (!pausing && appData.ringBellAfterMoves) {
7220 * Reenable menu items that were disabled while
7221 * machine was thinking
7223 if (gameMode != TwoMachinesPlay)
7224 SetUserThinkingEnables();
7226 // [HGM] book: after book hit opponent has received move and is now in force mode
7227 // force the book reply into it, and then fake that it outputted this move by jumping
7228 // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
7230 static char bookMove[MSG_SIZ]; // a bit generous?
7232 strcpy(bookMove, "move ");
7233 strcat(bookMove, bookHit);
7236 programStats.nodes = programStats.depth = programStats.time =
7237 programStats.score = programStats.got_only_move = 0;
7238 sprintf(programStats.movelist, "%s (xbook)", bookHit);
7240 if(cps->lastPing != cps->lastPong) {
7241 savedMessage = message; // args for deferred call
7243 ScheduleDelayedEvent(DeferredBookMove, 10);
7252 /* Set special modes for chess engines. Later something general
7253 * could be added here; for now there is just one kludge feature,
7254 * needed because Crafty 15.10 and earlier don't ignore SIGINT
7255 * when "xboard" is given as an interactive command.
7257 if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
7258 cps->useSigint = FALSE;
7259 cps->useSigterm = FALSE;
7261 if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
7262 ParseFeatures(message+8, cps);
7263 return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
7266 /* [HGM] Allow engine to set up a position. Don't ask me why one would
7267 * want this, I was asked to put it in, and obliged.
7269 if (!strncmp(message, "setboard ", 9)) {
7270 Board initial_position;
7272 GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
7274 if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
7275 DisplayError(_("Bad FEN received from engine"), 0);
7279 CopyBoard(boards[0], initial_position);
7280 initialRulePlies = FENrulePlies;
7281 if(blackPlaysFirst) gameMode = MachinePlaysWhite;
7282 else gameMode = MachinePlaysBlack;
7283 DrawPosition(FALSE, boards[currentMove]);
7289 * Look for communication commands
7291 if (!strncmp(message, "telluser ", 9)) {
7292 DisplayNote(message + 9);
7295 if (!strncmp(message, "tellusererror ", 14)) {
7297 DisplayError(message + 14, 0);
7300 if (!strncmp(message, "tellopponent ", 13)) {
7301 if (appData.icsActive) {
7303 snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
7307 DisplayNote(message + 13);
7311 if (!strncmp(message, "tellothers ", 11)) {
7312 if (appData.icsActive) {
7314 snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
7320 if (!strncmp(message, "tellall ", 8)) {
7321 if (appData.icsActive) {
7323 snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
7327 DisplayNote(message + 8);
7331 if (strncmp(message, "warning", 7) == 0) {
7332 /* Undocumented feature, use tellusererror in new code */
7333 DisplayError(message, 0);
7336 if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
7337 strcpy(realname, cps->tidy);
7338 strcat(realname, " query");
7339 AskQuestion(realname, buf2, buf1, cps->pr);
7342 /* Commands from the engine directly to ICS. We don't allow these to be
7343 * sent until we are logged on. Crafty kibitzes have been known to
7344 * interfere with the login process.
7347 if (!strncmp(message, "tellics ", 8)) {
7348 SendToICS(message + 8);
7352 if (!strncmp(message, "tellicsnoalias ", 15)) {
7353 SendToICS(ics_prefix);
7354 SendToICS(message + 15);
7358 /* The following are for backward compatibility only */
7359 if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
7360 !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
7361 SendToICS(ics_prefix);
7367 if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
7371 * If the move is illegal, cancel it and redraw the board.
7372 * Also deal with other error cases. Matching is rather loose
7373 * here to accommodate engines written before the spec.
7375 if (strncmp(message + 1, "llegal move", 11) == 0 ||
7376 strncmp(message, "Error", 5) == 0) {
7377 if (StrStr(message, "name") ||
7378 StrStr(message, "rating") || StrStr(message, "?") ||
7379 StrStr(message, "result") || StrStr(message, "board") ||
7380 StrStr(message, "bk") || StrStr(message, "computer") ||
7381 StrStr(message, "variant") || StrStr(message, "hint") ||
7382 StrStr(message, "random") || StrStr(message, "depth") ||
7383 StrStr(message, "accepted")) {
7386 if (StrStr(message, "protover")) {
7387 /* Program is responding to input, so it's apparently done
7388 initializing, and this error message indicates it is
7389 protocol version 1. So we don't need to wait any longer
7390 for it to initialize and send feature commands. */
7391 FeatureDone(cps, 1);
7392 cps->protocolVersion = 1;
7395 cps->maybeThinking = FALSE;
7397 if (StrStr(message, "draw")) {
7398 /* Program doesn't have "draw" command */
7399 cps->sendDrawOffers = 0;
7402 if (cps->sendTime != 1 &&
7403 (StrStr(message, "time") || StrStr(message, "otim"))) {
7404 /* Program apparently doesn't have "time" or "otim" command */
7408 if (StrStr(message, "analyze")) {
7409 cps->analysisSupport = FALSE;
7410 cps->analyzing = FALSE;
7412 sprintf(buf2, _("%s does not support analysis"), cps->tidy);
7413 DisplayError(buf2, 0);
7416 if (StrStr(message, "(no matching move)st")) {
7417 /* Special kludge for GNU Chess 4 only */
7418 cps->stKludge = TRUE;
7419 SendTimeControl(cps, movesPerSession, timeControl,
7420 timeIncrement, appData.searchDepth,
7424 if (StrStr(message, "(no matching move)sd")) {
7425 /* Special kludge for GNU Chess 4 only */
7426 cps->sdKludge = TRUE;
7427 SendTimeControl(cps, movesPerSession, timeControl,
7428 timeIncrement, appData.searchDepth,
7432 if (!StrStr(message, "llegal")) {
7435 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
7436 gameMode == IcsIdle) return;
7437 if (forwardMostMove <= backwardMostMove) return;
7438 if (pausing) PauseEvent();
7439 if(appData.forceIllegal) {
7440 // [HGM] illegal: machine refused move; force position after move into it
7441 SendToProgram("force\n", cps);
7442 if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
7443 // we have a real problem now, as SendBoard will use the a2a3 kludge
7444 // when black is to move, while there might be nothing on a2 or black
7445 // might already have the move. So send the board as if white has the move.
7446 // But first we must change the stm of the engine, as it refused the last move
7447 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
7448 if(WhiteOnMove(forwardMostMove)) {
7449 SendToProgram("a7a6\n", cps); // for the engine black still had the move
7450 SendBoard(cps, forwardMostMove); // kludgeless board
7452 SendToProgram("a2a3\n", cps); // for the engine white still had the move
7453 CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
7454 SendBoard(cps, forwardMostMove+1); // kludgeless board
7456 } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
7457 if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
7458 gameMode == TwoMachinesPlay)
7459 SendToProgram("go\n", cps);
7462 if (gameMode == PlayFromGameFile) {
7463 /* Stop reading this game file */
7464 gameMode = EditGame;
7467 currentMove = --forwardMostMove;
7468 DisplayMove(currentMove-1); /* before DisplayMoveError */
7470 DisplayBothClocks();
7471 sprintf(buf1, _("Illegal move \"%s\" (rejected by %s chess program)"),
7472 parseList[currentMove], cps->which);
7473 DisplayMoveError(buf1);
7474 DrawPosition(FALSE, boards[currentMove]);
7476 /* [HGM] illegal-move claim should forfeit game when Xboard */
7477 /* only passes fully legal moves */
7478 if( appData.testLegality && gameMode == TwoMachinesPlay ) {
7479 GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
7480 "False illegal-move claim", GE_XBOARD );
7484 if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
7485 /* Program has a broken "time" command that
7486 outputs a string not ending in newline.
7492 * If chess program startup fails, exit with an error message.
7493 * Attempts to recover here are futile.
7495 if ((StrStr(message, "unknown host") != NULL)
7496 || (StrStr(message, "No remote directory") != NULL)
7497 || (StrStr(message, "not found") != NULL)
7498 || (StrStr(message, "No such file") != NULL)
7499 || (StrStr(message, "can't alloc") != NULL)
7500 || (StrStr(message, "Permission denied") != NULL)) {
7502 cps->maybeThinking = FALSE;
7503 snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
7504 cps->which, cps->program, cps->host, message);
7505 RemoveInputSource(cps->isr);
7506 DisplayFatalError(buf1, 0, 1);
7511 * Look for hint output
7513 if (sscanf(message, "Hint: %s", buf1) == 1) {
7514 if (cps == &first && hintRequested) {
7515 hintRequested = FALSE;
7516 if (ParseOneMove(buf1, forwardMostMove, &moveType,
7517 &fromX, &fromY, &toX, &toY, &promoChar)) {
7518 (void) CoordsToAlgebraic(boards[forwardMostMove],
7519 PosFlags(forwardMostMove),
7520 fromY, fromX, toY, toX, promoChar, buf1);
7521 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
7522 DisplayInformation(buf2);
7524 /* Hint move could not be parsed!? */
7525 snprintf(buf2, sizeof(buf2),
7526 _("Illegal hint move \"%s\"\nfrom %s chess program"),
7528 DisplayError(buf2, 0);
7531 strcpy(lastHint, buf1);
7537 * Ignore other messages if game is not in progress
7539 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
7540 gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
7543 * look for win, lose, draw, or draw offer
7545 if (strncmp(message, "1-0", 3) == 0) {
7546 char *p, *q, *r = "";
7547 p = strchr(message, '{');
7555 GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
7557 } else if (strncmp(message, "0-1", 3) == 0) {
7558 char *p, *q, *r = "";
7559 p = strchr(message, '{');
7567 /* Kludge for Arasan 4.1 bug */
7568 if (strcmp(r, "Black resigns") == 0) {
7569 GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
7572 GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
7574 } else if (strncmp(message, "1/2", 3) == 0) {
7575 char *p, *q, *r = "";
7576 p = strchr(message, '{');
7585 GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
7588 } else if (strncmp(message, "White resign", 12) == 0) {
7589 GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
7591 } else if (strncmp(message, "Black resign", 12) == 0) {
7592 GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
7594 } else if (strncmp(message, "White matches", 13) == 0 ||
7595 strncmp(message, "Black matches", 13) == 0 ) {
7596 /* [HGM] ignore GNUShogi noises */
7598 } else if (strncmp(message, "White", 5) == 0 &&
7599 message[5] != '(' &&
7600 StrStr(message, "Black") == NULL) {
7601 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7603 } else if (strncmp(message, "Black", 5) == 0 &&
7604 message[5] != '(') {
7605 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7607 } else if (strcmp(message, "resign") == 0 ||
7608 strcmp(message, "computer resigns") == 0) {
7610 case MachinePlaysBlack:
7611 case IcsPlayingBlack:
7612 GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
7614 case MachinePlaysWhite:
7615 case IcsPlayingWhite:
7616 GameEnds(BlackWins, "White resigns", GE_ENGINE);
7618 case TwoMachinesPlay:
7619 if (cps->twoMachinesColor[0] == 'w')
7620 GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
7622 GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
7629 } else if (strncmp(message, "opponent mates", 14) == 0) {
7631 case MachinePlaysBlack:
7632 case IcsPlayingBlack:
7633 GameEnds(WhiteWins, "White mates", GE_ENGINE);
7635 case MachinePlaysWhite:
7636 case IcsPlayingWhite:
7637 GameEnds(BlackWins, "Black mates", GE_ENGINE);
7639 case TwoMachinesPlay:
7640 if (cps->twoMachinesColor[0] == 'w')
7641 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7643 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7650 } else if (strncmp(message, "computer mates", 14) == 0) {
7652 case MachinePlaysBlack:
7653 case IcsPlayingBlack:
7654 GameEnds(BlackWins, "Black mates", GE_ENGINE1);
7656 case MachinePlaysWhite:
7657 case IcsPlayingWhite:
7658 GameEnds(WhiteWins, "White mates", GE_ENGINE);
7660 case TwoMachinesPlay:
7661 if (cps->twoMachinesColor[0] == 'w')
7662 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7664 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7671 } else if (strncmp(message, "checkmate", 9) == 0) {
7672 if (WhiteOnMove(forwardMostMove)) {
7673 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7675 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7678 } else if (strstr(message, "Draw") != NULL ||
7679 strstr(message, "game is a draw") != NULL) {
7680 GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
7682 } else if (strstr(message, "offer") != NULL &&
7683 strstr(message, "draw") != NULL) {
7685 if (appData.zippyPlay && first.initDone) {
7686 /* Relay offer to ICS */
7687 SendToICS(ics_prefix);
7688 SendToICS("draw\n");
7691 cps->offeredDraw = 2; /* valid until this engine moves twice */
7692 if (gameMode == TwoMachinesPlay) {
7693 if (cps->other->offeredDraw) {
7694 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
7695 /* [HGM] in two-machine mode we delay relaying draw offer */
7696 /* until after we also have move, to see if it is really claim */
7698 } else if (gameMode == MachinePlaysWhite ||
7699 gameMode == MachinePlaysBlack) {
7700 if (userOfferedDraw) {
7701 DisplayInformation(_("Machine accepts your draw offer"));
7702 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
7704 DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
7711 * Look for thinking output
7713 if ( appData.showThinking // [HGM] thinking: test all options that cause this output
7714 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
7716 int plylev, mvleft, mvtot, curscore, time;
7717 char mvname[MOVE_LEN];
7721 int prefixHint = FALSE;
7722 mvname[0] = NULLCHAR;
7725 case MachinePlaysBlack:
7726 case IcsPlayingBlack:
7727 if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
7729 case MachinePlaysWhite:
7730 case IcsPlayingWhite:
7731 if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
7736 case IcsObserving: /* [DM] icsEngineAnalyze */
7737 if (!appData.icsEngineAnalyze) ignore = TRUE;
7739 case TwoMachinesPlay:
7740 if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
7751 if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
7752 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
7754 if (plyext != ' ' && plyext != '\t') {
7758 /* [AS] Negate score if machine is playing black and reporting absolute scores */
7759 if( cps->scoreIsAbsolute &&
7760 ( gameMode == MachinePlaysBlack ||
7761 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
7762 gameMode == IcsPlayingBlack || // [HGM] also add other situations where engine should report black POV
7763 (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
7764 !WhiteOnMove(currentMove)
7767 curscore = -curscore;
7771 programStats.depth = plylev;
7772 programStats.nodes = nodes;
7773 programStats.time = time;
7774 programStats.score = curscore;
7775 programStats.got_only_move = 0;
7777 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
7780 if(cps->nps == 0) ticklen = 10*time; // use engine reported time
7781 else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
7782 if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
7783 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
7784 whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
7785 if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
7786 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
7787 blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
7790 /* Buffer overflow protection */
7791 if (buf1[0] != NULLCHAR) {
7792 if (strlen(buf1) >= sizeof(programStats.movelist)
7793 && appData.debugMode) {
7795 "PV is too long; using the first %u bytes.\n",
7796 (unsigned) sizeof(programStats.movelist) - 1);
7799 safeStrCpy( programStats.movelist, buf1, sizeof(programStats.movelist) );
7801 sprintf(programStats.movelist, " no PV\n");
7804 if (programStats.seen_stat) {
7805 programStats.ok_to_send = 1;
7808 if (strchr(programStats.movelist, '(') != NULL) {
7809 programStats.line_is_book = 1;
7810 programStats.nr_moves = 0;
7811 programStats.moves_left = 0;
7813 programStats.line_is_book = 0;
7816 SendProgramStatsToFrontend( cps, &programStats );
7819 [AS] Protect the thinkOutput buffer from overflow... this
7820 is only useful if buf1 hasn't overflowed first!
7822 sprintf(thinkOutput, "[%d]%c%+.2f %s%s",
7824 (gameMode == TwoMachinesPlay ?
7825 ToUpper(cps->twoMachinesColor[0]) : ' '),
7826 ((double) curscore) / 100.0,
7827 prefixHint ? lastHint : "",
7828 prefixHint ? " " : "" );
7830 if( buf1[0] != NULLCHAR ) {
7831 unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
7833 if( strlen(buf1) > max_len ) {
7834 if( appData.debugMode) {
7835 fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
7837 buf1[max_len+1] = '\0';
7840 strcat( thinkOutput, buf1 );
7843 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
7844 || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7845 DisplayMove(currentMove - 1);
7849 } else if ((p=StrStr(message, "(only move)")) != NULL) {
7850 /* crafty (9.25+) says "(only move) <move>"
7851 * if there is only 1 legal move
7853 sscanf(p, "(only move) %s", buf1);
7854 sprintf(thinkOutput, "%s (only move)", buf1);
7855 sprintf(programStats.movelist, "%s (only move)", buf1);
7856 programStats.depth = 1;
7857 programStats.nr_moves = 1;
7858 programStats.moves_left = 1;
7859 programStats.nodes = 1;
7860 programStats.time = 1;
7861 programStats.got_only_move = 1;
7863 /* Not really, but we also use this member to
7864 mean "line isn't going to change" (Crafty
7865 isn't searching, so stats won't change) */
7866 programStats.line_is_book = 1;
7868 SendProgramStatsToFrontend( cps, &programStats );
7870 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
7871 gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7872 DisplayMove(currentMove - 1);
7875 } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
7876 &time, &nodes, &plylev, &mvleft,
7877 &mvtot, mvname) >= 5) {
7878 /* The stat01: line is from Crafty (9.29+) in response
7879 to the "." command */
7880 programStats.seen_stat = 1;
7881 cps->maybeThinking = TRUE;
7883 if (programStats.got_only_move || !appData.periodicUpdates)
7886 programStats.depth = plylev;
7887 programStats.time = time;
7888 programStats.nodes = nodes;
7889 programStats.moves_left = mvleft;
7890 programStats.nr_moves = mvtot;
7891 strcpy(programStats.move_name, mvname);
7892 programStats.ok_to_send = 1;
7893 programStats.movelist[0] = '\0';
7895 SendProgramStatsToFrontend( cps, &programStats );
7899 } else if (strncmp(message,"++",2) == 0) {
7900 /* Crafty 9.29+ outputs this */
7901 programStats.got_fail = 2;
7904 } else if (strncmp(message,"--",2) == 0) {
7905 /* Crafty 9.29+ outputs this */
7906 programStats.got_fail = 1;
7909 } else if (thinkOutput[0] != NULLCHAR &&
7910 strncmp(message, " ", 4) == 0) {
7911 unsigned message_len;
7914 while (*p && *p == ' ') p++;
7916 message_len = strlen( p );
7918 /* [AS] Avoid buffer overflow */
7919 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
7920 strcat(thinkOutput, " ");
7921 strcat(thinkOutput, p);
7924 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
7925 strcat(programStats.movelist, " ");
7926 strcat(programStats.movelist, p);
7929 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
7930 gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7931 DisplayMove(currentMove - 1);
7939 if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
7940 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
7942 ChessProgramStats cpstats;
7944 if (plyext != ' ' && plyext != '\t') {
7948 /* [AS] Negate score if machine is playing black and reporting absolute scores */
7949 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
7950 curscore = -curscore;
7953 cpstats.depth = plylev;
7954 cpstats.nodes = nodes;
7955 cpstats.time = time;
7956 cpstats.score = curscore;
7957 cpstats.got_only_move = 0;
7958 cpstats.movelist[0] = '\0';
7960 if (buf1[0] != NULLCHAR) {
7961 safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist) );
7964 cpstats.ok_to_send = 0;
7965 cpstats.line_is_book = 0;
7966 cpstats.nr_moves = 0;
7967 cpstats.moves_left = 0;
7969 SendProgramStatsToFrontend( cps, &cpstats );
7976 /* Parse a game score from the character string "game", and
7977 record it as the history of the current game. The game
7978 score is NOT assumed to start from the standard position.
7979 The display is not updated in any way.
7982 ParseGameHistory(game)
7986 int fromX, fromY, toX, toY, boardIndex;
7991 if (appData.debugMode)
7992 fprintf(debugFP, "Parsing game history: %s\n", game);
7994 if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
7995 gameInfo.site = StrSave(appData.icsHost);
7996 gameInfo.date = PGNDate();
7997 gameInfo.round = StrSave("-");
7999 /* Parse out names of players */
8000 while (*game == ' ') game++;
8002 while (*game != ' ') *p++ = *game++;
8004 gameInfo.white = StrSave(buf);
8005 while (*game == ' ') game++;
8007 while (*game != ' ' && *game != '\n') *p++ = *game++;
8009 gameInfo.black = StrSave(buf);
8012 boardIndex = blackPlaysFirst ? 1 : 0;
8015 yyboardindex = boardIndex;
8016 moveType = (ChessMove) yylex();
8018 case IllegalMove: /* maybe suicide chess, etc. */
8019 if (appData.debugMode) {
8020 fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
8021 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8022 setbuf(debugFP, NULL);
8024 case WhitePromotionChancellor:
8025 case BlackPromotionChancellor:
8026 case WhitePromotionArchbishop:
8027 case BlackPromotionArchbishop:
8028 case WhitePromotionQueen:
8029 case BlackPromotionQueen:
8030 case WhitePromotionRook:
8031 case BlackPromotionRook:
8032 case WhitePromotionBishop:
8033 case BlackPromotionBishop:
8034 case WhitePromotionKnight:
8035 case BlackPromotionKnight:
8036 case WhitePromotionKing:
8037 case BlackPromotionKing:
8039 case WhiteCapturesEnPassant:
8040 case BlackCapturesEnPassant:
8041 case WhiteKingSideCastle:
8042 case WhiteQueenSideCastle:
8043 case BlackKingSideCastle:
8044 case BlackQueenSideCastle:
8045 case WhiteKingSideCastleWild:
8046 case WhiteQueenSideCastleWild:
8047 case BlackKingSideCastleWild:
8048 case BlackQueenSideCastleWild:
8050 case WhiteHSideCastleFR:
8051 case WhiteASideCastleFR:
8052 case BlackHSideCastleFR:
8053 case BlackASideCastleFR:
8055 fromX = currentMoveString[0] - AAA;
8056 fromY = currentMoveString[1] - ONE;
8057 toX = currentMoveString[2] - AAA;
8058 toY = currentMoveString[3] - ONE;
8059 promoChar = currentMoveString[4];
8063 fromX = moveType == WhiteDrop ?
8064 (int) CharToPiece(ToUpper(currentMoveString[0])) :
8065 (int) CharToPiece(ToLower(currentMoveString[0]));
8067 toX = currentMoveString[2] - AAA;
8068 toY = currentMoveString[3] - ONE;
8069 promoChar = NULLCHAR;
8073 sprintf(buf, _("Ambiguous move in ICS output: \"%s\""), yy_text);
8074 if (appData.debugMode) {
8075 fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
8076 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8077 setbuf(debugFP, NULL);
8079 DisplayError(buf, 0);
8081 case ImpossibleMove:
8083 sprintf(buf, _("Illegal move in ICS output: \"%s\""), yy_text);
8084 if (appData.debugMode) {
8085 fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
8086 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8087 setbuf(debugFP, NULL);
8089 DisplayError(buf, 0);
8091 case (ChessMove) 0: /* end of file */
8092 if (boardIndex < backwardMostMove) {
8093 /* Oops, gap. How did that happen? */
8094 DisplayError(_("Gap in move list"), 0);
8097 backwardMostMove = blackPlaysFirst ? 1 : 0;
8098 if (boardIndex > forwardMostMove) {
8099 forwardMostMove = boardIndex;
8103 if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
8104 strcat(parseList[boardIndex-1], " ");
8105 strcat(parseList[boardIndex-1], yy_text);
8117 case GameUnfinished:
8118 if (gameMode == IcsExamining) {
8119 if (boardIndex < backwardMostMove) {
8120 /* Oops, gap. How did that happen? */
8123 backwardMostMove = blackPlaysFirst ? 1 : 0;
8126 gameInfo.result = moveType;
8127 p = strchr(yy_text, '{');
8128 if (p == NULL) p = strchr(yy_text, '(');
8131 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
8133 q = strchr(p, *p == '{' ? '}' : ')');
8134 if (q != NULL) *q = NULLCHAR;
8137 gameInfo.resultDetails = StrSave(p);
8140 if (boardIndex >= forwardMostMove &&
8141 !(gameMode == IcsObserving && ics_gamenum == -1)) {
8142 backwardMostMove = blackPlaysFirst ? 1 : 0;
8145 (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
8146 fromY, fromX, toY, toX, promoChar,
8147 parseList[boardIndex]);
8148 CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
8149 /* currentMoveString is set as a side-effect of yylex */
8150 strcpy(moveList[boardIndex], currentMoveString);
8151 strcat(moveList[boardIndex], "\n");
8153 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
8154 switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
8160 if(gameInfo.variant != VariantShogi)
8161 strcat(parseList[boardIndex - 1], "+");
8165 strcat(parseList[boardIndex - 1], "#");
8172 /* Apply a move to the given board */
8174 ApplyMove(fromX, fromY, toX, toY, promoChar, board)
8175 int fromX, fromY, toX, toY;
8179 ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
8180 int promoRank = gameInfo.variant == VariantMakruk ? 3 : 1;
8182 /* [HGM] compute & store e.p. status and castling rights for new position */
8183 /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
8186 if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
8187 oldEP = (signed char)board[EP_STATUS];
8188 board[EP_STATUS] = EP_NONE;
8190 if( board[toY][toX] != EmptySquare )
8191 board[EP_STATUS] = EP_CAPTURE;
8193 if( board[fromY][fromX] == WhitePawn ) {
8194 if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
8195 board[EP_STATUS] = EP_PAWN_MOVE;
8197 if(toX>BOARD_LEFT && board[toY][toX-1] == BlackPawn &&
8198 gameInfo.variant != VariantBerolina || toX < fromX)
8199 board[EP_STATUS] = toX | berolina;
8200 if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
8201 gameInfo.variant != VariantBerolina || toX > fromX)
8202 board[EP_STATUS] = toX;
8205 if( board[fromY][fromX] == BlackPawn ) {
8206 if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
8207 board[EP_STATUS] = EP_PAWN_MOVE;
8208 if( toY-fromY== -2) {
8209 if(toX>BOARD_LEFT && board[toY][toX-1] == WhitePawn &&
8210 gameInfo.variant != VariantBerolina || toX < fromX)
8211 board[EP_STATUS] = toX | berolina;
8212 if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
8213 gameInfo.variant != VariantBerolina || toX > fromX)
8214 board[EP_STATUS] = toX;
8218 for(i=0; i<nrCastlingRights; i++) {
8219 if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
8220 board[CASTLING][i] == toX && castlingRank[i] == toY
8221 ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
8226 /* [HGM] In Shatranj and Courier all promotions are to Ferz */
8227 if((gameInfo.variant==VariantShatranj || gameInfo.variant==VariantCourier || gameInfo.variant == VariantMakruk)
8228 && promoChar != 0) promoChar = PieceToChar(WhiteFerz);
8230 if (fromX == toX && fromY == toY) return;
8232 if (fromY == DROP_RANK) {
8234 piece = board[toY][toX] = (ChessSquare) fromX;
8236 piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
8237 king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
8238 if(gameInfo.variant == VariantKnightmate)
8239 king += (int) WhiteUnicorn - (int) WhiteKing;
8241 /* Code added by Tord: */
8242 /* FRC castling assumed when king captures friendly rook. */
8243 if (board[fromY][fromX] == WhiteKing &&
8244 board[toY][toX] == WhiteRook) {
8245 board[fromY][fromX] = EmptySquare;
8246 board[toY][toX] = EmptySquare;
8248 board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
8250 board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
8252 } else if (board[fromY][fromX] == BlackKing &&
8253 board[toY][toX] == BlackRook) {
8254 board[fromY][fromX] = EmptySquare;
8255 board[toY][toX] = EmptySquare;
8257 board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
8259 board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
8261 /* End of code added by Tord */
8263 } else if (board[fromY][fromX] == king
8264 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8265 && toY == fromY && toX > fromX+1) {
8266 board[fromY][fromX] = EmptySquare;
8267 board[toY][toX] = king;
8268 board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
8269 board[fromY][BOARD_RGHT-1] = EmptySquare;
8270 } else if (board[fromY][fromX] == king
8271 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8272 && toY == fromY && toX < fromX-1) {
8273 board[fromY][fromX] = EmptySquare;
8274 board[toY][toX] = king;
8275 board[toY][toX+1] = board[fromY][BOARD_LEFT];
8276 board[fromY][BOARD_LEFT] = EmptySquare;
8277 } else if (board[fromY][fromX] == WhitePawn
8278 && toY >= BOARD_HEIGHT-promoRank
8279 && gameInfo.variant != VariantXiangqi
8281 /* white pawn promotion */
8282 board[toY][toX] = CharToPiece(ToUpper(promoChar));
8283 if (board[toY][toX] == EmptySquare) {
8284 board[toY][toX] = WhiteQueen;
8286 if(gameInfo.variant==VariantBughouse ||
8287 gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
8288 board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
8289 board[fromY][fromX] = EmptySquare;
8290 } else if ((fromY == BOARD_HEIGHT-4)
8292 && gameInfo.variant != VariantXiangqi
8293 && gameInfo.variant != VariantBerolina
8294 && (board[fromY][fromX] == WhitePawn)
8295 && (board[toY][toX] == EmptySquare)) {
8296 board[fromY][fromX] = EmptySquare;
8297 board[toY][toX] = WhitePawn;
8298 captured = board[toY - 1][toX];
8299 board[toY - 1][toX] = EmptySquare;
8300 } else if ((fromY == BOARD_HEIGHT-4)
8302 && gameInfo.variant == VariantBerolina
8303 && (board[fromY][fromX] == WhitePawn)
8304 && (board[toY][toX] == EmptySquare)) {
8305 board[fromY][fromX] = EmptySquare;
8306 board[toY][toX] = WhitePawn;
8307 if(oldEP & EP_BEROLIN_A) {
8308 captured = board[fromY][fromX-1];
8309 board[fromY][fromX-1] = EmptySquare;
8310 }else{ captured = board[fromY][fromX+1];
8311 board[fromY][fromX+1] = EmptySquare;
8313 } else if (board[fromY][fromX] == king
8314 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8315 && toY == fromY && toX > fromX+1) {
8316 board[fromY][fromX] = EmptySquare;
8317 board[toY][toX] = king;
8318 board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
8319 board[fromY][BOARD_RGHT-1] = EmptySquare;
8320 } else if (board[fromY][fromX] == king
8321 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8322 && toY == fromY && toX < fromX-1) {
8323 board[fromY][fromX] = EmptySquare;
8324 board[toY][toX] = king;
8325 board[toY][toX+1] = board[fromY][BOARD_LEFT];
8326 board[fromY][BOARD_LEFT] = EmptySquare;
8327 } else if (fromY == 7 && fromX == 3
8328 && board[fromY][fromX] == BlackKing
8329 && toY == 7 && toX == 5) {
8330 board[fromY][fromX] = EmptySquare;
8331 board[toY][toX] = BlackKing;
8332 board[fromY][7] = EmptySquare;
8333 board[toY][4] = BlackRook;
8334 } else if (fromY == 7 && fromX == 3
8335 && board[fromY][fromX] == BlackKing
8336 && toY == 7 && toX == 1) {
8337 board[fromY][fromX] = EmptySquare;
8338 board[toY][toX] = BlackKing;
8339 board[fromY][0] = EmptySquare;
8340 board[toY][2] = BlackRook;
8341 } else if (board[fromY][fromX] == BlackPawn
8343 && gameInfo.variant != VariantXiangqi
8345 /* black pawn promotion */
8346 board[toY][toX] = CharToPiece(ToLower(promoChar));
8347 if (board[toY][toX] == EmptySquare) {
8348 board[toY][toX] = BlackQueen;
8350 if(gameInfo.variant==VariantBughouse ||
8351 gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
8352 board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
8353 board[fromY][fromX] = EmptySquare;
8354 } else if ((fromY == 3)
8356 && gameInfo.variant != VariantXiangqi
8357 && gameInfo.variant != VariantBerolina
8358 && (board[fromY][fromX] == BlackPawn)
8359 && (board[toY][toX] == EmptySquare)) {
8360 board[fromY][fromX] = EmptySquare;
8361 board[toY][toX] = BlackPawn;
8362 captured = board[toY + 1][toX];
8363 board[toY + 1][toX] = EmptySquare;
8364 } else if ((fromY == 3)
8366 && gameInfo.variant == VariantBerolina
8367 && (board[fromY][fromX] == BlackPawn)
8368 && (board[toY][toX] == EmptySquare)) {
8369 board[fromY][fromX] = EmptySquare;
8370 board[toY][toX] = BlackPawn;
8371 if(oldEP & EP_BEROLIN_A) {
8372 captured = board[fromY][fromX-1];
8373 board[fromY][fromX-1] = EmptySquare;
8374 }else{ captured = board[fromY][fromX+1];
8375 board[fromY][fromX+1] = EmptySquare;
8378 board[toY][toX] = board[fromY][fromX];
8379 board[fromY][fromX] = EmptySquare;
8382 /* [HGM] now we promote for Shogi, if needed */
8383 if(gameInfo.variant == VariantShogi && promoChar == 'q')
8384 board[toY][toX] = (ChessSquare) (PROMOTED piece);
8387 if (gameInfo.holdingsWidth != 0) {
8389 /* !!A lot more code needs to be written to support holdings */
8390 /* [HGM] OK, so I have written it. Holdings are stored in the */
8391 /* penultimate board files, so they are automaticlly stored */
8392 /* in the game history. */
8393 if (fromY == DROP_RANK) {
8394 /* Delete from holdings, by decreasing count */
8395 /* and erasing image if necessary */
8397 if(p < (int) BlackPawn) { /* white drop */
8398 p -= (int)WhitePawn;
8399 p = PieceToNumber((ChessSquare)p);
8400 if(p >= gameInfo.holdingsSize) p = 0;
8401 if(--board[p][BOARD_WIDTH-2] <= 0)
8402 board[p][BOARD_WIDTH-1] = EmptySquare;
8403 if((int)board[p][BOARD_WIDTH-2] < 0)
8404 board[p][BOARD_WIDTH-2] = 0;
8405 } else { /* black drop */
8406 p -= (int)BlackPawn;
8407 p = PieceToNumber((ChessSquare)p);
8408 if(p >= gameInfo.holdingsSize) p = 0;
8409 if(--board[BOARD_HEIGHT-1-p][1] <= 0)
8410 board[BOARD_HEIGHT-1-p][0] = EmptySquare;
8411 if((int)board[BOARD_HEIGHT-1-p][1] < 0)
8412 board[BOARD_HEIGHT-1-p][1] = 0;
8415 if (captured != EmptySquare && gameInfo.holdingsSize > 0
8416 && gameInfo.variant != VariantBughouse ) {
8417 /* [HGM] holdings: Add to holdings, if holdings exist */
8418 if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
8419 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
8420 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
8423 if (p >= (int) BlackPawn) {
8424 p -= (int)BlackPawn;
8425 if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
8426 /* in Shogi restore piece to its original first */
8427 captured = (ChessSquare) (DEMOTED captured);
8430 p = PieceToNumber((ChessSquare)p);
8431 if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
8432 board[p][BOARD_WIDTH-2]++;
8433 board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
8435 p -= (int)WhitePawn;
8436 if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
8437 captured = (ChessSquare) (DEMOTED captured);
8440 p = PieceToNumber((ChessSquare)p);
8441 if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
8442 board[BOARD_HEIGHT-1-p][1]++;
8443 board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
8446 } else if (gameInfo.variant == VariantAtomic) {
8447 if (captured != EmptySquare) {
8449 for (y = toY-1; y <= toY+1; y++) {
8450 for (x = toX-1; x <= toX+1; x++) {
8451 if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
8452 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
8453 board[y][x] = EmptySquare;
8457 board[toY][toX] = EmptySquare;
8460 if(gameInfo.variant == VariantShogi && promoChar != NULLCHAR && promoChar != '=') {
8461 /* [HGM] Shogi promotions */
8462 board[toY][toX] = (ChessSquare) (PROMOTED piece);
8465 if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
8466 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
8467 // [HGM] superchess: take promotion piece out of holdings
8468 int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
8469 if((int)piece < (int)BlackPawn) { // determine stm from piece color
8470 if(!--board[k][BOARD_WIDTH-2])
8471 board[k][BOARD_WIDTH-1] = EmptySquare;
8473 if(!--board[BOARD_HEIGHT-1-k][1])
8474 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
8480 /* Updates forwardMostMove */
8482 MakeMove(fromX, fromY, toX, toY, promoChar)
8483 int fromX, fromY, toX, toY;
8486 // forwardMostMove++; // [HGM] bare: moved downstream
8488 if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
8489 int timeLeft; static int lastLoadFlag=0; int king, piece;
8490 piece = boards[forwardMostMove][fromY][fromX];
8491 king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
8492 if(gameInfo.variant == VariantKnightmate)
8493 king += (int) WhiteUnicorn - (int) WhiteKing;
8494 if(forwardMostMove == 0) {
8496 fprintf(serverMoves, "%s;", second.tidy);
8497 fprintf(serverMoves, "%s;", first.tidy);
8498 if(!blackPlaysFirst)
8499 fprintf(serverMoves, "%s;", second.tidy);
8500 } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
8501 lastLoadFlag = loadFlag;
8503 fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
8504 // print castling suffix
8505 if( toY == fromY && piece == king ) {
8507 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
8509 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
8512 if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
8513 boards[forwardMostMove][fromY][fromX] == BlackPawn ) &&
8514 boards[forwardMostMove][toY][toX] == EmptySquare
8516 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
8518 if(promoChar != NULLCHAR)
8519 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);
8521 fprintf(serverMoves, "/%d/%d",
8522 pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
8523 if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
8524 else timeLeft = blackTimeRemaining/1000;
8525 fprintf(serverMoves, "/%d", timeLeft);
8527 fflush(serverMoves);
8530 if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations
8531 DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
8535 UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
8536 if (commentList[forwardMostMove+1] != NULL) {
8537 free(commentList[forwardMostMove+1]);
8538 commentList[forwardMostMove+1] = NULL;
8540 CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
8541 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
8542 forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
8543 SwitchClocks(); // uses forwardMostMove, so must be done after incrementing it !
8544 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
8545 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
8546 gameInfo.result = GameUnfinished;
8547 if (gameInfo.resultDetails != NULL) {
8548 free(gameInfo.resultDetails);
8549 gameInfo.resultDetails = NULL;
8551 CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
8552 moveList[forwardMostMove - 1]);
8553 (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
8554 PosFlags(forwardMostMove - 1),
8555 fromY, fromX, toY, toX, promoChar,
8556 parseList[forwardMostMove - 1]);
8557 switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
8563 if(gameInfo.variant != VariantShogi)
8564 strcat(parseList[forwardMostMove - 1], "+");
8568 strcat(parseList[forwardMostMove - 1], "#");
8571 if (appData.debugMode) {
8572 fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
8577 /* Updates currentMove if not pausing */
8579 ShowMove(fromX, fromY, toX, toY)
8581 int instant = (gameMode == PlayFromGameFile) ?
8582 (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
8584 if(appData.noGUI) return;
8586 if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile)
8590 if (forwardMostMove == currentMove + 1)
8593 // AnimateMove(boards[forwardMostMove - 1],
8594 // fromX, fromY, toX, toY);
8596 if (appData.highlightLastMove)
8598 SetHighlights(fromX, fromY, toX, toY);
8601 currentMove = forwardMostMove;
8604 if (instant) return;
8606 DisplayMove(currentMove - 1);
8607 DrawPosition(FALSE, boards[currentMove]);
8608 DisplayBothClocks();
8609 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
8614 void SendEgtPath(ChessProgramState *cps)
8615 { /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
8616 char buf[MSG_SIZ], name[MSG_SIZ], *p;
8618 if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
8621 char c, *q = name+1, *r, *s;
8623 name[0] = ','; // extract next format name from feature and copy with prefixed ','
8624 while(*p && *p != ',') *q++ = *p++;
8626 if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
8627 strcmp(name, ",nalimov:") == 0 ) {
8628 // take nalimov path from the menu-changeable option first, if it is defined
8629 sprintf(buf, "egtpath nalimov %s\n", appData.defaultPathEGTB);
8630 SendToProgram(buf,cps); // send egtbpath command for nalimov
8632 if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
8633 (s = StrStr(appData.egtFormats, name)) != NULL) {
8634 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
8635 s = r = StrStr(s, ":") + 1; // beginning of path info
8636 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
8637 c = *r; *r = 0; // temporarily null-terminate path info
8638 *--q = 0; // strip of trailig ':' from name
8639 sprintf(buf, "egtpath %s %s\n", name+1, s);
8641 SendToProgram(buf,cps); // send egtbpath command for this format
8643 if(*p == ',') p++; // read away comma to position for next format name
8648 InitChessProgram(cps, setup)
8649 ChessProgramState *cps;
8650 int setup; /* [HGM] needed to setup FRC opening position */
8652 char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
8653 if (appData.noChessProgram) return;
8654 hintRequested = FALSE;
8655 bookRequested = FALSE;
8657 /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
8658 /* moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
8659 if(cps->memSize) { /* [HGM] memory */
8660 sprintf(buf, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
8661 SendToProgram(buf, cps);
8663 SendEgtPath(cps); /* [HGM] EGT */
8664 if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
8665 sprintf(buf, "cores %d\n", appData.smpCores);
8666 SendToProgram(buf, cps);
8669 SendToProgram(cps->initString, cps);
8670 if (gameInfo.variant != VariantNormal &&
8671 gameInfo.variant != VariantLoadable
8672 /* [HGM] also send variant if board size non-standard */
8673 || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
8675 char *v = VariantName(gameInfo.variant);
8676 if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
8677 /* [HGM] in protocol 1 we have to assume all variants valid */
8678 sprintf(buf, _("Variant %s not supported by %s"), v, cps->tidy);
8679 DisplayFatalError(buf, 0, 1);
8683 /* [HGM] make prefix for non-standard board size. Awkward testing... */
8684 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8685 if( gameInfo.variant == VariantXiangqi )
8686 overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
8687 if( gameInfo.variant == VariantShogi )
8688 overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
8689 if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
8690 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
8691 if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
8692 gameInfo.variant == VariantGothic || gameInfo.variant == VariantFalcon )
8693 overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8694 if( gameInfo.variant == VariantCourier )
8695 overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8696 if( gameInfo.variant == VariantSuper )
8697 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
8698 if( gameInfo.variant == VariantGreat )
8699 overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
8702 sprintf(b, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
8703 gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
8704 /* [HGM] varsize: try first if this defiant size variant is specifically known */
8705 if(StrStr(cps->variants, b) == NULL) {
8706 // specific sized variant not known, check if general sizing allowed
8707 if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
8708 if(StrStr(cps->variants, "boardsize") == NULL) {
8709 sprintf(buf, "Board size %dx%d+%d not supported by %s",
8710 gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
8711 DisplayFatalError(buf, 0, 1);
8714 /* [HGM] here we really should compare with the maximum supported board size */
8717 } else sprintf(b, "%s", VariantName(gameInfo.variant));
8718 sprintf(buf, "variant %s\n", b);
8719 SendToProgram(buf, cps);
8721 currentlyInitializedVariant = gameInfo.variant;
8723 /* [HGM] send opening position in FRC to first engine */
8725 SendToProgram("force\n", cps);
8727 /* engine is now in force mode! Set flag to wake it up after first move. */
8728 setboardSpoiledMachineBlack = 1;
8732 snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
8733 SendToProgram(buf, cps);
8735 cps->maybeThinking = FALSE;
8736 cps->offeredDraw = 0;
8737 if (!appData.icsActive) {
8738 SendTimeControl(cps, movesPerSession, timeControl,
8739 timeIncrement, appData.searchDepth,
8742 if (appData.showThinking
8743 // [HGM] thinking: four options require thinking output to be sent
8744 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
8746 SendToProgram("post\n", cps);
8748 SendToProgram("hard\n", cps);
8749 if (!appData.ponderNextMove) {
8750 /* Warning: "easy" is a toggle in GNU Chess, so don't send
8751 it without being sure what state we are in first. "hard"
8752 is not a toggle, so that one is OK.
8754 SendToProgram("easy\n", cps);
8757 sprintf(buf, "ping %d\n", ++cps->lastPing);
8758 SendToProgram(buf, cps);
8760 cps->initDone = TRUE;
8765 StartChessProgram(cps)
8766 ChessProgramState *cps;
8771 if (appData.noChessProgram) return;
8772 cps->initDone = FALSE;
8774 if (strcmp(cps->host, "localhost") == 0) {
8775 err = StartChildProcess(cps->program, cps->dir, &cps->pr);
8776 } else if (*appData.remoteShell == NULLCHAR) {
8777 err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
8779 if (*appData.remoteUser == NULLCHAR) {
8780 snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
8783 snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
8784 cps->host, appData.remoteUser, cps->program);
8786 err = StartChildProcess(buf, "", &cps->pr);
8790 sprintf(buf, _("Startup failure on '%s'"), cps->program);
8791 DisplayFatalError(buf, err, 1);
8797 cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
8798 if (cps->protocolVersion > 1) {
8799 sprintf(buf, "xboard\nprotover %d\n", cps->protocolVersion);
8800 cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
8801 cps->comboCnt = 0; // and values of combo boxes
8802 SendToProgram(buf, cps);
8804 SendToProgram("xboard\n", cps);
8810 TwoMachinesEventIfReady P((void))
8812 if (first.lastPing != first.lastPong) {
8813 DisplayMessage("", _("Waiting for first chess program"));
8814 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
8817 if (second.lastPing != second.lastPong) {
8818 DisplayMessage("", _("Waiting for second chess program"));
8819 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
8827 NextMatchGame P((void))
8829 int index; /* [HGM] autoinc: step load index during match */
8831 if (*appData.loadGameFile != NULLCHAR) {
8832 index = appData.loadGameIndex;
8833 if(index < 0) { // [HGM] autoinc
8834 lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
8835 if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
8837 LoadGameFromFile(appData.loadGameFile,
8839 appData.loadGameFile, FALSE);
8840 } else if (*appData.loadPositionFile != NULLCHAR) {
8841 index = appData.loadPositionIndex;
8842 if(index < 0) { // [HGM] autoinc
8843 lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
8844 if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
8846 LoadPositionFromFile(appData.loadPositionFile,
8848 appData.loadPositionFile);
8850 TwoMachinesEventIfReady();
8853 void UserAdjudicationEvent( int result )
8855 ChessMove gameResult = GameIsDrawn;
8858 gameResult = WhiteWins;
8860 else if( result < 0 ) {
8861 gameResult = BlackWins;
8864 if( gameMode == TwoMachinesPlay ) {
8865 GameEnds( gameResult, "User adjudication", GE_XBOARD );
8870 // [HGM] save: calculate checksum of game to make games easily identifiable
8871 int StringCheckSum(char *s)
8874 if(s==NULL) return 0;
8875 while(*s) i = i*259 + *s++;
8882 for(i=backwardMostMove; i<forwardMostMove; i++) {
8883 sum += pvInfoList[i].depth;
8884 sum += StringCheckSum(parseList[i]);
8885 sum += StringCheckSum(commentList[i]);
8888 if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
8889 return sum + StringCheckSum(commentList[i]);
8890 } // end of save patch
8893 GameEnds(result, resultDetails, whosays)
8895 char *resultDetails;
8898 GameMode nextGameMode;
8902 if(endingGame) return; /* [HGM] crash: forbid recursion */
8905 if (appData.debugMode) {
8906 fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
8907 result, resultDetails ? resultDetails : "(null)", whosays);
8910 if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
8911 /* If we are playing on ICS, the server decides when the
8912 game is over, but the engine can offer to draw, claim
8916 if (appData.zippyPlay && first.initDone) {
8917 if (result == GameIsDrawn) {
8918 /* In case draw still needs to be claimed */
8919 SendToICS(ics_prefix);
8920 SendToICS("draw\n");
8921 } else if (StrCaseStr(resultDetails, "resign")) {
8922 SendToICS(ics_prefix);
8923 SendToICS("resign\n");
8927 endingGame = 0; /* [HGM] crash */
8931 /* If we're loading the game from a file, stop */
8932 if (whosays == GE_FILE) {
8933 (void) StopLoadGameTimer();
8937 /* Cancel draw offers */
8938 first.offeredDraw = second.offeredDraw = 0;
8940 /* If this is an ICS game, only ICS can really say it's done;
8941 if not, anyone can. */
8942 isIcsGame = (gameMode == IcsPlayingWhite ||
8943 gameMode == IcsPlayingBlack ||
8944 gameMode == IcsObserving ||
8945 gameMode == IcsExamining);
8947 if (!isIcsGame || whosays == GE_ICS) {
8948 /* OK -- not an ICS game, or ICS said it was done */
8950 if (!isIcsGame && !appData.noChessProgram)
8951 SetUserThinkingEnables();
8953 /* [HGM] if a machine claims the game end we verify this claim */
8954 if(gameMode == TwoMachinesPlay && appData.testClaims) {
8955 if(appData.testLegality && whosays >= GE_ENGINE1 ) {
8957 ChessMove trueResult = (ChessMove) -1;
8959 claimer = whosays == GE_ENGINE1 ? /* color of claimer */
8960 first.twoMachinesColor[0] :
8961 second.twoMachinesColor[0] ;
8963 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
8964 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
8965 /* [HGM] verify: engine mate claims accepted if they were flagged */
8966 trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
8968 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
8969 /* [HGM] verify: engine mate claims accepted if they were flagged */
8970 trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8972 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
8973 trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
8976 // now verify win claims, but not in drop games, as we don't understand those yet
8977 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
8978 || gameInfo.variant == VariantGreat) &&
8979 (result == WhiteWins && claimer == 'w' ||
8980 result == BlackWins && claimer == 'b' ) ) { // case to verify: engine claims own win
8981 if (appData.debugMode) {
8982 fprintf(debugFP, "result=%d sp=%d move=%d\n",
8983 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
8985 if(result != trueResult) {
8986 sprintf(buf, "False win claim: '%s'", resultDetails);
8987 result = claimer == 'w' ? BlackWins : WhiteWins;
8988 resultDetails = buf;
8991 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
8992 && (forwardMostMove <= backwardMostMove ||
8993 (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
8994 (claimer=='b')==(forwardMostMove&1))
8996 /* [HGM] verify: draws that were not flagged are false claims */
8997 sprintf(buf, "False draw claim: '%s'", resultDetails);
8998 result = claimer == 'w' ? BlackWins : WhiteWins;
8999 resultDetails = buf;
9001 /* (Claiming a loss is accepted no questions asked!) */
9004 /* [HGM] bare: don't allow bare King to win */
9005 if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
9006 && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
9007 && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
9008 && result != GameIsDrawn)
9009 { int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
9010 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
9011 int p = (signed char)boards[forwardMostMove][i][j] - color;
9012 if(p >= 0 && p <= (int)WhiteKing) k++;
9014 if (appData.debugMode) {
9015 fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
9016 result, resultDetails ? resultDetails : "(null)", whosays, k, color);
9019 result = GameIsDrawn;
9020 sprintf(buf, "%s but bare king", resultDetails);
9021 resultDetails = buf;
9026 if(serverMoves != NULL && !loadFlag) { char c = '=';
9027 if(result==WhiteWins) c = '+';
9028 if(result==BlackWins) c = '-';
9029 if(resultDetails != NULL)
9030 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
9032 if (resultDetails != NULL) {
9033 gameInfo.result = result;
9034 gameInfo.resultDetails = StrSave(resultDetails);
9036 /* display last move only if game was not loaded from file */
9037 if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
9038 DisplayMove(currentMove - 1);
9040 if (forwardMostMove != 0) {
9041 if (gameMode != PlayFromGameFile && gameMode != EditGame
9042 && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
9044 if (*appData.saveGameFile != NULLCHAR) {
9045 SaveGameToFile(appData.saveGameFile, TRUE);
9046 } else if (appData.autoSaveGames) {
9049 if (*appData.savePositionFile != NULLCHAR) {
9050 SavePositionToFile(appData.savePositionFile);
9055 /* Tell program how game ended in case it is learning */
9056 /* [HGM] Moved this to after saving the PGN, just in case */
9057 /* engine died and we got here through time loss. In that */
9058 /* case we will get a fatal error writing the pipe, which */
9059 /* would otherwise lose us the PGN. */
9060 /* [HGM] crash: not needed anymore, but doesn't hurt; */
9061 /* output during GameEnds should never be fatal anymore */
9062 if (gameMode == MachinePlaysWhite ||
9063 gameMode == MachinePlaysBlack ||
9064 gameMode == TwoMachinesPlay ||
9065 gameMode == IcsPlayingWhite ||
9066 gameMode == IcsPlayingBlack ||
9067 gameMode == BeginningOfGame) {
9069 sprintf(buf, "result %s {%s}\n", PGNResult(result),
9071 if (first.pr != NoProc) {
9072 SendToProgram(buf, &first);
9074 if (second.pr != NoProc &&
9075 gameMode == TwoMachinesPlay) {
9076 SendToProgram(buf, &second);
9081 if (appData.icsActive) {
9082 if (appData.quietPlay &&
9083 (gameMode == IcsPlayingWhite ||
9084 gameMode == IcsPlayingBlack)) {
9085 SendToICS(ics_prefix);
9086 SendToICS("set shout 1\n");
9088 nextGameMode = IcsIdle;
9089 ics_user_moved = FALSE;
9090 /* clean up premove. It's ugly when the game has ended and the
9091 * premove highlights are still on the board.
9095 ClearPremoveHighlights();
9096 DrawPosition(FALSE, boards[currentMove]);
9098 if (whosays == GE_ICS) {
9101 if (gameMode == IcsPlayingWhite)
9103 else if(gameMode == IcsPlayingBlack)
9107 if (gameMode == IcsPlayingBlack)
9109 else if(gameMode == IcsPlayingWhite)
9116 PlayIcsUnfinishedSound();
9119 } else if (gameMode == EditGame ||
9120 gameMode == PlayFromGameFile ||
9121 gameMode == AnalyzeMode ||
9122 gameMode == AnalyzeFile) {
9123 nextGameMode = gameMode;
9125 nextGameMode = EndOfGame;
9130 nextGameMode = gameMode;
9133 if (appData.noChessProgram) {
9134 gameMode = nextGameMode;
9136 endingGame = 0; /* [HGM] crash */
9141 /* Put first chess program into idle state */
9142 if (first.pr != NoProc &&
9143 (gameMode == MachinePlaysWhite ||
9144 gameMode == MachinePlaysBlack ||
9145 gameMode == TwoMachinesPlay ||
9146 gameMode == IcsPlayingWhite ||
9147 gameMode == IcsPlayingBlack ||
9148 gameMode == BeginningOfGame)) {
9149 SendToProgram("force\n", &first);
9150 if (first.usePing) {
9152 sprintf(buf, "ping %d\n", ++first.lastPing);
9153 SendToProgram(buf, &first);
9156 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
9157 /* Kill off first chess program */
9158 if (first.isr != NULL)
9159 RemoveInputSource(first.isr);
9162 if (first.pr != NoProc) {
9164 DoSleep( appData.delayBeforeQuit );
9165 SendToProgram("quit\n", &first);
9166 DoSleep( appData.delayAfterQuit );
9167 DestroyChildProcess(first.pr, first.useSigterm);
9172 /* Put second chess program into idle state */
9173 if (second.pr != NoProc &&
9174 gameMode == TwoMachinesPlay) {
9175 SendToProgram("force\n", &second);
9176 if (second.usePing) {
9178 sprintf(buf, "ping %d\n", ++second.lastPing);
9179 SendToProgram(buf, &second);
9182 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
9183 /* Kill off second chess program */
9184 if (second.isr != NULL)
9185 RemoveInputSource(second.isr);
9188 if (second.pr != NoProc) {
9189 DoSleep( appData.delayBeforeQuit );
9190 SendToProgram("quit\n", &second);
9191 DoSleep( appData.delayAfterQuit );
9192 DestroyChildProcess(second.pr, second.useSigterm);
9197 if (matchMode && gameMode == TwoMachinesPlay) {
9200 if (first.twoMachinesColor[0] == 'w') {
9207 if (first.twoMachinesColor[0] == 'b') {
9216 if (matchGame < appData.matchGames) {
9218 if(appData.sameColorGames <= 1) { /* [HGM] alternate: suppress color swap */
9219 tmp = first.twoMachinesColor;
9220 first.twoMachinesColor = second.twoMachinesColor;
9221 second.twoMachinesColor = tmp;
9223 gameMode = nextGameMode;
9225 if(appData.matchPause>10000 || appData.matchPause<10)
9226 appData.matchPause = 10000; /* [HGM] make pause adjustable */
9227 ScheduleDelayedEvent(NextMatchGame, appData.matchPause);
9228 endingGame = 0; /* [HGM] crash */
9232 gameMode = nextGameMode;
9233 sprintf(buf, _("Match %s vs. %s: final score %d-%d-%d"),
9234 first.tidy, second.tidy,
9235 first.matchWins, second.matchWins,
9236 appData.matchGames - (first.matchWins + second.matchWins));
9237 DisplayFatalError(buf, 0, 0);
9240 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
9241 !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
9243 gameMode = nextGameMode;
9245 endingGame = 0; /* [HGM] crash */
9248 /* Assumes program was just initialized (initString sent).
9249 Leaves program in force mode. */
9251 FeedMovesToProgram(cps, upto)
9252 ChessProgramState *cps;
9257 if (appData.debugMode)
9258 fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
9259 startedFromSetupPosition ? "position and " : "",
9260 backwardMostMove, upto, cps->which);
9261 if(currentlyInitializedVariant != gameInfo.variant) { char buf[MSG_SIZ];
9262 // [HGM] variantswitch: make engine aware of new variant
9263 if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
9264 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
9265 sprintf(buf, "variant %s\n", VariantName(gameInfo.variant));
9266 SendToProgram(buf, cps);
9267 currentlyInitializedVariant = gameInfo.variant;
9269 SendToProgram("force\n", cps);
9270 if (startedFromSetupPosition) {
9271 SendBoard(cps, backwardMostMove);
9272 if (appData.debugMode) {
9273 fprintf(debugFP, "feedMoves\n");
9276 for (i = backwardMostMove; i < upto; i++) {
9277 SendMoveToProgram(i, cps);
9283 ResurrectChessProgram()
9285 /* The chess program may have exited.
9286 If so, restart it and feed it all the moves made so far. */
9288 if (appData.noChessProgram || first.pr != NoProc) return;
9290 StartChessProgram(&first);
9291 InitChessProgram(&first, FALSE);
9292 FeedMovesToProgram(&first, currentMove);
9294 if (!first.sendTime) {
9295 /* can't tell gnuchess what its clock should read,
9296 so we bow to its notion. */
9298 timeRemaining[0][currentMove] = whiteTimeRemaining;
9299 timeRemaining[1][currentMove] = blackTimeRemaining;
9302 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
9303 appData.icsEngineAnalyze) && first.analysisSupport) {
9304 SendToProgram("analyze\n", &first);
9305 first.analyzing = TRUE;
9318 if (appData.debugMode) {
9319 fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
9320 redraw, init, gameMode);
9322 CleanupTail(); // [HGM] vari: delete any stored variations
9323 pausing = pauseExamInvalid = FALSE;
9324 startedFromSetupPosition = blackPlaysFirst = FALSE;
9326 whiteFlag = blackFlag = FALSE;
9327 userOfferedDraw = FALSE;
9328 hintRequested = bookRequested = FALSE;
9329 first.maybeThinking = FALSE;
9330 second.maybeThinking = FALSE;
9331 first.bookSuspend = FALSE; // [HGM] book
9332 second.bookSuspend = FALSE;
9333 thinkOutput[0] = NULLCHAR;
9334 lastHint[0] = NULLCHAR;
9335 ClearGameInfo(&gameInfo);
9336 gameInfo.variant = StringToVariant(appData.variant);
9337 ics_user_moved = ics_clock_paused = FALSE;
9338 ics_getting_history = H_FALSE;
9340 white_holding[0] = black_holding[0] = NULLCHAR;
9341 ClearProgramStats();
9342 opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
9346 flipView = appData.flipView;
9347 ClearPremoveHighlights();
9349 alarmSounded = FALSE;
9351 GameEnds((ChessMove) 0, NULL, GE_PLAYER);
9352 if(appData.serverMovesName != NULL) {
9353 /* [HGM] prepare to make moves file for broadcasting */
9354 clock_t t = clock();
9355 if(serverMoves != NULL) fclose(serverMoves);
9356 serverMoves = fopen(appData.serverMovesName, "r");
9357 if(serverMoves != NULL) {
9358 fclose(serverMoves);
9359 /* delay 15 sec before overwriting, so all clients can see end */
9360 while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
9362 serverMoves = fopen(appData.serverMovesName, "w");
9366 gameMode = BeginningOfGame;
9369 if(appData.icsActive) gameInfo.variant = VariantNormal;
9370 currentMove = forwardMostMove = backwardMostMove = 0;
9371 InitPosition(redraw);
9372 for (i = 0; i < MAX_MOVES; i++) {
9373 if (commentList[i] != NULL) {
9374 free(commentList[i]);
9375 commentList[i] = NULL;
9380 timeRemaining[0][0] = whiteTimeRemaining;
9381 timeRemaining[1][0] = blackTimeRemaining;
9382 if (first.pr == NULL) {
9383 StartChessProgram(&first);
9386 InitChessProgram(&first, startedFromSetupPosition);
9390 DisplayMessage("", "");
9391 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
9392 lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
9400 if (!AutoPlayOneMove())
9402 if (matchMode || appData.timeDelay == 0)
9404 if (appData.timeDelay < 0 || gameMode == AnalyzeFile)
9406 StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
9415 int fromX, fromY, toX, toY;
9417 if (appData.debugMode) {
9418 fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
9421 if (gameMode != PlayFromGameFile)
9424 if (currentMove >= forwardMostMove) {
9425 gameMode = EditGame;
9428 /* [AS] Clear current move marker at the end of a game */
9429 /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
9434 toX = moveList[currentMove][2] - AAA;
9435 toY = moveList[currentMove][3] - ONE;
9437 if (moveList[currentMove][1] == '@') {
9438 if (appData.highlightLastMove) {
9439 SetHighlights(-1, -1, toX, toY);
9442 fromX = moveList[currentMove][0] - AAA;
9443 fromY = moveList[currentMove][1] - ONE;
9445 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
9447 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
9449 if (appData.highlightLastMove) {
9450 SetHighlights(fromX, fromY, toX, toY);
9453 DisplayMove(currentMove);
9454 SendMoveToProgram(currentMove++, &first);
9455 DisplayBothClocks();
9456 DrawPosition(FALSE, boards[currentMove]);
9457 // [HGM] PV info: always display, routine tests if empty
9458 DisplayComment(currentMove - 1, commentList[currentMove]);
9464 LoadGameOneMove(readAhead)
9465 ChessMove readAhead;
9467 int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
9468 char promoChar = NULLCHAR;
9473 if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
9474 gameMode != AnalyzeMode && gameMode != Training) {
9479 yyboardindex = forwardMostMove;
9480 if (readAhead != (ChessMove)0) {
9481 moveType = readAhead;
9483 if (gameFileFP == NULL)
9485 moveType = (ChessMove) yylex();
9491 if (appData.debugMode)
9492 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9495 /* append the comment but don't display it */
9496 AppendComment(currentMove, p, FALSE);
9499 case WhiteCapturesEnPassant:
9500 case BlackCapturesEnPassant:
9501 case WhitePromotionChancellor:
9502 case BlackPromotionChancellor:
9503 case WhitePromotionArchbishop:
9504 case BlackPromotionArchbishop:
9505 case WhitePromotionCentaur:
9506 case BlackPromotionCentaur:
9507 case WhitePromotionQueen:
9508 case BlackPromotionQueen:
9509 case WhitePromotionRook:
9510 case BlackPromotionRook:
9511 case WhitePromotionBishop:
9512 case BlackPromotionBishop:
9513 case WhitePromotionKnight:
9514 case BlackPromotionKnight:
9515 case WhitePromotionKing:
9516 case BlackPromotionKing:
9518 case WhiteKingSideCastle:
9519 case WhiteQueenSideCastle:
9520 case BlackKingSideCastle:
9521 case BlackQueenSideCastle:
9522 case WhiteKingSideCastleWild:
9523 case WhiteQueenSideCastleWild:
9524 case BlackKingSideCastleWild:
9525 case BlackQueenSideCastleWild:
9527 case WhiteHSideCastleFR:
9528 case WhiteASideCastleFR:
9529 case BlackHSideCastleFR:
9530 case BlackASideCastleFR:
9532 if (appData.debugMode)
9533 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
9534 fromX = currentMoveString[0] - AAA;
9535 fromY = currentMoveString[1] - ONE;
9536 toX = currentMoveString[2] - AAA;
9537 toY = currentMoveString[3] - ONE;
9538 promoChar = currentMoveString[4];
9543 if (appData.debugMode)
9544 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
9545 fromX = moveType == WhiteDrop ?
9546 (int) CharToPiece(ToUpper(currentMoveString[0])) :
9547 (int) CharToPiece(ToLower(currentMoveString[0]));
9549 toX = currentMoveString[2] - AAA;
9550 toY = currentMoveString[3] - ONE;
9556 case GameUnfinished:
9557 if (appData.debugMode)
9558 fprintf(debugFP, "Parsed game end: %s\n", yy_text);
9559 p = strchr(yy_text, '{');
9560 if (p == NULL) p = strchr(yy_text, '(');
9563 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
9565 q = strchr(p, *p == '{' ? '}' : ')');
9566 if (q != NULL) *q = NULLCHAR;
9569 GameEnds(moveType, p, GE_FILE);
9571 if (cmailMsgLoaded) {
9573 flipView = WhiteOnMove(currentMove);
9574 if (moveType == GameUnfinished) flipView = !flipView;
9575 if (appData.debugMode)
9576 fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
9580 case (ChessMove) 0: /* end of file */
9581 if (appData.debugMode)
9582 fprintf(debugFP, "Parser hit end of file\n");
9583 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9589 if (WhiteOnMove(currentMove)) {
9590 GameEnds(BlackWins, "Black mates", GE_FILE);
9592 GameEnds(WhiteWins, "White mates", GE_FILE);
9596 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
9603 if (lastLoadGameStart == GNUChessGame) {
9604 /* GNUChessGames have numbers, but they aren't move numbers */
9605 if (appData.debugMode)
9606 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
9607 yy_text, (int) moveType);
9608 return LoadGameOneMove((ChessMove)0); /* tail recursion */
9610 /* else fall thru */
9615 /* Reached start of next game in file */
9616 if (appData.debugMode)
9617 fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
9618 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9624 if (WhiteOnMove(currentMove)) {
9625 GameEnds(BlackWins, "Black mates", GE_FILE);
9627 GameEnds(WhiteWins, "White mates", GE_FILE);
9631 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
9637 case PositionDiagram: /* should not happen; ignore */
9638 case ElapsedTime: /* ignore */
9639 case NAG: /* ignore */
9640 if (appData.debugMode)
9641 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
9642 yy_text, (int) moveType);
9643 return LoadGameOneMove((ChessMove)0); /* tail recursion */
9646 if (appData.testLegality) {
9647 if (appData.debugMode)
9648 fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
9649 sprintf(move, _("Illegal move: %d.%s%s"),
9650 (forwardMostMove / 2) + 1,
9651 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9652 DisplayError(move, 0);
9655 if (appData.debugMode)
9656 fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
9657 yy_text, currentMoveString);
9658 fromX = currentMoveString[0] - AAA;
9659 fromY = currentMoveString[1] - ONE;
9660 toX = currentMoveString[2] - AAA;
9661 toY = currentMoveString[3] - ONE;
9662 promoChar = currentMoveString[4];
9667 if (appData.debugMode)
9668 fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
9669 sprintf(move, _("Ambiguous move: %d.%s%s"),
9670 (forwardMostMove / 2) + 1,
9671 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9672 DisplayError(move, 0);
9677 case ImpossibleMove:
9678 if (appData.debugMode)
9679 fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
9680 sprintf(move, _("Illegal move: %d.%s%s"),
9681 (forwardMostMove / 2) + 1,
9682 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9683 DisplayError(move, 0);
9689 if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
9690 DrawPosition(FALSE, boards[currentMove]);
9691 DisplayBothClocks();
9692 if (!appData.matchMode) // [HGM] PV info: routine tests if empty
9693 DisplayComment(currentMove - 1, commentList[currentMove]);
9695 (void) StopLoadGameTimer();
9697 cmailOldMove = forwardMostMove;
9700 /* currentMoveString is set as a side-effect of yylex */
9701 strcat(currentMoveString, "\n");
9702 strcpy(moveList[forwardMostMove], currentMoveString);
9704 thinkOutput[0] = NULLCHAR;
9705 MakeMove(fromX, fromY, toX, toY, promoChar);
9706 currentMove = forwardMostMove;
9711 /* Load the nth game from the given file */
9713 LoadGameFromFile(filename, n, title, useList)
9717 /*Boolean*/ int useList;
9722 if (strcmp(filename, "-") == 0) {
9726 f = fopen(filename, "rb");
9728 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9729 DisplayError(buf, errno);
9733 if (fseek(f, 0, 0) == -1) {
9734 /* f is not seekable; probably a pipe */
9737 if (useList && n == 0) {
9738 int error = GameListBuild(f);
9740 DisplayError(_("Cannot build game list"), error);
9741 } else if (!ListEmpty(&gameList) &&
9742 ((ListGame *) gameList.tailPred)->number > 1) {
9743 // TODO convert to GTK
9744 // GameListPopUp(f, title);
9751 return LoadGame(f, n, title, FALSE);
9756 MakeRegisteredMove()
9758 int fromX, fromY, toX, toY;
9760 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
9761 switch (cmailMoveType[lastLoadGameNumber - 1]) {
9764 if (appData.debugMode)
9765 fprintf(debugFP, "Restoring %s for game %d\n",
9766 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
9768 thinkOutput[0] = NULLCHAR;
9769 strcpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1]);
9770 fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
9771 fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
9772 toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
9773 toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
9774 promoChar = cmailMove[lastLoadGameNumber - 1][4];
9775 MakeMove(fromX, fromY, toX, toY, promoChar);
9776 ShowMove(fromX, fromY, toX, toY);
9777 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9784 if (WhiteOnMove(currentMove)) {
9785 GameEnds(BlackWins, "Black mates", GE_PLAYER);
9787 GameEnds(WhiteWins, "White mates", GE_PLAYER);
9792 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
9799 if (WhiteOnMove(currentMove)) {
9800 GameEnds(BlackWins, "White resigns", GE_PLAYER);
9802 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
9807 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
9818 /* Wrapper around LoadGame for use when a Cmail message is loaded */
9820 CmailLoadGame(f, gameNumber, title, useList)
9828 if (gameNumber > nCmailGames) {
9829 DisplayError(_("No more games in this message"), 0);
9832 if (f == lastLoadGameFP) {
9833 int offset = gameNumber - lastLoadGameNumber;
9835 cmailMsg[0] = NULLCHAR;
9836 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
9837 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
9838 nCmailMovesRegistered--;
9840 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
9841 if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
9842 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
9845 if (! RegisterMove()) return FALSE;
9849 retVal = LoadGame(f, gameNumber, title, useList);
9851 /* Make move registered during previous look at this game, if any */
9852 MakeRegisteredMove();
9854 if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
9855 commentList[currentMove]
9856 = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
9857 DisplayComment(currentMove - 1, commentList[currentMove]);
9863 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
9868 int gameNumber = lastLoadGameNumber + offset;
9869 if (lastLoadGameFP == NULL) {
9870 DisplayError(_("No game has been loaded yet"), 0);
9873 if (gameNumber <= 0) {
9874 DisplayError(_("Can't back up any further"), 0);
9877 if (cmailMsgLoaded) {
9878 return CmailLoadGame(lastLoadGameFP, gameNumber,
9879 lastLoadGameTitle, lastLoadGameUseList);
9881 return LoadGame(lastLoadGameFP, gameNumber,
9882 lastLoadGameTitle, lastLoadGameUseList);
9888 /* Load the nth game from open file f */
9890 LoadGame(f, gameNumber, title, useList)
9898 int gn = gameNumber;
9899 ListGame *lg = NULL;
9902 GameMode oldGameMode;
9903 VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
9905 if (appData.debugMode)
9906 fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
9908 if (gameMode == Training )
9909 SetTrainingModeOff();
9911 oldGameMode = gameMode;
9912 if (gameMode != BeginningOfGame)
9918 if (lastLoadGameFP != NULL && lastLoadGameFP != f)
9920 fclose(lastLoadGameFP);
9925 lg = (ListGame *) ListElem(&gameList, gameNumber-1);
9929 fseek(f, lg->offset, 0);
9930 GameListHighlight(gameNumber);
9935 DisplayError(_("Game number out of range"), 0);
9942 if (fseek(f, 0, 0) == -1)
9944 if (f == lastLoadGameFP ?
9945 gameNumber == lastLoadGameNumber + 1 :
9952 DisplayError(_("Can't seek on game file"), 0);
9959 lastLoadGameNumber = gameNumber;
9960 strcpy(lastLoadGameTitle, title);
9961 lastLoadGameUseList = useList;
9965 if (lg && lg->gameInfo.white && lg->gameInfo.black)
9967 snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
9968 lg->gameInfo.black);
9971 else if (*title != NULLCHAR)
9975 sprintf(buf, "%s %d", title, gameNumber);
9980 DisplayTitle(title);
9984 if (gameMode != AnalyzeFile && gameMode != AnalyzeMode)
9986 gameMode = PlayFromGameFile;
9990 currentMove = forwardMostMove = backwardMostMove = 0;
9991 CopyBoard(boards[0], initialPosition);
9995 * Skip the first gn-1 games in the file.
9996 * Also skip over anything that precedes an identifiable
9997 * start of game marker, to avoid being confused by
9998 * garbage at the start of the file. Currently
9999 * recognized start of game markers are the move number "1",
10000 * the pattern "gnuchess .* game", the pattern
10001 * "^[#;%] [^ ]* game file", and a PGN tag block.
10002 * A game that starts with one of the latter two patterns
10003 * will also have a move number 1, possibly
10004 * following a position diagram.
10005 * 5-4-02: Let's try being more lenient and allowing a game to
10006 * start with an unnumbered move. Does that break anything?
10008 cm = lastLoadGameStart = (ChessMove) 0;
10010 yyboardindex = forwardMostMove;
10011 cm = (ChessMove) yylex();
10013 case (ChessMove) 0:
10014 if (cmailMsgLoaded) {
10015 nCmailGames = CMAIL_MAX_GAMES - gn;
10018 DisplayError(_("Game not found in file"), 0);
10025 lastLoadGameStart = cm;
10028 case MoveNumberOne:
10029 switch (lastLoadGameStart) {
10034 case MoveNumberOne:
10035 case (ChessMove) 0:
10036 gn--; /* count this game */
10037 lastLoadGameStart = cm;
10046 switch (lastLoadGameStart) {
10049 case MoveNumberOne:
10050 case (ChessMove) 0:
10051 gn--; /* count this game */
10052 lastLoadGameStart = cm;
10055 lastLoadGameStart = cm; /* game counted already */
10063 yyboardindex = forwardMostMove;
10064 cm = (ChessMove) yylex();
10065 } while (cm == PGNTag || cm == Comment);
10072 if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
10073 if ( cmailResult[CMAIL_MAX_GAMES - gn - 1]
10074 != CMAIL_OLD_RESULT) {
10076 cmailResult[ CMAIL_MAX_GAMES
10077 - gn - 1] = CMAIL_OLD_RESULT;
10083 /* Only a NormalMove can be at the start of a game
10084 * without a position diagram. */
10085 if (lastLoadGameStart == (ChessMove) 0) {
10087 lastLoadGameStart = MoveNumberOne;
10096 if (appData.debugMode)
10097 fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
10099 if (cm == XBoardGame) {
10100 /* Skip any header junk before position diagram and/or move 1 */
10102 yyboardindex = forwardMostMove;
10103 cm = (ChessMove) yylex();
10105 if (cm == (ChessMove) 0 ||
10106 cm == GNUChessGame || cm == XBoardGame) {
10107 /* Empty game; pretend end-of-file and handle later */
10108 cm = (ChessMove) 0;
10112 if (cm == MoveNumberOne || cm == PositionDiagram ||
10113 cm == PGNTag || cm == Comment)
10116 } else if (cm == GNUChessGame) {
10117 if (gameInfo.event != NULL) {
10118 free(gameInfo.event);
10120 gameInfo.event = StrSave(yy_text);
10123 startedFromSetupPosition = FALSE;
10124 while (cm == PGNTag) {
10125 if (appData.debugMode)
10126 fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
10127 err = ParsePGNTag(yy_text, &gameInfo);
10128 if (!err) numPGNTags++;
10130 /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
10131 if(gameInfo.variant != oldVariant) {
10132 startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
10133 InitPosition(TRUE);
10134 oldVariant = gameInfo.variant;
10135 if (appData.debugMode)
10136 fprintf(debugFP, "New variant %d\n", (int) oldVariant);
10140 if (gameInfo.fen != NULL) {
10141 Board initial_position;
10142 startedFromSetupPosition = TRUE;
10143 if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
10145 DisplayError(_("Bad FEN position in file"), 0);
10148 CopyBoard(boards[0], initial_position);
10149 if (blackPlaysFirst) {
10150 currentMove = forwardMostMove = backwardMostMove = 1;
10151 CopyBoard(boards[1], initial_position);
10152 strcpy(moveList[0], "");
10153 strcpy(parseList[0], "");
10154 timeRemaining[0][1] = whiteTimeRemaining;
10155 timeRemaining[1][1] = blackTimeRemaining;
10156 if (commentList[0] != NULL) {
10157 commentList[1] = commentList[0];
10158 commentList[0] = NULL;
10161 currentMove = forwardMostMove = backwardMostMove = 0;
10163 /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
10165 initialRulePlies = FENrulePlies;
10166 for( i=0; i< nrCastlingRights; i++ )
10167 initialRights[i] = initial_position[CASTLING][i];
10169 yyboardindex = forwardMostMove;
10170 free(gameInfo.fen);
10171 gameInfo.fen = NULL;
10174 yyboardindex = forwardMostMove;
10175 cm = (ChessMove) yylex();
10177 /* Handle comments interspersed among the tags */
10178 while (cm == Comment) {
10180 if (appData.debugMode)
10181 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10183 AppendComment(currentMove, p, FALSE);
10184 yyboardindex = forwardMostMove;
10185 cm = (ChessMove) yylex();
10189 /* don't rely on existence of Event tag since if game was
10190 * pasted from clipboard the Event tag may not exist
10192 if (numPGNTags > 0){
10194 if (gameInfo.variant == VariantNormal) {
10195 gameInfo.variant = StringToVariant(gameInfo.event);
10198 if( appData.autoDisplayTags ) {
10199 tags = PGNTags(&gameInfo);
10200 TagsPopUp(tags, CmailMsg());
10205 /* Make something up, but don't display it now */
10210 if (cm == PositionDiagram) {
10213 Board initial_position;
10215 if (appData.debugMode)
10216 fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
10218 if (!startedFromSetupPosition) {
10220 for (i = BOARD_HEIGHT - 1; i >= 0; i--)
10221 for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
10231 initial_position[i][j++] = CharToPiece(*p);
10234 while (*p == ' ' || *p == '\t' ||
10235 *p == '\n' || *p == '\r') p++;
10237 if (strncmp(p, "black", strlen("black"))==0)
10238 blackPlaysFirst = TRUE;
10240 blackPlaysFirst = FALSE;
10241 startedFromSetupPosition = TRUE;
10243 CopyBoard(boards[0], initial_position);
10244 if (blackPlaysFirst) {
10245 currentMove = forwardMostMove = backwardMostMove = 1;
10246 CopyBoard(boards[1], initial_position);
10247 strcpy(moveList[0], "");
10248 strcpy(parseList[0], "");
10249 timeRemaining[0][1] = whiteTimeRemaining;
10250 timeRemaining[1][1] = blackTimeRemaining;
10251 if (commentList[0] != NULL) {
10252 commentList[1] = commentList[0];
10253 commentList[0] = NULL;
10256 currentMove = forwardMostMove = backwardMostMove = 0;
10259 yyboardindex = forwardMostMove;
10260 cm = (ChessMove) yylex();
10263 if (first.pr == NoProc) {
10264 StartChessProgram(&first);
10266 InitChessProgram(&first, FALSE);
10267 SendToProgram("force\n", &first);
10268 if (startedFromSetupPosition) {
10269 SendBoard(&first, forwardMostMove);
10270 if (appData.debugMode) {
10271 fprintf(debugFP, "Load Game\n");
10273 DisplayBothClocks();
10276 /* [HGM] server: flag to write setup moves in broadcast file as one */
10277 loadFlag = appData.suppressLoadMoves;
10279 while (cm == Comment) {
10281 if (appData.debugMode)
10282 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10284 AppendComment(currentMove, p, FALSE);
10285 yyboardindex = forwardMostMove;
10286 cm = (ChessMove) yylex();
10289 if ((cm == (ChessMove) 0 && lastLoadGameStart != (ChessMove) 0) ||
10290 cm == WhiteWins || cm == BlackWins ||
10291 cm == GameIsDrawn || cm == GameUnfinished) {
10292 DisplayMessage("", _("No moves in game"));
10293 if (cmailMsgLoaded) {
10294 if (appData.debugMode)
10295 fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
10299 DrawPosition(FALSE, boards[currentMove]);
10300 DisplayBothClocks();
10301 gameMode = EditGame;
10308 // [HGM] PV info: routine tests if comment empty
10309 if (!matchMode && (pausing || appData.timeDelay != 0)) {
10310 DisplayComment(currentMove - 1, commentList[currentMove]);
10312 if (!matchMode && appData.timeDelay != 0)
10313 DrawPosition(FALSE, boards[currentMove]);
10315 if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
10316 programStats.ok_to_send = 1;
10319 /* if the first token after the PGN tags is a move
10320 * and not move number 1, retrieve it from the parser
10322 if (cm != MoveNumberOne)
10323 LoadGameOneMove(cm);
10325 /* load the remaining moves from the file */
10326 while (LoadGameOneMove((ChessMove)0)) {
10327 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10328 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10331 /* rewind to the start of the game */
10332 currentMove = backwardMostMove;
10334 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10336 if (oldGameMode == AnalyzeFile ||
10337 oldGameMode == AnalyzeMode) {
10338 AnalyzeFileEvent();
10341 if (matchMode || appData.timeDelay == 0) {
10343 gameMode = EditGame;
10345 } else if (appData.timeDelay > 0) {
10346 AutoPlayGameLoop();
10349 if (appData.debugMode)
10350 fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
10352 loadFlag = 0; /* [HGM] true game starts */
10356 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
10358 ReloadPosition(offset)
10361 int positionNumber = lastLoadPositionNumber + offset;
10362 if (lastLoadPositionFP == NULL) {
10363 DisplayError(_("No position has been loaded yet"), 0);
10366 if (positionNumber <= 0) {
10367 DisplayError(_("Can't back up any further"), 0);
10370 return LoadPosition(lastLoadPositionFP, positionNumber,
10371 lastLoadPositionTitle);
10374 /* Load the nth position from the given file */
10376 LoadPositionFromFile(filename, n, title)
10384 if (strcmp(filename, "-") == 0) {
10385 return LoadPosition(stdin, n, "stdin");
10387 f = fopen(filename, "rb");
10389 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10390 DisplayError(buf, errno);
10393 return LoadPosition(f, n, title);
10398 /* Load the nth position from the given open file, and close it */
10400 LoadPosition(f, positionNumber, title)
10402 int positionNumber;
10405 char *p, line[MSG_SIZ];
10406 Board initial_position;
10407 int i, j, fenMode, pn;
10409 if (gameMode == Training )
10410 SetTrainingModeOff();
10412 if (gameMode != BeginningOfGame) {
10413 Reset(FALSE, TRUE);
10415 if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
10416 fclose(lastLoadPositionFP);
10418 if (positionNumber == 0) positionNumber = 1;
10419 lastLoadPositionFP = f;
10420 lastLoadPositionNumber = positionNumber;
10421 strcpy(lastLoadPositionTitle, title);
10422 if (first.pr == NoProc) {
10423 StartChessProgram(&first);
10424 InitChessProgram(&first, FALSE);
10426 pn = positionNumber;
10427 if (positionNumber < 0) {
10428 /* Negative position number means to seek to that byte offset */
10429 if (fseek(f, -positionNumber, 0) == -1) {
10430 DisplayError(_("Can't seek on position file"), 0);
10435 if (fseek(f, 0, 0) == -1) {
10436 if (f == lastLoadPositionFP ?
10437 positionNumber == lastLoadPositionNumber + 1 :
10438 positionNumber == 1) {
10441 DisplayError(_("Can't seek on position file"), 0);
10446 /* See if this file is FEN or old-style xboard */
10447 if (fgets(line, MSG_SIZ, f) == NULL) {
10448 DisplayError(_("Position not found in file"), 0);
10451 // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
10452 fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
10455 if (fenMode || line[0] == '#') pn--;
10457 /* skip positions before number pn */
10458 if (fgets(line, MSG_SIZ, f) == NULL) {
10460 DisplayError(_("Position not found in file"), 0);
10463 if (fenMode || line[0] == '#') pn--;
10468 if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
10469 DisplayError(_("Bad FEN position in file"), 0);
10473 (void) fgets(line, MSG_SIZ, f);
10474 (void) fgets(line, MSG_SIZ, f);
10476 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
10477 (void) fgets(line, MSG_SIZ, f);
10478 for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
10481 initial_position[i][j++] = CharToPiece(*p);
10485 blackPlaysFirst = FALSE;
10487 (void) fgets(line, MSG_SIZ, f);
10488 if (strncmp(line, "black", strlen("black"))==0)
10489 blackPlaysFirst = TRUE;
10492 startedFromSetupPosition = TRUE;
10494 SendToProgram("force\n", &first);
10495 CopyBoard(boards[0], initial_position);
10496 if (blackPlaysFirst) {
10497 currentMove = forwardMostMove = backwardMostMove = 1;
10498 strcpy(moveList[0], "");
10499 strcpy(parseList[0], "");
10500 CopyBoard(boards[1], initial_position);
10501 DisplayMessage("", _("Black to play"));
10503 currentMove = forwardMostMove = backwardMostMove = 0;
10504 DisplayMessage("", _("White to play"));
10506 initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
10507 SendBoard(&first, forwardMostMove);
10508 if (appData.debugMode) {
10510 for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
10511 for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
10512 fprintf(debugFP, "Load Position\n");
10515 if (positionNumber > 1) {
10516 sprintf(line, "%s %d", title, positionNumber);
10517 DisplayTitle(line);
10519 DisplayTitle(title);
10521 gameMode = EditGame;
10524 timeRemaining[0][1] = whiteTimeRemaining;
10525 timeRemaining[1][1] = blackTimeRemaining;
10526 DrawPosition(FALSE, boards[currentMove]);
10533 CopyPlayerNameIntoFileName(dest, src)
10536 while (*src != NULLCHAR && *src != ',') {
10541 *(*dest)++ = *src++;
10546 char *DefaultFileName(ext)
10549 static char def[MSG_SIZ];
10552 if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
10554 CopyPlayerNameIntoFileName(&p, gameInfo.white);
10556 CopyPlayerNameIntoFileName(&p, gameInfo.black);
10565 /* Save the current game to the given file */
10567 SaveGameToFile(filename, append)
10574 if (strcmp(filename, "-") == 0) {
10575 return SaveGame(stdout, 0, NULL);
10577 f = fopen(filename, append ? "a" : "w");
10579 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10580 DisplayError(buf, errno);
10583 return SaveGame(f, 0, NULL);
10592 static char buf[MSG_SIZ];
10595 p = strchr(str, ' ');
10596 if (p == NULL) return str;
10597 strncpy(buf, str, p - str);
10598 buf[p - str] = NULLCHAR;
10602 #define PGN_MAX_LINE 75
10604 #define PGN_SIDE_WHITE 0
10605 #define PGN_SIDE_BLACK 1
10608 static int FindFirstMoveOutOfBook( int side )
10612 if( backwardMostMove == 0 && ! startedFromSetupPosition) {
10613 int index = backwardMostMove;
10614 int has_book_hit = 0;
10616 if( (index % 2) != side ) {
10620 while( index < forwardMostMove ) {
10621 /* Check to see if engine is in book */
10622 int depth = pvInfoList[index].depth;
10623 int score = pvInfoList[index].score;
10629 else if( score == 0 && depth == 63 ) {
10630 in_book = 1; /* Zappa */
10632 else if( score == 2 && depth == 99 ) {
10633 in_book = 1; /* Abrok */
10636 has_book_hit += in_book;
10652 void GetOutOfBookInfo( char * buf )
10656 int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10658 oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
10659 oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
10663 if( oob[0] >= 0 || oob[1] >= 0 ) {
10664 for( i=0; i<2; i++ ) {
10668 if( i > 0 && oob[0] >= 0 ) {
10669 strcat( buf, " " );
10672 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
10673 sprintf( buf+strlen(buf), "%s%.2f",
10674 pvInfoList[idx].score >= 0 ? "+" : "",
10675 pvInfoList[idx].score / 100.0 );
10681 /* Save game in PGN style and close the file */
10686 int i, offset, linelen, newblock;
10690 int movelen, numlen, blank;
10691 char move_buffer[100]; /* [AS] Buffer for move+PV info */
10693 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10695 tm = time((time_t *) NULL);
10697 PrintPGNTags(f, &gameInfo);
10699 if (backwardMostMove > 0 || startedFromSetupPosition) {
10700 char *fen = PositionToFEN(backwardMostMove, NULL);
10701 fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
10702 fprintf(f, "\n{--------------\n");
10703 PrintPosition(f, backwardMostMove);
10704 fprintf(f, "--------------}\n");
10708 /* [AS] Out of book annotation */
10709 if( appData.saveOutOfBookInfo ) {
10712 GetOutOfBookInfo( buf );
10714 if( buf[0] != '\0' ) {
10715 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
10722 i = backwardMostMove;
10726 while (i < forwardMostMove) {
10727 /* Print comments preceding this move */
10728 if (commentList[i] != NULL) {
10729 if (linelen > 0) fprintf(f, "\n");
10730 fprintf(f, "%s", commentList[i]);
10735 /* Format move number */
10736 if ((i % 2) == 0) {
10737 sprintf(numtext, "%d.", (i - offset)/2 + 1);
10740 sprintf(numtext, "%d...", (i - offset)/2 + 1);
10742 numtext[0] = NULLCHAR;
10745 numlen = strlen(numtext);
10748 /* Print move number */
10749 blank = linelen > 0 && numlen > 0;
10750 if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
10759 fprintf(f, "%s", numtext);
10763 strcpy(move_buffer, SavePart(parseList[i])); // [HGM] pgn: print move via buffer, so it can be edited
10764 movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
10767 blank = linelen > 0 && movelen > 0;
10768 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
10777 fprintf(f, "%s", move_buffer);
10778 linelen += movelen;
10780 /* [AS] Add PV info if present */
10781 if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
10782 /* [HGM] add time */
10783 char buf[MSG_SIZ]; int seconds;
10785 seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
10787 if( seconds <= 0) buf[0] = 0; else
10788 if( seconds < 30 ) sprintf(buf, " %3.1f%c", seconds/10., 0); else {
10789 seconds = (seconds + 4)/10; // round to full seconds
10790 if( seconds < 60 ) sprintf(buf, " %d%c", seconds, 0); else
10791 sprintf(buf, " %d:%02d%c", seconds/60, seconds%60, 0);
10794 sprintf( move_buffer, "{%s%.2f/%d%s}",
10795 pvInfoList[i].score >= 0 ? "+" : "",
10796 pvInfoList[i].score / 100.0,
10797 pvInfoList[i].depth,
10800 movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
10802 /* Print score/depth */
10803 blank = linelen > 0 && movelen > 0;
10804 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
10813 fprintf(f, "%s", move_buffer);
10814 linelen += movelen;
10820 /* Start a new line */
10821 if (linelen > 0) fprintf(f, "\n");
10823 /* Print comments after last move */
10824 if (commentList[i] != NULL) {
10825 fprintf(f, "%s\n", commentList[i]);
10829 if (gameInfo.resultDetails != NULL &&
10830 gameInfo.resultDetails[0] != NULLCHAR) {
10831 fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
10832 PGNResult(gameInfo.result));
10834 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
10838 lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
10842 /* Save game in old style and close the file */
10844 SaveGameOldStyle(f)
10850 tm = time((time_t *) NULL);
10852 fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
10855 if (backwardMostMove > 0 || startedFromSetupPosition) {
10856 fprintf(f, "\n[--------------\n");
10857 PrintPosition(f, backwardMostMove);
10858 fprintf(f, "--------------]\n");
10863 i = backwardMostMove;
10864 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10866 while (i < forwardMostMove) {
10867 if (commentList[i] != NULL) {
10868 fprintf(f, "[%s]\n", commentList[i]);
10871 if ((i % 2) == 1) {
10872 fprintf(f, "%d. ... %s\n", (i - offset)/2 + 1, parseList[i]);
10875 fprintf(f, "%d. %s ", (i - offset)/2 + 1, parseList[i]);
10877 if (commentList[i] != NULL) {
10881 if (i >= forwardMostMove) {
10885 fprintf(f, "%s\n", parseList[i]);
10890 if (commentList[i] != NULL) {
10891 fprintf(f, "[%s]\n", commentList[i]);
10894 /* This isn't really the old style, but it's close enough */
10895 if (gameInfo.resultDetails != NULL &&
10896 gameInfo.resultDetails[0] != NULLCHAR) {
10897 fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
10898 gameInfo.resultDetails);
10900 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
10907 /* Save the current game to open file f and close the file */
10909 SaveGame(f, dummy, dummy2)
10914 if (gameMode == EditPosition) EditPositionDone(TRUE);
10915 lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
10916 if (appData.oldSaveStyle)
10917 return SaveGameOldStyle(f);
10919 return SaveGamePGN(f);
10922 /* Save the current position to the given file */
10924 SavePositionToFile(filename)
10930 if (strcmp(filename, "-") == 0) {
10931 return SavePosition(stdout, 0, NULL);
10933 f = fopen(filename, "a");
10935 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10936 DisplayError(buf, errno);
10939 SavePosition(f, 0, NULL);
10945 /* Save the current position to the given open file and close the file */
10947 SavePosition(f, dummy, dummy2)
10954 if (gameMode == EditPosition) EditPositionDone(TRUE);
10955 if (appData.oldSaveStyle) {
10956 tm = time((time_t *) NULL);
10958 fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
10960 fprintf(f, "[--------------\n");
10961 PrintPosition(f, currentMove);
10962 fprintf(f, "--------------]\n");
10964 fen = PositionToFEN(currentMove, NULL);
10965 fprintf(f, "%s\n", fen);
10973 ReloadCmailMsgEvent(unregister)
10977 static char *inFilename = NULL;
10978 static char *outFilename;
10980 struct stat inbuf, outbuf;
10983 /* Any registered moves are unregistered if unregister is set, */
10984 /* i.e. invoked by the signal handler */
10986 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
10987 cmailMoveRegistered[i] = FALSE;
10988 if (cmailCommentList[i] != NULL) {
10989 free(cmailCommentList[i]);
10990 cmailCommentList[i] = NULL;
10993 nCmailMovesRegistered = 0;
10996 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
10997 cmailResult[i] = CMAIL_NOT_RESULT;
11001 if (inFilename == NULL) {
11002 /* Because the filenames are static they only get malloced once */
11003 /* and they never get freed */
11004 inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
11005 sprintf(inFilename, "%s.game.in", appData.cmailGameName);
11007 outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
11008 sprintf(outFilename, "%s.out", appData.cmailGameName);
11011 status = stat(outFilename, &outbuf);
11013 cmailMailedMove = FALSE;
11015 status = stat(inFilename, &inbuf);
11016 cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
11019 /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
11020 counts the games, notes how each one terminated, etc.
11022 It would be nice to remove this kludge and instead gather all
11023 the information while building the game list. (And to keep it
11024 in the game list nodes instead of having a bunch of fixed-size
11025 parallel arrays.) Note this will require getting each game's
11026 termination from the PGN tags, as the game list builder does
11027 not process the game moves. --mann
11029 cmailMsgLoaded = TRUE;
11030 LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
11032 /* Load first game in the file or popup game menu */
11033 LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
11035 #endif /* !WIN32 */
11043 char string[MSG_SIZ];
11045 if ( cmailMailedMove
11046 || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
11047 return TRUE; /* Allow free viewing */
11050 /* Unregister move to ensure that we don't leave RegisterMove */
11051 /* with the move registered when the conditions for registering no */
11053 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11054 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
11055 nCmailMovesRegistered --;
11057 if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
11059 free(cmailCommentList[lastLoadGameNumber - 1]);
11060 cmailCommentList[lastLoadGameNumber - 1] = NULL;
11064 if (cmailOldMove == -1) {
11065 DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
11069 if (currentMove > cmailOldMove + 1) {
11070 DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
11074 if (currentMove < cmailOldMove) {
11075 DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
11079 if (forwardMostMove > currentMove) {
11080 /* Silently truncate extra moves */
11084 if ( (currentMove == cmailOldMove + 1)
11085 || ( (currentMove == cmailOldMove)
11086 && ( (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
11087 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
11088 if (gameInfo.result != GameUnfinished) {
11089 cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
11092 if (commentList[currentMove] != NULL) {
11093 cmailCommentList[lastLoadGameNumber - 1]
11094 = StrSave(commentList[currentMove]);
11096 strcpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1]);
11098 if (appData.debugMode)
11099 fprintf(debugFP, "Saving %s for game %d\n",
11100 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
11103 "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
11105 f = fopen(string, "w");
11106 if (appData.oldSaveStyle) {
11107 SaveGameOldStyle(f); /* also closes the file */
11109 sprintf(string, "%s.pos.out", appData.cmailGameName);
11110 f = fopen(string, "w");
11111 SavePosition(f, 0, NULL); /* also closes the file */
11113 fprintf(f, "{--------------\n");
11114 PrintPosition(f, currentMove);
11115 fprintf(f, "--------------}\n\n");
11117 SaveGame(f, 0, NULL); /* also closes the file*/
11120 cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
11121 nCmailMovesRegistered ++;
11122 } else if (nCmailGames == 1) {
11123 DisplayError(_("You have not made a move yet"), 0);
11134 static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
11135 FILE *commandOutput;
11136 char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
11137 int nBytes = 0; /* Suppress warnings on uninitialized variables */
11143 if (! cmailMsgLoaded) {
11144 DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
11148 if (nCmailGames == nCmailResults) {
11149 DisplayError(_("No unfinished games"), 0);
11153 #if CMAIL_PROHIBIT_REMAIL
11154 if (cmailMailedMove) {
11155 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);
11156 DisplayError(msg, 0);
11161 if (! (cmailMailedMove || RegisterMove())) return;
11163 if ( cmailMailedMove
11164 || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
11165 sprintf(string, partCommandString,
11166 appData.debugMode ? " -v" : "", appData.cmailGameName);
11167 commandOutput = popen(string, "r");
11169 if (commandOutput == NULL) {
11170 DisplayError(_("Failed to invoke cmail"), 0);
11172 for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
11173 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
11175 if (nBuffers > 1) {
11176 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
11177 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
11178 nBytes = MSG_SIZ - 1;
11180 (void) memcpy(msg, buffer, nBytes);
11182 *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
11184 if(StrStr(msg, "Mailed cmail message to ") != NULL) {
11185 cmailMailedMove = TRUE; /* Prevent >1 moves */
11188 for (i = 0; i < nCmailGames; i ++) {
11189 if (cmailResult[i] == CMAIL_NOT_RESULT) {
11194 && ( (arcDir = (char *) getenv("CMAIL_ARCDIR"))
11196 sprintf(buffer, "%s/%s.%s.archive",
11198 appData.cmailGameName,
11200 LoadGameFromFile(buffer, 1, buffer, FALSE);
11201 cmailMsgLoaded = FALSE;
11205 DisplayInformation(msg);
11206 pclose(commandOutput);
11209 if ((*cmailMsg) != '\0') {
11210 DisplayInformation(cmailMsg);
11215 #endif /* !WIN32 */
11224 int prependComma = 0;
11226 char string[MSG_SIZ]; /* Space for game-list */
11229 if (!cmailMsgLoaded) return "";
11231 if (cmailMailedMove) {
11232 sprintf(cmailMsg, _("Waiting for reply from opponent\n"));
11234 /* Create a list of games left */
11235 sprintf(string, "[");
11236 for (i = 0; i < nCmailGames; i ++) {
11237 if (! ( cmailMoveRegistered[i]
11238 || (cmailResult[i] == CMAIL_OLD_RESULT))) {
11239 if (prependComma) {
11240 sprintf(number, ",%d", i + 1);
11242 sprintf(number, "%d", i + 1);
11246 strcat(string, number);
11249 strcat(string, "]");
11251 if (nCmailMovesRegistered + nCmailResults == 0) {
11252 switch (nCmailGames) {
11255 _("Still need to make move for game\n"));
11260 _("Still need to make moves for both games\n"));
11265 _("Still need to make moves for all %d games\n"),
11270 switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
11273 _("Still need to make a move for game %s\n"),
11278 if (nCmailResults == nCmailGames) {
11279 sprintf(cmailMsg, _("No unfinished games\n"));
11281 sprintf(cmailMsg, _("Ready to send mail\n"));
11287 _("Still need to make moves for games %s\n"),
11299 if (gameMode == Training)
11300 SetTrainingModeOff();
11303 cmailMsgLoaded = FALSE;
11304 if (appData.icsActive) {
11305 SendToICS(ics_prefix);
11306 SendToICS("refresh\n");
11316 /* Give up on clean exit */
11320 /* Keep trying for clean exit */
11324 if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
11326 if (telnetISR != NULL) {
11327 RemoveInputSource(telnetISR);
11329 if (icsPR != NoProc) {
11330 DestroyChildProcess(icsPR, TRUE);
11333 /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
11334 GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
11336 /* [HGM] crash: the above GameEnds() is a dud if another one was running */
11337 /* make sure this other one finishes before killing it! */
11338 if(endingGame) { int count = 0;
11339 if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
11340 while(endingGame && count++ < 10) DoSleep(1);
11341 if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
11344 /* Kill off chess programs */
11345 if (first.pr != NoProc) {
11348 DoSleep( appData.delayBeforeQuit );
11349 SendToProgram("quit\n", &first);
11350 DoSleep( appData.delayAfterQuit );
11351 DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
11353 if (second.pr != NoProc) {
11354 DoSleep( appData.delayBeforeQuit );
11355 SendToProgram("quit\n", &second);
11356 DoSleep( appData.delayAfterQuit );
11357 DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
11359 if (first.isr != NULL) {
11360 RemoveInputSource(first.isr);
11362 if (second.isr != NULL) {
11363 RemoveInputSource(second.isr);
11366 ShutDownFrontEnd();
11373 if (appData.debugMode)
11374 fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
11378 if (gameMode == MachinePlaysWhite ||
11379 gameMode == MachinePlaysBlack) {
11382 DisplayBothClocks();
11384 if (gameMode == PlayFromGameFile) {
11385 if (appData.timeDelay >= 0)
11386 AutoPlayGameLoop();
11387 } else if (gameMode == IcsExamining && pauseExamInvalid) {
11388 Reset(FALSE, TRUE);
11389 SendToICS(ics_prefix);
11390 SendToICS("refresh\n");
11391 } else if (currentMove < forwardMostMove) {
11392 ForwardInner(forwardMostMove);
11394 pauseExamInvalid = FALSE;
11396 switch (gameMode) {
11400 pauseExamForwardMostMove = forwardMostMove;
11401 pauseExamInvalid = FALSE;
11404 case IcsPlayingWhite:
11405 case IcsPlayingBlack:
11409 case PlayFromGameFile:
11410 (void) StopLoadGameTimer();
11414 case BeginningOfGame:
11415 if (appData.icsActive) return;
11416 /* else fall through */
11417 case MachinePlaysWhite:
11418 case MachinePlaysBlack:
11419 case TwoMachinesPlay:
11420 if (forwardMostMove == 0)
11421 return; /* don't pause if no one has moved */
11422 if ((gameMode == MachinePlaysWhite &&
11423 !WhiteOnMove(forwardMostMove)) ||
11424 (gameMode == MachinePlaysBlack &&
11425 WhiteOnMove(forwardMostMove))) {
11438 char title[MSG_SIZ];
11440 if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
11441 strcpy(title, _("Edit comment"));
11443 sprintf(title, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
11444 WhiteOnMove(currentMove - 1) ? " " : ".. ",
11445 parseList[currentMove - 1]);
11448 EditCommentPopUp(currentMove, title, commentList[currentMove]);
11455 char *tags = PGNTags(&gameInfo);
11456 EditTagsPopUp(tags);
11463 if (appData.noChessProgram || gameMode == AnalyzeMode)
11466 if (gameMode != AnalyzeFile) {
11467 if (!appData.icsEngineAnalyze) {
11469 if (gameMode != EditGame) return;
11471 ResurrectChessProgram();
11472 SendToProgram("analyze\n", &first);
11473 first.analyzing = TRUE;
11474 /*first.maybeThinking = TRUE;*/
11475 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11476 EngineOutputPopUp();
11478 if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
11483 StartAnalysisClock();
11484 GetTimeMark(&lastNodeCountTime);
11491 if (appData.noChessProgram || gameMode == AnalyzeFile)
11494 if (gameMode != AnalyzeMode) {
11496 if (gameMode != EditGame) return;
11497 ResurrectChessProgram();
11498 SendToProgram("analyze\n", &first);
11499 first.analyzing = TRUE;
11500 /*first.maybeThinking = TRUE;*/
11501 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11502 EngineOutputPopUp();
11504 gameMode = AnalyzeFile;
11509 StartAnalysisClock();
11510 GetTimeMark(&lastNodeCountTime);
11515 MachineWhiteEvent()
11518 char *bookHit = NULL;
11520 if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
11524 if (gameMode == PlayFromGameFile ||
11525 gameMode == TwoMachinesPlay ||
11526 gameMode == Training ||
11527 gameMode == AnalyzeMode ||
11528 gameMode == EndOfGame)
11531 if (gameMode == EditPosition)
11532 EditPositionDone(TRUE);
11534 if (!WhiteOnMove(currentMove)) {
11535 DisplayError(_("It is not White's turn"), 0);
11539 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
11542 if (gameMode == EditGame || gameMode == AnalyzeMode ||
11543 gameMode == AnalyzeFile)
11546 ResurrectChessProgram(); /* in case it isn't running */
11547 if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
11548 gameMode = MachinePlaysWhite;
11551 gameMode = MachinePlaysWhite;
11555 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
11557 if (first.sendName) {
11558 sprintf(buf, "name %s\n", gameInfo.black);
11559 SendToProgram(buf, &first);
11561 if (first.sendTime) {
11562 if (first.useColors) {
11563 SendToProgram("black\n", &first); /*gnu kludge*/
11565 SendTimeRemaining(&first, TRUE);
11567 if (first.useColors) {
11568 SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
11570 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
11571 SetMachineThinkingEnables();
11572 first.maybeThinking = TRUE;
11576 if (appData.autoFlipView && !flipView) {
11577 flipView = !flipView;
11578 DrawPosition(FALSE, NULL);
11579 DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;
11582 if(bookHit) { // [HGM] book: simulate book reply
11583 static char bookMove[MSG_SIZ]; // a bit generous?
11585 programStats.nodes = programStats.depth = programStats.time =
11586 programStats.score = programStats.got_only_move = 0;
11587 sprintf(programStats.movelist, "%s (xbook)", bookHit);
11589 strcpy(bookMove, "move ");
11590 strcat(bookMove, bookHit);
11591 HandleMachineMove(bookMove, &first);
11596 MachineBlackEvent()
11599 char *bookHit = NULL;
11601 if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
11605 if (gameMode == PlayFromGameFile
11606 || gameMode == TwoMachinesPlay
11607 || gameMode == Training
11608 || gameMode == AnalyzeMode
11609 || gameMode == EndOfGame)
11612 if (gameMode == EditPosition)
11613 EditPositionDone(TRUE);
11615 if (WhiteOnMove(currentMove))
11617 DisplayError(_("It is not Black's turn"), 0);
11621 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
11624 if (gameMode == EditGame || gameMode == AnalyzeMode
11625 || gameMode == AnalyzeFile)
11628 ResurrectChessProgram(); /* in case it isn't running */
11629 gameMode = MachinePlaysBlack;
11633 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
11635 if (first.sendName)
11637 sprintf(buf, "name %s\n", gameInfo.white);
11638 SendToProgram(buf, &first);
11640 if (first.sendTime)
11642 if (first.useColors)
11644 SendToProgram("white\n", &first); /*gnu kludge*/
11646 SendTimeRemaining(&first, FALSE);
11648 if (first.useColors)
11650 SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
11652 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
11653 SetMachineThinkingEnables();
11654 first.maybeThinking = TRUE;
11657 if (appData.autoFlipView && flipView)
11659 flipView = !flipView;
11660 DrawPosition(FALSE, NULL);
11661 DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;
11664 { // [HGM] book: simulate book reply
11665 static char bookMove[MSG_SIZ]; // a bit generous?
11667 programStats.nodes = programStats.depth = programStats.time
11668 = programStats.score = programStats.got_only_move = 0;
11669 sprintf(programStats.movelist, "%s (xbook)", bookHit);
11671 strcpy(bookMove, "move ");
11672 strcat(bookMove, bookHit);
11673 HandleMachineMove(bookMove, &first);
11680 DisplayTwoMachinesTitle()
11683 if (appData.matchGames > 0) {
11684 if (first.twoMachinesColor[0] == 'w') {
11685 sprintf(buf, "%s vs. %s (%d-%d-%d)",
11686 gameInfo.white, gameInfo.black,
11687 first.matchWins, second.matchWins,
11688 matchGame - 1 - (first.matchWins + second.matchWins));
11690 sprintf(buf, "%s vs. %s (%d-%d-%d)",
11691 gameInfo.white, gameInfo.black,
11692 second.matchWins, first.matchWins,
11693 matchGame - 1 - (first.matchWins + second.matchWins));
11696 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
11702 TwoMachinesEvent P((void))
11706 ChessProgramState *onmove;
11707 char *bookHit = NULL;
11709 if (appData.noChessProgram) return;
11711 switch (gameMode) {
11712 case TwoMachinesPlay:
11714 case MachinePlaysWhite:
11715 case MachinePlaysBlack:
11716 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
11717 DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
11721 case BeginningOfGame:
11722 case PlayFromGameFile:
11725 if (gameMode != EditGame) return;
11728 EditPositionDone(TRUE);
11739 // forwardMostMove = currentMove;
11740 TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
11741 ResurrectChessProgram(); /* in case first program isn't running */
11743 if (second.pr == NULL) {
11744 StartChessProgram(&second);
11745 if (second.protocolVersion == 1) {
11746 TwoMachinesEventIfReady();
11748 /* kludge: allow timeout for initial "feature" command */
11750 DisplayMessage("", _("Starting second chess program"));
11751 ScheduleDelayedEvent(TwoMachinesEventIfReady, FEATURE_TIMEOUT);
11755 DisplayMessage("", "");
11756 InitChessProgram(&second, FALSE);
11757 SendToProgram("force\n", &second);
11758 if (startedFromSetupPosition) {
11759 SendBoard(&second, backwardMostMove);
11760 if (appData.debugMode) {
11761 fprintf(debugFP, "Two Machines\n");
11764 for (i = backwardMostMove; i < forwardMostMove; i++) {
11765 SendMoveToProgram(i, &second);
11768 gameMode = TwoMachinesPlay;
11772 DisplayTwoMachinesTitle();
11774 if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
11780 SendToProgram(first.computerString, &first);
11781 if (first.sendName) {
11782 sprintf(buf, "name %s\n", second.tidy);
11783 SendToProgram(buf, &first);
11785 SendToProgram(second.computerString, &second);
11786 if (second.sendName) {
11787 sprintf(buf, "name %s\n", first.tidy);
11788 SendToProgram(buf, &second);
11792 if (!first.sendTime || !second.sendTime) {
11793 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11794 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11796 if (onmove->sendTime) {
11797 if (onmove->useColors) {
11798 SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
11800 SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
11802 if (onmove->useColors) {
11803 SendToProgram(onmove->twoMachinesColor, onmove);
11805 bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
11806 // SendToProgram("go\n", onmove);
11807 onmove->maybeThinking = TRUE;
11808 SetMachineThinkingEnables();
11812 if(bookHit) { // [HGM] book: simulate book reply
11813 static char bookMove[MSG_SIZ]; // a bit generous?
11815 programStats.nodes = programStats.depth = programStats.time =
11816 programStats.score = programStats.got_only_move = 0;
11817 sprintf(programStats.movelist, "%s (xbook)", bookHit);
11819 strcpy(bookMove, "move ");
11820 strcat(bookMove, bookHit);
11821 savedMessage = bookMove; // args for deferred call
11822 savedState = onmove;
11823 ScheduleDelayedEvent(DeferredBookMove, 1);
11830 if (gameMode == Training) {
11831 SetTrainingModeOff();
11832 gameMode = PlayFromGameFile;
11833 DisplayMessage("", _("Training mode off"));
11835 gameMode = Training;
11836 animateTraining = appData.animate;
11838 /* make sure we are not already at the end of the game */
11839 if (currentMove < forwardMostMove) {
11840 SetTrainingModeOn();
11841 DisplayMessage("", _("Training mode on"));
11843 gameMode = PlayFromGameFile;
11844 DisplayError(_("Already at end of game"), 0);
11853 if (!appData.icsActive) return;
11854 switch (gameMode) {
11855 case IcsPlayingWhite:
11856 case IcsPlayingBlack:
11859 case BeginningOfGame:
11867 EditPositionDone(TRUE);
11880 gameMode = IcsIdle;
11891 switch (gameMode) {
11893 SetTrainingModeOff();
11895 case MachinePlaysWhite:
11896 case MachinePlaysBlack:
11897 case BeginningOfGame:
11898 SendToProgram("force\n", &first);
11899 SetUserThinkingEnables();
11901 case PlayFromGameFile:
11902 (void) StopLoadGameTimer();
11903 if (gameFileFP != NULL) {
11908 EditPositionDone(TRUE);
11913 SendToProgram("force\n", &first);
11915 case TwoMachinesPlay:
11916 GameEnds((ChessMove) 0, NULL, GE_PLAYER);
11917 ResurrectChessProgram();
11918 SetUserThinkingEnables();
11921 ResurrectChessProgram();
11923 case IcsPlayingBlack:
11924 case IcsPlayingWhite:
11925 DisplayError(_("Warning: You are still playing a game"), 0);
11928 DisplayError(_("Warning: You are still observing a game"), 0);
11931 DisplayError(_("Warning: You are still examining a game"), 0);
11942 first.offeredDraw = second.offeredDraw = 0;
11944 if (gameMode == PlayFromGameFile) {
11945 whiteTimeRemaining = timeRemaining[0][currentMove];
11946 blackTimeRemaining = timeRemaining[1][currentMove];
11950 if (gameMode == MachinePlaysWhite ||
11951 gameMode == MachinePlaysBlack ||
11952 gameMode == TwoMachinesPlay ||
11953 gameMode == EndOfGame) {
11954 i = forwardMostMove;
11955 while (i > currentMove) {
11956 SendToProgram("undo\n", &first);
11959 whiteTimeRemaining = timeRemaining[0][currentMove];
11960 blackTimeRemaining = timeRemaining[1][currentMove];
11961 DisplayBothClocks();
11962 if (whiteFlag || blackFlag) {
11963 whiteFlag = blackFlag = 0;
11968 gameMode = EditGame;
11975 EditPositionEvent()
11977 if (gameMode == EditPosition) {
11983 if (gameMode != EditGame) return;
11985 gameMode = EditPosition;
11988 if (currentMove > 0)
11989 CopyBoard(boards[0], boards[currentMove]);
11991 blackPlaysFirst = !WhiteOnMove(currentMove);
11993 currentMove = forwardMostMove = backwardMostMove = 0;
11994 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12001 /* [DM] icsEngineAnalyze - possible call from other functions */
12002 if (appData.icsEngineAnalyze) {
12003 appData.icsEngineAnalyze = FALSE;
12005 DisplayMessage("",_("Close ICS engine analyze..."));
12007 if (first.analysisSupport && first.analyzing) {
12008 SendToProgram("exit\n", &first);
12009 first.analyzing = FALSE;
12011 thinkOutput[0] = NULLCHAR;
12015 EditPositionDone(Boolean fakeRights)
12017 int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
12019 startedFromSetupPosition = TRUE;
12020 InitChessProgram(&first, FALSE);
12021 if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
12022 boards[0][EP_STATUS] = EP_NONE;
12023 boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
12024 if(boards[0][0][BOARD_WIDTH>>1] == king) {
12025 boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : NoRights;
12026 boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
12027 } else boards[0][CASTLING][2] = NoRights;
12028 if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
12029 boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : NoRights;
12030 boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
12031 } else boards[0][CASTLING][5] = NoRights;
12033 SendToProgram("force\n", &first);
12034 if (blackPlaysFirst) {
12035 strcpy(moveList[0], "");
12036 strcpy(parseList[0], "");
12037 currentMove = forwardMostMove = backwardMostMove = 1;
12038 CopyBoard(boards[1], boards[0]);
12040 currentMove = forwardMostMove = backwardMostMove = 0;
12042 SendBoard(&first, forwardMostMove);
12043 if (appData.debugMode) {
12044 fprintf(debugFP, "EditPosDone\n");
12047 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12048 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12049 gameMode = EditGame;
12051 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12052 ClearHighlights(); /* [AS] */
12055 /* Pause for `ms' milliseconds */
12056 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
12066 } while (SubtractTimeMarks(&m2, &m1) < ms);
12069 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
12071 SendMultiLineToICS(buf)
12074 char temp[MSG_SIZ+1], *p;
12081 strncpy(temp, buf, len);
12086 if (*p == '\n' || *p == '\r')
12091 strcat(temp, "\n");
12093 SendToPlayer(temp, strlen(temp));
12097 SetWhiteToPlayEvent()
12099 if (gameMode == EditPosition) {
12100 blackPlaysFirst = FALSE;
12101 DisplayBothClocks(); /* works because currentMove is 0 */
12102 } else if (gameMode == IcsExamining) {
12103 SendToICS(ics_prefix);
12104 SendToICS("tomove white\n");
12109 SetBlackToPlayEvent()
12111 if (gameMode == EditPosition) {
12112 blackPlaysFirst = TRUE;
12113 currentMove = 1; /* kludge */
12114 DisplayBothClocks();
12116 } else if (gameMode == IcsExamining) {
12117 SendToICS(ics_prefix);
12118 SendToICS("tomove black\n");
12123 EditPositionMenuEvent(selection, x, y)
12124 ChessSquare selection;
12128 ChessSquare piece = boards[0][y][x];
12130 if (gameMode != EditPosition && gameMode != IcsExamining) return;
12132 switch (selection) {
12134 if (gameMode == IcsExamining && ics_type == ICS_FICS) {
12135 SendToICS(ics_prefix);
12136 SendToICS("bsetup clear\n");
12137 } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
12138 SendToICS(ics_prefix);
12139 SendToICS("clearboard\n");
12141 for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
12142 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
12143 for (y = 0; y < BOARD_HEIGHT; y++) {
12144 if (gameMode == IcsExamining) {
12145 if (boards[currentMove][y][x] != EmptySquare) {
12146 sprintf(buf, "%sx@%c%c\n", ics_prefix,
12151 boards[0][y][x] = p;
12156 if (gameMode == EditPosition) {
12157 DrawPosition(FALSE, boards[0]);
12162 SetWhiteToPlayEvent();
12166 SetBlackToPlayEvent();
12170 if (gameMode == IcsExamining) {
12171 if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
12172 sprintf(buf, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
12175 if(x < BOARD_LEFT || x >= BOARD_RGHT) {
12176 if(x == BOARD_LEFT-2) {
12177 if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
12178 boards[0][y][1] = 0;
12180 if(x == BOARD_RGHT+1) {
12181 if(y >= gameInfo.holdingsSize) break;
12182 boards[0][y][BOARD_WIDTH-2] = 0;
12185 boards[0][y][x] = EmptySquare;
12186 DrawPosition(FALSE, boards[0]);
12191 if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
12192 piece >= (int)BlackPawn && piece < (int)BlackMan ) {
12193 selection = (ChessSquare) (PROMOTED piece);
12194 } else if(piece == EmptySquare) selection = WhiteSilver;
12195 else selection = (ChessSquare)((int)piece - 1);
12199 if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
12200 piece > (int)BlackMan && piece <= (int)BlackKing ) {
12201 selection = (ChessSquare) (DEMOTED piece);
12202 } else if(piece == EmptySquare) selection = BlackSilver;
12203 else selection = (ChessSquare)((int)piece + 1);
12208 if(gameInfo.variant == VariantShatranj ||
12209 gameInfo.variant == VariantXiangqi ||
12210 gameInfo.variant == VariantCourier ||
12211 gameInfo.variant == VariantMakruk )
12212 selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
12217 if(gameInfo.variant == VariantXiangqi)
12218 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
12219 if(gameInfo.variant == VariantKnightmate)
12220 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
12223 if (gameMode == IcsExamining) {
12224 if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
12225 sprintf(buf, "%s%c@%c%c\n", ics_prefix,
12226 PieceToChar(selection), AAA + x, ONE + y);
12229 if(x < BOARD_LEFT || x >= BOARD_RGHT) {
12231 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
12232 n = PieceToNumber(selection - BlackPawn);
12233 if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
12234 boards[0][BOARD_HEIGHT-1-n][0] = selection;
12235 boards[0][BOARD_HEIGHT-1-n][1]++;
12237 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
12238 n = PieceToNumber(selection);
12239 if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
12240 boards[0][n][BOARD_WIDTH-1] = selection;
12241 boards[0][n][BOARD_WIDTH-2]++;
12244 boards[0][y][x] = selection;
12245 DrawPosition(TRUE, boards[0]);
12253 DropMenuEvent(selection, x, y)
12254 ChessSquare selection;
12257 ChessMove moveType;
12259 switch (gameMode) {
12260 case IcsPlayingWhite:
12261 case MachinePlaysBlack:
12262 if (!WhiteOnMove(currentMove)) {
12263 DisplayMoveError(_("It is Black's turn"));
12266 moveType = WhiteDrop;
12268 case IcsPlayingBlack:
12269 case MachinePlaysWhite:
12270 if (WhiteOnMove(currentMove)) {
12271 DisplayMoveError(_("It is White's turn"));
12274 moveType = BlackDrop;
12277 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
12283 if (moveType == BlackDrop && selection < BlackPawn) {
12284 selection = (ChessSquare) ((int) selection
12285 + (int) BlackPawn - (int) WhitePawn);
12287 if (boards[currentMove][y][x] != EmptySquare) {
12288 DisplayMoveError(_("That square is occupied"));
12292 FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
12298 /* Accept a pending offer of any kind from opponent */
12300 if (appData.icsActive) {
12301 SendToICS(ics_prefix);
12302 SendToICS("accept\n");
12303 } else if (cmailMsgLoaded) {
12304 if (currentMove == cmailOldMove &&
12305 commentList[cmailOldMove] != NULL &&
12306 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12307 "Black offers a draw" : "White offers a draw")) {
12309 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
12310 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
12312 DisplayError(_("There is no pending offer on this move"), 0);
12313 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12316 /* Not used for offers from chess program */
12323 /* Decline a pending offer of any kind from opponent */
12325 if (appData.icsActive) {
12326 SendToICS(ics_prefix);
12327 SendToICS("decline\n");
12328 } else if (cmailMsgLoaded) {
12329 if (currentMove == cmailOldMove &&
12330 commentList[cmailOldMove] != NULL &&
12331 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12332 "Black offers a draw" : "White offers a draw")) {
12334 AppendComment(cmailOldMove, "Draw declined", TRUE);
12335 DisplayComment(cmailOldMove - 1, "Draw declined");
12338 DisplayError(_("There is no pending offer on this move"), 0);
12341 /* Not used for offers from chess program */
12348 /* Issue ICS rematch command */
12349 if (appData.icsActive) {
12350 SendToICS(ics_prefix);
12351 SendToICS("rematch\n");
12358 /* Call your opponent's flag (claim a win on time) */
12359 if (appData.icsActive) {
12360 SendToICS(ics_prefix);
12361 SendToICS("flag\n");
12363 switch (gameMode) {
12366 case MachinePlaysWhite:
12369 GameEnds(GameIsDrawn, "Both players ran out of time",
12372 GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
12374 DisplayError(_("Your opponent is not out of time"), 0);
12377 case MachinePlaysBlack:
12380 GameEnds(GameIsDrawn, "Both players ran out of time",
12383 GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
12385 DisplayError(_("Your opponent is not out of time"), 0);
12395 /* Offer draw or accept pending draw offer from opponent */
12397 if (appData.icsActive) {
12398 /* Note: tournament rules require draw offers to be
12399 made after you make your move but before you punch
12400 your clock. Currently ICS doesn't let you do that;
12401 instead, you immediately punch your clock after making
12402 a move, but you can offer a draw at any time. */
12404 SendToICS(ics_prefix);
12405 SendToICS("draw\n");
12406 userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
12407 } else if (cmailMsgLoaded) {
12408 if (currentMove == cmailOldMove &&
12409 commentList[cmailOldMove] != NULL &&
12410 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12411 "Black offers a draw" : "White offers a draw")) {
12412 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
12413 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
12414 } else if (currentMove == cmailOldMove + 1) {
12415 char *offer = WhiteOnMove(cmailOldMove) ?
12416 "White offers a draw" : "Black offers a draw";
12417 AppendComment(currentMove, offer, TRUE);
12418 DisplayComment(currentMove - 1, offer);
12419 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
12421 DisplayError(_("You must make your move before offering a draw"), 0);
12422 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12424 } else if (first.offeredDraw) {
12425 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
12427 if (first.sendDrawOffers) {
12428 SendToProgram("draw\n", &first);
12429 userOfferedDraw = TRUE;
12437 /* Offer Adjourn or accept pending Adjourn offer from opponent */
12439 if (appData.icsActive) {
12440 SendToICS(ics_prefix);
12441 SendToICS("adjourn\n");
12443 /* Currently GNU Chess doesn't offer or accept Adjourns */
12451 /* Offer Abort or accept pending Abort offer from opponent */
12453 if (appData.icsActive) {
12454 SendToICS(ics_prefix);
12455 SendToICS("abort\n");
12457 GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
12464 /* Resign. You can do this even if it's not your turn. */
12466 if (appData.icsActive) {
12467 SendToICS(ics_prefix);
12468 SendToICS("resign\n");
12470 switch (gameMode) {
12471 case MachinePlaysWhite:
12472 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12474 case MachinePlaysBlack:
12475 GameEnds(BlackWins, "White resigns", GE_PLAYER);
12478 if (cmailMsgLoaded) {
12480 if (WhiteOnMove(cmailOldMove)) {
12481 GameEnds(BlackWins, "White resigns", GE_PLAYER);
12483 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12485 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
12496 StopObservingEvent()
12498 /* Stop observing current games */
12499 SendToICS(ics_prefix);
12500 SendToICS("unobserve\n");
12504 StopExaminingEvent()
12506 /* Stop observing current game */
12507 SendToICS(ics_prefix);
12508 SendToICS("unexamine\n");
12512 ForwardInner(target)
12517 if (appData.debugMode)
12518 fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
12519 target, currentMove, forwardMostMove);
12521 if (gameMode == EditPosition)
12524 if (gameMode == PlayFromGameFile && !pausing)
12527 if (gameMode == IcsExamining && pausing)
12528 limit = pauseExamForwardMostMove;
12530 limit = forwardMostMove;
12532 if (target > limit) target = limit;
12534 if (target > 0 && moveList[target - 1][0]) {
12535 int fromX, fromY, toX, toY;
12536 toX = moveList[target - 1][2] - AAA;
12537 toY = moveList[target - 1][3] - ONE;
12538 if (moveList[target - 1][1] == '@') {
12539 if (appData.highlightLastMove) {
12540 SetHighlights(-1, -1, toX, toY);
12543 fromX = moveList[target - 1][0] - AAA;
12544 fromY = moveList[target - 1][1] - ONE;
12545 if (target == currentMove + 1) {
12546 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
12548 if (appData.highlightLastMove) {
12549 SetHighlights(fromX, fromY, toX, toY);
12553 if (gameMode == EditGame || gameMode == AnalyzeMode ||
12554 gameMode == Training || gameMode == PlayFromGameFile ||
12555 gameMode == AnalyzeFile) {
12556 while (currentMove < target) {
12557 SendMoveToProgram(currentMove++, &first);
12560 currentMove = target;
12563 if (gameMode == EditGame || gameMode == EndOfGame) {
12564 whiteTimeRemaining = timeRemaining[0][currentMove];
12565 blackTimeRemaining = timeRemaining[1][currentMove];
12567 DisplayBothClocks();
12568 DisplayMove(currentMove - 1);
12569 DrawPosition(FALSE, boards[currentMove]);
12570 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
12571 if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
12572 DisplayComment(currentMove - 1, commentList[currentMove]);
12580 if (gameMode == IcsExamining && !pausing) {
12581 SendToICS(ics_prefix);
12582 SendToICS("forward\n");
12584 ForwardInner(currentMove + 1);
12591 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12592 /* to optimze, we temporarily turn off analysis mode while we feed
12593 * the remaining moves to the engine. Otherwise we get analysis output
12596 if (first.analysisSupport) {
12597 SendToProgram("exit\nforce\n", &first);
12598 first.analyzing = FALSE;
12602 if (gameMode == IcsExamining && !pausing) {
12603 SendToICS(ics_prefix);
12604 SendToICS("forward 999999\n");
12606 ForwardInner(forwardMostMove);
12609 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12610 /* we have fed all the moves, so reactivate analysis mode */
12611 SendToProgram("analyze\n", &first);
12612 first.analyzing = TRUE;
12613 /*first.maybeThinking = TRUE;*/
12614 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12619 BackwardInner(target)
12622 int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
12624 if (appData.debugMode)
12625 fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
12626 target, currentMove, forwardMostMove);
12628 if (gameMode == EditPosition) return;
12629 if (currentMove <= backwardMostMove) {
12631 DrawPosition(full_redraw, boards[currentMove]);
12634 if (gameMode == PlayFromGameFile && !pausing)
12637 if (moveList[target][0]) {
12638 int fromX, fromY, toX, toY;
12639 toX = moveList[target][2] - AAA;
12640 toY = moveList[target][3] - ONE;
12641 if (moveList[target][1] == '@') {
12642 if (appData.highlightLastMove) {
12643 SetHighlights(-1, -1, toX, toY);
12646 fromX = moveList[target][0] - AAA;
12647 fromY = moveList[target][1] - ONE;
12648 if (target == currentMove - 1) {
12649 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
12651 if (appData.highlightLastMove) {
12652 SetHighlights(fromX, fromY, toX, toY);
12656 if (gameMode == EditGame || gameMode==AnalyzeMode ||
12657 gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
12658 while (currentMove > target) {
12659 SendToProgram("undo\n", &first);
12663 currentMove = target;
12666 if (gameMode == EditGame || gameMode == EndOfGame) {
12667 whiteTimeRemaining = timeRemaining[0][currentMove];
12668 blackTimeRemaining = timeRemaining[1][currentMove];
12670 DisplayBothClocks();
12671 DisplayMove(currentMove - 1);
12672 DrawPosition(full_redraw, boards[currentMove]);
12673 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
12674 // [HGM] PV info: routine tests if comment empty
12675 DisplayComment(currentMove - 1, commentList[currentMove]);
12681 if (gameMode == IcsExamining && !pausing) {
12682 SendToICS(ics_prefix);
12683 SendToICS("backward\n");
12685 BackwardInner(currentMove - 1);
12692 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12693 /* to optimize, we temporarily turn off analysis mode while we undo
12694 * all the moves. Otherwise we get analysis output after each undo.
12696 if (first.analysisSupport) {
12697 SendToProgram("exit\nforce\n", &first);
12698 first.analyzing = FALSE;
12702 if (gameMode == IcsExamining && !pausing) {
12703 SendToICS(ics_prefix);
12704 SendToICS("backward 999999\n");
12706 BackwardInner(backwardMostMove);
12709 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12710 /* we have fed all the moves, so reactivate analysis mode */
12711 SendToProgram("analyze\n", &first);
12712 first.analyzing = TRUE;
12713 /*first.maybeThinking = TRUE;*/
12714 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12721 if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
12722 if (to >= forwardMostMove) to = forwardMostMove;
12723 if (to <= backwardMostMove) to = backwardMostMove;
12724 if (to < currentMove) {
12734 if(PopTail(TRUE)) { // [HGM] vari: restore old game tail
12737 if (gameMode != IcsExamining) {
12738 DisplayError(_("You are not examining a game"), 0);
12742 DisplayError(_("You can't revert while pausing"), 0);
12745 SendToICS(ics_prefix);
12746 SendToICS("revert\n");
12752 switch (gameMode) {
12753 case MachinePlaysWhite:
12754 case MachinePlaysBlack:
12755 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
12756 DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
12759 if (forwardMostMove < 2) return;
12760 currentMove = forwardMostMove = forwardMostMove - 2;
12761 whiteTimeRemaining = timeRemaining[0][currentMove];
12762 blackTimeRemaining = timeRemaining[1][currentMove];
12763 DisplayBothClocks();
12764 DisplayMove(currentMove - 1);
12765 ClearHighlights();/*!! could figure this out*/
12766 DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
12767 SendToProgram("remove\n", &first);
12768 /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
12771 case BeginningOfGame:
12775 case IcsPlayingWhite:
12776 case IcsPlayingBlack:
12777 if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
12778 SendToICS(ics_prefix);
12779 SendToICS("takeback 2\n");
12781 SendToICS(ics_prefix);
12782 SendToICS("takeback 1\n");
12791 ChessProgramState *cps;
12793 switch (gameMode) {
12794 case MachinePlaysWhite:
12795 if (!WhiteOnMove(forwardMostMove)) {
12796 DisplayError(_("It is your turn"), 0);
12801 case MachinePlaysBlack:
12802 if (WhiteOnMove(forwardMostMove)) {
12803 DisplayError(_("It is your turn"), 0);
12808 case TwoMachinesPlay:
12809 if (WhiteOnMove(forwardMostMove) ==
12810 (first.twoMachinesColor[0] == 'w')) {
12816 case BeginningOfGame:
12820 SendToProgram("?\n", cps);
12824 TruncateGameEvent()
12827 if (gameMode != EditGame) return;
12834 CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
12835 if (forwardMostMove > currentMove) {
12836 if (gameInfo.resultDetails != NULL) {
12837 free(gameInfo.resultDetails);
12838 gameInfo.resultDetails = NULL;
12839 gameInfo.result = GameUnfinished;
12841 forwardMostMove = currentMove;
12842 HistorySet(parseList, backwardMostMove, forwardMostMove,
12850 if (appData.noChessProgram) return;
12851 switch (gameMode) {
12852 case MachinePlaysWhite:
12853 if (WhiteOnMove(forwardMostMove)) {
12854 DisplayError(_("Wait until your turn"), 0);
12858 case BeginningOfGame:
12859 case MachinePlaysBlack:
12860 if (!WhiteOnMove(forwardMostMove)) {
12861 DisplayError(_("Wait until your turn"), 0);
12866 DisplayError(_("No hint available"), 0);
12869 SendToProgram("hint\n", &first);
12870 hintRequested = TRUE;
12876 if (appData.noChessProgram) return;
12877 switch (gameMode) {
12878 case MachinePlaysWhite:
12879 if (WhiteOnMove(forwardMostMove)) {
12880 DisplayError(_("Wait until your turn"), 0);
12884 case BeginningOfGame:
12885 case MachinePlaysBlack:
12886 if (!WhiteOnMove(forwardMostMove)) {
12887 DisplayError(_("Wait until your turn"), 0);
12892 EditPositionDone(TRUE);
12894 case TwoMachinesPlay:
12899 SendToProgram("bk\n", &first);
12900 bookOutput[0] = NULLCHAR;
12901 bookRequested = TRUE;
12907 char *tags = PGNTags(&gameInfo);
12908 TagsPopUp(tags, CmailMsg());
12912 /* end button procedures */
12915 PrintPosition(fp, move)
12921 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
12922 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
12923 char c = PieceToChar(boards[move][i][j]);
12924 fputc(c == 'x' ? '.' : c, fp);
12925 fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
12928 if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
12929 fprintf(fp, "white to play\n");
12931 fprintf(fp, "black to play\n");
12938 if (gameInfo.white != NULL) {
12939 fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
12945 /* Find last component of program's own name, using some heuristics */
12947 TidyProgramName(prog, host, buf)
12948 char *prog, *host, buf[MSG_SIZ];
12951 int local = (strcmp(host, "localhost") == 0);
12952 while (!local && (p = strchr(prog, ';')) != NULL) {
12954 while (*p == ' ') p++;
12957 if (*prog == '"' || *prog == '\'') {
12958 q = strchr(prog + 1, *prog);
12960 q = strchr(prog, ' ');
12962 if (q == NULL) q = prog + strlen(prog);
12964 while (p >= prog && *p != '/' && *p != '\\') p--;
12966 if(p == prog && *p == '"') p++;
12967 if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
12968 memcpy(buf, p, q - p);
12969 buf[q - p] = NULLCHAR;
12977 TimeControlTagValue()
12980 if (!appData.clockMode) {
12982 } else if (movesPerSession > 0) {
12983 sprintf(buf, "%d/%ld", movesPerSession, timeControl/1000);
12984 } else if (timeIncrement == 0) {
12985 sprintf(buf, "%ld", timeControl/1000);
12987 sprintf(buf, "%ld+%ld", timeControl/1000, timeIncrement/1000);
12989 return StrSave(buf);
12995 /* This routine is used only for certain modes */
12996 VariantClass v = gameInfo.variant;
12997 ChessMove r = GameUnfinished;
13000 if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
13001 r = gameInfo.result;
13002 p = gameInfo.resultDetails;
13003 gameInfo.resultDetails = NULL;
13005 ClearGameInfo(&gameInfo);
13006 gameInfo.variant = v;
13008 switch (gameMode) {
13009 case MachinePlaysWhite:
13010 gameInfo.event = StrSave( appData.pgnEventHeader );
13011 gameInfo.site = StrSave(HostName());
13012 gameInfo.date = PGNDate();
13013 gameInfo.round = StrSave("-");
13014 gameInfo.white = StrSave(first.tidy);
13015 gameInfo.black = StrSave(UserName());
13016 gameInfo.timeControl = TimeControlTagValue();
13019 case MachinePlaysBlack:
13020 gameInfo.event = StrSave( appData.pgnEventHeader );
13021 gameInfo.site = StrSave(HostName());
13022 gameInfo.date = PGNDate();
13023 gameInfo.round = StrSave("-");
13024 gameInfo.white = StrSave(UserName());
13025 gameInfo.black = StrSave(first.tidy);
13026 gameInfo.timeControl = TimeControlTagValue();
13029 case TwoMachinesPlay:
13030 gameInfo.event = StrSave( appData.pgnEventHeader );
13031 gameInfo.site = StrSave(HostName());
13032 gameInfo.date = PGNDate();
13033 if (matchGame > 0) {
13035 sprintf(buf, "%d", matchGame);
13036 gameInfo.round = StrSave(buf);
13038 gameInfo.round = StrSave("-");
13040 if (first.twoMachinesColor[0] == 'w') {
13041 gameInfo.white = StrSave(first.tidy);
13042 gameInfo.black = StrSave(second.tidy);
13044 gameInfo.white = StrSave(second.tidy);
13045 gameInfo.black = StrSave(first.tidy);
13047 gameInfo.timeControl = TimeControlTagValue();
13051 gameInfo.event = StrSave("Edited game");
13052 gameInfo.site = StrSave(HostName());
13053 gameInfo.date = PGNDate();
13054 gameInfo.round = StrSave("-");
13055 gameInfo.white = StrSave("-");
13056 gameInfo.black = StrSave("-");
13057 gameInfo.result = r;
13058 gameInfo.resultDetails = p;
13062 gameInfo.event = StrSave("Edited position");
13063 gameInfo.site = StrSave(HostName());
13064 gameInfo.date = PGNDate();
13065 gameInfo.round = StrSave("-");
13066 gameInfo.white = StrSave("-");
13067 gameInfo.black = StrSave("-");
13070 case IcsPlayingWhite:
13071 case IcsPlayingBlack:
13076 case PlayFromGameFile:
13077 gameInfo.event = StrSave("Game from non-PGN file");
13078 gameInfo.site = StrSave(HostName());
13079 gameInfo.date = PGNDate();
13080 gameInfo.round = StrSave("-");
13081 gameInfo.white = StrSave("?");
13082 gameInfo.black = StrSave("?");
13091 ReplaceComment(index, text)
13097 while (*text == '\n') text++;
13098 len = strlen(text);
13099 while (len > 0 && text[len - 1] == '\n') len--;
13101 if (commentList[index] != NULL)
13102 free(commentList[index]);
13105 commentList[index] = NULL;
13108 if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
13109 *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
13110 *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
13111 commentList[index] = (char *) malloc(len + 2);
13112 strncpy(commentList[index], text, len);
13113 commentList[index][len] = '\n';
13114 commentList[index][len + 1] = NULLCHAR;
13116 // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
13118 commentList[index] = (char *) malloc(len + 6);
13119 strcpy(commentList[index], "{\n");
13120 strncpy(commentList[index]+2, text, len);
13121 commentList[index][len+2] = NULLCHAR;
13122 while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
13123 strcat(commentList[index], "\n}\n");
13137 if (ch == '\r') continue;
13139 } while (ch != '\0');
13143 AppendComment(index, text, addBraces)
13146 Boolean addBraces; // [HGM] braces: tells if we should add {}
13151 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
13152 text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
13155 while (*text == '\n') text++;
13156 len = strlen(text);
13157 while (len > 0 && text[len - 1] == '\n') len--;
13159 if (len == 0) return;
13161 if (commentList[index] != NULL) {
13162 old = commentList[index];
13163 oldlen = strlen(old);
13164 while(commentList[index][oldlen-1] == '\n')
13165 commentList[index][--oldlen] = NULLCHAR;
13166 commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
13167 strcpy(commentList[index], old);
13169 // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
13170 if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces)) {
13171 if(addBraces) addBraces = FALSE; else { text++; len--; }
13172 while (*text == '\n') { text++; len--; }
13173 commentList[index][--oldlen] = NULLCHAR;
13175 if(addBraces) strcat(commentList[index], "\n{\n");
13176 else strcat(commentList[index], "\n");
13177 strcat(commentList[index], text);
13178 if(addBraces) strcat(commentList[index], "\n}\n");
13179 else strcat(commentList[index], "\n");
13181 commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
13183 strcpy(commentList[index], "{\n");
13184 else commentList[index][0] = NULLCHAR;
13185 strcat(commentList[index], text);
13186 strcat(commentList[index], "\n");
13187 if(addBraces) strcat(commentList[index], "}\n");
13191 static char * FindStr( char * text, char * sub_text )
13193 char * result = strstr( text, sub_text );
13195 if( result != NULL ) {
13196 result += strlen( sub_text );
13202 /* [AS] Try to extract PV info from PGN comment */
13203 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
13204 char *GetInfoFromComment( int index, char * text )
13208 if( text != NULL && index > 0 ) {
13211 int time = -1, sec = 0, deci;
13212 char * s_eval = FindStr( text, "[%eval " );
13213 char * s_emt = FindStr( text, "[%emt " );
13215 if( s_eval != NULL || s_emt != NULL ) {
13219 if( s_eval != NULL ) {
13220 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
13224 if( delim != ']' ) {
13229 if( s_emt != NULL ) {
13234 /* We expect something like: [+|-]nnn.nn/dd */
13237 if(*text != '{') return text; // [HGM] braces: must be normal comment
13239 sep = strchr( text, '/' );
13240 if( sep == NULL || sep < (text+4) ) {
13244 time = -1; sec = -1; deci = -1;
13245 if( sscanf( text+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
13246 sscanf( text+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
13247 sscanf( text+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
13248 sscanf( text+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3 ) {
13252 if( score_lo < 0 || score_lo >= 100 ) {
13256 if(sec >= 0) time = 600*time + 10*sec; else
13257 if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
13259 score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
13261 /* [HGM] PV time: now locate end of PV info */
13262 while( *++sep >= '0' && *sep <= '9'); // strip depth
13264 while( *++sep >= '0' && *sep <= '9'); // strip time
13266 while( *++sep >= '0' && *sep <= '9'); // strip seconds
13268 while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
13269 while(*sep == ' ') sep++;
13280 pvInfoList[index-1].depth = depth;
13281 pvInfoList[index-1].score = score;
13282 pvInfoList[index-1].time = 10*time; // centi-sec
13283 if(*sep == '}') *sep = 0; else *--sep = '{';
13289 SendToProgram(message, cps)
13291 ChessProgramState *cps;
13293 int count, outCount, error;
13296 if (cps->pr == NULL) return;
13299 if (appData.debugMode) {
13302 fprintf(debugFP, "%ld >%-6s: %s",
13303 SubtractTimeMarks(&now, &programStartTime),
13304 cps->which, message);
13307 count = strlen(message);
13308 outCount = OutputToProcess(cps->pr, message, count, &error);
13309 if (outCount < count && !exiting
13310 && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
13311 sprintf(buf, _("Error writing to %s chess program"), cps->which);
13312 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
13313 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
13314 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
13315 sprintf(buf, "%s program exits in draw position (%s)", cps->which, cps->program);
13317 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
13319 gameInfo.resultDetails = StrSave(buf);
13321 if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
13326 ReceiveFromProgram(isr, closure, message, count, error)
13327 InputSourceRef isr;
13335 ChessProgramState *cps = (ChessProgramState *)closure;
13337 if (isr != cps->isr) return; /* Killed intentionally */
13341 _("Error: %s chess program (%s) exited unexpectedly"),
13342 cps->which, cps->program);
13343 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
13344 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
13345 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
13346 sprintf(buf, _("%s program exits in draw position (%s)"), cps->which, cps->program);
13348 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
13350 gameInfo.resultDetails = StrSave(buf);
13352 RemoveInputSource(cps->isr);
13353 if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
13356 _("Error reading from %s chess program (%s)"),
13357 cps->which, cps->program);
13358 RemoveInputSource(cps->isr);
13360 /* [AS] Program is misbehaving badly... kill it */
13361 if( count == -2 ) {
13362 DestroyChildProcess( cps->pr, 9 );
13366 if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
13371 if ((end_str = strchr(message, '\r')) != NULL)
13372 *end_str = NULLCHAR;
13373 if ((end_str = strchr(message, '\n')) != NULL)
13374 *end_str = NULLCHAR;
13376 if (appData.debugMode) {
13377 TimeMark now; int print = 1;
13378 char *quote = ""; char c; int i;
13380 if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
13381 char start = message[0];
13382 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
13383 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
13384 sscanf(message, "move %c", &c)!=1 && sscanf(message, "offer%c", &c)!=1 &&
13385 sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
13386 sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
13387 sscanf(message, "tell%c", &c)!=1 && sscanf(message, "0-1 %c", &c)!=1 &&
13388 sscanf(message, "1-0 %c", &c)!=1 && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
13389 sscanf(message, "pong %c", &c)!=1 && start != '#')
13390 { quote = "# "; print = (appData.engineComments == 2); }
13391 message[0] = start; // restore original message
13395 fprintf(debugFP, "%ld <%-6s: %s%s\n",
13396 SubtractTimeMarks(&now, &programStartTime), cps->which,
13402 /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
13403 if (appData.icsEngineAnalyze) {
13404 if (strstr(message, "whisper") != NULL ||
13405 strstr(message, "kibitz") != NULL ||
13406 strstr(message, "tellics") != NULL) return;
13409 HandleMachineMove(message, cps);
13414 SendTimeControl(cps, mps, tc, inc, sd, st)
13415 ChessProgramState *cps;
13416 int mps, inc, sd, st;
13422 if( timeControl_2 > 0 ) {
13423 if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
13424 tc = timeControl_2;
13427 tc /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
13428 inc /= cps->timeOdds;
13429 st /= cps->timeOdds;
13431 seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
13434 /* Set exact time per move, normally using st command */
13435 if (cps->stKludge) {
13436 /* GNU Chess 4 has no st command; uses level in a nonstandard way */
13438 if (seconds == 0) {
13439 sprintf(buf, "level 1 %d\n", st/60);
13441 sprintf(buf, "level 1 %d:%02d\n", st/60, seconds);
13444 sprintf(buf, "st %d\n", st);
13447 /* Set conventional or incremental time control, using level command */
13448 if (seconds == 0) {
13449 /* Note old gnuchess bug -- minutes:seconds used to not work.
13450 Fixed in later versions, but still avoid :seconds
13451 when seconds is 0. */
13452 sprintf(buf, "level %d %ld %d\n", mps, tc/60000, inc/1000);
13454 sprintf(buf, "level %d %ld:%02d %d\n", mps, tc/60000,
13455 seconds, inc/1000);
13458 SendToProgram(buf, cps);
13460 /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
13461 /* Orthogonally, limit search to given depth */
13463 if (cps->sdKludge) {
13464 sprintf(buf, "depth\n%d\n", sd);
13466 sprintf(buf, "sd %d\n", sd);
13468 SendToProgram(buf, cps);
13471 if(cps->nps > 0) { /* [HGM] nps */
13472 if(cps->supportsNPS == FALSE) cps->nps = -1; // don't use if engine explicitly says not supported!
13474 sprintf(buf, "nps %d\n", cps->nps);
13475 SendToProgram(buf, cps);
13480 ChessProgramState *WhitePlayer()
13481 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
13483 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
13484 gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
13490 SendTimeRemaining(cps, machineWhite)
13491 ChessProgramState *cps;
13492 int /*boolean*/ machineWhite;
13494 char message[MSG_SIZ];
13497 /* Note: this routine must be called when the clocks are stopped
13498 or when they have *just* been set or switched; otherwise
13499 it will be off by the time since the current tick started.
13501 if (machineWhite) {
13502 time = whiteTimeRemaining / 10;
13503 otime = blackTimeRemaining / 10;
13505 time = blackTimeRemaining / 10;
13506 otime = whiteTimeRemaining / 10;
13508 /* [HGM] translate opponent's time by time-odds factor */
13509 otime = (otime * cps->other->timeOdds) / cps->timeOdds;
13510 if (appData.debugMode) {
13511 fprintf(debugFP, "time odds: %f %f \n", cps->timeOdds, cps->other->timeOdds);
13514 if (time <= 0) time = 1;
13515 if (otime <= 0) otime = 1;
13517 sprintf(message, "time %ld\n", time);
13518 SendToProgram(message, cps);
13520 sprintf(message, "otim %ld\n", otime);
13521 SendToProgram(message, cps);
13525 BoolFeature(p, name, loc, cps)
13529 ChessProgramState *cps;
13532 int len = strlen(name);
13534 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
13536 sscanf(*p, "%d", &val);
13538 while (**p && **p != ' ') (*p)++;
13539 sprintf(buf, "accepted %s\n", name);
13540 SendToProgram(buf, cps);
13547 IntFeature(p, name, loc, cps)
13551 ChessProgramState *cps;
13554 int len = strlen(name);
13555 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
13557 sscanf(*p, "%d", loc);
13558 while (**p && **p != ' ') (*p)++;
13559 sprintf(buf, "accepted %s\n", name);
13560 SendToProgram(buf, cps);
13567 StringFeature(p, name, loc, cps)
13571 ChessProgramState *cps;
13574 int len = strlen(name);
13575 if (strncmp((*p), name, len) == 0
13576 && (*p)[len] == '=' && (*p)[len+1] == '\"') {
13578 sscanf(*p, "%[^\"]", loc);
13579 while (**p && **p != '\"') (*p)++;
13580 if (**p == '\"') (*p)++;
13581 sprintf(buf, "accepted %s\n", name);
13582 SendToProgram(buf, cps);
13589 ParseOption(Option *opt, ChessProgramState *cps)
13590 // [HGM] options: process the string that defines an engine option, and determine
13591 // name, type, default value, and allowed value range
13593 char *p, *q, buf[MSG_SIZ];
13594 int n, min = (-1)<<31, max = 1<<31, def;
13596 if(p = strstr(opt->name, " -spin ")) {
13597 if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
13598 if(max < min) max = min; // enforce consistency
13599 if(def < min) def = min;
13600 if(def > max) def = max;
13605 } else if((p = strstr(opt->name, " -slider "))) {
13606 // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
13607 if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
13608 if(max < min) max = min; // enforce consistency
13609 if(def < min) def = min;
13610 if(def > max) def = max;
13614 opt->type = Spin; // Slider;
13615 } else if((p = strstr(opt->name, " -string "))) {
13616 opt->textValue = p+9;
13617 opt->type = TextBox;
13618 } else if((p = strstr(opt->name, " -file "))) {
13619 // for now -file is a synonym for -string, to already provide compatibility with future polyglots
13620 opt->textValue = p+7;
13621 opt->type = TextBox; // FileName;
13622 } else if((p = strstr(opt->name, " -path "))) {
13623 // for now -file is a synonym for -string, to already provide compatibility with future polyglots
13624 opt->textValue = p+7;
13625 opt->type = TextBox; // PathName;
13626 } else if(p = strstr(opt->name, " -check ")) {
13627 if(sscanf(p, " -check %d", &def) < 1) return FALSE;
13628 opt->value = (def != 0);
13629 opt->type = CheckBox;
13630 } else if(p = strstr(opt->name, " -combo ")) {
13631 opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
13632 cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
13633 if(*q == '*') cps->comboList[cps->comboCnt-1]++;
13634 opt->value = n = 0;
13635 while(q = StrStr(q, " /// ")) {
13636 n++; *q = 0; // count choices, and null-terminate each of them
13638 if(*q == '*') { // remember default, which is marked with * prefix
13642 cps->comboList[cps->comboCnt++] = q;
13644 cps->comboList[cps->comboCnt++] = NULL;
13646 opt->type = ComboBox;
13647 } else if(p = strstr(opt->name, " -button")) {
13648 opt->type = Button;
13649 } else if(p = strstr(opt->name, " -save")) {
13650 opt->type = SaveButton;
13651 } else return FALSE;
13652 *p = 0; // terminate option name
13653 // now look if the command-line options define a setting for this engine option.
13654 if(cps->optionSettings && cps->optionSettings[0])
13655 p = strstr(cps->optionSettings, opt->name); else p = NULL;
13656 if(p && (p == cps->optionSettings || p[-1] == ',')) {
13657 sprintf(buf, "option %s", p);
13658 if(p = strstr(buf, ",")) *p = 0;
13660 SendToProgram(buf, cps);
13666 FeatureDone(cps, val)
13667 ChessProgramState* cps;
13670 DelayedEventCallback cb = GetDelayedEvent();
13671 if ((cb == InitBackEnd3 && cps == &first) ||
13672 (cb == TwoMachinesEventIfReady && cps == &second)) {
13673 CancelDelayedEvent();
13674 ScheduleDelayedEvent(cb, val ? 1 : 3600000);
13676 cps->initDone = val;
13679 /* Parse feature command from engine */
13681 ParseFeatures(args, cps)
13683 ChessProgramState *cps;
13691 while (*p == ' ') p++;
13692 if (*p == NULLCHAR) return;
13694 if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
13695 if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
13696 if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
13697 if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
13698 if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
13699 if (BoolFeature(&p, "reuse", &val, cps)) {
13700 /* Engine can disable reuse, but can't enable it if user said no */
13701 if (!val) cps->reuse = FALSE;
13704 if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
13705 if (StringFeature(&p, "myname", &cps->tidy, cps)) {
13706 if (gameMode == TwoMachinesPlay) {
13707 DisplayTwoMachinesTitle();
13713 if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
13714 if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
13715 if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
13716 if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
13717 if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
13718 if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
13719 if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
13720 if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
13721 if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
13722 if (IntFeature(&p, "done", &val, cps)) {
13723 FeatureDone(cps, val);
13726 /* Added by Tord: */
13727 if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
13728 if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
13729 /* End of additions by Tord */
13731 /* [HGM] added features: */
13732 if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
13733 if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
13734 if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
13735 if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
13736 if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
13737 if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
13738 if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
13739 if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
13740 sprintf(buf, "rejected option %s\n", cps->option[--cps->nrOptions].name);
13741 SendToProgram(buf, cps);
13744 if(cps->nrOptions >= MAX_OPTIONS) {
13746 sprintf(buf, "%s engine has too many options\n", cps->which);
13747 DisplayError(buf, 0);
13751 if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
13752 /* End of additions by HGM */
13754 /* unknown feature: complain and skip */
13756 while (*q && *q != '=') q++;
13757 sprintf(buf, "rejected %.*s\n", (int)(q-p), p);
13758 SendToProgram(buf, cps);
13764 while (*p && *p != '\"') p++;
13765 if (*p == '\"') p++;
13767 while (*p && *p != ' ') p++;
13775 PeriodicUpdatesEvent(newState)
13778 if (newState == appData.periodicUpdates)
13781 appData.periodicUpdates=newState;
13783 /* Display type changes, so update it now */
13784 // DisplayAnalysis();
13786 /* Get the ball rolling again... */
13788 AnalysisPeriodicEvent(1);
13789 StartAnalysisClock();
13794 PonderNextMoveEvent(newState)
13797 if (newState == appData.ponderNextMove) return;
13798 if (gameMode == EditPosition) EditPositionDone(TRUE);
13800 SendToProgram("hard\n", &first);
13801 if (gameMode == TwoMachinesPlay) {
13802 SendToProgram("hard\n", &second);
13805 SendToProgram("easy\n", &first);
13806 thinkOutput[0] = NULLCHAR;
13807 if (gameMode == TwoMachinesPlay) {
13808 SendToProgram("easy\n", &second);
13811 appData.ponderNextMove = newState;
13815 NewSettingEvent(option, command, value)
13821 if (gameMode == EditPosition) EditPositionDone(TRUE);
13822 sprintf(buf, "%s%s %d\n", (option ? "option ": ""), command, value);
13823 SendToProgram(buf, &first);
13824 if (gameMode == TwoMachinesPlay) {
13825 SendToProgram(buf, &second);
13830 ShowThinkingEvent()
13831 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
13833 static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
13834 int newState = appData.showThinking
13835 // [HGM] thinking: other features now need thinking output as well
13836 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
13838 if (oldState == newState) return;
13839 oldState = newState;
13840 if (gameMode == EditPosition) EditPositionDone(TRUE);
13842 SendToProgram("post\n", &first);
13843 if (gameMode == TwoMachinesPlay) {
13844 SendToProgram("post\n", &second);
13847 SendToProgram("nopost\n", &first);
13848 thinkOutput[0] = NULLCHAR;
13849 if (gameMode == TwoMachinesPlay) {
13850 SendToProgram("nopost\n", &second);
13853 // appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
13857 AskQuestionEvent(title, question, replyPrefix, which)
13858 char *title; char *question; char *replyPrefix; char *which;
13860 ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
13861 if (pr == NoProc) return;
13862 AskQuestion(title, question, replyPrefix, pr);
13866 DisplayMove(moveNumber)
13869 char message[MSG_SIZ];
13871 char cpThinkOutput[MSG_SIZ];
13873 if(appData.noGUI) return; // [HGM] fast: suppress display of moves
13875 if (moveNumber == forwardMostMove - 1 ||
13876 gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13878 safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput));
13880 if (strchr(cpThinkOutput, '\n')) {
13881 *strchr(cpThinkOutput, '\n') = NULLCHAR;
13884 *cpThinkOutput = NULLCHAR;
13887 /* [AS] Hide thinking from human user */
13888 if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
13889 *cpThinkOutput = NULLCHAR;
13890 if( thinkOutput[0] != NULLCHAR ) {
13893 for( i=0; i<=hiddenThinkOutputState; i++ ) {
13894 cpThinkOutput[i] = '.';
13896 cpThinkOutput[i] = NULLCHAR;
13897 hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
13901 if (moveNumber == forwardMostMove - 1 &&
13902 gameInfo.resultDetails != NULL) {
13903 if (gameInfo.resultDetails[0] == NULLCHAR) {
13904 sprintf(res, " %s", PGNResult(gameInfo.result));
13906 sprintf(res, " {%s} %s",
13907 gameInfo.resultDetails, PGNResult(gameInfo.result));
13913 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
13914 DisplayMessage(res, cpThinkOutput);
13916 sprintf(message, "%d.%s%s%s", moveNumber / 2 + 1,
13917 WhiteOnMove(moveNumber) ? " " : ".. ",
13918 parseList[moveNumber], res);
13919 DisplayMessage(message, cpThinkOutput);
13924 DisplayComment(moveNumber, text)
13928 char title[MSG_SIZ];
13929 char buf[8000]; // comment can be long!
13931 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
13932 strcpy(title, "Comment");
13934 sprintf(title, "Comment on %d.%s%s", moveNumber / 2 + 1,
13935 WhiteOnMove(moveNumber) ? " " : ".. ",
13936 parseList[moveNumber]);
13938 // [HGM] PV info: display PV info together with (or as) comment
13939 if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {
13940 if(text == NULL) text = "";
13941 score = pvInfoList[moveNumber].score;
13942 sprintf(buf, "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,
13943 depth, (pvInfoList[moveNumber].time+50)/100, text);
13946 if (text != NULL && (appData.autoDisplayComment || commentUp))
13947 CommentPopUp(title, text);
13950 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
13951 * might be busy thinking or pondering. It can be omitted if your
13952 * gnuchess is configured to stop thinking immediately on any user
13953 * input. However, that gnuchess feature depends on the FIONREAD
13954 * ioctl, which does not work properly on some flavors of Unix.
13958 ChessProgramState *cps;
13961 if (!cps->useSigint) return;
13962 if (appData.noChessProgram || (cps->pr == NoProc)) return;
13963 switch (gameMode) {
13964 case MachinePlaysWhite:
13965 case MachinePlaysBlack:
13966 case TwoMachinesPlay:
13967 case IcsPlayingWhite:
13968 case IcsPlayingBlack:
13971 /* Skip if we know it isn't thinking */
13972 if (!cps->maybeThinking) return;
13973 if (appData.debugMode)
13974 fprintf(debugFP, "Interrupting %s\n", cps->which);
13975 InterruptChildProcess(cps->pr);
13976 cps->maybeThinking = FALSE;
13981 #endif /*ATTENTION*/
13987 if (whiteTimeRemaining <= 0) {
13990 if (appData.icsActive) {
13991 if (appData.autoCallFlag &&
13992 gameMode == IcsPlayingBlack && !blackFlag) {
13993 SendToICS(ics_prefix);
13994 SendToICS("flag\n");
13998 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
14000 if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
14001 if (appData.autoCallFlag) {
14002 GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
14009 if (blackTimeRemaining <= 0) {
14012 if (appData.icsActive) {
14013 if (appData.autoCallFlag &&
14014 gameMode == IcsPlayingWhite && !whiteFlag) {
14015 SendToICS(ics_prefix);
14016 SendToICS("flag\n");
14020 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
14022 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
14023 if (appData.autoCallFlag) {
14024 GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
14037 if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
14038 gameMode == PlayFromGameFile || forwardMostMove == 0) return;
14041 * add time to clocks when time control is achieved ([HGM] now also used for increment)
14043 if ( !WhiteOnMove(forwardMostMove) )
14044 /* White made time control */
14045 whiteTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
14046 /* [HGM] time odds: correct new time quota for time odds! */
14047 / WhitePlayer()->timeOdds;
14049 /* Black made time control */
14050 blackTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
14051 / WhitePlayer()->other->timeOdds;
14055 DisplayBothClocks()
14057 int wom = gameMode == EditPosition ?
14058 !blackPlaysFirst : WhiteOnMove(currentMove);
14059 DisplayWhiteClock(whiteTimeRemaining, wom);
14060 DisplayBlackClock(blackTimeRemaining, !wom);
14064 /* Timekeeping seems to be a portability nightmare. I think everyone
14065 has ftime(), but I'm really not sure, so I'm including some ifdefs
14066 to use other calls if you don't. Clocks will be less accurate if
14067 you have neither ftime nor gettimeofday.
14070 /* VS 2008 requires the #include outside of the function */
14071 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
14072 #include <sys/timeb.h>
14075 /* Get the current time as a TimeMark */
14080 #if HAVE_GETTIMEOFDAY
14082 struct timeval timeVal;
14083 struct timezone timeZone;
14085 gettimeofday(&timeVal, &timeZone);
14086 tm->sec = (long) timeVal.tv_sec;
14087 tm->ms = (int) (timeVal.tv_usec / 1000L);
14089 #else /*!HAVE_GETTIMEOFDAY*/
14092 // include <sys/timeb.h> / moved to just above start of function
14093 struct timeb timeB;
14096 tm->sec = (long) timeB.time;
14097 tm->ms = (int) timeB.millitm;
14099 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
14100 tm->sec = (long) time(NULL);
14106 /* Return the difference in milliseconds between two
14107 time marks. We assume the difference will fit in a long!
14110 SubtractTimeMarks(tm2, tm1)
14111 TimeMark *tm2, *tm1;
14113 return 1000L*(tm2->sec - tm1->sec) +
14114 (long) (tm2->ms - tm1->ms);
14119 * Code to manage the game clocks.
14121 * In tournament play, black starts the clock and then white makes a move.
14122 * We give the human user a slight advantage if he is playing white---the
14123 * clocks don't run until he makes his first move, so it takes zero time.
14124 * Also, we don't account for network lag, so we could get out of sync
14125 * with GNU Chess's clock -- but then, referees are always right.
14128 static TimeMark tickStartTM;
14129 static long intendedTickLength;
14132 NextTickLength(timeRemaining)
14133 long timeRemaining;
14135 long nominalTickLength, nextTickLength;
14137 if (timeRemaining > 0L && timeRemaining <= 10000L)
14138 nominalTickLength = 100L;
14140 nominalTickLength = 1000L;
14141 nextTickLength = timeRemaining % nominalTickLength;
14142 if (nextTickLength <= 0) nextTickLength += nominalTickLength;
14144 return nextTickLength;
14147 /* Adjust clock one minute up or down */
14149 AdjustClock(Boolean which, int dir)
14151 if(which) blackTimeRemaining += 60000*dir;
14152 else whiteTimeRemaining += 60000*dir;
14153 DisplayBothClocks();
14156 /* Stop clocks and reset to a fresh time control */
14160 (void) StopClockTimer();
14161 if (appData.icsActive) {
14162 whiteTimeRemaining = blackTimeRemaining = 0;
14163 } else if (searchTime) {
14164 whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
14165 blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
14166 } else { /* [HGM] correct new time quote for time odds */
14167 whiteTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->timeOdds;
14168 blackTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->other->timeOdds;
14170 if (whiteFlag || blackFlag) {
14172 whiteFlag = blackFlag = FALSE;
14174 DisplayBothClocks();
14177 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
14179 /* Decrement running clock by amount of time that has passed */
14183 long timeRemaining;
14184 long lastTickLength, fudge;
14187 if (!appData.clockMode) return;
14188 if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
14192 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
14194 /* Fudge if we woke up a little too soon */
14195 fudge = intendedTickLength - lastTickLength;
14196 if (fudge < 0 || fudge > FUDGE) fudge = 0;
14198 if (WhiteOnMove(forwardMostMove)) {
14199 if(whiteNPS >= 0) lastTickLength = 0;
14200 timeRemaining = whiteTimeRemaining -= lastTickLength;
14201 DisplayWhiteClock(whiteTimeRemaining - fudge,
14202 WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
14204 if(blackNPS >= 0) lastTickLength = 0;
14205 timeRemaining = blackTimeRemaining -= lastTickLength;
14206 DisplayBlackClock(blackTimeRemaining - fudge,
14207 !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
14210 if (CheckFlags()) return;
14213 intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
14214 StartClockTimer(intendedTickLength);
14216 /* if the time remaining has fallen below the alarm threshold, sound the
14217 * alarm. if the alarm has sounded and (due to a takeback or time control
14218 * with increment) the time remaining has increased to a level above the
14219 * threshold, reset the alarm so it can sound again.
14222 if (appData.icsActive && appData.icsAlarm) {
14224 /* make sure we are dealing with the user's clock */
14225 if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
14226 ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
14229 if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
14230 alarmSounded = FALSE;
14231 } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
14233 alarmSounded = TRUE;
14239 /* A player has just moved, so stop the previously running
14240 clock and (if in clock mode) start the other one.
14241 We redisplay both clocks in case we're in ICS mode, because
14242 ICS gives us an update to both clocks after every move.
14243 Note that this routine is called *after* forwardMostMove
14244 is updated, so the last fractional tick must be subtracted
14245 from the color that is *not* on move now.
14250 long lastTickLength;
14252 int flagged = FALSE;
14256 if (StopClockTimer() && appData.clockMode) {
14257 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
14258 if (WhiteOnMove(forwardMostMove)) {
14259 if(blackNPS >= 0) lastTickLength = 0;
14260 blackTimeRemaining -= lastTickLength;
14261 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
14262 // if(pvInfoList[forwardMostMove-1].time == -1)
14263 pvInfoList[forwardMostMove-1].time = // use GUI time
14264 (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
14266 if(whiteNPS >= 0) lastTickLength = 0;
14267 whiteTimeRemaining -= lastTickLength;
14268 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
14269 // if(pvInfoList[forwardMostMove-1].time == -1)
14270 pvInfoList[forwardMostMove-1].time =
14271 (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
14273 flagged = CheckFlags();
14275 CheckTimeControl();
14277 if (flagged || !appData.clockMode) return;
14279 switch (gameMode) {
14280 case MachinePlaysBlack:
14281 case MachinePlaysWhite:
14282 case BeginningOfGame:
14283 if (pausing) return;
14287 case PlayFromGameFile:
14295 if (searchTime) { // [HGM] st: set clock of player that has to move to max time
14296 if(WhiteOnMove(forwardMostMove))
14297 whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
14298 else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
14302 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
14303 whiteTimeRemaining : blackTimeRemaining);
14304 StartClockTimer(intendedTickLength);
14308 /* Stop both clocks */
14312 long lastTickLength;
14315 if (!StopClockTimer()) return;
14316 if (!appData.clockMode) return;
14320 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
14321 if (WhiteOnMove(forwardMostMove)) {
14322 if(whiteNPS >= 0) lastTickLength = 0;
14323 whiteTimeRemaining -= lastTickLength;
14324 DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
14326 if(blackNPS >= 0) lastTickLength = 0;
14327 blackTimeRemaining -= lastTickLength;
14328 DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
14333 /* Start clock of player on move. Time may have been reset, so
14334 if clock is already running, stop and restart it. */
14338 (void) StopClockTimer(); /* in case it was running already */
14339 DisplayBothClocks();
14340 if (CheckFlags()) return;
14342 if (!appData.clockMode) return;
14343 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
14345 GetTimeMark(&tickStartTM);
14346 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
14347 whiteTimeRemaining : blackTimeRemaining);
14349 /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
14350 whiteNPS = blackNPS = -1;
14351 if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
14352 || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
14353 whiteNPS = first.nps;
14354 if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
14355 || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
14356 blackNPS = first.nps;
14357 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
14358 whiteNPS = second.nps;
14359 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
14360 blackNPS = second.nps;
14361 if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
14363 StartClockTimer(intendedTickLength);
14370 long second, minute, hour, day;
14372 static char buf[32];
14374 if (ms > 0 && ms <= 9900) {
14375 /* convert milliseconds to tenths, rounding up */
14376 double tenths = floor( ((double)(ms + 99L)) / 100.00 );
14378 sprintf(buf, " %03.1f ", tenths/10.0);
14382 /* convert milliseconds to seconds, rounding up */
14383 /* use floating point to avoid strangeness of integer division
14384 with negative dividends on many machines */
14385 second = (long) floor(((double) (ms + 999L)) / 1000.0);
14392 day = second / (60 * 60 * 24);
14393 second = second % (60 * 60 * 24);
14394 hour = second / (60 * 60);
14395 second = second % (60 * 60);
14396 minute = second / 60;
14397 second = second % 60;
14400 sprintf(buf, " %s%ld:%02ld:%02ld:%02ld ",
14401 sign, day, hour, minute, second);
14403 sprintf(buf, " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
14405 sprintf(buf, " %s%2ld:%02ld ", sign, minute, second);
14412 * This is necessary because some C libraries aren't ANSI C compliant yet.
14415 StrStr(string, match)
14416 char *string, *match;
14420 length = strlen(match);
14422 for (i = strlen(string) - length; i >= 0; i--, string++)
14423 if (!strncmp(match, string, length))
14430 StrCaseStr(string, match)
14431 char *string, *match;
14435 length = strlen(match);
14437 for (i = strlen(string) - length; i >= 0; i--, string++) {
14438 for (j = 0; j < length; j++) {
14439 if (ToLower(match[j]) != ToLower(string[j]))
14442 if (j == length) return string;
14456 c1 = ToLower(*s1++);
14457 c2 = ToLower(*s2++);
14458 if (c1 > c2) return 1;
14459 if (c1 < c2) return -1;
14460 if (c1 == NULLCHAR) return 0;
14469 return isupper(c) ? tolower(c) : c;
14477 return islower(c) ? toupper(c) : c;
14479 #endif /* !_amigados */
14487 if ((ret = (char *) malloc(strlen(s) + 1))) {
14494 StrSavePtr(s, savePtr)
14495 char *s, **savePtr;
14500 if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
14501 strcpy(*savePtr, s);
14513 clock = time((time_t *)NULL);
14514 tm = localtime(&clock);
14515 sprintf(buf, "%04d.%02d.%02d",
14516 tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
14517 return StrSave(buf);
14522 PositionToFEN(move, overrideCastling)
14524 char *overrideCastling;
14526 int i, j, fromX, fromY, toX, toY;
14533 whiteToPlay = (gameMode == EditPosition) ?
14534 !blackPlaysFirst : (move % 2 == 0);
14537 /* Piece placement data */
14538 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
14540 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
14541 if (boards[move][i][j] == EmptySquare) {
14543 } else { ChessSquare piece = boards[move][i][j];
14544 if (emptycount > 0) {
14545 if(emptycount<10) /* [HGM] can be >= 10 */
14546 *p++ = '0' + emptycount;
14547 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
14550 if(PieceToChar(piece) == '+') {
14551 /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
14553 piece = (ChessSquare)(DEMOTED piece);
14555 *p++ = PieceToChar(piece);
14557 /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
14558 p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
14563 if (emptycount > 0) {
14564 if(emptycount<10) /* [HGM] can be >= 10 */
14565 *p++ = '0' + emptycount;
14566 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
14573 /* [HGM] print Crazyhouse or Shogi holdings */
14574 if( gameInfo.holdingsWidth ) {
14575 *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
14577 for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
14578 piece = boards[move][i][BOARD_WIDTH-1];
14579 if( piece != EmptySquare )
14580 for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
14581 *p++ = PieceToChar(piece);
14583 for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
14584 piece = boards[move][BOARD_HEIGHT-i-1][0];
14585 if( piece != EmptySquare )
14586 for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
14587 *p++ = PieceToChar(piece);
14590 if( q == p ) *p++ = '-';
14596 *p++ = whiteToPlay ? 'w' : 'b';
14599 if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
14600 while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
14602 if(nrCastlingRights) {
14604 if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
14605 /* [HGM] write directly from rights */
14606 if(boards[move][CASTLING][2] != NoRights &&
14607 boards[move][CASTLING][0] != NoRights )
14608 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
14609 if(boards[move][CASTLING][2] != NoRights &&
14610 boards[move][CASTLING][1] != NoRights )
14611 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
14612 if(boards[move][CASTLING][5] != NoRights &&
14613 boards[move][CASTLING][3] != NoRights )
14614 *p++ = boards[move][CASTLING][3] + AAA;
14615 if(boards[move][CASTLING][5] != NoRights &&
14616 boards[move][CASTLING][4] != NoRights )
14617 *p++ = boards[move][CASTLING][4] + AAA;
14620 /* [HGM] write true castling rights */
14621 if( nrCastlingRights == 6 ) {
14622 if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
14623 boards[move][CASTLING][2] != NoRights ) *p++ = 'K';
14624 if(boards[move][CASTLING][1] == BOARD_LEFT &&
14625 boards[move][CASTLING][2] != NoRights ) *p++ = 'Q';
14626 if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
14627 boards[move][CASTLING][5] != NoRights ) *p++ = 'k';
14628 if(boards[move][CASTLING][4] == BOARD_LEFT &&
14629 boards[move][CASTLING][5] != NoRights ) *p++ = 'q';
14632 if (q == p) *p++ = '-'; /* No castling rights */
14636 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
14637 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
14638 /* En passant target square */
14639 if (move > backwardMostMove) {
14640 fromX = moveList[move - 1][0] - AAA;
14641 fromY = moveList[move - 1][1] - ONE;
14642 toX = moveList[move - 1][2] - AAA;
14643 toY = moveList[move - 1][3] - ONE;
14644 if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
14645 toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
14646 boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
14648 /* 2-square pawn move just happened */
14650 *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
14654 } else if(move == backwardMostMove) {
14655 // [HGM] perhaps we should always do it like this, and forget the above?
14656 if((signed char)boards[move][EP_STATUS] >= 0) {
14657 *p++ = boards[move][EP_STATUS] + AAA;
14658 *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
14669 /* [HGM] find reversible plies */
14670 { int i = 0, j=move;
14672 if (appData.debugMode) { int k;
14673 fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
14674 for(k=backwardMostMove; k<=forwardMostMove; k++)
14675 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
14679 while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
14680 if( j == backwardMostMove ) i += initialRulePlies;
14681 sprintf(p, "%d ", i);
14682 p += i>=100 ? 4 : i >= 10 ? 3 : 2;
14684 /* Fullmove number */
14685 sprintf(p, "%d", (move / 2) + 1);
14687 return StrSave(buf);
14691 ParseFEN(board, blackPlaysFirst, fen)
14693 int *blackPlaysFirst;
14703 /* [HGM] by default clear Crazyhouse holdings, if present */
14704 if(gameInfo.holdingsWidth) {
14705 for(i=0; i<BOARD_HEIGHT; i++) {
14706 board[i][0] = EmptySquare; /* black holdings */
14707 board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
14708 board[i][1] = (ChessSquare) 0; /* black counts */
14709 board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
14713 /* Piece placement data */
14714 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
14717 if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
14718 if (*p == '/') p++;
14719 emptycount = gameInfo.boardWidth - j;
14720 while (emptycount--)
14721 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
14723 #if(BOARD_FILES >= 10)
14724 } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
14725 p++; emptycount=10;
14726 if (j + emptycount > gameInfo.boardWidth) return FALSE;
14727 while (emptycount--)
14728 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
14730 } else if (isdigit(*p)) {
14731 emptycount = *p++ - '0';
14732 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
14733 if (j + emptycount > gameInfo.boardWidth) return FALSE;
14734 while (emptycount--)
14735 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
14736 } else if (*p == '+' || isalpha(*p)) {
14737 if (j >= gameInfo.boardWidth) return FALSE;
14739 piece = CharToPiece(*++p);
14740 if(piece == EmptySquare) return FALSE; /* unknown piece */
14741 piece = (ChessSquare) (PROMOTED piece ); p++;
14742 if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
14743 } else piece = CharToPiece(*p++);
14745 if(piece==EmptySquare) return FALSE; /* unknown piece */
14746 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
14747 piece = (ChessSquare) (PROMOTED piece);
14748 if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
14751 board[i][(j++)+gameInfo.holdingsWidth] = piece;
14757 while (*p == '/' || *p == ' ') p++;
14759 /* [HGM] look for Crazyhouse holdings here */
14760 while(*p==' ') p++;
14761 if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
14763 if(*p == '-' ) *p++; /* empty holdings */ else {
14764 if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
14765 /* if we would allow FEN reading to set board size, we would */
14766 /* have to add holdings and shift the board read so far here */
14767 while( (piece = CharToPiece(*p) ) != EmptySquare ) {
14769 if((int) piece >= (int) BlackPawn ) {
14770 i = (int)piece - (int)BlackPawn;
14771 i = PieceToNumber((ChessSquare)i);
14772 if( i >= gameInfo.holdingsSize ) return FALSE;
14773 board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
14774 board[BOARD_HEIGHT-1-i][1]++; /* black counts */
14776 i = (int)piece - (int)WhitePawn;
14777 i = PieceToNumber((ChessSquare)i);
14778 if( i >= gameInfo.holdingsSize ) return FALSE;
14779 board[i][BOARD_WIDTH-1] = piece; /* white holdings */
14780 board[i][BOARD_WIDTH-2]++; /* black holdings */
14784 if(*p == ']') *p++;
14787 while(*p == ' ') p++;
14792 *blackPlaysFirst = FALSE;
14795 *blackPlaysFirst = TRUE;
14801 /* [HGM] We NO LONGER ignore the rest of the FEN notation */
14802 /* return the extra info in global variiables */
14804 /* set defaults in case FEN is incomplete */
14805 board[EP_STATUS] = EP_UNKNOWN;
14806 for(i=0; i<nrCastlingRights; i++ ) {
14807 board[CASTLING][i] =
14808 gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
14809 } /* assume possible unless obviously impossible */
14810 if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
14811 if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
14812 if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
14813 && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
14814 if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
14815 if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
14816 if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
14817 && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
14820 while(*p==' ') p++;
14821 if(nrCastlingRights) {
14822 if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
14823 /* castling indicator present, so default becomes no castlings */
14824 for(i=0; i<nrCastlingRights; i++ ) {
14825 board[CASTLING][i] = NoRights;
14828 while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
14829 (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
14830 ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
14831 ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth) ) {
14832 char c = *p++; int whiteKingFile=NoRights, blackKingFile=NoRights;
14834 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
14835 if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
14836 if(board[0 ][i] == WhiteKing) whiteKingFile = i;
14838 if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
14839 whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
14840 if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
14841 && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
14842 if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
14843 && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
14846 for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
14847 board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
14848 board[CASTLING][2] = whiteKingFile;
14851 for(i=BOARD_LEFT; i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
14852 board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
14853 board[CASTLING][2] = whiteKingFile;
14856 for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
14857 board[CASTLING][3] = i != blackKingFile ? i : NoRights;
14858 board[CASTLING][5] = blackKingFile;
14861 for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
14862 board[CASTLING][4] = i != blackKingFile ? i : NoRights;
14863 board[CASTLING][5] = blackKingFile;
14866 default: /* FRC castlings */
14867 if(c >= 'a') { /* black rights */
14868 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
14869 if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
14870 if(i == BOARD_RGHT) break;
14871 board[CASTLING][5] = i;
14873 if(board[BOARD_HEIGHT-1][c] < BlackPawn ||
14874 board[BOARD_HEIGHT-1][c] >= BlackKing ) break;
14876 board[CASTLING][3] = c;
14878 board[CASTLING][4] = c;
14879 } else { /* white rights */
14880 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
14881 if(board[0][i] == WhiteKing) break;
14882 if(i == BOARD_RGHT) break;
14883 board[CASTLING][2] = i;
14884 c -= AAA - 'a' + 'A';
14885 if(board[0][c] >= WhiteKing) break;
14887 board[CASTLING][0] = c;
14889 board[CASTLING][1] = c;
14893 for(i=0; i<nrCastlingRights; i++)
14894 if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
14895 if (appData.debugMode) {
14896 fprintf(debugFP, "FEN castling rights:");
14897 for(i=0; i<nrCastlingRights; i++)
14898 fprintf(debugFP, " %d", board[CASTLING][i]);
14899 fprintf(debugFP, "\n");
14902 while(*p==' ') p++;
14905 /* read e.p. field in games that know e.p. capture */
14906 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
14907 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
14909 p++; board[EP_STATUS] = EP_NONE;
14911 char c = *p++ - AAA;
14913 if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
14914 if(*p >= '0' && *p <='9') *p++;
14915 board[EP_STATUS] = c;
14920 if(sscanf(p, "%d", &i) == 1) {
14921 FENrulePlies = i; /* 50-move ply counter */
14922 /* (The move number is still ignored) */
14929 EditPositionPasteFEN(char *fen)
14932 Board initial_position;
14934 if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
14935 DisplayError(_("Bad FEN position in clipboard"), 0);
14938 int savedBlackPlaysFirst = blackPlaysFirst;
14939 EditPositionEvent();
14940 blackPlaysFirst = savedBlackPlaysFirst;
14941 CopyBoard(boards[0], initial_position);
14942 initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
14943 EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
14944 DisplayBothClocks();
14945 DrawPosition(FALSE, boards[currentMove]);
14950 static char cseq[12] = "\\ ";
14952 Boolean set_cont_sequence(char *new_seq)
14957 // handle bad attempts to set the sequence
14959 return 0; // acceptable error - no debug
14961 len = strlen(new_seq);
14962 ret = (len > 0) && (len < sizeof(cseq));
14964 strcpy(cseq, new_seq);
14965 else if (appData.debugMode)
14966 fprintf(debugFP, "Invalid continuation sequence \"%s\" (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
14971 reformat a source message so words don't cross the width boundary. internal
14972 newlines are not removed. returns the wrapped size (no null character unless
14973 included in source message). If dest is NULL, only calculate the size required
14974 for the dest buffer. lp argument indicats line position upon entry, and it's
14975 passed back upon exit.
14977 int wrap(char *dest, char *src, int count, int width, int *lp)
14979 int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
14981 cseq_len = strlen(cseq);
14982 old_line = line = *lp;
14983 ansi = len = clen = 0;
14985 for (i=0; i < count; i++)
14987 if (src[i] == '\033')
14990 // if we hit the width, back up
14991 if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
14993 // store i & len in case the word is too long
14994 old_i = i, old_len = len;
14996 // find the end of the last word
14997 while (i && src[i] != ' ' && src[i] != '\n')
15003 // word too long? restore i & len before splitting it
15004 if ((old_i-i+clen) >= width)
15011 if (i && src[i-1] == ' ')
15014 if (src[i] != ' ' && src[i] != '\n')
15021 // now append the newline and continuation sequence
15026 strncpy(dest+len, cseq, cseq_len);
15034 dest[len] = src[i];
15038 if (src[i] == '\n')
15043 if (dest && appData.debugMode)
15045 fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
15046 count, width, line, len, *lp);
15047 show_bytes(debugFP, src, count);
15048 fprintf(debugFP, "\ndest: ");
15049 show_bytes(debugFP, dest, len);
15050 fprintf(debugFP, "\n");
15052 *lp = dest ? line : old_line;
15057 // [HGM] vari: routines for shelving variations
15060 PushTail(int firstMove, int lastMove)
15062 int i, j, nrMoves = lastMove - firstMove;
15064 if(appData.icsActive) { // only in local mode
15065 forwardMostMove = currentMove; // mimic old ICS behavior
15068 if(storedGames >= MAX_VARIATIONS-1) return;
15070 // push current tail of game on stack
15071 savedResult[storedGames] = gameInfo.result;
15072 savedDetails[storedGames] = gameInfo.resultDetails;
15073 gameInfo.resultDetails = NULL;
15074 savedFirst[storedGames] = firstMove;
15075 savedLast [storedGames] = lastMove;
15076 savedFramePtr[storedGames] = framePtr;
15077 framePtr -= nrMoves; // reserve space for the boards
15078 for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
15079 CopyBoard(boards[framePtr+i], boards[firstMove+i]);
15080 for(j=0; j<MOVE_LEN; j++)
15081 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
15082 for(j=0; j<2*MOVE_LEN; j++)
15083 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
15084 timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
15085 timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
15086 pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
15087 pvInfoList[firstMove+i-1].depth = 0;
15088 commentList[framePtr+i] = commentList[firstMove+i];
15089 commentList[firstMove+i] = NULL;
15093 forwardMostMove = currentMove; // truncte game so we can start variation
15094 if(storedGames == 1) GreyRevert(FALSE);
15098 PopTail(Boolean annotate)
15101 char buf[8000], moveBuf[20];
15103 if(appData.icsActive) return FALSE; // only in local mode
15104 if(!storedGames) return FALSE; // sanity
15107 ToNrEvent(savedFirst[storedGames]); // sets currentMove
15108 nrMoves = savedLast[storedGames] - currentMove;
15111 if(!WhiteOnMove(currentMove)) sprintf(buf, "(%d...", currentMove+2>>1);
15112 else strcpy(buf, "(");
15113 for(i=currentMove; i<forwardMostMove; i++) {
15115 sprintf(moveBuf, " %d. %s", i+2>>1, SavePart(parseList[i]));
15116 else sprintf(moveBuf, " %s", SavePart(parseList[i]));
15117 strcat(buf, moveBuf);
15118 if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
15122 for(i=1; i<nrMoves; i++) { // copy last variation back
15123 CopyBoard(boards[currentMove+i], boards[framePtr+i]);
15124 for(j=0; j<MOVE_LEN; j++)
15125 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
15126 for(j=0; j<2*MOVE_LEN; j++)
15127 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
15128 timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
15129 timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
15130 pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
15131 if(commentList[currentMove+i]) free(commentList[currentMove+i]);
15132 commentList[currentMove+i] = commentList[framePtr+i];
15133 commentList[framePtr+i] = NULL;
15135 if(annotate) AppendComment(currentMove+1, buf, FALSE);
15136 framePtr = savedFramePtr[storedGames];
15137 gameInfo.result = savedResult[storedGames];
15138 if(gameInfo.resultDetails != NULL) {
15139 free(gameInfo.resultDetails);
15141 gameInfo.resultDetails = savedDetails[storedGames];
15142 forwardMostMove = currentMove + nrMoves;
15143 if(storedGames == 0) GreyRevert(TRUE);
15149 { // remove all shelved variations
15151 for(i=0; i<storedGames; i++) {
15152 if(savedDetails[i])
15153 free(savedDetails[i]);
15154 savedDetails[i] = NULL;
15156 for(i=framePtr; i<MAX_MOVES; i++) {
15157 if(commentList[i]) free(commentList[i]);
15158 commentList[i] = NULL;
15160 framePtr = MAX_MOVES-1;