2 * backend.c -- Common back end for X and Windows NT versions of
4 * Copyright 1991 by Digital Equipment Corporation, Maynard,
7 * Enhancements Copyright 1992-2001, 2002, 2003, 2004, 2005, 2006,
8 * 2007, 2008, 2009, 2010, 2011 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)
134 # define T_(s) gettext(s)
147 /* A point in time */
149 long sec; /* Assuming this is >= 32 bits */
150 int ms; /* Assuming this is >= 16 bits */
153 int establish P((void));
154 void read_from_player P((InputSourceRef isr, VOIDSTAR closure,
155 char *buf, int count, int error));
156 void read_from_ics P((InputSourceRef isr, VOIDSTAR closure,
157 char *buf, int count, int error));
158 void ics_printf P((char *format, ...));
159 void SendToICS P((char *s));
160 void SendToICSDelayed P((char *s, long msdelay));
161 void SendMoveToICS P((ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar));
162 void HandleMachineMove P((char *message, ChessProgramState *cps));
163 int AutoPlayOneMove P((void));
164 int LoadGameOneMove P((ChessMove readAhead));
165 int LoadGameFromFile P((char *filename, int n, char *title, int useList));
166 int LoadPositionFromFile P((char *filename, int n, char *title));
167 int SavePositionToFile P((char *filename));
168 void ApplyMove P((int fromX, int fromY, int toX, int toY, int promoChar,
170 void MakeMove P((int fromX, int fromY, int toX, int toY, int promoChar));
171 void ShowMove P((int fromX, int fromY, int toX, int toY));
172 int FinishMove P((ChessMove moveType, int fromX, int fromY, int toX, int toY,
173 /*char*/int promoChar));
174 void BackwardInner P((int target));
175 void ForwardInner P((int target));
176 int Adjudicate P((ChessProgramState *cps));
177 void GameEnds P((ChessMove result, char *resultDetails, int whosays));
178 void EditPositionDone P((Boolean fakeRights));
179 void PrintOpponents P((FILE *fp));
180 void PrintPosition P((FILE *fp, int move));
181 void StartChessProgram P((ChessProgramState *cps));
182 void SendToProgram P((char *message, ChessProgramState *cps));
183 void SendMoveToProgram P((int moveNum, ChessProgramState *cps));
184 void ReceiveFromProgram P((InputSourceRef isr, VOIDSTAR closure,
185 char *buf, int count, int error));
186 void SendTimeControl P((ChessProgramState *cps,
187 int mps, long tc, int inc, int sd, int st));
188 char *TimeControlTagValue P((void));
189 void Attention P((ChessProgramState *cps));
190 void FeedMovesToProgram P((ChessProgramState *cps, int upto));
191 void ResurrectChessProgram P((void));
192 void DisplayComment P((int moveNumber, char *text));
193 void DisplayMove P((int moveNumber));
195 void ParseGameHistory P((char *game));
196 void ParseBoard12 P((char *string));
197 void KeepAlive P((void));
198 void StartClocks P((void));
199 void SwitchClocks P((int nr));
200 void StopClocks P((void));
201 void ResetClocks P((void));
202 char *PGNDate P((void));
203 void SetGameInfo P((void));
204 Boolean ParseFEN P((Board board, int *blackPlaysFirst, char *fen));
205 int RegisterMove P((void));
206 void MakeRegisteredMove P((void));
207 void TruncateGame P((void));
208 int looking_at P((char *, int *, char *));
209 void CopyPlayerNameIntoFileName P((char **, char *));
210 char *SavePart P((char *));
211 int SaveGameOldStyle P((FILE *));
212 int SaveGamePGN P((FILE *));
213 void GetTimeMark P((TimeMark *));
214 long SubtractTimeMarks P((TimeMark *, TimeMark *));
215 int CheckFlags P((void));
216 long NextTickLength P((long));
217 void CheckTimeControl P((void));
218 void show_bytes P((FILE *, char *, int));
219 int string_to_rating P((char *str));
220 void ParseFeatures P((char* args, ChessProgramState *cps));
221 void InitBackEnd3 P((void));
222 void FeatureDone P((ChessProgramState* cps, int val));
223 void InitChessProgram P((ChessProgramState *cps, int setup));
224 void OutputKibitz(int window, char *text);
225 int PerpetualChase(int first, int last);
226 int EngineOutputIsUp();
227 void InitDrawingSizes(int x, int y);
230 extern void ConsoleCreate();
233 ChessProgramState *WhitePlayer();
234 void InsertIntoMemo P((int which, char *text)); // [HGM] kibitz: in engineo.c
235 int VerifyDisplayMode P(());
237 char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment
238 void InitEngineUCI( const char * iniDir, ChessProgramState * cps ); // [HGM] moved here from winboard.c
239 char *ProbeBook P((int moveNr, char *book)); // [HGM] book: returns a book move
240 char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book
241 void ics_update_width P((int new_width));
242 extern char installDir[MSG_SIZ];
243 VariantClass startVariant; /* [HGM] nicks: initial variant */
245 extern int tinyLayout, smallLayout;
246 ChessProgramStats programStats;
247 char lastPV[2][2*MSG_SIZ]; /* [HGM] pv: last PV in thinking output of each engine */
249 static int exiting = 0; /* [HGM] moved to top */
250 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
251 int startedFromPositionFile = FALSE; Board filePosition; /* [HGM] loadPos */
252 Board partnerBoard; /* [HGM] bughouse: for peeking at partner game */
253 int partnerHighlight[2];
254 Boolean partnerBoardValid = 0;
255 char partnerStatus[MSG_SIZ];
257 Boolean originalFlip;
258 Boolean twoBoards = 0;
259 char endingGame = 0; /* [HGM] crash: flag to prevent recursion of GameEnds() */
260 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS */
261 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
262 int lastIndex = 0; /* [HGM] autoinc: last game/position used in match mode */
263 Boolean connectionAlive;/* [HGM] alive: ICS connection status from probing */
264 int opponentKibitzes;
265 int lastSavedGame; /* [HGM] save: ID of game */
266 char chatPartner[MAX_CHAT][MSG_SIZ]; /* [HGM] chat: list of chatting partners */
267 extern int chatCount;
269 char marker[BOARD_RANKS][BOARD_FILES]; /* [HGM] marks for target squares */
270 ChessSquare pieceSweep = EmptySquare;
271 ChessSquare promoSweep = EmptySquare, defaultPromoChoice;
272 int promoDefaultAltered;
274 /* States for ics_getting_history */
276 #define H_REQUESTED 1
277 #define H_GOT_REQ_HEADER 2
278 #define H_GOT_UNREQ_HEADER 3
279 #define H_GETTING_MOVES 4
280 #define H_GOT_UNWANTED_HEADER 5
282 /* whosays values for GameEnds */
291 /* Maximum number of games in a cmail message */
292 #define CMAIL_MAX_GAMES 20
294 /* Different types of move when calling RegisterMove */
296 #define CMAIL_RESIGN 1
298 #define CMAIL_ACCEPT 3
300 /* Different types of result to remember for each game */
301 #define CMAIL_NOT_RESULT 0
302 #define CMAIL_OLD_RESULT 1
303 #define CMAIL_NEW_RESULT 2
305 /* Telnet protocol constants */
316 safeStrCpy( char *dst, const char *src, size_t count )
319 assert( dst != NULL );
320 assert( src != NULL );
323 for(i=0; i<count; i++) if((dst[i] = src[i]) == NULLCHAR) break;
324 if( i == count && dst[count-1] != NULLCHAR)
326 dst[ count-1 ] = '\0'; // make sure incomplete copy still null-terminated
327 if(appData.debugMode)
328 fprintf(debugFP, "safeStrCpy: copying %s into %s didn't work, not enough space %d\n",src,dst,count);
334 /* Some compiler can't cast u64 to double
335 * This function do the job for us:
337 * We use the highest bit for cast, this only
338 * works if the highest bit is not
339 * in use (This should not happen)
341 * We used this for all compiler
344 u64ToDouble(u64 value)
347 u64 tmp = value & u64Const(0x7fffffffffffffff);
348 r = (double)(s64)tmp;
349 if (value & u64Const(0x8000000000000000))
350 r += 9.2233720368547758080e18; /* 2^63 */
354 /* Fake up flags for now, as we aren't keeping track of castling
355 availability yet. [HGM] Change of logic: the flag now only
356 indicates the type of castlings allowed by the rule of the game.
357 The actual rights themselves are maintained in the array
358 castlingRights, as part of the game history, and are not probed
364 int flags = F_ALL_CASTLE_OK;
365 if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
366 switch (gameInfo.variant) {
368 flags &= ~F_ALL_CASTLE_OK;
369 case VariantGiveaway: // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
370 flags |= F_IGNORE_CHECK;
372 flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
375 flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
377 case VariantKriegspiel:
378 flags |= F_KRIEGSPIEL_CAPTURE;
380 case VariantCapaRandom:
381 case VariantFischeRandom:
382 flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
383 case VariantNoCastle:
384 case VariantShatranj:
387 flags &= ~F_ALL_CASTLE_OK;
395 FILE *gameFileFP, *debugFP;
398 [AS] Note: sometimes, the sscanf() function is used to parse the input
399 into a fixed-size buffer. Because of this, we must be prepared to
400 receive strings as long as the size of the input buffer, which is currently
401 set to 4K for Windows and 8K for the rest.
402 So, we must either allocate sufficiently large buffers here, or
403 reduce the size of the input buffer in the input reading part.
406 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
407 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
408 char thinkOutput1[MSG_SIZ*10];
410 ChessProgramState first, second;
412 /* premove variables */
415 int premoveFromX = 0;
416 int premoveFromY = 0;
417 int premovePromoChar = 0;
419 Boolean alarmSounded;
420 /* end premove variables */
422 char *ics_prefix = "$";
423 int ics_type = ICS_GENERIC;
425 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
426 int pauseExamForwardMostMove = 0;
427 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
428 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
429 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
430 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
431 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
432 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
433 int whiteFlag = FALSE, blackFlag = FALSE;
434 int userOfferedDraw = FALSE;
435 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
436 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
437 int cmailMoveType[CMAIL_MAX_GAMES];
438 long ics_clock_paused = 0;
439 ProcRef icsPR = NoProc, cmailPR = NoProc;
440 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
441 GameMode gameMode = BeginningOfGame;
442 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
443 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
444 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
445 int hiddenThinkOutputState = 0; /* [AS] */
446 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
447 int adjudicateLossPlies = 6;
448 char white_holding[64], black_holding[64];
449 TimeMark lastNodeCountTime;
450 long lastNodeCount=0;
451 int shiftKey; // [HGM] set by mouse handler
453 int have_sent_ICS_logon = 0;
455 int suddenDeath, whiteStartMove, blackStartMove; /* [HGM] for implementation of 'any per time' sessions, as in first part of byoyomi TC */
456 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement, lastWhite, lastBlack;
457 long timeControl_2; /* [AS] Allow separate time controls */
458 char *fullTimeControlString = NULL, *nextSession, *whiteTC, *blackTC; /* [HGM] secondary TC: merge of MPS, TC and inc */
459 long timeRemaining[2][MAX_MOVES];
461 TimeMark programStartTime;
462 char ics_handle[MSG_SIZ];
463 int have_set_title = 0;
465 /* animateTraining preserves the state of appData.animate
466 * when Training mode is activated. This allows the
467 * response to be animated when appData.animate == TRUE and
468 * appData.animateDragging == TRUE.
470 Boolean animateTraining;
476 Board boards[MAX_MOVES];
477 /* [HGM] Following 7 needed for accurate legality tests: */
478 signed char castlingRank[BOARD_FILES]; // and corresponding ranks
479 signed char initialRights[BOARD_FILES];
480 int nrCastlingRights; // For TwoKings, or to implement castling-unknown status
481 int initialRulePlies, FENrulePlies;
482 FILE *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
485 int mute; // mute all sounds
487 // [HGM] vari: next 12 to save and restore variations
488 #define MAX_VARIATIONS 10
489 int framePtr = MAX_MOVES-1; // points to free stack entry
491 int savedFirst[MAX_VARIATIONS];
492 int savedLast[MAX_VARIATIONS];
493 int savedFramePtr[MAX_VARIATIONS];
494 char *savedDetails[MAX_VARIATIONS];
495 ChessMove savedResult[MAX_VARIATIONS];
497 void PushTail P((int firstMove, int lastMove));
498 Boolean PopTail P((Boolean annotate));
499 void CleanupTail P((void));
501 ChessSquare FIDEArray[2][BOARD_FILES] = {
502 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
503 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
504 { BlackRook, BlackKnight, BlackBishop, BlackQueen,
505 BlackKing, BlackBishop, BlackKnight, BlackRook }
508 ChessSquare twoKingsArray[2][BOARD_FILES] = {
509 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
510 WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
511 { BlackRook, BlackKnight, BlackBishop, BlackQueen,
512 BlackKing, BlackKing, BlackKnight, BlackRook }
515 ChessSquare KnightmateArray[2][BOARD_FILES] = {
516 { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
517 WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
518 { BlackRook, BlackMan, BlackBishop, BlackQueen,
519 BlackUnicorn, BlackBishop, BlackMan, BlackRook }
522 ChessSquare SpartanArray[2][BOARD_FILES] = {
523 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
524 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
525 { BlackAlfil, BlackMarshall, BlackKing, BlackDragon,
526 BlackDragon, BlackKing, BlackAngel, BlackAlfil }
529 ChessSquare fairyArray[2][BOARD_FILES] = { /* [HGM] Queen side differs from King side */
530 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
531 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
532 { BlackCardinal, BlackAlfil, BlackMarshall, BlackAngel,
533 BlackKing, BlackMarshall, BlackAlfil, BlackCardinal }
536 ChessSquare ShatranjArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
537 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
538 WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
539 { BlackRook, BlackKnight, BlackAlfil, BlackKing,
540 BlackFerz, BlackAlfil, BlackKnight, BlackRook }
543 ChessSquare makrukArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
544 { WhiteRook, WhiteKnight, WhiteMan, WhiteKing,
545 WhiteFerz, WhiteMan, WhiteKnight, WhiteRook },
546 { BlackRook, BlackKnight, BlackMan, BlackFerz,
547 BlackKing, BlackMan, BlackKnight, BlackRook }
551 #if (BOARD_FILES>=10)
552 ChessSquare ShogiArray[2][BOARD_FILES] = {
553 { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
554 WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
555 { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
556 BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
559 ChessSquare XiangqiArray[2][BOARD_FILES] = {
560 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
561 WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
562 { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
563 BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
566 ChessSquare CapablancaArray[2][BOARD_FILES] = {
567 { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
568 WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
569 { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
570 BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
573 ChessSquare GreatArray[2][BOARD_FILES] = {
574 { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
575 WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
576 { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
577 BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
580 ChessSquare JanusArray[2][BOARD_FILES] = {
581 { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
582 WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
583 { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
584 BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
588 ChessSquare GothicArray[2][BOARD_FILES] = {
589 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
590 WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
591 { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
592 BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
595 #define GothicArray CapablancaArray
599 ChessSquare FalconArray[2][BOARD_FILES] = {
600 { WhiteRook, WhiteKnight, WhiteBishop, WhiteLance, WhiteQueen,
601 WhiteKing, WhiteLance, WhiteBishop, WhiteKnight, WhiteRook },
602 { BlackRook, BlackKnight, BlackBishop, BlackLance, BlackQueen,
603 BlackKing, BlackLance, BlackBishop, BlackKnight, BlackRook }
606 #define FalconArray CapablancaArray
609 #else // !(BOARD_FILES>=10)
610 #define XiangqiPosition FIDEArray
611 #define CapablancaArray FIDEArray
612 #define GothicArray FIDEArray
613 #define GreatArray FIDEArray
614 #endif // !(BOARD_FILES>=10)
616 #if (BOARD_FILES>=12)
617 ChessSquare CourierArray[2][BOARD_FILES] = {
618 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
619 WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
620 { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
621 BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
623 #else // !(BOARD_FILES>=12)
624 #define CourierArray CapablancaArray
625 #endif // !(BOARD_FILES>=12)
628 Board initialPosition;
631 /* Convert str to a rating. Checks for special cases of "----",
633 "++++", etc. Also strips ()'s */
635 string_to_rating(str)
638 while(*str && !isdigit(*str)) ++str;
640 return 0; /* One of the special "no rating" cases */
648 /* Init programStats */
649 programStats.movelist[0] = 0;
650 programStats.depth = 0;
651 programStats.nr_moves = 0;
652 programStats.moves_left = 0;
653 programStats.nodes = 0;
654 programStats.time = -1; // [HGM] PGNtime: make invalid to recognize engine output
655 programStats.score = 0;
656 programStats.got_only_move = 0;
657 programStats.got_fail = 0;
658 programStats.line_is_book = 0;
664 int matched, min, sec;
666 ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
667 startVariant = StringToVariant(appData.variant); // [HGM] nicks: remember original variant
669 GetTimeMark(&programStartTime);
670 srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
673 programStats.ok_to_send = 1;
674 programStats.seen_stat = 0;
677 * Initialize game list
683 * Internet chess server status
685 if (appData.icsActive) {
686 appData.matchMode = FALSE;
687 appData.matchGames = 0;
689 appData.noChessProgram = !appData.zippyPlay;
691 appData.zippyPlay = FALSE;
692 appData.zippyTalk = FALSE;
693 appData.noChessProgram = TRUE;
695 if (*appData.icsHelper != NULLCHAR) {
696 appData.useTelnet = TRUE;
697 appData.telnetProgram = appData.icsHelper;
700 appData.zippyTalk = appData.zippyPlay = FALSE;
703 /* [AS] Initialize pv info list [HGM] and game state */
707 for( i=0; i<=framePtr; i++ ) {
708 pvInfoList[i].depth = -1;
709 boards[i][EP_STATUS] = EP_NONE;
710 for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
715 * Parse timeControl resource
717 if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
718 appData.movesPerSession)) {
720 snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
721 DisplayFatalError(buf, 0, 2);
725 * Parse searchTime resource
727 if (*appData.searchTime != NULLCHAR) {
728 matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
730 searchTime = min * 60;
731 } else if (matched == 2) {
732 searchTime = min * 60 + sec;
735 snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
736 DisplayFatalError(buf, 0, 2);
740 /* [AS] Adjudication threshold */
741 adjudicateLossThreshold = appData.adjudicateLossThreshold;
743 first.which = "first";
744 second.which = "second";
745 first.maybeThinking = second.maybeThinking = FALSE;
746 first.pr = second.pr = NoProc;
747 first.isr = second.isr = NULL;
748 first.sendTime = second.sendTime = 2;
749 first.sendDrawOffers = 1;
750 if (appData.firstPlaysBlack) {
751 first.twoMachinesColor = "black\n";
752 second.twoMachinesColor = "white\n";
754 first.twoMachinesColor = "white\n";
755 second.twoMachinesColor = "black\n";
757 first.program = appData.firstChessProgram;
758 second.program = appData.secondChessProgram;
759 first.host = appData.firstHost;
760 second.host = appData.secondHost;
761 first.dir = appData.firstDirectory;
762 second.dir = appData.secondDirectory;
763 first.other = &second;
764 second.other = &first;
765 first.initString = appData.initString;
766 second.initString = appData.secondInitString;
767 first.computerString = appData.firstComputerString;
768 second.computerString = appData.secondComputerString;
769 first.useSigint = second.useSigint = TRUE;
770 first.useSigterm = second.useSigterm = TRUE;
771 first.reuse = appData.reuseFirst;
772 second.reuse = appData.reuseSecond;
773 first.nps = appData.firstNPS; // [HGM] nps: copy nodes per second
774 second.nps = appData.secondNPS;
775 first.useSetboard = second.useSetboard = FALSE;
776 first.useSAN = second.useSAN = FALSE;
777 first.usePing = second.usePing = FALSE;
778 first.lastPing = second.lastPing = 0;
779 first.lastPong = second.lastPong = 0;
780 first.usePlayother = second.usePlayother = FALSE;
781 first.useColors = second.useColors = TRUE;
782 first.useUsermove = second.useUsermove = FALSE;
783 first.sendICS = second.sendICS = FALSE;
784 first.sendName = second.sendName = appData.icsActive;
785 first.sdKludge = second.sdKludge = FALSE;
786 first.stKludge = second.stKludge = FALSE;
787 TidyProgramName(first.program, first.host, first.tidy);
788 TidyProgramName(second.program, second.host, second.tidy);
789 first.matchWins = second.matchWins = 0;
790 safeStrCpy(first.variants, appData.variant, sizeof(first.variants)/sizeof(first.variants[0]));
791 safeStrCpy(second.variants, appData.variant,sizeof(second.variants)/sizeof(second.variants[0]));
792 first.analysisSupport = second.analysisSupport = 2; /* detect */
793 first.analyzing = second.analyzing = FALSE;
794 first.initDone = second.initDone = FALSE;
796 /* New features added by Tord: */
797 first.useFEN960 = FALSE; second.useFEN960 = FALSE;
798 first.useOOCastle = TRUE; second.useOOCastle = TRUE;
799 /* End of new features added by Tord. */
800 first.fenOverride = appData.fenOverride1;
801 second.fenOverride = appData.fenOverride2;
803 /* [HGM] time odds: set factor for each machine */
804 first.timeOdds = appData.firstTimeOdds;
805 second.timeOdds = appData.secondTimeOdds;
807 if(appData.timeOddsMode) {
808 norm = first.timeOdds;
809 if(norm > second.timeOdds) norm = second.timeOdds;
811 first.timeOdds /= norm;
812 second.timeOdds /= norm;
815 /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
816 first.accumulateTC = appData.firstAccumulateTC;
817 second.accumulateTC = appData.secondAccumulateTC;
818 first.maxNrOfSessions = second.maxNrOfSessions = 1;
821 first.debug = second.debug = FALSE;
822 first.supportsNPS = second.supportsNPS = UNKNOWN;
825 first.optionSettings = appData.firstOptions;
826 second.optionSettings = appData.secondOptions;
828 first.scoreIsAbsolute = appData.firstScoreIsAbsolute; /* [AS] */
829 second.scoreIsAbsolute = appData.secondScoreIsAbsolute; /* [AS] */
830 first.isUCI = appData.firstIsUCI; /* [AS] */
831 second.isUCI = appData.secondIsUCI; /* [AS] */
832 first.hasOwnBookUCI = appData.firstHasOwnBookUCI; /* [AS] */
833 second.hasOwnBookUCI = appData.secondHasOwnBookUCI; /* [AS] */
835 if (appData.firstProtocolVersion > PROTOVER
836 || appData.firstProtocolVersion < 1)
841 len = snprintf(buf, MSG_SIZ, _("protocol version %d not supported"),
842 appData.firstProtocolVersion);
843 if( (len > MSG_SIZ) && appData.debugMode )
844 fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
846 DisplayFatalError(buf, 0, 2);
850 first.protocolVersion = appData.firstProtocolVersion;
853 if (appData.secondProtocolVersion > PROTOVER
854 || appData.secondProtocolVersion < 1)
859 len = snprintf(buf, MSG_SIZ, _("protocol version %d not supported"),
860 appData.secondProtocolVersion);
861 if( (len > MSG_SIZ) && appData.debugMode )
862 fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
864 DisplayFatalError(buf, 0, 2);
868 second.protocolVersion = appData.secondProtocolVersion;
871 if (appData.icsActive) {
872 appData.clockMode = TRUE; /* changes dynamically in ICS mode */
873 // } else if (*appData.searchTime != NULLCHAR || appData.noChessProgram) {
874 } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
875 appData.clockMode = FALSE;
876 first.sendTime = second.sendTime = 0;
880 /* Override some settings from environment variables, for backward
881 compatibility. Unfortunately it's not feasible to have the env
882 vars just set defaults, at least in xboard. Ugh.
884 if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
889 if (appData.noChessProgram) {
890 programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
891 sprintf(programVersion, "%s", PACKAGE_STRING);
893 /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
894 programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
895 sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
898 if (!appData.icsActive) {
902 /* Check for variants that are supported only in ICS mode,
903 or not at all. Some that are accepted here nevertheless
904 have bugs; see comments below.
906 VariantClass variant = StringToVariant(appData.variant);
908 case VariantBughouse: /* need four players and two boards */
909 case VariantKriegspiel: /* need to hide pieces and move details */
910 /* case VariantFischeRandom: (Fabien: moved below) */
911 len = snprintf(buf,MSG_SIZ, _("Variant %s supported only in ICS mode"), appData.variant);
912 if( (len > MSG_SIZ) && appData.debugMode )
913 fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
915 DisplayFatalError(buf, 0, 2);
919 case VariantLoadable:
929 len = snprintf(buf, MSG_SIZ, _("Unknown variant name %s"), appData.variant);
930 if( (len > MSG_SIZ) && appData.debugMode )
931 fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
933 DisplayFatalError(buf, 0, 2);
936 case VariantXiangqi: /* [HGM] repetition rules not implemented */
937 case VariantFairy: /* [HGM] TestLegality definitely off! */
938 case VariantGothic: /* [HGM] should work */
939 case VariantCapablanca: /* [HGM] should work */
940 case VariantCourier: /* [HGM] initial forced moves not implemented */
941 case VariantShogi: /* [HGM] could still mate with pawn drop */
942 case VariantKnightmate: /* [HGM] should work */
943 case VariantCylinder: /* [HGM] untested */
944 case VariantFalcon: /* [HGM] untested */
945 case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
946 offboard interposition not understood */
947 case VariantNormal: /* definitely works! */
948 case VariantWildCastle: /* pieces not automatically shuffled */
949 case VariantNoCastle: /* pieces not automatically shuffled */
950 case VariantFischeRandom: /* [HGM] works and shuffles pieces */
951 case VariantLosers: /* should work except for win condition,
952 and doesn't know captures are mandatory */
953 case VariantSuicide: /* should work except for win condition,
954 and doesn't know captures are mandatory */
955 case VariantGiveaway: /* should work except for win condition,
956 and doesn't know captures are mandatory */
957 case VariantTwoKings: /* should work */
958 case VariantAtomic: /* should work except for win condition */
959 case Variant3Check: /* should work except for win condition */
960 case VariantShatranj: /* should work except for all win conditions */
961 case VariantMakruk: /* should work except for daw countdown */
962 case VariantBerolina: /* might work if TestLegality is off */
963 case VariantCapaRandom: /* should work */
964 case VariantJanus: /* should work */
965 case VariantSuper: /* experimental */
966 case VariantGreat: /* experimental, requires legality testing to be off */
967 case VariantSChess: /* S-Chess, should work */
968 case VariantSpartan: /* should work */
973 InitEngineUCI( installDir, &first ); // [HGM] moved here from winboard.c, to make available in xboard
974 InitEngineUCI( installDir, &second );
977 int NextIntegerFromString( char ** str, long * value )
982 while( *s == ' ' || *s == '\t' ) {
988 if( *s >= '0' && *s <= '9' ) {
989 while( *s >= '0' && *s <= '9' ) {
990 *value = *value * 10 + (*s - '0');
1002 int NextTimeControlFromString( char ** str, long * value )
1005 int result = NextIntegerFromString( str, &temp );
1008 *value = temp * 60; /* Minutes */
1009 if( **str == ':' ) {
1011 result = NextIntegerFromString( str, &temp );
1012 *value += temp; /* Seconds */
1019 int NextSessionFromString( char ** str, int *moves, long * tc, long *inc, int *incType)
1020 { /* [HGM] routine added to read '+moves/time' for secondary time control. */
1021 int result = -1, type = 0; long temp, temp2;
1023 if(**str != ':') return -1; // old params remain in force!
1025 if(**str == '*') type = *(*str)++, temp = 0; // sandclock TC
1026 if( NextIntegerFromString( str, &temp ) ) return -1;
1027 if(type) { *moves = 0; *tc = temp * 500; *inc = temp * 1000; *incType = '*'; return 0; }
1030 /* time only: incremental or sudden-death time control */
1031 if(**str == '+') { /* increment follows; read it */
1033 if(**str == '!') type = *(*str)++; // Bronstein TC
1034 if(result = NextIntegerFromString( str, &temp2)) return -1;
1035 *inc = temp2 * 1000;
1036 if(**str == '.') { // read fraction of increment
1037 char *start = ++(*str);
1038 if(result = NextIntegerFromString( str, &temp2)) return -1;
1040 while(start++ < *str) temp2 /= 10;
1044 *moves = 0; *tc = temp * 1000; *incType = type;
1048 (*str)++; /* classical time control */
1049 result = NextIntegerFromString( str, &temp2); // NOTE: already converted to seconds by ParseTimeControl()
1060 int GetTimeQuota(int movenr, int lastUsed, char *tcString)
1061 { /* [HGM] get time to add from the multi-session time-control string */
1062 int incType, moves=1; /* kludge to force reading of first session */
1063 long time, increment;
1066 if(!*s) return 0; // empty TC string means we ran out of the last sudden-death version
1067 if(appData.debugMode) fprintf(debugFP, "TC string = '%s'\n", tcString);
1069 if(moves) NextSessionFromString(&s, &moves, &time, &increment, &incType);
1070 nextSession = s; suddenDeath = moves == 0 && increment == 0;
1071 if(appData.debugMode) fprintf(debugFP, "mps=%d tc=%d inc=%d\n", moves, (int) time, (int) increment);
1072 if(movenr == -1) return time; /* last move before new session */
1073 if(incType == '*') increment = 0; else // for sandclock, time is added while not thinking
1074 if(incType == '!' && lastUsed < increment) increment = lastUsed;
1075 if(!moves) return increment; /* current session is incremental */
1076 if(movenr >= 0) movenr -= moves; /* we already finished this session */
1077 } while(movenr >= -1); /* try again for next session */
1079 return 0; // no new time quota on this move
1083 ParseTimeControl(tc, ti, mps)
1090 char buf[MSG_SIZ], buf2[MSG_SIZ], *mytc = tc;
1093 if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1094 if(!strchr(tc, '+') && !strchr(tc, '/') && sscanf(tc, "%d:%d", &min, &sec) >= 1)
1095 sprintf(mytc=buf2, "%d", 60*min+sec); // convert 'classical' min:sec tc string to seconds
1099 snprintf(buf, MSG_SIZ, ":%d/%s+%g", mps, mytc, ti);
1101 snprintf(buf, MSG_SIZ, ":%s+%g", mytc, ti);
1104 snprintf(buf, MSG_SIZ, ":%d/%s", mps, mytc);
1106 snprintf(buf, MSG_SIZ, ":%s", mytc);
1108 fullTimeControlString = StrSave(buf); // this should now be in PGN format
1110 if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1115 /* Parse second time control */
1118 if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1126 timeControl_2 = tc2 * 1000;
1136 timeControl = tc1 * 1000;
1139 timeIncrement = ti * 1000; /* convert to ms */
1140 movesPerSession = 0;
1143 movesPerSession = mps;
1151 if (appData.debugMode) {
1152 fprintf(debugFP, "%s\n", programVersion);
1155 set_cont_sequence(appData.wrapContSeq);
1156 if (appData.matchGames > 0) {
1157 appData.matchMode = TRUE;
1158 } else if (appData.matchMode) {
1159 appData.matchGames = 1;
1161 if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1162 appData.matchGames = appData.sameColorGames;
1163 if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1164 if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1165 if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1168 if (appData.noChessProgram || first.protocolVersion == 1) {
1171 /* kludge: allow timeout for initial "feature" commands */
1173 DisplayMessage("", _("Starting chess program"));
1174 ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1179 InitBackEnd3 P((void))
1181 GameMode initialMode;
1185 InitChessProgram(&first, startedFromSetupPosition);
1187 if(!appData.noChessProgram) { /* [HGM] tidy: redo program version to use name from myname feature */
1188 free(programVersion);
1189 programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1190 sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1193 if (appData.icsActive) {
1195 /* [DM] Make a console window if needed [HGM] merged ifs */
1201 if (*appData.icsCommPort != NULLCHAR)
1202 len = snprintf(buf, MSG_SIZ, _("Could not open comm port %s"),
1203 appData.icsCommPort);
1205 len = snprintf(buf, MSG_SIZ, _("Could not connect to host %s, port %s"),
1206 appData.icsHost, appData.icsPort);
1208 if( (len > MSG_SIZ) && appData.debugMode )
1209 fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1211 DisplayFatalError(buf, err, 1);
1216 AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1218 AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1219 if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1220 ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1221 } else if (appData.noChessProgram) {
1227 if (*appData.cmailGameName != NULLCHAR) {
1229 OpenLoopback(&cmailPR);
1231 AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1235 DisplayMessage("", "");
1236 if (StrCaseCmp(appData.initialMode, "") == 0) {
1237 initialMode = BeginningOfGame;
1238 } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1239 initialMode = TwoMachinesPlay;
1240 } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1241 initialMode = AnalyzeFile;
1242 } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1243 initialMode = AnalyzeMode;
1244 } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1245 initialMode = MachinePlaysWhite;
1246 } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1247 initialMode = MachinePlaysBlack;
1248 } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1249 initialMode = EditGame;
1250 } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1251 initialMode = EditPosition;
1252 } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1253 initialMode = Training;
1255 len = snprintf(buf, MSG_SIZ, _("Unknown initialMode %s"), appData.initialMode);
1256 if( (len > MSG_SIZ) && appData.debugMode )
1257 fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1259 DisplayFatalError(buf, 0, 2);
1263 if (appData.matchMode) {
1264 /* Set up machine vs. machine match */
1265 if (appData.noChessProgram) {
1266 DisplayFatalError(_("Can't have a match with no chess programs"),
1272 if (*appData.loadGameFile != NULLCHAR) {
1273 int index = appData.loadGameIndex; // [HGM] autoinc
1274 if(index<0) lastIndex = index = 1;
1275 if (!LoadGameFromFile(appData.loadGameFile,
1277 appData.loadGameFile, FALSE)) {
1278 DisplayFatalError(_("Bad game file"), 0, 1);
1281 } else if (*appData.loadPositionFile != NULLCHAR) {
1282 int index = appData.loadPositionIndex; // [HGM] autoinc
1283 if(index<0) lastIndex = index = 1;
1284 if (!LoadPositionFromFile(appData.loadPositionFile,
1286 appData.loadPositionFile)) {
1287 DisplayFatalError(_("Bad position file"), 0, 1);
1292 } else if (*appData.cmailGameName != NULLCHAR) {
1293 /* Set up cmail mode */
1294 ReloadCmailMsgEvent(TRUE);
1296 /* Set up other modes */
1297 if (initialMode == AnalyzeFile) {
1298 if (*appData.loadGameFile == NULLCHAR) {
1299 DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1303 if (*appData.loadGameFile != NULLCHAR) {
1304 (void) LoadGameFromFile(appData.loadGameFile,
1305 appData.loadGameIndex,
1306 appData.loadGameFile, TRUE);
1307 } else if (*appData.loadPositionFile != NULLCHAR) {
1308 (void) LoadPositionFromFile(appData.loadPositionFile,
1309 appData.loadPositionIndex,
1310 appData.loadPositionFile);
1311 /* [HGM] try to make self-starting even after FEN load */
1312 /* to allow automatic setup of fairy variants with wtm */
1313 if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1314 gameMode = BeginningOfGame;
1315 setboardSpoiledMachineBlack = 1;
1317 /* [HGM] loadPos: make that every new game uses the setup */
1318 /* from file as long as we do not switch variant */
1319 if(!blackPlaysFirst) {
1320 startedFromPositionFile = TRUE;
1321 CopyBoard(filePosition, boards[0]);
1324 if (initialMode == AnalyzeMode) {
1325 if (appData.noChessProgram) {
1326 DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1329 if (appData.icsActive) {
1330 DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1334 } else if (initialMode == AnalyzeFile) {
1335 appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1336 ShowThinkingEvent();
1338 AnalysisPeriodicEvent(1);
1339 } else if (initialMode == MachinePlaysWhite) {
1340 if (appData.noChessProgram) {
1341 DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1345 if (appData.icsActive) {
1346 DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1350 MachineWhiteEvent();
1351 } else if (initialMode == MachinePlaysBlack) {
1352 if (appData.noChessProgram) {
1353 DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1357 if (appData.icsActive) {
1358 DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1362 MachineBlackEvent();
1363 } else if (initialMode == TwoMachinesPlay) {
1364 if (appData.noChessProgram) {
1365 DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1369 if (appData.icsActive) {
1370 DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1375 } else if (initialMode == EditGame) {
1377 } else if (initialMode == EditPosition) {
1378 EditPositionEvent();
1379 } else if (initialMode == Training) {
1380 if (*appData.loadGameFile == NULLCHAR) {
1381 DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1390 * Establish will establish a contact to a remote host.port.
1391 * Sets icsPR to a ProcRef for a process (or pseudo-process)
1392 * used to talk to the host.
1393 * Returns 0 if okay, error code if not.
1400 if (*appData.icsCommPort != NULLCHAR) {
1401 /* Talk to the host through a serial comm port */
1402 return OpenCommPort(appData.icsCommPort, &icsPR);
1404 } else if (*appData.gateway != NULLCHAR) {
1405 if (*appData.remoteShell == NULLCHAR) {
1406 /* Use the rcmd protocol to run telnet program on a gateway host */
1407 snprintf(buf, sizeof(buf), "%s %s %s",
1408 appData.telnetProgram, appData.icsHost, appData.icsPort);
1409 return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1412 /* Use the rsh program to run telnet program on a gateway host */
1413 if (*appData.remoteUser == NULLCHAR) {
1414 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1415 appData.gateway, appData.telnetProgram,
1416 appData.icsHost, appData.icsPort);
1418 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1419 appData.remoteShell, appData.gateway,
1420 appData.remoteUser, appData.telnetProgram,
1421 appData.icsHost, appData.icsPort);
1423 return StartChildProcess(buf, "", &icsPR);
1426 } else if (appData.useTelnet) {
1427 return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1430 /* TCP socket interface differs somewhat between
1431 Unix and NT; handle details in the front end.
1433 return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1437 void EscapeExpand(char *p, char *q)
1438 { // [HGM] initstring: routine to shape up string arguments
1439 while(*p++ = *q++) if(p[-1] == '\\')
1441 case 'n': p[-1] = '\n'; break;
1442 case 'r': p[-1] = '\r'; break;
1443 case 't': p[-1] = '\t'; break;
1444 case '\\': p[-1] = '\\'; break;
1445 case 0: *p = 0; return;
1446 default: p[-1] = q[-1]; break;
1451 show_bytes(fp, buf, count)
1457 if (*buf < 040 || *(unsigned char *) buf > 0177) {
1458 fprintf(fp, "\\%03o", *buf & 0xff);
1467 /* Returns an errno value */
1469 OutputMaybeTelnet(pr, message, count, outError)
1475 char buf[8192], *p, *q, *buflim;
1476 int left, newcount, outcount;
1478 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1479 *appData.gateway != NULLCHAR) {
1480 if (appData.debugMode) {
1481 fprintf(debugFP, ">ICS: ");
1482 show_bytes(debugFP, message, count);
1483 fprintf(debugFP, "\n");
1485 return OutputToProcess(pr, message, count, outError);
1488 buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1495 if (appData.debugMode) {
1496 fprintf(debugFP, ">ICS: ");
1497 show_bytes(debugFP, buf, newcount);
1498 fprintf(debugFP, "\n");
1500 outcount = OutputToProcess(pr, buf, newcount, outError);
1501 if (outcount < newcount) return -1; /* to be sure */
1508 } else if (((unsigned char) *p) == TN_IAC) {
1509 *q++ = (char) TN_IAC;
1516 if (appData.debugMode) {
1517 fprintf(debugFP, ">ICS: ");
1518 show_bytes(debugFP, buf, newcount);
1519 fprintf(debugFP, "\n");
1521 outcount = OutputToProcess(pr, buf, newcount, outError);
1522 if (outcount < newcount) return -1; /* to be sure */
1527 read_from_player(isr, closure, message, count, error)
1534 int outError, outCount;
1535 static int gotEof = 0;
1537 /* Pass data read from player on to ICS */
1540 outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1541 if (outCount < count) {
1542 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1544 } else if (count < 0) {
1545 RemoveInputSource(isr);
1546 DisplayFatalError(_("Error reading from keyboard"), error, 1);
1547 } else if (gotEof++ > 0) {
1548 RemoveInputSource(isr);
1549 DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1555 { // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1556 if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1557 connectionAlive = FALSE; // only sticks if no response to 'date' command.
1558 SendToICS("date\n");
1559 if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1562 /* added routine for printf style output to ics */
1563 void ics_printf(char *format, ...)
1565 char buffer[MSG_SIZ];
1568 va_start(args, format);
1569 vsnprintf(buffer, sizeof(buffer), format, args);
1570 buffer[sizeof(buffer)-1] = '\0';
1579 int count, outCount, outError;
1581 if (icsPR == NULL) return;
1584 outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1585 if (outCount < count) {
1586 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1590 /* This is used for sending logon scripts to the ICS. Sending
1591 without a delay causes problems when using timestamp on ICC
1592 (at least on my machine). */
1594 SendToICSDelayed(s,msdelay)
1598 int count, outCount, outError;
1600 if (icsPR == NULL) return;
1603 if (appData.debugMode) {
1604 fprintf(debugFP, ">ICS: ");
1605 show_bytes(debugFP, s, count);
1606 fprintf(debugFP, "\n");
1608 outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1610 if (outCount < count) {
1611 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1616 /* Remove all highlighting escape sequences in s
1617 Also deletes any suffix starting with '('
1620 StripHighlightAndTitle(s)
1623 static char retbuf[MSG_SIZ];
1626 while (*s != NULLCHAR) {
1627 while (*s == '\033') {
1628 while (*s != NULLCHAR && !isalpha(*s)) s++;
1629 if (*s != NULLCHAR) s++;
1631 while (*s != NULLCHAR && *s != '\033') {
1632 if (*s == '(' || *s == '[') {
1643 /* Remove all highlighting escape sequences in s */
1648 static char retbuf[MSG_SIZ];
1651 while (*s != NULLCHAR) {
1652 while (*s == '\033') {
1653 while (*s != NULLCHAR && !isalpha(*s)) s++;
1654 if (*s != NULLCHAR) s++;
1656 while (*s != NULLCHAR && *s != '\033') {
1664 char *variantNames[] = VARIANT_NAMES;
1669 return variantNames[v];
1673 /* Identify a variant from the strings the chess servers use or the
1674 PGN Variant tag names we use. */
1681 VariantClass v = VariantNormal;
1682 int i, found = FALSE;
1688 /* [HGM] skip over optional board-size prefixes */
1689 if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1690 sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1691 while( *e++ != '_');
1694 if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
1698 for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
1699 if (StrCaseStr(e, variantNames[i])) {
1700 v = (VariantClass) i;
1707 if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
1708 || StrCaseStr(e, "wild/fr")
1709 || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
1710 v = VariantFischeRandom;
1711 } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
1712 (i = 1, p = StrCaseStr(e, "w"))) {
1714 while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
1721 case 0: /* FICS only, actually */
1723 /* Castling legal even if K starts on d-file */
1724 v = VariantWildCastle;
1729 /* Castling illegal even if K & R happen to start in
1730 normal positions. */
1731 v = VariantNoCastle;
1744 /* Castling legal iff K & R start in normal positions */
1750 /* Special wilds for position setup; unclear what to do here */
1751 v = VariantLoadable;
1754 /* Bizarre ICC game */
1755 v = VariantTwoKings;
1758 v = VariantKriegspiel;
1764 v = VariantFischeRandom;
1767 v = VariantCrazyhouse;
1770 v = VariantBughouse;
1776 /* Not quite the same as FICS suicide! */
1777 v = VariantGiveaway;
1783 v = VariantShatranj;
1786 /* Temporary names for future ICC types. The name *will* change in
1787 the next xboard/WinBoard release after ICC defines it. */
1825 v = VariantCapablanca;
1828 v = VariantKnightmate;
1834 v = VariantCylinder;
1840 v = VariantCapaRandom;
1843 v = VariantBerolina;
1855 /* Found "wild" or "w" in the string but no number;
1856 must assume it's normal chess. */
1860 len = snprintf(buf, MSG_SIZ, _("Unknown wild type %d"), wnum);
1861 if( (len > MSG_SIZ) && appData.debugMode )
1862 fprintf(debugFP, "StringToVariant: buffer truncated.\n");
1864 DisplayError(buf, 0);
1870 if (appData.debugMode) {
1871 fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
1872 e, wnum, VariantName(v));
1877 static int leftover_start = 0, leftover_len = 0;
1878 char star_match[STAR_MATCH_N][MSG_SIZ];
1880 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
1881 advance *index beyond it, and set leftover_start to the new value of
1882 *index; else return FALSE. If pattern contains the character '*', it
1883 matches any sequence of characters not containing '\r', '\n', or the
1884 character following the '*' (if any), and the matched sequence(s) are
1885 copied into star_match.
1888 looking_at(buf, index, pattern)
1893 char *bufp = &buf[*index], *patternp = pattern;
1895 char *matchp = star_match[0];
1898 if (*patternp == NULLCHAR) {
1899 *index = leftover_start = bufp - buf;
1903 if (*bufp == NULLCHAR) return FALSE;
1904 if (*patternp == '*') {
1905 if (*bufp == *(patternp + 1)) {
1907 matchp = star_match[++star_count];
1911 } else if (*bufp == '\n' || *bufp == '\r') {
1913 if (*patternp == NULLCHAR)
1918 *matchp++ = *bufp++;
1922 if (*patternp != *bufp) return FALSE;
1929 SendToPlayer(data, length)
1933 int error, outCount;
1934 outCount = OutputToProcess(NoProc, data, length, &error);
1935 if (outCount < length) {
1936 DisplayFatalError(_("Error writing to display"), error, 1);
1941 PackHolding(packed, holding)
1953 switch (runlength) {
1964 sprintf(q, "%d", runlength);
1976 /* Telnet protocol requests from the front end */
1978 TelnetRequest(ddww, option)
1979 unsigned char ddww, option;
1981 unsigned char msg[3];
1982 int outCount, outError;
1984 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
1986 if (appData.debugMode) {
1987 char buf1[8], buf2[8], *ddwwStr, *optionStr;
2003 snprintf(buf1,sizeof(buf1)/sizeof(buf1[0]), "%d", ddww);
2012 snprintf(buf2,sizeof(buf2)/sizeof(buf2[0]), "%d", option);
2015 fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
2020 outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
2022 DisplayFatalError(_("Error writing to ICS"), outError, 1);
2029 if (!appData.icsActive) return;
2030 TelnetRequest(TN_DO, TN_ECHO);
2036 if (!appData.icsActive) return;
2037 TelnetRequest(TN_DONT, TN_ECHO);
2041 CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece)
2043 /* put the holdings sent to us by the server on the board holdings area */
2044 int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2048 if(gameInfo.holdingsWidth < 2) return;
2049 if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2050 return; // prevent overwriting by pre-board holdings
2052 if( (int)lowestPiece >= BlackPawn ) {
2055 holdingsStartRow = BOARD_HEIGHT-1;
2058 holdingsColumn = BOARD_WIDTH-1;
2059 countsColumn = BOARD_WIDTH-2;
2060 holdingsStartRow = 0;
2064 for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
2065 board[i][holdingsColumn] = EmptySquare;
2066 board[i][countsColumn] = (ChessSquare) 0;
2068 while( (p=*holdings++) != NULLCHAR ) {
2069 piece = CharToPiece( ToUpper(p) );
2070 if(piece == EmptySquare) continue;
2071 /*j = (int) piece - (int) WhitePawn;*/
2072 j = PieceToNumber(piece);
2073 if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2074 if(j < 0) continue; /* should not happen */
2075 piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2076 board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2077 board[holdingsStartRow+j*direction][countsColumn]++;
2083 VariantSwitch(Board board, VariantClass newVariant)
2085 int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2086 static Board oldBoard;
2088 startedFromPositionFile = FALSE;
2089 if(gameInfo.variant == newVariant) return;
2091 /* [HGM] This routine is called each time an assignment is made to
2092 * gameInfo.variant during a game, to make sure the board sizes
2093 * are set to match the new variant. If that means adding or deleting
2094 * holdings, we shift the playing board accordingly
2095 * This kludge is needed because in ICS observe mode, we get boards
2096 * of an ongoing game without knowing the variant, and learn about the
2097 * latter only later. This can be because of the move list we requested,
2098 * in which case the game history is refilled from the beginning anyway,
2099 * but also when receiving holdings of a crazyhouse game. In the latter
2100 * case we want to add those holdings to the already received position.
2104 if (appData.debugMode) {
2105 fprintf(debugFP, "Switch board from %s to %s\n",
2106 VariantName(gameInfo.variant), VariantName(newVariant));
2107 setbuf(debugFP, NULL);
2109 shuffleOpenings = 0; /* [HGM] shuffle */
2110 gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2114 newWidth = 9; newHeight = 9;
2115 gameInfo.holdingsSize = 7;
2116 case VariantBughouse:
2117 case VariantCrazyhouse:
2118 newHoldingsWidth = 2; break;
2122 newHoldingsWidth = 2;
2123 gameInfo.holdingsSize = 8;
2126 case VariantCapablanca:
2127 case VariantCapaRandom:
2130 newHoldingsWidth = gameInfo.holdingsSize = 0;
2133 if(newWidth != gameInfo.boardWidth ||
2134 newHeight != gameInfo.boardHeight ||
2135 newHoldingsWidth != gameInfo.holdingsWidth ) {
2137 /* shift position to new playing area, if needed */
2138 if(newHoldingsWidth > gameInfo.holdingsWidth) {
2139 for(i=0; i<BOARD_HEIGHT; i++)
2140 for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2141 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2143 for(i=0; i<newHeight; i++) {
2144 board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2145 board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2147 } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2148 for(i=0; i<BOARD_HEIGHT; i++)
2149 for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2150 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2153 gameInfo.boardWidth = newWidth;
2154 gameInfo.boardHeight = newHeight;
2155 gameInfo.holdingsWidth = newHoldingsWidth;
2156 gameInfo.variant = newVariant;
2157 InitDrawingSizes(-2, 0);
2158 } else gameInfo.variant = newVariant;
2159 CopyBoard(oldBoard, board); // remember correctly formatted board
2160 InitPosition(FALSE); /* this sets up board[0], but also other stuff */
2161 DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2164 static int loggedOn = FALSE;
2166 /*-- Game start info cache: --*/
2168 char gs_kind[MSG_SIZ];
2169 static char player1Name[128] = "";
2170 static char player2Name[128] = "";
2171 static char cont_seq[] = "\n\\ ";
2172 static int player1Rating = -1;
2173 static int player2Rating = -1;
2174 /*----------------------------*/
2176 ColorClass curColor = ColorNormal;
2177 int suppressKibitz = 0;
2180 Boolean soughtPending = FALSE;
2181 Boolean seekGraphUp;
2182 #define MAX_SEEK_ADS 200
2184 char *seekAdList[MAX_SEEK_ADS];
2185 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2186 float tcList[MAX_SEEK_ADS];
2187 char colorList[MAX_SEEK_ADS];
2188 int nrOfSeekAds = 0;
2189 int minRating = 1010, maxRating = 2800;
2190 int hMargin = 10, vMargin = 20, h, w;
2191 extern int squareSize, lineGap;
2196 int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2197 xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2198 if(r < minRating+100 && r >=0 ) r = minRating+100;
2199 if(r > maxRating) r = maxRating;
2200 if(tc < 1.) tc = 1.;
2201 if(tc > 95.) tc = 95.;
2202 x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2203 y = ((double)r - minRating)/(maxRating - minRating)
2204 * (h-vMargin-squareSize/8-1) + vMargin;
2205 if(ratingList[i] < 0) y = vMargin + squareSize/4;
2206 if(strstr(seekAdList[i], " u ")) color = 1;
2207 if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2208 !strstr(seekAdList[i], "bullet") &&
2209 !strstr(seekAdList[i], "blitz") &&
2210 !strstr(seekAdList[i], "standard") ) color = 2;
2211 if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2212 DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2216 AddAd(char *handle, char *rating, int base, int inc, char rated, char *type, int nr, Boolean plot)
2218 char buf[MSG_SIZ], *ext = "";
2219 VariantClass v = StringToVariant(type);
2220 if(strstr(type, "wild")) {
2221 ext = type + 4; // append wild number
2222 if(v == VariantFischeRandom) type = "chess960"; else
2223 if(v == VariantLoadable) type = "setup"; else
2224 type = VariantName(v);
2226 snprintf(buf, MSG_SIZ, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2227 if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2228 if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2229 ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2230 sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2231 tcList[nrOfSeekAds] = base + (2./3.)*inc;
2232 seekNrList[nrOfSeekAds] = nr;
2233 zList[nrOfSeekAds] = 0;
2234 seekAdList[nrOfSeekAds++] = StrSave(buf);
2235 if(plot) PlotSeekAd(nrOfSeekAds-1);
2242 int x = xList[i], y = yList[i], d=squareSize/4, k;
2243 DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2244 if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2245 // now replot every dot that overlapped
2246 for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2247 int xx = xList[k], yy = yList[k];
2248 if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2249 DrawSeekDot(xx, yy, colorList[k]);
2254 RemoveSeekAd(int nr)
2257 for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2259 if(seekAdList[i]) free(seekAdList[i]);
2260 seekAdList[i] = seekAdList[--nrOfSeekAds];
2261 seekNrList[i] = seekNrList[nrOfSeekAds];
2262 ratingList[i] = ratingList[nrOfSeekAds];
2263 colorList[i] = colorList[nrOfSeekAds];
2264 tcList[i] = tcList[nrOfSeekAds];
2265 xList[i] = xList[nrOfSeekAds];
2266 yList[i] = yList[nrOfSeekAds];
2267 zList[i] = zList[nrOfSeekAds];
2268 seekAdList[nrOfSeekAds] = NULL;
2274 MatchSoughtLine(char *line)
2276 char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2277 int nr, base, inc, u=0; char dummy;
2279 if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2280 sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2282 (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2283 sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7) ) {
2284 // match: compact and save the line
2285 AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2295 if(!seekGraphUp) return FALSE;
2296 h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
2297 w = BOARD_WIDTH * (squareSize + lineGap) + lineGap;
2299 DrawSeekBackground(0, 0, w, h);
2300 DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2301 DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2302 for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2303 int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2305 DrawSeekAxis(hMargin+5*(i%500==0), yy, hMargin-5, yy); // rating ticks
2308 snprintf(buf, MSG_SIZ, "%d", i);
2309 DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2312 DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2313 for(i=1; i<100; i+=(i<10?1:5)) {
2314 int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2315 DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2316 if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2318 snprintf(buf, MSG_SIZ, "%d", i);
2319 DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2322 for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2326 int SeekGraphClick(ClickType click, int x, int y, int moving)
2328 static int lastDown = 0, displayed = 0, lastSecond;
2329 if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2330 if(click == Release || moving) return FALSE;
2332 soughtPending = TRUE;
2333 SendToICS(ics_prefix);
2334 SendToICS("sought\n"); // should this be "sought all"?
2335 } else { // issue challenge based on clicked ad
2336 int dist = 10000; int i, closest = 0, second = 0;
2337 for(i=0; i<nrOfSeekAds; i++) {
2338 int d = (x-xList[i])*(x-xList[i]) + (y-yList[i])*(y-yList[i]) + zList[i];
2339 if(d < dist) { dist = d; closest = i; }
2340 second += (d - zList[i] < 120); // count in-range ads
2341 if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2345 second = (second > 1);
2346 if(displayed != closest || second != lastSecond) {
2347 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2348 lastSecond = second; displayed = closest;
2350 if(click == Press) {
2351 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2354 } // on press 'hit', only show info
2355 if(moving == 2) return TRUE; // ignore right up-clicks on dot
2356 snprintf(buf, MSG_SIZ, "play %d\n", seekNrList[closest]);
2357 SendToICS(ics_prefix);
2359 return TRUE; // let incoming board of started game pop down the graph
2360 } else if(click == Release) { // release 'miss' is ignored
2361 zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2362 if(moving == 2) { // right up-click
2363 nrOfSeekAds = 0; // refresh graph
2364 soughtPending = TRUE;
2365 SendToICS(ics_prefix);
2366 SendToICS("sought\n"); // should this be "sought all"?
2369 } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2370 // press miss or release hit 'pop down' seek graph
2371 seekGraphUp = FALSE;
2372 DrawPosition(TRUE, NULL);
2378 read_from_ics(isr, closure, data, count, error)
2385 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2386 #define STARTED_NONE 0
2387 #define STARTED_MOVES 1
2388 #define STARTED_BOARD 2
2389 #define STARTED_OBSERVE 3
2390 #define STARTED_HOLDINGS 4
2391 #define STARTED_CHATTER 5
2392 #define STARTED_COMMENT 6
2393 #define STARTED_MOVES_NOHIDE 7
2395 static int started = STARTED_NONE;
2396 static char parse[20000];
2397 static int parse_pos = 0;
2398 static char buf[BUF_SIZE + 1];
2399 static int firstTime = TRUE, intfSet = FALSE;
2400 static ColorClass prevColor = ColorNormal;
2401 static int savingComment = FALSE;
2402 static int cmatch = 0; // continuation sequence match
2409 int backup; /* [DM] For zippy color lines */
2411 char talker[MSG_SIZ]; // [HGM] chat
2414 connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2416 if (appData.debugMode) {
2418 fprintf(debugFP, "<ICS: ");
2419 show_bytes(debugFP, data, count);
2420 fprintf(debugFP, "\n");
2424 if (appData.debugMode) { int f = forwardMostMove;
2425 fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2426 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2427 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2430 /* If last read ended with a partial line that we couldn't parse,
2431 prepend it to the new read and try again. */
2432 if (leftover_len > 0) {
2433 for (i=0; i<leftover_len; i++)
2434 buf[i] = buf[leftover_start + i];
2437 /* copy new characters into the buffer */
2438 bp = buf + leftover_len;
2439 buf_len=leftover_len;
2440 for (i=0; i<count; i++)
2443 if (data[i] == '\r')
2446 // join lines split by ICS?
2447 if (!appData.noJoin)
2450 Joining just consists of finding matches against the
2451 continuation sequence, and discarding that sequence
2452 if found instead of copying it. So, until a match
2453 fails, there's nothing to do since it might be the
2454 complete sequence, and thus, something we don't want
2457 if (data[i] == cont_seq[cmatch])
2460 if (cmatch == strlen(cont_seq))
2462 cmatch = 0; // complete match. just reset the counter
2465 it's possible for the ICS to not include the space
2466 at the end of the last word, making our [correct]
2467 join operation fuse two separate words. the server
2468 does this when the space occurs at the width setting.
2470 if (!buf_len || buf[buf_len-1] != ' ')
2481 match failed, so we have to copy what matched before
2482 falling through and copying this character. In reality,
2483 this will only ever be just the newline character, but
2484 it doesn't hurt to be precise.
2486 strncpy(bp, cont_seq, cmatch);
2498 buf[buf_len] = NULLCHAR;
2499 // next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2504 while (i < buf_len) {
2505 /* Deal with part of the TELNET option negotiation
2506 protocol. We refuse to do anything beyond the
2507 defaults, except that we allow the WILL ECHO option,
2508 which ICS uses to turn off password echoing when we are
2509 directly connected to it. We reject this option
2510 if localLineEditing mode is on (always on in xboard)
2511 and we are talking to port 23, which might be a real
2512 telnet server that will try to keep WILL ECHO on permanently.
2514 if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2515 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2516 unsigned char option;
2518 switch ((unsigned char) buf[++i]) {
2520 if (appData.debugMode)
2521 fprintf(debugFP, "\n<WILL ");
2522 switch (option = (unsigned char) buf[++i]) {
2524 if (appData.debugMode)
2525 fprintf(debugFP, "ECHO ");
2526 /* Reply only if this is a change, according
2527 to the protocol rules. */
2528 if (remoteEchoOption) break;
2529 if (appData.localLineEditing &&
2530 atoi(appData.icsPort) == TN_PORT) {
2531 TelnetRequest(TN_DONT, TN_ECHO);
2534 TelnetRequest(TN_DO, TN_ECHO);
2535 remoteEchoOption = TRUE;
2539 if (appData.debugMode)
2540 fprintf(debugFP, "%d ", option);
2541 /* Whatever this is, we don't want it. */
2542 TelnetRequest(TN_DONT, option);
2547 if (appData.debugMode)
2548 fprintf(debugFP, "\n<WONT ");
2549 switch (option = (unsigned char) buf[++i]) {
2551 if (appData.debugMode)
2552 fprintf(debugFP, "ECHO ");
2553 /* Reply only if this is a change, according
2554 to the protocol rules. */
2555 if (!remoteEchoOption) break;
2557 TelnetRequest(TN_DONT, TN_ECHO);
2558 remoteEchoOption = FALSE;
2561 if (appData.debugMode)
2562 fprintf(debugFP, "%d ", (unsigned char) option);
2563 /* Whatever this is, it must already be turned
2564 off, because we never agree to turn on
2565 anything non-default, so according to the
2566 protocol rules, we don't reply. */
2571 if (appData.debugMode)
2572 fprintf(debugFP, "\n<DO ");
2573 switch (option = (unsigned char) buf[++i]) {
2575 /* Whatever this is, we refuse to do it. */
2576 if (appData.debugMode)
2577 fprintf(debugFP, "%d ", option);
2578 TelnetRequest(TN_WONT, option);
2583 if (appData.debugMode)
2584 fprintf(debugFP, "\n<DONT ");
2585 switch (option = (unsigned char) buf[++i]) {
2587 if (appData.debugMode)
2588 fprintf(debugFP, "%d ", option);
2589 /* Whatever this is, we are already not doing
2590 it, because we never agree to do anything
2591 non-default, so according to the protocol
2592 rules, we don't reply. */
2597 if (appData.debugMode)
2598 fprintf(debugFP, "\n<IAC ");
2599 /* Doubled IAC; pass it through */
2603 if (appData.debugMode)
2604 fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2605 /* Drop all other telnet commands on the floor */
2608 if (oldi > next_out)
2609 SendToPlayer(&buf[next_out], oldi - next_out);
2615 /* OK, this at least will *usually* work */
2616 if (!loggedOn && looking_at(buf, &i, "ics%")) {
2620 if (loggedOn && !intfSet) {
2621 if (ics_type == ICS_ICC) {
2622 snprintf(str, MSG_SIZ,
2623 "/set-quietly interface %s\n/set-quietly style 12\n",
2625 if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2626 strcat(str, "/set-2 51 1\n/set seek 1\n");
2627 } else if (ics_type == ICS_CHESSNET) {
2628 snprintf(str, MSG_SIZ, "/style 12\n");
2630 safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
2631 strcat(str, programVersion);
2632 strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2633 if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2634 strcat(str, "$iset seekremove 1\n$set seek 1\n");
2636 strcat(str, "$iset nohighlight 1\n");
2638 strcat(str, "$iset lock 1\n$style 12\n");
2641 NotifyFrontendLogin();
2645 if (started == STARTED_COMMENT) {
2646 /* Accumulate characters in comment */
2647 parse[parse_pos++] = buf[i];
2648 if (buf[i] == '\n') {
2649 parse[parse_pos] = NULLCHAR;
2650 if(chattingPartner>=0) {
2652 snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
2653 OutputChatMessage(chattingPartner, mess);
2654 chattingPartner = -1;
2655 next_out = i+1; // [HGM] suppress printing in ICS window
2657 if(!suppressKibitz) // [HGM] kibitz
2658 AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
2659 else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2660 int nrDigit = 0, nrAlph = 0, j;
2661 if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2662 { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2663 parse[parse_pos] = NULLCHAR;
2664 // try to be smart: if it does not look like search info, it should go to
2665 // ICS interaction window after all, not to engine-output window.
2666 for(j=0; j<parse_pos; j++) { // count letters and digits
2667 nrDigit += (parse[j] >= '0' && parse[j] <= '9');
2668 nrAlph += (parse[j] >= 'a' && parse[j] <= 'z');
2669 nrAlph += (parse[j] >= 'A' && parse[j] <= 'Z');
2671 if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2672 int depth=0; float score;
2673 if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2674 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2675 pvInfoList[forwardMostMove-1].depth = depth;
2676 pvInfoList[forwardMostMove-1].score = 100*score;
2678 OutputKibitz(suppressKibitz, parse);
2681 snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
2682 SendToPlayer(tmp, strlen(tmp));
2684 next_out = i+1; // [HGM] suppress printing in ICS window
2686 started = STARTED_NONE;
2688 /* Don't match patterns against characters in comment */
2693 if (started == STARTED_CHATTER) {
2694 if (buf[i] != '\n') {
2695 /* Don't match patterns against characters in chatter */
2699 started = STARTED_NONE;
2700 if(suppressKibitz) next_out = i+1;
2703 /* Kludge to deal with rcmd protocol */
2704 if (firstTime && looking_at(buf, &i, "\001*")) {
2705 DisplayFatalError(&buf[1], 0, 1);
2711 if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2714 if (appData.debugMode)
2715 fprintf(debugFP, "ics_type %d\n", ics_type);
2718 if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
2719 ics_type = ICS_FICS;
2721 if (appData.debugMode)
2722 fprintf(debugFP, "ics_type %d\n", ics_type);
2725 if (!loggedOn && looking_at(buf, &i, "chess.net")) {
2726 ics_type = ICS_CHESSNET;
2728 if (appData.debugMode)
2729 fprintf(debugFP, "ics_type %d\n", ics_type);
2734 (looking_at(buf, &i, "\"*\" is *a registered name") ||
2735 looking_at(buf, &i, "Logging you in as \"*\"") ||
2736 looking_at(buf, &i, "will be \"*\""))) {
2737 safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
2741 if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
2743 snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
2744 DisplayIcsInteractionTitle(buf);
2745 have_set_title = TRUE;
2748 /* skip finger notes */
2749 if (started == STARTED_NONE &&
2750 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
2751 (buf[i] == '1' && buf[i+1] == '0')) &&
2752 buf[i+2] == ':' && buf[i+3] == ' ') {
2753 started = STARTED_CHATTER;
2759 // [HGM] seekgraph: recognize sought lines and end-of-sought message
2760 if(appData.seekGraph) {
2761 if(soughtPending && MatchSoughtLine(buf+i)) {
2762 i = strstr(buf+i, "rated") - buf;
2763 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2764 next_out = leftover_start = i;
2765 started = STARTED_CHATTER;
2766 suppressKibitz = TRUE;
2769 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
2770 && looking_at(buf, &i, "* ads displayed")) {
2771 soughtPending = FALSE;
2776 if(appData.autoRefresh) {
2777 if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
2778 int s = (ics_type == ICS_ICC); // ICC format differs
2780 AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
2781 star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
2782 looking_at(buf, &i, "*% "); // eat prompt
2783 if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
2784 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2785 next_out = i; // suppress
2788 if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
2789 char *p = star_match[0];
2791 if(seekGraphUp) RemoveSeekAd(atoi(p));
2792 while(*p && *p++ != ' '); // next
2794 looking_at(buf, &i, "*% "); // eat prompt
2795 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2802 /* skip formula vars */
2803 if (started == STARTED_NONE &&
2804 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
2805 started = STARTED_CHATTER;
2810 // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
2811 if (appData.autoKibitz && started == STARTED_NONE &&
2812 !appData.icsEngineAnalyze && // [HGM] [DM] ICS analyze
2813 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
2814 if((looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
2815 (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
2816 StrStr(star_match[0], gameInfo.black) == star_match[0] )) { // kibitz of self or opponent
2817 suppressKibitz = TRUE;
2818 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2820 if((StrStr(star_match[0], gameInfo.white) == star_match[0]
2821 && (gameMode == IcsPlayingWhite)) ||
2822 (StrStr(star_match[0], gameInfo.black) == star_match[0]
2823 && (gameMode == IcsPlayingBlack)) ) // opponent kibitz
2824 started = STARTED_CHATTER; // own kibitz we simply discard
2826 started = STARTED_COMMENT; // make sure it will be collected in parse[]
2827 parse_pos = 0; parse[0] = NULLCHAR;
2828 savingComment = TRUE;
2829 suppressKibitz = gameMode != IcsObserving ? 2 :
2830 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
2834 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
2835 looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
2836 && atoi(star_match[0])) {
2837 // suppress the acknowledgements of our own autoKibitz
2839 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2840 if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
2841 SendToPlayer(star_match[0], strlen(star_match[0]));
2842 if(looking_at(buf, &i, "*% ")) // eat prompt
2843 suppressKibitz = FALSE;
2847 } // [HGM] kibitz: end of patch
2849 // [HGM] chat: intercept tells by users for which we have an open chat window
2851 if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
2852 looking_at(buf, &i, "* whispers:") ||
2853 looking_at(buf, &i, "* kibitzes:") ||
2854 looking_at(buf, &i, "* shouts:") ||
2855 looking_at(buf, &i, "* c-shouts:") ||
2856 looking_at(buf, &i, "--> * ") ||
2857 looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
2858 looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
2859 looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
2860 looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
2862 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
2863 chattingPartner = -1;
2865 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
2866 for(p=0; p<MAX_CHAT; p++) {
2867 if(chatPartner[p][0] >= '0' && chatPartner[p][0] <= '9' && channel == atoi(chatPartner[p])) {
2868 talker[0] = '['; strcat(talker, "] ");
2869 Colorize(channel == 1 ? ColorChannel1 : ColorChannel, FALSE);
2870 chattingPartner = p; break;
2873 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
2874 for(p=0; p<MAX_CHAT; p++) {
2875 if(!strcmp("kibitzes", chatPartner[p])) {
2876 talker[0] = '['; strcat(talker, "] ");
2877 chattingPartner = p; break;
2880 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
2881 for(p=0; p<MAX_CHAT; p++) {
2882 if(!strcmp("whispers", chatPartner[p])) {
2883 talker[0] = '['; strcat(talker, "] ");
2884 chattingPartner = p; break;
2887 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
2888 if(buf[i-8] == '-' && buf[i-3] == 't')
2889 for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
2890 if(!strcmp("c-shouts", chatPartner[p])) {
2891 talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
2892 chattingPartner = p; break;
2895 if(chattingPartner < 0)
2896 for(p=0; p<MAX_CHAT; p++) {
2897 if(!strcmp("shouts", chatPartner[p])) {
2898 if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
2899 else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
2900 else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
2901 chattingPartner = p; break;
2905 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
2906 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
2907 talker[0] = 0; Colorize(ColorTell, FALSE);
2908 chattingPartner = p; break;
2910 if(chattingPartner<0) i = oldi; else {
2911 Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
2912 if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
2913 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2914 started = STARTED_COMMENT;
2915 parse_pos = 0; parse[0] = NULLCHAR;
2916 savingComment = 3 + chattingPartner; // counts as TRUE
2917 suppressKibitz = TRUE;
2920 } // [HGM] chat: end of patch
2922 if (appData.zippyTalk || appData.zippyPlay) {
2923 /* [DM] Backup address for color zippy lines */
2926 if (loggedOn == TRUE)
2927 if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
2928 (appData.zippyPlay && ZippyMatch(buf, &backup)));
2930 } // [DM] 'else { ' deleted
2932 /* Regular tells and says */
2933 (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
2934 looking_at(buf, &i, "* (your partner) tells you: ") ||
2935 looking_at(buf, &i, "* says: ") ||
2936 /* Don't color "message" or "messages" output */
2937 (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
2938 looking_at(buf, &i, "*. * at *:*: ") ||
2939 looking_at(buf, &i, "--* (*:*): ") ||
2940 /* Message notifications (same color as tells) */
2941 looking_at(buf, &i, "* has left a message ") ||
2942 looking_at(buf, &i, "* just sent you a message:\n") ||
2943 /* Whispers and kibitzes */
2944 (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
2945 looking_at(buf, &i, "* kibitzes: ") ||
2947 (tkind = 3, looking_at(buf, &i, "*(*: "))) {
2949 if (tkind == 1 && strchr(star_match[0], ':')) {
2950 /* Avoid "tells you:" spoofs in channels */
2953 if (star_match[0][0] == NULLCHAR ||
2954 strchr(star_match[0], ' ') ||
2955 (tkind == 3 && strchr(star_match[1], ' '))) {
2956 /* Reject bogus matches */
2959 if (appData.colorize) {
2960 if (oldi > next_out) {
2961 SendToPlayer(&buf[next_out], oldi - next_out);
2966 Colorize(ColorTell, FALSE);
2967 curColor = ColorTell;
2970 Colorize(ColorKibitz, FALSE);
2971 curColor = ColorKibitz;
2974 p = strrchr(star_match[1], '(');
2981 Colorize(ColorChannel1, FALSE);
2982 curColor = ColorChannel1;
2984 Colorize(ColorChannel, FALSE);
2985 curColor = ColorChannel;
2989 curColor = ColorNormal;
2993 if (started == STARTED_NONE && appData.autoComment &&
2994 (gameMode == IcsObserving ||
2995 gameMode == IcsPlayingWhite ||
2996 gameMode == IcsPlayingBlack)) {
2997 parse_pos = i - oldi;
2998 memcpy(parse, &buf[oldi], parse_pos);
2999 parse[parse_pos] = NULLCHAR;
3000 started = STARTED_COMMENT;
3001 savingComment = TRUE;
3003 started = STARTED_CHATTER;
3004 savingComment = FALSE;
3011 if (looking_at(buf, &i, "* s-shouts: ") ||
3012 looking_at(buf, &i, "* c-shouts: ")) {
3013 if (appData.colorize) {
3014 if (oldi > next_out) {
3015 SendToPlayer(&buf[next_out], oldi - next_out);
3018 Colorize(ColorSShout, FALSE);
3019 curColor = ColorSShout;
3022 started = STARTED_CHATTER;
3026 if (looking_at(buf, &i, "--->")) {
3031 if (looking_at(buf, &i, "* shouts: ") ||
3032 looking_at(buf, &i, "--> ")) {
3033 if (appData.colorize) {
3034 if (oldi > next_out) {
3035 SendToPlayer(&buf[next_out], oldi - next_out);
3038 Colorize(ColorShout, FALSE);
3039 curColor = ColorShout;
3042 started = STARTED_CHATTER;
3046 if (looking_at( buf, &i, "Challenge:")) {
3047 if (appData.colorize) {
3048 if (oldi > next_out) {
3049 SendToPlayer(&buf[next_out], oldi - next_out);
3052 Colorize(ColorChallenge, FALSE);
3053 curColor = ColorChallenge;
3059 if (looking_at(buf, &i, "* offers you") ||
3060 looking_at(buf, &i, "* offers to be") ||
3061 looking_at(buf, &i, "* would like to") ||
3062 looking_at(buf, &i, "* requests to") ||
3063 looking_at(buf, &i, "Your opponent offers") ||
3064 looking_at(buf, &i, "Your opponent requests")) {
3066 if (appData.colorize) {
3067 if (oldi > next_out) {
3068 SendToPlayer(&buf[next_out], oldi - next_out);
3071 Colorize(ColorRequest, FALSE);
3072 curColor = ColorRequest;
3077 if (looking_at(buf, &i, "* (*) seeking")) {
3078 if (appData.colorize) {
3079 if (oldi > next_out) {
3080 SendToPlayer(&buf[next_out], oldi - next_out);
3083 Colorize(ColorSeek, FALSE);
3084 curColor = ColorSeek;
3089 if (looking_at(buf, &i, "\\ ")) {
3090 if (prevColor != ColorNormal) {
3091 if (oldi > next_out) {
3092 SendToPlayer(&buf[next_out], oldi - next_out);
3095 Colorize(prevColor, TRUE);
3096 curColor = prevColor;
3098 if (savingComment) {
3099 parse_pos = i - oldi;
3100 memcpy(parse, &buf[oldi], parse_pos);
3101 parse[parse_pos] = NULLCHAR;
3102 started = STARTED_COMMENT;
3103 if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3104 chattingPartner = savingComment - 3; // kludge to remember the box
3106 started = STARTED_CHATTER;
3111 if (looking_at(buf, &i, "Black Strength :") ||
3112 looking_at(buf, &i, "<<< style 10 board >>>") ||
3113 looking_at(buf, &i, "<10>") ||
3114 looking_at(buf, &i, "#@#")) {
3115 /* Wrong board style */
3117 SendToICS(ics_prefix);
3118 SendToICS("set style 12\n");
3119 SendToICS(ics_prefix);
3120 SendToICS("refresh\n");
3124 if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
3126 have_sent_ICS_logon = 1;
3130 if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3131 (looking_at(buf, &i, "\n<12> ") ||
3132 looking_at(buf, &i, "<12> "))) {
3134 if (oldi > next_out) {
3135 SendToPlayer(&buf[next_out], oldi - next_out);
3138 started = STARTED_BOARD;
3143 if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3144 looking_at(buf, &i, "<b1> ")) {
3145 if (oldi > next_out) {
3146 SendToPlayer(&buf[next_out], oldi - next_out);
3149 started = STARTED_HOLDINGS;
3154 if (looking_at(buf, &i, "* *vs. * *--- *")) {
3156 /* Header for a move list -- first line */
3158 switch (ics_getting_history) {
3162 case BeginningOfGame:
3163 /* User typed "moves" or "oldmoves" while we
3164 were idle. Pretend we asked for these
3165 moves and soak them up so user can step
3166 through them and/or save them.
3169 gameMode = IcsObserving;
3172 ics_getting_history = H_GOT_UNREQ_HEADER;
3174 case EditGame: /*?*/
3175 case EditPosition: /*?*/
3176 /* Should above feature work in these modes too? */
3177 /* For now it doesn't */
3178 ics_getting_history = H_GOT_UNWANTED_HEADER;
3181 ics_getting_history = H_GOT_UNWANTED_HEADER;
3186 /* Is this the right one? */
3187 if (gameInfo.white && gameInfo.black &&
3188 strcmp(gameInfo.white, star_match[0]) == 0 &&
3189 strcmp(gameInfo.black, star_match[2]) == 0) {
3191 ics_getting_history = H_GOT_REQ_HEADER;
3194 case H_GOT_REQ_HEADER:
3195 case H_GOT_UNREQ_HEADER:
3196 case H_GOT_UNWANTED_HEADER:
3197 case H_GETTING_MOVES:
3198 /* Should not happen */
3199 DisplayError(_("Error gathering move list: two headers"), 0);
3200 ics_getting_history = H_FALSE;
3204 /* Save player ratings into gameInfo if needed */
3205 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3206 ics_getting_history == H_GOT_UNREQ_HEADER) &&
3207 (gameInfo.whiteRating == -1 ||
3208 gameInfo.blackRating == -1)) {
3210 gameInfo.whiteRating = string_to_rating(star_match[1]);
3211 gameInfo.blackRating = string_to_rating(star_match[3]);
3212 if (appData.debugMode)
3213 fprintf(debugFP, _("Ratings from header: W %d, B %d\n"),
3214 gameInfo.whiteRating, gameInfo.blackRating);
3219 if (looking_at(buf, &i,
3220 "* * match, initial time: * minute*, increment: * second")) {
3221 /* Header for a move list -- second line */
3222 /* Initial board will follow if this is a wild game */
3223 if (gameInfo.event != NULL) free(gameInfo.event);
3224 snprintf(str, MSG_SIZ, "ICS %s %s match", star_match[0], star_match[1]);
3225 gameInfo.event = StrSave(str);
3226 /* [HGM] we switched variant. Translate boards if needed. */
3227 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3231 if (looking_at(buf, &i, "Move ")) {
3232 /* Beginning of a move list */
3233 switch (ics_getting_history) {
3235 /* Normally should not happen */
3236 /* Maybe user hit reset while we were parsing */
3239 /* Happens if we are ignoring a move list that is not
3240 * the one we just requested. Common if the user
3241 * tries to observe two games without turning off
3244 case H_GETTING_MOVES:
3245 /* Should not happen */
3246 DisplayError(_("Error gathering move list: nested"), 0);
3247 ics_getting_history = H_FALSE;
3249 case H_GOT_REQ_HEADER:
3250 ics_getting_history = H_GETTING_MOVES;
3251 started = STARTED_MOVES;
3253 if (oldi > next_out) {
3254 SendToPlayer(&buf[next_out], oldi - next_out);
3257 case H_GOT_UNREQ_HEADER:
3258 ics_getting_history = H_GETTING_MOVES;
3259 started = STARTED_MOVES_NOHIDE;
3262 case H_GOT_UNWANTED_HEADER:
3263 ics_getting_history = H_FALSE;
3269 if (looking_at(buf, &i, "% ") ||
3270 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3271 && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3272 if(ics_type == ICS_ICC && soughtPending) { // [HGM] seekgraph: on ICC sought-list has no termination line
3273 soughtPending = FALSE;
3277 if(suppressKibitz) next_out = i;
3278 savingComment = FALSE;
3282 case STARTED_MOVES_NOHIDE:
3283 memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3284 parse[parse_pos + i - oldi] = NULLCHAR;
3285 ParseGameHistory(parse);
3287 if (appData.zippyPlay && first.initDone) {
3288 FeedMovesToProgram(&first, forwardMostMove);
3289 if (gameMode == IcsPlayingWhite) {
3290 if (WhiteOnMove(forwardMostMove)) {
3291 if (first.sendTime) {
3292 if (first.useColors) {
3293 SendToProgram("black\n", &first);
3295 SendTimeRemaining(&first, TRUE);
3297 if (first.useColors) {
3298 SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3300 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3301 first.maybeThinking = TRUE;
3303 if (first.usePlayother) {
3304 if (first.sendTime) {
3305 SendTimeRemaining(&first, TRUE);
3307 SendToProgram("playother\n", &first);
3313 } else if (gameMode == IcsPlayingBlack) {
3314 if (!WhiteOnMove(forwardMostMove)) {
3315 if (first.sendTime) {
3316 if (first.useColors) {
3317 SendToProgram("white\n", &first);
3319 SendTimeRemaining(&first, FALSE);
3321 if (first.useColors) {
3322 SendToProgram("black\n", &first);
3324 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3325 first.maybeThinking = TRUE;
3327 if (first.usePlayother) {
3328 if (first.sendTime) {
3329 SendTimeRemaining(&first, FALSE);
3331 SendToProgram("playother\n", &first);
3340 if (gameMode == IcsObserving && ics_gamenum == -1) {
3341 /* Moves came from oldmoves or moves command
3342 while we weren't doing anything else.
3344 currentMove = forwardMostMove;
3345 ClearHighlights();/*!!could figure this out*/
3346 flipView = appData.flipView;
3347 DrawPosition(TRUE, boards[currentMove]);
3348 DisplayBothClocks();
3349 snprintf(str, MSG_SIZ, "%s vs. %s",
3350 gameInfo.white, gameInfo.black);
3354 /* Moves were history of an active game */
3355 if (gameInfo.resultDetails != NULL) {
3356 free(gameInfo.resultDetails);
3357 gameInfo.resultDetails = NULL;
3360 HistorySet(parseList, backwardMostMove,
3361 forwardMostMove, currentMove-1);
3362 DisplayMove(currentMove - 1);
3363 if (started == STARTED_MOVES) next_out = i;
3364 started = STARTED_NONE;
3365 ics_getting_history = H_FALSE;
3368 case STARTED_OBSERVE:
3369 started = STARTED_NONE;
3370 SendToICS(ics_prefix);
3371 SendToICS("refresh\n");
3377 if(bookHit) { // [HGM] book: simulate book reply
3378 static char bookMove[MSG_SIZ]; // a bit generous?
3380 programStats.nodes = programStats.depth = programStats.time =
3381 programStats.score = programStats.got_only_move = 0;
3382 sprintf(programStats.movelist, "%s (xbook)", bookHit);
3384 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
3385 strcat(bookMove, bookHit);
3386 HandleMachineMove(bookMove, &first);
3391 if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3392 started == STARTED_HOLDINGS ||
3393 started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3394 /* Accumulate characters in move list or board */
3395 parse[parse_pos++] = buf[i];
3398 /* Start of game messages. Mostly we detect start of game
3399 when the first board image arrives. On some versions
3400 of the ICS, though, we need to do a "refresh" after starting
3401 to observe in order to get the current board right away. */
3402 if (looking_at(buf, &i, "Adding game * to observation list")) {
3403 started = STARTED_OBSERVE;
3407 /* Handle auto-observe */
3408 if (appData.autoObserve &&
3409 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3410 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3412 /* Choose the player that was highlighted, if any. */
3413 if (star_match[0][0] == '\033' ||
3414 star_match[1][0] != '\033') {
3415 player = star_match[0];
3417 player = star_match[2];
3419 snprintf(str, MSG_SIZ, "%sobserve %s\n",
3420 ics_prefix, StripHighlightAndTitle(player));
3423 /* Save ratings from notify string */
3424 safeStrCpy(player1Name, star_match[0], sizeof(player1Name)/sizeof(player1Name[0]));
3425 player1Rating = string_to_rating(star_match[1]);
3426 safeStrCpy(player2Name, star_match[2], sizeof(player2Name)/sizeof(player2Name[0]));
3427 player2Rating = string_to_rating(star_match[3]);
3429 if (appData.debugMode)
3431 "Ratings from 'Game notification:' %s %d, %s %d\n",
3432 player1Name, player1Rating,
3433 player2Name, player2Rating);
3438 /* Deal with automatic examine mode after a game,
3439 and with IcsObserving -> IcsExamining transition */
3440 if (looking_at(buf, &i, "Entering examine mode for game *") ||
3441 looking_at(buf, &i, "has made you an examiner of game *")) {
3443 int gamenum = atoi(star_match[0]);
3444 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3445 gamenum == ics_gamenum) {
3446 /* We were already playing or observing this game;
3447 no need to refetch history */
3448 gameMode = IcsExamining;
3450 pauseExamForwardMostMove = forwardMostMove;
3451 } else if (currentMove < forwardMostMove) {
3452 ForwardInner(forwardMostMove);
3455 /* I don't think this case really can happen */
3456 SendToICS(ics_prefix);
3457 SendToICS("refresh\n");
3462 /* Error messages */
3463 // if (ics_user_moved) {
3464 if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3465 if (looking_at(buf, &i, "Illegal move") ||
3466 looking_at(buf, &i, "Not a legal move") ||
3467 looking_at(buf, &i, "Your king is in check") ||
3468 looking_at(buf, &i, "It isn't your turn") ||
3469 looking_at(buf, &i, "It is not your move")) {
3471 if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3472 currentMove = forwardMostMove-1;
3473 DisplayMove(currentMove - 1); /* before DMError */
3474 DrawPosition(FALSE, boards[currentMove]);
3475 SwitchClocks(forwardMostMove-1); // [HGM] race
3476 DisplayBothClocks();
3478 DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3484 if (looking_at(buf, &i, "still have time") ||
3485 looking_at(buf, &i, "not out of time") ||
3486 looking_at(buf, &i, "either player is out of time") ||
3487 looking_at(buf, &i, "has timeseal; checking")) {
3488 /* We must have called his flag a little too soon */
3489 whiteFlag = blackFlag = FALSE;
3493 if (looking_at(buf, &i, "added * seconds to") ||
3494 looking_at(buf, &i, "seconds were added to")) {
3495 /* Update the clocks */
3496 SendToICS(ics_prefix);
3497 SendToICS("refresh\n");
3501 if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3502 ics_clock_paused = TRUE;
3507 if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3508 ics_clock_paused = FALSE;
3513 /* Grab player ratings from the Creating: message.
3514 Note we have to check for the special case when
3515 the ICS inserts things like [white] or [black]. */
3516 if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3517 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3519 0 player 1 name (not necessarily white)
3521 2 empty, white, or black (IGNORED)
3522 3 player 2 name (not necessarily black)
3525 The names/ratings are sorted out when the game
3526 actually starts (below).
3528 safeStrCpy(player1Name, StripHighlightAndTitle(star_match[0]), sizeof(player1Name)/sizeof(player1Name[0]));
3529 player1Rating = string_to_rating(star_match[1]);
3530 safeStrCpy(player2Name, StripHighlightAndTitle(star_match[3]), sizeof(player2Name)/sizeof(player2Name[0]));
3531 player2Rating = string_to_rating(star_match[4]);
3533 if (appData.debugMode)
3535 "Ratings from 'Creating:' %s %d, %s %d\n",
3536 player1Name, player1Rating,
3537 player2Name, player2Rating);
3542 /* Improved generic start/end-of-game messages */
3543 if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3544 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3545 /* If tkind == 0: */
3546 /* star_match[0] is the game number */
3547 /* [1] is the white player's name */
3548 /* [2] is the black player's name */
3549 /* For end-of-game: */
3550 /* [3] is the reason for the game end */
3551 /* [4] is a PGN end game-token, preceded by " " */
3552 /* For start-of-game: */
3553 /* [3] begins with "Creating" or "Continuing" */
3554 /* [4] is " *" or empty (don't care). */
3555 int gamenum = atoi(star_match[0]);
3556 char *whitename, *blackname, *why, *endtoken;
3557 ChessMove endtype = EndOfFile;
3560 whitename = star_match[1];
3561 blackname = star_match[2];
3562 why = star_match[3];
3563 endtoken = star_match[4];
3565 whitename = star_match[1];
3566 blackname = star_match[3];
3567 why = star_match[5];
3568 endtoken = star_match[6];
3571 /* Game start messages */
3572 if (strncmp(why, "Creating ", 9) == 0 ||
3573 strncmp(why, "Continuing ", 11) == 0) {
3574 gs_gamenum = gamenum;
3575 safeStrCpy(gs_kind, strchr(why, ' ') + 1,sizeof(gs_kind)/sizeof(gs_kind[0]));
3576 VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
3578 if (appData.zippyPlay) {
3579 ZippyGameStart(whitename, blackname);
3582 partnerBoardValid = FALSE; // [HGM] bughouse
3586 /* Game end messages */
3587 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3588 ics_gamenum != gamenum) {
3591 while (endtoken[0] == ' ') endtoken++;
3592 switch (endtoken[0]) {
3595 endtype = GameUnfinished;
3598 endtype = BlackWins;
3601 if (endtoken[1] == '/')
3602 endtype = GameIsDrawn;
3604 endtype = WhiteWins;
3607 GameEnds(endtype, why, GE_ICS);
3609 if (appData.zippyPlay && first.initDone) {
3610 ZippyGameEnd(endtype, why);
3611 if (first.pr == NULL) {
3612 /* Start the next process early so that we'll
3613 be ready for the next challenge */
3614 StartChessProgram(&first);
3616 /* Send "new" early, in case this command takes
3617 a long time to finish, so that we'll be ready
3618 for the next challenge. */
3619 gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3623 if(appData.bgObserve && partnerBoardValid) DrawPosition(TRUE, partnerBoard);
3627 if (looking_at(buf, &i, "Removing game * from observation") ||
3628 looking_at(buf, &i, "no longer observing game *") ||
3629 looking_at(buf, &i, "Game * (*) has no examiners")) {
3630 if (gameMode == IcsObserving &&
3631 atoi(star_match[0]) == ics_gamenum)
3633 /* icsEngineAnalyze */
3634 if (appData.icsEngineAnalyze) {
3641 ics_user_moved = FALSE;
3646 if (looking_at(buf, &i, "no longer examining game *")) {
3647 if (gameMode == IcsExamining &&
3648 atoi(star_match[0]) == ics_gamenum)
3652 ics_user_moved = FALSE;
3657 /* Advance leftover_start past any newlines we find,
3658 so only partial lines can get reparsed */
3659 if (looking_at(buf, &i, "\n")) {
3660 prevColor = curColor;
3661 if (curColor != ColorNormal) {
3662 if (oldi > next_out) {
3663 SendToPlayer(&buf[next_out], oldi - next_out);
3666 Colorize(ColorNormal, FALSE);
3667 curColor = ColorNormal;
3669 if (started == STARTED_BOARD) {
3670 started = STARTED_NONE;
3671 parse[parse_pos] = NULLCHAR;
3672 ParseBoard12(parse);
3675 /* Send premove here */
3676 if (appData.premove) {
3678 if (currentMove == 0 &&
3679 gameMode == IcsPlayingWhite &&
3680 appData.premoveWhite) {
3681 snprintf(str, MSG_SIZ, "%s\n", appData.premoveWhiteText);
3682 if (appData.debugMode)
3683 fprintf(debugFP, "Sending premove:\n");
3685 } else if (currentMove == 1 &&
3686 gameMode == IcsPlayingBlack &&
3687 appData.premoveBlack) {
3688 snprintf(str, MSG_SIZ, "%s\n", appData.premoveBlackText);
3689 if (appData.debugMode)
3690 fprintf(debugFP, "Sending premove:\n");
3692 } else if (gotPremove) {
3694 ClearPremoveHighlights();
3695 if (appData.debugMode)
3696 fprintf(debugFP, "Sending premove:\n");
3697 UserMoveEvent(premoveFromX, premoveFromY,
3698 premoveToX, premoveToY,
3703 /* Usually suppress following prompt */
3704 if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
3705 while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
3706 if (looking_at(buf, &i, "*% ")) {
3707 savingComment = FALSE;
3712 } else if (started == STARTED_HOLDINGS) {
3714 char new_piece[MSG_SIZ];
3715 started = STARTED_NONE;
3716 parse[parse_pos] = NULLCHAR;
3717 if (appData.debugMode)
3718 fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
3719 parse, currentMove);
3720 if (sscanf(parse, " game %d", &gamenum) == 1) {
3721 if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game
3722 if (gameInfo.variant == VariantNormal) {
3723 /* [HGM] We seem to switch variant during a game!
3724 * Presumably no holdings were displayed, so we have
3725 * to move the position two files to the right to
3726 * create room for them!
3728 VariantClass newVariant;
3729 switch(gameInfo.boardWidth) { // base guess on board width
3730 case 9: newVariant = VariantShogi; break;
3731 case 10: newVariant = VariantGreat; break;
3732 default: newVariant = VariantCrazyhouse; break;
3734 VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3735 /* Get a move list just to see the header, which
3736 will tell us whether this is really bug or zh */
3737 if (ics_getting_history == H_FALSE) {
3738 ics_getting_history = H_REQUESTED;
3739 snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
3743 new_piece[0] = NULLCHAR;
3744 sscanf(parse, "game %d white [%s black [%s <- %s",
3745 &gamenum, white_holding, black_holding,
3747 white_holding[strlen(white_holding)-1] = NULLCHAR;
3748 black_holding[strlen(black_holding)-1] = NULLCHAR;
3749 /* [HGM] copy holdings to board holdings area */
3750 CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
3751 CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
3752 boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
3754 if (appData.zippyPlay && first.initDone) {
3755 ZippyHoldings(white_holding, black_holding,
3759 if (tinyLayout || smallLayout) {
3760 char wh[16], bh[16];
3761 PackHolding(wh, white_holding);
3762 PackHolding(bh, black_holding);
3763 snprintf(str, MSG_SIZ,"[%s-%s] %s-%s", wh, bh,
3764 gameInfo.white, gameInfo.black);
3766 snprintf(str, MSG_SIZ, "%s [%s] vs. %s [%s]",
3767 gameInfo.white, white_holding,
3768 gameInfo.black, black_holding);
3770 if(!partnerUp) // [HGM] bughouse: when peeking at partner game we already know what he captured...
3771 DrawPosition(FALSE, boards[currentMove]);
3773 } else if(appData.bgObserve) { // [HGM] bughouse: holdings of other game => background
3774 sscanf(parse, "game %d white [%s black [%s <- %s",
3775 &gamenum, white_holding, black_holding,
3777 white_holding[strlen(white_holding)-1] = NULLCHAR;
3778 black_holding[strlen(black_holding)-1] = NULLCHAR;
3779 /* [HGM] copy holdings to partner-board holdings area */
3780 CopyHoldings(partnerBoard, white_holding, WhitePawn);
3781 CopyHoldings(partnerBoard, black_holding, BlackPawn);
3782 if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual: always draw
3783 if(partnerUp) DrawPosition(FALSE, partnerBoard);
3784 if(twoBoards) { partnerUp = 0; flipView = !flipView; }
3787 /* Suppress following prompt */
3788 if (looking_at(buf, &i, "*% ")) {
3789 if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
3790 savingComment = FALSE;
3798 i++; /* skip unparsed character and loop back */
3801 if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
3802 // started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
3803 // SendToPlayer(&buf[next_out], i - next_out);
3804 started != STARTED_HOLDINGS && leftover_start > next_out) {
3805 SendToPlayer(&buf[next_out], leftover_start - next_out);
3809 leftover_len = buf_len - leftover_start;
3810 /* if buffer ends with something we couldn't parse,
3811 reparse it after appending the next read */
3813 } else if (count == 0) {
3814 RemoveInputSource(isr);
3815 DisplayFatalError(_("Connection closed by ICS"), 0, 0);
3817 DisplayFatalError(_("Error reading from ICS"), error, 1);
3822 /* Board style 12 looks like this:
3824 <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
3826 * The "<12> " is stripped before it gets to this routine. The two
3827 * trailing 0's (flip state and clock ticking) are later addition, and
3828 * some chess servers may not have them, or may have only the first.
3829 * Additional trailing fields may be added in the future.
3832 #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"
3834 #define RELATION_OBSERVING_PLAYED 0
3835 #define RELATION_OBSERVING_STATIC -2 /* examined, oldmoves, or smoves */
3836 #define RELATION_PLAYING_MYMOVE 1
3837 #define RELATION_PLAYING_NOTMYMOVE -1
3838 #define RELATION_EXAMINING 2
3839 #define RELATION_ISOLATED_BOARD -3
3840 #define RELATION_STARTING_POSITION -4 /* FICS only */
3843 ParseBoard12(string)
3846 GameMode newGameMode;
3847 int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
3848 int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
3849 int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
3850 char to_play, board_chars[200];
3851 char move_str[MSG_SIZ], str[MSG_SIZ], elapsed_time[MSG_SIZ];
3852 char black[32], white[32];
3854 int prevMove = currentMove;
3857 int fromX, fromY, toX, toY;
3859 int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
3860 char *bookHit = NULL; // [HGM] book
3861 Boolean weird = FALSE, reqFlag = FALSE;
3863 fromX = fromY = toX = toY = -1;
3867 if (appData.debugMode)
3868 fprintf(debugFP, _("Parsing board: %s\n"), string);
3870 move_str[0] = NULLCHAR;
3871 elapsed_time[0] = NULLCHAR;
3872 { /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
3874 while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
3875 if(string[i] == ' ') { ranks++; files = 0; }
3877 if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
3880 for(j = 0; j <i; j++) board_chars[j] = string[j];
3881 board_chars[i] = '\0';
3884 n = sscanf(string, PATTERN, &to_play, &double_push,
3885 &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
3886 &gamenum, white, black, &relation, &basetime, &increment,
3887 &white_stren, &black_stren, &white_time, &black_time,
3888 &moveNum, str, elapsed_time, move_str, &ics_flip,
3892 snprintf(str, MSG_SIZ, _("Failed to parse board string:\n\"%s\""), string);
3893 DisplayError(str, 0);
3897 /* Convert the move number to internal form */
3898 moveNum = (moveNum - 1) * 2;
3899 if (to_play == 'B') moveNum++;
3900 if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
3901 DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
3907 case RELATION_OBSERVING_PLAYED:
3908 case RELATION_OBSERVING_STATIC:
3909 if (gamenum == -1) {
3910 /* Old ICC buglet */
3911 relation = RELATION_OBSERVING_STATIC;
3913 newGameMode = IcsObserving;
3915 case RELATION_PLAYING_MYMOVE:
3916 case RELATION_PLAYING_NOTMYMOVE:
3918 ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
3919 IcsPlayingWhite : IcsPlayingBlack;
3921 case RELATION_EXAMINING:
3922 newGameMode = IcsExamining;
3924 case RELATION_ISOLATED_BOARD:
3926 /* Just display this board. If user was doing something else,
3927 we will forget about it until the next board comes. */
3928 newGameMode = IcsIdle;
3930 case RELATION_STARTING_POSITION:
3931 newGameMode = gameMode;
3935 if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
3936 && newGameMode == IcsObserving && gamenum != ics_gamenum && appData.bgObserve) {
3937 // [HGM] bughouse: don't act on alien boards while we play. Just parse the board and save it */
3939 for (k = 0; k < ranks; k++) {
3940 for (j = 0; j < files; j++)
3941 board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
3942 if(gameInfo.holdingsWidth > 1) {
3943 board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
3944 board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
3947 CopyBoard(partnerBoard, board);
3948 if(toSqr = strchr(str, '/')) { // extract highlights from long move
3949 partnerBoard[EP_STATUS-3] = toSqr[1] - AAA; // kludge: hide highlighting info in board
3950 partnerBoard[EP_STATUS-4] = toSqr[2] - ONE;
3951 } else partnerBoard[EP_STATUS-4] = partnerBoard[EP_STATUS-3] = -1;
3952 if(toSqr = strchr(str, '-')) {
3953 partnerBoard[EP_STATUS-1] = toSqr[1] - AAA;
3954 partnerBoard[EP_STATUS-2] = toSqr[2] - ONE;
3955 } else partnerBoard[EP_STATUS-1] = partnerBoard[EP_STATUS-2] = -1;
3956 if(appData.dualBoard && !twoBoards) { twoBoards = 1; InitDrawingSizes(-2,0); }
3957 if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual
3958 if(partnerUp) DrawPosition(FALSE, partnerBoard);
3959 if(twoBoards) { partnerUp = 0; flipView = !flipView; } // [HGM] dual
3960 snprintf(partnerStatus, MSG_SIZ,"W: %d:%02d B: %d:%02d (%d-%d) %c", white_time/60000, (white_time%60000)/1000,
3961 (black_time/60000), (black_time%60000)/1000, white_stren, black_stren, to_play);
3962 DisplayMessage(partnerStatus, "");
3963 partnerBoardValid = TRUE;
3967 /* Modify behavior for initial board display on move listing
3970 switch (ics_getting_history) {
3974 case H_GOT_REQ_HEADER:
3975 case H_GOT_UNREQ_HEADER:
3976 /* This is the initial position of the current game */
3977 gamenum = ics_gamenum;
3978 moveNum = 0; /* old ICS bug workaround */
3979 if (to_play == 'B') {
3980 startedFromSetupPosition = TRUE;
3981 blackPlaysFirst = TRUE;
3983 if (forwardMostMove == 0) forwardMostMove = 1;
3984 if (backwardMostMove == 0) backwardMostMove = 1;
3985 if (currentMove == 0) currentMove = 1;
3987 newGameMode = gameMode;
3988 relation = RELATION_STARTING_POSITION; /* ICC needs this */
3990 case H_GOT_UNWANTED_HEADER:
3991 /* This is an initial board that we don't want */
3993 case H_GETTING_MOVES:
3994 /* Should not happen */
3995 DisplayError(_("Error gathering move list: extra board"), 0);
3996 ics_getting_history = H_FALSE;
4000 if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
4001 weird && (int)gameInfo.variant < (int)VariantShogi) {
4002 /* [HGM] We seem to have switched variant unexpectedly
4003 * Try to guess new variant from board size
4005 VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
4006 if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
4007 if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
4008 if(ranks == 8 && files == 12) newVariant = VariantCourier; else
4009 if(ranks == 9 && files == 9) newVariant = VariantShogi; else
4010 if(!weird) newVariant = VariantNormal;
4011 VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4012 /* Get a move list just to see the header, which
4013 will tell us whether this is really bug or zh */
4014 if (ics_getting_history == H_FALSE) {
4015 ics_getting_history = H_REQUESTED; reqFlag = TRUE;
4016 snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4021 /* Take action if this is the first board of a new game, or of a
4022 different game than is currently being displayed. */
4023 if (gamenum != ics_gamenum || newGameMode != gameMode ||
4024 relation == RELATION_ISOLATED_BOARD) {
4026 /* Forget the old game and get the history (if any) of the new one */
4027 if (gameMode != BeginningOfGame) {
4031 if (appData.autoRaiseBoard) BoardToTop();
4033 if (gamenum == -1) {
4034 newGameMode = IcsIdle;
4035 } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
4036 appData.getMoveList && !reqFlag) {
4037 /* Need to get game history */
4038 ics_getting_history = H_REQUESTED;
4039 snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4043 /* Initially flip the board to have black on the bottom if playing
4044 black or if the ICS flip flag is set, but let the user change
4045 it with the Flip View button. */
4046 flipView = appData.autoFlipView ?
4047 (newGameMode == IcsPlayingBlack) || ics_flip :
4050 /* Done with values from previous mode; copy in new ones */
4051 gameMode = newGameMode;
4053 ics_gamenum = gamenum;
4054 if (gamenum == gs_gamenum) {
4055 int klen = strlen(gs_kind);
4056 if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
4057 snprintf(str, MSG_SIZ, "ICS %s", gs_kind);
4058 gameInfo.event = StrSave(str);
4060 gameInfo.event = StrSave("ICS game");
4062 gameInfo.site = StrSave(appData.icsHost);
4063 gameInfo.date = PGNDate();
4064 gameInfo.round = StrSave("-");
4065 gameInfo.white = StrSave(white);
4066 gameInfo.black = StrSave(black);
4067 timeControl = basetime * 60 * 1000;
4069 timeIncrement = increment * 1000;
4070 movesPerSession = 0;
4071 gameInfo.timeControl = TimeControlTagValue();
4072 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
4073 if (appData.debugMode) {
4074 fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
4075 fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
4076 setbuf(debugFP, NULL);
4079 gameInfo.outOfBook = NULL;
4081 /* Do we have the ratings? */
4082 if (strcmp(player1Name, white) == 0 &&
4083 strcmp(player2Name, black) == 0) {
4084 if (appData.debugMode)
4085 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4086 player1Rating, player2Rating);
4087 gameInfo.whiteRating = player1Rating;
4088 gameInfo.blackRating = player2Rating;
4089 } else if (strcmp(player2Name, white) == 0 &&
4090 strcmp(player1Name, black) == 0) {
4091 if (appData.debugMode)
4092 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4093 player2Rating, player1Rating);
4094 gameInfo.whiteRating = player2Rating;
4095 gameInfo.blackRating = player1Rating;
4097 player1Name[0] = player2Name[0] = NULLCHAR;
4099 /* Silence shouts if requested */
4100 if (appData.quietPlay &&
4101 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
4102 SendToICS(ics_prefix);
4103 SendToICS("set shout 0\n");
4107 /* Deal with midgame name changes */
4109 if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
4110 if (gameInfo.white) free(gameInfo.white);
4111 gameInfo.white = StrSave(white);
4113 if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
4114 if (gameInfo.black) free(gameInfo.black);
4115 gameInfo.black = StrSave(black);
4119 /* Throw away game result if anything actually changes in examine mode */
4120 if (gameMode == IcsExamining && !newGame) {
4121 gameInfo.result = GameUnfinished;
4122 if (gameInfo.resultDetails != NULL) {
4123 free(gameInfo.resultDetails);
4124 gameInfo.resultDetails = NULL;
4128 /* In pausing && IcsExamining mode, we ignore boards coming
4129 in if they are in a different variation than we are. */
4130 if (pauseExamInvalid) return;
4131 if (pausing && gameMode == IcsExamining) {
4132 if (moveNum <= pauseExamForwardMostMove) {
4133 pauseExamInvalid = TRUE;
4134 forwardMostMove = pauseExamForwardMostMove;
4139 if (appData.debugMode) {
4140 fprintf(debugFP, "load %dx%d board\n", files, ranks);
4142 /* Parse the board */
4143 for (k = 0; k < ranks; k++) {
4144 for (j = 0; j < files; j++)
4145 board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4146 if(gameInfo.holdingsWidth > 1) {
4147 board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4148 board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4151 CopyBoard(boards[moveNum], board);
4152 boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
4154 startedFromSetupPosition =
4155 !CompareBoards(board, initialPosition);
4156 if(startedFromSetupPosition)
4157 initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
4160 /* [HGM] Set castling rights. Take the outermost Rooks,
4161 to make it also work for FRC opening positions. Note that board12
4162 is really defective for later FRC positions, as it has no way to
4163 indicate which Rook can castle if they are on the same side of King.
4164 For the initial position we grant rights to the outermost Rooks,
4165 and remember thos rights, and we then copy them on positions
4166 later in an FRC game. This means WB might not recognize castlings with
4167 Rooks that have moved back to their original position as illegal,
4168 but in ICS mode that is not its job anyway.
4170 if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
4171 { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
4173 for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4174 if(board[0][i] == WhiteRook) j = i;
4175 initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4176 for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4177 if(board[0][i] == WhiteRook) j = i;
4178 initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4179 for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4180 if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4181 initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4182 for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4183 if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4184 initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4186 if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
4187 for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4188 if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
4189 for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4190 if(board[BOARD_HEIGHT-1][k] == bKing)
4191 initialRights[5] = boards[moveNum][CASTLING][5] = k;
4192 if(gameInfo.variant == VariantTwoKings) {
4193 // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
4194 if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
4195 if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
4198 r = boards[moveNum][CASTLING][0] = initialRights[0];
4199 if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4200 r = boards[moveNum][CASTLING][1] = initialRights[1];
4201 if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4202 r = boards[moveNum][CASTLING][3] = initialRights[3];
4203 if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4204 r = boards[moveNum][CASTLING][4] = initialRights[4];
4205 if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4206 /* wildcastle kludge: always assume King has rights */
4207 r = boards[moveNum][CASTLING][2] = initialRights[2];
4208 r = boards[moveNum][CASTLING][5] = initialRights[5];
4210 /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4211 boards[moveNum][EP_STATUS] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
4214 if (ics_getting_history == H_GOT_REQ_HEADER ||
4215 ics_getting_history == H_GOT_UNREQ_HEADER) {
4216 /* This was an initial position from a move list, not
4217 the current position */
4221 /* Update currentMove and known move number limits */
4222 newMove = newGame || moveNum > forwardMostMove;
4225 forwardMostMove = backwardMostMove = currentMove = moveNum;
4226 if (gameMode == IcsExamining && moveNum == 0) {
4227 /* Workaround for ICS limitation: we are not told the wild
4228 type when starting to examine a game. But if we ask for
4229 the move list, the move list header will tell us */
4230 ics_getting_history = H_REQUESTED;
4231 snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4234 } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4235 || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4237 /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4238 /* [HGM] applied this also to an engine that is silently watching */
4239 if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4240 (gameMode == IcsObserving || gameMode == IcsExamining) &&
4241 gameInfo.variant == currentlyInitializedVariant) {
4242 takeback = forwardMostMove - moveNum;
4243 for (i = 0; i < takeback; i++) {
4244 if (appData.debugMode) fprintf(debugFP, "take back move\n");
4245 SendToProgram("undo\n", &first);
4250 forwardMostMove = moveNum;
4251 if (!pausing || currentMove > forwardMostMove)
4252 currentMove = forwardMostMove;
4254 /* New part of history that is not contiguous with old part */
4255 if (pausing && gameMode == IcsExamining) {
4256 pauseExamInvalid = TRUE;
4257 forwardMostMove = pauseExamForwardMostMove;
4260 if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4262 if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4263 // [HGM] when we will receive the move list we now request, it will be
4264 // fed to the engine from the first move on. So if the engine is not
4265 // in the initial position now, bring it there.
4266 InitChessProgram(&first, 0);
4269 ics_getting_history = H_REQUESTED;
4270 snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4273 forwardMostMove = backwardMostMove = currentMove = moveNum;
4276 /* Update the clocks */
4277 if (strchr(elapsed_time, '.')) {
4279 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4280 timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4282 /* Time is in seconds */
4283 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4284 timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4289 if (appData.zippyPlay && newGame &&
4290 gameMode != IcsObserving && gameMode != IcsIdle &&
4291 gameMode != IcsExamining)
4292 ZippyFirstBoard(moveNum, basetime, increment);
4295 /* Put the move on the move list, first converting
4296 to canonical algebraic form. */
4298 if (appData.debugMode) {
4299 if (appData.debugMode) { int f = forwardMostMove;
4300 fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4301 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4302 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4304 fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4305 fprintf(debugFP, "moveNum = %d\n", moveNum);
4306 fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4307 setbuf(debugFP, NULL);
4309 if (moveNum <= backwardMostMove) {
4310 /* We don't know what the board looked like before
4312 safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4313 strcat(parseList[moveNum - 1], " ");
4314 strcat(parseList[moveNum - 1], elapsed_time);
4315 moveList[moveNum - 1][0] = NULLCHAR;
4316 } else if (strcmp(move_str, "none") == 0) {
4317 // [HGM] long SAN: swapped order; test for 'none' before parsing move
4318 /* Again, we don't know what the board looked like;
4319 this is really the start of the game. */
4320 parseList[moveNum - 1][0] = NULLCHAR;
4321 moveList[moveNum - 1][0] = NULLCHAR;
4322 backwardMostMove = moveNum;
4323 startedFromSetupPosition = TRUE;
4324 fromX = fromY = toX = toY = -1;
4326 // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
4327 // So we parse the long-algebraic move string in stead of the SAN move
4328 int valid; char buf[MSG_SIZ], *prom;
4330 if(gameInfo.variant == VariantShogi && !strchr(move_str, '=') && !strchr(move_str, '@'))
4331 strcat(move_str, "="); // if ICS does not say 'promote' on non-drop, we defer.
4332 // str looks something like "Q/a1-a2"; kill the slash
4334 snprintf(buf, MSG_SIZ,"%c%s", str[0], str+2);
4335 else safeStrCpy(buf, str, sizeof(buf)/sizeof(buf[0])); // might be castling
4336 if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
4337 strcat(buf, prom); // long move lacks promo specification!
4338 if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4339 if(appData.debugMode)
4340 fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4341 safeStrCpy(move_str, buf, MSG_SIZ);
4343 valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4344 &fromX, &fromY, &toX, &toY, &promoChar)
4345 || ParseOneMove(buf, moveNum - 1, &moveType,
4346 &fromX, &fromY, &toX, &toY, &promoChar);
4347 // end of long SAN patch
4349 (void) CoordsToAlgebraic(boards[moveNum - 1],
4350 PosFlags(moveNum - 1),
4351 fromY, fromX, toY, toX, promoChar,
4352 parseList[moveNum-1]);
4353 switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4359 if(gameInfo.variant != VariantShogi)
4360 strcat(parseList[moveNum - 1], "+");
4363 case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4364 strcat(parseList[moveNum - 1], "#");
4367 strcat(parseList[moveNum - 1], " ");
4368 strcat(parseList[moveNum - 1], elapsed_time);
4369 /* currentMoveString is set as a side-effect of ParseOneMove */
4370 if(gameInfo.variant == VariantShogi && currentMoveString[4]) currentMoveString[4] = '^';
4371 safeStrCpy(moveList[moveNum - 1], currentMoveString, sizeof(moveList[moveNum - 1])/sizeof(moveList[moveNum - 1][0]));
4372 strcat(moveList[moveNum - 1], "\n");
4374 if(gameInfo.holdingsWidth && !appData.disguise && gameInfo.variant != VariantSuper
4375 && gameInfo.variant != VariantGreat) // inherit info that ICS does not give from previous board
4376 for(k=0; k<ranks; k++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
4377 ChessSquare old, new = boards[moveNum][k][j];
4378 if(fromY == DROP_RANK && k==toY && j==toX) continue; // dropped pieces always stand for themselves
4379 old = (k==toY && j==toX) ? boards[moveNum-1][fromY][fromX] : boards[moveNum-1][k][j]; // trace back mover
4380 if(old == new) continue;
4381 if(old == PROMOTED new) boards[moveNum][k][j] = old; // prevent promoted pieces to revert to primordial ones
4382 else if(new == WhiteWazir || new == BlackWazir) {
4383 if(old < WhiteCannon || old >= BlackPawn && old < BlackCannon)
4384 boards[moveNum][k][j] = PROMOTED old; // choose correct type of Gold in promotion
4385 else boards[moveNum][k][j] = old; // preserve type of Gold
4386 } else if((old == WhitePawn || old == BlackPawn) && new != EmptySquare) // Pawn promotions (but not e.p.capture!)
4387 boards[moveNum][k][j] = PROMOTED new; // use non-primordial representation of chosen piece
4390 /* Move from ICS was illegal!? Punt. */
4391 if (appData.debugMode) {
4392 fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4393 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4395 safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4396 strcat(parseList[moveNum - 1], " ");
4397 strcat(parseList[moveNum - 1], elapsed_time);
4398 moveList[moveNum - 1][0] = NULLCHAR;
4399 fromX = fromY = toX = toY = -1;
4402 if (appData.debugMode) {
4403 fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4404 setbuf(debugFP, NULL);
4408 /* Send move to chess program (BEFORE animating it). */
4409 if (appData.zippyPlay && !newGame && newMove &&
4410 (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4412 if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4413 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4414 if (moveList[moveNum - 1][0] == NULLCHAR) {
4415 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"),
4417 DisplayError(str, 0);
4419 if (first.sendTime) {
4420 SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4422 bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4423 if (firstMove && !bookHit) {
4425 if (first.useColors) {
4426 SendToProgram(gameMode == IcsPlayingWhite ?
4428 "black\ngo\n", &first);
4430 SendToProgram("go\n", &first);
4432 first.maybeThinking = TRUE;
4435 } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4436 if (moveList[moveNum - 1][0] == NULLCHAR) {
4437 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"), move_str);
4438 DisplayError(str, 0);
4440 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4441 SendMoveToProgram(moveNum - 1, &first);
4448 if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4449 /* If move comes from a remote source, animate it. If it
4450 isn't remote, it will have already been animated. */
4451 if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4452 AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4454 if (!pausing && appData.highlightLastMove) {
4455 SetHighlights(fromX, fromY, toX, toY);
4459 /* Start the clocks */
4460 whiteFlag = blackFlag = FALSE;
4461 appData.clockMode = !(basetime == 0 && increment == 0);
4463 ics_clock_paused = TRUE;
4465 } else if (ticking == 1) {
4466 ics_clock_paused = FALSE;
4468 if (gameMode == IcsIdle ||
4469 relation == RELATION_OBSERVING_STATIC ||
4470 relation == RELATION_EXAMINING ||
4472 DisplayBothClocks();
4476 /* Display opponents and material strengths */
4477 if (gameInfo.variant != VariantBughouse &&
4478 gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4479 if (tinyLayout || smallLayout) {
4480 if(gameInfo.variant == VariantNormal)
4481 snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d}",
4482 gameInfo.white, white_stren, gameInfo.black, black_stren,
4483 basetime, increment);
4485 snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d w%d}",
4486 gameInfo.white, white_stren, gameInfo.black, black_stren,
4487 basetime, increment, (int) gameInfo.variant);
4489 if(gameInfo.variant == VariantNormal)
4490 snprintf(str, MSG_SIZ, "%s (%d) vs. %s (%d) {%d %d}",
4491 gameInfo.white, white_stren, gameInfo.black, black_stren,
4492 basetime, increment);
4494 snprintf(str, MSG_SIZ, "%s (%d) vs. %s (%d) {%d %d %s}",
4495 gameInfo.white, white_stren, gameInfo.black, black_stren,
4496 basetime, increment, VariantName(gameInfo.variant));
4499 if (appData.debugMode) {
4500 fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
4505 /* Display the board */
4506 if (!pausing && !appData.noGUI) {
4508 if (appData.premove)
4510 ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
4511 ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
4512 ClearPremoveHighlights();
4514 j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
4515 if(partnerUp) { flipView = originalFlip; partnerUp = FALSE; j = TRUE; } // [HGM] bughouse: restore view
4516 DrawPosition(j, boards[currentMove]);
4518 DisplayMove(moveNum - 1);
4519 if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4520 !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4521 (gameMode == IcsPlayingBlack) && (WhiteOnMove(moveNum)) ) ) {
4522 if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4526 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4528 if(bookHit) { // [HGM] book: simulate book reply
4529 static char bookMove[MSG_SIZ]; // a bit generous?
4531 programStats.nodes = programStats.depth = programStats.time =
4532 programStats.score = programStats.got_only_move = 0;
4533 sprintf(programStats.movelist, "%s (xbook)", bookHit);
4535 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
4536 strcat(bookMove, bookHit);
4537 HandleMachineMove(bookMove, &first);
4546 if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4547 ics_getting_history = H_REQUESTED;
4548 snprintf(buf, MSG_SIZ, "%smoves %d\n", ics_prefix, ics_gamenum);
4554 AnalysisPeriodicEvent(force)
4557 if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4558 && !force) || !appData.periodicUpdates)
4561 /* Send . command to Crafty to collect stats */
4562 SendToProgram(".\n", &first);
4564 /* Don't send another until we get a response (this makes
4565 us stop sending to old Crafty's which don't understand
4566 the "." command (sending illegal cmds resets node count & time,
4567 which looks bad)) */
4568 programStats.ok_to_send = 0;
4571 void ics_update_width(new_width)
4574 ics_printf("set width %d\n", new_width);
4578 SendMoveToProgram(moveNum, cps)
4580 ChessProgramState *cps;
4584 if (cps->useUsermove) {
4585 SendToProgram("usermove ", cps);
4589 if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4590 int len = space - parseList[moveNum];
4591 memcpy(buf, parseList[moveNum], len);
4593 buf[len] = NULLCHAR;
4595 snprintf(buf, MSG_SIZ,"%s\n", parseList[moveNum]);
4597 SendToProgram(buf, cps);
4599 if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4600 AlphaRank(moveList[moveNum], 4);
4601 SendToProgram(moveList[moveNum], cps);
4602 AlphaRank(moveList[moveNum], 4); // and back
4604 /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
4605 * the engine. It would be nice to have a better way to identify castle
4607 if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
4608 && cps->useOOCastle) {
4609 int fromX = moveList[moveNum][0] - AAA;
4610 int fromY = moveList[moveNum][1] - ONE;
4611 int toX = moveList[moveNum][2] - AAA;
4612 int toY = moveList[moveNum][3] - ONE;
4613 if((boards[moveNum][fromY][fromX] == WhiteKing
4614 && boards[moveNum][toY][toX] == WhiteRook)
4615 || (boards[moveNum][fromY][fromX] == BlackKing
4616 && boards[moveNum][toY][toX] == BlackRook)) {
4617 if(toX > fromX) SendToProgram("O-O\n", cps);
4618 else SendToProgram("O-O-O\n", cps);
4620 else SendToProgram(moveList[moveNum], cps);
4622 else SendToProgram(moveList[moveNum], cps);
4623 /* End of additions by Tord */
4626 /* [HGM] setting up the opening has brought engine in force mode! */
4627 /* Send 'go' if we are in a mode where machine should play. */
4628 if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4629 (gameMode == TwoMachinesPlay ||
4631 gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite ||
4633 gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4634 SendToProgram("go\n", cps);
4635 if (appData.debugMode) {
4636 fprintf(debugFP, "(extra)\n");
4639 setboardSpoiledMachineBlack = 0;
4643 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar)
4645 int fromX, fromY, toX, toY;
4648 char user_move[MSG_SIZ];
4652 snprintf(user_move, MSG_SIZ, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4653 (int)moveType, fromX, fromY, toX, toY);
4654 DisplayError(user_move + strlen("say "), 0);
4656 case WhiteKingSideCastle:
4657 case BlackKingSideCastle:
4658 case WhiteQueenSideCastleWild:
4659 case BlackQueenSideCastleWild:
4661 case WhiteHSideCastleFR:
4662 case BlackHSideCastleFR:
4664 snprintf(user_move, MSG_SIZ, "o-o\n");
4666 case WhiteQueenSideCastle:
4667 case BlackQueenSideCastle:
4668 case WhiteKingSideCastleWild:
4669 case BlackKingSideCastleWild:
4671 case WhiteASideCastleFR:
4672 case BlackASideCastleFR:
4674 snprintf(user_move, MSG_SIZ, "o-o-o\n");
4676 case WhiteNonPromotion:
4677 case BlackNonPromotion:
4678 sprintf(user_move, "%c%c%c%c==\n", AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4680 case WhitePromotion:
4681 case BlackPromotion:
4682 if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
4683 snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
4684 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4685 PieceToChar(WhiteFerz));
4686 else if(gameInfo.variant == VariantGreat)
4687 snprintf(user_move, MSG_SIZ,"%c%c%c%c=%c\n",
4688 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4689 PieceToChar(WhiteMan));
4691 snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
4692 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4698 snprintf(user_move, MSG_SIZ, "%c@%c%c\n",
4699 ToUpper(PieceToChar((ChessSquare) fromX)),
4700 AAA + toX, ONE + toY);
4702 case IllegalMove: /* could be a variant we don't quite understand */
4703 if(fromY == DROP_RANK) goto drop; // We need 'IllegalDrop' move type?
4705 case WhiteCapturesEnPassant:
4706 case BlackCapturesEnPassant:
4707 snprintf(user_move, MSG_SIZ,"%c%c%c%c\n",
4708 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4711 SendToICS(user_move);
4712 if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
4713 ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
4718 { // [HGM] upload: send entire stored game to ICS as long-algebraic moves.
4719 int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
4720 static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
4721 if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
4722 DisplayError("You cannot do this while you are playing or observing", 0);
4725 if(gameMode != IcsExamining) { // is this ever not the case?
4726 char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
4728 if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
4729 snprintf(command,MSG_SIZ, "match %s", ics_handle);
4730 } else { // on FICS we must first go to general examine mode
4731 safeStrCpy(command, "examine\nbsetup", sizeof(command)/sizeof(command[0])); // and specify variant within it with bsetups
4733 if(gameInfo.variant != VariantNormal) {
4734 // try figure out wild number, as xboard names are not always valid on ICS
4735 for(i=1; i<=36; i++) {
4736 snprintf(buf, MSG_SIZ, "wild/%d", i);
4737 if(StringToVariant(buf) == gameInfo.variant) break;
4739 if(i<=36 && ics_type == ICS_ICC) snprintf(buf, MSG_SIZ,"%s w%d\n", command, i);
4740 else if(i == 22) snprintf(buf,MSG_SIZ, "%s fr\n", command);
4741 else snprintf(buf, MSG_SIZ,"%s %s\n", command, VariantName(gameInfo.variant));
4742 } else snprintf(buf, MSG_SIZ,"%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
4743 SendToICS(ics_prefix);
4745 if(startedFromSetupPosition || backwardMostMove != 0) {
4746 fen = PositionToFEN(backwardMostMove, NULL);
4747 if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
4748 snprintf(buf, MSG_SIZ,"loadfen %s\n", fen);
4750 } else { // FICS: everything has to set by separate bsetup commands
4751 p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board
4752 snprintf(buf, MSG_SIZ,"bsetup fen %s\n", fen);
4754 if(!WhiteOnMove(backwardMostMove)) {
4755 SendToICS("bsetup tomove black\n");
4757 i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL);
4758 snprintf(buf, MSG_SIZ,"bsetup wcastle %s\n", castlingStrings[i]);
4760 i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
4761 snprintf(buf, MSG_SIZ, "bsetup bcastle %s\n", castlingStrings[i]);
4763 i = boards[backwardMostMove][EP_STATUS];
4764 if(i >= 0) { // set e.p.
4765 snprintf(buf, MSG_SIZ,"bsetup eppos %c\n", i+AAA);
4771 if(bsetup || ics_type != ICS_ICC && gameInfo.variant != VariantNormal)
4772 SendToICS("bsetup done\n"); // switch to normal examining.
4774 for(i = backwardMostMove; i<last; i++) {
4776 snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s\n", parseList[i]);
4779 SendToICS(ics_prefix);
4780 SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
4784 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
4789 if (rf == DROP_RANK) {
4790 sprintf(move, "%c@%c%c\n",
4791 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
4793 if (promoChar == 'x' || promoChar == NULLCHAR) {
4794 sprintf(move, "%c%c%c%c\n",
4795 AAA + ff, ONE + rf, AAA + ft, ONE + rt);
4797 sprintf(move, "%c%c%c%c%c\n",
4798 AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
4804 ProcessICSInitScript(f)
4809 while (fgets(buf, MSG_SIZ, f)) {
4810 SendToICSDelayed(buf,(long)appData.msLoginDelay);
4817 static int lastX, lastY, selectFlag, dragging;
4822 ChessSquare king = WhiteKing, pawn = WhitePawn, last = promoSweep;
4823 if(gameInfo.variant == VariantKnightmate) king = WhiteUnicorn;
4824 if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway) king = EmptySquare;
4825 if(promoSweep >= BlackPawn) king = WHITE_TO_BLACK king, pawn = WHITE_TO_BLACK pawn;
4826 if(gameInfo.variant == VariantSpartan && pawn == BlackPawn) pawn = BlackLance, king = EmptySquare;
4827 if(fromY != BOARD_HEIGHT-2 && fromY != 1) pawn = EmptySquare;
4830 if(promoSweep == EmptySquare) promoSweep = BlackPawn; // wrap
4831 else if((int)promoSweep == -1) promoSweep = WhiteKing;
4832 else if(promoSweep == BlackPawn && step < 0) promoSweep = WhitePawn;
4833 else if(promoSweep == WhiteKing && step > 0) promoSweep = BlackKing;
4835 } while(PieceToChar(promoSweep) == '.' || PieceToChar(promoSweep) == '~' || promoSweep == pawn ||
4836 appData.testLegality && (promoSweep == king ||
4837 gameInfo.variant == VariantShogi && promoSweep != PROMOTED last && last != PROMOTED promoSweep && last != promoSweep));
4838 ChangeDragPiece(promoSweep);
4841 int PromoScroll(int x, int y)
4845 if(promoSweep == EmptySquare || !appData.sweepSelect) return FALSE;
4846 if(abs(x - lastX) < 15 && abs(y - lastY) < 15) return FALSE;
4847 if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
4848 if(!step) return FALSE;
4849 lastX = x; lastY = y;
4850 if((promoSweep < BlackPawn) == flipView) step = -step;
4851 if(step > 0) selectFlag = 1;
4852 if(!selectFlag) Sweep(step);
4859 ChessSquare piece = boards[currentMove][toY][toX];
4862 if(pieceSweep == EmptySquare) pieceSweep = WhitePawn; // wrap
4863 if((int)pieceSweep == -1) pieceSweep = BlackKing;
4864 if(!step) step = -1;
4865 } while(PieceToChar(pieceSweep) == '.');
4866 boards[currentMove][toY][toX] = pieceSweep;
4867 DrawPosition(FALSE, boards[currentMove]);
4868 boards[currentMove][toY][toX] = piece;
4870 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
4872 AlphaRank(char *move, int n)
4874 // char *p = move, c; int x, y;
4876 if (appData.debugMode) {
4877 fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
4881 move[2]>='0' && move[2]<='9' &&
4882 move[3]>='a' && move[3]<='x' ) {
4884 move[2] = BOARD_RGHT -1 - (move[2]-'1') + AAA;
4885 move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4887 if(move[0]>='0' && move[0]<='9' &&
4888 move[1]>='a' && move[1]<='x' &&
4889 move[2]>='0' && move[2]<='9' &&
4890 move[3]>='a' && move[3]<='x' ) {
4891 /* input move, Shogi -> normal */
4892 move[0] = BOARD_RGHT -1 - (move[0]-'1') + AAA;
4893 move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
4894 move[2] = BOARD_RGHT -1 - (move[2]-'1') + AAA;
4895 move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4898 move[3]>='0' && move[3]<='9' &&
4899 move[2]>='a' && move[2]<='x' ) {
4901 move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4902 move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4905 move[0]>='a' && move[0]<='x' &&
4906 move[3]>='0' && move[3]<='9' &&
4907 move[2]>='a' && move[2]<='x' ) {
4908 /* output move, normal -> Shogi */
4909 move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
4910 move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
4911 move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4912 move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4913 if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
4915 if (appData.debugMode) {
4916 fprintf(debugFP, " out = '%s'\n", move);
4920 char yy_textstr[8000];
4922 /* Parser for moves from gnuchess, ICS, or user typein box */
4924 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
4927 ChessMove *moveType;
4928 int *fromX, *fromY, *toX, *toY;
4931 *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
4933 switch (*moveType) {
4934 case WhitePromotion:
4935 case BlackPromotion:
4936 case WhiteNonPromotion:
4937 case BlackNonPromotion:
4939 case WhiteCapturesEnPassant:
4940 case BlackCapturesEnPassant:
4941 case WhiteKingSideCastle:
4942 case WhiteQueenSideCastle:
4943 case BlackKingSideCastle:
4944 case BlackQueenSideCastle:
4945 case WhiteKingSideCastleWild:
4946 case WhiteQueenSideCastleWild:
4947 case BlackKingSideCastleWild:
4948 case BlackQueenSideCastleWild:
4949 /* Code added by Tord: */
4950 case WhiteHSideCastleFR:
4951 case WhiteASideCastleFR:
4952 case BlackHSideCastleFR:
4953 case BlackASideCastleFR:
4954 /* End of code added by Tord */
4955 case IllegalMove: /* bug or odd chess variant */
4956 *fromX = currentMoveString[0] - AAA;
4957 *fromY = currentMoveString[1] - ONE;
4958 *toX = currentMoveString[2] - AAA;
4959 *toY = currentMoveString[3] - ONE;
4960 *promoChar = currentMoveString[4];
4961 if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
4962 *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
4963 if (appData.debugMode) {
4964 fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
4966 *fromX = *fromY = *toX = *toY = 0;
4969 if (appData.testLegality) {
4970 return (*moveType != IllegalMove);
4972 return !(*fromX == *toX && *fromY == *toY) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
4973 WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
4978 *fromX = *moveType == WhiteDrop ?
4979 (int) CharToPiece(ToUpper(currentMoveString[0])) :
4980 (int) CharToPiece(ToLower(currentMoveString[0]));
4982 *toX = currentMoveString[2] - AAA;
4983 *toY = currentMoveString[3] - ONE;
4984 *promoChar = NULLCHAR;
4988 case ImpossibleMove:
4998 if (appData.debugMode) {
4999 fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
5002 *fromX = *fromY = *toX = *toY = 0;
5003 *promoChar = NULLCHAR;
5010 ParsePV(char *pv, Boolean storeComments)
5011 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
5012 int fromX, fromY, toX, toY; char promoChar;
5017 endPV = forwardMostMove;
5019 while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
5020 if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
5021 valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
5022 if(appData.debugMode){
5023 fprintf(debugFP,"parsePV: %d %c%c%c%c yy='%s'\nPV = '%s'\n", valid, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, yy_textstr, pv);
5025 if(!valid && nr == 0 &&
5026 ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
5027 nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
5028 // Hande case where played move is different from leading PV move
5029 CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
5030 CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
5031 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
5032 if(!CompareBoards(boards[endPV], boards[endPV+2])) {
5033 endPV += 2; // if position different, keep this
5034 moveList[endPV-1][0] = fromX + AAA;
5035 moveList[endPV-1][1] = fromY + ONE;
5036 moveList[endPV-1][2] = toX + AAA;
5037 moveList[endPV-1][3] = toY + ONE;
5038 parseList[endPV-1][0] = NULLCHAR;
5039 safeStrCpy(moveList[endPV-2], "_0_0", sizeof(moveList[endPV-2])/sizeof(moveList[endPV-2][0])); // suppress premove highlight on takeback move
5042 pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
5043 if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
5044 if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
5045 if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
5046 valid++; // allow comments in PV
5050 if(endPV+1 > framePtr) break; // no space, truncate
5053 CopyBoard(boards[endPV], boards[endPV-1]);
5054 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
5055 moveList[endPV-1][0] = fromX + AAA;
5056 moveList[endPV-1][1] = fromY + ONE;
5057 moveList[endPV-1][2] = toX + AAA;
5058 moveList[endPV-1][3] = toY + ONE;
5059 moveList[endPV-1][4] = promoChar;
5060 moveList[endPV-1][5] = NULLCHAR;
5061 strncat(moveList[endPV-1], "\n", MOVE_LEN);
5063 CoordsToAlgebraic(boards[endPV - 1],
5064 PosFlags(endPV - 1),
5065 fromY, fromX, toY, toX, promoChar,
5066 parseList[endPV - 1]);
5068 parseList[endPV-1][0] = NULLCHAR;
5070 currentMove = endPV;
5071 if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5072 SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5073 moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5074 DrawPosition(TRUE, boards[currentMove]);
5078 LoadMultiPV(int x, int y, char *buf, int index, int *start, int *end)
5083 if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
5084 lastX = x; lastY = y;
5085 while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
5087 while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
5088 if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
5090 do{ while(buf[index] && buf[index] != '\n') index++;
5091 } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
5093 ParsePV(buf+startPV, FALSE);
5094 *start = startPV; *end = index-1;
5099 LoadPV(int x, int y)
5100 { // called on right mouse click to load PV
5101 int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
5102 lastX = x; lastY = y;
5103 ParsePV(lastPV[which], FALSE); // load the PV of the thinking engine in the boards array.
5110 if(endPV < 0) return;
5112 currentMove = forwardMostMove;
5113 ClearPremoveHighlights();
5114 DrawPosition(TRUE, boards[currentMove]);
5118 MovePV(int x, int y, int h)
5119 { // step through PV based on mouse coordinates (called on mouse move)
5120 int margin = h>>3, step = 0;
5122 // we must somehow check if right button is still down (might be released off board!)
5123 if(endPV < 0 && pieceSweep == EmptySquare) return; // needed in XBoard because lastX/Y is shared :-(
5124 if(abs(x - lastX) < 7 && abs(y - lastY) < 7) return;
5125 if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5127 lastX = x; lastY = y;
5129 if(pieceSweep != EmptySquare) { NextPiece(step); return; }
5130 if(endPV < 0) return;
5131 if(y < margin) step = 1; else
5132 if(y > h - margin) step = -1;
5133 if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
5134 currentMove += step;
5135 if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5136 SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5137 moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5138 DrawPosition(FALSE, boards[currentMove]);
5142 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
5143 // All positions will have equal probability, but the current method will not provide a unique
5144 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
5150 int piecesLeft[(int)BlackPawn];
5151 int seed, nrOfShuffles;
5153 void GetPositionNumber()
5154 { // sets global variable seed
5157 seed = appData.defaultFrcPosition;
5158 if(seed < 0) { // randomize based on time for negative FRC position numbers
5159 for(i=0; i<50; i++) seed += random();
5160 seed = random() ^ random() >> 8 ^ random() << 8;
5161 if(seed<0) seed = -seed;
5165 int put(Board board, int pieceType, int rank, int n, int shade)
5166 // put the piece on the (n-1)-th empty squares of the given shade
5170 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5171 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5172 board[rank][i] = (ChessSquare) pieceType;
5173 squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5175 piecesLeft[pieceType]--;
5183 void AddOnePiece(Board board, int pieceType, int rank, int shade)
5184 // calculate where the next piece goes, (any empty square), and put it there
5188 i = seed % squaresLeft[shade];
5189 nrOfShuffles *= squaresLeft[shade];
5190 seed /= squaresLeft[shade];
5191 put(board, pieceType, rank, i, shade);
5194 void AddTwoPieces(Board board, int pieceType, int rank)
5195 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5197 int i, n=squaresLeft[ANY], j=n-1, k;
5199 k = n*(n-1)/2; // nr of possibilities, not counting permutations
5200 i = seed % k; // pick one
5203 while(i >= j) i -= j--;
5204 j = n - 1 - j; i += j;
5205 put(board, pieceType, rank, j, ANY);
5206 put(board, pieceType, rank, i, ANY);
5209 void SetUpShuffle(Board board, int number)
5213 GetPositionNumber(); nrOfShuffles = 1;
5215 squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5216 squaresLeft[ANY] = BOARD_RGHT - BOARD_LEFT;
5217 squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5219 for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5221 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5222 p = (int) board[0][i];
5223 if(p < (int) BlackPawn) piecesLeft[p] ++;
5224 board[0][i] = EmptySquare;
5227 if(PosFlags(0) & F_ALL_CASTLE_OK) {
5228 // shuffles restricted to allow normal castling put KRR first
5229 if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5230 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5231 else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5232 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5233 if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5234 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5235 if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5236 put(board, WhiteRook, 0, 0, ANY);
5237 // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5240 if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5241 // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5242 for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5243 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5244 while(piecesLeft[p] >= 2) {
5245 AddOnePiece(board, p, 0, LITE);
5246 AddOnePiece(board, p, 0, DARK);
5248 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5251 for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5252 // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5253 // but we leave King and Rooks for last, to possibly obey FRC restriction
5254 if(p == (int)WhiteRook) continue;
5255 while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5256 if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY); // add the odd piece
5259 // now everything is placed, except perhaps King (Unicorn) and Rooks
5261 if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5262 // Last King gets castling rights
5263 while(piecesLeft[(int)WhiteUnicorn]) {
5264 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5265 initialRights[2] = initialRights[5] = board[CASTLING][2] = board[CASTLING][5] = i;
5268 while(piecesLeft[(int)WhiteKing]) {
5269 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5270 initialRights[2] = initialRights[5] = board[CASTLING][2] = board[CASTLING][5] = i;
5275 while(piecesLeft[(int)WhiteKing]) AddOnePiece(board, WhiteKing, 0, ANY);
5276 while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
5279 // Only Rooks can be left; simply place them all
5280 while(piecesLeft[(int)WhiteRook]) {
5281 i = put(board, WhiteRook, 0, 0, ANY);
5282 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
5285 initialRights[1] = initialRights[4] = board[CASTLING][1] = board[CASTLING][4] = i;
5287 initialRights[0] = initialRights[3] = board[CASTLING][0] = board[CASTLING][3] = i;
5290 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
5291 board[BOARD_HEIGHT-1][i] = (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
5294 if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
5297 int SetCharTable( char *table, const char * map )
5298 /* [HGM] moved here from winboard.c because of its general usefulness */
5299 /* Basically a safe strcpy that uses the last character as King */
5301 int result = FALSE; int NrPieces;
5303 if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
5304 && NrPieces >= 12 && !(NrPieces&1)) {
5305 int i; /* [HGM] Accept even length from 12 to 34 */
5307 for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
5308 for( i=0; i<NrPieces/2-1; i++ ) {
5310 table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
5312 table[(int) WhiteKing] = map[NrPieces/2-1];
5313 table[(int) BlackKing] = map[NrPieces-1];
5321 void Prelude(Board board)
5322 { // [HGM] superchess: random selection of exo-pieces
5323 int i, j, k; ChessSquare p;
5324 static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
5326 GetPositionNumber(); // use FRC position number
5328 if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
5329 SetCharTable(pieceToChar, appData.pieceToCharTable);
5330 for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
5331 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
5334 j = seed%4; seed /= 4;
5335 p = board[0][BOARD_LEFT+j]; board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5336 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
5337 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
5338 j = seed%3 + (seed%3 >= j); seed /= 3;
5339 p = board[0][BOARD_LEFT+j]; board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5340 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
5341 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
5342 j = seed%3; seed /= 3;
5343 p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5344 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
5345 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
5346 j = seed%2 + (seed%2 >= j); seed /= 2;
5347 p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5348 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
5349 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
5350 j = seed%4; seed /= 4; put(board, exoPieces[3], 0, j, ANY);
5351 j = seed%3; seed /= 3; put(board, exoPieces[2], 0, j, ANY);
5352 j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
5353 put(board, exoPieces[0], 0, 0, ANY);
5354 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
5358 InitPosition(redraw)
5361 ChessSquare (* pieces)[BOARD_FILES];
5362 int i, j, pawnRow, overrule,
5363 oldx = gameInfo.boardWidth,
5364 oldy = gameInfo.boardHeight,
5365 oldh = gameInfo.holdingsWidth;
5368 if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
5370 /* [AS] Initialize pv info list [HGM] and game status */
5372 for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
5373 pvInfoList[i].depth = 0;
5374 boards[i][EP_STATUS] = EP_NONE;
5375 for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
5378 initialRulePlies = 0; /* 50-move counter start */
5380 castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
5381 castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
5385 /* [HGM] logic here is completely changed. In stead of full positions */
5386 /* the initialized data only consist of the two backranks. The switch */
5387 /* selects which one we will use, which is than copied to the Board */
5388 /* initialPosition, which for the rest is initialized by Pawns and */
5389 /* empty squares. This initial position is then copied to boards[0], */
5390 /* possibly after shuffling, so that it remains available. */
5392 gameInfo.holdingsWidth = 0; /* default board sizes */
5393 gameInfo.boardWidth = 8;
5394 gameInfo.boardHeight = 8;
5395 gameInfo.holdingsSize = 0;
5396 nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
5397 for(i=0; i<BOARD_FILES-2; i++)
5398 initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
5399 initialPosition[EP_STATUS] = EP_NONE;
5400 SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
5401 if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
5402 SetCharTable(pieceNickName, appData.pieceNickNames);
5403 else SetCharTable(pieceNickName, "............");
5406 switch (gameInfo.variant) {
5407 case VariantFischeRandom:
5408 shuffleOpenings = TRUE;
5411 case VariantShatranj:
5412 pieces = ShatranjArray;
5413 nrCastlingRights = 0;
5414 SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
5417 pieces = makrukArray;
5418 nrCastlingRights = 0;
5419 startedFromSetupPosition = TRUE;
5420 SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
5422 case VariantTwoKings:
5423 pieces = twoKingsArray;
5425 case VariantCapaRandom:
5426 shuffleOpenings = TRUE;
5427 case VariantCapablanca:
5428 pieces = CapablancaArray;
5429 gameInfo.boardWidth = 10;
5430 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5433 pieces = GothicArray;
5434 gameInfo.boardWidth = 10;
5435 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5438 SetCharTable(pieceToChar, "PNBRQ..HEKpnbrq..hek");
5439 gameInfo.holdingsSize = 7;
5442 pieces = JanusArray;
5443 gameInfo.boardWidth = 10;
5444 SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
5445 nrCastlingRights = 6;
5446 initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5447 initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5448 initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
5449 initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5450 initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5451 initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
5454 pieces = FalconArray;
5455 gameInfo.boardWidth = 10;
5456 SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
5458 case VariantXiangqi:
5459 pieces = XiangqiArray;
5460 gameInfo.boardWidth = 9;
5461 gameInfo.boardHeight = 10;
5462 nrCastlingRights = 0;
5463 SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
5466 pieces = ShogiArray;
5467 gameInfo.boardWidth = 9;
5468 gameInfo.boardHeight = 9;
5469 gameInfo.holdingsSize = 7;
5470 nrCastlingRights = 0;
5471 SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
5473 case VariantCourier:
5474 pieces = CourierArray;
5475 gameInfo.boardWidth = 12;
5476 nrCastlingRights = 0;
5477 SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
5479 case VariantKnightmate:
5480 pieces = KnightmateArray;
5481 SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
5483 case VariantSpartan:
5484 pieces = SpartanArray;
5485 SetCharTable(pieceToChar, "PNBRQ................K......lwg.....c...h..k");
5488 pieces = fairyArray;
5489 SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
5492 pieces = GreatArray;
5493 gameInfo.boardWidth = 10;
5494 SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
5495 gameInfo.holdingsSize = 8;
5499 SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
5500 gameInfo.holdingsSize = 8;
5501 startedFromSetupPosition = TRUE;
5503 case VariantCrazyhouse:
5504 case VariantBughouse:
5506 SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
5507 gameInfo.holdingsSize = 5;
5509 case VariantWildCastle:
5511 /* !!?shuffle with kings guaranteed to be on d or e file */
5512 shuffleOpenings = 1;
5514 case VariantNoCastle:
5516 nrCastlingRights = 0;
5517 /* !!?unconstrained back-rank shuffle */
5518 shuffleOpenings = 1;
5523 if(appData.NrFiles >= 0) {
5524 if(gameInfo.boardWidth != appData.NrFiles) overrule++;
5525 gameInfo.boardWidth = appData.NrFiles;
5527 if(appData.NrRanks >= 0) {
5528 gameInfo.boardHeight = appData.NrRanks;
5530 if(appData.holdingsSize >= 0) {
5531 i = appData.holdingsSize;
5532 if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
5533 gameInfo.holdingsSize = i;
5535 if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
5536 if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
5537 DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
5539 pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
5540 if(pawnRow < 1) pawnRow = 1;
5541 if(gameInfo.variant == VariantMakruk) pawnRow = 2;
5543 /* User pieceToChar list overrules defaults */
5544 if(appData.pieceToCharTable != NULL)
5545 SetCharTable(pieceToChar, appData.pieceToCharTable);
5547 for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
5549 if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
5550 s = (ChessSquare) 0; /* account holding counts in guard band */
5551 for( i=0; i<BOARD_HEIGHT; i++ )
5552 initialPosition[i][j] = s;
5554 if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
5555 initialPosition[0][j] = pieces[0][j-gameInfo.holdingsWidth];
5556 initialPosition[pawnRow][j] = WhitePawn;
5557 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = gameInfo.variant == VariantSpartan ? BlackLance : BlackPawn;
5558 if(gameInfo.variant == VariantXiangqi) {
5560 initialPosition[pawnRow][j] =
5561 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
5562 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
5563 initialPosition[2][j] = WhiteCannon;
5564 initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
5568 initialPosition[BOARD_HEIGHT-1][j] = pieces[1][j-gameInfo.holdingsWidth];
5570 if( (gameInfo.variant == VariantShogi) && !overrule ) {
5573 initialPosition[1][j] = WhiteBishop;
5574 initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
5576 initialPosition[1][j] = WhiteRook;
5577 initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
5580 if( nrCastlingRights == -1) {
5581 /* [HGM] Build normal castling rights (must be done after board sizing!) */
5582 /* This sets default castling rights from none to normal corners */
5583 /* Variants with other castling rights must set them themselves above */
5584 nrCastlingRights = 6;
5586 initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5587 initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5588 initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
5589 initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5590 initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5591 initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
5594 if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
5595 if(gameInfo.variant == VariantGreat) { // promotion commoners
5596 initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
5597 initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
5598 initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
5599 initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
5601 if( gameInfo.variant == VariantSChess ) {
5602 initialPosition[1][0] = BlackMarshall;
5603 initialPosition[2][0] = BlackAngel;
5604 initialPosition[6][BOARD_WIDTH-1] = WhiteMarshall;
5605 initialPosition[5][BOARD_WIDTH-1] = WhiteAngel;
5606 initialPosition[1][1] = initialPosition[2][1] =
5607 initialPosition[6][BOARD_WIDTH-2] = initialPosition[5][BOARD_WIDTH-2] = 1;
5609 if (appData.debugMode) {
5610 fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
5612 if(shuffleOpenings) {
5613 SetUpShuffle(initialPosition, appData.defaultFrcPosition);
5614 startedFromSetupPosition = TRUE;
5616 if(startedFromPositionFile) {
5617 /* [HGM] loadPos: use PositionFile for every new game */
5618 CopyBoard(initialPosition, filePosition);
5619 for(i=0; i<nrCastlingRights; i++)
5620 initialRights[i] = filePosition[CASTLING][i];
5621 startedFromSetupPosition = TRUE;
5624 CopyBoard(boards[0], initialPosition);
5626 if(oldx != gameInfo.boardWidth ||
5627 oldy != gameInfo.boardHeight ||
5628 oldv != gameInfo.variant ||
5629 oldh != gameInfo.holdingsWidth
5631 InitDrawingSizes(-2 ,0);
5633 oldv = gameInfo.variant;
5635 DrawPosition(TRUE, boards[currentMove]);
5639 SendBoard(cps, moveNum)
5640 ChessProgramState *cps;
5643 char message[MSG_SIZ];
5645 if (cps->useSetboard) {
5646 char* fen = PositionToFEN(moveNum, cps->fenOverride);
5647 snprintf(message, MSG_SIZ,"setboard %s\n", fen);
5648 SendToProgram(message, cps);
5654 /* Kludge to set black to move, avoiding the troublesome and now
5655 * deprecated "black" command.
5657 if (!WhiteOnMove(moveNum)) // [HGM] but better a deprecated command than an illegal move...
5658 SendToProgram(boards[0][1][BOARD_LEFT] == WhitePawn ? "a2a3\n" : "black\n", cps);
5660 SendToProgram("edit\n", cps);
5661 SendToProgram("#\n", cps);
5662 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
5663 bp = &boards[moveNum][i][BOARD_LEFT];
5664 for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
5665 if ((int) *bp < (int) BlackPawn) {
5666 snprintf(message, MSG_SIZ, "%c%c%c\n", PieceToChar(*bp),
5668 if(message[0] == '+' || message[0] == '~') {
5669 snprintf(message, MSG_SIZ,"%c%c%c+\n",
5670 PieceToChar((ChessSquare)(DEMOTED *bp)),
5673 if(cps->alphaRank) { /* [HGM] shogi: translate coords */
5674 message[1] = BOARD_RGHT - 1 - j + '1';
5675 message[2] = BOARD_HEIGHT - 1 - i + 'a';
5677 SendToProgram(message, cps);
5682 SendToProgram("c\n", cps);
5683 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
5684 bp = &boards[moveNum][i][BOARD_LEFT];
5685 for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
5686 if (((int) *bp != (int) EmptySquare)
5687 && ((int) *bp >= (int) BlackPawn)) {
5688 snprintf(message,MSG_SIZ, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
5690 if(message[0] == '+' || message[0] == '~') {
5691 snprintf(message, MSG_SIZ,"%c%c%c+\n",
5692 PieceToChar((ChessSquare)(DEMOTED *bp)),
5695 if(cps->alphaRank) { /* [HGM] shogi: translate coords */
5696 message[1] = BOARD_RGHT - 1 - j + '1';
5697 message[2] = BOARD_HEIGHT - 1 - i + 'a';
5699 SendToProgram(message, cps);
5704 SendToProgram(".\n", cps);
5706 setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
5710 DefaultPromoChoice(int white)
5713 if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
5714 result = WhiteFerz; // no choice
5715 else if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway)
5716 result= WhiteKing; // in Suicide Q is the last thing we want
5717 else if(gameInfo.variant == VariantSpartan)
5718 result = white ? WhiteQueen : WhiteAngel;
5719 else result = WhiteQueen;
5720 if(!white) result = WHITE_TO_BLACK result;
5724 static int autoQueen; // [HGM] oneclick
5727 HasPromotionChoice(int fromX, int fromY, int toX, int toY, char *promoChoice)
5729 /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
5730 /* [HGM] add Shogi promotions */
5731 int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
5736 if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
5737 if(toX < BOARD_LEFT || toX >= BOARD_RGHT) return FALSE; // move into holdings
5739 if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
5740 !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
5743 piece = boards[currentMove][fromY][fromX];
5744 if(gameInfo.variant == VariantShogi) {
5745 promotionZoneSize = BOARD_HEIGHT/3;
5746 highestPromotingPiece = (int)WhiteFerz;
5747 } else if(gameInfo.variant == VariantMakruk) {
5748 promotionZoneSize = 3;
5751 // Treat Lance as Pawn when it is not representing Amazon
5752 if(gameInfo.variant != VariantSuper) {
5753 if(piece == WhiteLance) piece = WhitePawn; else
5754 if(piece == BlackLance) piece = BlackPawn;
5757 // next weed out all moves that do not touch the promotion zone at all
5758 if((int)piece >= BlackPawn) {
5759 if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
5761 highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
5763 if( toY < BOARD_HEIGHT - promotionZoneSize &&
5764 fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
5767 if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
5769 // weed out mandatory Shogi promotions
5770 if(gameInfo.variant == VariantShogi) {
5771 if(piece >= BlackPawn) {
5772 if(toY == 0 && piece == BlackPawn ||
5773 toY == 0 && piece == BlackQueen ||
5774 toY <= 1 && piece == BlackKnight) {
5779 if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
5780 toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
5781 toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
5788 // weed out obviously illegal Pawn moves
5789 if(appData.testLegality && (piece == WhitePawn || piece == BlackPawn) ) {
5790 if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
5791 if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
5792 if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
5793 if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
5794 // note we are not allowed to test for valid (non-)capture, due to premove
5797 // we either have a choice what to promote to, or (in Shogi) whether to promote
5798 if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk) {
5799 *promoChoice = PieceToChar(BlackFerz); // no choice
5802 // no sense asking what we must promote to if it is going to explode...
5803 if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) {
5804 *promoChoice = PieceToChar(BlackQueen); // Queen as good as any
5807 // give caller the default choice even if we will not make it
5808 *promoChoice = ToLower(PieceToChar(defaultPromoChoice));
5809 if(gameInfo.variant == VariantShogi) *promoChoice = '+';
5810 if(appData.sweepSelect && gameInfo.variant != VariantGreat
5811 && gameInfo.variant != VariantShogi
5812 && gameInfo.variant != VariantSuper) return FALSE;
5813 if(autoQueen) return FALSE; // predetermined
5815 // suppress promotion popup on illegal moves that are not premoves
5816 premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
5817 gameMode == IcsPlayingBlack && WhiteOnMove(currentMove);
5818 if(appData.testLegality && !premove) {
5819 moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5820 fromY, fromX, toY, toX, gameInfo.variant == VariantShogi ? '+' : NULLCHAR);
5821 if(moveType != WhitePromotion && moveType != BlackPromotion)
5829 InPalace(row, column)
5831 { /* [HGM] for Xiangqi */
5832 if( (row < 3 || row > BOARD_HEIGHT-4) &&
5833 column < (BOARD_WIDTH + 4)/2 &&
5834 column > (BOARD_WIDTH - 5)/2 ) return TRUE;
5839 PieceForSquare (x, y)
5843 if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
5846 return boards[currentMove][y][x];
5850 OKToStartUserMove(x, y)
5853 ChessSquare from_piece;
5856 if (matchMode) return FALSE;
5857 if (gameMode == EditPosition) return TRUE;
5859 if (x >= 0 && y >= 0)
5860 from_piece = boards[currentMove][y][x];
5862 from_piece = EmptySquare;
5864 if (from_piece == EmptySquare) return FALSE;
5866 white_piece = (int)from_piece >= (int)WhitePawn &&
5867 (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
5870 case PlayFromGameFile:
5872 case TwoMachinesPlay:
5880 case MachinePlaysWhite:
5881 case IcsPlayingBlack:
5882 if (appData.zippyPlay) return FALSE;
5884 DisplayMoveError(_("You are playing Black"));
5889 case MachinePlaysBlack:
5890 case IcsPlayingWhite:
5891 if (appData.zippyPlay) return FALSE;
5893 DisplayMoveError(_("You are playing White"));
5899 if (!white_piece && WhiteOnMove(currentMove)) {
5900 DisplayMoveError(_("It is White's turn"));
5903 if (white_piece && !WhiteOnMove(currentMove)) {
5904 DisplayMoveError(_("It is Black's turn"));
5907 if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
5908 /* Editing correspondence game history */
5909 /* Could disallow this or prompt for confirmation */
5914 case BeginningOfGame:
5915 if (appData.icsActive) return FALSE;
5916 if (!appData.noChessProgram) {
5918 DisplayMoveError(_("You are playing White"));
5925 if (!white_piece && WhiteOnMove(currentMove)) {
5926 DisplayMoveError(_("It is White's turn"));
5929 if (white_piece && !WhiteOnMove(currentMove)) {
5930 DisplayMoveError(_("It is Black's turn"));
5939 if (currentMove != forwardMostMove && gameMode != AnalyzeMode
5940 && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
5941 && gameMode != AnalyzeFile && gameMode != Training) {
5942 DisplayMoveError(_("Displayed position is not current"));
5949 OnlyMove(int *x, int *y, Boolean captures) {
5950 DisambiguateClosure cl;
5951 if (appData.zippyPlay) return FALSE;
5953 case MachinePlaysBlack:
5954 case IcsPlayingWhite:
5955 case BeginningOfGame:
5956 if(!WhiteOnMove(currentMove)) return FALSE;
5958 case MachinePlaysWhite:
5959 case IcsPlayingBlack:
5960 if(WhiteOnMove(currentMove)) return FALSE;
5967 cl.pieceIn = EmptySquare;
5972 cl.promoCharIn = NULLCHAR;
5973 Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
5974 if( cl.kind == NormalMove ||
5975 cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
5976 cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
5977 cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
5984 if(cl.kind != ImpossibleMove) return FALSE;
5985 cl.pieceIn = EmptySquare;
5990 cl.promoCharIn = NULLCHAR;
5991 Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
5992 if( cl.kind == NormalMove ||
5993 cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
5994 cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
5995 cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6000 autoQueen = TRUE; // act as if autoQueen on when we click to-square
6006 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
6007 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
6008 int lastLoadGameUseList = FALSE;
6009 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
6010 ChessMove lastLoadGameStart = EndOfFile;
6013 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
6014 int fromX, fromY, toX, toY;
6018 ChessSquare pdown, pup;
6020 /* Check if the user is playing in turn. This is complicated because we
6021 let the user "pick up" a piece before it is his turn. So the piece he
6022 tried to pick up may have been captured by the time he puts it down!
6023 Therefore we use the color the user is supposed to be playing in this
6024 test, not the color of the piece that is currently on the starting
6025 square---except in EditGame mode, where the user is playing both
6026 sides; fortunately there the capture race can't happen. (It can
6027 now happen in IcsExamining mode, but that's just too bad. The user
6028 will get a somewhat confusing message in that case.)
6032 case PlayFromGameFile:
6034 case TwoMachinesPlay:
6038 /* We switched into a game mode where moves are not accepted,
6039 perhaps while the mouse button was down. */
6042 case MachinePlaysWhite:
6043 /* User is moving for Black */
6044 if (WhiteOnMove(currentMove)) {
6045 DisplayMoveError(_("It is White's turn"));
6050 case MachinePlaysBlack:
6051 /* User is moving for White */
6052 if (!WhiteOnMove(currentMove)) {
6053 DisplayMoveError(_("It is Black's turn"));
6060 case BeginningOfGame:
6063 if(fromY == DROP_RANK) break; // [HGM] drop moves (entered through move type-in) are automatically assigned to side-to-move
6064 if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
6065 (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
6066 /* User is moving for Black */
6067 if (WhiteOnMove(currentMove)) {
6068 DisplayMoveError(_("It is White's turn"));
6072 /* User is moving for White */
6073 if (!WhiteOnMove(currentMove)) {
6074 DisplayMoveError(_("It is Black's turn"));
6080 case IcsPlayingBlack:
6081 /* User is moving for Black */
6082 if (WhiteOnMove(currentMove)) {
6083 if (!appData.premove) {
6084 DisplayMoveError(_("It is White's turn"));
6085 } else if (toX >= 0 && toY >= 0) {
6088 premoveFromX = fromX;
6089 premoveFromY = fromY;
6090 premovePromoChar = promoChar;
6092 if (appData.debugMode)
6093 fprintf(debugFP, "Got premove: fromX %d,"
6094 "fromY %d, toX %d, toY %d\n",
6095 fromX, fromY, toX, toY);
6101 case IcsPlayingWhite:
6102 /* User is moving for White */
6103 if (!WhiteOnMove(currentMove)) {
6104 if (!appData.premove) {
6105 DisplayMoveError(_("It is Black's turn"));
6106 } else if (toX >= 0 && toY >= 0) {
6109 premoveFromX = fromX;
6110 premoveFromY = fromY;
6111 premovePromoChar = promoChar;
6113 if (appData.debugMode)
6114 fprintf(debugFP, "Got premove: fromX %d,"
6115 "fromY %d, toX %d, toY %d\n",
6116 fromX, fromY, toX, toY);
6126 /* EditPosition, empty square, or different color piece;
6127 click-click move is possible */
6128 if (toX == -2 || toY == -2) {
6129 boards[0][fromY][fromX] = EmptySquare;
6130 DrawPosition(FALSE, boards[currentMove]);
6132 } else if (toX >= 0 && toY >= 0) {
6133 boards[0][toY][toX] = boards[0][fromY][fromX];
6134 if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
6135 if(boards[0][fromY][0] != EmptySquare) {
6136 if(boards[0][fromY][1]) boards[0][fromY][1]--;
6137 if(boards[0][fromY][1] == 0) boards[0][fromY][0] = EmptySquare;
6140 if(fromX == BOARD_RGHT+1) {
6141 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
6142 if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
6143 if(boards[0][fromY][BOARD_WIDTH-2] == 0) boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
6146 boards[0][fromY][fromX] = EmptySquare;
6147 DrawPosition(FALSE, boards[currentMove]);
6153 if(toX < 0 || toY < 0) return;
6154 pdown = boards[currentMove][fromY][fromX];
6155 pup = boards[currentMove][toY][toX];
6157 /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
6158 if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ) {
6159 if( pup != EmptySquare ) return;
6160 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
6161 if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n",
6162 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
6163 // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
6164 if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
6165 fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
6166 while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++;
6170 /* [HGM] always test for legality, to get promotion info */
6171 moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6172 fromY, fromX, toY, toX, promoChar);
6173 /* [HGM] but possibly ignore an IllegalMove result */
6174 if (appData.testLegality) {
6175 if (moveType == IllegalMove || moveType == ImpossibleMove) {
6176 DisplayMoveError(_("Illegal move"));
6181 FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
6184 /* Common tail of UserMoveEvent and DropMenuEvent */
6186 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
6188 int fromX, fromY, toX, toY;
6189 /*char*/int promoChar;
6193 if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) && promoChar != NULLCHAR) {
6194 // [HGM] superchess: suppress promotions to non-available piece
6195 int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
6196 if(WhiteOnMove(currentMove)) {
6197 if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
6199 if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
6203 /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
6204 move type in caller when we know the move is a legal promotion */
6205 if(moveType == NormalMove && promoChar)
6206 moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
6208 /* [HGM] <popupFix> The following if has been moved here from
6209 UserMoveEvent(). Because it seemed to belong here (why not allow
6210 piece drops in training games?), and because it can only be
6211 performed after it is known to what we promote. */
6212 if (gameMode == Training) {
6213 /* compare the move played on the board to the next move in the
6214 * game. If they match, display the move and the opponent's response.
6215 * If they don't match, display an error message.
6219 CopyBoard(testBoard, boards[currentMove]);
6220 ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
6222 if (CompareBoards(testBoard, boards[currentMove+1])) {
6223 ForwardInner(currentMove+1);
6225 /* Autoplay the opponent's response.
6226 * if appData.animate was TRUE when Training mode was entered,
6227 * the response will be animated.
6229 saveAnimate = appData.animate;
6230 appData.animate = animateTraining;
6231 ForwardInner(currentMove+1);
6232 appData.animate = saveAnimate;
6234 /* check for the end of the game */
6235 if (currentMove >= forwardMostMove) {
6236 gameMode = PlayFromGameFile;
6238 SetTrainingModeOff();
6239 DisplayInformation(_("End of game"));
6242 DisplayError(_("Incorrect move"), 0);
6247 /* Ok, now we know that the move is good, so we can kill
6248 the previous line in Analysis Mode */
6249 if ((gameMode == AnalyzeMode || gameMode == EditGame)
6250 && currentMove < forwardMostMove) {
6251 if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
6252 else forwardMostMove = currentMove;
6255 /* If we need the chess program but it's dead, restart it */
6256 ResurrectChessProgram();
6258 /* A user move restarts a paused game*/
6262 thinkOutput[0] = NULLCHAR;
6264 MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
6266 if(Adjudicate(NULL)) { // [HGM] adjudicate: take care of automatic game end
6267 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6271 if (gameMode == BeginningOfGame) {
6272 if (appData.noChessProgram) {
6273 gameMode = EditGame;
6277 gameMode = MachinePlaysBlack;
6280 snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
6282 if (first.sendName) {
6283 snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
6284 SendToProgram(buf, &first);
6291 /* Relay move to ICS or chess engine */
6292 if (appData.icsActive) {
6293 if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
6294 gameMode == IcsExamining) {
6295 if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
6296 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
6298 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6300 // also send plain move, in case ICS does not understand atomic claims
6301 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6305 if (first.sendTime && (gameMode == BeginningOfGame ||
6306 gameMode == MachinePlaysWhite ||
6307 gameMode == MachinePlaysBlack)) {
6308 SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
6310 if (gameMode != EditGame && gameMode != PlayFromGameFile) {
6311 // [HGM] book: if program might be playing, let it use book
6312 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
6313 first.maybeThinking = TRUE;
6314 } else SendMoveToProgram(forwardMostMove-1, &first);
6315 if (currentMove == cmailOldMove + 1) {
6316 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
6320 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6324 if(appData.testLegality)
6325 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
6331 if (WhiteOnMove(currentMove)) {
6332 GameEnds(BlackWins, "Black mates", GE_PLAYER);
6334 GameEnds(WhiteWins, "White mates", GE_PLAYER);
6338 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
6343 case MachinePlaysBlack:
6344 case MachinePlaysWhite:
6345 /* disable certain menu options while machine is thinking */
6346 SetMachineThinkingEnables();
6353 userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
6354 promoDefaultAltered = FALSE; // [HGM] fall back on default choice
6356 if(bookHit) { // [HGM] book: simulate book reply
6357 static char bookMove[MSG_SIZ]; // a bit generous?
6359 programStats.nodes = programStats.depth = programStats.time =
6360 programStats.score = programStats.got_only_move = 0;
6361 sprintf(programStats.movelist, "%s (xbook)", bookHit);
6363 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
6364 strcat(bookMove, bookHit);
6365 HandleMachineMove(bookMove, &first);
6371 Mark(board, flags, kind, rf, ff, rt, ft, closure)
6378 typedef char Markers[BOARD_RANKS][BOARD_FILES];
6379 Markers *m = (Markers *) closure;
6380 if(rf == fromY && ff == fromX)
6381 (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
6382 || kind == WhiteCapturesEnPassant
6383 || kind == BlackCapturesEnPassant);
6384 else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
6388 MarkTargetSquares(int clear)
6391 if(!appData.markers || !appData.highlightDragging ||
6392 !appData.testLegality || gameMode == EditPosition) return;
6394 for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) marker[y][x] = 0;
6397 GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker);
6398 if(PosFlags(0) & F_MANDATORY_CAPTURE) {
6399 for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
6401 for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
6404 DrawPosition(TRUE, NULL);
6408 Explode(Board board, int fromX, int fromY, int toX, int toY)
6410 if(gameInfo.variant == VariantAtomic &&
6411 (board[toY][toX] != EmptySquare || // capture?
6412 toX != fromX && (board[fromY][fromX] == WhitePawn || // e.p. ?
6413 board[fromY][fromX] == BlackPawn )
6415 AnimateAtomicCapture(board, fromX, fromY, toX, toY);
6421 ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
6423 int CanPromote(ChessSquare piece, int y)
6425 if(gameMode == EditPosition) return FALSE; // no promotions when editing position
6426 // some variants have fixed promotion piece, no promotion at all, or another selection mechanism
6427 if(gameInfo.variant == VariantShogi || gameInfo.variant == VariantXiangqi ||
6428 gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat ||
6429 gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6430 gameInfo.variant == VariantMakruk) return FALSE;
6431 return (piece == BlackPawn && y == 1 ||
6432 piece == WhitePawn && y == BOARD_HEIGHT-2 ||
6433 piece == BlackLance && y == 1 ||
6434 piece == WhiteLance && y == BOARD_HEIGHT-2 );
6437 void LeftClick(ClickType clickType, int xPix, int yPix)
6440 Boolean saveAnimate;
6441 static int second = 0, promotionChoice = 0;
6442 char promoChoice = NULLCHAR;
6445 if(appData.seekGraph && appData.icsActive && loggedOn &&
6446 (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
6447 SeekGraphClick(clickType, xPix, yPix, 0);
6451 if (clickType == Press) ErrorPopDown();
6452 MarkTargetSquares(1);
6454 x = EventToSquare(xPix, BOARD_WIDTH);
6455 y = EventToSquare(yPix, BOARD_HEIGHT);
6456 if (!flipView && y >= 0) {
6457 y = BOARD_HEIGHT - 1 - y;
6459 if (flipView && x >= 0) {
6460 x = BOARD_WIDTH - 1 - x;
6463 if(promoSweep != EmptySquare) { // up-click during sweep-select of promo-piece
6464 defaultPromoChoice = promoSweep;
6465 promoSweep = EmptySquare; // terminate sweep
6466 promoDefaultAltered = TRUE;
6467 if(!selectFlag) x = fromX, y = fromY; // and fake up-click on same square if we were still selecting
6470 if(promotionChoice) { // we are waiting for a click to indicate promotion piece
6471 if(clickType == Release) return; // ignore upclick of click-click destination
6472 promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
6473 if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
6474 if(gameInfo.holdingsWidth &&
6475 (WhiteOnMove(currentMove)
6476 ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y > 0
6477 : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT-1) ) {
6478 // click in right holdings, for determining promotion piece
6479 ChessSquare p = boards[currentMove][y][x];
6480 if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
6481 if(p != EmptySquare) {
6482 FinishMove(NormalMove, fromX, fromY, toX, toY, ToLower(PieceToChar(p)));
6487 DrawPosition(FALSE, boards[currentMove]);
6491 /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
6492 if(clickType == Press
6493 && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
6494 || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
6495 || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
6498 if(clickType == Press && fromX == x && fromY == y && promoDefaultAltered)
6499 fromX = fromY = -1; // second click on piece after altering default promo piece treated as first click
6501 if(!promoDefaultAltered) { // determine default promotion piece, based on the side the user is moving for
6502 int side = (gameMode == IcsPlayingWhite || gameMode == MachinePlaysBlack ||
6503 gameMode != MachinePlaysWhite && gameMode != IcsPlayingBlack && WhiteOnMove(currentMove));
6504 defaultPromoChoice = DefaultPromoChoice(side);
6507 autoQueen = appData.alwaysPromoteToQueen;
6511 gatingPiece = EmptySquare;
6512 if (clickType != Press) {
6513 if(dragging) { // [HGM] from-square must have been reset due to game end since last press
6514 DragPieceEnd(xPix, yPix); dragging = 0;
6515 DrawPosition(FALSE, NULL);
6519 fromX = x; fromY = y;
6520 if(!appData.oneClick || !OnlyMove(&x, &y, FALSE) ||
6521 // even if only move, we treat as normal when this would trigger a promotion popup, to allow sweep selection
6522 appData.sweepSelect && CanPromote(boards[currentMove][fromY][fromX], fromY) && originalY != y) {
6524 if (OKToStartUserMove(fromX, fromY)) {
6526 MarkTargetSquares(0);
6527 DragPieceBegin(xPix, yPix); dragging = 1;
6528 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][fromY][fromX], fromY)) {
6529 promoSweep = defaultPromoChoice;
6530 selectFlag = 0; lastX = xPix; lastY = yPix;
6531 Sweep(0); // Pawn that is going to promote: preview promotion piece
6532 DisplayMessage("", _("Pull pawn backwards to under-promote"));
6534 if (appData.highlightDragging) {
6535 SetHighlights(fromX, fromY, -1, -1);
6543 if (clickType == Press && gameMode != EditPosition) {
6548 // ignore off-board to clicks
6549 if(y < 0 || x < 0) return;
6551 /* Check if clicking again on the same color piece */
6552 fromP = boards[currentMove][fromY][fromX];
6553 toP = boards[currentMove][y][x];
6554 frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess;
6555 if ((WhitePawn <= fromP && fromP <= WhiteKing &&
6556 WhitePawn <= toP && toP <= WhiteKing &&
6557 !(fromP == WhiteKing && toP == WhiteRook && frc) &&
6558 !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
6559 (BlackPawn <= fromP && fromP <= BlackKing &&
6560 BlackPawn <= toP && toP <= BlackKing &&
6561 !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
6562 !(fromP == BlackKing && toP == BlackRook && frc))) {
6563 /* Clicked again on same color piece -- changed his mind */
6564 second = (x == fromX && y == fromY);
6565 promoDefaultAltered = FALSE;
6566 if(!second || appData.oneClick && !OnlyMove(&x, &y, TRUE)) {
6567 if (appData.highlightDragging) {
6568 SetHighlights(x, y, -1, -1);
6572 if (OKToStartUserMove(x, y)) {
6573 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
6574 (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
6575 y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
6576 gatingPiece = boards[currentMove][fromY][fromX];
6577 else gatingPiece = EmptySquare;
6579 fromY = y; dragging = 1;
6580 MarkTargetSquares(0);
6581 DragPieceBegin(xPix, yPix);
6582 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) {
6583 promoSweep = defaultPromoChoice;
6584 selectFlag = 0; lastX = xPix; lastY = yPix;
6585 Sweep(0); // Pawn that is going to promote: preview promotion piece
6589 if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
6592 // ignore clicks on holdings
6593 if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
6596 if (clickType == Release && x == fromX && y == fromY) {
6597 DragPieceEnd(xPix, yPix); dragging = 0;
6598 if (appData.animateDragging) {
6599 /* Undo animation damage if any */
6600 DrawPosition(FALSE, NULL);
6603 /* Second up/down in same square; just abort move */
6606 gatingPiece = EmptySquare;
6609 ClearPremoveHighlights();
6611 /* First upclick in same square; start click-click mode */
6612 SetHighlights(x, y, -1, -1);
6617 /* we now have a different from- and (possibly off-board) to-square */
6618 /* Completed move */
6621 saveAnimate = appData.animate;
6622 if (clickType == Press) {
6623 /* Finish clickclick move */
6624 if (appData.animate || appData.highlightLastMove) {
6625 SetHighlights(fromX, fromY, toX, toY);
6630 /* Finish drag move */
6631 if (appData.highlightLastMove) {
6632 SetHighlights(fromX, fromY, toX, toY);
6636 DragPieceEnd(xPix, yPix); dragging = 0;
6637 /* Don't animate move and drag both */
6638 appData.animate = FALSE;
6641 // moves into holding are invalid for now (except in EditPosition, adapting to-square)
6642 if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
6643 ChessSquare piece = boards[currentMove][fromY][fromX];
6644 if(gameMode == EditPosition && piece != EmptySquare &&
6645 fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
6648 if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
6649 n = PieceToNumber(piece - (int)BlackPawn);
6650 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
6651 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
6652 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
6654 if(x == BOARD_RGHT+1 && piece < BlackPawn) {
6655 n = PieceToNumber(piece);
6656 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
6657 boards[currentMove][n][BOARD_WIDTH-1] = piece;
6658 boards[currentMove][n][BOARD_WIDTH-2]++;
6660 boards[currentMove][fromY][fromX] = EmptySquare;
6664 DrawPosition(TRUE, boards[currentMove]);
6668 // off-board moves should not be highlighted
6669 if(x < 0 || y < 0) ClearHighlights();
6671 if(gatingPiece != EmptySquare) promoChoice = ToLower(PieceToChar(gatingPiece));
6673 if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice)) {
6674 SetHighlights(fromX, fromY, toX, toY);
6675 if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
6676 // [HGM] super: promotion to captured piece selected from holdings
6677 ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
6678 promotionChoice = TRUE;
6679 // kludge follows to temporarily execute move on display, without promoting yet
6680 boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
6681 boards[currentMove][toY][toX] = p;
6682 DrawPosition(FALSE, boards[currentMove]);
6683 boards[currentMove][fromY][fromX] = p; // take back, but display stays
6684 boards[currentMove][toY][toX] = q;
6685 DisplayMessage("Click in holdings to choose piece", "");
6690 int oldMove = currentMove;
6691 UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
6692 if (!appData.highlightLastMove || gotPremove) ClearHighlights();
6693 if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
6694 if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
6695 Explode(boards[currentMove-1], fromX, fromY, toX, toY))
6696 DrawPosition(TRUE, boards[currentMove]);
6699 appData.animate = saveAnimate;
6700 if (appData.animate || appData.animateDragging) {
6701 /* Undo animation damage if needed */
6702 DrawPosition(FALSE, NULL);
6706 int RightClick(ClickType action, int x, int y, int *fromX, int *fromY)
6707 { // front-end-free part taken out of PieceMenuPopup
6708 int whichMenu; int xSqr, ySqr;
6710 if(seekGraphUp) { // [HGM] seekgraph
6711 if(action == Press) SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
6712 if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
6716 if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
6717 && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
6718 if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
6719 if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
6720 if(action == Press) {
6721 originalFlip = flipView;
6722 flipView = !flipView; // temporarily flip board to see game from partners perspective
6723 DrawPosition(TRUE, partnerBoard);
6724 DisplayMessage(partnerStatus, "");
6726 } else if(action == Release) {
6727 flipView = originalFlip;
6728 DrawPosition(TRUE, boards[currentMove]);
6734 xSqr = EventToSquare(x, BOARD_WIDTH);
6735 ySqr = EventToSquare(y, BOARD_HEIGHT);
6736 if (action == Release) {
6737 if(pieceSweep != EmptySquare) {
6738 EditPositionMenuEvent(pieceSweep, toX, toY);
6739 pieceSweep = EmptySquare;
6740 } else UnLoadPV(); // [HGM] pv
6742 if (action != Press) return -2; // return code to be ignored
6745 if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;
\r
6747 if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;
\r
6748 if (xSqr < 0 || ySqr < 0) return -1;
6749 if(appData.pieceMenu) { whichMenu = 0; break; } // edit-position menu
6750 pieceSweep = shiftKey ? BlackPawn : WhitePawn; // [HGM] sweep: prepare selecting piece by mouse sweep
6751 toX = xSqr; toY = ySqr; lastX = x, lastY = y;
6752 if(flipView) toX = BOARD_WIDTH - 1 - toX; else toY = BOARD_HEIGHT - 1 - toY;
6756 if(!appData.icsEngineAnalyze) return -1;
6757 case IcsPlayingWhite:
6758 case IcsPlayingBlack:
6759 if(!appData.zippyPlay) goto noZip;
6762 case MachinePlaysWhite:
6763 case MachinePlaysBlack:
6764 case TwoMachinesPlay: // [HGM] pv: use for showing PV
6765 if (!appData.dropMenu) {
6767 return 2; // flag front-end to grab mouse events
6769 if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
6770 gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
6773 if (xSqr < 0 || ySqr < 0) return -1;
6774 if (!appData.dropMenu || appData.testLegality &&
6775 gameInfo.variant != VariantBughouse &&
6776 gameInfo.variant != VariantCrazyhouse) return -1;
6777 whichMenu = 1; // drop menu
6783 if (((*fromX = xSqr) < 0) ||
6784 ((*fromY = ySqr) < 0)) {
6785 *fromX = *fromY = -1;
6789 *fromX = BOARD_WIDTH - 1 - *fromX;
6791 *fromY = BOARD_HEIGHT - 1 - *fromY;
6796 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
6798 // char * hint = lastHint;
6799 FrontEndProgramStats stats;
6801 stats.which = cps == &first ? 0 : 1;
6802 stats.depth = cpstats->depth;
6803 stats.nodes = cpstats->nodes;
6804 stats.score = cpstats->score;
6805 stats.time = cpstats->time;
6806 stats.pv = cpstats->movelist;
6807 stats.hint = lastHint;
6808 stats.an_move_index = 0;
6809 stats.an_move_count = 0;
6811 if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
6812 stats.hint = cpstats->move_name;
6813 stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
6814 stats.an_move_count = cpstats->nr_moves;
6817 if(stats.pv && stats.pv[0]) safeStrCpy(lastPV[stats.which], stats.pv, sizeof(lastPV[stats.which])/sizeof(lastPV[stats.which][0])); // [HGM] pv: remember last PV of each
6819 SetProgramStats( &stats );
6823 Count(Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
6824 { // count all piece types
6826 *nB = *nW = *wStale = *bStale = *bishopColor = 0;
6827 for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
6828 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
6831 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
6832 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
6833 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
6834 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
6835 p == BlackBishop || p == BlackFerz || p == BlackAlfil )
6836 *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
6841 SufficientDefence(int pCnt[], int side, int nMine, int nHis)
6843 int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
6844 int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
6846 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
6847 if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
6848 if(myPawns == 2 && nMine == 3) // KPP
6849 return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
6850 if(myPawns == 1 && nMine == 2) // KP
6851 return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
6852 if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
6853 return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
6854 if(myPawns) return FALSE;
6855 if(pCnt[WhiteRook+side])
6856 return pCnt[BlackRook-side] ||
6857 pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
6858 pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
6859 pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
6860 if(pCnt[WhiteCannon+side]) {
6861 if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
6862 return majorDefense || pCnt[BlackAlfil-side] >= 2;
6864 if(pCnt[WhiteKnight+side])
6865 return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
6870 MatingPotential(int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
6872 VariantClass v = gameInfo.variant;
6874 if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
6875 if(v == VariantShatranj) return TRUE; // always winnable through baring
6876 if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
6877 if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
6879 if(v == VariantXiangqi) {
6880 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
6882 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
6883 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
6884 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
6885 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
6886 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
6887 if(stale) // we have at least one last-rank P plus perhaps C
6888 return majors // KPKX
6889 || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
6891 return pCnt[WhiteFerz+side] // KCAK
6892 || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
6893 || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
6894 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
6896 } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
6897 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
6899 if(nMine == 1) return FALSE; // bare King
6900 if(nBishops && bisColor == 3) return TRUE; // There must be a second B/A/F, which can either block (his) or attack (mine) the escape square
6901 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
6902 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
6903 // by now we have King + 1 piece (or multiple Bishops on the same color)
6904 if(pCnt[WhiteKnight+side])
6905 return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
6906 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
6907 || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
6909 return (pCnt[BlackKnight-side]); // KBKN, KFKN
6910 if(pCnt[WhiteAlfil+side])
6911 return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
6912 if(pCnt[WhiteWazir+side])
6913 return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
6920 Adjudicate(ChessProgramState *cps)
6921 { // [HGM] some adjudications useful with buggy engines
6922 // [HGM] adjudicate: made into separate routine, which now can be called after every move
6923 // In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
6924 // Actually ending the game is now based on the additional internal condition canAdjudicate.
6925 // Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
6926 int k, count = 0; static int bare = 1;
6927 ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
6928 Boolean canAdjudicate = !appData.icsActive;
6930 // most tests only when we understand the game, i.e. legality-checking on
6931 if( appData.testLegality )
6932 { /* [HGM] Some more adjudications for obstinate engines */
6933 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+1], i;
6934 static int moveCount = 6;
6936 char *reason = NULL;
6938 /* Count what is on board. */
6939 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
6941 /* Some material-based adjudications that have to be made before stalemate test */
6942 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
6943 // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
6944 boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
6945 if(canAdjudicate && appData.checkMates) {
6947 SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
6948 GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
6949 "Xboard adjudication: King destroyed", GE_XBOARD );
6954 /* Bare King in Shatranj (loses) or Losers (wins) */
6955 if( nrW == 1 || nrB == 1) {
6956 if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
6957 boards[forwardMostMove][EP_STATUS] = EP_WINS; // mark as win, so it becomes claimable
6958 if(canAdjudicate && appData.checkMates) {
6960 SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
6961 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
6962 "Xboard adjudication: Bare king", GE_XBOARD );
6966 if( gameInfo.variant == VariantShatranj && --bare < 0)
6968 boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
6969 if(canAdjudicate && appData.checkMates) {
6970 /* but only adjudicate if adjudication enabled */
6972 SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
6973 GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
6974 "Xboard adjudication: Bare king", GE_XBOARD );
6981 // don't wait for engine to announce game end if we can judge ourselves
6982 switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
6984 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
6985 int i, checkCnt = 0; // (should really be done by making nr of checks part of game state)
6986 for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
6987 if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
6990 reason = "Xboard adjudication: 3rd check";
6991 boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
7001 reason = "Xboard adjudication: Stalemate";
7002 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
7003 boards[forwardMostMove][EP_STATUS] = EP_STALEMATE; // default result for stalemate is draw
7004 if(gameInfo.variant == VariantLosers || gameInfo.variant == VariantGiveaway) // [HGM] losers:
7005 boards[forwardMostMove][EP_STATUS] = EP_WINS; // in these variants stalemated is always a win
7006 else if(gameInfo.variant == VariantSuicide) // in suicide it depends
7007 boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
7008 ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
7009 EP_CHECKMATE : EP_WINS);
7010 else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
7011 boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
7015 reason = "Xboard adjudication: Checkmate";
7016 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
7020 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
7022 result = GameIsDrawn; break;
7024 result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
7026 result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
7030 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
7032 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7033 GameEnds( result, reason, GE_XBOARD );
7037 /* Next absolutely insufficient mating material. */
7038 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
7039 !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
7040 { /* includes KBK, KNK, KK of KBKB with like Bishops */
7042 /* always flag draws, for judging claims */
7043 boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
7045 if(canAdjudicate && appData.materialDraws) {
7046 /* but only adjudicate them if adjudication enabled */
7047 if(engineOpponent) {
7048 SendToProgram("force\n", engineOpponent); // suppress reply
7049 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
7051 GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
7056 /* Then some trivial draws (only adjudicate, cannot be claimed) */
7057 if(gameInfo.variant == VariantXiangqi ?
7058 SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
7060 ( nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
7061 || nr[WhiteQueen] && nr[BlackQueen]==1 /* KQKQ */
7062 || nr[WhiteKnight]==2 || nr[BlackKnight]==2 /* KNNK */
7063 || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
7065 if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
7066 { /* if the first 3 moves do not show a tactical win, declare draw */
7067 if(engineOpponent) {
7068 SendToProgram("force\n", engineOpponent); // suppress reply
7069 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7071 GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
7074 } else moveCount = 6;
7076 if (appData.debugMode) { int i;
7077 fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
7078 forwardMostMove, backwardMostMove, boards[backwardMostMove][EP_STATUS],
7079 appData.drawRepeats);
7080 for( i=forwardMostMove; i>=backwardMostMove; i-- )
7081 fprintf(debugFP, "%d ep=%d\n", i, (signed char)boards[i][EP_STATUS]);
7085 // Repetition draws and 50-move rule can be applied independently of legality testing
7087 /* Check for rep-draws */
7089 for(k = forwardMostMove-2;
7090 k>=backwardMostMove && k>=forwardMostMove-100 &&
7091 (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
7092 (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE;
7095 if(CompareBoards(boards[k], boards[forwardMostMove])) {
7096 /* compare castling rights */
7097 if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
7098 (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
7099 rights++; /* King lost rights, while rook still had them */
7100 if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
7101 if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
7102 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
7103 rights++; /* but at least one rook lost them */
7105 if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
7106 (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
7108 if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
7109 if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
7110 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
7113 if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
7114 && appData.drawRepeats > 1) {
7115 /* adjudicate after user-specified nr of repeats */
7116 int result = GameIsDrawn;
7117 char *details = "XBoard adjudication: repetition draw";
7118 if(gameInfo.variant == VariantXiangqi && appData.testLegality) {
7119 // [HGM] xiangqi: check for forbidden perpetuals
7120 int m, ourPerpetual = 1, hisPerpetual = 1;
7121 for(m=forwardMostMove; m>k; m-=2) {
7122 if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
7123 ourPerpetual = 0; // the current mover did not always check
7124 if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
7125 hisPerpetual = 0; // the opponent did not always check
7127 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
7128 ourPerpetual, hisPerpetual);
7129 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
7130 result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7131 details = "Xboard adjudication: perpetual checking";
7133 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
7134 break; // (or we would have caught him before). Abort repetition-checking loop.
7136 // Now check for perpetual chases
7137 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
7138 hisPerpetual = PerpetualChase(k, forwardMostMove);
7139 ourPerpetual = PerpetualChase(k+1, forwardMostMove);
7140 if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
7141 result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7142 details = "Xboard adjudication: perpetual chasing";
7144 if(hisPerpetual && !ourPerpetual) // he is chasing us, but did not repeat yet
7145 break; // Abort repetition-checking loop.
7147 // if neither of us is checking or chasing all the time, or both are, it is draw
7149 if(engineOpponent) {
7150 SendToProgram("force\n", engineOpponent); // suppress reply
7151 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7153 GameEnds( result, details, GE_XBOARD );
7156 if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
7157 boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
7161 /* Now we test for 50-move draws. Determine ply count */
7162 count = forwardMostMove;
7163 /* look for last irreversble move */
7164 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
7166 /* if we hit starting position, add initial plies */
7167 if( count == backwardMostMove )
7168 count -= initialRulePlies;
7169 count = forwardMostMove - count;
7170 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
7171 // adjust reversible move counter for checks in Xiangqi
7172 int i = forwardMostMove - count, inCheck = 0, lastCheck;
7173 if(i < backwardMostMove) i = backwardMostMove;
7174 while(i <= forwardMostMove) {
7175 lastCheck = inCheck; // check evasion does not count
7176 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
7177 if(inCheck || lastCheck) count--; // check does not count
7182 boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
7183 /* this is used to judge if draw claims are legal */
7184 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
7185 if(engineOpponent) {
7186 SendToProgram("force\n", engineOpponent); // suppress reply
7187 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7189 GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
7193 /* if draw offer is pending, treat it as a draw claim
7194 * when draw condition present, to allow engines a way to
7195 * claim draws before making their move to avoid a race
7196 * condition occurring after their move
7198 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
7200 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
7201 p = "Draw claim: 50-move rule";
7202 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
7203 p = "Draw claim: 3-fold repetition";
7204 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
7205 p = "Draw claim: insufficient mating material";
7206 if( p != NULL && canAdjudicate) {
7207 if(engineOpponent) {
7208 SendToProgram("force\n", engineOpponent); // suppress reply
7209 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7211 GameEnds( GameIsDrawn, p, GE_XBOARD );
7216 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
7217 if(engineOpponent) {
7218 SendToProgram("force\n", engineOpponent); // suppress reply
7219 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7221 GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
7227 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
7228 { // [HGM] book: this routine intercepts moves to simulate book replies
7229 char *bookHit = NULL;
7231 //first determine if the incoming move brings opponent into his book
7232 if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
7233 bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
7234 if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
7235 if(bookHit != NULL && !cps->bookSuspend) {
7236 // make sure opponent is not going to reply after receiving move to book position
7237 SendToProgram("force\n", cps);
7238 cps->bookSuspend = TRUE; // flag indicating it has to be restarted
7240 if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
7241 // now arrange restart after book miss
7243 // after a book hit we never send 'go', and the code after the call to this routine
7244 // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
7246 snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), bookHit); // force book move into program supposed to play it
7247 SendToProgram(buf, cps);
7248 if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
7249 } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
7250 SendToProgram("go\n", cps);
7251 cps->bookSuspend = FALSE; // after a 'go' we are never suspended
7252 } else { // 'go' might be sent based on 'firstMove' after this routine returns
7253 if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
7254 SendToProgram("go\n", cps);
7255 cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
7257 return bookHit; // notify caller of hit, so it can take action to send move to opponent
7261 ChessProgramState *savedState;
7262 void DeferredBookMove(void)
7264 if(savedState->lastPing != savedState->lastPong)
7265 ScheduleDelayedEvent(DeferredBookMove, 10);
7267 HandleMachineMove(savedMessage, savedState);
7271 HandleMachineMove(message, cps)
7273 ChessProgramState *cps;
7275 char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
7276 char realname[MSG_SIZ];
7277 int fromX, fromY, toX, toY;
7286 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
7288 * Kludge to ignore BEL characters
7290 while (*message == '\007') message++;
7293 * [HGM] engine debug message: ignore lines starting with '#' character
7295 if(cps->debug && *message == '#') return;
7298 * Look for book output
7300 if (cps == &first && bookRequested) {
7301 if (message[0] == '\t' || message[0] == ' ') {
7302 /* Part of the book output is here; append it */
7303 strcat(bookOutput, message);
7304 strcat(bookOutput, " \n");
7306 } else if (bookOutput[0] != NULLCHAR) {
7307 /* All of book output has arrived; display it */
7308 char *p = bookOutput;
7309 while (*p != NULLCHAR) {
7310 if (*p == '\t') *p = ' ';
7313 DisplayInformation(bookOutput);
7314 bookRequested = FALSE;
7315 /* Fall through to parse the current output */
7320 * Look for machine move.
7322 if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
7323 (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
7325 /* This method is only useful on engines that support ping */
7326 if (cps->lastPing != cps->lastPong) {
7327 if (gameMode == BeginningOfGame) {
7328 /* Extra move from before last new; ignore */
7329 if (appData.debugMode) {
7330 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7333 if (appData.debugMode) {
7334 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7335 cps->which, gameMode);
7338 SendToProgram("undo\n", cps);
7344 case BeginningOfGame:
7345 /* Extra move from before last reset; ignore */
7346 if (appData.debugMode) {
7347 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7354 /* Extra move after we tried to stop. The mode test is
7355 not a reliable way of detecting this problem, but it's
7356 the best we can do on engines that don't support ping.
7358 if (appData.debugMode) {
7359 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7360 cps->which, gameMode);
7362 SendToProgram("undo\n", cps);
7365 case MachinePlaysWhite:
7366 case IcsPlayingWhite:
7367 machineWhite = TRUE;
7370 case MachinePlaysBlack:
7371 case IcsPlayingBlack:
7372 machineWhite = FALSE;
7375 case TwoMachinesPlay:
7376 machineWhite = (cps->twoMachinesColor[0] == 'w');
7379 if (WhiteOnMove(forwardMostMove) != machineWhite) {
7380 if (appData.debugMode) {
7382 "Ignoring move out of turn by %s, gameMode %d"
7383 ", forwardMost %d\n",
7384 cps->which, gameMode, forwardMostMove);
7389 if (appData.debugMode) { int f = forwardMostMove;
7390 fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
7391 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
7392 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
7394 if(cps->alphaRank) AlphaRank(machineMove, 4);
7395 if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
7396 &fromX, &fromY, &toX, &toY, &promoChar)) {
7397 /* Machine move could not be parsed; ignore it. */
7398 snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
7399 machineMove, _(cps->which));
7400 DisplayError(buf1, 0);
7401 snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
7402 machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
7403 if (gameMode == TwoMachinesPlay) {
7404 GameEnds(machineWhite ? BlackWins : WhiteWins,
7410 /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
7411 /* So we have to redo legality test with true e.p. status here, */
7412 /* to make sure an illegal e.p. capture does not slip through, */
7413 /* to cause a forfeit on a justified illegal-move complaint */
7414 /* of the opponent. */
7415 if( gameMode==TwoMachinesPlay && appData.testLegality ) {
7417 moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
7418 fromY, fromX, toY, toX, promoChar);
7419 if (appData.debugMode) {
7421 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
7422 boards[forwardMostMove][CASTLING][i], castlingRank[i]);
7423 fprintf(debugFP, "castling rights\n");
7425 if(moveType == IllegalMove) {
7426 snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
7427 machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
7428 GameEnds(machineWhite ? BlackWins : WhiteWins,
7431 } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
7432 /* [HGM] Kludge to handle engines that send FRC-style castling
7433 when they shouldn't (like TSCP-Gothic) */
7435 case WhiteASideCastleFR:
7436 case BlackASideCastleFR:
7438 currentMoveString[2]++;
7440 case WhiteHSideCastleFR:
7441 case BlackHSideCastleFR:
7443 currentMoveString[2]--;
7445 default: ; // nothing to do, but suppresses warning of pedantic compilers
7448 hintRequested = FALSE;
7449 lastHint[0] = NULLCHAR;
7450 bookRequested = FALSE;
7451 /* Program may be pondering now */
7452 cps->maybeThinking = TRUE;
7453 if (cps->sendTime == 2) cps->sendTime = 1;
7454 if (cps->offeredDraw) cps->offeredDraw--;
7456 /* [AS] Save move info*/
7457 pvInfoList[ forwardMostMove ].score = programStats.score;
7458 pvInfoList[ forwardMostMove ].depth = programStats.depth;
7459 pvInfoList[ forwardMostMove ].time = programStats.time; // [HGM] PGNtime: take time from engine stats
7461 MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
7463 /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
7464 if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
7467 while( count < adjudicateLossPlies ) {
7468 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
7471 score = -score; /* Flip score for winning side */
7474 if( score > adjudicateLossThreshold ) {
7481 if( count >= adjudicateLossPlies ) {
7482 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7484 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7485 "Xboard adjudication",
7492 if(Adjudicate(cps)) {
7493 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7494 return; // [HGM] adjudicate: for all automatic game ends
7498 if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
7500 if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
7501 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
7503 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7505 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7507 if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
7508 char buf[3*MSG_SIZ];
7510 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
7511 programStats.score / 100.,
7513 programStats.time / 100.,
7514 (unsigned int)programStats.nodes,
7515 (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
7516 programStats.movelist);
7518 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
7523 /* [AS] Clear stats for next move */
7524 ClearProgramStats();
7525 thinkOutput[0] = NULLCHAR;
7526 hiddenThinkOutputState = 0;
7529 if (gameMode == TwoMachinesPlay) {
7530 /* [HGM] relaying draw offers moved to after reception of move */
7531 /* and interpreting offer as claim if it brings draw condition */
7532 if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
7533 SendToProgram("draw\n", cps->other);
7535 if (cps->other->sendTime) {
7536 SendTimeRemaining(cps->other,
7537 cps->other->twoMachinesColor[0] == 'w');
7539 bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
7540 if (firstMove && !bookHit) {
7542 if (cps->other->useColors) {
7543 SendToProgram(cps->other->twoMachinesColor, cps->other);
7545 SendToProgram("go\n", cps->other);
7547 cps->other->maybeThinking = TRUE;
7550 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7552 if (!pausing && appData.ringBellAfterMoves) {
7557 * Reenable menu items that were disabled while
7558 * machine was thinking
7560 if (gameMode != TwoMachinesPlay)
7561 SetUserThinkingEnables();
7563 // [HGM] book: after book hit opponent has received move and is now in force mode
7564 // force the book reply into it, and then fake that it outputted this move by jumping
7565 // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
7567 static char bookMove[MSG_SIZ]; // a bit generous?
7569 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
7570 strcat(bookMove, bookHit);
7573 programStats.nodes = programStats.depth = programStats.time =
7574 programStats.score = programStats.got_only_move = 0;
7575 sprintf(programStats.movelist, "%s (xbook)", bookHit);
7577 if(cps->lastPing != cps->lastPong) {
7578 savedMessage = message; // args for deferred call
7580 ScheduleDelayedEvent(DeferredBookMove, 10);
7589 /* Set special modes for chess engines. Later something general
7590 * could be added here; for now there is just one kludge feature,
7591 * needed because Crafty 15.10 and earlier don't ignore SIGINT
7592 * when "xboard" is given as an interactive command.
7594 if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
7595 cps->useSigint = FALSE;
7596 cps->useSigterm = FALSE;
7598 if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
7599 ParseFeatures(message+8, cps);
7600 return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
7603 if (!appData.testLegality && !strncmp(message, "setup ", 6)) { // [HGM] allow first engine to define opening position
7604 int dummy, s=6; char buf[MSG_SIZ];
7605 if(appData.icsActive || forwardMostMove != 0 || cps != &first || startedFromSetupPosition) return;
7606 if(sscanf(message, "setup (%s", buf) == 1) s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTable(pieceToChar, buf);
7607 ParseFEN(boards[0], &dummy, message+s);
7608 DrawPosition(TRUE, boards[0]);
7609 startedFromSetupPosition = TRUE;
7612 /* [HGM] Allow engine to set up a position. Don't ask me why one would
7613 * want this, I was asked to put it in, and obliged.
7615 if (!strncmp(message, "setboard ", 9)) {
7616 Board initial_position;
7618 GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
7620 if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
7621 DisplayError(_("Bad FEN received from engine"), 0);
7625 CopyBoard(boards[0], initial_position);
7626 initialRulePlies = FENrulePlies;
7627 if(blackPlaysFirst) gameMode = MachinePlaysWhite;
7628 else gameMode = MachinePlaysBlack;
7629 DrawPosition(FALSE, boards[currentMove]);
7635 * Look for communication commands
7637 if (!strncmp(message, "telluser ", 9)) {
7638 if(message[9] == '\\' && message[10] == '\\')
7639 EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
7640 DisplayNote(message + 9);
7643 if (!strncmp(message, "tellusererror ", 14)) {
7645 if(message[14] == '\\' && message[15] == '\\')
7646 EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
7647 DisplayError(message + 14, 0);
7650 if (!strncmp(message, "tellopponent ", 13)) {
7651 if (appData.icsActive) {
7653 snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
7657 DisplayNote(message + 13);
7661 if (!strncmp(message, "tellothers ", 11)) {
7662 if (appData.icsActive) {
7664 snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
7670 if (!strncmp(message, "tellall ", 8)) {
7671 if (appData.icsActive) {
7673 snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
7677 DisplayNote(message + 8);
7681 if (strncmp(message, "warning", 7) == 0) {
7682 /* Undocumented feature, use tellusererror in new code */
7683 DisplayError(message, 0);
7686 if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
7687 safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
7688 strcat(realname, " query");
7689 AskQuestion(realname, buf2, buf1, cps->pr);
7692 /* Commands from the engine directly to ICS. We don't allow these to be
7693 * sent until we are logged on. Crafty kibitzes have been known to
7694 * interfere with the login process.
7697 if (!strncmp(message, "tellics ", 8)) {
7698 SendToICS(message + 8);
7702 if (!strncmp(message, "tellicsnoalias ", 15)) {
7703 SendToICS(ics_prefix);
7704 SendToICS(message + 15);
7708 /* The following are for backward compatibility only */
7709 if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
7710 !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
7711 SendToICS(ics_prefix);
7717 if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
7721 * If the move is illegal, cancel it and redraw the board.
7722 * Also deal with other error cases. Matching is rather loose
7723 * here to accommodate engines written before the spec.
7725 if (strncmp(message + 1, "llegal move", 11) == 0 ||
7726 strncmp(message, "Error", 5) == 0) {
7727 if (StrStr(message, "name") ||
7728 StrStr(message, "rating") || StrStr(message, "?") ||
7729 StrStr(message, "result") || StrStr(message, "board") ||
7730 StrStr(message, "bk") || StrStr(message, "computer") ||
7731 StrStr(message, "variant") || StrStr(message, "hint") ||
7732 StrStr(message, "random") || StrStr(message, "depth") ||
7733 StrStr(message, "accepted")) {
7736 if (StrStr(message, "protover")) {
7737 /* Program is responding to input, so it's apparently done
7738 initializing, and this error message indicates it is
7739 protocol version 1. So we don't need to wait any longer
7740 for it to initialize and send feature commands. */
7741 FeatureDone(cps, 1);
7742 cps->protocolVersion = 1;
7745 cps->maybeThinking = FALSE;
7747 if (StrStr(message, "draw")) {
7748 /* Program doesn't have "draw" command */
7749 cps->sendDrawOffers = 0;
7752 if (cps->sendTime != 1 &&
7753 (StrStr(message, "time") || StrStr(message, "otim"))) {
7754 /* Program apparently doesn't have "time" or "otim" command */
7758 if (StrStr(message, "analyze")) {
7759 cps->analysisSupport = FALSE;
7760 cps->analyzing = FALSE;
7762 snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
7763 DisplayError(buf2, 0);
7766 if (StrStr(message, "(no matching move)st")) {
7767 /* Special kludge for GNU Chess 4 only */
7768 cps->stKludge = TRUE;
7769 SendTimeControl(cps, movesPerSession, timeControl,
7770 timeIncrement, appData.searchDepth,
7774 if (StrStr(message, "(no matching move)sd")) {
7775 /* Special kludge for GNU Chess 4 only */
7776 cps->sdKludge = TRUE;
7777 SendTimeControl(cps, movesPerSession, timeControl,
7778 timeIncrement, appData.searchDepth,
7782 if (!StrStr(message, "llegal")) {
7785 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
7786 gameMode == IcsIdle) return;
7787 if (forwardMostMove <= backwardMostMove) return;
7788 if (pausing) PauseEvent();
7789 if(appData.forceIllegal) {
7790 // [HGM] illegal: machine refused move; force position after move into it
7791 SendToProgram("force\n", cps);
7792 if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
7793 // we have a real problem now, as SendBoard will use the a2a3 kludge
7794 // when black is to move, while there might be nothing on a2 or black
7795 // might already have the move. So send the board as if white has the move.
7796 // But first we must change the stm of the engine, as it refused the last move
7797 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
7798 if(WhiteOnMove(forwardMostMove)) {
7799 SendToProgram("a7a6\n", cps); // for the engine black still had the move
7800 SendBoard(cps, forwardMostMove); // kludgeless board
7802 SendToProgram("a2a3\n", cps); // for the engine white still had the move
7803 CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
7804 SendBoard(cps, forwardMostMove+1); // kludgeless board
7806 } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
7807 if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
7808 gameMode == TwoMachinesPlay)
7809 SendToProgram("go\n", cps);
7812 if (gameMode == PlayFromGameFile) {
7813 /* Stop reading this game file */
7814 gameMode = EditGame;
7817 /* [HGM] illegal-move claim should forfeit game when Xboard */
7818 /* only passes fully legal moves */
7819 if( appData.testLegality && gameMode == TwoMachinesPlay ) {
7820 GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
7821 "False illegal-move claim", GE_XBOARD );
7822 return; // do not take back move we tested as valid
7824 currentMove = forwardMostMove-1;
7825 DisplayMove(currentMove-1); /* before DisplayMoveError */
7826 SwitchClocks(forwardMostMove-1); // [HGM] race
7827 DisplayBothClocks();
7828 snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
7829 parseList[currentMove], _(cps->which));
7830 DisplayMoveError(buf1);
7831 DrawPosition(FALSE, boards[currentMove]);
7834 if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
7835 /* Program has a broken "time" command that
7836 outputs a string not ending in newline.
7842 * If chess program startup fails, exit with an error message.
7843 * Attempts to recover here are futile.
7845 if ((StrStr(message, "unknown host") != NULL)
7846 || (StrStr(message, "No remote directory") != NULL)
7847 || (StrStr(message, "not found") != NULL)
7848 || (StrStr(message, "No such file") != NULL)
7849 || (StrStr(message, "can't alloc") != NULL)
7850 || (StrStr(message, "Permission denied") != NULL)) {
7852 cps->maybeThinking = FALSE;
7853 snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
7854 _(cps->which), cps->program, cps->host, message);
7855 RemoveInputSource(cps->isr);
7856 DisplayFatalError(buf1, 0, 1);
7861 * Look for hint output
7863 if (sscanf(message, "Hint: %s", buf1) == 1) {
7864 if (cps == &first && hintRequested) {
7865 hintRequested = FALSE;
7866 if (ParseOneMove(buf1, forwardMostMove, &moveType,
7867 &fromX, &fromY, &toX, &toY, &promoChar)) {
7868 (void) CoordsToAlgebraic(boards[forwardMostMove],
7869 PosFlags(forwardMostMove),
7870 fromY, fromX, toY, toX, promoChar, buf1);
7871 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
7872 DisplayInformation(buf2);
7874 /* Hint move could not be parsed!? */
7875 snprintf(buf2, sizeof(buf2),
7876 _("Illegal hint move \"%s\"\nfrom %s chess program"),
7877 buf1, _(cps->which));
7878 DisplayError(buf2, 0);
7881 safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
7887 * Ignore other messages if game is not in progress
7889 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
7890 gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
7893 * look for win, lose, draw, or draw offer
7895 if (strncmp(message, "1-0", 3) == 0) {
7896 char *p, *q, *r = "";
7897 p = strchr(message, '{');
7905 GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
7907 } else if (strncmp(message, "0-1", 3) == 0) {
7908 char *p, *q, *r = "";
7909 p = strchr(message, '{');
7917 /* Kludge for Arasan 4.1 bug */
7918 if (strcmp(r, "Black resigns") == 0) {
7919 GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
7922 GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
7924 } else if (strncmp(message, "1/2", 3) == 0) {
7925 char *p, *q, *r = "";
7926 p = strchr(message, '{');
7935 GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
7938 } else if (strncmp(message, "White resign", 12) == 0) {
7939 GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
7941 } else if (strncmp(message, "Black resign", 12) == 0) {
7942 GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
7944 } else if (strncmp(message, "White matches", 13) == 0 ||
7945 strncmp(message, "Black matches", 13) == 0 ) {
7946 /* [HGM] ignore GNUShogi noises */
7948 } else if (strncmp(message, "White", 5) == 0 &&
7949 message[5] != '(' &&
7950 StrStr(message, "Black") == NULL) {
7951 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7953 } else if (strncmp(message, "Black", 5) == 0 &&
7954 message[5] != '(') {
7955 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7957 } else if (strcmp(message, "resign") == 0 ||
7958 strcmp(message, "computer resigns") == 0) {
7960 case MachinePlaysBlack:
7961 case IcsPlayingBlack:
7962 GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
7964 case MachinePlaysWhite:
7965 case IcsPlayingWhite:
7966 GameEnds(BlackWins, "White resigns", GE_ENGINE);
7968 case TwoMachinesPlay:
7969 if (cps->twoMachinesColor[0] == 'w')
7970 GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
7972 GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
7979 } else if (strncmp(message, "opponent mates", 14) == 0) {
7981 case MachinePlaysBlack:
7982 case IcsPlayingBlack:
7983 GameEnds(WhiteWins, "White mates", GE_ENGINE);
7985 case MachinePlaysWhite:
7986 case IcsPlayingWhite:
7987 GameEnds(BlackWins, "Black mates", GE_ENGINE);
7989 case TwoMachinesPlay:
7990 if (cps->twoMachinesColor[0] == 'w')
7991 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7993 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8000 } else if (strncmp(message, "computer mates", 14) == 0) {
8002 case MachinePlaysBlack:
8003 case IcsPlayingBlack:
8004 GameEnds(BlackWins, "Black mates", GE_ENGINE1);
8006 case MachinePlaysWhite:
8007 case IcsPlayingWhite:
8008 GameEnds(WhiteWins, "White mates", GE_ENGINE);
8010 case TwoMachinesPlay:
8011 if (cps->twoMachinesColor[0] == 'w')
8012 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8014 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8021 } else if (strncmp(message, "checkmate", 9) == 0) {
8022 if (WhiteOnMove(forwardMostMove)) {
8023 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8025 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8028 } else if (strstr(message, "Draw") != NULL ||
8029 strstr(message, "game is a draw") != NULL) {
8030 GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
8032 } else if (strstr(message, "offer") != NULL &&
8033 strstr(message, "draw") != NULL) {
8035 if (appData.zippyPlay && first.initDone) {
8036 /* Relay offer to ICS */
8037 SendToICS(ics_prefix);
8038 SendToICS("draw\n");
8041 cps->offeredDraw = 2; /* valid until this engine moves twice */
8042 if (gameMode == TwoMachinesPlay) {
8043 if (cps->other->offeredDraw) {
8044 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8045 /* [HGM] in two-machine mode we delay relaying draw offer */
8046 /* until after we also have move, to see if it is really claim */
8048 } else if (gameMode == MachinePlaysWhite ||
8049 gameMode == MachinePlaysBlack) {
8050 if (userOfferedDraw) {
8051 DisplayInformation(_("Machine accepts your draw offer"));
8052 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8054 DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
8061 * Look for thinking output
8063 if ( appData.showThinking // [HGM] thinking: test all options that cause this output
8064 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
8066 int plylev, mvleft, mvtot, curscore, time;
8067 char mvname[MOVE_LEN];
8071 int prefixHint = FALSE;
8072 mvname[0] = NULLCHAR;
8075 case MachinePlaysBlack:
8076 case IcsPlayingBlack:
8077 if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8079 case MachinePlaysWhite:
8080 case IcsPlayingWhite:
8081 if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8086 case IcsObserving: /* [DM] icsEngineAnalyze */
8087 if (!appData.icsEngineAnalyze) ignore = TRUE;
8089 case TwoMachinesPlay:
8090 if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
8100 ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
8102 if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8103 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
8105 if (plyext != ' ' && plyext != '\t') {
8109 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8110 if( cps->scoreIsAbsolute &&
8111 ( gameMode == MachinePlaysBlack ||
8112 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
8113 gameMode == IcsPlayingBlack || // [HGM] also add other situations where engine should report black POV
8114 (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
8115 !WhiteOnMove(currentMove)
8118 curscore = -curscore;
8122 tempStats.depth = plylev;
8123 tempStats.nodes = nodes;
8124 tempStats.time = time;
8125 tempStats.score = curscore;
8126 tempStats.got_only_move = 0;
8128 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
8131 if(cps->nps == 0) ticklen = 10*time; // use engine reported time
8132 else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
8133 if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
8134 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
8135 whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
8136 if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
8137 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
8138 blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
8141 /* Buffer overflow protection */
8142 if (buf1[0] != NULLCHAR) {
8143 if (strlen(buf1) >= sizeof(tempStats.movelist)
8144 && appData.debugMode) {
8146 "PV is too long; using the first %u bytes.\n",
8147 (unsigned) sizeof(tempStats.movelist) - 1);
8150 safeStrCpy( tempStats.movelist, buf1, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
8152 sprintf(tempStats.movelist, " no PV\n");
8155 if (tempStats.seen_stat) {
8156 tempStats.ok_to_send = 1;
8159 if (strchr(tempStats.movelist, '(') != NULL) {
8160 tempStats.line_is_book = 1;
8161 tempStats.nr_moves = 0;
8162 tempStats.moves_left = 0;
8164 tempStats.line_is_book = 0;
8167 if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
8168 programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
8170 SendProgramStatsToFrontend( cps, &tempStats );
8173 [AS] Protect the thinkOutput buffer from overflow... this
8174 is only useful if buf1 hasn't overflowed first!
8176 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%+.2f %s%s",
8178 (gameMode == TwoMachinesPlay ?
8179 ToUpper(cps->twoMachinesColor[0]) : ' '),
8180 ((double) curscore) / 100.0,
8181 prefixHint ? lastHint : "",
8182 prefixHint ? " " : "" );
8184 if( buf1[0] != NULLCHAR ) {
8185 unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
8187 if( strlen(buf1) > max_len ) {
8188 if( appData.debugMode) {
8189 fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
8191 buf1[max_len+1] = '\0';
8194 strcat( thinkOutput, buf1 );
8197 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
8198 || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8199 DisplayMove(currentMove - 1);
8203 } else if ((p=StrStr(message, "(only move)")) != NULL) {
8204 /* crafty (9.25+) says "(only move) <move>"
8205 * if there is only 1 legal move
8207 sscanf(p, "(only move) %s", buf1);
8208 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
8209 sprintf(programStats.movelist, "%s (only move)", buf1);
8210 programStats.depth = 1;
8211 programStats.nr_moves = 1;
8212 programStats.moves_left = 1;
8213 programStats.nodes = 1;
8214 programStats.time = 1;
8215 programStats.got_only_move = 1;
8217 /* Not really, but we also use this member to
8218 mean "line isn't going to change" (Crafty
8219 isn't searching, so stats won't change) */
8220 programStats.line_is_book = 1;
8222 SendProgramStatsToFrontend( cps, &programStats );
8224 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8225 gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8226 DisplayMove(currentMove - 1);
8229 } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
8230 &time, &nodes, &plylev, &mvleft,
8231 &mvtot, mvname) >= 5) {
8232 /* The stat01: line is from Crafty (9.29+) in response
8233 to the "." command */
8234 programStats.seen_stat = 1;
8235 cps->maybeThinking = TRUE;
8237 if (programStats.got_only_move || !appData.periodicUpdates)
8240 programStats.depth = plylev;
8241 programStats.time = time;
8242 programStats.nodes = nodes;
8243 programStats.moves_left = mvleft;
8244 programStats.nr_moves = mvtot;
8245 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
8246 programStats.ok_to_send = 1;
8247 programStats.movelist[0] = '\0';
8249 SendProgramStatsToFrontend( cps, &programStats );
8253 } else if (strncmp(message,"++",2) == 0) {
8254 /* Crafty 9.29+ outputs this */
8255 programStats.got_fail = 2;
8258 } else if (strncmp(message,"--",2) == 0) {
8259 /* Crafty 9.29+ outputs this */
8260 programStats.got_fail = 1;
8263 } else if (thinkOutput[0] != NULLCHAR &&
8264 strncmp(message, " ", 4) == 0) {
8265 unsigned message_len;
8268 while (*p && *p == ' ') p++;
8270 message_len = strlen( p );
8272 /* [AS] Avoid buffer overflow */
8273 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
8274 strcat(thinkOutput, " ");
8275 strcat(thinkOutput, p);
8278 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
8279 strcat(programStats.movelist, " ");
8280 strcat(programStats.movelist, p);
8283 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8284 gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8285 DisplayMove(currentMove - 1);
8293 if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8294 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
8296 ChessProgramStats cpstats;
8298 if (plyext != ' ' && plyext != '\t') {
8302 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8303 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
8304 curscore = -curscore;
8307 cpstats.depth = plylev;
8308 cpstats.nodes = nodes;
8309 cpstats.time = time;
8310 cpstats.score = curscore;
8311 cpstats.got_only_move = 0;
8312 cpstats.movelist[0] = '\0';
8314 if (buf1[0] != NULLCHAR) {
8315 safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
8318 cpstats.ok_to_send = 0;
8319 cpstats.line_is_book = 0;
8320 cpstats.nr_moves = 0;
8321 cpstats.moves_left = 0;
8323 SendProgramStatsToFrontend( cps, &cpstats );
8330 /* Parse a game score from the character string "game", and
8331 record it as the history of the current game. The game
8332 score is NOT assumed to start from the standard position.
8333 The display is not updated in any way.
8336 ParseGameHistory(game)
8340 int fromX, fromY, toX, toY, boardIndex;
8345 if (appData.debugMode)
8346 fprintf(debugFP, "Parsing game history: %s\n", game);
8348 if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
8349 gameInfo.site = StrSave(appData.icsHost);
8350 gameInfo.date = PGNDate();
8351 gameInfo.round = StrSave("-");
8353 /* Parse out names of players */
8354 while (*game == ' ') game++;
8356 while (*game != ' ') *p++ = *game++;
8358 gameInfo.white = StrSave(buf);
8359 while (*game == ' ') game++;
8361 while (*game != ' ' && *game != '\n') *p++ = *game++;
8363 gameInfo.black = StrSave(buf);
8366 boardIndex = blackPlaysFirst ? 1 : 0;
8369 yyboardindex = boardIndex;
8370 moveType = (ChessMove) Myylex();
8372 case IllegalMove: /* maybe suicide chess, etc. */
8373 if (appData.debugMode) {
8374 fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
8375 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8376 setbuf(debugFP, NULL);
8378 case WhitePromotion:
8379 case BlackPromotion:
8380 case WhiteNonPromotion:
8381 case BlackNonPromotion:
8383 case WhiteCapturesEnPassant:
8384 case BlackCapturesEnPassant:
8385 case WhiteKingSideCastle:
8386 case WhiteQueenSideCastle:
8387 case BlackKingSideCastle:
8388 case BlackQueenSideCastle:
8389 case WhiteKingSideCastleWild:
8390 case WhiteQueenSideCastleWild:
8391 case BlackKingSideCastleWild:
8392 case BlackQueenSideCastleWild:
8394 case WhiteHSideCastleFR:
8395 case WhiteASideCastleFR:
8396 case BlackHSideCastleFR:
8397 case BlackASideCastleFR:
8399 fromX = currentMoveString[0] - AAA;
8400 fromY = currentMoveString[1] - ONE;
8401 toX = currentMoveString[2] - AAA;
8402 toY = currentMoveString[3] - ONE;
8403 promoChar = currentMoveString[4];
8407 fromX = moveType == WhiteDrop ?
8408 (int) CharToPiece(ToUpper(currentMoveString[0])) :
8409 (int) CharToPiece(ToLower(currentMoveString[0]));
8411 toX = currentMoveString[2] - AAA;
8412 toY = currentMoveString[3] - ONE;
8413 promoChar = NULLCHAR;
8417 snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
8418 if (appData.debugMode) {
8419 fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
8420 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8421 setbuf(debugFP, NULL);
8423 DisplayError(buf, 0);
8425 case ImpossibleMove:
8427 snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
8428 if (appData.debugMode) {
8429 fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
8430 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8431 setbuf(debugFP, NULL);
8433 DisplayError(buf, 0);
8436 if (boardIndex < backwardMostMove) {
8437 /* Oops, gap. How did that happen? */
8438 DisplayError(_("Gap in move list"), 0);
8441 backwardMostMove = blackPlaysFirst ? 1 : 0;
8442 if (boardIndex > forwardMostMove) {
8443 forwardMostMove = boardIndex;
8447 if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
8448 strcat(parseList[boardIndex-1], " ");
8449 strcat(parseList[boardIndex-1], yy_text);
8461 case GameUnfinished:
8462 if (gameMode == IcsExamining) {
8463 if (boardIndex < backwardMostMove) {
8464 /* Oops, gap. How did that happen? */
8467 backwardMostMove = blackPlaysFirst ? 1 : 0;
8470 gameInfo.result = moveType;
8471 p = strchr(yy_text, '{');
8472 if (p == NULL) p = strchr(yy_text, '(');
8475 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
8477 q = strchr(p, *p == '{' ? '}' : ')');
8478 if (q != NULL) *q = NULLCHAR;
8481 while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
8482 gameInfo.resultDetails = StrSave(p);
8485 if (boardIndex >= forwardMostMove &&
8486 !(gameMode == IcsObserving && ics_gamenum == -1)) {
8487 backwardMostMove = blackPlaysFirst ? 1 : 0;
8490 (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
8491 fromY, fromX, toY, toX, promoChar,
8492 parseList[boardIndex]);
8493 CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
8494 /* currentMoveString is set as a side-effect of yylex */
8495 safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
8496 strcat(moveList[boardIndex], "\n");
8498 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
8499 switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
8505 if(gameInfo.variant != VariantShogi)
8506 strcat(parseList[boardIndex - 1], "+");
8510 strcat(parseList[boardIndex - 1], "#");
8517 /* Apply a move to the given board */
8519 ApplyMove(fromX, fromY, toX, toY, promoChar, board)
8520 int fromX, fromY, toX, toY;
8524 ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
8525 int promoRank = gameInfo.variant == VariantMakruk ? 3 : 1;
8527 /* [HGM] compute & store e.p. status and castling rights for new position */
8528 /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
8530 if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
8531 oldEP = (signed char)board[EP_STATUS];
8532 board[EP_STATUS] = EP_NONE;
8534 if( board[toY][toX] != EmptySquare )
8535 board[EP_STATUS] = EP_CAPTURE;
8537 if (fromY == DROP_RANK) {
8539 piece = board[toY][toX] = (ChessSquare) fromX;
8543 if( board[fromY][fromX] == WhiteLance || board[fromY][fromX] == BlackLance ) {
8544 if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi )
8545 board[EP_STATUS] = EP_PAWN_MOVE; // Lance is Pawn-like in most variants
8547 if( board[fromY][fromX] == WhitePawn ) {
8548 if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
8549 board[EP_STATUS] = EP_PAWN_MOVE;
8551 if(toX>BOARD_LEFT && board[toY][toX-1] == BlackPawn &&
8552 gameInfo.variant != VariantBerolina || toX < fromX)
8553 board[EP_STATUS] = toX | berolina;
8554 if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
8555 gameInfo.variant != VariantBerolina || toX > fromX)
8556 board[EP_STATUS] = toX;
8559 if( board[fromY][fromX] == BlackPawn ) {
8560 if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
8561 board[EP_STATUS] = EP_PAWN_MOVE;
8562 if( toY-fromY== -2) {
8563 if(toX>BOARD_LEFT && board[toY][toX-1] == WhitePawn &&
8564 gameInfo.variant != VariantBerolina || toX < fromX)
8565 board[EP_STATUS] = toX | berolina;
8566 if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
8567 gameInfo.variant != VariantBerolina || toX > fromX)
8568 board[EP_STATUS] = toX;
8572 for(i=0; i<nrCastlingRights; i++) {
8573 if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
8574 board[CASTLING][i] == toX && castlingRank[i] == toY
8575 ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
8578 if (fromX == toX && fromY == toY) return;
8580 piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
8581 king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
8582 if(gameInfo.variant == VariantKnightmate)
8583 king += (int) WhiteUnicorn - (int) WhiteKing;
8585 /* Code added by Tord: */
8586 /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
8587 if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
8588 board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
8589 board[fromY][fromX] = EmptySquare;
8590 board[toY][toX] = EmptySquare;
8591 if((toX > fromX) != (piece == WhiteRook)) {
8592 board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
8594 board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
8596 } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
8597 board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
8598 board[fromY][fromX] = EmptySquare;
8599 board[toY][toX] = EmptySquare;
8600 if((toX > fromX) != (piece == BlackRook)) {
8601 board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
8603 board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
8605 /* End of code added by Tord */
8607 } else if (board[fromY][fromX] == king
8608 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8609 && toY == fromY && toX > fromX+1) {
8610 board[fromY][fromX] = EmptySquare;
8611 board[toY][toX] = king;
8612 board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
8613 board[fromY][BOARD_RGHT-1] = EmptySquare;
8614 } else if (board[fromY][fromX] == king
8615 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8616 && toY == fromY && toX < fromX-1) {
8617 board[fromY][fromX] = EmptySquare;
8618 board[toY][toX] = king;
8619 board[toY][toX+1] = board[fromY][BOARD_LEFT];
8620 board[fromY][BOARD_LEFT] = EmptySquare;
8621 } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
8622 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
8623 && toY >= BOARD_HEIGHT-promoRank
8625 /* white pawn promotion */
8626 board[toY][toX] = CharToPiece(ToUpper(promoChar));
8627 if (board[toY][toX] == EmptySquare) {
8628 board[toY][toX] = WhiteQueen;
8630 if(gameInfo.variant==VariantBughouse ||
8631 gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
8632 board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
8633 board[fromY][fromX] = EmptySquare;
8634 } else if ((fromY == BOARD_HEIGHT-4)
8636 && gameInfo.variant != VariantXiangqi
8637 && gameInfo.variant != VariantBerolina
8638 && (board[fromY][fromX] == WhitePawn)
8639 && (board[toY][toX] == EmptySquare)) {
8640 board[fromY][fromX] = EmptySquare;
8641 board[toY][toX] = WhitePawn;
8642 captured = board[toY - 1][toX];
8643 board[toY - 1][toX] = EmptySquare;
8644 } else if ((fromY == BOARD_HEIGHT-4)
8646 && gameInfo.variant == VariantBerolina
8647 && (board[fromY][fromX] == WhitePawn)
8648 && (board[toY][toX] == EmptySquare)) {
8649 board[fromY][fromX] = EmptySquare;
8650 board[toY][toX] = WhitePawn;
8651 if(oldEP & EP_BEROLIN_A) {
8652 captured = board[fromY][fromX-1];
8653 board[fromY][fromX-1] = EmptySquare;
8654 }else{ captured = board[fromY][fromX+1];
8655 board[fromY][fromX+1] = EmptySquare;
8657 } else if (board[fromY][fromX] == king
8658 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8659 && toY == fromY && toX > fromX+1) {
8660 board[fromY][fromX] = EmptySquare;
8661 board[toY][toX] = king;
8662 board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
8663 board[fromY][BOARD_RGHT-1] = EmptySquare;
8664 } else if (board[fromY][fromX] == king
8665 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8666 && toY == fromY && toX < fromX-1) {
8667 board[fromY][fromX] = EmptySquare;
8668 board[toY][toX] = king;
8669 board[toY][toX+1] = board[fromY][BOARD_LEFT];
8670 board[fromY][BOARD_LEFT] = EmptySquare;
8671 } else if (fromY == 7 && fromX == 3
8672 && board[fromY][fromX] == BlackKing
8673 && toY == 7 && toX == 5) {
8674 board[fromY][fromX] = EmptySquare;
8675 board[toY][toX] = BlackKing;
8676 board[fromY][7] = EmptySquare;
8677 board[toY][4] = BlackRook;
8678 } else if (fromY == 7 && fromX == 3
8679 && board[fromY][fromX] == BlackKing
8680 && toY == 7 && toX == 1) {
8681 board[fromY][fromX] = EmptySquare;
8682 board[toY][toX] = BlackKing;
8683 board[fromY][0] = EmptySquare;
8684 board[toY][2] = BlackRook;
8685 } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
8686 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
8689 /* black pawn promotion */
8690 board[toY][toX] = CharToPiece(ToLower(promoChar));
8691 if (board[toY][toX] == EmptySquare) {
8692 board[toY][toX] = BlackQueen;
8694 if(gameInfo.variant==VariantBughouse ||
8695 gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
8696 board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
8697 board[fromY][fromX] = EmptySquare;
8698 } else if ((fromY == 3)
8700 && gameInfo.variant != VariantXiangqi
8701 && gameInfo.variant != VariantBerolina
8702 && (board[fromY][fromX] == BlackPawn)
8703 && (board[toY][toX] == EmptySquare)) {
8704 board[fromY][fromX] = EmptySquare;
8705 board[toY][toX] = BlackPawn;
8706 captured = board[toY + 1][toX];
8707 board[toY + 1][toX] = EmptySquare;
8708 } else if ((fromY == 3)
8710 && gameInfo.variant == VariantBerolina
8711 && (board[fromY][fromX] == BlackPawn)
8712 && (board[toY][toX] == EmptySquare)) {
8713 board[fromY][fromX] = EmptySquare;
8714 board[toY][toX] = BlackPawn;
8715 if(oldEP & EP_BEROLIN_A) {
8716 captured = board[fromY][fromX-1];
8717 board[fromY][fromX-1] = EmptySquare;
8718 }else{ captured = board[fromY][fromX+1];
8719 board[fromY][fromX+1] = EmptySquare;
8722 board[toY][toX] = board[fromY][fromX];
8723 board[fromY][fromX] = EmptySquare;
8727 if (gameInfo.holdingsWidth != 0) {
8729 /* !!A lot more code needs to be written to support holdings */
8730 /* [HGM] OK, so I have written it. Holdings are stored in the */
8731 /* penultimate board files, so they are automaticlly stored */
8732 /* in the game history. */
8733 if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
8734 && promoChar && piece != WhitePawn && piece != BlackPawn) {
8735 /* Delete from holdings, by decreasing count */
8736 /* and erasing image if necessary */
8737 p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
8738 if(p < (int) BlackPawn) { /* white drop */
8739 p -= (int)WhitePawn;
8740 p = PieceToNumber((ChessSquare)p);
8741 if(p >= gameInfo.holdingsSize) p = 0;
8742 if(--board[p][BOARD_WIDTH-2] <= 0)
8743 board[p][BOARD_WIDTH-1] = EmptySquare;
8744 if((int)board[p][BOARD_WIDTH-2] < 0)
8745 board[p][BOARD_WIDTH-2] = 0;
8746 } else { /* black drop */
8747 p -= (int)BlackPawn;
8748 p = PieceToNumber((ChessSquare)p);
8749 if(p >= gameInfo.holdingsSize) p = 0;
8750 if(--board[BOARD_HEIGHT-1-p][1] <= 0)
8751 board[BOARD_HEIGHT-1-p][0] = EmptySquare;
8752 if((int)board[BOARD_HEIGHT-1-p][1] < 0)
8753 board[BOARD_HEIGHT-1-p][1] = 0;
8756 if (captured != EmptySquare && gameInfo.holdingsSize > 0
8757 && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess ) {
8758 /* [HGM] holdings: Add to holdings, if holdings exist */
8759 if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
8760 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
8761 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
8764 if (p >= (int) BlackPawn) {
8765 p -= (int)BlackPawn;
8766 if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
8767 /* in Shogi restore piece to its original first */
8768 captured = (ChessSquare) (DEMOTED captured);
8771 p = PieceToNumber((ChessSquare)p);
8772 if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
8773 board[p][BOARD_WIDTH-2]++;
8774 board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
8776 p -= (int)WhitePawn;
8777 if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
8778 captured = (ChessSquare) (DEMOTED captured);
8781 p = PieceToNumber((ChessSquare)p);
8782 if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
8783 board[BOARD_HEIGHT-1-p][1]++;
8784 board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
8787 } else if (gameInfo.variant == VariantAtomic) {
8788 if (captured != EmptySquare) {
8790 for (y = toY-1; y <= toY+1; y++) {
8791 for (x = toX-1; x <= toX+1; x++) {
8792 if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
8793 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
8794 board[y][x] = EmptySquare;
8798 board[toY][toX] = EmptySquare;
8801 if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
8802 board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
8804 if(promoChar == '+') {
8805 /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite orinary Pawn promotion) */
8806 board[toY][toX] = (ChessSquare) (PROMOTED piece);
8807 } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
8808 board[toY][toX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
8810 if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
8811 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
8812 // [HGM] superchess: take promotion piece out of holdings
8813 int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
8814 if((int)piece < (int)BlackPawn) { // determine stm from piece color
8815 if(!--board[k][BOARD_WIDTH-2])
8816 board[k][BOARD_WIDTH-1] = EmptySquare;
8818 if(!--board[BOARD_HEIGHT-1-k][1])
8819 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
8825 /* Updates forwardMostMove */
8827 MakeMove(fromX, fromY, toX, toY, promoChar)
8828 int fromX, fromY, toX, toY;
8831 // forwardMostMove++; // [HGM] bare: moved downstream
8833 if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
8834 int timeLeft; static int lastLoadFlag=0; int king, piece;
8835 piece = boards[forwardMostMove][fromY][fromX];
8836 king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
8837 if(gameInfo.variant == VariantKnightmate)
8838 king += (int) WhiteUnicorn - (int) WhiteKing;
8839 if(forwardMostMove == 0) {
8841 fprintf(serverMoves, "%s;", second.tidy);
8842 fprintf(serverMoves, "%s;", first.tidy);
8843 if(!blackPlaysFirst)
8844 fprintf(serverMoves, "%s;", second.tidy);
8845 } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
8846 lastLoadFlag = loadFlag;
8848 fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
8849 // print castling suffix
8850 if( toY == fromY && piece == king ) {
8852 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
8854 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
8857 if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
8858 boards[forwardMostMove][fromY][fromX] == BlackPawn ) &&
8859 boards[forwardMostMove][toY][toX] == EmptySquare
8860 && fromX != toX && fromY != toY)
8861 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
8863 if(promoChar != NULLCHAR)
8864 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);
8866 fprintf(serverMoves, "/%d/%d",
8867 pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
8868 if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
8869 else timeLeft = blackTimeRemaining/1000;
8870 fprintf(serverMoves, "/%d", timeLeft);
8872 fflush(serverMoves);
8875 if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations
8876 DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
8880 UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
8881 if (commentList[forwardMostMove+1] != NULL) {
8882 free(commentList[forwardMostMove+1]);
8883 commentList[forwardMostMove+1] = NULL;
8885 CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
8886 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
8887 // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
8888 SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
8889 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
8890 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
8891 gameInfo.result = GameUnfinished;
8892 if (gameInfo.resultDetails != NULL) {
8893 free(gameInfo.resultDetails);
8894 gameInfo.resultDetails = NULL;
8896 CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
8897 moveList[forwardMostMove - 1]);
8898 (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
8899 PosFlags(forwardMostMove - 1),
8900 fromY, fromX, toY, toX, promoChar,
8901 parseList[forwardMostMove - 1]);
8902 switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
8908 if(gameInfo.variant != VariantShogi)
8909 strcat(parseList[forwardMostMove - 1], "+");
8913 strcat(parseList[forwardMostMove - 1], "#");
8916 if (appData.debugMode) {
8917 fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
8922 /* Updates currentMove if not pausing */
8924 ShowMove(fromX, fromY, toX, toY)
8926 int instant = (gameMode == PlayFromGameFile) ?
8927 (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
8928 if(appData.noGUI) return;
8929 if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
8931 if (forwardMostMove == currentMove + 1) {
8932 AnimateMove(boards[forwardMostMove - 1],
8933 fromX, fromY, toX, toY);
8935 if (appData.highlightLastMove) {
8936 SetHighlights(fromX, fromY, toX, toY);
8939 currentMove = forwardMostMove;
8942 if (instant) return;
8944 DisplayMove(currentMove - 1);
8945 DrawPosition(FALSE, boards[currentMove]);
8946 DisplayBothClocks();
8947 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
8950 void SendEgtPath(ChessProgramState *cps)
8951 { /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
8952 char buf[MSG_SIZ], name[MSG_SIZ], *p;
8954 if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
8957 char c, *q = name+1, *r, *s;
8959 name[0] = ','; // extract next format name from feature and copy with prefixed ','
8960 while(*p && *p != ',') *q++ = *p++;
8962 if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
8963 strcmp(name, ",nalimov:") == 0 ) {
8964 // take nalimov path from the menu-changeable option first, if it is defined
8965 snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
8966 SendToProgram(buf,cps); // send egtbpath command for nalimov
8968 if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
8969 (s = StrStr(appData.egtFormats, name)) != NULL) {
8970 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
8971 s = r = StrStr(s, ":") + 1; // beginning of path info
8972 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
8973 c = *r; *r = 0; // temporarily null-terminate path info
8974 *--q = 0; // strip of trailig ':' from name
8975 snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
8977 SendToProgram(buf,cps); // send egtbpath command for this format
8979 if(*p == ',') p++; // read away comma to position for next format name
8984 InitChessProgram(cps, setup)
8985 ChessProgramState *cps;
8986 int setup; /* [HGM] needed to setup FRC opening position */
8988 char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
8989 if (appData.noChessProgram) return;
8990 hintRequested = FALSE;
8991 bookRequested = FALSE;
8993 /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
8994 /* moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
8995 if(cps->memSize) { /* [HGM] memory */
8996 snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
8997 SendToProgram(buf, cps);
8999 SendEgtPath(cps); /* [HGM] EGT */
9000 if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
9001 snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
9002 SendToProgram(buf, cps);
9005 SendToProgram(cps->initString, cps);
9006 if (gameInfo.variant != VariantNormal &&
9007 gameInfo.variant != VariantLoadable
9008 /* [HGM] also send variant if board size non-standard */
9009 || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
9011 char *v = VariantName(gameInfo.variant);
9012 if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
9013 /* [HGM] in protocol 1 we have to assume all variants valid */
9014 snprintf(buf, MSG_SIZ, _("Variant %s not supported by %s"), v, cps->tidy);
9015 DisplayFatalError(buf, 0, 1);
9019 /* [HGM] make prefix for non-standard board size. Awkward testing... */
9020 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9021 if( gameInfo.variant == VariantXiangqi )
9022 overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
9023 if( gameInfo.variant == VariantShogi )
9024 overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
9025 if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
9026 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
9027 if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
9028 gameInfo.variant == VariantGothic || gameInfo.variant == VariantFalcon || gameInfo.variant == VariantJanus )
9029 overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9030 if( gameInfo.variant == VariantCourier )
9031 overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9032 if( gameInfo.variant == VariantSuper )
9033 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9034 if( gameInfo.variant == VariantGreat )
9035 overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9036 if( gameInfo.variant == VariantSChess )
9037 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 7;
9040 snprintf(b, MSG_SIZ, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
9041 gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
9042 /* [HGM] varsize: try first if this defiant size variant is specifically known */
9043 if(StrStr(cps->variants, b) == NULL) {
9044 // specific sized variant not known, check if general sizing allowed
9045 if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
9046 if(StrStr(cps->variants, "boardsize") == NULL) {
9047 snprintf(buf, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
9048 gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
9049 DisplayFatalError(buf, 0, 1);
9052 /* [HGM] here we really should compare with the maximum supported board size */
9055 } else snprintf(b, MSG_SIZ,"%s", VariantName(gameInfo.variant));
9056 snprintf(buf, MSG_SIZ, "variant %s\n", b);
9057 SendToProgram(buf, cps);
9059 currentlyInitializedVariant = gameInfo.variant;
9061 /* [HGM] send opening position in FRC to first engine */
9063 SendToProgram("force\n", cps);
9065 /* engine is now in force mode! Set flag to wake it up after first move. */
9066 setboardSpoiledMachineBlack = 1;
9070 snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
9071 SendToProgram(buf, cps);
9073 cps->maybeThinking = FALSE;
9074 cps->offeredDraw = 0;
9075 if (!appData.icsActive) {
9076 SendTimeControl(cps, movesPerSession, timeControl,
9077 timeIncrement, appData.searchDepth,
9080 if (appData.showThinking
9081 // [HGM] thinking: four options require thinking output to be sent
9082 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
9084 SendToProgram("post\n", cps);
9086 SendToProgram("hard\n", cps);
9087 if (!appData.ponderNextMove) {
9088 /* Warning: "easy" is a toggle in GNU Chess, so don't send
9089 it without being sure what state we are in first. "hard"
9090 is not a toggle, so that one is OK.
9092 SendToProgram("easy\n", cps);
9095 snprintf(buf, MSG_SIZ, "ping %d\n", ++cps->lastPing);
9096 SendToProgram(buf, cps);
9098 cps->initDone = TRUE;
9103 StartChessProgram(cps)
9104 ChessProgramState *cps;
9109 if (appData.noChessProgram) return;
9110 cps->initDone = FALSE;
9112 if (strcmp(cps->host, "localhost") == 0) {
9113 err = StartChildProcess(cps->program, cps->dir, &cps->pr);
9114 } else if (*appData.remoteShell == NULLCHAR) {
9115 err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
9117 if (*appData.remoteUser == NULLCHAR) {
9118 snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
9121 snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
9122 cps->host, appData.remoteUser, cps->program);
9124 err = StartChildProcess(buf, "", &cps->pr);
9128 snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
9129 DisplayFatalError(buf, err, 1);
9135 cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
9136 if (cps->protocolVersion > 1) {
9137 snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
9138 cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
9139 cps->comboCnt = 0; // and values of combo boxes
9140 SendToProgram(buf, cps);
9142 SendToProgram("xboard\n", cps);
9148 TwoMachinesEventIfReady P((void))
9150 if (first.lastPing != first.lastPong) {
9151 DisplayMessage("", _("Waiting for first chess program"));
9152 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9155 if (second.lastPing != second.lastPong) {
9156 DisplayMessage("", _("Waiting for second chess program"));
9157 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9165 NextMatchGame P((void))
9167 int index; /* [HGM] autoinc: step load index during match */
9169 if (*appData.loadGameFile != NULLCHAR) {
9170 index = appData.loadGameIndex;
9171 if(index < 0) { // [HGM] autoinc
9172 lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
9173 if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
9175 LoadGameFromFile(appData.loadGameFile,
9177 appData.loadGameFile, FALSE);
9178 } else if (*appData.loadPositionFile != NULLCHAR) {
9179 index = appData.loadPositionIndex;
9180 if(index < 0) { // [HGM] autoinc
9181 lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
9182 if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
9184 LoadPositionFromFile(appData.loadPositionFile,
9186 appData.loadPositionFile);
9188 TwoMachinesEventIfReady();
9191 void UserAdjudicationEvent( int result )
9193 ChessMove gameResult = GameIsDrawn;
9196 gameResult = WhiteWins;
9198 else if( result < 0 ) {
9199 gameResult = BlackWins;
9202 if( gameMode == TwoMachinesPlay ) {
9203 GameEnds( gameResult, "User adjudication", GE_XBOARD );
9208 // [HGM] save: calculate checksum of game to make games easily identifiable
9209 int StringCheckSum(char *s)
9212 if(s==NULL) return 0;
9213 while(*s) i = i*259 + *s++;
9220 for(i=backwardMostMove; i<forwardMostMove; i++) {
9221 sum += pvInfoList[i].depth;
9222 sum += StringCheckSum(parseList[i]);
9223 sum += StringCheckSum(commentList[i]);
9226 if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
9227 return sum + StringCheckSum(commentList[i]);
9228 } // end of save patch
9231 GameEnds(result, resultDetails, whosays)
9233 char *resultDetails;
9236 GameMode nextGameMode;
9238 char buf[MSG_SIZ], popupRequested = 0;
9240 if(endingGame) return; /* [HGM] crash: forbid recursion */
9242 if(twoBoards) { // [HGM] dual: switch back to one board
9243 twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
9244 DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
9246 if (appData.debugMode) {
9247 fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
9248 result, resultDetails ? resultDetails : "(null)", whosays);
9251 fromX = fromY = -1; // [HGM] abort any move the user is entering.
9253 if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
9254 /* If we are playing on ICS, the server decides when the
9255 game is over, but the engine can offer to draw, claim
9259 if (appData.zippyPlay && first.initDone) {
9260 if (result == GameIsDrawn) {
9261 /* In case draw still needs to be claimed */
9262 SendToICS(ics_prefix);
9263 SendToICS("draw\n");
9264 } else if (StrCaseStr(resultDetails, "resign")) {
9265 SendToICS(ics_prefix);
9266 SendToICS("resign\n");
9270 endingGame = 0; /* [HGM] crash */
9274 /* If we're loading the game from a file, stop */
9275 if (whosays == GE_FILE) {
9276 (void) StopLoadGameTimer();
9280 /* Cancel draw offers */
9281 first.offeredDraw = second.offeredDraw = 0;
9283 /* If this is an ICS game, only ICS can really say it's done;
9284 if not, anyone can. */
9285 isIcsGame = (gameMode == IcsPlayingWhite ||
9286 gameMode == IcsPlayingBlack ||
9287 gameMode == IcsObserving ||
9288 gameMode == IcsExamining);
9290 if (!isIcsGame || whosays == GE_ICS) {
9291 /* OK -- not an ICS game, or ICS said it was done */
9293 if (!isIcsGame && !appData.noChessProgram)
9294 SetUserThinkingEnables();
9296 /* [HGM] if a machine claims the game end we verify this claim */
9297 if(gameMode == TwoMachinesPlay && appData.testClaims) {
9298 if(appData.testLegality && whosays >= GE_ENGINE1 ) {
9300 ChessMove trueResult = (ChessMove) -1;
9302 claimer = whosays == GE_ENGINE1 ? /* color of claimer */
9303 first.twoMachinesColor[0] :
9304 second.twoMachinesColor[0] ;
9306 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
9307 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
9308 /* [HGM] verify: engine mate claims accepted if they were flagged */
9309 trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
9311 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
9312 /* [HGM] verify: engine mate claims accepted if they were flagged */
9313 trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
9315 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
9316 trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
9319 // now verify win claims, but not in drop games, as we don't understand those yet
9320 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
9321 || gameInfo.variant == VariantGreat) &&
9322 (result == WhiteWins && claimer == 'w' ||
9323 result == BlackWins && claimer == 'b' ) ) { // case to verify: engine claims own win
9324 if (appData.debugMode) {
9325 fprintf(debugFP, "result=%d sp=%d move=%d\n",
9326 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
9328 if(result != trueResult) {
9329 snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
9330 result = claimer == 'w' ? BlackWins : WhiteWins;
9331 resultDetails = buf;
9334 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
9335 && (forwardMostMove <= backwardMostMove ||
9336 (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
9337 (claimer=='b')==(forwardMostMove&1))
9339 /* [HGM] verify: draws that were not flagged are false claims */
9340 snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
9341 result = claimer == 'w' ? BlackWins : WhiteWins;
9342 resultDetails = buf;
9344 /* (Claiming a loss is accepted no questions asked!) */
9346 /* [HGM] bare: don't allow bare King to win */
9347 if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
9348 && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
9349 && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
9350 && result != GameIsDrawn)
9351 { int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
9352 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
9353 int p = (signed char)boards[forwardMostMove][i][j] - color;
9354 if(p >= 0 && p <= (int)WhiteKing) k++;
9356 if (appData.debugMode) {
9357 fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
9358 result, resultDetails ? resultDetails : "(null)", whosays, k, color);
9361 result = GameIsDrawn;
9362 snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
9363 resultDetails = buf;
9369 if(serverMoves != NULL && !loadFlag) { char c = '=';
9370 if(result==WhiteWins) c = '+';
9371 if(result==BlackWins) c = '-';
9372 if(resultDetails != NULL)
9373 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
9375 if (resultDetails != NULL) {
9376 gameInfo.result = result;
9377 gameInfo.resultDetails = StrSave(resultDetails);
9379 /* display last move only if game was not loaded from file */
9380 if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
9381 DisplayMove(currentMove - 1);
9383 if (forwardMostMove != 0) {
9384 if (gameMode != PlayFromGameFile && gameMode != EditGame
9385 && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
9387 if (*appData.saveGameFile != NULLCHAR) {
9388 SaveGameToFile(appData.saveGameFile, TRUE);
9389 } else if (appData.autoSaveGames) {
9392 if (*appData.savePositionFile != NULLCHAR) {
9393 SavePositionToFile(appData.savePositionFile);
9398 /* Tell program how game ended in case it is learning */
9399 /* [HGM] Moved this to after saving the PGN, just in case */
9400 /* engine died and we got here through time loss. In that */
9401 /* case we will get a fatal error writing the pipe, which */
9402 /* would otherwise lose us the PGN. */
9403 /* [HGM] crash: not needed anymore, but doesn't hurt; */
9404 /* output during GameEnds should never be fatal anymore */
9405 if (gameMode == MachinePlaysWhite ||
9406 gameMode == MachinePlaysBlack ||
9407 gameMode == TwoMachinesPlay ||
9408 gameMode == IcsPlayingWhite ||
9409 gameMode == IcsPlayingBlack ||
9410 gameMode == BeginningOfGame) {
9412 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
9414 if (first.pr != NoProc) {
9415 SendToProgram(buf, &first);
9417 if (second.pr != NoProc &&
9418 gameMode == TwoMachinesPlay) {
9419 SendToProgram(buf, &second);
9424 if (appData.icsActive) {
9425 if (appData.quietPlay &&
9426 (gameMode == IcsPlayingWhite ||
9427 gameMode == IcsPlayingBlack)) {
9428 SendToICS(ics_prefix);
9429 SendToICS("set shout 1\n");
9431 nextGameMode = IcsIdle;
9432 ics_user_moved = FALSE;
9433 /* clean up premove. It's ugly when the game has ended and the
9434 * premove highlights are still on the board.
9438 ClearPremoveHighlights();
9439 DrawPosition(FALSE, boards[currentMove]);
9441 if (whosays == GE_ICS) {
9444 if (gameMode == IcsPlayingWhite)
9446 else if(gameMode == IcsPlayingBlack)
9450 if (gameMode == IcsPlayingBlack)
9452 else if(gameMode == IcsPlayingWhite)
9459 PlayIcsUnfinishedSound();
9462 } else if (gameMode == EditGame ||
9463 gameMode == PlayFromGameFile ||
9464 gameMode == AnalyzeMode ||
9465 gameMode == AnalyzeFile) {
9466 nextGameMode = gameMode;
9468 nextGameMode = EndOfGame;
9473 nextGameMode = gameMode;
9476 if (appData.noChessProgram) {
9477 gameMode = nextGameMode;
9479 endingGame = 0; /* [HGM] crash */
9484 /* Put first chess program into idle state */
9485 if (first.pr != NoProc &&
9486 (gameMode == MachinePlaysWhite ||
9487 gameMode == MachinePlaysBlack ||
9488 gameMode == TwoMachinesPlay ||
9489 gameMode == IcsPlayingWhite ||
9490 gameMode == IcsPlayingBlack ||
9491 gameMode == BeginningOfGame)) {
9492 SendToProgram("force\n", &first);
9493 if (first.usePing) {
9495 snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
9496 SendToProgram(buf, &first);
9499 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
9500 /* Kill off first chess program */
9501 if (first.isr != NULL)
9502 RemoveInputSource(first.isr);
9505 if (first.pr != NoProc) {
9507 DoSleep( appData.delayBeforeQuit );
9508 SendToProgram("quit\n", &first);
9509 DoSleep( appData.delayAfterQuit );
9510 DestroyChildProcess(first.pr, first.useSigterm);
9515 /* Put second chess program into idle state */
9516 if (second.pr != NoProc &&
9517 gameMode == TwoMachinesPlay) {
9518 SendToProgram("force\n", &second);
9519 if (second.usePing) {
9521 snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
9522 SendToProgram(buf, &second);
9525 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
9526 /* Kill off second chess program */
9527 if (second.isr != NULL)
9528 RemoveInputSource(second.isr);
9531 if (second.pr != NoProc) {
9532 DoSleep( appData.delayBeforeQuit );
9533 SendToProgram("quit\n", &second);
9534 DoSleep( appData.delayAfterQuit );
9535 DestroyChildProcess(second.pr, second.useSigterm);
9540 if (matchMode && gameMode == TwoMachinesPlay) {
9543 if (first.twoMachinesColor[0] == 'w') {
9550 if (first.twoMachinesColor[0] == 'b') {
9559 if (matchGame < appData.matchGames) {
9561 if(appData.sameColorGames <= 1) { /* [HGM] alternate: suppress color swap */
9562 tmp = first.twoMachinesColor;
9563 first.twoMachinesColor = second.twoMachinesColor;
9564 second.twoMachinesColor = tmp;
9566 gameMode = nextGameMode;
9568 if(appData.matchPause>10000 || appData.matchPause<10)
9569 appData.matchPause = 10000; /* [HGM] make pause adjustable */
9570 ScheduleDelayedEvent(NextMatchGame, appData.matchPause);
9571 endingGame = 0; /* [HGM] crash */
9574 gameMode = nextGameMode;
9575 snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
9576 first.tidy, second.tidy,
9577 first.matchWins, second.matchWins,
9578 appData.matchGames - (first.matchWins + second.matchWins));
9579 popupRequested++; // [HGM] crash: postpone to after resetting endingGame
9580 if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
9581 first.twoMachinesColor = "black\n";
9582 second.twoMachinesColor = "white\n";
9584 first.twoMachinesColor = "white\n";
9585 second.twoMachinesColor = "black\n";
9589 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
9590 !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
9592 gameMode = nextGameMode;
9594 endingGame = 0; /* [HGM] crash */
9595 if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
9596 if(matchMode == TRUE) DisplayFatalError(buf, 0, 0); else {
9597 matchMode = FALSE; appData.matchGames = matchGame = 0;
9603 /* Assumes program was just initialized (initString sent).
9604 Leaves program in force mode. */
9606 FeedMovesToProgram(cps, upto)
9607 ChessProgramState *cps;
9612 if (appData.debugMode)
9613 fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
9614 startedFromSetupPosition ? "position and " : "",
9615 backwardMostMove, upto, cps->which);
9616 if(currentlyInitializedVariant != gameInfo.variant) {
9618 // [HGM] variantswitch: make engine aware of new variant
9619 if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
9620 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
9621 snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
9622 SendToProgram(buf, cps);
9623 currentlyInitializedVariant = gameInfo.variant;
9625 SendToProgram("force\n", cps);
9626 if (startedFromSetupPosition) {
9627 SendBoard(cps, backwardMostMove);
9628 if (appData.debugMode) {
9629 fprintf(debugFP, "feedMoves\n");
9632 for (i = backwardMostMove; i < upto; i++) {
9633 SendMoveToProgram(i, cps);
9639 ResurrectChessProgram()
9641 /* The chess program may have exited.
9642 If so, restart it and feed it all the moves made so far. */
9644 if (appData.noChessProgram || first.pr != NoProc) return;
9646 StartChessProgram(&first);
9647 InitChessProgram(&first, FALSE);
9648 FeedMovesToProgram(&first, currentMove);
9650 if (!first.sendTime) {
9651 /* can't tell gnuchess what its clock should read,
9652 so we bow to its notion. */
9654 timeRemaining[0][currentMove] = whiteTimeRemaining;
9655 timeRemaining[1][currentMove] = blackTimeRemaining;
9658 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
9659 appData.icsEngineAnalyze) && first.analysisSupport) {
9660 SendToProgram("analyze\n", &first);
9661 first.analyzing = TRUE;
9674 if (appData.debugMode) {
9675 fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
9676 redraw, init, gameMode);
9678 CleanupTail(); // [HGM] vari: delete any stored variations
9679 pausing = pauseExamInvalid = FALSE;
9680 startedFromSetupPosition = blackPlaysFirst = FALSE;
9682 whiteFlag = blackFlag = FALSE;
9683 userOfferedDraw = FALSE;
9684 hintRequested = bookRequested = FALSE;
9685 first.maybeThinking = FALSE;
9686 second.maybeThinking = FALSE;
9687 first.bookSuspend = FALSE; // [HGM] book
9688 second.bookSuspend = FALSE;
9689 thinkOutput[0] = NULLCHAR;
9690 lastHint[0] = NULLCHAR;
9691 ClearGameInfo(&gameInfo);
9692 gameInfo.variant = StringToVariant(appData.variant);
9693 ics_user_moved = ics_clock_paused = FALSE;
9694 ics_getting_history = H_FALSE;
9696 white_holding[0] = black_holding[0] = NULLCHAR;
9697 ClearProgramStats();
9698 opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
9702 flipView = appData.flipView;
9703 ClearPremoveHighlights();
9705 alarmSounded = FALSE;
9707 GameEnds(EndOfFile, NULL, GE_PLAYER);
9708 if(appData.serverMovesName != NULL) {
9709 /* [HGM] prepare to make moves file for broadcasting */
9710 clock_t t = clock();
9711 if(serverMoves != NULL) fclose(serverMoves);
9712 serverMoves = fopen(appData.serverMovesName, "r");
9713 if(serverMoves != NULL) {
9714 fclose(serverMoves);
9715 /* delay 15 sec before overwriting, so all clients can see end */
9716 while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
9718 serverMoves = fopen(appData.serverMovesName, "w");
9722 gameMode = BeginningOfGame;
9724 if(appData.icsActive) gameInfo.variant = VariantNormal;
9725 currentMove = forwardMostMove = backwardMostMove = 0;
9726 InitPosition(redraw);
9727 for (i = 0; i < MAX_MOVES; i++) {
9728 if (commentList[i] != NULL) {
9729 free(commentList[i]);
9730 commentList[i] = NULL;
9734 timeRemaining[0][0] = whiteTimeRemaining;
9735 timeRemaining[1][0] = blackTimeRemaining;
9736 if (first.pr == NULL) {
9737 StartChessProgram(&first);
9740 InitChessProgram(&first, startedFromSetupPosition);
9743 DisplayMessage("", "");
9744 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
9745 lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
9752 if (!AutoPlayOneMove())
9754 if (matchMode || appData.timeDelay == 0)
9756 if (appData.timeDelay < 0)
9758 StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
9767 int fromX, fromY, toX, toY;
9769 if (appData.debugMode) {
9770 fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
9773 if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
9776 if (gameMode == AnalyzeFile && currentMove > backwardMostMove) {
9777 pvInfoList[currentMove].depth = programStats.depth;
9778 pvInfoList[currentMove].score = programStats.score;
9779 pvInfoList[currentMove].time = 0;
9780 if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
9783 if (currentMove >= forwardMostMove) {
9784 if(gameMode == AnalyzeFile) { ExitAnalyzeMode(); SendToProgram("force\n", &first); }
9785 gameMode = EditGame;
9788 /* [AS] Clear current move marker at the end of a game */
9789 /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
9794 toX = moveList[currentMove][2] - AAA;
9795 toY = moveList[currentMove][3] - ONE;
9797 if (moveList[currentMove][1] == '@') {
9798 if (appData.highlightLastMove) {
9799 SetHighlights(-1, -1, toX, toY);
9802 fromX = moveList[currentMove][0] - AAA;
9803 fromY = moveList[currentMove][1] - ONE;
9805 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
9807 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
9809 if (appData.highlightLastMove) {
9810 SetHighlights(fromX, fromY, toX, toY);
9813 DisplayMove(currentMove);
9814 SendMoveToProgram(currentMove++, &first);
9815 DisplayBothClocks();
9816 DrawPosition(FALSE, boards[currentMove]);
9817 // [HGM] PV info: always display, routine tests if empty
9818 DisplayComment(currentMove - 1, commentList[currentMove]);
9824 LoadGameOneMove(readAhead)
9825 ChessMove readAhead;
9827 int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
9828 char promoChar = NULLCHAR;
9833 if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
9834 gameMode != AnalyzeMode && gameMode != Training) {
9839 yyboardindex = forwardMostMove;
9840 if (readAhead != EndOfFile) {
9841 moveType = readAhead;
9843 if (gameFileFP == NULL)
9845 moveType = (ChessMove) Myylex();
9851 if (appData.debugMode)
9852 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9855 /* append the comment but don't display it */
9856 AppendComment(currentMove, p, FALSE);
9859 case WhiteCapturesEnPassant:
9860 case BlackCapturesEnPassant:
9861 case WhitePromotion:
9862 case BlackPromotion:
9863 case WhiteNonPromotion:
9864 case BlackNonPromotion:
9866 case WhiteKingSideCastle:
9867 case WhiteQueenSideCastle:
9868 case BlackKingSideCastle:
9869 case BlackQueenSideCastle:
9870 case WhiteKingSideCastleWild:
9871 case WhiteQueenSideCastleWild:
9872 case BlackKingSideCastleWild:
9873 case BlackQueenSideCastleWild:
9875 case WhiteHSideCastleFR:
9876 case WhiteASideCastleFR:
9877 case BlackHSideCastleFR:
9878 case BlackASideCastleFR:
9880 if (appData.debugMode)
9881 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
9882 fromX = currentMoveString[0] - AAA;
9883 fromY = currentMoveString[1] - ONE;
9884 toX = currentMoveString[2] - AAA;
9885 toY = currentMoveString[3] - ONE;
9886 promoChar = currentMoveString[4];
9891 if (appData.debugMode)
9892 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
9893 fromX = moveType == WhiteDrop ?
9894 (int) CharToPiece(ToUpper(currentMoveString[0])) :
9895 (int) CharToPiece(ToLower(currentMoveString[0]));
9897 toX = currentMoveString[2] - AAA;
9898 toY = currentMoveString[3] - ONE;
9904 case GameUnfinished:
9905 if (appData.debugMode)
9906 fprintf(debugFP, "Parsed game end: %s\n", yy_text);
9907 p = strchr(yy_text, '{');
9908 if (p == NULL) p = strchr(yy_text, '(');
9911 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
9913 q = strchr(p, *p == '{' ? '}' : ')');
9914 if (q != NULL) *q = NULLCHAR;
9917 while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
9918 GameEnds(moveType, p, GE_FILE);
9920 if (cmailMsgLoaded) {
9922 flipView = WhiteOnMove(currentMove);
9923 if (moveType == GameUnfinished) flipView = !flipView;
9924 if (appData.debugMode)
9925 fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
9930 if (appData.debugMode)
9931 fprintf(debugFP, "Parser hit end of file\n");
9932 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9938 if (WhiteOnMove(currentMove)) {
9939 GameEnds(BlackWins, "Black mates", GE_FILE);
9941 GameEnds(WhiteWins, "White mates", GE_FILE);
9945 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
9952 if (lastLoadGameStart == GNUChessGame) {
9953 /* GNUChessGames have numbers, but they aren't move numbers */
9954 if (appData.debugMode)
9955 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
9956 yy_text, (int) moveType);
9957 return LoadGameOneMove(EndOfFile); /* tail recursion */
9959 /* else fall thru */
9964 /* Reached start of next game in file */
9965 if (appData.debugMode)
9966 fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
9967 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9973 if (WhiteOnMove(currentMove)) {
9974 GameEnds(BlackWins, "Black mates", GE_FILE);
9976 GameEnds(WhiteWins, "White mates", GE_FILE);
9980 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
9986 case PositionDiagram: /* should not happen; ignore */
9987 case ElapsedTime: /* ignore */
9988 case NAG: /* ignore */
9989 if (appData.debugMode)
9990 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
9991 yy_text, (int) moveType);
9992 return LoadGameOneMove(EndOfFile); /* tail recursion */
9995 if (appData.testLegality) {
9996 if (appData.debugMode)
9997 fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
9998 snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
9999 (forwardMostMove / 2) + 1,
10000 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10001 DisplayError(move, 0);
10004 if (appData.debugMode)
10005 fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
10006 yy_text, currentMoveString);
10007 fromX = currentMoveString[0] - AAA;
10008 fromY = currentMoveString[1] - ONE;
10009 toX = currentMoveString[2] - AAA;
10010 toY = currentMoveString[3] - ONE;
10011 promoChar = currentMoveString[4];
10015 case AmbiguousMove:
10016 if (appData.debugMode)
10017 fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
10018 snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
10019 (forwardMostMove / 2) + 1,
10020 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10021 DisplayError(move, 0);
10026 case ImpossibleMove:
10027 if (appData.debugMode)
10028 fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
10029 snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
10030 (forwardMostMove / 2) + 1,
10031 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10032 DisplayError(move, 0);
10038 if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
10039 DrawPosition(FALSE, boards[currentMove]);
10040 DisplayBothClocks();
10041 if (!appData.matchMode) // [HGM] PV info: routine tests if empty
10042 DisplayComment(currentMove - 1, commentList[currentMove]);
10044 (void) StopLoadGameTimer();
10046 cmailOldMove = forwardMostMove;
10049 /* currentMoveString is set as a side-effect of yylex */
10051 thinkOutput[0] = NULLCHAR;
10052 MakeMove(fromX, fromY, toX, toY, promoChar);
10053 currentMove = forwardMostMove;
10058 /* Load the nth game from the given file */
10060 LoadGameFromFile(filename, n, title, useList)
10064 /*Boolean*/ int useList;
10069 if (strcmp(filename, "-") == 0) {
10073 f = fopen(filename, "rb");
10075 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10076 DisplayError(buf, errno);
10080 if (fseek(f, 0, 0) == -1) {
10081 /* f is not seekable; probably a pipe */
10084 if (useList && n == 0) {
10085 int error = GameListBuild(f);
10087 DisplayError(_("Cannot build game list"), error);
10088 } else if (!ListEmpty(&gameList) &&
10089 ((ListGame *) gameList.tailPred)->number > 1) {
10090 GameListPopUp(f, title);
10097 return LoadGame(f, n, title, FALSE);
10102 MakeRegisteredMove()
10104 int fromX, fromY, toX, toY;
10106 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
10107 switch (cmailMoveType[lastLoadGameNumber - 1]) {
10110 if (appData.debugMode)
10111 fprintf(debugFP, "Restoring %s for game %d\n",
10112 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
10114 thinkOutput[0] = NULLCHAR;
10115 safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
10116 fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
10117 fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
10118 toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
10119 toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
10120 promoChar = cmailMove[lastLoadGameNumber - 1][4];
10121 MakeMove(fromX, fromY, toX, toY, promoChar);
10122 ShowMove(fromX, fromY, toX, toY);
10124 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
10131 if (WhiteOnMove(currentMove)) {
10132 GameEnds(BlackWins, "Black mates", GE_PLAYER);
10134 GameEnds(WhiteWins, "White mates", GE_PLAYER);
10139 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
10146 if (WhiteOnMove(currentMove)) {
10147 GameEnds(BlackWins, "White resigns", GE_PLAYER);
10149 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
10154 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
10165 /* Wrapper around LoadGame for use when a Cmail message is loaded */
10167 CmailLoadGame(f, gameNumber, title, useList)
10175 if (gameNumber > nCmailGames) {
10176 DisplayError(_("No more games in this message"), 0);
10179 if (f == lastLoadGameFP) {
10180 int offset = gameNumber - lastLoadGameNumber;
10182 cmailMsg[0] = NULLCHAR;
10183 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
10184 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
10185 nCmailMovesRegistered--;
10187 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
10188 if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
10189 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
10192 if (! RegisterMove()) return FALSE;
10196 retVal = LoadGame(f, gameNumber, title, useList);
10198 /* Make move registered during previous look at this game, if any */
10199 MakeRegisteredMove();
10201 if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
10202 commentList[currentMove]
10203 = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
10204 DisplayComment(currentMove - 1, commentList[currentMove]);
10210 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
10215 int gameNumber = lastLoadGameNumber + offset;
10216 if (lastLoadGameFP == NULL) {
10217 DisplayError(_("No game has been loaded yet"), 0);
10220 if (gameNumber <= 0) {
10221 DisplayError(_("Can't back up any further"), 0);
10224 if (cmailMsgLoaded) {
10225 return CmailLoadGame(lastLoadGameFP, gameNumber,
10226 lastLoadGameTitle, lastLoadGameUseList);
10228 return LoadGame(lastLoadGameFP, gameNumber,
10229 lastLoadGameTitle, lastLoadGameUseList);
10235 /* Load the nth game from open file f */
10237 LoadGame(f, gameNumber, title, useList)
10245 int gn = gameNumber;
10246 ListGame *lg = NULL;
10247 int numPGNTags = 0;
10249 GameMode oldGameMode;
10250 VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
10252 if (appData.debugMode)
10253 fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
10255 if (gameMode == Training )
10256 SetTrainingModeOff();
10258 oldGameMode = gameMode;
10259 if (gameMode != BeginningOfGame) {
10260 Reset(FALSE, TRUE);
10264 if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
10265 fclose(lastLoadGameFP);
10269 lg = (ListGame *) ListElem(&gameList, gameNumber-1);
10272 fseek(f, lg->offset, 0);
10273 GameListHighlight(gameNumber);
10277 DisplayError(_("Game number out of range"), 0);
10282 if (fseek(f, 0, 0) == -1) {
10283 if (f == lastLoadGameFP ?
10284 gameNumber == lastLoadGameNumber + 1 :
10288 DisplayError(_("Can't seek on game file"), 0);
10293 lastLoadGameFP = f;
10294 lastLoadGameNumber = gameNumber;
10295 safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
10296 lastLoadGameUseList = useList;
10300 if (lg && lg->gameInfo.white && lg->gameInfo.black) {
10301 snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
10302 lg->gameInfo.black);
10304 } else if (*title != NULLCHAR) {
10305 if (gameNumber > 1) {
10306 snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
10309 DisplayTitle(title);
10313 if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
10314 gameMode = PlayFromGameFile;
10318 currentMove = forwardMostMove = backwardMostMove = 0;
10319 CopyBoard(boards[0], initialPosition);
10323 * Skip the first gn-1 games in the file.
10324 * Also skip over anything that precedes an identifiable
10325 * start of game marker, to avoid being confused by
10326 * garbage at the start of the file. Currently
10327 * recognized start of game markers are the move number "1",
10328 * the pattern "gnuchess .* game", the pattern
10329 * "^[#;%] [^ ]* game file", and a PGN tag block.
10330 * A game that starts with one of the latter two patterns
10331 * will also have a move number 1, possibly
10332 * following a position diagram.
10333 * 5-4-02: Let's try being more lenient and allowing a game to
10334 * start with an unnumbered move. Does that break anything?
10336 cm = lastLoadGameStart = EndOfFile;
10338 yyboardindex = forwardMostMove;
10339 cm = (ChessMove) Myylex();
10342 if (cmailMsgLoaded) {
10343 nCmailGames = CMAIL_MAX_GAMES - gn;
10346 DisplayError(_("Game not found in file"), 0);
10353 lastLoadGameStart = cm;
10356 case MoveNumberOne:
10357 switch (lastLoadGameStart) {
10362 case MoveNumberOne:
10364 gn--; /* count this game */
10365 lastLoadGameStart = cm;
10374 switch (lastLoadGameStart) {
10377 case MoveNumberOne:
10379 gn--; /* count this game */
10380 lastLoadGameStart = cm;
10383 lastLoadGameStart = cm; /* game counted already */
10391 yyboardindex = forwardMostMove;
10392 cm = (ChessMove) Myylex();
10393 } while (cm == PGNTag || cm == Comment);
10400 if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
10401 if ( cmailResult[CMAIL_MAX_GAMES - gn - 1]
10402 != CMAIL_OLD_RESULT) {
10404 cmailResult[ CMAIL_MAX_GAMES
10405 - gn - 1] = CMAIL_OLD_RESULT;
10411 /* Only a NormalMove can be at the start of a game
10412 * without a position diagram. */
10413 if (lastLoadGameStart == EndOfFile ) {
10415 lastLoadGameStart = MoveNumberOne;
10424 if (appData.debugMode)
10425 fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
10427 if (cm == XBoardGame) {
10428 /* Skip any header junk before position diagram and/or move 1 */
10430 yyboardindex = forwardMostMove;
10431 cm = (ChessMove) Myylex();
10433 if (cm == EndOfFile ||
10434 cm == GNUChessGame || cm == XBoardGame) {
10435 /* Empty game; pretend end-of-file and handle later */
10440 if (cm == MoveNumberOne || cm == PositionDiagram ||
10441 cm == PGNTag || cm == Comment)
10444 } else if (cm == GNUChessGame) {
10445 if (gameInfo.event != NULL) {
10446 free(gameInfo.event);
10448 gameInfo.event = StrSave(yy_text);
10451 startedFromSetupPosition = FALSE;
10452 while (cm == PGNTag) {
10453 if (appData.debugMode)
10454 fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
10455 err = ParsePGNTag(yy_text, &gameInfo);
10456 if (!err) numPGNTags++;
10458 /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
10459 if(gameInfo.variant != oldVariant) {
10460 startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
10461 ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
10462 InitPosition(TRUE);
10463 oldVariant = gameInfo.variant;
10464 if (appData.debugMode)
10465 fprintf(debugFP, "New variant %d\n", (int) oldVariant);
10469 if (gameInfo.fen != NULL) {
10470 Board initial_position;
10471 startedFromSetupPosition = TRUE;
10472 if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
10474 DisplayError(_("Bad FEN position in file"), 0);
10477 CopyBoard(boards[0], initial_position);
10478 if (blackPlaysFirst) {
10479 currentMove = forwardMostMove = backwardMostMove = 1;
10480 CopyBoard(boards[1], initial_position);
10481 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
10482 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
10483 timeRemaining[0][1] = whiteTimeRemaining;
10484 timeRemaining[1][1] = blackTimeRemaining;
10485 if (commentList[0] != NULL) {
10486 commentList[1] = commentList[0];
10487 commentList[0] = NULL;
10490 currentMove = forwardMostMove = backwardMostMove = 0;
10492 /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
10494 initialRulePlies = FENrulePlies;
10495 for( i=0; i< nrCastlingRights; i++ )
10496 initialRights[i] = initial_position[CASTLING][i];
10498 yyboardindex = forwardMostMove;
10499 free(gameInfo.fen);
10500 gameInfo.fen = NULL;
10503 yyboardindex = forwardMostMove;
10504 cm = (ChessMove) Myylex();
10506 /* Handle comments interspersed among the tags */
10507 while (cm == Comment) {
10509 if (appData.debugMode)
10510 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10512 AppendComment(currentMove, p, FALSE);
10513 yyboardindex = forwardMostMove;
10514 cm = (ChessMove) Myylex();
10518 /* don't rely on existence of Event tag since if game was
10519 * pasted from clipboard the Event tag may not exist
10521 if (numPGNTags > 0){
10523 if (gameInfo.variant == VariantNormal) {
10524 VariantClass v = StringToVariant(gameInfo.event);
10525 // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
10526 if(v < VariantShogi) gameInfo.variant = v;
10529 if( appData.autoDisplayTags ) {
10530 tags = PGNTags(&gameInfo);
10531 TagsPopUp(tags, CmailMsg());
10536 /* Make something up, but don't display it now */
10541 if (cm == PositionDiagram) {
10544 Board initial_position;
10546 if (appData.debugMode)
10547 fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
10549 if (!startedFromSetupPosition) {
10551 for (i = BOARD_HEIGHT - 1; i >= 0; i--)
10552 for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
10563 initial_position[i][j++] = CharToPiece(*p);
10566 while (*p == ' ' || *p == '\t' ||
10567 *p == '\n' || *p == '\r') p++;
10569 if (strncmp(p, "black", strlen("black"))==0)
10570 blackPlaysFirst = TRUE;
10572 blackPlaysFirst = FALSE;
10573 startedFromSetupPosition = TRUE;
10575 CopyBoard(boards[0], initial_position);
10576 if (blackPlaysFirst) {
10577 currentMove = forwardMostMove = backwardMostMove = 1;
10578 CopyBoard(boards[1], initial_position);
10579 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
10580 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
10581 timeRemaining[0][1] = whiteTimeRemaining;
10582 timeRemaining[1][1] = blackTimeRemaining;
10583 if (commentList[0] != NULL) {
10584 commentList[1] = commentList[0];
10585 commentList[0] = NULL;
10588 currentMove = forwardMostMove = backwardMostMove = 0;
10591 yyboardindex = forwardMostMove;
10592 cm = (ChessMove) Myylex();
10595 if (first.pr == NoProc) {
10596 StartChessProgram(&first);
10598 InitChessProgram(&first, FALSE);
10599 SendToProgram("force\n", &first);
10600 if (startedFromSetupPosition) {
10601 SendBoard(&first, forwardMostMove);
10602 if (appData.debugMode) {
10603 fprintf(debugFP, "Load Game\n");
10605 DisplayBothClocks();
10608 /* [HGM] server: flag to write setup moves in broadcast file as one */
10609 loadFlag = appData.suppressLoadMoves;
10611 while (cm == Comment) {
10613 if (appData.debugMode)
10614 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10616 AppendComment(currentMove, p, FALSE);
10617 yyboardindex = forwardMostMove;
10618 cm = (ChessMove) Myylex();
10621 if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
10622 cm == WhiteWins || cm == BlackWins ||
10623 cm == GameIsDrawn || cm == GameUnfinished) {
10624 DisplayMessage("", _("No moves in game"));
10625 if (cmailMsgLoaded) {
10626 if (appData.debugMode)
10627 fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
10631 DrawPosition(FALSE, boards[currentMove]);
10632 DisplayBothClocks();
10633 gameMode = EditGame;
10640 // [HGM] PV info: routine tests if comment empty
10641 if (!matchMode && (pausing || appData.timeDelay != 0)) {
10642 DisplayComment(currentMove - 1, commentList[currentMove]);
10644 if (!matchMode && appData.timeDelay != 0)
10645 DrawPosition(FALSE, boards[currentMove]);
10647 if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
10648 programStats.ok_to_send = 1;
10651 /* if the first token after the PGN tags is a move
10652 * and not move number 1, retrieve it from the parser
10654 if (cm != MoveNumberOne)
10655 LoadGameOneMove(cm);
10657 /* load the remaining moves from the file */
10658 while (LoadGameOneMove(EndOfFile)) {
10659 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10660 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10663 /* rewind to the start of the game */
10664 currentMove = backwardMostMove;
10666 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10668 if (oldGameMode == AnalyzeFile ||
10669 oldGameMode == AnalyzeMode) {
10670 AnalyzeFileEvent();
10673 if (matchMode || appData.timeDelay == 0) {
10675 gameMode = EditGame;
10677 } else if (appData.timeDelay > 0) {
10678 AutoPlayGameLoop();
10681 if (appData.debugMode)
10682 fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
10684 loadFlag = 0; /* [HGM] true game starts */
10688 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
10690 ReloadPosition(offset)
10693 int positionNumber = lastLoadPositionNumber + offset;
10694 if (lastLoadPositionFP == NULL) {
10695 DisplayError(_("No position has been loaded yet"), 0);
10698 if (positionNumber <= 0) {
10699 DisplayError(_("Can't back up any further"), 0);
10702 return LoadPosition(lastLoadPositionFP, positionNumber,
10703 lastLoadPositionTitle);
10706 /* Load the nth position from the given file */
10708 LoadPositionFromFile(filename, n, title)
10716 if (strcmp(filename, "-") == 0) {
10717 return LoadPosition(stdin, n, "stdin");
10719 f = fopen(filename, "rb");
10721 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10722 DisplayError(buf, errno);
10725 return LoadPosition(f, n, title);
10730 /* Load the nth position from the given open file, and close it */
10732 LoadPosition(f, positionNumber, title)
10734 int positionNumber;
10737 char *p, line[MSG_SIZ];
10738 Board initial_position;
10739 int i, j, fenMode, pn;
10741 if (gameMode == Training )
10742 SetTrainingModeOff();
10744 if (gameMode != BeginningOfGame) {
10745 Reset(FALSE, TRUE);
10747 if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
10748 fclose(lastLoadPositionFP);
10750 if (positionNumber == 0) positionNumber = 1;
10751 lastLoadPositionFP = f;
10752 lastLoadPositionNumber = positionNumber;
10753 safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
10754 if (first.pr == NoProc) {
10755 StartChessProgram(&first);
10756 InitChessProgram(&first, FALSE);
10758 pn = positionNumber;
10759 if (positionNumber < 0) {
10760 /* Negative position number means to seek to that byte offset */
10761 if (fseek(f, -positionNumber, 0) == -1) {
10762 DisplayError(_("Can't seek on position file"), 0);
10767 if (fseek(f, 0, 0) == -1) {
10768 if (f == lastLoadPositionFP ?
10769 positionNumber == lastLoadPositionNumber + 1 :
10770 positionNumber == 1) {
10773 DisplayError(_("Can't seek on position file"), 0);
10778 /* See if this file is FEN or old-style xboard */
10779 if (fgets(line, MSG_SIZ, f) == NULL) {
10780 DisplayError(_("Position not found in file"), 0);
10783 // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
10784 fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
10787 if (fenMode || line[0] == '#') pn--;
10789 /* skip positions before number pn */
10790 if (fgets(line, MSG_SIZ, f) == NULL) {
10792 DisplayError(_("Position not found in file"), 0);
10795 if (fenMode || line[0] == '#') pn--;
10800 if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
10801 DisplayError(_("Bad FEN position in file"), 0);
10805 (void) fgets(line, MSG_SIZ, f);
10806 (void) fgets(line, MSG_SIZ, f);
10808 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
10809 (void) fgets(line, MSG_SIZ, f);
10810 for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
10813 initial_position[i][j++] = CharToPiece(*p);
10817 blackPlaysFirst = FALSE;
10819 (void) fgets(line, MSG_SIZ, f);
10820 if (strncmp(line, "black", strlen("black"))==0)
10821 blackPlaysFirst = TRUE;
10824 startedFromSetupPosition = TRUE;
10826 SendToProgram("force\n", &first);
10827 CopyBoard(boards[0], initial_position);
10828 if (blackPlaysFirst) {
10829 currentMove = forwardMostMove = backwardMostMove = 1;
10830 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
10831 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
10832 CopyBoard(boards[1], initial_position);
10833 DisplayMessage("", _("Black to play"));
10835 currentMove = forwardMostMove = backwardMostMove = 0;
10836 DisplayMessage("", _("White to play"));
10838 initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
10839 SendBoard(&first, forwardMostMove);
10840 if (appData.debugMode) {
10842 for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
10843 for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
10844 fprintf(debugFP, "Load Position\n");
10847 if (positionNumber > 1) {
10848 snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
10849 DisplayTitle(line);
10851 DisplayTitle(title);
10853 gameMode = EditGame;
10856 timeRemaining[0][1] = whiteTimeRemaining;
10857 timeRemaining[1][1] = blackTimeRemaining;
10858 DrawPosition(FALSE, boards[currentMove]);
10865 CopyPlayerNameIntoFileName(dest, src)
10868 while (*src != NULLCHAR && *src != ',') {
10873 *(*dest)++ = *src++;
10878 char *DefaultFileName(ext)
10881 static char def[MSG_SIZ];
10884 if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
10886 CopyPlayerNameIntoFileName(&p, gameInfo.white);
10888 CopyPlayerNameIntoFileName(&p, gameInfo.black);
10890 safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
10897 /* Save the current game to the given file */
10899 SaveGameToFile(filename, append)
10906 if (strcmp(filename, "-") == 0) {
10907 return SaveGame(stdout, 0, NULL);
10909 f = fopen(filename, append ? "a" : "w");
10911 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10912 DisplayError(buf, errno);
10915 return SaveGame(f, 0, NULL);
10924 static char buf[MSG_SIZ];
10927 p = strchr(str, ' ');
10928 if (p == NULL) return str;
10929 strncpy(buf, str, p - str);
10930 buf[p - str] = NULLCHAR;
10934 #define PGN_MAX_LINE 75
10936 #define PGN_SIDE_WHITE 0
10937 #define PGN_SIDE_BLACK 1
10940 static int FindFirstMoveOutOfBook( int side )
10944 if( backwardMostMove == 0 && ! startedFromSetupPosition) {
10945 int index = backwardMostMove;
10946 int has_book_hit = 0;
10948 if( (index % 2) != side ) {
10952 while( index < forwardMostMove ) {
10953 /* Check to see if engine is in book */
10954 int depth = pvInfoList[index].depth;
10955 int score = pvInfoList[index].score;
10961 else if( score == 0 && depth == 63 ) {
10962 in_book = 1; /* Zappa */
10964 else if( score == 2 && depth == 99 ) {
10965 in_book = 1; /* Abrok */
10968 has_book_hit += in_book;
10984 void GetOutOfBookInfo( char * buf )
10988 int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10990 oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
10991 oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
10995 if( oob[0] >= 0 || oob[1] >= 0 ) {
10996 for( i=0; i<2; i++ ) {
11000 if( i > 0 && oob[0] >= 0 ) {
11001 strcat( buf, " " );
11004 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
11005 sprintf( buf+strlen(buf), "%s%.2f",
11006 pvInfoList[idx].score >= 0 ? "+" : "",
11007 pvInfoList[idx].score / 100.0 );
11013 /* Save game in PGN style and close the file */
11018 int i, offset, linelen, newblock;
11022 int movelen, numlen, blank;
11023 char move_buffer[100]; /* [AS] Buffer for move+PV info */
11025 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
11027 tm = time((time_t *) NULL);
11029 PrintPGNTags(f, &gameInfo);
11031 if (backwardMostMove > 0 || startedFromSetupPosition) {
11032 char *fen = PositionToFEN(backwardMostMove, NULL);
11033 fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
11034 fprintf(f, "\n{--------------\n");
11035 PrintPosition(f, backwardMostMove);
11036 fprintf(f, "--------------}\n");
11040 /* [AS] Out of book annotation */
11041 if( appData.saveOutOfBookInfo ) {
11044 GetOutOfBookInfo( buf );
11046 if( buf[0] != '\0' ) {
11047 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
11054 i = backwardMostMove;
11058 while (i < forwardMostMove) {
11059 /* Print comments preceding this move */
11060 if (commentList[i] != NULL) {
11061 if (linelen > 0) fprintf(f, "\n");
11062 fprintf(f, "%s", commentList[i]);
11067 /* Format move number */
11069 snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
11072 snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
11074 numtext[0] = NULLCHAR;
11076 numlen = strlen(numtext);
11079 /* Print move number */
11080 blank = linelen > 0 && numlen > 0;
11081 if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
11090 fprintf(f, "%s", numtext);
11094 safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
11095 movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
11098 blank = linelen > 0 && movelen > 0;
11099 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
11108 fprintf(f, "%s", move_buffer);
11109 linelen += movelen;
11111 /* [AS] Add PV info if present */
11112 if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
11113 /* [HGM] add time */
11114 char buf[MSG_SIZ]; int seconds;
11116 seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
11122 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
11125 seconds = (seconds + 4)/10; // round to full seconds
11127 snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
11129 snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
11132 snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
11133 pvInfoList[i].score >= 0 ? "+" : "",
11134 pvInfoList[i].score / 100.0,
11135 pvInfoList[i].depth,
11138 movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
11140 /* Print score/depth */
11141 blank = linelen > 0 && movelen > 0;
11142 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
11151 fprintf(f, "%s", move_buffer);
11152 linelen += movelen;
11158 /* Start a new line */
11159 if (linelen > 0) fprintf(f, "\n");
11161 /* Print comments after last move */
11162 if (commentList[i] != NULL) {
11163 fprintf(f, "%s\n", commentList[i]);
11167 if (gameInfo.resultDetails != NULL &&
11168 gameInfo.resultDetails[0] != NULLCHAR) {
11169 fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
11170 PGNResult(gameInfo.result));
11172 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
11176 lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
11180 /* Save game in old style and close the file */
11182 SaveGameOldStyle(f)
11188 tm = time((time_t *) NULL);
11190 fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
11193 if (backwardMostMove > 0 || startedFromSetupPosition) {
11194 fprintf(f, "\n[--------------\n");
11195 PrintPosition(f, backwardMostMove);
11196 fprintf(f, "--------------]\n");
11201 i = backwardMostMove;
11202 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
11204 while (i < forwardMostMove) {
11205 if (commentList[i] != NULL) {
11206 fprintf(f, "[%s]\n", commentList[i]);
11209 if ((i % 2) == 1) {
11210 fprintf(f, "%d. ... %s\n", (i - offset)/2 + 1, parseList[i]);
11213 fprintf(f, "%d. %s ", (i - offset)/2 + 1, parseList[i]);
11215 if (commentList[i] != NULL) {
11219 if (i >= forwardMostMove) {
11223 fprintf(f, "%s\n", parseList[i]);
11228 if (commentList[i] != NULL) {
11229 fprintf(f, "[%s]\n", commentList[i]);
11232 /* This isn't really the old style, but it's close enough */
11233 if (gameInfo.resultDetails != NULL &&
11234 gameInfo.resultDetails[0] != NULLCHAR) {
11235 fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
11236 gameInfo.resultDetails);
11238 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
11245 /* Save the current game to open file f and close the file */
11247 SaveGame(f, dummy, dummy2)
11252 if (gameMode == EditPosition) EditPositionDone(TRUE);
11253 lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
11254 if (appData.oldSaveStyle)
11255 return SaveGameOldStyle(f);
11257 return SaveGamePGN(f);
11260 /* Save the current position to the given file */
11262 SavePositionToFile(filename)
11268 if (strcmp(filename, "-") == 0) {
11269 return SavePosition(stdout, 0, NULL);
11271 f = fopen(filename, "a");
11273 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
11274 DisplayError(buf, errno);
11277 SavePosition(f, 0, NULL);
11283 /* Save the current position to the given open file and close the file */
11285 SavePosition(f, dummy, dummy2)
11293 if (gameMode == EditPosition) EditPositionDone(TRUE);
11294 if (appData.oldSaveStyle) {
11295 tm = time((time_t *) NULL);
11297 fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
11299 fprintf(f, "[--------------\n");
11300 PrintPosition(f, currentMove);
11301 fprintf(f, "--------------]\n");
11303 fen = PositionToFEN(currentMove, NULL);
11304 fprintf(f, "%s\n", fen);
11312 ReloadCmailMsgEvent(unregister)
11316 static char *inFilename = NULL;
11317 static char *outFilename;
11319 struct stat inbuf, outbuf;
11322 /* Any registered moves are unregistered if unregister is set, */
11323 /* i.e. invoked by the signal handler */
11325 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
11326 cmailMoveRegistered[i] = FALSE;
11327 if (cmailCommentList[i] != NULL) {
11328 free(cmailCommentList[i]);
11329 cmailCommentList[i] = NULL;
11332 nCmailMovesRegistered = 0;
11335 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
11336 cmailResult[i] = CMAIL_NOT_RESULT;
11340 if (inFilename == NULL) {
11341 /* Because the filenames are static they only get malloced once */
11342 /* and they never get freed */
11343 inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
11344 sprintf(inFilename, "%s.game.in", appData.cmailGameName);
11346 outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
11347 sprintf(outFilename, "%s.out", appData.cmailGameName);
11350 status = stat(outFilename, &outbuf);
11352 cmailMailedMove = FALSE;
11354 status = stat(inFilename, &inbuf);
11355 cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
11358 /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
11359 counts the games, notes how each one terminated, etc.
11361 It would be nice to remove this kludge and instead gather all
11362 the information while building the game list. (And to keep it
11363 in the game list nodes instead of having a bunch of fixed-size
11364 parallel arrays.) Note this will require getting each game's
11365 termination from the PGN tags, as the game list builder does
11366 not process the game moves. --mann
11368 cmailMsgLoaded = TRUE;
11369 LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
11371 /* Load first game in the file or popup game menu */
11372 LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
11374 #endif /* !WIN32 */
11382 char string[MSG_SIZ];
11384 if ( cmailMailedMove
11385 || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
11386 return TRUE; /* Allow free viewing */
11389 /* Unregister move to ensure that we don't leave RegisterMove */
11390 /* with the move registered when the conditions for registering no */
11392 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11393 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
11394 nCmailMovesRegistered --;
11396 if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
11398 free(cmailCommentList[lastLoadGameNumber - 1]);
11399 cmailCommentList[lastLoadGameNumber - 1] = NULL;
11403 if (cmailOldMove == -1) {
11404 DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
11408 if (currentMove > cmailOldMove + 1) {
11409 DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
11413 if (currentMove < cmailOldMove) {
11414 DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
11418 if (forwardMostMove > currentMove) {
11419 /* Silently truncate extra moves */
11423 if ( (currentMove == cmailOldMove + 1)
11424 || ( (currentMove == cmailOldMove)
11425 && ( (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
11426 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
11427 if (gameInfo.result != GameUnfinished) {
11428 cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
11431 if (commentList[currentMove] != NULL) {
11432 cmailCommentList[lastLoadGameNumber - 1]
11433 = StrSave(commentList[currentMove]);
11435 safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
11437 if (appData.debugMode)
11438 fprintf(debugFP, "Saving %s for game %d\n",
11439 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
11441 snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
11443 f = fopen(string, "w");
11444 if (appData.oldSaveStyle) {
11445 SaveGameOldStyle(f); /* also closes the file */
11447 snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
11448 f = fopen(string, "w");
11449 SavePosition(f, 0, NULL); /* also closes the file */
11451 fprintf(f, "{--------------\n");
11452 PrintPosition(f, currentMove);
11453 fprintf(f, "--------------}\n\n");
11455 SaveGame(f, 0, NULL); /* also closes the file*/
11458 cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
11459 nCmailMovesRegistered ++;
11460 } else if (nCmailGames == 1) {
11461 DisplayError(_("You have not made a move yet"), 0);
11472 static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
11473 FILE *commandOutput;
11474 char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
11475 int nBytes = 0; /* Suppress warnings on uninitialized variables */
11481 if (! cmailMsgLoaded) {
11482 DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
11486 if (nCmailGames == nCmailResults) {
11487 DisplayError(_("No unfinished games"), 0);
11491 #if CMAIL_PROHIBIT_REMAIL
11492 if (cmailMailedMove) {
11493 snprintf(msg, MSG_SIZ, _("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);
11494 DisplayError(msg, 0);
11499 if (! (cmailMailedMove || RegisterMove())) return;
11501 if ( cmailMailedMove
11502 || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
11503 snprintf(string, MSG_SIZ, partCommandString,
11504 appData.debugMode ? " -v" : "", appData.cmailGameName);
11505 commandOutput = popen(string, "r");
11507 if (commandOutput == NULL) {
11508 DisplayError(_("Failed to invoke cmail"), 0);
11510 for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
11511 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
11513 if (nBuffers > 1) {
11514 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
11515 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
11516 nBytes = MSG_SIZ - 1;
11518 (void) memcpy(msg, buffer, nBytes);
11520 *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
11522 if(StrStr(msg, "Mailed cmail message to ") != NULL) {
11523 cmailMailedMove = TRUE; /* Prevent >1 moves */
11526 for (i = 0; i < nCmailGames; i ++) {
11527 if (cmailResult[i] == CMAIL_NOT_RESULT) {
11532 && ( (arcDir = (char *) getenv("CMAIL_ARCDIR"))
11534 snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
11536 appData.cmailGameName,
11538 LoadGameFromFile(buffer, 1, buffer, FALSE);
11539 cmailMsgLoaded = FALSE;
11543 DisplayInformation(msg);
11544 pclose(commandOutput);
11547 if ((*cmailMsg) != '\0') {
11548 DisplayInformation(cmailMsg);
11553 #endif /* !WIN32 */
11562 int prependComma = 0;
11564 char string[MSG_SIZ]; /* Space for game-list */
11567 if (!cmailMsgLoaded) return "";
11569 if (cmailMailedMove) {
11570 snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
11572 /* Create a list of games left */
11573 snprintf(string, MSG_SIZ, "[");
11574 for (i = 0; i < nCmailGames; i ++) {
11575 if (! ( cmailMoveRegistered[i]
11576 || (cmailResult[i] == CMAIL_OLD_RESULT))) {
11577 if (prependComma) {
11578 snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
11580 snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
11584 strcat(string, number);
11587 strcat(string, "]");
11589 if (nCmailMovesRegistered + nCmailResults == 0) {
11590 switch (nCmailGames) {
11592 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
11596 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
11600 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
11605 switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
11607 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
11612 if (nCmailResults == nCmailGames) {
11613 snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
11615 snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
11620 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
11632 if (gameMode == Training)
11633 SetTrainingModeOff();
11636 cmailMsgLoaded = FALSE;
11637 if (appData.icsActive) {
11638 SendToICS(ics_prefix);
11639 SendToICS("refresh\n");
11649 /* Give up on clean exit */
11653 /* Keep trying for clean exit */
11657 if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
11659 if (telnetISR != NULL) {
11660 RemoveInputSource(telnetISR);
11662 if (icsPR != NoProc) {
11663 DestroyChildProcess(icsPR, TRUE);
11666 /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
11667 GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
11669 /* [HGM] crash: the above GameEnds() is a dud if another one was running */
11670 /* make sure this other one finishes before killing it! */
11671 if(endingGame) { int count = 0;
11672 if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
11673 while(endingGame && count++ < 10) DoSleep(1);
11674 if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
11677 /* Kill off chess programs */
11678 if (first.pr != NoProc) {
11681 DoSleep( appData.delayBeforeQuit );
11682 SendToProgram("quit\n", &first);
11683 DoSleep( appData.delayAfterQuit );
11684 DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
11686 if (second.pr != NoProc) {
11687 DoSleep( appData.delayBeforeQuit );
11688 SendToProgram("quit\n", &second);
11689 DoSleep( appData.delayAfterQuit );
11690 DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
11692 if (first.isr != NULL) {
11693 RemoveInputSource(first.isr);
11695 if (second.isr != NULL) {
11696 RemoveInputSource(second.isr);
11699 ShutDownFrontEnd();
11706 if (appData.debugMode)
11707 fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
11711 if (gameMode == MachinePlaysWhite ||
11712 gameMode == MachinePlaysBlack) {
11715 DisplayBothClocks();
11717 if (gameMode == PlayFromGameFile) {
11718 if (appData.timeDelay >= 0)
11719 AutoPlayGameLoop();
11720 } else if (gameMode == IcsExamining && pauseExamInvalid) {
11721 Reset(FALSE, TRUE);
11722 SendToICS(ics_prefix);
11723 SendToICS("refresh\n");
11724 } else if (currentMove < forwardMostMove) {
11725 ForwardInner(forwardMostMove);
11727 pauseExamInvalid = FALSE;
11729 switch (gameMode) {
11733 pauseExamForwardMostMove = forwardMostMove;
11734 pauseExamInvalid = FALSE;
11737 case IcsPlayingWhite:
11738 case IcsPlayingBlack:
11742 case PlayFromGameFile:
11743 (void) StopLoadGameTimer();
11747 case BeginningOfGame:
11748 if (appData.icsActive) return;
11749 /* else fall through */
11750 case MachinePlaysWhite:
11751 case MachinePlaysBlack:
11752 case TwoMachinesPlay:
11753 if (forwardMostMove == 0)
11754 return; /* don't pause if no one has moved */
11755 if ((gameMode == MachinePlaysWhite &&
11756 !WhiteOnMove(forwardMostMove)) ||
11757 (gameMode == MachinePlaysBlack &&
11758 WhiteOnMove(forwardMostMove))) {
11771 char title[MSG_SIZ];
11773 if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
11774 safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
11776 snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
11777 WhiteOnMove(currentMove - 1) ? " " : ".. ",
11778 parseList[currentMove - 1]);
11781 EditCommentPopUp(currentMove, title, commentList[currentMove]);
11788 char *tags = PGNTags(&gameInfo);
11789 EditTagsPopUp(tags, NULL);
11796 if (appData.noChessProgram || gameMode == AnalyzeMode)
11799 if (gameMode != AnalyzeFile) {
11800 if (!appData.icsEngineAnalyze) {
11802 if (gameMode != EditGame) return;
11804 ResurrectChessProgram();
11805 SendToProgram("analyze\n", &first);
11806 first.analyzing = TRUE;
11807 /*first.maybeThinking = TRUE;*/
11808 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11809 EngineOutputPopUp();
11811 if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
11816 StartAnalysisClock();
11817 GetTimeMark(&lastNodeCountTime);
11824 if (appData.noChessProgram || gameMode == AnalyzeFile)
11827 if (gameMode != AnalyzeMode) {
11829 if (gameMode != EditGame) return;
11830 ResurrectChessProgram();
11831 SendToProgram("analyze\n", &first);
11832 first.analyzing = TRUE;
11833 /*first.maybeThinking = TRUE;*/
11834 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11835 EngineOutputPopUp();
11837 gameMode = AnalyzeFile;
11842 StartAnalysisClock();
11843 GetTimeMark(&lastNodeCountTime);
11848 MachineWhiteEvent()
11851 char *bookHit = NULL;
11853 if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
11857 if (gameMode == PlayFromGameFile ||
11858 gameMode == TwoMachinesPlay ||
11859 gameMode == Training ||
11860 gameMode == AnalyzeMode ||
11861 gameMode == EndOfGame)
11864 if (gameMode == EditPosition)
11865 EditPositionDone(TRUE);
11867 if (!WhiteOnMove(currentMove)) {
11868 DisplayError(_("It is not White's turn"), 0);
11872 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
11875 if (gameMode == EditGame || gameMode == AnalyzeMode ||
11876 gameMode == AnalyzeFile)
11879 ResurrectChessProgram(); /* in case it isn't running */
11880 if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
11881 gameMode = MachinePlaysWhite;
11884 gameMode = MachinePlaysWhite;
11888 snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
11890 if (first.sendName) {
11891 snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
11892 SendToProgram(buf, &first);
11894 if (first.sendTime) {
11895 if (first.useColors) {
11896 SendToProgram("black\n", &first); /*gnu kludge*/
11898 SendTimeRemaining(&first, TRUE);
11900 if (first.useColors) {
11901 SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
11903 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
11904 SetMachineThinkingEnables();
11905 first.maybeThinking = TRUE;
11909 if (appData.autoFlipView && !flipView) {
11910 flipView = !flipView;
11911 DrawPosition(FALSE, NULL);
11912 DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;
11915 if(bookHit) { // [HGM] book: simulate book reply
11916 static char bookMove[MSG_SIZ]; // a bit generous?
11918 programStats.nodes = programStats.depth = programStats.time =
11919 programStats.score = programStats.got_only_move = 0;
11920 sprintf(programStats.movelist, "%s (xbook)", bookHit);
11922 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
11923 strcat(bookMove, bookHit);
11924 HandleMachineMove(bookMove, &first);
11929 MachineBlackEvent()
11932 char *bookHit = NULL;
11934 if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
11938 if (gameMode == PlayFromGameFile ||
11939 gameMode == TwoMachinesPlay ||
11940 gameMode == Training ||
11941 gameMode == AnalyzeMode ||
11942 gameMode == EndOfGame)
11945 if (gameMode == EditPosition)
11946 EditPositionDone(TRUE);
11948 if (WhiteOnMove(currentMove)) {
11949 DisplayError(_("It is not Black's turn"), 0);
11953 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
11956 if (gameMode == EditGame || gameMode == AnalyzeMode ||
11957 gameMode == AnalyzeFile)
11960 ResurrectChessProgram(); /* in case it isn't running */
11961 gameMode = MachinePlaysBlack;
11965 snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
11967 if (first.sendName) {
11968 snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
11969 SendToProgram(buf, &first);
11971 if (first.sendTime) {
11972 if (first.useColors) {
11973 SendToProgram("white\n", &first); /*gnu kludge*/
11975 SendTimeRemaining(&first, FALSE);
11977 if (first.useColors) {
11978 SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
11980 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
11981 SetMachineThinkingEnables();
11982 first.maybeThinking = TRUE;
11985 if (appData.autoFlipView && flipView) {
11986 flipView = !flipView;
11987 DrawPosition(FALSE, NULL);
11988 DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;
11990 if(bookHit) { // [HGM] book: simulate book reply
11991 static char bookMove[MSG_SIZ]; // a bit generous?
11993 programStats.nodes = programStats.depth = programStats.time =
11994 programStats.score = programStats.got_only_move = 0;
11995 sprintf(programStats.movelist, "%s (xbook)", bookHit);
11997 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
11998 strcat(bookMove, bookHit);
11999 HandleMachineMove(bookMove, &first);
12005 DisplayTwoMachinesTitle()
12008 if (appData.matchGames > 0) {
12009 if (first.twoMachinesColor[0] == 'w') {
12010 snprintf(buf, MSG_SIZ, "%s vs. %s (%d-%d-%d)",
12011 gameInfo.white, gameInfo.black,
12012 first.matchWins, second.matchWins,
12013 matchGame - 1 - (first.matchWins + second.matchWins));
12015 snprintf(buf, MSG_SIZ, "%s vs. %s (%d-%d-%d)",
12016 gameInfo.white, gameInfo.black,
12017 second.matchWins, first.matchWins,
12018 matchGame - 1 - (first.matchWins + second.matchWins));
12021 snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
12027 SettingsMenuIfReady()
12029 if (second.lastPing != second.lastPong) {
12030 DisplayMessage("", _("Waiting for second chess program"));
12031 ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
12035 DisplayMessage("", "");
12036 SettingsPopUp(&second);
12040 WaitForSecond(DelayedEventCallback retry)
12042 if (second.pr == NULL) {
12043 StartChessProgram(&second);
12044 if (second.protocolVersion == 1) {
12047 /* kludge: allow timeout for initial "feature" command */
12049 DisplayMessage("", _("Starting second chess program"));
12050 ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
12058 TwoMachinesEvent P((void))
12062 ChessProgramState *onmove;
12063 char *bookHit = NULL;
12065 if (appData.noChessProgram) return;
12067 switch (gameMode) {
12068 case TwoMachinesPlay:
12070 case MachinePlaysWhite:
12071 case MachinePlaysBlack:
12072 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
12073 DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
12077 case BeginningOfGame:
12078 case PlayFromGameFile:
12081 if (gameMode != EditGame) return;
12084 EditPositionDone(TRUE);
12095 // forwardMostMove = currentMove;
12096 TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
12097 ResurrectChessProgram(); /* in case first program isn't running */
12099 if(WaitForSecond(TwoMachinesEventIfReady)) return;
12100 DisplayMessage("", "");
12101 InitChessProgram(&second, FALSE);
12102 SendToProgram("force\n", &second);
12103 if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
12104 ScheduleDelayedEvent(TwoMachinesEvent, 10);
12107 if (startedFromSetupPosition) {
12108 SendBoard(&second, backwardMostMove);
12109 if (appData.debugMode) {
12110 fprintf(debugFP, "Two Machines\n");
12113 for (i = backwardMostMove; i < forwardMostMove; i++) {
12114 SendMoveToProgram(i, &second);
12117 gameMode = TwoMachinesPlay;
12121 DisplayTwoMachinesTitle();
12123 if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
12129 SendToProgram(first.computerString, &first);
12130 if (first.sendName) {
12131 snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
12132 SendToProgram(buf, &first);
12134 SendToProgram(second.computerString, &second);
12135 if (second.sendName) {
12136 snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
12137 SendToProgram(buf, &second);
12141 if (!first.sendTime || !second.sendTime) {
12142 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12143 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12145 if (onmove->sendTime) {
12146 if (onmove->useColors) {
12147 SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
12149 SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
12151 if (onmove->useColors) {
12152 SendToProgram(onmove->twoMachinesColor, onmove);
12154 bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
12155 // SendToProgram("go\n", onmove);
12156 onmove->maybeThinking = TRUE;
12157 SetMachineThinkingEnables();
12161 if(bookHit) { // [HGM] book: simulate book reply
12162 static char bookMove[MSG_SIZ]; // a bit generous?
12164 programStats.nodes = programStats.depth = programStats.time =
12165 programStats.score = programStats.got_only_move = 0;
12166 sprintf(programStats.movelist, "%s (xbook)", bookHit);
12168 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
12169 strcat(bookMove, bookHit);
12170 savedMessage = bookMove; // args for deferred call
12171 savedState = onmove;
12172 ScheduleDelayedEvent(DeferredBookMove, 1);
12179 if (gameMode == Training) {
12180 SetTrainingModeOff();
12181 gameMode = PlayFromGameFile;
12182 DisplayMessage("", _("Training mode off"));
12184 gameMode = Training;
12185 animateTraining = appData.animate;
12187 /* make sure we are not already at the end of the game */
12188 if (currentMove < forwardMostMove) {
12189 SetTrainingModeOn();
12190 DisplayMessage("", _("Training mode on"));
12192 gameMode = PlayFromGameFile;
12193 DisplayError(_("Already at end of game"), 0);
12202 if (!appData.icsActive) return;
12203 switch (gameMode) {
12204 case IcsPlayingWhite:
12205 case IcsPlayingBlack:
12208 case BeginningOfGame:
12216 EditPositionDone(TRUE);
12229 gameMode = IcsIdle;
12240 switch (gameMode) {
12242 SetTrainingModeOff();
12244 case MachinePlaysWhite:
12245 case MachinePlaysBlack:
12246 case BeginningOfGame:
12247 SendToProgram("force\n", &first);
12248 SetUserThinkingEnables();
12250 case PlayFromGameFile:
12251 (void) StopLoadGameTimer();
12252 if (gameFileFP != NULL) {
12257 EditPositionDone(TRUE);
12262 SendToProgram("force\n", &first);
12264 case TwoMachinesPlay:
12265 GameEnds(EndOfFile, NULL, GE_PLAYER);
12266 ResurrectChessProgram();
12267 SetUserThinkingEnables();
12270 ResurrectChessProgram();
12272 case IcsPlayingBlack:
12273 case IcsPlayingWhite:
12274 DisplayError(_("Warning: You are still playing a game"), 0);
12277 DisplayError(_("Warning: You are still observing a game"), 0);
12280 DisplayError(_("Warning: You are still examining a game"), 0);
12291 first.offeredDraw = second.offeredDraw = 0;
12293 if (gameMode == PlayFromGameFile) {
12294 whiteTimeRemaining = timeRemaining[0][currentMove];
12295 blackTimeRemaining = timeRemaining[1][currentMove];
12299 if (gameMode == MachinePlaysWhite ||
12300 gameMode == MachinePlaysBlack ||
12301 gameMode == TwoMachinesPlay ||
12302 gameMode == EndOfGame) {
12303 i = forwardMostMove;
12304 while (i > currentMove) {
12305 SendToProgram("undo\n", &first);
12308 whiteTimeRemaining = timeRemaining[0][currentMove];
12309 blackTimeRemaining = timeRemaining[1][currentMove];
12310 DisplayBothClocks();
12311 if (whiteFlag || blackFlag) {
12312 whiteFlag = blackFlag = 0;
12317 gameMode = EditGame;
12324 EditPositionEvent()
12326 if (gameMode == EditPosition) {
12332 if (gameMode != EditGame) return;
12334 gameMode = EditPosition;
12337 if (currentMove > 0)
12338 CopyBoard(boards[0], boards[currentMove]);
12340 blackPlaysFirst = !WhiteOnMove(currentMove);
12342 currentMove = forwardMostMove = backwardMostMove = 0;
12343 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12350 /* [DM] icsEngineAnalyze - possible call from other functions */
12351 if (appData.icsEngineAnalyze) {
12352 appData.icsEngineAnalyze = FALSE;
12354 DisplayMessage("",_("Close ICS engine analyze..."));
12356 if (first.analysisSupport && first.analyzing) {
12357 SendToProgram("exit\n", &first);
12358 first.analyzing = FALSE;
12360 thinkOutput[0] = NULLCHAR;
12364 EditPositionDone(Boolean fakeRights)
12366 int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
12368 startedFromSetupPosition = TRUE;
12369 InitChessProgram(&first, FALSE);
12370 if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
12371 boards[0][EP_STATUS] = EP_NONE;
12372 boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
12373 if(boards[0][0][BOARD_WIDTH>>1] == king) {
12374 boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : NoRights;
12375 boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
12376 } else boards[0][CASTLING][2] = NoRights;
12377 if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
12378 boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : NoRights;
12379 boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
12380 } else boards[0][CASTLING][5] = NoRights;
12382 SendToProgram("force\n", &first);
12383 if (blackPlaysFirst) {
12384 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12385 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12386 currentMove = forwardMostMove = backwardMostMove = 1;
12387 CopyBoard(boards[1], boards[0]);
12389 currentMove = forwardMostMove = backwardMostMove = 0;
12391 SendBoard(&first, forwardMostMove);
12392 if (appData.debugMode) {
12393 fprintf(debugFP, "EditPosDone\n");
12396 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12397 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12398 gameMode = EditGame;
12400 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12401 ClearHighlights(); /* [AS] */
12404 /* Pause for `ms' milliseconds */
12405 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
12415 } while (SubtractTimeMarks(&m2, &m1) < ms);
12418 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
12420 SendMultiLineToICS(buf)
12423 char temp[MSG_SIZ+1], *p;
12430 strncpy(temp, buf, len);
12435 if (*p == '\n' || *p == '\r')
12440 strcat(temp, "\n");
12442 SendToPlayer(temp, strlen(temp));
12446 SetWhiteToPlayEvent()
12448 if (gameMode == EditPosition) {
12449 blackPlaysFirst = FALSE;
12450 DisplayBothClocks(); /* works because currentMove is 0 */
12451 } else if (gameMode == IcsExamining) {
12452 SendToICS(ics_prefix);
12453 SendToICS("tomove white\n");
12458 SetBlackToPlayEvent()
12460 if (gameMode == EditPosition) {
12461 blackPlaysFirst = TRUE;
12462 currentMove = 1; /* kludge */
12463 DisplayBothClocks();
12465 } else if (gameMode == IcsExamining) {
12466 SendToICS(ics_prefix);
12467 SendToICS("tomove black\n");
12472 EditPositionMenuEvent(selection, x, y)
12473 ChessSquare selection;
12477 ChessSquare piece = boards[0][y][x];
12479 if (gameMode != EditPosition && gameMode != IcsExamining) return;
12481 switch (selection) {
12483 if (gameMode == IcsExamining && ics_type == ICS_FICS) {
12484 SendToICS(ics_prefix);
12485 SendToICS("bsetup clear\n");
12486 } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
12487 SendToICS(ics_prefix);
12488 SendToICS("clearboard\n");
12490 for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
12491 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
12492 for (y = 0; y < BOARD_HEIGHT; y++) {
12493 if (gameMode == IcsExamining) {
12494 if (boards[currentMove][y][x] != EmptySquare) {
12495 snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
12500 boards[0][y][x] = p;
12505 if (gameMode == EditPosition) {
12506 DrawPosition(FALSE, boards[0]);
12511 SetWhiteToPlayEvent();
12515 SetBlackToPlayEvent();
12519 if (gameMode == IcsExamining) {
12520 if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
12521 snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
12524 if(x < BOARD_LEFT || x >= BOARD_RGHT) {
12525 if(x == BOARD_LEFT-2) {
12526 if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
12527 boards[0][y][1] = 0;
12529 if(x == BOARD_RGHT+1) {
12530 if(y >= gameInfo.holdingsSize) break;
12531 boards[0][y][BOARD_WIDTH-2] = 0;
12534 boards[0][y][x] = EmptySquare;
12535 DrawPosition(FALSE, boards[0]);
12540 if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
12541 piece >= (int)BlackPawn && piece < (int)BlackMan ) {
12542 selection = (ChessSquare) (PROMOTED piece);
12543 } else if(piece == EmptySquare) selection = WhiteSilver;
12544 else selection = (ChessSquare)((int)piece - 1);
12548 if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
12549 piece > (int)BlackMan && piece <= (int)BlackKing ) {
12550 selection = (ChessSquare) (DEMOTED piece);
12551 } else if(piece == EmptySquare) selection = BlackSilver;
12552 else selection = (ChessSquare)((int)piece + 1);
12557 if(gameInfo.variant == VariantShatranj ||
12558 gameInfo.variant == VariantXiangqi ||
12559 gameInfo.variant == VariantCourier ||
12560 gameInfo.variant == VariantMakruk )
12561 selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
12566 if(gameInfo.variant == VariantXiangqi)
12567 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
12568 if(gameInfo.variant == VariantKnightmate)
12569 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
12572 if (gameMode == IcsExamining) {
12573 if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
12574 snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
12575 PieceToChar(selection), AAA + x, ONE + y);
12578 if(x < BOARD_LEFT || x >= BOARD_RGHT) {
12580 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
12581 n = PieceToNumber(selection - BlackPawn);
12582 if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
12583 boards[0][BOARD_HEIGHT-1-n][0] = selection;
12584 boards[0][BOARD_HEIGHT-1-n][1]++;
12586 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
12587 n = PieceToNumber(selection);
12588 if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
12589 boards[0][n][BOARD_WIDTH-1] = selection;
12590 boards[0][n][BOARD_WIDTH-2]++;
12593 boards[0][y][x] = selection;
12594 DrawPosition(TRUE, boards[0]);
12602 DropMenuEvent(selection, x, y)
12603 ChessSquare selection;
12606 ChessMove moveType;
12608 switch (gameMode) {
12609 case IcsPlayingWhite:
12610 case MachinePlaysBlack:
12611 if (!WhiteOnMove(currentMove)) {
12612 DisplayMoveError(_("It is Black's turn"));
12615 moveType = WhiteDrop;
12617 case IcsPlayingBlack:
12618 case MachinePlaysWhite:
12619 if (WhiteOnMove(currentMove)) {
12620 DisplayMoveError(_("It is White's turn"));
12623 moveType = BlackDrop;
12626 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
12632 if (moveType == BlackDrop && selection < BlackPawn) {
12633 selection = (ChessSquare) ((int) selection
12634 + (int) BlackPawn - (int) WhitePawn);
12636 if (boards[currentMove][y][x] != EmptySquare) {
12637 DisplayMoveError(_("That square is occupied"));
12641 FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
12647 /* Accept a pending offer of any kind from opponent */
12649 if (appData.icsActive) {
12650 SendToICS(ics_prefix);
12651 SendToICS("accept\n");
12652 } else if (cmailMsgLoaded) {
12653 if (currentMove == cmailOldMove &&
12654 commentList[cmailOldMove] != NULL &&
12655 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12656 "Black offers a draw" : "White offers a draw")) {
12658 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
12659 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
12661 DisplayError(_("There is no pending offer on this move"), 0);
12662 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12665 /* Not used for offers from chess program */
12672 /* Decline a pending offer of any kind from opponent */
12674 if (appData.icsActive) {
12675 SendToICS(ics_prefix);
12676 SendToICS("decline\n");
12677 } else if (cmailMsgLoaded) {
12678 if (currentMove == cmailOldMove &&
12679 commentList[cmailOldMove] != NULL &&
12680 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12681 "Black offers a draw" : "White offers a draw")) {
12683 AppendComment(cmailOldMove, "Draw declined", TRUE);
12684 DisplayComment(cmailOldMove - 1, "Draw declined");
12687 DisplayError(_("There is no pending offer on this move"), 0);
12690 /* Not used for offers from chess program */
12697 /* Issue ICS rematch command */
12698 if (appData.icsActive) {
12699 SendToICS(ics_prefix);
12700 SendToICS("rematch\n");
12707 /* Call your opponent's flag (claim a win on time) */
12708 if (appData.icsActive) {
12709 SendToICS(ics_prefix);
12710 SendToICS("flag\n");
12712 switch (gameMode) {
12715 case MachinePlaysWhite:
12718 GameEnds(GameIsDrawn, "Both players ran out of time",
12721 GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
12723 DisplayError(_("Your opponent is not out of time"), 0);
12726 case MachinePlaysBlack:
12729 GameEnds(GameIsDrawn, "Both players ran out of time",
12732 GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
12734 DisplayError(_("Your opponent is not out of time"), 0);
12742 ClockClick(int which)
12743 { // [HGM] code moved to back-end from winboard.c
12744 if(which) { // black clock
12745 if (gameMode == EditPosition || gameMode == IcsExamining) {
12746 if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
12747 SetBlackToPlayEvent();
12748 } else if (gameMode == EditGame || shiftKey) {
12749 AdjustClock(which, -1);
12750 } else if (gameMode == IcsPlayingWhite ||
12751 gameMode == MachinePlaysBlack) {
12754 } else { // white clock
12755 if (gameMode == EditPosition || gameMode == IcsExamining) {
12756 if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
12757 SetWhiteToPlayEvent();
12758 } else if (gameMode == EditGame || shiftKey) {
12759 AdjustClock(which, -1);
12760 } else if (gameMode == IcsPlayingBlack ||
12761 gameMode == MachinePlaysWhite) {
12770 /* Offer draw or accept pending draw offer from opponent */
12772 if (appData.icsActive) {
12773 /* Note: tournament rules require draw offers to be
12774 made after you make your move but before you punch
12775 your clock. Currently ICS doesn't let you do that;
12776 instead, you immediately punch your clock after making
12777 a move, but you can offer a draw at any time. */
12779 SendToICS(ics_prefix);
12780 SendToICS("draw\n");
12781 userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
12782 } else if (cmailMsgLoaded) {
12783 if (currentMove == cmailOldMove &&
12784 commentList[cmailOldMove] != NULL &&
12785 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12786 "Black offers a draw" : "White offers a draw")) {
12787 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
12788 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
12789 } else if (currentMove == cmailOldMove + 1) {
12790 char *offer = WhiteOnMove(cmailOldMove) ?
12791 "White offers a draw" : "Black offers a draw";
12792 AppendComment(currentMove, offer, TRUE);
12793 DisplayComment(currentMove - 1, offer);
12794 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
12796 DisplayError(_("You must make your move before offering a draw"), 0);
12797 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12799 } else if (first.offeredDraw) {
12800 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
12802 if (first.sendDrawOffers) {
12803 SendToProgram("draw\n", &first);
12804 userOfferedDraw = TRUE;
12812 /* Offer Adjourn or accept pending Adjourn offer from opponent */
12814 if (appData.icsActive) {
12815 SendToICS(ics_prefix);
12816 SendToICS("adjourn\n");
12818 /* Currently GNU Chess doesn't offer or accept Adjourns */
12826 /* Offer Abort or accept pending Abort offer from opponent */
12828 if (appData.icsActive) {
12829 SendToICS(ics_prefix);
12830 SendToICS("abort\n");
12832 GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
12839 /* Resign. You can do this even if it's not your turn. */
12841 if (appData.icsActive) {
12842 SendToICS(ics_prefix);
12843 SendToICS("resign\n");
12845 switch (gameMode) {
12846 case MachinePlaysWhite:
12847 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12849 case MachinePlaysBlack:
12850 GameEnds(BlackWins, "White resigns", GE_PLAYER);
12853 if (cmailMsgLoaded) {
12855 if (WhiteOnMove(cmailOldMove)) {
12856 GameEnds(BlackWins, "White resigns", GE_PLAYER);
12858 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12860 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
12871 StopObservingEvent()
12873 /* Stop observing current games */
12874 SendToICS(ics_prefix);
12875 SendToICS("unobserve\n");
12879 StopExaminingEvent()
12881 /* Stop observing current game */
12882 SendToICS(ics_prefix);
12883 SendToICS("unexamine\n");
12887 ForwardInner(target)
12892 if (appData.debugMode)
12893 fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
12894 target, currentMove, forwardMostMove);
12896 if (gameMode == EditPosition)
12899 if (gameMode == PlayFromGameFile && !pausing)
12902 if (gameMode == IcsExamining && pausing)
12903 limit = pauseExamForwardMostMove;
12905 limit = forwardMostMove;
12907 if (target > limit) target = limit;
12909 if (target > 0 && moveList[target - 1][0]) {
12910 int fromX, fromY, toX, toY;
12911 toX = moveList[target - 1][2] - AAA;
12912 toY = moveList[target - 1][3] - ONE;
12913 if (moveList[target - 1][1] == '@') {
12914 if (appData.highlightLastMove) {
12915 SetHighlights(-1, -1, toX, toY);
12918 fromX = moveList[target - 1][0] - AAA;
12919 fromY = moveList[target - 1][1] - ONE;
12920 if (target == currentMove + 1) {
12921 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
12923 if (appData.highlightLastMove) {
12924 SetHighlights(fromX, fromY, toX, toY);
12928 if (gameMode == EditGame || gameMode == AnalyzeMode ||
12929 gameMode == Training || gameMode == PlayFromGameFile ||
12930 gameMode == AnalyzeFile) {
12931 while (currentMove < target) {
12932 SendMoveToProgram(currentMove++, &first);
12935 currentMove = target;
12938 if (gameMode == EditGame || gameMode == EndOfGame) {
12939 whiteTimeRemaining = timeRemaining[0][currentMove];
12940 blackTimeRemaining = timeRemaining[1][currentMove];
12942 DisplayBothClocks();
12943 DisplayMove(currentMove - 1);
12944 DrawPosition(FALSE, boards[currentMove]);
12945 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
12946 if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
12947 DisplayComment(currentMove - 1, commentList[currentMove]);
12955 if (gameMode == IcsExamining && !pausing) {
12956 SendToICS(ics_prefix);
12957 SendToICS("forward\n");
12959 ForwardInner(currentMove + 1);
12966 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12967 /* to optimze, we temporarily turn off analysis mode while we feed
12968 * the remaining moves to the engine. Otherwise we get analysis output
12971 if (first.analysisSupport) {
12972 SendToProgram("exit\nforce\n", &first);
12973 first.analyzing = FALSE;
12977 if (gameMode == IcsExamining && !pausing) {
12978 SendToICS(ics_prefix);
12979 SendToICS("forward 999999\n");
12981 ForwardInner(forwardMostMove);
12984 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12985 /* we have fed all the moves, so reactivate analysis mode */
12986 SendToProgram("analyze\n", &first);
12987 first.analyzing = TRUE;
12988 /*first.maybeThinking = TRUE;*/
12989 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12994 BackwardInner(target)
12997 int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
12999 if (appData.debugMode)
13000 fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
13001 target, currentMove, forwardMostMove);
13003 if (gameMode == EditPosition) return;
13004 if (currentMove <= backwardMostMove) {
13006 DrawPosition(full_redraw, boards[currentMove]);
13009 if (gameMode == PlayFromGameFile && !pausing)
13012 if (moveList[target][0]) {
13013 int fromX, fromY, toX, toY;
13014 toX = moveList[target][2] - AAA;
13015 toY = moveList[target][3] - ONE;
13016 if (moveList[target][1] == '@') {
13017 if (appData.highlightLastMove) {
13018 SetHighlights(-1, -1, toX, toY);
13021 fromX = moveList[target][0] - AAA;
13022 fromY = moveList[target][1] - ONE;
13023 if (target == currentMove - 1) {
13024 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
13026 if (appData.highlightLastMove) {
13027 SetHighlights(fromX, fromY, toX, toY);
13031 if (gameMode == EditGame || gameMode==AnalyzeMode ||
13032 gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
13033 while (currentMove > target) {
13034 SendToProgram("undo\n", &first);
13038 currentMove = target;
13041 if (gameMode == EditGame || gameMode == EndOfGame) {
13042 whiteTimeRemaining = timeRemaining[0][currentMove];
13043 blackTimeRemaining = timeRemaining[1][currentMove];
13045 DisplayBothClocks();
13046 DisplayMove(currentMove - 1);
13047 DrawPosition(full_redraw, boards[currentMove]);
13048 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
13049 // [HGM] PV info: routine tests if comment empty
13050 DisplayComment(currentMove - 1, commentList[currentMove]);
13056 if (gameMode == IcsExamining && !pausing) {
13057 SendToICS(ics_prefix);
13058 SendToICS("backward\n");
13060 BackwardInner(currentMove - 1);
13067 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13068 /* to optimize, we temporarily turn off analysis mode while we undo
13069 * all the moves. Otherwise we get analysis output after each undo.
13071 if (first.analysisSupport) {
13072 SendToProgram("exit\nforce\n", &first);
13073 first.analyzing = FALSE;
13077 if (gameMode == IcsExamining && !pausing) {
13078 SendToICS(ics_prefix);
13079 SendToICS("backward 999999\n");
13081 BackwardInner(backwardMostMove);
13084 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13085 /* we have fed all the moves, so reactivate analysis mode */
13086 SendToProgram("analyze\n", &first);
13087 first.analyzing = TRUE;
13088 /*first.maybeThinking = TRUE;*/
13089 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13096 if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
13097 if (to >= forwardMostMove) to = forwardMostMove;
13098 if (to <= backwardMostMove) to = backwardMostMove;
13099 if (to < currentMove) {
13107 RevertEvent(Boolean annotate)
13109 if(PopTail(annotate)) { // [HGM] vari: restore old game tail
13112 if (gameMode != IcsExamining) {
13113 DisplayError(_("You are not examining a game"), 0);
13117 DisplayError(_("You can't revert while pausing"), 0);
13120 SendToICS(ics_prefix);
13121 SendToICS("revert\n");
13127 switch (gameMode) {
13128 case MachinePlaysWhite:
13129 case MachinePlaysBlack:
13130 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
13131 DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
13134 if (forwardMostMove < 2) return;
13135 currentMove = forwardMostMove = forwardMostMove - 2;
13136 whiteTimeRemaining = timeRemaining[0][currentMove];
13137 blackTimeRemaining = timeRemaining[1][currentMove];
13138 DisplayBothClocks();
13139 DisplayMove(currentMove - 1);
13140 ClearHighlights();/*!! could figure this out*/
13141 DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
13142 SendToProgram("remove\n", &first);
13143 /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
13146 case BeginningOfGame:
13150 case IcsPlayingWhite:
13151 case IcsPlayingBlack:
13152 if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
13153 SendToICS(ics_prefix);
13154 SendToICS("takeback 2\n");
13156 SendToICS(ics_prefix);
13157 SendToICS("takeback 1\n");
13166 ChessProgramState *cps;
13168 switch (gameMode) {
13169 case MachinePlaysWhite:
13170 if (!WhiteOnMove(forwardMostMove)) {
13171 DisplayError(_("It is your turn"), 0);
13176 case MachinePlaysBlack:
13177 if (WhiteOnMove(forwardMostMove)) {
13178 DisplayError(_("It is your turn"), 0);
13183 case TwoMachinesPlay:
13184 if (WhiteOnMove(forwardMostMove) ==
13185 (first.twoMachinesColor[0] == 'w')) {
13191 case BeginningOfGame:
13195 SendToProgram("?\n", cps);
13199 TruncateGameEvent()
13202 if (gameMode != EditGame) return;
13209 CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
13210 if (forwardMostMove > currentMove) {
13211 if (gameInfo.resultDetails != NULL) {
13212 free(gameInfo.resultDetails);
13213 gameInfo.resultDetails = NULL;
13214 gameInfo.result = GameUnfinished;
13216 forwardMostMove = currentMove;
13217 HistorySet(parseList, backwardMostMove, forwardMostMove,
13225 if (appData.noChessProgram) return;
13226 switch (gameMode) {
13227 case MachinePlaysWhite:
13228 if (WhiteOnMove(forwardMostMove)) {
13229 DisplayError(_("Wait until your turn"), 0);
13233 case BeginningOfGame:
13234 case MachinePlaysBlack:
13235 if (!WhiteOnMove(forwardMostMove)) {
13236 DisplayError(_("Wait until your turn"), 0);
13241 DisplayError(_("No hint available"), 0);
13244 SendToProgram("hint\n", &first);
13245 hintRequested = TRUE;
13251 if (appData.noChessProgram) return;
13252 switch (gameMode) {
13253 case MachinePlaysWhite:
13254 if (WhiteOnMove(forwardMostMove)) {
13255 DisplayError(_("Wait until your turn"), 0);
13259 case BeginningOfGame:
13260 case MachinePlaysBlack:
13261 if (!WhiteOnMove(forwardMostMove)) {
13262 DisplayError(_("Wait until your turn"), 0);
13267 EditPositionDone(TRUE);
13269 case TwoMachinesPlay:
13274 SendToProgram("bk\n", &first);
13275 bookOutput[0] = NULLCHAR;
13276 bookRequested = TRUE;
13282 char *tags = PGNTags(&gameInfo);
13283 TagsPopUp(tags, CmailMsg());
13287 /* end button procedures */
13290 PrintPosition(fp, move)
13296 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13297 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
13298 char c = PieceToChar(boards[move][i][j]);
13299 fputc(c == 'x' ? '.' : c, fp);
13300 fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
13303 if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
13304 fprintf(fp, "white to play\n");
13306 fprintf(fp, "black to play\n");
13313 if (gameInfo.white != NULL) {
13314 fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
13320 /* Find last component of program's own name, using some heuristics */
13322 TidyProgramName(prog, host, buf)
13323 char *prog, *host, buf[MSG_SIZ];
13326 int local = (strcmp(host, "localhost") == 0);
13327 while (!local && (p = strchr(prog, ';')) != NULL) {
13329 while (*p == ' ') p++;
13332 if (*prog == '"' || *prog == '\'') {
13333 q = strchr(prog + 1, *prog);
13335 q = strchr(prog, ' ');
13337 if (q == NULL) q = prog + strlen(prog);
13339 while (p >= prog && *p != '/' && *p != '\\') p--;
13341 if(p == prog && *p == '"') p++;
13342 if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
13343 memcpy(buf, p, q - p);
13344 buf[q - p] = NULLCHAR;
13352 TimeControlTagValue()
13355 if (!appData.clockMode) {
13356 safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
13357 } else if (movesPerSession > 0) {
13358 snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
13359 } else if (timeIncrement == 0) {
13360 snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
13362 snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
13364 return StrSave(buf);
13370 /* This routine is used only for certain modes */
13371 VariantClass v = gameInfo.variant;
13372 ChessMove r = GameUnfinished;
13375 if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
13376 r = gameInfo.result;
13377 p = gameInfo.resultDetails;
13378 gameInfo.resultDetails = NULL;
13380 ClearGameInfo(&gameInfo);
13381 gameInfo.variant = v;
13383 switch (gameMode) {
13384 case MachinePlaysWhite:
13385 gameInfo.event = StrSave( appData.pgnEventHeader );
13386 gameInfo.site = StrSave(HostName());
13387 gameInfo.date = PGNDate();
13388 gameInfo.round = StrSave("-");
13389 gameInfo.white = StrSave(first.tidy);
13390 gameInfo.black = StrSave(UserName());
13391 gameInfo.timeControl = TimeControlTagValue();
13394 case MachinePlaysBlack:
13395 gameInfo.event = StrSave( appData.pgnEventHeader );
13396 gameInfo.site = StrSave(HostName());
13397 gameInfo.date = PGNDate();
13398 gameInfo.round = StrSave("-");
13399 gameInfo.white = StrSave(UserName());
13400 gameInfo.black = StrSave(first.tidy);
13401 gameInfo.timeControl = TimeControlTagValue();
13404 case TwoMachinesPlay:
13405 gameInfo.event = StrSave( appData.pgnEventHeader );
13406 gameInfo.site = StrSave(HostName());
13407 gameInfo.date = PGNDate();
13408 if (matchGame > 0) {
13410 snprintf(buf, MSG_SIZ, "%d", matchGame);
13411 gameInfo.round = StrSave(buf);
13413 gameInfo.round = StrSave("-");
13415 if (first.twoMachinesColor[0] == 'w') {
13416 gameInfo.white = StrSave(first.tidy);
13417 gameInfo.black = StrSave(second.tidy);
13419 gameInfo.white = StrSave(second.tidy);
13420 gameInfo.black = StrSave(first.tidy);
13422 gameInfo.timeControl = TimeControlTagValue();
13426 gameInfo.event = StrSave("Edited game");
13427 gameInfo.site = StrSave(HostName());
13428 gameInfo.date = PGNDate();
13429 gameInfo.round = StrSave("-");
13430 gameInfo.white = StrSave("-");
13431 gameInfo.black = StrSave("-");
13432 gameInfo.result = r;
13433 gameInfo.resultDetails = p;
13437 gameInfo.event = StrSave("Edited position");
13438 gameInfo.site = StrSave(HostName());
13439 gameInfo.date = PGNDate();
13440 gameInfo.round = StrSave("-");
13441 gameInfo.white = StrSave("-");
13442 gameInfo.black = StrSave("-");
13445 case IcsPlayingWhite:
13446 case IcsPlayingBlack:
13451 case PlayFromGameFile:
13452 gameInfo.event = StrSave("Game from non-PGN file");
13453 gameInfo.site = StrSave(HostName());
13454 gameInfo.date = PGNDate();
13455 gameInfo.round = StrSave("-");
13456 gameInfo.white = StrSave("?");
13457 gameInfo.black = StrSave("?");
13466 ReplaceComment(index, text)
13474 if(index && sscanf(text, "%f/%d", &score, &len) == 2 &&
13475 pvInfoList[index-1].depth == len &&
13476 fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
13477 (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
13478 while (*text == '\n') text++;
13479 len = strlen(text);
13480 while (len > 0 && text[len - 1] == '\n') len--;
13482 if (commentList[index] != NULL)
13483 free(commentList[index]);
13486 commentList[index] = NULL;
13489 if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
13490 *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
13491 *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
13492 commentList[index] = (char *) malloc(len + 2);
13493 strncpy(commentList[index], text, len);
13494 commentList[index][len] = '\n';
13495 commentList[index][len + 1] = NULLCHAR;
13497 // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
13499 commentList[index] = (char *) malloc(len + 7);
13500 safeStrCpy(commentList[index], "{\n", 3);
13501 safeStrCpy(commentList[index]+2, text, len+1);
13502 commentList[index][len+2] = NULLCHAR;
13503 while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
13504 strcat(commentList[index], "\n}\n");
13518 if (ch == '\r') continue;
13520 } while (ch != '\0');
13524 AppendComment(index, text, addBraces)
13527 Boolean addBraces; // [HGM] braces: tells if we should add {}
13532 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
13533 text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
13536 while (*text == '\n') text++;
13537 len = strlen(text);
13538 while (len > 0 && text[len - 1] == '\n') len--;
13540 if (len == 0) return;
13542 if (commentList[index] != NULL) {
13543 old = commentList[index];
13544 oldlen = strlen(old);
13545 while(commentList[index][oldlen-1] == '\n')
13546 commentList[index][--oldlen] = NULLCHAR;
13547 commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
13548 safeStrCpy(commentList[index], old, oldlen + len + 6);
13550 // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
13551 if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
13552 if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
13553 while (*text == '\n') { text++; len--; }
13554 commentList[index][--oldlen] = NULLCHAR;
13556 if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
13557 else strcat(commentList[index], "\n");
13558 strcat(commentList[index], text);
13559 if(addBraces) strcat(commentList[index], addBraces == 2 ? ")\n" : "\n}\n");
13560 else strcat(commentList[index], "\n");
13562 commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
13564 safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
13565 else commentList[index][0] = NULLCHAR;
13566 strcat(commentList[index], text);
13567 strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
13568 if(addBraces == TRUE) strcat(commentList[index], "}\n");
13572 static char * FindStr( char * text, char * sub_text )
13574 char * result = strstr( text, sub_text );
13576 if( result != NULL ) {
13577 result += strlen( sub_text );
13583 /* [AS] Try to extract PV info from PGN comment */
13584 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
13585 char *GetInfoFromComment( int index, char * text )
13587 char * sep = text, *p;
13589 if( text != NULL && index > 0 ) {
13592 int time = -1, sec = 0, deci;
13593 char * s_eval = FindStr( text, "[%eval " );
13594 char * s_emt = FindStr( text, "[%emt " );
13596 if( s_eval != NULL || s_emt != NULL ) {
13600 if( s_eval != NULL ) {
13601 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
13605 if( delim != ']' ) {
13610 if( s_emt != NULL ) {
13615 /* We expect something like: [+|-]nnn.nn/dd */
13618 if(*text != '{') return text; // [HGM] braces: must be normal comment
13620 sep = strchr( text, '/' );
13621 if( sep == NULL || sep < (text+4) ) {
13626 if(p[1] == '(') { // comment starts with PV
13627 p = strchr(p, ')'); // locate end of PV
13628 if(p == NULL || sep < p+5) return text;
13629 // at this point we have something like "{(.*) +0.23/6 ..."
13630 p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
13631 *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
13632 // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
13634 time = -1; sec = -1; deci = -1;
13635 if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
13636 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
13637 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
13638 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3 ) {
13642 if( score_lo < 0 || score_lo >= 100 ) {
13646 if(sec >= 0) time = 600*time + 10*sec; else
13647 if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
13649 score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
13651 /* [HGM] PV time: now locate end of PV info */
13652 while( *++sep >= '0' && *sep <= '9'); // strip depth
13654 while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
13656 while( *++sep >= '0' && *sep <= '9'); // strip seconds
13658 while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
13659 while(*sep == ' ') sep++;
13670 pvInfoList[index-1].depth = depth;
13671 pvInfoList[index-1].score = score;
13672 pvInfoList[index-1].time = 10*time; // centi-sec
13673 if(*sep == '}') *sep = 0; else *--sep = '{';
13674 if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
13680 SendToProgram(message, cps)
13682 ChessProgramState *cps;
13684 int count, outCount, error;
13687 if (cps->pr == NULL) return;
13690 if (appData.debugMode) {
13693 fprintf(debugFP, "%ld >%-6s: %s",
13694 SubtractTimeMarks(&now, &programStartTime),
13695 cps->which, message);
13698 count = strlen(message);
13699 outCount = OutputToProcess(cps->pr, message, count, &error);
13700 if (outCount < count && !exiting
13701 && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
13702 snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
13703 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
13704 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
13705 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
13706 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
13708 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
13710 gameInfo.resultDetails = StrSave(buf);
13712 if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
13717 ReceiveFromProgram(isr, closure, message, count, error)
13718 InputSourceRef isr;
13726 ChessProgramState *cps = (ChessProgramState *)closure;
13728 if (isr != cps->isr) return; /* Killed intentionally */
13731 snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
13732 _(cps->which), cps->program);
13733 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
13734 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
13735 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
13736 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
13738 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
13740 gameInfo.resultDetails = StrSave(buf);
13742 RemoveInputSource(cps->isr);
13743 if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
13745 snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
13746 _(cps->which), cps->program);
13747 RemoveInputSource(cps->isr);
13749 /* [AS] Program is misbehaving badly... kill it */
13750 if( count == -2 ) {
13751 DestroyChildProcess( cps->pr, 9 );
13755 if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
13760 if ((end_str = strchr(message, '\r')) != NULL)
13761 *end_str = NULLCHAR;
13762 if ((end_str = strchr(message, '\n')) != NULL)
13763 *end_str = NULLCHAR;
13765 if (appData.debugMode) {
13766 TimeMark now; int print = 1;
13767 char *quote = ""; char c; int i;
13769 if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
13770 char start = message[0];
13771 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
13772 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
13773 sscanf(message, "move %c", &c)!=1 && sscanf(message, "offer%c", &c)!=1 &&
13774 sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
13775 sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
13776 sscanf(message, "tell%c", &c)!=1 && sscanf(message, "0-1 %c", &c)!=1 &&
13777 sscanf(message, "1-0 %c", &c)!=1 && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
13778 sscanf(message, "setboard %c", &c)!=1 && sscanf(message, "setup %c", &c)!=1 &&
13779 sscanf(message, "hint: %c", &c)!=1 &&
13780 sscanf(message, "pong %c", &c)!=1 && start != '#') {
13781 quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
13782 print = (appData.engineComments >= 2);
13784 message[0] = start; // restore original message
13788 fprintf(debugFP, "%ld <%-6s: %s%s\n",
13789 SubtractTimeMarks(&now, &programStartTime), cps->which,
13795 /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
13796 if (appData.icsEngineAnalyze) {
13797 if (strstr(message, "whisper") != NULL ||
13798 strstr(message, "kibitz") != NULL ||
13799 strstr(message, "tellics") != NULL) return;
13802 HandleMachineMove(message, cps);
13807 SendTimeControl(cps, mps, tc, inc, sd, st)
13808 ChessProgramState *cps;
13809 int mps, inc, sd, st;
13815 if( timeControl_2 > 0 ) {
13816 if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
13817 tc = timeControl_2;
13820 tc /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
13821 inc /= cps->timeOdds;
13822 st /= cps->timeOdds;
13824 seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
13827 /* Set exact time per move, normally using st command */
13828 if (cps->stKludge) {
13829 /* GNU Chess 4 has no st command; uses level in a nonstandard way */
13831 if (seconds == 0) {
13832 snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
13834 snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
13837 snprintf(buf, MSG_SIZ, "st %d\n", st);
13840 /* Set conventional or incremental time control, using level command */
13841 if (seconds == 0) {
13842 /* Note old gnuchess bug -- minutes:seconds used to not work.
13843 Fixed in later versions, but still avoid :seconds
13844 when seconds is 0. */
13845 snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
13847 snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
13848 seconds, inc/1000.);
13851 SendToProgram(buf, cps);
13853 /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
13854 /* Orthogonally, limit search to given depth */
13856 if (cps->sdKludge) {
13857 snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
13859 snprintf(buf, MSG_SIZ, "sd %d\n", sd);
13861 SendToProgram(buf, cps);
13864 if(cps->nps >= 0) { /* [HGM] nps */
13865 if(cps->supportsNPS == FALSE)
13866 cps->nps = -1; // don't use if engine explicitly says not supported!
13868 snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
13869 SendToProgram(buf, cps);
13874 ChessProgramState *WhitePlayer()
13875 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
13877 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
13878 gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
13884 SendTimeRemaining(cps, machineWhite)
13885 ChessProgramState *cps;
13886 int /*boolean*/ machineWhite;
13888 char message[MSG_SIZ];
13891 /* Note: this routine must be called when the clocks are stopped
13892 or when they have *just* been set or switched; otherwise
13893 it will be off by the time since the current tick started.
13895 if (machineWhite) {
13896 time = whiteTimeRemaining / 10;
13897 otime = blackTimeRemaining / 10;
13899 time = blackTimeRemaining / 10;
13900 otime = whiteTimeRemaining / 10;
13902 /* [HGM] translate opponent's time by time-odds factor */
13903 otime = (otime * cps->other->timeOdds) / cps->timeOdds;
13904 if (appData.debugMode) {
13905 fprintf(debugFP, "time odds: %f %f \n", cps->timeOdds, cps->other->timeOdds);
13908 if (time <= 0) time = 1;
13909 if (otime <= 0) otime = 1;
13911 snprintf(message, MSG_SIZ, "time %ld\n", time);
13912 SendToProgram(message, cps);
13914 snprintf(message, MSG_SIZ, "otim %ld\n", otime);
13915 SendToProgram(message, cps);
13919 BoolFeature(p, name, loc, cps)
13923 ChessProgramState *cps;
13926 int len = strlen(name);
13929 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
13931 sscanf(*p, "%d", &val);
13933 while (**p && **p != ' ')
13935 snprintf(buf, MSG_SIZ, "accepted %s\n", name);
13936 SendToProgram(buf, cps);
13943 IntFeature(p, name, loc, cps)
13947 ChessProgramState *cps;
13950 int len = strlen(name);
13951 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
13953 sscanf(*p, "%d", loc);
13954 while (**p && **p != ' ') (*p)++;
13955 snprintf(buf, MSG_SIZ, "accepted %s\n", name);
13956 SendToProgram(buf, cps);
13963 StringFeature(p, name, loc, cps)
13967 ChessProgramState *cps;
13970 int len = strlen(name);
13971 if (strncmp((*p), name, len) == 0
13972 && (*p)[len] == '=' && (*p)[len+1] == '\"') {
13974 sscanf(*p, "%[^\"]", loc);
13975 while (**p && **p != '\"') (*p)++;
13976 if (**p == '\"') (*p)++;
13977 snprintf(buf, MSG_SIZ, "accepted %s\n", name);
13978 SendToProgram(buf, cps);
13985 ParseOption(Option *opt, ChessProgramState *cps)
13986 // [HGM] options: process the string that defines an engine option, and determine
13987 // name, type, default value, and allowed value range
13989 char *p, *q, buf[MSG_SIZ];
13990 int n, min = (-1)<<31, max = 1<<31, def;
13992 if(p = strstr(opt->name, " -spin ")) {
13993 if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
13994 if(max < min) max = min; // enforce consistency
13995 if(def < min) def = min;
13996 if(def > max) def = max;
14001 } else if((p = strstr(opt->name, " -slider "))) {
14002 // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
14003 if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
14004 if(max < min) max = min; // enforce consistency
14005 if(def < min) def = min;
14006 if(def > max) def = max;
14010 opt->type = Spin; // Slider;
14011 } else if((p = strstr(opt->name, " -string "))) {
14012 opt->textValue = p+9;
14013 opt->type = TextBox;
14014 } else if((p = strstr(opt->name, " -file "))) {
14015 // for now -file is a synonym for -string, to already provide compatibility with future polyglots
14016 opt->textValue = p+7;
14017 opt->type = FileName; // FileName;
14018 } else if((p = strstr(opt->name, " -path "))) {
14019 // for now -file is a synonym for -string, to already provide compatibility with future polyglots
14020 opt->textValue = p+7;
14021 opt->type = PathName; // PathName;
14022 } else if(p = strstr(opt->name, " -check ")) {
14023 if(sscanf(p, " -check %d", &def) < 1) return FALSE;
14024 opt->value = (def != 0);
14025 opt->type = CheckBox;
14026 } else if(p = strstr(opt->name, " -combo ")) {
14027 opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
14028 cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
14029 if(*q == '*') cps->comboList[cps->comboCnt-1]++;
14030 opt->value = n = 0;
14031 while(q = StrStr(q, " /// ")) {
14032 n++; *q = 0; // count choices, and null-terminate each of them
14034 if(*q == '*') { // remember default, which is marked with * prefix
14038 cps->comboList[cps->comboCnt++] = q;
14040 cps->comboList[cps->comboCnt++] = NULL;
14042 opt->type = ComboBox;
14043 } else if(p = strstr(opt->name, " -button")) {
14044 opt->type = Button;
14045 } else if(p = strstr(opt->name, " -save")) {
14046 opt->type = SaveButton;
14047 } else return FALSE;
14048 *p = 0; // terminate option name
14049 // now look if the command-line options define a setting for this engine option.
14050 if(cps->optionSettings && cps->optionSettings[0])
14051 p = strstr(cps->optionSettings, opt->name); else p = NULL;
14052 if(p && (p == cps->optionSettings || p[-1] == ',')) {
14053 snprintf(buf, MSG_SIZ, "option %s", p);
14054 if(p = strstr(buf, ",")) *p = 0;
14055 if(q = strchr(buf, '=')) switch(opt->type) {
14057 for(n=0; n<opt->max; n++)
14058 if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
14061 safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
14065 opt->value = atoi(q+1);
14070 SendToProgram(buf, cps);
14076 FeatureDone(cps, val)
14077 ChessProgramState* cps;
14080 DelayedEventCallback cb = GetDelayedEvent();
14081 if ((cb == InitBackEnd3 && cps == &first) ||
14082 (cb == SettingsMenuIfReady && cps == &second) ||
14083 (cb == TwoMachinesEventIfReady && cps == &second)) {
14084 CancelDelayedEvent();
14085 ScheduleDelayedEvent(cb, val ? 1 : 3600000);
14087 cps->initDone = val;
14090 /* Parse feature command from engine */
14092 ParseFeatures(args, cps)
14094 ChessProgramState *cps;
14102 while (*p == ' ') p++;
14103 if (*p == NULLCHAR) return;
14105 if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
14106 if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
14107 if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
14108 if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
14109 if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
14110 if (BoolFeature(&p, "reuse", &val, cps)) {
14111 /* Engine can disable reuse, but can't enable it if user said no */
14112 if (!val) cps->reuse = FALSE;
14115 if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
14116 if (StringFeature(&p, "myname", &cps->tidy, cps)) {
14117 if (gameMode == TwoMachinesPlay) {
14118 DisplayTwoMachinesTitle();
14124 if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
14125 if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
14126 if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
14127 if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
14128 if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
14129 if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
14130 if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
14131 if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
14132 if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
14133 if (IntFeature(&p, "done", &val, cps)) {
14134 FeatureDone(cps, val);
14137 /* Added by Tord: */
14138 if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
14139 if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
14140 /* End of additions by Tord */
14142 /* [HGM] added features: */
14143 if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
14144 if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
14145 if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
14146 if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
14147 if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
14148 if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
14149 if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
14150 if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
14151 snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
14152 SendToProgram(buf, cps);
14155 if(cps->nrOptions >= MAX_OPTIONS) {
14157 snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
14158 DisplayError(buf, 0);
14162 /* End of additions by HGM */
14164 /* unknown feature: complain and skip */
14166 while (*q && *q != '=') q++;
14167 snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
14168 SendToProgram(buf, cps);
14174 while (*p && *p != '\"') p++;
14175 if (*p == '\"') p++;
14177 while (*p && *p != ' ') p++;
14185 PeriodicUpdatesEvent(newState)
14188 if (newState == appData.periodicUpdates)
14191 appData.periodicUpdates=newState;
14193 /* Display type changes, so update it now */
14194 // DisplayAnalysis();
14196 /* Get the ball rolling again... */
14198 AnalysisPeriodicEvent(1);
14199 StartAnalysisClock();
14204 PonderNextMoveEvent(newState)
14207 if (newState == appData.ponderNextMove) return;
14208 if (gameMode == EditPosition) EditPositionDone(TRUE);
14210 SendToProgram("hard\n", &first);
14211 if (gameMode == TwoMachinesPlay) {
14212 SendToProgram("hard\n", &second);
14215 SendToProgram("easy\n", &first);
14216 thinkOutput[0] = NULLCHAR;
14217 if (gameMode == TwoMachinesPlay) {
14218 SendToProgram("easy\n", &second);
14221 appData.ponderNextMove = newState;
14225 NewSettingEvent(option, feature, command, value)
14227 int option, value, *feature;
14231 if (gameMode == EditPosition) EditPositionDone(TRUE);
14232 snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
14233 if(feature == NULL || *feature) SendToProgram(buf, &first);
14234 if (gameMode == TwoMachinesPlay) {
14235 if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
14240 ShowThinkingEvent()
14241 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
14243 static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
14244 int newState = appData.showThinking
14245 // [HGM] thinking: other features now need thinking output as well
14246 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
14248 if (oldState == newState) return;
14249 oldState = newState;
14250 if (gameMode == EditPosition) EditPositionDone(TRUE);
14252 SendToProgram("post\n", &first);
14253 if (gameMode == TwoMachinesPlay) {
14254 SendToProgram("post\n", &second);
14257 SendToProgram("nopost\n", &first);
14258 thinkOutput[0] = NULLCHAR;
14259 if (gameMode == TwoMachinesPlay) {
14260 SendToProgram("nopost\n", &second);
14263 // appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
14267 AskQuestionEvent(title, question, replyPrefix, which)
14268 char *title; char *question; char *replyPrefix; char *which;
14270 ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
14271 if (pr == NoProc) return;
14272 AskQuestion(title, question, replyPrefix, pr);
14276 DisplayMove(moveNumber)
14279 char message[MSG_SIZ];
14281 char cpThinkOutput[MSG_SIZ];
14283 if(appData.noGUI) return; // [HGM] fast: suppress display of moves
14285 if (moveNumber == forwardMostMove - 1 ||
14286 gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14288 safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
14290 if (strchr(cpThinkOutput, '\n')) {
14291 *strchr(cpThinkOutput, '\n') = NULLCHAR;
14294 *cpThinkOutput = NULLCHAR;
14297 /* [AS] Hide thinking from human user */
14298 if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
14299 *cpThinkOutput = NULLCHAR;
14300 if( thinkOutput[0] != NULLCHAR ) {
14303 for( i=0; i<=hiddenThinkOutputState; i++ ) {
14304 cpThinkOutput[i] = '.';
14306 cpThinkOutput[i] = NULLCHAR;
14307 hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
14311 if (moveNumber == forwardMostMove - 1 &&
14312 gameInfo.resultDetails != NULL) {
14313 if (gameInfo.resultDetails[0] == NULLCHAR) {
14314 snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
14316 snprintf(res, MSG_SIZ, " {%s} %s",
14317 T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
14323 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
14324 DisplayMessage(res, cpThinkOutput);
14326 snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
14327 WhiteOnMove(moveNumber) ? " " : ".. ",
14328 parseList[moveNumber], res);
14329 DisplayMessage(message, cpThinkOutput);
14334 DisplayComment(moveNumber, text)
14338 char title[MSG_SIZ];
14339 char buf[8000]; // comment can be long!
14342 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
14343 safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
14345 snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
14346 WhiteOnMove(moveNumber) ? " " : ".. ",
14347 parseList[moveNumber]);
14349 // [HGM] PV info: display PV info together with (or as) comment
14350 if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {
14351 if(text == NULL) text = "";
14352 score = pvInfoList[moveNumber].score;
14353 snprintf(buf,sizeof(buf)/sizeof(buf[0]), "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,
14354 depth, (pvInfoList[moveNumber].time+50)/100, text);
14357 if (text != NULL && (appData.autoDisplayComment || commentUp))
14358 CommentPopUp(title, text);
14361 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
14362 * might be busy thinking or pondering. It can be omitted if your
14363 * gnuchess is configured to stop thinking immediately on any user
14364 * input. However, that gnuchess feature depends on the FIONREAD
14365 * ioctl, which does not work properly on some flavors of Unix.
14369 ChessProgramState *cps;
14372 if (!cps->useSigint) return;
14373 if (appData.noChessProgram || (cps->pr == NoProc)) return;
14374 switch (gameMode) {
14375 case MachinePlaysWhite:
14376 case MachinePlaysBlack:
14377 case TwoMachinesPlay:
14378 case IcsPlayingWhite:
14379 case IcsPlayingBlack:
14382 /* Skip if we know it isn't thinking */
14383 if (!cps->maybeThinking) return;
14384 if (appData.debugMode)
14385 fprintf(debugFP, "Interrupting %s\n", cps->which);
14386 InterruptChildProcess(cps->pr);
14387 cps->maybeThinking = FALSE;
14392 #endif /*ATTENTION*/
14398 if (whiteTimeRemaining <= 0) {
14401 if (appData.icsActive) {
14402 if (appData.autoCallFlag &&
14403 gameMode == IcsPlayingBlack && !blackFlag) {
14404 SendToICS(ics_prefix);
14405 SendToICS("flag\n");
14409 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
14411 if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
14412 if (appData.autoCallFlag) {
14413 GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
14420 if (blackTimeRemaining <= 0) {
14423 if (appData.icsActive) {
14424 if (appData.autoCallFlag &&
14425 gameMode == IcsPlayingWhite && !whiteFlag) {
14426 SendToICS(ics_prefix);
14427 SendToICS("flag\n");
14431 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
14433 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
14434 if (appData.autoCallFlag) {
14435 GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
14448 if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
14449 gameMode == PlayFromGameFile || forwardMostMove == 0) return;
14452 * add time to clocks when time control is achieved ([HGM] now also used for increment)
14454 if ( !WhiteOnMove(forwardMostMove) ) {
14455 /* White made time control */
14456 lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
14457 whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
14458 /* [HGM] time odds: correct new time quota for time odds! */
14459 / WhitePlayer()->timeOdds;
14460 lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
14462 lastBlack -= blackTimeRemaining;
14463 /* Black made time control */
14464 blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
14465 / WhitePlayer()->other->timeOdds;
14466 lastWhite = whiteTimeRemaining;
14471 DisplayBothClocks()
14473 int wom = gameMode == EditPosition ?
14474 !blackPlaysFirst : WhiteOnMove(currentMove);
14475 DisplayWhiteClock(whiteTimeRemaining, wom);
14476 DisplayBlackClock(blackTimeRemaining, !wom);
14480 /* Timekeeping seems to be a portability nightmare. I think everyone
14481 has ftime(), but I'm really not sure, so I'm including some ifdefs
14482 to use other calls if you don't. Clocks will be less accurate if
14483 you have neither ftime nor gettimeofday.
14486 /* VS 2008 requires the #include outside of the function */
14487 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
14488 #include <sys/timeb.h>
14491 /* Get the current time as a TimeMark */
14496 #if HAVE_GETTIMEOFDAY
14498 struct timeval timeVal;
14499 struct timezone timeZone;
14501 gettimeofday(&timeVal, &timeZone);
14502 tm->sec = (long) timeVal.tv_sec;
14503 tm->ms = (int) (timeVal.tv_usec / 1000L);
14505 #else /*!HAVE_GETTIMEOFDAY*/
14508 // include <sys/timeb.h> / moved to just above start of function
14509 struct timeb timeB;
14512 tm->sec = (long) timeB.time;
14513 tm->ms = (int) timeB.millitm;
14515 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
14516 tm->sec = (long) time(NULL);
14522 /* Return the difference in milliseconds between two
14523 time marks. We assume the difference will fit in a long!
14526 SubtractTimeMarks(tm2, tm1)
14527 TimeMark *tm2, *tm1;
14529 return 1000L*(tm2->sec - tm1->sec) +
14530 (long) (tm2->ms - tm1->ms);
14535 * Code to manage the game clocks.
14537 * In tournament play, black starts the clock and then white makes a move.
14538 * We give the human user a slight advantage if he is playing white---the
14539 * clocks don't run until he makes his first move, so it takes zero time.
14540 * Also, we don't account for network lag, so we could get out of sync
14541 * with GNU Chess's clock -- but then, referees are always right.
14544 static TimeMark tickStartTM;
14545 static long intendedTickLength;
14548 NextTickLength(timeRemaining)
14549 long timeRemaining;
14551 long nominalTickLength, nextTickLength;
14553 if (timeRemaining > 0L && timeRemaining <= 10000L)
14554 nominalTickLength = 100L;
14556 nominalTickLength = 1000L;
14557 nextTickLength = timeRemaining % nominalTickLength;
14558 if (nextTickLength <= 0) nextTickLength += nominalTickLength;
14560 return nextTickLength;
14563 /* Adjust clock one minute up or down */
14565 AdjustClock(Boolean which, int dir)
14567 if(which) blackTimeRemaining += 60000*dir;
14568 else whiteTimeRemaining += 60000*dir;
14569 DisplayBothClocks();
14572 /* Stop clocks and reset to a fresh time control */
14576 (void) StopClockTimer();
14577 if (appData.icsActive) {
14578 whiteTimeRemaining = blackTimeRemaining = 0;
14579 } else if (searchTime) {
14580 whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
14581 blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
14582 } else { /* [HGM] correct new time quote for time odds */
14583 whiteTC = blackTC = fullTimeControlString;
14584 whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
14585 blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
14587 if (whiteFlag || blackFlag) {
14589 whiteFlag = blackFlag = FALSE;
14591 lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
14592 DisplayBothClocks();
14595 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
14597 /* Decrement running clock by amount of time that has passed */
14601 long timeRemaining;
14602 long lastTickLength, fudge;
14605 if (!appData.clockMode) return;
14606 if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
14610 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
14612 /* Fudge if we woke up a little too soon */
14613 fudge = intendedTickLength - lastTickLength;
14614 if (fudge < 0 || fudge > FUDGE) fudge = 0;
14616 if (WhiteOnMove(forwardMostMove)) {
14617 if(whiteNPS >= 0) lastTickLength = 0;
14618 timeRemaining = whiteTimeRemaining -= lastTickLength;
14619 if(timeRemaining < 0 && !appData.icsActive) {
14620 GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
14621 if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
14622 whiteStartMove = forwardMostMove; whiteTC = nextSession;
14623 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
14626 DisplayWhiteClock(whiteTimeRemaining - fudge,
14627 WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
14629 if(blackNPS >= 0) lastTickLength = 0;
14630 timeRemaining = blackTimeRemaining -= lastTickLength;
14631 if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
14632 GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
14634 blackStartMove = forwardMostMove;
14635 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
14638 DisplayBlackClock(blackTimeRemaining - fudge,
14639 !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
14641 if (CheckFlags()) return;
14644 intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
14645 StartClockTimer(intendedTickLength);
14647 /* if the time remaining has fallen below the alarm threshold, sound the
14648 * alarm. if the alarm has sounded and (due to a takeback or time control
14649 * with increment) the time remaining has increased to a level above the
14650 * threshold, reset the alarm so it can sound again.
14653 if (appData.icsActive && appData.icsAlarm) {
14655 /* make sure we are dealing with the user's clock */
14656 if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
14657 ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
14660 if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
14661 alarmSounded = FALSE;
14662 } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
14664 alarmSounded = TRUE;
14670 /* A player has just moved, so stop the previously running
14671 clock and (if in clock mode) start the other one.
14672 We redisplay both clocks in case we're in ICS mode, because
14673 ICS gives us an update to both clocks after every move.
14674 Note that this routine is called *after* forwardMostMove
14675 is updated, so the last fractional tick must be subtracted
14676 from the color that is *not* on move now.
14679 SwitchClocks(int newMoveNr)
14681 long lastTickLength;
14683 int flagged = FALSE;
14687 if (StopClockTimer() && appData.clockMode) {
14688 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
14689 if (!WhiteOnMove(forwardMostMove)) {
14690 if(blackNPS >= 0) lastTickLength = 0;
14691 blackTimeRemaining -= lastTickLength;
14692 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
14693 // if(pvInfoList[forwardMostMove].time == -1)
14694 pvInfoList[forwardMostMove].time = // use GUI time
14695 (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
14697 if(whiteNPS >= 0) lastTickLength = 0;
14698 whiteTimeRemaining -= lastTickLength;
14699 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
14700 // if(pvInfoList[forwardMostMove].time == -1)
14701 pvInfoList[forwardMostMove].time =
14702 (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
14704 flagged = CheckFlags();
14706 forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
14707 CheckTimeControl();
14709 if (flagged || !appData.clockMode) return;
14711 switch (gameMode) {
14712 case MachinePlaysBlack:
14713 case MachinePlaysWhite:
14714 case BeginningOfGame:
14715 if (pausing) return;
14719 case PlayFromGameFile:
14727 if (searchTime) { // [HGM] st: set clock of player that has to move to max time
14728 if(WhiteOnMove(forwardMostMove))
14729 whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
14730 else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
14734 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
14735 whiteTimeRemaining : blackTimeRemaining);
14736 StartClockTimer(intendedTickLength);
14740 /* Stop both clocks */
14744 long lastTickLength;
14747 if (!StopClockTimer()) return;
14748 if (!appData.clockMode) return;
14752 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
14753 if (WhiteOnMove(forwardMostMove)) {
14754 if(whiteNPS >= 0) lastTickLength = 0;
14755 whiteTimeRemaining -= lastTickLength;
14756 DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
14758 if(blackNPS >= 0) lastTickLength = 0;
14759 blackTimeRemaining -= lastTickLength;
14760 DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
14765 /* Start clock of player on move. Time may have been reset, so
14766 if clock is already running, stop and restart it. */
14770 (void) StopClockTimer(); /* in case it was running already */
14771 DisplayBothClocks();
14772 if (CheckFlags()) return;
14774 if (!appData.clockMode) return;
14775 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
14777 GetTimeMark(&tickStartTM);
14778 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
14779 whiteTimeRemaining : blackTimeRemaining);
14781 /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
14782 whiteNPS = blackNPS = -1;
14783 if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
14784 || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
14785 whiteNPS = first.nps;
14786 if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
14787 || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
14788 blackNPS = first.nps;
14789 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
14790 whiteNPS = second.nps;
14791 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
14792 blackNPS = second.nps;
14793 if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
14795 StartClockTimer(intendedTickLength);
14802 long second, minute, hour, day;
14804 static char buf[32];
14806 if (ms > 0 && ms <= 9900) {
14807 /* convert milliseconds to tenths, rounding up */
14808 double tenths = floor( ((double)(ms + 99L)) / 100.00 );
14810 snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
14814 /* convert milliseconds to seconds, rounding up */
14815 /* use floating point to avoid strangeness of integer division
14816 with negative dividends on many machines */
14817 second = (long) floor(((double) (ms + 999L)) / 1000.0);
14824 day = second / (60 * 60 * 24);
14825 second = second % (60 * 60 * 24);
14826 hour = second / (60 * 60);
14827 second = second % (60 * 60);
14828 minute = second / 60;
14829 second = second % 60;
14832 snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
14833 sign, day, hour, minute, second);
14835 snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
14837 snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
14844 * This is necessary because some C libraries aren't ANSI C compliant yet.
14847 StrStr(string, match)
14848 char *string, *match;
14852 length = strlen(match);
14854 for (i = strlen(string) - length; i >= 0; i--, string++)
14855 if (!strncmp(match, string, length))
14862 StrCaseStr(string, match)
14863 char *string, *match;
14867 length = strlen(match);
14869 for (i = strlen(string) - length; i >= 0; i--, string++) {
14870 for (j = 0; j < length; j++) {
14871 if (ToLower(match[j]) != ToLower(string[j]))
14874 if (j == length) return string;
14888 c1 = ToLower(*s1++);
14889 c2 = ToLower(*s2++);
14890 if (c1 > c2) return 1;
14891 if (c1 < c2) return -1;
14892 if (c1 == NULLCHAR) return 0;
14901 return isupper(c) ? tolower(c) : c;
14909 return islower(c) ? toupper(c) : c;
14911 #endif /* !_amigados */
14919 if ((ret = (char *) malloc(strlen(s) + 1)))
14921 safeStrCpy(ret, s, strlen(s)+1);
14927 StrSavePtr(s, savePtr)
14928 char *s, **savePtr;
14933 if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
14934 safeStrCpy(*savePtr, s, strlen(s)+1);
14946 clock = time((time_t *)NULL);
14947 tm = localtime(&clock);
14948 snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
14949 tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
14950 return StrSave(buf);
14955 PositionToFEN(move, overrideCastling)
14957 char *overrideCastling;
14959 int i, j, fromX, fromY, toX, toY;
14966 whiteToPlay = (gameMode == EditPosition) ?
14967 !blackPlaysFirst : (move % 2 == 0);
14970 /* Piece placement data */
14971 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
14973 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
14974 if (boards[move][i][j] == EmptySquare) {
14976 } else { ChessSquare piece = boards[move][i][j];
14977 if (emptycount > 0) {
14978 if(emptycount<10) /* [HGM] can be >= 10 */
14979 *p++ = '0' + emptycount;
14980 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
14983 if(PieceToChar(piece) == '+') {
14984 /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
14986 piece = (ChessSquare)(DEMOTED piece);
14988 *p++ = PieceToChar(piece);
14990 /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
14991 p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
14996 if (emptycount > 0) {
14997 if(emptycount<10) /* [HGM] can be >= 10 */
14998 *p++ = '0' + emptycount;
14999 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
15006 /* [HGM] print Crazyhouse or Shogi holdings */
15007 if( gameInfo.holdingsWidth ) {
15008 *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
15010 for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
15011 piece = boards[move][i][BOARD_WIDTH-1];
15012 if( piece != EmptySquare )
15013 for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
15014 *p++ = PieceToChar(piece);
15016 for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
15017 piece = boards[move][BOARD_HEIGHT-i-1][0];
15018 if( piece != EmptySquare )
15019 for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
15020 *p++ = PieceToChar(piece);
15023 if( q == p ) *p++ = '-';
15029 *p++ = whiteToPlay ? 'w' : 'b';
15032 if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
15033 while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
15035 if(nrCastlingRights) {
15037 if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
15038 /* [HGM] write directly from rights */
15039 if(boards[move][CASTLING][2] != NoRights &&
15040 boards[move][CASTLING][0] != NoRights )
15041 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
15042 if(boards[move][CASTLING][2] != NoRights &&
15043 boards[move][CASTLING][1] != NoRights )
15044 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
15045 if(boards[move][CASTLING][5] != NoRights &&
15046 boards[move][CASTLING][3] != NoRights )
15047 *p++ = boards[move][CASTLING][3] + AAA;
15048 if(boards[move][CASTLING][5] != NoRights &&
15049 boards[move][CASTLING][4] != NoRights )
15050 *p++ = boards[move][CASTLING][4] + AAA;
15053 /* [HGM] write true castling rights */
15054 if( nrCastlingRights == 6 ) {
15055 if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
15056 boards[move][CASTLING][2] != NoRights ) *p++ = 'K';
15057 if(boards[move][CASTLING][1] == BOARD_LEFT &&
15058 boards[move][CASTLING][2] != NoRights ) *p++ = 'Q';
15059 if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
15060 boards[move][CASTLING][5] != NoRights ) *p++ = 'k';
15061 if(boards[move][CASTLING][4] == BOARD_LEFT &&
15062 boards[move][CASTLING][5] != NoRights ) *p++ = 'q';
15065 if (q == p) *p++ = '-'; /* No castling rights */
15069 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
15070 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
15071 /* En passant target square */
15072 if (move > backwardMostMove) {
15073 fromX = moveList[move - 1][0] - AAA;
15074 fromY = moveList[move - 1][1] - ONE;
15075 toX = moveList[move - 1][2] - AAA;
15076 toY = moveList[move - 1][3] - ONE;
15077 if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
15078 toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
15079 boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
15081 /* 2-square pawn move just happened */
15083 *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
15087 } else if(move == backwardMostMove) {
15088 // [HGM] perhaps we should always do it like this, and forget the above?
15089 if((signed char)boards[move][EP_STATUS] >= 0) {
15090 *p++ = boards[move][EP_STATUS] + AAA;
15091 *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
15102 /* [HGM] find reversible plies */
15103 { int i = 0, j=move;
15105 if (appData.debugMode) { int k;
15106 fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
15107 for(k=backwardMostMove; k<=forwardMostMove; k++)
15108 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
15112 while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
15113 if( j == backwardMostMove ) i += initialRulePlies;
15114 sprintf(p, "%d ", i);
15115 p += i>=100 ? 4 : i >= 10 ? 3 : 2;
15117 /* Fullmove number */
15118 sprintf(p, "%d", (move / 2) + 1);
15120 return StrSave(buf);
15124 ParseFEN(board, blackPlaysFirst, fen)
15126 int *blackPlaysFirst;
15136 /* [HGM] by default clear Crazyhouse holdings, if present */
15137 if(gameInfo.holdingsWidth) {
15138 for(i=0; i<BOARD_HEIGHT; i++) {
15139 board[i][0] = EmptySquare; /* black holdings */
15140 board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
15141 board[i][1] = (ChessSquare) 0; /* black counts */
15142 board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
15146 /* Piece placement data */
15147 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
15150 if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
15151 if (*p == '/') p++;
15152 emptycount = gameInfo.boardWidth - j;
15153 while (emptycount--)
15154 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
15156 #if(BOARD_FILES >= 10)
15157 } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
15158 p++; emptycount=10;
15159 if (j + emptycount > gameInfo.boardWidth) return FALSE;
15160 while (emptycount--)
15161 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
15163 } else if (isdigit(*p)) {
15164 emptycount = *p++ - '0';
15165 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
15166 if (j + emptycount > gameInfo.boardWidth) return FALSE;
15167 while (emptycount--)
15168 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
15169 } else if (*p == '+' || isalpha(*p)) {
15170 if (j >= gameInfo.boardWidth) return FALSE;
15172 piece = CharToPiece(*++p);
15173 if(piece == EmptySquare) return FALSE; /* unknown piece */
15174 piece = (ChessSquare) (PROMOTED piece ); p++;
15175 if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
15176 } else piece = CharToPiece(*p++);
15178 if(piece==EmptySquare) return FALSE; /* unknown piece */
15179 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
15180 piece = (ChessSquare) (PROMOTED piece);
15181 if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
15184 board[i][(j++)+gameInfo.holdingsWidth] = piece;
15190 while (*p == '/' || *p == ' ') p++;
15192 /* [HGM] look for Crazyhouse holdings here */
15193 while(*p==' ') p++;
15194 if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
15196 if(*p == '-' ) p++; /* empty holdings */ else {
15197 if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
15198 /* if we would allow FEN reading to set board size, we would */
15199 /* have to add holdings and shift the board read so far here */
15200 while( (piece = CharToPiece(*p) ) != EmptySquare ) {
15202 if((int) piece >= (int) BlackPawn ) {
15203 i = (int)piece - (int)BlackPawn;
15204 i = PieceToNumber((ChessSquare)i);
15205 if( i >= gameInfo.holdingsSize ) return FALSE;
15206 board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
15207 board[BOARD_HEIGHT-1-i][1]++; /* black counts */
15209 i = (int)piece - (int)WhitePawn;
15210 i = PieceToNumber((ChessSquare)i);
15211 if( i >= gameInfo.holdingsSize ) return FALSE;
15212 board[i][BOARD_WIDTH-1] = piece; /* white holdings */
15213 board[i][BOARD_WIDTH-2]++; /* black holdings */
15220 while(*p == ' ') p++;
15224 if(appData.colorNickNames) {
15225 if( c == appData.colorNickNames[0] ) c = 'w'; else
15226 if( c == appData.colorNickNames[1] ) c = 'b';
15230 *blackPlaysFirst = FALSE;
15233 *blackPlaysFirst = TRUE;
15239 /* [HGM] We NO LONGER ignore the rest of the FEN notation */
15240 /* return the extra info in global variiables */
15242 /* set defaults in case FEN is incomplete */
15243 board[EP_STATUS] = EP_UNKNOWN;
15244 for(i=0; i<nrCastlingRights; i++ ) {
15245 board[CASTLING][i] =
15246 gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
15247 } /* assume possible unless obviously impossible */
15248 if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
15249 if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
15250 if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
15251 && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
15252 if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
15253 if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
15254 if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
15255 && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
15258 while(*p==' ') p++;
15259 if(nrCastlingRights) {
15260 if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
15261 /* castling indicator present, so default becomes no castlings */
15262 for(i=0; i<nrCastlingRights; i++ ) {
15263 board[CASTLING][i] = NoRights;
15266 while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
15267 (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
15268 ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
15269 ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth) ) {
15270 char c = *p++; int whiteKingFile=NoRights, blackKingFile=NoRights;
15272 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
15273 if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
15274 if(board[0 ][i] == WhiteKing) whiteKingFile = i;
15276 if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
15277 whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
15278 if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
15279 && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
15280 if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
15281 && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
15284 for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
15285 board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
15286 board[CASTLING][2] = whiteKingFile;
15289 for(i=BOARD_LEFT; i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
15290 board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
15291 board[CASTLING][2] = whiteKingFile;
15294 for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
15295 board[CASTLING][3] = i != blackKingFile ? i : NoRights;
15296 board[CASTLING][5] = blackKingFile;
15299 for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
15300 board[CASTLING][4] = i != blackKingFile ? i : NoRights;
15301 board[CASTLING][5] = blackKingFile;
15304 default: /* FRC castlings */
15305 if(c >= 'a') { /* black rights */
15306 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
15307 if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
15308 if(i == BOARD_RGHT) break;
15309 board[CASTLING][5] = i;
15311 if(board[BOARD_HEIGHT-1][c] < BlackPawn ||
15312 board[BOARD_HEIGHT-1][c] >= BlackKing ) break;
15314 board[CASTLING][3] = c;
15316 board[CASTLING][4] = c;
15317 } else { /* white rights */
15318 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
15319 if(board[0][i] == WhiteKing) break;
15320 if(i == BOARD_RGHT) break;
15321 board[CASTLING][2] = i;
15322 c -= AAA - 'a' + 'A';
15323 if(board[0][c] >= WhiteKing) break;
15325 board[CASTLING][0] = c;
15327 board[CASTLING][1] = c;
15331 for(i=0; i<nrCastlingRights; i++)
15332 if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
15333 if (appData.debugMode) {
15334 fprintf(debugFP, "FEN castling rights:");
15335 for(i=0; i<nrCastlingRights; i++)
15336 fprintf(debugFP, " %d", board[CASTLING][i]);
15337 fprintf(debugFP, "\n");
15340 while(*p==' ') p++;
15343 /* read e.p. field in games that know e.p. capture */
15344 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
15345 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
15347 p++; board[EP_STATUS] = EP_NONE;
15349 char c = *p++ - AAA;
15351 if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
15352 if(*p >= '0' && *p <='9') p++;
15353 board[EP_STATUS] = c;
15358 if(sscanf(p, "%d", &i) == 1) {
15359 FENrulePlies = i; /* 50-move ply counter */
15360 /* (The move number is still ignored) */
15367 EditPositionPasteFEN(char *fen)
15370 Board initial_position;
15372 if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
15373 DisplayError(_("Bad FEN position in clipboard"), 0);
15376 int savedBlackPlaysFirst = blackPlaysFirst;
15377 EditPositionEvent();
15378 blackPlaysFirst = savedBlackPlaysFirst;
15379 CopyBoard(boards[0], initial_position);
15380 initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
15381 EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
15382 DisplayBothClocks();
15383 DrawPosition(FALSE, boards[currentMove]);
15388 static char cseq[12] = "\\ ";
15390 Boolean set_cont_sequence(char *new_seq)
15395 // handle bad attempts to set the sequence
15397 return 0; // acceptable error - no debug
15399 len = strlen(new_seq);
15400 ret = (len > 0) && (len < sizeof(cseq));
15402 safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
15403 else if (appData.debugMode)
15404 fprintf(debugFP, "Invalid continuation sequence \"%s\" (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
15409 reformat a source message so words don't cross the width boundary. internal
15410 newlines are not removed. returns the wrapped size (no null character unless
15411 included in source message). If dest is NULL, only calculate the size required
15412 for the dest buffer. lp argument indicats line position upon entry, and it's
15413 passed back upon exit.
15415 int wrap(char *dest, char *src, int count, int width, int *lp)
15417 int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
15419 cseq_len = strlen(cseq);
15420 old_line = line = *lp;
15421 ansi = len = clen = 0;
15423 for (i=0; i < count; i++)
15425 if (src[i] == '\033')
15428 // if we hit the width, back up
15429 if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
15431 // store i & len in case the word is too long
15432 old_i = i, old_len = len;
15434 // find the end of the last word
15435 while (i && src[i] != ' ' && src[i] != '\n')
15441 // word too long? restore i & len before splitting it
15442 if ((old_i-i+clen) >= width)
15449 if (i && src[i-1] == ' ')
15452 if (src[i] != ' ' && src[i] != '\n')
15459 // now append the newline and continuation sequence
15464 strncpy(dest+len, cseq, cseq_len);
15472 dest[len] = src[i];
15476 if (src[i] == '\n')
15481 if (dest && appData.debugMode)
15483 fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
15484 count, width, line, len, *lp);
15485 show_bytes(debugFP, src, count);
15486 fprintf(debugFP, "\ndest: ");
15487 show_bytes(debugFP, dest, len);
15488 fprintf(debugFP, "\n");
15490 *lp = dest ? line : old_line;
15495 // [HGM] vari: routines for shelving variations
15498 PushTail(int firstMove, int lastMove)
15500 int i, j, nrMoves = lastMove - firstMove;
15502 if(appData.icsActive) { // only in local mode
15503 forwardMostMove = currentMove; // mimic old ICS behavior
15506 if(storedGames >= MAX_VARIATIONS-1) return;
15508 // push current tail of game on stack
15509 savedResult[storedGames] = gameInfo.result;
15510 savedDetails[storedGames] = gameInfo.resultDetails;
15511 gameInfo.resultDetails = NULL;
15512 savedFirst[storedGames] = firstMove;
15513 savedLast [storedGames] = lastMove;
15514 savedFramePtr[storedGames] = framePtr;
15515 framePtr -= nrMoves; // reserve space for the boards
15516 for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
15517 CopyBoard(boards[framePtr+i], boards[firstMove+i]);
15518 for(j=0; j<MOVE_LEN; j++)
15519 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
15520 for(j=0; j<2*MOVE_LEN; j++)
15521 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
15522 timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
15523 timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
15524 pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
15525 pvInfoList[firstMove+i-1].depth = 0;
15526 commentList[framePtr+i] = commentList[firstMove+i];
15527 commentList[firstMove+i] = NULL;
15531 forwardMostMove = firstMove; // truncate game so we can start variation
15532 if(storedGames == 1) GreyRevert(FALSE);
15536 PopTail(Boolean annotate)
15539 char buf[8000], moveBuf[20];
15541 if(appData.icsActive) return FALSE; // only in local mode
15542 if(!storedGames) return FALSE; // sanity
15543 CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
15546 ToNrEvent(savedFirst[storedGames]); // sets currentMove
15547 nrMoves = savedLast[storedGames] - currentMove;
15550 if(!WhiteOnMove(currentMove))
15551 snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
15552 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
15553 for(i=currentMove; i<forwardMostMove; i++) {
15555 snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
15556 else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
15557 strcat(buf, moveBuf);
15558 if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
15559 if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
15563 for(i=1; i<=nrMoves; i++) { // copy last variation back
15564 CopyBoard(boards[currentMove+i], boards[framePtr+i]);
15565 for(j=0; j<MOVE_LEN; j++)
15566 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
15567 for(j=0; j<2*MOVE_LEN; j++)
15568 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
15569 timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
15570 timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
15571 pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
15572 if(commentList[currentMove+i]) free(commentList[currentMove+i]);
15573 commentList[currentMove+i] = commentList[framePtr+i];
15574 commentList[framePtr+i] = NULL;
15576 if(annotate) AppendComment(currentMove+1, buf, FALSE);
15577 framePtr = savedFramePtr[storedGames];
15578 gameInfo.result = savedResult[storedGames];
15579 if(gameInfo.resultDetails != NULL) {
15580 free(gameInfo.resultDetails);
15582 gameInfo.resultDetails = savedDetails[storedGames];
15583 forwardMostMove = currentMove + nrMoves;
15584 if(storedGames == 0) GreyRevert(TRUE);
15590 { // remove all shelved variations
15592 for(i=0; i<storedGames; i++) {
15593 if(savedDetails[i])
15594 free(savedDetails[i]);
15595 savedDetails[i] = NULL;
15597 for(i=framePtr; i<MAX_MOVES; i++) {
15598 if(commentList[i]) free(commentList[i]);
15599 commentList[i] = NULL;
15601 framePtr = MAX_MOVES-1;
15606 LoadVariation(int index, char *text)
15607 { // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
15608 char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
15609 int level = 0, move;
15611 if(gameMode != EditGame && gameMode != AnalyzeMode) return;
15612 // first find outermost bracketing variation
15613 while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
15614 if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
15615 if(*p == '{') wait = '}'; else
15616 if(*p == '[') wait = ']'; else
15617 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
15618 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
15620 if(*p == wait) wait = NULLCHAR; // closing ]} found
15623 if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
15624 if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
15625 end[1] = NULLCHAR; // clip off comment beyond variation
15626 ToNrEvent(currentMove-1);
15627 PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
15628 // kludge: use ParsePV() to append variation to game
15629 move = currentMove;
15630 ParsePV(start, TRUE);
15631 forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
15632 ClearPremoveHighlights();
15634 ToNrEvent(currentMove+1);