2 * backend.c -- Common back end for X and Windows NT versions of
4 * Copyright 1991 by Digital Equipment Corporation, Maynard,
7 * Enhancements Copyright 1992-2001, 2002, 2003, 2004, 2005, 2006,
8 * 2007, 2008, 2009, 2010 Free Software Foundation, Inc.
10 * Enhancements Copyright 2005 Alessandro Scotti
12 * The following terms apply to Digital Equipment Corporation's copyright
14 * ------------------------------------------------------------------------
17 * Permission to use, copy, modify, and distribute this software and its
18 * documentation for any purpose and without fee is hereby granted,
19 * provided that the above copyright notice appear in all copies and that
20 * both that copyright notice and this permission notice appear in
21 * supporting documentation, and that the name of Digital not be
22 * used in advertising or publicity pertaining to distribution of the
23 * software without specific, written prior permission.
25 * DIGITAL DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
26 * ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL
27 * DIGITAL BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR
28 * ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
29 * WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
30 * ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
32 * ------------------------------------------------------------------------
34 * The following terms apply to the enhanced version of XBoard
35 * distributed by the Free Software Foundation:
36 * ------------------------------------------------------------------------
38 * GNU XBoard is free software: you can redistribute it and/or modify
39 * it under the terms of the GNU General Public License as published by
40 * the Free Software Foundation, either version 3 of the License, or (at
41 * your option) any later version.
43 * GNU XBoard is distributed in the hope that it will be useful, but
44 * WITHOUT ANY WARRANTY; without even the implied warranty of
45 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
46 * General Public License for more details.
48 * You should have received a copy of the GNU General Public License
49 * along with this program. If not, see http://www.gnu.org/licenses/. *
51 *------------------------------------------------------------------------
52 ** See the file ChangeLog for a revision history. */
54 /* [AS] Also useful here for debugging */
58 #define DoSleep( n ) if( (n) != 0 ) Sleep( (n) );
62 #define DoSleep( n ) if( (n) >= 0) sleep(n)
72 #include <sys/types.h>
81 #else /* not STDC_HEADERS */
84 # else /* not HAVE_STRING_H */
86 # endif /* not HAVE_STRING_H */
87 #endif /* not STDC_HEADERS */
90 # include <sys/fcntl.h>
91 #else /* not HAVE_SYS_FCNTL_H */
94 # endif /* HAVE_FCNTL_H */
95 #endif /* not HAVE_SYS_FCNTL_H */
97 #if TIME_WITH_SYS_TIME
98 # include <sys/time.h>
102 # include <sys/time.h>
108 #if defined(_amigados) && !defined(__GNUC__)
113 extern int gettimeofday(struct timeval *, struct timezone *);
121 #include "frontend.h"
128 #include "backendz.h"
132 # define _(s) gettext (s)
133 # define N_(s) gettext_noop (s)
140 /* A point in time */
142 long sec; /* Assuming this is >= 32 bits */
143 int ms; /* Assuming this is >= 16 bits */
146 int establish P((void));
147 void read_from_player P((InputSourceRef isr, VOIDSTAR closure,
148 char *buf, int count, int error));
149 void read_from_ics P((InputSourceRef isr, VOIDSTAR closure,
150 char *buf, int count, int error));
151 void ics_printf P((char *format, ...));
152 void SendToICS P((char *s));
153 void SendToICSDelayed P((char *s, long msdelay));
154 void SendMoveToICS P((ChessMove moveType, int fromX, int fromY,
156 void HandleMachineMove P((char *message, ChessProgramState *cps));
157 int AutoPlayOneMove P((void));
158 int LoadGameOneMove P((ChessMove readAhead));
159 int LoadGameFromFile P((char *filename, int n, char *title, int useList));
160 int LoadPositionFromFile P((char *filename, int n, char *title));
161 int SavePositionToFile P((char *filename));
162 void ApplyMove P((int fromX, int fromY, int toX, int toY, int promoChar,
164 void MakeMove P((int fromX, int fromY, int toX, int toY, int promoChar));
165 void ShowMove P((int fromX, int fromY, int toX, int toY));
166 int FinishMove P((ChessMove moveType, int fromX, int fromY, int toX, int toY,
167 /*char*/int promoChar));
168 void BackwardInner P((int target));
169 void ForwardInner P((int target));
170 int Adjudicate P((ChessProgramState *cps));
171 void GameEnds P((ChessMove result, char *resultDetails, int whosays));
172 void EditPositionDone P((Boolean fakeRights));
173 void PrintOpponents P((FILE *fp));
174 void PrintPosition P((FILE *fp, int move));
175 void StartChessProgram P((ChessProgramState *cps));
176 void SendToProgram P((char *message, ChessProgramState *cps));
177 void SendMoveToProgram P((int moveNum, ChessProgramState *cps));
178 void ReceiveFromProgram P((InputSourceRef isr, VOIDSTAR closure,
179 char *buf, int count, int error));
180 void SendTimeControl P((ChessProgramState *cps,
181 int mps, long tc, int inc, int sd, int st));
182 char *TimeControlTagValue P((void));
183 void Attention P((ChessProgramState *cps));
184 void FeedMovesToProgram P((ChessProgramState *cps, int upto));
185 void ResurrectChessProgram P((void));
186 void DisplayComment P((int moveNumber, char *text));
187 void DisplayMove P((int moveNumber));
189 void ParseGameHistory P((char *game));
190 void ParseBoard12 P((char *string));
191 void KeepAlive P((void));
192 void StartClocks P((void));
193 void SwitchClocks P((int nr));
194 void StopClocks P((void));
195 void ResetClocks P((void));
196 char *PGNDate P((void));
197 void SetGameInfo P((void));
198 Boolean ParseFEN P((Board board, int *blackPlaysFirst, char *fen));
199 int RegisterMove P((void));
200 void MakeRegisteredMove P((void));
201 void TruncateGame P((void));
202 int looking_at P((char *, int *, char *));
203 void CopyPlayerNameIntoFileName P((char **, char *));
204 char *SavePart P((char *));
205 int SaveGameOldStyle P((FILE *));
206 int SaveGamePGN P((FILE *));
207 void GetTimeMark P((TimeMark *));
208 long SubtractTimeMarks P((TimeMark *, TimeMark *));
209 int CheckFlags P((void));
210 long NextTickLength P((long));
211 void CheckTimeControl P((void));
212 void show_bytes P((FILE *, char *, int));
213 int string_to_rating P((char *str));
214 void ParseFeatures P((char* args, ChessProgramState *cps));
215 void InitBackEnd3 P((void));
216 void FeatureDone P((ChessProgramState* cps, int val));
217 void InitChessProgram P((ChessProgramState *cps, int setup));
218 void OutputKibitz(int window, char *text);
219 int PerpetualChase(int first, int last);
220 int EngineOutputIsUp();
221 void InitDrawingSizes(int x, int y);
224 extern void ConsoleCreate();
227 ChessProgramState *WhitePlayer();
228 void InsertIntoMemo P((int which, char *text)); // [HGM] kibitz: in engineo.c
229 int VerifyDisplayMode P(());
231 char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment
232 void InitEngineUCI( const char * iniDir, ChessProgramState * cps ); // [HGM] moved here from winboard.c
233 char *ProbeBook P((int moveNr, char *book)); // [HGM] book: returns a book move
234 char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book
235 void ics_update_width P((int new_width));
236 extern char installDir[MSG_SIZ];
238 extern int tinyLayout, smallLayout;
239 ChessProgramStats programStats;
240 char lastPV[2][2*MSG_SIZ]; /* [HGM] pv: last PV in thinking output of each engine */
242 static int exiting = 0; /* [HGM] moved to top */
243 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
244 int startedFromPositionFile = FALSE; Board filePosition; /* [HGM] loadPos */
245 Board partnerBoard; /* [HGM] bughouse: for peeking at partner game */
247 char endingGame = 0; /* [HGM] crash: flag to prevent recursion of GameEnds() */
248 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS */
249 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
250 int lastIndex = 0; /* [HGM] autoinc: last game/position used in match mode */
251 Boolean connectionAlive;/* [HGM] alive: ICS connection status from probing */
252 int opponentKibitzes;
253 int lastSavedGame; /* [HGM] save: ID of game */
254 char chatPartner[MAX_CHAT][MSG_SIZ]; /* [HGM] chat: list of chatting partners */
255 extern int chatCount;
257 char marker[BOARD_RANKS][BOARD_FILES]; /* [HGM] marks for target squares */
259 /* States for ics_getting_history */
261 #define H_REQUESTED 1
262 #define H_GOT_REQ_HEADER 2
263 #define H_GOT_UNREQ_HEADER 3
264 #define H_GETTING_MOVES 4
265 #define H_GOT_UNWANTED_HEADER 5
267 /* whosays values for GameEnds */
276 /* Maximum number of games in a cmail message */
277 #define CMAIL_MAX_GAMES 20
279 /* Different types of move when calling RegisterMove */
281 #define CMAIL_RESIGN 1
283 #define CMAIL_ACCEPT 3
285 /* Different types of result to remember for each game */
286 #define CMAIL_NOT_RESULT 0
287 #define CMAIL_OLD_RESULT 1
288 #define CMAIL_NEW_RESULT 2
290 /* Telnet protocol constants */
301 static char * safeStrCpy( char * dst, const char * src, size_t count )
303 assert( dst != NULL );
304 assert( src != NULL );
307 strncpy( dst, src, count );
308 dst[ count-1 ] = '\0';
312 /* Some compiler can't cast u64 to double
313 * This function do the job for us:
315 * We use the highest bit for cast, this only
316 * works if the highest bit is not
317 * in use (This should not happen)
319 * We used this for all compiler
322 u64ToDouble(u64 value)
325 u64 tmp = value & u64Const(0x7fffffffffffffff);
326 r = (double)(s64)tmp;
327 if (value & u64Const(0x8000000000000000))
328 r += 9.2233720368547758080e18; /* 2^63 */
332 /* Fake up flags for now, as we aren't keeping track of castling
333 availability yet. [HGM] Change of logic: the flag now only
334 indicates the type of castlings allowed by the rule of the game.
335 The actual rights themselves are maintained in the array
336 castlingRights, as part of the game history, and are not probed
342 int flags = F_ALL_CASTLE_OK;
343 if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
344 switch (gameInfo.variant) {
346 flags &= ~F_ALL_CASTLE_OK;
347 case VariantGiveaway: // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
348 flags |= F_IGNORE_CHECK;
350 flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
353 flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
355 case VariantKriegspiel:
356 flags |= F_KRIEGSPIEL_CAPTURE;
358 case VariantCapaRandom:
359 case VariantFischeRandom:
360 flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
361 case VariantNoCastle:
362 case VariantShatranj:
365 flags &= ~F_ALL_CASTLE_OK;
373 FILE *gameFileFP, *debugFP;
376 [AS] Note: sometimes, the sscanf() function is used to parse the input
377 into a fixed-size buffer. Because of this, we must be prepared to
378 receive strings as long as the size of the input buffer, which is currently
379 set to 4K for Windows and 8K for the rest.
380 So, we must either allocate sufficiently large buffers here, or
381 reduce the size of the input buffer in the input reading part.
384 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
385 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
386 char thinkOutput1[MSG_SIZ*10];
388 ChessProgramState first, second;
390 /* premove variables */
393 int premoveFromX = 0;
394 int premoveFromY = 0;
395 int premovePromoChar = 0;
397 Boolean alarmSounded;
398 /* end premove variables */
400 char *ics_prefix = "$";
401 int ics_type = ICS_GENERIC;
403 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
404 int pauseExamForwardMostMove = 0;
405 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
406 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
407 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
408 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
409 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
410 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
411 int whiteFlag = FALSE, blackFlag = FALSE;
412 int userOfferedDraw = FALSE;
413 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
414 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
415 int cmailMoveType[CMAIL_MAX_GAMES];
416 long ics_clock_paused = 0;
417 ProcRef icsPR = NoProc, cmailPR = NoProc;
418 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
419 GameMode gameMode = BeginningOfGame;
420 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
421 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
422 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
423 int hiddenThinkOutputState = 0; /* [AS] */
424 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
425 int adjudicateLossPlies = 6;
426 char white_holding[64], black_holding[64];
427 TimeMark lastNodeCountTime;
428 long lastNodeCount=0;
429 int have_sent_ICS_logon = 0;
431 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement;
432 long timeControl_2; /* [AS] Allow separate time controls */
433 char *fullTimeControlString = NULL; /* [HGM] secondary TC: merge of MPS, TC and inc */
434 long timeRemaining[2][MAX_MOVES];
436 TimeMark programStartTime;
437 char ics_handle[MSG_SIZ];
438 int have_set_title = 0;
440 /* animateTraining preserves the state of appData.animate
441 * when Training mode is activated. This allows the
442 * response to be animated when appData.animate == TRUE and
443 * appData.animateDragging == TRUE.
445 Boolean animateTraining;
451 Board boards[MAX_MOVES];
452 /* [HGM] Following 7 needed for accurate legality tests: */
453 signed char castlingRank[BOARD_FILES]; // and corresponding ranks
454 signed char initialRights[BOARD_FILES];
455 int nrCastlingRights; // For TwoKings, or to implement castling-unknown status
456 int initialRulePlies, FENrulePlies;
457 FILE *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
460 int mute; // mute all sounds
462 // [HGM] vari: next 12 to save and restore variations
463 #define MAX_VARIATIONS 10
464 int framePtr = MAX_MOVES-1; // points to free stack entry
466 int savedFirst[MAX_VARIATIONS];
467 int savedLast[MAX_VARIATIONS];
468 int savedFramePtr[MAX_VARIATIONS];
469 char *savedDetails[MAX_VARIATIONS];
470 ChessMove savedResult[MAX_VARIATIONS];
472 void PushTail P((int firstMove, int lastMove));
473 Boolean PopTail P((Boolean annotate));
474 void CleanupTail P((void));
476 ChessSquare FIDEArray[2][BOARD_FILES] = {
477 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
478 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
479 { BlackRook, BlackKnight, BlackBishop, BlackQueen,
480 BlackKing, BlackBishop, BlackKnight, BlackRook }
483 ChessSquare twoKingsArray[2][BOARD_FILES] = {
484 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
485 WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
486 { BlackRook, BlackKnight, BlackBishop, BlackQueen,
487 BlackKing, BlackKing, BlackKnight, BlackRook }
490 ChessSquare KnightmateArray[2][BOARD_FILES] = {
491 { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
492 WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
493 { BlackRook, BlackMan, BlackBishop, BlackQueen,
494 BlackUnicorn, BlackBishop, BlackMan, BlackRook }
497 ChessSquare fairyArray[2][BOARD_FILES] = { /* [HGM] Queen side differs from King side */
498 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
499 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
500 { BlackLance, BlackAlfil, BlackMarshall, BlackAngel,
501 BlackKing, BlackMarshall, BlackAlfil, BlackLance }
504 ChessSquare ShatranjArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
505 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
506 WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
507 { BlackRook, BlackKnight, BlackAlfil, BlackKing,
508 BlackFerz, BlackAlfil, BlackKnight, BlackRook }
511 ChessSquare makrukArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
512 { WhiteRook, WhiteKnight, WhiteMan, WhiteKing,
513 WhiteFerz, WhiteMan, WhiteKnight, WhiteRook },
514 { BlackRook, BlackKnight, BlackMan, BlackFerz,
515 BlackKing, BlackMan, BlackKnight, BlackRook }
519 #if (BOARD_FILES>=10)
520 ChessSquare ShogiArray[2][BOARD_FILES] = {
521 { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
522 WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
523 { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
524 BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
527 ChessSquare XiangqiArray[2][BOARD_FILES] = {
528 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
529 WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
530 { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
531 BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
534 ChessSquare CapablancaArray[2][BOARD_FILES] = {
535 { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
536 WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
537 { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
538 BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
541 ChessSquare GreatArray[2][BOARD_FILES] = {
542 { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
543 WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
544 { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
545 BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
548 ChessSquare JanusArray[2][BOARD_FILES] = {
549 { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
550 WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
551 { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
552 BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
556 ChessSquare GothicArray[2][BOARD_FILES] = {
557 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
558 WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
559 { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
560 BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
563 #define GothicArray CapablancaArray
567 ChessSquare FalconArray[2][BOARD_FILES] = {
568 { WhiteRook, WhiteKnight, WhiteBishop, WhiteLance, WhiteQueen,
569 WhiteKing, WhiteLance, WhiteBishop, WhiteKnight, WhiteRook },
570 { BlackRook, BlackKnight, BlackBishop, BlackLance, BlackQueen,
571 BlackKing, BlackLance, BlackBishop, BlackKnight, BlackRook }
574 #define FalconArray CapablancaArray
577 #else // !(BOARD_FILES>=10)
578 #define XiangqiPosition FIDEArray
579 #define CapablancaArray FIDEArray
580 #define GothicArray FIDEArray
581 #define GreatArray FIDEArray
582 #endif // !(BOARD_FILES>=10)
584 #if (BOARD_FILES>=12)
585 ChessSquare CourierArray[2][BOARD_FILES] = {
586 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
587 WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
588 { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
589 BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
591 #else // !(BOARD_FILES>=12)
592 #define CourierArray CapablancaArray
593 #endif // !(BOARD_FILES>=12)
596 Board initialPosition;
599 /* Convert str to a rating. Checks for special cases of "----",
601 "++++", etc. Also strips ()'s */
603 string_to_rating(str)
606 while(*str && !isdigit(*str)) ++str;
608 return 0; /* One of the special "no rating" cases */
616 /* Init programStats */
617 programStats.movelist[0] = 0;
618 programStats.depth = 0;
619 programStats.nr_moves = 0;
620 programStats.moves_left = 0;
621 programStats.nodes = 0;
622 programStats.time = -1; // [HGM] PGNtime: make invalid to recognize engine output
623 programStats.score = 0;
624 programStats.got_only_move = 0;
625 programStats.got_fail = 0;
626 programStats.line_is_book = 0;
632 int matched, min, sec;
634 ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
636 GetTimeMark(&programStartTime);
637 srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
640 programStats.ok_to_send = 1;
641 programStats.seen_stat = 0;
644 * Initialize game list
650 * Internet chess server status
652 if (appData.icsActive) {
653 appData.matchMode = FALSE;
654 appData.matchGames = 0;
656 appData.noChessProgram = !appData.zippyPlay;
658 appData.zippyPlay = FALSE;
659 appData.zippyTalk = FALSE;
660 appData.noChessProgram = TRUE;
662 if (*appData.icsHelper != NULLCHAR) {
663 appData.useTelnet = TRUE;
664 appData.telnetProgram = appData.icsHelper;
667 appData.zippyTalk = appData.zippyPlay = FALSE;
670 /* [AS] Initialize pv info list [HGM] and game state */
674 for( i=0; i<=framePtr; i++ ) {
675 pvInfoList[i].depth = -1;
676 boards[i][EP_STATUS] = EP_NONE;
677 for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
682 * Parse timeControl resource
684 if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
685 appData.movesPerSession)) {
687 snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
688 DisplayFatalError(buf, 0, 2);
692 * Parse searchTime resource
694 if (*appData.searchTime != NULLCHAR) {
695 matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
697 searchTime = min * 60;
698 } else if (matched == 2) {
699 searchTime = min * 60 + sec;
702 snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
703 DisplayFatalError(buf, 0, 2);
707 /* [AS] Adjudication threshold */
708 adjudicateLossThreshold = appData.adjudicateLossThreshold;
710 first.which = "first";
711 second.which = "second";
712 first.maybeThinking = second.maybeThinking = FALSE;
713 first.pr = second.pr = NoProc;
714 first.isr = second.isr = NULL;
715 first.sendTime = second.sendTime = 2;
716 first.sendDrawOffers = 1;
717 if (appData.firstPlaysBlack) {
718 first.twoMachinesColor = "black\n";
719 second.twoMachinesColor = "white\n";
721 first.twoMachinesColor = "white\n";
722 second.twoMachinesColor = "black\n";
724 first.program = appData.firstChessProgram;
725 second.program = appData.secondChessProgram;
726 first.host = appData.firstHost;
727 second.host = appData.secondHost;
728 first.dir = appData.firstDirectory;
729 second.dir = appData.secondDirectory;
730 first.other = &second;
731 second.other = &first;
732 first.initString = appData.initString;
733 second.initString = appData.secondInitString;
734 first.computerString = appData.firstComputerString;
735 second.computerString = appData.secondComputerString;
736 first.useSigint = second.useSigint = TRUE;
737 first.useSigterm = second.useSigterm = TRUE;
738 first.reuse = appData.reuseFirst;
739 second.reuse = appData.reuseSecond;
740 first.nps = appData.firstNPS; // [HGM] nps: copy nodes per second
741 second.nps = appData.secondNPS;
742 first.useSetboard = second.useSetboard = FALSE;
743 first.useSAN = second.useSAN = FALSE;
744 first.usePing = second.usePing = FALSE;
745 first.lastPing = second.lastPing = 0;
746 first.lastPong = second.lastPong = 0;
747 first.usePlayother = second.usePlayother = FALSE;
748 first.useColors = second.useColors = TRUE;
749 first.useUsermove = second.useUsermove = FALSE;
750 first.sendICS = second.sendICS = FALSE;
751 first.sendName = second.sendName = appData.icsActive;
752 first.sdKludge = second.sdKludge = FALSE;
753 first.stKludge = second.stKludge = FALSE;
754 TidyProgramName(first.program, first.host, first.tidy);
755 TidyProgramName(second.program, second.host, second.tidy);
756 first.matchWins = second.matchWins = 0;
757 strcpy(first.variants, appData.variant);
758 strcpy(second.variants, appData.variant);
759 first.analysisSupport = second.analysisSupport = 2; /* detect */
760 first.analyzing = second.analyzing = FALSE;
761 first.initDone = second.initDone = FALSE;
763 /* New features added by Tord: */
764 first.useFEN960 = FALSE; second.useFEN960 = FALSE;
765 first.useOOCastle = TRUE; second.useOOCastle = TRUE;
766 /* End of new features added by Tord. */
767 first.fenOverride = appData.fenOverride1;
768 second.fenOverride = appData.fenOverride2;
770 /* [HGM] time odds: set factor for each machine */
771 first.timeOdds = appData.firstTimeOdds;
772 second.timeOdds = appData.secondTimeOdds;
774 if(appData.timeOddsMode) {
775 norm = first.timeOdds;
776 if(norm > second.timeOdds) norm = second.timeOdds;
778 first.timeOdds /= norm;
779 second.timeOdds /= norm;
782 /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
783 first.accumulateTC = appData.firstAccumulateTC;
784 second.accumulateTC = appData.secondAccumulateTC;
785 first.maxNrOfSessions = second.maxNrOfSessions = 1;
788 first.debug = second.debug = FALSE;
789 first.supportsNPS = second.supportsNPS = UNKNOWN;
792 first.optionSettings = appData.firstOptions;
793 second.optionSettings = appData.secondOptions;
795 first.scoreIsAbsolute = appData.firstScoreIsAbsolute; /* [AS] */
796 second.scoreIsAbsolute = appData.secondScoreIsAbsolute; /* [AS] */
797 first.isUCI = appData.firstIsUCI; /* [AS] */
798 second.isUCI = appData.secondIsUCI; /* [AS] */
799 first.hasOwnBookUCI = appData.firstHasOwnBookUCI; /* [AS] */
800 second.hasOwnBookUCI = appData.secondHasOwnBookUCI; /* [AS] */
802 if (appData.firstProtocolVersion > PROTOVER ||
803 appData.firstProtocolVersion < 1) {
805 sprintf(buf, _("protocol version %d not supported"),
806 appData.firstProtocolVersion);
807 DisplayFatalError(buf, 0, 2);
809 first.protocolVersion = appData.firstProtocolVersion;
812 if (appData.secondProtocolVersion > PROTOVER ||
813 appData.secondProtocolVersion < 1) {
815 sprintf(buf, _("protocol version %d not supported"),
816 appData.secondProtocolVersion);
817 DisplayFatalError(buf, 0, 2);
819 second.protocolVersion = appData.secondProtocolVersion;
822 if (appData.icsActive) {
823 appData.clockMode = TRUE; /* changes dynamically in ICS mode */
824 // } else if (*appData.searchTime != NULLCHAR || appData.noChessProgram) {
825 } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
826 appData.clockMode = FALSE;
827 first.sendTime = second.sendTime = 0;
831 /* Override some settings from environment variables, for backward
832 compatibility. Unfortunately it's not feasible to have the env
833 vars just set defaults, at least in xboard. Ugh.
835 if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
840 if (appData.noChessProgram) {
841 programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
842 sprintf(programVersion, "%s", PACKAGE_STRING);
844 /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
845 programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
846 sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
849 if (!appData.icsActive) {
851 /* Check for variants that are supported only in ICS mode,
852 or not at all. Some that are accepted here nevertheless
853 have bugs; see comments below.
855 VariantClass variant = StringToVariant(appData.variant);
857 case VariantBughouse: /* need four players and two boards */
858 case VariantKriegspiel: /* need to hide pieces and move details */
859 /* case VariantFischeRandom: (Fabien: moved below) */
860 sprintf(buf, _("Variant %s supported only in ICS mode"), appData.variant);
861 DisplayFatalError(buf, 0, 2);
865 case VariantLoadable:
875 sprintf(buf, _("Unknown variant name %s"), appData.variant);
876 DisplayFatalError(buf, 0, 2);
879 case VariantXiangqi: /* [HGM] repetition rules not implemented */
880 case VariantFairy: /* [HGM] TestLegality definitely off! */
881 case VariantGothic: /* [HGM] should work */
882 case VariantCapablanca: /* [HGM] should work */
883 case VariantCourier: /* [HGM] initial forced moves not implemented */
884 case VariantShogi: /* [HGM] drops not tested for legality */
885 case VariantKnightmate: /* [HGM] should work */
886 case VariantCylinder: /* [HGM] untested */
887 case VariantFalcon: /* [HGM] untested */
888 case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
889 offboard interposition not understood */
890 case VariantNormal: /* definitely works! */
891 case VariantWildCastle: /* pieces not automatically shuffled */
892 case VariantNoCastle: /* pieces not automatically shuffled */
893 case VariantFischeRandom: /* [HGM] works and shuffles pieces */
894 case VariantLosers: /* should work except for win condition,
895 and doesn't know captures are mandatory */
896 case VariantSuicide: /* should work except for win condition,
897 and doesn't know captures are mandatory */
898 case VariantGiveaway: /* should work except for win condition,
899 and doesn't know captures are mandatory */
900 case VariantTwoKings: /* should work */
901 case VariantAtomic: /* should work except for win condition */
902 case Variant3Check: /* should work except for win condition */
903 case VariantShatranj: /* should work except for all win conditions */
904 case VariantMakruk: /* should work except for daw countdown */
905 case VariantBerolina: /* might work if TestLegality is off */
906 case VariantCapaRandom: /* should work */
907 case VariantJanus: /* should work */
908 case VariantSuper: /* experimental */
909 case VariantGreat: /* experimental, requires legality testing to be off */
914 InitEngineUCI( installDir, &first ); // [HGM] moved here from winboard.c, to make available in xboard
915 InitEngineUCI( installDir, &second );
918 int NextIntegerFromString( char ** str, long * value )
923 while( *s == ' ' || *s == '\t' ) {
929 if( *s >= '0' && *s <= '9' ) {
930 while( *s >= '0' && *s <= '9' ) {
931 *value = *value * 10 + (*s - '0');
943 int NextTimeControlFromString( char ** str, long * value )
946 int result = NextIntegerFromString( str, &temp );
949 *value = temp * 60; /* Minutes */
952 result = NextIntegerFromString( str, &temp );
953 *value += temp; /* Seconds */
960 int NextSessionFromString( char ** str, int *moves, long * tc, long *inc)
961 { /* [HGM] routine added to read '+moves/time' for secondary time control */
962 int result = -1; long temp, temp2;
964 if(**str != '+') return -1; // old params remain in force!
966 if( NextTimeControlFromString( str, &temp ) ) return -1;
969 /* time only: incremental or sudden-death time control */
970 if(**str == '+') { /* increment follows; read it */
972 if(result = NextIntegerFromString( str, &temp2)) return -1;
975 *moves = 0; *tc = temp * 1000;
977 } else if(temp % 60 != 0) return -1; /* moves was given as min:sec */
979 (*str)++; /* classical time control */
980 result = NextTimeControlFromString( str, &temp2);
989 int GetTimeQuota(int movenr)
990 { /* [HGM] get time to add from the multi-session time-control string */
991 int moves=1; /* kludge to force reading of first session */
992 long time, increment;
993 char *s = fullTimeControlString;
995 if(appData.debugMode) fprintf(debugFP, "TC string = '%s'\n", fullTimeControlString);
997 if(moves) NextSessionFromString(&s, &moves, &time, &increment);
998 if(appData.debugMode) fprintf(debugFP, "mps=%d tc=%d inc=%d\n", moves, (int) time, (int) increment);
999 if(movenr == -1) return time; /* last move before new session */
1000 if(!moves) return increment; /* current session is incremental */
1001 if(movenr >= 0) movenr -= moves; /* we already finished this session */
1002 } while(movenr >= -1); /* try again for next session */
1004 return 0; // no new time quota on this move
1008 ParseTimeControl(tc, ti, mps)
1017 if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1020 sprintf(buf, "+%d/%s+%d", mps, tc, ti);
1021 else sprintf(buf, "+%s+%d", tc, ti);
1024 sprintf(buf, "+%d/%s", mps, tc);
1025 else sprintf(buf, "+%s", tc);
1027 fullTimeControlString = StrSave(buf);
1029 if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1034 /* Parse second time control */
1037 if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1045 timeControl_2 = tc2 * 1000;
1055 timeControl = tc1 * 1000;
1058 timeIncrement = ti * 1000; /* convert to ms */
1059 movesPerSession = 0;
1062 movesPerSession = mps;
1070 if (appData.debugMode) {
1071 fprintf(debugFP, "%s\n", programVersion);
1074 set_cont_sequence(appData.wrapContSeq);
1075 if (appData.matchGames > 0) {
1076 appData.matchMode = TRUE;
1077 } else if (appData.matchMode) {
1078 appData.matchGames = 1;
1080 if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1081 appData.matchGames = appData.sameColorGames;
1082 if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1083 if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1084 if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1087 if (appData.noChessProgram || first.protocolVersion == 1) {
1090 /* kludge: allow timeout for initial "feature" commands */
1092 DisplayMessage("", _("Starting chess program"));
1093 ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1098 InitBackEnd3 P((void))
1100 GameMode initialMode;
1104 InitChessProgram(&first, startedFromSetupPosition);
1107 if (appData.icsActive) {
1109 /* [DM] Make a console window if needed [HGM] merged ifs */
1114 if (*appData.icsCommPort != NULLCHAR) {
1115 sprintf(buf, _("Could not open comm port %s"),
1116 appData.icsCommPort);
1118 snprintf(buf, sizeof(buf), _("Could not connect to host %s, port %s"),
1119 appData.icsHost, appData.icsPort);
1121 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);
1131 } else if (appData.noChessProgram) {
1137 if (*appData.cmailGameName != NULLCHAR) {
1139 OpenLoopback(&cmailPR);
1141 AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1145 DisplayMessage("", "");
1146 if (StrCaseCmp(appData.initialMode, "") == 0) {
1147 initialMode = BeginningOfGame;
1148 } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1149 initialMode = TwoMachinesPlay;
1150 } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1151 initialMode = AnalyzeFile;
1152 } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1153 initialMode = AnalyzeMode;
1154 } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1155 initialMode = MachinePlaysWhite;
1156 } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1157 initialMode = MachinePlaysBlack;
1158 } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1159 initialMode = EditGame;
1160 } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1161 initialMode = EditPosition;
1162 } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1163 initialMode = Training;
1165 sprintf(buf, _("Unknown initialMode %s"), appData.initialMode);
1166 DisplayFatalError(buf, 0, 2);
1170 if (appData.matchMode) {
1171 /* Set up machine vs. machine match */
1172 if (appData.noChessProgram) {
1173 DisplayFatalError(_("Can't have a match with no chess programs"),
1179 if (*appData.loadGameFile != NULLCHAR) {
1180 int index = appData.loadGameIndex; // [HGM] autoinc
1181 if(index<0) lastIndex = index = 1;
1182 if (!LoadGameFromFile(appData.loadGameFile,
1184 appData.loadGameFile, FALSE)) {
1185 DisplayFatalError(_("Bad game file"), 0, 1);
1188 } else if (*appData.loadPositionFile != NULLCHAR) {
1189 int index = appData.loadPositionIndex; // [HGM] autoinc
1190 if(index<0) lastIndex = index = 1;
1191 if (!LoadPositionFromFile(appData.loadPositionFile,
1193 appData.loadPositionFile)) {
1194 DisplayFatalError(_("Bad position file"), 0, 1);
1199 } else if (*appData.cmailGameName != NULLCHAR) {
1200 /* Set up cmail mode */
1201 ReloadCmailMsgEvent(TRUE);
1203 /* Set up other modes */
1204 if (initialMode == AnalyzeFile) {
1205 if (*appData.loadGameFile == NULLCHAR) {
1206 DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1210 if (*appData.loadGameFile != NULLCHAR) {
1211 (void) LoadGameFromFile(appData.loadGameFile,
1212 appData.loadGameIndex,
1213 appData.loadGameFile, TRUE);
1214 } else if (*appData.loadPositionFile != NULLCHAR) {
1215 (void) LoadPositionFromFile(appData.loadPositionFile,
1216 appData.loadPositionIndex,
1217 appData.loadPositionFile);
1218 /* [HGM] try to make self-starting even after FEN load */
1219 /* to allow automatic setup of fairy variants with wtm */
1220 if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1221 gameMode = BeginningOfGame;
1222 setboardSpoiledMachineBlack = 1;
1224 /* [HGM] loadPos: make that every new game uses the setup */
1225 /* from file as long as we do not switch variant */
1226 if(!blackPlaysFirst) {
1227 startedFromPositionFile = TRUE;
1228 CopyBoard(filePosition, boards[0]);
1231 if (initialMode == AnalyzeMode) {
1232 if (appData.noChessProgram) {
1233 DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1236 if (appData.icsActive) {
1237 DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1241 } else if (initialMode == AnalyzeFile) {
1242 appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1243 ShowThinkingEvent();
1245 AnalysisPeriodicEvent(1);
1246 } else if (initialMode == MachinePlaysWhite) {
1247 if (appData.noChessProgram) {
1248 DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1252 if (appData.icsActive) {
1253 DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1257 MachineWhiteEvent();
1258 } else if (initialMode == MachinePlaysBlack) {
1259 if (appData.noChessProgram) {
1260 DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1264 if (appData.icsActive) {
1265 DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1269 MachineBlackEvent();
1270 } else if (initialMode == TwoMachinesPlay) {
1271 if (appData.noChessProgram) {
1272 DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1276 if (appData.icsActive) {
1277 DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1282 } else if (initialMode == EditGame) {
1284 } else if (initialMode == EditPosition) {
1285 EditPositionEvent();
1286 } else if (initialMode == Training) {
1287 if (*appData.loadGameFile == NULLCHAR) {
1288 DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1297 * Establish will establish a contact to a remote host.port.
1298 * Sets icsPR to a ProcRef for a process (or pseudo-process)
1299 * used to talk to the host.
1300 * Returns 0 if okay, error code if not.
1307 if (*appData.icsCommPort != NULLCHAR) {
1308 /* Talk to the host through a serial comm port */
1309 return OpenCommPort(appData.icsCommPort, &icsPR);
1311 } else if (*appData.gateway != NULLCHAR) {
1312 if (*appData.remoteShell == NULLCHAR) {
1313 /* Use the rcmd protocol to run telnet program on a gateway host */
1314 snprintf(buf, sizeof(buf), "%s %s %s",
1315 appData.telnetProgram, appData.icsHost, appData.icsPort);
1316 return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1319 /* Use the rsh program to run telnet program on a gateway host */
1320 if (*appData.remoteUser == NULLCHAR) {
1321 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1322 appData.gateway, appData.telnetProgram,
1323 appData.icsHost, appData.icsPort);
1325 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1326 appData.remoteShell, appData.gateway,
1327 appData.remoteUser, appData.telnetProgram,
1328 appData.icsHost, appData.icsPort);
1330 return StartChildProcess(buf, "", &icsPR);
1333 } else if (appData.useTelnet) {
1334 return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1337 /* TCP socket interface differs somewhat between
1338 Unix and NT; handle details in the front end.
1340 return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1345 show_bytes(fp, buf, count)
1351 if (*buf < 040 || *(unsigned char *) buf > 0177) {
1352 fprintf(fp, "\\%03o", *buf & 0xff);
1361 /* Returns an errno value */
1363 OutputMaybeTelnet(pr, message, count, outError)
1369 char buf[8192], *p, *q, *buflim;
1370 int left, newcount, outcount;
1372 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1373 *appData.gateway != NULLCHAR) {
1374 if (appData.debugMode) {
1375 fprintf(debugFP, ">ICS: ");
1376 show_bytes(debugFP, message, count);
1377 fprintf(debugFP, "\n");
1379 return OutputToProcess(pr, message, count, outError);
1382 buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1389 if (appData.debugMode) {
1390 fprintf(debugFP, ">ICS: ");
1391 show_bytes(debugFP, buf, newcount);
1392 fprintf(debugFP, "\n");
1394 outcount = OutputToProcess(pr, buf, newcount, outError);
1395 if (outcount < newcount) return -1; /* to be sure */
1402 } else if (((unsigned char) *p) == TN_IAC) {
1403 *q++ = (char) TN_IAC;
1410 if (appData.debugMode) {
1411 fprintf(debugFP, ">ICS: ");
1412 show_bytes(debugFP, buf, newcount);
1413 fprintf(debugFP, "\n");
1415 outcount = OutputToProcess(pr, buf, newcount, outError);
1416 if (outcount < newcount) return -1; /* to be sure */
1421 read_from_player(isr, closure, message, count, error)
1428 int outError, outCount;
1429 static int gotEof = 0;
1431 /* Pass data read from player on to ICS */
1434 outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1435 if (outCount < count) {
1436 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1438 } else if (count < 0) {
1439 RemoveInputSource(isr);
1440 DisplayFatalError(_("Error reading from keyboard"), error, 1);
1441 } else if (gotEof++ > 0) {
1442 RemoveInputSource(isr);
1443 DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1449 { // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1450 if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1451 connectionAlive = FALSE; // only sticks if no response to 'date' command.
1452 SendToICS("date\n");
1453 if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1456 /* added routine for printf style output to ics */
1457 void ics_printf(char *format, ...)
1459 char buffer[MSG_SIZ];
1462 va_start(args, format);
1463 vsnprintf(buffer, sizeof(buffer), format, args);
1464 buffer[sizeof(buffer)-1] = '\0';
1473 int count, outCount, outError;
1475 if (icsPR == NULL) return;
1478 outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1479 if (outCount < count) {
1480 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1484 /* This is used for sending logon scripts to the ICS. Sending
1485 without a delay causes problems when using timestamp on ICC
1486 (at least on my machine). */
1488 SendToICSDelayed(s,msdelay)
1492 int count, outCount, outError;
1494 if (icsPR == NULL) return;
1497 if (appData.debugMode) {
1498 fprintf(debugFP, ">ICS: ");
1499 show_bytes(debugFP, s, count);
1500 fprintf(debugFP, "\n");
1502 outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1504 if (outCount < count) {
1505 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1510 /* Remove all highlighting escape sequences in s
1511 Also deletes any suffix starting with '('
1514 StripHighlightAndTitle(s)
1517 static char retbuf[MSG_SIZ];
1520 while (*s != NULLCHAR) {
1521 while (*s == '\033') {
1522 while (*s != NULLCHAR && !isalpha(*s)) s++;
1523 if (*s != NULLCHAR) s++;
1525 while (*s != NULLCHAR && *s != '\033') {
1526 if (*s == '(' || *s == '[') {
1537 /* Remove all highlighting escape sequences in s */
1542 static char retbuf[MSG_SIZ];
1545 while (*s != NULLCHAR) {
1546 while (*s == '\033') {
1547 while (*s != NULLCHAR && !isalpha(*s)) s++;
1548 if (*s != NULLCHAR) s++;
1550 while (*s != NULLCHAR && *s != '\033') {
1558 char *variantNames[] = VARIANT_NAMES;
1563 return variantNames[v];
1567 /* Identify a variant from the strings the chess servers use or the
1568 PGN Variant tag names we use. */
1575 VariantClass v = VariantNormal;
1576 int i, found = FALSE;
1581 /* [HGM] skip over optional board-size prefixes */
1582 if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1583 sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1584 while( *e++ != '_');
1587 if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
1591 for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
1592 if (StrCaseStr(e, variantNames[i])) {
1593 v = (VariantClass) i;
1600 if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
1601 || StrCaseStr(e, "wild/fr")
1602 || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
1603 v = VariantFischeRandom;
1604 } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
1605 (i = 1, p = StrCaseStr(e, "w"))) {
1607 while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
1614 case 0: /* FICS only, actually */
1616 /* Castling legal even if K starts on d-file */
1617 v = VariantWildCastle;
1622 /* Castling illegal even if K & R happen to start in
1623 normal positions. */
1624 v = VariantNoCastle;
1637 /* Castling legal iff K & R start in normal positions */
1643 /* Special wilds for position setup; unclear what to do here */
1644 v = VariantLoadable;
1647 /* Bizarre ICC game */
1648 v = VariantTwoKings;
1651 v = VariantKriegspiel;
1657 v = VariantFischeRandom;
1660 v = VariantCrazyhouse;
1663 v = VariantBughouse;
1669 /* Not quite the same as FICS suicide! */
1670 v = VariantGiveaway;
1676 v = VariantShatranj;
1679 /* Temporary names for future ICC types. The name *will* change in
1680 the next xboard/WinBoard release after ICC defines it. */
1718 v = VariantCapablanca;
1721 v = VariantKnightmate;
1727 v = VariantCylinder;
1733 v = VariantCapaRandom;
1736 v = VariantBerolina;
1748 /* Found "wild" or "w" in the string but no number;
1749 must assume it's normal chess. */
1753 sprintf(buf, _("Unknown wild type %d"), wnum);
1754 DisplayError(buf, 0);
1760 if (appData.debugMode) {
1761 fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
1762 e, wnum, VariantName(v));
1767 static int leftover_start = 0, leftover_len = 0;
1768 char star_match[STAR_MATCH_N][MSG_SIZ];
1770 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
1771 advance *index beyond it, and set leftover_start to the new value of
1772 *index; else return FALSE. If pattern contains the character '*', it
1773 matches any sequence of characters not containing '\r', '\n', or the
1774 character following the '*' (if any), and the matched sequence(s) are
1775 copied into star_match.
1778 looking_at(buf, index, pattern)
1783 char *bufp = &buf[*index], *patternp = pattern;
1785 char *matchp = star_match[0];
1788 if (*patternp == NULLCHAR) {
1789 *index = leftover_start = bufp - buf;
1793 if (*bufp == NULLCHAR) return FALSE;
1794 if (*patternp == '*') {
1795 if (*bufp == *(patternp + 1)) {
1797 matchp = star_match[++star_count];
1801 } else if (*bufp == '\n' || *bufp == '\r') {
1803 if (*patternp == NULLCHAR)
1808 *matchp++ = *bufp++;
1812 if (*patternp != *bufp) return FALSE;
1819 SendToPlayer(data, length)
1823 int error, outCount;
1824 outCount = OutputToProcess(NoProc, data, length, &error);
1825 if (outCount < length) {
1826 DisplayFatalError(_("Error writing to display"), error, 1);
1831 PackHolding(packed, holding)
1843 switch (runlength) {
1854 sprintf(q, "%d", runlength);
1866 /* Telnet protocol requests from the front end */
1868 TelnetRequest(ddww, option)
1869 unsigned char ddww, option;
1871 unsigned char msg[3];
1872 int outCount, outError;
1874 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
1876 if (appData.debugMode) {
1877 char buf1[8], buf2[8], *ddwwStr, *optionStr;
1893 sprintf(buf1, "%d", ddww);
1902 sprintf(buf2, "%d", option);
1905 fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
1910 outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
1912 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1919 if (!appData.icsActive) return;
1920 TelnetRequest(TN_DO, TN_ECHO);
1926 if (!appData.icsActive) return;
1927 TelnetRequest(TN_DONT, TN_ECHO);
1931 CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece)
1933 /* put the holdings sent to us by the server on the board holdings area */
1934 int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
1938 if(gameInfo.holdingsWidth < 2) return;
1939 if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
1940 return; // prevent overwriting by pre-board holdings
1942 if( (int)lowestPiece >= BlackPawn ) {
1945 holdingsStartRow = BOARD_HEIGHT-1;
1948 holdingsColumn = BOARD_WIDTH-1;
1949 countsColumn = BOARD_WIDTH-2;
1950 holdingsStartRow = 0;
1954 for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
1955 board[i][holdingsColumn] = EmptySquare;
1956 board[i][countsColumn] = (ChessSquare) 0;
1958 while( (p=*holdings++) != NULLCHAR ) {
1959 piece = CharToPiece( ToUpper(p) );
1960 if(piece == EmptySquare) continue;
1961 /*j = (int) piece - (int) WhitePawn;*/
1962 j = PieceToNumber(piece);
1963 if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
1964 if(j < 0) continue; /* should not happen */
1965 piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
1966 board[holdingsStartRow+j*direction][holdingsColumn] = piece;
1967 board[holdingsStartRow+j*direction][countsColumn]++;
1973 VariantSwitch(Board board, VariantClass newVariant)
1975 int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
1978 startedFromPositionFile = FALSE;
1979 if(gameInfo.variant == newVariant) return;
1981 /* [HGM] This routine is called each time an assignment is made to
1982 * gameInfo.variant during a game, to make sure the board sizes
1983 * are set to match the new variant. If that means adding or deleting
1984 * holdings, we shift the playing board accordingly
1985 * This kludge is needed because in ICS observe mode, we get boards
1986 * of an ongoing game without knowing the variant, and learn about the
1987 * latter only later. This can be because of the move list we requested,
1988 * in which case the game history is refilled from the beginning anyway,
1989 * but also when receiving holdings of a crazyhouse game. In the latter
1990 * case we want to add those holdings to the already received position.
1994 if (appData.debugMode) {
1995 fprintf(debugFP, "Switch board from %s to %s\n",
1996 VariantName(gameInfo.variant), VariantName(newVariant));
1997 setbuf(debugFP, NULL);
1999 shuffleOpenings = 0; /* [HGM] shuffle */
2000 gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2004 newWidth = 9; newHeight = 9;
2005 gameInfo.holdingsSize = 7;
2006 case VariantBughouse:
2007 case VariantCrazyhouse:
2008 newHoldingsWidth = 2; break;
2012 newHoldingsWidth = 2;
2013 gameInfo.holdingsSize = 8;
2016 case VariantCapablanca:
2017 case VariantCapaRandom:
2020 newHoldingsWidth = gameInfo.holdingsSize = 0;
2023 if(newWidth != gameInfo.boardWidth ||
2024 newHeight != gameInfo.boardHeight ||
2025 newHoldingsWidth != gameInfo.holdingsWidth ) {
2027 /* shift position to new playing area, if needed */
2028 if(newHoldingsWidth > gameInfo.holdingsWidth) {
2029 for(i=0; i<BOARD_HEIGHT; i++)
2030 for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2031 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2033 for(i=0; i<newHeight; i++) {
2034 board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2035 board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2037 } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2038 for(i=0; i<BOARD_HEIGHT; i++)
2039 for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2040 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2043 gameInfo.boardWidth = newWidth;
2044 gameInfo.boardHeight = newHeight;
2045 gameInfo.holdingsWidth = newHoldingsWidth;
2046 gameInfo.variant = newVariant;
2047 InitDrawingSizes(-2, 0);
2048 } else gameInfo.variant = newVariant;
2049 CopyBoard(oldBoard, board); // remember correctly formatted board
2050 InitPosition(FALSE); /* this sets up board[0], but also other stuff */
2051 DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2054 static int loggedOn = FALSE;
2056 /*-- Game start info cache: --*/
2058 char gs_kind[MSG_SIZ];
2059 static char player1Name[128] = "";
2060 static char player2Name[128] = "";
2061 static char cont_seq[] = "\n\\ ";
2062 static int player1Rating = -1;
2063 static int player2Rating = -1;
2064 /*----------------------------*/
2066 ColorClass curColor = ColorNormal;
2067 int suppressKibitz = 0;
2070 Boolean soughtPending = FALSE;
2071 Boolean seekGraphUp;
2072 #define MAX_SEEK_ADS 200
2074 char *seekAdList[MAX_SEEK_ADS];
2075 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2076 float tcList[MAX_SEEK_ADS];
2077 char colorList[MAX_SEEK_ADS];
2078 int nrOfSeekAds = 0;
2079 int minRating = 1010, maxRating = 2800;
2080 int hMargin = 10, vMargin = 20, h, w;
2081 extern int squareSize, lineGap;
2086 int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2087 xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2088 if(r < minRating+100 && r >=0 ) r = minRating+100;
2089 if(r > maxRating) r = maxRating;
2090 if(tc < 1.) tc = 1.;
2091 if(tc > 95.) tc = 95.;
2092 x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2093 y = ((double)r - minRating)/(maxRating - minRating)
2094 * (h-vMargin-squareSize/8-1) + vMargin;
2095 if(ratingList[i] < 0) y = vMargin + squareSize/4;
2096 if(strstr(seekAdList[i], " u ")) color = 1;
2097 if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2098 !strstr(seekAdList[i], "bullet") &&
2099 !strstr(seekAdList[i], "blitz") &&
2100 !strstr(seekAdList[i], "standard") ) color = 2;
2101 if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2102 DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2106 AddAd(char *handle, char *rating, int base, int inc, char rated, char *type, int nr, Boolean plot)
2108 char buf[MSG_SIZ], *ext = "";
2109 VariantClass v = StringToVariant(type);
2110 if(strstr(type, "wild")) {
2111 ext = type + 4; // append wild number
2112 if(v == VariantFischeRandom) type = "chess960"; else
2113 if(v == VariantLoadable) type = "setup"; else
2114 type = VariantName(v);
2116 sprintf(buf, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2117 if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2118 if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2119 ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2120 sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2121 tcList[nrOfSeekAds] = base + (2./3.)*inc;
2122 seekNrList[nrOfSeekAds] = nr;
2123 zList[nrOfSeekAds] = 0;
2124 seekAdList[nrOfSeekAds++] = StrSave(buf);
2125 if(plot) PlotSeekAd(nrOfSeekAds-1);
2132 int x = xList[i], y = yList[i], d=squareSize/4, k;
2133 DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2134 if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2135 // now replot every dot that overlapped
2136 for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2137 int xx = xList[k], yy = yList[k];
2138 if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2139 DrawSeekDot(xx, yy, colorList[k]);
2144 RemoveSeekAd(int nr)
2147 for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2149 if(seekAdList[i]) free(seekAdList[i]);
2150 seekAdList[i] = seekAdList[--nrOfSeekAds];
2151 seekNrList[i] = seekNrList[nrOfSeekAds];
2152 ratingList[i] = ratingList[nrOfSeekAds];
2153 colorList[i] = colorList[nrOfSeekAds];
2154 tcList[i] = tcList[nrOfSeekAds];
2155 xList[i] = xList[nrOfSeekAds];
2156 yList[i] = yList[nrOfSeekAds];
2157 zList[i] = zList[nrOfSeekAds];
2158 seekAdList[nrOfSeekAds] = NULL;
2164 MatchSoughtLine(char *line)
2166 char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2167 int nr, base, inc, u=0; char dummy;
2169 if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2170 sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2172 (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2173 sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7) ) {
2174 // match: compact and save the line
2175 AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2184 if(!seekGraphUp) return FALSE;
2186 h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
2187 w = BOARD_WIDTH * (squareSize + lineGap) + lineGap;
2189 DrawSeekBackground(0, 0, w, h);
2190 DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2191 DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2192 for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2193 int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2195 DrawSeekAxis(hMargin+5*(i%500==0), yy, hMargin-5, yy); // rating ticks
2198 sprintf(buf, "%d", i);
2199 DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2202 DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2203 for(i=1; i<100; i+=(i<10?1:5)) {
2204 int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2205 DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2206 if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2208 sprintf(buf, "%d", i);
2209 DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2212 for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2216 int SeekGraphClick(ClickType click, int x, int y, int moving)
2218 static int lastDown = 0, displayed = 0, lastSecond;
2219 if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2220 if(click == Release || moving) return FALSE;
2222 soughtPending = TRUE;
2223 SendToICS(ics_prefix);
2224 SendToICS("sought\n"); // should this be "sought all"?
2225 } else { // issue challenge based on clicked ad
2226 int dist = 10000; int i, closest = 0, second = 0;
2227 for(i=0; i<nrOfSeekAds; i++) {
2228 int d = (x-xList[i])*(x-xList[i]) + (y-yList[i])*(y-yList[i]) + zList[i];
2229 if(d < dist) { dist = d; closest = i; }
2230 second += (d - zList[i] < 120); // count in-range ads
2231 if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2235 second = (second > 1);
2236 if(displayed != closest || second != lastSecond) {
2237 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2238 lastSecond = second; displayed = closest;
2240 if(click == Press) {
2241 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2244 } // on press 'hit', only show info
2245 if(moving == 2) return TRUE; // ignore right up-clicks on dot
2246 sprintf(buf, "play %d\n", seekNrList[closest]);
2247 SendToICS(ics_prefix);
2249 return TRUE; // let incoming board of started game pop down the graph
2250 } else if(click == Release) { // release 'miss' is ignored
2251 zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2252 if(moving == 2) { // right up-click
2253 nrOfSeekAds = 0; // refresh graph
2254 soughtPending = TRUE;
2255 SendToICS(ics_prefix);
2256 SendToICS("sought\n"); // should this be "sought all"?
2259 } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2260 // press miss or release hit 'pop down' seek graph
2261 seekGraphUp = FALSE;
2262 DrawPosition(TRUE, NULL);
2268 read_from_ics(isr, closure, data, count, error)
2275 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2276 #define STARTED_NONE 0
2277 #define STARTED_MOVES 1
2278 #define STARTED_BOARD 2
2279 #define STARTED_OBSERVE 3
2280 #define STARTED_HOLDINGS 4
2281 #define STARTED_CHATTER 5
2282 #define STARTED_COMMENT 6
2283 #define STARTED_MOVES_NOHIDE 7
2285 static int started = STARTED_NONE;
2286 static char parse[20000];
2287 static int parse_pos = 0;
2288 static char buf[BUF_SIZE + 1];
2289 static int firstTime = TRUE, intfSet = FALSE;
2290 static ColorClass prevColor = ColorNormal;
2291 static int savingComment = FALSE;
2292 static int cmatch = 0; // continuation sequence match
2299 int backup; /* [DM] For zippy color lines */
2301 char talker[MSG_SIZ]; // [HGM] chat
2304 connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2306 if (appData.debugMode) {
2308 fprintf(debugFP, "<ICS: ");
2309 show_bytes(debugFP, data, count);
2310 fprintf(debugFP, "\n");
2314 if (appData.debugMode) { int f = forwardMostMove;
2315 fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2316 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2317 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2320 /* If last read ended with a partial line that we couldn't parse,
2321 prepend it to the new read and try again. */
2322 if (leftover_len > 0) {
2323 for (i=0; i<leftover_len; i++)
2324 buf[i] = buf[leftover_start + i];
2327 /* copy new characters into the buffer */
2328 bp = buf + leftover_len;
2329 buf_len=leftover_len;
2330 for (i=0; i<count; i++)
2333 if (data[i] == '\r')
2336 // join lines split by ICS?
2337 if (!appData.noJoin)
2340 Joining just consists of finding matches against the
2341 continuation sequence, and discarding that sequence
2342 if found instead of copying it. So, until a match
2343 fails, there's nothing to do since it might be the
2344 complete sequence, and thus, something we don't want
2347 if (data[i] == cont_seq[cmatch])
2350 if (cmatch == strlen(cont_seq))
2352 cmatch = 0; // complete match. just reset the counter
2355 it's possible for the ICS to not include the space
2356 at the end of the last word, making our [correct]
2357 join operation fuse two separate words. the server
2358 does this when the space occurs at the width setting.
2360 if (!buf_len || buf[buf_len-1] != ' ')
2371 match failed, so we have to copy what matched before
2372 falling through and copying this character. In reality,
2373 this will only ever be just the newline character, but
2374 it doesn't hurt to be precise.
2376 strncpy(bp, cont_seq, cmatch);
2388 buf[buf_len] = NULLCHAR;
2389 // next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2394 while (i < buf_len) {
2395 /* Deal with part of the TELNET option negotiation
2396 protocol. We refuse to do anything beyond the
2397 defaults, except that we allow the WILL ECHO option,
2398 which ICS uses to turn off password echoing when we are
2399 directly connected to it. We reject this option
2400 if localLineEditing mode is on (always on in xboard)
2401 and we are talking to port 23, which might be a real
2402 telnet server that will try to keep WILL ECHO on permanently.
2404 if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2405 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2406 unsigned char option;
2408 switch ((unsigned char) buf[++i]) {
2410 if (appData.debugMode)
2411 fprintf(debugFP, "\n<WILL ");
2412 switch (option = (unsigned char) buf[++i]) {
2414 if (appData.debugMode)
2415 fprintf(debugFP, "ECHO ");
2416 /* Reply only if this is a change, according
2417 to the protocol rules. */
2418 if (remoteEchoOption) break;
2419 if (appData.localLineEditing &&
2420 atoi(appData.icsPort) == TN_PORT) {
2421 TelnetRequest(TN_DONT, TN_ECHO);
2424 TelnetRequest(TN_DO, TN_ECHO);
2425 remoteEchoOption = TRUE;
2429 if (appData.debugMode)
2430 fprintf(debugFP, "%d ", option);
2431 /* Whatever this is, we don't want it. */
2432 TelnetRequest(TN_DONT, option);
2437 if (appData.debugMode)
2438 fprintf(debugFP, "\n<WONT ");
2439 switch (option = (unsigned char) buf[++i]) {
2441 if (appData.debugMode)
2442 fprintf(debugFP, "ECHO ");
2443 /* Reply only if this is a change, according
2444 to the protocol rules. */
2445 if (!remoteEchoOption) break;
2447 TelnetRequest(TN_DONT, TN_ECHO);
2448 remoteEchoOption = FALSE;
2451 if (appData.debugMode)
2452 fprintf(debugFP, "%d ", (unsigned char) option);
2453 /* Whatever this is, it must already be turned
2454 off, because we never agree to turn on
2455 anything non-default, so according to the
2456 protocol rules, we don't reply. */
2461 if (appData.debugMode)
2462 fprintf(debugFP, "\n<DO ");
2463 switch (option = (unsigned char) buf[++i]) {
2465 /* Whatever this is, we refuse to do it. */
2466 if (appData.debugMode)
2467 fprintf(debugFP, "%d ", option);
2468 TelnetRequest(TN_WONT, option);
2473 if (appData.debugMode)
2474 fprintf(debugFP, "\n<DONT ");
2475 switch (option = (unsigned char) buf[++i]) {
2477 if (appData.debugMode)
2478 fprintf(debugFP, "%d ", option);
2479 /* Whatever this is, we are already not doing
2480 it, because we never agree to do anything
2481 non-default, so according to the protocol
2482 rules, we don't reply. */
2487 if (appData.debugMode)
2488 fprintf(debugFP, "\n<IAC ");
2489 /* Doubled IAC; pass it through */
2493 if (appData.debugMode)
2494 fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2495 /* Drop all other telnet commands on the floor */
2498 if (oldi > next_out)
2499 SendToPlayer(&buf[next_out], oldi - next_out);
2505 /* OK, this at least will *usually* work */
2506 if (!loggedOn && looking_at(buf, &i, "ics%")) {
2510 if (loggedOn && !intfSet) {
2511 if (ics_type == ICS_ICC) {
2513 "/set-quietly interface %s\n/set-quietly style 12\n",
2515 if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2516 strcat(str, "/set-2 51 1\n/set seek 1\n");
2517 } else if (ics_type == ICS_CHESSNET) {
2518 sprintf(str, "/style 12\n");
2520 strcpy(str, "alias $ @\n$set interface ");
2521 strcat(str, programVersion);
2522 strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2523 if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2524 strcat(str, "$iset seekremove 1\n$set seek 1\n");
2526 strcat(str, "$iset nohighlight 1\n");
2528 strcat(str, "$iset lock 1\n$style 12\n");
2531 NotifyFrontendLogin();
2535 if (started == STARTED_COMMENT) {
2536 /* Accumulate characters in comment */
2537 parse[parse_pos++] = buf[i];
2538 if (buf[i] == '\n') {
2539 parse[parse_pos] = NULLCHAR;
2540 if(chattingPartner>=0) {
2542 sprintf(mess, "%s%s", talker, parse);
2543 OutputChatMessage(chattingPartner, mess);
2544 chattingPartner = -1;
2545 next_out = i+1; // [HGM] suppress printing in ICS window
2547 if(!suppressKibitz) // [HGM] kibitz
2548 AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
2549 else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2550 int nrDigit = 0, nrAlph = 0, j;
2551 if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2552 { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2553 parse[parse_pos] = NULLCHAR;
2554 // try to be smart: if it does not look like search info, it should go to
2555 // ICS interaction window after all, not to engine-output window.
2556 for(j=0; j<parse_pos; j++) { // count letters and digits
2557 nrDigit += (parse[j] >= '0' && parse[j] <= '9');
2558 nrAlph += (parse[j] >= 'a' && parse[j] <= 'z');
2559 nrAlph += (parse[j] >= 'A' && parse[j] <= 'Z');
2561 if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2562 int depth=0; float score;
2563 if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2564 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2565 pvInfoList[forwardMostMove-1].depth = depth;
2566 pvInfoList[forwardMostMove-1].score = 100*score;
2568 OutputKibitz(suppressKibitz, parse);
2571 sprintf(tmp, _("your opponent kibitzes: %s"), parse);
2572 SendToPlayer(tmp, strlen(tmp));
2574 next_out = i+1; // [HGM] suppress printing in ICS window
2576 started = STARTED_NONE;
2578 /* Don't match patterns against characters in comment */
2583 if (started == STARTED_CHATTER) {
2584 if (buf[i] != '\n') {
2585 /* Don't match patterns against characters in chatter */
2589 started = STARTED_NONE;
2590 if(suppressKibitz) next_out = i+1;
2593 /* Kludge to deal with rcmd protocol */
2594 if (firstTime && looking_at(buf, &i, "\001*")) {
2595 DisplayFatalError(&buf[1], 0, 1);
2601 if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2604 if (appData.debugMode)
2605 fprintf(debugFP, "ics_type %d\n", ics_type);
2608 if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
2609 ics_type = ICS_FICS;
2611 if (appData.debugMode)
2612 fprintf(debugFP, "ics_type %d\n", ics_type);
2615 if (!loggedOn && looking_at(buf, &i, "chess.net")) {
2616 ics_type = ICS_CHESSNET;
2618 if (appData.debugMode)
2619 fprintf(debugFP, "ics_type %d\n", ics_type);
2624 (looking_at(buf, &i, "\"*\" is *a registered name") ||
2625 looking_at(buf, &i, "Logging you in as \"*\"") ||
2626 looking_at(buf, &i, "will be \"*\""))) {
2627 strcpy(ics_handle, star_match[0]);
2631 if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
2633 snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
2634 DisplayIcsInteractionTitle(buf);
2635 have_set_title = TRUE;
2638 /* skip finger notes */
2639 if (started == STARTED_NONE &&
2640 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
2641 (buf[i] == '1' && buf[i+1] == '0')) &&
2642 buf[i+2] == ':' && buf[i+3] == ' ') {
2643 started = STARTED_CHATTER;
2649 // [HGM] seekgraph: recognize sought lines and end-of-sought message
2650 if(appData.seekGraph) {
2651 if(soughtPending && MatchSoughtLine(buf+i)) {
2652 i = strstr(buf+i, "rated") - buf;
2653 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2654 next_out = leftover_start = i;
2655 started = STARTED_CHATTER;
2656 suppressKibitz = TRUE;
2659 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
2660 && looking_at(buf, &i, "* ads displayed")) {
2661 soughtPending = FALSE;
2666 if(appData.autoRefresh) {
2667 if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
2668 int s = (ics_type == ICS_ICC); // ICC format differs
2670 AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
2671 star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
2672 looking_at(buf, &i, "*% "); // eat prompt
2673 if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
2674 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2675 next_out = i; // suppress
2678 if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
2679 char *p = star_match[0];
2681 if(seekGraphUp) RemoveSeekAd(atoi(p));
2682 while(*p && *p++ != ' '); // next
2684 looking_at(buf, &i, "*% "); // eat prompt
2685 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2692 /* skip formula vars */
2693 if (started == STARTED_NONE &&
2694 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
2695 started = STARTED_CHATTER;
2700 // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
2701 if (appData.autoKibitz && started == STARTED_NONE &&
2702 !appData.icsEngineAnalyze && // [HGM] [DM] ICS analyze
2703 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
2704 if(looking_at(buf, &i, "* kibitzes: ") &&
2705 (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
2706 StrStr(star_match[0], gameInfo.black) == star_match[0] )) { // kibitz of self or opponent
2707 suppressKibitz = TRUE;
2708 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2710 if((StrStr(star_match[0], gameInfo.white) == star_match[0]
2711 && (gameMode == IcsPlayingWhite)) ||
2712 (StrStr(star_match[0], gameInfo.black) == star_match[0]
2713 && (gameMode == IcsPlayingBlack)) ) // opponent kibitz
2714 started = STARTED_CHATTER; // own kibitz we simply discard
2716 started = STARTED_COMMENT; // make sure it will be collected in parse[]
2717 parse_pos = 0; parse[0] = NULLCHAR;
2718 savingComment = TRUE;
2719 suppressKibitz = gameMode != IcsObserving ? 2 :
2720 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
2724 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
2725 looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
2726 && atoi(star_match[0])) {
2727 // suppress the acknowledgements of our own autoKibitz
2729 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2730 if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
2731 SendToPlayer(star_match[0], strlen(star_match[0]));
2732 if(looking_at(buf, &i, "*% ")) // eat prompt
2733 suppressKibitz = FALSE;
2737 } // [HGM] kibitz: end of patch
2739 // [HGM] chat: intercept tells by users for which we have an open chat window
2741 if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
2742 looking_at(buf, &i, "* whispers:") ||
2743 looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
2744 looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
2745 looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
2746 looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
2748 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
2749 chattingPartner = -1;
2751 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
2752 for(p=0; p<MAX_CHAT; p++) {
2753 if(channel == atoi(chatPartner[p])) {
2754 talker[0] = '['; strcat(talker, "] ");
2755 chattingPartner = p; break;
2758 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
2759 for(p=0; p<MAX_CHAT; p++) {
2760 if(!strcmp("WHISPER", chatPartner[p])) {
2761 talker[0] = '['; strcat(talker, "] ");
2762 chattingPartner = p; break;
2765 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
2766 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
2768 chattingPartner = p; break;
2770 if(chattingPartner<0) i = oldi; else {
2771 if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
2772 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2773 started = STARTED_COMMENT;
2774 parse_pos = 0; parse[0] = NULLCHAR;
2775 savingComment = 3 + chattingPartner; // counts as TRUE
2776 suppressKibitz = TRUE;
2779 } // [HGM] chat: end of patch
2781 if (appData.zippyTalk || appData.zippyPlay) {
2782 /* [DM] Backup address for color zippy lines */
2786 if (loggedOn == TRUE)
2787 if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
2788 (appData.zippyPlay && ZippyMatch(buf, &backup)));
2790 if (ZippyControl(buf, &i) ||
2791 ZippyConverse(buf, &i) ||
2792 (appData.zippyPlay && ZippyMatch(buf, &i))) {
2794 if (!appData.colorize) continue;
2798 } // [DM] 'else { ' deleted
2800 /* Regular tells and says */
2801 (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
2802 looking_at(buf, &i, "* (your partner) tells you: ") ||
2803 looking_at(buf, &i, "* says: ") ||
2804 /* Don't color "message" or "messages" output */
2805 (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
2806 looking_at(buf, &i, "*. * at *:*: ") ||
2807 looking_at(buf, &i, "--* (*:*): ") ||
2808 /* Message notifications (same color as tells) */
2809 looking_at(buf, &i, "* has left a message ") ||
2810 looking_at(buf, &i, "* just sent you a message:\n") ||
2811 /* Whispers and kibitzes */
2812 (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
2813 looking_at(buf, &i, "* kibitzes: ") ||
2815 (tkind = 3, looking_at(buf, &i, "*(*: "))) {
2817 if (tkind == 1 && strchr(star_match[0], ':')) {
2818 /* Avoid "tells you:" spoofs in channels */
2821 if (star_match[0][0] == NULLCHAR ||
2822 strchr(star_match[0], ' ') ||
2823 (tkind == 3 && strchr(star_match[1], ' '))) {
2824 /* Reject bogus matches */
2827 if (appData.colorize) {
2828 if (oldi > next_out) {
2829 SendToPlayer(&buf[next_out], oldi - next_out);
2834 Colorize(ColorTell, FALSE);
2835 curColor = ColorTell;
2838 Colorize(ColorKibitz, FALSE);
2839 curColor = ColorKibitz;
2842 p = strrchr(star_match[1], '(');
2849 Colorize(ColorChannel1, FALSE);
2850 curColor = ColorChannel1;
2852 Colorize(ColorChannel, FALSE);
2853 curColor = ColorChannel;
2857 curColor = ColorNormal;
2861 if (started == STARTED_NONE && appData.autoComment &&
2862 (gameMode == IcsObserving ||
2863 gameMode == IcsPlayingWhite ||
2864 gameMode == IcsPlayingBlack)) {
2865 parse_pos = i - oldi;
2866 memcpy(parse, &buf[oldi], parse_pos);
2867 parse[parse_pos] = NULLCHAR;
2868 started = STARTED_COMMENT;
2869 savingComment = TRUE;
2871 started = STARTED_CHATTER;
2872 savingComment = FALSE;
2879 if (looking_at(buf, &i, "* s-shouts: ") ||
2880 looking_at(buf, &i, "* c-shouts: ")) {
2881 if (appData.colorize) {
2882 if (oldi > next_out) {
2883 SendToPlayer(&buf[next_out], oldi - next_out);
2886 Colorize(ColorSShout, FALSE);
2887 curColor = ColorSShout;
2890 started = STARTED_CHATTER;
2894 if (looking_at(buf, &i, "--->")) {
2899 if (looking_at(buf, &i, "* shouts: ") ||
2900 looking_at(buf, &i, "--> ")) {
2901 if (appData.colorize) {
2902 if (oldi > next_out) {
2903 SendToPlayer(&buf[next_out], oldi - next_out);
2906 Colorize(ColorShout, FALSE);
2907 curColor = ColorShout;
2910 started = STARTED_CHATTER;
2914 if (looking_at( buf, &i, "Challenge:")) {
2915 if (appData.colorize) {
2916 if (oldi > next_out) {
2917 SendToPlayer(&buf[next_out], oldi - next_out);
2920 Colorize(ColorChallenge, FALSE);
2921 curColor = ColorChallenge;
2927 if (looking_at(buf, &i, "* offers you") ||
2928 looking_at(buf, &i, "* offers to be") ||
2929 looking_at(buf, &i, "* would like to") ||
2930 looking_at(buf, &i, "* requests to") ||
2931 looking_at(buf, &i, "Your opponent offers") ||
2932 looking_at(buf, &i, "Your opponent requests")) {
2934 if (appData.colorize) {
2935 if (oldi > next_out) {
2936 SendToPlayer(&buf[next_out], oldi - next_out);
2939 Colorize(ColorRequest, FALSE);
2940 curColor = ColorRequest;
2945 if (looking_at(buf, &i, "* (*) seeking")) {
2946 if (appData.colorize) {
2947 if (oldi > next_out) {
2948 SendToPlayer(&buf[next_out], oldi - next_out);
2951 Colorize(ColorSeek, FALSE);
2952 curColor = ColorSeek;
2957 if (looking_at(buf, &i, "\\ ")) {
2958 if (prevColor != ColorNormal) {
2959 if (oldi > next_out) {
2960 SendToPlayer(&buf[next_out], oldi - next_out);
2963 Colorize(prevColor, TRUE);
2964 curColor = prevColor;
2966 if (savingComment) {
2967 parse_pos = i - oldi;
2968 memcpy(parse, &buf[oldi], parse_pos);
2969 parse[parse_pos] = NULLCHAR;
2970 started = STARTED_COMMENT;
2971 if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
2972 chattingPartner = savingComment - 3; // kludge to remember the box
2974 started = STARTED_CHATTER;
2979 if (looking_at(buf, &i, "Black Strength :") ||
2980 looking_at(buf, &i, "<<< style 10 board >>>") ||
2981 looking_at(buf, &i, "<10>") ||
2982 looking_at(buf, &i, "#@#")) {
2983 /* Wrong board style */
2985 SendToICS(ics_prefix);
2986 SendToICS("set style 12\n");
2987 SendToICS(ics_prefix);
2988 SendToICS("refresh\n");
2992 if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
2994 have_sent_ICS_logon = 1;
2998 if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
2999 (looking_at(buf, &i, "\n<12> ") ||
3000 looking_at(buf, &i, "<12> "))) {
3002 if (oldi > next_out) {
3003 SendToPlayer(&buf[next_out], oldi - next_out);
3006 started = STARTED_BOARD;
3011 if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3012 looking_at(buf, &i, "<b1> ")) {
3013 if (oldi > next_out) {
3014 SendToPlayer(&buf[next_out], oldi - next_out);
3017 started = STARTED_HOLDINGS;
3022 if (looking_at(buf, &i, "* *vs. * *--- *")) {
3024 /* Header for a move list -- first line */
3026 switch (ics_getting_history) {
3030 case BeginningOfGame:
3031 /* User typed "moves" or "oldmoves" while we
3032 were idle. Pretend we asked for these
3033 moves and soak them up so user can step
3034 through them and/or save them.
3037 gameMode = IcsObserving;
3040 ics_getting_history = H_GOT_UNREQ_HEADER;
3042 case EditGame: /*?*/
3043 case EditPosition: /*?*/
3044 /* Should above feature work in these modes too? */
3045 /* For now it doesn't */
3046 ics_getting_history = H_GOT_UNWANTED_HEADER;
3049 ics_getting_history = H_GOT_UNWANTED_HEADER;
3054 /* Is this the right one? */
3055 if (gameInfo.white && gameInfo.black &&
3056 strcmp(gameInfo.white, star_match[0]) == 0 &&
3057 strcmp(gameInfo.black, star_match[2]) == 0) {
3059 ics_getting_history = H_GOT_REQ_HEADER;
3062 case H_GOT_REQ_HEADER:
3063 case H_GOT_UNREQ_HEADER:
3064 case H_GOT_UNWANTED_HEADER:
3065 case H_GETTING_MOVES:
3066 /* Should not happen */
3067 DisplayError(_("Error gathering move list: two headers"), 0);
3068 ics_getting_history = H_FALSE;
3072 /* Save player ratings into gameInfo if needed */
3073 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3074 ics_getting_history == H_GOT_UNREQ_HEADER) &&
3075 (gameInfo.whiteRating == -1 ||
3076 gameInfo.blackRating == -1)) {
3078 gameInfo.whiteRating = string_to_rating(star_match[1]);
3079 gameInfo.blackRating = string_to_rating(star_match[3]);
3080 if (appData.debugMode)
3081 fprintf(debugFP, _("Ratings from header: W %d, B %d\n"),
3082 gameInfo.whiteRating, gameInfo.blackRating);
3087 if (looking_at(buf, &i,
3088 "* * match, initial time: * minute*, increment: * second")) {
3089 /* Header for a move list -- second line */
3090 /* Initial board will follow if this is a wild game */
3091 if (gameInfo.event != NULL) free(gameInfo.event);
3092 sprintf(str, "ICS %s %s match", star_match[0], star_match[1]);
3093 gameInfo.event = StrSave(str);
3094 /* [HGM] we switched variant. Translate boards if needed. */
3095 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3099 if (looking_at(buf, &i, "Move ")) {
3100 /* Beginning of a move list */
3101 switch (ics_getting_history) {
3103 /* Normally should not happen */
3104 /* Maybe user hit reset while we were parsing */
3107 /* Happens if we are ignoring a move list that is not
3108 * the one we just requested. Common if the user
3109 * tries to observe two games without turning off
3112 case H_GETTING_MOVES:
3113 /* Should not happen */
3114 DisplayError(_("Error gathering move list: nested"), 0);
3115 ics_getting_history = H_FALSE;
3117 case H_GOT_REQ_HEADER:
3118 ics_getting_history = H_GETTING_MOVES;
3119 started = STARTED_MOVES;
3121 if (oldi > next_out) {
3122 SendToPlayer(&buf[next_out], oldi - next_out);
3125 case H_GOT_UNREQ_HEADER:
3126 ics_getting_history = H_GETTING_MOVES;
3127 started = STARTED_MOVES_NOHIDE;
3130 case H_GOT_UNWANTED_HEADER:
3131 ics_getting_history = H_FALSE;
3137 if (looking_at(buf, &i, "% ") ||
3138 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3139 && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3140 if(ics_type == ICS_ICC && soughtPending) { // [HGM] seekgraph: on ICC sought-list has no termination line
3141 soughtPending = FALSE;
3145 if(suppressKibitz) next_out = i;
3146 savingComment = FALSE;
3150 case STARTED_MOVES_NOHIDE:
3151 memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3152 parse[parse_pos + i - oldi] = NULLCHAR;
3153 ParseGameHistory(parse);
3155 if (appData.zippyPlay && first.initDone) {
3156 FeedMovesToProgram(&first, forwardMostMove);
3157 if (gameMode == IcsPlayingWhite) {
3158 if (WhiteOnMove(forwardMostMove)) {
3159 if (first.sendTime) {
3160 if (first.useColors) {
3161 SendToProgram("black\n", &first);
3163 SendTimeRemaining(&first, TRUE);
3165 if (first.useColors) {
3166 SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3168 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3169 first.maybeThinking = TRUE;
3171 if (first.usePlayother) {
3172 if (first.sendTime) {
3173 SendTimeRemaining(&first, TRUE);
3175 SendToProgram("playother\n", &first);
3181 } else if (gameMode == IcsPlayingBlack) {
3182 if (!WhiteOnMove(forwardMostMove)) {
3183 if (first.sendTime) {
3184 if (first.useColors) {
3185 SendToProgram("white\n", &first);
3187 SendTimeRemaining(&first, FALSE);
3189 if (first.useColors) {
3190 SendToProgram("black\n", &first);
3192 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3193 first.maybeThinking = TRUE;
3195 if (first.usePlayother) {
3196 if (first.sendTime) {
3197 SendTimeRemaining(&first, FALSE);
3199 SendToProgram("playother\n", &first);
3208 if (gameMode == IcsObserving && ics_gamenum == -1) {
3209 /* Moves came from oldmoves or moves command
3210 while we weren't doing anything else.
3212 currentMove = forwardMostMove;
3213 ClearHighlights();/*!!could figure this out*/
3214 flipView = appData.flipView;
3215 DrawPosition(TRUE, boards[currentMove]);
3216 DisplayBothClocks();
3217 sprintf(str, "%s vs. %s",
3218 gameInfo.white, gameInfo.black);
3222 /* Moves were history of an active game */
3223 if (gameInfo.resultDetails != NULL) {
3224 free(gameInfo.resultDetails);
3225 gameInfo.resultDetails = NULL;
3228 HistorySet(parseList, backwardMostMove,
3229 forwardMostMove, currentMove-1);
3230 DisplayMove(currentMove - 1);
3231 if (started == STARTED_MOVES) next_out = i;
3232 started = STARTED_NONE;
3233 ics_getting_history = H_FALSE;
3236 case STARTED_OBSERVE:
3237 started = STARTED_NONE;
3238 SendToICS(ics_prefix);
3239 SendToICS("refresh\n");
3245 if(bookHit) { // [HGM] book: simulate book reply
3246 static char bookMove[MSG_SIZ]; // a bit generous?
3248 programStats.nodes = programStats.depth = programStats.time =
3249 programStats.score = programStats.got_only_move = 0;
3250 sprintf(programStats.movelist, "%s (xbook)", bookHit);
3252 strcpy(bookMove, "move ");
3253 strcat(bookMove, bookHit);
3254 HandleMachineMove(bookMove, &first);
3259 if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3260 started == STARTED_HOLDINGS ||
3261 started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3262 /* Accumulate characters in move list or board */
3263 parse[parse_pos++] = buf[i];
3266 /* Start of game messages. Mostly we detect start of game
3267 when the first board image arrives. On some versions
3268 of the ICS, though, we need to do a "refresh" after starting
3269 to observe in order to get the current board right away. */
3270 if (looking_at(buf, &i, "Adding game * to observation list")) {
3271 started = STARTED_OBSERVE;
3275 /* Handle auto-observe */
3276 if (appData.autoObserve &&
3277 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3278 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3280 /* Choose the player that was highlighted, if any. */
3281 if (star_match[0][0] == '\033' ||
3282 star_match[1][0] != '\033') {
3283 player = star_match[0];
3285 player = star_match[2];
3287 sprintf(str, "%sobserve %s\n",
3288 ics_prefix, StripHighlightAndTitle(player));
3291 /* Save ratings from notify string */
3292 strcpy(player1Name, star_match[0]);
3293 player1Rating = string_to_rating(star_match[1]);
3294 strcpy(player2Name, star_match[2]);
3295 player2Rating = string_to_rating(star_match[3]);
3297 if (appData.debugMode)
3299 "Ratings from 'Game notification:' %s %d, %s %d\n",
3300 player1Name, player1Rating,
3301 player2Name, player2Rating);
3306 /* Deal with automatic examine mode after a game,
3307 and with IcsObserving -> IcsExamining transition */
3308 if (looking_at(buf, &i, "Entering examine mode for game *") ||
3309 looking_at(buf, &i, "has made you an examiner of game *")) {
3311 int gamenum = atoi(star_match[0]);
3312 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3313 gamenum == ics_gamenum) {
3314 /* We were already playing or observing this game;
3315 no need to refetch history */
3316 gameMode = IcsExamining;
3318 pauseExamForwardMostMove = forwardMostMove;
3319 } else if (currentMove < forwardMostMove) {
3320 ForwardInner(forwardMostMove);
3323 /* I don't think this case really can happen */
3324 SendToICS(ics_prefix);
3325 SendToICS("refresh\n");
3330 /* Error messages */
3331 // if (ics_user_moved) {
3332 if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3333 if (looking_at(buf, &i, "Illegal move") ||
3334 looking_at(buf, &i, "Not a legal move") ||
3335 looking_at(buf, &i, "Your king is in check") ||
3336 looking_at(buf, &i, "It isn't your turn") ||
3337 looking_at(buf, &i, "It is not your move")) {
3339 if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3340 currentMove = forwardMostMove-1;
3341 DisplayMove(currentMove - 1); /* before DMError */
3342 DrawPosition(FALSE, boards[currentMove]);
3343 SwitchClocks(forwardMostMove-1); // [HGM] race
3344 DisplayBothClocks();
3346 DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3352 if (looking_at(buf, &i, "still have time") ||
3353 looking_at(buf, &i, "not out of time") ||
3354 looking_at(buf, &i, "either player is out of time") ||
3355 looking_at(buf, &i, "has timeseal; checking")) {
3356 /* We must have called his flag a little too soon */
3357 whiteFlag = blackFlag = FALSE;
3361 if (looking_at(buf, &i, "added * seconds to") ||
3362 looking_at(buf, &i, "seconds were added to")) {
3363 /* Update the clocks */
3364 SendToICS(ics_prefix);
3365 SendToICS("refresh\n");
3369 if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3370 ics_clock_paused = TRUE;
3375 if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3376 ics_clock_paused = FALSE;
3381 /* Grab player ratings from the Creating: message.
3382 Note we have to check for the special case when
3383 the ICS inserts things like [white] or [black]. */
3384 if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3385 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3387 0 player 1 name (not necessarily white)
3389 2 empty, white, or black (IGNORED)
3390 3 player 2 name (not necessarily black)
3393 The names/ratings are sorted out when the game
3394 actually starts (below).
3396 strcpy(player1Name, StripHighlightAndTitle(star_match[0]));
3397 player1Rating = string_to_rating(star_match[1]);
3398 strcpy(player2Name, StripHighlightAndTitle(star_match[3]));
3399 player2Rating = string_to_rating(star_match[4]);
3401 if (appData.debugMode)
3403 "Ratings from 'Creating:' %s %d, %s %d\n",
3404 player1Name, player1Rating,
3405 player2Name, player2Rating);
3410 /* Improved generic start/end-of-game messages */
3411 if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3412 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3413 /* If tkind == 0: */
3414 /* star_match[0] is the game number */
3415 /* [1] is the white player's name */
3416 /* [2] is the black player's name */
3417 /* For end-of-game: */
3418 /* [3] is the reason for the game end */
3419 /* [4] is a PGN end game-token, preceded by " " */
3420 /* For start-of-game: */
3421 /* [3] begins with "Creating" or "Continuing" */
3422 /* [4] is " *" or empty (don't care). */
3423 int gamenum = atoi(star_match[0]);
3424 char *whitename, *blackname, *why, *endtoken;
3425 ChessMove endtype = (ChessMove) 0;
3428 whitename = star_match[1];
3429 blackname = star_match[2];
3430 why = star_match[3];
3431 endtoken = star_match[4];
3433 whitename = star_match[1];
3434 blackname = star_match[3];
3435 why = star_match[5];
3436 endtoken = star_match[6];
3439 /* Game start messages */
3440 if (strncmp(why, "Creating ", 9) == 0 ||
3441 strncmp(why, "Continuing ", 11) == 0) {
3442 gs_gamenum = gamenum;
3443 strcpy(gs_kind, strchr(why, ' ') + 1);
3444 VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
3446 if (appData.zippyPlay) {
3447 ZippyGameStart(whitename, blackname);
3453 /* Game end messages */
3454 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3455 ics_gamenum != gamenum) {
3458 while (endtoken[0] == ' ') endtoken++;
3459 switch (endtoken[0]) {
3462 endtype = GameUnfinished;
3465 endtype = BlackWins;
3468 if (endtoken[1] == '/')
3469 endtype = GameIsDrawn;
3471 endtype = WhiteWins;
3474 GameEnds(endtype, why, GE_ICS);
3476 if (appData.zippyPlay && first.initDone) {
3477 ZippyGameEnd(endtype, why);
3478 if (first.pr == NULL) {
3479 /* Start the next process early so that we'll
3480 be ready for the next challenge */
3481 StartChessProgram(&first);
3483 /* Send "new" early, in case this command takes
3484 a long time to finish, so that we'll be ready
3485 for the next challenge. */
3486 gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3493 if (looking_at(buf, &i, "Removing game * from observation") ||
3494 looking_at(buf, &i, "no longer observing game *") ||
3495 looking_at(buf, &i, "Game * (*) has no examiners")) {
3496 if (gameMode == IcsObserving &&
3497 atoi(star_match[0]) == ics_gamenum)
3499 /* icsEngineAnalyze */
3500 if (appData.icsEngineAnalyze) {
3507 ics_user_moved = FALSE;
3512 if (looking_at(buf, &i, "no longer examining game *")) {
3513 if (gameMode == IcsExamining &&
3514 atoi(star_match[0]) == ics_gamenum)
3518 ics_user_moved = FALSE;
3523 /* Advance leftover_start past any newlines we find,
3524 so only partial lines can get reparsed */
3525 if (looking_at(buf, &i, "\n")) {
3526 prevColor = curColor;
3527 if (curColor != ColorNormal) {
3528 if (oldi > next_out) {
3529 SendToPlayer(&buf[next_out], oldi - next_out);
3532 Colorize(ColorNormal, FALSE);
3533 curColor = ColorNormal;
3535 if (started == STARTED_BOARD) {
3536 started = STARTED_NONE;
3537 parse[parse_pos] = NULLCHAR;
3538 ParseBoard12(parse);
3541 /* Send premove here */
3542 if (appData.premove) {
3544 if (currentMove == 0 &&
3545 gameMode == IcsPlayingWhite &&
3546 appData.premoveWhite) {
3547 sprintf(str, "%s\n", appData.premoveWhiteText);
3548 if (appData.debugMode)
3549 fprintf(debugFP, "Sending premove:\n");
3551 } else if (currentMove == 1 &&
3552 gameMode == IcsPlayingBlack &&
3553 appData.premoveBlack) {
3554 sprintf(str, "%s\n", appData.premoveBlackText);
3555 if (appData.debugMode)
3556 fprintf(debugFP, "Sending premove:\n");
3558 } else if (gotPremove) {
3560 ClearPremoveHighlights();
3561 if (appData.debugMode)
3562 fprintf(debugFP, "Sending premove:\n");
3563 UserMoveEvent(premoveFromX, premoveFromY,
3564 premoveToX, premoveToY,
3569 /* Usually suppress following prompt */
3570 if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
3571 while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
3572 if (looking_at(buf, &i, "*% ")) {
3573 savingComment = FALSE;
3578 } else if (started == STARTED_HOLDINGS) {
3580 char new_piece[MSG_SIZ];
3581 started = STARTED_NONE;
3582 parse[parse_pos] = NULLCHAR;
3583 if (appData.debugMode)
3584 fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
3585 parse, currentMove);
3586 if (sscanf(parse, " game %d", &gamenum) == 1) {
3587 if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game
3588 if (gameInfo.variant == VariantNormal) {
3589 /* [HGM] We seem to switch variant during a game!
3590 * Presumably no holdings were displayed, so we have
3591 * to move the position two files to the right to
3592 * create room for them!
3594 VariantClass newVariant;
3595 switch(gameInfo.boardWidth) { // base guess on board width
3596 case 9: newVariant = VariantShogi; break;
3597 case 10: newVariant = VariantGreat; break;
3598 default: newVariant = VariantCrazyhouse; break;
3600 VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3601 /* Get a move list just to see the header, which
3602 will tell us whether this is really bug or zh */
3603 if (ics_getting_history == H_FALSE) {
3604 ics_getting_history = H_REQUESTED;
3605 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3609 new_piece[0] = NULLCHAR;
3610 sscanf(parse, "game %d white [%s black [%s <- %s",
3611 &gamenum, white_holding, black_holding,
3613 white_holding[strlen(white_holding)-1] = NULLCHAR;
3614 black_holding[strlen(black_holding)-1] = NULLCHAR;
3615 /* [HGM] copy holdings to board holdings area */
3616 CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
3617 CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
3618 boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
3620 if (appData.zippyPlay && first.initDone) {
3621 ZippyHoldings(white_holding, black_holding,
3625 if (tinyLayout || smallLayout) {
3626 char wh[16], bh[16];
3627 PackHolding(wh, white_holding);
3628 PackHolding(bh, black_holding);
3629 sprintf(str, "[%s-%s] %s-%s", wh, bh,
3630 gameInfo.white, gameInfo.black);
3632 sprintf(str, "%s [%s] vs. %s [%s]",
3633 gameInfo.white, white_holding,
3634 gameInfo.black, black_holding);
3637 DrawPosition(FALSE, boards[currentMove]);
3639 } else if(appData.bgObserve) { // [HGM] bughouse: holdings of other game => background
3640 sscanf(parse, "game %d white [%s black [%s <- %s",
3641 &gamenum, white_holding, black_holding,
3643 white_holding[strlen(white_holding)-1] = NULLCHAR;
3644 black_holding[strlen(black_holding)-1] = NULLCHAR;
3645 /* [HGM] copy holdings to partner-board holdings area */
3646 CopyHoldings(partnerBoard, white_holding, WhitePawn);
3647 CopyHoldings(partnerBoard, black_holding, BlackPawn);
3648 if(partnerUp) DrawPosition(FALSE, partnerBoard);
3651 /* Suppress following prompt */
3652 if (looking_at(buf, &i, "*% ")) {
3653 if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
3654 savingComment = FALSE;
3662 i++; /* skip unparsed character and loop back */
3665 if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
3666 // started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
3667 // SendToPlayer(&buf[next_out], i - next_out);
3668 started != STARTED_HOLDINGS && leftover_start > next_out) {
3669 SendToPlayer(&buf[next_out], leftover_start - next_out);
3673 leftover_len = buf_len - leftover_start;
3674 /* if buffer ends with something we couldn't parse,
3675 reparse it after appending the next read */
3677 } else if (count == 0) {
3678 RemoveInputSource(isr);
3679 DisplayFatalError(_("Connection closed by ICS"), 0, 0);
3681 DisplayFatalError(_("Error reading from ICS"), error, 1);
3686 /* Board style 12 looks like this:
3688 <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
3690 * The "<12> " is stripped before it gets to this routine. The two
3691 * trailing 0's (flip state and clock ticking) are later addition, and
3692 * some chess servers may not have them, or may have only the first.
3693 * Additional trailing fields may be added in the future.
3696 #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"
3698 #define RELATION_OBSERVING_PLAYED 0
3699 #define RELATION_OBSERVING_STATIC -2 /* examined, oldmoves, or smoves */
3700 #define RELATION_PLAYING_MYMOVE 1
3701 #define RELATION_PLAYING_NOTMYMOVE -1
3702 #define RELATION_EXAMINING 2
3703 #define RELATION_ISOLATED_BOARD -3
3704 #define RELATION_STARTING_POSITION -4 /* FICS only */
3707 ParseBoard12(string)
3710 GameMode newGameMode;
3711 int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
3712 int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
3713 int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
3714 char to_play, board_chars[200];
3715 char move_str[500], str[500], elapsed_time[500];
3716 char black[32], white[32];
3718 int prevMove = currentMove;
3721 int fromX, fromY, toX, toY;
3723 int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
3724 char *bookHit = NULL; // [HGM] book
3725 Boolean weird = FALSE, reqFlag = FALSE;
3727 fromX = fromY = toX = toY = -1;
3731 if (appData.debugMode)
3732 fprintf(debugFP, _("Parsing board: %s\n"), string);
3734 move_str[0] = NULLCHAR;
3735 elapsed_time[0] = NULLCHAR;
3736 { /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
3738 while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
3739 if(string[i] == ' ') { ranks++; files = 0; }
3741 if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
3744 for(j = 0; j <i; j++) board_chars[j] = string[j];
3745 board_chars[i] = '\0';
3748 n = sscanf(string, PATTERN, &to_play, &double_push,
3749 &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
3750 &gamenum, white, black, &relation, &basetime, &increment,
3751 &white_stren, &black_stren, &white_time, &black_time,
3752 &moveNum, str, elapsed_time, move_str, &ics_flip,
3756 snprintf(str, sizeof(str), _("Failed to parse board string:\n\"%s\""), string);
3757 DisplayError(str, 0);
3761 /* Convert the move number to internal form */
3762 moveNum = (moveNum - 1) * 2;
3763 if (to_play == 'B') moveNum++;
3764 if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
3765 DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
3771 case RELATION_OBSERVING_PLAYED:
3772 case RELATION_OBSERVING_STATIC:
3773 if (gamenum == -1) {
3774 /* Old ICC buglet */
3775 relation = RELATION_OBSERVING_STATIC;
3777 newGameMode = IcsObserving;
3779 case RELATION_PLAYING_MYMOVE:
3780 case RELATION_PLAYING_NOTMYMOVE:
3782 ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
3783 IcsPlayingWhite : IcsPlayingBlack;
3785 case RELATION_EXAMINING:
3786 newGameMode = IcsExamining;
3788 case RELATION_ISOLATED_BOARD:
3790 /* Just display this board. If user was doing something else,
3791 we will forget about it until the next board comes. */
3792 newGameMode = IcsIdle;
3794 case RELATION_STARTING_POSITION:
3795 newGameMode = gameMode;
3799 if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
3800 && newGameMode == IcsObserving && appData.bgObserve) {
3801 // [HGM] bughouse: don't act on alien boards while we play. Just parse the board and save it */
3803 for (k = 0; k < ranks; k++) {
3804 for (j = 0; j < files; j++)
3805 board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
3806 if(gameInfo.holdingsWidth > 1) {
3807 board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
3808 board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
3811 CopyBoard(partnerBoard, board);
3812 if(partnerUp) DrawPosition(FALSE, partnerBoard);
3813 sprintf(buf, "W: %d:%d B: %d:%d (%d-%d) %c", white_time/60000, (white_time%60000)/1000,
3814 (black_time/60000), (black_time%60000)/1000, white_stren, black_stren, to_play);
3815 DisplayMessage(buf, "");
3819 /* Modify behavior for initial board display on move listing
3822 switch (ics_getting_history) {
3826 case H_GOT_REQ_HEADER:
3827 case H_GOT_UNREQ_HEADER:
3828 /* This is the initial position of the current game */
3829 gamenum = ics_gamenum;
3830 moveNum = 0; /* old ICS bug workaround */
3831 if (to_play == 'B') {
3832 startedFromSetupPosition = TRUE;
3833 blackPlaysFirst = TRUE;
3835 if (forwardMostMove == 0) forwardMostMove = 1;
3836 if (backwardMostMove == 0) backwardMostMove = 1;
3837 if (currentMove == 0) currentMove = 1;
3839 newGameMode = gameMode;
3840 relation = RELATION_STARTING_POSITION; /* ICC needs this */
3842 case H_GOT_UNWANTED_HEADER:
3843 /* This is an initial board that we don't want */
3845 case H_GETTING_MOVES:
3846 /* Should not happen */
3847 DisplayError(_("Error gathering move list: extra board"), 0);
3848 ics_getting_history = H_FALSE;
3852 if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
3853 weird && (int)gameInfo.variant <= (int)VariantShogi) {
3854 /* [HGM] We seem to have switched variant unexpectedly
3855 * Try to guess new variant from board size
3857 VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
3858 if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
3859 if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
3860 if(ranks == 8 && files == 12) newVariant = VariantCourier; else
3861 if(ranks == 9 && files == 9) newVariant = VariantShogi; else
3862 if(!weird) newVariant = VariantNormal;
3863 VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3864 /* Get a move list just to see the header, which
3865 will tell us whether this is really bug or zh */
3866 if (ics_getting_history == H_FALSE) {
3867 ics_getting_history = H_REQUESTED; reqFlag = TRUE;
3868 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3873 /* Take action if this is the first board of a new game, or of a
3874 different game than is currently being displayed. */
3875 if (gamenum != ics_gamenum || newGameMode != gameMode ||
3876 relation == RELATION_ISOLATED_BOARD) {
3878 /* Forget the old game and get the history (if any) of the new one */
3879 if (gameMode != BeginningOfGame) {
3883 if (appData.autoRaiseBoard) BoardToTop();
3885 if (gamenum == -1) {
3886 newGameMode = IcsIdle;
3887 } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
3888 appData.getMoveList && !reqFlag) {
3889 /* Need to get game history */
3890 ics_getting_history = H_REQUESTED;
3891 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3895 /* Initially flip the board to have black on the bottom if playing
3896 black or if the ICS flip flag is set, but let the user change
3897 it with the Flip View button. */
3898 flipView = appData.autoFlipView ?
3899 (newGameMode == IcsPlayingBlack) || ics_flip :
3902 /* Done with values from previous mode; copy in new ones */
3903 gameMode = newGameMode;
3905 ics_gamenum = gamenum;
3906 if (gamenum == gs_gamenum) {
3907 int klen = strlen(gs_kind);
3908 if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
3909 sprintf(str, "ICS %s", gs_kind);
3910 gameInfo.event = StrSave(str);
3912 gameInfo.event = StrSave("ICS game");
3914 gameInfo.site = StrSave(appData.icsHost);
3915 gameInfo.date = PGNDate();
3916 gameInfo.round = StrSave("-");
3917 gameInfo.white = StrSave(white);
3918 gameInfo.black = StrSave(black);
3919 timeControl = basetime * 60 * 1000;
3921 timeIncrement = increment * 1000;
3922 movesPerSession = 0;
3923 gameInfo.timeControl = TimeControlTagValue();
3924 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
3925 if (appData.debugMode) {
3926 fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
3927 fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
3928 setbuf(debugFP, NULL);
3931 gameInfo.outOfBook = NULL;
3933 /* Do we have the ratings? */
3934 if (strcmp(player1Name, white) == 0 &&
3935 strcmp(player2Name, black) == 0) {
3936 if (appData.debugMode)
3937 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3938 player1Rating, player2Rating);
3939 gameInfo.whiteRating = player1Rating;
3940 gameInfo.blackRating = player2Rating;
3941 } else if (strcmp(player2Name, white) == 0 &&
3942 strcmp(player1Name, black) == 0) {
3943 if (appData.debugMode)
3944 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3945 player2Rating, player1Rating);
3946 gameInfo.whiteRating = player2Rating;
3947 gameInfo.blackRating = player1Rating;
3949 player1Name[0] = player2Name[0] = NULLCHAR;
3951 /* Silence shouts if requested */
3952 if (appData.quietPlay &&
3953 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
3954 SendToICS(ics_prefix);
3955 SendToICS("set shout 0\n");
3959 /* Deal with midgame name changes */
3961 if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
3962 if (gameInfo.white) free(gameInfo.white);
3963 gameInfo.white = StrSave(white);
3965 if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
3966 if (gameInfo.black) free(gameInfo.black);
3967 gameInfo.black = StrSave(black);
3971 /* Throw away game result if anything actually changes in examine mode */
3972 if (gameMode == IcsExamining && !newGame) {
3973 gameInfo.result = GameUnfinished;
3974 if (gameInfo.resultDetails != NULL) {
3975 free(gameInfo.resultDetails);
3976 gameInfo.resultDetails = NULL;
3980 /* In pausing && IcsExamining mode, we ignore boards coming
3981 in if they are in a different variation than we are. */
3982 if (pauseExamInvalid) return;
3983 if (pausing && gameMode == IcsExamining) {
3984 if (moveNum <= pauseExamForwardMostMove) {
3985 pauseExamInvalid = TRUE;
3986 forwardMostMove = pauseExamForwardMostMove;
3991 if (appData.debugMode) {
3992 fprintf(debugFP, "load %dx%d board\n", files, ranks);
3994 /* Parse the board */
3995 for (k = 0; k < ranks; k++) {
3996 for (j = 0; j < files; j++)
3997 board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
3998 if(gameInfo.holdingsWidth > 1) {
3999 board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4000 board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4003 CopyBoard(boards[moveNum], board);
4004 boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
4006 startedFromSetupPosition =
4007 !CompareBoards(board, initialPosition);
4008 if(startedFromSetupPosition)
4009 initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
4012 /* [HGM] Set castling rights. Take the outermost Rooks,
4013 to make it also work for FRC opening positions. Note that board12
4014 is really defective for later FRC positions, as it has no way to
4015 indicate which Rook can castle if they are on the same side of King.
4016 For the initial position we grant rights to the outermost Rooks,
4017 and remember thos rights, and we then copy them on positions
4018 later in an FRC game. This means WB might not recognize castlings with
4019 Rooks that have moved back to their original position as illegal,
4020 but in ICS mode that is not its job anyway.
4022 if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
4023 { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
4025 for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4026 if(board[0][i] == WhiteRook) j = i;
4027 initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4028 for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4029 if(board[0][i] == WhiteRook) j = i;
4030 initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4031 for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4032 if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4033 initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4034 for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4035 if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4036 initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4038 if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
4039 for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4040 if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
4041 for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4042 if(board[BOARD_HEIGHT-1][k] == bKing)
4043 initialRights[5] = boards[moveNum][CASTLING][5] = k;
4044 if(gameInfo.variant == VariantTwoKings) {
4045 // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
4046 if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
4047 if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
4050 r = boards[moveNum][CASTLING][0] = initialRights[0];
4051 if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4052 r = boards[moveNum][CASTLING][1] = initialRights[1];
4053 if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4054 r = boards[moveNum][CASTLING][3] = initialRights[3];
4055 if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4056 r = boards[moveNum][CASTLING][4] = initialRights[4];
4057 if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4058 /* wildcastle kludge: always assume King has rights */
4059 r = boards[moveNum][CASTLING][2] = initialRights[2];
4060 r = boards[moveNum][CASTLING][5] = initialRights[5];
4062 /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4063 boards[moveNum][EP_STATUS] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
4066 if (ics_getting_history == H_GOT_REQ_HEADER ||
4067 ics_getting_history == H_GOT_UNREQ_HEADER) {
4068 /* This was an initial position from a move list, not
4069 the current position */
4073 /* Update currentMove and known move number limits */
4074 newMove = newGame || moveNum > forwardMostMove;
4077 forwardMostMove = backwardMostMove = currentMove = moveNum;
4078 if (gameMode == IcsExamining && moveNum == 0) {
4079 /* Workaround for ICS limitation: we are not told the wild
4080 type when starting to examine a game. But if we ask for
4081 the move list, the move list header will tell us */
4082 ics_getting_history = H_REQUESTED;
4083 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
4086 } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4087 || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4089 /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4090 /* [HGM] applied this also to an engine that is silently watching */
4091 if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4092 (gameMode == IcsObserving || gameMode == IcsExamining) &&
4093 gameInfo.variant == currentlyInitializedVariant) {
4094 takeback = forwardMostMove - moveNum;
4095 for (i = 0; i < takeback; i++) {
4096 if (appData.debugMode) fprintf(debugFP, "take back move\n");
4097 SendToProgram("undo\n", &first);
4102 forwardMostMove = moveNum;
4103 if (!pausing || currentMove > forwardMostMove)
4104 currentMove = forwardMostMove;
4106 /* New part of history that is not contiguous with old part */
4107 if (pausing && gameMode == IcsExamining) {
4108 pauseExamInvalid = TRUE;
4109 forwardMostMove = pauseExamForwardMostMove;
4112 if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4114 if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4115 // [HGM] when we will receive the move list we now request, it will be
4116 // fed to the engine from the first move on. So if the engine is not
4117 // in the initial position now, bring it there.
4118 InitChessProgram(&first, 0);
4121 ics_getting_history = H_REQUESTED;
4122 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
4125 forwardMostMove = backwardMostMove = currentMove = moveNum;
4128 /* Update the clocks */
4129 if (strchr(elapsed_time, '.')) {
4131 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4132 timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4134 /* Time is in seconds */
4135 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4136 timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4141 if (appData.zippyPlay && newGame &&
4142 gameMode != IcsObserving && gameMode != IcsIdle &&
4143 gameMode != IcsExamining)
4144 ZippyFirstBoard(moveNum, basetime, increment);
4147 /* Put the move on the move list, first converting
4148 to canonical algebraic form. */
4150 if (appData.debugMode) {
4151 if (appData.debugMode) { int f = forwardMostMove;
4152 fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4153 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4154 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4156 fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4157 fprintf(debugFP, "moveNum = %d\n", moveNum);
4158 fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4159 setbuf(debugFP, NULL);
4161 if (moveNum <= backwardMostMove) {
4162 /* We don't know what the board looked like before
4164 strcpy(parseList[moveNum - 1], move_str);
4165 strcat(parseList[moveNum - 1], " ");
4166 strcat(parseList[moveNum - 1], elapsed_time);
4167 moveList[moveNum - 1][0] = NULLCHAR;
4168 } else if (strcmp(move_str, "none") == 0) {
4169 // [HGM] long SAN: swapped order; test for 'none' before parsing move
4170 /* Again, we don't know what the board looked like;
4171 this is really the start of the game. */
4172 parseList[moveNum - 1][0] = NULLCHAR;
4173 moveList[moveNum - 1][0] = NULLCHAR;
4174 backwardMostMove = moveNum;
4175 startedFromSetupPosition = TRUE;
4176 fromX = fromY = toX = toY = -1;
4178 // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
4179 // So we parse the long-algebraic move string in stead of the SAN move
4180 int valid; char buf[MSG_SIZ], *prom;
4182 // str looks something like "Q/a1-a2"; kill the slash
4184 sprintf(buf, "%c%s", str[0], str+2);
4185 else strcpy(buf, str); // might be castling
4186 if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
4187 strcat(buf, prom); // long move lacks promo specification!
4188 if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4189 if(appData.debugMode)
4190 fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4191 strcpy(move_str, buf);
4193 valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4194 &fromX, &fromY, &toX, &toY, &promoChar)
4195 || ParseOneMove(buf, moveNum - 1, &moveType,
4196 &fromX, &fromY, &toX, &toY, &promoChar);
4197 // end of long SAN patch
4199 (void) CoordsToAlgebraic(boards[moveNum - 1],
4200 PosFlags(moveNum - 1),
4201 fromY, fromX, toY, toX, promoChar,
4202 parseList[moveNum-1]);
4203 switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4209 if(gameInfo.variant != VariantShogi)
4210 strcat(parseList[moveNum - 1], "+");
4213 case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4214 strcat(parseList[moveNum - 1], "#");
4217 strcat(parseList[moveNum - 1], " ");
4218 strcat(parseList[moveNum - 1], elapsed_time);
4219 /* currentMoveString is set as a side-effect of ParseOneMove */
4220 strcpy(moveList[moveNum - 1], currentMoveString);
4221 strcat(moveList[moveNum - 1], "\n");
4223 /* Move from ICS was illegal!? Punt. */
4224 if (appData.debugMode) {
4225 fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4226 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4228 strcpy(parseList[moveNum - 1], move_str);
4229 strcat(parseList[moveNum - 1], " ");
4230 strcat(parseList[moveNum - 1], elapsed_time);
4231 moveList[moveNum - 1][0] = NULLCHAR;
4232 fromX = fromY = toX = toY = -1;
4235 if (appData.debugMode) {
4236 fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4237 setbuf(debugFP, NULL);
4241 /* Send move to chess program (BEFORE animating it). */
4242 if (appData.zippyPlay && !newGame && newMove &&
4243 (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4245 if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4246 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4247 if (moveList[moveNum - 1][0] == NULLCHAR) {
4248 sprintf(str, _("Couldn't parse move \"%s\" from ICS"),
4250 DisplayError(str, 0);
4252 if (first.sendTime) {
4253 SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4255 bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4256 if (firstMove && !bookHit) {
4258 if (first.useColors) {
4259 SendToProgram(gameMode == IcsPlayingWhite ?
4261 "black\ngo\n", &first);
4263 SendToProgram("go\n", &first);
4265 first.maybeThinking = TRUE;
4268 } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4269 if (moveList[moveNum - 1][0] == NULLCHAR) {
4270 sprintf(str, _("Couldn't parse move \"%s\" from ICS"), move_str);
4271 DisplayError(str, 0);
4273 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4274 SendMoveToProgram(moveNum - 1, &first);
4281 if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4282 /* If move comes from a remote source, animate it. If it
4283 isn't remote, it will have already been animated. */
4284 if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4285 AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4287 if (!pausing && appData.highlightLastMove) {
4288 SetHighlights(fromX, fromY, toX, toY);
4292 /* Start the clocks */
4293 whiteFlag = blackFlag = FALSE;
4294 appData.clockMode = !(basetime == 0 && increment == 0);
4296 ics_clock_paused = TRUE;
4298 } else if (ticking == 1) {
4299 ics_clock_paused = FALSE;
4301 if (gameMode == IcsIdle ||
4302 relation == RELATION_OBSERVING_STATIC ||
4303 relation == RELATION_EXAMINING ||
4305 DisplayBothClocks();
4309 /* Display opponents and material strengths */
4310 if (gameInfo.variant != VariantBughouse &&
4311 gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4312 if (tinyLayout || smallLayout) {
4313 if(gameInfo.variant == VariantNormal)
4314 sprintf(str, "%s(%d) %s(%d) {%d %d}",
4315 gameInfo.white, white_stren, gameInfo.black, black_stren,
4316 basetime, increment);
4318 sprintf(str, "%s(%d) %s(%d) {%d %d w%d}",
4319 gameInfo.white, white_stren, gameInfo.black, black_stren,
4320 basetime, increment, (int) gameInfo.variant);
4322 if(gameInfo.variant == VariantNormal)
4323 sprintf(str, "%s (%d) vs. %s (%d) {%d %d}",
4324 gameInfo.white, white_stren, gameInfo.black, black_stren,
4325 basetime, increment);
4327 sprintf(str, "%s (%d) vs. %s (%d) {%d %d %s}",
4328 gameInfo.white, white_stren, gameInfo.black, black_stren,
4329 basetime, increment, VariantName(gameInfo.variant));
4332 if (appData.debugMode) {
4333 fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
4338 /* Display the board */
4339 if (!pausing && !appData.noGUI) {
4341 if (appData.premove)
4343 ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
4344 ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
4345 ClearPremoveHighlights();
4347 j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
4348 DrawPosition(j, boards[currentMove]);
4350 DisplayMove(moveNum - 1);
4351 if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4352 !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4353 (gameMode == IcsPlayingBlack) && (WhiteOnMove(moveNum)) ) ) {
4354 if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4358 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4360 if(bookHit) { // [HGM] book: simulate book reply
4361 static char bookMove[MSG_SIZ]; // a bit generous?
4363 programStats.nodes = programStats.depth = programStats.time =
4364 programStats.score = programStats.got_only_move = 0;
4365 sprintf(programStats.movelist, "%s (xbook)", bookHit);
4367 strcpy(bookMove, "move ");
4368 strcat(bookMove, bookHit);
4369 HandleMachineMove(bookMove, &first);
4378 if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4379 ics_getting_history = H_REQUESTED;
4380 sprintf(buf, "%smoves %d\n", ics_prefix, ics_gamenum);
4386 AnalysisPeriodicEvent(force)
4389 if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4390 && !force) || !appData.periodicUpdates)
4393 /* Send . command to Crafty to collect stats */
4394 SendToProgram(".\n", &first);
4396 /* Don't send another until we get a response (this makes
4397 us stop sending to old Crafty's which don't understand
4398 the "." command (sending illegal cmds resets node count & time,
4399 which looks bad)) */
4400 programStats.ok_to_send = 0;
4403 void ics_update_width(new_width)
4406 ics_printf("set width %d\n", new_width);
4410 SendMoveToProgram(moveNum, cps)
4412 ChessProgramState *cps;
4416 if (cps->useUsermove) {
4417 SendToProgram("usermove ", cps);
4421 if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4422 int len = space - parseList[moveNum];
4423 memcpy(buf, parseList[moveNum], len);
4425 buf[len] = NULLCHAR;
4427 sprintf(buf, "%s\n", parseList[moveNum]);
4429 SendToProgram(buf, cps);
4431 if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4432 AlphaRank(moveList[moveNum], 4);
4433 SendToProgram(moveList[moveNum], cps);
4434 AlphaRank(moveList[moveNum], 4); // and back
4436 /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
4437 * the engine. It would be nice to have a better way to identify castle
4439 if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
4440 && cps->useOOCastle) {
4441 int fromX = moveList[moveNum][0] - AAA;
4442 int fromY = moveList[moveNum][1] - ONE;
4443 int toX = moveList[moveNum][2] - AAA;
4444 int toY = moveList[moveNum][3] - ONE;
4445 if((boards[moveNum][fromY][fromX] == WhiteKing
4446 && boards[moveNum][toY][toX] == WhiteRook)
4447 || (boards[moveNum][fromY][fromX] == BlackKing
4448 && boards[moveNum][toY][toX] == BlackRook)) {
4449 if(toX > fromX) SendToProgram("O-O\n", cps);
4450 else SendToProgram("O-O-O\n", cps);
4452 else SendToProgram(moveList[moveNum], cps);
4454 else SendToProgram(moveList[moveNum], cps);
4455 /* End of additions by Tord */
4458 /* [HGM] setting up the opening has brought engine in force mode! */
4459 /* Send 'go' if we are in a mode where machine should play. */
4460 if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4461 (gameMode == TwoMachinesPlay ||
4463 gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite ||
4465 gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4466 SendToProgram("go\n", cps);
4467 if (appData.debugMode) {
4468 fprintf(debugFP, "(extra)\n");
4471 setboardSpoiledMachineBlack = 0;
4475 SendMoveToICS(moveType, fromX, fromY, toX, toY)
4477 int fromX, fromY, toX, toY;
4479 char user_move[MSG_SIZ];
4483 sprintf(user_move, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4484 (int)moveType, fromX, fromY, toX, toY);
4485 DisplayError(user_move + strlen("say "), 0);
4487 case WhiteKingSideCastle:
4488 case BlackKingSideCastle:
4489 case WhiteQueenSideCastleWild:
4490 case BlackQueenSideCastleWild:
4492 case WhiteHSideCastleFR:
4493 case BlackHSideCastleFR:
4495 sprintf(user_move, "o-o\n");
4497 case WhiteQueenSideCastle:
4498 case BlackQueenSideCastle:
4499 case WhiteKingSideCastleWild:
4500 case BlackKingSideCastleWild:
4502 case WhiteASideCastleFR:
4503 case BlackASideCastleFR:
4505 sprintf(user_move, "o-o-o\n");
4507 case WhitePromotionQueen:
4508 case BlackPromotionQueen:
4509 case WhitePromotionRook:
4510 case BlackPromotionRook:
4511 case WhitePromotionBishop:
4512 case BlackPromotionBishop:
4513 case WhitePromotionKnight:
4514 case BlackPromotionKnight:
4515 case WhitePromotionKing:
4516 case BlackPromotionKing:
4517 case WhitePromotionChancellor:
4518 case BlackPromotionChancellor:
4519 case WhitePromotionArchbishop:
4520 case BlackPromotionArchbishop:
4521 if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
4522 sprintf(user_move, "%c%c%c%c=%c\n",
4523 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4524 PieceToChar(WhiteFerz));
4525 else if(gameInfo.variant == VariantGreat)
4526 sprintf(user_move, "%c%c%c%c=%c\n",
4527 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4528 PieceToChar(WhiteMan));
4530 sprintf(user_move, "%c%c%c%c=%c\n",
4531 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4532 PieceToChar(PromoPiece(moveType)));
4536 sprintf(user_move, "%c@%c%c\n",
4537 ToUpper(PieceToChar((ChessSquare) fromX)),
4538 AAA + toX, ONE + toY);
4541 case WhiteCapturesEnPassant:
4542 case BlackCapturesEnPassant:
4543 case IllegalMove: /* could be a variant we don't quite understand */
4544 sprintf(user_move, "%c%c%c%c\n",
4545 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4548 SendToICS(user_move);
4549 if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
4550 ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
4554 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
4559 if (rf == DROP_RANK) {
4560 sprintf(move, "%c@%c%c\n",
4561 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
4563 if (promoChar == 'x' || promoChar == NULLCHAR) {
4564 sprintf(move, "%c%c%c%c\n",
4565 AAA + ff, ONE + rf, AAA + ft, ONE + rt);
4567 sprintf(move, "%c%c%c%c%c\n",
4568 AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
4574 ProcessICSInitScript(f)
4579 while (fgets(buf, MSG_SIZ, f)) {
4580 SendToICSDelayed(buf,(long)appData.msLoginDelay);
4587 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
4589 AlphaRank(char *move, int n)
4591 // char *p = move, c; int x, y;
4593 if (appData.debugMode) {
4594 fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
4598 move[2]>='0' && move[2]<='9' &&
4599 move[3]>='a' && move[3]<='x' ) {
4601 move[2] = BOARD_RGHT -1 - (move[2]-'1') + AAA;
4602 move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4604 if(move[0]>='0' && move[0]<='9' &&
4605 move[1]>='a' && move[1]<='x' &&
4606 move[2]>='0' && move[2]<='9' &&
4607 move[3]>='a' && move[3]<='x' ) {
4608 /* input move, Shogi -> normal */
4609 move[0] = BOARD_RGHT -1 - (move[0]-'1') + AAA;
4610 move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
4611 move[2] = BOARD_RGHT -1 - (move[2]-'1') + AAA;
4612 move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4615 move[3]>='0' && move[3]<='9' &&
4616 move[2]>='a' && move[2]<='x' ) {
4618 move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4619 move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4622 move[0]>='a' && move[0]<='x' &&
4623 move[3]>='0' && move[3]<='9' &&
4624 move[2]>='a' && move[2]<='x' ) {
4625 /* output move, normal -> Shogi */
4626 move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
4627 move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
4628 move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4629 move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4630 if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
4632 if (appData.debugMode) {
4633 fprintf(debugFP, " out = '%s'\n", move);
4637 /* Parser for moves from gnuchess, ICS, or user typein box */
4639 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
4642 ChessMove *moveType;
4643 int *fromX, *fromY, *toX, *toY;
4646 if (appData.debugMode) {
4647 fprintf(debugFP, "move to parse: %s\n", move);
4649 *moveType = yylexstr(moveNum, move);
4651 switch (*moveType) {
4652 case WhitePromotionChancellor:
4653 case BlackPromotionChancellor:
4654 case WhitePromotionArchbishop:
4655 case BlackPromotionArchbishop:
4656 case WhitePromotionQueen:
4657 case BlackPromotionQueen:
4658 case WhitePromotionRook:
4659 case BlackPromotionRook:
4660 case WhitePromotionBishop:
4661 case BlackPromotionBishop:
4662 case WhitePromotionKnight:
4663 case BlackPromotionKnight:
4664 case WhitePromotionKing:
4665 case BlackPromotionKing:
4667 case WhiteCapturesEnPassant:
4668 case BlackCapturesEnPassant:
4669 case WhiteKingSideCastle:
4670 case WhiteQueenSideCastle:
4671 case BlackKingSideCastle:
4672 case BlackQueenSideCastle:
4673 case WhiteKingSideCastleWild:
4674 case WhiteQueenSideCastleWild:
4675 case BlackKingSideCastleWild:
4676 case BlackQueenSideCastleWild:
4677 /* Code added by Tord: */
4678 case WhiteHSideCastleFR:
4679 case WhiteASideCastleFR:
4680 case BlackHSideCastleFR:
4681 case BlackASideCastleFR:
4682 /* End of code added by Tord */
4683 case IllegalMove: /* bug or odd chess variant */
4684 *fromX = currentMoveString[0] - AAA;
4685 *fromY = currentMoveString[1] - ONE;
4686 *toX = currentMoveString[2] - AAA;
4687 *toY = currentMoveString[3] - ONE;
4688 *promoChar = currentMoveString[4];
4689 if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
4690 *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
4691 if (appData.debugMode) {
4692 fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
4694 *fromX = *fromY = *toX = *toY = 0;
4697 if (appData.testLegality) {
4698 return (*moveType != IllegalMove);
4700 return !(*fromX == *toX && *fromY == *toY) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
4701 WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
4706 *fromX = *moveType == WhiteDrop ?
4707 (int) CharToPiece(ToUpper(currentMoveString[0])) :
4708 (int) CharToPiece(ToLower(currentMoveString[0]));
4710 *toX = currentMoveString[2] - AAA;
4711 *toY = currentMoveString[3] - ONE;
4712 *promoChar = NULLCHAR;
4716 case ImpossibleMove:
4717 case (ChessMove) 0: /* end of file */
4726 if (appData.debugMode) {
4727 fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
4730 *fromX = *fromY = *toX = *toY = 0;
4731 *promoChar = NULLCHAR;
4739 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
4740 int fromX, fromY, toX, toY; char promoChar;
4745 endPV = forwardMostMove;
4747 while(*pv == ' ') pv++;
4748 if(*pv == '(') pv++; // first (ponder) move can be in parentheses
4749 valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
4750 if(appData.debugMode){
4751 fprintf(debugFP,"parsePV: %d %c%c%c%c '%s'\n", valid, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, pv);
4753 if(!valid && nr == 0 &&
4754 ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
4755 nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
4757 while(*pv && *pv++ != ' '); // skip what we parsed; assume space separators
4758 if(moveType == Comment) { valid++; continue; } // allow comments in PV
4760 if(endPV+1 > framePtr) break; // no space, truncate
4763 CopyBoard(boards[endPV], boards[endPV-1]);
4764 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
4765 moveList[endPV-1][0] = fromX + AAA;
4766 moveList[endPV-1][1] = fromY + ONE;
4767 moveList[endPV-1][2] = toX + AAA;
4768 moveList[endPV-1][3] = toY + ONE;
4769 parseList[endPV-1][0] = NULLCHAR;
4771 currentMove = endPV;
4772 if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
4773 SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
4774 moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
4775 DrawPosition(TRUE, boards[currentMove]);
4778 static int lastX, lastY;
4781 LoadMultiPV(int x, int y, char *buf, int index, int *start, int *end)
4785 if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
4786 lastX = x; lastY = y;
4787 while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
4789 while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
4791 while(buf[index] && buf[index] != '\n') index++;
4793 ParsePV(buf+startPV);
4794 *start = startPV; *end = index-1;
4799 LoadPV(int x, int y)
4800 { // called on right mouse click to load PV
4801 int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
4802 lastX = x; lastY = y;
4803 ParsePV(lastPV[which]); // load the PV of the thinking engine in the boards array.
4810 if(endPV < 0) return;
4812 currentMove = forwardMostMove;
4813 ClearPremoveHighlights();
4814 DrawPosition(TRUE, boards[currentMove]);
4818 MovePV(int x, int y, int h)
4819 { // step through PV based on mouse coordinates (called on mouse move)
4820 int margin = h>>3, step = 0;
4822 if(endPV < 0) return;
4823 // we must somehow check if right button is still down (might be released off board!)
4824 if(y < margin && (abs(x - lastX) > 6 || abs(y - lastY) > 6)) step = 1; else
4825 if(y > h - margin && (abs(x - lastX) > 6 || abs(y - lastY) > 6)) step = -1; else
4826 if( y > lastY + 6 ) step = -1; else if(y < lastY - 6) step = 1;
4828 lastX = x; lastY = y;
4829 if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
4830 currentMove += step;
4831 if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
4832 SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
4833 moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
4834 DrawPosition(FALSE, boards[currentMove]);
4838 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
4839 // All positions will have equal probability, but the current method will not provide a unique
4840 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
4846 int piecesLeft[(int)BlackPawn];
4847 int seed, nrOfShuffles;
4849 void GetPositionNumber()
4850 { // sets global variable seed
4853 seed = appData.defaultFrcPosition;
4854 if(seed < 0) { // randomize based on time for negative FRC position numbers
4855 for(i=0; i<50; i++) seed += random();
4856 seed = random() ^ random() >> 8 ^ random() << 8;
4857 if(seed<0) seed = -seed;
4861 int put(Board board, int pieceType, int rank, int n, int shade)
4862 // put the piece on the (n-1)-th empty squares of the given shade
4866 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
4867 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
4868 board[rank][i] = (ChessSquare) pieceType;
4869 squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
4871 piecesLeft[pieceType]--;
4879 void AddOnePiece(Board board, int pieceType, int rank, int shade)
4880 // calculate where the next piece goes, (any empty square), and put it there
4884 i = seed % squaresLeft[shade];
4885 nrOfShuffles *= squaresLeft[shade];
4886 seed /= squaresLeft[shade];
4887 put(board, pieceType, rank, i, shade);
4890 void AddTwoPieces(Board board, int pieceType, int rank)
4891 // calculate where the next 2 identical pieces go, (any empty square), and put it there
4893 int i, n=squaresLeft[ANY], j=n-1, k;
4895 k = n*(n-1)/2; // nr of possibilities, not counting permutations
4896 i = seed % k; // pick one
4899 while(i >= j) i -= j--;
4900 j = n - 1 - j; i += j;
4901 put(board, pieceType, rank, j, ANY);
4902 put(board, pieceType, rank, i, ANY);
4905 void SetUpShuffle(Board board, int number)
4909 GetPositionNumber(); nrOfShuffles = 1;
4911 squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
4912 squaresLeft[ANY] = BOARD_RGHT - BOARD_LEFT;
4913 squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
4915 for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
4917 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
4918 p = (int) board[0][i];
4919 if(p < (int) BlackPawn) piecesLeft[p] ++;
4920 board[0][i] = EmptySquare;
4923 if(PosFlags(0) & F_ALL_CASTLE_OK) {
4924 // shuffles restricted to allow normal castling put KRR first
4925 if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
4926 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
4927 else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
4928 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
4929 if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
4930 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
4931 if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
4932 put(board, WhiteRook, 0, 0, ANY);
4933 // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
4936 if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
4937 // only for even boards make effort to put pairs of colorbound pieces on opposite colors
4938 for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
4939 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
4940 while(piecesLeft[p] >= 2) {
4941 AddOnePiece(board, p, 0, LITE);
4942 AddOnePiece(board, p, 0, DARK);
4944 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
4947 for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
4948 // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
4949 // but we leave King and Rooks for last, to possibly obey FRC restriction
4950 if(p == (int)WhiteRook) continue;
4951 while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
4952 if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY); // add the odd piece
4955 // now everything is placed, except perhaps King (Unicorn) and Rooks
4957 if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
4958 // Last King gets castling rights
4959 while(piecesLeft[(int)WhiteUnicorn]) {
4960 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
4961 initialRights[2] = initialRights[5] = board[CASTLING][2] = board[CASTLING][5] = i;
4964 while(piecesLeft[(int)WhiteKing]) {
4965 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
4966 initialRights[2] = initialRights[5] = board[CASTLING][2] = board[CASTLING][5] = i;
4971 while(piecesLeft[(int)WhiteKing]) AddOnePiece(board, WhiteKing, 0, ANY);
4972 while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
4975 // Only Rooks can be left; simply place them all
4976 while(piecesLeft[(int)WhiteRook]) {
4977 i = put(board, WhiteRook, 0, 0, ANY);
4978 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
4981 initialRights[1] = initialRights[4] = board[CASTLING][1] = board[CASTLING][4] = i;
4983 initialRights[0] = initialRights[3] = board[CASTLING][0] = board[CASTLING][3] = i;
4986 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
4987 board[BOARD_HEIGHT-1][i] = (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
4990 if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
4993 int SetCharTable( char *table, const char * map )
4994 /* [HGM] moved here from winboard.c because of its general usefulness */
4995 /* Basically a safe strcpy that uses the last character as King */
4997 int result = FALSE; int NrPieces;
4999 if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
5000 && NrPieces >= 12 && !(NrPieces&1)) {
5001 int i; /* [HGM] Accept even length from 12 to 34 */
5003 for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
5004 for( i=0; i<NrPieces/2-1; i++ ) {
5006 table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
5008 table[(int) WhiteKing] = map[NrPieces/2-1];
5009 table[(int) BlackKing] = map[NrPieces-1];
5017 void Prelude(Board board)
5018 { // [HGM] superchess: random selection of exo-pieces
5019 int i, j, k; ChessSquare p;
5020 static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
5022 GetPositionNumber(); // use FRC position number
5024 if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
5025 SetCharTable(pieceToChar, appData.pieceToCharTable);
5026 for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
5027 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
5030 j = seed%4; seed /= 4;
5031 p = board[0][BOARD_LEFT+j]; board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5032 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
5033 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
5034 j = seed%3 + (seed%3 >= j); seed /= 3;
5035 p = board[0][BOARD_LEFT+j]; board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5036 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
5037 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
5038 j = seed%3; seed /= 3;
5039 p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5040 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
5041 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
5042 j = seed%2 + (seed%2 >= j); seed /= 2;
5043 p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5044 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
5045 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
5046 j = seed%4; seed /= 4; put(board, exoPieces[3], 0, j, ANY);
5047 j = seed%3; seed /= 3; put(board, exoPieces[2], 0, j, ANY);
5048 j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
5049 put(board, exoPieces[0], 0, 0, ANY);
5050 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
5054 InitPosition(redraw)
5057 ChessSquare (* pieces)[BOARD_FILES];
5058 int i, j, pawnRow, overrule,
5059 oldx = gameInfo.boardWidth,
5060 oldy = gameInfo.boardHeight,
5061 oldh = gameInfo.holdingsWidth,
5062 oldv = gameInfo.variant;
5064 if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
5066 /* [AS] Initialize pv info list [HGM] and game status */
5068 for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
5069 pvInfoList[i].depth = 0;
5070 boards[i][EP_STATUS] = EP_NONE;
5071 for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
5074 initialRulePlies = 0; /* 50-move counter start */
5076 castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
5077 castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
5081 /* [HGM] logic here is completely changed. In stead of full positions */
5082 /* the initialized data only consist of the two backranks. The switch */
5083 /* selects which one we will use, which is than copied to the Board */
5084 /* initialPosition, which for the rest is initialized by Pawns and */
5085 /* empty squares. This initial position is then copied to boards[0], */
5086 /* possibly after shuffling, so that it remains available. */
5088 gameInfo.holdingsWidth = 0; /* default board sizes */
5089 gameInfo.boardWidth = 8;
5090 gameInfo.boardHeight = 8;
5091 gameInfo.holdingsSize = 0;
5092 nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
5093 for(i=0; i<BOARD_FILES-2; i++)
5094 initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
5095 initialPosition[EP_STATUS] = EP_NONE;
5096 SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
5098 switch (gameInfo.variant) {
5099 case VariantFischeRandom:
5100 shuffleOpenings = TRUE;
5104 case VariantShatranj:
5105 pieces = ShatranjArray;
5106 nrCastlingRights = 0;
5107 SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
5110 pieces = makrukArray;
5111 nrCastlingRights = 0;
5112 startedFromSetupPosition = TRUE;
5113 SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
5115 case VariantTwoKings:
5116 pieces = twoKingsArray;
5118 case VariantCapaRandom:
5119 shuffleOpenings = TRUE;
5120 case VariantCapablanca:
5121 pieces = CapablancaArray;
5122 gameInfo.boardWidth = 10;
5123 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5126 pieces = GothicArray;
5127 gameInfo.boardWidth = 10;
5128 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5131 pieces = JanusArray;
5132 gameInfo.boardWidth = 10;
5133 SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
5134 nrCastlingRights = 6;
5135 initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5136 initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5137 initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
5138 initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5139 initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5140 initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
5143 pieces = FalconArray;
5144 gameInfo.boardWidth = 10;
5145 SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
5147 case VariantXiangqi:
5148 pieces = XiangqiArray;
5149 gameInfo.boardWidth = 9;
5150 gameInfo.boardHeight = 10;
5151 nrCastlingRights = 0;
5152 SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
5155 pieces = ShogiArray;
5156 gameInfo.boardWidth = 9;
5157 gameInfo.boardHeight = 9;
5158 gameInfo.holdingsSize = 7;
5159 nrCastlingRights = 0;
5160 SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
5162 case VariantCourier:
5163 pieces = CourierArray;
5164 gameInfo.boardWidth = 12;
5165 nrCastlingRights = 0;
5166 SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
5168 case VariantKnightmate:
5169 pieces = KnightmateArray;
5170 SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
5173 pieces = fairyArray;
5174 SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
5177 pieces = GreatArray;
5178 gameInfo.boardWidth = 10;
5179 SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
5180 gameInfo.holdingsSize = 8;
5184 SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
5185 gameInfo.holdingsSize = 8;
5186 startedFromSetupPosition = TRUE;
5188 case VariantCrazyhouse:
5189 case VariantBughouse:
5191 SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
5192 gameInfo.holdingsSize = 5;
5194 case VariantWildCastle:
5196 /* !!?shuffle with kings guaranteed to be on d or e file */
5197 shuffleOpenings = 1;
5199 case VariantNoCastle:
5201 nrCastlingRights = 0;
5202 /* !!?unconstrained back-rank shuffle */
5203 shuffleOpenings = 1;
5208 if(appData.NrFiles >= 0) {
5209 if(gameInfo.boardWidth != appData.NrFiles) overrule++;
5210 gameInfo.boardWidth = appData.NrFiles;
5212 if(appData.NrRanks >= 0) {
5213 gameInfo.boardHeight = appData.NrRanks;
5215 if(appData.holdingsSize >= 0) {
5216 i = appData.holdingsSize;
5217 if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
5218 gameInfo.holdingsSize = i;
5220 if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
5221 if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
5222 DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
5224 pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
5225 if(pawnRow < 1) pawnRow = 1;
5226 if(gameInfo.variant == VariantMakruk) pawnRow = 2;
5228 /* User pieceToChar list overrules defaults */
5229 if(appData.pieceToCharTable != NULL)
5230 SetCharTable(pieceToChar, appData.pieceToCharTable);
5232 for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
5234 if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
5235 s = (ChessSquare) 0; /* account holding counts in guard band */
5236 for( i=0; i<BOARD_HEIGHT; i++ )
5237 initialPosition[i][j] = s;
5239 if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
5240 initialPosition[0][j] = pieces[0][j-gameInfo.holdingsWidth];
5241 initialPosition[pawnRow][j] = WhitePawn;
5242 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = BlackPawn;
5243 if(gameInfo.variant == VariantXiangqi) {
5245 initialPosition[pawnRow][j] =
5246 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
5247 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
5248 initialPosition[2][j] = WhiteCannon;
5249 initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
5253 initialPosition[BOARD_HEIGHT-1][j] = pieces[1][j-gameInfo.holdingsWidth];
5255 if( (gameInfo.variant == VariantShogi) && !overrule ) {
5258 initialPosition[1][j] = WhiteBishop;
5259 initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
5261 initialPosition[1][j] = WhiteRook;
5262 initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
5265 if( nrCastlingRights == -1) {
5266 /* [HGM] Build normal castling rights (must be done after board sizing!) */
5267 /* This sets default castling rights from none to normal corners */
5268 /* Variants with other castling rights must set them themselves above */
5269 nrCastlingRights = 6;
5271 initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5272 initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5273 initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
5274 initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5275 initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5276 initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
5279 if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
5280 if(gameInfo.variant == VariantGreat) { // promotion commoners
5281 initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
5282 initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
5283 initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
5284 initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
5286 if (appData.debugMode) {
5287 fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
5289 if(shuffleOpenings) {
5290 SetUpShuffle(initialPosition, appData.defaultFrcPosition);
5291 startedFromSetupPosition = TRUE;
5293 if(startedFromPositionFile) {
5294 /* [HGM] loadPos: use PositionFile for every new game */
5295 CopyBoard(initialPosition, filePosition);
5296 for(i=0; i<nrCastlingRights; i++)
5297 initialRights[i] = filePosition[CASTLING][i];
5298 startedFromSetupPosition = TRUE;
5301 CopyBoard(boards[0], initialPosition);
5303 if(oldx != gameInfo.boardWidth ||
5304 oldy != gameInfo.boardHeight ||
5305 oldh != gameInfo.holdingsWidth
5307 || oldv == VariantGothic || // For licensing popups
5308 gameInfo.variant == VariantGothic
5311 || oldv == VariantFalcon ||
5312 gameInfo.variant == VariantFalcon
5315 InitDrawingSizes(-2 ,0);
5318 DrawPosition(TRUE, boards[currentMove]);
5322 SendBoard(cps, moveNum)
5323 ChessProgramState *cps;
5326 char message[MSG_SIZ];
5328 if (cps->useSetboard) {
5329 char* fen = PositionToFEN(moveNum, cps->fenOverride);
5330 sprintf(message, "setboard %s\n", fen);
5331 SendToProgram(message, cps);
5337 /* Kludge to set black to move, avoiding the troublesome and now
5338 * deprecated "black" command.
5340 if (!WhiteOnMove(moveNum)) SendToProgram("a2a3\n", cps);
5342 SendToProgram("edit\n", cps);
5343 SendToProgram("#\n", cps);
5344 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
5345 bp = &boards[moveNum][i][BOARD_LEFT];
5346 for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
5347 if ((int) *bp < (int) BlackPawn) {
5348 sprintf(message, "%c%c%c\n", PieceToChar(*bp),
5350 if(message[0] == '+' || message[0] == '~') {
5351 sprintf(message, "%c%c%c+\n",
5352 PieceToChar((ChessSquare)(DEMOTED *bp)),
5355 if(cps->alphaRank) { /* [HGM] shogi: translate coords */
5356 message[1] = BOARD_RGHT - 1 - j + '1';
5357 message[2] = BOARD_HEIGHT - 1 - i + 'a';
5359 SendToProgram(message, cps);
5364 SendToProgram("c\n", cps);
5365 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
5366 bp = &boards[moveNum][i][BOARD_LEFT];
5367 for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
5368 if (((int) *bp != (int) EmptySquare)
5369 && ((int) *bp >= (int) BlackPawn)) {
5370 sprintf(message, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
5372 if(message[0] == '+' || message[0] == '~') {
5373 sprintf(message, "%c%c%c+\n",
5374 PieceToChar((ChessSquare)(DEMOTED *bp)),
5377 if(cps->alphaRank) { /* [HGM] shogi: translate coords */
5378 message[1] = BOARD_RGHT - 1 - j + '1';
5379 message[2] = BOARD_HEIGHT - 1 - i + 'a';
5381 SendToProgram(message, cps);
5386 SendToProgram(".\n", cps);
5388 setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
5391 static int autoQueen; // [HGM] oneclick
5394 HasPromotionChoice(int fromX, int fromY, int toX, int toY, char *promoChoice)
5396 /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
5397 /* [HGM] add Shogi promotions */
5398 int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
5403 if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
5404 if(toX < BOARD_LEFT || toX >= BOARD_RGHT) return FALSE; // move into holdings
5406 if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
5407 !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
5410 piece = boards[currentMove][fromY][fromX];
5411 if(gameInfo.variant == VariantShogi) {
5412 promotionZoneSize = 3;
5413 highestPromotingPiece = (int)WhiteFerz;
5414 } else if(gameInfo.variant == VariantMakruk) {
5415 promotionZoneSize = 3;
5418 // next weed out all moves that do not touch the promotion zone at all
5419 if((int)piece >= BlackPawn) {
5420 if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
5422 highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
5424 if( toY < BOARD_HEIGHT - promotionZoneSize &&
5425 fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
5428 if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
5430 // weed out mandatory Shogi promotions
5431 if(gameInfo.variant == VariantShogi) {
5432 if(piece >= BlackPawn) {
5433 if(toY == 0 && piece == BlackPawn ||
5434 toY == 0 && piece == BlackQueen ||
5435 toY <= 1 && piece == BlackKnight) {
5440 if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
5441 toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
5442 toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
5449 // weed out obviously illegal Pawn moves
5450 if(appData.testLegality && (piece == WhitePawn || piece == BlackPawn) ) {
5451 if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
5452 if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
5453 if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
5454 if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
5455 // note we are not allowed to test for valid (non-)capture, due to premove
5458 // we either have a choice what to promote to, or (in Shogi) whether to promote
5459 if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk) {
5460 *promoChoice = PieceToChar(BlackFerz); // no choice
5463 if(autoQueen) { // predetermined
5464 if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantLosers)
5465 *promoChoice = PieceToChar(BlackKing); // in Suicide Q is the last thing we want
5466 else *promoChoice = PieceToChar(BlackQueen);
5470 // suppress promotion popup on illegal moves that are not premoves
5471 premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
5472 gameMode == IcsPlayingBlack && WhiteOnMove(currentMove);
5473 if(appData.testLegality && !premove) {
5474 moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5475 fromY, fromX, toY, toX, NULLCHAR);
5476 if(moveType != WhitePromotionQueen && moveType != BlackPromotionQueen &&
5477 moveType != WhitePromotionKnight && moveType != BlackPromotionKnight)
5485 InPalace(row, column)
5487 { /* [HGM] for Xiangqi */
5488 if( (row < 3 || row > BOARD_HEIGHT-4) &&
5489 column < (BOARD_WIDTH + 4)/2 &&
5490 column > (BOARD_WIDTH - 5)/2 ) return TRUE;
5495 PieceForSquare (x, y)
5499 if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
5502 return boards[currentMove][y][x];
5506 OKToStartUserMove(x, y)
5509 ChessSquare from_piece;
5512 if (matchMode) return FALSE;
5513 if (gameMode == EditPosition) return TRUE;
5515 if (x >= 0 && y >= 0)
5516 from_piece = boards[currentMove][y][x];
5518 from_piece = EmptySquare;
5520 if (from_piece == EmptySquare) return FALSE;
5522 white_piece = (int)from_piece >= (int)WhitePawn &&
5523 (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
5526 case PlayFromGameFile:
5528 case TwoMachinesPlay:
5536 case MachinePlaysWhite:
5537 case IcsPlayingBlack:
5538 if (appData.zippyPlay) return FALSE;
5540 DisplayMoveError(_("You are playing Black"));
5545 case MachinePlaysBlack:
5546 case IcsPlayingWhite:
5547 if (appData.zippyPlay) return FALSE;
5549 DisplayMoveError(_("You are playing White"));
5555 if (!white_piece && WhiteOnMove(currentMove)) {
5556 DisplayMoveError(_("It is White's turn"));
5559 if (white_piece && !WhiteOnMove(currentMove)) {
5560 DisplayMoveError(_("It is Black's turn"));
5563 if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
5564 /* Editing correspondence game history */
5565 /* Could disallow this or prompt for confirmation */
5570 case BeginningOfGame:
5571 if (appData.icsActive) return FALSE;
5572 if (!appData.noChessProgram) {
5574 DisplayMoveError(_("You are playing White"));
5581 if (!white_piece && WhiteOnMove(currentMove)) {
5582 DisplayMoveError(_("It is White's turn"));
5585 if (white_piece && !WhiteOnMove(currentMove)) {
5586 DisplayMoveError(_("It is Black's turn"));
5595 if (currentMove != forwardMostMove && gameMode != AnalyzeMode
5596 && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
5597 && gameMode != AnalyzeFile && gameMode != Training) {
5598 DisplayMoveError(_("Displayed position is not current"));
5605 OnlyMove(int *x, int *y, Boolean captures) {
5606 DisambiguateClosure cl;
5607 if (appData.zippyPlay) return FALSE;
5609 case MachinePlaysBlack:
5610 case IcsPlayingWhite:
5611 case BeginningOfGame:
5612 if(!WhiteOnMove(currentMove)) return FALSE;
5614 case MachinePlaysWhite:
5615 case IcsPlayingBlack:
5616 if(WhiteOnMove(currentMove)) return FALSE;
5621 cl.pieceIn = EmptySquare;
5626 cl.promoCharIn = NULLCHAR;
5627 Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
5628 if( cl.kind == NormalMove ||
5629 cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
5630 cl.kind == WhitePromotionQueen || cl.kind == BlackPromotionQueen ||
5631 cl.kind == WhitePromotionKnight || cl.kind == BlackPromotionKnight ||
5632 cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
5639 if(cl.kind != ImpossibleMove) return FALSE;
5640 cl.pieceIn = EmptySquare;
5645 cl.promoCharIn = NULLCHAR;
5646 Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
5647 if( cl.kind == NormalMove ||
5648 cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
5649 cl.kind == WhitePromotionQueen || cl.kind == BlackPromotionQueen ||
5650 cl.kind == WhitePromotionKnight || cl.kind == BlackPromotionKnight ||
5651 cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
5656 autoQueen = TRUE; // act as if autoQueen on when we click to-square
5662 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
5663 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
5664 int lastLoadGameUseList = FALSE;
5665 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
5666 ChessMove lastLoadGameStart = (ChessMove) 0;
5669 UserMoveTest(fromX, fromY, toX, toY, promoChar, captureOwn)
5670 int fromX, fromY, toX, toY;
5675 ChessSquare pdown, pup;
5677 /* Check if the user is playing in turn. This is complicated because we
5678 let the user "pick up" a piece before it is his turn. So the piece he
5679 tried to pick up may have been captured by the time he puts it down!
5680 Therefore we use the color the user is supposed to be playing in this
5681 test, not the color of the piece that is currently on the starting
5682 square---except in EditGame mode, where the user is playing both
5683 sides; fortunately there the capture race can't happen. (It can
5684 now happen in IcsExamining mode, but that's just too bad. The user
5685 will get a somewhat confusing message in that case.)
5689 case PlayFromGameFile:
5691 case TwoMachinesPlay:
5695 /* We switched into a game mode where moves are not accepted,
5696 perhaps while the mouse button was down. */
5697 return ImpossibleMove;
5699 case MachinePlaysWhite:
5700 /* User is moving for Black */
5701 if (WhiteOnMove(currentMove)) {
5702 DisplayMoveError(_("It is White's turn"));
5703 return ImpossibleMove;
5707 case MachinePlaysBlack:
5708 /* User is moving for White */
5709 if (!WhiteOnMove(currentMove)) {
5710 DisplayMoveError(_("It is Black's turn"));
5711 return ImpossibleMove;
5717 case BeginningOfGame:
5720 if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
5721 (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
5722 /* User is moving for Black */
5723 if (WhiteOnMove(currentMove)) {
5724 DisplayMoveError(_("It is White's turn"));
5725 return ImpossibleMove;
5728 /* User is moving for White */
5729 if (!WhiteOnMove(currentMove)) {
5730 DisplayMoveError(_("It is Black's turn"));
5731 return ImpossibleMove;
5736 case IcsPlayingBlack:
5737 /* User is moving for Black */
5738 if (WhiteOnMove(currentMove)) {
5739 if (!appData.premove) {
5740 DisplayMoveError(_("It is White's turn"));
5741 } else if (toX >= 0 && toY >= 0) {
5744 premoveFromX = fromX;
5745 premoveFromY = fromY;
5746 premovePromoChar = promoChar;
5748 if (appData.debugMode)
5749 fprintf(debugFP, "Got premove: fromX %d,"
5750 "fromY %d, toX %d, toY %d\n",
5751 fromX, fromY, toX, toY);
5753 return ImpossibleMove;
5757 case IcsPlayingWhite:
5758 /* User is moving for White */
5759 if (!WhiteOnMove(currentMove)) {
5760 if (!appData.premove) {
5761 DisplayMoveError(_("It is Black's turn"));
5762 } else if (toX >= 0 && toY >= 0) {
5765 premoveFromX = fromX;
5766 premoveFromY = fromY;
5767 premovePromoChar = promoChar;
5769 if (appData.debugMode)
5770 fprintf(debugFP, "Got premove: fromX %d,"
5771 "fromY %d, toX %d, toY %d\n",
5772 fromX, fromY, toX, toY);
5774 return ImpossibleMove;
5782 /* EditPosition, empty square, or different color piece;
5783 click-click move is possible */
5784 if (toX == -2 || toY == -2) {
5785 boards[0][fromY][fromX] = EmptySquare;
5786 return AmbiguousMove;
5787 } else if (toX >= 0 && toY >= 0) {
5788 boards[0][toY][toX] = boards[0][fromY][fromX];
5789 if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
5790 if(boards[0][fromY][0] != EmptySquare) {
5791 if(boards[0][fromY][1]) boards[0][fromY][1]--;
5792 if(boards[0][fromY][1] == 0) boards[0][fromY][0] = EmptySquare;
5795 if(fromX == BOARD_RGHT+1) {
5796 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
5797 if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
5798 if(boards[0][fromY][BOARD_WIDTH-2] == 0) boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
5801 boards[0][fromY][fromX] = EmptySquare;
5802 return AmbiguousMove;
5804 return ImpossibleMove;
5807 if(toX < 0 || toY < 0) return ImpossibleMove;
5808 pdown = boards[currentMove][fromY][fromX];
5809 pup = boards[currentMove][toY][toX];
5811 /* [HGM] If move started in holdings, it means a drop */
5812 if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) {
5813 if( pup != EmptySquare ) return ImpossibleMove;
5814 if(appData.testLegality) {
5815 /* it would be more logical if LegalityTest() also figured out
5816 * which drops are legal. For now we forbid pawns on back rank.
5817 * Shogi is on its own here...
5819 if( (pdown == WhitePawn || pdown == BlackPawn) &&
5820 (toY == 0 || toY == BOARD_HEIGHT -1 ) )
5821 return(ImpossibleMove); /* no pawn drops on 1st/8th */
5823 return WhiteDrop; /* Not needed to specify white or black yet */
5826 /* [HGM] always test for legality, to get promotion info */
5827 moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5828 fromY, fromX, toY, toX, promoChar);
5829 /* [HGM] but possibly ignore an IllegalMove result */
5830 if (appData.testLegality) {
5831 if (moveType == IllegalMove || moveType == ImpossibleMove) {
5832 DisplayMoveError(_("Illegal move"));
5833 return ImpossibleMove;
5838 /* [HGM] <popupFix> in stead of calling FinishMove directly, this
5839 function is made into one that returns an OK move type if FinishMove
5840 should be called. This to give the calling driver routine the
5841 opportunity to finish the userMove input with a promotion popup,
5842 without bothering the user with this for invalid or illegal moves */
5844 /* FinishMove(moveType, fromX, fromY, toX, toY, promoChar); */
5847 /* Common tail of UserMoveEvent and DropMenuEvent */
5849 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
5851 int fromX, fromY, toX, toY;
5852 /*char*/int promoChar;
5856 if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) && promoChar != NULLCHAR) {
5857 // [HGM] superchess: suppress promotions to non-available piece
5858 int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
5859 if(WhiteOnMove(currentMove)) {
5860 if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
5862 if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
5866 /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
5867 move type in caller when we know the move is a legal promotion */
5868 if(moveType == NormalMove && promoChar)
5869 moveType = PromoCharToMoveType(WhiteOnMove(currentMove), promoChar);
5871 /* [HGM] convert drag-and-drop piece drops to standard form */
5872 if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ){
5873 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
5874 if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n",
5875 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
5876 // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
5877 if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
5878 fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
5879 while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++;
5883 /* [HGM] <popupFix> The following if has been moved here from
5884 UserMoveEvent(). Because it seemed to belong here (why not allow
5885 piece drops in training games?), and because it can only be
5886 performed after it is known to what we promote. */
5887 if (gameMode == Training) {
5888 /* compare the move played on the board to the next move in the
5889 * game. If they match, display the move and the opponent's response.
5890 * If they don't match, display an error message.
5894 CopyBoard(testBoard, boards[currentMove]);
5895 ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
5897 if (CompareBoards(testBoard, boards[currentMove+1])) {
5898 ForwardInner(currentMove+1);
5900 /* Autoplay the opponent's response.
5901 * if appData.animate was TRUE when Training mode was entered,
5902 * the response will be animated.
5904 saveAnimate = appData.animate;
5905 appData.animate = animateTraining;
5906 ForwardInner(currentMove+1);
5907 appData.animate = saveAnimate;
5909 /* check for the end of the game */
5910 if (currentMove >= forwardMostMove) {
5911 gameMode = PlayFromGameFile;
5913 SetTrainingModeOff();
5914 DisplayInformation(_("End of game"));
5917 DisplayError(_("Incorrect move"), 0);
5922 /* Ok, now we know that the move is good, so we can kill
5923 the previous line in Analysis Mode */
5924 if ((gameMode == AnalyzeMode || gameMode == EditGame)
5925 && currentMove < forwardMostMove) {
5926 PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
5929 /* If we need the chess program but it's dead, restart it */
5930 ResurrectChessProgram();
5932 /* A user move restarts a paused game*/
5936 thinkOutput[0] = NULLCHAR;
5938 MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
5940 if(Adjudicate(NULL)) return 1; // [HGM] adjudicate: take care of automtic game end
5942 if (gameMode == BeginningOfGame) {
5943 if (appData.noChessProgram) {
5944 gameMode = EditGame;
5948 gameMode = MachinePlaysBlack;
5951 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
5953 if (first.sendName) {
5954 sprintf(buf, "name %s\n", gameInfo.white);
5955 SendToProgram(buf, &first);
5962 /* Relay move to ICS or chess engine */
5963 if (appData.icsActive) {
5964 if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
5965 gameMode == IcsExamining) {
5966 if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
5967 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
5969 SendMoveToICS(moveType, fromX, fromY, toX, toY);
5971 // also send plain move, in case ICS does not understand atomic claims
5972 SendMoveToICS(moveType, fromX, fromY, toX, toY);
5976 if (first.sendTime && (gameMode == BeginningOfGame ||
5977 gameMode == MachinePlaysWhite ||
5978 gameMode == MachinePlaysBlack)) {
5979 SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
5981 if (gameMode != EditGame && gameMode != PlayFromGameFile) {
5982 // [HGM] book: if program might be playing, let it use book
5983 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
5984 first.maybeThinking = TRUE;
5985 } else SendMoveToProgram(forwardMostMove-1, &first);
5986 if (currentMove == cmailOldMove + 1) {
5987 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
5991 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5995 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
6001 if (WhiteOnMove(currentMove)) {
6002 GameEnds(BlackWins, "Black mates", GE_PLAYER);
6004 GameEnds(WhiteWins, "White mates", GE_PLAYER);
6008 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
6013 case MachinePlaysBlack:
6014 case MachinePlaysWhite:
6015 /* disable certain menu options while machine is thinking */
6016 SetMachineThinkingEnables();
6023 userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
6025 if(bookHit) { // [HGM] book: simulate book reply
6026 static char bookMove[MSG_SIZ]; // a bit generous?
6028 programStats.nodes = programStats.depth = programStats.time =
6029 programStats.score = programStats.got_only_move = 0;
6030 sprintf(programStats.movelist, "%s (xbook)", bookHit);
6032 strcpy(bookMove, "move ");
6033 strcat(bookMove, bookHit);
6034 HandleMachineMove(bookMove, &first);
6040 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
6041 int fromX, fromY, toX, toY;
6044 /* [HGM] This routine was added to allow calling of its two logical
6045 parts from other modules in the old way. Before, UserMoveEvent()
6046 automatically called FinishMove() if the move was OK, and returned
6047 otherwise. I separated the two, in order to make it possible to
6048 slip a promotion popup in between. But that it always needs two
6049 calls, to the first part, (now called UserMoveTest() ), and to
6050 FinishMove if the first part succeeded. Calls that do not need
6051 to do anything in between, can call this routine the old way.
6053 ChessMove moveType = UserMoveTest(fromX, fromY, toX, toY, promoChar, FALSE);
6054 if(appData.debugMode) fprintf(debugFP, "moveType 4 = %d, promochar = %x\n", moveType, promoChar);
6055 if(moveType == AmbiguousMove)
6056 DrawPosition(FALSE, boards[currentMove]);
6057 else if(moveType != ImpossibleMove && moveType != Comment)
6058 FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
6062 Mark(board, flags, kind, rf, ff, rt, ft, closure)
6069 typedef char Markers[BOARD_RANKS][BOARD_FILES];
6070 Markers *m = (Markers *) closure;
6071 if(rf == fromY && ff == fromX)
6072 (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
6073 || kind == WhiteCapturesEnPassant
6074 || kind == BlackCapturesEnPassant);
6075 else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
6079 MarkTargetSquares(int clear)
6082 if(!appData.markers || !appData.highlightDragging ||
6083 !appData.testLegality || gameMode == EditPosition) return;
6085 for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) marker[y][x] = 0;
6088 GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker);
6089 if(PosFlags(0) & F_MANDATORY_CAPTURE) {
6090 for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
6092 for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
6095 DrawPosition(TRUE, NULL);
6098 void LeftClick(ClickType clickType, int xPix, int yPix)
6101 Boolean saveAnimate;
6102 static int second = 0, promotionChoice = 0;
6103 char promoChoice = NULLCHAR;
6105 if(appData.seekGraph && appData.icsActive && loggedOn &&
6106 (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
6107 SeekGraphClick(clickType, xPix, yPix, 0);
6111 if (clickType == Press) ErrorPopDown();
6112 MarkTargetSquares(1);
6114 x = EventToSquare(xPix, BOARD_WIDTH);
6115 y = EventToSquare(yPix, BOARD_HEIGHT);
6116 if (!flipView && y >= 0) {
6117 y = BOARD_HEIGHT - 1 - y;
6119 if (flipView && x >= 0) {
6120 x = BOARD_WIDTH - 1 - x;
6123 if(promotionChoice) { // we are waiting for a click to indicate promotion piece
6124 if(clickType == Release) return; // ignore upclick of click-click destination
6125 promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
6126 if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
6127 if(gameInfo.holdingsWidth &&
6128 (WhiteOnMove(currentMove)
6129 ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y > 0
6130 : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT-1) ) {
6131 // click in right holdings, for determining promotion piece
6132 ChessSquare p = boards[currentMove][y][x];
6133 if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
6134 if(p != EmptySquare) {
6135 FinishMove(NormalMove, fromX, fromY, toX, toY, ToLower(PieceToChar(p)));
6140 DrawPosition(FALSE, boards[currentMove]);
6144 /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
6145 if(clickType == Press
6146 && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
6147 || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
6148 || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
6151 autoQueen = appData.alwaysPromoteToQueen;
6154 if(!appData.oneClick || !OnlyMove(&x, &y, FALSE)) {
6155 if (clickType == Press) {
6157 if (OKToStartUserMove(x, y)) {
6161 MarkTargetSquares(0);
6162 DragPieceBegin(xPix, yPix);
6163 if (appData.highlightDragging) {
6164 SetHighlights(x, y, -1, -1);
6173 if (clickType == Press && gameMode != EditPosition) {
6178 // ignore off-board to clicks
6179 if(y < 0 || x < 0) return;
6181 /* Check if clicking again on the same color piece */
6182 fromP = boards[currentMove][fromY][fromX];
6183 toP = boards[currentMove][y][x];
6184 frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom;
6185 if ((WhitePawn <= fromP && fromP <= WhiteKing &&
6186 WhitePawn <= toP && toP <= WhiteKing &&
6187 !(fromP == WhiteKing && toP == WhiteRook && frc) &&
6188 !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
6189 (BlackPawn <= fromP && fromP <= BlackKing &&
6190 BlackPawn <= toP && toP <= BlackKing &&
6191 !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
6192 !(fromP == BlackKing && toP == BlackRook && frc))) {
6193 /* Clicked again on same color piece -- changed his mind */
6194 second = (x == fromX && y == fromY);
6195 if(!second || !OnlyMove(&x, &y, TRUE)) {
6196 if (appData.highlightDragging) {
6197 SetHighlights(x, y, -1, -1);
6201 if (OKToStartUserMove(x, y)) {
6204 MarkTargetSquares(0);
6205 DragPieceBegin(xPix, yPix);
6210 // ignore clicks on holdings
6211 if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
6214 if (clickType == Release && x == fromX && y == fromY) {
6215 DragPieceEnd(xPix, yPix);
6216 if (appData.animateDragging) {
6217 /* Undo animation damage if any */
6218 DrawPosition(FALSE, NULL);
6221 /* Second up/down in same square; just abort move */
6226 ClearPremoveHighlights();
6228 /* First upclick in same square; start click-click mode */
6229 SetHighlights(x, y, -1, -1);
6234 /* we now have a different from- and (possibly off-board) to-square */
6235 /* Completed move */
6238 saveAnimate = appData.animate;
6239 if (clickType == Press) {
6240 /* Finish clickclick move */
6241 if (appData.animate || appData.highlightLastMove) {
6242 SetHighlights(fromX, fromY, toX, toY);
6247 /* Finish drag move */
6248 if (appData.highlightLastMove) {
6249 SetHighlights(fromX, fromY, toX, toY);
6253 DragPieceEnd(xPix, yPix);
6254 /* Don't animate move and drag both */
6255 appData.animate = FALSE;
6258 // moves into holding are invalid for now (except in EditPosition, adapting to-square)
6259 if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
6260 ChessSquare piece = boards[currentMove][fromY][fromX];
6261 if(gameMode == EditPosition && piece != EmptySquare &&
6262 fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
6265 if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
6266 n = PieceToNumber(piece - (int)BlackPawn);
6267 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
6268 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
6269 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
6271 if(x == BOARD_RGHT+1 && piece < BlackPawn) {
6272 n = PieceToNumber(piece);
6273 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
6274 boards[currentMove][n][BOARD_WIDTH-1] = piece;
6275 boards[currentMove][n][BOARD_WIDTH-2]++;
6277 boards[currentMove][fromY][fromX] = EmptySquare;
6281 DrawPosition(TRUE, boards[currentMove]);
6285 // off-board moves should not be highlighted
6286 if(x < 0 || x < 0) ClearHighlights();
6288 if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice)) {
6289 SetHighlights(fromX, fromY, toX, toY);
6290 if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
6291 // [HGM] super: promotion to captured piece selected from holdings
6292 ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
6293 promotionChoice = TRUE;
6294 // kludge follows to temporarily execute move on display, without promoting yet
6295 boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
6296 boards[currentMove][toY][toX] = p;
6297 DrawPosition(FALSE, boards[currentMove]);
6298 boards[currentMove][fromY][fromX] = p; // take back, but display stays
6299 boards[currentMove][toY][toX] = q;
6300 DisplayMessage("Click in holdings to choose piece", "");
6305 UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
6306 if (!appData.highlightLastMove || gotPremove) ClearHighlights();
6307 if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
6310 appData.animate = saveAnimate;
6311 if (appData.animate || appData.animateDragging) {
6312 /* Undo animation damage if needed */
6313 DrawPosition(FALSE, NULL);
6317 int RightClick(ClickType action, int x, int y, int *fromX, int *fromY)
6318 { // front-end-free part taken out of PieceMenuPopup
6319 int whichMenu; int xSqr, ySqr;
6321 if(seekGraphUp) { // [HGM] seekgraph
6322 if(action == Press) SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
6323 if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
6327 if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
6328 && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
6329 if(action == Press) { flipView = !flipView; DrawPosition(TRUE, partnerBoard); partnerUp = TRUE; } else
6330 if(action == Release) { flipView = !flipView; DrawPosition(TRUE, boards[currentMove]); partnerUp = FALSE; }
6334 xSqr = EventToSquare(x, BOARD_WIDTH);
6335 ySqr = EventToSquare(y, BOARD_HEIGHT);
6336 if (action == Release) UnLoadPV(); // [HGM] pv
6337 if (action != Press) return -2; // return code to be ignored
6340 if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;
\r
6342 if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;
\r
6343 if (xSqr < 0 || ySqr < 0) return -1;
\r
6344 whichMenu = 0; // edit-position menu
6347 if(!appData.icsEngineAnalyze) return -1;
6348 case IcsPlayingWhite:
6349 case IcsPlayingBlack:
6350 if(!appData.zippyPlay) goto noZip;
6353 case MachinePlaysWhite:
6354 case MachinePlaysBlack:
6355 case TwoMachinesPlay: // [HGM] pv: use for showing PV
6356 if (!appData.dropMenu) {
6358 return 2; // flag front-end to grab mouse events
6360 if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
6361 gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
6364 if (xSqr < 0 || ySqr < 0) return -1;
6365 if (!appData.dropMenu || appData.testLegality &&
6366 gameInfo.variant != VariantBughouse &&
6367 gameInfo.variant != VariantCrazyhouse) return -1;
6368 whichMenu = 1; // drop menu
6374 if (((*fromX = xSqr) < 0) ||
6375 ((*fromY = ySqr) < 0)) {
6376 *fromX = *fromY = -1;
6380 *fromX = BOARD_WIDTH - 1 - *fromX;
6382 *fromY = BOARD_HEIGHT - 1 - *fromY;
6387 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
6389 // char * hint = lastHint;
6390 FrontEndProgramStats stats;
6392 stats.which = cps == &first ? 0 : 1;
6393 stats.depth = cpstats->depth;
6394 stats.nodes = cpstats->nodes;
6395 stats.score = cpstats->score;
6396 stats.time = cpstats->time;
6397 stats.pv = cpstats->movelist;
6398 stats.hint = lastHint;
6399 stats.an_move_index = 0;
6400 stats.an_move_count = 0;
6402 if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
6403 stats.hint = cpstats->move_name;
6404 stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
6405 stats.an_move_count = cpstats->nr_moves;
6408 if(stats.pv && stats.pv[0]) strcpy(lastPV[stats.which], stats.pv); // [HGM] pv: remember last PV of each
6410 SetProgramStats( &stats );
6414 Adjudicate(ChessProgramState *cps)
6415 { // [HGM] some adjudications useful with buggy engines
6416 // [HGM] adjudicate: made into separate routine, which now can be called after every move
6417 // In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
6418 // Actually ending the game is now based on the additional internal condition canAdjudicate.
6419 // Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
6420 int k, count = 0; static int bare = 1;
6421 ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
6422 Boolean canAdjudicate = !appData.icsActive;
6424 // most tests only when we understand the game, i.e. legality-checking on, and (for the time being) no piece drops
6425 if(gameInfo.holdingsSize == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
6426 if( appData.testLegality )
6427 { /* [HGM] Some more adjudications for obstinate engines */
6428 int NrWN=0, NrBN=0, NrWB=0, NrBB=0, NrWR=0, NrBR=0,
6429 NrWQ=0, NrBQ=0, NrW=0, NrK=0, bishopsColor = 0,
6430 NrPieces=0, NrPawns=0, PawnAdvance=0, i, j;
6431 static int moveCount = 6;
6433 char *reason = NULL;
6435 /* Count what is on board. */
6436 for(i=0; i<BOARD_HEIGHT; i++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
6437 { ChessSquare p = boards[forwardMostMove][i][j];
6441 { /* count B,N,R and other of each side */
6444 NrK++; break; // [HGM] atomic: count Kings
6448 case WhiteFerz: // [HGM] shatranj: kludge to mke it work in shatranj
6449 bishopsColor |= 1 << ((i^j)&1);
6454 case BlackFerz: // [HGM] shatranj: kludge to mke it work in shatranj
6455 bishopsColor |= 1 << ((i^j)&1);
6470 PawnAdvance += m; NrPawns++;
6472 NrPieces += (p != EmptySquare);
6473 NrW += ((int)p < (int)BlackPawn);
6474 if(gameInfo.variant == VariantXiangqi &&
6475 (p == WhiteFerz || p == WhiteAlfil || p == BlackFerz || p == BlackAlfil)) {
6476 NrPieces--; // [HGM] XQ: do not count purely defensive pieces
6477 NrW -= ((int)p < (int)BlackPawn);
6481 /* Some material-based adjudications that have to be made before stalemate test */
6482 if(gameInfo.variant == VariantAtomic && NrK < 2) {
6483 // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
6484 boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
6485 if(canAdjudicate && appData.checkMates) {
6487 SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
6488 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6489 GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
6490 "Xboard adjudication: King destroyed", GE_XBOARD );
6495 /* Bare King in Shatranj (loses) or Losers (wins) */
6496 if( NrW == 1 || NrPieces - NrW == 1) {
6497 if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
6498 boards[forwardMostMove][EP_STATUS] = EP_WINS; // mark as win, so it becomes claimable
6499 if(canAdjudicate && appData.checkMates) {
6501 SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
6502 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6503 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
6504 "Xboard adjudication: Bare king", GE_XBOARD );
6508 if( gameInfo.variant == VariantShatranj && --bare < 0)
6510 boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
6511 if(canAdjudicate && appData.checkMates) {
6512 /* but only adjudicate if adjudication enabled */
6514 SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
6515 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6516 GameEnds( NrW > 1 ? WhiteWins : NrPieces - NrW > 1 ? BlackWins : GameIsDrawn,
6517 "Xboard adjudication: Bare king", GE_XBOARD );
6524 // don't wait for engine to announce game end if we can judge ourselves
6525 switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
6527 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
6528 int i, checkCnt = 0; // (should really be done by making nr of checks part of game state)
6529 for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
6530 if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
6533 reason = "Xboard adjudication: 3rd check";
6534 boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
6544 reason = "Xboard adjudication: Stalemate";
6545 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
6546 boards[forwardMostMove][EP_STATUS] = EP_STALEMATE; // default result for stalemate is draw
6547 if(gameInfo.variant == VariantLosers || gameInfo.variant == VariantGiveaway) // [HGM] losers:
6548 boards[forwardMostMove][EP_STATUS] = EP_WINS; // in these variants stalemated is always a win
6549 else if(gameInfo.variant == VariantSuicide) // in suicide it depends
6550 boards[forwardMostMove][EP_STATUS] = NrW == NrPieces-NrW ? EP_STALEMATE :
6551 ((NrW < NrPieces-NrW) != WhiteOnMove(forwardMostMove) ?
6552 EP_CHECKMATE : EP_WINS);
6553 else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
6554 boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
6558 reason = "Xboard adjudication: Checkmate";
6559 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
6563 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
6565 result = GameIsDrawn; break;
6567 result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
6569 result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
6571 result = (ChessMove) 0;
6573 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
6575 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6576 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6577 GameEnds( result, reason, GE_XBOARD );
6581 /* Next absolutely insufficient mating material. */
6582 if( NrPieces == 2 || gameInfo.variant != VariantXiangqi &&
6583 gameInfo.variant != VariantShatranj && // [HGM] baring will remain possible
6584 (NrPieces == 3 && NrWN+NrBN+NrWB+NrBB == 1 ||
6585 NrPieces == NrBB+NrWB+2 && bishopsColor != 3)) // [HGM] all Bishops (Ferz!) same color
6586 { /* KBK, KNK, KK of KBKB with like Bishops */
6588 /* always flag draws, for judging claims */
6589 boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
6591 if(canAdjudicate && appData.materialDraws) {
6592 /* but only adjudicate them if adjudication enabled */
6593 if(engineOpponent) {
6594 SendToProgram("force\n", engineOpponent); // suppress reply
6595 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
6597 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6598 GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
6603 /* Then some trivial draws (only adjudicate, cannot be claimed) */
6605 ( NrWR == 1 && NrBR == 1 /* KRKR */
6606 || NrWQ==1 && NrBQ==1 /* KQKQ */
6607 || NrWN==2 || NrBN==2 /* KNNK */
6608 || NrWN+NrWB == 1 && NrBN+NrBB == 1 /* KBKN, KBKB, KNKN */
6610 if(canAdjudicate && --moveCount < 0 && appData.trivialDraws)
6611 { /* if the first 3 moves do not show a tactical win, declare draw */
6612 if(engineOpponent) {
6613 SendToProgram("force\n", engineOpponent); // suppress reply
6614 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6616 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6617 GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
6620 } else moveCount = 6;
6624 if (appData.debugMode) { int i;
6625 fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
6626 forwardMostMove, backwardMostMove, boards[backwardMostMove][EP_STATUS],
6627 appData.drawRepeats);
6628 for( i=forwardMostMove; i>=backwardMostMove; i-- )
6629 fprintf(debugFP, "%d ep=%d\n", i, (signed char)boards[i][EP_STATUS]);
6633 // Repetition draws and 50-move rule can be applied independently of legality testing
6635 /* Check for rep-draws */
6637 for(k = forwardMostMove-2;
6638 k>=backwardMostMove && k>=forwardMostMove-100 &&
6639 (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
6640 (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE;
6643 if(CompareBoards(boards[k], boards[forwardMostMove])) {
6644 /* compare castling rights */
6645 if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
6646 (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
6647 rights++; /* King lost rights, while rook still had them */
6648 if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
6649 if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
6650 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
6651 rights++; /* but at least one rook lost them */
6653 if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
6654 (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
6656 if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
6657 if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
6658 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
6661 if( canAdjudicate && rights == 0 && ++count > appData.drawRepeats-2
6662 && appData.drawRepeats > 1) {
6663 /* adjudicate after user-specified nr of repeats */
6664 if(engineOpponent) {
6665 SendToProgram("force\n", engineOpponent); // suppress reply
6666 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6668 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6669 if(gameInfo.variant == VariantXiangqi && appData.testLegality) {
6670 // [HGM] xiangqi: check for forbidden perpetuals
6671 int m, ourPerpetual = 1, hisPerpetual = 1;
6672 for(m=forwardMostMove; m>k; m-=2) {
6673 if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
6674 ourPerpetual = 0; // the current mover did not always check
6675 if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
6676 hisPerpetual = 0; // the opponent did not always check
6678 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
6679 ourPerpetual, hisPerpetual);
6680 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
6681 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
6682 "Xboard adjudication: perpetual checking", GE_XBOARD );
6685 if(hisPerpetual && !ourPerpetual) // he is checking us, but did not repeat yet
6686 break; // (or we would have caught him before). Abort repetition-checking loop.
6687 // Now check for perpetual chases
6688 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
6689 hisPerpetual = PerpetualChase(k, forwardMostMove);
6690 ourPerpetual = PerpetualChase(k+1, forwardMostMove);
6691 if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
6692 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
6693 "Xboard adjudication: perpetual chasing", GE_XBOARD );
6696 if(hisPerpetual && !ourPerpetual) // he is chasing us, but did not repeat yet
6697 break; // Abort repetition-checking loop.
6699 // if neither of us is checking or chasing all the time, or both are, it is draw
6701 GameEnds( GameIsDrawn, "Xboard adjudication: repetition draw", GE_XBOARD );
6704 if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
6705 boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
6709 /* Now we test for 50-move draws. Determine ply count */
6710 count = forwardMostMove;
6711 /* look for last irreversble move */
6712 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
6714 /* if we hit starting position, add initial plies */
6715 if( count == backwardMostMove )
6716 count -= initialRulePlies;
6717 count = forwardMostMove - count;
6719 boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
6720 /* this is used to judge if draw claims are legal */
6721 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
6722 if(engineOpponent) {
6723 SendToProgram("force\n", engineOpponent); // suppress reply
6724 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6726 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6727 GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
6731 /* if draw offer is pending, treat it as a draw claim
6732 * when draw condition present, to allow engines a way to
6733 * claim draws before making their move to avoid a race
6734 * condition occurring after their move
6736 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
6738 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
6739 p = "Draw claim: 50-move rule";
6740 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
6741 p = "Draw claim: 3-fold repetition";
6742 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
6743 p = "Draw claim: insufficient mating material";
6744 if( p != NULL && canAdjudicate) {
6745 if(engineOpponent) {
6746 SendToProgram("force\n", engineOpponent); // suppress reply
6747 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6749 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6750 GameEnds( GameIsDrawn, p, GE_XBOARD );
6755 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
6756 if(engineOpponent) {
6757 SendToProgram("force\n", engineOpponent); // suppress reply
6758 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6760 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6761 GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
6767 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
6768 { // [HGM] book: this routine intercepts moves to simulate book replies
6769 char *bookHit = NULL;
6771 //first determine if the incoming move brings opponent into his book
6772 if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
6773 bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
6774 if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
6775 if(bookHit != NULL && !cps->bookSuspend) {
6776 // make sure opponent is not going to reply after receiving move to book position
6777 SendToProgram("force\n", cps);
6778 cps->bookSuspend = TRUE; // flag indicating it has to be restarted
6780 if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
6781 // now arrange restart after book miss
6783 // after a book hit we never send 'go', and the code after the call to this routine
6784 // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
6786 if (cps->useUsermove) sprintf(buf, "usermove "); // sorry, no SAN yet :(
6787 sprintf(buf, "%s\n", bookHit); // force book move into program supposed to play it
6788 SendToProgram(buf, cps);
6789 if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
6790 } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
6791 SendToProgram("go\n", cps);
6792 cps->bookSuspend = FALSE; // after a 'go' we are never suspended
6793 } else { // 'go' might be sent based on 'firstMove' after this routine returns
6794 if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
6795 SendToProgram("go\n", cps);
6796 cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
6798 return bookHit; // notify caller of hit, so it can take action to send move to opponent
6802 ChessProgramState *savedState;
6803 void DeferredBookMove(void)
6805 if(savedState->lastPing != savedState->lastPong)
6806 ScheduleDelayedEvent(DeferredBookMove, 10);
6808 HandleMachineMove(savedMessage, savedState);
6812 HandleMachineMove(message, cps)
6814 ChessProgramState *cps;
6816 char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
6817 char realname[MSG_SIZ];
6818 int fromX, fromY, toX, toY;
6827 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
6829 * Kludge to ignore BEL characters
6831 while (*message == '\007') message++;
6834 * [HGM] engine debug message: ignore lines starting with '#' character
6836 if(cps->debug && *message == '#') return;
6839 * Look for book output
6841 if (cps == &first && bookRequested) {
6842 if (message[0] == '\t' || message[0] == ' ') {
6843 /* Part of the book output is here; append it */
6844 strcat(bookOutput, message);
6845 strcat(bookOutput, " \n");
6847 } else if (bookOutput[0] != NULLCHAR) {
6848 /* All of book output has arrived; display it */
6849 char *p = bookOutput;
6850 while (*p != NULLCHAR) {
6851 if (*p == '\t') *p = ' ';
6854 DisplayInformation(bookOutput);
6855 bookRequested = FALSE;
6856 /* Fall through to parse the current output */
6861 * Look for machine move.
6863 if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
6864 (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
6866 /* This method is only useful on engines that support ping */
6867 if (cps->lastPing != cps->lastPong) {
6868 if (gameMode == BeginningOfGame) {
6869 /* Extra move from before last new; ignore */
6870 if (appData.debugMode) {
6871 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
6874 if (appData.debugMode) {
6875 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
6876 cps->which, gameMode);
6879 SendToProgram("undo\n", cps);
6885 case BeginningOfGame:
6886 /* Extra move from before last reset; ignore */
6887 if (appData.debugMode) {
6888 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
6895 /* Extra move after we tried to stop. The mode test is
6896 not a reliable way of detecting this problem, but it's
6897 the best we can do on engines that don't support ping.
6899 if (appData.debugMode) {
6900 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
6901 cps->which, gameMode);
6903 SendToProgram("undo\n", cps);
6906 case MachinePlaysWhite:
6907 case IcsPlayingWhite:
6908 machineWhite = TRUE;
6911 case MachinePlaysBlack:
6912 case IcsPlayingBlack:
6913 machineWhite = FALSE;
6916 case TwoMachinesPlay:
6917 machineWhite = (cps->twoMachinesColor[0] == 'w');
6920 if (WhiteOnMove(forwardMostMove) != machineWhite) {
6921 if (appData.debugMode) {
6923 "Ignoring move out of turn by %s, gameMode %d"
6924 ", forwardMost %d\n",
6925 cps->which, gameMode, forwardMostMove);
6930 if (appData.debugMode) { int f = forwardMostMove;
6931 fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
6932 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
6933 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
6935 if(cps->alphaRank) AlphaRank(machineMove, 4);
6936 if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
6937 &fromX, &fromY, &toX, &toY, &promoChar)) {
6938 /* Machine move could not be parsed; ignore it. */
6939 sprintf(buf1, _("Illegal move \"%s\" from %s machine"),
6940 machineMove, cps->which);
6941 DisplayError(buf1, 0);
6942 sprintf(buf1, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
6943 machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
6944 if (gameMode == TwoMachinesPlay) {
6945 GameEnds(machineWhite ? BlackWins : WhiteWins,
6951 /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
6952 /* So we have to redo legality test with true e.p. status here, */
6953 /* to make sure an illegal e.p. capture does not slip through, */
6954 /* to cause a forfeit on a justified illegal-move complaint */
6955 /* of the opponent. */
6956 if( gameMode==TwoMachinesPlay && appData.testLegality
6957 && fromY != DROP_RANK /* [HGM] temporary; should still add legality test for drops */
6960 moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
6961 fromY, fromX, toY, toX, promoChar);
6962 if (appData.debugMode) {
6964 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
6965 boards[forwardMostMove][CASTLING][i], castlingRank[i]);
6966 fprintf(debugFP, "castling rights\n");
6968 if(moveType == IllegalMove) {
6969 sprintf(buf1, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
6970 machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
6971 GameEnds(machineWhite ? BlackWins : WhiteWins,
6974 } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
6975 /* [HGM] Kludge to handle engines that send FRC-style castling
6976 when they shouldn't (like TSCP-Gothic) */
6978 case WhiteASideCastleFR:
6979 case BlackASideCastleFR:
6981 currentMoveString[2]++;
6983 case WhiteHSideCastleFR:
6984 case BlackHSideCastleFR:
6986 currentMoveString[2]--;
6988 default: ; // nothing to do, but suppresses warning of pedantic compilers
6991 hintRequested = FALSE;
6992 lastHint[0] = NULLCHAR;
6993 bookRequested = FALSE;
6994 /* Program may be pondering now */
6995 cps->maybeThinking = TRUE;
6996 if (cps->sendTime == 2) cps->sendTime = 1;
6997 if (cps->offeredDraw) cps->offeredDraw--;
6999 /* currentMoveString is set as a side-effect of ParseOneMove */
7000 strcpy(machineMove, currentMoveString);
7001 strcat(machineMove, "\n");
7002 strcpy(moveList[forwardMostMove], machineMove);
7004 MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
7006 /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
7007 if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
7010 while( count < adjudicateLossPlies ) {
7011 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
7014 score = -score; /* Flip score for winning side */
7017 if( score > adjudicateLossThreshold ) {
7024 if( count >= adjudicateLossPlies ) {
7025 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7027 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7028 "Xboard adjudication",
7035 if(Adjudicate(cps)) return; // [HGM] adjudicate: for all automatic game ends
7038 if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
7040 if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
7041 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
7043 SendMoveToICS(moveType, fromX, fromY, toX, toY);
7045 SendMoveToICS(moveType, fromX, fromY, toX, toY);
7047 if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
7048 char buf[3*MSG_SIZ];
7050 sprintf(buf, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
7051 programStats.score / 100.,
7053 programStats.time / 100.,
7054 (unsigned int)programStats.nodes,
7055 (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
7056 programStats.movelist);
7058 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
7063 /* [AS] Save move info and clear stats for next move */
7064 pvInfoList[ forwardMostMove-1 ].score = programStats.score;
7065 pvInfoList[ forwardMostMove-1 ].depth = programStats.depth;
7066 pvInfoList[ forwardMostMove-1 ].time = programStats.time; // [HGM] PGNtime: take time from engine stats
7067 ClearProgramStats();
7068 thinkOutput[0] = NULLCHAR;
7069 hiddenThinkOutputState = 0;
7072 if (gameMode == TwoMachinesPlay) {
7073 /* [HGM] relaying draw offers moved to after reception of move */
7074 /* and interpreting offer as claim if it brings draw condition */
7075 if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
7076 SendToProgram("draw\n", cps->other);
7078 if (cps->other->sendTime) {
7079 SendTimeRemaining(cps->other,
7080 cps->other->twoMachinesColor[0] == 'w');
7082 bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
7083 if (firstMove && !bookHit) {
7085 if (cps->other->useColors) {
7086 SendToProgram(cps->other->twoMachinesColor, cps->other);
7088 SendToProgram("go\n", cps->other);
7090 cps->other->maybeThinking = TRUE;
7093 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7095 if (!pausing && appData.ringBellAfterMoves) {
7100 * Reenable menu items that were disabled while
7101 * machine was thinking
7103 if (gameMode != TwoMachinesPlay)
7104 SetUserThinkingEnables();
7106 // [HGM] book: after book hit opponent has received move and is now in force mode
7107 // force the book reply into it, and then fake that it outputted this move by jumping
7108 // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
7110 static char bookMove[MSG_SIZ]; // a bit generous?
7112 strcpy(bookMove, "move ");
7113 strcat(bookMove, bookHit);
7116 programStats.nodes = programStats.depth = programStats.time =
7117 programStats.score = programStats.got_only_move = 0;
7118 sprintf(programStats.movelist, "%s (xbook)", bookHit);
7120 if(cps->lastPing != cps->lastPong) {
7121 savedMessage = message; // args for deferred call
7123 ScheduleDelayedEvent(DeferredBookMove, 10);
7132 /* Set special modes for chess engines. Later something general
7133 * could be added here; for now there is just one kludge feature,
7134 * needed because Crafty 15.10 and earlier don't ignore SIGINT
7135 * when "xboard" is given as an interactive command.
7137 if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
7138 cps->useSigint = FALSE;
7139 cps->useSigterm = FALSE;
7141 if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
7142 ParseFeatures(message+8, cps);
7143 return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
7146 /* [HGM] Allow engine to set up a position. Don't ask me why one would
7147 * want this, I was asked to put it in, and obliged.
7149 if (!strncmp(message, "setboard ", 9)) {
7150 Board initial_position;
7152 GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
7154 if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
7155 DisplayError(_("Bad FEN received from engine"), 0);
7159 CopyBoard(boards[0], initial_position);
7160 initialRulePlies = FENrulePlies;
7161 if(blackPlaysFirst) gameMode = MachinePlaysWhite;
7162 else gameMode = MachinePlaysBlack;
7163 DrawPosition(FALSE, boards[currentMove]);
7169 * Look for communication commands
7171 if (!strncmp(message, "telluser ", 9)) {
7172 DisplayNote(message + 9);
7175 if (!strncmp(message, "tellusererror ", 14)) {
7177 DisplayError(message + 14, 0);
7180 if (!strncmp(message, "tellopponent ", 13)) {
7181 if (appData.icsActive) {
7183 snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
7187 DisplayNote(message + 13);
7191 if (!strncmp(message, "tellothers ", 11)) {
7192 if (appData.icsActive) {
7194 snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
7200 if (!strncmp(message, "tellall ", 8)) {
7201 if (appData.icsActive) {
7203 snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
7207 DisplayNote(message + 8);
7211 if (strncmp(message, "warning", 7) == 0) {
7212 /* Undocumented feature, use tellusererror in new code */
7213 DisplayError(message, 0);
7216 if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
7217 strcpy(realname, cps->tidy);
7218 strcat(realname, " query");
7219 AskQuestion(realname, buf2, buf1, cps->pr);
7222 /* Commands from the engine directly to ICS. We don't allow these to be
7223 * sent until we are logged on. Crafty kibitzes have been known to
7224 * interfere with the login process.
7227 if (!strncmp(message, "tellics ", 8)) {
7228 SendToICS(message + 8);
7232 if (!strncmp(message, "tellicsnoalias ", 15)) {
7233 SendToICS(ics_prefix);
7234 SendToICS(message + 15);
7238 /* The following are for backward compatibility only */
7239 if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
7240 !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
7241 SendToICS(ics_prefix);
7247 if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
7251 * If the move is illegal, cancel it and redraw the board.
7252 * Also deal with other error cases. Matching is rather loose
7253 * here to accommodate engines written before the spec.
7255 if (strncmp(message + 1, "llegal move", 11) == 0 ||
7256 strncmp(message, "Error", 5) == 0) {
7257 if (StrStr(message, "name") ||
7258 StrStr(message, "rating") || StrStr(message, "?") ||
7259 StrStr(message, "result") || StrStr(message, "board") ||
7260 StrStr(message, "bk") || StrStr(message, "computer") ||
7261 StrStr(message, "variant") || StrStr(message, "hint") ||
7262 StrStr(message, "random") || StrStr(message, "depth") ||
7263 StrStr(message, "accepted")) {
7266 if (StrStr(message, "protover")) {
7267 /* Program is responding to input, so it's apparently done
7268 initializing, and this error message indicates it is
7269 protocol version 1. So we don't need to wait any longer
7270 for it to initialize and send feature commands. */
7271 FeatureDone(cps, 1);
7272 cps->protocolVersion = 1;
7275 cps->maybeThinking = FALSE;
7277 if (StrStr(message, "draw")) {
7278 /* Program doesn't have "draw" command */
7279 cps->sendDrawOffers = 0;
7282 if (cps->sendTime != 1 &&
7283 (StrStr(message, "time") || StrStr(message, "otim"))) {
7284 /* Program apparently doesn't have "time" or "otim" command */
7288 if (StrStr(message, "analyze")) {
7289 cps->analysisSupport = FALSE;
7290 cps->analyzing = FALSE;
7292 sprintf(buf2, _("%s does not support analysis"), cps->tidy);
7293 DisplayError(buf2, 0);
7296 if (StrStr(message, "(no matching move)st")) {
7297 /* Special kludge for GNU Chess 4 only */
7298 cps->stKludge = TRUE;
7299 SendTimeControl(cps, movesPerSession, timeControl,
7300 timeIncrement, appData.searchDepth,
7304 if (StrStr(message, "(no matching move)sd")) {
7305 /* Special kludge for GNU Chess 4 only */
7306 cps->sdKludge = TRUE;
7307 SendTimeControl(cps, movesPerSession, timeControl,
7308 timeIncrement, appData.searchDepth,
7312 if (!StrStr(message, "llegal")) {
7315 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
7316 gameMode == IcsIdle) return;
7317 if (forwardMostMove <= backwardMostMove) return;
7318 if (pausing) PauseEvent();
7319 if(appData.forceIllegal) {
7320 // [HGM] illegal: machine refused move; force position after move into it
7321 SendToProgram("force\n", cps);
7322 if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
7323 // we have a real problem now, as SendBoard will use the a2a3 kludge
7324 // when black is to move, while there might be nothing on a2 or black
7325 // might already have the move. So send the board as if white has the move.
7326 // But first we must change the stm of the engine, as it refused the last move
7327 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
7328 if(WhiteOnMove(forwardMostMove)) {
7329 SendToProgram("a7a6\n", cps); // for the engine black still had the move
7330 SendBoard(cps, forwardMostMove); // kludgeless board
7332 SendToProgram("a2a3\n", cps); // for the engine white still had the move
7333 CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
7334 SendBoard(cps, forwardMostMove+1); // kludgeless board
7336 } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
7337 if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
7338 gameMode == TwoMachinesPlay)
7339 SendToProgram("go\n", cps);
7342 if (gameMode == PlayFromGameFile) {
7343 /* Stop reading this game file */
7344 gameMode = EditGame;
7347 currentMove = forwardMostMove-1;
7348 DisplayMove(currentMove-1); /* before DisplayMoveError */
7349 SwitchClocks(forwardMostMove-1); // [HGM] race
7350 DisplayBothClocks();
7351 sprintf(buf1, _("Illegal move \"%s\" (rejected by %s chess program)"),
7352 parseList[currentMove], cps->which);
7353 DisplayMoveError(buf1);
7354 DrawPosition(FALSE, boards[currentMove]);
7356 /* [HGM] illegal-move claim should forfeit game when Xboard */
7357 /* only passes fully legal moves */
7358 if( appData.testLegality && gameMode == TwoMachinesPlay ) {
7359 GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
7360 "False illegal-move claim", GE_XBOARD );
7364 if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
7365 /* Program has a broken "time" command that
7366 outputs a string not ending in newline.
7372 * If chess program startup fails, exit with an error message.
7373 * Attempts to recover here are futile.
7375 if ((StrStr(message, "unknown host") != NULL)
7376 || (StrStr(message, "No remote directory") != NULL)
7377 || (StrStr(message, "not found") != NULL)
7378 || (StrStr(message, "No such file") != NULL)
7379 || (StrStr(message, "can't alloc") != NULL)
7380 || (StrStr(message, "Permission denied") != NULL)) {
7382 cps->maybeThinking = FALSE;
7383 snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
7384 cps->which, cps->program, cps->host, message);
7385 RemoveInputSource(cps->isr);
7386 DisplayFatalError(buf1, 0, 1);
7391 * Look for hint output
7393 if (sscanf(message, "Hint: %s", buf1) == 1) {
7394 if (cps == &first && hintRequested) {
7395 hintRequested = FALSE;
7396 if (ParseOneMove(buf1, forwardMostMove, &moveType,
7397 &fromX, &fromY, &toX, &toY, &promoChar)) {
7398 (void) CoordsToAlgebraic(boards[forwardMostMove],
7399 PosFlags(forwardMostMove),
7400 fromY, fromX, toY, toX, promoChar, buf1);
7401 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
7402 DisplayInformation(buf2);
7404 /* Hint move could not be parsed!? */
7405 snprintf(buf2, sizeof(buf2),
7406 _("Illegal hint move \"%s\"\nfrom %s chess program"),
7408 DisplayError(buf2, 0);
7411 strcpy(lastHint, buf1);
7417 * Ignore other messages if game is not in progress
7419 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
7420 gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
7423 * look for win, lose, draw, or draw offer
7425 if (strncmp(message, "1-0", 3) == 0) {
7426 char *p, *q, *r = "";
7427 p = strchr(message, '{');
7435 GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
7437 } else if (strncmp(message, "0-1", 3) == 0) {
7438 char *p, *q, *r = "";
7439 p = strchr(message, '{');
7447 /* Kludge for Arasan 4.1 bug */
7448 if (strcmp(r, "Black resigns") == 0) {
7449 GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
7452 GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
7454 } else if (strncmp(message, "1/2", 3) == 0) {
7455 char *p, *q, *r = "";
7456 p = strchr(message, '{');
7465 GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
7468 } else if (strncmp(message, "White resign", 12) == 0) {
7469 GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
7471 } else if (strncmp(message, "Black resign", 12) == 0) {
7472 GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
7474 } else if (strncmp(message, "White matches", 13) == 0 ||
7475 strncmp(message, "Black matches", 13) == 0 ) {
7476 /* [HGM] ignore GNUShogi noises */
7478 } else if (strncmp(message, "White", 5) == 0 &&
7479 message[5] != '(' &&
7480 StrStr(message, "Black") == NULL) {
7481 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7483 } else if (strncmp(message, "Black", 5) == 0 &&
7484 message[5] != '(') {
7485 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7487 } else if (strcmp(message, "resign") == 0 ||
7488 strcmp(message, "computer resigns") == 0) {
7490 case MachinePlaysBlack:
7491 case IcsPlayingBlack:
7492 GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
7494 case MachinePlaysWhite:
7495 case IcsPlayingWhite:
7496 GameEnds(BlackWins, "White resigns", GE_ENGINE);
7498 case TwoMachinesPlay:
7499 if (cps->twoMachinesColor[0] == 'w')
7500 GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
7502 GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
7509 } else if (strncmp(message, "opponent mates", 14) == 0) {
7511 case MachinePlaysBlack:
7512 case IcsPlayingBlack:
7513 GameEnds(WhiteWins, "White mates", GE_ENGINE);
7515 case MachinePlaysWhite:
7516 case IcsPlayingWhite:
7517 GameEnds(BlackWins, "Black mates", GE_ENGINE);
7519 case TwoMachinesPlay:
7520 if (cps->twoMachinesColor[0] == 'w')
7521 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7523 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7530 } else if (strncmp(message, "computer mates", 14) == 0) {
7532 case MachinePlaysBlack:
7533 case IcsPlayingBlack:
7534 GameEnds(BlackWins, "Black mates", GE_ENGINE1);
7536 case MachinePlaysWhite:
7537 case IcsPlayingWhite:
7538 GameEnds(WhiteWins, "White mates", GE_ENGINE);
7540 case TwoMachinesPlay:
7541 if (cps->twoMachinesColor[0] == 'w')
7542 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7544 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7551 } else if (strncmp(message, "checkmate", 9) == 0) {
7552 if (WhiteOnMove(forwardMostMove)) {
7553 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7555 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7558 } else if (strstr(message, "Draw") != NULL ||
7559 strstr(message, "game is a draw") != NULL) {
7560 GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
7562 } else if (strstr(message, "offer") != NULL &&
7563 strstr(message, "draw") != NULL) {
7565 if (appData.zippyPlay && first.initDone) {
7566 /* Relay offer to ICS */
7567 SendToICS(ics_prefix);
7568 SendToICS("draw\n");
7571 cps->offeredDraw = 2; /* valid until this engine moves twice */
7572 if (gameMode == TwoMachinesPlay) {
7573 if (cps->other->offeredDraw) {
7574 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
7575 /* [HGM] in two-machine mode we delay relaying draw offer */
7576 /* until after we also have move, to see if it is really claim */
7578 } else if (gameMode == MachinePlaysWhite ||
7579 gameMode == MachinePlaysBlack) {
7580 if (userOfferedDraw) {
7581 DisplayInformation(_("Machine accepts your draw offer"));
7582 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
7584 DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
7591 * Look for thinking output
7593 if ( appData.showThinking // [HGM] thinking: test all options that cause this output
7594 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
7596 int plylev, mvleft, mvtot, curscore, time;
7597 char mvname[MOVE_LEN];
7601 int prefixHint = FALSE;
7602 mvname[0] = NULLCHAR;
7605 case MachinePlaysBlack:
7606 case IcsPlayingBlack:
7607 if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
7609 case MachinePlaysWhite:
7610 case IcsPlayingWhite:
7611 if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
7616 case IcsObserving: /* [DM] icsEngineAnalyze */
7617 if (!appData.icsEngineAnalyze) ignore = TRUE;
7619 case TwoMachinesPlay:
7620 if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
7631 if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
7632 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
7634 if (plyext != ' ' && plyext != '\t') {
7638 /* [AS] Negate score if machine is playing black and reporting absolute scores */
7639 if( cps->scoreIsAbsolute &&
7640 ( gameMode == MachinePlaysBlack ||
7641 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
7642 gameMode == IcsPlayingBlack || // [HGM] also add other situations where engine should report black POV
7643 (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
7644 !WhiteOnMove(currentMove)
7647 curscore = -curscore;
7651 programStats.depth = plylev;
7652 programStats.nodes = nodes;
7653 programStats.time = time;
7654 programStats.score = curscore;
7655 programStats.got_only_move = 0;
7657 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
7660 if(cps->nps == 0) ticklen = 10*time; // use engine reported time
7661 else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
7662 if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
7663 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
7664 whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
7665 if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
7666 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
7667 blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
7670 /* Buffer overflow protection */
7671 if (buf1[0] != NULLCHAR) {
7672 if (strlen(buf1) >= sizeof(programStats.movelist)
7673 && appData.debugMode) {
7675 "PV is too long; using the first %u bytes.\n",
7676 (unsigned) sizeof(programStats.movelist) - 1);
7679 safeStrCpy( programStats.movelist, buf1, sizeof(programStats.movelist) );
7681 sprintf(programStats.movelist, " no PV\n");
7684 if (programStats.seen_stat) {
7685 programStats.ok_to_send = 1;
7688 if (strchr(programStats.movelist, '(') != NULL) {
7689 programStats.line_is_book = 1;
7690 programStats.nr_moves = 0;
7691 programStats.moves_left = 0;
7693 programStats.line_is_book = 0;
7696 SendProgramStatsToFrontend( cps, &programStats );
7699 [AS] Protect the thinkOutput buffer from overflow... this
7700 is only useful if buf1 hasn't overflowed first!
7702 sprintf(thinkOutput, "[%d]%c%+.2f %s%s",
7704 (gameMode == TwoMachinesPlay ?
7705 ToUpper(cps->twoMachinesColor[0]) : ' '),
7706 ((double) curscore) / 100.0,
7707 prefixHint ? lastHint : "",
7708 prefixHint ? " " : "" );
7710 if( buf1[0] != NULLCHAR ) {
7711 unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
7713 if( strlen(buf1) > max_len ) {
7714 if( appData.debugMode) {
7715 fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
7717 buf1[max_len+1] = '\0';
7720 strcat( thinkOutput, buf1 );
7723 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
7724 || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7725 DisplayMove(currentMove - 1);
7729 } else if ((p=StrStr(message, "(only move)")) != NULL) {
7730 /* crafty (9.25+) says "(only move) <move>"
7731 * if there is only 1 legal move
7733 sscanf(p, "(only move) %s", buf1);
7734 sprintf(thinkOutput, "%s (only move)", buf1);
7735 sprintf(programStats.movelist, "%s (only move)", buf1);
7736 programStats.depth = 1;
7737 programStats.nr_moves = 1;
7738 programStats.moves_left = 1;
7739 programStats.nodes = 1;
7740 programStats.time = 1;
7741 programStats.got_only_move = 1;
7743 /* Not really, but we also use this member to
7744 mean "line isn't going to change" (Crafty
7745 isn't searching, so stats won't change) */
7746 programStats.line_is_book = 1;
7748 SendProgramStatsToFrontend( cps, &programStats );
7750 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
7751 gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7752 DisplayMove(currentMove - 1);
7755 } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
7756 &time, &nodes, &plylev, &mvleft,
7757 &mvtot, mvname) >= 5) {
7758 /* The stat01: line is from Crafty (9.29+) in response
7759 to the "." command */
7760 programStats.seen_stat = 1;
7761 cps->maybeThinking = TRUE;
7763 if (programStats.got_only_move || !appData.periodicUpdates)
7766 programStats.depth = plylev;
7767 programStats.time = time;
7768 programStats.nodes = nodes;
7769 programStats.moves_left = mvleft;
7770 programStats.nr_moves = mvtot;
7771 strcpy(programStats.move_name, mvname);
7772 programStats.ok_to_send = 1;
7773 programStats.movelist[0] = '\0';
7775 SendProgramStatsToFrontend( cps, &programStats );
7779 } else if (strncmp(message,"++",2) == 0) {
7780 /* Crafty 9.29+ outputs this */
7781 programStats.got_fail = 2;
7784 } else if (strncmp(message,"--",2) == 0) {
7785 /* Crafty 9.29+ outputs this */
7786 programStats.got_fail = 1;
7789 } else if (thinkOutput[0] != NULLCHAR &&
7790 strncmp(message, " ", 4) == 0) {
7791 unsigned message_len;
7794 while (*p && *p == ' ') p++;
7796 message_len = strlen( p );
7798 /* [AS] Avoid buffer overflow */
7799 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
7800 strcat(thinkOutput, " ");
7801 strcat(thinkOutput, p);
7804 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
7805 strcat(programStats.movelist, " ");
7806 strcat(programStats.movelist, p);
7809 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
7810 gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7811 DisplayMove(currentMove - 1);
7819 if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
7820 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
7822 ChessProgramStats cpstats;
7824 if (plyext != ' ' && plyext != '\t') {
7828 /* [AS] Negate score if machine is playing black and reporting absolute scores */
7829 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
7830 curscore = -curscore;
7833 cpstats.depth = plylev;
7834 cpstats.nodes = nodes;
7835 cpstats.time = time;
7836 cpstats.score = curscore;
7837 cpstats.got_only_move = 0;
7838 cpstats.movelist[0] = '\0';
7840 if (buf1[0] != NULLCHAR) {
7841 safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist) );
7844 cpstats.ok_to_send = 0;
7845 cpstats.line_is_book = 0;
7846 cpstats.nr_moves = 0;
7847 cpstats.moves_left = 0;
7849 SendProgramStatsToFrontend( cps, &cpstats );
7856 /* Parse a game score from the character string "game", and
7857 record it as the history of the current game. The game
7858 score is NOT assumed to start from the standard position.
7859 The display is not updated in any way.
7862 ParseGameHistory(game)
7866 int fromX, fromY, toX, toY, boardIndex;
7871 if (appData.debugMode)
7872 fprintf(debugFP, "Parsing game history: %s\n", game);
7874 if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
7875 gameInfo.site = StrSave(appData.icsHost);
7876 gameInfo.date = PGNDate();
7877 gameInfo.round = StrSave("-");
7879 /* Parse out names of players */
7880 while (*game == ' ') game++;
7882 while (*game != ' ') *p++ = *game++;
7884 gameInfo.white = StrSave(buf);
7885 while (*game == ' ') game++;
7887 while (*game != ' ' && *game != '\n') *p++ = *game++;
7889 gameInfo.black = StrSave(buf);
7892 boardIndex = blackPlaysFirst ? 1 : 0;
7895 yyboardindex = boardIndex;
7896 moveType = (ChessMove) yylex();
7898 case IllegalMove: /* maybe suicide chess, etc. */
7899 if (appData.debugMode) {
7900 fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
7901 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7902 setbuf(debugFP, NULL);
7904 case WhitePromotionChancellor:
7905 case BlackPromotionChancellor:
7906 case WhitePromotionArchbishop:
7907 case BlackPromotionArchbishop:
7908 case WhitePromotionQueen:
7909 case BlackPromotionQueen:
7910 case WhitePromotionRook:
7911 case BlackPromotionRook:
7912 case WhitePromotionBishop:
7913 case BlackPromotionBishop:
7914 case WhitePromotionKnight:
7915 case BlackPromotionKnight:
7916 case WhitePromotionKing:
7917 case BlackPromotionKing:
7919 case WhiteCapturesEnPassant:
7920 case BlackCapturesEnPassant:
7921 case WhiteKingSideCastle:
7922 case WhiteQueenSideCastle:
7923 case BlackKingSideCastle:
7924 case BlackQueenSideCastle:
7925 case WhiteKingSideCastleWild:
7926 case WhiteQueenSideCastleWild:
7927 case BlackKingSideCastleWild:
7928 case BlackQueenSideCastleWild:
7930 case WhiteHSideCastleFR:
7931 case WhiteASideCastleFR:
7932 case BlackHSideCastleFR:
7933 case BlackASideCastleFR:
7935 fromX = currentMoveString[0] - AAA;
7936 fromY = currentMoveString[1] - ONE;
7937 toX = currentMoveString[2] - AAA;
7938 toY = currentMoveString[3] - ONE;
7939 promoChar = currentMoveString[4];
7943 fromX = moveType == WhiteDrop ?
7944 (int) CharToPiece(ToUpper(currentMoveString[0])) :
7945 (int) CharToPiece(ToLower(currentMoveString[0]));
7947 toX = currentMoveString[2] - AAA;
7948 toY = currentMoveString[3] - ONE;
7949 promoChar = NULLCHAR;
7953 sprintf(buf, _("Ambiguous move in ICS output: \"%s\""), yy_text);
7954 if (appData.debugMode) {
7955 fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
7956 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7957 setbuf(debugFP, NULL);
7959 DisplayError(buf, 0);
7961 case ImpossibleMove:
7963 sprintf(buf, _("Illegal move in ICS output: \"%s\""), yy_text);
7964 if (appData.debugMode) {
7965 fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
7966 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7967 setbuf(debugFP, NULL);
7969 DisplayError(buf, 0);
7971 case (ChessMove) 0: /* end of file */
7972 if (boardIndex < backwardMostMove) {
7973 /* Oops, gap. How did that happen? */
7974 DisplayError(_("Gap in move list"), 0);
7977 backwardMostMove = blackPlaysFirst ? 1 : 0;
7978 if (boardIndex > forwardMostMove) {
7979 forwardMostMove = boardIndex;
7983 if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
7984 strcat(parseList[boardIndex-1], " ");
7985 strcat(parseList[boardIndex-1], yy_text);
7997 case GameUnfinished:
7998 if (gameMode == IcsExamining) {
7999 if (boardIndex < backwardMostMove) {
8000 /* Oops, gap. How did that happen? */
8003 backwardMostMove = blackPlaysFirst ? 1 : 0;
8006 gameInfo.result = moveType;
8007 p = strchr(yy_text, '{');
8008 if (p == NULL) p = strchr(yy_text, '(');
8011 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
8013 q = strchr(p, *p == '{' ? '}' : ')');
8014 if (q != NULL) *q = NULLCHAR;
8017 gameInfo.resultDetails = StrSave(p);
8020 if (boardIndex >= forwardMostMove &&
8021 !(gameMode == IcsObserving && ics_gamenum == -1)) {
8022 backwardMostMove = blackPlaysFirst ? 1 : 0;
8025 (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
8026 fromY, fromX, toY, toX, promoChar,
8027 parseList[boardIndex]);
8028 CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
8029 /* currentMoveString is set as a side-effect of yylex */
8030 strcpy(moveList[boardIndex], currentMoveString);
8031 strcat(moveList[boardIndex], "\n");
8033 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
8034 switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
8040 if(gameInfo.variant != VariantShogi)
8041 strcat(parseList[boardIndex - 1], "+");
8045 strcat(parseList[boardIndex - 1], "#");
8052 /* Apply a move to the given board */
8054 ApplyMove(fromX, fromY, toX, toY, promoChar, board)
8055 int fromX, fromY, toX, toY;
8059 ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
8060 int promoRank = gameInfo.variant == VariantMakruk ? 3 : 1;
8062 /* [HGM] compute & store e.p. status and castling rights for new position */
8063 /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
8066 if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
8067 oldEP = (signed char)board[EP_STATUS];
8068 board[EP_STATUS] = EP_NONE;
8070 if( board[toY][toX] != EmptySquare )
8071 board[EP_STATUS] = EP_CAPTURE;
8073 if( board[fromY][fromX] == WhitePawn ) {
8074 if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
8075 board[EP_STATUS] = EP_PAWN_MOVE;
8077 if(toX>BOARD_LEFT && board[toY][toX-1] == BlackPawn &&
8078 gameInfo.variant != VariantBerolina || toX < fromX)
8079 board[EP_STATUS] = toX | berolina;
8080 if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
8081 gameInfo.variant != VariantBerolina || toX > fromX)
8082 board[EP_STATUS] = toX;
8085 if( board[fromY][fromX] == BlackPawn ) {
8086 if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
8087 board[EP_STATUS] = EP_PAWN_MOVE;
8088 if( toY-fromY== -2) {
8089 if(toX>BOARD_LEFT && board[toY][toX-1] == WhitePawn &&
8090 gameInfo.variant != VariantBerolina || toX < fromX)
8091 board[EP_STATUS] = toX | berolina;
8092 if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
8093 gameInfo.variant != VariantBerolina || toX > fromX)
8094 board[EP_STATUS] = toX;
8098 for(i=0; i<nrCastlingRights; i++) {
8099 if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
8100 board[CASTLING][i] == toX && castlingRank[i] == toY
8101 ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
8106 /* [HGM] In Shatranj and Courier all promotions are to Ferz */
8107 if((gameInfo.variant==VariantShatranj || gameInfo.variant==VariantCourier || gameInfo.variant == VariantMakruk)
8108 && promoChar != 0) promoChar = PieceToChar(WhiteFerz);
8110 if (fromX == toX && fromY == toY) return;
8112 if (fromY == DROP_RANK) {
8114 piece = board[toY][toX] = (ChessSquare) fromX;
8116 piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
8117 king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
8118 if(gameInfo.variant == VariantKnightmate)
8119 king += (int) WhiteUnicorn - (int) WhiteKing;
8121 /* Code added by Tord: */
8122 /* FRC castling assumed when king captures friendly rook. */
8123 if (board[fromY][fromX] == WhiteKing &&
8124 board[toY][toX] == WhiteRook) {
8125 board[fromY][fromX] = EmptySquare;
8126 board[toY][toX] = EmptySquare;
8128 board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
8130 board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
8132 } else if (board[fromY][fromX] == BlackKing &&
8133 board[toY][toX] == BlackRook) {
8134 board[fromY][fromX] = EmptySquare;
8135 board[toY][toX] = EmptySquare;
8137 board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
8139 board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
8141 /* End of code added by Tord */
8143 } else if (board[fromY][fromX] == king
8144 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8145 && toY == fromY && toX > fromX+1) {
8146 board[fromY][fromX] = EmptySquare;
8147 board[toY][toX] = king;
8148 board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
8149 board[fromY][BOARD_RGHT-1] = EmptySquare;
8150 } else if (board[fromY][fromX] == king
8151 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8152 && toY == fromY && toX < fromX-1) {
8153 board[fromY][fromX] = EmptySquare;
8154 board[toY][toX] = king;
8155 board[toY][toX+1] = board[fromY][BOARD_LEFT];
8156 board[fromY][BOARD_LEFT] = EmptySquare;
8157 } else if (board[fromY][fromX] == WhitePawn
8158 && toY >= BOARD_HEIGHT-promoRank
8159 && gameInfo.variant != VariantXiangqi
8161 /* white pawn promotion */
8162 board[toY][toX] = CharToPiece(ToUpper(promoChar));
8163 if (board[toY][toX] == EmptySquare) {
8164 board[toY][toX] = WhiteQueen;
8166 if(gameInfo.variant==VariantBughouse ||
8167 gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
8168 board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
8169 board[fromY][fromX] = EmptySquare;
8170 } else if ((fromY == BOARD_HEIGHT-4)
8172 && gameInfo.variant != VariantXiangqi
8173 && gameInfo.variant != VariantBerolina
8174 && (board[fromY][fromX] == WhitePawn)
8175 && (board[toY][toX] == EmptySquare)) {
8176 board[fromY][fromX] = EmptySquare;
8177 board[toY][toX] = WhitePawn;
8178 captured = board[toY - 1][toX];
8179 board[toY - 1][toX] = EmptySquare;
8180 } else if ((fromY == BOARD_HEIGHT-4)
8182 && gameInfo.variant == VariantBerolina
8183 && (board[fromY][fromX] == WhitePawn)
8184 && (board[toY][toX] == EmptySquare)) {
8185 board[fromY][fromX] = EmptySquare;
8186 board[toY][toX] = WhitePawn;
8187 if(oldEP & EP_BEROLIN_A) {
8188 captured = board[fromY][fromX-1];
8189 board[fromY][fromX-1] = EmptySquare;
8190 }else{ captured = board[fromY][fromX+1];
8191 board[fromY][fromX+1] = EmptySquare;
8193 } else if (board[fromY][fromX] == king
8194 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8195 && toY == fromY && toX > fromX+1) {
8196 board[fromY][fromX] = EmptySquare;
8197 board[toY][toX] = king;
8198 board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
8199 board[fromY][BOARD_RGHT-1] = EmptySquare;
8200 } else if (board[fromY][fromX] == king
8201 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8202 && toY == fromY && toX < fromX-1) {
8203 board[fromY][fromX] = EmptySquare;
8204 board[toY][toX] = king;
8205 board[toY][toX+1] = board[fromY][BOARD_LEFT];
8206 board[fromY][BOARD_LEFT] = EmptySquare;
8207 } else if (fromY == 7 && fromX == 3
8208 && board[fromY][fromX] == BlackKing
8209 && toY == 7 && toX == 5) {
8210 board[fromY][fromX] = EmptySquare;
8211 board[toY][toX] = BlackKing;
8212 board[fromY][7] = EmptySquare;
8213 board[toY][4] = BlackRook;
8214 } else if (fromY == 7 && fromX == 3
8215 && board[fromY][fromX] == BlackKing
8216 && toY == 7 && toX == 1) {
8217 board[fromY][fromX] = EmptySquare;
8218 board[toY][toX] = BlackKing;
8219 board[fromY][0] = EmptySquare;
8220 board[toY][2] = BlackRook;
8221 } else if (board[fromY][fromX] == BlackPawn
8223 && gameInfo.variant != VariantXiangqi
8225 /* black pawn promotion */
8226 board[toY][toX] = CharToPiece(ToLower(promoChar));
8227 if (board[toY][toX] == EmptySquare) {
8228 board[toY][toX] = BlackQueen;
8230 if(gameInfo.variant==VariantBughouse ||
8231 gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
8232 board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
8233 board[fromY][fromX] = EmptySquare;
8234 } else if ((fromY == 3)
8236 && gameInfo.variant != VariantXiangqi
8237 && gameInfo.variant != VariantBerolina
8238 && (board[fromY][fromX] == BlackPawn)
8239 && (board[toY][toX] == EmptySquare)) {
8240 board[fromY][fromX] = EmptySquare;
8241 board[toY][toX] = BlackPawn;
8242 captured = board[toY + 1][toX];
8243 board[toY + 1][toX] = EmptySquare;
8244 } else if ((fromY == 3)
8246 && gameInfo.variant == VariantBerolina
8247 && (board[fromY][fromX] == BlackPawn)
8248 && (board[toY][toX] == EmptySquare)) {
8249 board[fromY][fromX] = EmptySquare;
8250 board[toY][toX] = BlackPawn;
8251 if(oldEP & EP_BEROLIN_A) {
8252 captured = board[fromY][fromX-1];
8253 board[fromY][fromX-1] = EmptySquare;
8254 }else{ captured = board[fromY][fromX+1];
8255 board[fromY][fromX+1] = EmptySquare;
8258 board[toY][toX] = board[fromY][fromX];
8259 board[fromY][fromX] = EmptySquare;
8262 /* [HGM] now we promote for Shogi, if needed */
8263 if(gameInfo.variant == VariantShogi && promoChar == 'q')
8264 board[toY][toX] = (ChessSquare) (PROMOTED piece);
8267 if (gameInfo.holdingsWidth != 0) {
8269 /* !!A lot more code needs to be written to support holdings */
8270 /* [HGM] OK, so I have written it. Holdings are stored in the */
8271 /* penultimate board files, so they are automaticlly stored */
8272 /* in the game history. */
8273 if (fromY == DROP_RANK) {
8274 /* Delete from holdings, by decreasing count */
8275 /* and erasing image if necessary */
8277 if(p < (int) BlackPawn) { /* white drop */
8278 p -= (int)WhitePawn;
8279 p = PieceToNumber((ChessSquare)p);
8280 if(p >= gameInfo.holdingsSize) p = 0;
8281 if(--board[p][BOARD_WIDTH-2] <= 0)
8282 board[p][BOARD_WIDTH-1] = EmptySquare;
8283 if((int)board[p][BOARD_WIDTH-2] < 0)
8284 board[p][BOARD_WIDTH-2] = 0;
8285 } else { /* black drop */
8286 p -= (int)BlackPawn;
8287 p = PieceToNumber((ChessSquare)p);
8288 if(p >= gameInfo.holdingsSize) p = 0;
8289 if(--board[BOARD_HEIGHT-1-p][1] <= 0)
8290 board[BOARD_HEIGHT-1-p][0] = EmptySquare;
8291 if((int)board[BOARD_HEIGHT-1-p][1] < 0)
8292 board[BOARD_HEIGHT-1-p][1] = 0;
8295 if (captured != EmptySquare && gameInfo.holdingsSize > 0
8296 && gameInfo.variant != VariantBughouse ) {
8297 /* [HGM] holdings: Add to holdings, if holdings exist */
8298 if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
8299 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
8300 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
8303 if (p >= (int) BlackPawn) {
8304 p -= (int)BlackPawn;
8305 if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
8306 /* in Shogi restore piece to its original first */
8307 captured = (ChessSquare) (DEMOTED captured);
8310 p = PieceToNumber((ChessSquare)p);
8311 if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
8312 board[p][BOARD_WIDTH-2]++;
8313 board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
8315 p -= (int)WhitePawn;
8316 if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
8317 captured = (ChessSquare) (DEMOTED captured);
8320 p = PieceToNumber((ChessSquare)p);
8321 if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
8322 board[BOARD_HEIGHT-1-p][1]++;
8323 board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
8326 } else if (gameInfo.variant == VariantAtomic) {
8327 if (captured != EmptySquare) {
8329 for (y = toY-1; y <= toY+1; y++) {
8330 for (x = toX-1; x <= toX+1; x++) {
8331 if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
8332 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
8333 board[y][x] = EmptySquare;
8337 board[toY][toX] = EmptySquare;
8340 if(gameInfo.variant == VariantShogi && promoChar != NULLCHAR && promoChar != '=') {
8341 /* [HGM] Shogi promotions */
8342 board[toY][toX] = (ChessSquare) (PROMOTED piece);
8345 if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
8346 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
8347 // [HGM] superchess: take promotion piece out of holdings
8348 int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
8349 if((int)piece < (int)BlackPawn) { // determine stm from piece color
8350 if(!--board[k][BOARD_WIDTH-2])
8351 board[k][BOARD_WIDTH-1] = EmptySquare;
8353 if(!--board[BOARD_HEIGHT-1-k][1])
8354 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
8360 /* Updates forwardMostMove */
8362 MakeMove(fromX, fromY, toX, toY, promoChar)
8363 int fromX, fromY, toX, toY;
8366 // forwardMostMove++; // [HGM] bare: moved downstream
8368 if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
8369 int timeLeft; static int lastLoadFlag=0; int king, piece;
8370 piece = boards[forwardMostMove][fromY][fromX];
8371 king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
8372 if(gameInfo.variant == VariantKnightmate)
8373 king += (int) WhiteUnicorn - (int) WhiteKing;
8374 if(forwardMostMove == 0) {
8376 fprintf(serverMoves, "%s;", second.tidy);
8377 fprintf(serverMoves, "%s;", first.tidy);
8378 if(!blackPlaysFirst)
8379 fprintf(serverMoves, "%s;", second.tidy);
8380 } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
8381 lastLoadFlag = loadFlag;
8383 fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
8384 // print castling suffix
8385 if( toY == fromY && piece == king ) {
8387 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
8389 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
8392 if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
8393 boards[forwardMostMove][fromY][fromX] == BlackPawn ) &&
8394 boards[forwardMostMove][toY][toX] == EmptySquare
8396 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
8398 if(promoChar != NULLCHAR)
8399 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);
8401 fprintf(serverMoves, "/%d/%d",
8402 pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
8403 if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
8404 else timeLeft = blackTimeRemaining/1000;
8405 fprintf(serverMoves, "/%d", timeLeft);
8407 fflush(serverMoves);
8410 if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations
8411 DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
8415 UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
8416 if (commentList[forwardMostMove+1] != NULL) {
8417 free(commentList[forwardMostMove+1]);
8418 commentList[forwardMostMove+1] = NULL;
8420 CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
8421 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
8422 // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
8423 SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
8424 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
8425 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
8426 gameInfo.result = GameUnfinished;
8427 if (gameInfo.resultDetails != NULL) {
8428 free(gameInfo.resultDetails);
8429 gameInfo.resultDetails = NULL;
8431 CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
8432 moveList[forwardMostMove - 1]);
8433 (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
8434 PosFlags(forwardMostMove - 1),
8435 fromY, fromX, toY, toX, promoChar,
8436 parseList[forwardMostMove - 1]);
8437 switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
8443 if(gameInfo.variant != VariantShogi)
8444 strcat(parseList[forwardMostMove - 1], "+");
8448 strcat(parseList[forwardMostMove - 1], "#");
8451 if (appData.debugMode) {
8452 fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
8457 /* Updates currentMove if not pausing */
8459 ShowMove(fromX, fromY, toX, toY)
8461 int instant = (gameMode == PlayFromGameFile) ?
8462 (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
8463 if(appData.noGUI) return;
8464 if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
8466 if (forwardMostMove == currentMove + 1) {
8467 AnimateMove(boards[forwardMostMove - 1],
8468 fromX, fromY, toX, toY);
8470 if (appData.highlightLastMove) {
8471 SetHighlights(fromX, fromY, toX, toY);
8474 currentMove = forwardMostMove;
8477 if (instant) return;
8479 DisplayMove(currentMove - 1);
8480 DrawPosition(FALSE, boards[currentMove]);
8481 DisplayBothClocks();
8482 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
8485 void SendEgtPath(ChessProgramState *cps)
8486 { /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
8487 char buf[MSG_SIZ], name[MSG_SIZ], *p;
8489 if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
8492 char c, *q = name+1, *r, *s;
8494 name[0] = ','; // extract next format name from feature and copy with prefixed ','
8495 while(*p && *p != ',') *q++ = *p++;
8497 if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
8498 strcmp(name, ",nalimov:") == 0 ) {
8499 // take nalimov path from the menu-changeable option first, if it is defined
8500 sprintf(buf, "egtpath nalimov %s\n", appData.defaultPathEGTB);
8501 SendToProgram(buf,cps); // send egtbpath command for nalimov
8503 if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
8504 (s = StrStr(appData.egtFormats, name)) != NULL) {
8505 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
8506 s = r = StrStr(s, ":") + 1; // beginning of path info
8507 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
8508 c = *r; *r = 0; // temporarily null-terminate path info
8509 *--q = 0; // strip of trailig ':' from name
8510 sprintf(buf, "egtpath %s %s\n", name+1, s);
8512 SendToProgram(buf,cps); // send egtbpath command for this format
8514 if(*p == ',') p++; // read away comma to position for next format name
8519 InitChessProgram(cps, setup)
8520 ChessProgramState *cps;
8521 int setup; /* [HGM] needed to setup FRC opening position */
8523 char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
8524 if (appData.noChessProgram) return;
8525 hintRequested = FALSE;
8526 bookRequested = FALSE;
8528 /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
8529 /* moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
8530 if(cps->memSize) { /* [HGM] memory */
8531 sprintf(buf, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
8532 SendToProgram(buf, cps);
8534 SendEgtPath(cps); /* [HGM] EGT */
8535 if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
8536 sprintf(buf, "cores %d\n", appData.smpCores);
8537 SendToProgram(buf, cps);
8540 SendToProgram(cps->initString, cps);
8541 if (gameInfo.variant != VariantNormal &&
8542 gameInfo.variant != VariantLoadable
8543 /* [HGM] also send variant if board size non-standard */
8544 || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
8546 char *v = VariantName(gameInfo.variant);
8547 if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
8548 /* [HGM] in protocol 1 we have to assume all variants valid */
8549 sprintf(buf, _("Variant %s not supported by %s"), v, cps->tidy);
8550 DisplayFatalError(buf, 0, 1);
8554 /* [HGM] make prefix for non-standard board size. Awkward testing... */
8555 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8556 if( gameInfo.variant == VariantXiangqi )
8557 overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
8558 if( gameInfo.variant == VariantShogi )
8559 overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
8560 if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
8561 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
8562 if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
8563 gameInfo.variant == VariantGothic || gameInfo.variant == VariantFalcon )
8564 overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8565 if( gameInfo.variant == VariantCourier )
8566 overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8567 if( gameInfo.variant == VariantSuper )
8568 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
8569 if( gameInfo.variant == VariantGreat )
8570 overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
8573 sprintf(b, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
8574 gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
8575 /* [HGM] varsize: try first if this defiant size variant is specifically known */
8576 if(StrStr(cps->variants, b) == NULL) {
8577 // specific sized variant not known, check if general sizing allowed
8578 if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
8579 if(StrStr(cps->variants, "boardsize") == NULL) {
8580 sprintf(buf, "Board size %dx%d+%d not supported by %s",
8581 gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
8582 DisplayFatalError(buf, 0, 1);
8585 /* [HGM] here we really should compare with the maximum supported board size */
8588 } else sprintf(b, "%s", VariantName(gameInfo.variant));
8589 sprintf(buf, "variant %s\n", b);
8590 SendToProgram(buf, cps);
8592 currentlyInitializedVariant = gameInfo.variant;
8594 /* [HGM] send opening position in FRC to first engine */
8596 SendToProgram("force\n", cps);
8598 /* engine is now in force mode! Set flag to wake it up after first move. */
8599 setboardSpoiledMachineBlack = 1;
8603 snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
8604 SendToProgram(buf, cps);
8606 cps->maybeThinking = FALSE;
8607 cps->offeredDraw = 0;
8608 if (!appData.icsActive) {
8609 SendTimeControl(cps, movesPerSession, timeControl,
8610 timeIncrement, appData.searchDepth,
8613 if (appData.showThinking
8614 // [HGM] thinking: four options require thinking output to be sent
8615 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
8617 SendToProgram("post\n", cps);
8619 SendToProgram("hard\n", cps);
8620 if (!appData.ponderNextMove) {
8621 /* Warning: "easy" is a toggle in GNU Chess, so don't send
8622 it without being sure what state we are in first. "hard"
8623 is not a toggle, so that one is OK.
8625 SendToProgram("easy\n", cps);
8628 sprintf(buf, "ping %d\n", ++cps->lastPing);
8629 SendToProgram(buf, cps);
8631 cps->initDone = TRUE;
8636 StartChessProgram(cps)
8637 ChessProgramState *cps;
8642 if (appData.noChessProgram) return;
8643 cps->initDone = FALSE;
8645 if (strcmp(cps->host, "localhost") == 0) {
8646 err = StartChildProcess(cps->program, cps->dir, &cps->pr);
8647 } else if (*appData.remoteShell == NULLCHAR) {
8648 err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
8650 if (*appData.remoteUser == NULLCHAR) {
8651 snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
8654 snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
8655 cps->host, appData.remoteUser, cps->program);
8657 err = StartChildProcess(buf, "", &cps->pr);
8661 sprintf(buf, _("Startup failure on '%s'"), cps->program);
8662 DisplayFatalError(buf, err, 1);
8668 cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
8669 if (cps->protocolVersion > 1) {
8670 sprintf(buf, "xboard\nprotover %d\n", cps->protocolVersion);
8671 cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
8672 cps->comboCnt = 0; // and values of combo boxes
8673 SendToProgram(buf, cps);
8675 SendToProgram("xboard\n", cps);
8681 TwoMachinesEventIfReady P((void))
8683 if (first.lastPing != first.lastPong) {
8684 DisplayMessage("", _("Waiting for first chess program"));
8685 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
8688 if (second.lastPing != second.lastPong) {
8689 DisplayMessage("", _("Waiting for second chess program"));
8690 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
8698 NextMatchGame P((void))
8700 int index; /* [HGM] autoinc: step load index during match */
8702 if (*appData.loadGameFile != NULLCHAR) {
8703 index = appData.loadGameIndex;
8704 if(index < 0) { // [HGM] autoinc
8705 lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
8706 if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
8708 LoadGameFromFile(appData.loadGameFile,
8710 appData.loadGameFile, FALSE);
8711 } else if (*appData.loadPositionFile != NULLCHAR) {
8712 index = appData.loadPositionIndex;
8713 if(index < 0) { // [HGM] autoinc
8714 lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
8715 if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
8717 LoadPositionFromFile(appData.loadPositionFile,
8719 appData.loadPositionFile);
8721 TwoMachinesEventIfReady();
8724 void UserAdjudicationEvent( int result )
8726 ChessMove gameResult = GameIsDrawn;
8729 gameResult = WhiteWins;
8731 else if( result < 0 ) {
8732 gameResult = BlackWins;
8735 if( gameMode == TwoMachinesPlay ) {
8736 GameEnds( gameResult, "User adjudication", GE_XBOARD );
8741 // [HGM] save: calculate checksum of game to make games easily identifiable
8742 int StringCheckSum(char *s)
8745 if(s==NULL) return 0;
8746 while(*s) i = i*259 + *s++;
8753 for(i=backwardMostMove; i<forwardMostMove; i++) {
8754 sum += pvInfoList[i].depth;
8755 sum += StringCheckSum(parseList[i]);
8756 sum += StringCheckSum(commentList[i]);
8759 if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
8760 return sum + StringCheckSum(commentList[i]);
8761 } // end of save patch
8764 GameEnds(result, resultDetails, whosays)
8766 char *resultDetails;
8769 GameMode nextGameMode;
8773 if(endingGame) return; /* [HGM] crash: forbid recursion */
8776 if (appData.debugMode) {
8777 fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
8778 result, resultDetails ? resultDetails : "(null)", whosays);
8781 fromX = fromY = -1; // [HGM] abort any move the user is entering.
8783 if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
8784 /* If we are playing on ICS, the server decides when the
8785 game is over, but the engine can offer to draw, claim
8789 if (appData.zippyPlay && first.initDone) {
8790 if (result == GameIsDrawn) {
8791 /* In case draw still needs to be claimed */
8792 SendToICS(ics_prefix);
8793 SendToICS("draw\n");
8794 } else if (StrCaseStr(resultDetails, "resign")) {
8795 SendToICS(ics_prefix);
8796 SendToICS("resign\n");
8800 endingGame = 0; /* [HGM] crash */
8804 /* If we're loading the game from a file, stop */
8805 if (whosays == GE_FILE) {
8806 (void) StopLoadGameTimer();
8810 /* Cancel draw offers */
8811 first.offeredDraw = second.offeredDraw = 0;
8813 /* If this is an ICS game, only ICS can really say it's done;
8814 if not, anyone can. */
8815 isIcsGame = (gameMode == IcsPlayingWhite ||
8816 gameMode == IcsPlayingBlack ||
8817 gameMode == IcsObserving ||
8818 gameMode == IcsExamining);
8820 if (!isIcsGame || whosays == GE_ICS) {
8821 /* OK -- not an ICS game, or ICS said it was done */
8823 if (!isIcsGame && !appData.noChessProgram)
8824 SetUserThinkingEnables();
8826 /* [HGM] if a machine claims the game end we verify this claim */
8827 if(gameMode == TwoMachinesPlay && appData.testClaims) {
8828 if(appData.testLegality && whosays >= GE_ENGINE1 ) {
8830 ChessMove trueResult = (ChessMove) -1;
8832 claimer = whosays == GE_ENGINE1 ? /* color of claimer */
8833 first.twoMachinesColor[0] :
8834 second.twoMachinesColor[0] ;
8836 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
8837 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
8838 /* [HGM] verify: engine mate claims accepted if they were flagged */
8839 trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
8841 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
8842 /* [HGM] verify: engine mate claims accepted if they were flagged */
8843 trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8845 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
8846 trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
8849 // now verify win claims, but not in drop games, as we don't understand those yet
8850 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
8851 || gameInfo.variant == VariantGreat) &&
8852 (result == WhiteWins && claimer == 'w' ||
8853 result == BlackWins && claimer == 'b' ) ) { // case to verify: engine claims own win
8854 if (appData.debugMode) {
8855 fprintf(debugFP, "result=%d sp=%d move=%d\n",
8856 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
8858 if(result != trueResult) {
8859 sprintf(buf, "False win claim: '%s'", resultDetails);
8860 result = claimer == 'w' ? BlackWins : WhiteWins;
8861 resultDetails = buf;
8864 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
8865 && (forwardMostMove <= backwardMostMove ||
8866 (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
8867 (claimer=='b')==(forwardMostMove&1))
8869 /* [HGM] verify: draws that were not flagged are false claims */
8870 sprintf(buf, "False draw claim: '%s'", resultDetails);
8871 result = claimer == 'w' ? BlackWins : WhiteWins;
8872 resultDetails = buf;
8874 /* (Claiming a loss is accepted no questions asked!) */
8876 /* [HGM] bare: don't allow bare King to win */
8877 if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
8878 && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
8879 && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
8880 && result != GameIsDrawn)
8881 { int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
8882 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
8883 int p = (signed char)boards[forwardMostMove][i][j] - color;
8884 if(p >= 0 && p <= (int)WhiteKing) k++;
8886 if (appData.debugMode) {
8887 fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
8888 result, resultDetails ? resultDetails : "(null)", whosays, k, color);
8891 result = GameIsDrawn;
8892 sprintf(buf, "%s but bare king", resultDetails);
8893 resultDetails = buf;
8899 if(serverMoves != NULL && !loadFlag) { char c = '=';
8900 if(result==WhiteWins) c = '+';
8901 if(result==BlackWins) c = '-';
8902 if(resultDetails != NULL)
8903 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
8905 if (resultDetails != NULL) {
8906 gameInfo.result = result;
8907 gameInfo.resultDetails = StrSave(resultDetails);
8909 /* display last move only if game was not loaded from file */
8910 if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
8911 DisplayMove(currentMove - 1);
8913 if (forwardMostMove != 0) {
8914 if (gameMode != PlayFromGameFile && gameMode != EditGame
8915 && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
8917 if (*appData.saveGameFile != NULLCHAR) {
8918 SaveGameToFile(appData.saveGameFile, TRUE);
8919 } else if (appData.autoSaveGames) {
8922 if (*appData.savePositionFile != NULLCHAR) {
8923 SavePositionToFile(appData.savePositionFile);
8928 /* Tell program how game ended in case it is learning */
8929 /* [HGM] Moved this to after saving the PGN, just in case */
8930 /* engine died and we got here through time loss. In that */
8931 /* case we will get a fatal error writing the pipe, which */
8932 /* would otherwise lose us the PGN. */
8933 /* [HGM] crash: not needed anymore, but doesn't hurt; */
8934 /* output during GameEnds should never be fatal anymore */
8935 if (gameMode == MachinePlaysWhite ||
8936 gameMode == MachinePlaysBlack ||
8937 gameMode == TwoMachinesPlay ||
8938 gameMode == IcsPlayingWhite ||
8939 gameMode == IcsPlayingBlack ||
8940 gameMode == BeginningOfGame) {
8942 sprintf(buf, "result %s {%s}\n", PGNResult(result),
8944 if (first.pr != NoProc) {
8945 SendToProgram(buf, &first);
8947 if (second.pr != NoProc &&
8948 gameMode == TwoMachinesPlay) {
8949 SendToProgram(buf, &second);
8954 if (appData.icsActive) {
8955 if (appData.quietPlay &&
8956 (gameMode == IcsPlayingWhite ||
8957 gameMode == IcsPlayingBlack)) {
8958 SendToICS(ics_prefix);
8959 SendToICS("set shout 1\n");
8961 nextGameMode = IcsIdle;
8962 ics_user_moved = FALSE;
8963 /* clean up premove. It's ugly when the game has ended and the
8964 * premove highlights are still on the board.
8968 ClearPremoveHighlights();
8969 DrawPosition(FALSE, boards[currentMove]);
8971 if (whosays == GE_ICS) {
8974 if (gameMode == IcsPlayingWhite)
8976 else if(gameMode == IcsPlayingBlack)
8980 if (gameMode == IcsPlayingBlack)
8982 else if(gameMode == IcsPlayingWhite)
8989 PlayIcsUnfinishedSound();
8992 } else if (gameMode == EditGame ||
8993 gameMode == PlayFromGameFile ||
8994 gameMode == AnalyzeMode ||
8995 gameMode == AnalyzeFile) {
8996 nextGameMode = gameMode;
8998 nextGameMode = EndOfGame;
9003 nextGameMode = gameMode;
9006 if (appData.noChessProgram) {
9007 gameMode = nextGameMode;
9009 endingGame = 0; /* [HGM] crash */
9014 /* Put first chess program into idle state */
9015 if (first.pr != NoProc &&
9016 (gameMode == MachinePlaysWhite ||
9017 gameMode == MachinePlaysBlack ||
9018 gameMode == TwoMachinesPlay ||
9019 gameMode == IcsPlayingWhite ||
9020 gameMode == IcsPlayingBlack ||
9021 gameMode == BeginningOfGame)) {
9022 SendToProgram("force\n", &first);
9023 if (first.usePing) {
9025 sprintf(buf, "ping %d\n", ++first.lastPing);
9026 SendToProgram(buf, &first);
9029 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
9030 /* Kill off first chess program */
9031 if (first.isr != NULL)
9032 RemoveInputSource(first.isr);
9035 if (first.pr != NoProc) {
9037 DoSleep( appData.delayBeforeQuit );
9038 SendToProgram("quit\n", &first);
9039 DoSleep( appData.delayAfterQuit );
9040 DestroyChildProcess(first.pr, first.useSigterm);
9045 /* Put second chess program into idle state */
9046 if (second.pr != NoProc &&
9047 gameMode == TwoMachinesPlay) {
9048 SendToProgram("force\n", &second);
9049 if (second.usePing) {
9051 sprintf(buf, "ping %d\n", ++second.lastPing);
9052 SendToProgram(buf, &second);
9055 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
9056 /* Kill off second chess program */
9057 if (second.isr != NULL)
9058 RemoveInputSource(second.isr);
9061 if (second.pr != NoProc) {
9062 DoSleep( appData.delayBeforeQuit );
9063 SendToProgram("quit\n", &second);
9064 DoSleep( appData.delayAfterQuit );
9065 DestroyChildProcess(second.pr, second.useSigterm);
9070 if (matchMode && gameMode == TwoMachinesPlay) {
9073 if (first.twoMachinesColor[0] == 'w') {
9080 if (first.twoMachinesColor[0] == 'b') {
9089 if (matchGame < appData.matchGames) {
9091 if(appData.sameColorGames <= 1) { /* [HGM] alternate: suppress color swap */
9092 tmp = first.twoMachinesColor;
9093 first.twoMachinesColor = second.twoMachinesColor;
9094 second.twoMachinesColor = tmp;
9096 gameMode = nextGameMode;
9098 if(appData.matchPause>10000 || appData.matchPause<10)
9099 appData.matchPause = 10000; /* [HGM] make pause adjustable */
9100 ScheduleDelayedEvent(NextMatchGame, appData.matchPause);
9101 endingGame = 0; /* [HGM] crash */
9105 gameMode = nextGameMode;
9106 sprintf(buf, _("Match %s vs. %s: final score %d-%d-%d"),
9107 first.tidy, second.tidy,
9108 first.matchWins, second.matchWins,
9109 appData.matchGames - (first.matchWins + second.matchWins));
9110 DisplayFatalError(buf, 0, 0);
9113 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
9114 !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
9116 gameMode = nextGameMode;
9118 endingGame = 0; /* [HGM] crash */
9121 /* Assumes program was just initialized (initString sent).
9122 Leaves program in force mode. */
9124 FeedMovesToProgram(cps, upto)
9125 ChessProgramState *cps;
9130 if (appData.debugMode)
9131 fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
9132 startedFromSetupPosition ? "position and " : "",
9133 backwardMostMove, upto, cps->which);
9134 if(currentlyInitializedVariant != gameInfo.variant) { char buf[MSG_SIZ];
9135 // [HGM] variantswitch: make engine aware of new variant
9136 if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
9137 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
9138 sprintf(buf, "variant %s\n", VariantName(gameInfo.variant));
9139 SendToProgram(buf, cps);
9140 currentlyInitializedVariant = gameInfo.variant;
9142 SendToProgram("force\n", cps);
9143 if (startedFromSetupPosition) {
9144 SendBoard(cps, backwardMostMove);
9145 if (appData.debugMode) {
9146 fprintf(debugFP, "feedMoves\n");
9149 for (i = backwardMostMove; i < upto; i++) {
9150 SendMoveToProgram(i, cps);
9156 ResurrectChessProgram()
9158 /* The chess program may have exited.
9159 If so, restart it and feed it all the moves made so far. */
9161 if (appData.noChessProgram || first.pr != NoProc) return;
9163 StartChessProgram(&first);
9164 InitChessProgram(&first, FALSE);
9165 FeedMovesToProgram(&first, currentMove);
9167 if (!first.sendTime) {
9168 /* can't tell gnuchess what its clock should read,
9169 so we bow to its notion. */
9171 timeRemaining[0][currentMove] = whiteTimeRemaining;
9172 timeRemaining[1][currentMove] = blackTimeRemaining;
9175 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
9176 appData.icsEngineAnalyze) && first.analysisSupport) {
9177 SendToProgram("analyze\n", &first);
9178 first.analyzing = TRUE;
9191 if (appData.debugMode) {
9192 fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
9193 redraw, init, gameMode);
9195 CleanupTail(); // [HGM] vari: delete any stored variations
9196 pausing = pauseExamInvalid = FALSE;
9197 startedFromSetupPosition = blackPlaysFirst = FALSE;
9199 whiteFlag = blackFlag = FALSE;
9200 userOfferedDraw = FALSE;
9201 hintRequested = bookRequested = FALSE;
9202 first.maybeThinking = FALSE;
9203 second.maybeThinking = FALSE;
9204 first.bookSuspend = FALSE; // [HGM] book
9205 second.bookSuspend = FALSE;
9206 thinkOutput[0] = NULLCHAR;
9207 lastHint[0] = NULLCHAR;
9208 ClearGameInfo(&gameInfo);
9209 gameInfo.variant = StringToVariant(appData.variant);
9210 ics_user_moved = ics_clock_paused = FALSE;
9211 ics_getting_history = H_FALSE;
9213 white_holding[0] = black_holding[0] = NULLCHAR;
9214 ClearProgramStats();
9215 opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
9219 flipView = appData.flipView;
9220 ClearPremoveHighlights();
9222 alarmSounded = FALSE;
9224 GameEnds((ChessMove) 0, NULL, GE_PLAYER);
9225 if(appData.serverMovesName != NULL) {
9226 /* [HGM] prepare to make moves file for broadcasting */
9227 clock_t t = clock();
9228 if(serverMoves != NULL) fclose(serverMoves);
9229 serverMoves = fopen(appData.serverMovesName, "r");
9230 if(serverMoves != NULL) {
9231 fclose(serverMoves);
9232 /* delay 15 sec before overwriting, so all clients can see end */
9233 while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
9235 serverMoves = fopen(appData.serverMovesName, "w");
9239 gameMode = BeginningOfGame;
9241 if(appData.icsActive) gameInfo.variant = VariantNormal;
9242 currentMove = forwardMostMove = backwardMostMove = 0;
9243 InitPosition(redraw);
9244 for (i = 0; i < MAX_MOVES; i++) {
9245 if (commentList[i] != NULL) {
9246 free(commentList[i]);
9247 commentList[i] = NULL;
9251 timeRemaining[0][0] = whiteTimeRemaining;
9252 timeRemaining[1][0] = blackTimeRemaining;
9253 if (first.pr == NULL) {
9254 StartChessProgram(&first);
9257 InitChessProgram(&first, startedFromSetupPosition);
9260 DisplayMessage("", "");
9261 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
9262 lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
9269 if (!AutoPlayOneMove())
9271 if (matchMode || appData.timeDelay == 0)
9273 if (appData.timeDelay < 0 || gameMode == AnalyzeFile)
9275 StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
9284 int fromX, fromY, toX, toY;
9286 if (appData.debugMode) {
9287 fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
9290 if (gameMode != PlayFromGameFile)
9293 if (currentMove >= forwardMostMove) {
9294 gameMode = EditGame;
9297 /* [AS] Clear current move marker at the end of a game */
9298 /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
9303 toX = moveList[currentMove][2] - AAA;
9304 toY = moveList[currentMove][3] - ONE;
9306 if (moveList[currentMove][1] == '@') {
9307 if (appData.highlightLastMove) {
9308 SetHighlights(-1, -1, toX, toY);
9311 fromX = moveList[currentMove][0] - AAA;
9312 fromY = moveList[currentMove][1] - ONE;
9314 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
9316 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
9318 if (appData.highlightLastMove) {
9319 SetHighlights(fromX, fromY, toX, toY);
9322 DisplayMove(currentMove);
9323 SendMoveToProgram(currentMove++, &first);
9324 DisplayBothClocks();
9325 DrawPosition(FALSE, boards[currentMove]);
9326 // [HGM] PV info: always display, routine tests if empty
9327 DisplayComment(currentMove - 1, commentList[currentMove]);
9333 LoadGameOneMove(readAhead)
9334 ChessMove readAhead;
9336 int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
9337 char promoChar = NULLCHAR;
9342 if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
9343 gameMode != AnalyzeMode && gameMode != Training) {
9348 yyboardindex = forwardMostMove;
9349 if (readAhead != (ChessMove)0) {
9350 moveType = readAhead;
9352 if (gameFileFP == NULL)
9354 moveType = (ChessMove) yylex();
9360 if (appData.debugMode)
9361 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9364 /* append the comment but don't display it */
9365 AppendComment(currentMove, p, FALSE);
9368 case WhiteCapturesEnPassant:
9369 case BlackCapturesEnPassant:
9370 case WhitePromotionChancellor:
9371 case BlackPromotionChancellor:
9372 case WhitePromotionArchbishop:
9373 case BlackPromotionArchbishop:
9374 case WhitePromotionCentaur:
9375 case BlackPromotionCentaur:
9376 case WhitePromotionQueen:
9377 case BlackPromotionQueen:
9378 case WhitePromotionRook:
9379 case BlackPromotionRook:
9380 case WhitePromotionBishop:
9381 case BlackPromotionBishop:
9382 case WhitePromotionKnight:
9383 case BlackPromotionKnight:
9384 case WhitePromotionKing:
9385 case BlackPromotionKing:
9387 case WhiteKingSideCastle:
9388 case WhiteQueenSideCastle:
9389 case BlackKingSideCastle:
9390 case BlackQueenSideCastle:
9391 case WhiteKingSideCastleWild:
9392 case WhiteQueenSideCastleWild:
9393 case BlackKingSideCastleWild:
9394 case BlackQueenSideCastleWild:
9396 case WhiteHSideCastleFR:
9397 case WhiteASideCastleFR:
9398 case BlackHSideCastleFR:
9399 case BlackASideCastleFR:
9401 if (appData.debugMode)
9402 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
9403 fromX = currentMoveString[0] - AAA;
9404 fromY = currentMoveString[1] - ONE;
9405 toX = currentMoveString[2] - AAA;
9406 toY = currentMoveString[3] - ONE;
9407 promoChar = currentMoveString[4];
9412 if (appData.debugMode)
9413 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
9414 fromX = moveType == WhiteDrop ?
9415 (int) CharToPiece(ToUpper(currentMoveString[0])) :
9416 (int) CharToPiece(ToLower(currentMoveString[0]));
9418 toX = currentMoveString[2] - AAA;
9419 toY = currentMoveString[3] - ONE;
9425 case GameUnfinished:
9426 if (appData.debugMode)
9427 fprintf(debugFP, "Parsed game end: %s\n", yy_text);
9428 p = strchr(yy_text, '{');
9429 if (p == NULL) p = strchr(yy_text, '(');
9432 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
9434 q = strchr(p, *p == '{' ? '}' : ')');
9435 if (q != NULL) *q = NULLCHAR;
9438 GameEnds(moveType, p, GE_FILE);
9440 if (cmailMsgLoaded) {
9442 flipView = WhiteOnMove(currentMove);
9443 if (moveType == GameUnfinished) flipView = !flipView;
9444 if (appData.debugMode)
9445 fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
9449 case (ChessMove) 0: /* end of file */
9450 if (appData.debugMode)
9451 fprintf(debugFP, "Parser hit end of file\n");
9452 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9458 if (WhiteOnMove(currentMove)) {
9459 GameEnds(BlackWins, "Black mates", GE_FILE);
9461 GameEnds(WhiteWins, "White mates", GE_FILE);
9465 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
9472 if (lastLoadGameStart == GNUChessGame) {
9473 /* GNUChessGames have numbers, but they aren't move numbers */
9474 if (appData.debugMode)
9475 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
9476 yy_text, (int) moveType);
9477 return LoadGameOneMove((ChessMove)0); /* tail recursion */
9479 /* else fall thru */
9484 /* Reached start of next game in file */
9485 if (appData.debugMode)
9486 fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
9487 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9493 if (WhiteOnMove(currentMove)) {
9494 GameEnds(BlackWins, "Black mates", GE_FILE);
9496 GameEnds(WhiteWins, "White mates", GE_FILE);
9500 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
9506 case PositionDiagram: /* should not happen; ignore */
9507 case ElapsedTime: /* ignore */
9508 case NAG: /* ignore */
9509 if (appData.debugMode)
9510 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
9511 yy_text, (int) moveType);
9512 return LoadGameOneMove((ChessMove)0); /* tail recursion */
9515 if (appData.testLegality) {
9516 if (appData.debugMode)
9517 fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
9518 sprintf(move, _("Illegal move: %d.%s%s"),
9519 (forwardMostMove / 2) + 1,
9520 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9521 DisplayError(move, 0);
9524 if (appData.debugMode)
9525 fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
9526 yy_text, currentMoveString);
9527 fromX = currentMoveString[0] - AAA;
9528 fromY = currentMoveString[1] - ONE;
9529 toX = currentMoveString[2] - AAA;
9530 toY = currentMoveString[3] - ONE;
9531 promoChar = currentMoveString[4];
9536 if (appData.debugMode)
9537 fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
9538 sprintf(move, _("Ambiguous move: %d.%s%s"),
9539 (forwardMostMove / 2) + 1,
9540 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9541 DisplayError(move, 0);
9546 case ImpossibleMove:
9547 if (appData.debugMode)
9548 fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
9549 sprintf(move, _("Illegal move: %d.%s%s"),
9550 (forwardMostMove / 2) + 1,
9551 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9552 DisplayError(move, 0);
9558 if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
9559 DrawPosition(FALSE, boards[currentMove]);
9560 DisplayBothClocks();
9561 if (!appData.matchMode) // [HGM] PV info: routine tests if empty
9562 DisplayComment(currentMove - 1, commentList[currentMove]);
9564 (void) StopLoadGameTimer();
9566 cmailOldMove = forwardMostMove;
9569 /* currentMoveString is set as a side-effect of yylex */
9570 strcat(currentMoveString, "\n");
9571 strcpy(moveList[forwardMostMove], currentMoveString);
9573 thinkOutput[0] = NULLCHAR;
9574 MakeMove(fromX, fromY, toX, toY, promoChar);
9575 currentMove = forwardMostMove;
9580 /* Load the nth game from the given file */
9582 LoadGameFromFile(filename, n, title, useList)
9586 /*Boolean*/ int useList;
9591 if (strcmp(filename, "-") == 0) {
9595 f = fopen(filename, "rb");
9597 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9598 DisplayError(buf, errno);
9602 if (fseek(f, 0, 0) == -1) {
9603 /* f is not seekable; probably a pipe */
9606 if (useList && n == 0) {
9607 int error = GameListBuild(f);
9609 DisplayError(_("Cannot build game list"), error);
9610 } else if (!ListEmpty(&gameList) &&
9611 ((ListGame *) gameList.tailPred)->number > 1) {
9612 GameListPopUp(f, title);
9619 return LoadGame(f, n, title, FALSE);
9624 MakeRegisteredMove()
9626 int fromX, fromY, toX, toY;
9628 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
9629 switch (cmailMoveType[lastLoadGameNumber - 1]) {
9632 if (appData.debugMode)
9633 fprintf(debugFP, "Restoring %s for game %d\n",
9634 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
9636 thinkOutput[0] = NULLCHAR;
9637 strcpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1]);
9638 fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
9639 fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
9640 toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
9641 toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
9642 promoChar = cmailMove[lastLoadGameNumber - 1][4];
9643 MakeMove(fromX, fromY, toX, toY, promoChar);
9644 ShowMove(fromX, fromY, toX, toY);
9646 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9653 if (WhiteOnMove(currentMove)) {
9654 GameEnds(BlackWins, "Black mates", GE_PLAYER);
9656 GameEnds(WhiteWins, "White mates", GE_PLAYER);
9661 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
9668 if (WhiteOnMove(currentMove)) {
9669 GameEnds(BlackWins, "White resigns", GE_PLAYER);
9671 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
9676 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
9687 /* Wrapper around LoadGame for use when a Cmail message is loaded */
9689 CmailLoadGame(f, gameNumber, title, useList)
9697 if (gameNumber > nCmailGames) {
9698 DisplayError(_("No more games in this message"), 0);
9701 if (f == lastLoadGameFP) {
9702 int offset = gameNumber - lastLoadGameNumber;
9704 cmailMsg[0] = NULLCHAR;
9705 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
9706 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
9707 nCmailMovesRegistered--;
9709 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
9710 if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
9711 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
9714 if (! RegisterMove()) return FALSE;
9718 retVal = LoadGame(f, gameNumber, title, useList);
9720 /* Make move registered during previous look at this game, if any */
9721 MakeRegisteredMove();
9723 if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
9724 commentList[currentMove]
9725 = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
9726 DisplayComment(currentMove - 1, commentList[currentMove]);
9732 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
9737 int gameNumber = lastLoadGameNumber + offset;
9738 if (lastLoadGameFP == NULL) {
9739 DisplayError(_("No game has been loaded yet"), 0);
9742 if (gameNumber <= 0) {
9743 DisplayError(_("Can't back up any further"), 0);
9746 if (cmailMsgLoaded) {
9747 return CmailLoadGame(lastLoadGameFP, gameNumber,
9748 lastLoadGameTitle, lastLoadGameUseList);
9750 return LoadGame(lastLoadGameFP, gameNumber,
9751 lastLoadGameTitle, lastLoadGameUseList);
9757 /* Load the nth game from open file f */
9759 LoadGame(f, gameNumber, title, useList)
9767 int gn = gameNumber;
9768 ListGame *lg = NULL;
9771 GameMode oldGameMode;
9772 VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
9774 if (appData.debugMode)
9775 fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
9777 if (gameMode == Training )
9778 SetTrainingModeOff();
9780 oldGameMode = gameMode;
9781 if (gameMode != BeginningOfGame) {
9786 if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
9787 fclose(lastLoadGameFP);
9791 lg = (ListGame *) ListElem(&gameList, gameNumber-1);
9794 fseek(f, lg->offset, 0);
9795 GameListHighlight(gameNumber);
9799 DisplayError(_("Game number out of range"), 0);
9804 if (fseek(f, 0, 0) == -1) {
9805 if (f == lastLoadGameFP ?
9806 gameNumber == lastLoadGameNumber + 1 :
9810 DisplayError(_("Can't seek on game file"), 0);
9816 lastLoadGameNumber = gameNumber;
9817 strcpy(lastLoadGameTitle, title);
9818 lastLoadGameUseList = useList;
9822 if (lg && lg->gameInfo.white && lg->gameInfo.black) {
9823 snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
9824 lg->gameInfo.black);
9826 } else if (*title != NULLCHAR) {
9827 if (gameNumber > 1) {
9828 sprintf(buf, "%s %d", title, gameNumber);
9831 DisplayTitle(title);
9835 if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
9836 gameMode = PlayFromGameFile;
9840 currentMove = forwardMostMove = backwardMostMove = 0;
9841 CopyBoard(boards[0], initialPosition);
9845 * Skip the first gn-1 games in the file.
9846 * Also skip over anything that precedes an identifiable
9847 * start of game marker, to avoid being confused by
9848 * garbage at the start of the file. Currently
9849 * recognized start of game markers are the move number "1",
9850 * the pattern "gnuchess .* game", the pattern
9851 * "^[#;%] [^ ]* game file", and a PGN tag block.
9852 * A game that starts with one of the latter two patterns
9853 * will also have a move number 1, possibly
9854 * following a position diagram.
9855 * 5-4-02: Let's try being more lenient and allowing a game to
9856 * start with an unnumbered move. Does that break anything?
9858 cm = lastLoadGameStart = (ChessMove) 0;
9860 yyboardindex = forwardMostMove;
9861 cm = (ChessMove) yylex();
9864 if (cmailMsgLoaded) {
9865 nCmailGames = CMAIL_MAX_GAMES - gn;
9868 DisplayError(_("Game not found in file"), 0);
9875 lastLoadGameStart = cm;
9879 switch (lastLoadGameStart) {
9886 gn--; /* count this game */
9887 lastLoadGameStart = cm;
9896 switch (lastLoadGameStart) {
9901 gn--; /* count this game */
9902 lastLoadGameStart = cm;
9905 lastLoadGameStart = cm; /* game counted already */
9913 yyboardindex = forwardMostMove;
9914 cm = (ChessMove) yylex();
9915 } while (cm == PGNTag || cm == Comment);
9922 if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
9923 if ( cmailResult[CMAIL_MAX_GAMES - gn - 1]
9924 != CMAIL_OLD_RESULT) {
9926 cmailResult[ CMAIL_MAX_GAMES
9927 - gn - 1] = CMAIL_OLD_RESULT;
9933 /* Only a NormalMove can be at the start of a game
9934 * without a position diagram. */
9935 if (lastLoadGameStart == (ChessMove) 0) {
9937 lastLoadGameStart = MoveNumberOne;
9946 if (appData.debugMode)
9947 fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
9949 if (cm == XBoardGame) {
9950 /* Skip any header junk before position diagram and/or move 1 */
9952 yyboardindex = forwardMostMove;
9953 cm = (ChessMove) yylex();
9955 if (cm == (ChessMove) 0 ||
9956 cm == GNUChessGame || cm == XBoardGame) {
9957 /* Empty game; pretend end-of-file and handle later */
9962 if (cm == MoveNumberOne || cm == PositionDiagram ||
9963 cm == PGNTag || cm == Comment)
9966 } else if (cm == GNUChessGame) {
9967 if (gameInfo.event != NULL) {
9968 free(gameInfo.event);
9970 gameInfo.event = StrSave(yy_text);
9973 startedFromSetupPosition = FALSE;
9974 while (cm == PGNTag) {
9975 if (appData.debugMode)
9976 fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
9977 err = ParsePGNTag(yy_text, &gameInfo);
9978 if (!err) numPGNTags++;
9980 /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
9981 if(gameInfo.variant != oldVariant) {
9982 startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
9984 oldVariant = gameInfo.variant;
9985 if (appData.debugMode)
9986 fprintf(debugFP, "New variant %d\n", (int) oldVariant);
9990 if (gameInfo.fen != NULL) {
9991 Board initial_position;
9992 startedFromSetupPosition = TRUE;
9993 if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
9995 DisplayError(_("Bad FEN position in file"), 0);
9998 CopyBoard(boards[0], initial_position);
9999 if (blackPlaysFirst) {
10000 currentMove = forwardMostMove = backwardMostMove = 1;
10001 CopyBoard(boards[1], initial_position);
10002 strcpy(moveList[0], "");
10003 strcpy(parseList[0], "");
10004 timeRemaining[0][1] = whiteTimeRemaining;
10005 timeRemaining[1][1] = blackTimeRemaining;
10006 if (commentList[0] != NULL) {
10007 commentList[1] = commentList[0];
10008 commentList[0] = NULL;
10011 currentMove = forwardMostMove = backwardMostMove = 0;
10013 /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
10015 initialRulePlies = FENrulePlies;
10016 for( i=0; i< nrCastlingRights; i++ )
10017 initialRights[i] = initial_position[CASTLING][i];
10019 yyboardindex = forwardMostMove;
10020 free(gameInfo.fen);
10021 gameInfo.fen = NULL;
10024 yyboardindex = forwardMostMove;
10025 cm = (ChessMove) yylex();
10027 /* Handle comments interspersed among the tags */
10028 while (cm == Comment) {
10030 if (appData.debugMode)
10031 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10033 AppendComment(currentMove, p, FALSE);
10034 yyboardindex = forwardMostMove;
10035 cm = (ChessMove) yylex();
10039 /* don't rely on existence of Event tag since if game was
10040 * pasted from clipboard the Event tag may not exist
10042 if (numPGNTags > 0){
10044 if (gameInfo.variant == VariantNormal) {
10045 gameInfo.variant = StringToVariant(gameInfo.event);
10048 if( appData.autoDisplayTags ) {
10049 tags = PGNTags(&gameInfo);
10050 TagsPopUp(tags, CmailMsg());
10055 /* Make something up, but don't display it now */
10060 if (cm == PositionDiagram) {
10063 Board initial_position;
10065 if (appData.debugMode)
10066 fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
10068 if (!startedFromSetupPosition) {
10070 for (i = BOARD_HEIGHT - 1; i >= 0; i--)
10071 for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
10081 initial_position[i][j++] = CharToPiece(*p);
10084 while (*p == ' ' || *p == '\t' ||
10085 *p == '\n' || *p == '\r') p++;
10087 if (strncmp(p, "black", strlen("black"))==0)
10088 blackPlaysFirst = TRUE;
10090 blackPlaysFirst = FALSE;
10091 startedFromSetupPosition = TRUE;
10093 CopyBoard(boards[0], initial_position);
10094 if (blackPlaysFirst) {
10095 currentMove = forwardMostMove = backwardMostMove = 1;
10096 CopyBoard(boards[1], initial_position);
10097 strcpy(moveList[0], "");
10098 strcpy(parseList[0], "");
10099 timeRemaining[0][1] = whiteTimeRemaining;
10100 timeRemaining[1][1] = blackTimeRemaining;
10101 if (commentList[0] != NULL) {
10102 commentList[1] = commentList[0];
10103 commentList[0] = NULL;
10106 currentMove = forwardMostMove = backwardMostMove = 0;
10109 yyboardindex = forwardMostMove;
10110 cm = (ChessMove) yylex();
10113 if (first.pr == NoProc) {
10114 StartChessProgram(&first);
10116 InitChessProgram(&first, FALSE);
10117 SendToProgram("force\n", &first);
10118 if (startedFromSetupPosition) {
10119 SendBoard(&first, forwardMostMove);
10120 if (appData.debugMode) {
10121 fprintf(debugFP, "Load Game\n");
10123 DisplayBothClocks();
10126 /* [HGM] server: flag to write setup moves in broadcast file as one */
10127 loadFlag = appData.suppressLoadMoves;
10129 while (cm == Comment) {
10131 if (appData.debugMode)
10132 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10134 AppendComment(currentMove, p, FALSE);
10135 yyboardindex = forwardMostMove;
10136 cm = (ChessMove) yylex();
10139 if ((cm == (ChessMove) 0 && lastLoadGameStart != (ChessMove) 0) ||
10140 cm == WhiteWins || cm == BlackWins ||
10141 cm == GameIsDrawn || cm == GameUnfinished) {
10142 DisplayMessage("", _("No moves in game"));
10143 if (cmailMsgLoaded) {
10144 if (appData.debugMode)
10145 fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
10149 DrawPosition(FALSE, boards[currentMove]);
10150 DisplayBothClocks();
10151 gameMode = EditGame;
10158 // [HGM] PV info: routine tests if comment empty
10159 if (!matchMode && (pausing || appData.timeDelay != 0)) {
10160 DisplayComment(currentMove - 1, commentList[currentMove]);
10162 if (!matchMode && appData.timeDelay != 0)
10163 DrawPosition(FALSE, boards[currentMove]);
10165 if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
10166 programStats.ok_to_send = 1;
10169 /* if the first token after the PGN tags is a move
10170 * and not move number 1, retrieve it from the parser
10172 if (cm != MoveNumberOne)
10173 LoadGameOneMove(cm);
10175 /* load the remaining moves from the file */
10176 while (LoadGameOneMove((ChessMove)0)) {
10177 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10178 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10181 /* rewind to the start of the game */
10182 currentMove = backwardMostMove;
10184 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10186 if (oldGameMode == AnalyzeFile ||
10187 oldGameMode == AnalyzeMode) {
10188 AnalyzeFileEvent();
10191 if (matchMode || appData.timeDelay == 0) {
10193 gameMode = EditGame;
10195 } else if (appData.timeDelay > 0) {
10196 AutoPlayGameLoop();
10199 if (appData.debugMode)
10200 fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
10202 loadFlag = 0; /* [HGM] true game starts */
10206 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
10208 ReloadPosition(offset)
10211 int positionNumber = lastLoadPositionNumber + offset;
10212 if (lastLoadPositionFP == NULL) {
10213 DisplayError(_("No position has been loaded yet"), 0);
10216 if (positionNumber <= 0) {
10217 DisplayError(_("Can't back up any further"), 0);
10220 return LoadPosition(lastLoadPositionFP, positionNumber,
10221 lastLoadPositionTitle);
10224 /* Load the nth position from the given file */
10226 LoadPositionFromFile(filename, n, title)
10234 if (strcmp(filename, "-") == 0) {
10235 return LoadPosition(stdin, n, "stdin");
10237 f = fopen(filename, "rb");
10239 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10240 DisplayError(buf, errno);
10243 return LoadPosition(f, n, title);
10248 /* Load the nth position from the given open file, and close it */
10250 LoadPosition(f, positionNumber, title)
10252 int positionNumber;
10255 char *p, line[MSG_SIZ];
10256 Board initial_position;
10257 int i, j, fenMode, pn;
10259 if (gameMode == Training )
10260 SetTrainingModeOff();
10262 if (gameMode != BeginningOfGame) {
10263 Reset(FALSE, TRUE);
10265 if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
10266 fclose(lastLoadPositionFP);
10268 if (positionNumber == 0) positionNumber = 1;
10269 lastLoadPositionFP = f;
10270 lastLoadPositionNumber = positionNumber;
10271 strcpy(lastLoadPositionTitle, title);
10272 if (first.pr == NoProc) {
10273 StartChessProgram(&first);
10274 InitChessProgram(&first, FALSE);
10276 pn = positionNumber;
10277 if (positionNumber < 0) {
10278 /* Negative position number means to seek to that byte offset */
10279 if (fseek(f, -positionNumber, 0) == -1) {
10280 DisplayError(_("Can't seek on position file"), 0);
10285 if (fseek(f, 0, 0) == -1) {
10286 if (f == lastLoadPositionFP ?
10287 positionNumber == lastLoadPositionNumber + 1 :
10288 positionNumber == 1) {
10291 DisplayError(_("Can't seek on position file"), 0);
10296 /* See if this file is FEN or old-style xboard */
10297 if (fgets(line, MSG_SIZ, f) == NULL) {
10298 DisplayError(_("Position not found in file"), 0);
10301 // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
10302 fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
10305 if (fenMode || line[0] == '#') pn--;
10307 /* skip positions before number pn */
10308 if (fgets(line, MSG_SIZ, f) == NULL) {
10310 DisplayError(_("Position not found in file"), 0);
10313 if (fenMode || line[0] == '#') pn--;
10318 if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
10319 DisplayError(_("Bad FEN position in file"), 0);
10323 (void) fgets(line, MSG_SIZ, f);
10324 (void) fgets(line, MSG_SIZ, f);
10326 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
10327 (void) fgets(line, MSG_SIZ, f);
10328 for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
10331 initial_position[i][j++] = CharToPiece(*p);
10335 blackPlaysFirst = FALSE;
10337 (void) fgets(line, MSG_SIZ, f);
10338 if (strncmp(line, "black", strlen("black"))==0)
10339 blackPlaysFirst = TRUE;
10342 startedFromSetupPosition = TRUE;
10344 SendToProgram("force\n", &first);
10345 CopyBoard(boards[0], initial_position);
10346 if (blackPlaysFirst) {
10347 currentMove = forwardMostMove = backwardMostMove = 1;
10348 strcpy(moveList[0], "");
10349 strcpy(parseList[0], "");
10350 CopyBoard(boards[1], initial_position);
10351 DisplayMessage("", _("Black to play"));
10353 currentMove = forwardMostMove = backwardMostMove = 0;
10354 DisplayMessage("", _("White to play"));
10356 initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
10357 SendBoard(&first, forwardMostMove);
10358 if (appData.debugMode) {
10360 for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
10361 for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
10362 fprintf(debugFP, "Load Position\n");
10365 if (positionNumber > 1) {
10366 sprintf(line, "%s %d", title, positionNumber);
10367 DisplayTitle(line);
10369 DisplayTitle(title);
10371 gameMode = EditGame;
10374 timeRemaining[0][1] = whiteTimeRemaining;
10375 timeRemaining[1][1] = blackTimeRemaining;
10376 DrawPosition(FALSE, boards[currentMove]);
10383 CopyPlayerNameIntoFileName(dest, src)
10386 while (*src != NULLCHAR && *src != ',') {
10391 *(*dest)++ = *src++;
10396 char *DefaultFileName(ext)
10399 static char def[MSG_SIZ];
10402 if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
10404 CopyPlayerNameIntoFileName(&p, gameInfo.white);
10406 CopyPlayerNameIntoFileName(&p, gameInfo.black);
10415 /* Save the current game to the given file */
10417 SaveGameToFile(filename, append)
10424 if (strcmp(filename, "-") == 0) {
10425 return SaveGame(stdout, 0, NULL);
10427 f = fopen(filename, append ? "a" : "w");
10429 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10430 DisplayError(buf, errno);
10433 return SaveGame(f, 0, NULL);
10442 static char buf[MSG_SIZ];
10445 p = strchr(str, ' ');
10446 if (p == NULL) return str;
10447 strncpy(buf, str, p - str);
10448 buf[p - str] = NULLCHAR;
10452 #define PGN_MAX_LINE 75
10454 #define PGN_SIDE_WHITE 0
10455 #define PGN_SIDE_BLACK 1
10458 static int FindFirstMoveOutOfBook( int side )
10462 if( backwardMostMove == 0 && ! startedFromSetupPosition) {
10463 int index = backwardMostMove;
10464 int has_book_hit = 0;
10466 if( (index % 2) != side ) {
10470 while( index < forwardMostMove ) {
10471 /* Check to see if engine is in book */
10472 int depth = pvInfoList[index].depth;
10473 int score = pvInfoList[index].score;
10479 else if( score == 0 && depth == 63 ) {
10480 in_book = 1; /* Zappa */
10482 else if( score == 2 && depth == 99 ) {
10483 in_book = 1; /* Abrok */
10486 has_book_hit += in_book;
10502 void GetOutOfBookInfo( char * buf )
10506 int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10508 oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
10509 oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
10513 if( oob[0] >= 0 || oob[1] >= 0 ) {
10514 for( i=0; i<2; i++ ) {
10518 if( i > 0 && oob[0] >= 0 ) {
10519 strcat( buf, " " );
10522 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
10523 sprintf( buf+strlen(buf), "%s%.2f",
10524 pvInfoList[idx].score >= 0 ? "+" : "",
10525 pvInfoList[idx].score / 100.0 );
10531 /* Save game in PGN style and close the file */
10536 int i, offset, linelen, newblock;
10540 int movelen, numlen, blank;
10541 char move_buffer[100]; /* [AS] Buffer for move+PV info */
10543 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10545 tm = time((time_t *) NULL);
10547 PrintPGNTags(f, &gameInfo);
10549 if (backwardMostMove > 0 || startedFromSetupPosition) {
10550 char *fen = PositionToFEN(backwardMostMove, NULL);
10551 fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
10552 fprintf(f, "\n{--------------\n");
10553 PrintPosition(f, backwardMostMove);
10554 fprintf(f, "--------------}\n");
10558 /* [AS] Out of book annotation */
10559 if( appData.saveOutOfBookInfo ) {
10562 GetOutOfBookInfo( buf );
10564 if( buf[0] != '\0' ) {
10565 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
10572 i = backwardMostMove;
10576 while (i < forwardMostMove) {
10577 /* Print comments preceding this move */
10578 if (commentList[i] != NULL) {
10579 if (linelen > 0) fprintf(f, "\n");
10580 fprintf(f, "%s", commentList[i]);
10585 /* Format move number */
10586 if ((i % 2) == 0) {
10587 sprintf(numtext, "%d.", (i - offset)/2 + 1);
10590 sprintf(numtext, "%d...", (i - offset)/2 + 1);
10592 numtext[0] = NULLCHAR;
10595 numlen = strlen(numtext);
10598 /* Print move number */
10599 blank = linelen > 0 && numlen > 0;
10600 if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
10609 fprintf(f, "%s", numtext);
10613 strcpy(move_buffer, SavePart(parseList[i])); // [HGM] pgn: print move via buffer, so it can be edited
10614 movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
10617 blank = linelen > 0 && movelen > 0;
10618 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
10627 fprintf(f, "%s", move_buffer);
10628 linelen += movelen;
10630 /* [AS] Add PV info if present */
10631 if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
10632 /* [HGM] add time */
10633 char buf[MSG_SIZ]; int seconds;
10635 seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
10637 if( seconds <= 0) buf[0] = 0; else
10638 if( seconds < 30 ) sprintf(buf, " %3.1f%c", seconds/10., 0); else {
10639 seconds = (seconds + 4)/10; // round to full seconds
10640 if( seconds < 60 ) sprintf(buf, " %d%c", seconds, 0); else
10641 sprintf(buf, " %d:%02d%c", seconds/60, seconds%60, 0);
10644 sprintf( move_buffer, "{%s%.2f/%d%s}",
10645 pvInfoList[i].score >= 0 ? "+" : "",
10646 pvInfoList[i].score / 100.0,
10647 pvInfoList[i].depth,
10650 movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
10652 /* Print score/depth */
10653 blank = linelen > 0 && movelen > 0;
10654 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
10663 fprintf(f, "%s", move_buffer);
10664 linelen += movelen;
10670 /* Start a new line */
10671 if (linelen > 0) fprintf(f, "\n");
10673 /* Print comments after last move */
10674 if (commentList[i] != NULL) {
10675 fprintf(f, "%s\n", commentList[i]);
10679 if (gameInfo.resultDetails != NULL &&
10680 gameInfo.resultDetails[0] != NULLCHAR) {
10681 fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
10682 PGNResult(gameInfo.result));
10684 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
10688 lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
10692 /* Save game in old style and close the file */
10694 SaveGameOldStyle(f)
10700 tm = time((time_t *) NULL);
10702 fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
10705 if (backwardMostMove > 0 || startedFromSetupPosition) {
10706 fprintf(f, "\n[--------------\n");
10707 PrintPosition(f, backwardMostMove);
10708 fprintf(f, "--------------]\n");
10713 i = backwardMostMove;
10714 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10716 while (i < forwardMostMove) {
10717 if (commentList[i] != NULL) {
10718 fprintf(f, "[%s]\n", commentList[i]);
10721 if ((i % 2) == 1) {
10722 fprintf(f, "%d. ... %s\n", (i - offset)/2 + 1, parseList[i]);
10725 fprintf(f, "%d. %s ", (i - offset)/2 + 1, parseList[i]);
10727 if (commentList[i] != NULL) {
10731 if (i >= forwardMostMove) {
10735 fprintf(f, "%s\n", parseList[i]);
10740 if (commentList[i] != NULL) {
10741 fprintf(f, "[%s]\n", commentList[i]);
10744 /* This isn't really the old style, but it's close enough */
10745 if (gameInfo.resultDetails != NULL &&
10746 gameInfo.resultDetails[0] != NULLCHAR) {
10747 fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
10748 gameInfo.resultDetails);
10750 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
10757 /* Save the current game to open file f and close the file */
10759 SaveGame(f, dummy, dummy2)
10764 if (gameMode == EditPosition) EditPositionDone(TRUE);
10765 lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
10766 if (appData.oldSaveStyle)
10767 return SaveGameOldStyle(f);
10769 return SaveGamePGN(f);
10772 /* Save the current position to the given file */
10774 SavePositionToFile(filename)
10780 if (strcmp(filename, "-") == 0) {
10781 return SavePosition(stdout, 0, NULL);
10783 f = fopen(filename, "a");
10785 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10786 DisplayError(buf, errno);
10789 SavePosition(f, 0, NULL);
10795 /* Save the current position to the given open file and close the file */
10797 SavePosition(f, dummy, dummy2)
10805 if (gameMode == EditPosition) EditPositionDone(TRUE);
10806 if (appData.oldSaveStyle) {
10807 tm = time((time_t *) NULL);
10809 fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
10811 fprintf(f, "[--------------\n");
10812 PrintPosition(f, currentMove);
10813 fprintf(f, "--------------]\n");
10815 fen = PositionToFEN(currentMove, NULL);
10816 fprintf(f, "%s\n", fen);
10824 ReloadCmailMsgEvent(unregister)
10828 static char *inFilename = NULL;
10829 static char *outFilename;
10831 struct stat inbuf, outbuf;
10834 /* Any registered moves are unregistered if unregister is set, */
10835 /* i.e. invoked by the signal handler */
10837 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
10838 cmailMoveRegistered[i] = FALSE;
10839 if (cmailCommentList[i] != NULL) {
10840 free(cmailCommentList[i]);
10841 cmailCommentList[i] = NULL;
10844 nCmailMovesRegistered = 0;
10847 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
10848 cmailResult[i] = CMAIL_NOT_RESULT;
10852 if (inFilename == NULL) {
10853 /* Because the filenames are static they only get malloced once */
10854 /* and they never get freed */
10855 inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
10856 sprintf(inFilename, "%s.game.in", appData.cmailGameName);
10858 outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
10859 sprintf(outFilename, "%s.out", appData.cmailGameName);
10862 status = stat(outFilename, &outbuf);
10864 cmailMailedMove = FALSE;
10866 status = stat(inFilename, &inbuf);
10867 cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
10870 /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
10871 counts the games, notes how each one terminated, etc.
10873 It would be nice to remove this kludge and instead gather all
10874 the information while building the game list. (And to keep it
10875 in the game list nodes instead of having a bunch of fixed-size
10876 parallel arrays.) Note this will require getting each game's
10877 termination from the PGN tags, as the game list builder does
10878 not process the game moves. --mann
10880 cmailMsgLoaded = TRUE;
10881 LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
10883 /* Load first game in the file or popup game menu */
10884 LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
10886 #endif /* !WIN32 */
10894 char string[MSG_SIZ];
10896 if ( cmailMailedMove
10897 || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
10898 return TRUE; /* Allow free viewing */
10901 /* Unregister move to ensure that we don't leave RegisterMove */
10902 /* with the move registered when the conditions for registering no */
10904 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
10905 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
10906 nCmailMovesRegistered --;
10908 if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
10910 free(cmailCommentList[lastLoadGameNumber - 1]);
10911 cmailCommentList[lastLoadGameNumber - 1] = NULL;
10915 if (cmailOldMove == -1) {
10916 DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
10920 if (currentMove > cmailOldMove + 1) {
10921 DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
10925 if (currentMove < cmailOldMove) {
10926 DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
10930 if (forwardMostMove > currentMove) {
10931 /* Silently truncate extra moves */
10935 if ( (currentMove == cmailOldMove + 1)
10936 || ( (currentMove == cmailOldMove)
10937 && ( (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
10938 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
10939 if (gameInfo.result != GameUnfinished) {
10940 cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
10943 if (commentList[currentMove] != NULL) {
10944 cmailCommentList[lastLoadGameNumber - 1]
10945 = StrSave(commentList[currentMove]);
10947 strcpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1]);
10949 if (appData.debugMode)
10950 fprintf(debugFP, "Saving %s for game %d\n",
10951 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
10954 "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
10956 f = fopen(string, "w");
10957 if (appData.oldSaveStyle) {
10958 SaveGameOldStyle(f); /* also closes the file */
10960 sprintf(string, "%s.pos.out", appData.cmailGameName);
10961 f = fopen(string, "w");
10962 SavePosition(f, 0, NULL); /* also closes the file */
10964 fprintf(f, "{--------------\n");
10965 PrintPosition(f, currentMove);
10966 fprintf(f, "--------------}\n\n");
10968 SaveGame(f, 0, NULL); /* also closes the file*/
10971 cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
10972 nCmailMovesRegistered ++;
10973 } else if (nCmailGames == 1) {
10974 DisplayError(_("You have not made a move yet"), 0);
10985 static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
10986 FILE *commandOutput;
10987 char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
10988 int nBytes = 0; /* Suppress warnings on uninitialized variables */
10994 if (! cmailMsgLoaded) {
10995 DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
10999 if (nCmailGames == nCmailResults) {
11000 DisplayError(_("No unfinished games"), 0);
11004 #if CMAIL_PROHIBIT_REMAIL
11005 if (cmailMailedMove) {
11006 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);
11007 DisplayError(msg, 0);
11012 if (! (cmailMailedMove || RegisterMove())) return;
11014 if ( cmailMailedMove
11015 || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
11016 sprintf(string, partCommandString,
11017 appData.debugMode ? " -v" : "", appData.cmailGameName);
11018 commandOutput = popen(string, "r");
11020 if (commandOutput == NULL) {
11021 DisplayError(_("Failed to invoke cmail"), 0);
11023 for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
11024 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
11026 if (nBuffers > 1) {
11027 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
11028 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
11029 nBytes = MSG_SIZ - 1;
11031 (void) memcpy(msg, buffer, nBytes);
11033 *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
11035 if(StrStr(msg, "Mailed cmail message to ") != NULL) {
11036 cmailMailedMove = TRUE; /* Prevent >1 moves */
11039 for (i = 0; i < nCmailGames; i ++) {
11040 if (cmailResult[i] == CMAIL_NOT_RESULT) {
11045 && ( (arcDir = (char *) getenv("CMAIL_ARCDIR"))
11047 sprintf(buffer, "%s/%s.%s.archive",
11049 appData.cmailGameName,
11051 LoadGameFromFile(buffer, 1, buffer, FALSE);
11052 cmailMsgLoaded = FALSE;
11056 DisplayInformation(msg);
11057 pclose(commandOutput);
11060 if ((*cmailMsg) != '\0') {
11061 DisplayInformation(cmailMsg);
11066 #endif /* !WIN32 */
11075 int prependComma = 0;
11077 char string[MSG_SIZ]; /* Space for game-list */
11080 if (!cmailMsgLoaded) return "";
11082 if (cmailMailedMove) {
11083 sprintf(cmailMsg, _("Waiting for reply from opponent\n"));
11085 /* Create a list of games left */
11086 sprintf(string, "[");
11087 for (i = 0; i < nCmailGames; i ++) {
11088 if (! ( cmailMoveRegistered[i]
11089 || (cmailResult[i] == CMAIL_OLD_RESULT))) {
11090 if (prependComma) {
11091 sprintf(number, ",%d", i + 1);
11093 sprintf(number, "%d", i + 1);
11097 strcat(string, number);
11100 strcat(string, "]");
11102 if (nCmailMovesRegistered + nCmailResults == 0) {
11103 switch (nCmailGames) {
11106 _("Still need to make move for game\n"));
11111 _("Still need to make moves for both games\n"));
11116 _("Still need to make moves for all %d games\n"),
11121 switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
11124 _("Still need to make a move for game %s\n"),
11129 if (nCmailResults == nCmailGames) {
11130 sprintf(cmailMsg, _("No unfinished games\n"));
11132 sprintf(cmailMsg, _("Ready to send mail\n"));
11138 _("Still need to make moves for games %s\n"),
11150 if (gameMode == Training)
11151 SetTrainingModeOff();
11154 cmailMsgLoaded = FALSE;
11155 if (appData.icsActive) {
11156 SendToICS(ics_prefix);
11157 SendToICS("refresh\n");
11167 /* Give up on clean exit */
11171 /* Keep trying for clean exit */
11175 if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
11177 if (telnetISR != NULL) {
11178 RemoveInputSource(telnetISR);
11180 if (icsPR != NoProc) {
11181 DestroyChildProcess(icsPR, TRUE);
11184 /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
11185 GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
11187 /* [HGM] crash: the above GameEnds() is a dud if another one was running */
11188 /* make sure this other one finishes before killing it! */
11189 if(endingGame) { int count = 0;
11190 if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
11191 while(endingGame && count++ < 10) DoSleep(1);
11192 if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
11195 /* Kill off chess programs */
11196 if (first.pr != NoProc) {
11199 DoSleep( appData.delayBeforeQuit );
11200 SendToProgram("quit\n", &first);
11201 DoSleep( appData.delayAfterQuit );
11202 DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
11204 if (second.pr != NoProc) {
11205 DoSleep( appData.delayBeforeQuit );
11206 SendToProgram("quit\n", &second);
11207 DoSleep( appData.delayAfterQuit );
11208 DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
11210 if (first.isr != NULL) {
11211 RemoveInputSource(first.isr);
11213 if (second.isr != NULL) {
11214 RemoveInputSource(second.isr);
11217 ShutDownFrontEnd();
11224 if (appData.debugMode)
11225 fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
11229 if (gameMode == MachinePlaysWhite ||
11230 gameMode == MachinePlaysBlack) {
11233 DisplayBothClocks();
11235 if (gameMode == PlayFromGameFile) {
11236 if (appData.timeDelay >= 0)
11237 AutoPlayGameLoop();
11238 } else if (gameMode == IcsExamining && pauseExamInvalid) {
11239 Reset(FALSE, TRUE);
11240 SendToICS(ics_prefix);
11241 SendToICS("refresh\n");
11242 } else if (currentMove < forwardMostMove) {
11243 ForwardInner(forwardMostMove);
11245 pauseExamInvalid = FALSE;
11247 switch (gameMode) {
11251 pauseExamForwardMostMove = forwardMostMove;
11252 pauseExamInvalid = FALSE;
11255 case IcsPlayingWhite:
11256 case IcsPlayingBlack:
11260 case PlayFromGameFile:
11261 (void) StopLoadGameTimer();
11265 case BeginningOfGame:
11266 if (appData.icsActive) return;
11267 /* else fall through */
11268 case MachinePlaysWhite:
11269 case MachinePlaysBlack:
11270 case TwoMachinesPlay:
11271 if (forwardMostMove == 0)
11272 return; /* don't pause if no one has moved */
11273 if ((gameMode == MachinePlaysWhite &&
11274 !WhiteOnMove(forwardMostMove)) ||
11275 (gameMode == MachinePlaysBlack &&
11276 WhiteOnMove(forwardMostMove))) {
11289 char title[MSG_SIZ];
11291 if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
11292 strcpy(title, _("Edit comment"));
11294 sprintf(title, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
11295 WhiteOnMove(currentMove - 1) ? " " : ".. ",
11296 parseList[currentMove - 1]);
11299 EditCommentPopUp(currentMove, title, commentList[currentMove]);
11306 char *tags = PGNTags(&gameInfo);
11307 EditTagsPopUp(tags);
11314 if (appData.noChessProgram || gameMode == AnalyzeMode)
11317 if (gameMode != AnalyzeFile) {
11318 if (!appData.icsEngineAnalyze) {
11320 if (gameMode != EditGame) return;
11322 ResurrectChessProgram();
11323 SendToProgram("analyze\n", &first);
11324 first.analyzing = TRUE;
11325 /*first.maybeThinking = TRUE;*/
11326 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11327 EngineOutputPopUp();
11329 if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
11334 StartAnalysisClock();
11335 GetTimeMark(&lastNodeCountTime);
11342 if (appData.noChessProgram || gameMode == AnalyzeFile)
11345 if (gameMode != AnalyzeMode) {
11347 if (gameMode != EditGame) return;
11348 ResurrectChessProgram();
11349 SendToProgram("analyze\n", &first);
11350 first.analyzing = TRUE;
11351 /*first.maybeThinking = TRUE;*/
11352 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11353 EngineOutputPopUp();
11355 gameMode = AnalyzeFile;
11360 StartAnalysisClock();
11361 GetTimeMark(&lastNodeCountTime);
11366 MachineWhiteEvent()
11369 char *bookHit = NULL;
11371 if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
11375 if (gameMode == PlayFromGameFile ||
11376 gameMode == TwoMachinesPlay ||
11377 gameMode == Training ||
11378 gameMode == AnalyzeMode ||
11379 gameMode == EndOfGame)
11382 if (gameMode == EditPosition)
11383 EditPositionDone(TRUE);
11385 if (!WhiteOnMove(currentMove)) {
11386 DisplayError(_("It is not White's turn"), 0);
11390 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
11393 if (gameMode == EditGame || gameMode == AnalyzeMode ||
11394 gameMode == AnalyzeFile)
11397 ResurrectChessProgram(); /* in case it isn't running */
11398 if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
11399 gameMode = MachinePlaysWhite;
11402 gameMode = MachinePlaysWhite;
11406 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
11408 if (first.sendName) {
11409 sprintf(buf, "name %s\n", gameInfo.black);
11410 SendToProgram(buf, &first);
11412 if (first.sendTime) {
11413 if (first.useColors) {
11414 SendToProgram("black\n", &first); /*gnu kludge*/
11416 SendTimeRemaining(&first, TRUE);
11418 if (first.useColors) {
11419 SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
11421 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
11422 SetMachineThinkingEnables();
11423 first.maybeThinking = TRUE;
11427 if (appData.autoFlipView && !flipView) {
11428 flipView = !flipView;
11429 DrawPosition(FALSE, NULL);
11430 DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;
11433 if(bookHit) { // [HGM] book: simulate book reply
11434 static char bookMove[MSG_SIZ]; // a bit generous?
11436 programStats.nodes = programStats.depth = programStats.time =
11437 programStats.score = programStats.got_only_move = 0;
11438 sprintf(programStats.movelist, "%s (xbook)", bookHit);
11440 strcpy(bookMove, "move ");
11441 strcat(bookMove, bookHit);
11442 HandleMachineMove(bookMove, &first);
11447 MachineBlackEvent()
11450 char *bookHit = NULL;
11452 if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
11456 if (gameMode == PlayFromGameFile ||
11457 gameMode == TwoMachinesPlay ||
11458 gameMode == Training ||
11459 gameMode == AnalyzeMode ||
11460 gameMode == EndOfGame)
11463 if (gameMode == EditPosition)
11464 EditPositionDone(TRUE);
11466 if (WhiteOnMove(currentMove)) {
11467 DisplayError(_("It is not Black's turn"), 0);
11471 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
11474 if (gameMode == EditGame || gameMode == AnalyzeMode ||
11475 gameMode == AnalyzeFile)
11478 ResurrectChessProgram(); /* in case it isn't running */
11479 gameMode = MachinePlaysBlack;
11483 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
11485 if (first.sendName) {
11486 sprintf(buf, "name %s\n", gameInfo.white);
11487 SendToProgram(buf, &first);
11489 if (first.sendTime) {
11490 if (first.useColors) {
11491 SendToProgram("white\n", &first); /*gnu kludge*/
11493 SendTimeRemaining(&first, FALSE);
11495 if (first.useColors) {
11496 SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
11498 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
11499 SetMachineThinkingEnables();
11500 first.maybeThinking = TRUE;
11503 if (appData.autoFlipView && flipView) {
11504 flipView = !flipView;
11505 DrawPosition(FALSE, NULL);
11506 DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;
11508 if(bookHit) { // [HGM] book: simulate book reply
11509 static char bookMove[MSG_SIZ]; // a bit generous?
11511 programStats.nodes = programStats.depth = programStats.time =
11512 programStats.score = programStats.got_only_move = 0;
11513 sprintf(programStats.movelist, "%s (xbook)", bookHit);
11515 strcpy(bookMove, "move ");
11516 strcat(bookMove, bookHit);
11517 HandleMachineMove(bookMove, &first);
11523 DisplayTwoMachinesTitle()
11526 if (appData.matchGames > 0) {
11527 if (first.twoMachinesColor[0] == 'w') {
11528 sprintf(buf, "%s vs. %s (%d-%d-%d)",
11529 gameInfo.white, gameInfo.black,
11530 first.matchWins, second.matchWins,
11531 matchGame - 1 - (first.matchWins + second.matchWins));
11533 sprintf(buf, "%s vs. %s (%d-%d-%d)",
11534 gameInfo.white, gameInfo.black,
11535 second.matchWins, first.matchWins,
11536 matchGame - 1 - (first.matchWins + second.matchWins));
11539 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
11545 TwoMachinesEvent P((void))
11549 ChessProgramState *onmove;
11550 char *bookHit = NULL;
11552 if (appData.noChessProgram) return;
11554 switch (gameMode) {
11555 case TwoMachinesPlay:
11557 case MachinePlaysWhite:
11558 case MachinePlaysBlack:
11559 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
11560 DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
11564 case BeginningOfGame:
11565 case PlayFromGameFile:
11568 if (gameMode != EditGame) return;
11571 EditPositionDone(TRUE);
11582 // forwardMostMove = currentMove;
11583 TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
11584 ResurrectChessProgram(); /* in case first program isn't running */
11586 if (second.pr == NULL) {
11587 StartChessProgram(&second);
11588 if (second.protocolVersion == 1) {
11589 TwoMachinesEventIfReady();
11591 /* kludge: allow timeout for initial "feature" command */
11593 DisplayMessage("", _("Starting second chess program"));
11594 ScheduleDelayedEvent(TwoMachinesEventIfReady, FEATURE_TIMEOUT);
11598 DisplayMessage("", "");
11599 InitChessProgram(&second, FALSE);
11600 SendToProgram("force\n", &second);
11601 if (startedFromSetupPosition) {
11602 SendBoard(&second, backwardMostMove);
11603 if (appData.debugMode) {
11604 fprintf(debugFP, "Two Machines\n");
11607 for (i = backwardMostMove; i < forwardMostMove; i++) {
11608 SendMoveToProgram(i, &second);
11611 gameMode = TwoMachinesPlay;
11615 DisplayTwoMachinesTitle();
11617 if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
11623 SendToProgram(first.computerString, &first);
11624 if (first.sendName) {
11625 sprintf(buf, "name %s\n", second.tidy);
11626 SendToProgram(buf, &first);
11628 SendToProgram(second.computerString, &second);
11629 if (second.sendName) {
11630 sprintf(buf, "name %s\n", first.tidy);
11631 SendToProgram(buf, &second);
11635 if (!first.sendTime || !second.sendTime) {
11636 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11637 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11639 if (onmove->sendTime) {
11640 if (onmove->useColors) {
11641 SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
11643 SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
11645 if (onmove->useColors) {
11646 SendToProgram(onmove->twoMachinesColor, onmove);
11648 bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
11649 // SendToProgram("go\n", onmove);
11650 onmove->maybeThinking = TRUE;
11651 SetMachineThinkingEnables();
11655 if(bookHit) { // [HGM] book: simulate book reply
11656 static char bookMove[MSG_SIZ]; // a bit generous?
11658 programStats.nodes = programStats.depth = programStats.time =
11659 programStats.score = programStats.got_only_move = 0;
11660 sprintf(programStats.movelist, "%s (xbook)", bookHit);
11662 strcpy(bookMove, "move ");
11663 strcat(bookMove, bookHit);
11664 savedMessage = bookMove; // args for deferred call
11665 savedState = onmove;
11666 ScheduleDelayedEvent(DeferredBookMove, 1);
11673 if (gameMode == Training) {
11674 SetTrainingModeOff();
11675 gameMode = PlayFromGameFile;
11676 DisplayMessage("", _("Training mode off"));
11678 gameMode = Training;
11679 animateTraining = appData.animate;
11681 /* make sure we are not already at the end of the game */
11682 if (currentMove < forwardMostMove) {
11683 SetTrainingModeOn();
11684 DisplayMessage("", _("Training mode on"));
11686 gameMode = PlayFromGameFile;
11687 DisplayError(_("Already at end of game"), 0);
11696 if (!appData.icsActive) return;
11697 switch (gameMode) {
11698 case IcsPlayingWhite:
11699 case IcsPlayingBlack:
11702 case BeginningOfGame:
11710 EditPositionDone(TRUE);
11723 gameMode = IcsIdle;
11734 switch (gameMode) {
11736 SetTrainingModeOff();
11738 case MachinePlaysWhite:
11739 case MachinePlaysBlack:
11740 case BeginningOfGame:
11741 SendToProgram("force\n", &first);
11742 SetUserThinkingEnables();
11744 case PlayFromGameFile:
11745 (void) StopLoadGameTimer();
11746 if (gameFileFP != NULL) {
11751 EditPositionDone(TRUE);
11756 SendToProgram("force\n", &first);
11758 case TwoMachinesPlay:
11759 GameEnds((ChessMove) 0, NULL, GE_PLAYER);
11760 ResurrectChessProgram();
11761 SetUserThinkingEnables();
11764 ResurrectChessProgram();
11766 case IcsPlayingBlack:
11767 case IcsPlayingWhite:
11768 DisplayError(_("Warning: You are still playing a game"), 0);
11771 DisplayError(_("Warning: You are still observing a game"), 0);
11774 DisplayError(_("Warning: You are still examining a game"), 0);
11785 first.offeredDraw = second.offeredDraw = 0;
11787 if (gameMode == PlayFromGameFile) {
11788 whiteTimeRemaining = timeRemaining[0][currentMove];
11789 blackTimeRemaining = timeRemaining[1][currentMove];
11793 if (gameMode == MachinePlaysWhite ||
11794 gameMode == MachinePlaysBlack ||
11795 gameMode == TwoMachinesPlay ||
11796 gameMode == EndOfGame) {
11797 i = forwardMostMove;
11798 while (i > currentMove) {
11799 SendToProgram("undo\n", &first);
11802 whiteTimeRemaining = timeRemaining[0][currentMove];
11803 blackTimeRemaining = timeRemaining[1][currentMove];
11804 DisplayBothClocks();
11805 if (whiteFlag || blackFlag) {
11806 whiteFlag = blackFlag = 0;
11811 gameMode = EditGame;
11818 EditPositionEvent()
11820 if (gameMode == EditPosition) {
11826 if (gameMode != EditGame) return;
11828 gameMode = EditPosition;
11831 if (currentMove > 0)
11832 CopyBoard(boards[0], boards[currentMove]);
11834 blackPlaysFirst = !WhiteOnMove(currentMove);
11836 currentMove = forwardMostMove = backwardMostMove = 0;
11837 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11844 /* [DM] icsEngineAnalyze - possible call from other functions */
11845 if (appData.icsEngineAnalyze) {
11846 appData.icsEngineAnalyze = FALSE;
11848 DisplayMessage("",_("Close ICS engine analyze..."));
11850 if (first.analysisSupport && first.analyzing) {
11851 SendToProgram("exit\n", &first);
11852 first.analyzing = FALSE;
11854 thinkOutput[0] = NULLCHAR;
11858 EditPositionDone(Boolean fakeRights)
11860 int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
11862 startedFromSetupPosition = TRUE;
11863 InitChessProgram(&first, FALSE);
11864 if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
11865 boards[0][EP_STATUS] = EP_NONE;
11866 boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
11867 if(boards[0][0][BOARD_WIDTH>>1] == king) {
11868 boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : NoRights;
11869 boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
11870 } else boards[0][CASTLING][2] = NoRights;
11871 if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
11872 boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : NoRights;
11873 boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
11874 } else boards[0][CASTLING][5] = NoRights;
11876 SendToProgram("force\n", &first);
11877 if (blackPlaysFirst) {
11878 strcpy(moveList[0], "");
11879 strcpy(parseList[0], "");
11880 currentMove = forwardMostMove = backwardMostMove = 1;
11881 CopyBoard(boards[1], boards[0]);
11883 currentMove = forwardMostMove = backwardMostMove = 0;
11885 SendBoard(&first, forwardMostMove);
11886 if (appData.debugMode) {
11887 fprintf(debugFP, "EditPosDone\n");
11890 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11891 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11892 gameMode = EditGame;
11894 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11895 ClearHighlights(); /* [AS] */
11898 /* Pause for `ms' milliseconds */
11899 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
11909 } while (SubtractTimeMarks(&m2, &m1) < ms);
11912 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
11914 SendMultiLineToICS(buf)
11917 char temp[MSG_SIZ+1], *p;
11924 strncpy(temp, buf, len);
11929 if (*p == '\n' || *p == '\r')
11934 strcat(temp, "\n");
11936 SendToPlayer(temp, strlen(temp));
11940 SetWhiteToPlayEvent()
11942 if (gameMode == EditPosition) {
11943 blackPlaysFirst = FALSE;
11944 DisplayBothClocks(); /* works because currentMove is 0 */
11945 } else if (gameMode == IcsExamining) {
11946 SendToICS(ics_prefix);
11947 SendToICS("tomove white\n");
11952 SetBlackToPlayEvent()
11954 if (gameMode == EditPosition) {
11955 blackPlaysFirst = TRUE;
11956 currentMove = 1; /* kludge */
11957 DisplayBothClocks();
11959 } else if (gameMode == IcsExamining) {
11960 SendToICS(ics_prefix);
11961 SendToICS("tomove black\n");
11966 EditPositionMenuEvent(selection, x, y)
11967 ChessSquare selection;
11971 ChessSquare piece = boards[0][y][x];
11973 if (gameMode != EditPosition && gameMode != IcsExamining) return;
11975 switch (selection) {
11977 if (gameMode == IcsExamining && ics_type == ICS_FICS) {
11978 SendToICS(ics_prefix);
11979 SendToICS("bsetup clear\n");
11980 } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
11981 SendToICS(ics_prefix);
11982 SendToICS("clearboard\n");
11984 for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
11985 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
11986 for (y = 0; y < BOARD_HEIGHT; y++) {
11987 if (gameMode == IcsExamining) {
11988 if (boards[currentMove][y][x] != EmptySquare) {
11989 sprintf(buf, "%sx@%c%c\n", ics_prefix,
11994 boards[0][y][x] = p;
11999 if (gameMode == EditPosition) {
12000 DrawPosition(FALSE, boards[0]);
12005 SetWhiteToPlayEvent();
12009 SetBlackToPlayEvent();
12013 if (gameMode == IcsExamining) {
12014 if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
12015 sprintf(buf, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
12018 if(x < BOARD_LEFT || x >= BOARD_RGHT) {
12019 if(x == BOARD_LEFT-2) {
12020 if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
12021 boards[0][y][1] = 0;
12023 if(x == BOARD_RGHT+1) {
12024 if(y >= gameInfo.holdingsSize) break;
12025 boards[0][y][BOARD_WIDTH-2] = 0;
12028 boards[0][y][x] = EmptySquare;
12029 DrawPosition(FALSE, boards[0]);
12034 if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
12035 piece >= (int)BlackPawn && piece < (int)BlackMan ) {
12036 selection = (ChessSquare) (PROMOTED piece);
12037 } else if(piece == EmptySquare) selection = WhiteSilver;
12038 else selection = (ChessSquare)((int)piece - 1);
12042 if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
12043 piece > (int)BlackMan && piece <= (int)BlackKing ) {
12044 selection = (ChessSquare) (DEMOTED piece);
12045 } else if(piece == EmptySquare) selection = BlackSilver;
12046 else selection = (ChessSquare)((int)piece + 1);
12051 if(gameInfo.variant == VariantShatranj ||
12052 gameInfo.variant == VariantXiangqi ||
12053 gameInfo.variant == VariantCourier ||
12054 gameInfo.variant == VariantMakruk )
12055 selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
12060 if(gameInfo.variant == VariantXiangqi)
12061 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
12062 if(gameInfo.variant == VariantKnightmate)
12063 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
12066 if (gameMode == IcsExamining) {
12067 if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
12068 sprintf(buf, "%s%c@%c%c\n", ics_prefix,
12069 PieceToChar(selection), AAA + x, ONE + y);
12072 if(x < BOARD_LEFT || x >= BOARD_RGHT) {
12074 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
12075 n = PieceToNumber(selection - BlackPawn);
12076 if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
12077 boards[0][BOARD_HEIGHT-1-n][0] = selection;
12078 boards[0][BOARD_HEIGHT-1-n][1]++;
12080 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
12081 n = PieceToNumber(selection);
12082 if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
12083 boards[0][n][BOARD_WIDTH-1] = selection;
12084 boards[0][n][BOARD_WIDTH-2]++;
12087 boards[0][y][x] = selection;
12088 DrawPosition(TRUE, boards[0]);
12096 DropMenuEvent(selection, x, y)
12097 ChessSquare selection;
12100 ChessMove moveType;
12102 switch (gameMode) {
12103 case IcsPlayingWhite:
12104 case MachinePlaysBlack:
12105 if (!WhiteOnMove(currentMove)) {
12106 DisplayMoveError(_("It is Black's turn"));
12109 moveType = WhiteDrop;
12111 case IcsPlayingBlack:
12112 case MachinePlaysWhite:
12113 if (WhiteOnMove(currentMove)) {
12114 DisplayMoveError(_("It is White's turn"));
12117 moveType = BlackDrop;
12120 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
12126 if (moveType == BlackDrop && selection < BlackPawn) {
12127 selection = (ChessSquare) ((int) selection
12128 + (int) BlackPawn - (int) WhitePawn);
12130 if (boards[currentMove][y][x] != EmptySquare) {
12131 DisplayMoveError(_("That square is occupied"));
12135 FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
12141 /* Accept a pending offer of any kind from opponent */
12143 if (appData.icsActive) {
12144 SendToICS(ics_prefix);
12145 SendToICS("accept\n");
12146 } else if (cmailMsgLoaded) {
12147 if (currentMove == cmailOldMove &&
12148 commentList[cmailOldMove] != NULL &&
12149 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12150 "Black offers a draw" : "White offers a draw")) {
12152 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
12153 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
12155 DisplayError(_("There is no pending offer on this move"), 0);
12156 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12159 /* Not used for offers from chess program */
12166 /* Decline a pending offer of any kind from opponent */
12168 if (appData.icsActive) {
12169 SendToICS(ics_prefix);
12170 SendToICS("decline\n");
12171 } else if (cmailMsgLoaded) {
12172 if (currentMove == cmailOldMove &&
12173 commentList[cmailOldMove] != NULL &&
12174 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12175 "Black offers a draw" : "White offers a draw")) {
12177 AppendComment(cmailOldMove, "Draw declined", TRUE);
12178 DisplayComment(cmailOldMove - 1, "Draw declined");
12181 DisplayError(_("There is no pending offer on this move"), 0);
12184 /* Not used for offers from chess program */
12191 /* Issue ICS rematch command */
12192 if (appData.icsActive) {
12193 SendToICS(ics_prefix);
12194 SendToICS("rematch\n");
12201 /* Call your opponent's flag (claim a win on time) */
12202 if (appData.icsActive) {
12203 SendToICS(ics_prefix);
12204 SendToICS("flag\n");
12206 switch (gameMode) {
12209 case MachinePlaysWhite:
12212 GameEnds(GameIsDrawn, "Both players ran out of time",
12215 GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
12217 DisplayError(_("Your opponent is not out of time"), 0);
12220 case MachinePlaysBlack:
12223 GameEnds(GameIsDrawn, "Both players ran out of time",
12226 GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
12228 DisplayError(_("Your opponent is not out of time"), 0);
12238 /* Offer draw or accept pending draw offer from opponent */
12240 if (appData.icsActive) {
12241 /* Note: tournament rules require draw offers to be
12242 made after you make your move but before you punch
12243 your clock. Currently ICS doesn't let you do that;
12244 instead, you immediately punch your clock after making
12245 a move, but you can offer a draw at any time. */
12247 SendToICS(ics_prefix);
12248 SendToICS("draw\n");
12249 userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
12250 } else if (cmailMsgLoaded) {
12251 if (currentMove == cmailOldMove &&
12252 commentList[cmailOldMove] != NULL &&
12253 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12254 "Black offers a draw" : "White offers a draw")) {
12255 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
12256 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
12257 } else if (currentMove == cmailOldMove + 1) {
12258 char *offer = WhiteOnMove(cmailOldMove) ?
12259 "White offers a draw" : "Black offers a draw";
12260 AppendComment(currentMove, offer, TRUE);
12261 DisplayComment(currentMove - 1, offer);
12262 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
12264 DisplayError(_("You must make your move before offering a draw"), 0);
12265 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12267 } else if (first.offeredDraw) {
12268 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
12270 if (first.sendDrawOffers) {
12271 SendToProgram("draw\n", &first);
12272 userOfferedDraw = TRUE;
12280 /* Offer Adjourn or accept pending Adjourn offer from opponent */
12282 if (appData.icsActive) {
12283 SendToICS(ics_prefix);
12284 SendToICS("adjourn\n");
12286 /* Currently GNU Chess doesn't offer or accept Adjourns */
12294 /* Offer Abort or accept pending Abort offer from opponent */
12296 if (appData.icsActive) {
12297 SendToICS(ics_prefix);
12298 SendToICS("abort\n");
12300 GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
12307 /* Resign. You can do this even if it's not your turn. */
12309 if (appData.icsActive) {
12310 SendToICS(ics_prefix);
12311 SendToICS("resign\n");
12313 switch (gameMode) {
12314 case MachinePlaysWhite:
12315 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12317 case MachinePlaysBlack:
12318 GameEnds(BlackWins, "White resigns", GE_PLAYER);
12321 if (cmailMsgLoaded) {
12323 if (WhiteOnMove(cmailOldMove)) {
12324 GameEnds(BlackWins, "White resigns", GE_PLAYER);
12326 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12328 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
12339 StopObservingEvent()
12341 /* Stop observing current games */
12342 SendToICS(ics_prefix);
12343 SendToICS("unobserve\n");
12347 StopExaminingEvent()
12349 /* Stop observing current game */
12350 SendToICS(ics_prefix);
12351 SendToICS("unexamine\n");
12355 ForwardInner(target)
12360 if (appData.debugMode)
12361 fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
12362 target, currentMove, forwardMostMove);
12364 if (gameMode == EditPosition)
12367 if (gameMode == PlayFromGameFile && !pausing)
12370 if (gameMode == IcsExamining && pausing)
12371 limit = pauseExamForwardMostMove;
12373 limit = forwardMostMove;
12375 if (target > limit) target = limit;
12377 if (target > 0 && moveList[target - 1][0]) {
12378 int fromX, fromY, toX, toY;
12379 toX = moveList[target - 1][2] - AAA;
12380 toY = moveList[target - 1][3] - ONE;
12381 if (moveList[target - 1][1] == '@') {
12382 if (appData.highlightLastMove) {
12383 SetHighlights(-1, -1, toX, toY);
12386 fromX = moveList[target - 1][0] - AAA;
12387 fromY = moveList[target - 1][1] - ONE;
12388 if (target == currentMove + 1) {
12389 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
12391 if (appData.highlightLastMove) {
12392 SetHighlights(fromX, fromY, toX, toY);
12396 if (gameMode == EditGame || gameMode == AnalyzeMode ||
12397 gameMode == Training || gameMode == PlayFromGameFile ||
12398 gameMode == AnalyzeFile) {
12399 while (currentMove < target) {
12400 SendMoveToProgram(currentMove++, &first);
12403 currentMove = target;
12406 if (gameMode == EditGame || gameMode == EndOfGame) {
12407 whiteTimeRemaining = timeRemaining[0][currentMove];
12408 blackTimeRemaining = timeRemaining[1][currentMove];
12410 DisplayBothClocks();
12411 DisplayMove(currentMove - 1);
12412 DrawPosition(FALSE, boards[currentMove]);
12413 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
12414 if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
12415 DisplayComment(currentMove - 1, commentList[currentMove]);
12423 if (gameMode == IcsExamining && !pausing) {
12424 SendToICS(ics_prefix);
12425 SendToICS("forward\n");
12427 ForwardInner(currentMove + 1);
12434 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12435 /* to optimze, we temporarily turn off analysis mode while we feed
12436 * the remaining moves to the engine. Otherwise we get analysis output
12439 if (first.analysisSupport) {
12440 SendToProgram("exit\nforce\n", &first);
12441 first.analyzing = FALSE;
12445 if (gameMode == IcsExamining && !pausing) {
12446 SendToICS(ics_prefix);
12447 SendToICS("forward 999999\n");
12449 ForwardInner(forwardMostMove);
12452 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12453 /* we have fed all the moves, so reactivate analysis mode */
12454 SendToProgram("analyze\n", &first);
12455 first.analyzing = TRUE;
12456 /*first.maybeThinking = TRUE;*/
12457 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12462 BackwardInner(target)
12465 int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
12467 if (appData.debugMode)
12468 fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
12469 target, currentMove, forwardMostMove);
12471 if (gameMode == EditPosition) return;
12472 if (currentMove <= backwardMostMove) {
12474 DrawPosition(full_redraw, boards[currentMove]);
12477 if (gameMode == PlayFromGameFile && !pausing)
12480 if (moveList[target][0]) {
12481 int fromX, fromY, toX, toY;
12482 toX = moveList[target][2] - AAA;
12483 toY = moveList[target][3] - ONE;
12484 if (moveList[target][1] == '@') {
12485 if (appData.highlightLastMove) {
12486 SetHighlights(-1, -1, toX, toY);
12489 fromX = moveList[target][0] - AAA;
12490 fromY = moveList[target][1] - ONE;
12491 if (target == currentMove - 1) {
12492 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
12494 if (appData.highlightLastMove) {
12495 SetHighlights(fromX, fromY, toX, toY);
12499 if (gameMode == EditGame || gameMode==AnalyzeMode ||
12500 gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
12501 while (currentMove > target) {
12502 SendToProgram("undo\n", &first);
12506 currentMove = target;
12509 if (gameMode == EditGame || gameMode == EndOfGame) {
12510 whiteTimeRemaining = timeRemaining[0][currentMove];
12511 blackTimeRemaining = timeRemaining[1][currentMove];
12513 DisplayBothClocks();
12514 DisplayMove(currentMove - 1);
12515 DrawPosition(full_redraw, boards[currentMove]);
12516 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
12517 // [HGM] PV info: routine tests if comment empty
12518 DisplayComment(currentMove - 1, commentList[currentMove]);
12524 if (gameMode == IcsExamining && !pausing) {
12525 SendToICS(ics_prefix);
12526 SendToICS("backward\n");
12528 BackwardInner(currentMove - 1);
12535 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12536 /* to optimize, we temporarily turn off analysis mode while we undo
12537 * all the moves. Otherwise we get analysis output after each undo.
12539 if (first.analysisSupport) {
12540 SendToProgram("exit\nforce\n", &first);
12541 first.analyzing = FALSE;
12545 if (gameMode == IcsExamining && !pausing) {
12546 SendToICS(ics_prefix);
12547 SendToICS("backward 999999\n");
12549 BackwardInner(backwardMostMove);
12552 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12553 /* we have fed all the moves, so reactivate analysis mode */
12554 SendToProgram("analyze\n", &first);
12555 first.analyzing = TRUE;
12556 /*first.maybeThinking = TRUE;*/
12557 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12564 if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
12565 if (to >= forwardMostMove) to = forwardMostMove;
12566 if (to <= backwardMostMove) to = backwardMostMove;
12567 if (to < currentMove) {
12577 if(PopTail(TRUE)) { // [HGM] vari: restore old game tail
12580 if (gameMode != IcsExamining) {
12581 DisplayError(_("You are not examining a game"), 0);
12585 DisplayError(_("You can't revert while pausing"), 0);
12588 SendToICS(ics_prefix);
12589 SendToICS("revert\n");
12595 switch (gameMode) {
12596 case MachinePlaysWhite:
12597 case MachinePlaysBlack:
12598 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
12599 DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
12602 if (forwardMostMove < 2) return;
12603 currentMove = forwardMostMove = forwardMostMove - 2;
12604 whiteTimeRemaining = timeRemaining[0][currentMove];
12605 blackTimeRemaining = timeRemaining[1][currentMove];
12606 DisplayBothClocks();
12607 DisplayMove(currentMove - 1);
12608 ClearHighlights();/*!! could figure this out*/
12609 DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
12610 SendToProgram("remove\n", &first);
12611 /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
12614 case BeginningOfGame:
12618 case IcsPlayingWhite:
12619 case IcsPlayingBlack:
12620 if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
12621 SendToICS(ics_prefix);
12622 SendToICS("takeback 2\n");
12624 SendToICS(ics_prefix);
12625 SendToICS("takeback 1\n");
12634 ChessProgramState *cps;
12636 switch (gameMode) {
12637 case MachinePlaysWhite:
12638 if (!WhiteOnMove(forwardMostMove)) {
12639 DisplayError(_("It is your turn"), 0);
12644 case MachinePlaysBlack:
12645 if (WhiteOnMove(forwardMostMove)) {
12646 DisplayError(_("It is your turn"), 0);
12651 case TwoMachinesPlay:
12652 if (WhiteOnMove(forwardMostMove) ==
12653 (first.twoMachinesColor[0] == 'w')) {
12659 case BeginningOfGame:
12663 SendToProgram("?\n", cps);
12667 TruncateGameEvent()
12670 if (gameMode != EditGame) return;
12677 CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
12678 if (forwardMostMove > currentMove) {
12679 if (gameInfo.resultDetails != NULL) {
12680 free(gameInfo.resultDetails);
12681 gameInfo.resultDetails = NULL;
12682 gameInfo.result = GameUnfinished;
12684 forwardMostMove = currentMove;
12685 HistorySet(parseList, backwardMostMove, forwardMostMove,
12693 if (appData.noChessProgram) return;
12694 switch (gameMode) {
12695 case MachinePlaysWhite:
12696 if (WhiteOnMove(forwardMostMove)) {
12697 DisplayError(_("Wait until your turn"), 0);
12701 case BeginningOfGame:
12702 case MachinePlaysBlack:
12703 if (!WhiteOnMove(forwardMostMove)) {
12704 DisplayError(_("Wait until your turn"), 0);
12709 DisplayError(_("No hint available"), 0);
12712 SendToProgram("hint\n", &first);
12713 hintRequested = TRUE;
12719 if (appData.noChessProgram) return;
12720 switch (gameMode) {
12721 case MachinePlaysWhite:
12722 if (WhiteOnMove(forwardMostMove)) {
12723 DisplayError(_("Wait until your turn"), 0);
12727 case BeginningOfGame:
12728 case MachinePlaysBlack:
12729 if (!WhiteOnMove(forwardMostMove)) {
12730 DisplayError(_("Wait until your turn"), 0);
12735 EditPositionDone(TRUE);
12737 case TwoMachinesPlay:
12742 SendToProgram("bk\n", &first);
12743 bookOutput[0] = NULLCHAR;
12744 bookRequested = TRUE;
12750 char *tags = PGNTags(&gameInfo);
12751 TagsPopUp(tags, CmailMsg());
12755 /* end button procedures */
12758 PrintPosition(fp, move)
12764 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
12765 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
12766 char c = PieceToChar(boards[move][i][j]);
12767 fputc(c == 'x' ? '.' : c, fp);
12768 fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
12771 if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
12772 fprintf(fp, "white to play\n");
12774 fprintf(fp, "black to play\n");
12781 if (gameInfo.white != NULL) {
12782 fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
12788 /* Find last component of program's own name, using some heuristics */
12790 TidyProgramName(prog, host, buf)
12791 char *prog, *host, buf[MSG_SIZ];
12794 int local = (strcmp(host, "localhost") == 0);
12795 while (!local && (p = strchr(prog, ';')) != NULL) {
12797 while (*p == ' ') p++;
12800 if (*prog == '"' || *prog == '\'') {
12801 q = strchr(prog + 1, *prog);
12803 q = strchr(prog, ' ');
12805 if (q == NULL) q = prog + strlen(prog);
12807 while (p >= prog && *p != '/' && *p != '\\') p--;
12809 if(p == prog && *p == '"') p++;
12810 if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
12811 memcpy(buf, p, q - p);
12812 buf[q - p] = NULLCHAR;
12820 TimeControlTagValue()
12823 if (!appData.clockMode) {
12825 } else if (movesPerSession > 0) {
12826 sprintf(buf, "%d/%ld", movesPerSession, timeControl/1000);
12827 } else if (timeIncrement == 0) {
12828 sprintf(buf, "%ld", timeControl/1000);
12830 sprintf(buf, "%ld+%ld", timeControl/1000, timeIncrement/1000);
12832 return StrSave(buf);
12838 /* This routine is used only for certain modes */
12839 VariantClass v = gameInfo.variant;
12840 ChessMove r = GameUnfinished;
12843 if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
12844 r = gameInfo.result;
12845 p = gameInfo.resultDetails;
12846 gameInfo.resultDetails = NULL;
12848 ClearGameInfo(&gameInfo);
12849 gameInfo.variant = v;
12851 switch (gameMode) {
12852 case MachinePlaysWhite:
12853 gameInfo.event = StrSave( appData.pgnEventHeader );
12854 gameInfo.site = StrSave(HostName());
12855 gameInfo.date = PGNDate();
12856 gameInfo.round = StrSave("-");
12857 gameInfo.white = StrSave(first.tidy);
12858 gameInfo.black = StrSave(UserName());
12859 gameInfo.timeControl = TimeControlTagValue();
12862 case MachinePlaysBlack:
12863 gameInfo.event = StrSave( appData.pgnEventHeader );
12864 gameInfo.site = StrSave(HostName());
12865 gameInfo.date = PGNDate();
12866 gameInfo.round = StrSave("-");
12867 gameInfo.white = StrSave(UserName());
12868 gameInfo.black = StrSave(first.tidy);
12869 gameInfo.timeControl = TimeControlTagValue();
12872 case TwoMachinesPlay:
12873 gameInfo.event = StrSave( appData.pgnEventHeader );
12874 gameInfo.site = StrSave(HostName());
12875 gameInfo.date = PGNDate();
12876 if (matchGame > 0) {
12878 sprintf(buf, "%d", matchGame);
12879 gameInfo.round = StrSave(buf);
12881 gameInfo.round = StrSave("-");
12883 if (first.twoMachinesColor[0] == 'w') {
12884 gameInfo.white = StrSave(first.tidy);
12885 gameInfo.black = StrSave(second.tidy);
12887 gameInfo.white = StrSave(second.tidy);
12888 gameInfo.black = StrSave(first.tidy);
12890 gameInfo.timeControl = TimeControlTagValue();
12894 gameInfo.event = StrSave("Edited game");
12895 gameInfo.site = StrSave(HostName());
12896 gameInfo.date = PGNDate();
12897 gameInfo.round = StrSave("-");
12898 gameInfo.white = StrSave("-");
12899 gameInfo.black = StrSave("-");
12900 gameInfo.result = r;
12901 gameInfo.resultDetails = p;
12905 gameInfo.event = StrSave("Edited position");
12906 gameInfo.site = StrSave(HostName());
12907 gameInfo.date = PGNDate();
12908 gameInfo.round = StrSave("-");
12909 gameInfo.white = StrSave("-");
12910 gameInfo.black = StrSave("-");
12913 case IcsPlayingWhite:
12914 case IcsPlayingBlack:
12919 case PlayFromGameFile:
12920 gameInfo.event = StrSave("Game from non-PGN file");
12921 gameInfo.site = StrSave(HostName());
12922 gameInfo.date = PGNDate();
12923 gameInfo.round = StrSave("-");
12924 gameInfo.white = StrSave("?");
12925 gameInfo.black = StrSave("?");
12934 ReplaceComment(index, text)
12940 while (*text == '\n') text++;
12941 len = strlen(text);
12942 while (len > 0 && text[len - 1] == '\n') len--;
12944 if (commentList[index] != NULL)
12945 free(commentList[index]);
12948 commentList[index] = NULL;
12951 if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
12952 *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
12953 *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
12954 commentList[index] = (char *) malloc(len + 2);
12955 strncpy(commentList[index], text, len);
12956 commentList[index][len] = '\n';
12957 commentList[index][len + 1] = NULLCHAR;
12959 // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
12961 commentList[index] = (char *) malloc(len + 6);
12962 strcpy(commentList[index], "{\n");
12963 strncpy(commentList[index]+2, text, len);
12964 commentList[index][len+2] = NULLCHAR;
12965 while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
12966 strcat(commentList[index], "\n}\n");
12980 if (ch == '\r') continue;
12982 } while (ch != '\0');
12986 AppendComment(index, text, addBraces)
12989 Boolean addBraces; // [HGM] braces: tells if we should add {}
12994 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
12995 text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
12998 while (*text == '\n') text++;
12999 len = strlen(text);
13000 while (len > 0 && text[len - 1] == '\n') len--;
13002 if (len == 0) return;
13004 if (commentList[index] != NULL) {
13005 old = commentList[index];
13006 oldlen = strlen(old);
13007 while(commentList[index][oldlen-1] == '\n')
13008 commentList[index][--oldlen] = NULLCHAR;
13009 commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
13010 strcpy(commentList[index], old);
13012 // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
13013 if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces)) {
13014 if(addBraces) addBraces = FALSE; else { text++; len--; }
13015 while (*text == '\n') { text++; len--; }
13016 commentList[index][--oldlen] = NULLCHAR;
13018 if(addBraces) strcat(commentList[index], "\n{\n");
13019 else strcat(commentList[index], "\n");
13020 strcat(commentList[index], text);
13021 if(addBraces) strcat(commentList[index], "\n}\n");
13022 else strcat(commentList[index], "\n");
13024 commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
13026 strcpy(commentList[index], "{\n");
13027 else commentList[index][0] = NULLCHAR;
13028 strcat(commentList[index], text);
13029 strcat(commentList[index], "\n");
13030 if(addBraces) strcat(commentList[index], "}\n");
13034 static char * FindStr( char * text, char * sub_text )
13036 char * result = strstr( text, sub_text );
13038 if( result != NULL ) {
13039 result += strlen( sub_text );
13045 /* [AS] Try to extract PV info from PGN comment */
13046 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
13047 char *GetInfoFromComment( int index, char * text )
13051 if( text != NULL && index > 0 ) {
13054 int time = -1, sec = 0, deci;
13055 char * s_eval = FindStr( text, "[%eval " );
13056 char * s_emt = FindStr( text, "[%emt " );
13058 if( s_eval != NULL || s_emt != NULL ) {
13062 if( s_eval != NULL ) {
13063 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
13067 if( delim != ']' ) {
13072 if( s_emt != NULL ) {
13077 /* We expect something like: [+|-]nnn.nn/dd */
13080 if(*text != '{') return text; // [HGM] braces: must be normal comment
13082 sep = strchr( text, '/' );
13083 if( sep == NULL || sep < (text+4) ) {
13087 time = -1; sec = -1; deci = -1;
13088 if( sscanf( text+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
13089 sscanf( text+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
13090 sscanf( text+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
13091 sscanf( text+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3 ) {
13095 if( score_lo < 0 || score_lo >= 100 ) {
13099 if(sec >= 0) time = 600*time + 10*sec; else
13100 if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
13102 score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
13104 /* [HGM] PV time: now locate end of PV info */
13105 while( *++sep >= '0' && *sep <= '9'); // strip depth
13107 while( *++sep >= '0' && *sep <= '9'); // strip time
13109 while( *++sep >= '0' && *sep <= '9'); // strip seconds
13111 while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
13112 while(*sep == ' ') sep++;
13123 pvInfoList[index-1].depth = depth;
13124 pvInfoList[index-1].score = score;
13125 pvInfoList[index-1].time = 10*time; // centi-sec
13126 if(*sep == '}') *sep = 0; else *--sep = '{';
13132 SendToProgram(message, cps)
13134 ChessProgramState *cps;
13136 int count, outCount, error;
13139 if (cps->pr == NULL) return;
13142 if (appData.debugMode) {
13145 fprintf(debugFP, "%ld >%-6s: %s",
13146 SubtractTimeMarks(&now, &programStartTime),
13147 cps->which, message);
13150 count = strlen(message);
13151 outCount = OutputToProcess(cps->pr, message, count, &error);
13152 if (outCount < count && !exiting
13153 && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
13154 sprintf(buf, _("Error writing to %s chess program"), cps->which);
13155 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
13156 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
13157 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
13158 sprintf(buf, "%s program exits in draw position (%s)", cps->which, cps->program);
13160 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
13162 gameInfo.resultDetails = StrSave(buf);
13164 if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
13169 ReceiveFromProgram(isr, closure, message, count, error)
13170 InputSourceRef isr;
13178 ChessProgramState *cps = (ChessProgramState *)closure;
13180 if (isr != cps->isr) return; /* Killed intentionally */
13184 _("Error: %s chess program (%s) exited unexpectedly"),
13185 cps->which, cps->program);
13186 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
13187 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
13188 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
13189 sprintf(buf, _("%s program exits in draw position (%s)"), cps->which, cps->program);
13191 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
13193 gameInfo.resultDetails = StrSave(buf);
13195 RemoveInputSource(cps->isr);
13196 if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
13199 _("Error reading from %s chess program (%s)"),
13200 cps->which, cps->program);
13201 RemoveInputSource(cps->isr);
13203 /* [AS] Program is misbehaving badly... kill it */
13204 if( count == -2 ) {
13205 DestroyChildProcess( cps->pr, 9 );
13209 if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
13214 if ((end_str = strchr(message, '\r')) != NULL)
13215 *end_str = NULLCHAR;
13216 if ((end_str = strchr(message, '\n')) != NULL)
13217 *end_str = NULLCHAR;
13219 if (appData.debugMode) {
13220 TimeMark now; int print = 1;
13221 char *quote = ""; char c; int i;
13223 if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
13224 char start = message[0];
13225 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
13226 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
13227 sscanf(message, "move %c", &c)!=1 && sscanf(message, "offer%c", &c)!=1 &&
13228 sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
13229 sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
13230 sscanf(message, "tell%c", &c)!=1 && sscanf(message, "0-1 %c", &c)!=1 &&
13231 sscanf(message, "1-0 %c", &c)!=1 && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
13232 sscanf(message, "pong %c", &c)!=1 && start != '#')
13233 { quote = "# "; print = (appData.engineComments == 2); }
13234 message[0] = start; // restore original message
13238 fprintf(debugFP, "%ld <%-6s: %s%s\n",
13239 SubtractTimeMarks(&now, &programStartTime), cps->which,
13245 /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
13246 if (appData.icsEngineAnalyze) {
13247 if (strstr(message, "whisper") != NULL ||
13248 strstr(message, "kibitz") != NULL ||
13249 strstr(message, "tellics") != NULL) return;
13252 HandleMachineMove(message, cps);
13257 SendTimeControl(cps, mps, tc, inc, sd, st)
13258 ChessProgramState *cps;
13259 int mps, inc, sd, st;
13265 if( timeControl_2 > 0 ) {
13266 if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
13267 tc = timeControl_2;
13270 tc /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
13271 inc /= cps->timeOdds;
13272 st /= cps->timeOdds;
13274 seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
13277 /* Set exact time per move, normally using st command */
13278 if (cps->stKludge) {
13279 /* GNU Chess 4 has no st command; uses level in a nonstandard way */
13281 if (seconds == 0) {
13282 sprintf(buf, "level 1 %d\n", st/60);
13284 sprintf(buf, "level 1 %d:%02d\n", st/60, seconds);
13287 sprintf(buf, "st %d\n", st);
13290 /* Set conventional or incremental time control, using level command */
13291 if (seconds == 0) {
13292 /* Note old gnuchess bug -- minutes:seconds used to not work.
13293 Fixed in later versions, but still avoid :seconds
13294 when seconds is 0. */
13295 sprintf(buf, "level %d %ld %d\n", mps, tc/60000, inc/1000);
13297 sprintf(buf, "level %d %ld:%02d %d\n", mps, tc/60000,
13298 seconds, inc/1000);
13301 SendToProgram(buf, cps);
13303 /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
13304 /* Orthogonally, limit search to given depth */
13306 if (cps->sdKludge) {
13307 sprintf(buf, "depth\n%d\n", sd);
13309 sprintf(buf, "sd %d\n", sd);
13311 SendToProgram(buf, cps);
13314 if(cps->nps > 0) { /* [HGM] nps */
13315 if(cps->supportsNPS == FALSE) cps->nps = -1; // don't use if engine explicitly says not supported!
13317 sprintf(buf, "nps %d\n", cps->nps);
13318 SendToProgram(buf, cps);
13323 ChessProgramState *WhitePlayer()
13324 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
13326 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
13327 gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
13333 SendTimeRemaining(cps, machineWhite)
13334 ChessProgramState *cps;
13335 int /*boolean*/ machineWhite;
13337 char message[MSG_SIZ];
13340 /* Note: this routine must be called when the clocks are stopped
13341 or when they have *just* been set or switched; otherwise
13342 it will be off by the time since the current tick started.
13344 if (machineWhite) {
13345 time = whiteTimeRemaining / 10;
13346 otime = blackTimeRemaining / 10;
13348 time = blackTimeRemaining / 10;
13349 otime = whiteTimeRemaining / 10;
13351 /* [HGM] translate opponent's time by time-odds factor */
13352 otime = (otime * cps->other->timeOdds) / cps->timeOdds;
13353 if (appData.debugMode) {
13354 fprintf(debugFP, "time odds: %f %f \n", cps->timeOdds, cps->other->timeOdds);
13357 if (time <= 0) time = 1;
13358 if (otime <= 0) otime = 1;
13360 sprintf(message, "time %ld\n", time);
13361 SendToProgram(message, cps);
13363 sprintf(message, "otim %ld\n", otime);
13364 SendToProgram(message, cps);
13368 BoolFeature(p, name, loc, cps)
13372 ChessProgramState *cps;
13375 int len = strlen(name);
13377 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
13379 sscanf(*p, "%d", &val);
13381 while (**p && **p != ' ') (*p)++;
13382 sprintf(buf, "accepted %s\n", name);
13383 SendToProgram(buf, cps);
13390 IntFeature(p, name, loc, cps)
13394 ChessProgramState *cps;
13397 int len = strlen(name);
13398 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
13400 sscanf(*p, "%d", loc);
13401 while (**p && **p != ' ') (*p)++;
13402 sprintf(buf, "accepted %s\n", name);
13403 SendToProgram(buf, cps);
13410 StringFeature(p, name, loc, cps)
13414 ChessProgramState *cps;
13417 int len = strlen(name);
13418 if (strncmp((*p), name, len) == 0
13419 && (*p)[len] == '=' && (*p)[len+1] == '\"') {
13421 sscanf(*p, "%[^\"]", loc);
13422 while (**p && **p != '\"') (*p)++;
13423 if (**p == '\"') (*p)++;
13424 sprintf(buf, "accepted %s\n", name);
13425 SendToProgram(buf, cps);
13432 ParseOption(Option *opt, ChessProgramState *cps)
13433 // [HGM] options: process the string that defines an engine option, and determine
13434 // name, type, default value, and allowed value range
13436 char *p, *q, buf[MSG_SIZ];
13437 int n, min = (-1)<<31, max = 1<<31, def;
13439 if(p = strstr(opt->name, " -spin ")) {
13440 if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
13441 if(max < min) max = min; // enforce consistency
13442 if(def < min) def = min;
13443 if(def > max) def = max;
13448 } else if((p = strstr(opt->name, " -slider "))) {
13449 // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
13450 if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
13451 if(max < min) max = min; // enforce consistency
13452 if(def < min) def = min;
13453 if(def > max) def = max;
13457 opt->type = Spin; // Slider;
13458 } else if((p = strstr(opt->name, " -string "))) {
13459 opt->textValue = p+9;
13460 opt->type = TextBox;
13461 } else if((p = strstr(opt->name, " -file "))) {
13462 // for now -file is a synonym for -string, to already provide compatibility with future polyglots
13463 opt->textValue = p+7;
13464 opt->type = TextBox; // FileName;
13465 } else if((p = strstr(opt->name, " -path "))) {
13466 // for now -file is a synonym for -string, to already provide compatibility with future polyglots
13467 opt->textValue = p+7;
13468 opt->type = TextBox; // PathName;
13469 } else if(p = strstr(opt->name, " -check ")) {
13470 if(sscanf(p, " -check %d", &def) < 1) return FALSE;
13471 opt->value = (def != 0);
13472 opt->type = CheckBox;
13473 } else if(p = strstr(opt->name, " -combo ")) {
13474 opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
13475 cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
13476 if(*q == '*') cps->comboList[cps->comboCnt-1]++;
13477 opt->value = n = 0;
13478 while(q = StrStr(q, " /// ")) {
13479 n++; *q = 0; // count choices, and null-terminate each of them
13481 if(*q == '*') { // remember default, which is marked with * prefix
13485 cps->comboList[cps->comboCnt++] = q;
13487 cps->comboList[cps->comboCnt++] = NULL;
13489 opt->type = ComboBox;
13490 } else if(p = strstr(opt->name, " -button")) {
13491 opt->type = Button;
13492 } else if(p = strstr(opt->name, " -save")) {
13493 opt->type = SaveButton;
13494 } else return FALSE;
13495 *p = 0; // terminate option name
13496 // now look if the command-line options define a setting for this engine option.
13497 if(cps->optionSettings && cps->optionSettings[0])
13498 p = strstr(cps->optionSettings, opt->name); else p = NULL;
13499 if(p && (p == cps->optionSettings || p[-1] == ',')) {
13500 sprintf(buf, "option %s", p);
13501 if(p = strstr(buf, ",")) *p = 0;
13503 SendToProgram(buf, cps);
13509 FeatureDone(cps, val)
13510 ChessProgramState* cps;
13513 DelayedEventCallback cb = GetDelayedEvent();
13514 if ((cb == InitBackEnd3 && cps == &first) ||
13515 (cb == TwoMachinesEventIfReady && cps == &second)) {
13516 CancelDelayedEvent();
13517 ScheduleDelayedEvent(cb, val ? 1 : 3600000);
13519 cps->initDone = val;
13522 /* Parse feature command from engine */
13524 ParseFeatures(args, cps)
13526 ChessProgramState *cps;
13534 while (*p == ' ') p++;
13535 if (*p == NULLCHAR) return;
13537 if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
13538 if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
13539 if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
13540 if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
13541 if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
13542 if (BoolFeature(&p, "reuse", &val, cps)) {
13543 /* Engine can disable reuse, but can't enable it if user said no */
13544 if (!val) cps->reuse = FALSE;
13547 if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
13548 if (StringFeature(&p, "myname", &cps->tidy, cps)) {
13549 if (gameMode == TwoMachinesPlay) {
13550 DisplayTwoMachinesTitle();
13556 if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
13557 if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
13558 if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
13559 if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
13560 if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
13561 if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
13562 if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
13563 if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
13564 if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
13565 if (IntFeature(&p, "done", &val, cps)) {
13566 FeatureDone(cps, val);
13569 /* Added by Tord: */
13570 if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
13571 if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
13572 /* End of additions by Tord */
13574 /* [HGM] added features: */
13575 if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
13576 if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
13577 if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
13578 if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
13579 if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
13580 if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
13581 if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
13582 if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
13583 sprintf(buf, "rejected option %s\n", cps->option[--cps->nrOptions].name);
13584 SendToProgram(buf, cps);
13587 if(cps->nrOptions >= MAX_OPTIONS) {
13589 sprintf(buf, "%s engine has too many options\n", cps->which);
13590 DisplayError(buf, 0);
13594 if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
13595 /* End of additions by HGM */
13597 /* unknown feature: complain and skip */
13599 while (*q && *q != '=') q++;
13600 sprintf(buf, "rejected %.*s\n", (int)(q-p), p);
13601 SendToProgram(buf, cps);
13607 while (*p && *p != '\"') p++;
13608 if (*p == '\"') p++;
13610 while (*p && *p != ' ') p++;
13618 PeriodicUpdatesEvent(newState)
13621 if (newState == appData.periodicUpdates)
13624 appData.periodicUpdates=newState;
13626 /* Display type changes, so update it now */
13627 // DisplayAnalysis();
13629 /* Get the ball rolling again... */
13631 AnalysisPeriodicEvent(1);
13632 StartAnalysisClock();
13637 PonderNextMoveEvent(newState)
13640 if (newState == appData.ponderNextMove) return;
13641 if (gameMode == EditPosition) EditPositionDone(TRUE);
13643 SendToProgram("hard\n", &first);
13644 if (gameMode == TwoMachinesPlay) {
13645 SendToProgram("hard\n", &second);
13648 SendToProgram("easy\n", &first);
13649 thinkOutput[0] = NULLCHAR;
13650 if (gameMode == TwoMachinesPlay) {
13651 SendToProgram("easy\n", &second);
13654 appData.ponderNextMove = newState;
13658 NewSettingEvent(option, command, value)
13664 if (gameMode == EditPosition) EditPositionDone(TRUE);
13665 sprintf(buf, "%s%s %d\n", (option ? "option ": ""), command, value);
13666 SendToProgram(buf, &first);
13667 if (gameMode == TwoMachinesPlay) {
13668 SendToProgram(buf, &second);
13673 ShowThinkingEvent()
13674 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
13676 static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
13677 int newState = appData.showThinking
13678 // [HGM] thinking: other features now need thinking output as well
13679 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
13681 if (oldState == newState) return;
13682 oldState = newState;
13683 if (gameMode == EditPosition) EditPositionDone(TRUE);
13685 SendToProgram("post\n", &first);
13686 if (gameMode == TwoMachinesPlay) {
13687 SendToProgram("post\n", &second);
13690 SendToProgram("nopost\n", &first);
13691 thinkOutput[0] = NULLCHAR;
13692 if (gameMode == TwoMachinesPlay) {
13693 SendToProgram("nopost\n", &second);
13696 // appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
13700 AskQuestionEvent(title, question, replyPrefix, which)
13701 char *title; char *question; char *replyPrefix; char *which;
13703 ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
13704 if (pr == NoProc) return;
13705 AskQuestion(title, question, replyPrefix, pr);
13709 DisplayMove(moveNumber)
13712 char message[MSG_SIZ];
13714 char cpThinkOutput[MSG_SIZ];
13716 if(appData.noGUI) return; // [HGM] fast: suppress display of moves
13718 if (moveNumber == forwardMostMove - 1 ||
13719 gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13721 safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput));
13723 if (strchr(cpThinkOutput, '\n')) {
13724 *strchr(cpThinkOutput, '\n') = NULLCHAR;
13727 *cpThinkOutput = NULLCHAR;
13730 /* [AS] Hide thinking from human user */
13731 if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
13732 *cpThinkOutput = NULLCHAR;
13733 if( thinkOutput[0] != NULLCHAR ) {
13736 for( i=0; i<=hiddenThinkOutputState; i++ ) {
13737 cpThinkOutput[i] = '.';
13739 cpThinkOutput[i] = NULLCHAR;
13740 hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
13744 if (moveNumber == forwardMostMove - 1 &&
13745 gameInfo.resultDetails != NULL) {
13746 if (gameInfo.resultDetails[0] == NULLCHAR) {
13747 sprintf(res, " %s", PGNResult(gameInfo.result));
13749 sprintf(res, " {%s} %s",
13750 gameInfo.resultDetails, PGNResult(gameInfo.result));
13756 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
13757 DisplayMessage(res, cpThinkOutput);
13759 sprintf(message, "%d.%s%s%s", moveNumber / 2 + 1,
13760 WhiteOnMove(moveNumber) ? " " : ".. ",
13761 parseList[moveNumber], res);
13762 DisplayMessage(message, cpThinkOutput);
13767 DisplayComment(moveNumber, text)
13771 char title[MSG_SIZ];
13772 char buf[8000]; // comment can be long!
13775 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
13776 strcpy(title, "Comment");
13778 sprintf(title, "Comment on %d.%s%s", moveNumber / 2 + 1,
13779 WhiteOnMove(moveNumber) ? " " : ".. ",
13780 parseList[moveNumber]);
13782 // [HGM] PV info: display PV info together with (or as) comment
13783 if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {
13784 if(text == NULL) text = "";
13785 score = pvInfoList[moveNumber].score;
13786 sprintf(buf, "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,
13787 depth, (pvInfoList[moveNumber].time+50)/100, text);
13790 if (text != NULL && (appData.autoDisplayComment || commentUp))
13791 CommentPopUp(title, text);
13794 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
13795 * might be busy thinking or pondering. It can be omitted if your
13796 * gnuchess is configured to stop thinking immediately on any user
13797 * input. However, that gnuchess feature depends on the FIONREAD
13798 * ioctl, which does not work properly on some flavors of Unix.
13802 ChessProgramState *cps;
13805 if (!cps->useSigint) return;
13806 if (appData.noChessProgram || (cps->pr == NoProc)) return;
13807 switch (gameMode) {
13808 case MachinePlaysWhite:
13809 case MachinePlaysBlack:
13810 case TwoMachinesPlay:
13811 case IcsPlayingWhite:
13812 case IcsPlayingBlack:
13815 /* Skip if we know it isn't thinking */
13816 if (!cps->maybeThinking) return;
13817 if (appData.debugMode)
13818 fprintf(debugFP, "Interrupting %s\n", cps->which);
13819 InterruptChildProcess(cps->pr);
13820 cps->maybeThinking = FALSE;
13825 #endif /*ATTENTION*/
13831 if (whiteTimeRemaining <= 0) {
13834 if (appData.icsActive) {
13835 if (appData.autoCallFlag &&
13836 gameMode == IcsPlayingBlack && !blackFlag) {
13837 SendToICS(ics_prefix);
13838 SendToICS("flag\n");
13842 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
13844 if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
13845 if (appData.autoCallFlag) {
13846 GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
13853 if (blackTimeRemaining <= 0) {
13856 if (appData.icsActive) {
13857 if (appData.autoCallFlag &&
13858 gameMode == IcsPlayingWhite && !whiteFlag) {
13859 SendToICS(ics_prefix);
13860 SendToICS("flag\n");
13864 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
13866 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
13867 if (appData.autoCallFlag) {
13868 GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
13881 if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
13882 gameMode == PlayFromGameFile || forwardMostMove == 0) return;
13885 * add time to clocks when time control is achieved ([HGM] now also used for increment)
13887 if ( !WhiteOnMove(forwardMostMove) )
13888 /* White made time control */
13889 whiteTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
13890 /* [HGM] time odds: correct new time quota for time odds! */
13891 / WhitePlayer()->timeOdds;
13893 /* Black made time control */
13894 blackTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
13895 / WhitePlayer()->other->timeOdds;
13899 DisplayBothClocks()
13901 int wom = gameMode == EditPosition ?
13902 !blackPlaysFirst : WhiteOnMove(currentMove);
13903 DisplayWhiteClock(whiteTimeRemaining, wom);
13904 DisplayBlackClock(blackTimeRemaining, !wom);
13908 /* Timekeeping seems to be a portability nightmare. I think everyone
13909 has ftime(), but I'm really not sure, so I'm including some ifdefs
13910 to use other calls if you don't. Clocks will be less accurate if
13911 you have neither ftime nor gettimeofday.
13914 /* VS 2008 requires the #include outside of the function */
13915 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
13916 #include <sys/timeb.h>
13919 /* Get the current time as a TimeMark */
13924 #if HAVE_GETTIMEOFDAY
13926 struct timeval timeVal;
13927 struct timezone timeZone;
13929 gettimeofday(&timeVal, &timeZone);
13930 tm->sec = (long) timeVal.tv_sec;
13931 tm->ms = (int) (timeVal.tv_usec / 1000L);
13933 #else /*!HAVE_GETTIMEOFDAY*/
13936 // include <sys/timeb.h> / moved to just above start of function
13937 struct timeb timeB;
13940 tm->sec = (long) timeB.time;
13941 tm->ms = (int) timeB.millitm;
13943 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
13944 tm->sec = (long) time(NULL);
13950 /* Return the difference in milliseconds between two
13951 time marks. We assume the difference will fit in a long!
13954 SubtractTimeMarks(tm2, tm1)
13955 TimeMark *tm2, *tm1;
13957 return 1000L*(tm2->sec - tm1->sec) +
13958 (long) (tm2->ms - tm1->ms);
13963 * Code to manage the game clocks.
13965 * In tournament play, black starts the clock and then white makes a move.
13966 * We give the human user a slight advantage if he is playing white---the
13967 * clocks don't run until he makes his first move, so it takes zero time.
13968 * Also, we don't account for network lag, so we could get out of sync
13969 * with GNU Chess's clock -- but then, referees are always right.
13972 static TimeMark tickStartTM;
13973 static long intendedTickLength;
13976 NextTickLength(timeRemaining)
13977 long timeRemaining;
13979 long nominalTickLength, nextTickLength;
13981 if (timeRemaining > 0L && timeRemaining <= 10000L)
13982 nominalTickLength = 100L;
13984 nominalTickLength = 1000L;
13985 nextTickLength = timeRemaining % nominalTickLength;
13986 if (nextTickLength <= 0) nextTickLength += nominalTickLength;
13988 return nextTickLength;
13991 /* Adjust clock one minute up or down */
13993 AdjustClock(Boolean which, int dir)
13995 if(which) blackTimeRemaining += 60000*dir;
13996 else whiteTimeRemaining += 60000*dir;
13997 DisplayBothClocks();
14000 /* Stop clocks and reset to a fresh time control */
14004 (void) StopClockTimer();
14005 if (appData.icsActive) {
14006 whiteTimeRemaining = blackTimeRemaining = 0;
14007 } else if (searchTime) {
14008 whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
14009 blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
14010 } else { /* [HGM] correct new time quote for time odds */
14011 whiteTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->timeOdds;
14012 blackTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->other->timeOdds;
14014 if (whiteFlag || blackFlag) {
14016 whiteFlag = blackFlag = FALSE;
14018 DisplayBothClocks();
14021 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
14023 /* Decrement running clock by amount of time that has passed */
14027 long timeRemaining;
14028 long lastTickLength, fudge;
14031 if (!appData.clockMode) return;
14032 if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
14036 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
14038 /* Fudge if we woke up a little too soon */
14039 fudge = intendedTickLength - lastTickLength;
14040 if (fudge < 0 || fudge > FUDGE) fudge = 0;
14042 if (WhiteOnMove(forwardMostMove)) {
14043 if(whiteNPS >= 0) lastTickLength = 0;
14044 timeRemaining = whiteTimeRemaining -= lastTickLength;
14045 DisplayWhiteClock(whiteTimeRemaining - fudge,
14046 WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
14048 if(blackNPS >= 0) lastTickLength = 0;
14049 timeRemaining = blackTimeRemaining -= lastTickLength;
14050 DisplayBlackClock(blackTimeRemaining - fudge,
14051 !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
14054 if (CheckFlags()) return;
14057 intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
14058 StartClockTimer(intendedTickLength);
14060 /* if the time remaining has fallen below the alarm threshold, sound the
14061 * alarm. if the alarm has sounded and (due to a takeback or time control
14062 * with increment) the time remaining has increased to a level above the
14063 * threshold, reset the alarm so it can sound again.
14066 if (appData.icsActive && appData.icsAlarm) {
14068 /* make sure we are dealing with the user's clock */
14069 if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
14070 ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
14073 if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
14074 alarmSounded = FALSE;
14075 } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
14077 alarmSounded = TRUE;
14083 /* A player has just moved, so stop the previously running
14084 clock and (if in clock mode) start the other one.
14085 We redisplay both clocks in case we're in ICS mode, because
14086 ICS gives us an update to both clocks after every move.
14087 Note that this routine is called *after* forwardMostMove
14088 is updated, so the last fractional tick must be subtracted
14089 from the color that is *not* on move now.
14092 SwitchClocks(int newMoveNr)
14094 long lastTickLength;
14096 int flagged = FALSE;
14100 if (StopClockTimer() && appData.clockMode) {
14101 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
14102 if (!WhiteOnMove(forwardMostMove)) {
14103 if(blackNPS >= 0) lastTickLength = 0;
14104 blackTimeRemaining -= lastTickLength;
14105 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
14106 // if(pvInfoList[forwardMostMove-1].time == -1)
14107 pvInfoList[forwardMostMove-1].time = // use GUI time
14108 (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
14110 if(whiteNPS >= 0) lastTickLength = 0;
14111 whiteTimeRemaining -= lastTickLength;
14112 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
14113 // if(pvInfoList[forwardMostMove-1].time == -1)
14114 pvInfoList[forwardMostMove-1].time =
14115 (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
14117 flagged = CheckFlags();
14119 forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
14120 CheckTimeControl();
14122 if (flagged || !appData.clockMode) return;
14124 switch (gameMode) {
14125 case MachinePlaysBlack:
14126 case MachinePlaysWhite:
14127 case BeginningOfGame:
14128 if (pausing) return;
14132 case PlayFromGameFile:
14140 if (searchTime) { // [HGM] st: set clock of player that has to move to max time
14141 if(WhiteOnMove(forwardMostMove))
14142 whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
14143 else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
14147 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
14148 whiteTimeRemaining : blackTimeRemaining);
14149 StartClockTimer(intendedTickLength);
14153 /* Stop both clocks */
14157 long lastTickLength;
14160 if (!StopClockTimer()) return;
14161 if (!appData.clockMode) return;
14165 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
14166 if (WhiteOnMove(forwardMostMove)) {
14167 if(whiteNPS >= 0) lastTickLength = 0;
14168 whiteTimeRemaining -= lastTickLength;
14169 DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
14171 if(blackNPS >= 0) lastTickLength = 0;
14172 blackTimeRemaining -= lastTickLength;
14173 DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
14178 /* Start clock of player on move. Time may have been reset, so
14179 if clock is already running, stop and restart it. */
14183 (void) StopClockTimer(); /* in case it was running already */
14184 DisplayBothClocks();
14185 if (CheckFlags()) return;
14187 if (!appData.clockMode) return;
14188 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
14190 GetTimeMark(&tickStartTM);
14191 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
14192 whiteTimeRemaining : blackTimeRemaining);
14194 /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
14195 whiteNPS = blackNPS = -1;
14196 if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
14197 || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
14198 whiteNPS = first.nps;
14199 if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
14200 || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
14201 blackNPS = first.nps;
14202 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
14203 whiteNPS = second.nps;
14204 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
14205 blackNPS = second.nps;
14206 if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
14208 StartClockTimer(intendedTickLength);
14215 long second, minute, hour, day;
14217 static char buf[32];
14219 if (ms > 0 && ms <= 9900) {
14220 /* convert milliseconds to tenths, rounding up */
14221 double tenths = floor( ((double)(ms + 99L)) / 100.00 );
14223 sprintf(buf, " %03.1f ", tenths/10.0);
14227 /* convert milliseconds to seconds, rounding up */
14228 /* use floating point to avoid strangeness of integer division
14229 with negative dividends on many machines */
14230 second = (long) floor(((double) (ms + 999L)) / 1000.0);
14237 day = second / (60 * 60 * 24);
14238 second = second % (60 * 60 * 24);
14239 hour = second / (60 * 60);
14240 second = second % (60 * 60);
14241 minute = second / 60;
14242 second = second % 60;
14245 sprintf(buf, " %s%ld:%02ld:%02ld:%02ld ",
14246 sign, day, hour, minute, second);
14248 sprintf(buf, " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
14250 sprintf(buf, " %s%2ld:%02ld ", sign, minute, second);
14257 * This is necessary because some C libraries aren't ANSI C compliant yet.
14260 StrStr(string, match)
14261 char *string, *match;
14265 length = strlen(match);
14267 for (i = strlen(string) - length; i >= 0; i--, string++)
14268 if (!strncmp(match, string, length))
14275 StrCaseStr(string, match)
14276 char *string, *match;
14280 length = strlen(match);
14282 for (i = strlen(string) - length; i >= 0; i--, string++) {
14283 for (j = 0; j < length; j++) {
14284 if (ToLower(match[j]) != ToLower(string[j]))
14287 if (j == length) return string;
14301 c1 = ToLower(*s1++);
14302 c2 = ToLower(*s2++);
14303 if (c1 > c2) return 1;
14304 if (c1 < c2) return -1;
14305 if (c1 == NULLCHAR) return 0;
14314 return isupper(c) ? tolower(c) : c;
14322 return islower(c) ? toupper(c) : c;
14324 #endif /* !_amigados */
14332 if ((ret = (char *) malloc(strlen(s) + 1))) {
14339 StrSavePtr(s, savePtr)
14340 char *s, **savePtr;
14345 if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
14346 strcpy(*savePtr, s);
14358 clock = time((time_t *)NULL);
14359 tm = localtime(&clock);
14360 sprintf(buf, "%04d.%02d.%02d",
14361 tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
14362 return StrSave(buf);
14367 PositionToFEN(move, overrideCastling)
14369 char *overrideCastling;
14371 int i, j, fromX, fromY, toX, toY;
14378 whiteToPlay = (gameMode == EditPosition) ?
14379 !blackPlaysFirst : (move % 2 == 0);
14382 /* Piece placement data */
14383 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
14385 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
14386 if (boards[move][i][j] == EmptySquare) {
14388 } else { ChessSquare piece = boards[move][i][j];
14389 if (emptycount > 0) {
14390 if(emptycount<10) /* [HGM] can be >= 10 */
14391 *p++ = '0' + emptycount;
14392 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
14395 if(PieceToChar(piece) == '+') {
14396 /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
14398 piece = (ChessSquare)(DEMOTED piece);
14400 *p++ = PieceToChar(piece);
14402 /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
14403 p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
14408 if (emptycount > 0) {
14409 if(emptycount<10) /* [HGM] can be >= 10 */
14410 *p++ = '0' + emptycount;
14411 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
14418 /* [HGM] print Crazyhouse or Shogi holdings */
14419 if( gameInfo.holdingsWidth ) {
14420 *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
14422 for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
14423 piece = boards[move][i][BOARD_WIDTH-1];
14424 if( piece != EmptySquare )
14425 for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
14426 *p++ = PieceToChar(piece);
14428 for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
14429 piece = boards[move][BOARD_HEIGHT-i-1][0];
14430 if( piece != EmptySquare )
14431 for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
14432 *p++ = PieceToChar(piece);
14435 if( q == p ) *p++ = '-';
14441 *p++ = whiteToPlay ? 'w' : 'b';
14444 if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
14445 while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
14447 if(nrCastlingRights) {
14449 if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
14450 /* [HGM] write directly from rights */
14451 if(boards[move][CASTLING][2] != NoRights &&
14452 boards[move][CASTLING][0] != NoRights )
14453 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
14454 if(boards[move][CASTLING][2] != NoRights &&
14455 boards[move][CASTLING][1] != NoRights )
14456 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
14457 if(boards[move][CASTLING][5] != NoRights &&
14458 boards[move][CASTLING][3] != NoRights )
14459 *p++ = boards[move][CASTLING][3] + AAA;
14460 if(boards[move][CASTLING][5] != NoRights &&
14461 boards[move][CASTLING][4] != NoRights )
14462 *p++ = boards[move][CASTLING][4] + AAA;
14465 /* [HGM] write true castling rights */
14466 if( nrCastlingRights == 6 ) {
14467 if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
14468 boards[move][CASTLING][2] != NoRights ) *p++ = 'K';
14469 if(boards[move][CASTLING][1] == BOARD_LEFT &&
14470 boards[move][CASTLING][2] != NoRights ) *p++ = 'Q';
14471 if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
14472 boards[move][CASTLING][5] != NoRights ) *p++ = 'k';
14473 if(boards[move][CASTLING][4] == BOARD_LEFT &&
14474 boards[move][CASTLING][5] != NoRights ) *p++ = 'q';
14477 if (q == p) *p++ = '-'; /* No castling rights */
14481 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
14482 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
14483 /* En passant target square */
14484 if (move > backwardMostMove) {
14485 fromX = moveList[move - 1][0] - AAA;
14486 fromY = moveList[move - 1][1] - ONE;
14487 toX = moveList[move - 1][2] - AAA;
14488 toY = moveList[move - 1][3] - ONE;
14489 if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
14490 toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
14491 boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
14493 /* 2-square pawn move just happened */
14495 *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
14499 } else if(move == backwardMostMove) {
14500 // [HGM] perhaps we should always do it like this, and forget the above?
14501 if((signed char)boards[move][EP_STATUS] >= 0) {
14502 *p++ = boards[move][EP_STATUS] + AAA;
14503 *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
14514 /* [HGM] find reversible plies */
14515 { int i = 0, j=move;
14517 if (appData.debugMode) { int k;
14518 fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
14519 for(k=backwardMostMove; k<=forwardMostMove; k++)
14520 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
14524 while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
14525 if( j == backwardMostMove ) i += initialRulePlies;
14526 sprintf(p, "%d ", i);
14527 p += i>=100 ? 4 : i >= 10 ? 3 : 2;
14529 /* Fullmove number */
14530 sprintf(p, "%d", (move / 2) + 1);
14532 return StrSave(buf);
14536 ParseFEN(board, blackPlaysFirst, fen)
14538 int *blackPlaysFirst;
14548 /* [HGM] by default clear Crazyhouse holdings, if present */
14549 if(gameInfo.holdingsWidth) {
14550 for(i=0; i<BOARD_HEIGHT; i++) {
14551 board[i][0] = EmptySquare; /* black holdings */
14552 board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
14553 board[i][1] = (ChessSquare) 0; /* black counts */
14554 board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
14558 /* Piece placement data */
14559 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
14562 if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
14563 if (*p == '/') p++;
14564 emptycount = gameInfo.boardWidth - j;
14565 while (emptycount--)
14566 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
14568 #if(BOARD_FILES >= 10)
14569 } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
14570 p++; emptycount=10;
14571 if (j + emptycount > gameInfo.boardWidth) return FALSE;
14572 while (emptycount--)
14573 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
14575 } else if (isdigit(*p)) {
14576 emptycount = *p++ - '0';
14577 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
14578 if (j + emptycount > gameInfo.boardWidth) return FALSE;
14579 while (emptycount--)
14580 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
14581 } else if (*p == '+' || isalpha(*p)) {
14582 if (j >= gameInfo.boardWidth) return FALSE;
14584 piece = CharToPiece(*++p);
14585 if(piece == EmptySquare) return FALSE; /* unknown piece */
14586 piece = (ChessSquare) (PROMOTED piece ); p++;
14587 if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
14588 } else piece = CharToPiece(*p++);
14590 if(piece==EmptySquare) return FALSE; /* unknown piece */
14591 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
14592 piece = (ChessSquare) (PROMOTED piece);
14593 if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
14596 board[i][(j++)+gameInfo.holdingsWidth] = piece;
14602 while (*p == '/' || *p == ' ') p++;
14604 /* [HGM] look for Crazyhouse holdings here */
14605 while(*p==' ') p++;
14606 if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
14608 if(*p == '-' ) *p++; /* empty holdings */ else {
14609 if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
14610 /* if we would allow FEN reading to set board size, we would */
14611 /* have to add holdings and shift the board read so far here */
14612 while( (piece = CharToPiece(*p) ) != EmptySquare ) {
14614 if((int) piece >= (int) BlackPawn ) {
14615 i = (int)piece - (int)BlackPawn;
14616 i = PieceToNumber((ChessSquare)i);
14617 if( i >= gameInfo.holdingsSize ) return FALSE;
14618 board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
14619 board[BOARD_HEIGHT-1-i][1]++; /* black counts */
14621 i = (int)piece - (int)WhitePawn;
14622 i = PieceToNumber((ChessSquare)i);
14623 if( i >= gameInfo.holdingsSize ) return FALSE;
14624 board[i][BOARD_WIDTH-1] = piece; /* white holdings */
14625 board[i][BOARD_WIDTH-2]++; /* black holdings */
14629 if(*p == ']') *p++;
14632 while(*p == ' ') p++;
14637 *blackPlaysFirst = FALSE;
14640 *blackPlaysFirst = TRUE;
14646 /* [HGM] We NO LONGER ignore the rest of the FEN notation */
14647 /* return the extra info in global variiables */
14649 /* set defaults in case FEN is incomplete */
14650 board[EP_STATUS] = EP_UNKNOWN;
14651 for(i=0; i<nrCastlingRights; i++ ) {
14652 board[CASTLING][i] =
14653 gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
14654 } /* assume possible unless obviously impossible */
14655 if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
14656 if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
14657 if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
14658 && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
14659 if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
14660 if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
14661 if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
14662 && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
14665 while(*p==' ') p++;
14666 if(nrCastlingRights) {
14667 if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
14668 /* castling indicator present, so default becomes no castlings */
14669 for(i=0; i<nrCastlingRights; i++ ) {
14670 board[CASTLING][i] = NoRights;
14673 while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
14674 (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
14675 ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
14676 ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth) ) {
14677 char c = *p++; int whiteKingFile=NoRights, blackKingFile=NoRights;
14679 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
14680 if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
14681 if(board[0 ][i] == WhiteKing) whiteKingFile = i;
14683 if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
14684 whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
14685 if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
14686 && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
14687 if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
14688 && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
14691 for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
14692 board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
14693 board[CASTLING][2] = whiteKingFile;
14696 for(i=BOARD_LEFT; i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
14697 board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
14698 board[CASTLING][2] = whiteKingFile;
14701 for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
14702 board[CASTLING][3] = i != blackKingFile ? i : NoRights;
14703 board[CASTLING][5] = blackKingFile;
14706 for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
14707 board[CASTLING][4] = i != blackKingFile ? i : NoRights;
14708 board[CASTLING][5] = blackKingFile;
14711 default: /* FRC castlings */
14712 if(c >= 'a') { /* black rights */
14713 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
14714 if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
14715 if(i == BOARD_RGHT) break;
14716 board[CASTLING][5] = i;
14718 if(board[BOARD_HEIGHT-1][c] < BlackPawn ||
14719 board[BOARD_HEIGHT-1][c] >= BlackKing ) break;
14721 board[CASTLING][3] = c;
14723 board[CASTLING][4] = c;
14724 } else { /* white rights */
14725 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
14726 if(board[0][i] == WhiteKing) break;
14727 if(i == BOARD_RGHT) break;
14728 board[CASTLING][2] = i;
14729 c -= AAA - 'a' + 'A';
14730 if(board[0][c] >= WhiteKing) break;
14732 board[CASTLING][0] = c;
14734 board[CASTLING][1] = c;
14738 for(i=0; i<nrCastlingRights; i++)
14739 if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
14740 if (appData.debugMode) {
14741 fprintf(debugFP, "FEN castling rights:");
14742 for(i=0; i<nrCastlingRights; i++)
14743 fprintf(debugFP, " %d", board[CASTLING][i]);
14744 fprintf(debugFP, "\n");
14747 while(*p==' ') p++;
14750 /* read e.p. field in games that know e.p. capture */
14751 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
14752 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
14754 p++; board[EP_STATUS] = EP_NONE;
14756 char c = *p++ - AAA;
14758 if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
14759 if(*p >= '0' && *p <='9') *p++;
14760 board[EP_STATUS] = c;
14765 if(sscanf(p, "%d", &i) == 1) {
14766 FENrulePlies = i; /* 50-move ply counter */
14767 /* (The move number is still ignored) */
14774 EditPositionPasteFEN(char *fen)
14777 Board initial_position;
14779 if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
14780 DisplayError(_("Bad FEN position in clipboard"), 0);
14783 int savedBlackPlaysFirst = blackPlaysFirst;
14784 EditPositionEvent();
14785 blackPlaysFirst = savedBlackPlaysFirst;
14786 CopyBoard(boards[0], initial_position);
14787 initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
14788 EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
14789 DisplayBothClocks();
14790 DrawPosition(FALSE, boards[currentMove]);
14795 static char cseq[12] = "\\ ";
14797 Boolean set_cont_sequence(char *new_seq)
14802 // handle bad attempts to set the sequence
14804 return 0; // acceptable error - no debug
14806 len = strlen(new_seq);
14807 ret = (len > 0) && (len < sizeof(cseq));
14809 strcpy(cseq, new_seq);
14810 else if (appData.debugMode)
14811 fprintf(debugFP, "Invalid continuation sequence \"%s\" (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
14816 reformat a source message so words don't cross the width boundary. internal
14817 newlines are not removed. returns the wrapped size (no null character unless
14818 included in source message). If dest is NULL, only calculate the size required
14819 for the dest buffer. lp argument indicats line position upon entry, and it's
14820 passed back upon exit.
14822 int wrap(char *dest, char *src, int count, int width, int *lp)
14824 int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
14826 cseq_len = strlen(cseq);
14827 old_line = line = *lp;
14828 ansi = len = clen = 0;
14830 for (i=0; i < count; i++)
14832 if (src[i] == '\033')
14835 // if we hit the width, back up
14836 if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
14838 // store i & len in case the word is too long
14839 old_i = i, old_len = len;
14841 // find the end of the last word
14842 while (i && src[i] != ' ' && src[i] != '\n')
14848 // word too long? restore i & len before splitting it
14849 if ((old_i-i+clen) >= width)
14856 if (i && src[i-1] == ' ')
14859 if (src[i] != ' ' && src[i] != '\n')
14866 // now append the newline and continuation sequence
14871 strncpy(dest+len, cseq, cseq_len);
14879 dest[len] = src[i];
14883 if (src[i] == '\n')
14888 if (dest && appData.debugMode)
14890 fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
14891 count, width, line, len, *lp);
14892 show_bytes(debugFP, src, count);
14893 fprintf(debugFP, "\ndest: ");
14894 show_bytes(debugFP, dest, len);
14895 fprintf(debugFP, "\n");
14897 *lp = dest ? line : old_line;
14902 // [HGM] vari: routines for shelving variations
14905 PushTail(int firstMove, int lastMove)
14907 int i, j, nrMoves = lastMove - firstMove;
14909 if(appData.icsActive) { // only in local mode
14910 forwardMostMove = currentMove; // mimic old ICS behavior
14913 if(storedGames >= MAX_VARIATIONS-1) return;
14915 // push current tail of game on stack
14916 savedResult[storedGames] = gameInfo.result;
14917 savedDetails[storedGames] = gameInfo.resultDetails;
14918 gameInfo.resultDetails = NULL;
14919 savedFirst[storedGames] = firstMove;
14920 savedLast [storedGames] = lastMove;
14921 savedFramePtr[storedGames] = framePtr;
14922 framePtr -= nrMoves; // reserve space for the boards
14923 for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
14924 CopyBoard(boards[framePtr+i], boards[firstMove+i]);
14925 for(j=0; j<MOVE_LEN; j++)
14926 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
14927 for(j=0; j<2*MOVE_LEN; j++)
14928 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
14929 timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
14930 timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
14931 pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
14932 pvInfoList[firstMove+i-1].depth = 0;
14933 commentList[framePtr+i] = commentList[firstMove+i];
14934 commentList[firstMove+i] = NULL;
14938 forwardMostMove = currentMove; // truncte game so we can start variation
14939 if(storedGames == 1) GreyRevert(FALSE);
14943 PopTail(Boolean annotate)
14946 char buf[8000], moveBuf[20];
14948 if(appData.icsActive) return FALSE; // only in local mode
14949 if(!storedGames) return FALSE; // sanity
14952 ToNrEvent(savedFirst[storedGames]); // sets currentMove
14953 nrMoves = savedLast[storedGames] - currentMove;
14956 if(!WhiteOnMove(currentMove)) sprintf(buf, "(%d...", currentMove+2>>1);
14957 else strcpy(buf, "(");
14958 for(i=currentMove; i<forwardMostMove; i++) {
14960 sprintf(moveBuf, " %d. %s", i+2>>1, SavePart(parseList[i]));
14961 else sprintf(moveBuf, " %s", SavePart(parseList[i]));
14962 strcat(buf, moveBuf);
14963 if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
14967 for(i=1; i<nrMoves; i++) { // copy last variation back
14968 CopyBoard(boards[currentMove+i], boards[framePtr+i]);
14969 for(j=0; j<MOVE_LEN; j++)
14970 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
14971 for(j=0; j<2*MOVE_LEN; j++)
14972 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
14973 timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
14974 timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
14975 pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
14976 if(commentList[currentMove+i]) free(commentList[currentMove+i]);
14977 commentList[currentMove+i] = commentList[framePtr+i];
14978 commentList[framePtr+i] = NULL;
14980 if(annotate) AppendComment(currentMove+1, buf, FALSE);
14981 framePtr = savedFramePtr[storedGames];
14982 gameInfo.result = savedResult[storedGames];
14983 if(gameInfo.resultDetails != NULL) {
14984 free(gameInfo.resultDetails);
14986 gameInfo.resultDetails = savedDetails[storedGames];
14987 forwardMostMove = currentMove + nrMoves;
14988 if(storedGames == 0) GreyRevert(TRUE);
14994 { // remove all shelved variations
14996 for(i=0; i<storedGames; i++) {
14997 if(savedDetails[i])
14998 free(savedDetails[i]);
14999 savedDetails[i] = NULL;
15001 for(i=framePtr; i<MAX_MOVES; i++) {
15002 if(commentList[i]) free(commentList[i]);
15003 commentList[i] = NULL;
15005 framePtr = MAX_MOVES-1;