2 * backend.c -- Common back end for X and Windows NT versions of
4 * Copyright 1991 by Digital Equipment Corporation, Maynard,
7 * Enhancements Copyright 1992-2001, 2002, 2003, 2004, 2005, 2006,
8 * 2007, 2008, 2009 Free Software Foundation, Inc.
10 * Enhancements Copyright 2005 Alessandro Scotti
12 * The following terms apply to Digital Equipment Corporation's copyright
14 * ------------------------------------------------------------------------
17 * Permission to use, copy, modify, and distribute this software and its
18 * documentation for any purpose and without fee is hereby granted,
19 * provided that the above copyright notice appear in all copies and that
20 * both that copyright notice and this permission notice appear in
21 * supporting documentation, and that the name of Digital not be
22 * used in advertising or publicity pertaining to distribution of the
23 * software without specific, written prior permission.
25 * DIGITAL DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
26 * ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL
27 * DIGITAL BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR
28 * ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
29 * WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
30 * ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
32 * ------------------------------------------------------------------------
34 * The following terms apply to the enhanced version of XBoard
35 * distributed by the Free Software Foundation:
36 * ------------------------------------------------------------------------
38 * GNU XBoard is free software: you can redistribute it and/or modify
39 * it under the terms of the GNU General Public License as published by
40 * the Free Software Foundation, either version 3 of the License, or (at
41 * your option) any later version.
43 * GNU XBoard is distributed in the hope that it will be useful, but
44 * WITHOUT ANY WARRANTY; without even the implied warranty of
45 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
46 * General Public License for more details.
48 * You should have received a copy of the GNU General Public License
49 * along with this program. If not, see http://www.gnu.org/licenses/. *
51 *------------------------------------------------------------------------
52 ** See the file ChangeLog for a revision history. */
54 /* [AS] Also useful here for debugging */
58 #define DoSleep( n ) if( (n) != 0 ) Sleep( (n) );
62 #define DoSleep( n ) if( (n) >= 0) sleep(n)
72 #include <sys/types.h>
81 #else /* not STDC_HEADERS */
84 # else /* not HAVE_STRING_H */
86 # endif /* not HAVE_STRING_H */
87 #endif /* not STDC_HEADERS */
90 # include <sys/fcntl.h>
91 #else /* not HAVE_SYS_FCNTL_H */
94 # endif /* HAVE_FCNTL_H */
95 #endif /* not HAVE_SYS_FCNTL_H */
97 #if TIME_WITH_SYS_TIME
98 # include <sys/time.h>
102 # include <sys/time.h>
108 #if defined(_amigados) && !defined(__GNUC__)
113 extern int gettimeofday(struct timeval *, struct timezone *);
121 #include "frontend.h"
128 #include "backendz.h"
132 # define _(s) gettext (s)
133 # define N_(s) gettext_noop (s)
140 /* A point in time */
142 long sec; /* Assuming this is >= 32 bits */
143 int ms; /* Assuming this is >= 16 bits */
146 int establish P((void));
147 void read_from_player P((InputSourceRef isr, VOIDSTAR closure,
148 char *buf, int count, int error));
149 void read_from_ics P((InputSourceRef isr, VOIDSTAR closure,
150 char *buf, int count, int error));
151 void ics_printf P((char *format, ...));
152 void SendToICS P((char *s));
153 void SendToICSDelayed P((char *s, long msdelay));
154 void SendMoveToICS P((ChessMove moveType, int fromX, int fromY,
156 void HandleMachineMove P((char *message, ChessProgramState *cps));
157 int AutoPlayOneMove P((void));
158 int LoadGameOneMove P((ChessMove readAhead));
159 int LoadGameFromFile P((char *filename, int n, char *title, int useList));
160 int LoadPositionFromFile P((char *filename, int n, char *title));
161 int SavePositionToFile P((char *filename));
162 void ApplyMove P((int fromX, int fromY, int toX, int toY, int promoChar,
164 void MakeMove P((int fromX, int fromY, int toX, int toY, int promoChar));
165 void ShowMove P((int fromX, int fromY, int toX, int toY));
166 int FinishMove P((ChessMove moveType, int fromX, int fromY, int toX, int toY,
167 /*char*/int promoChar));
168 void BackwardInner P((int target));
169 void ForwardInner P((int target));
170 int Adjudicate P((ChessProgramState *cps));
171 void GameEnds P((ChessMove result, char *resultDetails, int whosays));
172 void EditPositionDone P((Boolean fakeRights));
173 void PrintOpponents P((FILE *fp));
174 void PrintPosition P((FILE *fp, int move));
175 void StartChessProgram P((ChessProgramState *cps));
176 void SendToProgram P((char *message, ChessProgramState *cps));
177 void SendMoveToProgram P((int moveNum, ChessProgramState *cps));
178 void ReceiveFromProgram P((InputSourceRef isr, VOIDSTAR closure,
179 char *buf, int count, int error));
180 void SendTimeControl P((ChessProgramState *cps,
181 int mps, long tc, int inc, int sd, int st));
182 char *TimeControlTagValue P((void));
183 void Attention P((ChessProgramState *cps));
184 void FeedMovesToProgram P((ChessProgramState *cps, int upto));
185 void ResurrectChessProgram P((void));
186 void DisplayComment P((int moveNumber, char *text));
187 void DisplayMove P((int moveNumber));
189 void ParseGameHistory P((char *game));
190 void ParseBoard12 P((char *string));
191 void KeepAlive P((void));
192 void StartClocks P((void));
193 void SwitchClocks P((void));
194 void StopClocks P((void));
195 void ResetClocks P((void));
196 char *PGNDate P((void));
197 void SetGameInfo P((void));
198 Boolean ParseFEN P((Board board, int *blackPlaysFirst, char *fen));
199 int RegisterMove P((void));
200 void MakeRegisteredMove P((void));
201 void TruncateGame P((void));
202 int looking_at P((char *, int *, char *));
203 void CopyPlayerNameIntoFileName P((char **, char *));
204 char *SavePart P((char *));
205 int SaveGameOldStyle P((FILE *));
206 int SaveGamePGN P((FILE *));
207 void GetTimeMark P((TimeMark *));
208 long SubtractTimeMarks P((TimeMark *, TimeMark *));
209 int CheckFlags P((void));
210 long NextTickLength P((long));
211 void CheckTimeControl P((void));
212 void show_bytes P((FILE *, char *, int));
213 int string_to_rating P((char *str));
214 void ParseFeatures P((char* args, ChessProgramState *cps));
215 void InitBackEnd3 P((void));
216 void FeatureDone P((ChessProgramState* cps, int val));
217 void InitChessProgram P((ChessProgramState *cps, int setup));
218 void OutputKibitz(int window, char *text);
219 int PerpetualChase(int first, int last);
220 int EngineOutputIsUp();
221 void InitDrawingSizes(int x, int y);
224 extern void ConsoleCreate();
227 ChessProgramState *WhitePlayer();
228 void InsertIntoMemo P((int which, char *text)); // [HGM] kibitz: in engineo.c
229 int VerifyDisplayMode P(());
231 char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment
232 void InitEngineUCI( const char * iniDir, ChessProgramState * cps ); // [HGM] moved here from winboard.c
233 char *ProbeBook P((int moveNr, char *book)); // [HGM] book: returns a book move
234 char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book
235 void ics_update_width P((int new_width));
236 extern char installDir[MSG_SIZ];
238 extern int tinyLayout, smallLayout;
239 ChessProgramStats programStats;
240 char lastPV[2][2*MSG_SIZ]; /* [HGM] pv: last PV in thinking output of each engine */
242 static int exiting = 0; /* [HGM] moved to top */
243 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
244 int startedFromPositionFile = FALSE; Board filePosition; /* [HGM] loadPos */
245 char endingGame = 0; /* [HGM] crash: flag to prevent recursion of GameEnds() */
246 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS */
247 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
248 int lastIndex = 0; /* [HGM] autoinc: last game/position used in match mode */
249 Boolean connectionAlive;/* [HGM] alive: ICS connection status from probing */
250 int opponentKibitzes;
251 int lastSavedGame; /* [HGM] save: ID of game */
252 char chatPartner[MAX_CHAT][MSG_SIZ]; /* [HGM] chat: list of chatting partners */
253 extern int chatCount;
255 char marker[BOARD_RANKS][BOARD_FILES]; /* [HGM] marks for target squares */
257 /* States for ics_getting_history */
259 #define H_REQUESTED 1
260 #define H_GOT_REQ_HEADER 2
261 #define H_GOT_UNREQ_HEADER 3
262 #define H_GETTING_MOVES 4
263 #define H_GOT_UNWANTED_HEADER 5
265 /* whosays values for GameEnds */
274 /* Maximum number of games in a cmail message */
275 #define CMAIL_MAX_GAMES 20
277 /* Different types of move when calling RegisterMove */
279 #define CMAIL_RESIGN 1
281 #define CMAIL_ACCEPT 3
283 /* Different types of result to remember for each game */
284 #define CMAIL_NOT_RESULT 0
285 #define CMAIL_OLD_RESULT 1
286 #define CMAIL_NEW_RESULT 2
288 /* Telnet protocol constants */
299 static char * safeStrCpy( char * dst, const char * src, size_t count )
301 assert( dst != NULL );
302 assert( src != NULL );
305 strncpy( dst, src, count );
306 dst[ count-1 ] = '\0';
310 /* Some compiler can't cast u64 to double
311 * This function do the job for us:
313 * We use the highest bit for cast, this only
314 * works if the highest bit is not
315 * in use (This should not happen)
317 * We used this for all compiler
320 u64ToDouble(u64 value)
323 u64 tmp = value & u64Const(0x7fffffffffffffff);
324 r = (double)(s64)tmp;
325 if (value & u64Const(0x8000000000000000))
326 r += 9.2233720368547758080e18; /* 2^63 */
330 /* Fake up flags for now, as we aren't keeping track of castling
331 availability yet. [HGM] Change of logic: the flag now only
332 indicates the type of castlings allowed by the rule of the game.
333 The actual rights themselves are maintained in the array
334 castlingRights, as part of the game history, and are not probed
340 int flags = F_ALL_CASTLE_OK;
341 if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
342 switch (gameInfo.variant) {
344 flags &= ~F_ALL_CASTLE_OK;
345 case VariantGiveaway: // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
346 flags |= F_IGNORE_CHECK;
348 flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
351 flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
353 case VariantKriegspiel:
354 flags |= F_KRIEGSPIEL_CAPTURE;
356 case VariantCapaRandom:
357 case VariantFischeRandom:
358 flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
359 case VariantNoCastle:
360 case VariantShatranj:
363 flags &= ~F_ALL_CASTLE_OK;
371 FILE *gameFileFP, *debugFP;
374 [AS] Note: sometimes, the sscanf() function is used to parse the input
375 into a fixed-size buffer. Because of this, we must be prepared to
376 receive strings as long as the size of the input buffer, which is currently
377 set to 4K for Windows and 8K for the rest.
378 So, we must either allocate sufficiently large buffers here, or
379 reduce the size of the input buffer in the input reading part.
382 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
383 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
384 char thinkOutput1[MSG_SIZ*10];
386 ChessProgramState first, second;
388 /* premove variables */
391 int premoveFromX = 0;
392 int premoveFromY = 0;
393 int premovePromoChar = 0;
395 Boolean alarmSounded;
396 /* end premove variables */
398 char *ics_prefix = "$";
399 int ics_type = ICS_GENERIC;
401 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
402 int pauseExamForwardMostMove = 0;
403 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
404 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
405 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
406 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
407 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
408 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
409 int whiteFlag = FALSE, blackFlag = FALSE;
410 int userOfferedDraw = FALSE;
411 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
412 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
413 int cmailMoveType[CMAIL_MAX_GAMES];
414 long ics_clock_paused = 0;
415 ProcRef icsPR = NoProc, cmailPR = NoProc;
416 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
417 GameMode gameMode = BeginningOfGame;
418 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
419 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
420 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
421 int hiddenThinkOutputState = 0; /* [AS] */
422 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
423 int adjudicateLossPlies = 6;
424 char white_holding[64], black_holding[64];
425 TimeMark lastNodeCountTime;
426 long lastNodeCount=0;
427 int have_sent_ICS_logon = 0;
429 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement;
430 long timeControl_2; /* [AS] Allow separate time controls */
431 char *fullTimeControlString = NULL; /* [HGM] secondary TC: merge of MPS, TC and inc */
432 long timeRemaining[2][MAX_MOVES];
434 TimeMark programStartTime;
435 char ics_handle[MSG_SIZ];
436 int have_set_title = 0;
438 /* animateTraining preserves the state of appData.animate
439 * when Training mode is activated. This allows the
440 * response to be animated when appData.animate == TRUE and
441 * appData.animateDragging == TRUE.
443 Boolean animateTraining;
449 Board boards[MAX_MOVES];
450 /* [HGM] Following 7 needed for accurate legality tests: */
451 signed char castlingRank[BOARD_FILES]; // and corresponding ranks
452 signed char initialRights[BOARD_FILES];
453 int nrCastlingRights; // For TwoKings, or to implement castling-unknown status
454 int initialRulePlies, FENrulePlies;
455 FILE *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
458 int mute; // mute all sounds
460 // [HGM] vari: next 12 to save and restore variations
461 #define MAX_VARIATIONS 10
462 int framePtr = MAX_MOVES-1; // points to free stack entry
464 int savedFirst[MAX_VARIATIONS];
465 int savedLast[MAX_VARIATIONS];
466 int savedFramePtr[MAX_VARIATIONS];
467 char *savedDetails[MAX_VARIATIONS];
468 ChessMove savedResult[MAX_VARIATIONS];
470 void PushTail P((int firstMove, int lastMove));
471 Boolean PopTail P((Boolean annotate));
472 void CleanupTail P((void));
474 ChessSquare FIDEArray[2][BOARD_FILES] = {
475 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
476 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
477 { BlackRook, BlackKnight, BlackBishop, BlackQueen,
478 BlackKing, BlackBishop, BlackKnight, BlackRook }
481 ChessSquare twoKingsArray[2][BOARD_FILES] = {
482 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
483 WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
484 { BlackRook, BlackKnight, BlackBishop, BlackQueen,
485 BlackKing, BlackKing, BlackKnight, BlackRook }
488 ChessSquare KnightmateArray[2][BOARD_FILES] = {
489 { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
490 WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
491 { BlackRook, BlackMan, BlackBishop, BlackQueen,
492 BlackUnicorn, BlackBishop, BlackMan, BlackRook }
495 ChessSquare fairyArray[2][BOARD_FILES] = { /* [HGM] Queen side differs from King side */
496 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
497 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
498 { BlackLance, BlackAlfil, BlackMarshall, BlackAngel,
499 BlackKing, BlackMarshall, BlackAlfil, BlackLance }
502 ChessSquare ShatranjArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
503 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
504 WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
505 { BlackRook, BlackKnight, BlackAlfil, BlackKing,
506 BlackFerz, BlackAlfil, BlackKnight, BlackRook }
509 ChessSquare makrukArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
510 { WhiteRook, WhiteKnight, WhiteMan, WhiteKing,
511 WhiteFerz, WhiteMan, WhiteKnight, WhiteRook },
512 { BlackRook, BlackKnight, BlackMan, BlackFerz,
513 BlackKing, BlackMan, BlackKnight, BlackRook }
517 #if (BOARD_FILES>=10)
518 ChessSquare ShogiArray[2][BOARD_FILES] = {
519 { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
520 WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
521 { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
522 BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
525 ChessSquare XiangqiArray[2][BOARD_FILES] = {
526 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
527 WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
528 { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
529 BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
532 ChessSquare CapablancaArray[2][BOARD_FILES] = {
533 { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
534 WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
535 { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
536 BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
539 ChessSquare GreatArray[2][BOARD_FILES] = {
540 { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
541 WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
542 { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
543 BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
546 ChessSquare JanusArray[2][BOARD_FILES] = {
547 { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
548 WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
549 { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
550 BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
554 ChessSquare GothicArray[2][BOARD_FILES] = {
555 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
556 WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
557 { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
558 BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
561 #define GothicArray CapablancaArray
565 ChessSquare FalconArray[2][BOARD_FILES] = {
566 { WhiteRook, WhiteKnight, WhiteBishop, WhiteLance, WhiteQueen,
567 WhiteKing, WhiteLance, WhiteBishop, WhiteKnight, WhiteRook },
568 { BlackRook, BlackKnight, BlackBishop, BlackLance, BlackQueen,
569 BlackKing, BlackLance, BlackBishop, BlackKnight, BlackRook }
572 #define FalconArray CapablancaArray
575 #else // !(BOARD_FILES>=10)
576 #define XiangqiPosition FIDEArray
577 #define CapablancaArray FIDEArray
578 #define GothicArray FIDEArray
579 #define GreatArray FIDEArray
580 #endif // !(BOARD_FILES>=10)
582 #if (BOARD_FILES>=12)
583 ChessSquare CourierArray[2][BOARD_FILES] = {
584 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
585 WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
586 { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
587 BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
589 #else // !(BOARD_FILES>=12)
590 #define CourierArray CapablancaArray
591 #endif // !(BOARD_FILES>=12)
594 Board initialPosition;
597 /* Convert str to a rating. Checks for special cases of "----",
599 "++++", etc. Also strips ()'s */
601 string_to_rating(str)
604 while(*str && !isdigit(*str)) ++str;
606 return 0; /* One of the special "no rating" cases */
614 /* Init programStats */
615 programStats.movelist[0] = 0;
616 programStats.depth = 0;
617 programStats.nr_moves = 0;
618 programStats.moves_left = 0;
619 programStats.nodes = 0;
620 programStats.time = -1; // [HGM] PGNtime: make invalid to recognize engine output
621 programStats.score = 0;
622 programStats.got_only_move = 0;
623 programStats.got_fail = 0;
624 programStats.line_is_book = 0;
630 int matched, min, sec;
632 ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
634 GetTimeMark(&programStartTime);
635 srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
638 programStats.ok_to_send = 1;
639 programStats.seen_stat = 0;
642 * Initialize game list
648 * Internet chess server status
650 if (appData.icsActive) {
651 appData.matchMode = FALSE;
652 appData.matchGames = 0;
654 appData.noChessProgram = !appData.zippyPlay;
656 appData.zippyPlay = FALSE;
657 appData.zippyTalk = FALSE;
658 appData.noChessProgram = TRUE;
660 if (*appData.icsHelper != NULLCHAR) {
661 appData.useTelnet = TRUE;
662 appData.telnetProgram = appData.icsHelper;
665 appData.zippyTalk = appData.zippyPlay = FALSE;
668 /* [AS] Initialize pv info list [HGM] and game state */
672 for( i=0; i<=framePtr; i++ ) {
673 pvInfoList[i].depth = -1;
674 boards[i][EP_STATUS] = EP_NONE;
675 for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
680 * Parse timeControl resource
682 if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
683 appData.movesPerSession)) {
685 snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
686 DisplayFatalError(buf, 0, 2);
690 * Parse searchTime resource
692 if (*appData.searchTime != NULLCHAR) {
693 matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
695 searchTime = min * 60;
696 } else if (matched == 2) {
697 searchTime = min * 60 + sec;
700 snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
701 DisplayFatalError(buf, 0, 2);
705 /* [AS] Adjudication threshold */
706 adjudicateLossThreshold = appData.adjudicateLossThreshold;
708 first.which = "first";
709 second.which = "second";
710 first.maybeThinking = second.maybeThinking = FALSE;
711 first.pr = second.pr = NoProc;
712 first.isr = second.isr = NULL;
713 first.sendTime = second.sendTime = 2;
714 first.sendDrawOffers = 1;
715 if (appData.firstPlaysBlack) {
716 first.twoMachinesColor = "black\n";
717 second.twoMachinesColor = "white\n";
719 first.twoMachinesColor = "white\n";
720 second.twoMachinesColor = "black\n";
722 first.program = appData.firstChessProgram;
723 second.program = appData.secondChessProgram;
724 first.host = appData.firstHost;
725 second.host = appData.secondHost;
726 first.dir = appData.firstDirectory;
727 second.dir = appData.secondDirectory;
728 first.other = &second;
729 second.other = &first;
730 first.initString = appData.initString;
731 second.initString = appData.secondInitString;
732 first.computerString = appData.firstComputerString;
733 second.computerString = appData.secondComputerString;
734 first.useSigint = second.useSigint = TRUE;
735 first.useSigterm = second.useSigterm = TRUE;
736 first.reuse = appData.reuseFirst;
737 second.reuse = appData.reuseSecond;
738 first.nps = appData.firstNPS; // [HGM] nps: copy nodes per second
739 second.nps = appData.secondNPS;
740 first.useSetboard = second.useSetboard = FALSE;
741 first.useSAN = second.useSAN = FALSE;
742 first.usePing = second.usePing = FALSE;
743 first.lastPing = second.lastPing = 0;
744 first.lastPong = second.lastPong = 0;
745 first.usePlayother = second.usePlayother = FALSE;
746 first.useColors = second.useColors = TRUE;
747 first.useUsermove = second.useUsermove = FALSE;
748 first.sendICS = second.sendICS = FALSE;
749 first.sendName = second.sendName = appData.icsActive;
750 first.sdKludge = second.sdKludge = FALSE;
751 first.stKludge = second.stKludge = FALSE;
752 TidyProgramName(first.program, first.host, first.tidy);
753 TidyProgramName(second.program, second.host, second.tidy);
754 first.matchWins = second.matchWins = 0;
755 strcpy(first.variants, appData.variant);
756 strcpy(second.variants, appData.variant);
757 first.analysisSupport = second.analysisSupport = 2; /* detect */
758 first.analyzing = second.analyzing = FALSE;
759 first.initDone = second.initDone = FALSE;
761 /* New features added by Tord: */
762 first.useFEN960 = FALSE; second.useFEN960 = FALSE;
763 first.useOOCastle = TRUE; second.useOOCastle = TRUE;
764 /* End of new features added by Tord. */
765 first.fenOverride = appData.fenOverride1;
766 second.fenOverride = appData.fenOverride2;
768 /* [HGM] time odds: set factor for each machine */
769 first.timeOdds = appData.firstTimeOdds;
770 second.timeOdds = appData.secondTimeOdds;
772 if(appData.timeOddsMode) {
773 norm = first.timeOdds;
774 if(norm > second.timeOdds) norm = second.timeOdds;
776 first.timeOdds /= norm;
777 second.timeOdds /= norm;
780 /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
781 first.accumulateTC = appData.firstAccumulateTC;
782 second.accumulateTC = appData.secondAccumulateTC;
783 first.maxNrOfSessions = second.maxNrOfSessions = 1;
786 first.debug = second.debug = FALSE;
787 first.supportsNPS = second.supportsNPS = UNKNOWN;
790 first.optionSettings = appData.firstOptions;
791 second.optionSettings = appData.secondOptions;
793 first.scoreIsAbsolute = appData.firstScoreIsAbsolute; /* [AS] */
794 second.scoreIsAbsolute = appData.secondScoreIsAbsolute; /* [AS] */
795 first.isUCI = appData.firstIsUCI; /* [AS] */
796 second.isUCI = appData.secondIsUCI; /* [AS] */
797 first.hasOwnBookUCI = appData.firstHasOwnBookUCI; /* [AS] */
798 second.hasOwnBookUCI = appData.secondHasOwnBookUCI; /* [AS] */
800 if (appData.firstProtocolVersion > PROTOVER ||
801 appData.firstProtocolVersion < 1) {
803 sprintf(buf, _("protocol version %d not supported"),
804 appData.firstProtocolVersion);
805 DisplayFatalError(buf, 0, 2);
807 first.protocolVersion = appData.firstProtocolVersion;
810 if (appData.secondProtocolVersion > PROTOVER ||
811 appData.secondProtocolVersion < 1) {
813 sprintf(buf, _("protocol version %d not supported"),
814 appData.secondProtocolVersion);
815 DisplayFatalError(buf, 0, 2);
817 second.protocolVersion = appData.secondProtocolVersion;
820 if (appData.icsActive) {
821 appData.clockMode = TRUE; /* changes dynamically in ICS mode */
822 // } else if (*appData.searchTime != NULLCHAR || appData.noChessProgram) {
823 } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
824 appData.clockMode = FALSE;
825 first.sendTime = second.sendTime = 0;
829 /* Override some settings from environment variables, for backward
830 compatibility. Unfortunately it's not feasible to have the env
831 vars just set defaults, at least in xboard. Ugh.
833 if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
838 if (appData.noChessProgram) {
839 programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
840 sprintf(programVersion, "%s", PACKAGE_STRING);
842 /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
843 programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
844 sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
847 if (!appData.icsActive) {
849 /* Check for variants that are supported only in ICS mode,
850 or not at all. Some that are accepted here nevertheless
851 have bugs; see comments below.
853 VariantClass variant = StringToVariant(appData.variant);
855 case VariantBughouse: /* need four players and two boards */
856 case VariantKriegspiel: /* need to hide pieces and move details */
857 /* case VariantFischeRandom: (Fabien: moved below) */
858 sprintf(buf, _("Variant %s supported only in ICS mode"), appData.variant);
859 DisplayFatalError(buf, 0, 2);
863 case VariantLoadable:
873 sprintf(buf, _("Unknown variant name %s"), appData.variant);
874 DisplayFatalError(buf, 0, 2);
877 case VariantXiangqi: /* [HGM] repetition rules not implemented */
878 case VariantFairy: /* [HGM] TestLegality definitely off! */
879 case VariantGothic: /* [HGM] should work */
880 case VariantCapablanca: /* [HGM] should work */
881 case VariantCourier: /* [HGM] initial forced moves not implemented */
882 case VariantShogi: /* [HGM] drops not tested for legality */
883 case VariantKnightmate: /* [HGM] should work */
884 case VariantCylinder: /* [HGM] untested */
885 case VariantFalcon: /* [HGM] untested */
886 case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
887 offboard interposition not understood */
888 case VariantNormal: /* definitely works! */
889 case VariantWildCastle: /* pieces not automatically shuffled */
890 case VariantNoCastle: /* pieces not automatically shuffled */
891 case VariantFischeRandom: /* [HGM] works and shuffles pieces */
892 case VariantLosers: /* should work except for win condition,
893 and doesn't know captures are mandatory */
894 case VariantSuicide: /* should work except for win condition,
895 and doesn't know captures are mandatory */
896 case VariantGiveaway: /* should work except for win condition,
897 and doesn't know captures are mandatory */
898 case VariantTwoKings: /* should work */
899 case VariantAtomic: /* should work except for win condition */
900 case Variant3Check: /* should work except for win condition */
901 case VariantShatranj: /* should work except for all win conditions */
902 case VariantMakruk: /* should work except for daw countdown */
903 case VariantBerolina: /* might work if TestLegality is off */
904 case VariantCapaRandom: /* should work */
905 case VariantJanus: /* should work */
906 case VariantSuper: /* experimental */
907 case VariantGreat: /* experimental, requires legality testing to be off */
912 InitEngineUCI( installDir, &first ); // [HGM] moved here from winboard.c, to make available in xboard
913 InitEngineUCI( installDir, &second );
916 int NextIntegerFromString( char ** str, long * value )
921 while( *s == ' ' || *s == '\t' ) {
927 if( *s >= '0' && *s <= '9' ) {
928 while( *s >= '0' && *s <= '9' ) {
929 *value = *value * 10 + (*s - '0');
941 int NextTimeControlFromString( char ** str, long * value )
944 int result = NextIntegerFromString( str, &temp );
947 *value = temp * 60; /* Minutes */
950 result = NextIntegerFromString( str, &temp );
951 *value += temp; /* Seconds */
958 int NextSessionFromString( char ** str, int *moves, long * tc, long *inc)
959 { /* [HGM] routine added to read '+moves/time' for secondary time control */
960 int result = -1; long temp, temp2;
962 if(**str != '+') return -1; // old params remain in force!
964 if( NextTimeControlFromString( str, &temp ) ) return -1;
967 /* time only: incremental or sudden-death time control */
968 if(**str == '+') { /* increment follows; read it */
970 if(result = NextIntegerFromString( str, &temp2)) return -1;
973 *moves = 0; *tc = temp * 1000;
975 } else if(temp % 60 != 0) return -1; /* moves was given as min:sec */
977 (*str)++; /* classical time control */
978 result = NextTimeControlFromString( str, &temp2);
987 int GetTimeQuota(int movenr)
988 { /* [HGM] get time to add from the multi-session time-control string */
989 int moves=1; /* kludge to force reading of first session */
990 long time, increment;
991 char *s = fullTimeControlString;
993 if(appData.debugMode) fprintf(debugFP, "TC string = '%s'\n", fullTimeControlString);
995 if(moves) NextSessionFromString(&s, &moves, &time, &increment);
996 if(appData.debugMode) fprintf(debugFP, "mps=%d tc=%d inc=%d\n", moves, (int) time, (int) increment);
997 if(movenr == -1) return time; /* last move before new session */
998 if(!moves) return increment; /* current session is incremental */
999 if(movenr >= 0) movenr -= moves; /* we already finished this session */
1000 } while(movenr >= -1); /* try again for next session */
1002 return 0; // no new time quota on this move
1006 ParseTimeControl(tc, ti, mps)
1015 if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1018 sprintf(buf, "+%d/%s+%d", mps, tc, ti);
1019 else sprintf(buf, "+%s+%d", tc, ti);
1022 sprintf(buf, "+%d/%s", mps, tc);
1023 else sprintf(buf, "+%s", tc);
1025 fullTimeControlString = StrSave(buf);
1027 if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1032 /* Parse second time control */
1035 if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1043 timeControl_2 = tc2 * 1000;
1053 timeControl = tc1 * 1000;
1056 timeIncrement = ti * 1000; /* convert to ms */
1057 movesPerSession = 0;
1060 movesPerSession = mps;
1068 if (appData.debugMode) {
1069 fprintf(debugFP, "%s\n", programVersion);
1072 set_cont_sequence(appData.wrapContSeq);
1073 if (appData.matchGames > 0) {
1074 appData.matchMode = TRUE;
1075 } else if (appData.matchMode) {
1076 appData.matchGames = 1;
1078 if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1079 appData.matchGames = appData.sameColorGames;
1080 if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1081 if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1082 if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1085 if (appData.noChessProgram || first.protocolVersion == 1) {
1088 /* kludge: allow timeout for initial "feature" commands */
1090 DisplayMessage("", _("Starting chess program"));
1091 ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1096 InitBackEnd3 P((void))
1098 GameMode initialMode;
1102 InitChessProgram(&first, startedFromSetupPosition);
1105 if (appData.icsActive)
1110 if (*appData.icsCommPort != NULLCHAR)
1112 sprintf(buf, _("Could not open comm port %s"),
1113 appData.icsCommPort);
1117 snprintf(buf, sizeof(buf), _("Could not connect to host %s, port %s"),
1118 appData.icsHost, appData.icsPort);
1120 DisplayFatalError(buf, err, 1);
1126 AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1128 AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1129 if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1130 ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1132 else if (appData.noChessProgram)
1141 if (*appData.cmailGameName != NULLCHAR)
1144 OpenLoopback(&cmailPR);
1146 AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1150 DisplayMessage("", "");
1151 if (StrCaseCmp(appData.initialMode, "") == 0)
1153 initialMode = BeginningOfGame;
1155 else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0)
1157 initialMode = TwoMachinesPlay;
1159 else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0)
1161 initialMode = AnalyzeFile;
1163 else if (StrCaseCmp(appData.initialMode, "Analysis") == 0)
1165 initialMode = AnalyzeMode;
1167 else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0)
1169 initialMode = MachinePlaysWhite;
1171 else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0)
1173 initialMode = MachinePlaysBlack;
1175 else if (StrCaseCmp(appData.initialMode, "EditGame") == 0)
1177 initialMode = EditGame;
1179 else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0)
1181 initialMode = EditPosition;
1183 else if (StrCaseCmp(appData.initialMode, "Training") == 0)
1185 initialMode = Training;
1189 sprintf(buf, _("Unknown initialMode %s"), appData.initialMode);
1190 DisplayFatalError(buf, 0, 2);
1194 if (appData.matchMode)
1196 /* Set up machine vs. machine match */
1197 if (appData.noChessProgram)
1199 DisplayFatalError(_("Can't have a match with no chess programs"),
1205 if (*appData.loadGameFile != NULLCHAR)
1207 int index = appData.loadGameIndex; // [HGM] autoinc
1208 if(index<0) lastIndex = index = 1;
1209 if (!LoadGameFromFile(appData.loadGameFile,
1211 appData.loadGameFile, FALSE))
1213 DisplayFatalError(_("Bad game file"), 0, 1);
1217 else if (*appData.loadPositionFile != NULLCHAR)
1219 int index = appData.loadPositionIndex; // [HGM] autoinc
1220 if(index<0) lastIndex = index = 1;
1221 if (!LoadPositionFromFile(appData.loadPositionFile,
1223 appData.loadPositionFile))
1225 DisplayFatalError(_("Bad position file"), 0, 1);
1231 else if (*appData.cmailGameName != NULLCHAR)
1233 /* Set up cmail mode */
1234 ReloadCmailMsgEvent(TRUE);
1238 /* Set up other modes */
1239 if (initialMode == AnalyzeFile)
1241 if (*appData.loadGameFile == NULLCHAR)
1243 DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1247 if (*appData.loadGameFile != NULLCHAR)
1249 (void) LoadGameFromFile(appData.loadGameFile,
1250 appData.loadGameIndex,
1251 appData.loadGameFile, TRUE);
1253 else if (*appData.loadPositionFile != NULLCHAR)
1255 (void) LoadPositionFromFile(appData.loadPositionFile,
1256 appData.loadPositionIndex,
1257 appData.loadPositionFile);
1258 /* [HGM] try to make self-starting even after FEN load */
1259 /* to allow automatic setup of fairy variants with wtm */
1260 if(initialMode == BeginningOfGame && !blackPlaysFirst)
1262 gameMode = BeginningOfGame;
1263 setboardSpoiledMachineBlack = 1;
1265 /* [HGM] loadPos: make that every new game uses the setup */
1266 /* from file as long as we do not switch variant */
1267 if(!blackPlaysFirst)
1269 startedFromPositionFile = TRUE;
1270 CopyBoard(filePosition, boards[0]);
1273 if (initialMode == AnalyzeMode)
1275 if (appData.noChessProgram)
1277 DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1280 if (appData.icsActive)
1282 DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1287 else if (initialMode == AnalyzeFile)
1289 appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1290 ShowThinkingEvent();
1292 AnalysisPeriodicEvent(1);
1294 else if (initialMode == MachinePlaysWhite)
1296 if (appData.noChessProgram)
1298 DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1302 if (appData.icsActive)
1304 DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1308 MachineWhiteEvent();
1310 else if (initialMode == MachinePlaysBlack)
1312 if (appData.noChessProgram)
1314 DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1318 if (appData.icsActive)
1320 DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1324 MachineBlackEvent();
1326 else if (initialMode == TwoMachinesPlay)
1328 if (appData.noChessProgram)
1330 DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1334 if (appData.icsActive)
1336 DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1342 else if (initialMode == EditGame)
1346 else if (initialMode == EditPosition)
1348 EditPositionEvent();
1350 else if (initialMode == Training)
1352 if (*appData.loadGameFile == NULLCHAR)
1354 DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1365 * Establish will establish a contact to a remote host.port.
1366 * Sets icsPR to a ProcRef for a process (or pseudo-process)
1367 * used to talk to the host.
1368 * Returns 0 if okay, error code if not.
1375 if (*appData.icsCommPort != NULLCHAR) {
1376 /* Talk to the host through a serial comm port */
1377 return OpenCommPort(appData.icsCommPort, &icsPR);
1379 } else if (*appData.gateway != NULLCHAR) {
1380 if (*appData.remoteShell == NULLCHAR) {
1381 /* Use the rcmd protocol to run telnet program on a gateway host */
1382 snprintf(buf, sizeof(buf), "%s %s %s",
1383 appData.telnetProgram, appData.icsHost, appData.icsPort);
1384 return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1387 /* Use the rsh program to run telnet program on a gateway host */
1388 if (*appData.remoteUser == NULLCHAR) {
1389 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1390 appData.gateway, appData.telnetProgram,
1391 appData.icsHost, appData.icsPort);
1393 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1394 appData.remoteShell, appData.gateway,
1395 appData.remoteUser, appData.telnetProgram,
1396 appData.icsHost, appData.icsPort);
1398 return StartChildProcess(buf, "", &icsPR);
1401 } else if (appData.useTelnet) {
1402 return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1405 /* TCP socket interface differs somewhat between
1406 Unix and NT; handle details in the front end.
1408 return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1413 show_bytes(fp, buf, count)
1419 if (*buf < 040 || *(unsigned char *) buf > 0177) {
1420 fprintf(fp, "\\%03o", *buf & 0xff);
1429 /* Returns an errno value */
1431 OutputMaybeTelnet(pr, message, count, outError)
1437 char buf[8192], *p, *q, *buflim;
1438 int left, newcount, outcount;
1440 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1441 *appData.gateway != NULLCHAR) {
1442 if (appData.debugMode) {
1443 fprintf(debugFP, ">ICS: ");
1444 show_bytes(debugFP, message, count);
1445 fprintf(debugFP, "\n");
1447 return OutputToProcess(pr, message, count, outError);
1450 buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1457 if (appData.debugMode) {
1458 fprintf(debugFP, ">ICS: ");
1459 show_bytes(debugFP, buf, newcount);
1460 fprintf(debugFP, "\n");
1462 outcount = OutputToProcess(pr, buf, newcount, outError);
1463 if (outcount < newcount) return -1; /* to be sure */
1470 } else if (((unsigned char) *p) == TN_IAC) {
1471 *q++ = (char) TN_IAC;
1478 if (appData.debugMode) {
1479 fprintf(debugFP, ">ICS: ");
1480 show_bytes(debugFP, buf, newcount);
1481 fprintf(debugFP, "\n");
1483 outcount = OutputToProcess(pr, buf, newcount, outError);
1484 if (outcount < newcount) return -1; /* to be sure */
1489 read_from_player(isr, closure, message, count, error)
1496 int outError, outCount;
1497 static int gotEof = 0;
1499 /* Pass data read from player on to ICS */
1502 outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1503 if (outCount < count) {
1504 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1506 } else if (count < 0) {
1507 RemoveInputSource(isr);
1508 DisplayFatalError(_("Error reading from keyboard"), error, 1);
1509 } else if (gotEof++ > 0) {
1510 RemoveInputSource(isr);
1511 DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1517 { // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1518 if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1519 connectionAlive = FALSE; // only sticks if no response to 'date' command.
1520 SendToICS("date\n");
1521 if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1524 /* added routine for printf style output to ics */
1525 void ics_printf(char *format, ...)
1527 char buffer[MSG_SIZ];
1530 va_start(args, format);
1531 vsnprintf(buffer, sizeof(buffer), format, args);
1532 buffer[sizeof(buffer)-1] = '\0';
1541 int count, outCount, outError;
1543 if (icsPR == NULL) return;
1546 outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1547 if (outCount < count) {
1548 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1552 /* This is used for sending logon scripts to the ICS. Sending
1553 without a delay causes problems when using timestamp on ICC
1554 (at least on my machine). */
1556 SendToICSDelayed(s,msdelay)
1560 int count, outCount, outError;
1562 if (icsPR == NULL) return;
1565 if (appData.debugMode) {
1566 fprintf(debugFP, ">ICS: ");
1567 show_bytes(debugFP, s, count);
1568 fprintf(debugFP, "\n");
1570 outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1572 if (outCount < count) {
1573 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1578 /* Remove all highlighting escape sequences in s
1579 Also deletes any suffix starting with '('
1582 StripHighlightAndTitle(s)
1585 static char retbuf[MSG_SIZ];
1588 while (*s != NULLCHAR) {
1589 while (*s == '\033') {
1590 while (*s != NULLCHAR && !isalpha(*s)) s++;
1591 if (*s != NULLCHAR) s++;
1593 while (*s != NULLCHAR && *s != '\033') {
1594 if (*s == '(' || *s == '[') {
1605 /* Remove all highlighting escape sequences in s */
1610 static char retbuf[MSG_SIZ];
1613 while (*s != NULLCHAR) {
1614 while (*s == '\033') {
1615 while (*s != NULLCHAR && !isalpha(*s)) s++;
1616 if (*s != NULLCHAR) s++;
1618 while (*s != NULLCHAR && *s != '\033') {
1626 char *variantNames[] = VARIANT_NAMES;
1631 return variantNames[v];
1635 /* Identify a variant from the strings the chess servers use or the
1636 PGN Variant tag names we use. */
1643 VariantClass v = VariantNormal;
1644 int i, found = FALSE;
1649 /* [HGM] skip over optional board-size prefixes */
1650 if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1651 sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1652 while( *e++ != '_');
1655 if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
1659 for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
1660 if (StrCaseStr(e, variantNames[i])) {
1661 v = (VariantClass) i;
1668 if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
1669 || StrCaseStr(e, "wild/fr")
1670 || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
1671 v = VariantFischeRandom;
1672 } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
1673 (i = 1, p = StrCaseStr(e, "w"))) {
1675 while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
1682 case 0: /* FICS only, actually */
1684 /* Castling legal even if K starts on d-file */
1685 v = VariantWildCastle;
1690 /* Castling illegal even if K & R happen to start in
1691 normal positions. */
1692 v = VariantNoCastle;
1705 /* Castling legal iff K & R start in normal positions */
1711 /* Special wilds for position setup; unclear what to do here */
1712 v = VariantLoadable;
1715 /* Bizarre ICC game */
1716 v = VariantTwoKings;
1719 v = VariantKriegspiel;
1725 v = VariantFischeRandom;
1728 v = VariantCrazyhouse;
1731 v = VariantBughouse;
1737 /* Not quite the same as FICS suicide! */
1738 v = VariantGiveaway;
1744 v = VariantShatranj;
1747 /* Temporary names for future ICC types. The name *will* change in
1748 the next xboard/WinBoard release after ICC defines it. */
1786 v = VariantCapablanca;
1789 v = VariantKnightmate;
1795 v = VariantCylinder;
1801 v = VariantCapaRandom;
1804 v = VariantBerolina;
1816 /* Found "wild" or "w" in the string but no number;
1817 must assume it's normal chess. */
1821 sprintf(buf, _("Unknown wild type %d"), wnum);
1822 DisplayError(buf, 0);
1828 if (appData.debugMode) {
1829 fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
1830 e, wnum, VariantName(v));
1835 static int leftover_start = 0, leftover_len = 0;
1836 char star_match[STAR_MATCH_N][MSG_SIZ];
1838 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
1839 advance *index beyond it, and set leftover_start to the new value of
1840 *index; else return FALSE. If pattern contains the character '*', it
1841 matches any sequence of characters not containing '\r', '\n', or the
1842 character following the '*' (if any), and the matched sequence(s) are
1843 copied into star_match.
1846 looking_at(buf, index, pattern)
1851 char *bufp = &buf[*index], *patternp = pattern;
1853 char *matchp = star_match[0];
1856 if (*patternp == NULLCHAR) {
1857 *index = leftover_start = bufp - buf;
1861 if (*bufp == NULLCHAR) return FALSE;
1862 if (*patternp == '*') {
1863 if (*bufp == *(patternp + 1)) {
1865 matchp = star_match[++star_count];
1869 } else if (*bufp == '\n' || *bufp == '\r') {
1871 if (*patternp == NULLCHAR)
1876 *matchp++ = *bufp++;
1880 if (*patternp != *bufp) return FALSE;
1887 SendToPlayer(data, length)
1891 int error, outCount;
1892 outCount = OutputToProcess(NoProc, data, length, &error);
1893 if (outCount < length) {
1894 DisplayFatalError(_("Error writing to display"), error, 1);
1899 PackHolding(packed, holding)
1911 switch (runlength) {
1922 sprintf(q, "%d", runlength);
1934 /* Telnet protocol requests from the front end */
1936 TelnetRequest(ddww, option)
1937 unsigned char ddww, option;
1939 unsigned char msg[3];
1940 int outCount, outError;
1942 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
1944 if (appData.debugMode) {
1945 char buf1[8], buf2[8], *ddwwStr, *optionStr;
1961 sprintf(buf1, "%d", ddww);
1970 sprintf(buf2, "%d", option);
1973 fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
1978 outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
1980 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1987 if (!appData.icsActive) return;
1988 TelnetRequest(TN_DO, TN_ECHO);
1994 if (!appData.icsActive) return;
1995 TelnetRequest(TN_DONT, TN_ECHO);
1999 CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece)
2001 /* put the holdings sent to us by the server on the board holdings area */
2002 int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2006 if(gameInfo.holdingsWidth < 2) return;
2007 if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2008 return; // prevent overwriting by pre-board holdings
2010 if( (int)lowestPiece >= BlackPawn ) {
2013 holdingsStartRow = BOARD_HEIGHT-1;
2016 holdingsColumn = BOARD_WIDTH-1;
2017 countsColumn = BOARD_WIDTH-2;
2018 holdingsStartRow = 0;
2022 for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
2023 board[i][holdingsColumn] = EmptySquare;
2024 board[i][countsColumn] = (ChessSquare) 0;
2026 while( (p=*holdings++) != NULLCHAR ) {
2027 piece = CharToPiece( ToUpper(p) );
2028 if(piece == EmptySquare) continue;
2029 /*j = (int) piece - (int) WhitePawn;*/
2030 j = PieceToNumber(piece);
2031 if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2032 if(j < 0) continue; /* should not happen */
2033 piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2034 board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2035 board[holdingsStartRow+j*direction][countsColumn]++;
2041 VariantSwitch(Board board, VariantClass newVariant)
2043 int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2046 startedFromPositionFile = FALSE;
2047 if(gameInfo.variant == newVariant) return;
2049 /* [HGM] This routine is called each time an assignment is made to
2050 * gameInfo.variant during a game, to make sure the board sizes
2051 * are set to match the new variant. If that means adding or deleting
2052 * holdings, we shift the playing board accordingly
2053 * This kludge is needed because in ICS observe mode, we get boards
2054 * of an ongoing game without knowing the variant, and learn about the
2055 * latter only later. This can be because of the move list we requested,
2056 * in which case the game history is refilled from the beginning anyway,
2057 * but also when receiving holdings of a crazyhouse game. In the latter
2058 * case we want to add those holdings to the already received position.
2061 if (appData.debugMode) {
2062 fprintf(debugFP, "Switch board from %s to %s\n",
2063 VariantName(gameInfo.variant), VariantName(newVariant));
2064 setbuf(debugFP, NULL);
2066 shuffleOpenings = 0; /* [HGM] shuffle */
2067 gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2071 newWidth = 9; newHeight = 9;
2072 gameInfo.holdingsSize = 7;
2073 case VariantBughouse:
2074 case VariantCrazyhouse:
2075 newHoldingsWidth = 2; break;
2079 newHoldingsWidth = 2;
2080 gameInfo.holdingsSize = 8;
2083 case VariantCapablanca:
2084 case VariantCapaRandom:
2087 newHoldingsWidth = gameInfo.holdingsSize = 0;
2090 if(newWidth != gameInfo.boardWidth ||
2091 newHeight != gameInfo.boardHeight ||
2092 newHoldingsWidth != gameInfo.holdingsWidth ) {
2094 /* shift position to new playing area, if needed */
2095 if(newHoldingsWidth > gameInfo.holdingsWidth) {
2096 for(i=0; i<BOARD_HEIGHT; i++)
2097 for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2098 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2100 for(i=0; i<newHeight; i++) {
2101 board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2102 board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2104 } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2105 for(i=0; i<BOARD_HEIGHT; i++)
2106 for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2107 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2110 gameInfo.boardWidth = newWidth;
2111 gameInfo.boardHeight = newHeight;
2112 gameInfo.holdingsWidth = newHoldingsWidth;
2113 gameInfo.variant = newVariant;
2114 InitDrawingSizes(-2, 0);
2115 } else gameInfo.variant = newVariant;
2116 CopyBoard(oldBoard, board); // remember correctly formatted board
2117 InitPosition(FALSE); /* this sets up board[0], but also other stuff */
2118 DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2121 static int loggedOn = FALSE;
2123 /*-- Game start info cache: --*/
2125 char gs_kind[MSG_SIZ];
2126 static char player1Name[128] = "";
2127 static char player2Name[128] = "";
2128 static char cont_seq[] = "\n\\ ";
2129 static int player1Rating = -1;
2130 static int player2Rating = -1;
2131 /*----------------------------*/
2133 ColorClass curColor = ColorNormal;
2134 int suppressKibitz = 0;
2137 Boolean soughtPending = FALSE;
2138 Boolean seekGraphUp;
2139 #define MAX_SEEK_ADS 200
2141 char *seekAdList[MAX_SEEK_ADS];
2142 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2143 float tcList[MAX_SEEK_ADS];
2144 char colorList[MAX_SEEK_ADS];
2145 int nrOfSeekAds = 0;
2146 int minRating = 1010, maxRating = 2800;
2147 int hMargin = 10, vMargin = 20, h, w;
2148 extern int squareSize, lineGap;
2153 int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2154 xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2155 if(r < minRating+100 && r >=0 ) r = minRating+100;
2156 if(r > maxRating) r = maxRating;
2157 if(tc < 1.) tc = 1.;
2158 if(tc > 95.) tc = 95.;
2159 x = (w-hMargin)* log(tc)/log(100.) + hMargin;
2160 y = ((double)r - minRating)/(maxRating - minRating)
2161 * (h-vMargin-squareSize/8-1) + vMargin;
2162 if(ratingList[i] < 0) y = vMargin + squareSize/4;
2163 if(strstr(seekAdList[i], " u ")) color = 1;
2164 if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2165 !strstr(seekAdList[i], "bullet") &&
2166 !strstr(seekAdList[i], "blitz") &&
2167 !strstr(seekAdList[i], "standard") ) color = 2;
2168 if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2169 DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2173 AddAd(char *handle, char *rating, int base, int inc, char rated, char *type, int nr, Boolean plot)
2175 char buf[MSG_SIZ], *ext = "";
2176 VariantClass v = StringToVariant(type);
2177 if(strstr(type, "wild")) {
2178 ext = type + 4; // append wild number
2179 if(v == VariantFischeRandom) type = "chess960"; else
2180 if(v == VariantLoadable) type = "setup"; else
2181 type = VariantName(v);
2183 sprintf(buf, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2184 if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2185 if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2186 ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2187 sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2188 tcList[nrOfSeekAds] = base + (2./3.)*inc;
2189 seekNrList[nrOfSeekAds] = nr;
2190 zList[nrOfSeekAds] = 0;
2191 seekAdList[nrOfSeekAds++] = StrSave(buf);
2192 if(plot) PlotSeekAd(nrOfSeekAds-1);
2199 int x = xList[i], y = yList[i], d=squareSize/4, k;
2200 DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2201 if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2202 // now replot every dot that overlapped
2203 for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2204 int xx = xList[k], yy = yList[k];
2205 if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2206 DrawSeekDot(xx, yy, colorList[k]);
2211 RemoveSeekAd(int nr)
2214 for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2216 if(seekAdList[i]) free(seekAdList[i]);
2217 seekAdList[i] = seekAdList[--nrOfSeekAds];
2218 seekNrList[i] = seekNrList[nrOfSeekAds];
2219 ratingList[i] = ratingList[nrOfSeekAds];
2220 colorList[i] = colorList[nrOfSeekAds];
2221 tcList[i] = tcList[nrOfSeekAds];
2222 xList[i] = xList[nrOfSeekAds];
2223 yList[i] = yList[nrOfSeekAds];
2224 zList[i] = zList[nrOfSeekAds];
2225 seekAdList[nrOfSeekAds] = NULL;
2231 MatchSoughtLine(char *line)
2233 char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2234 int nr, base, inc, u=0; char dummy;
2236 if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2237 sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2239 (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2240 sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7) ) {
2241 // match: compact and save the line
2242 AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2251 if(!seekGraphUp) return FALSE;
2253 h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
2254 w = BOARD_WIDTH * (squareSize + lineGap) + lineGap;
2256 DrawSeekBackground(0, 0, w, h);
2257 DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2258 DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2259 for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2260 int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2262 DrawSeekAxis(hMargin+5*(i%500==0), yy, hMargin-5, yy); // rating ticks
2265 sprintf(buf, "%d", i);
2266 DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2269 DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2270 for(i=1; i<100; i+=(i<10?1:5)) {
2271 int xx = (w-hMargin)* log((double)i)/log(100.) + hMargin;
2272 DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2273 if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2275 sprintf(buf, "%d", i);
2276 DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2279 for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2283 int SeekGraphClick(ClickType click, int x, int y, int moving)
2285 static int lastDown = 0, displayed = 0, lastSecond;
2286 if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2287 if(click == Release || moving) return FALSE;
2289 soughtPending = TRUE;
2290 SendToICS(ics_prefix);
2291 SendToICS("sought\n"); // should this be "sought all"?
2292 } else { // issue challenge based on clicked ad
2293 int dist = 10000; int i, closest = 0, second = 0;
2294 for(i=0; i<nrOfSeekAds; i++) {
2295 int d = (x-xList[i])*(x-xList[i]) + (y-yList[i])*(y-yList[i]) + zList[i];
2296 if(d < dist) { dist = d; closest = i; }
2297 second += (d - zList[i] < 120); // count in-range ads
2298 if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2302 second = (second > 1);
2303 if(displayed != closest || second != lastSecond) {
2304 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2305 lastSecond = second; displayed = closest;
2307 sprintf(buf, "play %d\n", seekNrList[closest]);
2308 if(click == Press) {
2309 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2312 } // on press 'hit', only show info
2313 if(moving == 2) return TRUE; // ignore right up-clicks on dot
2314 SendToICS(ics_prefix);
2315 SendToICS(buf); // should this be "sought all"?
2316 } else if(click == Release) { // release 'miss' is ignored
2317 zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2318 if(moving == 2) { // right up-click
2319 nrOfSeekAds = 0; // refresh graph
2320 soughtPending = TRUE;
2321 SendToICS(ics_prefix);
2322 SendToICS("sought\n"); // should this be "sought all"?
2325 } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2326 // press miss or release hit 'pop down' seek graph
2327 seekGraphUp = FALSE;
2328 DrawPosition(TRUE, NULL);
2334 read_from_ics(isr, closure, data, count, error)
2341 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2342 #define STARTED_NONE 0
2343 #define STARTED_MOVES 1
2344 #define STARTED_BOARD 2
2345 #define STARTED_OBSERVE 3
2346 #define STARTED_HOLDINGS 4
2347 #define STARTED_CHATTER 5
2348 #define STARTED_COMMENT 6
2349 #define STARTED_MOVES_NOHIDE 7
2351 static int started = STARTED_NONE;
2352 static char parse[20000];
2353 static int parse_pos = 0;
2354 static char buf[BUF_SIZE + 1];
2355 static int firstTime = TRUE, intfSet = FALSE;
2356 static ColorClass prevColor = ColorNormal;
2357 static int savingComment = FALSE;
2358 static int cmatch = 0; // continuation sequence match
2365 int backup; /* [DM] For zippy color lines */
2367 char talker[MSG_SIZ]; // [HGM] chat
2370 connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2372 if (appData.debugMode) {
2374 fprintf(debugFP, "<ICS: ");
2375 show_bytes(debugFP, data, count);
2376 fprintf(debugFP, "\n");
2380 if (appData.debugMode) { int f = forwardMostMove;
2381 fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2382 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2383 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2386 /* If last read ended with a partial line that we couldn't parse,
2387 prepend it to the new read and try again. */
2388 if (leftover_len > 0) {
2389 for (i=0; i<leftover_len; i++)
2390 buf[i] = buf[leftover_start + i];
2393 /* copy new characters into the buffer */
2394 bp = buf + leftover_len;
2395 buf_len=leftover_len;
2396 for (i=0; i<count; i++)
2399 if (data[i] == '\r')
2402 // join lines split by ICS?
2403 if (!appData.noJoin)
2406 Joining just consists of finding matches against the
2407 continuation sequence, and discarding that sequence
2408 if found instead of copying it. So, until a match
2409 fails, there's nothing to do since it might be the
2410 complete sequence, and thus, something we don't want
2413 if (data[i] == cont_seq[cmatch])
2416 if (cmatch == strlen(cont_seq))
2418 cmatch = 0; // complete match. just reset the counter
2421 it's possible for the ICS to not include the space
2422 at the end of the last word, making our [correct]
2423 join operation fuse two separate words. the server
2424 does this when the space occurs at the width setting.
2426 if (!buf_len || buf[buf_len-1] != ' ')
2437 match failed, so we have to copy what matched before
2438 falling through and copying this character. In reality,
2439 this will only ever be just the newline character, but
2440 it doesn't hurt to be precise.
2442 strncpy(bp, cont_seq, cmatch);
2454 buf[buf_len] = NULLCHAR;
2455 // next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2460 while (i < buf_len) {
2461 /* Deal with part of the TELNET option negotiation
2462 protocol. We refuse to do anything beyond the
2463 defaults, except that we allow the WILL ECHO option,
2464 which ICS uses to turn off password echoing when we are
2465 directly connected to it. We reject this option
2466 if localLineEditing mode is on (always on in xboard)
2467 and we are talking to port 23, which might be a real
2468 telnet server that will try to keep WILL ECHO on permanently.
2470 if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2471 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2472 unsigned char option;
2474 switch ((unsigned char) buf[++i]) {
2476 if (appData.debugMode)
2477 fprintf(debugFP, "\n<WILL ");
2478 switch (option = (unsigned char) buf[++i]) {
2480 if (appData.debugMode)
2481 fprintf(debugFP, "ECHO ");
2482 /* Reply only if this is a change, according
2483 to the protocol rules. */
2484 if (remoteEchoOption) break;
2485 if (appData.localLineEditing &&
2486 atoi(appData.icsPort) == TN_PORT) {
2487 TelnetRequest(TN_DONT, TN_ECHO);
2490 TelnetRequest(TN_DO, TN_ECHO);
2491 remoteEchoOption = TRUE;
2495 if (appData.debugMode)
2496 fprintf(debugFP, "%d ", option);
2497 /* Whatever this is, we don't want it. */
2498 TelnetRequest(TN_DONT, option);
2503 if (appData.debugMode)
2504 fprintf(debugFP, "\n<WONT ");
2505 switch (option = (unsigned char) buf[++i]) {
2507 if (appData.debugMode)
2508 fprintf(debugFP, "ECHO ");
2509 /* Reply only if this is a change, according
2510 to the protocol rules. */
2511 if (!remoteEchoOption) break;
2513 TelnetRequest(TN_DONT, TN_ECHO);
2514 remoteEchoOption = FALSE;
2517 if (appData.debugMode)
2518 fprintf(debugFP, "%d ", (unsigned char) option);
2519 /* Whatever this is, it must already be turned
2520 off, because we never agree to turn on
2521 anything non-default, so according to the
2522 protocol rules, we don't reply. */
2527 if (appData.debugMode)
2528 fprintf(debugFP, "\n<DO ");
2529 switch (option = (unsigned char) buf[++i]) {
2531 /* Whatever this is, we refuse to do it. */
2532 if (appData.debugMode)
2533 fprintf(debugFP, "%d ", option);
2534 TelnetRequest(TN_WONT, option);
2539 if (appData.debugMode)
2540 fprintf(debugFP, "\n<DONT ");
2541 switch (option = (unsigned char) buf[++i]) {
2543 if (appData.debugMode)
2544 fprintf(debugFP, "%d ", option);
2545 /* Whatever this is, we are already not doing
2546 it, because we never agree to do anything
2547 non-default, so according to the protocol
2548 rules, we don't reply. */
2553 if (appData.debugMode)
2554 fprintf(debugFP, "\n<IAC ");
2555 /* Doubled IAC; pass it through */
2559 if (appData.debugMode)
2560 fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2561 /* Drop all other telnet commands on the floor */
2564 if (oldi > next_out)
2565 SendToPlayer(&buf[next_out], oldi - next_out);
2571 /* OK, this at least will *usually* work */
2572 if (!loggedOn && looking_at(buf, &i, "ics%")) {
2576 if (loggedOn && !intfSet) {
2577 if (ics_type == ICS_ICC) {
2579 "/set-quietly interface %s\n/set-quietly style 12\n",
2581 if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2582 strcat(str, "/set-2 51 1\n/set seek 1\n");
2583 } else if (ics_type == ICS_CHESSNET) {
2584 sprintf(str, "/style 12\n");
2586 strcpy(str, "alias $ @\n$set interface ");
2587 strcat(str, programVersion);
2588 strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2589 if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2590 strcat(str, "$iset seekremove 1\n$set seek 1\n");
2592 strcat(str, "$iset nohighlight 1\n");
2594 strcat(str, "$iset lock 1\n$style 12\n");
2597 NotifyFrontendLogin();
2601 if (started == STARTED_COMMENT) {
2602 /* Accumulate characters in comment */
2603 parse[parse_pos++] = buf[i];
2604 if (buf[i] == '\n') {
2605 parse[parse_pos] = NULLCHAR;
2606 if(chattingPartner>=0) {
2608 sprintf(mess, "%s%s", talker, parse);
2609 OutputChatMessage(chattingPartner, mess);
2610 chattingPartner = -1;
2612 if(!suppressKibitz) // [HGM] kibitz
2613 AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
2614 else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2615 int nrDigit = 0, nrAlph = 0, j;
2616 if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2617 { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2618 parse[parse_pos] = NULLCHAR;
2619 // try to be smart: if it does not look like search info, it should go to
2620 // ICS interaction window after all, not to engine-output window.
2621 for(j=0; j<parse_pos; j++) { // count letters and digits
2622 nrDigit += (parse[j] >= '0' && parse[j] <= '9');
2623 nrAlph += (parse[j] >= 'a' && parse[j] <= 'z');
2624 nrAlph += (parse[j] >= 'A' && parse[j] <= 'Z');
2626 if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2627 int depth=0; float score;
2628 if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2629 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2630 pvInfoList[forwardMostMove-1].depth = depth;
2631 pvInfoList[forwardMostMove-1].score = 100*score;
2633 OutputKibitz(suppressKibitz, parse);
2634 next_out = i+1; // [HGM] suppress printing in ICS window
2637 sprintf(tmp, _("your opponent kibitzes: %s"), parse);
2638 SendToPlayer(tmp, strlen(tmp));
2641 started = STARTED_NONE;
2643 /* Don't match patterns against characters in comment */
2648 if (started == STARTED_CHATTER) {
2649 if (buf[i] != '\n') {
2650 /* Don't match patterns against characters in chatter */
2654 started = STARTED_NONE;
2657 /* Kludge to deal with rcmd protocol */
2658 if (firstTime && looking_at(buf, &i, "\001*")) {
2659 DisplayFatalError(&buf[1], 0, 1);
2665 if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2668 if (appData.debugMode)
2669 fprintf(debugFP, "ics_type %d\n", ics_type);
2672 if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
2673 ics_type = ICS_FICS;
2675 if (appData.debugMode)
2676 fprintf(debugFP, "ics_type %d\n", ics_type);
2679 if (!loggedOn && looking_at(buf, &i, "chess.net")) {
2680 ics_type = ICS_CHESSNET;
2682 if (appData.debugMode)
2683 fprintf(debugFP, "ics_type %d\n", ics_type);
2688 (looking_at(buf, &i, "\"*\" is *a registered name") ||
2689 looking_at(buf, &i, "Logging you in as \"*\"") ||
2690 looking_at(buf, &i, "will be \"*\""))) {
2691 strcpy(ics_handle, star_match[0]);
2695 if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
2697 snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
2698 DisplayIcsInteractionTitle(buf);
2699 have_set_title = TRUE;
2702 /* skip finger notes */
2703 if (started == STARTED_NONE &&
2704 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
2705 (buf[i] == '1' && buf[i+1] == '0')) &&
2706 buf[i+2] == ':' && buf[i+3] == ' ') {
2707 started = STARTED_CHATTER;
2712 // [HGM] seekgraph: recognize sought lines and end-of-sought message
2713 if(appData.seekGraph) {
2714 if(soughtPending && MatchSoughtLine(buf+i)) {
2715 i = strstr(buf+i, "rated") - buf;
2716 next_out = leftover_start = i;
2717 started = STARTED_CHATTER;
2718 suppressKibitz = TRUE;
2721 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
2722 && looking_at(buf, &i, "* ads displayed")) {
2723 soughtPending = FALSE;
2728 if(appData.autoRefresh) {
2729 if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
2730 int s = (ics_type == ICS_ICC); // ICC format differs
2732 AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
2733 star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
2734 looking_at(buf, &i, "*% "); // eat prompt
2735 next_out = i; // suppress
2738 if(looking_at(buf, &i, "Ads removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
2739 char *p = star_match[0];
2741 if(seekGraphUp) RemoveSeekAd(atoi(p));
2742 while(*p && *p++ != ' '); // next
2744 looking_at(buf, &i, "*% "); // eat prompt
2751 /* skip formula vars */
2752 if (started == STARTED_NONE &&
2753 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
2754 started = STARTED_CHATTER;
2760 // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
2761 if (appData.autoKibitz && started == STARTED_NONE &&
2762 !appData.icsEngineAnalyze && // [HGM] [DM] ICS analyze
2763 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
2764 if(looking_at(buf, &i, "* kibitzes: ") &&
2765 (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
2766 StrStr(star_match[0], gameInfo.black) == star_match[0] )) { // kibitz of self or opponent
2767 suppressKibitz = TRUE;
2768 if((StrStr(star_match[0], gameInfo.white) == star_match[0]
2769 && (gameMode == IcsPlayingWhite)) ||
2770 (StrStr(star_match[0], gameInfo.black) == star_match[0]
2771 && (gameMode == IcsPlayingBlack)) ) // opponent kibitz
2772 started = STARTED_CHATTER; // own kibitz we simply discard
2774 started = STARTED_COMMENT; // make sure it will be collected in parse[]
2775 parse_pos = 0; parse[0] = NULLCHAR;
2776 savingComment = TRUE;
2777 suppressKibitz = gameMode != IcsObserving ? 2 :
2778 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
2782 if(looking_at(buf, &i, "kibitzed to *\n") && atoi(star_match[0])) {
2783 // suppress the acknowledgements of our own autoKibitz
2785 if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
2786 SendToPlayer(star_match[0], strlen(star_match[0]));
2787 looking_at(buf, &i, "*% "); // eat prompt
2790 } // [HGM] kibitz: end of patch
2792 //if(appData.debugMode) fprintf(debugFP, "hunt for tell, buf = %s\n", buf+i);
2794 // [HGM] chat: intercept tells by users for which we have an open chat window
2796 if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
2797 looking_at(buf, &i, "* whispers:") ||
2798 looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
2799 looking_at(buf, &i, "*(*)(*):") && sscanf(star_match[2], "%d", &channel) == 1 )) {
2801 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
2802 chattingPartner = -1;
2804 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
2805 for(p=0; p<MAX_CHAT; p++) {
2806 if(channel == atoi(chatPartner[p])) {
2807 talker[0] = '['; strcat(talker, "] ");
2808 chattingPartner = p; break;
2811 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
2812 for(p=0; p<MAX_CHAT; p++) {
2813 if(!strcmp("WHISPER", chatPartner[p])) {
2814 talker[0] = '['; strcat(talker, "] ");
2815 chattingPartner = p; break;
2818 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
2819 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
2821 chattingPartner = p; break;
2823 if(chattingPartner<0) i = oldi; else {
2824 started = STARTED_COMMENT;
2825 parse_pos = 0; parse[0] = NULLCHAR;
2826 savingComment = 3 + chattingPartner; // counts as TRUE
2827 suppressKibitz = TRUE;
2829 } // [HGM] chat: end of patch
2831 if (appData.zippyTalk || appData.zippyPlay) {
2832 /* [DM] Backup address for color zippy lines */
2836 if (loggedOn == TRUE)
2837 if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
2838 (appData.zippyPlay && ZippyMatch(buf, &backup)));
2840 if (ZippyControl(buf, &i) ||
2841 ZippyConverse(buf, &i) ||
2842 (appData.zippyPlay && ZippyMatch(buf, &i))) {
2844 if (!appData.colorize) continue;
2848 } // [DM] 'else { ' deleted
2850 /* Regular tells and says */
2851 (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
2852 looking_at(buf, &i, "* (your partner) tells you: ") ||
2853 looking_at(buf, &i, "* says: ") ||
2854 /* Don't color "message" or "messages" output */
2855 (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
2856 looking_at(buf, &i, "*. * at *:*: ") ||
2857 looking_at(buf, &i, "--* (*:*): ") ||
2858 /* Message notifications (same color as tells) */
2859 looking_at(buf, &i, "* has left a message ") ||
2860 looking_at(buf, &i, "* just sent you a message:\n") ||
2861 /* Whispers and kibitzes */
2862 (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
2863 looking_at(buf, &i, "* kibitzes: ") ||
2865 (tkind = 3, looking_at(buf, &i, "*(*: "))) {
2867 if (tkind == 1 && strchr(star_match[0], ':')) {
2868 /* Avoid "tells you:" spoofs in channels */
2871 if (star_match[0][0] == NULLCHAR ||
2872 strchr(star_match[0], ' ') ||
2873 (tkind == 3 && strchr(star_match[1], ' '))) {
2874 /* Reject bogus matches */
2877 if (appData.colorize) {
2878 if (oldi > next_out) {
2879 SendToPlayer(&buf[next_out], oldi - next_out);
2884 Colorize(ColorTell, FALSE);
2885 curColor = ColorTell;
2888 Colorize(ColorKibitz, FALSE);
2889 curColor = ColorKibitz;
2892 p = strrchr(star_match[1], '(');
2899 Colorize(ColorChannel1, FALSE);
2900 curColor = ColorChannel1;
2902 Colorize(ColorChannel, FALSE);
2903 curColor = ColorChannel;
2907 curColor = ColorNormal;
2911 if (started == STARTED_NONE && appData.autoComment &&
2912 (gameMode == IcsObserving ||
2913 gameMode == IcsPlayingWhite ||
2914 gameMode == IcsPlayingBlack)) {
2915 parse_pos = i - oldi;
2916 memcpy(parse, &buf[oldi], parse_pos);
2917 parse[parse_pos] = NULLCHAR;
2918 started = STARTED_COMMENT;
2919 savingComment = TRUE;
2921 started = STARTED_CHATTER;
2922 savingComment = FALSE;
2929 if (looking_at(buf, &i, "* s-shouts: ") ||
2930 looking_at(buf, &i, "* c-shouts: ")) {
2931 if (appData.colorize) {
2932 if (oldi > next_out) {
2933 SendToPlayer(&buf[next_out], oldi - next_out);
2936 Colorize(ColorSShout, FALSE);
2937 curColor = ColorSShout;
2940 started = STARTED_CHATTER;
2944 if (looking_at(buf, &i, "--->")) {
2949 if (looking_at(buf, &i, "* shouts: ") ||
2950 looking_at(buf, &i, "--> ")) {
2951 if (appData.colorize) {
2952 if (oldi > next_out) {
2953 SendToPlayer(&buf[next_out], oldi - next_out);
2956 Colorize(ColorShout, FALSE);
2957 curColor = ColorShout;
2960 started = STARTED_CHATTER;
2964 if (looking_at( buf, &i, "Challenge:")) {
2965 if (appData.colorize) {
2966 if (oldi > next_out) {
2967 SendToPlayer(&buf[next_out], oldi - next_out);
2970 Colorize(ColorChallenge, FALSE);
2971 curColor = ColorChallenge;
2977 if (looking_at(buf, &i, "* offers you") ||
2978 looking_at(buf, &i, "* offers to be") ||
2979 looking_at(buf, &i, "* would like to") ||
2980 looking_at(buf, &i, "* requests to") ||
2981 looking_at(buf, &i, "Your opponent offers") ||
2982 looking_at(buf, &i, "Your opponent requests")) {
2984 if (appData.colorize) {
2985 if (oldi > next_out) {
2986 SendToPlayer(&buf[next_out], oldi - next_out);
2989 Colorize(ColorRequest, FALSE);
2990 curColor = ColorRequest;
2995 if (looking_at(buf, &i, "* (*) seeking")) {
2996 if (appData.colorize) {
2997 if (oldi > next_out) {
2998 SendToPlayer(&buf[next_out], oldi - next_out);
3001 Colorize(ColorSeek, FALSE);
3002 curColor = ColorSeek;
3007 if (looking_at(buf, &i, "\\ ")) {
3008 if (prevColor != ColorNormal) {
3009 if (oldi > next_out) {
3010 SendToPlayer(&buf[next_out], oldi - next_out);
3013 Colorize(prevColor, TRUE);
3014 curColor = prevColor;
3016 if (savingComment) {
3017 parse_pos = i - oldi;
3018 memcpy(parse, &buf[oldi], parse_pos);
3019 parse[parse_pos] = NULLCHAR;
3020 started = STARTED_COMMENT;
3021 if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3022 chattingPartner = savingComment - 3; // kludge to remember the box
3024 started = STARTED_CHATTER;
3029 if (looking_at(buf, &i, "Black Strength :") ||
3030 looking_at(buf, &i, "<<< style 10 board >>>") ||
3031 looking_at(buf, &i, "<10>") ||
3032 looking_at(buf, &i, "#@#")) {
3033 /* Wrong board style */
3035 SendToICS(ics_prefix);
3036 SendToICS("set style 12\n");
3037 SendToICS(ics_prefix);
3038 SendToICS("refresh\n");
3042 if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
3044 have_sent_ICS_logon = 1;
3048 if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3049 (looking_at(buf, &i, "\n<12> ") ||
3050 looking_at(buf, &i, "<12> "))) {
3052 if (oldi > next_out) {
3053 SendToPlayer(&buf[next_out], oldi - next_out);
3056 started = STARTED_BOARD;
3061 if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3062 looking_at(buf, &i, "<b1> ")) {
3063 if (oldi > next_out) {
3064 SendToPlayer(&buf[next_out], oldi - next_out);
3067 started = STARTED_HOLDINGS;
3072 if (looking_at(buf, &i, "* *vs. * *--- *")) {
3074 /* Header for a move list -- first line */
3076 switch (ics_getting_history) {
3080 case BeginningOfGame:
3081 /* User typed "moves" or "oldmoves" while we
3082 were idle. Pretend we asked for these
3083 moves and soak them up so user can step
3084 through them and/or save them.
3087 gameMode = IcsObserving;
3090 ics_getting_history = H_GOT_UNREQ_HEADER;
3092 case EditGame: /*?*/
3093 case EditPosition: /*?*/
3094 /* Should above feature work in these modes too? */
3095 /* For now it doesn't */
3096 ics_getting_history = H_GOT_UNWANTED_HEADER;
3099 ics_getting_history = H_GOT_UNWANTED_HEADER;
3104 /* Is this the right one? */
3105 if (gameInfo.white && gameInfo.black &&
3106 strcmp(gameInfo.white, star_match[0]) == 0 &&
3107 strcmp(gameInfo.black, star_match[2]) == 0) {
3109 ics_getting_history = H_GOT_REQ_HEADER;
3112 case H_GOT_REQ_HEADER:
3113 case H_GOT_UNREQ_HEADER:
3114 case H_GOT_UNWANTED_HEADER:
3115 case H_GETTING_MOVES:
3116 /* Should not happen */
3117 DisplayError(_("Error gathering move list: two headers"), 0);
3118 ics_getting_history = H_FALSE;
3122 /* Save player ratings into gameInfo if needed */
3123 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3124 ics_getting_history == H_GOT_UNREQ_HEADER) &&
3125 (gameInfo.whiteRating == -1 ||
3126 gameInfo.blackRating == -1)) {
3128 gameInfo.whiteRating = string_to_rating(star_match[1]);
3129 gameInfo.blackRating = string_to_rating(star_match[3]);
3130 if (appData.debugMode)
3131 fprintf(debugFP, _("Ratings from header: W %d, B %d\n"),
3132 gameInfo.whiteRating, gameInfo.blackRating);
3137 if (looking_at(buf, &i,
3138 "* * match, initial time: * minute*, increment: * second")) {
3139 /* Header for a move list -- second line */
3140 /* Initial board will follow if this is a wild game */
3141 if (gameInfo.event != NULL) free(gameInfo.event);
3142 sprintf(str, "ICS %s %s match", star_match[0], star_match[1]);
3143 gameInfo.event = StrSave(str);
3144 /* [HGM] we switched variant. Translate boards if needed. */
3145 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3149 if (looking_at(buf, &i, "Move ")) {
3150 /* Beginning of a move list */
3151 switch (ics_getting_history) {
3153 /* Normally should not happen */
3154 /* Maybe user hit reset while we were parsing */
3157 /* Happens if we are ignoring a move list that is not
3158 * the one we just requested. Common if the user
3159 * tries to observe two games without turning off
3162 case H_GETTING_MOVES:
3163 /* Should not happen */
3164 DisplayError(_("Error gathering move list: nested"), 0);
3165 ics_getting_history = H_FALSE;
3167 case H_GOT_REQ_HEADER:
3168 ics_getting_history = H_GETTING_MOVES;
3169 started = STARTED_MOVES;
3171 if (oldi > next_out) {
3172 SendToPlayer(&buf[next_out], oldi - next_out);
3175 case H_GOT_UNREQ_HEADER:
3176 ics_getting_history = H_GETTING_MOVES;
3177 started = STARTED_MOVES_NOHIDE;
3180 case H_GOT_UNWANTED_HEADER:
3181 ics_getting_history = H_FALSE;
3187 if (looking_at(buf, &i, "% ") ||
3188 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3189 && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3190 if(ics_type == ICS_ICC && soughtPending) { // [HGM] seekgraph: on ICC sought-list has no termination line
3191 soughtPending = FALSE;
3195 if(suppressKibitz) next_out = i;
3196 savingComment = FALSE;
3200 case STARTED_MOVES_NOHIDE:
3201 memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3202 parse[parse_pos + i - oldi] = NULLCHAR;
3203 ParseGameHistory(parse);
3205 if (appData.zippyPlay && first.initDone) {
3206 FeedMovesToProgram(&first, forwardMostMove);
3207 if (gameMode == IcsPlayingWhite) {
3208 if (WhiteOnMove(forwardMostMove)) {
3209 if (first.sendTime) {
3210 if (first.useColors) {
3211 SendToProgram("black\n", &first);
3213 SendTimeRemaining(&first, TRUE);
3215 if (first.useColors) {
3216 SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3218 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3219 first.maybeThinking = TRUE;
3221 if (first.usePlayother) {
3222 if (first.sendTime) {
3223 SendTimeRemaining(&first, TRUE);
3225 SendToProgram("playother\n", &first);
3231 } else if (gameMode == IcsPlayingBlack) {
3232 if (!WhiteOnMove(forwardMostMove)) {
3233 if (first.sendTime) {
3234 if (first.useColors) {
3235 SendToProgram("white\n", &first);
3237 SendTimeRemaining(&first, FALSE);
3239 if (first.useColors) {
3240 SendToProgram("black\n", &first);
3242 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3243 first.maybeThinking = TRUE;
3245 if (first.usePlayother) {
3246 if (first.sendTime) {
3247 SendTimeRemaining(&first, FALSE);
3249 SendToProgram("playother\n", &first);
3258 if (gameMode == IcsObserving && ics_gamenum == -1) {
3259 /* Moves came from oldmoves or moves command
3260 while we weren't doing anything else.
3262 currentMove = forwardMostMove;
3263 ClearHighlights();/*!!could figure this out*/
3264 flipView = appData.flipView;
3265 DrawPosition(TRUE, boards[currentMove]);
3266 DisplayBothClocks();
3267 sprintf(str, "%s vs. %s",
3268 gameInfo.white, gameInfo.black);
3272 /* Moves were history of an active game */
3273 if (gameInfo.resultDetails != NULL) {
3274 free(gameInfo.resultDetails);
3275 gameInfo.resultDetails = NULL;
3278 HistorySet(parseList, backwardMostMove,
3279 forwardMostMove, currentMove-1);
3280 DisplayMove(currentMove - 1);
3281 if (started == STARTED_MOVES) next_out = i;
3282 started = STARTED_NONE;
3283 ics_getting_history = H_FALSE;
3286 case STARTED_OBSERVE:
3287 started = STARTED_NONE;
3288 SendToICS(ics_prefix);
3289 SendToICS("refresh\n");
3295 if(bookHit) { // [HGM] book: simulate book reply
3296 static char bookMove[MSG_SIZ]; // a bit generous?
3298 programStats.nodes = programStats.depth = programStats.time =
3299 programStats.score = programStats.got_only_move = 0;
3300 sprintf(programStats.movelist, "%s (xbook)", bookHit);
3302 strcpy(bookMove, "move ");
3303 strcat(bookMove, bookHit);
3304 HandleMachineMove(bookMove, &first);
3309 if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3310 started == STARTED_HOLDINGS ||
3311 started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3312 /* Accumulate characters in move list or board */
3313 parse[parse_pos++] = buf[i];
3316 /* Start of game messages. Mostly we detect start of game
3317 when the first board image arrives. On some versions
3318 of the ICS, though, we need to do a "refresh" after starting
3319 to observe in order to get the current board right away. */
3320 if (looking_at(buf, &i, "Adding game * to observation list")) {
3321 started = STARTED_OBSERVE;
3325 /* Handle auto-observe */
3326 if (appData.autoObserve &&
3327 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3328 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3330 /* Choose the player that was highlighted, if any. */
3331 if (star_match[0][0] == '\033' ||
3332 star_match[1][0] != '\033') {
3333 player = star_match[0];
3335 player = star_match[2];
3337 sprintf(str, "%sobserve %s\n",
3338 ics_prefix, StripHighlightAndTitle(player));
3341 /* Save ratings from notify string */
3342 strcpy(player1Name, star_match[0]);
3343 player1Rating = string_to_rating(star_match[1]);
3344 strcpy(player2Name, star_match[2]);
3345 player2Rating = string_to_rating(star_match[3]);
3347 if (appData.debugMode)
3349 "Ratings from 'Game notification:' %s %d, %s %d\n",
3350 player1Name, player1Rating,
3351 player2Name, player2Rating);
3356 /* Deal with automatic examine mode after a game,
3357 and with IcsObserving -> IcsExamining transition */
3358 if (looking_at(buf, &i, "Entering examine mode for game *") ||
3359 looking_at(buf, &i, "has made you an examiner of game *")) {
3361 int gamenum = atoi(star_match[0]);
3362 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3363 gamenum == ics_gamenum) {
3364 /* We were already playing or observing this game;
3365 no need to refetch history */
3366 gameMode = IcsExamining;
3368 pauseExamForwardMostMove = forwardMostMove;
3369 } else if (currentMove < forwardMostMove) {
3370 ForwardInner(forwardMostMove);
3373 /* I don't think this case really can happen */
3374 SendToICS(ics_prefix);
3375 SendToICS("refresh\n");
3380 /* Error messages */
3381 // if (ics_user_moved) {
3382 if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3383 if (looking_at(buf, &i, "Illegal move") ||
3384 looking_at(buf, &i, "Not a legal move") ||
3385 looking_at(buf, &i, "Your king is in check") ||
3386 looking_at(buf, &i, "It isn't your turn") ||
3387 looking_at(buf, &i, "It is not your move")) {
3389 if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3390 currentMove = --forwardMostMove;
3391 DisplayMove(currentMove - 1); /* before DMError */
3392 DrawPosition(FALSE, boards[currentMove]);
3394 DisplayBothClocks();
3396 DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3402 if (looking_at(buf, &i, "still have time") ||
3403 looking_at(buf, &i, "not out of time") ||
3404 looking_at(buf, &i, "either player is out of time") ||
3405 looking_at(buf, &i, "has timeseal; checking")) {
3406 /* We must have called his flag a little too soon */
3407 whiteFlag = blackFlag = FALSE;
3411 if (looking_at(buf, &i, "added * seconds to") ||
3412 looking_at(buf, &i, "seconds were added to")) {
3413 /* Update the clocks */
3414 SendToICS(ics_prefix);
3415 SendToICS("refresh\n");
3419 if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3420 ics_clock_paused = TRUE;
3425 if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3426 ics_clock_paused = FALSE;
3431 /* Grab player ratings from the Creating: message.
3432 Note we have to check for the special case when
3433 the ICS inserts things like [white] or [black]. */
3434 if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3435 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3437 0 player 1 name (not necessarily white)
3439 2 empty, white, or black (IGNORED)
3440 3 player 2 name (not necessarily black)
3443 The names/ratings are sorted out when the game
3444 actually starts (below).
3446 strcpy(player1Name, StripHighlightAndTitle(star_match[0]));
3447 player1Rating = string_to_rating(star_match[1]);
3448 strcpy(player2Name, StripHighlightAndTitle(star_match[3]));
3449 player2Rating = string_to_rating(star_match[4]);
3451 if (appData.debugMode)
3453 "Ratings from 'Creating:' %s %d, %s %d\n",
3454 player1Name, player1Rating,
3455 player2Name, player2Rating);
3460 /* Improved generic start/end-of-game messages */
3461 if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3462 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3463 /* If tkind == 0: */
3464 /* star_match[0] is the game number */
3465 /* [1] is the white player's name */
3466 /* [2] is the black player's name */
3467 /* For end-of-game: */
3468 /* [3] is the reason for the game end */
3469 /* [4] is a PGN end game-token, preceded by " " */
3470 /* For start-of-game: */
3471 /* [3] begins with "Creating" or "Continuing" */
3472 /* [4] is " *" or empty (don't care). */
3473 int gamenum = atoi(star_match[0]);
3474 char *whitename, *blackname, *why, *endtoken;
3475 ChessMove endtype = (ChessMove) 0;
3478 whitename = star_match[1];
3479 blackname = star_match[2];
3480 why = star_match[3];
3481 endtoken = star_match[4];
3483 whitename = star_match[1];
3484 blackname = star_match[3];
3485 why = star_match[5];
3486 endtoken = star_match[6];
3489 /* Game start messages */
3490 if (strncmp(why, "Creating ", 9) == 0 ||
3491 strncmp(why, "Continuing ", 11) == 0) {
3492 gs_gamenum = gamenum;
3493 strcpy(gs_kind, strchr(why, ' ') + 1);
3494 VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
3496 if (appData.zippyPlay) {
3497 ZippyGameStart(whitename, blackname);
3503 /* Game end messages */
3504 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3505 ics_gamenum != gamenum) {
3508 while (endtoken[0] == ' ') endtoken++;
3509 switch (endtoken[0]) {
3512 endtype = GameUnfinished;
3515 endtype = BlackWins;
3518 if (endtoken[1] == '/')
3519 endtype = GameIsDrawn;
3521 endtype = WhiteWins;
3524 GameEnds(endtype, why, GE_ICS);
3526 if (appData.zippyPlay && first.initDone) {
3527 ZippyGameEnd(endtype, why);
3528 if (first.pr == NULL) {
3529 /* Start the next process early so that we'll
3530 be ready for the next challenge */
3531 StartChessProgram(&first);
3533 /* Send "new" early, in case this command takes
3534 a long time to finish, so that we'll be ready
3535 for the next challenge. */
3536 gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3543 if (looking_at(buf, &i, "Removing game * from observation") ||
3544 looking_at(buf, &i, "no longer observing game *") ||
3545 looking_at(buf, &i, "Game * (*) has no examiners")) {
3546 if (gameMode == IcsObserving &&
3547 atoi(star_match[0]) == ics_gamenum)
3549 /* icsEngineAnalyze */
3550 if (appData.icsEngineAnalyze) {
3557 ics_user_moved = FALSE;
3562 if (looking_at(buf, &i, "no longer examining game *")) {
3563 if (gameMode == IcsExamining &&
3564 atoi(star_match[0]) == ics_gamenum)
3568 ics_user_moved = FALSE;
3573 /* Advance leftover_start past any newlines we find,
3574 so only partial lines can get reparsed */
3575 if (looking_at(buf, &i, "\n")) {
3576 prevColor = curColor;
3577 if (curColor != ColorNormal) {
3578 if (oldi > next_out) {
3579 SendToPlayer(&buf[next_out], oldi - next_out);
3582 Colorize(ColorNormal, FALSE);
3583 curColor = ColorNormal;
3585 if (started == STARTED_BOARD) {
3586 started = STARTED_NONE;
3587 parse[parse_pos] = NULLCHAR;
3588 ParseBoard12(parse);
3591 /* Send premove here */
3592 if (appData.premove) {
3594 if (currentMove == 0 &&
3595 gameMode == IcsPlayingWhite &&
3596 appData.premoveWhite) {
3597 sprintf(str, "%s\n", appData.premoveWhiteText);
3598 if (appData.debugMode)
3599 fprintf(debugFP, "Sending premove:\n");
3601 } else if (currentMove == 1 &&
3602 gameMode == IcsPlayingBlack &&
3603 appData.premoveBlack) {
3604 sprintf(str, "%s\n", appData.premoveBlackText);
3605 if (appData.debugMode)
3606 fprintf(debugFP, "Sending premove:\n");
3608 } else if (gotPremove) {
3610 ClearPremoveHighlights();
3611 if (appData.debugMode)
3612 fprintf(debugFP, "Sending premove:\n");
3613 UserMoveEvent(premoveFromX, premoveFromY,
3614 premoveToX, premoveToY,
3619 /* Usually suppress following prompt */
3620 if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
3621 while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
3622 if (looking_at(buf, &i, "*% ")) {
3623 savingComment = FALSE;
3628 } else if (started == STARTED_HOLDINGS) {
3630 char new_piece[MSG_SIZ];
3631 started = STARTED_NONE;
3632 parse[parse_pos] = NULLCHAR;
3633 if (appData.debugMode)
3634 fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
3635 parse, currentMove);
3636 if (sscanf(parse, " game %d", &gamenum) == 1 &&
3637 gamenum == ics_gamenum) {
3638 if (gameInfo.variant == VariantNormal) {
3639 /* [HGM] We seem to switch variant during a game!
3640 * Presumably no holdings were displayed, so we have
3641 * to move the position two files to the right to
3642 * create room for them!
3644 VariantClass newVariant;
3645 switch(gameInfo.boardWidth) { // base guess on board width
3646 case 9: newVariant = VariantShogi; break;
3647 case 10: newVariant = VariantGreat; break;
3648 default: newVariant = VariantCrazyhouse; break;
3650 VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3651 /* Get a move list just to see the header, which
3652 will tell us whether this is really bug or zh */
3653 if (ics_getting_history == H_FALSE) {
3654 ics_getting_history = H_REQUESTED;
3655 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3659 new_piece[0] = NULLCHAR;
3660 sscanf(parse, "game %d white [%s black [%s <- %s",
3661 &gamenum, white_holding, black_holding,
3663 white_holding[strlen(white_holding)-1] = NULLCHAR;
3664 black_holding[strlen(black_holding)-1] = NULLCHAR;
3665 /* [HGM] copy holdings to board holdings area */
3666 CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
3667 CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
3668 boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
3670 if (appData.zippyPlay && first.initDone) {
3671 ZippyHoldings(white_holding, black_holding,
3675 if (tinyLayout || smallLayout) {
3676 char wh[16], bh[16];
3677 PackHolding(wh, white_holding);
3678 PackHolding(bh, black_holding);
3679 sprintf(str, "[%s-%s] %s-%s", wh, bh,
3680 gameInfo.white, gameInfo.black);
3682 sprintf(str, "%s [%s] vs. %s [%s]",
3683 gameInfo.white, white_holding,
3684 gameInfo.black, black_holding);
3687 DrawPosition(FALSE, boards[currentMove]);
3690 /* Suppress following prompt */
3691 if (looking_at(buf, &i, "*% ")) {
3692 if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
3693 savingComment = FALSE;
3701 i++; /* skip unparsed character and loop back */
3704 if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
3705 // started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
3706 // SendToPlayer(&buf[next_out], i - next_out);
3707 started != STARTED_HOLDINGS && leftover_start > next_out) {
3708 SendToPlayer(&buf[next_out], leftover_start - next_out);
3712 leftover_len = buf_len - leftover_start;
3713 /* if buffer ends with something we couldn't parse,
3714 reparse it after appending the next read */
3716 } else if (count == 0) {
3717 RemoveInputSource(isr);
3718 DisplayFatalError(_("Connection closed by ICS"), 0, 0);
3720 DisplayFatalError(_("Error reading from ICS"), error, 1);
3725 /* Board style 12 looks like this:
3727 <12> r-b---k- pp----pp ---bP--- ---p---- q------- ------P- P--Q--BP -----R-K W -1 0 0 0 0 0 0 paf MaxII 0 2 12 21 25 234 174 24 Q/d7-a4 (0:06) Qxa4 0 0
3729 * The "<12> " is stripped before it gets to this routine. The two
3730 * trailing 0's (flip state and clock ticking) are later addition, and
3731 * some chess servers may not have them, or may have only the first.
3732 * Additional trailing fields may be added in the future.
3735 #define PATTERN "%c%d%d%d%d%d%d%d%s%s%d%d%d%d%d%d%d%d%s%s%s%d%d"
3737 #define RELATION_OBSERVING_PLAYED 0
3738 #define RELATION_OBSERVING_STATIC -2 /* examined, oldmoves, or smoves */
3739 #define RELATION_PLAYING_MYMOVE 1
3740 #define RELATION_PLAYING_NOTMYMOVE -1
3741 #define RELATION_EXAMINING 2
3742 #define RELATION_ISOLATED_BOARD -3
3743 #define RELATION_STARTING_POSITION -4 /* FICS only */
3746 ParseBoard12(string)
3749 GameMode newGameMode;
3750 int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
3751 int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
3752 int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
3753 char to_play, board_chars[200];
3754 char move_str[500], str[500], elapsed_time[500];
3755 char black[32], white[32];
3757 int prevMove = currentMove;
3760 int fromX, fromY, toX, toY;
3762 int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
3763 char *bookHit = NULL; // [HGM] book
3764 Boolean weird = FALSE, reqFlag = FALSE;
3766 fromX = fromY = toX = toY = -1;
3770 if (appData.debugMode)
3771 fprintf(debugFP, _("Parsing board: %s\n"), string);
3773 move_str[0] = NULLCHAR;
3774 elapsed_time[0] = NULLCHAR;
3775 { /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
3777 while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
3778 if(string[i] == ' ') { ranks++; files = 0; }
3780 if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
3783 for(j = 0; j <i; j++) board_chars[j] = string[j];
3784 board_chars[i] = '\0';
3787 n = sscanf(string, PATTERN, &to_play, &double_push,
3788 &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
3789 &gamenum, white, black, &relation, &basetime, &increment,
3790 &white_stren, &black_stren, &white_time, &black_time,
3791 &moveNum, str, elapsed_time, move_str, &ics_flip,
3795 snprintf(str, sizeof(str), _("Failed to parse board string:\n\"%s\""), string);
3796 DisplayError(str, 0);
3800 /* Convert the move number to internal form */
3801 moveNum = (moveNum - 1) * 2;
3802 if (to_play == 'B') moveNum++;
3803 if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
3804 DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
3810 case RELATION_OBSERVING_PLAYED:
3811 case RELATION_OBSERVING_STATIC:
3812 if (gamenum == -1) {
3813 /* Old ICC buglet */
3814 relation = RELATION_OBSERVING_STATIC;
3816 newGameMode = IcsObserving;
3818 case RELATION_PLAYING_MYMOVE:
3819 case RELATION_PLAYING_NOTMYMOVE:
3821 ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
3822 IcsPlayingWhite : IcsPlayingBlack;
3824 case RELATION_EXAMINING:
3825 newGameMode = IcsExamining;
3827 case RELATION_ISOLATED_BOARD:
3829 /* Just display this board. If user was doing something else,
3830 we will forget about it until the next board comes. */
3831 newGameMode = IcsIdle;
3833 case RELATION_STARTING_POSITION:
3834 newGameMode = gameMode;
3838 /* Modify behavior for initial board display on move listing
3841 switch (ics_getting_history) {
3845 case H_GOT_REQ_HEADER:
3846 case H_GOT_UNREQ_HEADER:
3847 /* This is the initial position of the current game */
3848 gamenum = ics_gamenum;
3849 moveNum = 0; /* old ICS bug workaround */
3850 if (to_play == 'B') {
3851 startedFromSetupPosition = TRUE;
3852 blackPlaysFirst = TRUE;
3854 if (forwardMostMove == 0) forwardMostMove = 1;
3855 if (backwardMostMove == 0) backwardMostMove = 1;
3856 if (currentMove == 0) currentMove = 1;
3858 newGameMode = gameMode;
3859 relation = RELATION_STARTING_POSITION; /* ICC needs this */
3861 case H_GOT_UNWANTED_HEADER:
3862 /* This is an initial board that we don't want */
3864 case H_GETTING_MOVES:
3865 /* Should not happen */
3866 DisplayError(_("Error gathering move list: extra board"), 0);
3867 ics_getting_history = H_FALSE;
3871 if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
3872 weird && (int)gameInfo.variant <= (int)VariantShogi) {
3873 /* [HGM] We seem to have switched variant unexpectedly
3874 * Try to guess new variant from board size
3876 VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
3877 if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
3878 if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
3879 if(ranks == 8 && files == 12) newVariant = VariantCourier; else
3880 if(ranks == 9 && files == 9) newVariant = VariantShogi; else
3881 if(!weird) newVariant = VariantNormal;
3882 VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3883 /* Get a move list just to see the header, which
3884 will tell us whether this is really bug or zh */
3885 if (ics_getting_history == H_FALSE) {
3886 ics_getting_history = H_REQUESTED; reqFlag = TRUE;
3887 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3892 /* Take action if this is the first board of a new game, or of a
3893 different game than is currently being displayed. */
3894 if (gamenum != ics_gamenum || newGameMode != gameMode ||
3895 relation == RELATION_ISOLATED_BOARD) {
3897 /* Forget the old game and get the history (if any) of the new one */
3898 if (gameMode != BeginningOfGame) {
3902 if (appData.autoRaiseBoard) BoardToTop();
3904 if (gamenum == -1) {
3905 newGameMode = IcsIdle;
3906 } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
3907 appData.getMoveList && !reqFlag) {
3908 /* Need to get game history */
3909 ics_getting_history = H_REQUESTED;
3910 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3914 /* Initially flip the board to have black on the bottom if playing
3915 black or if the ICS flip flag is set, but let the user change
3916 it with the Flip View button. */
3917 flipView = appData.autoFlipView ?
3918 (newGameMode == IcsPlayingBlack) || ics_flip :
3921 /* Done with values from previous mode; copy in new ones */
3922 gameMode = newGameMode;
3924 ics_gamenum = gamenum;
3925 if (gamenum == gs_gamenum) {
3926 int klen = strlen(gs_kind);
3927 if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
3928 sprintf(str, "ICS %s", gs_kind);
3929 gameInfo.event = StrSave(str);
3931 gameInfo.event = StrSave("ICS game");
3933 gameInfo.site = StrSave(appData.icsHost);
3934 gameInfo.date = PGNDate();
3935 gameInfo.round = StrSave("-");
3936 gameInfo.white = StrSave(white);
3937 gameInfo.black = StrSave(black);
3938 timeControl = basetime * 60 * 1000;
3940 timeIncrement = increment * 1000;
3941 movesPerSession = 0;
3942 gameInfo.timeControl = TimeControlTagValue();
3943 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
3944 if (appData.debugMode) {
3945 fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
3946 fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
3947 setbuf(debugFP, NULL);
3950 gameInfo.outOfBook = NULL;
3952 /* Do we have the ratings? */
3953 if (strcmp(player1Name, white) == 0 &&
3954 strcmp(player2Name, black) == 0) {
3955 if (appData.debugMode)
3956 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3957 player1Rating, player2Rating);
3958 gameInfo.whiteRating = player1Rating;
3959 gameInfo.blackRating = player2Rating;
3960 } else if (strcmp(player2Name, white) == 0 &&
3961 strcmp(player1Name, black) == 0) {
3962 if (appData.debugMode)
3963 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3964 player2Rating, player1Rating);
3965 gameInfo.whiteRating = player2Rating;
3966 gameInfo.blackRating = player1Rating;
3968 player1Name[0] = player2Name[0] = NULLCHAR;
3970 /* Silence shouts if requested */
3971 if (appData.quietPlay &&
3972 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
3973 SendToICS(ics_prefix);
3974 SendToICS("set shout 0\n");
3978 /* Deal with midgame name changes */
3980 if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
3981 if (gameInfo.white) free(gameInfo.white);
3982 gameInfo.white = StrSave(white);
3984 if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
3985 if (gameInfo.black) free(gameInfo.black);
3986 gameInfo.black = StrSave(black);
3990 /* Throw away game result if anything actually changes in examine mode */
3991 if (gameMode == IcsExamining && !newGame) {
3992 gameInfo.result = GameUnfinished;
3993 if (gameInfo.resultDetails != NULL) {
3994 free(gameInfo.resultDetails);
3995 gameInfo.resultDetails = NULL;
3999 /* In pausing && IcsExamining mode, we ignore boards coming
4000 in if they are in a different variation than we are. */
4001 if (pauseExamInvalid) return;
4002 if (pausing && gameMode == IcsExamining) {
4003 if (moveNum <= pauseExamForwardMostMove) {
4004 pauseExamInvalid = TRUE;
4005 forwardMostMove = pauseExamForwardMostMove;
4010 if (appData.debugMode) {
4011 fprintf(debugFP, "load %dx%d board\n", files, ranks);
4013 /* Parse the board */
4014 for (k = 0; k < ranks; k++) {
4015 for (j = 0; j < files; j++)
4016 board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4017 if(gameInfo.holdingsWidth > 1) {
4018 board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4019 board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4022 CopyBoard(boards[moveNum], board);
4023 boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
4025 startedFromSetupPosition =
4026 !CompareBoards(board, initialPosition);
4027 if(startedFromSetupPosition)
4028 initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
4031 /* [HGM] Set castling rights. Take the outermost Rooks,
4032 to make it also work for FRC opening positions. Note that board12
4033 is really defective for later FRC positions, as it has no way to
4034 indicate which Rook can castle if they are on the same side of King.
4035 For the initial position we grant rights to the outermost Rooks,
4036 and remember thos rights, and we then copy them on positions
4037 later in an FRC game. This means WB might not recognize castlings with
4038 Rooks that have moved back to their original position as illegal,
4039 but in ICS mode that is not its job anyway.
4041 if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
4042 { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
4044 for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4045 if(board[0][i] == WhiteRook) j = i;
4046 initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4047 for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4048 if(board[0][i] == WhiteRook) j = i;
4049 initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4050 for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4051 if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4052 initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4053 for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4054 if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4055 initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4057 if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
4058 for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4059 if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
4060 for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4061 if(board[BOARD_HEIGHT-1][k] == bKing)
4062 initialRights[5] = boards[moveNum][CASTLING][5] = k;
4063 if(gameInfo.variant == VariantTwoKings) {
4064 // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
4065 if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
4066 if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
4069 r = boards[moveNum][CASTLING][0] = initialRights[0];
4070 if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4071 r = boards[moveNum][CASTLING][1] = initialRights[1];
4072 if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4073 r = boards[moveNum][CASTLING][3] = initialRights[3];
4074 if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4075 r = boards[moveNum][CASTLING][4] = initialRights[4];
4076 if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4077 /* wildcastle kludge: always assume King has rights */
4078 r = boards[moveNum][CASTLING][2] = initialRights[2];
4079 r = boards[moveNum][CASTLING][5] = initialRights[5];
4081 /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4082 boards[moveNum][EP_STATUS] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
4085 if (ics_getting_history == H_GOT_REQ_HEADER ||
4086 ics_getting_history == H_GOT_UNREQ_HEADER) {
4087 /* This was an initial position from a move list, not
4088 the current position */
4092 /* Update currentMove and known move number limits */
4093 newMove = newGame || moveNum > forwardMostMove;
4096 forwardMostMove = backwardMostMove = currentMove = moveNum;
4097 if (gameMode == IcsExamining && moveNum == 0) {
4098 /* Workaround for ICS limitation: we are not told the wild
4099 type when starting to examine a game. But if we ask for
4100 the move list, the move list header will tell us */
4101 ics_getting_history = H_REQUESTED;
4102 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
4105 } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4106 || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4108 /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4109 /* [HGM] applied this also to an engine that is silently watching */
4110 if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4111 (gameMode == IcsObserving || gameMode == IcsExamining) &&
4112 gameInfo.variant == currentlyInitializedVariant) {
4113 takeback = forwardMostMove - moveNum;
4114 for (i = 0; i < takeback; i++) {
4115 if (appData.debugMode) fprintf(debugFP, "take back move\n");
4116 SendToProgram("undo\n", &first);
4121 forwardMostMove = moveNum;
4122 if (!pausing || currentMove > forwardMostMove)
4123 currentMove = forwardMostMove;
4125 /* New part of history that is not contiguous with old part */
4126 if (pausing && gameMode == IcsExamining) {
4127 pauseExamInvalid = TRUE;
4128 forwardMostMove = pauseExamForwardMostMove;
4131 if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4133 if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4134 // [HGM] when we will receive the move list we now request, it will be
4135 // fed to the engine from the first move on. So if the engine is not
4136 // in the initial position now, bring it there.
4137 InitChessProgram(&first, 0);
4140 ics_getting_history = H_REQUESTED;
4141 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
4144 forwardMostMove = backwardMostMove = currentMove = moveNum;
4147 /* Update the clocks */
4148 if (strchr(elapsed_time, '.')) {
4150 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4151 timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4153 /* Time is in seconds */
4154 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4155 timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4160 if (appData.zippyPlay && newGame &&
4161 gameMode != IcsObserving && gameMode != IcsIdle &&
4162 gameMode != IcsExamining)
4163 ZippyFirstBoard(moveNum, basetime, increment);
4166 /* Put the move on the move list, first converting
4167 to canonical algebraic form. */
4169 if (appData.debugMode) {
4170 if (appData.debugMode) { int f = forwardMostMove;
4171 fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4172 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4173 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4175 fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4176 fprintf(debugFP, "moveNum = %d\n", moveNum);
4177 fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4178 setbuf(debugFP, NULL);
4180 if (moveNum <= backwardMostMove) {
4181 /* We don't know what the board looked like before
4183 strcpy(parseList[moveNum - 1], move_str);
4184 strcat(parseList[moveNum - 1], " ");
4185 strcat(parseList[moveNum - 1], elapsed_time);
4186 moveList[moveNum - 1][0] = NULLCHAR;
4187 } else if (strcmp(move_str, "none") == 0) {
4188 // [HGM] long SAN: swapped order; test for 'none' before parsing move
4189 /* Again, we don't know what the board looked like;
4190 this is really the start of the game. */
4191 parseList[moveNum - 1][0] = NULLCHAR;
4192 moveList[moveNum - 1][0] = NULLCHAR;
4193 backwardMostMove = moveNum;
4194 startedFromSetupPosition = TRUE;
4195 fromX = fromY = toX = toY = -1;
4197 // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
4198 // So we parse the long-algebraic move string in stead of the SAN move
4199 int valid; char buf[MSG_SIZ], *prom;
4201 // str looks something like "Q/a1-a2"; kill the slash
4203 sprintf(buf, "%c%s", str[0], str+2);
4204 else strcpy(buf, str); // might be castling
4205 if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
4206 strcat(buf, prom); // long move lacks promo specification!
4207 if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4208 if(appData.debugMode)
4209 fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4210 strcpy(move_str, buf);
4212 valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4213 &fromX, &fromY, &toX, &toY, &promoChar)
4214 || ParseOneMove(buf, moveNum - 1, &moveType,
4215 &fromX, &fromY, &toX, &toY, &promoChar);
4216 // end of long SAN patch
4218 (void) CoordsToAlgebraic(boards[moveNum - 1],
4219 PosFlags(moveNum - 1),
4220 fromY, fromX, toY, toX, promoChar,
4221 parseList[moveNum-1]);
4222 switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4228 if(gameInfo.variant != VariantShogi)
4229 strcat(parseList[moveNum - 1], "+");
4232 case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4233 strcat(parseList[moveNum - 1], "#");
4236 strcat(parseList[moveNum - 1], " ");
4237 strcat(parseList[moveNum - 1], elapsed_time);
4238 /* currentMoveString is set as a side-effect of ParseOneMove */
4239 strcpy(moveList[moveNum - 1], currentMoveString);
4240 strcat(moveList[moveNum - 1], "\n");
4242 /* Move from ICS was illegal!? Punt. */
4243 if (appData.debugMode) {
4244 fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4245 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4247 strcpy(parseList[moveNum - 1], move_str);
4248 strcat(parseList[moveNum - 1], " ");
4249 strcat(parseList[moveNum - 1], elapsed_time);
4250 moveList[moveNum - 1][0] = NULLCHAR;
4251 fromX = fromY = toX = toY = -1;
4254 if (appData.debugMode) {
4255 fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4256 setbuf(debugFP, NULL);
4260 /* Send move to chess program (BEFORE animating it). */
4261 if (appData.zippyPlay && !newGame && newMove &&
4262 (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4264 if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4265 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4266 if (moveList[moveNum - 1][0] == NULLCHAR) {
4267 sprintf(str, _("Couldn't parse move \"%s\" from ICS"),
4269 DisplayError(str, 0);
4271 if (first.sendTime) {
4272 SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4274 bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4275 if (firstMove && !bookHit) {
4277 if (first.useColors) {
4278 SendToProgram(gameMode == IcsPlayingWhite ?
4280 "black\ngo\n", &first);
4282 SendToProgram("go\n", &first);
4284 first.maybeThinking = TRUE;
4287 } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4288 if (moveList[moveNum - 1][0] == NULLCHAR) {
4289 sprintf(str, _("Couldn't parse move \"%s\" from ICS"), move_str);
4290 DisplayError(str, 0);
4292 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4293 SendMoveToProgram(moveNum - 1, &first);
4300 if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4301 /* If move comes from a remote source, animate it. If it
4302 isn't remote, it will have already been animated. */
4303 if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4304 AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4306 if (!pausing && appData.highlightLastMove) {
4307 SetHighlights(fromX, fromY, toX, toY);
4311 /* Start the clocks */
4312 whiteFlag = blackFlag = FALSE;
4313 appData.clockMode = !(basetime == 0 && increment == 0);
4315 ics_clock_paused = TRUE;
4317 } else if (ticking == 1) {
4318 ics_clock_paused = FALSE;
4320 if (gameMode == IcsIdle ||
4321 relation == RELATION_OBSERVING_STATIC ||
4322 relation == RELATION_EXAMINING ||
4324 DisplayBothClocks();
4328 /* Display opponents and material strengths */
4329 if (gameInfo.variant != VariantBughouse &&
4330 gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4331 if (tinyLayout || smallLayout) {
4332 if(gameInfo.variant == VariantNormal)
4333 sprintf(str, "%s(%d) %s(%d) {%d %d}",
4334 gameInfo.white, white_stren, gameInfo.black, black_stren,
4335 basetime, increment);
4337 sprintf(str, "%s(%d) %s(%d) {%d %d w%d}",
4338 gameInfo.white, white_stren, gameInfo.black, black_stren,
4339 basetime, increment, (int) gameInfo.variant);
4341 if(gameInfo.variant == VariantNormal)
4342 sprintf(str, "%s (%d) vs. %s (%d) {%d %d}",
4343 gameInfo.white, white_stren, gameInfo.black, black_stren,
4344 basetime, increment);
4346 sprintf(str, "%s (%d) vs. %s (%d) {%d %d %s}",
4347 gameInfo.white, white_stren, gameInfo.black, black_stren,
4348 basetime, increment, VariantName(gameInfo.variant));
4351 if (appData.debugMode) {
4352 fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
4357 /* Display the board */
4358 if (!pausing && !appData.noGUI) {
4359 if (appData.premove)
4361 ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
4362 ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
4363 ClearPremoveHighlights();
4365 j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
4366 DrawPosition(j, boards[currentMove]);
4368 DisplayMove(moveNum - 1);
4369 if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4370 !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4371 (gameMode == IcsPlayingBlack) && (WhiteOnMove(moveNum)) ) ) {
4372 if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4376 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4378 if(bookHit) { // [HGM] book: simulate book reply
4379 static char bookMove[MSG_SIZ]; // a bit generous?
4381 programStats.nodes = programStats.depth = programStats.time =
4382 programStats.score = programStats.got_only_move = 0;
4383 sprintf(programStats.movelist, "%s (xbook)", bookHit);
4385 strcpy(bookMove, "move ");
4386 strcat(bookMove, bookHit);
4387 HandleMachineMove(bookMove, &first);
4396 if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4397 ics_getting_history = H_REQUESTED;
4398 sprintf(buf, "%smoves %d\n", ics_prefix, ics_gamenum);
4404 AnalysisPeriodicEvent(force)
4407 if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4408 && !force) || !appData.periodicUpdates)
4411 /* Send . command to Crafty to collect stats */
4412 SendToProgram(".\n", &first);
4414 /* Don't send another until we get a response (this makes
4415 us stop sending to old Crafty's which don't understand
4416 the "." command (sending illegal cmds resets node count & time,
4417 which looks bad)) */
4418 programStats.ok_to_send = 0;
4421 void ics_update_width(new_width)
4424 ics_printf("set width %d\n", new_width);
4428 SendMoveToProgram(moveNum, cps)
4430 ChessProgramState *cps;
4434 if (cps->useUsermove) {
4435 SendToProgram("usermove ", cps);
4439 if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4440 int len = space - parseList[moveNum];
4441 memcpy(buf, parseList[moveNum], len);
4443 buf[len] = NULLCHAR;
4445 sprintf(buf, "%s\n", parseList[moveNum]);
4447 SendToProgram(buf, cps);
4449 if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4450 AlphaRank(moveList[moveNum], 4);
4451 SendToProgram(moveList[moveNum], cps);
4452 AlphaRank(moveList[moveNum], 4); // and back
4454 /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
4455 * the engine. It would be nice to have a better way to identify castle
4457 if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
4458 && cps->useOOCastle) {
4459 int fromX = moveList[moveNum][0] - AAA;
4460 int fromY = moveList[moveNum][1] - ONE;
4461 int toX = moveList[moveNum][2] - AAA;
4462 int toY = moveList[moveNum][3] - ONE;
4463 if((boards[moveNum][fromY][fromX] == WhiteKing
4464 && boards[moveNum][toY][toX] == WhiteRook)
4465 || (boards[moveNum][fromY][fromX] == BlackKing
4466 && boards[moveNum][toY][toX] == BlackRook)) {
4467 if(toX > fromX) SendToProgram("O-O\n", cps);
4468 else SendToProgram("O-O-O\n", cps);
4470 else SendToProgram(moveList[moveNum], cps);
4472 else SendToProgram(moveList[moveNum], cps);
4473 /* End of additions by Tord */
4476 /* [HGM] setting up the opening has brought engine in force mode! */
4477 /* Send 'go' if we are in a mode where machine should play. */
4478 if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4479 (gameMode == TwoMachinesPlay ||
4481 gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite ||
4483 gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4484 SendToProgram("go\n", cps);
4485 if (appData.debugMode) {
4486 fprintf(debugFP, "(extra)\n");
4489 setboardSpoiledMachineBlack = 0;
4493 SendMoveToICS(moveType, fromX, fromY, toX, toY)
4495 int fromX, fromY, toX, toY;
4497 char user_move[MSG_SIZ];
4501 sprintf(user_move, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4502 (int)moveType, fromX, fromY, toX, toY);
4503 DisplayError(user_move + strlen("say "), 0);
4505 case WhiteKingSideCastle:
4506 case BlackKingSideCastle:
4507 case WhiteQueenSideCastleWild:
4508 case BlackQueenSideCastleWild:
4510 case WhiteHSideCastleFR:
4511 case BlackHSideCastleFR:
4513 sprintf(user_move, "o-o\n");
4515 case WhiteQueenSideCastle:
4516 case BlackQueenSideCastle:
4517 case WhiteKingSideCastleWild:
4518 case BlackKingSideCastleWild:
4520 case WhiteASideCastleFR:
4521 case BlackASideCastleFR:
4523 sprintf(user_move, "o-o-o\n");
4525 case WhitePromotionQueen:
4526 case BlackPromotionQueen:
4527 case WhitePromotionRook:
4528 case BlackPromotionRook:
4529 case WhitePromotionBishop:
4530 case BlackPromotionBishop:
4531 case WhitePromotionKnight:
4532 case BlackPromotionKnight:
4533 case WhitePromotionKing:
4534 case BlackPromotionKing:
4535 case WhitePromotionChancellor:
4536 case BlackPromotionChancellor:
4537 case WhitePromotionArchbishop:
4538 case BlackPromotionArchbishop:
4539 if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
4540 sprintf(user_move, "%c%c%c%c=%c\n",
4541 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4542 PieceToChar(WhiteFerz));
4543 else if(gameInfo.variant == VariantGreat)
4544 sprintf(user_move, "%c%c%c%c=%c\n",
4545 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4546 PieceToChar(WhiteMan));
4548 sprintf(user_move, "%c%c%c%c=%c\n",
4549 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4550 PieceToChar(PromoPiece(moveType)));
4554 sprintf(user_move, "%c@%c%c\n",
4555 ToUpper(PieceToChar((ChessSquare) fromX)),
4556 AAA + toX, ONE + toY);
4559 case WhiteCapturesEnPassant:
4560 case BlackCapturesEnPassant:
4561 case IllegalMove: /* could be a variant we don't quite understand */
4562 sprintf(user_move, "%c%c%c%c\n",
4563 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4566 SendToICS(user_move);
4567 if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
4568 ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
4572 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
4577 if (rf == DROP_RANK) {
4578 sprintf(move, "%c@%c%c\n",
4579 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
4581 if (promoChar == 'x' || promoChar == NULLCHAR) {
4582 sprintf(move, "%c%c%c%c\n",
4583 AAA + ff, ONE + rf, AAA + ft, ONE + rt);
4585 sprintf(move, "%c%c%c%c%c\n",
4586 AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
4592 ProcessICSInitScript(f)
4597 while (fgets(buf, MSG_SIZ, f)) {
4598 SendToICSDelayed(buf,(long)appData.msLoginDelay);
4605 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
4607 AlphaRank(char *move, int n)
4609 // char *p = move, c; int x, y;
4611 if (appData.debugMode) {
4612 fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
4616 move[2]>='0' && move[2]<='9' &&
4617 move[3]>='a' && move[3]<='x' ) {
4619 move[2] = BOARD_RGHT -1 - (move[2]-'1') + AAA;
4620 move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4622 if(move[0]>='0' && move[0]<='9' &&
4623 move[1]>='a' && move[1]<='x' &&
4624 move[2]>='0' && move[2]<='9' &&
4625 move[3]>='a' && move[3]<='x' ) {
4626 /* input move, Shogi -> normal */
4627 move[0] = BOARD_RGHT -1 - (move[0]-'1') + AAA;
4628 move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
4629 move[2] = BOARD_RGHT -1 - (move[2]-'1') + AAA;
4630 move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4633 move[3]>='0' && move[3]<='9' &&
4634 move[2]>='a' && move[2]<='x' ) {
4636 move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4637 move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4640 move[0]>='a' && move[0]<='x' &&
4641 move[3]>='0' && move[3]<='9' &&
4642 move[2]>='a' && move[2]<='x' ) {
4643 /* output move, normal -> Shogi */
4644 move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
4645 move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
4646 move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4647 move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4648 if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
4650 if (appData.debugMode) {
4651 fprintf(debugFP, " out = '%s'\n", move);
4655 /* Parser for moves from gnuchess, ICS, or user typein box */
4657 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
4660 ChessMove *moveType;
4661 int *fromX, *fromY, *toX, *toY;
4664 if (appData.debugMode) {
4665 fprintf(debugFP, "move to parse: %s\n", move);
4667 *moveType = yylexstr(moveNum, move);
4669 switch (*moveType) {
4670 case WhitePromotionChancellor:
4671 case BlackPromotionChancellor:
4672 case WhitePromotionArchbishop:
4673 case BlackPromotionArchbishop:
4674 case WhitePromotionQueen:
4675 case BlackPromotionQueen:
4676 case WhitePromotionRook:
4677 case BlackPromotionRook:
4678 case WhitePromotionBishop:
4679 case BlackPromotionBishop:
4680 case WhitePromotionKnight:
4681 case BlackPromotionKnight:
4682 case WhitePromotionKing:
4683 case BlackPromotionKing:
4685 case WhiteCapturesEnPassant:
4686 case BlackCapturesEnPassant:
4687 case WhiteKingSideCastle:
4688 case WhiteQueenSideCastle:
4689 case BlackKingSideCastle:
4690 case BlackQueenSideCastle:
4691 case WhiteKingSideCastleWild:
4692 case WhiteQueenSideCastleWild:
4693 case BlackKingSideCastleWild:
4694 case BlackQueenSideCastleWild:
4695 /* Code added by Tord: */
4696 case WhiteHSideCastleFR:
4697 case WhiteASideCastleFR:
4698 case BlackHSideCastleFR:
4699 case BlackASideCastleFR:
4700 /* End of code added by Tord */
4701 case IllegalMove: /* bug or odd chess variant */
4702 *fromX = currentMoveString[0] - AAA;
4703 *fromY = currentMoveString[1] - ONE;
4704 *toX = currentMoveString[2] - AAA;
4705 *toY = currentMoveString[3] - ONE;
4706 *promoChar = currentMoveString[4];
4707 if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
4708 *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
4709 if (appData.debugMode) {
4710 fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
4712 *fromX = *fromY = *toX = *toY = 0;
4715 if (appData.testLegality) {
4716 return (*moveType != IllegalMove);
4718 return !(*fromX == *toX && *fromY == *toY) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
4719 WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
4724 *fromX = *moveType == WhiteDrop ?
4725 (int) CharToPiece(ToUpper(currentMoveString[0])) :
4726 (int) CharToPiece(ToLower(currentMoveString[0]));
4728 *toX = currentMoveString[2] - AAA;
4729 *toY = currentMoveString[3] - ONE;
4730 *promoChar = NULLCHAR;
4734 case ImpossibleMove:
4735 case (ChessMove) 0: /* end of file */
4744 if (appData.debugMode) {
4745 fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
4748 *fromX = *fromY = *toX = *toY = 0;
4749 *promoChar = NULLCHAR;
4757 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
4758 int fromX, fromY, toX, toY; char promoChar;
4763 endPV = forwardMostMove;
4765 while(*pv == ' ') pv++;
4766 if(*pv == '(') pv++; // first (ponder) move can be in parentheses
4767 valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
4768 if(appData.debugMode){
4769 fprintf(debugFP,"parsePV: %d %c%c%c%c '%s'\n", valid, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, pv);
4771 if(!valid && nr == 0 &&
4772 ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
4773 nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
4775 while(*pv && *pv++ != ' '); // skip what we parsed; assume space separators
4776 if(moveType == Comment) { valid++; continue; } // allow comments in PV
4778 if(endPV+1 > framePtr) break; // no space, truncate
4781 CopyBoard(boards[endPV], boards[endPV-1]);
4782 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
4783 moveList[endPV-1][0] = fromX + AAA;
4784 moveList[endPV-1][1] = fromY + ONE;
4785 moveList[endPV-1][2] = toX + AAA;
4786 moveList[endPV-1][3] = toY + ONE;
4787 parseList[endPV-1][0] = NULLCHAR;
4789 currentMove = endPV;
4790 if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
4791 SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
4792 moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
4793 DrawPosition(TRUE, boards[currentMove]);
4796 static int lastX, lastY;
4799 LoadMultiPV(int x, int y, char *buf, int index, int *start, int *end)
4803 if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
4804 lastX = x; lastY = y;
4805 while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
4807 while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
4809 while(buf[index] && buf[index] != '\n') index++;
4811 ParsePV(buf+startPV);
4812 *start = startPV; *end = index-1;
4817 LoadPV(int x, int y)
4818 { // called on right mouse click to load PV
4819 int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
4820 lastX = x; lastY = y;
4821 ParsePV(lastPV[which]); // load the PV of the thinking engine in the boards array.
4828 if(endPV < 0) return;
4830 currentMove = forwardMostMove;
4831 ClearPremoveHighlights();
4832 DrawPosition(TRUE, boards[currentMove]);
4836 MovePV(int x, int y, int h)
4837 { // step through PV based on mouse coordinates (called on mouse move)
4838 int margin = h>>3, step = 0;
4840 if(endPV < 0) return;
4841 // we must somehow check if right button is still down (might be released off board!)
4842 if(y < margin && (abs(x - lastX) > 6 || abs(y - lastY) > 6)) step = 1; else
4843 if(y > h - margin && (abs(x - lastX) > 6 || abs(y - lastY) > 6)) step = -1; else
4844 if( y > lastY + 6 ) step = -1; else if(y < lastY - 6) step = 1;
4846 lastX = x; lastY = y;
4847 if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
4848 currentMove += step;
4849 if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
4850 SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
4851 moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
4852 DrawPosition(FALSE, boards[currentMove]);
4856 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
4857 // All positions will have equal probability, but the current method will not provide a unique
4858 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
4864 int piecesLeft[(int)BlackPawn];
4865 int seed, nrOfShuffles;
4867 void GetPositionNumber()
4868 { // sets global variable seed
4871 seed = appData.defaultFrcPosition;
4872 if(seed < 0) { // randomize based on time for negative FRC position numbers
4873 for(i=0; i<50; i++) seed += random();
4874 seed = random() ^ random() >> 8 ^ random() << 8;
4875 if(seed<0) seed = -seed;
4879 int put(Board board, int pieceType, int rank, int n, int shade)
4880 // put the piece on the (n-1)-th empty squares of the given shade
4884 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
4885 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
4886 board[rank][i] = (ChessSquare) pieceType;
4887 squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
4889 piecesLeft[pieceType]--;
4897 void AddOnePiece(Board board, int pieceType, int rank, int shade)
4898 // calculate where the next piece goes, (any empty square), and put it there
4902 i = seed % squaresLeft[shade];
4903 nrOfShuffles *= squaresLeft[shade];
4904 seed /= squaresLeft[shade];
4905 put(board, pieceType, rank, i, shade);
4908 void AddTwoPieces(Board board, int pieceType, int rank)
4909 // calculate where the next 2 identical pieces go, (any empty square), and put it there
4911 int i, n=squaresLeft[ANY], j=n-1, k;
4913 k = n*(n-1)/2; // nr of possibilities, not counting permutations
4914 i = seed % k; // pick one
4917 while(i >= j) i -= j--;
4918 j = n - 1 - j; i += j;
4919 put(board, pieceType, rank, j, ANY);
4920 put(board, pieceType, rank, i, ANY);
4923 void SetUpShuffle(Board board, int number)
4927 GetPositionNumber(); nrOfShuffles = 1;
4929 squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
4930 squaresLeft[ANY] = BOARD_RGHT - BOARD_LEFT;
4931 squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
4933 for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
4935 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
4936 p = (int) board[0][i];
4937 if(p < (int) BlackPawn) piecesLeft[p] ++;
4938 board[0][i] = EmptySquare;
4941 if(PosFlags(0) & F_ALL_CASTLE_OK) {
4942 // shuffles restricted to allow normal castling put KRR first
4943 if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
4944 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
4945 else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
4946 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
4947 if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
4948 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
4949 if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
4950 put(board, WhiteRook, 0, 0, ANY);
4951 // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
4954 if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
4955 // only for even boards make effort to put pairs of colorbound pieces on opposite colors
4956 for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
4957 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
4958 while(piecesLeft[p] >= 2) {
4959 AddOnePiece(board, p, 0, LITE);
4960 AddOnePiece(board, p, 0, DARK);
4962 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
4965 for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
4966 // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
4967 // but we leave King and Rooks for last, to possibly obey FRC restriction
4968 if(p == (int)WhiteRook) continue;
4969 while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
4970 if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY); // add the odd piece
4973 // now everything is placed, except perhaps King (Unicorn) and Rooks
4975 if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
4976 // Last King gets castling rights
4977 while(piecesLeft[(int)WhiteUnicorn]) {
4978 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
4979 initialRights[2] = initialRights[5] = board[CASTLING][2] = board[CASTLING][5] = i;
4982 while(piecesLeft[(int)WhiteKing]) {
4983 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
4984 initialRights[2] = initialRights[5] = board[CASTLING][2] = board[CASTLING][5] = i;
4989 while(piecesLeft[(int)WhiteKing]) AddOnePiece(board, WhiteKing, 0, ANY);
4990 while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
4993 // Only Rooks can be left; simply place them all
4994 while(piecesLeft[(int)WhiteRook]) {
4995 i = put(board, WhiteRook, 0, 0, ANY);
4996 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
4999 initialRights[1] = initialRights[4] = board[CASTLING][1] = board[CASTLING][4] = i;
5001 initialRights[0] = initialRights[3] = board[CASTLING][0] = board[CASTLING][3] = i;
5004 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
5005 board[BOARD_HEIGHT-1][i] = (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
5008 if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
5011 int SetCharTable( char *table, const char * map )
5012 /* [HGM] moved here from winboard.c because of its general usefulness */
5013 /* Basically a safe strcpy that uses the last character as King */
5015 int result = FALSE; int NrPieces;
5017 if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
5018 && NrPieces >= 12 && !(NrPieces&1)) {
5019 int i; /* [HGM] Accept even length from 12 to 34 */
5021 for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
5022 for( i=0; i<NrPieces/2-1; i++ ) {
5024 table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
5026 table[(int) WhiteKing] = map[NrPieces/2-1];
5027 table[(int) BlackKing] = map[NrPieces-1];
5035 void Prelude(Board board)
5036 { // [HGM] superchess: random selection of exo-pieces
5037 int i, j, k; ChessSquare p;
5038 static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
5040 GetPositionNumber(); // use FRC position number
5042 if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
5043 SetCharTable(pieceToChar, appData.pieceToCharTable);
5044 for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
5045 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
5048 j = seed%4; seed /= 4;
5049 p = board[0][BOARD_LEFT+j]; board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5050 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
5051 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
5052 j = seed%3 + (seed%3 >= j); seed /= 3;
5053 p = board[0][BOARD_LEFT+j]; board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5054 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
5055 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
5056 j = seed%3; seed /= 3;
5057 p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5058 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
5059 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
5060 j = seed%2 + (seed%2 >= j); seed /= 2;
5061 p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5062 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
5063 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
5064 j = seed%4; seed /= 4; put(board, exoPieces[3], 0, j, ANY);
5065 j = seed%3; seed /= 3; put(board, exoPieces[2], 0, j, ANY);
5066 j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
5067 put(board, exoPieces[0], 0, 0, ANY);
5068 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
5072 InitPosition(redraw)
5075 ChessSquare (* pieces)[BOARD_FILES];
5076 int i, j, pawnRow, overrule,
5077 oldx = gameInfo.boardWidth,
5078 oldy = gameInfo.boardHeight,
5079 oldh = gameInfo.holdingsWidth,
5080 oldv = gameInfo.variant;
5082 if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
5084 /* [AS] Initialize pv info list [HGM] and game status */
5086 for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
5087 pvInfoList[i].depth = 0;
5088 boards[i][EP_STATUS] = EP_NONE;
5089 for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
5092 initialRulePlies = 0; /* 50-move counter start */
5094 castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
5095 castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
5099 /* [HGM] logic here is completely changed. In stead of full positions */
5100 /* the initialized data only consist of the two backranks. The switch */
5101 /* selects which one we will use, which is than copied to the Board */
5102 /* initialPosition, which for the rest is initialized by Pawns and */
5103 /* empty squares. This initial position is then copied to boards[0], */
5104 /* possibly after shuffling, so that it remains available. */
5106 gameInfo.holdingsWidth = 0; /* default board sizes */
5107 gameInfo.boardWidth = 8;
5108 gameInfo.boardHeight = 8;
5109 gameInfo.holdingsSize = 0;
5110 nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
5111 for(i=0; i<BOARD_FILES-2; i++)
5112 initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
5113 initialPosition[EP_STATUS] = EP_NONE;
5114 SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
5116 switch (gameInfo.variant) {
5117 case VariantFischeRandom:
5118 shuffleOpenings = TRUE;
5122 case VariantShatranj:
5123 pieces = ShatranjArray;
5124 nrCastlingRights = 0;
5125 SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
5128 pieces = makrukArray;
5129 nrCastlingRights = 0;
5130 startedFromSetupPosition = TRUE;
5131 SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
5133 case VariantTwoKings:
5134 pieces = twoKingsArray;
5136 case VariantCapaRandom:
5137 shuffleOpenings = TRUE;
5138 case VariantCapablanca:
5139 pieces = CapablancaArray;
5140 gameInfo.boardWidth = 10;
5141 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5144 pieces = GothicArray;
5145 gameInfo.boardWidth = 10;
5146 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5149 pieces = JanusArray;
5150 gameInfo.boardWidth = 10;
5151 SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
5152 nrCastlingRights = 6;
5153 initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5154 initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5155 initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
5156 initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5157 initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5158 initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
5161 pieces = FalconArray;
5162 gameInfo.boardWidth = 10;
5163 SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
5165 case VariantXiangqi:
5166 pieces = XiangqiArray;
5167 gameInfo.boardWidth = 9;
5168 gameInfo.boardHeight = 10;
5169 nrCastlingRights = 0;
5170 SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
5173 pieces = ShogiArray;
5174 gameInfo.boardWidth = 9;
5175 gameInfo.boardHeight = 9;
5176 gameInfo.holdingsSize = 7;
5177 nrCastlingRights = 0;
5178 SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
5180 case VariantCourier:
5181 pieces = CourierArray;
5182 gameInfo.boardWidth = 12;
5183 nrCastlingRights = 0;
5184 SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
5186 case VariantKnightmate:
5187 pieces = KnightmateArray;
5188 SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
5191 pieces = fairyArray;
5192 SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
5195 pieces = GreatArray;
5196 gameInfo.boardWidth = 10;
5197 SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
5198 gameInfo.holdingsSize = 8;
5202 SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
5203 gameInfo.holdingsSize = 8;
5204 startedFromSetupPosition = TRUE;
5206 case VariantCrazyhouse:
5207 case VariantBughouse:
5209 SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
5210 gameInfo.holdingsSize = 5;
5212 case VariantWildCastle:
5214 /* !!?shuffle with kings guaranteed to be on d or e file */
5215 shuffleOpenings = 1;
5217 case VariantNoCastle:
5219 nrCastlingRights = 0;
5220 /* !!?unconstrained back-rank shuffle */
5221 shuffleOpenings = 1;
5226 if(appData.NrFiles >= 0) {
5227 if(gameInfo.boardWidth != appData.NrFiles) overrule++;
5228 gameInfo.boardWidth = appData.NrFiles;
5230 if(appData.NrRanks >= 0) {
5231 gameInfo.boardHeight = appData.NrRanks;
5233 if(appData.holdingsSize >= 0) {
5234 i = appData.holdingsSize;
5235 if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
5236 gameInfo.holdingsSize = i;
5238 if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
5239 if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
5240 DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
5242 pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
5243 if(pawnRow < 1) pawnRow = 1;
5244 if(gameInfo.variant == VariantMakruk) pawnRow = 2;
5246 /* User pieceToChar list overrules defaults */
5247 if(appData.pieceToCharTable != NULL)
5248 SetCharTable(pieceToChar, appData.pieceToCharTable);
5250 for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
5252 if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
5253 s = (ChessSquare) 0; /* account holding counts in guard band */
5254 for( i=0; i<BOARD_HEIGHT; i++ )
5255 initialPosition[i][j] = s;
5257 if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
5258 initialPosition[0][j] = pieces[0][j-gameInfo.holdingsWidth];
5259 initialPosition[pawnRow][j] = WhitePawn;
5260 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = BlackPawn;
5261 if(gameInfo.variant == VariantXiangqi) {
5263 initialPosition[pawnRow][j] =
5264 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
5265 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
5266 initialPosition[2][j] = WhiteCannon;
5267 initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
5271 initialPosition[BOARD_HEIGHT-1][j] = pieces[1][j-gameInfo.holdingsWidth];
5273 if( (gameInfo.variant == VariantShogi) && !overrule ) {
5276 initialPosition[1][j] = WhiteBishop;
5277 initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
5279 initialPosition[1][j] = WhiteRook;
5280 initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
5283 if( nrCastlingRights == -1) {
5284 /* [HGM] Build normal castling rights (must be done after board sizing!) */
5285 /* This sets default castling rights from none to normal corners */
5286 /* Variants with other castling rights must set them themselves above */
5287 nrCastlingRights = 6;
5288 initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5289 initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5290 initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
5291 initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5292 initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5293 initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
5296 if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
5297 if(gameInfo.variant == VariantGreat) { // promotion commoners
5298 initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
5299 initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
5300 initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
5301 initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
5303 if (appData.debugMode) {
5304 fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
5306 if(shuffleOpenings) {
5307 SetUpShuffle(initialPosition, appData.defaultFrcPosition);
5308 startedFromSetupPosition = TRUE;
5310 if(startedFromPositionFile) {
5311 /* [HGM] loadPos: use PositionFile for every new game */
5312 CopyBoard(initialPosition, filePosition);
5313 for(i=0; i<nrCastlingRights; i++)
5314 initialRights[i] = filePosition[CASTLING][i];
5315 startedFromSetupPosition = TRUE;
5318 CopyBoard(boards[0], initialPosition);
5319 if(oldx != gameInfo.boardWidth ||
5320 oldy != gameInfo.boardHeight ||
5321 oldh != gameInfo.holdingsWidth
5323 || oldv == VariantGothic || // For licensing popups
5324 gameInfo.variant == VariantGothic
5327 || oldv == VariantFalcon ||
5328 gameInfo.variant == VariantFalcon
5332 InitDrawingSizes(-2 ,0);
5336 DrawPosition(TRUE, boards[currentMove]);
5341 SendBoard(cps, moveNum)
5342 ChessProgramState *cps;
5345 char message[MSG_SIZ];
5347 if (cps->useSetboard) {
5348 char* fen = PositionToFEN(moveNum, cps->fenOverride);
5349 sprintf(message, "setboard %s\n", fen);
5350 SendToProgram(message, cps);
5356 /* Kludge to set black to move, avoiding the troublesome and now
5357 * deprecated "black" command.
5359 if (!WhiteOnMove(moveNum)) SendToProgram("a2a3\n", cps);
5361 SendToProgram("edit\n", cps);
5362 SendToProgram("#\n", cps);
5363 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
5364 bp = &boards[moveNum][i][BOARD_LEFT];
5365 for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
5366 if ((int) *bp < (int) BlackPawn) {
5367 sprintf(message, "%c%c%c\n", PieceToChar(*bp),
5369 if(message[0] == '+' || message[0] == '~') {
5370 sprintf(message, "%c%c%c+\n",
5371 PieceToChar((ChessSquare)(DEMOTED *bp)),
5374 if(cps->alphaRank) { /* [HGM] shogi: translate coords */
5375 message[1] = BOARD_RGHT - 1 - j + '1';
5376 message[2] = BOARD_HEIGHT - 1 - i + 'a';
5378 SendToProgram(message, cps);
5383 SendToProgram("c\n", cps);
5384 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
5385 bp = &boards[moveNum][i][BOARD_LEFT];
5386 for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
5387 if (((int) *bp != (int) EmptySquare)
5388 && ((int) *bp >= (int) BlackPawn)) {
5389 sprintf(message, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
5391 if(message[0] == '+' || message[0] == '~') {
5392 sprintf(message, "%c%c%c+\n",
5393 PieceToChar((ChessSquare)(DEMOTED *bp)),
5396 if(cps->alphaRank) { /* [HGM] shogi: translate coords */
5397 message[1] = BOARD_RGHT - 1 - j + '1';
5398 message[2] = BOARD_HEIGHT - 1 - i + 'a';
5400 SendToProgram(message, cps);
5405 SendToProgram(".\n", cps);
5407 setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
5411 HasPromotionChoice(int fromX, int fromY, int toX, int toY, char *promoChoice)
5413 /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
5414 /* [HGM] add Shogi promotions */
5415 int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
5420 if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
5421 if(toX < BOARD_LEFT || toX >= BOARD_RGHT) return FALSE; // move into holdings
5423 if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
5424 !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
5427 piece = boards[currentMove][fromY][fromX];
5428 if(gameInfo.variant == VariantShogi) {
5429 promotionZoneSize = 3;
5430 highestPromotingPiece = (int)WhiteFerz;
5431 } else if(gameInfo.variant == VariantMakruk) {
5432 promotionZoneSize = 3;
5435 // next weed out all moves that do not touch the promotion zone at all
5436 if((int)piece >= BlackPawn) {
5437 if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
5439 highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
5441 if( toY < BOARD_HEIGHT - promotionZoneSize &&
5442 fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
5445 if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
5447 // weed out mandatory Shogi promotions
5448 if(gameInfo.variant == VariantShogi) {
5449 if(piece >= BlackPawn) {
5450 if(toY == 0 && piece == BlackPawn ||
5451 toY == 0 && piece == BlackQueen ||
5452 toY <= 1 && piece == BlackKnight) {
5457 if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
5458 toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
5459 toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
5466 // weed out obviously illegal Pawn moves
5467 if(appData.testLegality && (piece == WhitePawn || piece == BlackPawn) ) {
5468 if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
5469 if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
5470 if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
5471 if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
5472 // note we are not allowed to test for valid (non-)capture, due to premove
5475 // we either have a choice what to promote to, or (in Shogi) whether to promote
5476 if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk) {
5477 *promoChoice = PieceToChar(BlackFerz); // no choice
5480 if(appData.alwaysPromoteToQueen) { // predetermined
5481 if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantLosers)
5482 *promoChoice = PieceToChar(BlackKing); // in Suicide Q is the last thing we want
5483 else *promoChoice = PieceToChar(BlackQueen);
5487 // suppress promotion popup on illegal moves that are not premoves
5488 premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
5489 gameMode == IcsPlayingBlack && WhiteOnMove(currentMove);
5490 if(appData.testLegality && !premove) {
5491 moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5492 fromY, fromX, toY, toX, NULLCHAR);
5493 if(moveType != WhitePromotionQueen && moveType != BlackPromotionQueen &&
5494 moveType != WhitePromotionKnight && moveType != BlackPromotionKnight)
5502 InPalace(row, column)
5504 { /* [HGM] for Xiangqi */
5505 if( (row < 3 || row > BOARD_HEIGHT-4) &&
5506 column < (BOARD_WIDTH + 4)/2 &&
5507 column > (BOARD_WIDTH - 5)/2 ) return TRUE;
5512 PieceForSquare (x, y)
5516 if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
5519 return boards[currentMove][y][x];
5523 OKToStartUserMove(x, y)
5526 ChessSquare from_piece;
5529 if (matchMode) return FALSE;
5530 if (gameMode == EditPosition) return TRUE;
5532 if (x >= 0 && y >= 0)
5533 from_piece = boards[currentMove][y][x];
5535 from_piece = EmptySquare;
5537 if (from_piece == EmptySquare) return FALSE;
5539 white_piece = (int)from_piece >= (int)WhitePawn &&
5540 (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
5543 case PlayFromGameFile:
5545 case TwoMachinesPlay:
5553 case MachinePlaysWhite:
5554 case IcsPlayingBlack:
5555 if (appData.zippyPlay) return FALSE;
5557 DisplayMoveError(_("You are playing Black"));
5562 case MachinePlaysBlack:
5563 case IcsPlayingWhite:
5564 if (appData.zippyPlay) return FALSE;
5566 DisplayMoveError(_("You are playing White"));
5572 if (!white_piece && WhiteOnMove(currentMove)) {
5573 DisplayMoveError(_("It is White's turn"));
5576 if (white_piece && !WhiteOnMove(currentMove)) {
5577 DisplayMoveError(_("It is Black's turn"));
5580 if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
5581 /* Editing correspondence game history */
5582 /* Could disallow this or prompt for confirmation */
5587 case BeginningOfGame:
5588 if (appData.icsActive) return FALSE;
5589 if (!appData.noChessProgram) {
5591 DisplayMoveError(_("You are playing White"));
5598 if (!white_piece && WhiteOnMove(currentMove)) {
5599 DisplayMoveError(_("It is White's turn"));
5602 if (white_piece && !WhiteOnMove(currentMove)) {
5603 DisplayMoveError(_("It is Black's turn"));
5612 if (currentMove != forwardMostMove && gameMode != AnalyzeMode
5613 && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
5614 && gameMode != AnalyzeFile && gameMode != Training) {
5615 DisplayMoveError(_("Displayed position is not current"));
5622 OnlyMove(int *x, int *y) {
5623 DisambiguateClosure cl;
5624 if (appData.zippyPlay) return FALSE;
5626 case MachinePlaysBlack:
5627 case IcsPlayingWhite:
5628 case BeginningOfGame:
5629 if(!WhiteOnMove(currentMove)) return FALSE;
5631 case MachinePlaysWhite:
5632 case IcsPlayingBlack:
5633 if(WhiteOnMove(currentMove)) return FALSE;
5638 cl.pieceIn = EmptySquare;
5643 cl.promoCharIn = NULLCHAR;
5644 Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
5645 if(cl.kind == NormalMove) {
5652 if(cl.kind != ImpossibleMove) return FALSE;
5653 cl.pieceIn = EmptySquare;
5658 cl.promoCharIn = NULLCHAR;
5659 Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
5660 if(cl.kind == NormalMove) {
5670 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
5671 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
5672 int lastLoadGameUseList = FALSE;
5673 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
5674 ChessMove lastLoadGameStart = (ChessMove) 0;
5677 UserMoveTest(fromX, fromY, toX, toY, promoChar, captureOwn)
5678 int fromX, fromY, toX, toY;
5683 ChessSquare pdown, pup;
5685 /* Check if the user is playing in turn. This is complicated because we
5686 let the user "pick up" a piece before it is his turn. So the piece he
5687 tried to pick up may have been captured by the time he puts it down!
5688 Therefore we use the color the user is supposed to be playing in this
5689 test, not the color of the piece that is currently on the starting
5690 square---except in EditGame mode, where the user is playing both
5691 sides; fortunately there the capture race can't happen. (It can
5692 now happen in IcsExamining mode, but that's just too bad. The user
5693 will get a somewhat confusing message in that case.)
5697 case PlayFromGameFile:
5699 case TwoMachinesPlay:
5703 /* We switched into a game mode where moves are not accepted,
5704 perhaps while the mouse button was down. */
5705 return ImpossibleMove;
5707 case MachinePlaysWhite:
5708 /* User is moving for Black */
5709 if (WhiteOnMove(currentMove)) {
5710 DisplayMoveError(_("It is White's turn"));
5711 return ImpossibleMove;
5715 case MachinePlaysBlack:
5716 /* User is moving for White */
5717 if (!WhiteOnMove(currentMove)) {
5718 DisplayMoveError(_("It is Black's turn"));
5719 return ImpossibleMove;
5725 case BeginningOfGame:
5728 if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
5729 (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
5730 /* User is moving for Black */
5731 if (WhiteOnMove(currentMove)) {
5732 DisplayMoveError(_("It is White's turn"));
5733 return ImpossibleMove;
5736 /* User is moving for White */
5737 if (!WhiteOnMove(currentMove)) {
5738 DisplayMoveError(_("It is Black's turn"));
5739 return ImpossibleMove;
5744 case IcsPlayingBlack:
5745 /* User is moving for Black */
5746 if (WhiteOnMove(currentMove)) {
5747 if (!appData.premove) {
5748 DisplayMoveError(_("It is White's turn"));
5749 } else if (toX >= 0 && toY >= 0) {
5752 premoveFromX = fromX;
5753 premoveFromY = fromY;
5754 premovePromoChar = promoChar;
5756 if (appData.debugMode)
5757 fprintf(debugFP, "Got premove: fromX %d,"
5758 "fromY %d, toX %d, toY %d\n",
5759 fromX, fromY, toX, toY);
5761 return ImpossibleMove;
5765 case IcsPlayingWhite:
5766 /* User is moving for White */
5767 if (!WhiteOnMove(currentMove)) {
5768 if (!appData.premove) {
5769 DisplayMoveError(_("It is Black's turn"));
5770 } else if (toX >= 0 && toY >= 0) {
5773 premoveFromX = fromX;
5774 premoveFromY = fromY;
5775 premovePromoChar = promoChar;
5777 if (appData.debugMode)
5778 fprintf(debugFP, "Got premove: fromX %d,"
5779 "fromY %d, toX %d, toY %d\n",
5780 fromX, fromY, toX, toY);
5782 return ImpossibleMove;
5790 /* EditPosition, empty square, or different color piece;
5791 click-click move is possible */
5792 if (toX == -2 || toY == -2) {
5793 boards[0][fromY][fromX] = EmptySquare;
5794 return AmbiguousMove;
5795 } else if (toX >= 0 && toY >= 0) {
5796 boards[0][toY][toX] = boards[0][fromY][fromX];
5797 if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
5798 if(boards[0][fromY][0] != EmptySquare) {
5799 if(boards[0][fromY][1]) boards[0][fromY][1]--;
5800 if(boards[0][fromY][1] == 0) boards[0][fromY][0] = EmptySquare;
5803 if(fromX == BOARD_RGHT+1) {
5804 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
5805 if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
5806 if(boards[0][fromY][BOARD_WIDTH-2] == 0) boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
5809 boards[0][fromY][fromX] = EmptySquare;
5810 return AmbiguousMove;
5812 return ImpossibleMove;
5815 if(toX < 0 || toY < 0) return ImpossibleMove;
5816 pdown = boards[currentMove][fromY][fromX];
5817 pup = boards[currentMove][toY][toX];
5819 /* [HGM] If move started in holdings, it means a drop */
5820 if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) {
5821 if( pup != EmptySquare ) return ImpossibleMove;
5822 if(appData.testLegality) {
5823 /* it would be more logical if LegalityTest() also figured out
5824 * which drops are legal. For now we forbid pawns on back rank.
5825 * Shogi is on its own here...
5827 if( (pdown == WhitePawn || pdown == BlackPawn) &&
5828 (toY == 0 || toY == BOARD_HEIGHT -1 ) )
5829 return(ImpossibleMove); /* no pawn drops on 1st/8th */
5831 return WhiteDrop; /* Not needed to specify white or black yet */
5835 /* [HGM] always test for legality, to get promotion info */
5836 moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5837 fromY, fromX, toY, toX, promoChar);
5838 /* [HGM] but possibly ignore an IllegalMove result */
5839 if (appData.testLegality) {
5840 if (moveType == IllegalMove || moveType == ImpossibleMove) {
5841 DisplayMoveError(_("Illegal move"));
5842 return ImpossibleMove;
5847 /* [HGM] <popupFix> in stead of calling FinishMove directly, this
5848 function is made into one that returns an OK move type if FinishMove
5849 should be called. This to give the calling driver routine the
5850 opportunity to finish the userMove input with a promotion popup,
5851 without bothering the user with this for invalid or illegal moves */
5853 /* FinishMove(moveType, fromX, fromY, toX, toY, promoChar); */
5856 /* Common tail of UserMoveEvent and DropMenuEvent */
5858 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
5860 int fromX, fromY, toX, toY;
5861 /*char*/int promoChar;
5865 if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) && promoChar != NULLCHAR)
5867 // [HGM] superchess: suppress promotions to non-available piece
5868 int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
5869 if(WhiteOnMove(currentMove))
5871 if(!boards[currentMove][k][BOARD_WIDTH-2])
5876 if(!boards[currentMove][BOARD_HEIGHT-1-k][1])
5881 /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
5882 move type in caller when we know the move is a legal promotion */
5883 if(moveType == NormalMove && promoChar)
5884 moveType = PromoCharToMoveType(WhiteOnMove(currentMove), promoChar);
5886 /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
5887 move type in caller when we know the move is a legal promotion */
5888 if(moveType == NormalMove && promoChar)
5889 moveType = PromoCharToMoveType(WhiteOnMove(currentMove), promoChar);
5891 /* [HGM] convert drag-and-drop piece drops to standard form */
5892 if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK )
5894 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
5895 if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n",
5896 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
5897 // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
5898 if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
5899 fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
5900 while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++;
5904 /* [HGM] <popupFix> The following if has been moved here from
5905 UserMoveEvent(). Because it seemed to belong here (why not allow
5906 piece drops in training games?), and because it can only be
5907 performed after it is known to what we promote. */
5908 if (gameMode == Training)
5910 /* compare the move played on the board to the next move in the
5911 * game. If they match, display the move and the opponent's response.
5912 * If they don't match, display an error message.
5916 CopyBoard(testBoard, boards[currentMove]);
5917 ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
5919 if (CompareBoards(testBoard, boards[currentMove+1]))
5921 ForwardInner(currentMove+1);
5923 /* Autoplay the opponent's response.
5924 * if appData.animate was TRUE when Training mode was entered,
5925 * the response will be animated.
5927 saveAnimate = appData.animate;
5928 appData.animate = animateTraining;
5929 ForwardInner(currentMove+1);
5930 appData.animate = saveAnimate;
5932 /* check for the end of the game */
5933 if (currentMove >= forwardMostMove)
5935 gameMode = PlayFromGameFile;
5937 SetTrainingModeOff();
5938 DisplayInformation(_("End of game"));
5943 DisplayError(_("Incorrect move"), 0);
5948 /* Ok, now we know that the move is good, so we can kill
5949 the previous line in Analysis Mode */
5950 if ((gameMode == AnalyzeMode || gameMode == EditGame)
5951 && currentMove < forwardMostMove) {
5952 PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
5955 /* If we need the chess program but it's dead, restart it */
5956 ResurrectChessProgram();
5958 /* A user move restarts a paused game*/
5962 thinkOutput[0] = NULLCHAR;
5964 MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
5967 if(Adjudicate(NULL)) return 1; // [HGM] adjudicate: take care of automtic game end
5969 if (gameMode == BeginningOfGame)
5971 if (appData.noChessProgram)
5973 gameMode = EditGame;
5979 gameMode = MachinePlaysBlack;
5982 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
5986 sprintf(buf, "name %s\n", gameInfo.white);
5987 SendToProgram(buf, &first);
5995 /* Relay move to ICS or chess engine */
5997 if (appData.icsActive) {
5998 if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
5999 gameMode == IcsExamining) {
6000 if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
6001 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
6003 SendMoveToICS(moveType, fromX, fromY, toX, toY);
6005 // also send plain move, in case ICS does not understand atomic claims
6006 SendMoveToICS(moveType, fromX, fromY, toX, toY);
6010 if (first.sendTime && (gameMode == BeginningOfGame ||
6011 gameMode == MachinePlaysWhite ||
6012 gameMode == MachinePlaysBlack)) {
6013 SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
6015 if (gameMode != EditGame && gameMode != PlayFromGameFile) {
6016 // [HGM] book: if program might be playing, let it use book
6017 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
6018 first.maybeThinking = TRUE;
6019 } else SendMoveToProgram(forwardMostMove-1, &first);
6020 if (currentMove == cmailOldMove + 1) {
6021 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
6025 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6030 switch (MateTest(boards[currentMove], PosFlags(currentMove)) )
6037 if (WhiteOnMove(currentMove)) {
6038 GameEnds(BlackWins, "Black mates", GE_PLAYER);
6040 GameEnds(WhiteWins, "White mates", GE_PLAYER);
6044 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
6049 case MachinePlaysBlack:
6050 case MachinePlaysWhite:
6051 /* disable certain menu options while machine is thinking */
6052 SetMachineThinkingEnables();
6058 userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
6061 { // [HGM] book: simulate book reply
6062 static char bookMove[MSG_SIZ]; // a bit generous?
6065 programStats.nodes = programStats.depth = programStats.time =
6066 programStats.score = programStats.got_only_move = 0;
6067 sprintf(programStats.movelist, "%s (xbook)", bookHit);
6069 strcpy(bookMove, "move ");
6070 strcat(bookMove, bookHit);
6071 HandleMachineMove(bookMove, &first);
6078 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
6079 int fromX, fromY, toX, toY;
6082 /* [HGM] This routine was added to allow calling of its two logical
6083 parts from other modules in the old way. Before, UserMoveEvent()
6084 automatically called FinishMove() if the move was OK, and returned
6085 otherwise. I separated the two, in order to make it possible to
6086 slip a promotion popup in between. But that it always needs two
6087 calls, to the first part, (now called UserMoveTest() ), and to
6088 FinishMove if the first part succeeded. Calls that do not need
6089 to do anything in between, can call this routine the old way.
6091 ChessMove moveType = UserMoveTest(fromX, fromY, toX, toY, promoChar, FALSE);
6092 if(appData.debugMode) fprintf(debugFP, "moveType 4 = %d, promochar = %x\n", moveType, promoChar);
6093 if(moveType == AmbiguousMove)
6094 DrawPosition(FALSE, boards[currentMove]);
6095 else if(moveType != ImpossibleMove && moveType != Comment)
6096 FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
6099 int hTab[(int)EmptySquare/2+1] = { 1,1,1,1,1,1,2,1,2,3,2,3,3,3,2,3,4,3,3,4,4,3,4 };
6100 int wTab[(int)EmptySquare/2+1] = { 1,1,2,3,4,5,3,7,4,3,5,4,4,5,7,5,4,6,6,5,5,7,6 };
6102 int promotionChoice = 0;
6105 PiecePopUp(int x, int y)
6107 int i, j, h, w, nWhite=0, nBlack=0;
6108 ChessSquare list[EmptySquare];
6109 for(i=0; i<EmptySquare/2; i++) {
6110 if(PieceToChar(i) != '.') list[nWhite++] = i;
6111 if(PieceToChar(i+EmptySquare/2) != '.') list[EmptySquare - ++nBlack] = i + EmptySquare/2;
6113 CopyBoard(promoBoard, boards[currentMove]);
6114 for(i=0; i<BOARD_HEIGHT; i++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++) promoBoard[i][j] = EmptySquare;
6115 j = nWhite + nBlack + 1;
6116 h = sqrt((j+1)/2 + 1.); w = (j+h-1)/h;
6117 if(w>BOARD_RGHT-BOARD_LEFT) { w = BOARD_RGHT - BOARD_LEFT; h = (j+w-1)/w; }
6118 for(i=0; i<nWhite; i++) promoBoard[i/w][BOARD_LEFT+i%w] = list[nWhite-1-i];
6119 if(h==2 && nWhite == nBlack)
6120 for(i=0; i<nWhite; i++) promoBoard[1][BOARD_LEFT+i%w] = list[EmptySquare-nBlack+i];
6122 for(i=0; i<nBlack; i++) promoBoard[h-1-i/w][BOARD_LEFT+w-1-i%w] = list[EmptySquare-nBlack+i];
6123 promotionChoice = 3;
6125 PromoDialog(h, w, promoBoard, TRUE, _("Select piece:"), x, y);
6129 PromoPopUp(ChessSquare piece)
6130 { // determine the layout of the piece-choice dialog
6132 ChessSquare list[EmptySquare];
6134 for(i=0; i<BOARD_HEIGHT; i++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++) promoBoard[i][j] = EmptySquare;
6135 if(gameInfo.variant == VariantShogi) {
6136 // non-Pawn promotes; must be shogi
6137 h = 1; w = 1; promoBoard[0][BOARD_LEFT+0] = piece;
6138 if(PieceToChar(PROMOTED piece) != '.') {
6139 // promoted version is enabled
6140 w = 2; promoBoard[0][BOARD_LEFT+1] = PROMOTED piece;
6143 // Pawn, promotes to any enabled other piece
6145 for(i=1; i<EmptySquare/2; i++) {
6146 if(PieceToChar(piece+i) != '.'
6147 && PieceToChar(piece + i) != '~' // suppress bughouse true pieces
6149 list[w++] = piece+i; nr++;
6152 if(appData.testLegality && gameInfo.variant != VariantSuicide
6153 && gameInfo.variant != VariantGiveaway) nr--,w--; // remove King
6154 h = hTab[nr]; w = wTab[nr]; // factorize with nice ratio
6155 for(i=0; i < nr; i++) promoBoard[i/w][BOARD_LEFT+i%w] = list[i]; // layout
6157 promotionChoice = 2; // wait for click on board
6158 PromoDialog(h, w, promoBoard, FALSE, _("Promote to:"), -1, -1);
6162 Mark(board, flags, kind, rf, ff, rt, ft, closure)
6169 typedef char Markers[BOARD_RANKS][BOARD_FILES];
6170 Markers *m = (Markers *) closure;
6171 if(rf == fromY && ff == fromX)
6172 (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
6173 || kind == WhiteCapturesEnPassant
6174 || kind == BlackCapturesEnPassant);
6175 else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
6179 MarkTargetSquares(int clear)
6182 if(!appData.markers || !appData.highlightDragging ||
6183 !appData.testLegality || gameMode == EditPosition) return;
6185 for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) marker[y][x] = 0;
6188 GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker);
6189 if(PosFlags(0) & F_MANDATORY_CAPTURE) {
6190 for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
6192 for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
6195 DrawPosition(TRUE, NULL);
6198 void LeftClick(ClickType clickType, int xPix, int yPix)
6201 Boolean saveAnimate;
6202 static int second = 0;
6203 char promoChoice = NULLCHAR;
6205 if(appData.seekGraph && appData.icsActive && loggedOn &&
6206 (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
6207 SeekGraphClick(clickType, xPix, yPix, 0);
6211 if (clickType == Press) ErrorPopDown();
6212 MarkTargetSquares(1);
6214 x = EventToSquare(xPix, BOARD_WIDTH);
6215 y = EventToSquare(yPix, BOARD_HEIGHT);
6216 if (!flipView && y >= 0) {
6217 y = BOARD_HEIGHT - 1 - y;
6219 if (flipView && x >= 0) {
6220 x = BOARD_WIDTH - 1 - x;
6223 if(promotionChoice) { // we are waiting for a click to indicate promotion piece
6224 ChessSquare p = EmptySquare; Boolean inHoldings;
6225 if(promotionChoice == 3) {
6226 if(clickType == Press) EditPositionMenuEvent(promoBoard[y][x], fromX, fromY);
6227 else if(clickType == Release) promotionChoice = 0;
6231 if(clickType == Release) return; // ignore upclick of click-click destination
6232 if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
6233 inHoldings = gameInfo.holdingsWidth &&
6234 (WhiteOnMove(currentMove)
6235 ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y > 0
6236 : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT-1);
6237 // click in right holdings, for determining promotion piece
6238 if(promotionChoice == 1 && inHoldings || promotionChoice == 2 && x >= BOARD_LEFT && x < BOARD_RGHT) {
6239 p = promoBoard[y][x];
6240 if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
6241 if(p != EmptySquare) {
6242 char promoChar = PieceToChar(p);
6243 if(gameInfo.variant == VariantShogi && promoChar != '+') promoChar = '=';
6244 FinishMove(NormalMove, fromX, fromY, toX, toY, ToLower(promoChar));
6246 promotionChoice = 0;
6250 promotionChoice = 0; // only one chance: if click not OK it is interpreted as cancel
6251 DrawPosition(FALSE, boards[currentMove]);
6255 /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
6256 if(clickType == Press
6257 && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
6258 || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
6259 || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
6263 if(!appData.oneClick || !OnlyMove(&x, &y)) {
6264 if (clickType == Press) {
6266 if (OKToStartUserMove(x, y)) {
6270 MarkTargetSquares(0);
6271 DragPieceBegin(xPix, yPix);
6272 if (appData.highlightDragging) {
6273 SetHighlights(x, y, -1, -1);
6282 if (clickType == Press && gameMode != EditPosition) {
6287 // ignore off-board to clicks
6288 if(y < 0 || x < 0) return;
6290 /* Check if clicking again on the same color piece */
6291 fromP = boards[currentMove][fromY][fromX];
6292 toP = boards[currentMove][y][x];
6293 frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom;
6294 if ((WhitePawn <= fromP && fromP <= WhiteKing &&
6295 WhitePawn <= toP && toP <= WhiteKing &&
6296 !(fromP == WhiteKing && toP == WhiteRook && frc) &&
6297 !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
6298 (BlackPawn <= fromP && fromP <= BlackKing &&
6299 BlackPawn <= toP && toP <= BlackKing &&
6300 !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
6301 !(fromP == BlackKing && toP == BlackRook && frc))) {
6302 /* Clicked again on same color piece -- changed his mind */
6303 second = (x == fromX && y == fromY);
6304 if (appData.highlightDragging) {
6305 SetHighlights(x, y, -1, -1);
6309 if (OKToStartUserMove(x, y)) {
6312 MarkTargetSquares(0);
6313 DragPieceBegin(xPix, yPix);
6317 // ignore clicks on holdings
6318 if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
6321 if (clickType == Release && x == fromX && y == fromY) {
6322 DragPieceEnd(xPix, yPix);
6323 if (appData.animateDragging) {
6324 /* Undo animation damage if any */
6325 DrawPosition(FALSE, NULL);
6328 /* Second up/down in same square; just abort move */
6333 ClearPremoveHighlights();
6335 /* First upclick in same square; start click-click mode */
6336 SetHighlights(x, y, -1, -1);
6341 /* we now have a different from- and (possibly off-board) to-square */
6342 /* Completed move */
6345 saveAnimate = appData.animate;
6346 if (clickType == Press) {
6347 /* Finish clickclick move */
6348 if (appData.animate || appData.highlightLastMove) {
6349 SetHighlights(fromX, fromY, toX, toY);
6354 /* Finish drag move */
6355 if (appData.highlightLastMove) {
6356 SetHighlights(fromX, fromY, toX, toY);
6360 DragPieceEnd(xPix, yPix);
6361 /* Don't animate move and drag both */
6362 appData.animate = FALSE;
6365 // moves into holding are invalid for now (except in EditPosition, adapting to-square)
6366 if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
6367 ChessSquare piece = boards[currentMove][fromY][fromX];
6368 if(gameMode == EditPosition && piece != EmptySquare &&
6369 fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
6372 if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
6373 n = PieceToNumber(piece - (int)BlackPawn);
6374 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
6375 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
6376 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
6378 if(x == BOARD_RGHT+1 && piece < BlackPawn) {
6379 n = PieceToNumber(piece);
6380 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
6381 boards[currentMove][n][BOARD_WIDTH-1] = piece;
6382 boards[currentMove][n][BOARD_WIDTH-2]++;
6384 boards[currentMove][fromY][fromX] = EmptySquare;
6388 DrawPosition(TRUE, boards[currentMove]);
6392 // off-board moves should not be highlighted
6393 if(x < 0 || x < 0) ClearHighlights();
6395 if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice)) {
6396 SetHighlights(fromX, fromY, toX, toY);
6397 if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
6398 // [HGM] super: promotion to captured piece selected from holdings
6399 ChessSquare p = boards[currentMove][fromY][fromX];
6400 promotionChoice = 1;
6401 CopyBoard(promoBoard, boards[currentMove]);
6402 // kludge follows to temporarily execute move on display, without promoting yet
6403 promoBoard[fromY][fromX] = EmptySquare; // move Pawn to 8th rank
6404 promoBoard[toY][toX] = p;
6405 DrawPosition(FALSE, promoBoard);
6406 DisplayMessage("Click in holdings to choose piece", "");
6409 CopyBoard(promoBoard, boards[currentMove]);
6410 PromoPopUp(boards[currentMove][fromY][fromX]);
6412 UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
6413 if (!appData.highlightLastMove || gotPremove) ClearHighlights();
6414 if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
6417 appData.animate = saveAnimate;
6418 if (appData.animate || appData.animateDragging) {
6419 /* Undo animation damage if needed */
6420 DrawPosition(FALSE, NULL);
6424 int RightClick(ClickType action, int x, int y, int *xx, int *yy)
6425 { // front-end-free part taken out of PieceMenuPopup
6426 int whichMenu; int xSqr, ySqr;
6428 if(seekGraphUp) { // [HGM] seekgraph
6429 if(action == Press) SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
6430 if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
6434 xSqr = EventToSquare(x, BOARD_WIDTH);
6435 ySqr = EventToSquare(y, BOARD_HEIGHT);
6437 xSqr = BOARD_WIDTH - 1 - xSqr;
6439 ySqr = BOARD_HEIGHT - 1 - ySqr;
6440 if(promotionChoice == 3 && action == Release) {
6441 EditPositionMenuEvent(promoBoard[ySqr][xSqr], fromX, fromY);
6443 promotionChoice = 0;
6446 if (action == Release) UnLoadPV(); // [HGM] pv
6447 if (action != Press) return -2; // return code to be ignored
6450 if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;
6452 if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;
6453 if (xSqr < 0 || ySqr < 0) return -1;
6454 whichMenu = 0; // edit-position menu
6457 if(!appData.icsEngineAnalyze) return -1;
6458 case IcsPlayingWhite:
6459 case IcsPlayingBlack:
6460 if(!appData.zippyPlay) goto noZip;
6463 case MachinePlaysWhite:
6464 case MachinePlaysBlack:
6465 case TwoMachinesPlay: // [HGM] pv: use for showing PV
6466 if (!appData.dropMenu) {
6468 return 2; // flag front-end to grab mouse events
6470 if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
6471 gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
6474 if (xSqr < 0 || ySqr < 0) return -1;
6475 if (!appData.dropMenu || appData.testLegality &&
6476 gameInfo.variant != VariantBughouse &&
6477 gameInfo.variant != VariantCrazyhouse) return -1;
6478 whichMenu = 1; // drop menu
6484 if (((*xx = xSqr) < 0) ||
6485 ((*yy = ySqr) < 0)) {
6490 fromX = *xx; fromY = *yy;
6491 if(whichMenu == 0) { PiecePopUp(x, y); return -1; } // suppress EditPosition menu
6496 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
6498 // char * hint = lastHint;
6499 FrontEndProgramStats stats;
6501 stats.which = cps == &first ? 0 : 1;
6502 stats.depth = cpstats->depth;
6503 stats.nodes = cpstats->nodes;
6504 stats.score = cpstats->score;
6505 stats.time = cpstats->time;
6506 stats.pv = cpstats->movelist;
6507 stats.hint = lastHint;
6508 stats.an_move_index = 0;
6509 stats.an_move_count = 0;
6511 if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
6512 stats.hint = cpstats->move_name;
6513 stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
6514 stats.an_move_count = cpstats->nr_moves;
6517 if(stats.pv && stats.pv[0]) strcpy(lastPV[stats.which], stats.pv); // [HGM] pv: remember last PV of each
6519 SetProgramStats( &stats );
6523 Adjudicate(ChessProgramState *cps)
6524 { // [HGM] some adjudications useful with buggy engines
6525 // [HGM] adjudicate: made into separate routine, which now can be called after every move
6526 // In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
6527 // Actually ending the game is now based on the additional internal condition canAdjudicate.
6528 // Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
6529 int k, count = 0; static int bare = 1;
6530 ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
6531 Boolean canAdjudicate = !appData.icsActive;
6533 // most tests only when we understand the game, i.e. legality-checking on, and (for the time being) no piece drops
6534 if(gameInfo.holdingsSize == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
6535 if( appData.testLegality )
6536 { /* [HGM] Some more adjudications for obstinate engines */
6537 int NrWN=0, NrBN=0, NrWB=0, NrBB=0, NrWR=0, NrBR=0,
6538 NrWQ=0, NrBQ=0, NrW=0, NrK=0, bishopsColor = 0,
6539 NrPieces=0, NrPawns=0, PawnAdvance=0, i, j;
6540 static int moveCount = 6;
6542 char *reason = NULL;
6545 /* Count what is on board. */
6546 for(i=0; i<BOARD_HEIGHT; i++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
6547 { ChessSquare p = boards[forwardMostMove][i][j];
6551 { /* count B,N,R and other of each side */
6554 NrK++; break; // [HGM] atomic: count Kings
6558 case WhiteFerz: // [HGM] shatranj: kludge to mke it work in shatranj
6559 bishopsColor |= 1 << ((i^j)&1);
6564 case BlackFerz: // [HGM] shatranj: kludge to mke it work in shatranj
6565 bishopsColor |= 1 << ((i^j)&1);
6580 PawnAdvance += m; NrPawns++;
6582 NrPieces += (p != EmptySquare);
6583 NrW += ((int)p < (int)BlackPawn);
6584 if(gameInfo.variant == VariantXiangqi &&
6585 (p == WhiteFerz || p == WhiteAlfil || p == BlackFerz || p == BlackAlfil)) {
6586 NrPieces--; // [HGM] XQ: do not count purely defensive pieces
6587 NrW -= ((int)p < (int)BlackPawn);
6591 /* Some material-based adjudications that have to be made before stalemate test */
6592 if(gameInfo.variant == VariantAtomic && NrK < 2) {
6593 // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
6594 boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
6595 if(canAdjudicate && appData.checkMates) {
6597 SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
6598 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6599 GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
6600 "Xboard adjudication: King destroyed", GE_XBOARD );
6605 /* Bare King in Shatranj (loses) or Losers (wins) */
6606 if( NrW == 1 || NrPieces - NrW == 1) {
6607 if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
6608 boards[forwardMostMove][EP_STATUS] = EP_WINS; // mark as win, so it becomes claimable
6609 if(canAdjudicate && appData.checkMates) {
6611 SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
6612 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6613 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
6614 "Xboard adjudication: Bare king", GE_XBOARD );
6618 if( gameInfo.variant == VariantShatranj && --bare < 0)
6620 boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
6621 if(canAdjudicate && appData.checkMates) {
6622 /* but only adjudicate if adjudication enabled */
6624 SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
6625 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6626 GameEnds( NrW > 1 ? WhiteWins : NrPieces - NrW > 1 ? BlackWins : GameIsDrawn,
6627 "Xboard adjudication: Bare king", GE_XBOARD );
6634 // don't wait for engine to announce game end if we can judge ourselves
6635 switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
6637 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
6638 int i, checkCnt = 0; // (should really be done by making nr of checks part of game state)
6639 for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
6640 if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
6643 reason = "Xboard adjudication: 3rd check";
6644 boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
6654 reason = "Xboard adjudication: Stalemate";
6655 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
6656 boards[forwardMostMove][EP_STATUS] = EP_STALEMATE; // default result for stalemate is draw
6657 if(gameInfo.variant == VariantLosers || gameInfo.variant == VariantGiveaway) // [HGM] losers:
6658 boards[forwardMostMove][EP_STATUS] = EP_WINS; // in these variants stalemated is always a win
6659 else if(gameInfo.variant == VariantSuicide) // in suicide it depends
6660 boards[forwardMostMove][EP_STATUS] = NrW == NrPieces-NrW ? EP_STALEMATE :
6661 ((NrW < NrPieces-NrW) != WhiteOnMove(forwardMostMove) ?
6662 EP_CHECKMATE : EP_WINS);
6663 else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
6664 boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
6668 reason = "Xboard adjudication: Checkmate";
6669 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
6673 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
6675 result = GameIsDrawn; break;
6677 result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
6679 result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
6681 result = (ChessMove) 0;
6683 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
6685 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6686 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6687 GameEnds( result, reason, GE_XBOARD );
6691 /* Next absolutely insufficient mating material. */
6692 if( NrPieces == 2 || gameInfo.variant != VariantXiangqi &&
6693 gameInfo.variant != VariantShatranj && // [HGM] baring will remain possible
6694 (NrPieces == 3 && NrWN+NrBN+NrWB+NrBB == 1 ||
6695 NrPieces == NrBB+NrWB+2 && bishopsColor != 3)) // [HGM] all Bishops (Ferz!) same color
6696 { /* KBK, KNK, KK of KBKB with like Bishops */
6698 /* always flag draws, for judging claims */
6699 boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
6701 if(canAdjudicate && appData.materialDraws) {
6702 /* but only adjudicate them if adjudication enabled */
6703 if(engineOpponent) {
6704 SendToProgram("force\n", engineOpponent); // suppress reply
6705 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
6707 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6708 GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
6713 /* Then some trivial draws (only adjudicate, cannot be claimed) */
6715 ( NrWR == 1 && NrBR == 1 /* KRKR */
6716 || NrWQ==1 && NrBQ==1 /* KQKQ */
6717 || NrWN==2 || NrBN==2 /* KNNK */
6718 || NrWN+NrWB == 1 && NrBN+NrBB == 1 /* KBKN, KBKB, KNKN */
6720 if(canAdjudicate && --moveCount < 0 && appData.trivialDraws)
6721 { /* if the first 3 moves do not show a tactical win, declare draw */
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: Trivial draw", GE_XBOARD );
6730 } else moveCount = 6;
6734 if (appData.debugMode) { int i;
6735 fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
6736 forwardMostMove, backwardMostMove, boards[backwardMostMove][EP_STATUS],
6737 appData.drawRepeats);
6738 for( i=forwardMostMove; i>=backwardMostMove; i-- )
6739 fprintf(debugFP, "%d ep=%d\n", i, (signed char)boards[i][EP_STATUS]);
6743 // Repetition draws and 50-move rule can be applied independently of legality testing
6745 /* Check for rep-draws */
6747 for(k = forwardMostMove-2;
6748 k>=backwardMostMove && k>=forwardMostMove-100 &&
6749 (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
6750 (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE;
6753 if(CompareBoards(boards[k], boards[forwardMostMove])) {
6754 /* compare castling rights */
6755 if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
6756 (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
6757 rights++; /* King lost rights, while rook still had them */
6758 if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
6759 if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
6760 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
6761 rights++; /* but at least one rook lost them */
6763 if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
6764 (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
6766 if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
6767 if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
6768 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
6771 if( canAdjudicate && rights == 0 && ++count > appData.drawRepeats-2
6772 && appData.drawRepeats > 1) {
6773 /* adjudicate after user-specified nr of repeats */
6774 if(engineOpponent) {
6775 SendToProgram("force\n", engineOpponent); // suppress reply
6776 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6778 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6779 if(gameInfo.variant == VariantXiangqi && appData.testLegality) {
6780 // [HGM] xiangqi: check for forbidden perpetuals
6781 int m, ourPerpetual = 1, hisPerpetual = 1;
6782 for(m=forwardMostMove; m>k; m-=2) {
6783 if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
6784 ourPerpetual = 0; // the current mover did not always check
6785 if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
6786 hisPerpetual = 0; // the opponent did not always check
6788 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
6789 ourPerpetual, hisPerpetual);
6790 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
6791 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
6792 "Xboard adjudication: perpetual checking", GE_XBOARD );
6795 if(hisPerpetual && !ourPerpetual) // he is checking us, but did not repeat yet
6796 break; // (or we would have caught him before). Abort repetition-checking loop.
6797 // Now check for perpetual chases
6798 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
6799 hisPerpetual = PerpetualChase(k, forwardMostMove);
6800 ourPerpetual = PerpetualChase(k+1, forwardMostMove);
6801 if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
6802 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
6803 "Xboard adjudication: perpetual chasing", GE_XBOARD );
6806 if(hisPerpetual && !ourPerpetual) // he is chasing us, but did not repeat yet
6807 break; // Abort repetition-checking loop.
6809 // if neither of us is checking or chasing all the time, or both are, it is draw
6811 GameEnds( GameIsDrawn, "Xboard adjudication: repetition draw", GE_XBOARD );
6814 if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
6815 boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
6819 /* Now we test for 50-move draws. Determine ply count */
6820 count = forwardMostMove;
6821 /* look for last irreversble move */
6822 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
6824 /* if we hit starting position, add initial plies */
6825 if( count == backwardMostMove )
6826 count -= initialRulePlies;
6827 count = forwardMostMove - count;
6829 boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
6830 /* this is used to judge if draw claims are legal */
6831 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
6832 if(engineOpponent) {
6833 SendToProgram("force\n", engineOpponent); // suppress reply
6834 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6836 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6837 GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
6841 /* if draw offer is pending, treat it as a draw claim
6842 * when draw condition present, to allow engines a way to
6843 * claim draws before making their move to avoid a race
6844 * condition occurring after their move
6846 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
6848 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
6849 p = "Draw claim: 50-move rule";
6850 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
6851 p = "Draw claim: 3-fold repetition";
6852 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
6853 p = "Draw claim: insufficient mating material";
6854 if( p != NULL && canAdjudicate) {
6855 if(engineOpponent) {
6856 SendToProgram("force\n", engineOpponent); // suppress reply
6857 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6859 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6860 GameEnds( GameIsDrawn, p, GE_XBOARD );
6865 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
6866 if(engineOpponent) {
6867 SendToProgram("force\n", engineOpponent); // suppress reply
6868 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6870 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6871 GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
6877 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
6878 { // [HGM] book: this routine intercepts moves to simulate book replies
6879 char *bookHit = NULL;
6881 //first determine if the incoming move brings opponent into his book
6882 if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
6883 bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
6884 if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
6885 if(bookHit != NULL && !cps->bookSuspend) {
6886 // make sure opponent is not going to reply after receiving move to book position
6887 SendToProgram("force\n", cps);
6888 cps->bookSuspend = TRUE; // flag indicating it has to be restarted
6890 if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
6891 // now arrange restart after book miss
6893 // after a book hit we never send 'go', and the code after the call to this routine
6894 // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
6896 if (cps->useUsermove) sprintf(buf, "usermove "); // sorry, no SAN yet :(
6897 sprintf(buf, "%s\n", bookHit); // force book move into program supposed to play it
6898 SendToProgram(buf, cps);
6899 if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
6900 } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
6901 SendToProgram("go\n", cps);
6902 cps->bookSuspend = FALSE; // after a 'go' we are never suspended
6903 } else { // 'go' might be sent based on 'firstMove' after this routine returns
6904 if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
6905 SendToProgram("go\n", cps);
6906 cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
6908 return bookHit; // notify caller of hit, so it can take action to send move to opponent
6912 ChessProgramState *savedState;
6913 void DeferredBookMove(void)
6915 if(savedState->lastPing != savedState->lastPong)
6916 ScheduleDelayedEvent(DeferredBookMove, 10);
6918 HandleMachineMove(savedMessage, savedState);
6922 HandleMachineMove(message, cps)
6924 ChessProgramState *cps;
6926 char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
6927 char realname[MSG_SIZ];
6928 int fromX, fromY, toX, toY;
6937 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
6939 * Kludge to ignore BEL characters
6941 while (*message == '\007') message++;
6944 * [HGM] engine debug message: ignore lines starting with '#' character
6946 if(cps->debug && *message == '#') return;
6949 * Look for book output
6951 if (cps == &first && bookRequested) {
6952 if (message[0] == '\t' || message[0] == ' ') {
6953 /* Part of the book output is here; append it */
6954 strcat(bookOutput, message);
6955 strcat(bookOutput, " \n");
6957 } else if (bookOutput[0] != NULLCHAR) {
6958 /* All of book output has arrived; display it */
6959 char *p = bookOutput;
6960 while (*p != NULLCHAR) {
6961 if (*p == '\t') *p = ' ';
6964 DisplayInformation(bookOutput);
6965 bookRequested = FALSE;
6966 /* Fall through to parse the current output */
6971 * Look for machine move.
6973 if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
6974 (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
6976 /* This method is only useful on engines that support ping */
6977 if (cps->lastPing != cps->lastPong) {
6978 if (gameMode == BeginningOfGame) {
6979 /* Extra move from before last new; ignore */
6980 if (appData.debugMode) {
6981 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
6984 if (appData.debugMode) {
6985 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
6986 cps->which, gameMode);
6989 SendToProgram("undo\n", cps);
6995 case BeginningOfGame:
6996 /* Extra move from before last reset; ignore */
6997 if (appData.debugMode) {
6998 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7005 /* Extra move after we tried to stop. The mode test is
7006 not a reliable way of detecting this problem, but it's
7007 the best we can do on engines that don't support ping.
7009 if (appData.debugMode) {
7010 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7011 cps->which, gameMode);
7013 SendToProgram("undo\n", cps);
7016 case MachinePlaysWhite:
7017 case IcsPlayingWhite:
7018 machineWhite = TRUE;
7021 case MachinePlaysBlack:
7022 case IcsPlayingBlack:
7023 machineWhite = FALSE;
7026 case TwoMachinesPlay:
7027 machineWhite = (cps->twoMachinesColor[0] == 'w');
7030 if (WhiteOnMove(forwardMostMove) != machineWhite) {
7031 if (appData.debugMode) {
7033 "Ignoring move out of turn by %s, gameMode %d"
7034 ", forwardMost %d\n",
7035 cps->which, gameMode, forwardMostMove);
7040 if (appData.debugMode) { int f = forwardMostMove;
7041 fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
7042 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
7043 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
7045 if(cps->alphaRank) AlphaRank(machineMove, 4);
7046 if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
7047 &fromX, &fromY, &toX, &toY, &promoChar)) {
7048 /* Machine move could not be parsed; ignore it. */
7049 sprintf(buf1, _("Illegal move \"%s\" from %s machine"),
7050 machineMove, cps->which);
7051 DisplayError(buf1, 0);
7052 sprintf(buf1, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
7053 machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
7054 if (gameMode == TwoMachinesPlay) {
7055 GameEnds(machineWhite ? BlackWins : WhiteWins,
7061 /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
7062 /* So we have to redo legality test with true e.p. status here, */
7063 /* to make sure an illegal e.p. capture does not slip through, */
7064 /* to cause a forfeit on a justified illegal-move complaint */
7065 /* of the opponent. */
7066 if( gameMode==TwoMachinesPlay && appData.testLegality
7067 && fromY != DROP_RANK /* [HGM] temporary; should still add legality test for drops */
7070 moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
7071 fromY, fromX, toY, toX, promoChar);
7072 if (appData.debugMode) {
7074 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
7075 boards[forwardMostMove][CASTLING][i], castlingRank[i]);
7076 fprintf(debugFP, "castling rights\n");
7078 if(moveType == IllegalMove) {
7079 sprintf(buf1, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
7080 machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
7081 GameEnds(machineWhite ? BlackWins : WhiteWins,
7084 } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
7085 /* [HGM] Kludge to handle engines that send FRC-style castling
7086 when they shouldn't (like TSCP-Gothic) */
7088 case WhiteASideCastleFR:
7089 case BlackASideCastleFR:
7091 currentMoveString[2]++;
7093 case WhiteHSideCastleFR:
7094 case BlackHSideCastleFR:
7096 currentMoveString[2]--;
7098 default: ; // nothing to do, but suppresses warning of pedantic compilers
7101 hintRequested = FALSE;
7102 lastHint[0] = NULLCHAR;
7103 bookRequested = FALSE;
7104 /* Program may be pondering now */
7105 cps->maybeThinking = TRUE;
7106 if (cps->sendTime == 2) cps->sendTime = 1;
7107 if (cps->offeredDraw) cps->offeredDraw--;
7109 /* currentMoveString is set as a side-effect of ParseOneMove */
7110 strcpy(machineMove, currentMoveString);
7111 strcat(machineMove, "\n");
7112 strcpy(moveList[forwardMostMove], machineMove);
7114 MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
7116 /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
7117 if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
7120 while( count < adjudicateLossPlies ) {
7121 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
7124 score = -score; /* Flip score for winning side */
7127 if( score > adjudicateLossThreshold ) {
7134 if( count >= adjudicateLossPlies ) {
7135 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7137 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7138 "Xboard adjudication",
7145 if(Adjudicate(cps)) return; // [HGM] adjudicate: for all automatic game ends
7148 if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
7150 if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
7151 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
7153 SendMoveToICS(moveType, fromX, fromY, toX, toY);
7155 SendMoveToICS(moveType, fromX, fromY, toX, toY);
7157 if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
7158 char buf[3*MSG_SIZ];
7160 sprintf(buf, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
7161 programStats.score / 100.,
7163 programStats.time / 100.,
7164 (unsigned int)programStats.nodes,
7165 (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
7166 programStats.movelist);
7168 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
7173 /* [AS] Save move info and clear stats for next move */
7174 pvInfoList[ forwardMostMove-1 ].score = programStats.score;
7175 pvInfoList[ forwardMostMove-1 ].depth = programStats.depth;
7176 pvInfoList[ forwardMostMove-1 ].time = programStats.time; // [HGM] PGNtime: take time from engine stats
7177 ClearProgramStats();
7178 thinkOutput[0] = NULLCHAR;
7179 hiddenThinkOutputState = 0;
7182 if (gameMode == TwoMachinesPlay) {
7183 /* [HGM] relaying draw offers moved to after reception of move */
7184 /* and interpreting offer as claim if it brings draw condition */
7185 if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
7186 SendToProgram("draw\n", cps->other);
7188 if (cps->other->sendTime) {
7189 SendTimeRemaining(cps->other,
7190 cps->other->twoMachinesColor[0] == 'w');
7192 bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
7193 if (firstMove && !bookHit) {
7195 if (cps->other->useColors) {
7196 SendToProgram(cps->other->twoMachinesColor, cps->other);
7198 SendToProgram("go\n", cps->other);
7200 cps->other->maybeThinking = TRUE;
7203 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7205 if (!pausing && appData.ringBellAfterMoves) {
7210 * Reenable menu items that were disabled while
7211 * machine was thinking
7213 if (gameMode != TwoMachinesPlay)
7214 SetUserThinkingEnables();
7216 // [HGM] book: after book hit opponent has received move and is now in force mode
7217 // force the book reply into it, and then fake that it outputted this move by jumping
7218 // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
7220 static char bookMove[MSG_SIZ]; // a bit generous?
7222 strcpy(bookMove, "move ");
7223 strcat(bookMove, bookHit);
7226 programStats.nodes = programStats.depth = programStats.time =
7227 programStats.score = programStats.got_only_move = 0;
7228 sprintf(programStats.movelist, "%s (xbook)", bookHit);
7230 if(cps->lastPing != cps->lastPong) {
7231 savedMessage = message; // args for deferred call
7233 ScheduleDelayedEvent(DeferredBookMove, 10);
7242 /* Set special modes for chess engines. Later something general
7243 * could be added here; for now there is just one kludge feature,
7244 * needed because Crafty 15.10 and earlier don't ignore SIGINT
7245 * when "xboard" is given as an interactive command.
7247 if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
7248 cps->useSigint = FALSE;
7249 cps->useSigterm = FALSE;
7251 if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
7252 ParseFeatures(message+8, cps);
7253 return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
7256 /* [HGM] Allow engine to set up a position. Don't ask me why one would
7257 * want this, I was asked to put it in, and obliged.
7259 if (!strncmp(message, "setboard ", 9)) {
7260 Board initial_position;
7262 GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
7264 if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
7265 DisplayError(_("Bad FEN received from engine"), 0);
7269 CopyBoard(boards[0], initial_position);
7270 initialRulePlies = FENrulePlies;
7271 if(blackPlaysFirst) gameMode = MachinePlaysWhite;
7272 else gameMode = MachinePlaysBlack;
7273 DrawPosition(FALSE, boards[currentMove]);
7279 * Look for communication commands
7281 if (!strncmp(message, "telluser ", 9)) {
7282 DisplayNote(message + 9);
7285 if (!strncmp(message, "tellusererror ", 14)) {
7287 DisplayError(message + 14, 0);
7290 if (!strncmp(message, "tellopponent ", 13)) {
7291 if (appData.icsActive) {
7293 snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
7297 DisplayNote(message + 13);
7301 if (!strncmp(message, "tellothers ", 11)) {
7302 if (appData.icsActive) {
7304 snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
7310 if (!strncmp(message, "tellall ", 8)) {
7311 if (appData.icsActive) {
7313 snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
7317 DisplayNote(message + 8);
7321 if (strncmp(message, "warning", 7) == 0) {
7322 /* Undocumented feature, use tellusererror in new code */
7323 DisplayError(message, 0);
7326 if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
7327 strcpy(realname, cps->tidy);
7328 strcat(realname, " query");
7329 AskQuestion(realname, buf2, buf1, cps->pr);
7332 /* Commands from the engine directly to ICS. We don't allow these to be
7333 * sent until we are logged on. Crafty kibitzes have been known to
7334 * interfere with the login process.
7337 if (!strncmp(message, "tellics ", 8)) {
7338 SendToICS(message + 8);
7342 if (!strncmp(message, "tellicsnoalias ", 15)) {
7343 SendToICS(ics_prefix);
7344 SendToICS(message + 15);
7348 /* The following are for backward compatibility only */
7349 if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
7350 !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
7351 SendToICS(ics_prefix);
7357 if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
7361 * If the move is illegal, cancel it and redraw the board.
7362 * Also deal with other error cases. Matching is rather loose
7363 * here to accommodate engines written before the spec.
7365 if (strncmp(message + 1, "llegal move", 11) == 0 ||
7366 strncmp(message, "Error", 5) == 0) {
7367 if (StrStr(message, "name") ||
7368 StrStr(message, "rating") || StrStr(message, "?") ||
7369 StrStr(message, "result") || StrStr(message, "board") ||
7370 StrStr(message, "bk") || StrStr(message, "computer") ||
7371 StrStr(message, "variant") || StrStr(message, "hint") ||
7372 StrStr(message, "random") || StrStr(message, "depth") ||
7373 StrStr(message, "accepted")) {
7376 if (StrStr(message, "protover")) {
7377 /* Program is responding to input, so it's apparently done
7378 initializing, and this error message indicates it is
7379 protocol version 1. So we don't need to wait any longer
7380 for it to initialize and send feature commands. */
7381 FeatureDone(cps, 1);
7382 cps->protocolVersion = 1;
7385 cps->maybeThinking = FALSE;
7387 if (StrStr(message, "draw")) {
7388 /* Program doesn't have "draw" command */
7389 cps->sendDrawOffers = 0;
7392 if (cps->sendTime != 1 &&
7393 (StrStr(message, "time") || StrStr(message, "otim"))) {
7394 /* Program apparently doesn't have "time" or "otim" command */
7398 if (StrStr(message, "analyze")) {
7399 cps->analysisSupport = FALSE;
7400 cps->analyzing = FALSE;
7402 sprintf(buf2, _("%s does not support analysis"), cps->tidy);
7403 DisplayError(buf2, 0);
7406 if (StrStr(message, "(no matching move)st")) {
7407 /* Special kludge for GNU Chess 4 only */
7408 cps->stKludge = TRUE;
7409 SendTimeControl(cps, movesPerSession, timeControl,
7410 timeIncrement, appData.searchDepth,
7414 if (StrStr(message, "(no matching move)sd")) {
7415 /* Special kludge for GNU Chess 4 only */
7416 cps->sdKludge = TRUE;
7417 SendTimeControl(cps, movesPerSession, timeControl,
7418 timeIncrement, appData.searchDepth,
7422 if (!StrStr(message, "llegal")) {
7425 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
7426 gameMode == IcsIdle) return;
7427 if (forwardMostMove <= backwardMostMove) return;
7428 if (pausing) PauseEvent();
7429 if(appData.forceIllegal) {
7430 // [HGM] illegal: machine refused move; force position after move into it
7431 SendToProgram("force\n", cps);
7432 if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
7433 // we have a real problem now, as SendBoard will use the a2a3 kludge
7434 // when black is to move, while there might be nothing on a2 or black
7435 // might already have the move. So send the board as if white has the move.
7436 // But first we must change the stm of the engine, as it refused the last move
7437 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
7438 if(WhiteOnMove(forwardMostMove)) {
7439 SendToProgram("a7a6\n", cps); // for the engine black still had the move
7440 SendBoard(cps, forwardMostMove); // kludgeless board
7442 SendToProgram("a2a3\n", cps); // for the engine white still had the move
7443 CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
7444 SendBoard(cps, forwardMostMove+1); // kludgeless board
7446 } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
7447 if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
7448 gameMode == TwoMachinesPlay)
7449 SendToProgram("go\n", cps);
7452 if (gameMode == PlayFromGameFile) {
7453 /* Stop reading this game file */
7454 gameMode = EditGame;
7457 currentMove = --forwardMostMove;
7458 DisplayMove(currentMove-1); /* before DisplayMoveError */
7460 DisplayBothClocks();
7461 sprintf(buf1, _("Illegal move \"%s\" (rejected by %s chess program)"),
7462 parseList[currentMove], cps->which);
7463 DisplayMoveError(buf1);
7464 DrawPosition(FALSE, boards[currentMove]);
7466 /* [HGM] illegal-move claim should forfeit game when Xboard */
7467 /* only passes fully legal moves */
7468 if( appData.testLegality && gameMode == TwoMachinesPlay ) {
7469 GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
7470 "False illegal-move claim", GE_XBOARD );
7474 if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
7475 /* Program has a broken "time" command that
7476 outputs a string not ending in newline.
7482 * If chess program startup fails, exit with an error message.
7483 * Attempts to recover here are futile.
7485 if ((StrStr(message, "unknown host") != NULL)
7486 || (StrStr(message, "No remote directory") != NULL)
7487 || (StrStr(message, "not found") != NULL)
7488 || (StrStr(message, "No such file") != NULL)
7489 || (StrStr(message, "can't alloc") != NULL)
7490 || (StrStr(message, "Permission denied") != NULL)) {
7492 cps->maybeThinking = FALSE;
7493 snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
7494 cps->which, cps->program, cps->host, message);
7495 RemoveInputSource(cps->isr);
7496 DisplayFatalError(buf1, 0, 1);
7501 * Look for hint output
7503 if (sscanf(message, "Hint: %s", buf1) == 1) {
7504 if (cps == &first && hintRequested) {
7505 hintRequested = FALSE;
7506 if (ParseOneMove(buf1, forwardMostMove, &moveType,
7507 &fromX, &fromY, &toX, &toY, &promoChar)) {
7508 (void) CoordsToAlgebraic(boards[forwardMostMove],
7509 PosFlags(forwardMostMove),
7510 fromY, fromX, toY, toX, promoChar, buf1);
7511 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
7512 DisplayInformation(buf2);
7514 /* Hint move could not be parsed!? */
7515 snprintf(buf2, sizeof(buf2),
7516 _("Illegal hint move \"%s\"\nfrom %s chess program"),
7518 DisplayError(buf2, 0);
7521 strcpy(lastHint, buf1);
7527 * Ignore other messages if game is not in progress
7529 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
7530 gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
7533 * look for win, lose, draw, or draw offer
7535 if (strncmp(message, "1-0", 3) == 0) {
7536 char *p, *q, *r = "";
7537 p = strchr(message, '{');
7545 GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
7547 } else if (strncmp(message, "0-1", 3) == 0) {
7548 char *p, *q, *r = "";
7549 p = strchr(message, '{');
7557 /* Kludge for Arasan 4.1 bug */
7558 if (strcmp(r, "Black resigns") == 0) {
7559 GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
7562 GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
7564 } else if (strncmp(message, "1/2", 3) == 0) {
7565 char *p, *q, *r = "";
7566 p = strchr(message, '{');
7575 GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
7578 } else if (strncmp(message, "White resign", 12) == 0) {
7579 GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
7581 } else if (strncmp(message, "Black resign", 12) == 0) {
7582 GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
7584 } else if (strncmp(message, "White matches", 13) == 0 ||
7585 strncmp(message, "Black matches", 13) == 0 ) {
7586 /* [HGM] ignore GNUShogi noises */
7588 } else if (strncmp(message, "White", 5) == 0 &&
7589 message[5] != '(' &&
7590 StrStr(message, "Black") == NULL) {
7591 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7593 } else if (strncmp(message, "Black", 5) == 0 &&
7594 message[5] != '(') {
7595 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7597 } else if (strcmp(message, "resign") == 0 ||
7598 strcmp(message, "computer resigns") == 0) {
7600 case MachinePlaysBlack:
7601 case IcsPlayingBlack:
7602 GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
7604 case MachinePlaysWhite:
7605 case IcsPlayingWhite:
7606 GameEnds(BlackWins, "White resigns", GE_ENGINE);
7608 case TwoMachinesPlay:
7609 if (cps->twoMachinesColor[0] == 'w')
7610 GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
7612 GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
7619 } else if (strncmp(message, "opponent mates", 14) == 0) {
7621 case MachinePlaysBlack:
7622 case IcsPlayingBlack:
7623 GameEnds(WhiteWins, "White mates", GE_ENGINE);
7625 case MachinePlaysWhite:
7626 case IcsPlayingWhite:
7627 GameEnds(BlackWins, "Black mates", GE_ENGINE);
7629 case TwoMachinesPlay:
7630 if (cps->twoMachinesColor[0] == 'w')
7631 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7633 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7640 } else if (strncmp(message, "computer mates", 14) == 0) {
7642 case MachinePlaysBlack:
7643 case IcsPlayingBlack:
7644 GameEnds(BlackWins, "Black mates", GE_ENGINE1);
7646 case MachinePlaysWhite:
7647 case IcsPlayingWhite:
7648 GameEnds(WhiteWins, "White mates", GE_ENGINE);
7650 case TwoMachinesPlay:
7651 if (cps->twoMachinesColor[0] == 'w')
7652 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7654 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7661 } else if (strncmp(message, "checkmate", 9) == 0) {
7662 if (WhiteOnMove(forwardMostMove)) {
7663 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7665 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7668 } else if (strstr(message, "Draw") != NULL ||
7669 strstr(message, "game is a draw") != NULL) {
7670 GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
7672 } else if (strstr(message, "offer") != NULL &&
7673 strstr(message, "draw") != NULL) {
7675 if (appData.zippyPlay && first.initDone) {
7676 /* Relay offer to ICS */
7677 SendToICS(ics_prefix);
7678 SendToICS("draw\n");
7681 cps->offeredDraw = 2; /* valid until this engine moves twice */
7682 if (gameMode == TwoMachinesPlay) {
7683 if (cps->other->offeredDraw) {
7684 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
7685 /* [HGM] in two-machine mode we delay relaying draw offer */
7686 /* until after we also have move, to see if it is really claim */
7688 } else if (gameMode == MachinePlaysWhite ||
7689 gameMode == MachinePlaysBlack) {
7690 if (userOfferedDraw) {
7691 DisplayInformation(_("Machine accepts your draw offer"));
7692 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
7694 DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
7701 * Look for thinking output
7703 if ( appData.showThinking // [HGM] thinking: test all options that cause this output
7704 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
7706 int plylev, mvleft, mvtot, curscore, time;
7707 char mvname[MOVE_LEN];
7711 int prefixHint = FALSE;
7712 mvname[0] = NULLCHAR;
7715 case MachinePlaysBlack:
7716 case IcsPlayingBlack:
7717 if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
7719 case MachinePlaysWhite:
7720 case IcsPlayingWhite:
7721 if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
7726 case IcsObserving: /* [DM] icsEngineAnalyze */
7727 if (!appData.icsEngineAnalyze) ignore = TRUE;
7729 case TwoMachinesPlay:
7730 if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
7741 if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
7742 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
7744 if (plyext != ' ' && plyext != '\t') {
7748 /* [AS] Negate score if machine is playing black and reporting absolute scores */
7749 if( cps->scoreIsAbsolute &&
7750 ( gameMode == MachinePlaysBlack ||
7751 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
7752 gameMode == IcsPlayingBlack || // [HGM] also add other situations where engine should report black POV
7753 (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
7754 !WhiteOnMove(currentMove)
7757 curscore = -curscore;
7761 programStats.depth = plylev;
7762 programStats.nodes = nodes;
7763 programStats.time = time;
7764 programStats.score = curscore;
7765 programStats.got_only_move = 0;
7767 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
7770 if(cps->nps == 0) ticklen = 10*time; // use engine reported time
7771 else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
7772 if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
7773 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
7774 whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
7775 if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
7776 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
7777 blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
7780 /* Buffer overflow protection */
7781 if (buf1[0] != NULLCHAR) {
7782 if (strlen(buf1) >= sizeof(programStats.movelist)
7783 && appData.debugMode) {
7785 "PV is too long; using the first %u bytes.\n",
7786 (unsigned) sizeof(programStats.movelist) - 1);
7789 safeStrCpy( programStats.movelist, buf1, sizeof(programStats.movelist) );
7791 sprintf(programStats.movelist, " no PV\n");
7794 if (programStats.seen_stat) {
7795 programStats.ok_to_send = 1;
7798 if (strchr(programStats.movelist, '(') != NULL) {
7799 programStats.line_is_book = 1;
7800 programStats.nr_moves = 0;
7801 programStats.moves_left = 0;
7803 programStats.line_is_book = 0;
7806 SendProgramStatsToFrontend( cps, &programStats );
7809 [AS] Protect the thinkOutput buffer from overflow... this
7810 is only useful if buf1 hasn't overflowed first!
7812 sprintf(thinkOutput, "[%d]%c%+.2f %s%s",
7814 (gameMode == TwoMachinesPlay ?
7815 ToUpper(cps->twoMachinesColor[0]) : ' '),
7816 ((double) curscore) / 100.0,
7817 prefixHint ? lastHint : "",
7818 prefixHint ? " " : "" );
7820 if( buf1[0] != NULLCHAR ) {
7821 unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
7823 if( strlen(buf1) > max_len ) {
7824 if( appData.debugMode) {
7825 fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
7827 buf1[max_len+1] = '\0';
7830 strcat( thinkOutput, buf1 );
7833 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
7834 || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7835 DisplayMove(currentMove - 1);
7839 } else if ((p=StrStr(message, "(only move)")) != NULL) {
7840 /* crafty (9.25+) says "(only move) <move>"
7841 * if there is only 1 legal move
7843 sscanf(p, "(only move) %s", buf1);
7844 sprintf(thinkOutput, "%s (only move)", buf1);
7845 sprintf(programStats.movelist, "%s (only move)", buf1);
7846 programStats.depth = 1;
7847 programStats.nr_moves = 1;
7848 programStats.moves_left = 1;
7849 programStats.nodes = 1;
7850 programStats.time = 1;
7851 programStats.got_only_move = 1;
7853 /* Not really, but we also use this member to
7854 mean "line isn't going to change" (Crafty
7855 isn't searching, so stats won't change) */
7856 programStats.line_is_book = 1;
7858 SendProgramStatsToFrontend( cps, &programStats );
7860 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
7861 gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7862 DisplayMove(currentMove - 1);
7865 } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
7866 &time, &nodes, &plylev, &mvleft,
7867 &mvtot, mvname) >= 5) {
7868 /* The stat01: line is from Crafty (9.29+) in response
7869 to the "." command */
7870 programStats.seen_stat = 1;
7871 cps->maybeThinking = TRUE;
7873 if (programStats.got_only_move || !appData.periodicUpdates)
7876 programStats.depth = plylev;
7877 programStats.time = time;
7878 programStats.nodes = nodes;
7879 programStats.moves_left = mvleft;
7880 programStats.nr_moves = mvtot;
7881 strcpy(programStats.move_name, mvname);
7882 programStats.ok_to_send = 1;
7883 programStats.movelist[0] = '\0';
7885 SendProgramStatsToFrontend( cps, &programStats );
7889 } else if (strncmp(message,"++",2) == 0) {
7890 /* Crafty 9.29+ outputs this */
7891 programStats.got_fail = 2;
7894 } else if (strncmp(message,"--",2) == 0) {
7895 /* Crafty 9.29+ outputs this */
7896 programStats.got_fail = 1;
7899 } else if (thinkOutput[0] != NULLCHAR &&
7900 strncmp(message, " ", 4) == 0) {
7901 unsigned message_len;
7904 while (*p && *p == ' ') p++;
7906 message_len = strlen( p );
7908 /* [AS] Avoid buffer overflow */
7909 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
7910 strcat(thinkOutput, " ");
7911 strcat(thinkOutput, p);
7914 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
7915 strcat(programStats.movelist, " ");
7916 strcat(programStats.movelist, p);
7919 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
7920 gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7921 DisplayMove(currentMove - 1);
7929 if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
7930 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
7932 ChessProgramStats cpstats;
7934 if (plyext != ' ' && plyext != '\t') {
7938 /* [AS] Negate score if machine is playing black and reporting absolute scores */
7939 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
7940 curscore = -curscore;
7943 cpstats.depth = plylev;
7944 cpstats.nodes = nodes;
7945 cpstats.time = time;
7946 cpstats.score = curscore;
7947 cpstats.got_only_move = 0;
7948 cpstats.movelist[0] = '\0';
7950 if (buf1[0] != NULLCHAR) {
7951 safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist) );
7954 cpstats.ok_to_send = 0;
7955 cpstats.line_is_book = 0;
7956 cpstats.nr_moves = 0;
7957 cpstats.moves_left = 0;
7959 SendProgramStatsToFrontend( cps, &cpstats );
7966 /* Parse a game score from the character string "game", and
7967 record it as the history of the current game. The game
7968 score is NOT assumed to start from the standard position.
7969 The display is not updated in any way.
7972 ParseGameHistory(game)
7976 int fromX, fromY, toX, toY, boardIndex;
7981 if (appData.debugMode)
7982 fprintf(debugFP, "Parsing game history: %s\n", game);
7984 if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
7985 gameInfo.site = StrSave(appData.icsHost);
7986 gameInfo.date = PGNDate();
7987 gameInfo.round = StrSave("-");
7989 /* Parse out names of players */
7990 while (*game == ' ') game++;
7992 while (*game != ' ') *p++ = *game++;
7994 gameInfo.white = StrSave(buf);
7995 while (*game == ' ') game++;
7997 while (*game != ' ' && *game != '\n') *p++ = *game++;
7999 gameInfo.black = StrSave(buf);
8002 boardIndex = blackPlaysFirst ? 1 : 0;
8005 yyboardindex = boardIndex;
8006 moveType = (ChessMove) yylex();
8008 case IllegalMove: /* maybe suicide chess, etc. */
8009 if (appData.debugMode) {
8010 fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
8011 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8012 setbuf(debugFP, NULL);
8014 case WhitePromotionChancellor:
8015 case BlackPromotionChancellor:
8016 case WhitePromotionArchbishop:
8017 case BlackPromotionArchbishop:
8018 case WhitePromotionQueen:
8019 case BlackPromotionQueen:
8020 case WhitePromotionRook:
8021 case BlackPromotionRook:
8022 case WhitePromotionBishop:
8023 case BlackPromotionBishop:
8024 case WhitePromotionKnight:
8025 case BlackPromotionKnight:
8026 case WhitePromotionKing:
8027 case BlackPromotionKing:
8029 case WhiteCapturesEnPassant:
8030 case BlackCapturesEnPassant:
8031 case WhiteKingSideCastle:
8032 case WhiteQueenSideCastle:
8033 case BlackKingSideCastle:
8034 case BlackQueenSideCastle:
8035 case WhiteKingSideCastleWild:
8036 case WhiteQueenSideCastleWild:
8037 case BlackKingSideCastleWild:
8038 case BlackQueenSideCastleWild:
8040 case WhiteHSideCastleFR:
8041 case WhiteASideCastleFR:
8042 case BlackHSideCastleFR:
8043 case BlackASideCastleFR:
8045 fromX = currentMoveString[0] - AAA;
8046 fromY = currentMoveString[1] - ONE;
8047 toX = currentMoveString[2] - AAA;
8048 toY = currentMoveString[3] - ONE;
8049 promoChar = currentMoveString[4];
8053 fromX = moveType == WhiteDrop ?
8054 (int) CharToPiece(ToUpper(currentMoveString[0])) :
8055 (int) CharToPiece(ToLower(currentMoveString[0]));
8057 toX = currentMoveString[2] - AAA;
8058 toY = currentMoveString[3] - ONE;
8059 promoChar = NULLCHAR;
8063 sprintf(buf, _("Ambiguous move in ICS output: \"%s\""), yy_text);
8064 if (appData.debugMode) {
8065 fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
8066 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8067 setbuf(debugFP, NULL);
8069 DisplayError(buf, 0);
8071 case ImpossibleMove:
8073 sprintf(buf, _("Illegal move in ICS output: \"%s\""), yy_text);
8074 if (appData.debugMode) {
8075 fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
8076 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8077 setbuf(debugFP, NULL);
8079 DisplayError(buf, 0);
8081 case (ChessMove) 0: /* end of file */
8082 if (boardIndex < backwardMostMove) {
8083 /* Oops, gap. How did that happen? */
8084 DisplayError(_("Gap in move list"), 0);
8087 backwardMostMove = blackPlaysFirst ? 1 : 0;
8088 if (boardIndex > forwardMostMove) {
8089 forwardMostMove = boardIndex;
8093 if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
8094 strcat(parseList[boardIndex-1], " ");
8095 strcat(parseList[boardIndex-1], yy_text);
8107 case GameUnfinished:
8108 if (gameMode == IcsExamining) {
8109 if (boardIndex < backwardMostMove) {
8110 /* Oops, gap. How did that happen? */
8113 backwardMostMove = blackPlaysFirst ? 1 : 0;
8116 gameInfo.result = moveType;
8117 p = strchr(yy_text, '{');
8118 if (p == NULL) p = strchr(yy_text, '(');
8121 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
8123 q = strchr(p, *p == '{' ? '}' : ')');
8124 if (q != NULL) *q = NULLCHAR;
8127 gameInfo.resultDetails = StrSave(p);
8130 if (boardIndex >= forwardMostMove &&
8131 !(gameMode == IcsObserving && ics_gamenum == -1)) {
8132 backwardMostMove = blackPlaysFirst ? 1 : 0;
8135 (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
8136 fromY, fromX, toY, toX, promoChar,
8137 parseList[boardIndex]);
8138 CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
8139 /* currentMoveString is set as a side-effect of yylex */
8140 strcpy(moveList[boardIndex], currentMoveString);
8141 strcat(moveList[boardIndex], "\n");
8143 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
8144 switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
8150 if(gameInfo.variant != VariantShogi)
8151 strcat(parseList[boardIndex - 1], "+");
8155 strcat(parseList[boardIndex - 1], "#");
8162 /* Apply a move to the given board */
8164 ApplyMove(fromX, fromY, toX, toY, promoChar, board)
8165 int fromX, fromY, toX, toY;
8169 ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
8170 int promoRank = gameInfo.variant == VariantMakruk ? 3 : 1;
8172 /* [HGM] compute & store e.p. status and castling rights for new position */
8173 /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
8176 if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
8177 oldEP = (signed char)board[EP_STATUS];
8178 board[EP_STATUS] = EP_NONE;
8180 if( board[toY][toX] != EmptySquare )
8181 board[EP_STATUS] = EP_CAPTURE;
8183 if( board[fromY][fromX] == WhitePawn ) {
8184 if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
8185 board[EP_STATUS] = EP_PAWN_MOVE;
8187 if(toX>BOARD_LEFT && board[toY][toX-1] == BlackPawn &&
8188 gameInfo.variant != VariantBerolina || toX < fromX)
8189 board[EP_STATUS] = toX | berolina;
8190 if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
8191 gameInfo.variant != VariantBerolina || toX > fromX)
8192 board[EP_STATUS] = toX;
8195 if( board[fromY][fromX] == BlackPawn ) {
8196 if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
8197 board[EP_STATUS] = EP_PAWN_MOVE;
8198 if( toY-fromY== -2) {
8199 if(toX>BOARD_LEFT && board[toY][toX-1] == WhitePawn &&
8200 gameInfo.variant != VariantBerolina || toX < fromX)
8201 board[EP_STATUS] = toX | berolina;
8202 if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
8203 gameInfo.variant != VariantBerolina || toX > fromX)
8204 board[EP_STATUS] = toX;
8208 for(i=0; i<nrCastlingRights; i++) {
8209 if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
8210 board[CASTLING][i] == toX && castlingRank[i] == toY
8211 ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
8216 /* [HGM] In Shatranj and Courier all promotions are to Ferz */
8217 if((gameInfo.variant==VariantShatranj || gameInfo.variant==VariantCourier || gameInfo.variant == VariantMakruk)
8218 && promoChar != 0) promoChar = PieceToChar(WhiteFerz);
8220 if (fromX == toX && fromY == toY) return;
8222 if (fromY == DROP_RANK) {
8224 piece = board[toY][toX] = (ChessSquare) fromX;
8226 piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
8227 king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
8228 if(gameInfo.variant == VariantKnightmate)
8229 king += (int) WhiteUnicorn - (int) WhiteKing;
8231 /* Code added by Tord: */
8232 /* FRC castling assumed when king captures friendly rook. */
8233 if (board[fromY][fromX] == WhiteKing &&
8234 board[toY][toX] == WhiteRook) {
8235 board[fromY][fromX] = EmptySquare;
8236 board[toY][toX] = EmptySquare;
8238 board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
8240 board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
8242 } else if (board[fromY][fromX] == BlackKing &&
8243 board[toY][toX] == BlackRook) {
8244 board[fromY][fromX] = EmptySquare;
8245 board[toY][toX] = EmptySquare;
8247 board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
8249 board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
8251 /* End of code added by Tord */
8253 } else if (board[fromY][fromX] == king
8254 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8255 && toY == fromY && toX > fromX+1) {
8256 board[fromY][fromX] = EmptySquare;
8257 board[toY][toX] = king;
8258 board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
8259 board[fromY][BOARD_RGHT-1] = EmptySquare;
8260 } else if (board[fromY][fromX] == king
8261 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8262 && toY == fromY && toX < fromX-1) {
8263 board[fromY][fromX] = EmptySquare;
8264 board[toY][toX] = king;
8265 board[toY][toX+1] = board[fromY][BOARD_LEFT];
8266 board[fromY][BOARD_LEFT] = EmptySquare;
8267 } else if (board[fromY][fromX] == WhitePawn
8268 && toY >= BOARD_HEIGHT-promoRank
8269 && gameInfo.variant != VariantXiangqi
8271 /* white pawn promotion */
8272 board[toY][toX] = CharToPiece(ToUpper(promoChar));
8273 if (board[toY][toX] == EmptySquare) {
8274 board[toY][toX] = WhiteQueen;
8276 if(gameInfo.variant==VariantBughouse ||
8277 gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
8278 board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
8279 board[fromY][fromX] = EmptySquare;
8280 } else if ((fromY == BOARD_HEIGHT-4)
8282 && gameInfo.variant != VariantXiangqi
8283 && gameInfo.variant != VariantBerolina
8284 && (board[fromY][fromX] == WhitePawn)
8285 && (board[toY][toX] == EmptySquare)) {
8286 board[fromY][fromX] = EmptySquare;
8287 board[toY][toX] = WhitePawn;
8288 captured = board[toY - 1][toX];
8289 board[toY - 1][toX] = EmptySquare;
8290 } else if ((fromY == BOARD_HEIGHT-4)
8292 && gameInfo.variant == VariantBerolina
8293 && (board[fromY][fromX] == WhitePawn)
8294 && (board[toY][toX] == EmptySquare)) {
8295 board[fromY][fromX] = EmptySquare;
8296 board[toY][toX] = WhitePawn;
8297 if(oldEP & EP_BEROLIN_A) {
8298 captured = board[fromY][fromX-1];
8299 board[fromY][fromX-1] = EmptySquare;
8300 }else{ captured = board[fromY][fromX+1];
8301 board[fromY][fromX+1] = EmptySquare;
8303 } else if (board[fromY][fromX] == king
8304 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8305 && toY == fromY && toX > fromX+1) {
8306 board[fromY][fromX] = EmptySquare;
8307 board[toY][toX] = king;
8308 board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
8309 board[fromY][BOARD_RGHT-1] = EmptySquare;
8310 } else if (board[fromY][fromX] == king
8311 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8312 && toY == fromY && toX < fromX-1) {
8313 board[fromY][fromX] = EmptySquare;
8314 board[toY][toX] = king;
8315 board[toY][toX+1] = board[fromY][BOARD_LEFT];
8316 board[fromY][BOARD_LEFT] = EmptySquare;
8317 } else if (fromY == 7 && fromX == 3
8318 && board[fromY][fromX] == BlackKing
8319 && toY == 7 && toX == 5) {
8320 board[fromY][fromX] = EmptySquare;
8321 board[toY][toX] = BlackKing;
8322 board[fromY][7] = EmptySquare;
8323 board[toY][4] = BlackRook;
8324 } else if (fromY == 7 && fromX == 3
8325 && board[fromY][fromX] == BlackKing
8326 && toY == 7 && toX == 1) {
8327 board[fromY][fromX] = EmptySquare;
8328 board[toY][toX] = BlackKing;
8329 board[fromY][0] = EmptySquare;
8330 board[toY][2] = BlackRook;
8331 } else if (board[fromY][fromX] == BlackPawn
8333 && gameInfo.variant != VariantXiangqi
8335 /* black pawn promotion */
8336 board[toY][toX] = CharToPiece(ToLower(promoChar));
8337 if (board[toY][toX] == EmptySquare) {
8338 board[toY][toX] = BlackQueen;
8340 if(gameInfo.variant==VariantBughouse ||
8341 gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
8342 board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
8343 board[fromY][fromX] = EmptySquare;
8344 } else if ((fromY == 3)
8346 && gameInfo.variant != VariantXiangqi
8347 && gameInfo.variant != VariantBerolina
8348 && (board[fromY][fromX] == BlackPawn)
8349 && (board[toY][toX] == EmptySquare)) {
8350 board[fromY][fromX] = EmptySquare;
8351 board[toY][toX] = BlackPawn;
8352 captured = board[toY + 1][toX];
8353 board[toY + 1][toX] = EmptySquare;
8354 } else if ((fromY == 3)
8356 && gameInfo.variant == VariantBerolina
8357 && (board[fromY][fromX] == BlackPawn)
8358 && (board[toY][toX] == EmptySquare)) {
8359 board[fromY][fromX] = EmptySquare;
8360 board[toY][toX] = BlackPawn;
8361 if(oldEP & EP_BEROLIN_A) {
8362 captured = board[fromY][fromX-1];
8363 board[fromY][fromX-1] = EmptySquare;
8364 }else{ captured = board[fromY][fromX+1];
8365 board[fromY][fromX+1] = EmptySquare;
8368 board[toY][toX] = board[fromY][fromX];
8369 board[fromY][fromX] = EmptySquare;
8372 /* [HGM] now we promote for Shogi, if needed */
8373 if(gameInfo.variant == VariantShogi && promoChar == 'q')
8374 board[toY][toX] = (ChessSquare) (PROMOTED piece);
8377 if (gameInfo.holdingsWidth != 0) {
8379 /* !!A lot more code needs to be written to support holdings */
8380 /* [HGM] OK, so I have written it. Holdings are stored in the */
8381 /* penultimate board files, so they are automaticlly stored */
8382 /* in the game history. */
8383 if (fromY == DROP_RANK) {
8384 /* Delete from holdings, by decreasing count */
8385 /* and erasing image if necessary */
8387 if(p < (int) BlackPawn) { /* white drop */
8388 p -= (int)WhitePawn;
8389 p = PieceToNumber((ChessSquare)p);
8390 if(p >= gameInfo.holdingsSize) p = 0;
8391 if(--board[p][BOARD_WIDTH-2] <= 0)
8392 board[p][BOARD_WIDTH-1] = EmptySquare;
8393 if((int)board[p][BOARD_WIDTH-2] < 0)
8394 board[p][BOARD_WIDTH-2] = 0;
8395 } else { /* black drop */
8396 p -= (int)BlackPawn;
8397 p = PieceToNumber((ChessSquare)p);
8398 if(p >= gameInfo.holdingsSize) p = 0;
8399 if(--board[BOARD_HEIGHT-1-p][1] <= 0)
8400 board[BOARD_HEIGHT-1-p][0] = EmptySquare;
8401 if((int)board[BOARD_HEIGHT-1-p][1] < 0)
8402 board[BOARD_HEIGHT-1-p][1] = 0;
8405 if (captured != EmptySquare && gameInfo.holdingsSize > 0
8406 && gameInfo.variant != VariantBughouse ) {
8407 /* [HGM] holdings: Add to holdings, if holdings exist */
8408 if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
8409 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
8410 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
8413 if (p >= (int) BlackPawn) {
8414 p -= (int)BlackPawn;
8415 if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
8416 /* in Shogi restore piece to its original first */
8417 captured = (ChessSquare) (DEMOTED captured);
8420 p = PieceToNumber((ChessSquare)p);
8421 if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
8422 board[p][BOARD_WIDTH-2]++;
8423 board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
8425 p -= (int)WhitePawn;
8426 if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
8427 captured = (ChessSquare) (DEMOTED captured);
8430 p = PieceToNumber((ChessSquare)p);
8431 if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
8432 board[BOARD_HEIGHT-1-p][1]++;
8433 board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
8436 } else if (gameInfo.variant == VariantAtomic) {
8437 if (captured != EmptySquare) {
8439 for (y = toY-1; y <= toY+1; y++) {
8440 for (x = toX-1; x <= toX+1; x++) {
8441 if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
8442 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
8443 board[y][x] = EmptySquare;
8447 board[toY][toX] = EmptySquare;
8450 if(gameInfo.variant == VariantShogi && promoChar != NULLCHAR && promoChar != '=') {
8451 /* [HGM] Shogi promotions */
8452 board[toY][toX] = (ChessSquare) (PROMOTED piece);
8455 if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
8456 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
8457 // [HGM] superchess: take promotion piece out of holdings
8458 int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
8459 if((int)piece < (int)BlackPawn) { // determine stm from piece color
8460 if(!--board[k][BOARD_WIDTH-2])
8461 board[k][BOARD_WIDTH-1] = EmptySquare;
8463 if(!--board[BOARD_HEIGHT-1-k][1])
8464 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
8470 /* Updates forwardMostMove */
8472 MakeMove(fromX, fromY, toX, toY, promoChar)
8473 int fromX, fromY, toX, toY;
8476 // forwardMostMove++; // [HGM] bare: moved downstream
8478 if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
8479 int timeLeft; static int lastLoadFlag=0; int king, piece;
8480 piece = boards[forwardMostMove][fromY][fromX];
8481 king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
8482 if(gameInfo.variant == VariantKnightmate)
8483 king += (int) WhiteUnicorn - (int) WhiteKing;
8484 if(forwardMostMove == 0) {
8486 fprintf(serverMoves, "%s;", second.tidy);
8487 fprintf(serverMoves, "%s;", first.tidy);
8488 if(!blackPlaysFirst)
8489 fprintf(serverMoves, "%s;", second.tidy);
8490 } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
8491 lastLoadFlag = loadFlag;
8493 fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
8494 // print castling suffix
8495 if( toY == fromY && piece == king ) {
8497 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
8499 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
8502 if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
8503 boards[forwardMostMove][fromY][fromX] == BlackPawn ) &&
8504 boards[forwardMostMove][toY][toX] == EmptySquare
8506 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
8508 if(promoChar != NULLCHAR)
8509 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);
8511 fprintf(serverMoves, "/%d/%d",
8512 pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
8513 if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
8514 else timeLeft = blackTimeRemaining/1000;
8515 fprintf(serverMoves, "/%d", timeLeft);
8517 fflush(serverMoves);
8520 if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations
8521 DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
8525 UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
8526 if (commentList[forwardMostMove+1] != NULL) {
8527 free(commentList[forwardMostMove+1]);
8528 commentList[forwardMostMove+1] = NULL;
8530 CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
8531 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
8532 forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
8533 SwitchClocks(); // uses forwardMostMove, so must be done after incrementing it !
8534 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
8535 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
8536 gameInfo.result = GameUnfinished;
8537 if (gameInfo.resultDetails != NULL) {
8538 free(gameInfo.resultDetails);
8539 gameInfo.resultDetails = NULL;
8541 CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
8542 moveList[forwardMostMove - 1]);
8543 (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
8544 PosFlags(forwardMostMove - 1),
8545 fromY, fromX, toY, toX, promoChar,
8546 parseList[forwardMostMove - 1]);
8547 switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
8553 if(gameInfo.variant != VariantShogi)
8554 strcat(parseList[forwardMostMove - 1], "+");
8558 strcat(parseList[forwardMostMove - 1], "#");
8561 if (appData.debugMode) {
8562 fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
8567 /* Updates currentMove if not pausing */
8569 ShowMove(fromX, fromY, toX, toY)
8571 int instant = (gameMode == PlayFromGameFile) ?
8572 (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
8574 if(appData.noGUI) return;
8576 if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile)
8580 if (forwardMostMove == currentMove + 1)
8583 // AnimateMove(boards[forwardMostMove - 1],
8584 // fromX, fromY, toX, toY);
8586 if (appData.highlightLastMove)
8588 SetHighlights(fromX, fromY, toX, toY);
8591 currentMove = forwardMostMove;
8594 if (instant) return;
8596 DisplayMove(currentMove - 1);
8597 DrawPosition(FALSE, boards[currentMove]);
8598 DisplayBothClocks();
8599 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
8604 void SendEgtPath(ChessProgramState *cps)
8605 { /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
8606 char buf[MSG_SIZ], name[MSG_SIZ], *p;
8608 if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
8611 char c, *q = name+1, *r, *s;
8613 name[0] = ','; // extract next format name from feature and copy with prefixed ','
8614 while(*p && *p != ',') *q++ = *p++;
8616 if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
8617 strcmp(name, ",nalimov:") == 0 ) {
8618 // take nalimov path from the menu-changeable option first, if it is defined
8619 sprintf(buf, "egtpath nalimov %s\n", appData.defaultPathEGTB);
8620 SendToProgram(buf,cps); // send egtbpath command for nalimov
8622 if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
8623 (s = StrStr(appData.egtFormats, name)) != NULL) {
8624 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
8625 s = r = StrStr(s, ":") + 1; // beginning of path info
8626 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
8627 c = *r; *r = 0; // temporarily null-terminate path info
8628 *--q = 0; // strip of trailig ':' from name
8629 sprintf(buf, "egtpath %s %s\n", name+1, s);
8631 SendToProgram(buf,cps); // send egtbpath command for this format
8633 if(*p == ',') p++; // read away comma to position for next format name
8638 InitChessProgram(cps, setup)
8639 ChessProgramState *cps;
8640 int setup; /* [HGM] needed to setup FRC opening position */
8642 char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
8643 if (appData.noChessProgram) return;
8644 hintRequested = FALSE;
8645 bookRequested = FALSE;
8647 /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
8648 /* moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
8649 if(cps->memSize) { /* [HGM] memory */
8650 sprintf(buf, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
8651 SendToProgram(buf, cps);
8653 SendEgtPath(cps); /* [HGM] EGT */
8654 if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
8655 sprintf(buf, "cores %d\n", appData.smpCores);
8656 SendToProgram(buf, cps);
8659 SendToProgram(cps->initString, cps);
8660 if (gameInfo.variant != VariantNormal &&
8661 gameInfo.variant != VariantLoadable
8662 /* [HGM] also send variant if board size non-standard */
8663 || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
8665 char *v = VariantName(gameInfo.variant);
8666 if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
8667 /* [HGM] in protocol 1 we have to assume all variants valid */
8668 sprintf(buf, _("Variant %s not supported by %s"), v, cps->tidy);
8669 DisplayFatalError(buf, 0, 1);
8673 /* [HGM] make prefix for non-standard board size. Awkward testing... */
8674 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8675 if( gameInfo.variant == VariantXiangqi )
8676 overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
8677 if( gameInfo.variant == VariantShogi )
8678 overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
8679 if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
8680 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
8681 if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
8682 gameInfo.variant == VariantGothic || gameInfo.variant == VariantFalcon )
8683 overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8684 if( gameInfo.variant == VariantCourier )
8685 overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8686 if( gameInfo.variant == VariantSuper )
8687 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
8688 if( gameInfo.variant == VariantGreat )
8689 overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
8692 sprintf(b, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
8693 gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
8694 /* [HGM] varsize: try first if this defiant size variant is specifically known */
8695 if(StrStr(cps->variants, b) == NULL) {
8696 // specific sized variant not known, check if general sizing allowed
8697 if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
8698 if(StrStr(cps->variants, "boardsize") == NULL) {
8699 sprintf(buf, "Board size %dx%d+%d not supported by %s",
8700 gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
8701 DisplayFatalError(buf, 0, 1);
8704 /* [HGM] here we really should compare with the maximum supported board size */
8707 } else sprintf(b, "%s", VariantName(gameInfo.variant));
8708 sprintf(buf, "variant %s\n", b);
8709 SendToProgram(buf, cps);
8711 currentlyInitializedVariant = gameInfo.variant;
8713 /* [HGM] send opening position in FRC to first engine */
8715 SendToProgram("force\n", cps);
8717 /* engine is now in force mode! Set flag to wake it up after first move. */
8718 setboardSpoiledMachineBlack = 1;
8722 snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
8723 SendToProgram(buf, cps);
8725 cps->maybeThinking = FALSE;
8726 cps->offeredDraw = 0;
8727 if (!appData.icsActive) {
8728 SendTimeControl(cps, movesPerSession, timeControl,
8729 timeIncrement, appData.searchDepth,
8732 if (appData.showThinking
8733 // [HGM] thinking: four options require thinking output to be sent
8734 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
8736 SendToProgram("post\n", cps);
8738 SendToProgram("hard\n", cps);
8739 if (!appData.ponderNextMove) {
8740 /* Warning: "easy" is a toggle in GNU Chess, so don't send
8741 it without being sure what state we are in first. "hard"
8742 is not a toggle, so that one is OK.
8744 SendToProgram("easy\n", cps);
8747 sprintf(buf, "ping %d\n", ++cps->lastPing);
8748 SendToProgram(buf, cps);
8750 cps->initDone = TRUE;
8755 StartChessProgram(cps)
8756 ChessProgramState *cps;
8761 if (appData.noChessProgram) return;
8762 cps->initDone = FALSE;
8764 if (strcmp(cps->host, "localhost") == 0) {
8765 err = StartChildProcess(cps->program, cps->dir, &cps->pr);
8766 } else if (*appData.remoteShell == NULLCHAR) {
8767 err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
8769 if (*appData.remoteUser == NULLCHAR) {
8770 snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
8773 snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
8774 cps->host, appData.remoteUser, cps->program);
8776 err = StartChildProcess(buf, "", &cps->pr);
8780 sprintf(buf, _("Startup failure on '%s'"), cps->program);
8781 DisplayFatalError(buf, err, 1);
8787 cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
8788 if (cps->protocolVersion > 1) {
8789 sprintf(buf, "xboard\nprotover %d\n", cps->protocolVersion);
8790 cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
8791 cps->comboCnt = 0; // and values of combo boxes
8792 SendToProgram(buf, cps);
8794 SendToProgram("xboard\n", cps);
8800 TwoMachinesEventIfReady P((void))
8802 if (first.lastPing != first.lastPong) {
8803 DisplayMessage("", _("Waiting for first chess program"));
8804 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
8807 if (second.lastPing != second.lastPong) {
8808 DisplayMessage("", _("Waiting for second chess program"));
8809 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
8817 NextMatchGame P((void))
8819 int index; /* [HGM] autoinc: step load index during match */
8821 if (*appData.loadGameFile != NULLCHAR) {
8822 index = appData.loadGameIndex;
8823 if(index < 0) { // [HGM] autoinc
8824 lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
8825 if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
8827 LoadGameFromFile(appData.loadGameFile,
8829 appData.loadGameFile, FALSE);
8830 } else if (*appData.loadPositionFile != NULLCHAR) {
8831 index = appData.loadPositionIndex;
8832 if(index < 0) { // [HGM] autoinc
8833 lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
8834 if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
8836 LoadPositionFromFile(appData.loadPositionFile,
8838 appData.loadPositionFile);
8840 TwoMachinesEventIfReady();
8843 void UserAdjudicationEvent( int result )
8845 ChessMove gameResult = GameIsDrawn;
8848 gameResult = WhiteWins;
8850 else if( result < 0 ) {
8851 gameResult = BlackWins;
8854 if( gameMode == TwoMachinesPlay ) {
8855 GameEnds( gameResult, "User adjudication", GE_XBOARD );
8860 // [HGM] save: calculate checksum of game to make games easily identifiable
8861 int StringCheckSum(char *s)
8864 if(s==NULL) return 0;
8865 while(*s) i = i*259 + *s++;
8872 for(i=backwardMostMove; i<forwardMostMove; i++) {
8873 sum += pvInfoList[i].depth;
8874 sum += StringCheckSum(parseList[i]);
8875 sum += StringCheckSum(commentList[i]);
8878 if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
8879 return sum + StringCheckSum(commentList[i]);
8880 } // end of save patch
8883 GameEnds(result, resultDetails, whosays)
8885 char *resultDetails;
8888 GameMode nextGameMode;
8892 if(endingGame) return; /* [HGM] crash: forbid recursion */
8895 if (appData.debugMode) {
8896 fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
8897 result, resultDetails ? resultDetails : "(null)", whosays);
8900 if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
8901 /* If we are playing on ICS, the server decides when the
8902 game is over, but the engine can offer to draw, claim
8906 if (appData.zippyPlay && first.initDone) {
8907 if (result == GameIsDrawn) {
8908 /* In case draw still needs to be claimed */
8909 SendToICS(ics_prefix);
8910 SendToICS("draw\n");
8911 } else if (StrCaseStr(resultDetails, "resign")) {
8912 SendToICS(ics_prefix);
8913 SendToICS("resign\n");
8917 endingGame = 0; /* [HGM] crash */
8921 /* If we're loading the game from a file, stop */
8922 if (whosays == GE_FILE) {
8923 (void) StopLoadGameTimer();
8927 /* Cancel draw offers */
8928 first.offeredDraw = second.offeredDraw = 0;
8930 /* If this is an ICS game, only ICS can really say it's done;
8931 if not, anyone can. */
8932 isIcsGame = (gameMode == IcsPlayingWhite ||
8933 gameMode == IcsPlayingBlack ||
8934 gameMode == IcsObserving ||
8935 gameMode == IcsExamining);
8937 if (!isIcsGame || whosays == GE_ICS) {
8938 /* OK -- not an ICS game, or ICS said it was done */
8940 if (!isIcsGame && !appData.noChessProgram)
8941 SetUserThinkingEnables();
8943 /* [HGM] if a machine claims the game end we verify this claim */
8944 if(gameMode == TwoMachinesPlay && appData.testClaims) {
8945 if(appData.testLegality && whosays >= GE_ENGINE1 ) {
8947 ChessMove trueResult = (ChessMove) -1;
8949 claimer = whosays == GE_ENGINE1 ? /* color of claimer */
8950 first.twoMachinesColor[0] :
8951 second.twoMachinesColor[0] ;
8953 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
8954 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
8955 /* [HGM] verify: engine mate claims accepted if they were flagged */
8956 trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
8958 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
8959 /* [HGM] verify: engine mate claims accepted if they were flagged */
8960 trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8962 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
8963 trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
8966 // now verify win claims, but not in drop games, as we don't understand those yet
8967 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
8968 || gameInfo.variant == VariantGreat) &&
8969 (result == WhiteWins && claimer == 'w' ||
8970 result == BlackWins && claimer == 'b' ) ) { // case to verify: engine claims own win
8971 if (appData.debugMode) {
8972 fprintf(debugFP, "result=%d sp=%d move=%d\n",
8973 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
8975 if(result != trueResult) {
8976 sprintf(buf, "False win claim: '%s'", resultDetails);
8977 result = claimer == 'w' ? BlackWins : WhiteWins;
8978 resultDetails = buf;
8981 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
8982 && (forwardMostMove <= backwardMostMove ||
8983 (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
8984 (claimer=='b')==(forwardMostMove&1))
8986 /* [HGM] verify: draws that were not flagged are false claims */
8987 sprintf(buf, "False draw claim: '%s'", resultDetails);
8988 result = claimer == 'w' ? BlackWins : WhiteWins;
8989 resultDetails = buf;
8991 /* (Claiming a loss is accepted no questions asked!) */
8994 /* [HGM] bare: don't allow bare King to win */
8995 if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
8996 && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
8997 && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
8998 && result != GameIsDrawn)
8999 { int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
9000 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
9001 int p = (signed char)boards[forwardMostMove][i][j] - color;
9002 if(p >= 0 && p <= (int)WhiteKing) k++;
9004 if (appData.debugMode) {
9005 fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
9006 result, resultDetails ? resultDetails : "(null)", whosays, k, color);
9009 result = GameIsDrawn;
9010 sprintf(buf, "%s but bare king", resultDetails);
9011 resultDetails = buf;
9016 if(serverMoves != NULL && !loadFlag) { char c = '=';
9017 if(result==WhiteWins) c = '+';
9018 if(result==BlackWins) c = '-';
9019 if(resultDetails != NULL)
9020 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
9022 if (resultDetails != NULL) {
9023 gameInfo.result = result;
9024 gameInfo.resultDetails = StrSave(resultDetails);
9026 /* display last move only if game was not loaded from file */
9027 if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
9028 DisplayMove(currentMove - 1);
9030 if (forwardMostMove != 0) {
9031 if (gameMode != PlayFromGameFile && gameMode != EditGame
9032 && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
9034 if (*appData.saveGameFile != NULLCHAR) {
9035 SaveGameToFile(appData.saveGameFile, TRUE);
9036 } else if (appData.autoSaveGames) {
9039 if (*appData.savePositionFile != NULLCHAR) {
9040 SavePositionToFile(appData.savePositionFile);
9045 /* Tell program how game ended in case it is learning */
9046 /* [HGM] Moved this to after saving the PGN, just in case */
9047 /* engine died and we got here through time loss. In that */
9048 /* case we will get a fatal error writing the pipe, which */
9049 /* would otherwise lose us the PGN. */
9050 /* [HGM] crash: not needed anymore, but doesn't hurt; */
9051 /* output during GameEnds should never be fatal anymore */
9052 if (gameMode == MachinePlaysWhite ||
9053 gameMode == MachinePlaysBlack ||
9054 gameMode == TwoMachinesPlay ||
9055 gameMode == IcsPlayingWhite ||
9056 gameMode == IcsPlayingBlack ||
9057 gameMode == BeginningOfGame) {
9059 sprintf(buf, "result %s {%s}\n", PGNResult(result),
9061 if (first.pr != NoProc) {
9062 SendToProgram(buf, &first);
9064 if (second.pr != NoProc &&
9065 gameMode == TwoMachinesPlay) {
9066 SendToProgram(buf, &second);
9071 if (appData.icsActive) {
9072 if (appData.quietPlay &&
9073 (gameMode == IcsPlayingWhite ||
9074 gameMode == IcsPlayingBlack)) {
9075 SendToICS(ics_prefix);
9076 SendToICS("set shout 1\n");
9078 nextGameMode = IcsIdle;
9079 ics_user_moved = FALSE;
9080 /* clean up premove. It's ugly when the game has ended and the
9081 * premove highlights are still on the board.
9085 ClearPremoveHighlights();
9086 DrawPosition(FALSE, boards[currentMove]);
9088 if (whosays == GE_ICS) {
9091 if (gameMode == IcsPlayingWhite)
9093 else if(gameMode == IcsPlayingBlack)
9097 if (gameMode == IcsPlayingBlack)
9099 else if(gameMode == IcsPlayingWhite)
9106 PlayIcsUnfinishedSound();
9109 } else if (gameMode == EditGame ||
9110 gameMode == PlayFromGameFile ||
9111 gameMode == AnalyzeMode ||
9112 gameMode == AnalyzeFile) {
9113 nextGameMode = gameMode;
9115 nextGameMode = EndOfGame;
9120 nextGameMode = gameMode;
9123 if (appData.noChessProgram) {
9124 gameMode = nextGameMode;
9126 endingGame = 0; /* [HGM] crash */
9131 /* Put first chess program into idle state */
9132 if (first.pr != NoProc &&
9133 (gameMode == MachinePlaysWhite ||
9134 gameMode == MachinePlaysBlack ||
9135 gameMode == TwoMachinesPlay ||
9136 gameMode == IcsPlayingWhite ||
9137 gameMode == IcsPlayingBlack ||
9138 gameMode == BeginningOfGame)) {
9139 SendToProgram("force\n", &first);
9140 if (first.usePing) {
9142 sprintf(buf, "ping %d\n", ++first.lastPing);
9143 SendToProgram(buf, &first);
9146 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
9147 /* Kill off first chess program */
9148 if (first.isr != NULL)
9149 RemoveInputSource(first.isr);
9152 if (first.pr != NoProc) {
9154 DoSleep( appData.delayBeforeQuit );
9155 SendToProgram("quit\n", &first);
9156 DoSleep( appData.delayAfterQuit );
9157 DestroyChildProcess(first.pr, first.useSigterm);
9162 /* Put second chess program into idle state */
9163 if (second.pr != NoProc &&
9164 gameMode == TwoMachinesPlay) {
9165 SendToProgram("force\n", &second);
9166 if (second.usePing) {
9168 sprintf(buf, "ping %d\n", ++second.lastPing);
9169 SendToProgram(buf, &second);
9172 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
9173 /* Kill off second chess program */
9174 if (second.isr != NULL)
9175 RemoveInputSource(second.isr);
9178 if (second.pr != NoProc) {
9179 DoSleep( appData.delayBeforeQuit );
9180 SendToProgram("quit\n", &second);
9181 DoSleep( appData.delayAfterQuit );
9182 DestroyChildProcess(second.pr, second.useSigterm);
9187 if (matchMode && gameMode == TwoMachinesPlay) {
9190 if (first.twoMachinesColor[0] == 'w') {
9197 if (first.twoMachinesColor[0] == 'b') {
9206 if (matchGame < appData.matchGames) {
9208 if(appData.sameColorGames <= 1) { /* [HGM] alternate: suppress color swap */
9209 tmp = first.twoMachinesColor;
9210 first.twoMachinesColor = second.twoMachinesColor;
9211 second.twoMachinesColor = tmp;
9213 gameMode = nextGameMode;
9215 if(appData.matchPause>10000 || appData.matchPause<10)
9216 appData.matchPause = 10000; /* [HGM] make pause adjustable */
9217 ScheduleDelayedEvent(NextMatchGame, appData.matchPause);
9218 endingGame = 0; /* [HGM] crash */
9222 gameMode = nextGameMode;
9223 sprintf(buf, _("Match %s vs. %s: final score %d-%d-%d"),
9224 first.tidy, second.tidy,
9225 first.matchWins, second.matchWins,
9226 appData.matchGames - (first.matchWins + second.matchWins));
9227 DisplayFatalError(buf, 0, 0);
9230 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
9231 !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
9233 gameMode = nextGameMode;
9235 endingGame = 0; /* [HGM] crash */
9238 /* Assumes program was just initialized (initString sent).
9239 Leaves program in force mode. */
9241 FeedMovesToProgram(cps, upto)
9242 ChessProgramState *cps;
9247 if (appData.debugMode)
9248 fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
9249 startedFromSetupPosition ? "position and " : "",
9250 backwardMostMove, upto, cps->which);
9251 if(currentlyInitializedVariant != gameInfo.variant) { char buf[MSG_SIZ];
9252 // [HGM] variantswitch: make engine aware of new variant
9253 if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
9254 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
9255 sprintf(buf, "variant %s\n", VariantName(gameInfo.variant));
9256 SendToProgram(buf, cps);
9257 currentlyInitializedVariant = gameInfo.variant;
9259 SendToProgram("force\n", cps);
9260 if (startedFromSetupPosition) {
9261 SendBoard(cps, backwardMostMove);
9262 if (appData.debugMode) {
9263 fprintf(debugFP, "feedMoves\n");
9266 for (i = backwardMostMove; i < upto; i++) {
9267 SendMoveToProgram(i, cps);
9273 ResurrectChessProgram()
9275 /* The chess program may have exited.
9276 If so, restart it and feed it all the moves made so far. */
9278 if (appData.noChessProgram || first.pr != NoProc) return;
9280 StartChessProgram(&first);
9281 InitChessProgram(&first, FALSE);
9282 FeedMovesToProgram(&first, currentMove);
9284 if (!first.sendTime) {
9285 /* can't tell gnuchess what its clock should read,
9286 so we bow to its notion. */
9288 timeRemaining[0][currentMove] = whiteTimeRemaining;
9289 timeRemaining[1][currentMove] = blackTimeRemaining;
9292 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
9293 appData.icsEngineAnalyze) && first.analysisSupport) {
9294 SendToProgram("analyze\n", &first);
9295 first.analyzing = TRUE;
9308 if (appData.debugMode) {
9309 fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
9310 redraw, init, gameMode);
9312 CleanupTail(); // [HGM] vari: delete any stored variations
9313 pausing = pauseExamInvalid = FALSE;
9314 startedFromSetupPosition = blackPlaysFirst = FALSE;
9316 whiteFlag = blackFlag = FALSE;
9317 userOfferedDraw = FALSE;
9318 hintRequested = bookRequested = FALSE;
9319 first.maybeThinking = FALSE;
9320 second.maybeThinking = FALSE;
9321 first.bookSuspend = FALSE; // [HGM] book
9322 second.bookSuspend = FALSE;
9323 thinkOutput[0] = NULLCHAR;
9324 lastHint[0] = NULLCHAR;
9325 ClearGameInfo(&gameInfo);
9326 gameInfo.variant = StringToVariant(appData.variant);
9327 ics_user_moved = ics_clock_paused = FALSE;
9328 ics_getting_history = H_FALSE;
9330 white_holding[0] = black_holding[0] = NULLCHAR;
9331 ClearProgramStats();
9332 opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
9336 flipView = appData.flipView;
9337 ClearPremoveHighlights();
9339 alarmSounded = FALSE;
9341 GameEnds((ChessMove) 0, NULL, GE_PLAYER);
9342 if(appData.serverMovesName != NULL) {
9343 /* [HGM] prepare to make moves file for broadcasting */
9344 clock_t t = clock();
9345 if(serverMoves != NULL) fclose(serverMoves);
9346 serverMoves = fopen(appData.serverMovesName, "r");
9347 if(serverMoves != NULL) {
9348 fclose(serverMoves);
9349 /* delay 15 sec before overwriting, so all clients can see end */
9350 while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
9352 serverMoves = fopen(appData.serverMovesName, "w");
9356 gameMode = BeginningOfGame;
9359 if(appData.icsActive) gameInfo.variant = VariantNormal;
9360 currentMove = forwardMostMove = backwardMostMove = 0;
9361 InitPosition(redraw);
9362 for (i = 0; i < MAX_MOVES; i++) {
9363 if (commentList[i] != NULL) {
9364 free(commentList[i]);
9365 commentList[i] = NULL;
9370 timeRemaining[0][0] = whiteTimeRemaining;
9371 timeRemaining[1][0] = blackTimeRemaining;
9372 if (first.pr == NULL) {
9373 StartChessProgram(&first);
9376 InitChessProgram(&first, startedFromSetupPosition);
9380 DisplayMessage("", "");
9381 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
9382 lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
9390 if (!AutoPlayOneMove())
9392 if (matchMode || appData.timeDelay == 0)
9394 if (appData.timeDelay < 0 || gameMode == AnalyzeFile)
9396 StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
9405 int fromX, fromY, toX, toY;
9407 if (appData.debugMode) {
9408 fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
9411 if (gameMode != PlayFromGameFile)
9414 if (currentMove >= forwardMostMove) {
9415 gameMode = EditGame;
9418 /* [AS] Clear current move marker at the end of a game */
9419 /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
9424 toX = moveList[currentMove][2] - AAA;
9425 toY = moveList[currentMove][3] - ONE;
9427 if (moveList[currentMove][1] == '@') {
9428 if (appData.highlightLastMove) {
9429 SetHighlights(-1, -1, toX, toY);
9432 fromX = moveList[currentMove][0] - AAA;
9433 fromY = moveList[currentMove][1] - ONE;
9435 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
9437 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
9439 if (appData.highlightLastMove) {
9440 SetHighlights(fromX, fromY, toX, toY);
9443 DisplayMove(currentMove);
9444 SendMoveToProgram(currentMove++, &first);
9445 DisplayBothClocks();
9446 DrawPosition(FALSE, boards[currentMove]);
9447 // [HGM] PV info: always display, routine tests if empty
9448 DisplayComment(currentMove - 1, commentList[currentMove]);
9454 LoadGameOneMove(readAhead)
9455 ChessMove readAhead;
9457 int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
9458 char promoChar = NULLCHAR;
9463 if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
9464 gameMode != AnalyzeMode && gameMode != Training) {
9469 yyboardindex = forwardMostMove;
9470 if (readAhead != (ChessMove)0) {
9471 moveType = readAhead;
9473 if (gameFileFP == NULL)
9475 moveType = (ChessMove) yylex();
9481 if (appData.debugMode)
9482 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9485 /* append the comment but don't display it */
9486 AppendComment(currentMove, p, FALSE);
9489 case WhiteCapturesEnPassant:
9490 case BlackCapturesEnPassant:
9491 case WhitePromotionChancellor:
9492 case BlackPromotionChancellor:
9493 case WhitePromotionArchbishop:
9494 case BlackPromotionArchbishop:
9495 case WhitePromotionCentaur:
9496 case BlackPromotionCentaur:
9497 case WhitePromotionQueen:
9498 case BlackPromotionQueen:
9499 case WhitePromotionRook:
9500 case BlackPromotionRook:
9501 case WhitePromotionBishop:
9502 case BlackPromotionBishop:
9503 case WhitePromotionKnight:
9504 case BlackPromotionKnight:
9505 case WhitePromotionKing:
9506 case BlackPromotionKing:
9508 case WhiteKingSideCastle:
9509 case WhiteQueenSideCastle:
9510 case BlackKingSideCastle:
9511 case BlackQueenSideCastle:
9512 case WhiteKingSideCastleWild:
9513 case WhiteQueenSideCastleWild:
9514 case BlackKingSideCastleWild:
9515 case BlackQueenSideCastleWild:
9517 case WhiteHSideCastleFR:
9518 case WhiteASideCastleFR:
9519 case BlackHSideCastleFR:
9520 case BlackASideCastleFR:
9522 if (appData.debugMode)
9523 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
9524 fromX = currentMoveString[0] - AAA;
9525 fromY = currentMoveString[1] - ONE;
9526 toX = currentMoveString[2] - AAA;
9527 toY = currentMoveString[3] - ONE;
9528 promoChar = currentMoveString[4];
9533 if (appData.debugMode)
9534 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
9535 fromX = moveType == WhiteDrop ?
9536 (int) CharToPiece(ToUpper(currentMoveString[0])) :
9537 (int) CharToPiece(ToLower(currentMoveString[0]));
9539 toX = currentMoveString[2] - AAA;
9540 toY = currentMoveString[3] - ONE;
9546 case GameUnfinished:
9547 if (appData.debugMode)
9548 fprintf(debugFP, "Parsed game end: %s\n", yy_text);
9549 p = strchr(yy_text, '{');
9550 if (p == NULL) p = strchr(yy_text, '(');
9553 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
9555 q = strchr(p, *p == '{' ? '}' : ')');
9556 if (q != NULL) *q = NULLCHAR;
9559 GameEnds(moveType, p, GE_FILE);
9561 if (cmailMsgLoaded) {
9563 flipView = WhiteOnMove(currentMove);
9564 if (moveType == GameUnfinished) flipView = !flipView;
9565 if (appData.debugMode)
9566 fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
9570 case (ChessMove) 0: /* end of file */
9571 if (appData.debugMode)
9572 fprintf(debugFP, "Parser hit end of file\n");
9573 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9579 if (WhiteOnMove(currentMove)) {
9580 GameEnds(BlackWins, "Black mates", GE_FILE);
9582 GameEnds(WhiteWins, "White mates", GE_FILE);
9586 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
9593 if (lastLoadGameStart == GNUChessGame) {
9594 /* GNUChessGames have numbers, but they aren't move numbers */
9595 if (appData.debugMode)
9596 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
9597 yy_text, (int) moveType);
9598 return LoadGameOneMove((ChessMove)0); /* tail recursion */
9600 /* else fall thru */
9605 /* Reached start of next game in file */
9606 if (appData.debugMode)
9607 fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
9608 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9614 if (WhiteOnMove(currentMove)) {
9615 GameEnds(BlackWins, "Black mates", GE_FILE);
9617 GameEnds(WhiteWins, "White mates", GE_FILE);
9621 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
9627 case PositionDiagram: /* should not happen; ignore */
9628 case ElapsedTime: /* ignore */
9629 case NAG: /* ignore */
9630 if (appData.debugMode)
9631 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
9632 yy_text, (int) moveType);
9633 return LoadGameOneMove((ChessMove)0); /* tail recursion */
9636 if (appData.testLegality) {
9637 if (appData.debugMode)
9638 fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
9639 sprintf(move, _("Illegal move: %d.%s%s"),
9640 (forwardMostMove / 2) + 1,
9641 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9642 DisplayError(move, 0);
9645 if (appData.debugMode)
9646 fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
9647 yy_text, currentMoveString);
9648 fromX = currentMoveString[0] - AAA;
9649 fromY = currentMoveString[1] - ONE;
9650 toX = currentMoveString[2] - AAA;
9651 toY = currentMoveString[3] - ONE;
9652 promoChar = currentMoveString[4];
9657 if (appData.debugMode)
9658 fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
9659 sprintf(move, _("Ambiguous move: %d.%s%s"),
9660 (forwardMostMove / 2) + 1,
9661 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9662 DisplayError(move, 0);
9667 case ImpossibleMove:
9668 if (appData.debugMode)
9669 fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
9670 sprintf(move, _("Illegal move: %d.%s%s"),
9671 (forwardMostMove / 2) + 1,
9672 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9673 DisplayError(move, 0);
9679 if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
9680 DrawPosition(FALSE, boards[currentMove]);
9681 DisplayBothClocks();
9682 if (!appData.matchMode) // [HGM] PV info: routine tests if empty
9683 DisplayComment(currentMove - 1, commentList[currentMove]);
9685 (void) StopLoadGameTimer();
9687 cmailOldMove = forwardMostMove;
9690 /* currentMoveString is set as a side-effect of yylex */
9691 strcat(currentMoveString, "\n");
9692 strcpy(moveList[forwardMostMove], currentMoveString);
9694 thinkOutput[0] = NULLCHAR;
9695 MakeMove(fromX, fromY, toX, toY, promoChar);
9696 currentMove = forwardMostMove;
9701 /* Load the nth game from the given file */
9703 LoadGameFromFile(filename, n, title, useList)
9707 /*Boolean*/ int useList;
9712 if (strcmp(filename, "-") == 0) {
9716 f = fopen(filename, "rb");
9718 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9719 DisplayError(buf, errno);
9723 if (fseek(f, 0, 0) == -1) {
9724 /* f is not seekable; probably a pipe */
9727 if (useList && n == 0) {
9728 int error = GameListBuild(f);
9730 DisplayError(_("Cannot build game list"), error);
9731 } else if (!ListEmpty(&gameList) &&
9732 ((ListGame *) gameList.tailPred)->number > 1) {
9733 // TODO convert to GTK
9734 // GameListPopUp(f, title);
9741 return LoadGame(f, n, title, FALSE);
9746 MakeRegisteredMove()
9748 int fromX, fromY, toX, toY;
9750 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
9751 switch (cmailMoveType[lastLoadGameNumber - 1]) {
9754 if (appData.debugMode)
9755 fprintf(debugFP, "Restoring %s for game %d\n",
9756 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
9758 thinkOutput[0] = NULLCHAR;
9759 strcpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1]);
9760 fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
9761 fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
9762 toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
9763 toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
9764 promoChar = cmailMove[lastLoadGameNumber - 1][4];
9765 MakeMove(fromX, fromY, toX, toY, promoChar);
9766 ShowMove(fromX, fromY, toX, toY);
9767 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9774 if (WhiteOnMove(currentMove)) {
9775 GameEnds(BlackWins, "Black mates", GE_PLAYER);
9777 GameEnds(WhiteWins, "White mates", GE_PLAYER);
9782 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
9789 if (WhiteOnMove(currentMove)) {
9790 GameEnds(BlackWins, "White resigns", GE_PLAYER);
9792 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
9797 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
9808 /* Wrapper around LoadGame for use when a Cmail message is loaded */
9810 CmailLoadGame(f, gameNumber, title, useList)
9818 if (gameNumber > nCmailGames) {
9819 DisplayError(_("No more games in this message"), 0);
9822 if (f == lastLoadGameFP) {
9823 int offset = gameNumber - lastLoadGameNumber;
9825 cmailMsg[0] = NULLCHAR;
9826 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
9827 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
9828 nCmailMovesRegistered--;
9830 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
9831 if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
9832 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
9835 if (! RegisterMove()) return FALSE;
9839 retVal = LoadGame(f, gameNumber, title, useList);
9841 /* Make move registered during previous look at this game, if any */
9842 MakeRegisteredMove();
9844 if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
9845 commentList[currentMove]
9846 = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
9847 DisplayComment(currentMove - 1, commentList[currentMove]);
9853 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
9858 int gameNumber = lastLoadGameNumber + offset;
9859 if (lastLoadGameFP == NULL) {
9860 DisplayError(_("No game has been loaded yet"), 0);
9863 if (gameNumber <= 0) {
9864 DisplayError(_("Can't back up any further"), 0);
9867 if (cmailMsgLoaded) {
9868 return CmailLoadGame(lastLoadGameFP, gameNumber,
9869 lastLoadGameTitle, lastLoadGameUseList);
9871 return LoadGame(lastLoadGameFP, gameNumber,
9872 lastLoadGameTitle, lastLoadGameUseList);
9878 /* Load the nth game from open file f */
9880 LoadGame(f, gameNumber, title, useList)
9888 int gn = gameNumber;
9889 ListGame *lg = NULL;
9892 GameMode oldGameMode;
9893 VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
9895 if (appData.debugMode)
9896 fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
9898 if (gameMode == Training )
9899 SetTrainingModeOff();
9901 oldGameMode = gameMode;
9902 if (gameMode != BeginningOfGame)
9908 if (lastLoadGameFP != NULL && lastLoadGameFP != f)
9910 fclose(lastLoadGameFP);
9915 lg = (ListGame *) ListElem(&gameList, gameNumber-1);
9919 fseek(f, lg->offset, 0);
9920 GameListHighlight(gameNumber);
9925 DisplayError(_("Game number out of range"), 0);
9932 if (fseek(f, 0, 0) == -1)
9934 if (f == lastLoadGameFP ?
9935 gameNumber == lastLoadGameNumber + 1 :
9942 DisplayError(_("Can't seek on game file"), 0);
9949 lastLoadGameNumber = gameNumber;
9950 strcpy(lastLoadGameTitle, title);
9951 lastLoadGameUseList = useList;
9955 if (lg && lg->gameInfo.white && lg->gameInfo.black)
9957 snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
9958 lg->gameInfo.black);
9961 else if (*title != NULLCHAR)
9965 sprintf(buf, "%s %d", title, gameNumber);
9970 DisplayTitle(title);
9974 if (gameMode != AnalyzeFile && gameMode != AnalyzeMode)
9976 gameMode = PlayFromGameFile;
9980 currentMove = forwardMostMove = backwardMostMove = 0;
9981 CopyBoard(boards[0], initialPosition);
9985 * Skip the first gn-1 games in the file.
9986 * Also skip over anything that precedes an identifiable
9987 * start of game marker, to avoid being confused by
9988 * garbage at the start of the file. Currently
9989 * recognized start of game markers are the move number "1",
9990 * the pattern "gnuchess .* game", the pattern
9991 * "^[#;%] [^ ]* game file", and a PGN tag block.
9992 * A game that starts with one of the latter two patterns
9993 * will also have a move number 1, possibly
9994 * following a position diagram.
9995 * 5-4-02: Let's try being more lenient and allowing a game to
9996 * start with an unnumbered move. Does that break anything?
9998 cm = lastLoadGameStart = (ChessMove) 0;
10000 yyboardindex = forwardMostMove;
10001 cm = (ChessMove) yylex();
10003 case (ChessMove) 0:
10004 if (cmailMsgLoaded) {
10005 nCmailGames = CMAIL_MAX_GAMES - gn;
10008 DisplayError(_("Game not found in file"), 0);
10015 lastLoadGameStart = cm;
10018 case MoveNumberOne:
10019 switch (lastLoadGameStart) {
10024 case MoveNumberOne:
10025 case (ChessMove) 0:
10026 gn--; /* count this game */
10027 lastLoadGameStart = cm;
10036 switch (lastLoadGameStart) {
10039 case MoveNumberOne:
10040 case (ChessMove) 0:
10041 gn--; /* count this game */
10042 lastLoadGameStart = cm;
10045 lastLoadGameStart = cm; /* game counted already */
10053 yyboardindex = forwardMostMove;
10054 cm = (ChessMove) yylex();
10055 } while (cm == PGNTag || cm == Comment);
10062 if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
10063 if ( cmailResult[CMAIL_MAX_GAMES - gn - 1]
10064 != CMAIL_OLD_RESULT) {
10066 cmailResult[ CMAIL_MAX_GAMES
10067 - gn - 1] = CMAIL_OLD_RESULT;
10073 /* Only a NormalMove can be at the start of a game
10074 * without a position diagram. */
10075 if (lastLoadGameStart == (ChessMove) 0) {
10077 lastLoadGameStart = MoveNumberOne;
10086 if (appData.debugMode)
10087 fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
10089 if (cm == XBoardGame) {
10090 /* Skip any header junk before position diagram and/or move 1 */
10092 yyboardindex = forwardMostMove;
10093 cm = (ChessMove) yylex();
10095 if (cm == (ChessMove) 0 ||
10096 cm == GNUChessGame || cm == XBoardGame) {
10097 /* Empty game; pretend end-of-file and handle later */
10098 cm = (ChessMove) 0;
10102 if (cm == MoveNumberOne || cm == PositionDiagram ||
10103 cm == PGNTag || cm == Comment)
10106 } else if (cm == GNUChessGame) {
10107 if (gameInfo.event != NULL) {
10108 free(gameInfo.event);
10110 gameInfo.event = StrSave(yy_text);
10113 startedFromSetupPosition = FALSE;
10114 while (cm == PGNTag) {
10115 if (appData.debugMode)
10116 fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
10117 err = ParsePGNTag(yy_text, &gameInfo);
10118 if (!err) numPGNTags++;
10120 /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
10121 if(gameInfo.variant != oldVariant) {
10122 startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
10123 InitPosition(TRUE);
10124 oldVariant = gameInfo.variant;
10125 if (appData.debugMode)
10126 fprintf(debugFP, "New variant %d\n", (int) oldVariant);
10130 if (gameInfo.fen != NULL) {
10131 Board initial_position;
10132 startedFromSetupPosition = TRUE;
10133 if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
10135 DisplayError(_("Bad FEN position in file"), 0);
10138 CopyBoard(boards[0], initial_position);
10139 if (blackPlaysFirst) {
10140 currentMove = forwardMostMove = backwardMostMove = 1;
10141 CopyBoard(boards[1], initial_position);
10142 strcpy(moveList[0], "");
10143 strcpy(parseList[0], "");
10144 timeRemaining[0][1] = whiteTimeRemaining;
10145 timeRemaining[1][1] = blackTimeRemaining;
10146 if (commentList[0] != NULL) {
10147 commentList[1] = commentList[0];
10148 commentList[0] = NULL;
10151 currentMove = forwardMostMove = backwardMostMove = 0;
10153 /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
10155 initialRulePlies = FENrulePlies;
10156 for( i=0; i< nrCastlingRights; i++ )
10157 initialRights[i] = initial_position[CASTLING][i];
10159 yyboardindex = forwardMostMove;
10160 free(gameInfo.fen);
10161 gameInfo.fen = NULL;
10164 yyboardindex = forwardMostMove;
10165 cm = (ChessMove) yylex();
10167 /* Handle comments interspersed among the tags */
10168 while (cm == Comment) {
10170 if (appData.debugMode)
10171 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10173 AppendComment(currentMove, p, FALSE);
10174 yyboardindex = forwardMostMove;
10175 cm = (ChessMove) yylex();
10179 /* don't rely on existence of Event tag since if game was
10180 * pasted from clipboard the Event tag may not exist
10182 if (numPGNTags > 0){
10184 if (gameInfo.variant == VariantNormal) {
10185 gameInfo.variant = StringToVariant(gameInfo.event);
10188 if( appData.autoDisplayTags ) {
10189 tags = PGNTags(&gameInfo);
10190 TagsPopUp(tags, CmailMsg());
10195 /* Make something up, but don't display it now */
10200 if (cm == PositionDiagram) {
10203 Board initial_position;
10205 if (appData.debugMode)
10206 fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
10208 if (!startedFromSetupPosition) {
10210 for (i = BOARD_HEIGHT - 1; i >= 0; i--)
10211 for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
10221 initial_position[i][j++] = CharToPiece(*p);
10224 while (*p == ' ' || *p == '\t' ||
10225 *p == '\n' || *p == '\r') p++;
10227 if (strncmp(p, "black", strlen("black"))==0)
10228 blackPlaysFirst = TRUE;
10230 blackPlaysFirst = FALSE;
10231 startedFromSetupPosition = TRUE;
10233 CopyBoard(boards[0], initial_position);
10234 if (blackPlaysFirst) {
10235 currentMove = forwardMostMove = backwardMostMove = 1;
10236 CopyBoard(boards[1], initial_position);
10237 strcpy(moveList[0], "");
10238 strcpy(parseList[0], "");
10239 timeRemaining[0][1] = whiteTimeRemaining;
10240 timeRemaining[1][1] = blackTimeRemaining;
10241 if (commentList[0] != NULL) {
10242 commentList[1] = commentList[0];
10243 commentList[0] = NULL;
10246 currentMove = forwardMostMove = backwardMostMove = 0;
10249 yyboardindex = forwardMostMove;
10250 cm = (ChessMove) yylex();
10253 if (first.pr == NoProc) {
10254 StartChessProgram(&first);
10256 InitChessProgram(&first, FALSE);
10257 SendToProgram("force\n", &first);
10258 if (startedFromSetupPosition) {
10259 SendBoard(&first, forwardMostMove);
10260 if (appData.debugMode) {
10261 fprintf(debugFP, "Load Game\n");
10263 DisplayBothClocks();
10266 /* [HGM] server: flag to write setup moves in broadcast file as one */
10267 loadFlag = appData.suppressLoadMoves;
10269 while (cm == Comment) {
10271 if (appData.debugMode)
10272 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10274 AppendComment(currentMove, p, FALSE);
10275 yyboardindex = forwardMostMove;
10276 cm = (ChessMove) yylex();
10279 if ((cm == (ChessMove) 0 && lastLoadGameStart != (ChessMove) 0) ||
10280 cm == WhiteWins || cm == BlackWins ||
10281 cm == GameIsDrawn || cm == GameUnfinished) {
10282 DisplayMessage("", _("No moves in game"));
10283 if (cmailMsgLoaded) {
10284 if (appData.debugMode)
10285 fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
10289 DrawPosition(FALSE, boards[currentMove]);
10290 DisplayBothClocks();
10291 gameMode = EditGame;
10298 // [HGM] PV info: routine tests if comment empty
10299 if (!matchMode && (pausing || appData.timeDelay != 0)) {
10300 DisplayComment(currentMove - 1, commentList[currentMove]);
10302 if (!matchMode && appData.timeDelay != 0)
10303 DrawPosition(FALSE, boards[currentMove]);
10305 if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
10306 programStats.ok_to_send = 1;
10309 /* if the first token after the PGN tags is a move
10310 * and not move number 1, retrieve it from the parser
10312 if (cm != MoveNumberOne)
10313 LoadGameOneMove(cm);
10315 /* load the remaining moves from the file */
10316 while (LoadGameOneMove((ChessMove)0)) {
10317 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10318 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10321 /* rewind to the start of the game */
10322 currentMove = backwardMostMove;
10324 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10326 if (oldGameMode == AnalyzeFile ||
10327 oldGameMode == AnalyzeMode) {
10328 AnalyzeFileEvent();
10331 if (matchMode || appData.timeDelay == 0) {
10333 gameMode = EditGame;
10335 } else if (appData.timeDelay > 0) {
10336 AutoPlayGameLoop();
10339 if (appData.debugMode)
10340 fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
10342 loadFlag = 0; /* [HGM] true game starts */
10346 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
10348 ReloadPosition(offset)
10351 int positionNumber = lastLoadPositionNumber + offset;
10352 if (lastLoadPositionFP == NULL) {
10353 DisplayError(_("No position has been loaded yet"), 0);
10356 if (positionNumber <= 0) {
10357 DisplayError(_("Can't back up any further"), 0);
10360 return LoadPosition(lastLoadPositionFP, positionNumber,
10361 lastLoadPositionTitle);
10364 /* Load the nth position from the given file */
10366 LoadPositionFromFile(filename, n, title)
10374 if (strcmp(filename, "-") == 0) {
10375 return LoadPosition(stdin, n, "stdin");
10377 f = fopen(filename, "rb");
10379 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10380 DisplayError(buf, errno);
10383 return LoadPosition(f, n, title);
10388 /* Load the nth position from the given open file, and close it */
10390 LoadPosition(f, positionNumber, title)
10392 int positionNumber;
10395 char *p, line[MSG_SIZ];
10396 Board initial_position;
10397 int i, j, fenMode, pn;
10399 if (gameMode == Training )
10400 SetTrainingModeOff();
10402 if (gameMode != BeginningOfGame) {
10403 Reset(FALSE, TRUE);
10405 if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
10406 fclose(lastLoadPositionFP);
10408 if (positionNumber == 0) positionNumber = 1;
10409 lastLoadPositionFP = f;
10410 lastLoadPositionNumber = positionNumber;
10411 strcpy(lastLoadPositionTitle, title);
10412 if (first.pr == NoProc) {
10413 StartChessProgram(&first);
10414 InitChessProgram(&first, FALSE);
10416 pn = positionNumber;
10417 if (positionNumber < 0) {
10418 /* Negative position number means to seek to that byte offset */
10419 if (fseek(f, -positionNumber, 0) == -1) {
10420 DisplayError(_("Can't seek on position file"), 0);
10425 if (fseek(f, 0, 0) == -1) {
10426 if (f == lastLoadPositionFP ?
10427 positionNumber == lastLoadPositionNumber + 1 :
10428 positionNumber == 1) {
10431 DisplayError(_("Can't seek on position file"), 0);
10436 /* See if this file is FEN or old-style xboard */
10437 if (fgets(line, MSG_SIZ, f) == NULL) {
10438 DisplayError(_("Position not found in file"), 0);
10441 // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
10442 fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
10445 if (fenMode || line[0] == '#') pn--;
10447 /* skip positions before number pn */
10448 if (fgets(line, MSG_SIZ, f) == NULL) {
10450 DisplayError(_("Position not found in file"), 0);
10453 if (fenMode || line[0] == '#') pn--;
10458 if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
10459 DisplayError(_("Bad FEN position in file"), 0);
10463 (void) fgets(line, MSG_SIZ, f);
10464 (void) fgets(line, MSG_SIZ, f);
10466 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
10467 (void) fgets(line, MSG_SIZ, f);
10468 for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
10471 initial_position[i][j++] = CharToPiece(*p);
10475 blackPlaysFirst = FALSE;
10477 (void) fgets(line, MSG_SIZ, f);
10478 if (strncmp(line, "black", strlen("black"))==0)
10479 blackPlaysFirst = TRUE;
10482 startedFromSetupPosition = TRUE;
10484 SendToProgram("force\n", &first);
10485 CopyBoard(boards[0], initial_position);
10486 if (blackPlaysFirst) {
10487 currentMove = forwardMostMove = backwardMostMove = 1;
10488 strcpy(moveList[0], "");
10489 strcpy(parseList[0], "");
10490 CopyBoard(boards[1], initial_position);
10491 DisplayMessage("", _("Black to play"));
10493 currentMove = forwardMostMove = backwardMostMove = 0;
10494 DisplayMessage("", _("White to play"));
10496 initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
10497 SendBoard(&first, forwardMostMove);
10498 if (appData.debugMode) {
10500 for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
10501 for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
10502 fprintf(debugFP, "Load Position\n");
10505 if (positionNumber > 1) {
10506 sprintf(line, "%s %d", title, positionNumber);
10507 DisplayTitle(line);
10509 DisplayTitle(title);
10511 gameMode = EditGame;
10514 timeRemaining[0][1] = whiteTimeRemaining;
10515 timeRemaining[1][1] = blackTimeRemaining;
10516 DrawPosition(FALSE, boards[currentMove]);
10523 CopyPlayerNameIntoFileName(dest, src)
10526 while (*src != NULLCHAR && *src != ',') {
10531 *(*dest)++ = *src++;
10536 char *DefaultFileName(ext)
10539 static char def[MSG_SIZ];
10542 if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
10544 CopyPlayerNameIntoFileName(&p, gameInfo.white);
10546 CopyPlayerNameIntoFileName(&p, gameInfo.black);
10555 /* Save the current game to the given file */
10557 SaveGameToFile(filename, append)
10564 if (strcmp(filename, "-") == 0) {
10565 return SaveGame(stdout, 0, NULL);
10567 f = fopen(filename, append ? "a" : "w");
10569 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10570 DisplayError(buf, errno);
10573 return SaveGame(f, 0, NULL);
10582 static char buf[MSG_SIZ];
10585 p = strchr(str, ' ');
10586 if (p == NULL) return str;
10587 strncpy(buf, str, p - str);
10588 buf[p - str] = NULLCHAR;
10592 #define PGN_MAX_LINE 75
10594 #define PGN_SIDE_WHITE 0
10595 #define PGN_SIDE_BLACK 1
10598 static int FindFirstMoveOutOfBook( int side )
10602 if( backwardMostMove == 0 && ! startedFromSetupPosition) {
10603 int index = backwardMostMove;
10604 int has_book_hit = 0;
10606 if( (index % 2) != side ) {
10610 while( index < forwardMostMove ) {
10611 /* Check to see if engine is in book */
10612 int depth = pvInfoList[index].depth;
10613 int score = pvInfoList[index].score;
10619 else if( score == 0 && depth == 63 ) {
10620 in_book = 1; /* Zappa */
10622 else if( score == 2 && depth == 99 ) {
10623 in_book = 1; /* Abrok */
10626 has_book_hit += in_book;
10642 void GetOutOfBookInfo( char * buf )
10646 int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10648 oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
10649 oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
10653 if( oob[0] >= 0 || oob[1] >= 0 ) {
10654 for( i=0; i<2; i++ ) {
10658 if( i > 0 && oob[0] >= 0 ) {
10659 strcat( buf, " " );
10662 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
10663 sprintf( buf+strlen(buf), "%s%.2f",
10664 pvInfoList[idx].score >= 0 ? "+" : "",
10665 pvInfoList[idx].score / 100.0 );
10671 /* Save game in PGN style and close the file */
10676 int i, offset, linelen, newblock;
10680 int movelen, numlen, blank;
10681 char move_buffer[100]; /* [AS] Buffer for move+PV info */
10683 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10685 tm = time((time_t *) NULL);
10687 PrintPGNTags(f, &gameInfo);
10689 if (backwardMostMove > 0 || startedFromSetupPosition) {
10690 char *fen = PositionToFEN(backwardMostMove, NULL);
10691 fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
10692 fprintf(f, "\n{--------------\n");
10693 PrintPosition(f, backwardMostMove);
10694 fprintf(f, "--------------}\n");
10698 /* [AS] Out of book annotation */
10699 if( appData.saveOutOfBookInfo ) {
10702 GetOutOfBookInfo( buf );
10704 if( buf[0] != '\0' ) {
10705 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
10712 i = backwardMostMove;
10716 while (i < forwardMostMove) {
10717 /* Print comments preceding this move */
10718 if (commentList[i] != NULL) {
10719 if (linelen > 0) fprintf(f, "\n");
10720 fprintf(f, "%s", commentList[i]);
10725 /* Format move number */
10726 if ((i % 2) == 0) {
10727 sprintf(numtext, "%d.", (i - offset)/2 + 1);
10730 sprintf(numtext, "%d...", (i - offset)/2 + 1);
10732 numtext[0] = NULLCHAR;
10735 numlen = strlen(numtext);
10738 /* Print move number */
10739 blank = linelen > 0 && numlen > 0;
10740 if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
10749 fprintf(f, "%s", numtext);
10753 strcpy(move_buffer, SavePart(parseList[i])); // [HGM] pgn: print move via buffer, so it can be edited
10754 movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
10757 blank = linelen > 0 && movelen > 0;
10758 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
10767 fprintf(f, "%s", move_buffer);
10768 linelen += movelen;
10770 /* [AS] Add PV info if present */
10771 if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
10772 /* [HGM] add time */
10773 char buf[MSG_SIZ]; int seconds;
10775 seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
10777 if( seconds <= 0) buf[0] = 0; else
10778 if( seconds < 30 ) sprintf(buf, " %3.1f%c", seconds/10., 0); else {
10779 seconds = (seconds + 4)/10; // round to full seconds
10780 if( seconds < 60 ) sprintf(buf, " %d%c", seconds, 0); else
10781 sprintf(buf, " %d:%02d%c", seconds/60, seconds%60, 0);
10784 sprintf( move_buffer, "{%s%.2f/%d%s}",
10785 pvInfoList[i].score >= 0 ? "+" : "",
10786 pvInfoList[i].score / 100.0,
10787 pvInfoList[i].depth,
10790 movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
10792 /* Print score/depth */
10793 blank = linelen > 0 && movelen > 0;
10794 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
10803 fprintf(f, "%s", move_buffer);
10804 linelen += movelen;
10810 /* Start a new line */
10811 if (linelen > 0) fprintf(f, "\n");
10813 /* Print comments after last move */
10814 if (commentList[i] != NULL) {
10815 fprintf(f, "%s\n", commentList[i]);
10819 if (gameInfo.resultDetails != NULL &&
10820 gameInfo.resultDetails[0] != NULLCHAR) {
10821 fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
10822 PGNResult(gameInfo.result));
10824 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
10828 lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
10832 /* Save game in old style and close the file */
10834 SaveGameOldStyle(f)
10840 tm = time((time_t *) NULL);
10842 fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
10845 if (backwardMostMove > 0 || startedFromSetupPosition) {
10846 fprintf(f, "\n[--------------\n");
10847 PrintPosition(f, backwardMostMove);
10848 fprintf(f, "--------------]\n");
10853 i = backwardMostMove;
10854 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10856 while (i < forwardMostMove) {
10857 if (commentList[i] != NULL) {
10858 fprintf(f, "[%s]\n", commentList[i]);
10861 if ((i % 2) == 1) {
10862 fprintf(f, "%d. ... %s\n", (i - offset)/2 + 1, parseList[i]);
10865 fprintf(f, "%d. %s ", (i - offset)/2 + 1, parseList[i]);
10867 if (commentList[i] != NULL) {
10871 if (i >= forwardMostMove) {
10875 fprintf(f, "%s\n", parseList[i]);
10880 if (commentList[i] != NULL) {
10881 fprintf(f, "[%s]\n", commentList[i]);
10884 /* This isn't really the old style, but it's close enough */
10885 if (gameInfo.resultDetails != NULL &&
10886 gameInfo.resultDetails[0] != NULLCHAR) {
10887 fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
10888 gameInfo.resultDetails);
10890 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
10897 /* Save the current game to open file f and close the file */
10899 SaveGame(f, dummy, dummy2)
10904 if (gameMode == EditPosition) EditPositionDone(TRUE);
10905 lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
10906 if (appData.oldSaveStyle)
10907 return SaveGameOldStyle(f);
10909 return SaveGamePGN(f);
10912 /* Save the current position to the given file */
10914 SavePositionToFile(filename)
10920 if (strcmp(filename, "-") == 0) {
10921 return SavePosition(stdout, 0, NULL);
10923 f = fopen(filename, "a");
10925 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10926 DisplayError(buf, errno);
10929 SavePosition(f, 0, NULL);
10935 /* Save the current position to the given open file and close the file */
10937 SavePosition(f, dummy, dummy2)
10944 if (gameMode == EditPosition) EditPositionDone(TRUE);
10945 if (appData.oldSaveStyle) {
10946 tm = time((time_t *) NULL);
10948 fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
10950 fprintf(f, "[--------------\n");
10951 PrintPosition(f, currentMove);
10952 fprintf(f, "--------------]\n");
10954 fen = PositionToFEN(currentMove, NULL);
10955 fprintf(f, "%s\n", fen);
10963 ReloadCmailMsgEvent(unregister)
10967 static char *inFilename = NULL;
10968 static char *outFilename;
10970 struct stat inbuf, outbuf;
10973 /* Any registered moves are unregistered if unregister is set, */
10974 /* i.e. invoked by the signal handler */
10976 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
10977 cmailMoveRegistered[i] = FALSE;
10978 if (cmailCommentList[i] != NULL) {
10979 free(cmailCommentList[i]);
10980 cmailCommentList[i] = NULL;
10983 nCmailMovesRegistered = 0;
10986 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
10987 cmailResult[i] = CMAIL_NOT_RESULT;
10991 if (inFilename == NULL) {
10992 /* Because the filenames are static they only get malloced once */
10993 /* and they never get freed */
10994 inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
10995 sprintf(inFilename, "%s.game.in", appData.cmailGameName);
10997 outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
10998 sprintf(outFilename, "%s.out", appData.cmailGameName);
11001 status = stat(outFilename, &outbuf);
11003 cmailMailedMove = FALSE;
11005 status = stat(inFilename, &inbuf);
11006 cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
11009 /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
11010 counts the games, notes how each one terminated, etc.
11012 It would be nice to remove this kludge and instead gather all
11013 the information while building the game list. (And to keep it
11014 in the game list nodes instead of having a bunch of fixed-size
11015 parallel arrays.) Note this will require getting each game's
11016 termination from the PGN tags, as the game list builder does
11017 not process the game moves. --mann
11019 cmailMsgLoaded = TRUE;
11020 LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
11022 /* Load first game in the file or popup game menu */
11023 LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
11025 #endif /* !WIN32 */
11033 char string[MSG_SIZ];
11035 if ( cmailMailedMove
11036 || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
11037 return TRUE; /* Allow free viewing */
11040 /* Unregister move to ensure that we don't leave RegisterMove */
11041 /* with the move registered when the conditions for registering no */
11043 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11044 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
11045 nCmailMovesRegistered --;
11047 if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
11049 free(cmailCommentList[lastLoadGameNumber - 1]);
11050 cmailCommentList[lastLoadGameNumber - 1] = NULL;
11054 if (cmailOldMove == -1) {
11055 DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
11059 if (currentMove > cmailOldMove + 1) {
11060 DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
11064 if (currentMove < cmailOldMove) {
11065 DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
11069 if (forwardMostMove > currentMove) {
11070 /* Silently truncate extra moves */
11074 if ( (currentMove == cmailOldMove + 1)
11075 || ( (currentMove == cmailOldMove)
11076 && ( (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
11077 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
11078 if (gameInfo.result != GameUnfinished) {
11079 cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
11082 if (commentList[currentMove] != NULL) {
11083 cmailCommentList[lastLoadGameNumber - 1]
11084 = StrSave(commentList[currentMove]);
11086 strcpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1]);
11088 if (appData.debugMode)
11089 fprintf(debugFP, "Saving %s for game %d\n",
11090 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
11093 "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
11095 f = fopen(string, "w");
11096 if (appData.oldSaveStyle) {
11097 SaveGameOldStyle(f); /* also closes the file */
11099 sprintf(string, "%s.pos.out", appData.cmailGameName);
11100 f = fopen(string, "w");
11101 SavePosition(f, 0, NULL); /* also closes the file */
11103 fprintf(f, "{--------------\n");
11104 PrintPosition(f, currentMove);
11105 fprintf(f, "--------------}\n\n");
11107 SaveGame(f, 0, NULL); /* also closes the file*/
11110 cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
11111 nCmailMovesRegistered ++;
11112 } else if (nCmailGames == 1) {
11113 DisplayError(_("You have not made a move yet"), 0);
11124 static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
11125 FILE *commandOutput;
11126 char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
11127 int nBytes = 0; /* Suppress warnings on uninitialized variables */
11133 if (! cmailMsgLoaded) {
11134 DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
11138 if (nCmailGames == nCmailResults) {
11139 DisplayError(_("No unfinished games"), 0);
11143 #if CMAIL_PROHIBIT_REMAIL
11144 if (cmailMailedMove) {
11145 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);
11146 DisplayError(msg, 0);
11151 if (! (cmailMailedMove || RegisterMove())) return;
11153 if ( cmailMailedMove
11154 || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
11155 sprintf(string, partCommandString,
11156 appData.debugMode ? " -v" : "", appData.cmailGameName);
11157 commandOutput = popen(string, "r");
11159 if (commandOutput == NULL) {
11160 DisplayError(_("Failed to invoke cmail"), 0);
11162 for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
11163 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
11165 if (nBuffers > 1) {
11166 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
11167 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
11168 nBytes = MSG_SIZ - 1;
11170 (void) memcpy(msg, buffer, nBytes);
11172 *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
11174 if(StrStr(msg, "Mailed cmail message to ") != NULL) {
11175 cmailMailedMove = TRUE; /* Prevent >1 moves */
11178 for (i = 0; i < nCmailGames; i ++) {
11179 if (cmailResult[i] == CMAIL_NOT_RESULT) {
11184 && ( (arcDir = (char *) getenv("CMAIL_ARCDIR"))
11186 sprintf(buffer, "%s/%s.%s.archive",
11188 appData.cmailGameName,
11190 LoadGameFromFile(buffer, 1, buffer, FALSE);
11191 cmailMsgLoaded = FALSE;
11195 DisplayInformation(msg);
11196 pclose(commandOutput);
11199 if ((*cmailMsg) != '\0') {
11200 DisplayInformation(cmailMsg);
11205 #endif /* !WIN32 */
11214 int prependComma = 0;
11216 char string[MSG_SIZ]; /* Space for game-list */
11219 if (!cmailMsgLoaded) return "";
11221 if (cmailMailedMove) {
11222 sprintf(cmailMsg, _("Waiting for reply from opponent\n"));
11224 /* Create a list of games left */
11225 sprintf(string, "[");
11226 for (i = 0; i < nCmailGames; i ++) {
11227 if (! ( cmailMoveRegistered[i]
11228 || (cmailResult[i] == CMAIL_OLD_RESULT))) {
11229 if (prependComma) {
11230 sprintf(number, ",%d", i + 1);
11232 sprintf(number, "%d", i + 1);
11236 strcat(string, number);
11239 strcat(string, "]");
11241 if (nCmailMovesRegistered + nCmailResults == 0) {
11242 switch (nCmailGames) {
11245 _("Still need to make move for game\n"));
11250 _("Still need to make moves for both games\n"));
11255 _("Still need to make moves for all %d games\n"),
11260 switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
11263 _("Still need to make a move for game %s\n"),
11268 if (nCmailResults == nCmailGames) {
11269 sprintf(cmailMsg, _("No unfinished games\n"));
11271 sprintf(cmailMsg, _("Ready to send mail\n"));
11277 _("Still need to make moves for games %s\n"),
11289 if (gameMode == Training)
11290 SetTrainingModeOff();
11293 cmailMsgLoaded = FALSE;
11294 if (appData.icsActive) {
11295 SendToICS(ics_prefix);
11296 SendToICS("refresh\n");
11306 /* Give up on clean exit */
11310 /* Keep trying for clean exit */
11314 if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
11316 if (telnetISR != NULL) {
11317 RemoveInputSource(telnetISR);
11319 if (icsPR != NoProc) {
11320 DestroyChildProcess(icsPR, TRUE);
11323 /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
11324 GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
11326 /* [HGM] crash: the above GameEnds() is a dud if another one was running */
11327 /* make sure this other one finishes before killing it! */
11328 if(endingGame) { int count = 0;
11329 if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
11330 while(endingGame && count++ < 10) DoSleep(1);
11331 if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
11334 /* Kill off chess programs */
11335 if (first.pr != NoProc) {
11338 DoSleep( appData.delayBeforeQuit );
11339 SendToProgram("quit\n", &first);
11340 DoSleep( appData.delayAfterQuit );
11341 DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
11343 if (second.pr != NoProc) {
11344 DoSleep( appData.delayBeforeQuit );
11345 SendToProgram("quit\n", &second);
11346 DoSleep( appData.delayAfterQuit );
11347 DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
11349 if (first.isr != NULL) {
11350 RemoveInputSource(first.isr);
11352 if (second.isr != NULL) {
11353 RemoveInputSource(second.isr);
11356 ShutDownFrontEnd();
11363 if (appData.debugMode)
11364 fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
11368 if (gameMode == MachinePlaysWhite ||
11369 gameMode == MachinePlaysBlack) {
11372 DisplayBothClocks();
11374 if (gameMode == PlayFromGameFile) {
11375 if (appData.timeDelay >= 0)
11376 AutoPlayGameLoop();
11377 } else if (gameMode == IcsExamining && pauseExamInvalid) {
11378 Reset(FALSE, TRUE);
11379 SendToICS(ics_prefix);
11380 SendToICS("refresh\n");
11381 } else if (currentMove < forwardMostMove) {
11382 ForwardInner(forwardMostMove);
11384 pauseExamInvalid = FALSE;
11386 switch (gameMode) {
11390 pauseExamForwardMostMove = forwardMostMove;
11391 pauseExamInvalid = FALSE;
11394 case IcsPlayingWhite:
11395 case IcsPlayingBlack:
11399 case PlayFromGameFile:
11400 (void) StopLoadGameTimer();
11404 case BeginningOfGame:
11405 if (appData.icsActive) return;
11406 /* else fall through */
11407 case MachinePlaysWhite:
11408 case MachinePlaysBlack:
11409 case TwoMachinesPlay:
11410 if (forwardMostMove == 0)
11411 return; /* don't pause if no one has moved */
11412 if ((gameMode == MachinePlaysWhite &&
11413 !WhiteOnMove(forwardMostMove)) ||
11414 (gameMode == MachinePlaysBlack &&
11415 WhiteOnMove(forwardMostMove))) {
11428 char title[MSG_SIZ];
11430 if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
11431 strcpy(title, _("Edit comment"));
11433 sprintf(title, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
11434 WhiteOnMove(currentMove - 1) ? " " : ".. ",
11435 parseList[currentMove - 1]);
11438 EditCommentPopUp(currentMove, title, commentList[currentMove]);
11445 char *tags = PGNTags(&gameInfo);
11446 EditTagsPopUp(tags);
11453 if (appData.noChessProgram || gameMode == AnalyzeMode)
11456 if (gameMode != AnalyzeFile) {
11457 if (!appData.icsEngineAnalyze) {
11459 if (gameMode != EditGame) return;
11461 ResurrectChessProgram();
11462 SendToProgram("analyze\n", &first);
11463 first.analyzing = TRUE;
11464 /*first.maybeThinking = TRUE;*/
11465 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11466 EngineOutputPopUp();
11468 if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
11473 StartAnalysisClock();
11474 GetTimeMark(&lastNodeCountTime);
11481 if (appData.noChessProgram || gameMode == AnalyzeFile)
11484 if (gameMode != AnalyzeMode) {
11486 if (gameMode != EditGame) return;
11487 ResurrectChessProgram();
11488 SendToProgram("analyze\n", &first);
11489 first.analyzing = TRUE;
11490 /*first.maybeThinking = TRUE;*/
11491 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11492 EngineOutputPopUp();
11494 gameMode = AnalyzeFile;
11499 StartAnalysisClock();
11500 GetTimeMark(&lastNodeCountTime);
11505 MachineWhiteEvent()
11508 char *bookHit = NULL;
11510 if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
11514 if (gameMode == PlayFromGameFile ||
11515 gameMode == TwoMachinesPlay ||
11516 gameMode == Training ||
11517 gameMode == AnalyzeMode ||
11518 gameMode == EndOfGame)
11521 if (gameMode == EditPosition)
11522 EditPositionDone(TRUE);
11524 if (!WhiteOnMove(currentMove)) {
11525 DisplayError(_("It is not White's turn"), 0);
11529 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
11532 if (gameMode == EditGame || gameMode == AnalyzeMode ||
11533 gameMode == AnalyzeFile)
11536 ResurrectChessProgram(); /* in case it isn't running */
11537 if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
11538 gameMode = MachinePlaysWhite;
11541 gameMode = MachinePlaysWhite;
11545 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
11547 if (first.sendName) {
11548 sprintf(buf, "name %s\n", gameInfo.black);
11549 SendToProgram(buf, &first);
11551 if (first.sendTime) {
11552 if (first.useColors) {
11553 SendToProgram("black\n", &first); /*gnu kludge*/
11555 SendTimeRemaining(&first, TRUE);
11557 if (first.useColors) {
11558 SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
11560 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
11561 SetMachineThinkingEnables();
11562 first.maybeThinking = TRUE;
11566 if (appData.autoFlipView && !flipView) {
11567 flipView = !flipView;
11568 DrawPosition(FALSE, NULL);
11569 DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;
11572 if(bookHit) { // [HGM] book: simulate book reply
11573 static char bookMove[MSG_SIZ]; // a bit generous?
11575 programStats.nodes = programStats.depth = programStats.time =
11576 programStats.score = programStats.got_only_move = 0;
11577 sprintf(programStats.movelist, "%s (xbook)", bookHit);
11579 strcpy(bookMove, "move ");
11580 strcat(bookMove, bookHit);
11581 HandleMachineMove(bookMove, &first);
11586 MachineBlackEvent()
11589 char *bookHit = NULL;
11591 if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
11595 if (gameMode == PlayFromGameFile
11596 || gameMode == TwoMachinesPlay
11597 || gameMode == Training
11598 || gameMode == AnalyzeMode
11599 || gameMode == EndOfGame)
11602 if (gameMode == EditPosition)
11603 EditPositionDone(TRUE);
11605 if (WhiteOnMove(currentMove))
11607 DisplayError(_("It is not Black's turn"), 0);
11611 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
11614 if (gameMode == EditGame || gameMode == AnalyzeMode
11615 || gameMode == AnalyzeFile)
11618 ResurrectChessProgram(); /* in case it isn't running */
11619 gameMode = MachinePlaysBlack;
11623 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
11625 if (first.sendName)
11627 sprintf(buf, "name %s\n", gameInfo.white);
11628 SendToProgram(buf, &first);
11630 if (first.sendTime)
11632 if (first.useColors)
11634 SendToProgram("white\n", &first); /*gnu kludge*/
11636 SendTimeRemaining(&first, FALSE);
11638 if (first.useColors)
11640 SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
11642 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
11643 SetMachineThinkingEnables();
11644 first.maybeThinking = TRUE;
11647 if (appData.autoFlipView && flipView)
11649 flipView = !flipView;
11650 DrawPosition(FALSE, NULL);
11651 DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;
11654 { // [HGM] book: simulate book reply
11655 static char bookMove[MSG_SIZ]; // a bit generous?
11657 programStats.nodes = programStats.depth = programStats.time
11658 = programStats.score = programStats.got_only_move = 0;
11659 sprintf(programStats.movelist, "%s (xbook)", bookHit);
11661 strcpy(bookMove, "move ");
11662 strcat(bookMove, bookHit);
11663 HandleMachineMove(bookMove, &first);
11670 DisplayTwoMachinesTitle()
11673 if (appData.matchGames > 0) {
11674 if (first.twoMachinesColor[0] == 'w') {
11675 sprintf(buf, "%s vs. %s (%d-%d-%d)",
11676 gameInfo.white, gameInfo.black,
11677 first.matchWins, second.matchWins,
11678 matchGame - 1 - (first.matchWins + second.matchWins));
11680 sprintf(buf, "%s vs. %s (%d-%d-%d)",
11681 gameInfo.white, gameInfo.black,
11682 second.matchWins, first.matchWins,
11683 matchGame - 1 - (first.matchWins + second.matchWins));
11686 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
11692 TwoMachinesEvent P((void))
11696 ChessProgramState *onmove;
11697 char *bookHit = NULL;
11699 if (appData.noChessProgram) return;
11701 switch (gameMode) {
11702 case TwoMachinesPlay:
11704 case MachinePlaysWhite:
11705 case MachinePlaysBlack:
11706 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
11707 DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
11711 case BeginningOfGame:
11712 case PlayFromGameFile:
11715 if (gameMode != EditGame) return;
11718 EditPositionDone(TRUE);
11729 // forwardMostMove = currentMove;
11730 TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
11731 ResurrectChessProgram(); /* in case first program isn't running */
11733 if (second.pr == NULL) {
11734 StartChessProgram(&second);
11735 if (second.protocolVersion == 1) {
11736 TwoMachinesEventIfReady();
11738 /* kludge: allow timeout for initial "feature" command */
11740 DisplayMessage("", _("Starting second chess program"));
11741 ScheduleDelayedEvent(TwoMachinesEventIfReady, FEATURE_TIMEOUT);
11745 DisplayMessage("", "");
11746 InitChessProgram(&second, FALSE);
11747 SendToProgram("force\n", &second);
11748 if (startedFromSetupPosition) {
11749 SendBoard(&second, backwardMostMove);
11750 if (appData.debugMode) {
11751 fprintf(debugFP, "Two Machines\n");
11754 for (i = backwardMostMove; i < forwardMostMove; i++) {
11755 SendMoveToProgram(i, &second);
11758 gameMode = TwoMachinesPlay;
11762 DisplayTwoMachinesTitle();
11764 if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
11770 SendToProgram(first.computerString, &first);
11771 if (first.sendName) {
11772 sprintf(buf, "name %s\n", second.tidy);
11773 SendToProgram(buf, &first);
11775 SendToProgram(second.computerString, &second);
11776 if (second.sendName) {
11777 sprintf(buf, "name %s\n", first.tidy);
11778 SendToProgram(buf, &second);
11782 if (!first.sendTime || !second.sendTime) {
11783 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11784 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11786 if (onmove->sendTime) {
11787 if (onmove->useColors) {
11788 SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
11790 SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
11792 if (onmove->useColors) {
11793 SendToProgram(onmove->twoMachinesColor, onmove);
11795 bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
11796 // SendToProgram("go\n", onmove);
11797 onmove->maybeThinking = TRUE;
11798 SetMachineThinkingEnables();
11802 if(bookHit) { // [HGM] book: simulate book reply
11803 static char bookMove[MSG_SIZ]; // a bit generous?
11805 programStats.nodes = programStats.depth = programStats.time =
11806 programStats.score = programStats.got_only_move = 0;
11807 sprintf(programStats.movelist, "%s (xbook)", bookHit);
11809 strcpy(bookMove, "move ");
11810 strcat(bookMove, bookHit);
11811 savedMessage = bookMove; // args for deferred call
11812 savedState = onmove;
11813 ScheduleDelayedEvent(DeferredBookMove, 1);
11820 if (gameMode == Training) {
11821 SetTrainingModeOff();
11822 gameMode = PlayFromGameFile;
11823 DisplayMessage("", _("Training mode off"));
11825 gameMode = Training;
11826 animateTraining = appData.animate;
11828 /* make sure we are not already at the end of the game */
11829 if (currentMove < forwardMostMove) {
11830 SetTrainingModeOn();
11831 DisplayMessage("", _("Training mode on"));
11833 gameMode = PlayFromGameFile;
11834 DisplayError(_("Already at end of game"), 0);
11843 if (!appData.icsActive) return;
11844 switch (gameMode) {
11845 case IcsPlayingWhite:
11846 case IcsPlayingBlack:
11849 case BeginningOfGame:
11857 EditPositionDone(TRUE);
11870 gameMode = IcsIdle;
11881 switch (gameMode) {
11883 SetTrainingModeOff();
11885 case MachinePlaysWhite:
11886 case MachinePlaysBlack:
11887 case BeginningOfGame:
11888 SendToProgram("force\n", &first);
11889 SetUserThinkingEnables();
11891 case PlayFromGameFile:
11892 (void) StopLoadGameTimer();
11893 if (gameFileFP != NULL) {
11898 EditPositionDone(TRUE);
11903 SendToProgram("force\n", &first);
11905 case TwoMachinesPlay:
11906 GameEnds((ChessMove) 0, NULL, GE_PLAYER);
11907 ResurrectChessProgram();
11908 SetUserThinkingEnables();
11911 ResurrectChessProgram();
11913 case IcsPlayingBlack:
11914 case IcsPlayingWhite:
11915 DisplayError(_("Warning: You are still playing a game"), 0);
11918 DisplayError(_("Warning: You are still observing a game"), 0);
11921 DisplayError(_("Warning: You are still examining a game"), 0);
11932 first.offeredDraw = second.offeredDraw = 0;
11934 if (gameMode == PlayFromGameFile) {
11935 whiteTimeRemaining = timeRemaining[0][currentMove];
11936 blackTimeRemaining = timeRemaining[1][currentMove];
11940 if (gameMode == MachinePlaysWhite ||
11941 gameMode == MachinePlaysBlack ||
11942 gameMode == TwoMachinesPlay ||
11943 gameMode == EndOfGame) {
11944 i = forwardMostMove;
11945 while (i > currentMove) {
11946 SendToProgram("undo\n", &first);
11949 whiteTimeRemaining = timeRemaining[0][currentMove];
11950 blackTimeRemaining = timeRemaining[1][currentMove];
11951 DisplayBothClocks();
11952 if (whiteFlag || blackFlag) {
11953 whiteFlag = blackFlag = 0;
11958 gameMode = EditGame;
11965 EditPositionEvent()
11967 if (gameMode == EditPosition) {
11973 if (gameMode != EditGame) return;
11975 gameMode = EditPosition;
11978 if (currentMove > 0)
11979 CopyBoard(boards[0], boards[currentMove]);
11981 blackPlaysFirst = !WhiteOnMove(currentMove);
11983 currentMove = forwardMostMove = backwardMostMove = 0;
11984 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11991 /* [DM] icsEngineAnalyze - possible call from other functions */
11992 if (appData.icsEngineAnalyze) {
11993 appData.icsEngineAnalyze = FALSE;
11995 DisplayMessage("",_("Close ICS engine analyze..."));
11997 if (first.analysisSupport && first.analyzing) {
11998 SendToProgram("exit\n", &first);
11999 first.analyzing = FALSE;
12001 thinkOutput[0] = NULLCHAR;
12005 EditPositionDone(Boolean fakeRights)
12007 int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
12009 startedFromSetupPosition = TRUE;
12010 InitChessProgram(&first, FALSE);
12011 if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
12012 boards[0][EP_STATUS] = EP_NONE;
12013 boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
12014 if(boards[0][0][BOARD_WIDTH>>1] == king) {
12015 boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : NoRights;
12016 boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
12017 } else boards[0][CASTLING][2] = NoRights;
12018 if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
12019 boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : NoRights;
12020 boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
12021 } else boards[0][CASTLING][5] = NoRights;
12023 SendToProgram("force\n", &first);
12024 if (blackPlaysFirst) {
12025 strcpy(moveList[0], "");
12026 strcpy(parseList[0], "");
12027 currentMove = forwardMostMove = backwardMostMove = 1;
12028 CopyBoard(boards[1], boards[0]);
12030 currentMove = forwardMostMove = backwardMostMove = 0;
12032 SendBoard(&first, forwardMostMove);
12033 if (appData.debugMode) {
12034 fprintf(debugFP, "EditPosDone\n");
12037 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12038 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12039 gameMode = EditGame;
12041 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12042 ClearHighlights(); /* [AS] */
12045 /* Pause for `ms' milliseconds */
12046 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
12056 } while (SubtractTimeMarks(&m2, &m1) < ms);
12059 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
12061 SendMultiLineToICS(buf)
12064 char temp[MSG_SIZ+1], *p;
12071 strncpy(temp, buf, len);
12076 if (*p == '\n' || *p == '\r')
12081 strcat(temp, "\n");
12083 SendToPlayer(temp, strlen(temp));
12087 SetWhiteToPlayEvent()
12089 if (gameMode == EditPosition) {
12090 blackPlaysFirst = FALSE;
12091 DisplayBothClocks(); /* works because currentMove is 0 */
12092 } else if (gameMode == IcsExamining) {
12093 SendToICS(ics_prefix);
12094 SendToICS("tomove white\n");
12099 SetBlackToPlayEvent()
12101 if (gameMode == EditPosition) {
12102 blackPlaysFirst = TRUE;
12103 currentMove = 1; /* kludge */
12104 DisplayBothClocks();
12106 } else if (gameMode == IcsExamining) {
12107 SendToICS(ics_prefix);
12108 SendToICS("tomove black\n");
12113 EditPositionMenuEvent(selection, x, y)
12114 ChessSquare selection;
12118 ChessSquare piece = boards[0][y][x];
12120 if (gameMode != EditPosition && gameMode != IcsExamining) return;
12122 switch (selection) {
12124 if (gameMode == IcsExamining && ics_type == ICS_FICS) {
12125 SendToICS(ics_prefix);
12126 SendToICS("bsetup clear\n");
12127 } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
12128 SendToICS(ics_prefix);
12129 SendToICS("clearboard\n");
12131 for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
12132 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
12133 for (y = 0; y < BOARD_HEIGHT; y++) {
12134 if (gameMode == IcsExamining) {
12135 if (boards[currentMove][y][x] != EmptySquare) {
12136 sprintf(buf, "%sx@%c%c\n", ics_prefix,
12141 boards[0][y][x] = p;
12146 if (gameMode == EditPosition) {
12147 DrawPosition(FALSE, boards[0]);
12152 SetWhiteToPlayEvent();
12156 SetBlackToPlayEvent();
12160 if (gameMode == IcsExamining) {
12161 if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
12162 sprintf(buf, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
12165 if(x < BOARD_LEFT || x >= BOARD_RGHT) {
12166 if(x == BOARD_LEFT-2) {
12167 if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
12168 boards[0][y][1] = 0;
12170 if(x == BOARD_RGHT+1) {
12171 if(y >= gameInfo.holdingsSize) break;
12172 boards[0][y][BOARD_WIDTH-2] = 0;
12175 boards[0][y][x] = EmptySquare;
12176 DrawPosition(FALSE, boards[0]);
12181 if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
12182 piece >= (int)BlackPawn && piece < (int)BlackMan ) {
12183 selection = (ChessSquare) (PROMOTED piece);
12184 } else if(piece == EmptySquare) selection = WhiteSilver;
12185 else selection = (ChessSquare)((int)piece - 1);
12189 if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
12190 piece > (int)BlackMan && piece <= (int)BlackKing ) {
12191 selection = (ChessSquare) (DEMOTED piece);
12192 } else if(piece == EmptySquare) selection = BlackSilver;
12193 else selection = (ChessSquare)((int)piece + 1);
12198 if(gameInfo.variant == VariantShatranj ||
12199 gameInfo.variant == VariantXiangqi ||
12200 gameInfo.variant == VariantCourier ||
12201 gameInfo.variant == VariantMakruk )
12202 selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
12207 if(gameInfo.variant == VariantXiangqi)
12208 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
12209 if(gameInfo.variant == VariantKnightmate)
12210 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
12213 if (gameMode == IcsExamining) {
12214 if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
12215 sprintf(buf, "%s%c@%c%c\n", ics_prefix,
12216 PieceToChar(selection), AAA + x, ONE + y);
12219 if(x < BOARD_LEFT || x >= BOARD_RGHT) {
12221 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
12222 n = PieceToNumber(selection - BlackPawn);
12223 if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
12224 boards[0][BOARD_HEIGHT-1-n][0] = selection;
12225 boards[0][BOARD_HEIGHT-1-n][1]++;
12227 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
12228 n = PieceToNumber(selection);
12229 if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
12230 boards[0][n][BOARD_WIDTH-1] = selection;
12231 boards[0][n][BOARD_WIDTH-2]++;
12234 boards[0][y][x] = selection;
12235 DrawPosition(TRUE, boards[0]);
12243 DropMenuEvent(selection, x, y)
12244 ChessSquare selection;
12247 ChessMove moveType;
12249 switch (gameMode) {
12250 case IcsPlayingWhite:
12251 case MachinePlaysBlack:
12252 if (!WhiteOnMove(currentMove)) {
12253 DisplayMoveError(_("It is Black's turn"));
12256 moveType = WhiteDrop;
12258 case IcsPlayingBlack:
12259 case MachinePlaysWhite:
12260 if (WhiteOnMove(currentMove)) {
12261 DisplayMoveError(_("It is White's turn"));
12264 moveType = BlackDrop;
12267 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
12273 if (moveType == BlackDrop && selection < BlackPawn) {
12274 selection = (ChessSquare) ((int) selection
12275 + (int) BlackPawn - (int) WhitePawn);
12277 if (boards[currentMove][y][x] != EmptySquare) {
12278 DisplayMoveError(_("That square is occupied"));
12282 FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
12288 /* Accept a pending offer of any kind from opponent */
12290 if (appData.icsActive) {
12291 SendToICS(ics_prefix);
12292 SendToICS("accept\n");
12293 } else if (cmailMsgLoaded) {
12294 if (currentMove == cmailOldMove &&
12295 commentList[cmailOldMove] != NULL &&
12296 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12297 "Black offers a draw" : "White offers a draw")) {
12299 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
12300 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
12302 DisplayError(_("There is no pending offer on this move"), 0);
12303 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12306 /* Not used for offers from chess program */
12313 /* Decline a pending offer of any kind from opponent */
12315 if (appData.icsActive) {
12316 SendToICS(ics_prefix);
12317 SendToICS("decline\n");
12318 } else if (cmailMsgLoaded) {
12319 if (currentMove == cmailOldMove &&
12320 commentList[cmailOldMove] != NULL &&
12321 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12322 "Black offers a draw" : "White offers a draw")) {
12324 AppendComment(cmailOldMove, "Draw declined", TRUE);
12325 DisplayComment(cmailOldMove - 1, "Draw declined");
12328 DisplayError(_("There is no pending offer on this move"), 0);
12331 /* Not used for offers from chess program */
12338 /* Issue ICS rematch command */
12339 if (appData.icsActive) {
12340 SendToICS(ics_prefix);
12341 SendToICS("rematch\n");
12348 /* Call your opponent's flag (claim a win on time) */
12349 if (appData.icsActive) {
12350 SendToICS(ics_prefix);
12351 SendToICS("flag\n");
12353 switch (gameMode) {
12356 case MachinePlaysWhite:
12359 GameEnds(GameIsDrawn, "Both players ran out of time",
12362 GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
12364 DisplayError(_("Your opponent is not out of time"), 0);
12367 case MachinePlaysBlack:
12370 GameEnds(GameIsDrawn, "Both players ran out of time",
12373 GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
12375 DisplayError(_("Your opponent is not out of time"), 0);
12385 /* Offer draw or accept pending draw offer from opponent */
12387 if (appData.icsActive) {
12388 /* Note: tournament rules require draw offers to be
12389 made after you make your move but before you punch
12390 your clock. Currently ICS doesn't let you do that;
12391 instead, you immediately punch your clock after making
12392 a move, but you can offer a draw at any time. */
12394 SendToICS(ics_prefix);
12395 SendToICS("draw\n");
12396 userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
12397 } else if (cmailMsgLoaded) {
12398 if (currentMove == cmailOldMove &&
12399 commentList[cmailOldMove] != NULL &&
12400 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12401 "Black offers a draw" : "White offers a draw")) {
12402 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
12403 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
12404 } else if (currentMove == cmailOldMove + 1) {
12405 char *offer = WhiteOnMove(cmailOldMove) ?
12406 "White offers a draw" : "Black offers a draw";
12407 AppendComment(currentMove, offer, TRUE);
12408 DisplayComment(currentMove - 1, offer);
12409 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
12411 DisplayError(_("You must make your move before offering a draw"), 0);
12412 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12414 } else if (first.offeredDraw) {
12415 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
12417 if (first.sendDrawOffers) {
12418 SendToProgram("draw\n", &first);
12419 userOfferedDraw = TRUE;
12427 /* Offer Adjourn or accept pending Adjourn offer from opponent */
12429 if (appData.icsActive) {
12430 SendToICS(ics_prefix);
12431 SendToICS("adjourn\n");
12433 /* Currently GNU Chess doesn't offer or accept Adjourns */
12441 /* Offer Abort or accept pending Abort offer from opponent */
12443 if (appData.icsActive) {
12444 SendToICS(ics_prefix);
12445 SendToICS("abort\n");
12447 GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
12454 /* Resign. You can do this even if it's not your turn. */
12456 if (appData.icsActive) {
12457 SendToICS(ics_prefix);
12458 SendToICS("resign\n");
12460 switch (gameMode) {
12461 case MachinePlaysWhite:
12462 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12464 case MachinePlaysBlack:
12465 GameEnds(BlackWins, "White resigns", GE_PLAYER);
12468 if (cmailMsgLoaded) {
12470 if (WhiteOnMove(cmailOldMove)) {
12471 GameEnds(BlackWins, "White resigns", GE_PLAYER);
12473 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12475 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
12486 StopObservingEvent()
12488 /* Stop observing current games */
12489 SendToICS(ics_prefix);
12490 SendToICS("unobserve\n");
12494 StopExaminingEvent()
12496 /* Stop observing current game */
12497 SendToICS(ics_prefix);
12498 SendToICS("unexamine\n");
12502 ForwardInner(target)
12507 if (appData.debugMode)
12508 fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
12509 target, currentMove, forwardMostMove);
12511 if (gameMode == EditPosition)
12514 if (gameMode == PlayFromGameFile && !pausing)
12517 if (gameMode == IcsExamining && pausing)
12518 limit = pauseExamForwardMostMove;
12520 limit = forwardMostMove;
12522 if (target > limit) target = limit;
12524 if (target > 0 && moveList[target - 1][0]) {
12525 int fromX, fromY, toX, toY;
12526 toX = moveList[target - 1][2] - AAA;
12527 toY = moveList[target - 1][3] - ONE;
12528 if (moveList[target - 1][1] == '@') {
12529 if (appData.highlightLastMove) {
12530 SetHighlights(-1, -1, toX, toY);
12533 fromX = moveList[target - 1][0] - AAA;
12534 fromY = moveList[target - 1][1] - ONE;
12535 if (target == currentMove + 1) {
12536 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
12538 if (appData.highlightLastMove) {
12539 SetHighlights(fromX, fromY, toX, toY);
12543 if (gameMode == EditGame || gameMode == AnalyzeMode ||
12544 gameMode == Training || gameMode == PlayFromGameFile ||
12545 gameMode == AnalyzeFile) {
12546 while (currentMove < target) {
12547 SendMoveToProgram(currentMove++, &first);
12550 currentMove = target;
12553 if (gameMode == EditGame || gameMode == EndOfGame) {
12554 whiteTimeRemaining = timeRemaining[0][currentMove];
12555 blackTimeRemaining = timeRemaining[1][currentMove];
12557 DisplayBothClocks();
12558 DisplayMove(currentMove - 1);
12559 DrawPosition(FALSE, boards[currentMove]);
12560 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
12561 if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
12562 DisplayComment(currentMove - 1, commentList[currentMove]);
12570 if (gameMode == IcsExamining && !pausing) {
12571 SendToICS(ics_prefix);
12572 SendToICS("forward\n");
12574 ForwardInner(currentMove + 1);
12581 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12582 /* to optimze, we temporarily turn off analysis mode while we feed
12583 * the remaining moves to the engine. Otherwise we get analysis output
12586 if (first.analysisSupport) {
12587 SendToProgram("exit\nforce\n", &first);
12588 first.analyzing = FALSE;
12592 if (gameMode == IcsExamining && !pausing) {
12593 SendToICS(ics_prefix);
12594 SendToICS("forward 999999\n");
12596 ForwardInner(forwardMostMove);
12599 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12600 /* we have fed all the moves, so reactivate analysis mode */
12601 SendToProgram("analyze\n", &first);
12602 first.analyzing = TRUE;
12603 /*first.maybeThinking = TRUE;*/
12604 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12609 BackwardInner(target)
12612 int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
12614 if (appData.debugMode)
12615 fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
12616 target, currentMove, forwardMostMove);
12618 if (gameMode == EditPosition) return;
12619 if (currentMove <= backwardMostMove) {
12621 DrawPosition(full_redraw, boards[currentMove]);
12624 if (gameMode == PlayFromGameFile && !pausing)
12627 if (moveList[target][0]) {
12628 int fromX, fromY, toX, toY;
12629 toX = moveList[target][2] - AAA;
12630 toY = moveList[target][3] - ONE;
12631 if (moveList[target][1] == '@') {
12632 if (appData.highlightLastMove) {
12633 SetHighlights(-1, -1, toX, toY);
12636 fromX = moveList[target][0] - AAA;
12637 fromY = moveList[target][1] - ONE;
12638 if (target == currentMove - 1) {
12639 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
12641 if (appData.highlightLastMove) {
12642 SetHighlights(fromX, fromY, toX, toY);
12646 if (gameMode == EditGame || gameMode==AnalyzeMode ||
12647 gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
12648 while (currentMove > target) {
12649 SendToProgram("undo\n", &first);
12653 currentMove = target;
12656 if (gameMode == EditGame || gameMode == EndOfGame) {
12657 whiteTimeRemaining = timeRemaining[0][currentMove];
12658 blackTimeRemaining = timeRemaining[1][currentMove];
12660 DisplayBothClocks();
12661 DisplayMove(currentMove - 1);
12662 DrawPosition(full_redraw, boards[currentMove]);
12663 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
12664 // [HGM] PV info: routine tests if comment empty
12665 DisplayComment(currentMove - 1, commentList[currentMove]);
12671 if (gameMode == IcsExamining && !pausing) {
12672 SendToICS(ics_prefix);
12673 SendToICS("backward\n");
12675 BackwardInner(currentMove - 1);
12682 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12683 /* to optimize, we temporarily turn off analysis mode while we undo
12684 * all the moves. Otherwise we get analysis output after each undo.
12686 if (first.analysisSupport) {
12687 SendToProgram("exit\nforce\n", &first);
12688 first.analyzing = FALSE;
12692 if (gameMode == IcsExamining && !pausing) {
12693 SendToICS(ics_prefix);
12694 SendToICS("backward 999999\n");
12696 BackwardInner(backwardMostMove);
12699 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12700 /* we have fed all the moves, so reactivate analysis mode */
12701 SendToProgram("analyze\n", &first);
12702 first.analyzing = TRUE;
12703 /*first.maybeThinking = TRUE;*/
12704 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12711 if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
12712 if (to >= forwardMostMove) to = forwardMostMove;
12713 if (to <= backwardMostMove) to = backwardMostMove;
12714 if (to < currentMove) {
12724 if(PopTail(TRUE)) { // [HGM] vari: restore old game tail
12727 if (gameMode != IcsExamining) {
12728 DisplayError(_("You are not examining a game"), 0);
12732 DisplayError(_("You can't revert while pausing"), 0);
12735 SendToICS(ics_prefix);
12736 SendToICS("revert\n");
12742 switch (gameMode) {
12743 case MachinePlaysWhite:
12744 case MachinePlaysBlack:
12745 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
12746 DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
12749 if (forwardMostMove < 2) return;
12750 currentMove = forwardMostMove = forwardMostMove - 2;
12751 whiteTimeRemaining = timeRemaining[0][currentMove];
12752 blackTimeRemaining = timeRemaining[1][currentMove];
12753 DisplayBothClocks();
12754 DisplayMove(currentMove - 1);
12755 ClearHighlights();/*!! could figure this out*/
12756 DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
12757 SendToProgram("remove\n", &first);
12758 /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
12761 case BeginningOfGame:
12765 case IcsPlayingWhite:
12766 case IcsPlayingBlack:
12767 if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
12768 SendToICS(ics_prefix);
12769 SendToICS("takeback 2\n");
12771 SendToICS(ics_prefix);
12772 SendToICS("takeback 1\n");
12781 ChessProgramState *cps;
12783 switch (gameMode) {
12784 case MachinePlaysWhite:
12785 if (!WhiteOnMove(forwardMostMove)) {
12786 DisplayError(_("It is your turn"), 0);
12791 case MachinePlaysBlack:
12792 if (WhiteOnMove(forwardMostMove)) {
12793 DisplayError(_("It is your turn"), 0);
12798 case TwoMachinesPlay:
12799 if (WhiteOnMove(forwardMostMove) ==
12800 (first.twoMachinesColor[0] == 'w')) {
12806 case BeginningOfGame:
12810 SendToProgram("?\n", cps);
12814 TruncateGameEvent()
12817 if (gameMode != EditGame) return;
12824 CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
12825 if (forwardMostMove > currentMove) {
12826 if (gameInfo.resultDetails != NULL) {
12827 free(gameInfo.resultDetails);
12828 gameInfo.resultDetails = NULL;
12829 gameInfo.result = GameUnfinished;
12831 forwardMostMove = currentMove;
12832 HistorySet(parseList, backwardMostMove, forwardMostMove,
12840 if (appData.noChessProgram) return;
12841 switch (gameMode) {
12842 case MachinePlaysWhite:
12843 if (WhiteOnMove(forwardMostMove)) {
12844 DisplayError(_("Wait until your turn"), 0);
12848 case BeginningOfGame:
12849 case MachinePlaysBlack:
12850 if (!WhiteOnMove(forwardMostMove)) {
12851 DisplayError(_("Wait until your turn"), 0);
12856 DisplayError(_("No hint available"), 0);
12859 SendToProgram("hint\n", &first);
12860 hintRequested = TRUE;
12866 if (appData.noChessProgram) return;
12867 switch (gameMode) {
12868 case MachinePlaysWhite:
12869 if (WhiteOnMove(forwardMostMove)) {
12870 DisplayError(_("Wait until your turn"), 0);
12874 case BeginningOfGame:
12875 case MachinePlaysBlack:
12876 if (!WhiteOnMove(forwardMostMove)) {
12877 DisplayError(_("Wait until your turn"), 0);
12882 EditPositionDone(TRUE);
12884 case TwoMachinesPlay:
12889 SendToProgram("bk\n", &first);
12890 bookOutput[0] = NULLCHAR;
12891 bookRequested = TRUE;
12897 char *tags = PGNTags(&gameInfo);
12898 TagsPopUp(tags, CmailMsg());
12902 /* end button procedures */
12905 PrintPosition(fp, move)
12911 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
12912 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
12913 char c = PieceToChar(boards[move][i][j]);
12914 fputc(c == 'x' ? '.' : c, fp);
12915 fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
12918 if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
12919 fprintf(fp, "white to play\n");
12921 fprintf(fp, "black to play\n");
12928 if (gameInfo.white != NULL) {
12929 fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
12935 /* Find last component of program's own name, using some heuristics */
12937 TidyProgramName(prog, host, buf)
12938 char *prog, *host, buf[MSG_SIZ];
12941 int local = (strcmp(host, "localhost") == 0);
12942 while (!local && (p = strchr(prog, ';')) != NULL) {
12944 while (*p == ' ') p++;
12947 if (*prog == '"' || *prog == '\'') {
12948 q = strchr(prog + 1, *prog);
12950 q = strchr(prog, ' ');
12952 if (q == NULL) q = prog + strlen(prog);
12954 while (p >= prog && *p != '/' && *p != '\\') p--;
12956 if(p == prog && *p == '"') p++;
12957 if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
12958 memcpy(buf, p, q - p);
12959 buf[q - p] = NULLCHAR;
12967 TimeControlTagValue()
12970 if (!appData.clockMode) {
12972 } else if (movesPerSession > 0) {
12973 sprintf(buf, "%d/%ld", movesPerSession, timeControl/1000);
12974 } else if (timeIncrement == 0) {
12975 sprintf(buf, "%ld", timeControl/1000);
12977 sprintf(buf, "%ld+%ld", timeControl/1000, timeIncrement/1000);
12979 return StrSave(buf);
12985 /* This routine is used only for certain modes */
12986 VariantClass v = gameInfo.variant;
12987 ChessMove r = GameUnfinished;
12990 if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
12991 r = gameInfo.result;
12992 p = gameInfo.resultDetails;
12993 gameInfo.resultDetails = NULL;
12995 ClearGameInfo(&gameInfo);
12996 gameInfo.variant = v;
12998 switch (gameMode) {
12999 case MachinePlaysWhite:
13000 gameInfo.event = StrSave( appData.pgnEventHeader );
13001 gameInfo.site = StrSave(HostName());
13002 gameInfo.date = PGNDate();
13003 gameInfo.round = StrSave("-");
13004 gameInfo.white = StrSave(first.tidy);
13005 gameInfo.black = StrSave(UserName());
13006 gameInfo.timeControl = TimeControlTagValue();
13009 case MachinePlaysBlack:
13010 gameInfo.event = StrSave( appData.pgnEventHeader );
13011 gameInfo.site = StrSave(HostName());
13012 gameInfo.date = PGNDate();
13013 gameInfo.round = StrSave("-");
13014 gameInfo.white = StrSave(UserName());
13015 gameInfo.black = StrSave(first.tidy);
13016 gameInfo.timeControl = TimeControlTagValue();
13019 case TwoMachinesPlay:
13020 gameInfo.event = StrSave( appData.pgnEventHeader );
13021 gameInfo.site = StrSave(HostName());
13022 gameInfo.date = PGNDate();
13023 if (matchGame > 0) {
13025 sprintf(buf, "%d", matchGame);
13026 gameInfo.round = StrSave(buf);
13028 gameInfo.round = StrSave("-");
13030 if (first.twoMachinesColor[0] == 'w') {
13031 gameInfo.white = StrSave(first.tidy);
13032 gameInfo.black = StrSave(second.tidy);
13034 gameInfo.white = StrSave(second.tidy);
13035 gameInfo.black = StrSave(first.tidy);
13037 gameInfo.timeControl = TimeControlTagValue();
13041 gameInfo.event = StrSave("Edited game");
13042 gameInfo.site = StrSave(HostName());
13043 gameInfo.date = PGNDate();
13044 gameInfo.round = StrSave("-");
13045 gameInfo.white = StrSave("-");
13046 gameInfo.black = StrSave("-");
13047 gameInfo.result = r;
13048 gameInfo.resultDetails = p;
13052 gameInfo.event = StrSave("Edited position");
13053 gameInfo.site = StrSave(HostName());
13054 gameInfo.date = PGNDate();
13055 gameInfo.round = StrSave("-");
13056 gameInfo.white = StrSave("-");
13057 gameInfo.black = StrSave("-");
13060 case IcsPlayingWhite:
13061 case IcsPlayingBlack:
13066 case PlayFromGameFile:
13067 gameInfo.event = StrSave("Game from non-PGN file");
13068 gameInfo.site = StrSave(HostName());
13069 gameInfo.date = PGNDate();
13070 gameInfo.round = StrSave("-");
13071 gameInfo.white = StrSave("?");
13072 gameInfo.black = StrSave("?");
13081 ReplaceComment(index, text)
13087 while (*text == '\n') text++;
13088 len = strlen(text);
13089 while (len > 0 && text[len - 1] == '\n') len--;
13091 if (commentList[index] != NULL)
13092 free(commentList[index]);
13095 commentList[index] = NULL;
13098 if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
13099 *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
13100 *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
13101 commentList[index] = (char *) malloc(len + 2);
13102 strncpy(commentList[index], text, len);
13103 commentList[index][len] = '\n';
13104 commentList[index][len + 1] = NULLCHAR;
13106 // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
13108 commentList[index] = (char *) malloc(len + 6);
13109 strcpy(commentList[index], "{\n");
13110 strncpy(commentList[index]+2, text, len);
13111 commentList[index][len+2] = NULLCHAR;
13112 while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
13113 strcat(commentList[index], "\n}\n");
13127 if (ch == '\r') continue;
13129 } while (ch != '\0');
13133 AppendComment(index, text, addBraces)
13136 Boolean addBraces; // [HGM] braces: tells if we should add {}
13141 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
13142 text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
13145 while (*text == '\n') text++;
13146 len = strlen(text);
13147 while (len > 0 && text[len - 1] == '\n') len--;
13149 if (len == 0) return;
13151 if (commentList[index] != NULL) {
13152 old = commentList[index];
13153 oldlen = strlen(old);
13154 while(commentList[index][oldlen-1] == '\n')
13155 commentList[index][--oldlen] = NULLCHAR;
13156 commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
13157 strcpy(commentList[index], old);
13159 // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
13160 if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces)) {
13161 if(addBraces) addBraces = FALSE; else { text++; len--; }
13162 while (*text == '\n') { text++; len--; }
13163 commentList[index][--oldlen] = NULLCHAR;
13165 if(addBraces) strcat(commentList[index], "\n{\n");
13166 else strcat(commentList[index], "\n");
13167 strcat(commentList[index], text);
13168 if(addBraces) strcat(commentList[index], "\n}\n");
13169 else strcat(commentList[index], "\n");
13171 commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
13173 strcpy(commentList[index], "{\n");
13174 else commentList[index][0] = NULLCHAR;
13175 strcat(commentList[index], text);
13176 strcat(commentList[index], "\n");
13177 if(addBraces) strcat(commentList[index], "}\n");
13181 static char * FindStr( char * text, char * sub_text )
13183 char * result = strstr( text, sub_text );
13185 if( result != NULL ) {
13186 result += strlen( sub_text );
13192 /* [AS] Try to extract PV info from PGN comment */
13193 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
13194 char *GetInfoFromComment( int index, char * text )
13198 if( text != NULL && index > 0 ) {
13201 int time = -1, sec = 0, deci;
13202 char * s_eval = FindStr( text, "[%eval " );
13203 char * s_emt = FindStr( text, "[%emt " );
13205 if( s_eval != NULL || s_emt != NULL ) {
13209 if( s_eval != NULL ) {
13210 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
13214 if( delim != ']' ) {
13219 if( s_emt != NULL ) {
13224 /* We expect something like: [+|-]nnn.nn/dd */
13227 if(*text != '{') return text; // [HGM] braces: must be normal comment
13229 sep = strchr( text, '/' );
13230 if( sep == NULL || sep < (text+4) ) {
13234 time = -1; sec = -1; deci = -1;
13235 if( sscanf( text+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
13236 sscanf( text+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
13237 sscanf( text+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
13238 sscanf( text+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3 ) {
13242 if( score_lo < 0 || score_lo >= 100 ) {
13246 if(sec >= 0) time = 600*time + 10*sec; else
13247 if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
13249 score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
13251 /* [HGM] PV time: now locate end of PV info */
13252 while( *++sep >= '0' && *sep <= '9'); // strip depth
13254 while( *++sep >= '0' && *sep <= '9'); // strip time
13256 while( *++sep >= '0' && *sep <= '9'); // strip seconds
13258 while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
13259 while(*sep == ' ') sep++;
13270 pvInfoList[index-1].depth = depth;
13271 pvInfoList[index-1].score = score;
13272 pvInfoList[index-1].time = 10*time; // centi-sec
13273 if(*sep == '}') *sep = 0; else *--sep = '{';
13279 SendToProgram(message, cps)
13281 ChessProgramState *cps;
13283 int count, outCount, error;
13286 if (cps->pr == NULL) return;
13289 if (appData.debugMode) {
13292 fprintf(debugFP, "%ld >%-6s: %s",
13293 SubtractTimeMarks(&now, &programStartTime),
13294 cps->which, message);
13297 count = strlen(message);
13298 outCount = OutputToProcess(cps->pr, message, count, &error);
13299 if (outCount < count && !exiting
13300 && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
13301 sprintf(buf, _("Error writing to %s chess program"), cps->which);
13302 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
13303 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
13304 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
13305 sprintf(buf, "%s program exits in draw position (%s)", cps->which, cps->program);
13307 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
13309 gameInfo.resultDetails = StrSave(buf);
13311 if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
13316 ReceiveFromProgram(isr, closure, message, count, error)
13317 InputSourceRef isr;
13325 ChessProgramState *cps = (ChessProgramState *)closure;
13327 if (isr != cps->isr) return; /* Killed intentionally */
13331 _("Error: %s chess program (%s) exited unexpectedly"),
13332 cps->which, cps->program);
13333 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
13334 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
13335 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
13336 sprintf(buf, _("%s program exits in draw position (%s)"), cps->which, cps->program);
13338 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
13340 gameInfo.resultDetails = StrSave(buf);
13342 RemoveInputSource(cps->isr);
13343 if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
13346 _("Error reading from %s chess program (%s)"),
13347 cps->which, cps->program);
13348 RemoveInputSource(cps->isr);
13350 /* [AS] Program is misbehaving badly... kill it */
13351 if( count == -2 ) {
13352 DestroyChildProcess( cps->pr, 9 );
13356 if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
13361 if ((end_str = strchr(message, '\r')) != NULL)
13362 *end_str = NULLCHAR;
13363 if ((end_str = strchr(message, '\n')) != NULL)
13364 *end_str = NULLCHAR;
13366 if (appData.debugMode) {
13367 TimeMark now; int print = 1;
13368 char *quote = ""; char c; int i;
13370 if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
13371 char start = message[0];
13372 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
13373 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
13374 sscanf(message, "move %c", &c)!=1 && sscanf(message, "offer%c", &c)!=1 &&
13375 sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
13376 sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
13377 sscanf(message, "tell%c", &c)!=1 && sscanf(message, "0-1 %c", &c)!=1 &&
13378 sscanf(message, "1-0 %c", &c)!=1 && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
13379 sscanf(message, "pong %c", &c)!=1 && start != '#')
13380 { quote = "# "; print = (appData.engineComments == 2); }
13381 message[0] = start; // restore original message
13385 fprintf(debugFP, "%ld <%-6s: %s%s\n",
13386 SubtractTimeMarks(&now, &programStartTime), cps->which,
13392 /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
13393 if (appData.icsEngineAnalyze) {
13394 if (strstr(message, "whisper") != NULL ||
13395 strstr(message, "kibitz") != NULL ||
13396 strstr(message, "tellics") != NULL) return;
13399 HandleMachineMove(message, cps);
13404 SendTimeControl(cps, mps, tc, inc, sd, st)
13405 ChessProgramState *cps;
13406 int mps, inc, sd, st;
13412 if( timeControl_2 > 0 ) {
13413 if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
13414 tc = timeControl_2;
13417 tc /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
13418 inc /= cps->timeOdds;
13419 st /= cps->timeOdds;
13421 seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
13424 /* Set exact time per move, normally using st command */
13425 if (cps->stKludge) {
13426 /* GNU Chess 4 has no st command; uses level in a nonstandard way */
13428 if (seconds == 0) {
13429 sprintf(buf, "level 1 %d\n", st/60);
13431 sprintf(buf, "level 1 %d:%02d\n", st/60, seconds);
13434 sprintf(buf, "st %d\n", st);
13437 /* Set conventional or incremental time control, using level command */
13438 if (seconds == 0) {
13439 /* Note old gnuchess bug -- minutes:seconds used to not work.
13440 Fixed in later versions, but still avoid :seconds
13441 when seconds is 0. */
13442 sprintf(buf, "level %d %ld %d\n", mps, tc/60000, inc/1000);
13444 sprintf(buf, "level %d %ld:%02d %d\n", mps, tc/60000,
13445 seconds, inc/1000);
13448 SendToProgram(buf, cps);
13450 /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
13451 /* Orthogonally, limit search to given depth */
13453 if (cps->sdKludge) {
13454 sprintf(buf, "depth\n%d\n", sd);
13456 sprintf(buf, "sd %d\n", sd);
13458 SendToProgram(buf, cps);
13461 if(cps->nps > 0) { /* [HGM] nps */
13462 if(cps->supportsNPS == FALSE) cps->nps = -1; // don't use if engine explicitly says not supported!
13464 sprintf(buf, "nps %d\n", cps->nps);
13465 SendToProgram(buf, cps);
13470 ChessProgramState *WhitePlayer()
13471 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
13473 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
13474 gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
13480 SendTimeRemaining(cps, machineWhite)
13481 ChessProgramState *cps;
13482 int /*boolean*/ machineWhite;
13484 char message[MSG_SIZ];
13487 /* Note: this routine must be called when the clocks are stopped
13488 or when they have *just* been set or switched; otherwise
13489 it will be off by the time since the current tick started.
13491 if (machineWhite) {
13492 time = whiteTimeRemaining / 10;
13493 otime = blackTimeRemaining / 10;
13495 time = blackTimeRemaining / 10;
13496 otime = whiteTimeRemaining / 10;
13498 /* [HGM] translate opponent's time by time-odds factor */
13499 otime = (otime * cps->other->timeOdds) / cps->timeOdds;
13500 if (appData.debugMode) {
13501 fprintf(debugFP, "time odds: %f %f \n", cps->timeOdds, cps->other->timeOdds);
13504 if (time <= 0) time = 1;
13505 if (otime <= 0) otime = 1;
13507 sprintf(message, "time %ld\n", time);
13508 SendToProgram(message, cps);
13510 sprintf(message, "otim %ld\n", otime);
13511 SendToProgram(message, cps);
13515 BoolFeature(p, name, loc, cps)
13519 ChessProgramState *cps;
13522 int len = strlen(name);
13524 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
13526 sscanf(*p, "%d", &val);
13528 while (**p && **p != ' ') (*p)++;
13529 sprintf(buf, "accepted %s\n", name);
13530 SendToProgram(buf, cps);
13537 IntFeature(p, name, loc, cps)
13541 ChessProgramState *cps;
13544 int len = strlen(name);
13545 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
13547 sscanf(*p, "%d", loc);
13548 while (**p && **p != ' ') (*p)++;
13549 sprintf(buf, "accepted %s\n", name);
13550 SendToProgram(buf, cps);
13557 StringFeature(p, name, loc, cps)
13561 ChessProgramState *cps;
13564 int len = strlen(name);
13565 if (strncmp((*p), name, len) == 0
13566 && (*p)[len] == '=' && (*p)[len+1] == '\"') {
13568 sscanf(*p, "%[^\"]", loc);
13569 while (**p && **p != '\"') (*p)++;
13570 if (**p == '\"') (*p)++;
13571 sprintf(buf, "accepted %s\n", name);
13572 SendToProgram(buf, cps);
13579 ParseOption(Option *opt, ChessProgramState *cps)
13580 // [HGM] options: process the string that defines an engine option, and determine
13581 // name, type, default value, and allowed value range
13583 char *p, *q, buf[MSG_SIZ];
13584 int n, min = (-1)<<31, max = 1<<31, def;
13586 if(p = strstr(opt->name, " -spin ")) {
13587 if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
13588 if(max < min) max = min; // enforce consistency
13589 if(def < min) def = min;
13590 if(def > max) def = max;
13595 } else if((p = strstr(opt->name, " -slider "))) {
13596 // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
13597 if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
13598 if(max < min) max = min; // enforce consistency
13599 if(def < min) def = min;
13600 if(def > max) def = max;
13604 opt->type = Spin; // Slider;
13605 } else if((p = strstr(opt->name, " -string "))) {
13606 opt->textValue = p+9;
13607 opt->type = TextBox;
13608 } else if((p = strstr(opt->name, " -file "))) {
13609 // for now -file is a synonym for -string, to already provide compatibility with future polyglots
13610 opt->textValue = p+7;
13611 opt->type = TextBox; // FileName;
13612 } else if((p = strstr(opt->name, " -path "))) {
13613 // for now -file is a synonym for -string, to already provide compatibility with future polyglots
13614 opt->textValue = p+7;
13615 opt->type = TextBox; // PathName;
13616 } else if(p = strstr(opt->name, " -check ")) {
13617 if(sscanf(p, " -check %d", &def) < 1) return FALSE;
13618 opt->value = (def != 0);
13619 opt->type = CheckBox;
13620 } else if(p = strstr(opt->name, " -combo ")) {
13621 opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
13622 cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
13623 if(*q == '*') cps->comboList[cps->comboCnt-1]++;
13624 opt->value = n = 0;
13625 while(q = StrStr(q, " /// ")) {
13626 n++; *q = 0; // count choices, and null-terminate each of them
13628 if(*q == '*') { // remember default, which is marked with * prefix
13632 cps->comboList[cps->comboCnt++] = q;
13634 cps->comboList[cps->comboCnt++] = NULL;
13636 opt->type = ComboBox;
13637 } else if(p = strstr(opt->name, " -button")) {
13638 opt->type = Button;
13639 } else if(p = strstr(opt->name, " -save")) {
13640 opt->type = SaveButton;
13641 } else return FALSE;
13642 *p = 0; // terminate option name
13643 // now look if the command-line options define a setting for this engine option.
13644 if(cps->optionSettings && cps->optionSettings[0])
13645 p = strstr(cps->optionSettings, opt->name); else p = NULL;
13646 if(p && (p == cps->optionSettings || p[-1] == ',')) {
13647 sprintf(buf, "option %s", p);
13648 if(p = strstr(buf, ",")) *p = 0;
13650 SendToProgram(buf, cps);
13656 FeatureDone(cps, val)
13657 ChessProgramState* cps;
13660 DelayedEventCallback cb = GetDelayedEvent();
13661 if ((cb == InitBackEnd3 && cps == &first) ||
13662 (cb == TwoMachinesEventIfReady && cps == &second)) {
13663 CancelDelayedEvent();
13664 ScheduleDelayedEvent(cb, val ? 1 : 3600000);
13666 cps->initDone = val;
13669 /* Parse feature command from engine */
13671 ParseFeatures(args, cps)
13673 ChessProgramState *cps;
13681 while (*p == ' ') p++;
13682 if (*p == NULLCHAR) return;
13684 if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
13685 if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
13686 if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
13687 if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
13688 if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
13689 if (BoolFeature(&p, "reuse", &val, cps)) {
13690 /* Engine can disable reuse, but can't enable it if user said no */
13691 if (!val) cps->reuse = FALSE;
13694 if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
13695 if (StringFeature(&p, "myname", &cps->tidy, cps)) {
13696 if (gameMode == TwoMachinesPlay) {
13697 DisplayTwoMachinesTitle();
13703 if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
13704 if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
13705 if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
13706 if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
13707 if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
13708 if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
13709 if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
13710 if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
13711 if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
13712 if (IntFeature(&p, "done", &val, cps)) {
13713 FeatureDone(cps, val);
13716 /* Added by Tord: */
13717 if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
13718 if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
13719 /* End of additions by Tord */
13721 /* [HGM] added features: */
13722 if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
13723 if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
13724 if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
13725 if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
13726 if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
13727 if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
13728 if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
13729 if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
13730 sprintf(buf, "rejected option %s\n", cps->option[--cps->nrOptions].name);
13731 SendToProgram(buf, cps);
13734 if(cps->nrOptions >= MAX_OPTIONS) {
13736 sprintf(buf, "%s engine has too many options\n", cps->which);
13737 DisplayError(buf, 0);
13741 if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
13742 /* End of additions by HGM */
13744 /* unknown feature: complain and skip */
13746 while (*q && *q != '=') q++;
13747 sprintf(buf, "rejected %.*s\n", (int)(q-p), p);
13748 SendToProgram(buf, cps);
13754 while (*p && *p != '\"') p++;
13755 if (*p == '\"') p++;
13757 while (*p && *p != ' ') p++;
13765 PeriodicUpdatesEvent(newState)
13768 if (newState == appData.periodicUpdates)
13771 appData.periodicUpdates=newState;
13773 /* Display type changes, so update it now */
13774 // DisplayAnalysis();
13776 /* Get the ball rolling again... */
13778 AnalysisPeriodicEvent(1);
13779 StartAnalysisClock();
13784 PonderNextMoveEvent(newState)
13787 if (newState == appData.ponderNextMove) return;
13788 if (gameMode == EditPosition) EditPositionDone(TRUE);
13790 SendToProgram("hard\n", &first);
13791 if (gameMode == TwoMachinesPlay) {
13792 SendToProgram("hard\n", &second);
13795 SendToProgram("easy\n", &first);
13796 thinkOutput[0] = NULLCHAR;
13797 if (gameMode == TwoMachinesPlay) {
13798 SendToProgram("easy\n", &second);
13801 appData.ponderNextMove = newState;
13805 NewSettingEvent(option, command, value)
13811 if (gameMode == EditPosition) EditPositionDone(TRUE);
13812 sprintf(buf, "%s%s %d\n", (option ? "option ": ""), command, value);
13813 SendToProgram(buf, &first);
13814 if (gameMode == TwoMachinesPlay) {
13815 SendToProgram(buf, &second);
13820 ShowThinkingEvent()
13821 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
13823 static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
13824 int newState = appData.showThinking
13825 // [HGM] thinking: other features now need thinking output as well
13826 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
13828 if (oldState == newState) return;
13829 oldState = newState;
13830 if (gameMode == EditPosition) EditPositionDone(TRUE);
13832 SendToProgram("post\n", &first);
13833 if (gameMode == TwoMachinesPlay) {
13834 SendToProgram("post\n", &second);
13837 SendToProgram("nopost\n", &first);
13838 thinkOutput[0] = NULLCHAR;
13839 if (gameMode == TwoMachinesPlay) {
13840 SendToProgram("nopost\n", &second);
13843 // appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
13847 AskQuestionEvent(title, question, replyPrefix, which)
13848 char *title; char *question; char *replyPrefix; char *which;
13850 ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
13851 if (pr == NoProc) return;
13852 AskQuestion(title, question, replyPrefix, pr);
13856 DisplayMove(moveNumber)
13859 char message[MSG_SIZ];
13861 char cpThinkOutput[MSG_SIZ];
13863 if(appData.noGUI) return; // [HGM] fast: suppress display of moves
13865 if (moveNumber == forwardMostMove - 1 ||
13866 gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13868 safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput));
13870 if (strchr(cpThinkOutput, '\n')) {
13871 *strchr(cpThinkOutput, '\n') = NULLCHAR;
13874 *cpThinkOutput = NULLCHAR;
13877 /* [AS] Hide thinking from human user */
13878 if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
13879 *cpThinkOutput = NULLCHAR;
13880 if( thinkOutput[0] != NULLCHAR ) {
13883 for( i=0; i<=hiddenThinkOutputState; i++ ) {
13884 cpThinkOutput[i] = '.';
13886 cpThinkOutput[i] = NULLCHAR;
13887 hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
13891 if (moveNumber == forwardMostMove - 1 &&
13892 gameInfo.resultDetails != NULL) {
13893 if (gameInfo.resultDetails[0] == NULLCHAR) {
13894 sprintf(res, " %s", PGNResult(gameInfo.result));
13896 sprintf(res, " {%s} %s",
13897 gameInfo.resultDetails, PGNResult(gameInfo.result));
13903 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
13904 DisplayMessage(res, cpThinkOutput);
13906 sprintf(message, "%d.%s%s%s", moveNumber / 2 + 1,
13907 WhiteOnMove(moveNumber) ? " " : ".. ",
13908 parseList[moveNumber], res);
13909 DisplayMessage(message, cpThinkOutput);
13914 DisplayComment(moveNumber, text)
13918 char title[MSG_SIZ];
13919 char buf[8000]; // comment can be long!
13921 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
13922 strcpy(title, "Comment");
13924 sprintf(title, "Comment on %d.%s%s", moveNumber / 2 + 1,
13925 WhiteOnMove(moveNumber) ? " " : ".. ",
13926 parseList[moveNumber]);
13928 // [HGM] PV info: display PV info together with (or as) comment
13929 if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {
13930 if(text == NULL) text = "";
13931 score = pvInfoList[moveNumber].score;
13932 sprintf(buf, "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,
13933 depth, (pvInfoList[moveNumber].time+50)/100, text);
13936 if (text != NULL && (appData.autoDisplayComment || commentUp))
13937 CommentPopUp(title, text);
13940 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
13941 * might be busy thinking or pondering. It can be omitted if your
13942 * gnuchess is configured to stop thinking immediately on any user
13943 * input. However, that gnuchess feature depends on the FIONREAD
13944 * ioctl, which does not work properly on some flavors of Unix.
13948 ChessProgramState *cps;
13951 if (!cps->useSigint) return;
13952 if (appData.noChessProgram || (cps->pr == NoProc)) return;
13953 switch (gameMode) {
13954 case MachinePlaysWhite:
13955 case MachinePlaysBlack:
13956 case TwoMachinesPlay:
13957 case IcsPlayingWhite:
13958 case IcsPlayingBlack:
13961 /* Skip if we know it isn't thinking */
13962 if (!cps->maybeThinking) return;
13963 if (appData.debugMode)
13964 fprintf(debugFP, "Interrupting %s\n", cps->which);
13965 InterruptChildProcess(cps->pr);
13966 cps->maybeThinking = FALSE;
13971 #endif /*ATTENTION*/
13977 if (whiteTimeRemaining <= 0) {
13980 if (appData.icsActive) {
13981 if (appData.autoCallFlag &&
13982 gameMode == IcsPlayingBlack && !blackFlag) {
13983 SendToICS(ics_prefix);
13984 SendToICS("flag\n");
13988 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
13990 if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
13991 if (appData.autoCallFlag) {
13992 GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
13999 if (blackTimeRemaining <= 0) {
14002 if (appData.icsActive) {
14003 if (appData.autoCallFlag &&
14004 gameMode == IcsPlayingWhite && !whiteFlag) {
14005 SendToICS(ics_prefix);
14006 SendToICS("flag\n");
14010 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
14012 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
14013 if (appData.autoCallFlag) {
14014 GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
14027 if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
14028 gameMode == PlayFromGameFile || forwardMostMove == 0) return;
14031 * add time to clocks when time control is achieved ([HGM] now also used for increment)
14033 if ( !WhiteOnMove(forwardMostMove) )
14034 /* White made time control */
14035 whiteTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
14036 /* [HGM] time odds: correct new time quota for time odds! */
14037 / WhitePlayer()->timeOdds;
14039 /* Black made time control */
14040 blackTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
14041 / WhitePlayer()->other->timeOdds;
14045 DisplayBothClocks()
14047 int wom = gameMode == EditPosition ?
14048 !blackPlaysFirst : WhiteOnMove(currentMove);
14049 DisplayWhiteClock(whiteTimeRemaining, wom);
14050 DisplayBlackClock(blackTimeRemaining, !wom);
14054 /* Timekeeping seems to be a portability nightmare. I think everyone
14055 has ftime(), but I'm really not sure, so I'm including some ifdefs
14056 to use other calls if you don't. Clocks will be less accurate if
14057 you have neither ftime nor gettimeofday.
14060 /* VS 2008 requires the #include outside of the function */
14061 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
14062 #include <sys/timeb.h>
14065 /* Get the current time as a TimeMark */
14070 #if HAVE_GETTIMEOFDAY
14072 struct timeval timeVal;
14073 struct timezone timeZone;
14075 gettimeofday(&timeVal, &timeZone);
14076 tm->sec = (long) timeVal.tv_sec;
14077 tm->ms = (int) (timeVal.tv_usec / 1000L);
14079 #else /*!HAVE_GETTIMEOFDAY*/
14082 // include <sys/timeb.h> / moved to just above start of function
14083 struct timeb timeB;
14086 tm->sec = (long) timeB.time;
14087 tm->ms = (int) timeB.millitm;
14089 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
14090 tm->sec = (long) time(NULL);
14096 /* Return the difference in milliseconds between two
14097 time marks. We assume the difference will fit in a long!
14100 SubtractTimeMarks(tm2, tm1)
14101 TimeMark *tm2, *tm1;
14103 return 1000L*(tm2->sec - tm1->sec) +
14104 (long) (tm2->ms - tm1->ms);
14109 * Code to manage the game clocks.
14111 * In tournament play, black starts the clock and then white makes a move.
14112 * We give the human user a slight advantage if he is playing white---the
14113 * clocks don't run until he makes his first move, so it takes zero time.
14114 * Also, we don't account for network lag, so we could get out of sync
14115 * with GNU Chess's clock -- but then, referees are always right.
14118 static TimeMark tickStartTM;
14119 static long intendedTickLength;
14122 NextTickLength(timeRemaining)
14123 long timeRemaining;
14125 long nominalTickLength, nextTickLength;
14127 if (timeRemaining > 0L && timeRemaining <= 10000L)
14128 nominalTickLength = 100L;
14130 nominalTickLength = 1000L;
14131 nextTickLength = timeRemaining % nominalTickLength;
14132 if (nextTickLength <= 0) nextTickLength += nominalTickLength;
14134 return nextTickLength;
14137 /* Adjust clock one minute up or down */
14139 AdjustClock(Boolean which, int dir)
14141 if(which) blackTimeRemaining += 60000*dir;
14142 else whiteTimeRemaining += 60000*dir;
14143 DisplayBothClocks();
14146 /* Stop clocks and reset to a fresh time control */
14150 (void) StopClockTimer();
14151 if (appData.icsActive) {
14152 whiteTimeRemaining = blackTimeRemaining = 0;
14153 } else if (searchTime) {
14154 whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
14155 blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
14156 } else { /* [HGM] correct new time quote for time odds */
14157 whiteTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->timeOdds;
14158 blackTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->other->timeOdds;
14160 if (whiteFlag || blackFlag) {
14162 whiteFlag = blackFlag = FALSE;
14164 DisplayBothClocks();
14167 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
14169 /* Decrement running clock by amount of time that has passed */
14173 long timeRemaining;
14174 long lastTickLength, fudge;
14177 if (!appData.clockMode) return;
14178 if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
14182 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
14184 /* Fudge if we woke up a little too soon */
14185 fudge = intendedTickLength - lastTickLength;
14186 if (fudge < 0 || fudge > FUDGE) fudge = 0;
14188 if (WhiteOnMove(forwardMostMove)) {
14189 if(whiteNPS >= 0) lastTickLength = 0;
14190 timeRemaining = whiteTimeRemaining -= lastTickLength;
14191 DisplayWhiteClock(whiteTimeRemaining - fudge,
14192 WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
14194 if(blackNPS >= 0) lastTickLength = 0;
14195 timeRemaining = blackTimeRemaining -= lastTickLength;
14196 DisplayBlackClock(blackTimeRemaining - fudge,
14197 !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
14200 if (CheckFlags()) return;
14203 intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
14204 StartClockTimer(intendedTickLength);
14206 /* if the time remaining has fallen below the alarm threshold, sound the
14207 * alarm. if the alarm has sounded and (due to a takeback or time control
14208 * with increment) the time remaining has increased to a level above the
14209 * threshold, reset the alarm so it can sound again.
14212 if (appData.icsActive && appData.icsAlarm) {
14214 /* make sure we are dealing with the user's clock */
14215 if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
14216 ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
14219 if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
14220 alarmSounded = FALSE;
14221 } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
14223 alarmSounded = TRUE;
14229 /* A player has just moved, so stop the previously running
14230 clock and (if in clock mode) start the other one.
14231 We redisplay both clocks in case we're in ICS mode, because
14232 ICS gives us an update to both clocks after every move.
14233 Note that this routine is called *after* forwardMostMove
14234 is updated, so the last fractional tick must be subtracted
14235 from the color that is *not* on move now.
14240 long lastTickLength;
14242 int flagged = FALSE;
14246 if (StopClockTimer() && appData.clockMode) {
14247 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
14248 if (WhiteOnMove(forwardMostMove)) {
14249 if(blackNPS >= 0) lastTickLength = 0;
14250 blackTimeRemaining -= lastTickLength;
14251 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
14252 // if(pvInfoList[forwardMostMove-1].time == -1)
14253 pvInfoList[forwardMostMove-1].time = // use GUI time
14254 (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
14256 if(whiteNPS >= 0) lastTickLength = 0;
14257 whiteTimeRemaining -= lastTickLength;
14258 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
14259 // if(pvInfoList[forwardMostMove-1].time == -1)
14260 pvInfoList[forwardMostMove-1].time =
14261 (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
14263 flagged = CheckFlags();
14265 CheckTimeControl();
14267 if (flagged || !appData.clockMode) return;
14269 switch (gameMode) {
14270 case MachinePlaysBlack:
14271 case MachinePlaysWhite:
14272 case BeginningOfGame:
14273 if (pausing) return;
14277 case PlayFromGameFile:
14285 if (searchTime) { // [HGM] st: set clock of player that has to move to max time
14286 if(WhiteOnMove(forwardMostMove))
14287 whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
14288 else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
14292 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
14293 whiteTimeRemaining : blackTimeRemaining);
14294 StartClockTimer(intendedTickLength);
14298 /* Stop both clocks */
14302 long lastTickLength;
14305 if (!StopClockTimer()) return;
14306 if (!appData.clockMode) return;
14310 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
14311 if (WhiteOnMove(forwardMostMove)) {
14312 if(whiteNPS >= 0) lastTickLength = 0;
14313 whiteTimeRemaining -= lastTickLength;
14314 DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
14316 if(blackNPS >= 0) lastTickLength = 0;
14317 blackTimeRemaining -= lastTickLength;
14318 DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
14323 /* Start clock of player on move. Time may have been reset, so
14324 if clock is already running, stop and restart it. */
14328 (void) StopClockTimer(); /* in case it was running already */
14329 DisplayBothClocks();
14330 if (CheckFlags()) return;
14332 if (!appData.clockMode) return;
14333 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
14335 GetTimeMark(&tickStartTM);
14336 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
14337 whiteTimeRemaining : blackTimeRemaining);
14339 /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
14340 whiteNPS = blackNPS = -1;
14341 if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
14342 || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
14343 whiteNPS = first.nps;
14344 if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
14345 || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
14346 blackNPS = first.nps;
14347 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
14348 whiteNPS = second.nps;
14349 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
14350 blackNPS = second.nps;
14351 if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
14353 StartClockTimer(intendedTickLength);
14360 long second, minute, hour, day;
14362 static char buf[32];
14364 if (ms > 0 && ms <= 9900) {
14365 /* convert milliseconds to tenths, rounding up */
14366 double tenths = floor( ((double)(ms + 99L)) / 100.00 );
14368 sprintf(buf, " %03.1f ", tenths/10.0);
14372 /* convert milliseconds to seconds, rounding up */
14373 /* use floating point to avoid strangeness of integer division
14374 with negative dividends on many machines */
14375 second = (long) floor(((double) (ms + 999L)) / 1000.0);
14382 day = second / (60 * 60 * 24);
14383 second = second % (60 * 60 * 24);
14384 hour = second / (60 * 60);
14385 second = second % (60 * 60);
14386 minute = second / 60;
14387 second = second % 60;
14390 sprintf(buf, " %s%ld:%02ld:%02ld:%02ld ",
14391 sign, day, hour, minute, second);
14393 sprintf(buf, " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
14395 sprintf(buf, " %s%2ld:%02ld ", sign, minute, second);
14402 * This is necessary because some C libraries aren't ANSI C compliant yet.
14405 StrStr(string, match)
14406 char *string, *match;
14410 length = strlen(match);
14412 for (i = strlen(string) - length; i >= 0; i--, string++)
14413 if (!strncmp(match, string, length))
14420 StrCaseStr(string, match)
14421 char *string, *match;
14425 length = strlen(match);
14427 for (i = strlen(string) - length; i >= 0; i--, string++) {
14428 for (j = 0; j < length; j++) {
14429 if (ToLower(match[j]) != ToLower(string[j]))
14432 if (j == length) return string;
14446 c1 = ToLower(*s1++);
14447 c2 = ToLower(*s2++);
14448 if (c1 > c2) return 1;
14449 if (c1 < c2) return -1;
14450 if (c1 == NULLCHAR) return 0;
14459 return isupper(c) ? tolower(c) : c;
14467 return islower(c) ? toupper(c) : c;
14469 #endif /* !_amigados */
14477 if ((ret = (char *) malloc(strlen(s) + 1))) {
14484 StrSavePtr(s, savePtr)
14485 char *s, **savePtr;
14490 if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
14491 strcpy(*savePtr, s);
14503 clock = time((time_t *)NULL);
14504 tm = localtime(&clock);
14505 sprintf(buf, "%04d.%02d.%02d",
14506 tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
14507 return StrSave(buf);
14512 PositionToFEN(move, overrideCastling)
14514 char *overrideCastling;
14516 int i, j, fromX, fromY, toX, toY;
14523 whiteToPlay = (gameMode == EditPosition) ?
14524 !blackPlaysFirst : (move % 2 == 0);
14527 /* Piece placement data */
14528 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
14530 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
14531 if (boards[move][i][j] == EmptySquare) {
14533 } else { ChessSquare piece = boards[move][i][j];
14534 if (emptycount > 0) {
14535 if(emptycount<10) /* [HGM] can be >= 10 */
14536 *p++ = '0' + emptycount;
14537 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
14540 if(PieceToChar(piece) == '+') {
14541 /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
14543 piece = (ChessSquare)(DEMOTED piece);
14545 *p++ = PieceToChar(piece);
14547 /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
14548 p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
14553 if (emptycount > 0) {
14554 if(emptycount<10) /* [HGM] can be >= 10 */
14555 *p++ = '0' + emptycount;
14556 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
14563 /* [HGM] print Crazyhouse or Shogi holdings */
14564 if( gameInfo.holdingsWidth ) {
14565 *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
14567 for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
14568 piece = boards[move][i][BOARD_WIDTH-1];
14569 if( piece != EmptySquare )
14570 for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
14571 *p++ = PieceToChar(piece);
14573 for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
14574 piece = boards[move][BOARD_HEIGHT-i-1][0];
14575 if( piece != EmptySquare )
14576 for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
14577 *p++ = PieceToChar(piece);
14580 if( q == p ) *p++ = '-';
14586 *p++ = whiteToPlay ? 'w' : 'b';
14589 if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
14590 while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
14592 if(nrCastlingRights) {
14594 if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
14595 /* [HGM] write directly from rights */
14596 if(boards[move][CASTLING][2] != NoRights &&
14597 boards[move][CASTLING][0] != NoRights )
14598 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
14599 if(boards[move][CASTLING][2] != NoRights &&
14600 boards[move][CASTLING][1] != NoRights )
14601 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
14602 if(boards[move][CASTLING][5] != NoRights &&
14603 boards[move][CASTLING][3] != NoRights )
14604 *p++ = boards[move][CASTLING][3] + AAA;
14605 if(boards[move][CASTLING][5] != NoRights &&
14606 boards[move][CASTLING][4] != NoRights )
14607 *p++ = boards[move][CASTLING][4] + AAA;
14610 /* [HGM] write true castling rights */
14611 if( nrCastlingRights == 6 ) {
14612 if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
14613 boards[move][CASTLING][2] != NoRights ) *p++ = 'K';
14614 if(boards[move][CASTLING][1] == BOARD_LEFT &&
14615 boards[move][CASTLING][2] != NoRights ) *p++ = 'Q';
14616 if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
14617 boards[move][CASTLING][5] != NoRights ) *p++ = 'k';
14618 if(boards[move][CASTLING][4] == BOARD_LEFT &&
14619 boards[move][CASTLING][5] != NoRights ) *p++ = 'q';
14622 if (q == p) *p++ = '-'; /* No castling rights */
14626 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
14627 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
14628 /* En passant target square */
14629 if (move > backwardMostMove) {
14630 fromX = moveList[move - 1][0] - AAA;
14631 fromY = moveList[move - 1][1] - ONE;
14632 toX = moveList[move - 1][2] - AAA;
14633 toY = moveList[move - 1][3] - ONE;
14634 if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
14635 toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
14636 boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
14638 /* 2-square pawn move just happened */
14640 *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
14644 } else if(move == backwardMostMove) {
14645 // [HGM] perhaps we should always do it like this, and forget the above?
14646 if((signed char)boards[move][EP_STATUS] >= 0) {
14647 *p++ = boards[move][EP_STATUS] + AAA;
14648 *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
14659 /* [HGM] find reversible plies */
14660 { int i = 0, j=move;
14662 if (appData.debugMode) { int k;
14663 fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
14664 for(k=backwardMostMove; k<=forwardMostMove; k++)
14665 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
14669 while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
14670 if( j == backwardMostMove ) i += initialRulePlies;
14671 sprintf(p, "%d ", i);
14672 p += i>=100 ? 4 : i >= 10 ? 3 : 2;
14674 /* Fullmove number */
14675 sprintf(p, "%d", (move / 2) + 1);
14677 return StrSave(buf);
14681 ParseFEN(board, blackPlaysFirst, fen)
14683 int *blackPlaysFirst;
14693 /* [HGM] by default clear Crazyhouse holdings, if present */
14694 if(gameInfo.holdingsWidth) {
14695 for(i=0; i<BOARD_HEIGHT; i++) {
14696 board[i][0] = EmptySquare; /* black holdings */
14697 board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
14698 board[i][1] = (ChessSquare) 0; /* black counts */
14699 board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
14703 /* Piece placement data */
14704 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
14707 if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
14708 if (*p == '/') p++;
14709 emptycount = gameInfo.boardWidth - j;
14710 while (emptycount--)
14711 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
14713 #if(BOARD_FILES >= 10)
14714 } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
14715 p++; emptycount=10;
14716 if (j + emptycount > gameInfo.boardWidth) return FALSE;
14717 while (emptycount--)
14718 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
14720 } else if (isdigit(*p)) {
14721 emptycount = *p++ - '0';
14722 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
14723 if (j + emptycount > gameInfo.boardWidth) return FALSE;
14724 while (emptycount--)
14725 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
14726 } else if (*p == '+' || isalpha(*p)) {
14727 if (j >= gameInfo.boardWidth) return FALSE;
14729 piece = CharToPiece(*++p);
14730 if(piece == EmptySquare) return FALSE; /* unknown piece */
14731 piece = (ChessSquare) (PROMOTED piece ); p++;
14732 if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
14733 } else piece = CharToPiece(*p++);
14735 if(piece==EmptySquare) return FALSE; /* unknown piece */
14736 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
14737 piece = (ChessSquare) (PROMOTED piece);
14738 if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
14741 board[i][(j++)+gameInfo.holdingsWidth] = piece;
14747 while (*p == '/' || *p == ' ') p++;
14749 /* [HGM] look for Crazyhouse holdings here */
14750 while(*p==' ') p++;
14751 if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
14753 if(*p == '-' ) *p++; /* empty holdings */ else {
14754 if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
14755 /* if we would allow FEN reading to set board size, we would */
14756 /* have to add holdings and shift the board read so far here */
14757 while( (piece = CharToPiece(*p) ) != EmptySquare ) {
14759 if((int) piece >= (int) BlackPawn ) {
14760 i = (int)piece - (int)BlackPawn;
14761 i = PieceToNumber((ChessSquare)i);
14762 if( i >= gameInfo.holdingsSize ) return FALSE;
14763 board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
14764 board[BOARD_HEIGHT-1-i][1]++; /* black counts */
14766 i = (int)piece - (int)WhitePawn;
14767 i = PieceToNumber((ChessSquare)i);
14768 if( i >= gameInfo.holdingsSize ) return FALSE;
14769 board[i][BOARD_WIDTH-1] = piece; /* white holdings */
14770 board[i][BOARD_WIDTH-2]++; /* black holdings */
14774 if(*p == ']') *p++;
14777 while(*p == ' ') p++;
14782 *blackPlaysFirst = FALSE;
14785 *blackPlaysFirst = TRUE;
14791 /* [HGM] We NO LONGER ignore the rest of the FEN notation */
14792 /* return the extra info in global variiables */
14794 /* set defaults in case FEN is incomplete */
14795 board[EP_STATUS] = EP_UNKNOWN;
14796 for(i=0; i<nrCastlingRights; i++ ) {
14797 board[CASTLING][i] =
14798 gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
14799 } /* assume possible unless obviously impossible */
14800 if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
14801 if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
14802 if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
14803 && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
14804 if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
14805 if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
14806 if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
14807 && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
14810 while(*p==' ') p++;
14811 if(nrCastlingRights) {
14812 if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
14813 /* castling indicator present, so default becomes no castlings */
14814 for(i=0; i<nrCastlingRights; i++ ) {
14815 board[CASTLING][i] = NoRights;
14818 while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
14819 (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
14820 ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
14821 ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth) ) {
14822 char c = *p++; int whiteKingFile=NoRights, blackKingFile=NoRights;
14824 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
14825 if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
14826 if(board[0 ][i] == WhiteKing) whiteKingFile = i;
14828 if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
14829 whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
14830 if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
14831 && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
14832 if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
14833 && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
14836 for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
14837 board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
14838 board[CASTLING][2] = whiteKingFile;
14841 for(i=BOARD_LEFT; i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
14842 board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
14843 board[CASTLING][2] = whiteKingFile;
14846 for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
14847 board[CASTLING][3] = i != blackKingFile ? i : NoRights;
14848 board[CASTLING][5] = blackKingFile;
14851 for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
14852 board[CASTLING][4] = i != blackKingFile ? i : NoRights;
14853 board[CASTLING][5] = blackKingFile;
14856 default: /* FRC castlings */
14857 if(c >= 'a') { /* black rights */
14858 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
14859 if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
14860 if(i == BOARD_RGHT) break;
14861 board[CASTLING][5] = i;
14863 if(board[BOARD_HEIGHT-1][c] < BlackPawn ||
14864 board[BOARD_HEIGHT-1][c] >= BlackKing ) break;
14866 board[CASTLING][3] = c;
14868 board[CASTLING][4] = c;
14869 } else { /* white rights */
14870 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
14871 if(board[0][i] == WhiteKing) break;
14872 if(i == BOARD_RGHT) break;
14873 board[CASTLING][2] = i;
14874 c -= AAA - 'a' + 'A';
14875 if(board[0][c] >= WhiteKing) break;
14877 board[CASTLING][0] = c;
14879 board[CASTLING][1] = c;
14883 for(i=0; i<nrCastlingRights; i++)
14884 if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
14885 if (appData.debugMode) {
14886 fprintf(debugFP, "FEN castling rights:");
14887 for(i=0; i<nrCastlingRights; i++)
14888 fprintf(debugFP, " %d", board[CASTLING][i]);
14889 fprintf(debugFP, "\n");
14892 while(*p==' ') p++;
14895 /* read e.p. field in games that know e.p. capture */
14896 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
14897 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
14899 p++; board[EP_STATUS] = EP_NONE;
14901 char c = *p++ - AAA;
14903 if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
14904 if(*p >= '0' && *p <='9') *p++;
14905 board[EP_STATUS] = c;
14910 if(sscanf(p, "%d", &i) == 1) {
14911 FENrulePlies = i; /* 50-move ply counter */
14912 /* (The move number is still ignored) */
14919 EditPositionPasteFEN(char *fen)
14922 Board initial_position;
14924 if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
14925 DisplayError(_("Bad FEN position in clipboard"), 0);
14928 int savedBlackPlaysFirst = blackPlaysFirst;
14929 EditPositionEvent();
14930 blackPlaysFirst = savedBlackPlaysFirst;
14931 CopyBoard(boards[0], initial_position);
14932 initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
14933 EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
14934 DisplayBothClocks();
14935 DrawPosition(FALSE, boards[currentMove]);
14940 static char cseq[12] = "\\ ";
14942 Boolean set_cont_sequence(char *new_seq)
14947 // handle bad attempts to set the sequence
14949 return 0; // acceptable error - no debug
14951 len = strlen(new_seq);
14952 ret = (len > 0) && (len < sizeof(cseq));
14954 strcpy(cseq, new_seq);
14955 else if (appData.debugMode)
14956 fprintf(debugFP, "Invalid continuation sequence \"%s\" (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
14961 reformat a source message so words don't cross the width boundary. internal
14962 newlines are not removed. returns the wrapped size (no null character unless
14963 included in source message). If dest is NULL, only calculate the size required
14964 for the dest buffer. lp argument indicats line position upon entry, and it's
14965 passed back upon exit.
14967 int wrap(char *dest, char *src, int count, int width, int *lp)
14969 int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
14971 cseq_len = strlen(cseq);
14972 old_line = line = *lp;
14973 ansi = len = clen = 0;
14975 for (i=0; i < count; i++)
14977 if (src[i] == '\033')
14980 // if we hit the width, back up
14981 if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
14983 // store i & len in case the word is too long
14984 old_i = i, old_len = len;
14986 // find the end of the last word
14987 while (i && src[i] != ' ' && src[i] != '\n')
14993 // word too long? restore i & len before splitting it
14994 if ((old_i-i+clen) >= width)
15001 if (i && src[i-1] == ' ')
15004 if (src[i] != ' ' && src[i] != '\n')
15011 // now append the newline and continuation sequence
15016 strncpy(dest+len, cseq, cseq_len);
15024 dest[len] = src[i];
15028 if (src[i] == '\n')
15033 if (dest && appData.debugMode)
15035 fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
15036 count, width, line, len, *lp);
15037 show_bytes(debugFP, src, count);
15038 fprintf(debugFP, "\ndest: ");
15039 show_bytes(debugFP, dest, len);
15040 fprintf(debugFP, "\n");
15042 *lp = dest ? line : old_line;
15047 // [HGM] vari: routines for shelving variations
15050 PushTail(int firstMove, int lastMove)
15052 int i, j, nrMoves = lastMove - firstMove;
15054 if(appData.icsActive) { // only in local mode
15055 forwardMostMove = currentMove; // mimic old ICS behavior
15058 if(storedGames >= MAX_VARIATIONS-1) return;
15060 // push current tail of game on stack
15061 savedResult[storedGames] = gameInfo.result;
15062 savedDetails[storedGames] = gameInfo.resultDetails;
15063 gameInfo.resultDetails = NULL;
15064 savedFirst[storedGames] = firstMove;
15065 savedLast [storedGames] = lastMove;
15066 savedFramePtr[storedGames] = framePtr;
15067 framePtr -= nrMoves; // reserve space for the boards
15068 for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
15069 CopyBoard(boards[framePtr+i], boards[firstMove+i]);
15070 for(j=0; j<MOVE_LEN; j++)
15071 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
15072 for(j=0; j<2*MOVE_LEN; j++)
15073 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
15074 timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
15075 timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
15076 pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
15077 pvInfoList[firstMove+i-1].depth = 0;
15078 commentList[framePtr+i] = commentList[firstMove+i];
15079 commentList[firstMove+i] = NULL;
15083 forwardMostMove = currentMove; // truncte game so we can start variation
15084 if(storedGames == 1) GreyRevert(FALSE);
15088 PopTail(Boolean annotate)
15091 char buf[8000], moveBuf[20];
15093 if(appData.icsActive) return FALSE; // only in local mode
15094 if(!storedGames) return FALSE; // sanity
15097 ToNrEvent(savedFirst[storedGames]); // sets currentMove
15098 nrMoves = savedLast[storedGames] - currentMove;
15101 if(!WhiteOnMove(currentMove)) sprintf(buf, "(%d...", currentMove+2>>1);
15102 else strcpy(buf, "(");
15103 for(i=currentMove; i<forwardMostMove; i++) {
15105 sprintf(moveBuf, " %d. %s", i+2>>1, SavePart(parseList[i]));
15106 else sprintf(moveBuf, " %s", SavePart(parseList[i]));
15107 strcat(buf, moveBuf);
15108 if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
15112 for(i=1; i<nrMoves; i++) { // copy last variation back
15113 CopyBoard(boards[currentMove+i], boards[framePtr+i]);
15114 for(j=0; j<MOVE_LEN; j++)
15115 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
15116 for(j=0; j<2*MOVE_LEN; j++)
15117 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
15118 timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
15119 timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
15120 pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
15121 if(commentList[currentMove+i]) free(commentList[currentMove+i]);
15122 commentList[currentMove+i] = commentList[framePtr+i];
15123 commentList[framePtr+i] = NULL;
15125 if(annotate) AppendComment(currentMove+1, buf, FALSE);
15126 framePtr = savedFramePtr[storedGames];
15127 gameInfo.result = savedResult[storedGames];
15128 if(gameInfo.resultDetails != NULL) {
15129 free(gameInfo.resultDetails);
15131 gameInfo.resultDetails = savedDetails[storedGames];
15132 forwardMostMove = currentMove + nrMoves;
15133 if(storedGames == 0) GreyRevert(TRUE);
15139 { // remove all shelved variations
15141 for(i=0; i<storedGames; i++) {
15142 if(savedDetails[i])
15143 free(savedDetails[i]);
15144 savedDetails[i] = NULL;
15146 for(i=framePtr; i<MAX_MOVES; i++) {
15147 if(commentList[i]) free(commentList[i]);
15148 commentList[i] = NULL;
15150 framePtr = MAX_MOVES-1;